Python pack IP string to bytes - python

I want to kind of implement my own struct.pack specific function to pack an IP string (i.e. "192.168.0.1") to a 32-bit packed value, without using the socket.inet_aton built in method.
I got so far:
ip = "192.168.0.1"
hex_list = map(hex, map(int, ip.split('.')))
# hex list now is : ['0xc0', '0xa8', '0x0', '0x01']
My question is:
How do I get from that ['0xc0', '0xa8', '0x0', '0x01'] to '\xc0\xa8\x00\x01', (this is what I'm getting from socket.inet_aton(ip)?
(And also - How is it possible that there is a NUL (\x00) in the middle of that string? I think I lack some understanding of the \x format)

You can use string comprehension to format as you like:
ip = "192.168.0.1"
hex_list = map(int, ip.split('.'))
hex_string = ''.join(['\\x%02x' % x for x in hex_list])
or as a one liner:
hex_string = ''.join(['\\x%02x' % int(x) for x in ip.split('.')])

An alternative:
Can you use ipaddress and to_bytes (python 3.2)?
>>> import ipaddress
>>> address = ipaddress.IPv4Address('192.168.0.1')
>>> address_as_int = int(address)
>>> address_as_int.to_bytes(4, byteorder='big')
b'\xc0\xa8\x00\x01'
Note that you may actually only need the integer.
Can be shorter obviously, but wanted to show all steps clearly :)

Based loosely off #Stephen's answer but returns a string with the actual bytes rather than a string with literal slashes:
def pack_ip(ip):
num_list = map(int, ip.split('.'))
return bytearray(num_list)
src_ip = pack_ip('127.0.0.255')
print(repr(src_ip))
Works in Python 2 and 3. Returns a b'' rather than a string, matching best practice for Python3.

Related

Convert hex to decimal/string in python

So I wrote this small socket program to send a udp packet and receive the response
sock.sendto(data, (MCAST_GRP, MCAST_PORT))
msgFromServer = sock.recvfrom(1024)
banner=msgFromServer[0]
print(msgFromServer[0])
#name = msgFromServer[0].decode('ascii', 'ignore')
#print(name)
Response is
b'\xff\xff\xff\xffI\x11server banner\x00map\x00game\x00Counter-Strike: Global Offensive\x00\xda\x02\x00\x10\x00dl\x01\x011.38.2.2\x00\xa1\x87iempty,secure\x00\xda\x02\x00\x00\x00\x00\x00\x00'
Now the thing is I wanted to convert all hex value to decimal,
I tried the decode; but then I endup loosing all the hex values.
How can I convert all the hex values to decimal in my case
example: \x13 = 19
EDIT: I guess better way to iterate my question is
How do I convert only the hex values to decimal in the given response
There are two problems here:
handling the non-ASCII bytes
handling \xhh sequences which are legitimate characters in Python strings
We can address both with a mix of regular expressions and string methods.
First, decode the bytes to ASCII using the backslashreplace error handler to avoid losing the non-ASCII bytes.
>>> import re
>>>
>>> decoded = msgFromServer[0].decode('ascii', errors='backslashreplace')
>>> decoded
'\\xff\\xff\\xff\\xffI\x11server banner\x00map\x00game\x00Counter-Strike: Global Offensive\x00\\xda\x02\x00\x10\x00dl\x01\x011.38.2.2\x00\\xa1\\x87iempty,secure\x00\\xda\x02\x00\x00\x00\x00\x00\x00'
Next, use a regular expression to replace the non-ASCII '\\xhh' sequences with their numeric equivalents:
>>> temp = re.sub(r'\\x([a-fA-F0-9]{2})', lambda m: str(int(m.group(1), 16)), decoded)
>>> temp
'255255255255I\x11server banner\x00map\x00game\x00Counter-Strike: Global Offensive\x00218\x02\x00\x10\x00dl\x01\x011.38.2.2\x00161135iempty,secure\x00218\x02\x00\x00\x00\x00\x00\x00'
Finally, map \xhh escape sequences to their decimal values using str.translate:
>>> tt = str.maketrans({x: str(x) for x in range(32)})
>>> final = temp.translate(tt)
>>> final
'255255255255I17server banner0map0game0Counter-Strike: Global Offensive021820160dl111.38.2.20161135iempty,secure02182000000'
You can first convert the bytes representation to hex using the bytes.hex method and then cast it into an integer with the appropriate base with int(x, base)
>>> b'\x13'.hex()
'13'
>>> int(b'\x13'.hex(), 16)
19
Assume v contains the response, what you are asking for is
[int(i) for i in v]
I suspect it's not what you want, it is what I read from the question

Python: convert a dot separated hex values to string?

