これは、なにをしたくて書いたもの?
今まで特定のポートへの通信を遮断する(パケットをドロップする)のにはfirewalldなどを使うものだと思っていたのですが、tcコマンドでも
できそうだったので試しておこうかなと。
tcコマンドで特定の宛先へのパケットをドロップする
tcコマンドで特定の宛先へのパケットをドロップするには、まずqdiscを追加します。
Ubuntu Manpage: tc - show / manipulate traffic control settings
その後にフィルターを追加します。
フィルターはユニバーサル32ビットトラフィックフィルターというものを使うようです。
Ubuntu Manpage: u32 - universal 32bit traffic control filter
コマンドの説明を載せていくより、実際に動かした方がよさそうですね。
今回は以下の環境で、192.168.33.10のサーバーから192.168.33.11への通信を遮断してみます。
flowchart LR
subgraph 192.168.33.10
A["curl"]
end
subgraph 192.168.33.11
B["echo server/socat+cat"]
A -- telnet/dst port 5000 --> B
end
遮断する場所は、192.168.33.10と192.168.33.11の2箇所で行ってみます。
環境
今回の環境はこちらです。Ubuntu Linux 24.04 LTS。
$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 24.04.3 LTS Release: 24.04 Codename: noble $ uname -srvmpio Linux 6.8.0-78-generic #78-Ubuntu SMP PREEMPT_DYNAMIC Tue Aug 12 11:34:18 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
tcコマンド。
$ tc -V tc utility, iproute2-6.1.0, libbpf 1.3.0
socatコマンド。
$ socat -V socat by Gerhard Rieger and contributors - see www.dest-unreach.org socat version 1.8.0.0 on 08 Apr 2024 14:50:22 running on Linux version #78-Ubuntu SMP PREEMPT_DYNAMIC Tue Aug 12 11:34:18 UTC 2025, release 6.8.0-78-generic, machine x86_64 features: #define WITH_HELP 1 #define WITH_STATS 1 #define WITH_STDIO 1 #define WITH_FDNUM 1 #define WITH_FILE 1 #define WITH_CREAT 1 #define WITH_GOPEN 1 #define WITH_TERMIOS 1 #define WITH_PIPE 1 #define WITH_SOCKETPAIR 1 #define WITH_UNIX 1 #define WITH_ABSTRACT_UNIXSOCKET 1 #define WITH_IP4 1 #define WITH_IP6 1 #define WITH_RAWIP 1 #define WITH_GENERICSOCKET 1 #define WITH_INTERFACE 1 #define WITH_TCP 1 #define WITH_UDP 1 #define WITH_SCTP 1 #define WITH_DCCP 1 #define WITH_UDPLITE 1 #define WITH_LISTEN 1 #define WITH_POSIXMQ 1 #define WITH_SOCKS4 1 #define WITH_SOCKS4A 1 #define WITH_SOCKS5 1 #define WITH_VSOCK 1 #define WITH_NAMESPACES 1 #define WITH_PROXY 1 #define WITH_SYSTEM 1 #define WITH_SHELL 1 #define WITH_EXEC 1 #undef WITH_READLINE #define WITH_TUN 1 #define WITH_PTY 1 #define WITH_OPENSSL 1 #undef WITH_FIPS #define WITH_LIBWRAP 1 #define WITH_SYCLS 1 #define WITH_FILAN 1 #define WITH_RETRY 1 #define WITH_MSGLEVEL 0 /*debug*/ #define WITH_DEFAULT_IPV 0
192.168.33.10側の持つIPアドレスとネットワークデバイス。
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: ens5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:01:1b:a2 brd ff:ff:ff:ff:ff:ff
altname enp0s5
inet 192.168.121.195/24 metric 100 brd 192.168.121.255 scope global dynamic ens5
valid_lft 3529sec preferred_lft 3529sec
inet6 fe80::5054:ff:fe01:1ba2/64 scope link
valid_lft forever preferred_lft forever
3: ens6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:11:f9:7b brd ff:ff:ff:ff:ff:ff
altname enp0s6
inet 192.168.33.10/24 brd 192.168.33.255 scope global ens6
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe11:f97b/64 scope link
valid_lft forever preferred_lft forever
192.168.33.11側の持つIPアドレスとネットワークデバイス。
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: ens5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:9f:9a:82 brd ff:ff:ff:ff:ff:ff
altname enp0s5
inet 192.168.121.142/24 metric 100 brd 192.168.121.255 scope global dynamic ens5
valid_lft 3481sec preferred_lft 3481sec
inet6 fe80::5054:ff:fe9f:9a82/64 scope link
valid_lft forever preferred_lft forever
3: ens6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:21:15:a1 brd ff:ff:ff:ff:ff:ff
altname enp0s6
inet 192.168.33.11/24 brd 192.168.33.255 scope global ens6
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe21:15a1/64 scope link
valid_lft forever preferred_lft forever
準備
まずは192.168.33.11側で、socatとcatコマンドでEchoサーバーを立てておきます。
$ socat -v tcp-listen:5000,fork,reuseaddr exec:cat
192.168.33.10側からcurlを使ってtelnetプロトコルで確認。
$ curl telnet://192.168.33.11:5000 hello hello foo foo
192.168.33.11側には-vをつけているので、通信の様子がわかります。
> 2025/08/24 15:43:34.000835221 length=6 from=0 to=5 hello < 2025/08/24 15:43:34.000836155 length=6 from=0 to=5 hello > 2025/08/24 15:43:35.000652835 length=4 from=6 to=9 foo < 2025/08/24 15:43:35.000653275 length=4 from=6 to=9 foo
telnetはこのまま接続しっぱなしにしておきます。
なお、192.168.33.10から192.168.33.11に向かう時はens6が使われます。
## 192.168.33.10 → 192.168.33.11
$ ip route get 192.168.33.11
192.168.33.11 dev ens6 src 192.168.33.10 uid 1000
cache
prio/ingress qdiscで行う
tcコマンドを使ったパケットドロップでよくでてくるのはprioとingressでのqdiscなので、まずはこちらから。
192.168.33.10で192.168.33.11へのパケットをドロップする
ens6の現在のqdiscを確認。
$ sudo tc qdisc show dev ens6 qdisc fq_codel 0: root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
prioのqdiscを追加。prioは優先順位付きキューです。
$ sudo tc qdisc add dev ens6 root handle 1: prio
確認。
$ sudo tc qdisc show dev ens6 qdisc prio 1: root refcnt 2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1 $ sudo tc qdisc show dev ens6 root qdisc prio 1: root refcnt 2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
IPアドレス192.168.33.11、ポート5000に対するパケットをドロップするフィルターを追加。
$ sudo tc filter add dev ens6 protocol ip parent 1: prio 1 u32 \
match ip dst 192.168.33.11/32 \
match ip dport 5000 0xffff \
action drop
Ubuntu Manpage: u32 - universal 32bit traffic control filter
0xffffの部分はマスクで指定したポートに対してのマスクを設定するのですが、0xffffなので結局5000のままです。
IP := { { src | dst } { default | any | all | ip_address [ / { prefixlen | netmask } ] } AT | { dsfield |
ihl | protocol | precedence | icmp_type | icmp_code } VAL_MASK_8 | { sport | dport } VAL_MASK_16
| nofrag | firstfrag | df | mf }
フィルターが設定されたことを確認。
$ sudo tc filter show dev ens6
filter parent 1: protocol ip pref 1 u32 chain 0
filter parent 1: protocol ip pref 1 u32 chain 0 fh 800: ht divisor 1
filter parent 1: protocol ip pref 1 u32 chain 0 fh 800::800 order 2048 key ht 800 bkt 0 terminal flowid not_in_hw
match c0a8210b/ffffffff at 16
match 00001388/0000ffff at 20
action order 1: gact action drop
random type none pass val 0
index 1 ref 1 bind 1
$ sudo tc filter show dev ens6 parent 1:
filter protocol ip pref 1 u32 chain 0
filter protocol ip pref 1 u32 chain 0 fh 800: ht divisor 1
filter protocol ip pref 1 u32 chain 0 fh 800::800 order 2048 key ht 800 bkt 0 terminal flowid not_in_hw
match c0a8210b/ffffffff at 16
match 00001388/0000ffff at 20
action order 1: gact action drop
random type none pass val 0
index 1 ref 1 bind 1
これを設定すると、telnetで接続している192.168.33.10では192.168.33.11からの応答が返らなくなったように見えます。
hoge
192.168.33.11から見ると、なにも届いていないのですが。
ちなみに、この状態で改めてtelnetで接続しようとすると、当然つながりません。
$ curl telnet://192.168.33.11:5000
確認ができたので、フィルターを削除。
$ sudo tc filter del dev ens6 parent 1: pref 1
なくなりました。
$ sudo tc filter show dev ens6 parent 1:
qdiscも削除。
$ sudo tc qdisc del dev ens6 root
確認。
$ sudo tc qdisc show dev ens6 qdisc fq_codel 0: root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
これで最初の状態に戻りました。
192.168.33.11で192.168.33.10からのパケットをドロップする
今度は、192.168.33.11で192.168.33.10からのパケットをドロップしてみます。
相手はIPアドレスを直接指定してくるので、ens6を対象にしましょう。
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: ens5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:9f:9a:82 brd ff:ff:ff:ff:ff:ff
altname enp0s5
inet 192.168.121.142/24 metric 100 brd 192.168.121.255 scope global dynamic ens5
valid_lft 3481sec preferred_lft 3481sec
inet6 fe80::5054:ff:fe9f:9a82/64 scope link
valid_lft forever preferred_lft forever
3: ens6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:21:15:a1 brd ff:ff:ff:ff:ff:ff
altname enp0s6
inet 192.168.33.11/24 brd 192.168.33.255 scope global ens6
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe21:15a1/64 scope link
valid_lft forever preferred_lft forever
ens6の現在のqdiscを確認。
$ sudo tc qdisc show dev ens6 qdisc fq_codel 0: root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
今回はingressのqdiscを追加。prioは送信用のqdiscなので。
$ sudo tc qdisc add dev ens6 handle ffff: ingress
確認。
$ sudo tc qdisc show dev ens6 qdisc fq_codel 0: root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64 qdisc ingress ffff: parent ffff:fff1 ---------------- $ sudo tc qdisc show dev ens6 ingress qdisc ingress ffff: parent ffff:fff1 ----------------
フィルターを追加。
IPアドレス192.168.33.10から、宛先ポート5000へのパケットをドロップするフィルターを追加。
$ sudo tc filter add dev ens6 parent ffff: protocol ip u32 \
match ip src 192.168.33.10/32 \
match ip dport 5000 0xffff \
action drop
確認。
$ sudo tc filter show dev ens6 ingress
filter parent ffff: protocol ip pref 49152 u32 chain 0
filter parent ffff: protocol ip pref 49152 u32 chain 0 fh 800: ht divisor 1
filter parent ffff: protocol ip pref 49152 u32 chain 0 fh 800::800 order 2048 key ht 800 bkt 0 terminal flowid not_in_hw
match c0a8210a/ffffffff at 12
match 00001388/0000ffff at 20
action order 1: gact action drop
random type none pass val 0
index 1 ref 1 bind 1
$ sudo tc filter show dev ens6 parent ffff:
filter protocol ip pref 49152 u32 chain 0
filter protocol ip pref 49152 u32 chain 0 fh 800: ht divisor 1
filter protocol ip pref 49152 u32 chain 0 fh 800::800 order 2048 key ht 800 bkt 0 terminal flowid not_in_hw
match c0a8210a/ffffffff at 12
match 00001388/0000ffff at 20
action order 1: gact action drop
random type none pass val 0
index 1 ref 1 bind 1
telnetでの確認結果は同じなので省略します。
フィルターを削除。
$ sudo tc filter del dev ens6 parent ffff:
確認。
$ sudo tc filter show dev ens6 ingress
ingress qdiscも削除します。
$ sudo tc qdisc del dev ens6 ingress
確認。
$ sudo tc qdisc show dev ens6 qdisc fq_codel 0: root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
clsact qdiscで行う
もうひとつ、clsact qdiscで行ってみます。こちらはmanには載っていないようなんですよね…。
Ubuntu Manpage: tc - show / manipulate traffic control settings
net, sched: add clsact qdisc - kernel/git/torvalds/linux.git - Linux kernel source tree
clsact qdiscで送信も受信も扱えるようです。こちらは簡単に載せておきます。
192.168.33.10で192.168.33.11へのパケットをドロップする
clsactのqdiscを追加。
$ sudo tc qdisc add dev ens6 clsact
確認。
$ sudo tc qdisc show dev ens6 qdisc fq_codel 0: root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64 qdisc clsact ffff: parent ffff:fff1
フィルターを追加。egressの指定になります。
$ sudo tc filter add dev ens6 egress protocol ip u32 \
match ip dst 192.168.33.11/32 \
match ip dport 5000 0xffff \
action drop
確認。
$ sudo tc filter show dev ens6 egress
filter protocol ip pref 49152 u32 chain 0
filter protocol ip pref 49152 u32 chain 0 fh 800: ht divisor 1
filter protocol ip pref 49152 u32 chain 0 fh 800::800 order 2048 key ht 800 bkt 0 terminal flowid not_in_hw
match c0a8210b/ffffffff at 16
match 00001388/0000ffff at 20
action order 1: gact action drop
random type none pass val 0
index 1 ref 1 bind 1
telnetでの動作確認結果は省略します。
フィルターの削除。
$ sudo tc filter del dev ens6 egress
clsact qdiscの削除。
$ sudo tc qdisc del dev ens6 clsact
192.168.33.11で192.168.33.10からのパケットをドロップする
clsactのqdiscを追加。
$ sudo tc qdisc add dev ens6 clsact
確認。
$ sudo tc qdisc show dev ens6 qdisc fq_codel 0: root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64 qdisc clsact ffff: parent ffff:fff1
フィルターを追加。
$ sudo tc filter add dev ens6 ingress protocol ip u32 \
match ip src 192.168.33.10/32 \
match ip dport 5000 0xffff \
action drop
確認。
$ sudo tc filter show dev ens6 ingress
filter protocol ip pref 49152 u32 chain 0
filter protocol ip pref 49152 u32 chain 0 fh 800: ht divisor 1
filter protocol ip pref 49152 u32 chain 0 fh 800::800 order 2048 key ht 800 bkt 0 terminal flowid not_in_hw
match c0a8210a/ffffffff at 12
match 00001388/0000ffff at 20
action order 1: gact action drop
random type none pass val 0
index 1 ref 1 bind 1
telnetでの動作確認は省略します。
フィルターの削除。
$ sudo tc filter del dev ens6 ingress
clsact qdiscの削除。
$ sudo tc qdisc del dev ens6 clsact
こんなところでしょうか。
おわりに
tcコマンドでのパケットドロップを試してみました。
コマンド自体が不慣れなので、かなり手間取りました…。あまり使うことはないと思いますが、いざという時にこういう手数があった方が
便利なのかなと…。