UDPポートが開いているか確認する方法と、なぜ難しいのか

結論から言うと、UDPのポートが開いているかどうかは100%確実には確認できません。TCPのポート確認とは前提がまったく違うからです。それでも実用上「開いていそう」「閉じていそう」を判断する手段はいくつかあります。この記事では、まず何が難しいのかを説明したあと、確認手段を確実性の高い順に紹介します。

  • 一番確実:相手が応答を返すプロトコルなら、実際に問い合わせて応答を見る
  • 次に現実的:nmap -sU でスキャンする(要root)
  • 自分のサーバなら:待ち受けているプロセスをサーバ側で直接確認する
  • 補助:tcpdump でパケットが実際に届いたか見る

nc -vzu だけで判定するのはおすすめしません。理由は次の章で実際に確認します。

なぜUDPのポート確認はTCPより難しいのか

TCPは接続を始めるときに3ウェイハンドシェイク(SYN → SYN-ACK → ACK)を行います。ポートが開いていればSYN-ACKが返り、閉じていればRSTが返ります。どちらの返事も必ず来るので、「開いている/閉じている」をはっきり判定できます。

UDPにはこの仕組みがありません。UDPはコネクションレスなプロトコルで、データを送りつけるだけです。相手からの返事を待つという概念自体がプロトコルに組み込まれていません。そのため次のような、TCPには無い曖昧さが生まれます。

  • ポートが開いていても、無反応が正常です。待ち受けているアプリケーションが、受け取ったデータに対して何か返す作りになっていなければ、何も返ってきません。これは異常ではなく、UDPでは普通のことです。
  • ポートが閉じていれば、ICMP port unreachableが返ることがあります。宛先のOSは、送られたポートで待ち受けるプロセスが無いとき、送信元に向けて「ICMP Destination Unreachable(type 3)、code 3(port unreachable)」というエラーを返す仕様になっています(RFC 792)。
  • ただし、そのICMPは握りつぶされることが多いです。途中のファイアウォールがICMPをブロックしていたり、相手のOSがICMPエラーの送出をレート制限していたりすると、閉じているポートに送っても無反応になります。つまり無反応という結果だけでは、「開いていて応答が無い」のか「閉じているがICMPが届かない」のかを区別できません。

この非対称さのせいで、UDPのポートスキャンは原理的に「開いている」と断言しづらい結果になります。実際、定番のポートスキャナである nmap は、UDPスキャンの結果に openclosedfiltered に加えて open|filtered という、判定を保留した状態を用意しています。

open|filtered: Nmap places ports in this state when it is unable to determine whether a port is open or filtered. This occurs for scan types in which open ports give no response. The lack of response could also mean that a packet filter dropped the probe or any response it elicited.(Nmap公式マニュアルより)

「無反応だった」という事実だけでは、開いているのか、間にフィルタがあるのか分かりません。これがUDPのポート確認が難しい本質です。TCPの感覚のまま「pingみたいに一発で白黒つく」ものだと思っていると、ここでつまずきます。

nc -vzu の結果は鵜呑みにできない

UDPの疎通確認によく使われるのが nc(netcat)の -z(スキャンモード)と -u(UDP)を組み合わせた nc -vzu です。ですが、この結果は当てになりません。実際に確認してみます。

手元のmacOS(標準のnc、いわゆるOpenBSD系)で、まず12399番ポートでUDPの待ち受けを立てておきます。

