diff --git a/CHANGELOG.md b/CHANGELOG.md index f29a5a6..447a1be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. +## [v3.0.3-cesbit0](https://github.com/cesbit/icmplib/tags/v3.0.3-cesbit0) - 2022-06-23 +- Add `min_ttl` and `max_ttl` to the `Host` class and `ttl` to the `ICMPReply` class. + ## [v3.0.3](https://github.com/ValentinBELYN/icmplib/releases/tag/v3.0.3) - 2022-02-06 - Add the `sock` property to the `ICMPSocket` class. diff --git a/icmplib/__init__.py b/icmplib/__init__.py index d99ba8c..4dc63c7 100644 --- a/icmplib/__init__.py +++ b/icmplib/__init__.py @@ -40,5 +40,5 @@ __copyright__ = 'Copyright 2017-2022 Valentin BELYN' __license__ = 'GNU Lesser General Public License v3.0' -__version__ = '3.0.3' -__build__ = '220206' +__version__ = '3.0.3-cesbit0' +__build__ = '220623' diff --git a/icmplib/models.py b/icmplib/models.py index 0478440..d2b7e13 100644 --- a/icmplib/models.py +++ b/icmplib/models.py @@ -195,12 +195,16 @@ class ICMPReply: :type time: float :param time: The timestamp of the ICMP reply. + :type ttl: int/None + :param time: The time-to-live (TTL) for the ICMP reply. Can be None when + TTL is not applicable for the ICMP message. + ''' __slots__ = '_source', '_family', '_id', '_sequence', '_type', \ - '_code', '_bytes_received', '_time' + '_code', '_bytes_received', '_time', '_ttl' def __init__(self, source, family, id, sequence, type, code, - bytes_received, time): + bytes_received, time, ttl): self._source = source self._family = family @@ -210,6 +214,7 @@ def __init__(self, source, family, id, sequence, type, code, self._code = code self._bytes_received = bytes_received self._time = time + self._ttl = ttl def __repr__(self): return f'' @@ -303,6 +308,13 @@ def time(self): ''' return self._time + @property + def ttl(self): + ''' + The time-to-live (TTL) of the ICMP reply or None if not applicable. + + ''' + return self._ttl class Host: ''' @@ -321,12 +333,13 @@ class Host: :param rtts: The list of round-trip times expressed in milliseconds. ''' - __slots__ = '_address', '_packets_sent', '_rtts' + __slots__ = '_address', '_packets_sent', '_rtts', '_ttls' - def __init__(self, address, packets_sent, rtts): + def __init__(self, address, packets_sent, rtts, ttls): self._address = address self._packets_sent = packets_sent self._rtts = rtts + self._ttls = ttls def __repr__(self): return f'' @@ -338,7 +351,9 @@ def __str__(self): f' Packet loss: {self.packet_loss * 100}%\n' \ f' Round-trip times: {self.min_rtt} ms / ' \ f'{self.avg_rtt} ms / {self.max_rtt} ms\n' \ - f' Jitter: {self.jitter} ms\n' + '-' * 60 + f' Jitter: {self.jitter} ms\n' \ + f' Time-to-lives: {self.min_ttl} / {self.max_ttl}\n' +\ + '-' * 60 @property def address(self): @@ -447,6 +462,28 @@ def is_alive(self): ''' return len(self._rtts) > 0 + @property + def min_ttl(self): + ''' + The minimun time-to-live (TTL) value. + + ''' + if not self._ttls: + return 0 + + return min(self._ttls) + + @property + def max_ttl(self): + ''' + The minimun time-to-live (TTL) value. + + ''' + if not self._ttls: + return 0 + + return max(self._ttls) + class Hop(Host): ''' diff --git a/icmplib/ping.py b/icmplib/ping.py index fcd916d..40ba585 100644 --- a/icmplib/ping.py +++ b/icmplib/ping.py @@ -36,7 +36,7 @@ def ping(address, count=4, interval=1, timeout=2, id=None, source=None, - family=None, privileged=True, **kwargs): + family=None, privileged=True, **kwargs): ''' Send ICMP Echo Request packets to a network host. @@ -137,6 +137,7 @@ def ping(address, count=4, interval=1, timeout=2, id=None, source=None, id = id or unique_identifier() packets_sent = 0 rtts = [] + ttls = [] with _Socket(source, privileged) as sock: for sequence in range(count): @@ -159,14 +160,17 @@ def ping(address, count=4, interval=1, timeout=2, id=None, source=None, rtt = (reply.time - request.time) * 1000 rtts.append(rtt) + ttl = reply.ttl + ttls.append(0 if ttl is None else ttl) + except ICMPLibError: pass - return Host(address, packets_sent, rtts) + return Host(address, packets_sent, rtts, ttls) async def async_ping(address, count=4, interval=1, timeout=2, id=None, - source=None, family=None, privileged=True, **kwargs): + source=None, family=None, privileged=True, **kwargs): ''' Send ICMP Echo Request packets to a network host. @@ -270,6 +274,7 @@ async def async_ping(address, count=4, interval=1, timeout=2, id=None, id = id or unique_identifier() packets_sent = 0 rtts = [] + ttls = [] with AsyncSocket(_Socket(source, privileged)) as sock: for sequence in range(count): @@ -292,7 +297,10 @@ async def async_ping(address, count=4, interval=1, timeout=2, id=None, rtt = (reply.time - request.time) * 1000 rtts.append(rtt) + ttl = reply.ttl + ttls.append(0 if ttl is None else ttl) + except ICMPLibError: pass - return Host(address, packets_sent, rtts) + return Host(address, packets_sent, rtts, ttls) diff --git a/icmplib/sockets.py b/icmplib/sockets.py index a2a428e..55cee72 100644 --- a/icmplib/sockets.py +++ b/icmplib/sockets.py @@ -219,6 +219,13 @@ def _parse_reply(self, packet, source, current_time): if len(packet) < self._ICMP_PAYLOAD_OFFSET: return None + if type == 0: + # TODO: Type 0 is the ICMP Reply message for IPv4. To support IPv6, + # the library probably needs to read Type 129. + ttl, = unpack('B', packet[8:9]) + else: + ttl = None + id, sequence = unpack('!2H', packet[ self._ICMP_ID_OFFSET: self._ICMP_PAYLOAD_OFFSET]) @@ -231,7 +238,8 @@ def _parse_reply(self, packet, source, current_time): type=type, code=code, bytes_received=bytes_received, - time=current_time) + time=current_time, + ttl=ttl) def send(self, request): ''' diff --git a/setup.cfg b/setup.cfg index 3856421..3304620 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = icmplib - version = 3.0.3 + version = 3.0.3-cesbit0 description = The power to forge ICMP packets and do ping and traceroute. keywords = icmp, sockets, ping, multiping, traceroute, async, asyncio, ipv4, ipv6, python, python3