How can I parse a host:port pair in Python - python

Suppose I have a string of the of the format host:port, where :port is optional. How can I reliably extract the two components?
The host can be any of:
A hostname (localhost, www.google.com)
An IPv4 literal (1.2.3.4)
An IPv6 literal ([aaaa:bbbb::cccc]).
In other words, this is the standard format used across the internet (such as in URIs: complete grammar at https://www.rfc-editor.org/rfc/rfc3986#section-3.2, excluding the "User Information" component).
So, some possible inputs, and desired outputs:
'localhost' -> ('localhost', None)
'my-example.com:1234' -> ('my-example.com', 1234)
'1.2.3.4' -> ('1.2.3.4', None)
'[0abc:1def::1234]' -> ('[0abc:1def::1234]', None)

Well, this is Python, with batteries included. You have mention that the format is the standard one used in URIs, so how about urllib.parse?
import urllib.parse
def parse_hostport(hp):
# urlparse() and urlsplit() insists on absolute URLs starting with "//"
result = urllib.parse.urlsplit('//' + hp)
return result.hostname, result.port
This should handle any valid host:port you can throw at it.

This should handle the whole parse in a single regex
regex = re.compile(r'''
( # first capture group = Addr
\[ # literal open bracket IPv6
[:a-fA-F0-9]+ # one or more of these characters
\] # literal close bracket
| # ALTERNATELY
(?: # IPv4
\d{1,3}\. # one to three digits followed by a period
){3} # ...repeated three times
\d{1,3} # followed by one to three digits
| # ALTERNATELY
[-a-zA-Z0-9.]+ # one or more hostname chars ([-\w\d\.]) Hostname
) # end first capture group
(?:
: # a literal :
( # second capture group = PORT
\d+ # one or more digits
) # end second capture group
)? # ...or not.''', re.X)
All that's needed then is to cast the second group to int.
def parse_hostport(hp):
# regex from above should be defined here.
m = regex.match(hp)
addr, port = m.group(1, 2)
try:
return (addr, int(port))
except TypeError:
# port is None
return (addr, None)

def split_host_port(string):
if not string.rsplit(':', 1)[-1].isdigit():
return (string, None)
string = string.rsplit(':', 1)
host = string[0] # 1st index is always host
port = int(string[1])
return (host, port)
Actually confused on whether this is what you wanted, but I rewrote it up a bit and it still seems to follow the ideal output:
>>>> split_host_port("localhost")
('localhost', None)
>>>> split_host_port("example.com:1234")
('example.com', 1234)
>>>> split_host_port("1.2.3.4")
('1.2.3.4', None)
>>>> split_host_port("[0abc:1def::1234]")
('[0abc:1def::1234]', None)
>>>>
As on the first line I didn't really like the chained function calls e.g. getattr(getattr(getattr(string, 'rsplit')(':', 1), '__getitem__')(-1), 'isdigit')() for the expanded version and then it's repeated again two lines after, perhaps I should make it a variable instead so there's no need for all the calls.
But I'm nitpicking here so feel free to call me out on that, heh.

Here's my final attempt, with credit to other answerers who provided inspiration:
def parse_hostport(s, default_port=None):
if s[-1] == ']':
# ipv6 literal (with no port)
return (s, default_port)
out = s.rsplit(":", 1)
if len(out) == 1:
# No port
port = default_port
else:
try:
port = int(out[1])
except ValueError:
raise ValueError("Invalid host:port '%s'" % s)
return (out[0], port)

Came up with a dead simple regexp that seems to work in most cases:
def get_host_pair(value):
return re.search(r'^(.*?)(?::(\d+))?$', value).groups()
get_host_pair('localhost')
get_host_pair('localhost:80')
get_host_pair('[::1]')
get_host_pair('[::1]:8080')
It probably doesn't work when the base input is invalid however

Here's my attempt at this so far:
def parse_hostport(hp):
""" parse a host:port pair
"""
# start by special-casing the ipv6 literal case
x = re.match('^(\[[0-9a-fA-F:]+\])(:(\d+))?$', hp)
if x is not None:
return x.group(1, 3)
# otherwise, just split at the (hopefully only) colon
splits = hp.split(':')
if len(splits) == 1:
return splits + [None,]
elif len(splits) == 2:
return splits
raise ValueError("Invalid host:port input '%s'" % hp)

Here's a terser implementation which relies on attempting to parse the last component as an int:
def parse_hostport(s):
out = s.rsplit(":", 1)
try:
out[1] = int(out[1])
except (IndexError, ValueError):
# couldn't parse the last component as a port, so let's
# assume there isn't a port.
out = (s, None)
return out

Related

socketcan J1939 filter use in python

In Python, I am trying to use the J1939 filtering as mentionned in the linux kernel docs: https://www.kernel.org/doc/html/latest/networking/j1939.html
The following code fails at the setsockopt() line (setting up filters):
import socket
import struct
def pack_J1939_filters(can_filters):
can_filter_fmt = "=" + "2Q2B2I" * len(can_filters)
filter_data = []
for can_filter in can_filters:
name = can_filter['name']
name_mask = can_filter['name_mask']
addr = can_filter['addr']
addr_mask = can_filter['addr_mask']
pgn = can_filter['pgn']
pgn_mask = can_filter['pgn_mask']
filter_data.append(name)
filter_data.append(name_mask)
filter_data.append(addr)
filter_data.append(addr_mask)
filter_data.append(pgn)
filter_data.append(pgn_mask)
return struct.pack(can_filter_fmt, *filter_data)
s = socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_J1939)
interface = "vcan0"
src_name = socket.J1939_NO_NAME
src_pgn = socket.J1939_NO_PGN
src_addr = 0x81
src_sck_addr = (interface, src_name, src_pgn, src_addr)
s.bind(src_sck_addr)
filters = [{"name": 0, "name_mask":0, "addr":0, "addr_mask":0, "pgn": 0, "pgn_mask": 0}]
packed_filters = pack_J1939_filters(filters)
# socket.SOL_CAN_J1939 does not seem to exist
SOL_CAN_BASE = 100
CAN_J1939 = 7
SOL_CAN_J1939 = SOL_CAN_BASE + CAN_J1939
s.setsockopt(SOL_CAN_J1939, socket.SO_J1939_FILTER , packed_filters)
s.recvfrom(128)
s.close()
First, the kernel documentation mentions to use SOL_CAN_J1939 as the first argument. However socket.SOL_CAN_J1939 does not exist in the socket package. So looking at the code at this location I was able to understand that this int value should be 107: http://socket-can.996257.n3.nabble.com/RFC-v3-0-6-CAN-add-SAE-J1939-protocol-td7571.html
As for the setsockopt() third argument, I packed the filters to match the j1939_filter structure (26 bytes as described in the code from the previous link). This is similar to what is done in can.interfaces.socketcan.utils for raw CAN.
What am I doing wrong to cause setsockopt() to fail?
The first issue was with the struct.pack format (can_filter_fmt) being wrong. I first assumed that the kernel j1939_filter structure size was the sum of the members. This is wrong since the compiler adds padding. This can be added to the struct.pack format as x such as 2Q2I2B6x. Please see Why isn't sizeof for a struct equal to the sum of sizeof of each member?
The second issue was that can_filter_fmt is not packed as 2Q2B2I but as 2Q2I2B6x (the addr member is in the middle).
As for SOL_CAN_J1939 I was correct and needs to be created in file because it is not yet in the package.
The final code is the following:
#!/usr/bin/env python3
import socket
import struct
def pack_J1939_filters(can_filters=None):
if can_filters is None:
# Pass all messages
can_filters = [{}]
can_filter_fmt = "=" + "2Q2I2B6x" * len(can_filters)
filter_data = []
for can_filter in can_filters:
if 'name' in can_filter:
name = can_filter['name']
else:
name = 0
if 'name_mask' in can_filter:
name_mask = can_filter['name_mask']
else:
name_mask = 0
if 'pgn' in can_filter:
pgn = can_filter['pgn']
else:
pgn = 0
if 'pgn_mask' in can_filter:
pgn_mask = can_filter['pgn_mask']
else:
pgn_mask = 0
if 'addr' in can_filter:
addr = can_filter['addr']
else:
addr = 0
if 'addr_mask' in can_filter:
addr_mask = can_filter['addr_mask']
else:
addr_mask = 0
filter_data.append(name)
filter_data.append(name_mask)
filter_data.append(pgn)
filter_data.append(pgn_mask)
filter_data.append(addr)
filter_data.append(addr_mask)
return struct.pack(can_filter_fmt, *filter_data)
def print_msg(data, sck_addr):
print(f"SA:{hex(sck_addr[3])} PGN:{hex(sck_addr[2])}")
for j in range(len(data)):
if j % 8 == 0 and j != 0:
print()
if j % 8 == 0:
print(f"bytes {j} to {j+7}: ", end="")
print(f"{hex(data[j])} ", end="")
print()
print()
def main():
s = socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_J1939)
# allows to receive broadcast messages
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
interface = "vcan0"
src_name = socket.J1939_NO_NAME
src_pgn = socket.J1939_NO_PGN # always no PGN for source, unless filtering is needed
src_addr = 0x81 # recvfrom() will not return destination specific messages for other addresses
src_sck_addr = (interface, src_name, src_pgn, src_addr)
s.bind(src_sck_addr)
packed_filters = pack_J1939_filters()
SOL_CAN_BASE = 100
CAN_J1939 = 7
SOL_CAN_J1939 = SOL_CAN_BASE + CAN_J1939
s.setsockopt(SOL_CAN_J1939, socket.SO_J1939_FILTER , packed_filters)
(recv_data, recv_sck_addr) = s.recvfrom(128)
print_msg(recv_data, recv_sck_addr)
s.close()
if __name__ == "__main__":
main()
Thank you.
For J1939 to work with SocketCAN you need two things:
kernel 5.4+
can-j1939 kernel module enabled
Testing for can-1939:
If you install can-utils and after sudo modprobe can-j1939 all you get is fatal error, or if you start testj1939 from can-utils and you get error that protocol is not supported, then it means that can-j1939 was not enabled in your kernel and you need to compile it manually.
Here are my instructions for enabling can-j1939 in Debian 10 kernel:
https://github.com/linux-can/can-utils/blob/master/can-j1939-install-kernel-module.md

