Skip to content

Commit 5b6d672

Browse files
author
Alessandro Maggio
committed
fix: Fix packet length representation
1 parent 145af92 commit 5b6d672

3 files changed

Lines changed: 54 additions & 18 deletions

File tree

pythonping/__init__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ def ping(target,
1919
df=False,
2020
verbose=False,
2121
out=sys.stdout,
22-
match=False):
22+
match=False,
23+
out_format='legacy'):
2324
"""Pings a remote host and handles the responses
2425
2526
:param target: The remote hostname or IP address to ping
@@ -49,6 +50,8 @@ def ping(target,
4950
8.8.8.8 with 1000 bytes and reply is truncated to only the first 74 of request payload with packet identifiers
5051
the same in request and reply)
5152
:type match: bool
53+
:param repr_format: How to __repr__ the response. Allowed: legacy, None
54+
:type repr_format: str
5255
:return: List with the result of each ping
5356
:rtype: executor.ResponseList"""
5457
provider = payload_provider.Repeat(b'', 0)
@@ -74,7 +77,7 @@ def ping(target,
7477
break
7578

7679
comm = executor.Communicator(target, provider, timeout, interval, socket_options=options, verbose=verbose, output=out,
77-
seed_id=seed_id)
80+
seed_id=seed_id, repr_format=out_format)
7881
comm.run(match_payloads=match)
7982

8083
SEED_IDs.remove(seed_id)

pythonping/executor.py

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ def send(self, source_socket):
5252
:type source_socket: network.Socket"""
5353
source_socket.send(self.packet.packet)
5454

55+
def __repr__(self):
56+
return repr(self.packet)
57+
5558

5659
def represent_seconds_in_ms(seconds):
5760
"""Converts seconds into human-readable milliseconds with 2 digits decimal precision
@@ -65,15 +68,21 @@ def represent_seconds_in_ms(seconds):
6568

