From 1593d0bf79ca1e122f093da04808bcc2223ed113 Mon Sep 17 00:00:00 2001 From: Mabuchin Date: Sun, 8 Sep 2024 11:46:43 +0900 Subject: [PATCH] Add error type support for Linux Ping (#575) * feat: add icmp error handle into linux_parse * refactor: fixed timestamp offset logic(including error-response-type condition) --- jc/parsers/ping_s.py | 102 ++++++++++++++---- .../ping-dest-unreachable-streaming.json | 2 +- tests/test_ping_s.py | 2 +- 3 files changed, 83 insertions(+), 23 deletions(-) diff --git a/jc/parsers/ping_s.py b/jc/parsers/ping_s.py index 8178f2d7..46c4885a 100644 --- a/jc/parsers/ping_s.py +++ b/jc/parsers/ping_s.py @@ -169,7 +169,7 @@ def _ipv6_in(line): return ipv6 -def _error_type(line): +def _error_type_v4(line): # from https://github.com/dgibson/iputils/blob/master/ping.c # https://android.googlesource.com/platform/external/ping/+/8fc3c91cf9e7f87bc20b9e6d3ea2982d87b70d9a/ping.c # https://opensource.apple.com/source/network_cmds/network_cmds-328/ping.tproj/ping.c @@ -207,6 +207,37 @@ def _error_type(line): return None +def _error_type_v6(line): + type_map = { + 'Destination unreachable': 'destination_unreachable', + 'Packet too big': 'packet_too_big', + 'Time exceeded:': 'time_exceeded', + 'Parameter problem:': 'parameter_problem', + } + code_map = { + 'destination_unreachable': { + 'No route': 'no_route', + 'Administratively prohibited': 'administratively_prohibited', + "Beyond scope of source address": 'beyond_scope_of_source_address', + 'Address unreachable': 'address_unreachable', + 'Port unreachable': 'port_unreachable', + }, + 'time_exceeded': { + 'Hop limit': 'hop_limit', + 'Fragment reassembly time exceeded': 'fragment_reassembly_time_exceeded', + }, + } + + return_code = None + for err_type, code in type_map.items(): + if err_type in line: + return_code = code + for err_code, code_name in code_map[code].items(): + if err_code in line: + return_code += '_' + code_name + return return_code + + def _bsd_parse(line, s): output_line = {} @@ -263,6 +294,24 @@ def _bsd_parse(line, s): # ping response lines + err = None + if s.ipv4: + err = _error_type_v4(line) + else: + err = _error_type_v6(line) + + if err: + output_line = { + 'type': err + } + try: + output_line['sent_bytes'] = line.split()[0] + output_line['destination_ip'] = s.destination_ip + output_line['response_ip'] = line.split()[4].strip(':').strip('(').strip(')') + except Exception: + pass + return output_line + # ipv4 lines if not _ipv6_in(line): @@ -279,7 +328,7 @@ def _bsd_parse(line, s): return output_line # catch error responses - err = _error_type(line) + err = _error_type_v4(line) if err: output_line = { 'type': err @@ -444,25 +493,40 @@ def _linux_parse(line, s): } return output_line + # if timestamp option is specified, then shift icmp sequence field right by one + timestamp = False + if line[0] == '[': + timestamp = True + + timestamp_offset = 1 if timestamp else 0 + # ping response lines + err = None + if s.ipv4: + err = _error_type_v4(line) + else: + err = _error_type_v6(line) + + if err: + output_line = { + 'type': err, + 'destination_ip': s.destination_ip or None, + 'sent_bytes': s.sent_bytes or None, + 'response_ip': line.split()[timestamp_offset + 1] if type != 'timeout' else None, + 'icmp_seq': line.replace('=', ' ').split()[timestamp_offset + 3], + 'timestamp': line.split()[0].lstrip('[').rstrip(']') if timestamp else None, + } + return output_line # request timeout if 'no answer yet for icmp_seq=' in line: - timestamp = False - isequence = 5 - - # if timestamp option is specified, then shift icmp sequence field right by one - if line[0] == '[': - timestamp = True - isequence = 6 - output_line = { 'type': 'timeout', 'destination_ip': s.destination_ip or None, 'sent_bytes': s.sent_bytes or None, 'pattern': s.pattern or None, 'timestamp': line.split()[0].lstrip('[').rstrip(']') if timestamp else None, - 'icmp_seq': line.replace('=', ' ').split()[isequence] + 'icmp_seq': line.replace('=', ' ').split()[timestamp_offset + 5] } return output_line @@ -473,20 +537,16 @@ def _linux_parse(line, s): line = line.replace('(', ' ').replace(')', ' ').replace('=', ' ') # positions of items depend on whether ipv4/ipv6 and/or ip/hostname is used + param_positions = None if s.ipv4 and not s.hostname: - bts, rip, iseq, t2l, tms = (0, 3, 5, 7, 9) + param_positions = (0, 3, 5, 7, 9) elif s.ipv4 and s.hostname: - bts, rip, iseq, t2l, tms = (0, 4, 7, 9, 11) + param_positions = (0, 4, 7, 9, 11) elif not s.ipv4 and not s.hostname: - bts, rip, iseq, t2l, tms = (0, 3, 5, 7, 9) + param_positions = (0, 3, 5, 7, 9) elif not s.ipv4 and s.hostname: - bts, rip, iseq, t2l, tms = (0, 4, 7, 9, 11) - - # if timestamp option is specified, then shift everything right by one - timestamp = False - if line[0] == '[': - timestamp = True - bts, rip, iseq, t2l, tms = (bts + 1, rip + 1, iseq + 1, t2l + 1, tms + 1) + param_positions = (0, 4, 7, 9, 11) + bts, rip, iseq, t2l, tms = (x + timestamp_offset for x in param_positions) output_line = { 'type': 'reply', diff --git a/tests/fixtures/ubuntu-22.04/ping-dest-unreachable-streaming.json b/tests/fixtures/ubuntu-22.04/ping-dest-unreachable-streaming.json index 16aae853..97887860 100644 --- a/tests/fixtures/ubuntu-22.04/ping-dest-unreachable-streaming.json +++ b/tests/fixtures/ubuntu-22.04/ping-dest-unreachable-streaming.json @@ -1 +1 @@ -{"type":"summary","destination_ip":"10.0.3.22","sent_bytes":56,"pattern":null,"packets_transmitted":3,"packets_received":0,"packet_loss_percent":100.0,"duplicates":0,"errors":3,"corrupted":null,"time_ms":2049.0,"round_trip_ms_min":null,"round_trip_ms_avg":null,"round_trip_ms_max":null,"round_trip_ms_stddev":null} +[{"type": "destination_host_unreachable", "destination_ip": "10.0.3.22", "sent_bytes": 56, "response_ip": "10.0.0.1", "icmp_seq": 1, "timestamp": null}, {"type": "destination_host_unreachable", "destination_ip": "10.0.3.22", "sent_bytes": 56, "response_ip": "10.0.0.1", "icmp_seq": 2, "timestamp": null}, {"type": "destination_host_unreachable", "destination_ip": "10.0.3.22", "sent_bytes": 56, "response_ip": "10.0.0.1", "icmp_seq": 3, "timestamp": null}, {"type": "summary", "destination_ip": "10.0.3.22", "sent_bytes": 56, "pattern": null, "packets_transmitted": 3, "packets_received": 0, "packet_loss_percent": 100.0, "duplicates": 0, "errors": 3, "corrupted": null, "time_ms": 2049.0, "round_trip_ms_min": null, "round_trip_ms_avg": null, "round_trip_ms_max": null, "round_trip_ms_stddev": null}] diff --git a/tests/test_ping_s.py b/tests/test_ping_s.py index 2c475755..c6bb11f0 100644 --- a/tests/test_ping_s.py +++ b/tests/test_ping_s.py @@ -597,7 +597,7 @@ class MyTests(unittest.TestCase): """ Test 'ping' on Ubuntu 22.4 with destination unreachable message """ - self.assertEqual(list(jc.parsers.ping_s.parse(self.ubuntu_22_4_ping_dest_unreachable.splitlines(), quiet=True)), [self.ubuntu_22_4_ping_dest_unreachable_streaming_json]) + self.assertEqual(list(jc.parsers.ping_s.parse(self.ubuntu_22_4_ping_dest_unreachable.splitlines(), quiet=True)), self.ubuntu_22_4_ping_dest_unreachable_streaming_json) def test_ping_s_hostname_I_ubuntu_22_4(self):