- ネットワーク
UDPとは|TCPとの違い・ICMPとの関係を「届かない」実物で解説
結論
UDPは、接続・再送・順序の保証をしない代わりに、8バイトのヘッダーで「1回送って終わり」にできる軽いプロトコルです。確実に届けたいならTCP、速さと軽さを優先し、欠けても次のデータで補えるならUDPを使います。
この記事でわかること
- 同じ1行を送ったとき、TCPは8パケット・UDPは1パケットになるtcpdumpの原文
- ip netnsで損失30%と遅延を起こし、UDPで「届かない番号」「順番の入れ替わり」が出た実物
- DNS・QUICでUDPが選ばれる理由と、ping・tracerouteで見るICMPとUDPの関係
TCPは「つないでから送り、届いたか確かめ、足りなければ送り直す」プロトコルで、UDPは「宛先とポートを付けて投げる」だけのプロトコルです。どちらもIPの上で動き、アプリケーションをポート番号で区別します。
flowchart TD
D[送りたいデータ] --> U[UDP<br>1 個送って終わり]
D --> T[TCP<br>SYN・SYN+ACK・ACK]
U --> U2[届いたかは分からない<br>順番も保証しない]
T --> T2[データ → ACK<br>無ければ再送]
T2 --> T3[全部・順番どおり]
よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| UDPとは | 接続せず、届いたか確かめずに送るトランスポート層のプロトコル | UDPとは |
| UDPとTCPの違い | 接続・再送・順序・ヘッダーの大きさが違う | UDPとTCPの違い |
| 本当に届かないのか | 損失30%の経路で20個中7個が消え、順番も入れ替わった | 届かない・順番が変わる |
| UDPは速いのか | 軽いが「必ず速い」ではない。受け取る側が遅いと手元でも半分消えた | UDPは必ず速いのか |
| UDPが使われるところ | DNS・DHCP・NTP・動画・音声・ゲーム・QUIC(HTTP/3) | UDPが使われる理由 |
| ICMPとは | IPのエラーと診断を伝えるプロトコル。pingとtracerouteが使う | ICMPとは |
| UDPとICMPの関係 | UDPの宛先ポートが閉じていると、ICMPの「port unreachable」が返る | ICMPとは |
| どちらを使うか | 欠けて困るならTCP、遅れて困るならUDP | 使い分け判断表 |
確認環境:Ubuntu 24.04.5 LTS(Linux 6.18)の上で、nc(OpenBSD netcat 1.226)・Python 3.13.18・tcpdump 4.99.4・ss(iproute2 6.1.0)を 127.0.0.1 で動かしました。損失・遅延・順番の入れ替えは、ip netns で作った3つのネットワーク名前空間(送る側 10.213.0.1、中継 10.213.0.254/10.213.1.254、受け取る側 10.213.1.2)の中だけで起こし、損失は中継の nftables 1.0.9(numgen random)、遅延と入れ替えは中継の tc(HTB)で作っています。ping は iputils 20240117、traceroute は 2.1.5、DNS の応答は dnsmasq 2.91 を名前空間の中に立てて確かめました。ホストのネットワーク設定は変えていません。UDP・TCP・ICMP・QUIC の仕様は RFC 768・9293・792・9000、Linux の ICMP の送信制限はカーネルのドキュメントで確かめています。図は mermaid 11.16.0 で描画を確かめています(確認日 2026年10月29日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「UDPとは|TCPとの違い・ICMPとの関係を「届かない」実物で解説」
UDPとは(User Datagram Protocol)
この節では、UDPが何をして、何をしないプロトコルなのかが分かります。
UDP(User Datagram Protocol)は、IPの上でアプリケーション同士がデータをやり取りするためのトランスポート層のプロトコルで、「データグラム」と呼ぶひとかたまりを、接続の手続きなしに送ります。 仕様は RFC 768(1980年)で、本文はわずか3ページです。IPのプロトコル番号は17、TCPは6、ICMPは1です。
UDPがすること
ポート番号で届け先のアプリケーションを区別する
長さとチェックサムで、壊れたデータグラムを捨てる
1回の送信を1つのデータグラムとして、境界を保ったまま届ける
UDPがしないこと
接続の確立(相手がいるかどうかを確かめない)
届いたかの確認と再送
順番の並べ直し・重複の除去・流量の調整
「しないこと」が多いのは欠点ではなく設計です。確認や再送を省いた分、ヘッダーが小さく、1往復の待ち時間が無く、サーバーは相手ごとの接続の状態を持たずに済みます。足りない機能は、必要なアプリケーションが自分で足します(QUICがその代表です)。TCP側の仕組みは、関連記事「TCPの落とし穴8選|TIME_WAIT・40ms遅延の正体」で扱っています。
UDPとTCPの違い(一覧表)
この節では、UDPとTCPの違いが一覧で分かります。
| 項目 | UDP | TCP |
|---|---|---|
| 通信の形 | コネクションレス(つながずに送る) | コネクション型(3ウェイハンドシェイクで接続) |
| 届いたかの確認 | しない | ACKで確認し、届かなければ再送 |
| 順番の保証 | しない(入れ替わることがある) | シーケンス番号で並べ直す |
| データの単位 | データグラム(1回の送信=1個、境界が残る) | バイトストリーム(境界は残らない) |
| ヘッダー | 8バイト固定 | 20バイト以上(手元では32バイト) |
| 流量の調整 | しない(送りすぎると捨てられる) | ウィンドウと輻輳制御で調整する |
| サーバーが持つ状態 | ほぼ無し(ssでは UNCONN) | 接続ごとに持つ(LISTEN・ESTAB など) |
| 代表的な用途 | DNS・DHCP・NTP・音声・動画・ゲーム・QUIC | HTTP/1.1・HTTP/2・SSH・メール・ファイル転送 |
手紙にたとえると
見分ける問い:「1枚欠けたら困るか、1枚遅れたら困るか」
7層のどこに位置するかは、関連記事「OSI参照モデルとは|7層の早見表とTCP/IP対応・パケット実例」で扱っています。
ncで見比べる:同じ1行がTCPは8パケット・UDPは1パケット
この節では、同じ「hello」を送ったときに、TCPとUDPで実際に何が流れるのかが tcpdump の原文で分かります。
127.0.0.1 で nc の待ち受けを作り、hello を1行送って tcpdump で見ました。TCPは8パケット、UDPは1パケットでした。
# TCP:待ち受けと送信
$ nc -l 127.0.0.1 45613 &
$ printf 'hello\n' | nc -N 127.0.0.1 45613
# UDP:-u を付けるだけ
$ nc -lu 127.0.0.1 45614 &
$ printf 'hello\n' | nc -u -w1 127.0.0.1 45614
$ tcpdump -i lo -nn 'port 45613' # TCP(Flags の S=SYN . =ACK P=PUSH F=FIN)
127.0.0.1.43796 > 127.0.0.1.45613: Flags [S], seq 2863601208, win 65495, options [mss 65495,sackOK,TS val 3481000855 ecr 0,nop,wscale 10], length 0
127.0.0.1.45613 > 127.0.0.1.43796: Flags [S.], seq 2157785620, ack 2863601209, win 65483, options [...], length 0
127.0.0.1.43796 > 127.0.0.1.45613: Flags [.], ack 1, win 64, options [...], length 0
127.0.0.1.43796 > 127.0.0.1.45613: Flags [P.], seq 1:7, ack 1, win 64, options [...], length 6
127.0.0.1.45613 > 127.0.0.1.43796: Flags [.], ack 7, win 64, options [...], length 0
127.0.0.1.43796 > 127.0.0.1.45613: Flags [F.], seq 7, ack 1, win 64, options [...], length 0
127.0.0.1.45613 > 127.0.0.1.43796: Flags [F.], seq 1, ack 8, win 64, options [...], length 0
127.0.0.1.43796 > 127.0.0.1.45613: Flags [.], ack 2, win 64, options [...], length 0
$ tcpdump -i lo -nn 'port 45614' # UDP
127.0.0.1.47186 > 127.0.0.1.45614: UDP, length 6
1接続に3パケット
SYN → SYN+ACK → ACK。データを1バイトも送る前に1往復が要る。
2データに2パケット
6バイト(hello と改行)を送り、受け取った側が ack 7 で「7バイト目まで受け取った」と返す。
3切断に3パケット
FIN → FIN+ACK → ACK。UDPには始まりも終わりも無く、1個のデータグラムだけが流れた。
「UDPは軽い」の中身は、この往復の差です。DNSのように問い合わせ1個・答え1個で終わる通信では、TCPだと3往復以上、UDPなら1往復で済みます。
ヘッダーの大きさ:UDPは8バイト・TCPは20バイト以上
この節では、UDPとTCPのヘッダーの中身と、同じ6バイトを送ったときのパケット全体の大きさが分かります。
UDPのヘッダーは、送信元ポート・宛先ポート・長さ・チェックサムの4つで、各2バイト、合わせて8バイトです。 TCPは最小20バイトで、タイムスタンプなどのオプションが付くと増えます。上の tcpdump を -v で見ると、IPパケット全体の大きさが出ます。
$ tcpdump -i lo -nn -v 'port 45616 or port 45617'
IP (tos 0x0, ttl 64, id 54931, offset 0, flags [DF], proto TCP (6), length 58)
127.0.0.1.42598 > 127.0.0.1.45616: Flags [P.], ..., options [nop,nop,TS val 2832199986 ecr 2617674671], length 6
IP (tos 0x0, ttl 64, id 28271, offset 0, flags [DF], proto UDP (17), length 34)
127.0.0.1.40190 > 127.0.0.1.45617: UDP, length 6
| UDPヘッダーの欄 | 大きさ | 役割 |
|---|---|---|
| 送信元ポート | 2バイト | 返事の宛先。使わないなら0でもよい(RFC 768) |
| 宛先ポート | 2バイト | 届け先のアプリケーション |
| 長さ | 2バイト | ヘッダーを含むデータグラムの長さ(最小8) |
| チェックサム | 2バイト | 壊れていないかの検査。IPv4では0(省略)も許される。IPv6では必須 |
TCPヘッダーには、このほかにシーケンス番号・確認応答番号・フラグ・ウィンドウサイズが入ります。手元のTCPは「TS val … ecr …」のタイムスタンプオプション(12バイト)が付いて32バイトでした。
PythonでUDPとTCPを送受信する(最小のコード)
この節では、UDPとTCPのサーバーとクライアントを Python で書くと何が違うのかが、動かした結果で分かります。
違いは SOCK_DGRAM か SOCK_STREAM かと、TCPにだけ listen・accept・connect があることです。
# udp_server.py
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
s.bind(("127.0.0.1", 45620))
while True:
data, addr = s.recvfrom(65535) # 1 データグラム = 1 回の recvfrom
print(f"受信 {len(data)} バイト from {addr}: {data!r}")
s.sendto(data.upper(), addr) # 相手へそのまま返す
# udp_client.py
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(2)
s.sendto(b"hello udp", ("127.0.0.1", 45620)) # connect() も accept() も無い
data, addr = s.recvfrom(65535)
print("応答:", data, addr)
# tcp_server.py
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # TCP
s.bind(("127.0.0.1", 45621)); s.listen()
while True:
conn, addr = s.accept() # 接続(3ウェイハンドシェイク)が済んでから返る
with conn:
data = conn.recv(65535) # バイト列の一部だけが返ることもある
conn.sendall(data.upper())
# tcp_client.py
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 45621)) # ここで接続を確立する
s.sendall(b"hello tcp")
print("応答:", s.recv(65535))
$ python3 udp_client.py
応答: b'HELLO UDP' ('127.0.0.1', 45620)
$ python3 tcp_client.py
応答: b'HELLO TCP'
待ち受けが無いときの違い
サーバーを止めて同じクライアントを動かすと、エラーの出方が違いました。
$ python3 udp_client.py # sendto() は成功し、返事を待って時間切れになる
data, addr = s.recvfrom(65535)
TimeoutError: timed out
$ python3 tcp_client.py # connect() の時点で断られる
s.connect(("127.0.0.1", 45621))
ConnectionRefusedError: [Errno 111] Connection refused
UDPの sendto() は、相手がいなくても成功します。 送った後に何が起きたかを UDP 自身は知らないためです。相手がいないことを知る手がかりは、次の ICMP の節で扱う「port unreachable」だけで、それも connect() したソケットでないと受け取れません。
データグラムの境界が残る・残らない
3つのメッセージ aaa・bbb・ccc を続けて送り、受け取る側が少し待ってから読んだ結果です。
UDP recvfrom -> b'aaa'
UDP recvfrom -> b'bbb'
UDP recvfrom -> b'ccc'
TCP recv -> b'aaabbbccc'
UDPは1回の送信が1回の受信に対応します。TCPはバイトの流れなので、3回の送信が1回の recv にまとまって返りました。TCPでメッセージの区切りが要るときは、長さを先頭に付けるか改行で区切るなど、アプリケーション側で決めます。
UDPは届かない・順番が変わる:ip netnsで損失30%を起こした結果
この節では、本当にUDPのデータが消えるのか、順番が変わるのかが、損失と遅延を作った経路で動かした結果で分かります。
中継の名前空間で、受け取る側へ向かうパケットを30%の確率で捨てる規則を入れ、1〜20の番号を付けた1,000バイトのメッセージを20ミリ秒おきに20個送りました。UDPは7個消え、TCPは20個すべて届きました。
# 中継(t13r)の中だけに入れた規則。ホストの設定は変えていない
$ ip netns exec t13r nft add table ip t13
$ ip netns exec t13r nft 'add chain ip t13 lossy { type filter hook forward priority 0; }'
$ ip netns exec t13r nft add rule ip t13 lossy ip daddr 10.213.1.2 numgen random mod 100 '<' 30 counter drop
===== 損失 30%
UDP 届いた順: [1, 3, 5, 6, 8, 10, 12, 13, 14, 15, 16, 18, 20]
UDP 届いた数: 13/20 失われた番号: [2, 4, 7, 9, 11, 17, 19] 順番の入れ替わり: 0 箇所
TCP 届いた順: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
TCP 届いた数: 20/20 受信にかかった時間: 0.41 秒
$ ip netns exec t13a ping -c 20 -i 0.2 10.213.1.2
20 packets transmitted, 12 received, 40% packet loss, time 3875ms
送る側のプログラムは、UDPでもTCPでも「20個送信 完了」と表示して終わりました。UDPでは、送る側は7個消えたことを知る手段がありません。 ping(ICMP)も同じ経路なので、40%が返ってきませんでした。
TCPはどう直したか:再送とSACKの原文
同じ損失30%で、送る側の tcpdump を見ると、TCPが足りない部分をどう埋めたかが分かります。
10.213.0.1.47270 > 10.213.1.2.45631: Flags [P.], seq 7:9, ack 1, ..., length 2
10.213.1.2.45631 > 10.213.0.1.47270: Flags [.], ack 9, ...
10.213.0.1.47270 > 10.213.1.2.45631: Flags [P.], seq 9:11, ack 1, ..., length 2 ← 途中で捨てられた
(0.207 秒、ACK が来ない)
10.213.0.1.47270 > 10.213.1.2.45631: Flags [P.], seq 11:37, ack 1, ..., length 26 ← 待っている間に溜まった分
10.213.1.2.45631 > 10.213.0.1.47270: Flags [.], ack 9, ..., options [...,sack 1 {11:37}] ← 「9 まで届いた。11〜37 も持っている」
10.213.0.1.47270 > 10.213.1.2.45631: Flags [P.], seq 9:11, ack 1, ..., length 2 ← 9:11 だけ再送
10.213.1.2.45631 > 10.213.0.1.47270: Flags [.], ack 37, ... ← 37 まで揃った
- 受け取る側は、抜けた
seq 9:11より後ろのデータを捨てずに持ち、SACK(選択的確認応答)で「11〜37 は持っている」と伝えました。 送る側は抜けた2バイトだけを送り直しています。 - その間、アプリケーションには何も渡されません。抜けが埋まるまで0.2秒止まり、揃った瞬間にまとめて渡されました。TCPの「確実」は「待つ」ことで成り立っています。
遅延と順番の入れ替わり
中継の tc(HTB)で、IPヘッダーの識別番号が奇数のパケットだけを 100kbit/s の遅いクラスに回し、半分が遅れて届く経路を作りました。
# 中継(t13r)の受け取る側向きのインターフェースだけに付けた
$ ip netns exec t13r tc qdisc add dev v13rb root handle 1: htb default 10
$ ip netns exec t13r tc class add dev v13rb parent 1: classid 1:10 htb rate 100mbit
$ ip netns exec t13r tc class add dev v13rb parent 1: classid 1:20 htb rate 100kbit ceil 100kbit
$ ip netns exec t13r tc filter add dev v13rb parent 1: protocol ip prio 1 u32 match u16 0x0001 0x0001 at 4 flowid 1:20
===== 半分のパケットを遅い経路に回す
UDP 届いた順: [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 10, 14, 15, 17, 16, 20, 18, 19]
UDP 届いた数: 20/20 失われた番号: [] 順番の入れ替わり: 3 箇所
TCP 届いた順: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
TCP 届いた数: 20/20 受信にかかった時間: 0.63 秒(通常は 0.41 秒)
UDPは、10 が 13 の後に、16 が 17 の後に届きました。 送った順に処理したいアプリケーションは、自分で番号を付けて並べ直す必要があります。TCPは並べ直して順に渡した代わりに、0.2秒長くかかりました。
損失
UDPは消えた番号が空く。TCPは再送で埋めるが、埋まるまで後ろのデータも渡されない。
20個中7個が消えた
順番
UDPは届いた順のまま。TCPは並べ直す。
3箇所で入れ替わった
時間
UDPは遅れても止まらない。TCPは揃うまで待つ。
0.41秒 → 0.63秒
送る側の見え方
UDPもTCPも送信は「完了」と出る。違いは受け取る側にだけ現れる。
送信側のログは同じ
UDPは必ず速いのか:20万個送って半分が消えた実測
この節では、「UDPは速い」がどこまで本当なのかが、127.0.0.1 での実測で分かります。
100バイトのメッセージを20万個、できる限り速く送りました。UDPは受け取る側の処理が追いつかず、損失の無いループバックでも 48.8% が消えました。 TCPは流量を調整しながら全部届け、送信にかかった時間も短かったです。
$ python3 blast.py
UDP: 200000 個を 0.82 秒で送信、届いた数 102318 (51.2%)
TCP: 200000 個を 0.38 秒で送信、届いた数 200000 (100.0%)
$ nstat -az | grep Udp
UdpInErrors 97682
UdpRcvbufErrors 97682 ← 受信バッファがあふれて捨てた数(200000 − 102318 と一致)
- UDPの「速い」は、往復の待ちとヘッダーが小さいという意味です。受け取る側のバッファ(既定
net.core.rmem_default= 212,992 バイト)があふれれば、ネットワークの途中でなくても捨てられます。 - TCPが速かったのは、カーネルが小さな送信をまとめて大きなセグメントにし、相手のウィンドウに合わせて送ったためです。小さなメッセージを大量に送る用途では、UDPが速いとは限りません。
- UDPで送る側は、送る速さを自分で抑える必要があります。流量の調整も「しないこと」の1つです。
UDPが使われる理由:DNS・動画・ゲーム・QUIC
この節では、どんな通信がUDPを選び、なぜ選ぶのかが分かります。
共通するのは「1往復で終わる」か「遅れたデータに価値が無い」かのどちらかです。
| 用途 | UDPを選ぶ理由 | 欠けたときの扱い |
|---|---|---|
| DNS(名前解決) | 問い合わせ1個・答え1個。接続の往復が無駄 | 時間切れで問い合わせ直す。答えが512バイトを超えるとTCPに切り替える |
| DHCP | IPアドレスが決まる前に、ブロードキャストで送る必要がある | 再送はDHCP自身が行う |
| NTP(時刻合わせ) | 往復の時間そのものを測るので、再送で遅れた応答は役に立たない | 次の問い合わせで補う |
| 音声・動画の通話(RTP) | 0.1秒前の音声を再送されても再生に間に合わない | 欠けた分は飛ばす・補間する |
| オンラインゲーム | 位置の情報は最新の1個だけが要る | 次の更新で上書きされる |
| QUIC(HTTP/3) | 再送・順序・暗号を自分で持ち、1つの損失で全部が止まるのを避ける | ストリーム単位で再送する |
DNSの問い合わせを実際に流す
名前空間の中に dnsmasq を立て、Python で組み立てたDNSの問い合わせをUDPの53番へ送りました。
q = struct.pack("!HHHHHH", 0x1234, 0x0100, 1, 0, 0, 0) # ID, 再帰要求, 質問 1 件
q += b"".join(bytes([len(p)]) + p.encode() for p in name.split(".")) + b"\0"
q += struct.pack("!HH", 1, 1) # A レコード, IN
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.settimeout(2)
s.sendto(q, (server, 53)) # UDP 53 番へ 1 データグラム
r, _ = s.recvfrom(512)
$ ip netns exec t13a python3 dnsq.py www.example.test 10.213.1.2
質問 34 バイト → 応答 50 バイト / 応答の末尾 4 バイト(IPv4): 10.213.1.77
$ tcpdump -i v13a -nn 'port 53'
10.213.0.1.43039 > 10.213.1.2.53: 4660+ A? www.example.test. (34)
10.213.1.2.53 > 10.213.0.1.43039: 4660* 1/0/0 A 10.213.1.77 (50)
パケットは2個だけです。 TCPなら接続と切断で少なくとも7個増えます。DNSが「普段はUDP、大きな応答やゾーン転送はTCP」なのはこのためで、ファイアウォールで UDP の53番だけ開けていると、TCPに切り替わる応答が通らなくなります。
QUICとHTTP/3:UDPの上にTCPの機能を作り直した
QUIC(RFC 9000)は、UDPの上で、接続・再送・順序・流量の調整・TLS 1.3 の暗号化をまとめて行うプロトコルで、HTTP/3(RFC 9114)がその上で動きます。TCPではなくUDPを土台にした理由は、カーネルを変えずにユーザー空間で改良できることと、TCPで1つのパケットが欠けると後続の全データが待たされる(上の再送の原文のとおり)問題を、ストリームごとの再送で避けるためです。
| HTTPの版 | トランスポート | 接続の確立 |
|---|---|---|
| HTTP/1.1 | TCP(+TLS) | TCP 1往復 + TLS 1〜2往復 |
| HTTP/2 | TCP(+TLS) | 同上。1接続で多重化する |
| HTTP/3 | QUIC(UDP 443番) | QUIC が暗号化と接続を1往復で済ませる |
手元の curl 8.5.0(Ubuntu)は HTTP/3 を含まない構成だったため、HTTP/3 の通信は公式の仕様で確かめています。
UDPとTCPのポート一覧とファイアウォール
この節では、UDPとTCPのどちらで動くサービスが何番を使うかと、ポートの指定で間違えやすい点が分かります。
ポート番号はUDPとTCPで別の空間です。 「53番」と言うだけでは足りず、UDPの53番とTCPの53番は別の口です。
| ポート | サービス | 使うプロトコル |
|---|---|---|
| 53 | DNS | UDPとTCPの両方 |
| 67・68 | DHCP(サーバー・クライアント) | UDP |
| 123 | NTP | UDP |
| 161・162 | SNMP・SNMPトラップ | UDP |
| 443 | HTTPS(HTTP/1.1・2)/HTTP/3 | TCP/UDP(QUIC) |
| 514 | syslog | UDP(TCP版もある) |
| 22・25・80・110・143 | SSH・SMTP・HTTP・POP3・IMAP | TCP |
| 33434〜 | traceroute の探り(Linux の既定) | UDP |
どのポートを誰が使っているかは ss で見ます。UDPの待ち受けには状態がありません。
$ ss -tuln
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
udp UNCONN 0 0 127.0.0.1:45620 0.0.0.0:*
tcp LISTEN 0 128 127.0.0.1:45621 0.0.0.0:*
$ ss -tunp # TCP は接続ごとに ESTAB の行が増える。UDP には接続の行が無い
tcp ESTAB 0 0 127.0.0.1:51350 127.0.0.1:45621 users:(("python3",pid=17882,fd=3))
tcp ESTAB 0 0 127.0.0.1:45621 127.0.0.1:51350 users:(("python3",pid=17877,fd=4))
- 1ファイアウォールの規則は UDP と TCP を別に書くDNS は UDP 53 と TCP 53 の両方。片方だけだと大きな応答が通らない
- 2UDP の疎通は「応答が返るか」でしか分からないnc -zv は TCP では refused と出るが、UDP では何も出ずに終わった(下の表)
- 3ICMP を全部止めないport unreachable や分割が必要の通知が届かず、原因が見えなくなる
- 4ss -tuln で待ち受けを確かめるu が UDP、t が TCP。UDP は UNCONN と表示される
ポート番号の全体の一覧は、関連記事「ポート番号一覧【2026年版】well-knownポートと確認コマンドの早見表」で扱っています。
ICMPとは:ping・traceroute・到達不能とUDPの関係
この節では、ICMPが何を運ぶプロトコルで、UDPの通信とどう関わるのかが、名前空間の中で流した原文で分かります。
ICMP(Internet Control Message Protocol)は、IPの通信で起きたエラーの通知と、疎通の診断を運ぶプロトコルです(RFC 792)。 UDPやTCPと同じくIPの上で動きますが、アプリケーションのデータを運ぶものではなく、ポート番号もありません。
| タイプ/コード | 意味 | 手元で見た場面 |
|---|---|---|
| 8 → 0 | Echo(要求)→ Echo Reply(応答) | ping |
| 3/0 | Destination Unreachable:net unreachable | 経路の無い宛先に ping |
| 3/1 | host unreachable | 同じLANにいない相手に ping |
| 3/3 | port unreachable | 閉じたUDPポートへ送った・traceroute の最後 |
| 3/4 | fragmentation needed and DF set | MTUより大きいパケットをDF付きで送ったとき(経路MTU探索) |
| 11/0 | Time Exceeded:TTLが0になった | traceroute の途中 |
pingとtracerouteの原文
送る側の名前空間(10.213.0.1)から、中継(10.213.0.254)を挟んだ 10.213.1.2 へ traceroute と ping を行い、同時に tcpdump で見ました。
$ ip netns exec t13a traceroute -n -q 1 10.213.1.2
traceroute to 10.213.1.2 (10.213.1.2), 30 hops max, 60 byte packets
1 10.213.0.254 0.043 ms
2 10.213.1.2 0.010 ms
$ ip netns exec t13a ping -c 2 10.213.1.2
64 bytes from 10.213.1.2: icmp_seq=1 ttl=63 time=0.025 ms
64 bytes from 10.213.1.2: icmp_seq=2 ttl=63 time=0.043 ms
$ tcpdump -i v13a -nn 'icmp or udp'
10.213.0.1.48036 > 10.213.1.2.33434: UDP, length 32 ← traceroute の探り(TTL=1)
10.213.0.254 > 10.213.0.1: ICMP time exceeded in-transit, length 68 ← 中継が TTL 切れを通知
10.213.0.1.51787 > 10.213.1.2.33435: UDP, length 32 ← 探り(TTL=2)
10.213.1.2 > 10.213.0.1: ICMP 10.213.1.2 udp port 33435 unreachable, length 68 ← 終点に着いた
10.213.0.1 > 10.213.1.2: ICMP echo request, id 17901, seq 1, length 64 ← ping
10.213.1.2 > 10.213.0.1: ICMP echo reply, id 17901, seq 1, length 64
traceroute は UDP を送っている
Linux の traceroute は既定で、33434番から1つずつ増やしたUDPポートへ、TTLを1・2・3…と増やしながら送る。TCPでもICMPでもない。
途中の機器は ICMP Time Exceeded を返す
TTLが0になった機器が、自分のアドレスを送信元にして返す。これが1行目の 10.213.0.254。
終点は ICMP Port Unreachable を返す
誰も待ち受けていないUDPポートに届いたので、終点が「そのポートは閉じている」と返す。これで終点に着いたと分かる。
ping は Echo と Echo Reply
ttl=63 は、中継を1つ通って64から1減ったことを示す。
到達不能の3種類
$ ip netns exec t13a ping -c 3 10.213.0.99 # 同じ LAN にいない相手(ARP の応答が無い)
From 10.213.0.1 icmp_seq=2 Destination Host Unreachable
From 10.213.0.1 icmp_seq=3 Destination Host Unreachable
3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 2038ms
$ ip netns exec t13a ping -c 2 10.213.9.9 # 中継に経路が無い
From 10.213.0.254 icmp_seq=1 Destination Net Unreachable
From 10.213.0.254 icmp_seq=2 Destination Net Unreachable
$ python3 -c "...sendto(b'hi', ('10.213.1.2', 9999))" # 閉じた UDP ポート
10.213.0.1.43496 > 10.213.1.2.9999: UDP, length 2
10.213.1.2 > 10.213.0.1: ICMP 10.213.1.2 udp port 9999 unreachable, length 38
- 「From 誰」が原因の場所を教えます。 Host Unreachable は自分(10.213.0.1)が出していて、隣の相手が見つからないという意味です。Net Unreachable は中継(10.213.0.254)が出していて、そこから先の経路が無いという意味です。
- UDPで閉じたポートへ送ると、ICMP の port unreachable が返ります。TCPなら RST が返り、
Connection refusedになります。UDPでこのエラーをプログラムが受け取るには、connect()したソケットで送る必要があります。
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("127.0.0.1", 45620)) # UDP でも connect() はできる(宛先を固定するだけ)
s.send(b"first") # 成功する(まだ何も分からない)
time.sleep(0.1)
s.send(b"second") # ICMP port unreachable を受け取った後なので失敗する
s.send(b"second")
ConnectionRefusedError: [Errno 111] Connection refused
1回目の送信は成功し、2回目で初めて Connection refused になりました。UDPの「Connection refused」は、前の送信に対するICMPを受け取った結果です。
ICMPが返らないこともある
traceroute の原文をよく見ると、終点からの port unreachable は 33435〜33440 の6個だけで、残りの探りには応答がありませんでした。Linux はカーネルのドキュメントのとおり、Destination Unreachable・Time Exceeded などの ICMP の送信回数を既定で制限しています(icmp_ratelimit = 1000 ミリ秒、icmp_ratemask で対象を指定)。ファイアウォールが ICMP を止めていることもあります。ICMP が返らない=相手がいない、ではありません。 ping が通らないときの切り分けは、関連記事「ネットワークの切り分け8選|pingは通るのに繋がらない」で、traceroute の読み方は、関連記事「traceroute・tracertの使い方|見方と* * *・!Hの意味を実例で」で扱っています。
UDPの落とし穴:最大サイズ・分割・境界・connect()
この節では、UDPでプログラムを書くときに知らないとつまずく点が分かります。
- 11データグラムの最大は 65,507 バイトIPの長さの欄が16ビットなので、65,535 − IP 20 − UDP 8。超えると Message too long
- 2MTU を超えるとIPの層で分割される3,000 バイトは1,500のMTUで3つに分かれた。1つ欠けると全部が捨てられる
- 3recvfrom のバッファが小さいと残りは捨てられるTCPのように次の recv で続きは読めない
- 4sendto の成功は「届いた」ではない相手がいなくても成功する。応答か時間切れで判断する
- 5connect() すると ICMP のエラーが見える宛先を固定するだけで、ハンドシェイクは起きない
- 6送信元アドレスは偽装できる接続の手続きが無いため。公開するUDPサービスは応答の大きさと回数を抑える
$ ip netns exec t13a python3 -c "...sendto(b'x'*3000, ...); sendto(b'x'*70000, ...)"
3000 バイト送信
70000 バイト: [Errno 90] Message too long
最大: 65507 バイト
$ tcpdump -i v13b -nn udp # 3,000 バイトが 3 つの IP パケットに分かれた
10.213.0.1.35245 > 10.213.1.2.9999: UDP, length 3000
10.213.0.1 > 10.213.1.2: ip-proto-17
10.213.0.1 > 10.213.1.2: ip-proto-17
2つ目以降の断片には UDP ヘッダーが無いため、tcpdump は ip-proto-17 としか表示できません。分割を避けたいアプリケーション(DNS・QUIC)は、1データグラムを 1,200〜1,400 バイト程度に抑えています。パケットの中身を目で追う方法は、関連記事「Wiresharkの使い方|画面の見方・フィルター早見表を実例で解説」で扱っています。
よくあるエラー・つまずき(原文つき)
この節では、UDPとTCPのまわりで出るエラーや「おかしい」の原文と、その意味が分かります。
| 原文・症状 | 意味 | 対処 |
|---|---|---|
TimeoutError: timed out(UDP の recvfrom) |
相手がいない・届いていない・応答が捨てられた | 宛先とポート、ファイアウォール(UDP の規則)、サーバーの待ち受け(ss -uln)を確かめる |
ConnectionRefusedError: [Errno 111](TCP の connect) |
相手がそのポートで待ち受けていない(RST が返った) | サーバーの起動とポート番号、127.0.0.1 と 0.0.0.0 の違いを確かめる |
ConnectionRefusedError(UDP の2回目の send) |
前の送信に ICMP port unreachable が返った | 相手のUDPの待ち受けを確かめる。connect() しないソケットではこのエラーは出ない |
[Errno 90] Message too long |
1データグラムが 65,507 バイトを超えた | 分割して送る。実用上は MTU 以下(1,200〜1,400)に抑える |
UdpRcvbufErrors が増える |
受け取る側のバッファがあふれて捨てている | 受け取る側の処理を速くする・バッファを広げる・送る側が速度を抑える |
Destination Host Unreachable |
同じLANの相手が見つからない(ARP 応答なし) | アドレス・ケーブル・相手の電源・VLAN を確かめる |
Destination Net Unreachable(From が中継) |
中継にその先の経路が無い | From に出た機器の経路表を確かめる |
| nc -zv -u が何も言わずに終わる | UDP は接続しないので「開いている」とは言えない | 実際にそのサービスの問い合わせを送って応答を見る |
| DNS で小さな名前は引けるが大きな応答が返らない | TCP 53 が通っていない | ファイアウォールで UDP 53 と TCP 53 の両方を許可する |
よくある誤解
| 誤解 | 手元で確かめた事実 |
|---|---|
| UDPは必ずTCPより速い | 20万個の小さなメッセージでは、UDPは半分が消え、送信時間もTCPのほうが短かった |
| TCPなら絶対に届く | 経路が切れれば届かない。TCPが保証するのは「届いたか分かる」「届いたものは順番どおり」 |
| UDPは一方通行 | DNSの問い合わせと応答のように双方向で使える。送信元ポートに返事が来る |
| 同じポート番号ならUDPとTCPは同じ通信 | 別の空間。UDP 53 と TCP 53 は別の待ち受け |
| UDPは送ったらすぐ消える(信頼性ゼロ) | 損失の無い経路では20個すべて順番どおり届いた。「保証しない」であって「届かない」ではない |
| sendto が成功したから届いた | 相手がいなくても成功する |
| pingが通ればUDPも通る | ICMPとUDPはファイアウォールで別に扱われる。逆に ICMP が止められていても UDP は通ることがある |
UDPとTCPの使い分け判断表
この節では、自分の通信にUDPとTCPのどちらを使うかを決める順番が分かります。
| 問い | はい | いいえ |
|---|---|---|
| 1バイトでも欠けると意味が無いか(ファイル・決済・API) | TCP | 次へ |
| 遅れて届いたデータは捨ててよいか(音声・映像・位置) | UDP | 次へ |
| 1往復で終わり、相手の数が多いか(DNS・NTP・ログ) | UDP(再送は自分で) | 次へ |
| ブロードキャスト・マルチキャストで送るか | UDP(TCPではできない) | 次へ |
| 再送・順序・暗号を自分で作る余力があるか | UDP(または QUIC のライブラリ) | TCP(+TLS) |
| 迷っている | TCP。UDPを選ぶ理由を説明できるときだけUDPにする | |
- Webアプリケーションや業務システムの大半は、HTTP(TCP)を使うので、UDPを自分で扱う場面は限られます。 扱うのは、動画・音声・IoTの計測値・ゲーム・ネットワーク機器のログ(syslog・SNMP)・DNSやDHCPの運用のときです。
- HTTP/3 を有効にすると、アプリケーションのコードを変えずにUDP(QUIC)の恩恵を受けられます。その代わり、ファイアウォールと負荷分散装置でUDPの443番を通す設定が要ります。
よくある質問
Q. UDPとは何ですか? A. User Datagram Protocol の略で、IPの上でアプリケーション同士がデータグラム(ひとかたまりのデータ)をやり取りするトランスポート層のプロトコルです。接続・再送・順序の保証をせず、8バイトのヘッダーで送ります。仕様は RFC 768 です。
Q. UDPとTCPの違いを一言で言うと? A. TCPは「つないで、確かめて、足りなければ送り直す」、UDPは「宛先を付けて投げる」です。手元では同じ1行を送るのに、TCPは8パケット、UDPは1パケットでした。
Q. UDPは本当にデータが消えるのですか? A. 消えることがあります。30%の損失を起こした経路では、20個のうち7個が届きませんでした。損失の無い経路では20個すべて届いたので、「必ず消える」のではなく「消えても誰も直さない」が正確です。
Q. UDPはTCPより速いですか? A. 接続の往復が無くヘッダーも小さいので、1往復で終わる通信や遅延を嫌う通信では有利です。ただし、小さなメッセージを大量に送った実測では、受け取る側のバッファがあふれて半分が消え、送信時間もTCPのほうが短かったです。
Q. ICMPとは何ですか?UDPとどう関係しますか? A. IPのエラー通知と診断を運ぶプロトコルで、ping(Echo)と traceroute(Time Exceeded)が使います。UDPで閉じたポートに送ると、ICMPの port unreachable が返ります。traceroute は UDP を送って ICMP を受け取る仕組みです。
Q. ICMPはTCP・UDPと同じ層ですか? A. IPの上で動く点は同じですが、ポート番号を持たず、アプリケーションのデータを運ばない点が違います。プロトコル番号は ICMP が1、TCP が6、UDP が17です。
Q. DNSはUDPとTCPのどちらですか? A. 普段はUDPの53番で、応答が大きいとき(512バイトを超える・TCビットが立つ)やゾーン転送はTCPの53番を使います。ファイアウォールでは両方を許可します。
Q. なぜHTTP/3はUDPを使うのですか? A. QUIC がUDPの上で接続・再送・暗号化を自分で行うためです。TCPでは1つのパケットの損失で後ろのデータ全部が待たされますが、QUIC はストリームごとに再送するので、ほかのストリームは止まりません。
Q. UDPのポートが開いているか調べるには? A. 接続が無いので「開いている」を外から確かめる決まった方法はありません。閉じていれば ICMP port unreachable が返ることがありますが、ICMPは制限や遮断で返らないこともあります。確実なのは、そのサービスの問い合わせを送って応答を見ることです。
Q. UDPで確実に届けたいときは? A. アプリケーションで番号を付け、応答(ACK)と再送を自分で作ります。それが必要なら、最初からTCPか、QUICのライブラリを使うほうが確実です。
Q. ビデオ通話で音が途切れるのはUDPのせいですか? A. 途切れの原因は経路の損失や遅延で、UDPは「再送して遅らせる」代わりに「欠けた部分を飛ばす」ことを選んでいます。TCPにすると途切れの代わりに全体が遅れます。
Q. UDPは危険ですか? A. 接続の手続きが無いので送信元を偽装しやすく、公開するUDPサービスは応答の大きさと回数を抑える設計が要ります。社内の通信や、認証と暗号化を持つQUICのような上位プロトコルで使う分には、TCPと同じように使えます。
まとめ
- UDPは、接続・再送・順序の保証をしない代わりに、8バイトのヘッダーで1パケットで送れる。TCPは同じ1行に8パケット使った。
- 損失30%の経路で、UDPは20個中7個が消え、順番も入れ替わった。TCPは再送とSACKで全部を順番どおり届けたが、その間0.2秒止まった。
- 「UDPは必ず速い」ではない。受け取る側が追いつかないと、手元のループバックでも半分が消えた。
- DNS・DHCP・NTP・音声・動画・ゲーム・QUIC(HTTP/3)がUDPを使う。理由は「1往復で終わる」か「遅れたデータに価値が無い」か。
- ICMPはIPのエラー通知と診断。ping は Echo、traceroute は UDP を送って Time Exceeded と Port Unreachable を受け取る。ICMPが返らない=相手がいない、ではない。
参考・出典
確認日はいずれも 2026年10月29日です。
- IETF「RFC 768 User Datagram Protocol」:https://www.rfc-editor.org/rfc/rfc768
- IETF「RFC 9293 Transmission Control Protocol (TCP)」:https://www.rfc-editor.org/rfc/rfc9293
- IETF「RFC 792 Internet Control Message Protocol」:https://www.rfc-editor.org/rfc/rfc792
- IETF「RFC 9000 QUIC: A UDP-Based Multiplexed and Secure Transport」:https://www.rfc-editor.org/rfc/rfc9000
- IETF「RFC 9114 HTTP/3」:https://www.rfc-editor.org/rfc/rfc9114
- IETF「RFC 1035 Domain Names - Implementation and Specification」4.2 Transport:https://www.rfc-editor.org/rfc/rfc1035#section-4.2
- IETF「RFC 7766 DNS Transport over TCP - Implementation Requirements」:https://www.rfc-editor.org/rfc/rfc7766
- IANA「Protocol Numbers」:https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
- Linux カーネル「IP Sysctl」icmp_ratelimit・icmp_ratemask:https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt
- Python「socket — Low-level networking interface」:https://docs.python.org/3/library/socket.html
- nftables wiki「Numgen」:https://wiki.nftables.org/wiki-nftables/index.php/Numgen
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「UDPとは|TCPとの違い・ICMPとの関係を「届かない」実物で解説」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。