How to convert IPv6 link-local address to MAC address in Python

I am looking for a way to convert a IPV6 address, for example
fe80::1d81:b870:163c:5845
into a MAC-Adress with Python. So the output should be
1f:81:b8:3c:58:45
Like it is on this page: http://ben.akrin.com/?p=4103
How can I convert IPV6 to MAC?
Here are two functions to convert in both ways.
It might also be useful to check if the given arguments are correct MACs or IPv6s.
From MAC to IPv6
def mac2ipv6(mac):
# only accept MACs separated by a colon
parts = mac.split(":")
# modify parts to match IPv6 value
parts.insert(3, "ff")
parts.insert(4, "fe")
parts[0] = "%x" % (int(parts[0], 16) ^ 2)
# format output
ipv6Parts = []
for i in range(0, len(parts), 2):
ipv6Parts.append("".join(parts[i:i+2]))
ipv6 = "fe80::%s/64" % (":".join(ipv6Parts))
return ipv6
From IPv6 to MAC
def ipv62mac(ipv6):
# remove subnet info if given
subnetIndex = ipv6.find("/")
if subnetIndex != -1:
ipv6 = ipv6[:subnetIndex]
ipv6Parts = ipv6.split(":")
macParts = []
for ipv6Part in ipv6Parts[-4:]:
while len(ipv6Part) < 4:
ipv6Part = "0" + ipv6Part
macParts.append(ipv6Part[:2])
macParts.append(ipv6Part[-2:])
# modify parts to match MAC value
macParts[0] = "%02x" % (int(macParts[0], 16) ^ 2)
del macParts[4]
del macParts[3]
return ":".join(macParts)
Example
ipv6 = mac2ipv6("52:74:f2:b1:a8:7f")
back2mac = ipv62mac(ipv6)
print "IPv6:", ipv6 # prints IPv6: fe80::5074:f2ff:feb1:a87f/64
print "MAC:", back2mac # prints MAC: 52:74:f2:b1:a8:7f