6669
class Response:
6770
"""Represents a response to an ICMP message, with metadata like timing"""
68-
def __init__(self, message, time_elapsed):
71+
def __init__(self, message, time_elapsed, source_request=None, repr_format=None):
6972
"""Creates a representation of ICMP message received in response
7073
7174
:param message: The message received
7275
:type message: Union[None, Message]
7376
:param time_elapsed: Time elapsed since the original request was sent, in seconds
74-
:type time_elapsed: float"""
77+
:type time_elapsed: float
78+
:param source_request: ICMP packet represeting the request that originated this response
79+
:type source_request: ICMP
80+
:param repr_format: How to __repr__ the response. Allowed: legacy, None
81+
:type repr_format: str"""
7582
self.message = message
7683
self.time_elapsed = time_elapsed
84+
self.source_request = source_request
85+
self.repr_format = repr_format
7786

7887
@property
7988
def success(self):
@@ -119,17 +128,31 @@ def error_message(self):
119128
def time_elapsed_ms(self):
120129
return represent_seconds_in_ms(self.time_elapsed)
121130

122-
def __repr__(self):
131+
def legacy_repr(self):
123132
if self.message is None:
124133
return 'Request timed out'
125134
elif self.success:
126135
return 'Reply from {0}, {1} bytes in {2}ms'.format(self.message.source,
127-
len(self.message.packet.payload),
136+
len(self.message.packet.raw),
128137
self.time_elapsed_ms)
129138
else:
130139
# Not successful, but with some code (e.g. destination unreachable)
131140
return '{0} from {1} in {2}ms'.format(self.error_message, self.message.source, self.time_elapsed_ms)
132141

142+
def __repr__(self):
143+
if self.repr_format == 'legacy':
144+
return self.legacy_repr()
145+
if self.message is None:
146+
return 'Timed out'
147+
elif self.success:
148+
return 'status=OK\tfrom={0}\tms={1}\t\tbytes\tsnt={2}\trcv={3}'.format(
149+
self.message.source,
150+
self.time_elapsed_ms,
151+
len(self.source_request.raw)+20,
152+
len(self.message.packet.raw)
153+
)
154+
else:
155+
return 'status=ERR\tfrom={1}\terror="{0}"'.format(self.message.source, self.error_message)
133156

134157
class ResponseList:
135158
"""Represents a series of ICMP responses"""
@@ -228,7 +251,7 @@ def __iter__(self):
228251
class Communicator:
229252
"""Instance actually communicating over the network, sending messages and handling responses"""
230253
def __init__(self, target, payload_provider, timeout, interval, socket_options=(), seed_id=None,
231-
verbose=False, output=sys.stdout):
254+
verbose=False, output=sys.stdout, repr_format=None):
232255
"""Creates an instance that can handle communication with the target device
233256
234257
:param target: IP or hostname of the remote device
@@ -246,13 +269,16 @@ def __init__(self, target, payload_provider, timeout, interval, socket_options=(
246269
:param verbose: Flag to enable verbose mode, defaults to False
247270
:type verbose: bool
248271
:param output: File where to write verbose output, defaults to stdout
249-
:type output: file"""
272+
:type output: file
273+
:param repr_format: How to __repr__ the response. Allowed: legacy, None
274+
:type repr_format: str"""
250275
self.socket = network.Socket(target, 'icmp', source=None, options=socket_options)
251276
self.provider = payload_provider
252277
self.timeout = timeout
253278
self.interval = interval
254279
self.responses = ResponseList(verbose=verbose, output=output)
255280
self.seed_id = seed_id
281+
self.repr_format = repr_format
256282
# note that to make Communicator instances thread safe, the seed ID must be unique per thread
257283
if self.seed_id is None:
258284
self.seed_id = os.getpid() & 0xFFFF
@@ -269,15 +295,15 @@ def send_ping(self, packet_id, sequence_number, payload):
269295
:type sequence_number: int
270296
:param payload: The payload of the ICMP message
271297
:type payload: Union[str, bytes]
272-
:rtype: bytes"""
298+
:rtype: ICMP"""
273299
i = icmp.ICMP(
274300
icmp.Types.EchoRequest,
275301
payload=payload,
276302
identifier=packet_id, sequence_number=sequence_number)
277303
self.socket.send(i.packet)
278-
return i.payload
304+
return i
279305

280-
def listen_for(self, packet_id, timeout, payload_pattern=None):
306+
def listen_for(self, packet_id, timeout, payload_pattern=None, source_request=None):
281307
"""Listens for a packet of a given id for a given timeout
282308
283309
:param packet_id: The ID of the packet to listen for, the same for request and response
@@ -307,8 +333,8 @@ def listen_for(self, packet_id, timeout, payload_pattern=None):
307333
payload_matched = (payload_pattern == response.payload)
308334

309335
if payload_matched:
310-
return Response(Message('', response, source_socket[0]), timeout - time_left)
311-
return Response(None, timeout)
336+
return Response(Message('', response, source_socket[0]), timeout - time_left, source_request, repr_format=self.repr_format)
337+
return Response(None, timeout, source_request, repr_format=self.repr_format)
312338

313339
@staticmethod
314340
def increase_seq(sequence_number):
@@ -332,11 +358,11 @@ def run(self, match_payloads=False):
332358
identifier = self.seed_id
333359
seq = 1
334360
for payload in self.provider:
335-
payload_bytes_sent = self.send_ping(identifier, seq, payload)
361+
icmp_out = self.send_ping(identifier, seq, payload)
336362
if not match_payloads:
337-
self.responses.append(self.listen_for(identifier, self.timeout))
363+
self.responses.append(self.listen_for(identifier, self.timeout, None, icmp_out))
338364
else:
339-
self.responses.append(self.listen_for(identifier, self.timeout, payload_bytes_sent))
365+
self.responses.append(self.listen_for(identifier, self.timeout, icmp_out.payload, icmp_out))
340366

341367
seq = self.increase_seq(seq)
342368

pythonping/icmp.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,14 @@ def __init__(self, message_type=Types.EchoReply, payload=None, identifier=None,
154154
self.id = identifier & 0xFFFF # Prevent identifiers bigger than 16 bits
155155
self.sequence_number = sequence_number
156156
self.received_checksum = None
157+
self.raw = None
157158

158159
@property
159160
def packet(self):
160161
"""The raw packet with header, ready to be sent from a socket"""
161-
return self._header(check=self.expected_checksum) + self.payload
162+
p = self._header(check=self.expected_checksum) + self.payload
163+
if (self.raw is None): self.raw = p
164+
return p
162165

163166
def _header(self, check=0):
164167
"""The raw ICMP header
@@ -175,6 +178,9 @@ def _header(self, check=0):
175178
self.id,
176179
self.sequence_number)
177180

181+
def __repr__(self):
182+
return ' '.join('{:02x}'.format(b) for b in self.raw)
183+
178184
@property
179185
def is_valid(self):
180186
"""True if the received checksum is valid, otherwise False"""
@@ -209,9 +215,10 @@ def unpack(self, raw):
209215
210216
:param raw: The raw packet, including payload
211217
:type raw: bytes"""
218+
self.raw = raw
212219
self.message_type, \
213220
self.message_code, \
214221
self.received_checksum, \
215222
self.id, \
216-
sequence = struct.unpack("bbHHh", raw[20:28])
223+
self.sequence_number = struct.unpack("bbHHh", raw[20:28])
217224
self.payload = raw[28:]

0 commit comments

Comments
 (0)