CLOVER🍀

That was when it all began.

tcコマンドで特定の宛先への通信を遮断する(パケットをドロップする)

これは、なにをしたくて書いたもの?

今まで特定のポートへの通信を遮断する(パケットをドロップする)のには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

tc(8) classless qdiscs

確認。

$ 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

tc(8) classless qdiscs

確認。

$ 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コマンドでのパケットドロップを試してみました。

コマンド自体が不慣れなので、かなり手間取りました…。あまり使うことはないと思いますが、いざという時にこういう手数があった方が
便利なのかなと…。