How check application verision information in PySide

I created EXE file with Python (PySide) + PyInstaller. Once I try to use
print QtGui.QApplication.applicationVersion()
I don't see valid version in x.x.x.x format of the application.
Are there any built-in functions in PySide instead of this, or maybe should I use other library for it?
PS. I don't believe that Python doesn't have any methods to extract information about EXE :)
Have a look here.
There's everything you're looking for !
Hope this helps.
Found one way to do it:
import win32api
def get_version_info():
try:
filename = APP_FILENAME
return get_file_properties(filename)['FileVersion']
except BaseException, err:
log_error(err.message)
return '0.0.0.0'
def get_file_properties(filename):
"""
Read all properties of the given file return them as a dictionary.
"""
propNames = ('Comments', 'InternalName', 'ProductName',
'CompanyName', 'LegalCopyright', 'ProductVersion',
'FileDescription', 'LegalTrademarks', 'PrivateBuild',
'FileVersion', 'OriginalFilename', 'SpecialBuild')
props = {'FixedFileInfo': None, 'StringFileInfo': None, 'FileVersion': '0.0.0.0'}
try:
# backslash as parm returns dictionary of numeric info corresponding to VS_FIXEDFILEINFO struc
print filename
fixedInfo = win32api.GetFileVersionInfo(filename, '\\')
props['FixedFileInfo'] = fixedInfo
props['FileVersion'] = "%d.%d.%d.%d" % (fixedInfo['FileVersionMS'] / 65536,
fixedInfo['FileVersionMS'] % 65536, fixedInfo['FileVersionLS'] / 65536,
fixedInfo['FileVersionLS'] % 65536)
# \VarFileInfo\Translation returns list of available (language, codepage)
# pairs that can be used to retreive string info. We are using only the first pair.
lang, codepage = win32api.GetFileVersionInfo(filename, '\\VarFileInfo\\Translation')[0]
# any other must be of the form \StringfileInfo\%04X%04X\parm_name, middle
# two are language/codepage pair returned from above
strInfo = {}
for propName in propNames:
strInfoPath = u'\\StringFileInfo\\%04X%04X\\%s' % (lang, codepage, propName)
## print str_info
strInfo[propName] = win32api.GetFileVersionInfo(filename, strInfoPath)
props['StringFileInfo'] = strInfo
except BaseException, err:
log_error(err.message)
return props

Socket connection coming from LAN? [duplicate]

