| @@ -27,6 +27,7 @@ from socket import AF_INET, AF_INET6 | |||||
| from earwigbot.commands import Command | from earwigbot.commands import Command | ||||
| _IP = namedtuple("_IP", ["family", "ip"]) | |||||
| _Range = namedtuple("_Range", [ | _Range = namedtuple("_Range", [ | ||||
| "family", "range", "low", "high", "size", "addresses"]) | "family", "range", "low", "high", "size", "addresses"]) | ||||
| @@ -51,18 +52,19 @@ class CIDR(Command): | |||||
| return | return | ||||
| try: | try: | ||||
| ips = [self._parse_arg(arg) for arg in data.args] | |||||
| ips = [self._parse_ip(arg) for arg in data.args] | |||||
| except ValueError as exc: | except ValueError as exc: | ||||
| msg = "Can't parse IP address \x0302{0}\x0F." | msg = "Can't parse IP address \x0302{0}\x0F." | ||||
| self.reply(data, msg.format(exc.message)) | self.reply(data, msg.format(exc.message)) | ||||
| return | return | ||||
| if any(ip[0] == AF_INET for ip in ips) and any(ip[0] == AF_INET6 for ip in ips): | |||||
| if any(ip.family == AF_INET for ip in ips) and any( | |||||
| ip.family == AF_INET6 for ip in ips): | |||||
| msg = "Can't calculate a range for both IPv4 and IPv6 addresses." | msg = "Can't calculate a range for both IPv4 and IPv6 addresses." | ||||
| self.reply(data, msg) | self.reply(data, msg) | ||||
| return | return | ||||
| cidr = self._calculate_range(ips[0][0], [ip[1] for ip in ips]) | |||||
| cidr = self._calculate_range(ips[0].family, ips) | |||||
| descr = self._describe(cidr.family, cidr.size) | descr = self._describe(cidr.family, cidr.size) | ||||
| msg = ("Smallest CIDR range is \x02{0}\x0F, covering {1} from " | msg = ("Smallest CIDR range is \x02{0}\x0F, covering {1} from " | ||||
| @@ -71,8 +73,33 @@ class CIDR(Command): | |||||
| cidr.range, cidr.addresses, cidr.low, cidr.high, | cidr.range, cidr.addresses, cidr.low, cidr.high, | ||||
| " (\x0304{0}\x0F)".format(descr) if descr else "")) | " (\x0304{0}\x0F)".format(descr) if descr else "")) | ||||
| def _parse_ip(self, arg): | |||||
| """Converts an argument into an IP address object.""" | |||||
| arg = self._parse_arg(arg) | |||||
| oldarg = arg | |||||
| size = None | |||||
| if "/" in arg: | |||||
| arg, size = arg.split("/", 1) | |||||
| try: | |||||
| size = int(size, 10) | |||||
| except ValueError: | |||||
| raise ValueError(oldarg) | |||||
| if size < 0 or size > 128: | |||||
| raise ValueError(oldarg) | |||||
| try: | |||||
| ip = _IP(AF_INET, socket.inet_pton(AF_INET, arg), size) | |||||
| except socket.error: | |||||
| try: | |||||
| return _IP(AF_INET6, socket.inet_pton(AF_INET6, arg), size) | |||||
| except socket.error: | |||||
| raise ValueError(oldarg) | |||||
| if size > 32: | |||||
| raise ValueError(oldarg) | |||||
| return ip | |||||
| def _parse_arg(self, arg): | def _parse_arg(self, arg): | ||||
| """Converts an argument into an IP address.""" | |||||
| """Converts an argument into an IP address string.""" | |||||
| if "[[" in arg and "]]" in arg: | if "[[" in arg and "]]" in arg: | ||||
| regex = r"\[\[\s*(?:User(?:\stalk)?:)?(.*?)(?:\|.*?)?\s*\]\]" | regex = r"\[\[\s*(?:User(?:\stalk)?:)?(.*?)(?:\|.*?)?\s*\]\]" | ||||
| match = re.search(regex, arg, re.I) | match = re.search(regex, arg, re.I) | ||||
| @@ -95,23 +122,22 @@ class CIDR(Command): | |||||
| if not match: | if not match: | ||||
| raise ValueError(arg) | raise ValueError(arg) | ||||
| arg = match.group(1) | arg = match.group(1) | ||||
| try: | |||||
| return (AF_INET, socket.inet_pton(AF_INET, arg)) | |||||
| except socket.error: | |||||
| try: | |||||
| return (AF_INET6, socket.inet_pton(AF_INET6, arg)) | |||||
| except socket.error: | |||||
| raise ValueError(arg) | |||||
| return arg | |||||
| def _calculate_range(self, family, ips): | def _calculate_range(self, family, ips): | ||||
| """Calculate the smallest CIDR range encompassing a list of IPs.""" | """Calculate the smallest CIDR range encompassing a list of IPs.""" | ||||
| bin_ips = ["".join( | bin_ips = ["".join( | ||||
| bin(ord(octet))[2:].zfill(8) for octet in ip) for ip in ips] | |||||
| bin(ord(octet))[2:].zfill(8) for octet in ip.ip) for ip in ips] | |||||
| for i, ip in enumerate(ips): | |||||
| if ip.size: | |||||
| suffix = "X" * (len(bin_ips[i]) - ip.size) | |||||
| bin_ips[i] = bin_ips[i][:ip.size] + suffix | |||||
| size = len(bin_ips[0]) | size = len(bin_ips[0]) | ||||
| for i in xrange(len(bin_ips[0])): | for i in xrange(len(bin_ips[0])): | ||||
| if any(ip[i] == "0" for ip in bin_ips) and any( | |||||
| ip[i] == "1" for ip in bin_ips): | |||||
| if any(ip[i] == "X" for ip in bin_ips) or ( | |||||
| any(ip[i] == "0" for ip in bin_ips) and | |||||
| any(ip[i] == "1" for ip in bin_ips)): | |||||
| size = i | size = i | ||||
| break | break | ||||