# 別端末で待ち受け
nc -u -l 12399
$ nc -vzu -w2 127.0.0.1 12399
Connection to 127.0.0.1 port 12399 [udp/*] succeeded!

待ち受けがあるので「succeeded」と出ました。ここまでは期待どおりです。次に、何も待ち受けていない12400番ポートに対して、まったく同じコマンドを打ちます。

$ nc -vzu -w2 127.0.0.1 12400
Connection to 127.0.0.1 port 12400 [udp/*] succeeded!

何も待ち受けていないポートでも「succeeded」と出ました。これでは、その結果を見てもポートが実際に開いているかどうか判断できません。

なぜこうなるのか、tcpdump でパケットを覗いて確認します。何も待ち受けていないポート(12406番)に向けて nc -vzu を実行しながらキャプチャすると、次のように記録されました。

$ sudo tcpdump -i lo0 -n '(udp port 12406) or icmp'
17:16:29.931248 IP 127.0.0.1.64824 > 127.0.0.1.12406: UDP, length 1
17:16:29.931262 IP 127.0.0.1.64824 > 127.0.0.1.12406: UDP, length 1
17:16:29.931267 IP 127.0.0.1.64824 > 127.0.0.1.12406: UDP, length 1
17:16:29.931271 IP 127.0.0.1.64824 > 127.0.0.1.12406: UDP, length 1
17:16:29.931276 IP 127.0.0.1 > 127.0.0.1: ICMP 127.0.0.1 udp port 12406 unreachable, length 36

nc は1バイトの探りパケットを4回送っています。そして最後の行を見ると、OSはちゃんと「ICMP port unreachable」を返しています。つまりこの環境では「閉じている」ことを示す情報がちゃんと届いているのに、nc 側の画面には「succeeded」としか出ませんでした。nc -z は、UDPに対しては送信(writeシステムコール)がエラーにならなければ「succeeded」と表示するだけで、ICMPの応答を見て判定しているわけではないようです。macOSの man nc にも -z は「スキャンする」としか書かれておらず、UDPでの判定方法については説明がありません。

結論として、nc -vzu の「succeeded」は「UDPパケットの送信自体は失敗しなかった」という程度の意味しかなく、相手のポートが実際に開いているかどうかの判定には使えません。nc のオプション自体をもっと詳しく知りたい場合は netcat(nc)でUDP通信する使い方まとめ を参照してください。

nmap -sU で確認する

もう少し踏み込んで確認したいときは nmap のUDPスキャン(-sU)を使います。ICMPの応答を実際に解釈して open / closed / filtered / open|filtered を判定してくれます。

root権限が必要です。一般ユーザーのまま実行すると、こう言われて終了します。

$ nmap -sU -p 12401 127.0.0.1
You requested a scan type which requires root privileges.
QUITTING!

sudo を付けて実行します。

sudo nmap -sU -p 53,123,161 example.com

Nmap公式マニュアルによると、UDPスキャンは各ポートにUDPパケットを送り、返ってきた内容によって次のように状態を割り当てます。

応答判定される状態
そのポートから何かUDPの応答が返ってきた(まれ)open
ICMP port unreachable(type 3, code 3)closed
その他のICMP到達不能エラー(type 3, code 1, 2, 9, 10, 13など)filtered
再送しても応答が無いopen|filtered

filtered はさらに、type 3 code 13(通信が管理上禁止されている)のような明示的な拒否ICMPが返ってきた場合を含みます。ただしマニュアルも「単にプローブを黙って捨てるフィルタの方がずっと多い」と書いており、応答が無いケースの大半は結局 open|filtered に落ち着きます。

判定の根拠を知りたいときは --reason を付けると、どのパケット(ICMPの種類など)で状態が決まったかが表示されます。

sudo nmap -sU -p 53,123,161 --reason example.com

open|filtered のまま判断がつかない場合、-sV(サービス/バージョン検出)を併用すると、実際にそのサービス向けのプローブを送って応答を引き出せることがあり、open かどうかの切り分けに役立ちます。

sudo nmap -sU -sV -p 53,123,161 example.com

UDPスキャンが遅いのもこの仕組みが理由です。Nmap公式マニュアルは、多くのOSがICMP到達不能メッセージの送出をレート制限しており(Linuxは1秒あたり1個までなど)、Nmap側もそれを検出して送信間隔を落とすため、65536ポート全体のスキャンには18時間以上かかりうると説明しています。ポートを絞って(-p)使うのが現実的です。

アプリ層で確認する:一番確実な方法

ここまでの方法はいずれも「開いているらしい」までしか分かりません。一番確実なのは、そのポートで動いているはずのプロトコルの正規のリクエストを実際に送り、正規の応答が返ってくるかを見ることです。相手アプリケーションが正しく応答すれば、ポートが開いていて、かつサービスが正常に動いていることまで確認できます。

身近な例はDNS(UDP/53)です。dig で問い合わせて、応答が返ってくれば疎通しています。

$ dig +short example.com A
104.20.23.154
172.66.147.243

自作のUDPサービスを確認したいときも考え方は同じです。そのプロトコルが期待するリクエストを送り、期待する応答が返ってくるかをクライアント側で見てください。nc -u でも生のバイト列を送ることはできますが、相手が期待する形式のデータを送らないと応答は返りません。手っ取り早く送受信だけ試したい場合の nc -u の基本は コマンドラインでUDP通信を試す方法 を参照してください。

サーバ側で待ち受け状態を確認する

自分が管理しているサーバなら、外から探るより、サーバ上でプロセスが実際にそのポートをbindして待っているかを見るほうが確実です。コマンドはOSによって違います。

Linux: ss -ulnp

sudo ss -ulnp

-u でUDP、-l で待ち受けソケットのみ、-n で名前解決をせず、-p でプロセス名/PIDまで表示します。手元のmacOSには ss コマンドが無いため実機確認はしていませんが、オプションの意味は ss の公式man(iproute2)で確認しています。

macOS: lsof -iUDP -nP

手元で12402番ポートに nc -u -l の待ち受けを立てて確認すると、次のように表示されます。

$ lsof -iUDP -nP
COMMAND     PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
nc        99627   mi    3u  IPv4 0x7bfb8db5b7850a5b      0t0  UDP *:12402

UDP *:12402 の行が、そのポートをOSレベルで誰かが待ち受けていることを示しています。

Windows: Get-NetUDPEndpoint / netstat -an -p UDP

Windowsでは sslsof も使えません。PowerShellの Get-NetUDPEndpointnetstat -an -p UDP で待ち受けポートを確認します。UDPの送受信自体(Test-NetConnection がUDPに使えない点も含む)は WindowsでUDP通信・ポート確認をする方法 にまとめているので、そちらを参照してください。

共通の注意点

いずれの方法も、あくまで「自分のマシンの中でプロセスがポートを掴んでいるか」の確認です。外部からそのポートに実際に届くかどうかは、途中のファイアウォールやルーターの設定に左右されるので別問題です。

パケットが実際に届いたかは tcpdump で見る

「送ったはずなのに応答が無い」ときの切り分けには tcpdump が有効です。受信側のサーバで対象ポートを監視しておき、送信側からデータを送ってパケットが到達しているかを直接確認します。

sudo tcpdump -i eth0 -n udp port 12345

手元でも実際に送受信して確認しました。12404番ポートに何も待ち受けていない状態で tcpdump を仕掛けてから nc -u でデータを送ると、パケット自体はちゃんと出ていくのが見えます。

$ sudo tcpdump -i lo0 -n udp port 12404
17:15:39.785945 IP 127.0.0.1.49984 > 127.0.0.1.12404: UDP, length 6

送信元がパケットを送り出せたことと、相手に届いて処理されたことは別です。受信側でパケットが記録されているなら、少なくともネットワーク経路上は届いています。そこから先、アプリケーションが正しく処理しているかどうかは、アプリ層での確認(前述)で追ってください。

まとめ

確認したいこと使うもの分かること限界
とりあえず試すnc -vzuUDPパケットの送信自体が失敗しなかったか判定に使えない(常にsucceededになりがち)
ある程度体系的に確認nmap -sU(要root)open/closed/filtered/open|filteredの判定応答が無いとopen\|filtered止まり。遅い
一番確実な確認アプリ層での実際のやり取り(digなど)サービスが本当に動いているかそのプロトコルの正規リクエストを知っている必要がある
自分のサーバの待受確認ss -ulnp(Linux) / lsof -iUDP -nP(macOS) / Get-NetUDPEndpoint(Windows)プロセスがポートを掴んでいるか外部からの到達性は別問題
パケットが届いたかの確認tcpdumpネットワーク経路上パケットが到達したかアプリの処理結果までは分からない

あわせて読みたい

さいごに

  • UDPはコネクションレスなので、「無反応」だけでは開いているか閉じているか判定できません。これがTCPとの根本的な違いです。
  • nc -vzu の「succeeded」は送信が失敗しなかっただけの意味で、ポートの状態を保証しません。
  • 体系的に確認したいなら nmap -sU、確実に確認したいならアプリ層で実際にやり取りする、自分のサーバなら ss/lsof で待受を直接見る、と使い分けてください。