Given an ip address (say 192.168.0.1), how do I check if it's in a network (say 192.168.0.0/24) in Python?
Are there general tools in Python for ip address manipulation? Stuff like host lookups, ip adddress to int, network address with netmask to int and so on? Hopefully in the standard Python library for 2.5.
Using ipaddress (in the stdlib since 3.3, at PyPi for 2.6/2.7):
>>> import ipaddress
>>> ipaddress.ip_address('192.168.0.1') in ipaddress.ip_network('192.168.0.0/24')
True
If you want to evaluate a lot of IP addresses this way, you'll probably want to calculate the netmask upfront, like
n = ipaddress.ip_network('192.0.0.0/16')
netw = int(n.network_address)
mask = int(n.netmask)
Then, for each address, calculate the binary representation with one of
a = int(ipaddress.ip_address('192.0.43.10'))
a = struct.unpack('!I', socket.inet_pton(socket.AF_INET, '192.0.43.10'))[0]
a = struct.unpack('!I', socket.inet_aton('192.0.43.10'))[0] # IPv4 only
Finally, you can simply check:
in_network = (a & mask) == netw
I like to use netaddr for that:
from netaddr import CIDR, IP
if IP("192.168.0.1") in CIDR("192.168.0.0/24"):
print "Yay!"
As arno_v pointed out in the comments, new version of netaddr does it like this:
from netaddr import IPNetwork, IPAddress
if IPAddress("192.168.0.1") in IPNetwork("192.168.0.0/24"):
print "Yay!"
For python3
import ipaddress
ipaddress.IPv4Address('192.168.1.1') in ipaddress.IPv4Network('192.168.0.0/24')
ipaddress.IPv4Address('192.168.1.1') in ipaddress.IPv4Network('192.168.0.0/16')
Output :
False
True
This article shows you can do it with socket and struct modules without too much extra effort. I added a little to the article as follows:
import socket,struct
def makeMask(n):
"return a mask of n bits as a long integer"
return (2L<<n-1) - 1
def dottedQuadToNum(ip):
"convert decimal dotted quad string to long integer"
return struct.unpack('L',socket.inet_aton(ip))[0]
def networkMask(ip,bits):
"Convert a network address to a long integer"
return dottedQuadToNum(ip) & makeMask(bits)
def addressInNetwork(ip,net):
"Is an address in a network"
return ip & net == net
address = dottedQuadToNum("192.168.1.1")
networka = networkMask("10.0.0.0",24)
networkb = networkMask("192.168.0.0",24)
print (address,networka,networkb)
print addressInNetwork(address,networka)
print addressInNetwork(address,networkb)
This outputs:
False
True
If you just want a single function that takes strings it would look like this:
import socket,struct
def addressInNetwork(ip,net):
"Is an address in a network"
ipaddr = struct.unpack('L',socket.inet_aton(ip))[0]
netaddr,bits = net.split('/')
netmask = struct.unpack('L',socket.inet_aton(netaddr))[0] & ((2L<<int(bits)-1) - 1)
return ipaddr & netmask == netmask
Using Python >= 3.7 ipaddress:
import ipaddress
address = ipaddress.ip_address("192.168.0.1")
network = ipaddress.ip_network("192.168.0.0/16")
print(network.supernet_of(ipaddress.ip_network(f"{address}/{address.max_prefixlen}")))
Explanation
You can think of an IP Address as a Network with the largest possible netmask (/32 for IPv4, /128 for IPv6)
Checking whether 192.168.0.1 is in 192.168.0.0/16 is essentially the same as checking whether 192.168.0.1/32 is a subnet of 192.168.0.0/16
This code is working for me on Linux x86. I haven't really given any thought to endianess issues, but I have tested it against the "ipaddr" module using over 200K IP addresses tested against 8 different network strings, and the results of ipaddr are the same as this code.
def addressInNetwork(ip, net):
import socket,struct
ipaddr = int(''.join([ '%02x' % int(x) for x in ip.split('.') ]), 16)
netstr, bits = net.split('/')
netaddr = int(''.join([ '%02x' % int(x) for x in netstr.split('.') ]), 16)
mask = (0xffffffff << (32 - int(bits))) & 0xffffffff
return (ipaddr & mask) == (netaddr & mask)
Example:
>>> print addressInNetwork('10.9.8.7', '10.9.1.0/16')
True
>>> print addressInNetwork('10.9.8.7', '10.9.1.0/24')
False
Wherever possible I'd recommend the built in ipaddress module. It's only available in Python 3 though, but it is super easy to use, and supports IPv6. And why aren't you using Python 3 yet anyway, right?
The accepted answer doesn't work ... which is making me angry. Mask is backwards and doesn't work with any bits that are not a simple 8 bit block (eg /24). I adapted the answer, and it works nicely.
import socket,struct
def addressInNetwork(ip, net_n_bits):
ipaddr = struct.unpack('!L', socket.inet_aton(ip))[0]
net, bits = net_n_bits.split('/')
netaddr = struct.unpack('!L', socket.inet_aton(net))[0]
netmask = (0xFFFFFFFF >> int(bits)) ^ 0xFFFFFFFF
return ipaddr & netmask == netaddr
here is a function that returns a dotted binary string to help visualize the masking.. kind of like ipcalc output.
def bb(i):
def s = '{:032b}'.format(i)
def return s[0:8]+"."+s[8:16]+"."+s[16:24]+"."+s[24:32]
eg:
I'm not a fan of using modules when they are not needed. This job only requires simple math, so here is my simple function to do the job:
def ipToInt(ip):
o = map(int, ip.split('.'))
res = (16777216 * o[0]) + (65536 * o[1]) + (256 * o[2]) + o[3]
return res
def isIpInSubnet(ip, ipNetwork, maskLength):
ipInt = ipToInt(ip)#my test ip, in int form
maskLengthFromRight = 32 - maskLength
ipNetworkInt = ipToInt(ipNetwork) #convert the ip network into integer form
binString = "{0:b}".format(ipNetworkInt) #convert that into into binary (string format)
chopAmount = 0 #find out how much of that int I need to cut off
for i in range(maskLengthFromRight):
if i < len(binString):
chopAmount += int(binString[len(binString)-1-i]) * 2**i
minVal = ipNetworkInt-chopAmount
maxVal = minVal+2**maskLengthFromRight -1
return minVal <= ipInt and ipInt <= maxVal
Then to use it:
>>> print isIpInSubnet('66.151.97.0', '66.151.97.192',24)
True
>>> print isIpInSubnet('66.151.97.193', '66.151.97.192',29)
True
>>> print isIpInSubnet('66.151.96.0', '66.151.97.192',24)
False
>>> print isIpInSubnet('66.151.97.0', '66.151.97.192',29)
That's it, this is much faster than the solutions above with the included modules.
I tried Dave Webb's solution but hit some problems:
Most fundamentally - a match should be checked by ANDing the IP address with the mask, then checking the result matched the Network address exactly. Not ANDing the IP address with the Network address as was done.
I also noticed that just ignoring the Endian behaviour assuming that consistency will save you will only work for masks on octet boundaries (/24, /16). In order to get other masks (/23, /21) working correctly I added a "greater than" to the struct commands and changed the code for creating the binary mask to start with all "1" and shift left by (32-mask).
Finally, I added a simple check that the network address is valid for the mask and just print a warning if it is not.
Here's the result:
def addressInNetwork(ip,net):
"Is an address in a network"
ipaddr = struct.unpack('>L',socket.inet_aton(ip))[0]
netaddr,bits = net.split('/')
netmask = struct.unpack('>L',socket.inet_aton(netaddr))[0]
ipaddr_masked = ipaddr & (4294967295<<(32-int(bits))) # Logical AND of IP address and mask will equal the network address if it matches
if netmask == netmask & (4294967295<<(32-int(bits))): # Validate network address is valid for mask
return ipaddr_masked == netmask
else:
print "***WARNING*** Network",netaddr,"not valid with mask /"+bits
return ipaddr_masked == netmask
Not in the Standard library for 2.5, but ipaddr makes this very easy. I believe it is in 3.3 under the name ipaddress.
import ipaddr
a = ipaddr.IPAddress('192.168.0.1')
n = ipaddr.IPNetwork('192.168.0.0/24')
#This will return True
n.Contains(a)
Marc's code is nearly correct. A complete version of the code is -
def addressInNetwork3(ip,net):
'''This function allows you to check if on IP belogs to a Network'''
ipaddr = struct.unpack('=L',socket.inet_aton(ip))[0]
netaddr,bits = net.split('/')
netmask = struct.unpack('=L',socket.inet_aton(calcDottedNetmask(int(bits))))[0]
network = struct.unpack('=L',socket.inet_aton(netaddr))[0] & netmask
return (ipaddr & netmask) == (network & netmask)
def calcDottedNetmask(mask):
bits = 0
for i in xrange(32-mask,32):
bits |= (1 << i)
return "%d.%d.%d.%d" % ((bits & 0xff000000) >> 24, (bits & 0xff0000) >> 16, (bits & 0xff00) >> 8 , (bits & 0xff))
Obviously from the same sources as above...
A very Important note is that the first code has a small glitch - The IP address 255.255.255.255 also shows up as a Valid IP for any subnet. I had a heck of time getting this code to work and thanks to Marc for the correct answer.
Relying on the "struct" module can cause problems with endian-ness and type sizes, and just isn't needed. Nor is socket.inet_aton(). Python works very well with dotted-quad IP addresses:
def ip_to_u32(ip):
return int(''.join('%02x' % int(d) for d in ip.split('.')), 16)
I need to do IP matching on each socket accept() call, against a whole set of allowable source networks, so I precompute masks and networks, as integers:
SNS_SOURCES = [
# US-EAST-1
'207.171.167.101',
'207.171.167.25',
'207.171.167.26',
'207.171.172.6',
'54.239.98.0/24',
'54.240.217.16/29',
'54.240.217.8/29',
'54.240.217.64/28',
'54.240.217.80/29',
'72.21.196.64/29',
'72.21.198.64/29',
'72.21.198.72',
'72.21.217.0/24',
]
def build_masks():
masks = [ ]
for cidr in SNS_SOURCES:
if '/' in cidr:
netstr, bits = cidr.split('/')
mask = (0xffffffff << (32 - int(bits))) & 0xffffffff
net = ip_to_u32(netstr) & mask
else:
mask = 0xffffffff
net = ip_to_u32(cidr)
masks.append((mask, net))
return masks
Then I can quickly see if a given IP is within one of those networks:
ip = ip_to_u32(ipstr)
for mask, net in cached_masks:
if ip & mask == net:
# matched!
break
else:
raise BadClientIP(ipstr)
No module imports needed, and the code is very fast at matching.
As of Python 3.7, you can use subnet_of and supernet_of helper methods, which are part of the standard library:
To just test against a single IP, you can just use the subnet mask /32 which means "only this IP address" as a subnet, or you can pass the IP address to IPv4Nework or IPv6Nework constructors and they will return a subnet value for you.
So for your example:
from ipaddress import IPv4Network, IPv4Address
# Store IP Address as variable
>>> myip = IPv4Address('192.168.0.1')
>>> myip
IPv4Address('192.168.0.1')
# This treats the IP as a subnet
>>> myip_subnet = IPv4Network(myip)
>>> myip_subnet
IPv4Network('192.168.0.1/32')
# The other subnet to test membership against
>>> other_subnet = IPv4Network('192.168.0.0/24')
>>> other_subnet
IPv4Network('192.168.0.0/24')
# Now we can test
>>> myip_subnet.subnet_of(other_subnet)
True
Are there general tools in Python for ip address manipulation? Stuff
like host lookups, ip adddress to int, network address with netmask to
int and so on? Hopefully in the standard Python library for 2.5.
In Python 3, there's the ipaddress module which has tools for IPv4 and IPv6 manipulation. You can convert them to an int, by casting, i.e. int(IPv4Address('192.168.0.1')). Lots of other useful functions in the ipaddress module for hosts, etc.
The choosen answer has a bug.
Following is the correct code:
def addressInNetwork(ip, net_n_bits):
ipaddr = struct.unpack('<L', socket.inet_aton(ip))[0]
net, bits = net_n_bits.split('/')
netaddr = struct.unpack('<L', socket.inet_aton(net))[0]
netmask = ((1L << int(bits)) - 1)
return ipaddr & netmask == netaddr & netmask
Note: ipaddr & netmask == netaddr & netmask instead of ipaddr & netmask == netmask.
I also replace ((2L<<int(bits)-1) - 1) with ((1L << int(bits)) - 1), as the latter seems more understandable.
from netaddr import all_matching_cidrs
>>> from netaddr import all_matching_cidrs
>>> all_matching_cidrs("212.11.70.34", ["192.168.0.0/24","212.11.64.0/19"] )
[IPNetwork('212.11.64.0/19')]
Here is the usage for this method:
>>> help(all_matching_cidrs)
Help on function all_matching_cidrs in module netaddr.ip:
all_matching_cidrs(ip, cidrs)
Matches an IP address or subnet against a given sequence of IP addresses and subnets.
#param ip: a single IP address or subnet.
#param cidrs: a sequence of IP addresses and/or subnets.
#return: all matching IPAddress and/or IPNetwork objects from the provided
sequence, an empty list if there was no match.
Basically you provide an ip address as the first argument and a list of cidrs as the second argument. A list of hits are returned.
#This works properly without the weird byte by byte handling
def addressInNetwork(ip,net):
'''Is an address in a network'''
# Convert addresses to host order, so shifts actually make sense
ip = struct.unpack('>L',socket.inet_aton(ip))[0]
netaddr,bits = net.split('/')
netaddr = struct.unpack('>L',socket.inet_aton(netaddr))[0]
# Must shift left an all ones value, /32 = zero shift, /0 = 32 shift left
netmask = (0xffffffff &lt&lt (32-int(bits))) & 0xffffffff
# There's no need to mask the network address, as long as its a proper network address
return (ip & netmask) == netaddr
previous solution have a bug in ip & net == net. Correct ip lookup is ip & netmask = net
bugfixed code:
import socket
import struct
def makeMask(n):
"return a mask of n bits as a long integer"
return (2L<<n-1) - 1
def dottedQuadToNum(ip):
"convert decimal dotted quad string to long integer"
return struct.unpack('L',socket.inet_aton(ip))[0]
def addressInNetwork(ip,net,netmask):
"Is an address in a network"
print "IP "+str(ip) + " NET "+str(net) + " MASK "+str(netmask)+" AND "+str(ip & netmask)
return ip & netmask == net
def humannetcheck(ip,net):
address=dottedQuadToNum(ip)
netaddr=dottedQuadToNum(net.split("/")[0])
netmask=makeMask(long(net.split("/")[1]))
return addressInNetwork(address,netaddr,netmask)
print humannetcheck("192.168.0.1","192.168.0.0/24");
print humannetcheck("192.169.0.1","192.168.0.0/24");
Here is a class I wrote for longest prefix matching:
#!/usr/bin/env python
class Node:
def __init__(self):
self.left_child = None
self.right_child = None
self.data = "-"
def setData(self, data): self.data = data
def setLeft(self, pointer): self.left_child = pointer
def setRight(self, pointer): self.right_child = pointer
def getData(self): return self.data
def getLeft(self): return self.left_child
def getRight(self): return self.right_child
def __str__(self):
return "LC: %s RC: %s data: %s" % (self.left_child, self.right_child, self.data)
class LPMTrie:
def __init__(self):
self.nodes = [Node()]
self.curr_node_ind = 0
def addPrefix(self, prefix):
self.curr_node_ind = 0
prefix_bits = ''.join([bin(int(x)+256)[3:] for x in prefix.split('/')[0].split('.')])
prefix_length = int(prefix.split('/')[1])
for i in xrange(0, prefix_length):
if (prefix_bits[i] == '1'):
if (self.nodes[self.curr_node_ind].getRight()):
self.curr_node_ind = self.nodes[self.curr_node_ind].getRight()
else:
tmp = Node()
self.nodes[self.curr_node_ind].setRight(len(self.nodes))
tmp.setData(self.nodes[self.curr_node_ind].getData());
self.curr_node_ind = len(self.nodes)
self.nodes.append(tmp)
else:
if (self.nodes[self.curr_node_ind].getLeft()):
self.curr_node_ind = self.nodes[self.curr_node_ind].getLeft()
else:
tmp = Node()
self.nodes[self.curr_node_ind].setLeft(len(self.nodes))
tmp.setData(self.nodes[self.curr_node_ind].getData());
self.curr_node_ind = len(self.nodes)
self.nodes.append(tmp)
if i == prefix_length - 1 :
self.nodes[self.curr_node_ind].setData(prefix)
def searchPrefix(self, ip):
self.curr_node_ind = 0
ip_bits = ''.join([bin(int(x)+256)[3:] for x in ip.split('.')])
for i in xrange(0, 32):
if (ip_bits[i] == '1'):
if (self.nodes[self.curr_node_ind].getRight()):
self.curr_node_ind = self.nodes[self.curr_node_ind].getRight()
else:
return self.nodes[self.curr_node_ind].getData()
else:
if (self.nodes[self.curr_node_ind].getLeft()):
self.curr_node_ind = self.nodes[self.curr_node_ind].getLeft()
else:
return self.nodes[self.curr_node_ind].getData()
return None
def triePrint(self):
n = 1
for i in self.nodes:
print n, ':'
print i
n += 1
And here is a test program:
n=LPMTrie()
n.addPrefix('10.25.63.0/24')
n.addPrefix('10.25.63.0/16')
n.addPrefix('100.25.63.2/8')
n.addPrefix('100.25.0.3/16')
print n.searchPrefix('10.25.63.152')
print n.searchPrefix('100.25.63.200')
#10.25.63.0/24
#100.25.0.3/16
Thank you for your script!
I have work quite a long on it to make everything working... So I'm sharing it here
Using netaddr Class is 10 times slower than using binary conversion, so if you'd like to use it on a big list of IP, you should consider not using netaddr class
makeMask function is not working! Only working for /8,/16,/24 Ex:
bits = "21" ; socket.inet_ntoa(struct.pack('=L',(2L << int(bits)-1) - 1))
'255.255.31.0' whereas it should be 255.255.248.0
So I have used another function calcDottedNetmask(mask) from http://code.activestate.com/recipes/576483-convert-subnetmask-from-cidr-notation-to-dotdecima/
Ex:
#!/usr/bin/python
>>> calcDottedNetmask(21)
>>> '255.255.248.0'
Another problem is the process of matching if an IP belongs to a network! Basic Operation should be to compare (ipaddr & netmask) and (network & netmask).Ex: for the time being, the function is wrong
#!/usr/bin/python
>>> addressInNetwork('188.104.8.64','172.16.0.0/12')
>>>True which is completely WRONG!!
So my new addressInNetwork function looks-like:
#!/usr/bin/python
import socket,struct
def addressInNetwork(ip,net):
'''This function allows you to check if on IP belogs to a Network'''
ipaddr = struct.unpack('=L',socket.inet_aton(ip))[0]
netaddr,bits = net.split('/')
netmask = struct.unpack('=L',socket.inet_aton(calcDottedNetmask(bits)))[0]
network = struct.unpack('=L',socket.inet_aton(netaddr))[0] & netmask
return (ipaddr & netmask) == (network & netmask)
def calcDottedNetmask(mask):
bits = 0
for i in xrange(32-int(mask),32):
bits |= (1 > 24, (bits & 0xff0000) >> 16, (bits & 0xff00) >> 8 , (bits & 0xff))
And now, answer is right!!
#!/usr/bin/python
>>> addressInNetwork('188.104.8.64','172.16.0.0/12')
False
I hope that it will help other people, saving time for them!
Relating to all of the above, I think socket.inet_aton() returns bytes in network order, so the correct way to unpack them is probably
struct.unpack('!L', ... )
import socket,struct
def addressInNetwork(ip,net):
"Is an address in a network"
ipaddr = struct.unpack('!L',socket.inet_aton(ip))[0]
netaddr,bits = net.split('/')
netaddr = struct.unpack('!L',socket.inet_aton(netaddr))[0]
netmask = ((1<<(32-int(bits))) - 1)^0xffffffff
return ipaddr & netmask == netaddr & netmask
print addressInNetwork('10.10.10.110','10.10.10.128/25')
print addressInNetwork('10.10.10.110','10.10.10.0/25')
print addressInNetwork('10.10.10.110','10.20.10.128/25')
$ python check-subnet.py
False
True
False
I don't know of anything in the standard library, but PySubnetTree is a Python library that will do subnet matching.
From various sources above, and from my own research, this is how I got subnet and address calculation working. These pieces are enough to solve the question and other related questions.
class iptools:
#staticmethod
def dottedQuadToNum(ip):
"convert decimal dotted quad string to long integer"
return struct.unpack('>L', socket.inet_aton(ip))[0]
#staticmethod
def numToDottedQuad(n):
"convert long int to dotted quad string"
return socket.inet_ntoa(struct.pack('>L', n))
#staticmethod
def makeNetmask(mask):
bits = 0
for i in xrange(32-int(mask), 32):
bits |= (1 << i)
return bits
#staticmethod
def ipToNetAndHost(ip, maskbits):
"returns tuple (network, host) dotted-quad addresses given"
" IP and mask size"
# (by Greg Jorgensen)
n = iptools.dottedQuadToNum(ip)
m = iptools.makeMask(maskbits)
net = n & m
host = n - mask
return iptools.numToDottedQuad(net), iptools.numToDottedQuad(host)
There is an API that's called SubnetTree available in python that do this job very well.
This is a simple example :
import SubnetTree
t = SubnetTree.SubnetTree()
t.insert("10.0.1.3/32")
print("10.0.1.3" in t)
This is the link
Here is my code
# -*- coding: utf-8 -*-
import socket
class SubnetTest(object):
def __init__(self, network):
self.network, self.netmask = network.split('/')
self._network_int = int(socket.inet_aton(self.network).encode('hex'), 16)
self._mask = ((1L << int(self.netmask)) - 1) << (32 - int(self.netmask))
self._net_prefix = self._network_int & self._mask
def match(self, ip):
'''
判断传入的 IP 是不是本 Network 内的 IP
'''
ip_int = int(socket.inet_aton(ip).encode('hex'), 16)
return (ip_int & self._mask) == self._net_prefix
st = SubnetTest('100.98.21.0/24')
print st.match('100.98.23.32')
If you do not want to import other modules you could go with:
def ip_matches_network(self, network, ip):
"""
'{:08b}'.format(254): Converts 254 in a string of its binary representation
ip_bits[:net_mask] == net_ip_bits[:net_mask]: compare the ip bit streams
:param network: string like '192.168.33.0/24'
:param ip: string like '192.168.33.1'
:return: if ip matches network
"""
net_ip, net_mask = network.split('/')
net_mask = int(net_mask)
ip_bits = ''.join('{:08b}'.format(int(x)) for x in ip.split('.'))
net_ip_bits = ''.join('{:08b}'.format(int(x)) for x in net_ip.split('.'))
# example: net_mask=24 -> compare strings at position 0 to 23
return ip_bits[:net_mask] == net_ip_bits[:net_mask]
I tried one subset of proposed solutions in these answers.. with no success, I finally adapted and fixed the proposed code and wrote my fixed function.
I tested it and works at least on little endian architectures--e.g.x86-- if anyone likes to try on a big endian architecture, please give me feedback.
IP2Int code comes from this post, the other method is a fully (for my test cases) working fix of previous proposals in this question.
The code:
def IP2Int(ip):
o = map(int, ip.split('.'))
res = (16777216 * o[0]) + (65536 * o[1]) + (256 * o[2]) + o[3]
return res
def addressInNetwork(ip, net_n_bits):
ipaddr = IP2Int(ip)
net, bits = net_n_bits.split('/')
netaddr = IP2Int(net)
bits_num = int(bits)
netmask = ((1L << bits_num) - 1) << (32 - bits_num)
return ipaddr & netmask == netaddr & netmask
Hope useful,
Here is the solution using netaddr package
from netaddr import IPNetwork, IPAddress
def network_has_ip(network, ip):
if not isinstance(network, IPNetwork):
raise Exception("network parameter must be {0} instance".format(IPNetwork.__name__))
if not isinstance(ip, IPAddress):
raise Exception("ip parameter must be {0} instance".format(IPAddress.__name__))
return (network.cidr.ip.value & network.netmask.value) == (ip.value & network.netmask.value)
To avoid having builtin or third party modules change their syntax over time, I created my own that does this. I'm using this as an importable module. I hope this helps someone:
def subnet_lookup(subnet: str, netmask: str, ip_address: str):
"""
:param subnet: subnet to test against (as string)
:param netmask: mask of subnet
:param ip_address: ip to test against subnet and mask
:return True if a match; False if not a match
Steps:
1) convert entire subnet into one binary word
2) convert entire mask into one binary word
3) determine bcast from comparing subnet and mask
4) convert entire ip_address into one binary word
5) convert entire subnet into decimal
6) convert entire bcast into decimal
7) convert entire ip_address into decimal
8) determine if ip_address falls between subnet and bcast using range(); returns True if yes, False if no
"""
def convert_whole_to_bin(whole):
ip_dec_list = whole.split(".")
ip_bin_str = ""
for ip in ip_dec_list:
binary = dec_to_bin(int(ip))
ip_bin_str += binary
return ip_bin_str
def dec_to_bin(decimal_octet: int):
binary = bin(decimal_octet).replace("0b", "")
return binary.rjust(8, '0')
def split_binary_into_list(binary_octet: str):
bin_list = []
for s in binary_octet:
bin_list.append(s)
return bin_list
def determine_bcast(subnet, netmask):
subnet_split = split_binary_into_list(subnet)
netmask_split = split_binary_into_list(netmask)
bcast_list = []
for subnet, mask in zip(subnet_split, netmask_split):
if mask != '0':
bcast_list.append(subnet)
else:
bcast_list.append('1')
bcast_bin = "".join(bcast_list)
return bcast_bin
def bin_to_dec(binary_single_word: str):
decimal = int(binary_single_word, 2)
return decimal
def subnet_lookup(ip_address, subnet, bcast):
return ip_address in range(subnet, bcast + 1)
# 1) convert entire subnet into one binary word
subnet_single_bin = convert_whole_to_bin(whole=subnet)
# 2) convert entire mask into one binary word
mask_single_bin = convert_whole_to_bin(whole=netmask)
# 3) determine bcast from comparing subnet and mask
bcast_single_bin = determine_bcast(subnet=subnet_single_bin, netmask=mask_single_bin)
# 4) convert entire ip_address into one binary word
ip_address_single_bin = convert_whole_to_bin(whole=ip_address)
# 5) convert entire subnet into decimal
subnet_single_dec = bin_to_dec(binary_single_word=subnet_single_bin)
# 6) convert entire bcast into decimal
bcast_single_dec = bin_to_dec(binary_single_word=bcast_single_bin)
# 7) convert entire ip_address into decimal
ip_address_single_dec = bin_to_dec(binary_single_word=ip_address_single_bin)
# 8) determine if ip_address falls between subnet and bcast; returns True if yes, False if no
lookup_result = subnet_lookup(ip_address=ip_address_single_dec, subnet=subnet_single_dec, bcast=bcast_single_dec)
return lookup_result
# Testing:
subnet = "172.16.0.0"
netmask = "255.255.0.0"
ip_address = "172.16.255.255"
result = subnet_lookup(subnet=subnet, netmask=netmask, ip_address=ip_address)
print(result)