In this post: Print a string as hex bytes? I learned how to print as string into an "array" of hex bytes now I need something the other way around:
So for example the input would be: 73.69.67.6e.61.74.75.72.65 and the output would be a string.
you can use the built in binascii module. Do note however that this function will only work on ASCII encoded characters.
binascii.unhexlify(hexstr)
Your input string will need to be dotless however, but that is quite easy with a simple
string = string.replace('.','')
another (arguably safer) method would be to use base64 in the following way:
import base64
encoded = base64.b16encode(b'data to be encoded')
print (encoded)
data = base64.b16decode(encoded)
print (data)
or in your example:
data = base64.b16decode(b"7369676e6174757265", True)
print (data.decode("utf-8"))
The string can be sanitised before input into the b16decode method.
Note that I am using python 3.2 and you may not necessarily need the b out the front of the string to denote bytes.
Example was found here
Without binascii:
>>> a="73.69.67.6e.61.74.75.72.65"
>>> "".join(chr(int(e, 16)) for e in a.split('.'))
'signature'
>>>
or better:
>>> a="73.69.67.6e.61.74.75.72.65"
>>> "".join(e.decode('hex') for e in a.split('.'))
PS: works with unicode:
>>> a='.'.join(x.encode('hex') for x in 'Hellö Wörld!')
>>> a
'48.65.6c.6c.94.20.57.94.72.6c.64.21'
>>> print "".join(e.decode('hex') for e in a.split('.'))
Hellö Wörld!
>>>
EDIT:
No need for a generator expression here (thx to thg435):
a.replace('.', '').decode('hex')
Use string split to get a list of strings, then base 16 for decoding the bytes.
>>> inp="73.69.67.6e.61.74.75.72.65"
>>> ''.join((chr(int(i,16)) for i in inp.split('.')))
'signature'
>>>

How can I format an integer to a two digit hex?

Does anyone know how to get a chr to hex conversion where the output is always two digits?
for example, if my conversion yields 0x1, I need to convert that to 0x01, since I am concatenating a long hex string.
The code that I am using is:
hexStr += hex(ord(byteStr[i]))[2:]
You can use string formatting for this purpose:
>>> "0x{:02x}".format(13)
'0x0d'
>>> "0x{:02x}".format(131)
'0x83'
Edit: Your code suggests that you are trying to convert a string to a hexstring representation. There is a much easier way to do this (Python2.x):
>>> "abcd".encode("hex")
'61626364'
An alternative (that also works in Python 3.x) is the function binascii.hexlify().
You can use the format function:
>>> format(10, '02x')
'0a'
You won't need to remove the 0x part with that (like you did with the [2:])
If you're using python 3.6 or higher you can also use fstrings:
v = 10
s = f"0x{v:02x}"
print(s)
output:
0x0a
The syntax for the braces part is identical to string.format(), except you use the variable's name. See https://www.python.org/dev/peps/pep-0498/ for more.
htmlColor = "#%02X%02X%02X" % (red, green, blue)
The standard module binascii may also be the answer, namely when you need to convert a longer string:
>>> import binascii
>>> binascii.hexlify('abc\n')
'6162630a'
Use format instead of using the hex function:
>>> mychar = ord('a')
>>> hexstring = '%.2X' % mychar
You can also change the number "2" to the number of digits you want, and the "X" to "x" to choose between upper and lowercase representation of the hex alphanumeric digits.
By many, this is considered the old %-style formatting in Python, but I like it because the format string syntax is the same used by other languages, like C and Java.
The simpliest way (I think) is:
your_str = '0x%02X' % 10
print(your_str)
will print:
0x0A
The number after the % will be converted to hex inside the string, I think it's clear this way and from people that came from a C background (like me) feels more like home

passing large number of arguments to struct.pack

I am using struct.pack method which takes variable number of arguments. I want to convert a string to bytes. If a string is short (e.g. 'name') I can do it like:
bytes = struct.pack('4c','n','a','m','e')
But what to do when my string is 80 characters long?
I have tried the format string 's', instead of '80c' for struct.pack, but the result is not the same as that of above call.
Use "80s", not just "s". The input is a single string, rather than a series of characters. i.e.
bytes = struct.pack('4s','name')
Note that if you specify a length greater than that of the input, the output will be null-padded.
That doesn't make much sense. Strings are already bytes in python 2.x; So you could just do:
my_string = 'I am some big string'
my_bytes = my_string
On python 3, strings are unicode objects by default. To get bytes you have to encode the string.
my_bytes = my_string.encode('utf-8')
If really you want to use struct.pack, you'd use * syntax as described in the tutorial:
my_bytes = struct.pack('20c', *my_string)
or
my_bytes = struct.pack('20s', my_string)

How to do a string replace in a urlencoded string

I have a string like x = "http://query.yahooapis.com/v1/public/yql?q=select%20owner%2Curls%20from%20flickr.photos.info%20where%20photo_id%3D'%s'&format=json"
If I do x % 10 that fails as there are %20f etc which are being treated as format strings, so I have to do a string conactination. How can I use normal string replacements here.?
urldecode the string, do the formatting, and then urlencode it again:
import urllib
x = "http://query.yahooapis.com/v1/public/yql?q=select%20owner%2Curls%20from%20flickr.photos.info%20where%20photo_id%3D'%s'&format=json"
tmp = urllib.unquote(x)
tmp2 = tmp % (foo, bar)
x = urllib.quote(tmp2)
As one commenter noted, doing formatting using arbitrary user-inputted strings as the format specification is historically dangerous, and is certainly a bad idea.
In python string formatting, use %% to output a single % character (docs).
>>> "%d%%" % 50
'50%'
So you could replace all the % with %%, except where you want to substitute during formatting. But #Conrad Meyer's solution is obviously better in this case.
Otherwise you can use the new-style output formatting (available since v 2.6), which doesn't rely on %:
x = 'http://query.yahooapis.com/v1/public/yql?q=select%20owner%2Curls%20from%20flickr.photos.info%20where%20photo_id%3D{0}&format=json'
x.format(10)
It also has the advance of not being typedependent.

Categories