scapy にはネットワークツール的な関数が存在するため、いくつか試してみます。
※ここでは実際に設定、動作したものを掲載していますが、内容について保証するものではありません。流用される場合は各自の責任でお願いします。
scapy の他の機能については「scapy について」からご参照ください。
traceroute
名前のとおりですが、traceroute です。
参考:traceroute のヘルプ
>>> help(traceroute)
Help on function traceroute in module scapy.layers.inet:
traceroute(target, dport=80, minttl=1, maxttl=30, sport=<RandShort>, l4=None, filter=None, timeout=2, verbose=None, **kargs)
Instant TCP traceroute
:param target: hostnames or IP addresses
:param dport: TCP destination port (default is 80)
:param minttl: minimum TTL (default is 1)
:param maxttl: maximum TTL (default is 30)
:param sport: TCP source port (default is random)
:param l4: use a Scapy packet instead of TCP
:param filter: BPF filter applied to received packets
:param timeout: time to wait for answers (default is 2s)
:param verbose: detailed output
:return: an TracerouteResult, and a list of unanswered packets
(END)
デフォルトでは TCP で宛先ポートが 80 ですが、自分の環境で実施すると以下のとおり、いきなり最終目的地までパケットが届いてしまうようなので、以降は UDP で検証したいと思います。
いきなり最終目的地までパケットが届いてしまう理由について
Web サーバ宛に traceroute(*1)を実行すると、初回は IP パケットの TTL が 1 のため、直近のルータから ICMP の結果コードである 11 が返されることが想定されます。ですが自環境での検証では、しょっぱなから Web サーバ から SA(Syn-Ack)が返されています。
*1 TCP/80 がデフォルトです。Syn 送信に対する Syn-Ack の返却を確認することでターゲットへの到達を確認しています。ターゲットの手前のルータで TTL 超過が発生した場合は、パケットは破棄され ICMP の結果コードである 11(Time Exceeded)が返されます。
>>> traceroute("142.250.207.100", maxttl=2)
Begin emission
Finished sending 2 packets
Received 2 packets, got 2 answers, remaining 0 packets
142.250.207.100:tcp80
1 142.250.207.100 SA <<<いきなり Syn-Ack が返されている
2 142.250.207.100 SA
(<Traceroute: TCP:2 UDP:0 ICMP:0 Other:0>, <Unanswered: TCP:0 UDP:0 ICMP:0 Other:0>)
>>>
以下は traceroute 実行中のパケットキャプチャです。SYN(#1)に対し、SYN-ACK(#3)が返却されています。
[root@rocky8-client scapy]# tshark -t a -r /tmp/test.pcap
Running as user "root" and group "root". This could be dangerous.
1 16:06:13.793476159 10.0.2.44 → 142.250.207.100 TCP 54 12268 → 80 [SYN] Seq=0 Win=8192 Len=0
2 16:06:13.795105611 10.0.2.44 → 142.250.207.100 TCP 54 35056 → 80 [SYN] Seq=0 Win=8192 Len=0
3 16:06:13.863273391 142.250.207.100 → 10.0.2.44 TCP 60 80 → 12268 [SYN, ACK] Seq=0 Ack=1 Win=32768 Len=0 MSS=1460
4 16:06:13.863351536 10.0.2.44 → 142.250.207.100 TCP 54 12268 → 80 [RST] Seq=1 Win=0 Len=0
5 16:06:13.863575622 142.250.207.100 → 10.0.2.44 TCP 60 80 → 35056 [SYN, ACK] Seq=0 Ack=1 Win=32768 Len=0 MSS=1460
6 16:06:13.863596298 10.0.2.44 → 142.250.207.100 TCP 54 35056 → 80 [RST] Seq=1 Win=0 Len=0
[root@rocky8-client scapy]#
SYN(#1)を送信しているパケットの詳細をみると、TTL は確かに 1 です。
[root@rocky8-client scapy]# tshark -nn -r /tmp/test.pcap -Y '(frame.number==1)' -V | grep "Internet Protocol" -n -A100
Running as user "root" and group "root". This could be dangerous.
27:Internet Protocol Version 4, Src: 10.0.2.44, Dst: 142.250.207.100
28- 0100 .... = Version: 4
29- .... 0101 = Header Length: 20 bytes (5)
30- Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT)
31- 0000 00.. = Differentiated Services Codepoint: Default (0)
32- .... ..00 = Explicit Congestion Notification: Not ECN-Capable Transport (0)
33- Total Length: 40
34- Identification: 0xf188 (61832)
35- Flags: 0x0000
36- 0... .... .... .... = Reserved bit: Not set
37- .0.. .... .... .... = Don't fragment: Not set
38- ..0. .... .... .... = More fragments: Not set
39- ...0 0000 0000 0000 = Fragment offset: 0
40- Time to live: 1
41- [Expert Info (Note/Sequence): "Time To Live" only 1] <<<ココ
42- ["Time To Live" only 1]
43- [Severity level: Note]
44- [Group: Sequence]
45- Protocol: TCP (6)
46- Header checksum: 0x5dbd [validation disabled]
47- [Header checksum status: Unverified]
48- Source: 10.0.2.44
49- Destination: 142.250.207.100
50-Transmission Control Protocol, Src Port: 12268, Dst Port: 80, Seq: 0, Len: 0
- 以下省略 -
本環境は <<Rocky8> on VirtulBox> on Windows11 といった構成で動いていますが、上記パケットキャプチャは(traceroute と同じ)Rocky8 で行ったものです。
以下のパケットキャプチャは Windows11 上の WireShark で行ったものです。同じパケットについて確認してみると TTL が 128 に変更されています。おそらくですが、VirtualBox が IP を NAT(10.0.2.44 → 19.168.0.4)(*1)した際に、TTL についても書き換えたのではないかと思われます。ただし、TTL の書き換えは TCP の場合のみ発生し、UDP での検証時には発生しませんでした。
*1 10.0.2.44 は Rocky8 に割り当てられている IP アドレス、19.168.0.4 は Windows11 に割り当てられている IP アドレスです。

>>> traceroute("www.google.com", maxttl=5)
Begin emission
Finished sending 5 packets
Received 5 packets, got 5 answers, remaining 0 packets
142.250.207.100:tcp80
1 142.250.207.100 SA <<<いきなりターゲットから Syn-Ack が返されている
2 142.250.207.100 SA
3 142.250.207.100 SA
4 142.250.207.100 SA
5 142.250.207.100 SA
(<Traceroute: TCP:5 UDP:0 ICMP:0 Other:0>, <Unanswered: TCP:0 UDP:0 ICMP:0 Other:0>)
>>>
l4 オプションと dport オプションで TCP/80 以外のプロトコルとポートを指定できます。以下は DNS サーバを宛先とするため、プロトコルに UDP()/DNS() を、宛先ポートに 53 を指定し実行しています。
>>> a,ua = traceroute("a.dns.jp", l4=UDP(sport=RandShort())/DNS(), dport=53, maxttl=12)
Begin emission
Finished sending 12 packets
Received 5 packets, got 4 answers, remaining 8 packets
203.119.1.1:udp53
1 10.0.2.1 11
10 203.119.1.1
11 203.119.1.1
12 203.119.1.1
>>>
参考として以下に OS 上の traceroute コマンドの実行結果を示しておきます。
[root@rocky8-client scapy]# traceroute a.dns.jp -U -p 53 -m 12
traceroute to a.dns.jp (203.119.1.1), 12 hops max, 60 byte packets
1 _gateway (10.0.2.1) 11.394 ms 8.631 ms 0.594 ms
2 * * *
3 * * *
4 * * *
5 * * *
6 * * *
7 * * *
8 * * *
9 * * a.dns.jp (203.119.1.1) 21.456 ms
[root@rocky8-client scapy]#
scapy では traceroute の結果を合成できます。
>>> e, ue = traceroute("e.dns.jp", l4=UDP(sport=RandShort())/DNS(), dport=53, maxttl=12)
Begin emission
Finished sending 12 packets
Received 7 packets, got 5 answers, remaining 7 packets
192.50.43.53:udp53
1 10.0.2.1 11
9 192.50.43.53
10 192.50.43.53
11 192.50.43.53
12 192.50.43.53
>>>
>>> ae = a + e; ae.show()
192.50.43.53:udp53 203.119.1.1:udp53
1 10.0.2.1 11 10.0.2.1 11
9 192.50.43.53 -
10 192.50.43.53 203.119.1.1
11 192.50.43.53 203.119.1.1
12 192.50.43.53 203.119.1.1
>>>
traceroute では複数の宛先を同時に指定可能です。
>>> aef, uaef = traceroute([x+".dns.jp" for x in 'aef'], l4=UDP(sport=RandShort())/DNS(), dport=53, maxttl=12)
Begin emission
Finished sending 36 packets
Received 13 packets, got 12 answers, remaining 24 packets
150.100.6.8:udp53 192.50.43.53:udp53 203.119.1.1:udp53
1 10.0.2.1 11 10.0.2.1 11 10.0.2.1 11
9 - 192.50.43.53 -
10 - 192.50.43.53 203.119.1.1
11 150.100.6.8 192.50.43.53 203.119.1.1
12 150.100.6.8 192.50.43.53 203.119.1.1
>>>
arping
arping では、指定したサブネット内で稼働しているノードを確認できます。
参考:arping のヘルプ
>>> help(arping)
arping(net: str, timeout: int = 2, cache: int = 0, verbose: Optional[int] = None, threaded: bool = True, **kargs: Any) -> Tuple[scapy.layers.l2.ARPingResult, scapy.plist.PacketList]
Send ARP who-has requests to determine which hosts are up::
arping(net, [cache=0,] [iface=conf.iface,] [verbose=conf.verb]) -> None
Set cache=True if you want arping to modify internal ARP-Cache
(END)
>>> arping("10.0.2.0/24")
Begin emission
Finished sending 256 packets
Received 15 packets, got 15 answers, remaining 241 packets
src manuf psrc
52:54:00:12:35:00 RealtekU 10.0.2.1
08:00:27:fb:79:c9 PcsCompu 10.0.2.16
08:00:27:fb:79:c9 PcsCompu 10.0.2.17
08:00:27:fb:79:c9 PcsCompu 10.0.2.18
52:54:00:12:35:00 RealtekU 10.0.2.2
08:00:27:fb:79:c9 PcsCompu 10.0.2.20
08:00:27:fb:79:c9 PcsCompu 10.0.2.21
08:00:27:fb:79:c9 PcsCompu 10.0.2.22
08:00:27:fb:79:c9 PcsCompu 10.0.2.24
08:00:27:fb:79:c9 PcsCompu 10.0.2.25
08:00:27:fb:79:c9 PcsCompu 10.0.2.26
08:00:27:fb:79:c9 PcsCompu 10.0.2.28
08:00:27:fb:79:c9 PcsCompu 10.0.2.29
08:00:27:fb:79:c9 PcsCompu 10.0.2.32
08:00:27:8d:2f:ad PcsCompu 10.0.2.40
(<ARPing: TCP:0 UDP:0 ICMP:0 Other:15>, <Unanswered: TCP:0 UDP:0 ICMP:0 Other:241>)
>>>
OS レベルのコマンドにも arping がありますが、scapy の arping とは機能が異なるようです。arp-scan というコマンドがありますが、こちらに近いようです。(*1)
*1 対象サブネットがローカルネットワークの場合、サブネット(下記例の 10.0.2.0/24)の代わりに -l オプションを指定しても同じ結果が得られます。
[root@rocky8-client scapy]# arp-scan -t 100 -r 1 10.0.2.0/24
Interface: enp0s3, type: EN10MB, MAC: 08:00:27:7e:aa:05, IPv4: 10.0.2.44
Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan)
10.0.2.1 52:54:00:12:35:00 QEMU
10.0.2.2 52:54:00:12:35:00 QEMU
10.0.2.16 08:00:27:fb:79:c9 PCS Systemtechnik GmbH
10.0.2.17 08:00:27:fb:79:c9 PCS Systemtechnik GmbH
10.0.2.18 08:00:27:fb:79:c9 PCS Systemtechnik GmbH
10.0.2.20 08:00:27:fb:79:c9 PCS Systemtechnik GmbH
10.0.2.21 08:00:27:fb:79:c9 PCS Systemtechnik GmbH
10.0.2.22 08:00:27:fb:79:c9 PCS Systemtechnik GmbH
10.0.2.24 08:00:27:fb:79:c9 PCS Systemtechnik GmbH
10.0.2.25 08:00:27:fb:79:c9 PCS Systemtechnik GmbH
10.0.2.26 08:00:27:fb:79:c9 PCS Systemtechnik GmbH
10.0.2.28 08:00:27:fb:79:c9 PCS Systemtechnik GmbH
10.0.2.29 08:00:27:fb:79:c9 PCS Systemtechnik GmbH
10.0.2.32 08:00:27:fb:79:c9 PCS Systemtechnik GmbH
10.0.2.40 08:00:27:8d:2f:ad PCS Systemtechnik GmbH
15 packets received by filter, 0 packets dropped by kernel
Ending arp-scan 1.10.0: 256 hosts scanned in 0.791 seconds (323.64 hosts/sec). 15 responded
[root@rocky8-client scapy]#
※ちなみに上記コマンド(scapy の arping と OS コマンドの arp-scan)は arp キャッシュを表示しているわけではありませんのでご注意ください。
scapy は OS とは別の ARP キャッシュを持ちます。scapy の arping は、その結果を scapy の ARP キャッシュに反映させることができます。以下の検証で ARP キャッシュへの影響を確認します。
OS の ARP キャッシュの状況を確認します。
[root@rocky8-client scapy]# ip neigh flush all
[root@rocky8-client scapy]# ip neigh show
10.0.2.2 dev enp0s3 lladdr 52:54:00:12:35:00 REACHABLE
[root@rocky8-client scapy]#
scapy の ARP キャッシュの状況を確認します。
>>> conf.netcache.arp_cache.flush()
>>> conf.netcache
arp_cache: 0 valid items. Timeout=120s
in6_neighbor: 0 valid items. Timeout=120s
dns_cache: 0 valid items. Timeout=300s
dclocator: 0 valid items. Timeout=600s
>>> conf.netcache.arp_cache
>>>
まずは cache=False(default)で arping を実施します。(→ ARP キャッシュには影響しません)
>>> arping("10.0.2.16/30", cache=False)
Begin emission
Finished sending 4 packets
Received 3 packets, got 3 answers, remaining 1 packets
src manuf psrc
08:00:27:fb:79:c9 PcsCompu 10.0.2.16
08:00:27:fb:79:c9 PcsCompu 10.0.2.17
08:00:27:fb:79:c9 PcsCompu 10.0.2.18
(<ARPing: TCP:0 UDP:0 ICMP:0 Other:3>, <Unanswered: TCP:0 UDP:0 ICMP:0 Other:1>)
>>>
ARP キャッシュの内容が変更されていないことを確認します。
[root@rocky8-client scapy]# ip neigh show
10.0.2.2 dev enp0s3 lladdr 52:54:00:12:35:00 REACHABLE
[root@rocky8-client scapy]#
>>> conf.netcache
arp_cache: 0 valid items. Timeout=120s
in6_neighbor: 0 valid items. Timeout=120s
dns_cache: 0 valid items. Timeout=300s
dclocator: 0 valid items. Timeout=600s
>>> conf.netcache.arp_cache
>>>
次に cache=True で arping を実施します。(→ scapy の ARP キャッシュに反映されます)
>>> arping("10.0.2.16/30", cache=True)
Begin emission
Finished sending 4 packets
Received 3 packets, got 3 answers, remaining 1 packets
src manuf psrc
08:00:27:fb:79:c9 PcsCompu 10.0.2.16
08:00:27:fb:79:c9 PcsCompu 10.0.2.17
08:00:27:fb:79:c9 PcsCompu 10.0.2.18
(<ARPing: TCP:0 UDP:0 ICMP:0 Other:3>, <Unanswered: TCP:0 UDP:0 ICMP:0 Other:1>)
>>>
OS の ARP キャッシュに影響していないことを確認します。
[root@rocky8-client scapy]# ip neigh show
10.0.2.2 dev enp0s3 lladdr 52:54:00:12:35:00 REACHABLE
[root@rocky8-client scapy]#
scapy の ARP キャッシュに arping の結果が登録されていることを確認します。
>>> conf.netcache
arp_cache: 3 valid items. Timeout=120s
in6_neighbor: 0 valid items. Timeout=120s
dns_cache: 0 valid items. Timeout=300s
dclocator: 0 valid items. Timeout=600s
>>> conf.netcache.arp_cache
10.0.2.16 08:00:27:fb:79:c9
10.0.2.17 08:00:27:fb:79:c9
10.0.2.18 08:00:27:fb:79:c9
>>>
p0f
キャプチャしたパケットからOS情報を推測できるようです。
自分の環境で試した限りですが、いまいちこのツールの良さがわかりませんでした。せっかくなので確認した内容については備忘録として記載しておきます。
参考:p0f のヘルプ
>>> load_module("p0f")
>>> help(p0f)
Help on function p0f in module scapy.modules.p0f:
p0f(pkt)
(END)
参考:scapy の p0f を使用するための前準備について
scapy の p0f を使用するには、どうやら OS に p0f をインストールが必要があるようです。
p0f のインストールなしで scapy p0f を実行すると、以下のとおりエラーとなります。
>>> load_module("p0f")
>>> cap = sniff(filter="dst port 53", count=1)
>>> cap.summary()
Ether / IP / TCP 10.0.2.44:37297 > 10.0.2.28:domain S
>>> p0f(cap[0])
WARNING: Can't open base None
WARNING: p0f base empty.
>>>
これは、conf.p0f_base にデータベースが指定されていないためのエラーです。
>>> conf.p0f_base
>>>
OS に p0f をインストールします。(本環境は Rocky8 ですが、p0f は epel リポジトリーからのインストールとなります)
[root@rocky8-client scapy]# dnf install p0f -y
- 省略 -
インストール済み:
p0f-3.09b-20.el8.x86_64
完了しました!
[root@rocky8-client scapy]#
データベースファイルの存在を確認します。
>>> select_path(["/etc/p0f", "/usr/share/p0f", "/opt/local"], "p0f.fp")
'/etc/p0f/p0f.fp'
>>>
conf.p0f_base にデータベースファイルのパスが設定されることを確認します。
>>> load_module("p0f")
>>> conf.p0f_base
'/etc/p0f/p0f.fp'
>>>
以降、scapy の p0f が使用可能となりました。試行錯誤の末、前準備はできましたが、これが正しい手順なのかどうかはわかりません。
パケットをキャプチャします。
>>> cap = sniff(filter="port 53", count=1)
>>> cap.summary()
Ether / IP / TCP 10.0.2.44:59417 > 10.0.2.28:domain S
>>>
キャプチャ中に実行したコマンドは以下のとおりです。
[root@rocky8-client scapy]# dig @10.0.2.28 www.test.co.jp A +tcp +norec +short
1.2.3.4
[root@rocky8-client scapy]#
パケットから OS 情報を表示します。
>>> p0f(cap[0])
(('s', 'unix', 'Linux', '3.11 and newer'), 0, True)
>>>
実際の OS 情報は以下のとおりです。
[root@rocky8-dns01 named]# uname -a
Linux rocky8-dns01.mydomain 4.18.0-553.56.1.el8_10.x86_64 #1 SMP Tue Jun 10 17:00:45 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
[root@rocky8-dns01 named]#
上記のとおりですが、情報の精度はなんとも微妙です。しかも上記の p0f の出力はクライアント側の情報のようです。
sniff の prn オプションに prnp0f を指定すると、パケット着信毎に OS 情報が表示されます。
>>> cap = sniff(filter="port 53", timeout=20, prn=prnp0f)
.-[ 10.0.2.44:38907 -> 10.0.2.16:domain (SYN) ]-
|
| Client = 10.0.2.44:38907
| OS = Linux 3.11 and newer
| Distance = 0
| Raw sig = 4:64+0:0:1460:29200,7:mss,sok,ts,nop,ws::0
`____
.-[ 10.0.2.16:domain -> 10.0.2.44:38907 (SYN+ACK) ]-
|
| Server = 10.0.2.16:domain
| OS = UNKNOWN
| Raw sig = 4:64+0:0:1460:28960,7:mss,sok,ts,nop,ws:df:0
`____
.-[ 10.0.2.44:38695 -> 10.0.2.17:domain (SYN) ]-
|
| Client = 10.0.2.44:38695
| OS = Linux 3.11 and newer
| Distance = 0
| Raw sig = 4:64+0:0:1460:29200,7:mss,sok,ts,nop,ws::0
`____
.-[ 10.0.2.17:domain -> 10.0.2.44:38695 (SYN+ACK) ]-
|
| Server = 10.0.2.17:domain
| OS = UNKNOWN
| Raw sig = 4:64+0:0:1460:28960,7:mss,sok,ts,nop,ws:df:0
`____
.-[ 10.0.2.44:41115 -> 10.0.2.18:domain (SYN) ]-
|
| Client = 10.0.2.44:41115
| OS = Linux 3.11 and newer
| Distance = 0
| Raw sig = 4:64+0:0:1460:29200,7:mss,sok,ts,nop,ws::0
`____
.-[ 10.0.2.18:domain -> 10.0.2.44:41115 (SYN+ACK) ]-
|
| Server = 10.0.2.18:domain
| OS = UNKNOWN
| Raw sig = 4:64+0:0:1460:28960,7:mss,sok,ts,nop,ws:df:0
`____
>>>
キャプチャ中に実行したコマンドは以下のとおりです。
dig @10.0.2.16 www.test.co.jp A +tcp +norec +short
dig @10.0.2.17 www.test.co.jp A +tcp +norec +short
dig @10.0.2.18 www.test.co.jp A +tcp +norec +short
知りたいのはサーバ側(宛先 OS)の情報ですが、本環境での検証では UNKNOWN でした。参考までに OS コマンドの p0f の結果を以下に掲載しておきます。
[root@rocky8-client scapy]# p0f
--- p0f 3.09b by Michal Zalewski <lcamtuf@coredump.cx> ---
[+] Closed 1 file descriptor.
[+] Loaded 322 signatures from '/etc/p0f/p0f.fp'.
[+] Intercepting traffic on default interface 'enp0s3'.
[+] Default packet filtering configured [+VLAN].
[+] Entered main event loop.
.-[ 10.0.2.44/43937 -> 10.0.2.16/53 (syn) ]-
|
| client = 10.0.2.44/43937
| os = Linux 3.11 and newer
| dist = 0
| params = fuzzy
| raw_sig = 4:64+0:0:1460:mss*20,7:mss,sok,ts,nop,ws::0
|
`----
.-[ 10.0.2.44/43937 -> 10.0.2.16/53 (mtu) ]-
|
| client = 10.0.2.44/43937
| link = Ethernet or modem
| raw_mtu = 1500
|
`----
.-[ 10.0.2.44/43937 -> 10.0.2.16/53 (syn+ack) ]-
|
| server = 10.0.2.16/53
| os = ???
| dist = 0
| params = none
| raw_sig = 4:64+0:0:1460:mss*20,7:mss,sok,ts,nop,ws:df:0
|
`----
.-[ 10.0.2.44/43937 -> 10.0.2.16/53 (mtu) ]-
|
| server = 10.0.2.16/53
| link = Ethernet or modem
| raw_mtu = 1500
|
`----
.-[ 10.0.2.44/40861 -> 10.0.2.17/53 (syn) ]-
|
| client = 10.0.2.44/40861
| os = Linux 3.11 and newer
| dist = 0
| params = fuzzy
| raw_sig = 4:64+0:0:1460:mss*20,7:mss,sok,ts,nop,ws::0
|
`----
.-[ 10.0.2.44/40861 -> 10.0.2.17/53 (host change) ]-
|
| client = 10.0.2.44/40861
| reason = tstamp port
| raw_hits = 0,1,1,1
|
`----
.-[ 10.0.2.44/40861 -> 10.0.2.17/53 (mtu) ]-
|
| client = 10.0.2.44/40861
| link = Ethernet or modem
| raw_mtu = 1500
|
`----
.-[ 10.0.2.44/40861 -> 10.0.2.17/53 (syn+ack) ]-
|
| server = 10.0.2.17/53
| os = ???
| dist = 0
| params = none
| raw_sig = 4:64+0:0:1460:mss*20,7:mss,sok,ts,nop,ws:df:0
|
`----
.-[ 10.0.2.44/40861 -> 10.0.2.17/53 (mtu) ]-
|
| server = 10.0.2.17/53
| link = Ethernet or modem
| raw_mtu = 1500
|
`----
.-[ 10.0.2.44/44511 -> 10.0.2.18/53 (syn) ]-
|
| client = 10.0.2.44/44511
| os = Linux 3.11 and newer
| dist = 0
| params = fuzzy
| raw_sig = 4:64+0:0:1460:mss*20,7:mss,sok,ts,nop,ws::0
|
`----
.-[ 10.0.2.44/44511 -> 10.0.2.18/53 (mtu) ]-
|
| client = 10.0.2.44/44511
| link = Ethernet or modem
| raw_mtu = 1500
|
`----
.-[ 10.0.2.44/44511 -> 10.0.2.18/53 (syn+ack) ]-
|
| server = 10.0.2.18/53
| os = ???
| dist = 0
| params = none
| raw_sig = 4:64+0:0:1460:mss*20,7:mss,sok,ts,nop,ws:df:0
|
`----
.-[ 10.0.2.44/44511 -> 10.0.2.18/53 (mtu) ]-
|
| server = 10.0.2.18/53
| link = Ethernet or modem
| raw_mtu = 1500
|
`----
^C[!] WARNING: User-initiated shutdown.
All done. Processed 49 packets.
[root@rocky8-client scapy]#
dnsd
簡易 DNS サーバです。クエリ名とアドレスを対にしたリストをもとに、DNS のクエリ要求に応答します。
参考:dnsd のヘルプ
>>> help(dnsd)
Help on function dnsd in module scapy.ansmachine:
dnsd(self, joker=None, match=None, srvmatch=None, joker6=False, send_error=False, relay=False, from_ip=True, from_ip6=False, src_ip=None, src_ip6=None, ttl=10, jokerarpa=False)
Simple DNS answering machine.
:param joker: default IPv4 for unresolved domains.
Set to False to disable, None to mirror the interface's IP.
Defaults to None, unless 'match' is used, then it defaults to
False.
:param joker6: default IPv6 for unresolved domains.
Set to False to disable, None to mirror the interface's IPv6.
Defaults to False.
:param match: queries to match.
This can be a dictionary of {name: val} where name is a string
representing a domain name (A, AAAA) and val is a tuple of 2
elements, each representing an IP or a list of IPs. If val is
a single element, (A, None) is assumed.
This can also be a list or names, in which case joker(6) are
used as a response.
:param jokerarpa: answer for .in-addr.arpa PTR requests. (Default: False)
:param relay: relay unresolved domains to conf.nameservers (Default: False).
:param send_error: send an error message when this server can't answer
(Default: False)
:param srvmatch: a dictionary of {name: (port, target)} used for SRV
:param from_ip: an source IP to filter. Can contain a netmask. True for all,
False for none. Default True
:param from_ip6: an source IPv6 to filter. Can contain a netmask. True for all,
False for none. Default False
:param ttl: the DNS time to live (in seconds)
:param src_ip: override the source IP
:param src_ip6:
Examples:
- Answer all 'A' and 'AAAA' requests::
$ sudo iptables -I OUTPUT -p icmp --icmp-type 3/3 -j DROP
>>> dnsd(joker="192.168.0.2", joker6="fe80::260:8ff:fe52:f9d8",
... iface="eth0")
- Answer only 'A' query for google.com with 192.168.0.2::
>>> dnsd(match={"google.com": "192.168.0.2"}, iface="eth0")
- Answer DNS for a Windows domain controller ('SRV', 'A' and 'AAAA')::
>>> dnsd(
... srvmatch={
... "_ldap._tcp.dc._msdcs.DOMAIN.LOCAL.": (389,
... "srv1.domain.local"),
... },
... match={"src1.domain.local": ("192.168.0.102",
... "fe80::260:8ff:fe52:f9d8")},
... )
- Relay all queries to another DNS server, except some::
>>> conf.nameservers = ["1.1.1.1"] # server to relay to
>>> dnsd(
... match={"test.com": "1.1.1.1"},
... relay=True,
... )
(END)
リストに登録されたクエリ名とアドレスで DNS の名前解決を行う例を示します。
リソースレコードのリストを用意します。
>>> rrlist={
... b'google.com.':"192.168.1.100",
... b'foo.com.' :"192.168.1.200"
... }
>>>
dnsd を起動します。(終了は Ctrl+C です)
用意したリストは match オプションで指定します。リストに登録されていないクエリ名が要求された場合は joker オプションに指定されているアドレスが返されます。
下記実行例の 2 行目以降は、DNS クエリを処理した際のログです。
>>> dnsd(iface="enp0s3", match=rrlist, joker="192.168.0.2")
Ether / IP / UDP / DNS Qry b'google.com.' ==> Ether / IP / UDP / DNS Ans 192.168.1.100
Ether / IP / UDP / DNS Qry b'foo.com.' ==> Ether / IP / UDP / DNS Ans 192.168.1.200
Ether / IP / UDP / DNS Qry b'xxx.com.' ==> Ether / IP / UDP / DNS Ans 192.168.0.2
^C>>>
以下は、dnsd 起動中に別のマシンで実行した dig です。10.0.2.44 は dnsd を起動しているマシンです。google.com と foo.com は用意したリストで、xxx.com は joker オプションで解決されています。
[root@rocky8-dns02 ~]# dig @10.0.2.44 google.com +short
192.168.1.100
[root@rocky8-dns02 ~]# dig @10.0.2.44 foo.com +short
192.168.1.200
[root@rocky8-dns02 ~]# dig @10.0.2.44 xxx.com +short
192.168.0.2
[root@rocky8-dns02 ~]#
次に、DNS クエリを別のサーバに転送する例を示します。
dnsd の relay オプションが True の場合、conf.nameservers に登録されているアドレス(DNS サーバ)へ DNS クエリを転送します。
転送先のDNS サーバを登録(*1)します。(デフォルトでは OS と同じ DNS サーバが登録されているようです)
*1 10.0.2.40 はローカルに用意した自前のDNS キャッシュサーバです。
>>> conf.nameservers = ["10.0.2.40"]
>>> conf.nameservers
['10.0.2.40']
>>>
転送させたくないクエリについては、リストを用意し match オプションで指定します。
>>> rrlist={
... b'google.com.':"192.168.1.100",
... b'foo.com.' :"192.168.1.200"
... }
>>>
dnsd を起動します。(終了は Ctrl+C です)
下記実行例の 2 行目以降は、DNS クエリを処理した際のログです。
>>> dnsd(iface="enp0s3", match=rrlist, relay=True, )
Ether / IP / UDP / DNS Qry b'google.com.' ==> Ether / IP / UDP / DNS Ans 192.168.1.100
Ether / IP / UDP / DNS Qry b'foo.com.' ==> Ether / IP / UDP / DNS Ans 192.168.1.200
Ether / IP / UDP / DNS Qry b'www.test.co.jp.' ==> Ether / IP / UDP / DNS Ans 1.2.3.4
Ether / IP / UDP / DNS Qry b'www.test.co.jp.' ==> Ether / IP / UDP / DNS Ans 1.2.3.4
^C>>>
以下は、dnsd 起動中に別のマシンで実行した dig です。10.0.2.44 は dnsd を起動しているマシンです。google.com と foo.com は用意したリストで、www.test.co.jp は 転送先のサーバで解決されています。
[root@rocky8-dns02 ~]# dig @10.0.2.44 google.com +short
192.168.1.100
[root@rocky8-dns02 ~]# dig @10.0.2.44 foo.com +short
192.168.1.200
[root@rocky8-dns02 ~]# dig @10.0.2.44 www.test.co.jp +short
1.2.3.4
10.0.2.28
10.0.2.29
[root@rocky8-dns02 ~]#