How do you make an IPv4 address printable with the getdnsapi Python API?

I'm working with the new getdns Python API.
I'm specifically trying to get DNSSEC_BOGUS answers. To do this I need to look at the results.reply_tree.reply[n]['answer']['rdata']['ipv4_address'] elements. This is a 4-byte binary representation of the IPv4 address. Is there an easy way within getdns to change this into a printable IPv4 address?
I don't want to use the results.just_address_answers field, because there is no guarantee that the addresses are in the same order.
Here is my sample code:
import getdns, sys
def get_ip(hostname, ctx, extensions):
print("host: {} extensions: {}".format(hostname, extensions))
results = ctx.address(name=hostname, extensions=extensions)
print("addresses:")
for addr in results.just_address_answers:
print(" {}".format(addr['address_data']))
print("replies:")
for reply in results.replies_tree:
for a in reply['answer']:
if a['type']==getdns.RRTYPE_A:
try:
print(" type={} data={} dnssec_status={}".format(a['type'], a['rdata']['ipv4_address'], reply['dnssec_status']))
except KeyError:
print(" no dnssec_status")
print("")
if __name__=="__main__":
print("getdns.DNSSEC_SECURE={}".format(getdns.DNSSEC_SECURE))
print("getdns.DNSSEC_INDETERMINATE={}".format(getdns.DNSSEC_INDETERMINATE))
print("getdns.DNSSEC_INSECURE={}".format(getdns.DNSSEC_INSECURE))
print("getdns.DNSSEC_BOGUS={}".format(getdns.DNSSEC_BOGUS))
failed = 'www.dnssec-failed.org'
ctx = getdns.Context()
get_ip(failed, ctx, {})
get_ip(failed, ctx, {'dnssec_return_status' : getdns.EXTENSION_TRUE })
get_ip(failed, ctx, {"dnssec_return_validation_chain" : getdns.EXTENSION_TRUE})
Although this works, I really want the data= that prints to print an IPv4 address, not just four binary bytes.
bin_addr = results.reply_tree.reply[n]['answer']['rdata']['ipv4_address']
string_addr = '.'.join(map(str, map(ord, bin_addr)))

Categories