scapy の勉強がてら、基本的な操作をまとめてみました。
※ここでは実際に設定、動作したものを掲載していますが、内容について保証するものではありません。流用される場合は各自の責任でお願いします。
scapy のインストール手順については「scapy のインストール」をご参照ください。
環境は以下のとおりです。
[root@rocky8-client ~]# uname -a
Linux rocky8-client.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-client ~]# cat /etc/redhat-release
Rocky Linux release 8.10 (Green Obsidian)
[root@rocky8-client ~]#
- 対話モードの起動
- 対話モードの起動(ヘッダー非表示)
- パケット一覧の表示(ls)
- パケット詳細の表示(ls)
- オブジェクトタイプの確認(type)
- 関数一覧の表示(lsc)
- ヘルプの表示(help)
- インフォーメーションの抑制(verbose)
- 件数の確認(len)
- ペイロードの確認(payload)
- パケットの形式変換
- PCAP ファイルの操作
- パケットセットの作成と展開
- パケットの送信と受信
- 送受信結果の出力を編集
- 送受信結果から条件に一致するもののみ出力(filter)
- 送受信結果をテーブル形式で出力(make_table)
- 送受信をキャプチャ(sniff)
- キャプチャを非同期で行う(AsyncSniffer)
- ルーティングの管理
対話モードの起動
[root@rocky8-client scapy]# scapy
INFO: Can't import PyX. Won't be able to use psdump() or pdfdump().
INFO: Can't import python-cryptography v1.7+. Disabled PKI & TLS crypto-related features.
INFO: Can't import python-cryptography v1.7+. Disabled WEP decryption/encryption. (Dot11)
INFO: Can't import python-cryptography v1.7+. Disabled IPsec encryption/authentication.
WARNING: No alternative Python interpreters found ! Using standard Python shell instead.
INFO: Using the default Python shell: History is disabled.
aSPY//YASa
apyyyyCY//////////YCa |
sY//////YSpcs scpCY//Pp | Welcome to Scapy
ayp ayyyyyyySCP//Pp syY//C | Version 2.6.1
AYAsAYYYYYYYY///Ps cY//S |
pCCCCY//p cSSps y//Y | https://github.com/secdev/scapy
SPPPP///a pP///AC//Y |
A//A cyP////C | Have fun!
p///Ac sC///a |
P////YCpc A//A | To craft a packet, you have to be a
scccccp///pSP///p p//Y | packet, and learn how to swim in
sY/////////y caa S//P | the wires and in the waves.
cayCyayP//Ya pY/Ya | -- Jean-Claude Van Damme
sY/PsY////YCc aC//Yp |
sc sccaCY//PCypaapyCP//YSs
spCPY//////YPSps
ccaacs
>>> exit() <<exit() もしくは Ctrl+D で対話モードを終了します。
[root@rocky8-client scapy]#
対話モードの起動(ヘッダー非表示)
[root@rocky8-client scapy]# scapy -H
WARNING: No alternative Python interpreters found ! Using standard Python shell instead.
Welcome to Scapy (2.6.1)
>>> exit()
[root@rocky8-client scapy]#
パケット一覧の表示(ls)
>>> ls()
AD_AND_OR : None
AD_KDCIssued : None
AH : AH
- 省略 -
rtmsghdr : None
rtmsghdrs : None
twr_p_t : None
TIP: You may use explore() to navigate through all layers using a clear GUI
>>>
パケット詳細の表示(ls)
>>> # IP パケット作成
>>> a = IP(src='172.18.1.20',dst='172.18.1.10')
>>>
>>> # IP パケットの詳細を表示(右の値はおそらくデフォルト)
>>> ls(a, verbose=True)
version : BitField (4 bits) = 4 ('4')
ihl : BitField (4 bits) = None ('None')
tos : XByteField = 0 ('0')
len : ShortField = None ('None')
id : ShortField = 1 ('1')
flags : FlagsField = <Flag 0 ()> ('<Flag 0 ()>')
MF, DF, evil
frag : BitField (13 bits) = 0 ('0')
ttl : ByteField = 64 ('64')
proto : ByteEnumField = 0 ('0')
chksum : XShortField = None ('None')
src : SourceIPField = '172.18.1.20' ('None')
dst : DestIPField = '172.18.1.10' ('None')
options : PacketListField = [] ('[]')
>>>
オブジェクトタイプの確認(type)
>>> # IP パケット作成
>>> a = IP()
>>> # a に登録されているオブジェクトのタイプを確認
>>> type(a)
<class 'scapy.layers.inet.IP'>
>>>
>>> # IP/TCP パケット作成
>>> b = IP()/TCP()
>>>
>>> # IP ペイロードのオブジェクトタイプを確認
>>> type(b); type(b.payload)
<class 'scapy.layers.inet.IP'>
<class 'scapy.layers.inet.TCP'>
>>>
関数一覧の表示(lsc)
>>> lsc()
IPID_count : Identify IP id values classes in a list of packets
arp_mitm : ARP MitM: poison 2 target's ARP cache
arpcachepoison : Poison targets' ARP cache
- 省略 -
wrerf : Write a list of packets to a ERF file
wrpcap : Write a list of packets to a pcap file
wrpcapng : Write a list of packets to a pcapng file
>>>
ヘルプの表示(help)
>>> # IP パケットのヘルプ
>>> help(IP)
Help on class IP in module scapy.layers.inet:
class IP(scapy.packet.Packet, IPTools)
| IP(_pkt, /, *, version=4, ihl=None, tos=0, len=None, id=1, flags=<Flag 0 ()>, frag=0, ttl=64, proto=0, chksum=None, src=None, dst=None, options=[])
|
| Method resolution order:
| IP
| scapy.packet.Packet
| scapy.base_classes.BasePacket
| scapy.base_classes.Gen
| typing.Generic
| scapy.base_classes._CanvasDumpExtended
| IPTools
| builtins.object
|
- 省略 -
| ----------------------------------------------------------------------
| Methods inherited from IPTools:
|
| hops(self)
|
| ottl(self)
|
| whois(self)
| whois the source and print the output
(END)
>>>
>>> # hexdump 関数のヘルプ
>>> help(hexdump)
Help on function hexdump in module scapy.utils:
hexdump(p, dump=False)
Build a tcpdump like hexadecimal view
:param p: a Packet
:param dump: define if the result must be printed or returned in a variable
:return: a String only when dump=True
(END)
>>>
インフォーメーションの抑制(verbose)
※下記例は ICMP(ping)送信です。
>>> # verboseの指定がない場合(verboseのデフォルトはTrue)
>>> r, ur = sr(IP(dst="10.0.2.40/32")/ICMP(), timeout=3)
Begin emission
Finished sending 1 packets
Received 3 packets, got 1 answers, remaining 0 packets
>>>
>>> # verbose=Falseの場合
>>> r, ur = sr(IP(dst="10.0.2.40/32")/ICMP(), timeout=3, verbose=False)
>>>
件数の確認(len)
※下記例は ICMP(ping)送信です。送信先は dst に指定したサブネットの全 IP アドレスが対象です。例えば、10.0.2.16/30 を指定した場合、10.0.2.16, 10.0.2.17, 10.0.2.18, 10.0.2.19 に送信されます。
>>> # r は応答のあったクエリ、ur は応答のなかったクエリ。
>>> r, ur = sr(IP(dst="10.0.2.16/30")/ICMP(), timeout=3)
Begin emission
WARNING: MAC address to reach destination not found. Using broadcast.
Finished sending 4 packets
Received 8 packets, got 3 answers, remaining 1 packets
>>>
>>> # 応答ありのパケット
>>> r
<Results: TCP:0 UDP:0 ICMP:3 Other:0>
>>>
>>> # 応答なしのパケット
>>> ur
<Unanswered: TCP:0 UDP:0 ICMP:1 Other:0>
>>>
>>> # それぞれの件数を len() で表示
>>> print(f"r : {len(r)}, ur : {len(ur)}")
r : 3, ur : 1
>>>
ペイロードの確認(payload)
>>> # IP/UDP/DNS パケット作成
>>> a = IP()/UDP()/DNS(); a
<IP frag=0 proto=udp |<UDP sport=domain |<DNS qd=[<DNSQR |>] |>>>
>>>
>>> # IP のペイロードを確認
>>> a.payload
<UDP sport=domain |<DNS qd=[<DNSQR |>] |>>
>>>
>>> # UDP のペイロードを確認
>>> a[UDP].payload
<DNS qd=[<DNSQR |>] |>
>>>
>>> # a が IP クラスであるかを確認
>>> isinstance(a, IP)
True
>>>
>>> # a が IP, UDP, DNS のいずれかのクラスであるかを確認
>>> isinstance(a, (IP,UDP,DNS))
True
>>>
>>> # a が UDP, DNS のいずれかのクラスであるかを確認
>>> isinstance(a, (UDP,DNS))
False
>>>
>>> # IP ペイロードが UDP クラスであるかを確認
>>> isinstance(a.payload, UDP)
True
>>>
>>> # IP ペイロードが DNS クラスであるかを確認
>>> isinstance(a.payload, DNS)
False
>>>
>>> # UDP ペイロードが DNS クラスであるかを確認
>>> isinstance(a[UDP].payload, DNS)
True
>>>
パケットの形式変換
パケットとバイナリの相互変換(raw)
>>> # IP/UDP/DNS パケットを作成
>>> a = IP()/UDP()/DNS(); a
<IP frag=0 proto=udp |<UDP sport=domain |<DNS qd=[<DNSQR |>] |>>>
>>>
>>> # パケットをバイナリで出力
>>> raw(a)
b'E\x00\x00=\x00\x01\x00\x00@\x11|\xad\x7f\x00\x00\x01\x7f\x00\x00\x01\x005\x005\x00)\xb6\xd3\x00\x00\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x07example\x03com\x00\x00\x01\x00\x01'
>>>
>>> # bytes()でも同じ内容が出力される(raw() との違いは不明)
>>> bytes(a)
b'E\x00\x00=\x00\x01\x00\x00@\x11|\xad\x7f\x00\x00\x01\x7f\x00\x00\x01\x005\x005\x00)\xb6\xd3\x00\x00\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x07example\x03com\x00\x00\x01\x00\x01'
>>>
>>> # 対話モードの場合、直前の出力結果はアンダースコア”_”で受け取れる(スクリプト内では使えなかった)
>>> # bytes(a) の出力結果を受け取りパケットオブジェクトとして再構成
>>> IP(_)
<IP version=4 ihl=5 tos=0x0 len=61 id=1 flags= frag=0 ttl=64 proto=udp chksum=0x7cad src=127.0.0.1 dst=127.0.0.1 |<UDP sport=domain dport=domain len=41 chksum=0xb6d3 |<DNS id=0 qr=0 opcode=QUERY aa=0 tc=0 rd=1 ra=0 z=0 ad=0 cd=0 rcode=ok qdcount=1 ancount=0 nscount=0 arcount=0 qd=[<DNSQR qname=b'www.example.com.' qtype=A unicastresponse=0 qclass=IN |>] |>>>
>>>
>>> # 同じことをアンダースコア”_”を使用せずに実施
>>> b = raw(a); IP(b)
<IP version=4 ihl=5 tos=0x0 len=61 id=1 flags= frag=0 ttl=64 proto=udp chksum=0x7cad src=127.0.0.1 dst=127.0.0.1 |<UDP sport=domain dport=domain len=41 chksum=0xb6d3 |<DNS id=0 qr=0 opcode=QUERY aa=0 tc=0 rd=1 ra=0 z=0 ad=0 cd=0 rcode=ok qdcount=1 ancount=0 nscount=0 arcount=0 qd=[<DNSQR qname=b'www.example.com.' qtype=A unicastresponse=0 qclass=IN |>] |>>>
>>>
ちなみに、表示されている各パケットのフィールドは、ユーザ設定フィールド(*1)と上位レイヤにオーバーロードされたフィールド(*2)です。その他(デフォルト値等)のフィールドは表示されません。
上記例で多くのフィールドが表示されているのは、バイナリからパケットオブジェクトに再構成されたときに全フィールドがユーザ設定フィールドとして設定されたためと思われます。
*1 利用者が意図的に設定したフィールド(宛先の IP アドレス等)
*2 たとえば UDP()/DNS() の場合、UDP の上位レイヤに DNS が指定されているため、UDP() に sport=domain が自動的に設定される。
hide_defaults() を使用すればデフォルトと同じ値を持つフィールドを削除できます。ただし、デフォルトと同じ値なのに削除されないフィールドもあるようです。
>>> # バイナリをパケットオブジェクトとして再構成し、c に登録
>>> c = IP(b); c
<IP version=4 ihl=5 tos=0x0 len=61 id=1 flags= frag=0 ttl=64 proto=udp chksum=0x7cad src=127.0.0.1 dst=127.0.0.1 |<UDP sport=domain dport=domain len=41 chksum=0xb6d3 |<DNS id=0 qr=0 opcode=QUERY aa=0 tc=0 rd=1 ra=0 z=0 ad=0 cd=0 rcode=ok qdcount=1 ancount=0 nscount=0 arcount=0 qd=[<DNSQR qname=b'www.example.com.' qtype=A unicastresponse=0 qclass=IN |>] |
>>>
>>> # デフォルトと同じ値を持つフィールドを削除
>>> c.hide_defaults(); c
<IP ihl=5 len=61 frag=0 proto=udp chksum=0x7cad src=127.0.0.1 dst=127.0.0.1 |<UDP sport=domain len=41 chksum=0xb6d3 |<DNS qdcount=1 ancount=0 nscount=0 arcount=0 |>>>
>>>
パケットと 16 進の相互変換(hexdump/import_hexcap)
>>> # Ether/IP パケットを作成
>>> a = Ether()/IP(); a
<Ether type=IPv4 |<IP |>>
>>>
>>> # 16進で出力
>>> hexdump(a)
0000 FF FF FF FF FF FF 00 00 00 00 00 00 08 00 45 00 ..............E.
0010 00 14 00 01 00 00 40 00 7C E7 7F 00 00 01 7F 00 ......@.|.......
0020 00 01 ..
>>>
>>> # 16 進をパケットオブジェクトに再構成
>>> # わかりにくいが、import_hexcap()で入力待ちになるので、上記出力結果を丸ごとコピペして2回改行
>>> b = Ether(import_hexcap())
0000 FF FF FF FF FF FF 00 00 00 00 00 00 08 00 45 00 ..............E. <<ここから 3 行は上記出力結果を手動でコピペしたもの
0010 00 14 00 01 00 00 40 00 7C E7 7F 00 00 01 7F 00 ......@.|.......
0020 00 01
>>> # 結果の確認
>>> b
<Ether dst=ff:ff:ff:ff:ff:ff src=00:00:00:00:00:00 type=IPv4 |<IP version=4 ihl=5 tos=0x0 len=20 id=1 flags= frag=0 ttl=64 proto=hopopt chksum=0x7ce7 src=127.0.0.1 dst=127.0.0.1 |>>
>>>
>>> # デフォルトと同じ値を持つフィールドを削除
>>> b.hide_defaults(); b
<Ether dst=ff:ff:ff:ff:ff:ff src=00:00:00:00:00:00 type=IPv4 |<IP ihl=5 len=20 chksum=0x7ce7 src=127.0.0.1 dst=127.0.0.1 |>>
>>>
パケットと Base64 の相互変換(export_object/import_object)
>>> # IP/UDP/DNS パケットを作成
>>> a = IP()/UDP()/DNS(); a
<IP frag=0 proto=udp |<UDP sport=domain |<DNS qd=[<DNSQR |>] |>>>
>>>
>>> # Base64 で出力
>>> export_object(a)
eNprYEouTk4sqNTLSaxMLSrWy8xLLeHyDOAqZEiOT85PSU0u5krNAzG4ChkjHBgYGFwZGGwZGBkYHARrDq2tZ2BgBGEGUyDUPLTt8GQQB4RAgLm8vJw9tSIxtyAnlTk5PxckwVjIFMEGlMtJLMnMMyxkbitkCSpkbS1kCypk13B3vCXXWiphuNkPBEoKOZL0AHotKfg=
>>>
>>> # Base64 をパケットオブジェクトに再構成
>>> # わかりにくいが、import_object()で入力待ちになるので、上記出力結果を丸ごとコピペして改行 + Ctrl-D
>>> b = import_object()
eNprYEouTk4sqNTLSaxMLSrWy8xLLeHyDOAqZEiOT85PSU0u5krNAzG4ChkjHBgYGFwZGGwZGBkYHARrDq2tZ2BgBGEGUyDUPLTt8GQQB4RAgLm8vJw9tSIxtyAnlTk5PxckwVjIFMEGlMtJLMnMMyxkbitkCSpkbS1kCypk13B3vCXXWiphuNkPBEoKOZL0AHotKfg=
>>>
>>> # 結果の確認
>>> b
<IP version=4 ihl=5 tos=0x0 len=61 id=1 flags= frag=0 ttl=64 proto=udp chksum=0x7cad src=127.0.0.1 dst=127.0.0.1 |<UDP sport=domain dport=domain len=41 chksum=0xb6d3 |<DNS id=0 qr=0 opcode=QUERY aa=0 tc=0 rd=1 ra=0 z=0 ad=0 cd=0 rcode=ok qdcount=1 ancount=0 nscount=0 arcount=0 qd=[<DNSQR qname=b'www.example.com.' qtype=A unicastresponse=0 qclass=IN |>] |>>>
>>>
>>> # デフォルトと同じ値を持つフィールドを削除
>>> b.hide_defaults(); b
<IP ihl=5 len=61 frag=0 proto=udp chksum=0x7cad src=127.0.0.1 dst=127.0.0.1 |<UDP sport=domain len=41 chksum=0xb6d3 |<DNS qdcount=1 ancount=0 nscount=0 arcount=0 |>>>
>>>
PCAP ファイルの操作
PCAP ファイルの読み込み(rdpcap)
tshark で PCAP ファイルを出力します。
[root@rocky8-client scapy]# # pcap に出力するための DNS クエリ
[root@rocky8-client scapy]# dig @cachedns www.test.co.jp A +short
1.2.3.4
[root@rocky8-client scapy]#
[root@rocky8-client scapy]# # dig 実行中のパケットキャプチャ
[root@rocky8-client scapy]# tshark -i 1 -f "port 53" -w /tmp/udpdns.pcap
Running as user "root" and group "root". This could be dangerous.
Capturing on 'enp0s3'
2 ^C
[root@rocky8-client scapy]# tshark -t a -r /tmp/udpdns.pcap
Running as user "root" and group "root". This could be dangerous.
1 16:48:50.140034293 10.0.2.44 → 10.0.2.40 DNS 97 Standard query 0x5f86 A www.test.co.jp OPT
2 16:48:50.150866751 10.0.2.40 → 10.0.2.44 DNS 202 Standard query response 0x5f86 A www.test.co.jp A 1.2.3.4 NS ns1.test.jp NS ns2.test.jp A 10.0.2.29 A 10.0.2.28 OPT
[root@rocky8-client scapy]#
tshark で出力した PCAP ファイルを scapy で読み込みます。
>>> # pcap ファイルを rdpcap() で読み込む
>>> a = rdpcap("/tmp/udpdns.pcap")
>>>
>>> # 読み込んだ内容を確認
>>> a; a.nsummary(); a[0].summary(); a[0][IP].summary(); a[0][UDP].summary(); a[0][DNS].summary()
<udpdns.pcap: TCP:0 UDP:2 ICMP:0 Other:0>
0000 Ether / IP / UDP / DNS Qry b'www.test.co.jp.'
0001 Ether / IP / UDP / DNS Ans 1.2.3.4
"Ether / IP / UDP / DNS Qry b'www.test.co.jp.'"
"IP / UDP / DNS Qry b'www.test.co.jp.'"
"UDP / DNS Qry b'www.test.co.jp.'"
"DNS Qry b'www.test.co.jp.'"
>>>
PCAP ファイルへの書き込み(wrpcap)
scapy で PCAP ファイルを出力します。
>>> # www.test.co.jp/A の 名前解決を行った際のレスポンスパケットを r に格納
>>> pkt = IP(dst="10.0.2.40")/UDP()/DNS(qd=DNSQR(qname="www.test.co.jp", qtype="A", qclass="IN"))
>>> r = sr1(pkt, verbose=0)
>>>
>>> # レスポンスのパケットを確認
>>> r.summary()
'IP / UDP / DNS Ans 1.2.3.4'
>>>
>>> # レスポンスのパケットを PCAP ファイルへ書き込み
>>> wrpcap("/tmp/udpdns.pcap", r)
>>>
scapy で出力した PCAP ファイルを tshark で読み込みます。
[root@rocky8-client scapy]# # PCAP ファイルの内容を tshark(wireshark)で確認
[root@rocky8-client scapy]# tshark -t a -r /tmp/udpdns.pcap
Running as user "root" and group "root". This could be dangerous.
1 19:43:39.219748 10.0.2.40 → 10.0.2.44 DNS 149 Standard query response 0x0000 A www.test.co.jp A 1.2.3.4 NS ns1.test.jp NS ns2.test.jp A 10.0.2.29 A 10.0.2.28
[root@rocky8-client scapy]#
パケットセットの作成と展開
>>> # 以下はサブネットを指定した例。サブネット内の個別のアドレスに展開される
>>> a = IP(dst="10.0.2.16/30"); a; [p for p in a]
<IP dst=Net("10.0.2.16/30") |>
[<IP dst=10.0.2.16 |>, <IP dst=10.0.2.17 |>, <IP dst=10.0.2.18 |>, <IP dst=10.0.2.19 |>]
>>>
>>> # 以下は ttl を指定した例
>>> b = IP(ttl=[1,3,(5,7)]); b; [p for p in b]
<IP ttl=[1, 3, (5, 7)] |>
[<IP ttl=1 |>, <IP ttl=3 |>, <IP ttl=5 |>, <IP ttl=6 |>, <IP ttl=7 |>]
>>>
>>> # 以下はポート番号を指定した例
>>> c = TCP(dport=[80,443]); c; [p for p in c]
<TCP dport=['http', 'https'] |>
[<TCP dport=http |>, <TCP dport=https |>]
>>>
>>> # 組み合わせると総当たりのパケットセットになる
>>> d = IP(ttl=[1,2], dst="10.0.2.16/31"); [p for p in d]
[<IP ttl=1 dst=10.0.2.16 |>, <IP ttl=2 dst=10.0.2.16 |>, <IP ttl=1 dst=10.0.2.17 |>, <IP ttl=2 dst=10.0.2.17 |>]
>>> e = IP(dst="10.0.2.16/31"); f = TCP(dport=[80,443]); [p for p in e/f]
[<IP frag=0 proto=tcp dst=10.0.2.16 |<TCP dport=http |>>, <IP frag=0 proto=tcp dst=10.0.2.16 |<TCP dport=https |>>, <IP frag=0 proto=tcp dst=10.0.2.17 |<TCP dport=http |>>, <IP frag=0 proto=tcp dst=10.0.2.17 |<TCP dport=https |>>]
>>>
送信の際はパケットセットをそのまま指定すれば、内部で展開し送信してくれます。
>>> g = IP(dst="10.0.2.16/30"); [p for p in g]
[<IP dst=10.0.2.16 |>, <IP dst=10.0.2.17 |>, <IP dst=10.0.2.18 |>, <IP dst=10.0.2.19 |>]
>>>
>>> # ICMP(ping)送信
>>> r, ur = sr(g/ICMP(), timeout=3)
Begin emission
...*...*...*.........WARNING: MAC address to reach destination not found. Using broadcast.
.
Finished sending 4 packets
.............
Received 35 packets, got 3 answers, remaining 1 packets
>>>
>>> # r はレスポンスがあった通信
>>> r.nsummary()
0000 IP / ICMP 10.0.2.44 > 10.0.2.16 echo-request 0 ==> IP / ICMP 10.0.2.16 > 10.0.2.44 echo-reply 0 / Padding
0001 IP / ICMP 10.0.2.44 > 10.0.2.17 echo-request 0 ==> IP / ICMP 10.0.2.17 > 10.0.2.44 echo-reply 0 / Padding
0002 IP / ICMP 10.0.2.44 > 10.0.2.18 echo-request 0 ==> IP / ICMP 10.0.2.18 > 10.0.2.44 echo-reply 0 / Padding
>>>
>>> # ur はレスポンスがなかった通信
>>> ur.nsummary()
0000 IP / ICMP 10.0.2.44 > 10.0.2.19 echo-request 0
>>>
PacketList オブジェクトに展開すると個別にパケットを操作できます。
>>> # パケットセットを PacketList オブジェクトに展開し、 変数(h)に格納
>>> h = PacketList([p for p in IP(dst="10.0.2.16/31")/TCP(dport=[80,443])])
>>>
>>> # 内容を確認(※ 10.0.2.44:ftp_data は送信側の情報が未指定だったため、自動的に補完されたもの)
>>> h; h.nsummary()
<PacketList: TCP:4 UDP:0 ICMP:0 Other:0>
0000 IP / TCP 10.0.2.44:ftp_data > 10.0.2.16:http S
0001 IP / TCP 10.0.2.44:ftp_data > 10.0.2.16:https S
0002 IP / TCP 10.0.2.44:ftp_data > 10.0.2.17:http S
0003 IP / TCP 10.0.2.44:ftp_data > 10.0.2.17:https S
>>>
>>> h[0]
<IP frag=0 proto=tcp dst=10.0.2.16 |<TCP dport=http |>>
>>>
>>> h[0][TCP]
<TCP dport=http |>
>>>
変数に展開された複数のパケットから、インデックスで指定したパケットを使用します。
>>> i = PacketList([p for p in IP(dst="10.0.2.16/30")])
>>> i; i.nsummary()
<PacketList: TCP:0 UDP:0 ICMP:0 Other:4>
0000 10.0.2.44 > 10.0.2.16 hopopt
0001 10.0.2.44 > 10.0.2.17 hopopt
0002 10.0.2.44 > 10.0.2.18 hopopt
0003 10.0.2.44 > 10.0.2.19 hopopt
>>>
>>> # ICMP(ping)送信
>>> r, ur = sr(i[0]/ICMP(), timeout=3)
Begin emission
...*.
Finished sending 1 packets
Received 5 packets, got 1 answers, remaining 0 packets
>>>
>>> # r はレスポンスがあった通信
>>> r.nsummary()
0000 IP / ICMP 10.0.2.44 > 10.0.2.16 echo-request 0 ==> IP / ICMP 10.0.2.16 > 10.0.2.44 echo-reply 0 / Padding
>>>
>>> # ur はレスポンスがなかった通信
>>> ur.nsummary()
>>>
パケットの送信と受信
最初に検証で使用する IP パケットを準備します。
>>> ips = IP(dst="10.0.2.28/30"); ips; [p for p in ips]
<IP dst=Net("10.0.2.28/30") |>
[<IP dst=10.0.2.28 |>, <IP dst=10.0.2.29 |>, <IP dst=10.0.2.30 |>, <IP dst=10.0.2.31 |>]
>>>
Layer3 でリクエストを送信しレスポンスを 1 件のみ受信する(sr1)
request は 4 件送信していますが、sr1 で受信するのは最初の reply 1 件のみです。
※ timeout を指定しないと、レスポンス無しの場合、プロンプト(>>>)が返ってきません(Ctrl+C で中断できます)
>>> # sr1 で ICMP(ping)を送信
>>> r = sr1(ips/ICMP(), timeout=3)
Begin emission
WARNING: MAC address to reach destination not found. Using broadcast.
WARNING: MAC address to reach destination not found. Using broadcast.
Finished sending 4 packets
Received 11 packets, got 2 answers, remaining 2 packets
>>>
>>> # sr1 から返された変数のタイプを確認
>>> type(r)
<class 'scapy.layers.inet.IP'>
>>>
>>> # 受信した内容を確認
>>> r
<IP version=4 ihl=5 tos=0x0 len=28 id=2477 flags= frag=0 ttl=64 proto=icmp chksum=0x58ed src=10.0.2.28 dst=10.0.2.44 |<ICMP type=echo-reply code=0 chksum=0xffff id=0x0 seq=0x0 unused=b'' |<Padding load=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |>>>
>>>
>>> r.summary()
'IP / ICMP 10.0.2.28 > 10.0.2.44 echo-reply 0 / Padding'
>>>
以下は sr1 実行中のパケットキャプチャです。request は 4 件ですが、10.0.2.30, 10.0.2.31 は存在しないため reply は 2 件です。
[root@rocky8-client scapy]# tshark -i 1 -f "icmp" -w /tmp/test.pcap
Running as user "root" and group "root". This could be dangerous.
Capturing on 'enp0s3'
6 ^C
[root@rocky8-client scapy]# tshark -t a -r /tmp/test.pcap
Running as user "root" and group "root". This could be dangerous.
1 17:06:11.523071158 10.0.2.44 → 10.0.2.28 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
2 17:06:11.523965666 10.0.2.28 → 10.0.2.44 ICMP 60 Echo (ping) reply id=0x0000, seq=0/0, ttl=64 (request in 1)
3 17:06:11.527770850 10.0.2.44 → 10.0.2.29 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
4 17:06:11.528439788 10.0.2.29 → 10.0.2.44 ICMP 60 Echo (ping) reply id=0x0000, seq=0/0, ttl=64 (request in 3)
5 17:06:13.534363982 10.0.2.44 → 10.0.2.30 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
6 17:06:15.551407939 10.0.2.44 → 10.0.2.31 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
[root@rocky8-client scapy]#
Layer3 でリクエストを送信しレスポンスを複数件受信する(sr)
request を 4 件送信し、timeout までに返された reply を全件受信します。
※ timeout を指定しないと、レスポンス無しの場合、プロンプト(>>>)が返ってきません(Ctrl+C で中断できます)
>>> # sr で ICMP(ping)を送信
>>> r, ur = sr(ips/ICMP(), timeout=3)
Begin emission
WARNING: MAC address to reach destination not found. Using broadcast.
WARNING: MAC address to reach destination not found. Using broadcast.
Finished sending 4 packets
Received 11 packets, got 2 answers, remaining 2 packets
>>>
>>> # sr から返された変数のタイプを確認(sr からはリスト形式で返される)
>>> type(r); type(ur)
<class 'scapy.plist.SndRcvList'>
<class 'scapy.plist.PacketList'>
>>>
>>> # r は reply があったパケット
>>> r; r.summary()
<Results: TCP:0 UDP:0 ICMP:2 Other:0>
IP / ICMP 10.0.2.44 > 10.0.2.28 echo-request 0 ==> IP / ICMP 10.0.2.28 > 10.0.2.44 echo-reply 0 / Padding
IP / ICMP 10.0.2.44 > 10.0.2.29 echo-request 0 ==> IP / ICMP 10.0.2.29 > 10.0.2.44 echo-reply 0 / Padding
>>>
>>> # 個別で確認(1 番目の request と reply)
>>> r[0][0].summary(); r[0][1].summary()
'IP / ICMP 10.0.2.44 > 10.0.2.28 echo-request 0'
'IP / ICMP 10.0.2.28 > 10.0.2.44 echo-reply 0 / Padding'
>>>
>>> # 個別で確認(同じく 2 番目)
>>> r[1][0].summary(); r[1][1].summary()
'IP / ICMP 10.0.2.44 > 10.0.2.29 echo-request 0'
'IP / ICMP 10.0.2.29 > 10.0.2.44 echo-reply 0 / Padding'
>>>
>>> # ur は reply がなかったパケット
>>> ur; ur.summary()
<Unanswered: TCP:0 UDP:0 ICMP:2 Other:0>
IP / ICMP 10.0.2.44 > 10.0.2.30 echo-request 0
IP / ICMP 10.0.2.44 > 10.0.2.31 echo-request 0
>>>
以下は sr 実行中のパケットキャプチャです。request は 4 件ですが、10.0.2.30, 10.0.2.31 は存在しないため reply は 2 件です。
[root@rocky8-client scapy]# tshark -i 1 -f "icmp" -w /tmp/test.pcap
Running as user "root" and group "root". This could be dangerous.
Capturing on 'enp0s3'
6 ^C
[root@rocky8-client scapy]# tshark -t a -r /tmp/test.pcap
Running as user "root" and group "root". This could be dangerous.
1 17:11:10.435581712 10.0.2.44 → 10.0.2.28 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
2 17:11:10.436180263 10.0.2.28 → 10.0.2.44 ICMP 60 Echo (ping) reply id=0x0000, seq=0/0, ttl=64 (request in 1)
3 17:11:10.440069536 10.0.2.44 → 10.0.2.29 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
4 17:11:10.440465483 10.0.2.29 → 10.0.2.44 ICMP 60 Echo (ping) reply id=0x0000, seq=0/0, ttl=64 (request in 3)
5 17:11:12.447208321 10.0.2.44 → 10.0.2.30 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
6 17:11:14.456175819 10.0.2.44 → 10.0.2.31 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
[root@rocky8-client scapy]#
Layer2 でリクエストを送信しレスポンスを 1 件のみ受信する(srp1)
request は 4 件送信していますが、srp1 で受信するのは最初の reply 1 件のみです。
>>> # srp1 で ICMP(ping)を送信
>>> r = srp1(Ether()/ips/ICMP(), timeout=3)
Begin emission
WARNING: MAC address to reach destination not found. Using broadcast.
WARNING: MAC address to reach destination not found. Using broadcast.
Finished sending 4 packets
Received 11 packets, got 2 answers, remaining 2 packets
>>>
>>> # srp1 から返された変数のタイプを確認
>>> type(r)
<class 'scapy.layers.l2.Ether'>
>>>
>>> # 受信した内容を確認
>>> r
<Ether dst=08:00:27:7e:aa:05 src=08:00:27:fb:79:c9 type=IPv4 |<IP version=4 ihl=5 tos=0x0 len=28 id=1840 flags= frag=0 ttl=64 proto=icmp chksum=0x5b6a src=10.0.2.28 dst=10.0.2.44 |<ICMP type=echo-reply code=0 chksum=0xffff id=0x0 seq=0x0 unused=b'' |<Padding load=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |>>>>
>>>
>>> r.summary()
'Ether / IP / ICMP 10.0.2.28 > 10.0.2.44 echo-reply 0 / Padding'
>>>
以下は srp1 実行中のパケットキャプチャです。request は 4 件ですが、10.0.2.30, 10.0.2.31 は存在しないため reply は 2 件です。
[root@rocky8-client scapy]# tshark -i 1 -f "icmp" -w /tmp/test.pcap
Running as user "root" and group "root". This could be dangerous.
Capturing on 'enp0s3'
6 ^C
[root@rocky8-client scapy]# tshark -t a -r /tmp/test.pcap
Running as user "root" and group "root". This could be dangerous.
1 17:15:46.906062188 10.0.2.44 → 10.0.2.28 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
2 17:15:46.907505583 10.0.2.28 → 10.0.2.44 ICMP 60 Echo (ping) reply id=0x0000, seq=0/0, ttl=64 (request in 1)
3 17:15:46.914361440 10.0.2.44 → 10.0.2.29 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
4 17:15:46.914752983 10.0.2.29 → 10.0.2.44 ICMP 60 Echo (ping) reply id=0x0000, seq=0/0, ttl=64 (request in 3)
5 17:15:48.919974356 10.0.2.44 → 10.0.2.30 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
6 17:15:50.941431529 10.0.2.44 → 10.0.2.31 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
[root@rocky8-client scapy]#
Layer2 でリクエストを送信しレスポンスを複数件受信する(srp)
request を 4 件送信し、timeout までに返された reply を全件受信します。
>>> # srp で ICMP(ping)を送信
>>> r, ur = srp(Ether()/ips/ICMP(), timeout=3)
Begin emission
WARNING: MAC address to reach destination not found. Using broadcast.
WARNING: MAC address to reach destination not found. Using broadcast.
Finished sending 4 packets
Received 11 packets, got 2 answers, remaining 2 packets
>>>
>>> # srp から返された変数のタイプを確認(srp からはリスト形式で返される)
>>> type(r); type(ur)
<class 'scapy.plist.SndRcvList'>
<class 'scapy.plist.PacketList'>
>>>
>>> # r は reply があったパケット
>>> r; r.summary()
<Results: TCP:0 UDP:0 ICMP:2 Other:0>
Ether / IP / ICMP 10.0.2.44 > 10.0.2.28 echo-request 0 ==> Ether / IP / ICMP 10.0.2.28 > 10.0.2.44 echo-reply 0 / Padding
Ether / IP / ICMP 10.0.2.44 > 10.0.2.29 echo-request 0 ==> Ether / IP / ICMP 10.0.2.29 > 10.0.2.44 echo-reply 0 / Padding
>>>
>>> # 個別で確認(1 番目の request と reply)
>>> r[0][0].summary(); r[0][1].summary()
'Ether / IP / ICMP 10.0.2.44 > 10.0.2.28 echo-request 0'
'Ether / IP / ICMP 10.0.2.28 > 10.0.2.44 echo-reply 0 / Padding'
>>>
>>> # 個別で確認(同じく 2 番目)
>>> r[1][0].summary(); r[1][1].summary()
'Ether / IP / ICMP 10.0.2.44 > 10.0.2.29 echo-request 0'
'Ether / IP / ICMP 10.0.2.29 > 10.0.2.44 echo-reply 0 / Padding'
>>>
>>> # ur は reply がなかったパケット
>>> ur; ur.summary()
<Unanswered: TCP:0 UDP:0 ICMP:2 Other:0>
Ether / IP / ICMP 10.0.2.44 > 10.0.2.30 echo-request 0
Ether / IP / ICMP 10.0.2.44 > 10.0.2.31 echo-request 0
>>>
以下は srp1 実行中のパケットキャプチャです。request は 4 件ですが、10.0.2.30, 10.0.2.31 は存在しないため reply は 2 件です。
[root@rocky8-client scapy]# tshark -i 1 -f "icmp" -w /tmp/test.pcap
Running as user "root" and group "root". This could be dangerous.
Capturing on 'enp0s3'
6 ^C
[root@rocky8-client scapy]# tshark -t a -r /tmp/test.pcap
Running as user "root" and group "root". This could be dangerous.
1 17:18:55.351949606 10.0.2.44 → 10.0.2.28 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
2 17:18:55.352831173 10.0.2.28 → 10.0.2.44 ICMP 60 Echo (ping) reply id=0x0000, seq=0/0, ttl=64 (request in 1)
3 17:18:55.356194054 10.0.2.44 → 10.0.2.29 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
4 17:18:55.357133102 10.0.2.29 → 10.0.2.44 ICMP 60 Echo (ping) reply id=0x0000, seq=0/0, ttl=64 (request in 3)
5 17:18:57.361957169 10.0.2.44 → 10.0.2.30 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
6 17:18:59.377825198 10.0.2.44 → 10.0.2.31 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
[root@rocky8-client scapy]#
通信結果を使用し再送する
>>> # 初回の送受信(ICMP)
>>> r, ur = sr(ips/ICMP(), timeout=1, verbose=False)
WARNING: MAC address to reach destination not found. Using broadcast.
WARNING: MAC address to reach destination not found. Using broadcast.
>>>
>>> # reply があったパケット
>>> r.summary()
IP / ICMP 10.0.2.44 > 10.0.2.28 echo-request 0 ==> IP / ICMP 10.0.2.28 > 10.0.2.44 echo-reply 0 / Padding
IP / ICMP 10.0.2.44 > 10.0.2.29 echo-request 0 ==> IP / ICMP 10.0.2.29 > 10.0.2.44 echo-reply 0 / Padding
>>>
>>> # reply がなかったパケット
>>> ur.summary()
IP / ICMP 10.0.2.44 > 10.0.2.30 echo-request 0
IP / ICMP 10.0.2.44 > 10.0.2.31 echo-request 0
>>>
reply がなかったパケット(ur)で再送します。
>>> r1, ur1 = sr(ur, timeout=1, verbose=False)
WARNING: MAC address to reach destination not found. Using broadcast.
WARNING: MAC address to reach destination not found. Using broadcast.
>>> r1.summary()
>>> ur1.summary()
IP / ICMP 10.0.2.44 > 10.0.2.30 echo-request 0
IP / ICMP 10.0.2.44 > 10.0.2.31 echo-request 0
>>>
以下は sr(ur, …) 実行中のパケットキャプチャです。10.0.2.30, 10.0.2.31 は存在しないため、reply はありません。
[root@rocky8-client scapy]# tshark -i 1 -f "icmp" -w /tmp/test.pcap
Running as user "root" and group "root". This could be dangerous.
Capturing on 'enp0s3'
2 ^C
[root@rocky8-client scapy]# tshark -t a -r /tmp/test.pcap
Running as user "root" and group "root". This could be dangerous.
1 15:11:17.957502565 10.0.2.44 → 10.0.2.30 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
2 15:11:19.975592166 10.0.2.44 → 10.0.2.31 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
[root@rocky8-client scapy]#
必要性はわかりませんが、reply があったパケット(r)の再送についても記載しておきます。
# 個別パケットで再送
>>> r[0][0]
<IP frag=0 proto=icmp dst=10.0.2.28 |<ICMP |>>
>>> r2, ur2 = sr(r[0][0], timeout=1, verbose=False)
>>> r2.summary()
IP / ICMP 10.0.2.44 > 10.0.2.28 echo-request 0 ==> IP / ICMP 10.0.2.28 > 10.0.2.44 echo-reply 0 / Padding
>>> ur2.summary()
>>>
以下は sr(r[0][0], …) 実行中のパケットキャプチャです。
[root@rocky8-client scapy]# tshark -i 1 -f "icmp" -w /tmp/test.pcap
Running as user "root" and group "root". This could be dangerous.
Capturing on 'enp0s3'
2 ^C
[root@rocky8-client scapy]# tshark -t a -r /tmp/test.pcap
Running as user "root" and group "root". This could be dangerous.
1 15:27:48.278502409 10.0.2.44 → 10.0.2.28 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
2 15:27:48.279691783 10.0.2.28 → 10.0.2.44 ICMP 60 Echo (ping) reply id=0x0000, seq=0/0, ttl=64 (request in 1)
[root@rocky8-client scapy]#
# 前回 reply があった全てパケットで再送
>>> [req for req, rep in r]
[<IP frag=0 proto=icmp dst=10.0.2.28 |<ICMP |>>, <IP frag=0 proto=icmp dst=10.0.2.29 |<ICMP |>>]
>>> r3, ur3 = sr([req for req, rep in r], timeout=1, verbose=False)
>>> r3.summary()
IP / ICMP 10.0.2.44 > 10.0.2.28 echo-request 0 ==> IP / ICMP 10.0.2.28 > 10.0.2.44 echo-reply 0 / Padding
IP / ICMP 10.0.2.44 > 10.0.2.29 echo-request 0 ==> IP / ICMP 10.0.2.29 > 10.0.2.44 echo-reply 0 / Padding
>>> ur3.summary()
>>>
以下は sr([req for …) 実行中のパケットキャプチャです。
[root@rocky8-client scapy]# tshark -i 1 -f "icmp" -w /tmp/test.pcap
Running as user "root" and group "root". This could be dangerous.
Capturing on 'enp0s3'
4 ^C
[root@rocky8-client scapy]# tshark -t a -r /tmp/test.pcap
Running as user "root" and group "root". This could be dangerous.
1 15:40:43.114075207 10.0.2.44 → 10.0.2.28 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
2 15:40:43.115100979 10.0.2.28 → 10.0.2.44 ICMP 60 Echo (ping) reply id=0x0000, seq=0/0, ttl=64 (request in 1)
3 15:40:43.119683903 10.0.2.44 → 10.0.2.29 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
4 15:40:43.120126988 10.0.2.29 → 10.0.2.44 ICMP 60 Echo (ping) reply id=0x0000, seq=0/0, ttl=64 (request in 3)
[root@rocky8-client scapy]#
retry オプションで再送する
retry=1 の場合、最初の送信と再送で 2 回の送受信が発生します。ただし、再送が行われるのは、1 つ前の通信でレスポンスがなかった通信に対してのみです。
>>> # ICMP 送信。reply がなかった request については再送。
>>> r, ur = sr(ips/ICMP(), inter=0.2, retry=1, timeout=1)
Begin emission
WARNING: MAC address to reach destination not found. Using broadcast.
WARNING: MAC address to reach destination not found. Using broadcast.
Finished sending 4 packets
Begin emission
WARNING: MAC address to reach destination not found. Using broadcast.
WARNING: MAC address to reach destination not found. Using broadcast.
Finished sending 2 packets
Received 16 packets, got 2 answers, remaining 2 packets
>>>
>>> r.summary()
IP / ICMP 10.0.2.44 > 10.0.2.28 echo-request 0 ==> IP / ICMP 10.0.2.28 > 10.0.2.44 echo-reply 0 / Padding
IP / ICMP 10.0.2.44 > 10.0.2.29 echo-request 0 ==> IP / ICMP 10.0.2.29 > 10.0.2.44 echo-reply 0 / Padding
>>>
>>> ur.summary()
IP / ICMP 10.0.2.44 > 10.0.2.30 echo-request 0
IP / ICMP 10.0.2.44 > 10.0.2.31 echo-request 0
>>>
以下は sr 実行時のパケットキャプチャです。10.0.2.30, 10.0.2.31 は存在しないため、reply はありません。同アドレス宛てに対して再送が行われていることが確認できます。
[root@rocky8-client scapy]# tshark -i 1 -f "icmp" -w /tmp/test.pcap
Running as user "root" and group "root". This could be dangerous.
Capturing on 'enp0s3'
8 ^C
[root@rocky8-client scapy]# tshark -t a -r /tmp/test.pcap
Running as user "root" and group "root". This could be dangerous.
1 19:31:29.670140911 10.0.2.44 → 10.0.2.28 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
2 19:31:29.671089418 10.0.2.28 → 10.0.2.44 ICMP 60 Echo (ping) reply id=0x0000, seq=0/0, ttl=64 (request in 1)
3 19:31:29.874060014 10.0.2.44 → 10.0.2.29 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
4 19:31:29.876497619 10.0.2.29 → 10.0.2.44 ICMP 60 Echo (ping) reply id=0x0000, seq=0/0, ttl=64 (request in 3)
5 19:31:32.101387620 10.0.2.44 → 10.0.2.30 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
6 19:31:34.320021661 10.0.2.44 → 10.0.2.31 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
7 19:31:37.533957909 10.0.2.44 → 10.0.2.30 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
8 19:31:39.747258388 10.0.2.44 → 10.0.2.31 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=64
[root@rocky8-client scapy]#
同じパケットを繰り返し送信する(srloop)
>>> # count オプションで指定した回数送信する
>>> srloop(ips/ICMP(), count=2)
WARNING: MAC address to reach destination not found. Using broadcast.
WARNING: MAC address to reach destination not found. Using broadcast.
*.WARNING: MAC address to reach destination not found. Using broadcast.
WARNING: MAC address to reach destination not found. Using broadcast.
*.
Sent 8 packets, received 4 packets. 50.0% hits.
(<Results: TCP:0 UDP:0 ICMP:4 Other:0>, <PacketList: TCP:0 UDP:0 ICMP:4 Other:0>)
>>>
>>> # 結果回収
>>> r, ur = _
>>>
>>> # 結果確認(同じアドレス宛に 2 回ずつ request が発生)
>>> r.summary()
IP / ICMP 10.0.2.44 > 10.0.2.28 echo-request 0 ==> IP / ICMP 10.0.2.28 > 10.0.2.44 echo-reply 0 / Padding
IP / ICMP 10.0.2.44 > 10.0.2.29 echo-request 0 ==> IP / ICMP 10.0.2.29 > 10.0.2.44 echo-reply 0 / Padding
IP / ICMP 10.0.2.44 > 10.0.2.28 echo-request 0 ==> IP / ICMP 10.0.2.28 > 10.0.2.44 echo-reply 0 / Padding
IP / ICMP 10.0.2.44 > 10.0.2.29 echo-request 0 ==> IP / ICMP 10.0.2.29 > 10.0.2.44 echo-reply 0 / Padding
>>>
>>> ur.summary()
IP / ICMP 10.0.2.44 > 10.0.2.30 echo-request 0
IP / ICMP 10.0.2.44 > 10.0.2.31 echo-request 0
IP / ICMP 10.0.2.44 > 10.0.2.30 echo-request 0
IP / ICMP 10.0.2.44 > 10.0.2.31 echo-request 0
>>>
送受信結果の出力を編集
任意ポートを TCP(sync)でノックするためのパケットを準備します。
※ sport は展開の都度採番されるようなので、実際の送信時には下記とは異なるポート番号になる場合があります。
>>> ips = IP(dst="10.0.2.28/31"); [p for p in ips]
[<IP dst=10.0.2.28 |>, <IP dst=10.0.2.29 |>]
>>> tcps = TCP(sport=RandShort(), dport=[22,53,80], flags="S"); [p for p in tcps]
[<TCP sport=58630 dport=ssh flags=S |>, <TCP sport=28490 dport=domain flags=S |>, <TCP sport=32417 dport=http flags=S |>]
>>>
各ポート(ssh, domain, http)に TCP で sync を送信します。
>>> # 送受信実行
>>> r, ur = sr(ips/tcps, timeout=1, verbose=False)
>>>
結果を確認します。
>>> r.summary()
IP / TCP 10.0.2.44:60796 > 10.0.2.28:ssh S ==> IP / TCP 10.0.2.28:ssh > 10.0.2.44:60796 SA / Padding
IP / TCP 10.0.2.44:28272 > 10.0.2.28:domain S ==> IP / TCP 10.0.2.28:domain > 10.0.2.44:28272 SA / Padding
IP / TCP 10.0.2.44:9782 > 10.0.2.28:http S ==> IP / TCP 10.0.2.28:http > 10.0.2.44:9782 RA / Padding
IP / TCP 10.0.2.44:57109 > 10.0.2.29:ssh S ==> IP / TCP 10.0.2.29:ssh > 10.0.2.44:57109 SA / Padding
IP / TCP 10.0.2.44:48447 > 10.0.2.29:domain S ==> IP / TCP 10.0.2.29:domain > 10.0.2.44:48447 SA / Padding
IP / TCP 10.0.2.44:27580 > 10.0.2.29:http S ==> IP / TCP 10.0.2.29:http > 10.0.2.44:27580 RA / Padding
>>>
結果から送信のみ表示します。
>>> r.summary(lambda req, rep : req)
IP / TCP 10.0.2.44:60796 > 10.0.2.28:ssh S
IP / TCP 10.0.2.44:28272 > 10.0.2.28:domain S
IP / TCP 10.0.2.44:9782 > 10.0.2.28:http S
IP / TCP 10.0.2.44:57109 > 10.0.2.29:ssh S
IP / TCP 10.0.2.44:48447 > 10.0.2.29:domain S
IP / TCP 10.0.2.44:27580 > 10.0.2.29:http S
>>>
結果から受信のみ表示します。
>>> r.summary(lambda req, rep : rep)
IP / TCP 10.0.2.28:ssh > 10.0.2.44:60796 SA / Padding
IP / TCP 10.0.2.28:domain > 10.0.2.44:28272 SA / Padding
IP / TCP 10.0.2.28:http > 10.0.2.44:9782 RA / Padding
IP / TCP 10.0.2.29:ssh > 10.0.2.44:57109 SA / Padding
IP / TCP 10.0.2.29:domain > 10.0.2.44:48447 SA / Padding
IP / TCP 10.0.2.29:http > 10.0.2.44:27580 RA / Padding
>>>
フィールドを指定して表示します。その①
>>> r.summary(lambda req, rep : req.sprintf("%IP.src% \t %IP.dst% \t %TCP.sport% \t %TCP.dport% \t %TCP.flags%"))
10.0.2.44 10.0.2.28 40583 ssh S
10.0.2.44 10.0.2.28 28405 domain S
10.0.2.44 10.0.2.28 48552 http S
10.0.2.44 10.0.2.29 19301 ssh S
10.0.2.44 10.0.2.29 54755 domain S
10.0.2.44 10.0.2.29 52817 http S
>>>
フィールドを指定して表示します。その②(※ sprintf の “\t” は使用できませんでした)
>>> for req, rep in r: rep.sprintf("%IP.src% %IP.dst% %TCP.sport% %TCP.dport% %TCP.flags%")
...
'10.0.2.28 10.0.2.44 ssh 40082 SA'
'10.0.2.28 10.0.2.44 domain 48831 SA'
'10.0.2.28 10.0.2.44 http 23670 RA'
'10.0.2.29 10.0.2.44 ssh 57761 SA'
'10.0.2.29 10.0.2.44 domain 56187 SA'
'10.0.2.29 10.0.2.44 http 17698 RA'
>>>
PacketList でパケットを個別に表示します。
>>> # 送信パケットを PacketList クラスでオブジェクト化
>>> reqs = PacketList([req for req, rep in r])
>>>
>>> # フィールドの値を表示
>>> reqs[0][IP].src; reqs[0][IP].dst; reqs[0][TCP].sport; reqs[0][TCP].dport; reqs[0][TCP].flags
'10.0.2.44'
'10.0.2.28'
40082
22
<Flag 2 (S)>
>>>
>>> # sprintf で表示①(※ sprintf の "\t" は使用できませんでした)
>>> reqs[0].sprintf("%IP.src% %IP.dst% %TCP.sport% %TCP.dport% %TCP.flags%")
'10.0.2.44 10.0.2.28 40082 ssh S'
>>>
>>> # sprintf で表示②(※ sprintf の "\t" は使用できませんでした)
>>> reqs[0][IP].sprintf("%src% %dst%"); reqs[0][TCP].sprintf("%sport% %dport% %flags%")
'10.0.2.44 10.0.2.28'
'40082 ssh S'
>>>
>>> # 受信パケットを PacketList クラスでオブジェクト化
>>> reps = PacketList([rep for req, rep in r])
>>>
>>> # フィールドの値を表示
>>> reps[0][IP].src; reps[0][IP].dst; reps[0][TCP].sport; reps[0][TCP].dport; reps[0][TCP].flags
'10.0.2.28'
'10.0.2.44'
22
40082
<Flag 18 (SA)>
>>>
>>> # sprintf で表示①(※ sprintf の "\t" は使用できませんでした)
>>> reps[0].sprintf("%IP.src% %IP.dst% %TCP.sport% %TCP.dport% %TCP.flags%")
'10.0.2.28 10.0.2.44 ssh 40082 SA'
>>>
>>> # sprintf で表示②(※ sprintf の "\t" は使用できませんでした)
>>> reps[0][IP].sprintf("%src% %dst%"); reps[0][TCP].sprintf("%sport% %dport% %flags%")
'10.0.2.28 10.0.2.44'
'ssh 40082 SA'
>>>
送受信結果から条件に一致するもののみ出力(filter)
任意ポートを TCP(sync)でノックするためのパケットを準備します。
※ sport は展開の都度採番されるようなので、実際の送信時には下記とは異なるポート番号になる場合があります。
>>> ips = IP(dst="10.0.2.28/31"); [p for p in ips]
[<IP dst=10.0.2.28 |>, <IP dst=10.0.2.29 |>]
>>> tcps = TCP(sport=RandShort(), dport=[22,53,80], flags="S"); [p for p in tcps]
[<TCP sport=21312 dport=ssh flags=S |>, <TCP sport=43659 dport=domain flags=S |>, <TCP sport=28747 dport=http flags=S |>]
>>>
各ポート(ssh, domain, http)に TCP で sync を送信します。
>>> # 送受信実行
>>> r, ur = sr(ips/tcps, timeout=1, verbose=False)
>>>
以降のコマンドが長くなりそうなので、出力命令部分は変数化しておきます。
>>> custom_pr1 = lambda req, rep : rep.sprintf("%IP.src% \t %TCP.sport% is open")
>>>
受信パケットから以下の条件に一致するものを出力します。
TCP in rep : reply に TCP が含まれていること
rep.sprintf("%TCP.flags%")=="SA" : TCP フラグが Syn-Ack であること
>>> r.summary(lfilter=lambda req, rep : TCP in rep and rep.sprintf("%TCP.flags%")=="SA", prn=custom_pr1)
10.0.2.28 ssh is open
10.0.2.28 domain is open
10.0.2.29 ssh is open
10.0.2.29 domain is open
>>>
同じ条件のものを別の書式で出力します。
TCP in rep : reply に TCP が含まれていること
rep[TCP].flags==(0x02|0x10) : TCP フラグが Syn-Ack であること(0x02はSyn、0x10はAck。0x12 を指定しても条件は同じ)
>>> r.summary(lfilter=lambda req, rep : TCP in rep and rep[TCP].flags==(0x02|0x10), prn=custom_pr1)
10.0.2.28 ssh is open
10.0.2.28 domain is open
10.0.2.29 ssh is open
10.0.2.29 domain is open
>>>
同じ条件のものを別の書式で出力します。
rep.haslayer(TCP) : reply に TCP が含まれていること(haslayer で TCP の存在を検査)
rep.getlayer(TCP).flags==0x12 : TCP フラグが Syn-Ack であること(getlayer で TCP を取り出す)
>>> r.summary(lfilter=lambda req, rep : rep.haslayer(TCP) and rep.getlayer(TCP).flags==0x12, prn=custom_pr1)
10.0.2.28 ssh is open
10.0.2.28 domain is open
10.0.2.29 ssh is open
10.0.2.29 domain is open
>>>
同じ条件のものを別の書式で出力します。
TCP in rep : reply に TCP が含まれていること
rep[TCP].flags&0x02 and rep[TCP].flags&0x10 : TCP フラグが Syn-Ack であること
>>> r.summary(lfilter=lambda req, rep : TCP in rep and rep[TCP].flags&0x02 and rep[TCP].flags&0x10, prn=custom_pr1)
10.0.2.28 ssh is open
10.0.2.28 domain is open
10.0.2.29 ssh is open
10.0.2.29 domain is open
>>>
filter() で同じ条件を指定し出力します。
TCP in rep : reply に TCP が含まれていること
rep[TCP].flags==0x12 : TCP フラグが Syn-Ack であること
>>> r.filter(lambda req, rep : TCP in rep and rep[TCP].flags==0x12).summary(custom_pr1)
10.0.2.28 ssh is open
10.0.2.28 domain is open
10.0.2.29 ssh is open
10.0.2.29 domain is open
>>>
送受信結果をテーブル形式で出力(make_table)
make_table() を使用し、送受信結果をテーブル形式で表示します。
任意ポートを TCP(sync)でノックするためのパケットを準備します。
※ sport は展開の都度採番されるようなので、実際の送信時には下記とは異なるポート番号になる場合があります。
>>> ips = IP(dst="10.0.2.28/31"); [p for p in ips]
[<IP dst=10.0.2.28 |>, <IP dst=10.0.2.29 |>]
>>> tcps = TCP(sport=RandShort(), dport=[22,53,80], flags="S"); [p for p in tcps]
[<TCP sport=15198 dport=ssh flags=S |>, <TCP sport=hsl_storm dport=domain flags=S |>, <TCP sport=37953 dport=http flags=S |>]
>>>
各ポート(ssh, domain, http)に TCP で sync を送信します。
>>> r, ur = sr(ips/tcps, timeout=1, verbose=False)
>>>
request, reply から列ヘッダ、行ヘッダ、テーブルデータ(下記例ではそれぞれ req.dst, req.dport, rep.sprintf(…))を出力する lambda 式を make_table() に引き渡すと、データがテーブル形式で表示されます。
今回の例は TCP(sync)の送受信ですが、何らかの理由で TCP 接続できず、代わりに ICMP エラーが返却された場合の出力についても追加しています。
>>> r.make_table(lambda req, rep : (req.dst, req.dport, rep.sprintf("{TCP:%TCP.flags%}{ICMP:%IP.src% - %ICMP.type%}")))
10.0.2.28 10.0.2.29
22 SA SA
53 SA SA
80 RA RA
>>>
以下でも同じ出力になります。
>>> r.make_table(lambda req, rep : (rep.src, rep.sport, rep.sprintf("{TCP:%TCP.flags%}{ICMP:%IP.src% - %ICMP.type%}")))
10.0.2.28 10.0.2.29
22 SA SA
53 SA SA
80 RA RA
>>>
以下は条件に一致した送受信データのみテーブル形式で出力しています。
TCP in rep : reply に TCP が含まれていること
rep[TCP].flags&0x02 : TCP の Syn フラグが 1 であること(Syn-Ack が返ってきますが、ここでは Syn フラグのみを確認しています)
>>> r.filter(lambda req, rep : TCP in rep and rep[TCP].flags&0x02).make_table(lambda req, rep : (req.dst, req.dport, "ok"))
10.0.2.28 10.0.2.29
22 ok ok
53 ok ok
>>>
以下も同じ条件での出力になります。
rep.haslayer(TCP) : reply に TCP が含まれていること(haslayer で TCP の存在を検査)
rep.getlayer(TCP).flags&0x02 : TCP の Syn フラグが 1 であること(getlayer で TCP を取り出す)
>>> r.filter(lambda req, rep : rep.haslayer(TCP) and rep.getlayer(TCP).flags&0x02).make_table(lambda req, rep : (req.dst, req.dport, "ok"))
10.0.2.28 10.0.2.29
22 ok ok
53 ok ok
>>>
送受信をキャプチャ(sniff)
sniff でキャプチャ
sniff() でパケットをキャプチャできます。
count オプションにはキャプチャ件数、iface オプションにはキャプチャ対象のインターフェイスを指定できます。count に指定した件数分、パケットをキャプチャすると sniff() は処理を終了します。
下記例は enp0s3 で流れているパケットをキャプチャしています。裏で意図的に送受信しているわけではなく、常時流れているパケットを勝手に拾った結果です。
>>> cap = sniff(count=5, iface="enp0s3")
>>> cap.nsummary()
0000 Ether / IP / TCP 10.0.2.2:50657 > 10.0.2.44:ssh A / Padding
0001 Ether / IP / TCP 10.0.2.2:50653 > 10.0.2.44:ssh PA / Raw
0002 Ether / IP / TCP 10.0.2.44:ssh > 10.0.2.2:50653 A
0003 Ether / IP / TCP 10.0.2.44:ssh > 10.0.2.2:50653 PA / Raw
0004 Ether / IP / TCP 10.0.2.2:50653 > 10.0.2.44:ssh A / Padding
>>>
iface の指定が無ければ、conf.iface に定義されたインターフェイスが使用されます。
>>> conf.iface
<NetworkInterface enp0s3 [UP+BROADCAST+RUNNING+MULTICAST+LOWER_UP]>
>>>
フィルタを指定してキャプチャ
filter オプションでキャプチャ時のフィルタを指定できます。
>>> # プロトコル(icmp)でフィルタ
>>> cap = sniff(filter="icmp", count=2)
>>> cap.nsummary()
0000 Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
0001 Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
>>>
>>> # ポート番号(53:dns)でフィルタ
>>> cap = sniff(filter="port 53", count=2)
>>> cap.nsummary()
0000 Ether / IP / UDP / DNS Qry b'www.test.co.jp.'
0001 Ether / IP / UDP / DNS Ans 1.2.3.4
>>>
>>> # プロトコル(icmp)と送信元アドレスでフィルタ
>>> cap = sniff(filter="icmp and src host 10.0.2.44", count=2)
>>> cap.nsummary()
0000 Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
0001 Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
>>>
>>> # プロトコル(icmp)と宛先アドレスでフィルタ
>>> cap = sniff(filter="icmp and dst host 10.0.2.40", count=2)
>>> cap.nsummary()
0000 Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
0001 Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
>>>
>>> # プロトコル(icmp)とサブネットでフィルタ
>>> cap = sniff(filter="icmp and net 10.0.2.0/24", count=2)
>>> cap.nsummary()
0000 Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
0001 Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
>>>
PCAP ファイルでのやりとり
sniff でキャプチャしたパケットを PCAP ファイルに出力します。
>>> # キャプチャ
>>> cap = sniff(filter="icmp", count=2)
>>> cap.nsummary()
0000 Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
0001 Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
>>>
>>> # PCAP へ出力
>>> wrpcap("/tmp/test.pcap", cap)
>>>
PCAP ファイルを tshark で参照します。
[root@rocky8-client scapy]# tshark -n -t a -r /tmp/test.pcap
Running as user "root" and group "root". This could be dangerous.
1 13:39:10.907014 10.0.2.44 → 10.0.2.40 ICMP 98 Echo (ping) request id=0x0001, seq=1/256, ttl=64
2 13:39:10.907948 10.0.2.40 → 10.0.2.44 ICMP 98 Echo (ping) reply id=0x0001, seq=1/256, ttl=64 (request in 1)
[root@rocky8-client scapy]#
PCAP ファイルを sniff で読み込みます。
>>> cap2 = sniff(offline="/tmp/test.pcap")
>>> cap2.nsummary()
0000 Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
0001 Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
>>>
キャプチャパケットに対するリアクション
prn オプションで、キャプチャした各パケットを制御するための処理を指定できます。
以下の例では、dig 実行時にキャプチャしたパケットを prn に指定した命令に従い編集、表示しています。
>>> sniff(filter="dst port 53", count=1, prn=lambda x : x[DNS].qd[0].sprintf("qn:%qname%, qt:%qtype%"))
qn:b'www.test.co.jp.', qt:A
<Sniffed: TCP:0 UDP:1 ICMP:0 Other:0>
>>>
sniff から返された値は一時的にキャッシュされていますが、store=False を指定することでキャッシュされなくなります。
※ アンダースコア(_)は直前の出力結果を拾います。
>>> # store=False 指定なし
>>> sniff(filter="dst port 53", count=1, prn=lambda x : x[DNS].qd[0].sprintf("qn:%qname%, qt:%qtype%"))
qn:b'www.test.co.jp.', qt:A
<Sniffed: TCP:0 UDP:1 ICMP:0 Other:0>
>>> cap = _
>>> cap.summary()
Ether / IP / UDP / DNS Qry b'www.test.co.jp.'
>>>
>>> # store=False 指定あり
>>> sniff(filter="dst port 53", count=1, store=False, prn=lambda x : x[DNS].qd[0].sprintf("qn:%qname%, qt:%qtype%"))
qn:b'www.test.co.jp.', qt:A
<Sniffed: TCP:0 UDP:0 ICMP:0 Other:0>
>>> cap = _
>>> cap.summary()
>>>
prn にもう少し複雑な命令を指定してみます。
>>> custom_pr2 = lambda x : (x[DNS].sprintf("qr:%qr%, opc:%opcode%, rc:%rcode%, [qd:%qdcount% an:%ancount% ns:%nscount% ar:%arcount%], ") + x[DNS].qd[0].sprintf("qn:%qname%, qt:%qtype%"))
>>> sniff(filter="port 53", count=2, store=False, prn=custom_pr2)
qr:0, opc:QUERY, rc:ok, [qd:1 an:0 ns:0 ar:1], qn:b'www.test.co.jp.', qt:A
qr:1, opc:QUERY, rc:ok, [qd:1 an:1 ns:2 ar:3], qn:b'www.test.co.jp.', qt:A
<Sniffed: TCP:0 UDP:0 ICMP:0 Other:0>
>>>
同じ内容を、少し命令を変えて表示します。
>>> custom_pr3 = lambda x : x.sprintf("qr:%d, opc:%s, rc:%s, [qd:%d an:%d ns:%d ar:%d], qn:%s, qt:%d" %
... (x[DNS].qr, x[DNS].opcode, x[DNS].rcode, x[DNS].qdcount, x[DNS].ancount, x[DNS].nscount, x[DNS].arcount, x[DNS].qd[0].qname.decode('utf-8'), x[DNS].qd[0].qtype))
>>> sniff(filter="port 53", count=2, store=False, prn=custom_pr3)
qr:0, opc:0, rc:0, [qd:1 an:0 ns:0 ar:1], qn:www.test.co.jp., qt:1
qr:1, opc:0, rc:0, [qd:1 an:1 ns:2 ar:3], qn:www.test.co.jp., qt:1
<Sniffed: TCP:0 UDP:0 ICMP:0 Other:0>
>>>
キャプチャに使用したインターフェイスを確認
出力に sniffed_on を指定すると、どのインターフェイスで送受信したかわかります。
以下の例は、10.0.2.40 と 127.0.0.1 に対して ping を実行した際のキャプチャです。
>>> sniff(iface=["enp0s3","lo"], filter="icmp", prn=lambda x: x.sniffed_on + ": "+x.summary())
enp0s3: Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
enp0s3: Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
lo: Ether / IP / ICMP 127.0.0.1 > 127.0.0.1 echo-request 0 / Raw
lo: Ether / IP / ICMP 127.0.0.1 > 127.0.0.1 echo-request 0 / Raw
lo: Ether / IP / ICMP 127.0.0.1 > 127.0.0.1 echo-reply 0 / Raw
lo: Ether / IP / ICMP 127.0.0.1 > 127.0.0.1 echo-reply 0 / Raw
^C<Sniffed: TCP:0 UDP:0 ICMP:6 Other:0>
>>>
キャプチャを非同期で行う(AsyncSniffer)
sniff では、count で指定した件数に達するか、Ctrl+C 等で中断するまでコンソールに制御が返りませんが、AsyncSniffer を使用するとコンソールに制御を返し、裏でパケットキャプチャを継続します。
キャプチャの開始は start() で、停止は stop() で行います。
以下の例は ping 実行中のキャプチャです。
>>> # 時刻表示用に import
>>> import datetime
>>>
>>> # 非同期用のオブジェクト生成
>>> asniff = AsyncSniffer(filter="icmp")
>>>
>>> # キャプチャ開始(+ 開始時の時刻を表示)
>>> asniff.start(); datetime.datetime.now()
datetime.datetime(2025, 7, 29, 17, 6, 42, 832872)
>>>
>>> # キャプチャ停止(+ 停止時の時刻を表示)
>>> cap = asniff.stop(); datetime.datetime.now()
datetime.datetime(2025, 7, 29, 17, 6, 48, 306617)
>>>
>>> # キャプチャしたパケットの件数
>>> len(cap)
10
>>>
>>> # キャプチャしたパケット
>>> cap.summary()
Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
>>>
count オプションでキャプチャするパケット件数を指定できます。stop() ではなく、join() と results を使用します。
>>> asniff = AsyncSniffer(filter="icmp", count=5)
>>> asniff.start(); asniff.join(); time.sleep(5); cap = asniff.results
>>> len(cap)
5
>>> cap.summary()
Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
>>>
timeout オプションでキャプチャする時間を指定できます。こちらについても stop() ではなく、join() と results を使用します。
>>> asniff = AsyncSniffer(filter="icmp", timeout=5)
>>> asniff.start(); asniff.join(); time.sleep(10); cap = asniff.results
>>> len(cap)
10
>>> cap.summary()
Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
>>>
count オプションと timeout オプションを同時に指定できます。どちらかの条件が成立した時点でキャプチャは終了します。
>>> asniff = AsyncSniffer(filter="icmp", timeout=5, count=4)
>>> asniff.start(); asniff.join(); time.sleep(10); cap = asniff.results
>>> len(cap)
4
>>> cap.summary()
Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
Ether / IP / ICMP 10.0.2.44 > 10.0.2.40 echo-request 0 / Raw
Ether / IP / ICMP 10.0.2.40 > 10.0.2.44 echo-reply 0 / Raw
>>>
sniff 同様 prn オプションを使用可能です。
>>> asniff = AsyncSniffer(filter="icmp", store=False, prn=lambda x: x[IP].sprintf("%src% >>> %dst%"))
>>> asniff.start(); time.sleep(5); cap = asniff.stop()
10.0.2.44 >>> 10.0.2.40
10.0.2.40 >>> 10.0.2.44
10.0.2.44 >>> 10.0.2.40
10.0.2.40 >>> 10.0.2.44
10.0.2.44 >>> 10.0.2.40
10.0.2.40 >>> 10.0.2.44
10.0.2.44 >>> 10.0.2.40
10.0.2.40 >>> 10.0.2.44
10.0.2.44 >>> 10.0.2.40
10.0.2.40 >>> 10.0.2.44
>>>
ルーティングの管理
scapy は独自のルーティングテーブルを持ちます。
以下は OS のルーティングテーブルです。
[root@rocky8-client scapy]# ip r
default via 10.0.2.1 dev enp0s3 proto static metric 100
10.0.2.0/24 dev enp0s3 proto kernel scope link src 10.0.2.44 metric 100
[root@rocky8-client scapy]#
以下は scapy のルーティングテーブルです。
>>> conf.route
Network Netmask Gateway Iface Output IP Metric
0.0.0.0 0.0.0.0 10.0.2.1 enp0s3 0.0.0.0 100
10.0.2.0 255.255.255.255 0.0.0.0 enp0s3 10.0.2.44 0
10.0.2.0 255.255.255.0 0.0.0.0 enp0s3 10.0.2.44 100
10.0.2.255 255.255.255.255 0.0.0.0 enp0s3 10.0.2.44 0
10.0.2.44 255.255.255.255 0.0.0.0 enp0s3 10.0.2.44 0
127.0.0.0 255.255.255.255 0.0.0.0 lo 127.0.0.1 0
127.0.0.0 255.0.0.0 0.0.0.0 lo 127.0.0.1 0
127.0.0.1 255.255.255.255 0.0.0.0 lo 127.0.0.1 0
127.255.255.255 255.255.255.255 0.0.0.0 lo 127.0.0.1 0
224.0.0.0 240.0.0.0 0.0.0.0 enp0s3 10.0.2.44 250
>>>
以降の検証で、scapy のデフォルトルートを削除し、scapy から外部への通信ができなくなることを確認します。
デフォルトルートの削除に先立ち、scapy でインターネット上のサーバに ping の送受信ができることを確認します。
>>> r, ur = sr(IP(dst="8.8.8.8/32")/ICMP(), timeout=1, verbose=False)
>>> r; r.nsummary()
<Results: TCP:0 UDP:0 ICMP:1 Other:0>
0000 IP / ICMP 10.0.2.44 > 8.8.8.8 echo-request 0 ==> IP / ICMP 8.8.8.8 > 10.0.2.44 echo-reply 0 / Padding
>>>
scapy のデフォルトルートを削除します。ただし、conf.route.delt() では削除できなかったため、ルーティングテーブルのデータが登録されているリストから直接削除します。
参考:デフォルトルートを削除できなかった件について
conf.route.delt() でデフォルトルートを削除しようとしましたが以下のとおりエラーとなりました。
>>> conf.route.delt(net="0.0.0.0/0", gw="10.0.2.1", metric=100)
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/scapy/route.py", line 126, in delt
i = self.routes.index(route)
^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: (0, 0, '10.0.2.1', 'enp0s3', '10.0.2.44', 100) is not in list
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/usr/local/lib/python3.11/site-packages/scapy/route.py", line 129, in delt
raise ValueError("No matching route found!")
ValueError: No matching route found!
>>>
ルーティングテーブルのリストに削除対象である (0, 0, ‘10.0.2.1’, ‘enp0s3’, ‘10.0.2.44’, 100) のデータが無いとのことです。リストは以下のとおりです。
>>> conf.route.routes
[(167772672, 4294967295, '0.0.0.0', 'enp0s3', '10.0.2.44', 0), (167772716, 4294967295, '0.0.0.0', 'enp0s3', '10.0.2.44', 0), (167772927, 4294967295, '0.0.0.0', 'enp0s3', '10.0.2.44', 0), (2130706432, 4294967295, '0.0.0.0', 'lo', '127.0.0.1', 0), (2130706432, 4278190080, '0.0.0.0', 'lo', '127.0.0.1', 0), (2130706433, 4294967295, '0.0.0.0', 'lo', '127.0.0.1', 0), (2147483647, 4294967295, '0.0.0.0', 'lo', '127.0.0.1', 0), (0, 0, '10.0.2.1', 'enp0s3', '0.0.0.0', 100), (167772672, 4294967040, '0.0.0.0', 'enp0s3', '10.0.2.44', 100), (3758096384, 4026531840, '0.0.0.0', 'enp0s3', '10.0.2.44', 250)]
>>>
(0, 0, ‘10.0.2.1’, ‘enp0s3’, ‘0.0.0.0’, 100) はありますが、たしかにエラーで指摘されているデータは存在しません。
conf.route.delt() のオプションで Output IP に ‘10.0.2.44’ ではなく ‘0.0.0.0’ が指定できれば削除できると思うのですが、やり方がわかりませんでした。
>>> # scapy のルーティングテーブルのリストを表示
>>> conf.route.routes
[(167772672, 4294967295, '0.0.0.0', 'enp0s3', '10.0.2.44', 0), (167772716, 4294967295, '0.0.0.0', 'enp0s3', '10.0.2.44', 0), (167772927, 4294967295, '0.0.0.0', 'enp0s3', '10.0.2.44', 0), (2130706432, 4294967295, '0.0.0.0', 'lo', '127.0.0.1', 0), (2130706432, 4278190080, '0.0.0.0', 'lo', '127.0.0.1', 0), (2130706433, 4294967295, '0.0.0.0', 'lo', '127.0.0.1', 0), (2147483647, 4294967295, '0.0.0.0', 'lo', '127.0.0.1', 0), (0, 0, '10.0.2.1', 'enp0s3', '0.0.0.0', 100), (167772672, 4294967040, '0.0.0.0', 'enp0s3', '10.0.2.44', 100), (3758096384, 4026531840, '0.0.0.0', 'enp0s3', '10.0.2.44', 250)]
>>>
>>> # 対象ルーティングデータのインデックスを確認(→7)
>>> conf.route.routes[7]
(0, 0, '10.0.2.1', 'enp0s3', '0.0.0.0', 100)
>>>
>>> # デフォルトルートのデータを削除
>>> del(conf.route.routes[7])
>>>
scapy のルーティングテーブルからデフォルトルート(10.0.2.1)が削除されていることを確認します。
>>> conf.route
Network Netmask Gateway Iface Output IP Metric
10.0.2.0 255.255.255.255 0.0.0.0 enp0s3 10.0.2.44 0
10.0.2.0 255.255.255.0 0.0.0.0 enp0s3 10.0.2.44 100
10.0.2.255 255.255.255.255 0.0.0.0 enp0s3 10.0.2.44 0
10.0.2.44 255.255.255.255 0.0.0.0 enp0s3 10.0.2.44 0
127.0.0.0 255.255.255.255 0.0.0.0 lo 127.0.0.1 0
127.0.0.0 255.0.0.0 0.0.0.0 lo 127.0.0.1 0
127.0.0.1 255.255.255.255 0.0.0.0 lo 127.0.0.1 0
127.255.255.255 255.255.255.255 0.0.0.0 lo 127.0.0.1 0
224.0.0.0 240.0.0.0 0.0.0.0 enp0s3 10.0.2.44 250
>>>
OS のルーティングテーブルに影響がないことについても確認します。
[root@rocky8-client scapy]# ip r
default via 10.0.2.1 dev enp0s3 proto static metric 100
10.0.2.0/24 dev enp0s3 proto kernel scope link src 10.0.2.44 metric 100
[root@rocky8-client scapy]#
scapy からインターネット上のサーバに ping の送受信ができなくなったことを確認します。
>>> r, ur = sr(IP(dst="8.8.8.8/32")/ICMP(), timeout=1, verbose=False)
WARNING: No route found for IPv4 destination 8.8.8.8 (no default route?)
WARNING: No route found for IPv4 destination 8.8.8.8 (no default route?)
WARNING: more No route found for IPv4 destination 8.8.8.8 (no default route?)
>>>
OS レベルの ping では問題がないことを確認します。
[root@rocky8-client scapy]# ping -c 1 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=118 time=33.6 ms
--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 33.573/33.573/33.573/0.000 ms
[root@rocky8-client scapy]#
次に、scapy のデフォルトルートを手動で追加し、インターネット上のサーバとの通信を再開させます。
>>> # ルーティング追加
>>> conf.route.add(net="0.0.0.0/0",gw="10.0.2.1", metric=100)
>>>
>>> # ルーティングテーブルにデフォルトルート(10.0.2.1)が追加されていることを確認
>>> conf.route
Network Netmask Gateway Iface Output IP Metric
0.0.0.0 0.0.0.0 10.0.2.1 enp0s3 10.0.2.44 100
10.0.2.0 255.255.255.255 0.0.0.0 enp0s3 10.0.2.44 0
10.0.2.0 255.255.255.0 0.0.0.0 enp0s3 10.0.2.44 100
10.0.2.255 255.255.255.255 0.0.0.0 enp0s3 10.0.2.44 0
10.0.2.44 255.255.255.255 0.0.0.0 enp0s3 10.0.2.44 0
127.0.0.0 255.255.255.255 0.0.0.0 lo 127.0.0.1 0
127.0.0.0 255.0.0.0 0.0.0.0 lo 127.0.0.1 0
127.0.0.1 255.255.255.255 0.0.0.0 lo 127.0.0.1 0
127.255.255.255 255.255.255.255 0.0.0.0 lo 127.0.0.1 0
224.0.0.0 240.0.0.0 0.0.0.0 enp0s3 10.0.2.44 250
>>>
scapy でインターネット上のサーバに ping の送受信ができることを確認します。
>>> r, ur = sr(IP(dst="8.8.8.8/32")/ICMP(), timeout=1, verbose=False)
>>> r; r.nsummary()
<Results: TCP:0 UDP:0 ICMP:1 Other:0>
0000 IP / ICMP 10.0.2.44 > 8.8.8.8 echo-request 0 ==> IP / ICMP 8.8.8.8 > 10.0.2.44 echo-reply 0 / Padding
>>>
resync() でルーティングテーブルをリセットできることを、最後に確認しておきます。
>>> # 以下はデフォルトルート(10.0.2.1)が削除された状態
>>> conf.route
Network Netmask Gateway Iface Output IP Metric
10.0.2.0 255.255.255.255 0.0.0.0 enp0s3 10.0.2.44 0
10.0.2.0 255.255.255.0 0.0.0.0 enp0s3 10.0.2.44 100
10.0.2.255 255.255.255.255 0.0.0.0 enp0s3 10.0.2.44 0
10.0.2.44 255.255.255.255 0.0.0.0 enp0s3 10.0.2.44 0
127.0.0.0 255.255.255.255 0.0.0.0 lo 127.0.0.1 0
127.0.0.0 255.0.0.0 0.0.0.0 lo 127.0.0.1 0
127.0.0.1 255.255.255.255 0.0.0.0 lo 127.0.0.1 0
127.255.255.255 255.255.255.255 0.0.0.0 lo 127.0.0.1 0
224.0.0.0 240.0.0.0 0.0.0.0 enp0s3 10.0.2.44 250
>>>
>>> # resync()でリセット
>>> conf.route.resync()
>>>
>>> # リセット後のルーティングテーブル
>>> conf.route
Network Netmask Gateway Iface Output IP Metric
0.0.0.0 0.0.0.0 10.0.2.1 enp0s3 0.0.0.0 100
10.0.2.0 255.255.255.255 0.0.0.0 enp0s3 10.0.2.44 0
10.0.2.0 255.255.255.0 0.0.0.0 enp0s3 10.0.2.44 100
10.0.2.255 255.255.255.255 0.0.0.0 enp0s3 10.0.2.44 0
10.0.2.44 255.255.255.255 0.0.0.0 enp0s3 10.0.2.44 0
127.0.0.0 255.255.255.255 0.0.0.0 lo 127.0.0.1 0
127.0.0.0 255.0.0.0 0.0.0.0 lo 127.0.0.1 0
127.0.0.1 255.255.255.255 0.0.0.0 lo 127.0.0.1 0
127.255.255.255 255.255.255.255 0.0.0.0 lo 127.0.0.1 0
224.0.0.0 240.0.0.0 0.0.0.0 enp0s3 10.0.2.44 250
>>>