我编写了一个简化版本的Windows命令,作为练习,并了解包中的tracert。它显示了您的互联网流量(很可能)将采取的路径,在前往给定的目的地。
下面是一个例子(一些东西是从偏执狂中整理出来的):

python tracert.py google.com -h
usage: tracert.py [-h] [-d] [-o O] [-w W] [-t T] ip
positional arguments:
ip
optional arguments:
-h, --help show this help message and exit
-d Do not resolve addresses to hostnames.
-o O Maximum number of hops to search for target.
-w W Wait timeout milliseconds for each reply.
-t T How many per packets to send per hop.不幸的是,由于使用了Scapy,时间似乎被打破了。发送数据包似乎有一种固有的开销,这种开销使实际时间相形见绌。但是,除非Scapy提供了一种访问往返时间的方法,否则我认为如果不取消Scapy并手动进行数据包构造/发送,我就无法修复这个问题。我提到这一点,因为任何熟悉tracert的人都可能会发现时间的模式很奇怪。它们主要作为“半功能”占位符包括在内,因此我可以尝试实现tracert的增量行为。
我试着和很多tracerts的行为相匹配,但我没有做得太过火。为了简单起见,我选择先确定目标,而不是最后一个。
最困难的部分是显示增量,因为他们的到来,以匹配tracert。我对_tracert_hop_row进行了几次重新设计,尝试返回可迭代的结果,用户可以在生成结果时打印结果。但结果却是一团糟,因为我还需要告诉大家,我们是否找到了目的地。最后,我直接打印结果。
我主要关心的是_tracert_hop_row。太乱了,太长了。任何有关它或任何其他方面的建议将不胜感激。
reverse_dns.py
from typing import Union
from scapy.layers.inet import UDP, IP
from scapy.layers.dns import DNS, DNSQR, DNSRR
from scapy.sendrecv import sr1
_REVERSE_DOMAIN = ".in-addr.arpa"
_OCTET_DELIMITER = "."
def _reverse_dns_packet(ip: str, dns_server: str) -> DNS:
reversed_ip = _OCTET_DELIMITER.join(reversed(ip.split(_OCTET_DELIMITER)))
question = DNSQR(qname=f"{reversed_ip}{_REVERSE_DOMAIN}", qtype=12)
return IP(dst=dns_server) / UDP(dport=53) / DNS(qd=question)
def reverse_dns_lookup(ip: str, dns_server: str, **sr1_kwargs) -> Union[None, int, str]:
"""
Returns the str host-name if it could be resolved,
or an int response code if there was an error,
or None if the DNS server couldn't be reached.
Unless you know the DNS server will be reachable, setting a timeout via the kwargs is advised.
"""
request = _reverse_dns_packet(ip, dns_server)
resp = sr1(request, **sr1_kwargs)
if resp is None:
return None
resp_code = resp[1][DNS].rcode
if resp_code == 0:
raw = resp[1][DNSRR].rdata
return raw.decode("UTF-8")
else:
return resp_codetracert.py
from typing import Tuple, List, Optional, Callable, TypeVar
from collections import Counter
from time import perf_counter
import argparse as ap
from scapy.config import conf
from scapy.layers.inet import IP, ICMP
from scapy.sendrecv import sr1
from reverse_dns import reverse_dns_lookup
_MAX_TTL = 255
_DEFAULT_TIMEOUT = 5
_DEFAULT_VERBOSITY = False
_DEFAULT_TESTS_PER = 3
_DEFAULT_RESOLVE_HOSTNAMES = True
_TABLE_SPACING = 10
NO_INFO_SYM = "*"
T = TypeVar("T")
def _new_trace_packet(destination_ip: str, hop_n: int) -> ICMP:
return IP(dst=destination_ip, ttl=hop_n) / ICMP()
# Credit: https://stackoverflow.com/a/60845191/3000206
def get_gateway_of(ip: str) -> str:
return conf.route.route(ip)[2]
def _check_hop_n(destination_ip: str, hop_n: int, **sr1_kwargs) -> Optional[Tuple[str, bool]]:
"""
Returns a tuple of (hop_ip, destination_reached?)
"""
sr1_kwargs.setdefault("timeout", _DEFAULT_TIMEOUT)
sr1_kwargs.setdefault("verbose", _DEFAULT_VERBOSITY)
packet = _new_trace_packet(destination_ip, hop_n)
reply = sr1(packet, **sr1_kwargs)
return reply and (reply[IP].src, reply[ICMP].type == 0)
def _find_proper_route(replies: List[ICMP]) -> Optional[Tuple[str, bool]]:
if not replies:
return None
ip_isfin_pairs = [(resp[IP].src, resp[ICMP].type == 0) for resp in replies]
found_destination = next((ip for ip, isfin in ip_isfin_pairs if isfin), None)
selected_ip = found_destination or Counter(ip for ip, _ in ip_isfin_pairs).most_common(1)[0][0]
return selected_ip, bool(found_destination)
def _time_exec(f: Callable[[], T]) -> Tuple[float, T]:
"""
Executes the function and returns a tuple of [exec_seconds, result].
"""
start = perf_counter()
result = f()
end = perf_counter()
return end - start, result
def _cell_print(x: str) -> None:
print(x.ljust(_TABLE_SPACING), end="", flush=True)
def _tracert_hop_row(destination_ip: str,
n_tests: int,
hop_n: int,
resolve_hostname: bool,
**sr_kwargs) -> bool:
sr_kwargs.setdefault("timeout", _DEFAULT_TIMEOUT)
sr_kwargs.setdefault("verbose", _DEFAULT_VERBOSITY)
packet = _new_trace_packet(destination_ip, hop_n)
replies = []
for _ in range(n_tests):
secs, reply = _time_exec(lambda: sr1(packet, **sr_kwargs))
_cell_print(NO_INFO_SYM if reply is None else f"{int(secs * 1000)} ms")
if reply:
replies.append(reply)
if not replies:
_cell_print(NO_INFO_SYM)
return False
best_route, found_destination = _find_proper_route(replies)
if resolve_hostname:
host = reverse_dns_lookup(best_route, get_gateway_of(best_route), **sr_kwargs)
if isinstance(host, str):
_cell_print(f"{host} [{best_route}]")
else:
_cell_print(best_route)
else:
_cell_print(best_route)
return found_destination
def tracert_internal(destination_ip: str,
n_tests_per_hop: int = _DEFAULT_TESTS_PER,
resolve_hostnames: bool = _DEFAULT_RESOLVE_HOSTNAMES,
max_hops: int = _MAX_TTL,
**sr_kwargs) -> None:
for hop_n in range(1, max_hops + 1):
_cell_print(str(hop_n))
found_destination = _tracert_hop_row(destination_ip, n_tests_per_hop, hop_n, resolve_hostnames, **sr_kwargs)
print()
if found_destination:
break
def main():
parser = ap.ArgumentParser()
parser.add_argument("ip")
parser.add_argument("-d", action="store_false", default=True,
help="Do not resolve addresses to hostnames.")
parser.add_argument("-o", type=int, default=_MAX_TTL,
help="Maximum number of hops to search for target.")
parser.add_argument("-w", type=float, default=_DEFAULT_TIMEOUT,
help="Wait timeout milliseconds for each reply.")
parser.add_argument("-t", type=int, default=_DEFAULT_TESTS_PER,
help="How many per packets to send per hop.")
args = parser.parse_args()
try:
tracert_internal(args.ip, args.t, args.d, args.o, timeout=args.w)
except KeyboardInterrupt:
pass
if __name__ == '__main__':
main()发布于 2020-09-28 10:58:32
if resolve_hostname:
host = reverse_dns_lookup(best_route, get_gateway_of(best_route), **sr_kwargs)
if isinstance(host, str):
_cell_print(f"{host} [{best_route}]")
else:
_cell_print(best_route)
else:
_cell_print(best_route)您正在执行_cell_print,并在每种情况下使用best_route,这也是默认值。在这里使用不同的结构可能有动机,例如
output = best_route #default value
if resolve_hostname:
host = reversee_dns_lookup(best_route, get_gateway_of(best_route), **sr_kwargs)
if isinstance(host, str):
output = f"{host} [{best_route}]"
_cell_print(output)
```发布于 2020-11-17 17:38:20
由于@Cukic0d的建议,通过使用数据包的.time和.sent_time属性,可以很容易地改进时间。
我删除了_time_exec,并对for循环进行了重新定位:
for _ in range(n_tests):
reply = sr1(packet, **sr_kwargs)
if reply is None:
_cell_print(NO_INFO_SYM)
else:
_cell_print(f"{int((reply.time - packet.sent_time) * 1000)} ms")
replies.append(reply)现在的时间与tracert大致相当。
https://codereview.stackexchange.com/questions/249905
复制相似问题