-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathping.py
More file actions
135 lines (115 loc) · 4.56 KB
/
Copy pathping.py
File metadata and controls
135 lines (115 loc) · 4.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import os
import sys
import time
import socket
import struct
import select
# ICMP code
ICMP_TYPE_ECHO_REQ = 8
ICMP_TYPE_ECHO_REP = 0
# RFC1071 checksum
def inet_cksum(buf: bytes) -> int:
if len(buf) & 1:
buf += b"\x00"
total = 0
for i in range(0, len(buf), 2):
total += (buf[i] << 8) + buf[i + 1]
total = (total & 0xFFFF) + (total >> 16)
checksum = (~total) & 0xFFFF
return checksum
# craft ICMP echo request packet
def craft_echo(pid_id: int, seq_no: int, payload_len: int = 32) -> bytes:
hdr = struct.pack("!BBHHH", ICMP_TYPE_ECHO_REQ, 0, 0, pid_id, seq_no)
body = struct.pack("!d", time.time()) + b"P" * max(0, payload_len - 8)
csum = inet_cksum(hdr + body)
hdr = struct.pack("!BBHHH", ICMP_TYPE_ECHO_REQ, 0, csum, pid_id, seq_no) # [type, code, checksum, identifier, sequence] + [payload]
return hdr + body
# receive and validate one echo reply
def await_echo(sock: socket.socket, pid_id: int, seq_no: int, timeout_s: float):
deadline = time.time() + timeout_s
while True:
remaining = deadline - time.time()
if remaining <= 0:
return None
rlist, _, _ = select.select([sock], [], [], remaining)
if not rlist:
return None
t_recv = time.time()
packet, (src_ip, _) = sock.recvfrom(65535)
# IPv4 header
iphdr = packet[:20]
_ver_ihl, _tos, _len, _id, _flags_frag, ttl, proto, _sum, src_raw, _dst_raw = \
struct.unpack("!BBHHHBBHII", iphdr)
# ICMP header
icmph = packet[20:28]
icmp_type, icmp_code, _icmp_sum, recv_id, recv_seq = struct.unpack("!BBHHH", icmph)
# accept only our echo reply
if icmp_type == ICMP_TYPE_ECHO_REP and recv_id == pid_id and recv_seq == seq_no:
t_send = struct.unpack("!d", packet[28:36])[0] # timestamp from payload
rtt_ms = (t_recv - t_send) * 1000.0
data_sz = len(packet) - 20
return (rtt_ms, ttl, data_sz, src_ip)
# ping loop
def run_ping(target_host: str, probes: int = 6, per_timeout: float = 1.0, payload_len: int = 32):
try:
dst_ip = socket.gethostbyname(target_host)
except socket.gaierror as e:
print(f"DNS resolution failed for {target_host}: {e}")
return 2
icmp_proto = socket.getprotobyname("icmp")
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp_proto)
except PermissionError:
print("ERROR: run as Administrator/root (raw sockets required).")
return 2
pid_id = os.getpid() & 0xFFFF
tx_count = rx_count = 0
samples_ms = []
print("ping destination IP addresses in Python")
print(f"PING {target_host} ({dst_ip}): {payload_len} data bytes")
for seq_no in range(probes):
pkt = craft_echo(pid_id, seq_no, payload_len)
t0 = time.time()
sock.sendto(pkt, (dst_ip, 0))
tx_count += 1
result = await_echo(sock, pid_id, seq_no, per_timeout)
if result is None:
print(f"Request timed out for icmp_seq={seq_no}")
else:
rtt_ms, ttl, nbytes, src = result
rx_count += 1
samples_ms.append(rtt_ms)
print(f"{nbytes} bytes from {src}: ICMP_seq = {seq_no}, TTL = {ttl}, time = {rtt_ms:.3f}ms")
# probe time
pause = 1.0 - (time.time() - t0)
if pause > 0:
time.sleep(pause)
# ping & RTT stats
loss_pct = (1 - rx_count / tx_count) * 100 if tx_count else 0.0
print(f"\n{target_host} ping statistics:")
print(f"{tx_count} packets transmitted, {rx_count} received, {loss_pct:.1f}% packet loss")
if samples_ms:
mn, avg, mx = min(samples_ms), sum(samples_ms) / len(samples_ms), max(samples_ms)
print(f"RTT min: {mn:.3f}ms, avg: {avg:.3f}ms, max: {mx:.3f}ms")
sock.close()
return 0 if rx_count else 1
# continue?
def ask_continue(prompt="Another ping? (y/n): "):
while True:
ans = input(prompt).strip().lower()
if ans in ("y"):
return True
elif ans in ("n"):
return False
else:
print("Invalid input. Please enter 'y' or 'n'.")
# main function
if __name__ == "__main__":
first_arg = sys.argv[1] if len(sys.argv) > 1 else None
while True:
host = first_arg or input("\nEnter a host to ping: ").strip()
first_arg = None
run_ping(host)
if not ask_continue("Another ping? (y/n): "):
print("Cya")
break