-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix issue #359: Bin to str conversion of IPv6 addresses #431
Conversation
Current coverage is 67.63% (diff: 85.71%)@@ master #431 diff @@
==========================================
Files 103 103
Lines 25372 25365 -7
Methods 0 0
Messages 0 0
Branches 0 0
==========================================
+ Hits 17121 17156 +35
+ Misses 8251 8209 -42
Partials 0 0
|
if len(addr) != 16: | ||
raise Exception("Illegal syntax for IP address") | ||
parts = [] | ||
for left in [0, 2, 4, 6, 8, 10, 12, 14]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you create a list instead of for left in xrange(0, 16, 2):
?
def _ipv6_bin_to_str(addr): | ||
# IPv6 addresses have 128bits (16 bytes) | ||
if len(addr) != 16: | ||
raise Exception("Illegal syntax for IP address") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the address is in binary form, maybe "Illegal IPv6 address"
would be better?
Or even better, we should mimic the behavior of socket.inet_ntop
:
raise ValueError("invalid length of packed IP address string")
try: | ||
value = struct.unpack("!H", addr[left:left + 2])[0] | ||
hexstr = hex(value)[2:] | ||
except TypeError: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure to understand: where would a TypeError
come from here?
return result | ||
parts.append(hexstr.lower()) | ||
|
||
address = ":".join(parts) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole block (starting with parts = []
) could be replaced by an easier to read and more Pythonic:
address = ":".join(addr[idx:idx + 2].encode('hex').lstrip('0') or '0'
for idx in xrange(0, 16, 2))
matches = re.findall('(?::|^)(0(?::0)+)(?::|$)', address) | ||
if matches: | ||
# If multiple consecutive blocks have the same length, take the leftmost | ||
match = max(matches) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't you men match = max(matches, key=len)
?
address = ":".join(parts) | ||
|
||
# Find all consecutive zero blocks | ||
matches = re.findall('(?::|^)(0(?::0)+)(?::|$)', address) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using re.findall()
produces a (useless) list, plus you lose the regexp match object (which has interesting attributes for what you want to do).
I think the code starting from here to the end of the function can be rewritten as:
try:
match = max(re.finditer('(?::|^)(0(?::0)+)(?::|$)', address),
key=lambda x: x.end() - x.start())
except ValueError:
return address
return '%s::%s' % (address[:match.start()], address[match.end():])
I have not tested that code, however, so it might require some adjustments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, the regular expression should be defined and compiled as a global constant:
_IP6_ZEROS = re.compile('(?::|^)(0(?::0)+)(?::|$)')
Then
try:
match = max(_IP6_ZEROS.finditer(address),
key=lambda x: x.end() - x.start())
except ValueError:
return address
return '%s::%s' % (address[:match.start()], address[match.end():])
@@ -77,25 +77,53 @@ def inet_ntop(af, addr): | |||
except AttributeError: | |||
pass | |||
|
|||
# IPv6 addresses have 128bits (16 bytes) | |||
if len(addr) != 16: | |||
return _ipv6_bin_to_str(addr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't you move this call to the except
block?
@@ -6879,3 +6879,66 @@ x = f.i2repr(mp, {'*', '+', 'bit 2'}) | |||
assert(re.match(r'^.*Star \(\*\).*$', x) is not None) | |||
assert(re.match(r'^.*Plus \(\+\).*$', x) is not None) | |||
assert(re.match(r'^.*bit 2.*$', x) is not None) | |||
|
|||
########################################################################################################### |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tests sound good to me, but I think you should call inet_ntop
and let Scapy call either socket.inet_ntop
or _ipv6_bin_to_str()
, so that we really test how Scapy will behave. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason for that is that I'm able to test my code and not the code of socket.inet_ntop()
(also that is the reason that I extracted a separate function)
Do you see a better way to achieve this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I missed that. You're right. But maybe you could double the tests? Like that:
compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address)
assert compressed1 == compressed2 == '::'
What do you think?
Thanks for your suggestions, @p-l- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot! I still have some remarks (sorry about that).
@@ -10,7 +10,9 @@ | |||
without IPv6 support, on Windows for instance. | |||
""" | |||
|
|||
import socket,struct | |||
import socket,struct,re |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you rewrite this on separate lines? Also, PEP-8 requires a space after ,
.
import re
import socket
import struct
@@ -6879,3 +6879,66 @@ x = f.i2repr(mp, {'*', '+', 'bit 2'}) | |||
assert(re.match(r'^.*Star \(\*\).*$', x) is not None) | |||
assert(re.match(r'^.*Plus \(\+\).*$', x) is not None) | |||
assert(re.match(r'^.*bit 2.*$', x) is not None) | |||
|
|||
########################################################################################################### |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I missed that. You're right. But maybe you could double the tests? Like that:
compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address)
assert compressed1 == compressed2 == '::'
What do you think?
from scapy.pton_ntop import _ipv6_bin_to_str | ||
address=b'\x10\x00\x02\x00\x00\x30\x00\x04\x00\x05\x00\x60\x07\x00\x80\x00' # Leading zero suppression | ||
compressed = _ipv6_bin_to_str(address) | ||
assert(compressed == '1000:200:30:4:5:60:700:8000') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a newline at the end of your file (yeah I know I'm picky ;))
try: | ||
# Get the longest set of zero blocks | ||
# Actually we need to take a look at group 1 regarding the length as 0:0:1:0:0:2:3:4 would have two matches: | ||
# 0:0: and :0:0: where the latter is longer, though the first one should be taken. Group 1 is in both cases 0:0. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch and good comment! 👍
@p-l- I found why the travis build failes. On Mac OS it seems that As I wouldn't consider this an issue of scapy I'd modify the regression tests to accept the alternative representation. What do you think? |
@0xb35c it's OK to accept the "wrong" MacOS solution, but add a comment to explain that while it's not RFC-compliant, it's the result given under MacOS. |
Handle ipv6 addresses according to RFC 5952 Add regression tests
Better message for exception
PEP-8 compliance
@p-l- I don't know of you get notified of new commits here. So this is just a friendly notification from me ;) |
Starting over with my PR (old one was #395)
I hope this is better now (regarding rebase instead of merge)
Should fix issue #359 and also includes regression tests. When compressing the notation of an IPv6 address it is according to RFC 5952
A note on the implementation: I separated my fix into a own function for better unit-testing.