目次
1. なぜネットワークプログラミングの深い知識が必要か
現代(げんだい)のソフトウェアエンジニアにとって、ネットワークはもはや「インフラチームに任(まか)せておけばいい領域(りょういき)」ではありません。マイクロサービス間(かん)の通信遅延(つうしんちえん)がP99レイテンシを左右(さゆう)し、HTTP/3の導入(どうにゅう)がモバイルユーザー体験(たいけん)を決定(けってい)し、TLS設定(せってい)一つがセキュリティ監査(かんさ)の合否(ごうひ)を分(わ)けます。
このガイドで扱う主要トピック:
- TCP内部動作と状態マシン
- 輻輳制御(ふくそうせいぎょ)アルゴリズム(Cubic、BBR、BBRv2)
- ソケットプログラミングとI/O多重化(epoll、kqueue、io_uring)
- HTTP/1.1 → HTTP/2 → HTTP/3(QUIC)の進化
- DNS解決プロセスとセキュリティ(DNSSEC、DoH/DoT)
- TLS 1.3ハンドシェイクと0-RTT
- ネットワークデバッグツール(tcpdump、Wireshark、ss)
- パフォーマンスチューニング(Nagle、TCP_NODELAY、カーネルパラメータ)
2. TCPプロトコル徹底解説
2.1 3ウェイハンドシェイク
TCP接続(せつぞく)確立(かくりつ)の核心(かくしん)プロセスです。
Client Server
| |
|--- SYN (seq=x) ------->| (1) クライアントが接続要求
| |
|<-- SYN-ACK ------------| (2) サーバーが受諾 + 自身のSYN
| (seq=y, ack=x+1) |
| |
|--- ACK (ack=y+1) ----->| (3) クライアントがサーバーのSYNを確認
| |
|==== 接続確立完了 =======|
なぜ3ウェイなのか? 双方(そうほう)が相手(あいて)の初期(しょき)シーケンス番号(ISN)を確認する必要があるためです。2ウェイでは、サーバーがクライアントのACKを確認できません。
// サーバーソケット作成例
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(8080);
bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
// backlog: SYNキュー + Acceptキューのサイズを決定
listen(server_fd, SOMAXCONN);
// 3ウェイハンドシェイク完了した接続をacceptキューから取得
int client_fd = accept(server_fd, NULL, NULL);
2.2 4ウェイ終了
接続終了は双方向(そうほうこう)で独立(どくりつ)して行われます。
Client Server
| |
|--- FIN (seq=u) ------->| (1) クライアント:「送るデータなし」
| |
|<-- ACK (ack=u+1) ------| (2) サーバー:「了解」(まだ送信可能)
| |
| ... サーバーが残りのデータを送信 ...
| |
|<-- FIN (seq=w) --------| (3) サーバー:「こちらも送るデータなし」
| |
|--- ACK (ack=w+1) ----->| (4) クライアント:「了解」
| |
|=== TIME_WAIT (2MSL) ===| クライアントがTIME_WAIT状態に
2.3 TCP状態マシン
CLOSED
|
(能動 OPEN)
|
SYN_SENT
|
(SYN-ACK 受信)
|
ESTABLISHED
/ \
(能動 CLOSE) (受動 CLOSE)
/ \
FIN_WAIT_1 CLOSE_WAIT
| |
FIN_WAIT_2 LAST_ACK
| |
TIME_WAIT CLOSED
|
CLOSED
2.4 TIME_WAITとSO_REUSEADDR
TIME_WAIT状態は2MSL(Maximum Segment Lifetime、通常60秒)間維持(いじ)されます。
TIME_WAITが必要な理由:
- 遅延(ちえん)パケットが新しい接続に影響を与えることを防止
- 最後のACK損失時に再送可能
// サーバー再起動時の「Address already in use」を防止
int optval = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR,
&optval, sizeof(optval));
// LinuxではSO_REUSEPORTも使用可能(ロードバランシング)
setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT,
&optval, sizeof(optval));
TIME_WAIT過多時の対応:
# TIME_WAITソケット数の確認
ss -s | grep TIME-WAIT
# カーネルパラメータ調整(注意:副作用あり)
sysctl -w net.ipv4.tcp_tw_reuse=1
# TIME_WAITソケットの最大数
sysctl -w net.ipv4.tcp_max_tw_buckets=262144
3. TCP輻輳制御(ふくそうせいぎょ)
3.1 なぜ輻輳制御が必要か
ネットワーク輻輳時(ふくそうじ)にすべてのホストが無制限(むせいげん)にパケットを送信すると、ルーターバッファがオーバーフローしてパケット損失(そんしつ)が発生し、再送が輻輳をさらに悪化(あっか)させる輻輳崩壊(ふくそうほうかい、Congestion Collapse) が発生します。
3.2 基本アルゴリズム
cwnd (輻輳ウィンドウ)
^
| * * * *
| * * <-- Congestion Avoidance(線形増加)
| * *
| * \
| * \ パケット損失検出
| * \
| * <-- Slow Start * (cwnd半減)
| * (指数的増加) *
| * *
| * * ...
|*
+-----------------------------------------> 時間
ssthresh
Slow Start: cwndを1 MSSから開始、ACKごとに1 MSS増加(RTTごとに2倍)
Congestion Avoidance: cwndがssthreshに達したら、RTTごとに1 MSSずつ線形(せんけい)増加
Fast Retransmit: 3つの重複ACK受信時、タイムアウトを待たずに即座(そくざ)に再送
Fast Recovery: パケット損失時、cwndを1ではなくssthreshの半分に設定
3.3 Cubic vs BBR
アルゴリズム比較:
+----------+--------+-----------+-------------------------------+
| アルゴリズム | タイプ | 検出方式 | 特徴 |
+----------+--------+-----------+-------------------------------+
| Reno | 損失型 | パケット損失 | 基本AIMD |
| Cubic | 損失型 | パケット損失 | 3次関数、Linux デフォルト |
| BBR | モデル型 | RTT/BW | 帯域幅-遅延モデル、Google開発 |
| BBRv2 | モデル型 | RTT/BW | BBR改善、公平性向上 |
+----------+--------+-----------+-------------------------------+
Cubic: Linuxデフォルトの輻輳制御。ウィンドウサイズが3次関数(さんじかんすう)に従います。
# 現在の輻輳制御アルゴリズムを確認
sysctl net.ipv4.tcp_congestion_control
# 結果: net.ipv4.tcp_congestion_control = cubic
# BBRに変更
sysctl -w net.ipv4.tcp_congestion_control=bbr
# 利用可能なアルゴリズム一覧
sysctl net.ipv4.tcp_available_congestion_control
BBR(Bottleneck Bandwidth and RTT):
- パケット損失ではなく実際の帯域幅(たいいきはば)とRTTを測定して最適な送信速度を決定
- 高損失率環境(衛星、無線)でCubicよりはるかに優れた性能
- 欠点:他のフローとの公平性(こうへいせい)の問題
# BBR有効化(カーネル4.9以上)
modprobe tcp_bbr
echo "tcp_bbr" >> /etc/modules-load.d/bbr.conf
sysctl -w net.core.default_qdisc=fq
sysctl -w net.ipv4.tcp_congestion_control=bbr
4. UDP:いつ、なぜ使うのか
4.1 TCP vs UDP
+------------+----------------------------+----------------------------+
| 特性 | TCP | UDP |
+------------+----------------------------+----------------------------+
| 接続 | コネクション型 (3ウェイ) | コネクションレス |
| 信頼性 | 保証(再送、順序) | 保証なし |
| フロー制御 | あり(スライディングウィンドウ)| なし |
| 輻輳制御 | あり | なし(アプリが実装) |
| ヘッダサイズ | 20-60バイト | 8バイト |
| 遅延 | 高い(ハンドシェイク+再送) | 低い |
| 用途 | HTTP, SSH, DB | DNS, ゲーム, ストリーミング |
+------------+----------------------------+----------------------------+
4.2 UDP活用事例
ゲーム: フレーム単位(たんい)の位置データは最新の値だけが重要。再送よりも次のフレームが重要です。
# シンプルなUDPゲームサーバー
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('0.0.0.0', 9999))
players = {}
while True:
data, addr = sock.recvfrom(1024)
# プレイヤー位置更新(損失許容)
players[addr] = parse_position(data)
# 全プレイヤーにゲーム状態をブロードキャスト
state = serialize_game_state(players)
for player_addr in players:
sock.sendto(state, player_addr)
DNS: 単一のリクエスト-レスポンスパターン。TCPハンドシェイクのオーバーヘッドは不要です。
リアルタイムストリーミング: RTP/RTCPプロトコルはUDP上で動作します。
5. ソケットプログラミング
5.1 Berkeleyソケット API
// TCP クライアント完全例
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
// 1. ソケット作成
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
return 1;
}
// 2. サーバーアドレス設定
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(80);
inet_pton(AF_INET, "93.184.216.34", &server_addr.sin_addr);
// 3. 接続(3ウェイハンドシェイク発生)
if (connect(sock, (struct sockaddr *)&server_addr,
sizeof(server_addr)) < 0) {
perror("connect");
return 1;
}
// 4. HTTPリクエスト送信
const char *request = "GET / HTTP/1.1\r\n"
"Host: example.com\r\n"
"Connection: close\r\n\r\n";
send(sock, request, strlen(request), 0);
// 5. レスポンス受信
char buffer[4096];
int bytes;
while ((bytes = recv(sock, buffer, sizeof(buffer) - 1, 0)) > 0) {
buffer[bytes] = '\0';
printf("%s", buffer);
}
// 6. 接続終了(4ウェイ終了発生)
close(sock);
return 0;
}
5.2 ブロッキング vs ノンブロッキング
#include <fcntl.h>
// ノンブロッキングモード設定
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
// ノンブロッキングconnect
int ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0 && errno == EINPROGRESS) {
// 接続進行中 - poll/epollで完了確認が必要
struct pollfd pfd;
pfd.fd = sock;
pfd.events = POLLOUT;
poll(&pfd, 1, 5000); // 5秒タイムアウト
int error;
socklen_t len = sizeof(error);
getsockopt(sock, SOL_LEVEL, SO_ERROR, &error, &len);
if (error == 0) {
// 接続成功
}
}
// ノンブロッキングrecv
char buf[1024];
ssize_t n = recv(sock, buf, sizeof(buf), 0);
if (n < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// データがまだない - 後で再試行
} else {
// 実際のエラー
}
}
6. I/O多重化(たじゅうか)
6.1 select、poll、epoll、kqueue、io_uring比較
+------------+--------+-----------+----------+----------------------------+
| メカニズム | OS | 時間計算量 | FD制限 | 特徴 |
+------------+--------+-----------+----------+----------------------------+
| select | 全OS | O(n) | 1024 | 最古、移植性が高い |
| poll | 全OS | O(n) | なし | select改善、依然O(n) |
| epoll | Linux | O(1) | なし | イベント駆動、大規模対応 |
| kqueue | BSD | O(1) | なし | macOS/FreeBSD、機能豊富 |
| io_uring | Linux | O(1) | なし | 5.1以降、非同期I/O革命 |
+------------+--------+-----------+----------+----------------------------+
6.2 epoll詳解
#include <sys/epoll.h>
#define MAX_EVENTS 1024
int epoll_fd = epoll_create1(0);
// サーバーソケット登録
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = server_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev);
struct epoll_event events[MAX_EVENTS];
while (1) {
// イベント待機(ブロッキング)
int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
for (int i = 0; i < nfds; i++) {
if (events[i].data.fd == server_fd) {
// 新規接続受付
int client_fd = accept(server_fd, NULL, NULL);
// ノンブロッキングに設定
fcntl(client_fd, F_SETFL,
fcntl(client_fd, F_GETFL, 0) | O_NONBLOCK);
// Edge-Triggeredモードで登録
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = client_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev);
} else {
// クライアントデータ処理
handle_client(events[i].data.fd);
}
}
}
Level-Triggered vs Edge-Triggered:
- LT(デフォルト): データがある限り通知し続ける。安全だが不要なシステムコール発生
- ET: 状態変化時のみ通知。性能(せいのう)は良いが、一度にすべてのデータを読む必要あり
// Edge-Triggeredでデータを完全に読み取る
void handle_client_et(int fd) {
char buf[4096];
while (1) {
ssize_t n = read(fd, buf, sizeof(buf));
if (n < 0) {
if (errno == EAGAIN) break; // 全データ読了
perror("read");
break;
}
if (n == 0) {
// 接続終了
close(fd);
break;
}
process_data(buf, n);
}
}
6.3 io_uring(Linux 5.1以降)
#include <liburing.h>
struct io_uring ring;
io_uring_queue_init(256, &ring, 0);
// 非同期読み取りリクエスト送信
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, client_fd, buf, sizeof(buf), 0);
io_uring_sqe_set_data(sqe, client_ctx);
io_uring_submit(&ring);
// 完了イベント収穫
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);
struct client_ctx *ctx = io_uring_cqe_get_data(cqe);
int bytes_read = cqe->res;
// 処理...
io_uring_cqe_seen(&ring, cqe);
io_uringの革新(かくしん):
- システムコールなしでカーネルと通信(共有リングバッファ)
- バッチ送信/完了でオーバーヘッドを最小化
- ファイルI/O、ネットワークI/O、タイマーすべて対応
- epoll比で最大2-3倍のスループット向上
7. HTTPプロトコルの進化
7.1 HTTP/1.1
HTTP/1.1の主要機能:
1. Keep-Alive(持続的接続)
- 1つのTCP接続で複数のリクエスト/レスポンスを処理
- Connection: keep-alive(デフォルト)
2. パイプライニング
- レスポンスを待たずに複数リクエストを送信
- 実際にはHOLブロッキング問題でほぼ無効化
3. Chunked Transfer Encoding
- Content-Lengthなしでストリーミングレスポンス可能
HTTP/1.1の限界 -- Head-of-Line Blocking:
Client Server
|-- GET /a -------->|
| | (レスポンスa生成に3秒)
|-- GET /b -------->|
| | bは準備完了だがaの完了まで待機
|<-- Response /a ---|
|<-- Response /b ---| (不要な遅延)
実務ではドメインシャーディング(6-8個のTCP接続)で回避(かいひ)していました。
7.2 HTTP/2
HTTP/2の主要機能:
1. マルチプレキシング
- 1つのTCP接続で複数ストリームを同時送信
- 各ストリームは独立(HOLブロッキング解決...TCP層を除く)
2. HPACKヘッダ圧縮
- 静的/動的テーブルでヘッダの重複を排除
- 初回リクエスト: "content-type: application/json"(全体)
- 以降: インデックス番号のみ送信
3. Server Push
- HTML要求時にCSS/JSを事前プッシュ
- 実務ではキャッシュ問題でほぼ未使用
4. バイナリフレーミング
- テキストではなくバイナリフレーム
- パース効率の向上
HTTP/2フレーム構造:
+-----------------------------------------------+
| Length (24) |
+---------------+-------------------------------+
| Type (8) | Flags (8) |
+---------------+-------------------------------+
| Reserved (1) | Stream Identifier (31) |
+-----------------------------------------------+
| Frame Payload |
+-----------------------------------------------+
ストリームID:
- 奇数: クライアント開始
- 偶数: サーバー開始(Server Push)
- 0: 制御フレーム
7.3 HTTP/3(QUIC)
HTTPバージョン比較:
+----------+----------+---------+---------+----------------------------+
| バージョン | 転送層 | 暗号化 | RTT | HOLブロッキング |
+----------+----------+---------+---------+----------------------------+
| HTTP/1.1 | TCP | 選択 | 2-3 RTT | あり(レスポンスレベル) |
| HTTP/2 | TCP | 事実上 | 2-3 RTT | あり(TCPレベル) |
| HTTP/3 | QUIC/UDP | 必須 | 1 RTT | なし(ストリーム独立) |
| | | | (0 RTT) | |
+----------+----------+---------+---------+----------------------------+
8. QUICプロトコル詳解
8.1 QUICアーキテクチャ
従来のスタック: QUICスタック:
+----------+ +----------+
| HTTP/2 | | HTTP/3 |
+----------+ +----------+
| TLS 1.2+ | | QUIC | <- TLS 1.3内蔵
+----------+ +----------+
| TCP | | UDP |
+----------+ +----------+
| IP | | IP |
+----------+ +----------+
8.2 0-RTT接続
初回接続(1-RTT):
Client Server
| |
|--- Initial (CHLO) ---->| ClientHello + 転送パラメータ
| |
|<-- Initial (SHLO) -----| ServerHello + 証明書 + 転送パラメータ
| |
|--- Handshake Done ----->|
| |
|=== データ転送開始 =======| 1-RTTで接続完了!
再接続(0-RTT):
Client Server
| |
|--- Initial + 0-RTT --->| 前回セッションキーで暗号化されたデータを含む
| データ |
| | サーバーは即座にデータ処理可能
|<-- Handshake ----------|
| |
|=== 即時データ交換 ======| 0-RTT!
0-RTTのセキュリティ注意事項: リプレイ攻撃(こうげき)に脆弱(ぜいじゃく)です。冪等(べきとう、idempotent)なリクエストのみ0-RTTで送信すべきです。
8.3 接続マイグレーション
Wi-Fiからセルラーへの切り替え時:
TCP: 新しいIPアドレス -> 新しい接続が必要 -> 3ウェイ + TLS再度
QUIC: Connection ID基盤 -> IP変更でも接続維持!
Client (Wi-Fi: 192.168.1.10) ---QUIC (CID: abc123)---> Server
|
(Wi-Fi切断、セルラーに切替)
|
Client (セルラー: 10.0.0.5) ---QUIC (CID: abc123)---> Server
接続維持!
8.4 ストリーム多重化とHOLブロッキング解決
HTTP/2 over TCP:
Stream A: [1] [2] [_] [4] ... <- パケット3損失
Stream B: [1] [2] [3] [4] ... <- Bもブロック!(TCPが順序保証)
Stream C: [1] [2] [3] [4] ... <- Cもブロック!
TCP再送完了まですべてのストリームが待機
HTTP/3 over QUIC:
Stream A: [1] [2] [_] [4] ... <- パケット3損失(Aだけ待機)
Stream B: [1] [2] [3] [4] ... <- 正常進行!
Stream C: [1] [2] [3] [4] ... <- 正常進行!
各ストリームが独立して再送を処理
9. DNS詳解
9.1 DNS解決プロセス
ユーザーが www.example.com を入力:
(1) ローカルキャッシュ確認
Browser -------> OS Resolver -------> /etc/hosts
|
(2) キャッシュミス
|
v
再帰リゾルバ (ISP/8.8.8.8)
|
(3) ルートサーバーに問い合わせ
|
v
Root Server (.)
「.comはこのNSに聞いて」
|
(4) TLDサーバーに問い合わせ
|
v
.com TLD Server
「example.comはこのNSに聞いて」
|
(5) 権威サーバーに問い合わせ
|
v
example.com 権威NS
"www.example.com = 93.184.216.34"
|
(6) 結果をキャッシュして返却
|
v
Browser
9.2 DNSレコードタイプ
# Aレコード(IPv4)
dig A example.com
# example.com. 300 IN A 93.184.216.34
# AAAAレコード(IPv6)
dig AAAA example.com
# CNAME(エイリアス)
dig CNAME www.example.com
# MX(メール)
dig MX example.com
# NS(ネームサーバー)
dig NS example.com
# TXT(テキスト - SPF, DKIM等)
dig TXT example.com
# 完全な解決経路を追跡
dig +trace www.example.com
9.3 DNSSEC
DNSSECはDNS応答の完全性(かんぜんせい)を保証します。
署名チェーン:
Root Zone (.)
|-- KSK (Key Signing Key) がZSKに署名
|-- ZSK (Zone Signing Key) がレコードに署名
|-- DSレコード: .comのKSKハッシュ
|
v
.com TLD
|-- KSK, ZSK
|-- DSレコード: example.comのKSKハッシュ
|
v
example.com
|-- KSK, ZSK
|-- RRSIG: 各レコードのデジタル署名
|-- Aレコード + RRSIG(A)
9.4 DoH / DoT
従来のDNS: 平文UDP 53番ポート(盗聴可能)
DoT(DNS over TLS):
- TLSで暗号化されたDNS
- TCP 853番ポート
- システムレベル設定可能
DoH(DNS over HTTPS):
- HTTPSでカプセル化されたDNS
- TCP 443番ポート(通常のHTTPSと区別不可)
- ブラウザレベル設定(Firefox, Chrome)
10. TLS 1.3詳解
10.1 TLS 1.2 vs TLS 1.3
+----------------+------------------+------------------+
| 項目 | TLS 1.2 | TLS 1.3 |
+----------------+------------------+------------------+
| ハンドシェイク | 2-RTT | 1-RTT |
| 再接続 | 1-RTT | 0-RTT |
| 鍵交換 | RSA, DH, ECDH | ECDHE, X25519のみ |
| 暗号化 | CBC, RC4等を含む | AEADのみ |
| | | (AES-GCM, |
| | | ChaCha20-Poly) |
| 静的RSA | 可能(PFSなし) | 削除(PFS必須) |
| 圧縮 | 可能(CRIME脆弱) | 削除 |
+----------------+------------------+------------------+
10.2 TLS 1.3ハンドシェイク
Client Server
| |
|--- ClientHello ------------------>|
| + supported_versions |
| + key_share (ECDHE公開鍵) |
| + signature_algorithms |
| + psk_key_exchange_modes |
| |
|<-- ServerHello --------------------|
| + key_share (サーバーECDHE鍵) |
| |
| [以降すべての通信が暗号化] |
| |
|<-- EncryptedExtensions ------------|
|<-- Certificate --------------------|
|<-- CertificateVerify --------------|
|<-- Finished -----------------------|
| |
|--- Finished ---------------------->|
| |
|=== 1-RTTハンドシェイク完了 ========|
10.3 Certificate Transparency
証明書発行プロセス:
1. ドメイン所有者がCAに証明書を要求
2. CAが証明書を発行
3. CAがCTログに証明書を登録(透明性!)
4. CTログがSCT(Signed Certificate Timestamp)を返却
5. サーバーがTLSハンドシェイクにSCTを含める
CTログ監視:
- 誰かが自分のドメインの証明書を無断発行すると検知可能
- crt.shで特定ドメインのすべての証明書を照会可能
# サーバーのTLS証明書を確認
openssl s_client -connect example.com:443 -servername example.com \
| openssl x509 -noout -text
# TLS 1.3接続テスト
openssl s_client -connect example.com:443 -tls1_3
# 証明書チェーンを確認
openssl s_client -connect example.com:443 -showcerts
11. ネットワークデバッグ
11.1 tcpdump
# ホストとポートでフィルタ
tcpdump -i eth0 host 10.0.0.1 and port 80
# TCPフラグフィルタ(SYNパケットのみ)
tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn) != 0'
# 3ウェイハンドシェイクをキャプチャ
tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0' -c 100
# HTTPリクエスト/レスポンスの内容を表示
tcpdump -i eth0 -A -s 0 'tcp port 80'
# pcapファイルに保存(Wiresharkで分析)
tcpdump -i eth0 -w capture.pcap -c 10000
# DNSクエリをキャプチャ
tcpdump -i eth0 udp port 53
11.2 ss(Socket Statistics)
# TCP接続状態サマリー
ss -s
# LISTENソケット(サーバーポート)
ss -tlnp
# ESTABLISHED接続
ss -tnp state established
# TIME_WAITソケット数
ss -s | grep TIME-WAIT
# 特定ポートの接続情報
ss -tnp dst :443
# ソケットバッファサイズ確認
ss -tnm
# 輻輳制御情報
ss -ti dst :80
11.3 traceroute / mtr
# 経路追跡
traceroute example.com
# TCP traceroute(ファイアウォール回避)
traceroute -T -p 443 example.com
# mtr(traceroute + ping統合、リアルタイム監視)
mtr --report-wide example.com
11.4 curlデバッグ
# 詳細な接続情報
curl -v https://example.com
# タイミング詳細情報
curl -w @- -o /dev/null -s https://example.com <<'EOF'
time_namelookup: %{time_namelookup}s\n
time_connect: %{time_connect}s\n
time_appconnect: %{time_appconnect}s\n
time_pretransfer: %{time_pretransfer}s\n
time_redirect: %{time_redirect}s\n
time_starttransfer: %{time_starttransfer}s\n
----------\n
time_total: %{time_total}s\n
EOF
# HTTP/2を強制
curl --http2 -v https://example.com
# HTTP/3(curl 7.88+、nghttp3必要)
curl --http3 -v https://example.com
12. パフォーマンスチューニング
12.1 Nagleアルゴリズムと TCP_NODELAY
// Nagleアルゴリズム: 小さなパケットをまとめて送信(帯域効率)
// 問題: 遅延が発生(特にリアルタイムアプリケーション)
// TCP_NODELAYでNagleを無効化
int flag = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
&flag, sizeof(flag));
// TCP_NODELAYを使うべき場合:
// - リアルタイムゲーム(フレーム単位データ)
// - 対話型プロトコル(SSH, telnet)
// - 小さなメッセージを頻繁に送信する場合
// - リクエスト-レスポンスパターン(Redis, Memcached)
12.2 ソケットバッファチューニング
# 現在のTCPバッファ設定を確認
sysctl net.ipv4.tcp_rmem # 受信バッファ (min, default, max)
sysctl net.ipv4.tcp_wmem # 送信バッファ (min, default, max)
# 高帯域環境(10Gbps以上)チューニング
sysctl -w net.ipv4.tcp_rmem="4096 1048576 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 1048576 16777216"
# TCP全体メモリ制限(ページ単位)
sysctl -w net.ipv4.tcp_mem="786432 1048576 1572864"
# 受信バッファ自動チューニング有効化
sysctl -w net.ipv4.tcp_moderate_rcvbuf=1
12.3 カーネルネットワークパラメータ
# === 接続管理 ===
# SYNバックログサイズ(DDoS防御)
sysctl -w net.ipv4.tcp_max_syn_backlog=65535
# Acceptキューサイズ
sysctl -w net.core.somaxconn=65535
# SYNクッキー(SYN Flood防御)
sysctl -w net.ipv4.tcp_syncookies=1
# === タイムアウト ===
# FIN-WAIT-2タイムアウト(デフォルト60秒)
sysctl -w net.ipv4.tcp_fin_timeout=15
# Keepalive設定
sysctl -w net.ipv4.tcp_keepalive_time=600
sysctl -w net.ipv4.tcp_keepalive_intvl=60
sysctl -w net.ipv4.tcp_keepalive_probes=3
# === ファイルディスクリプタ ===
# システム全体のファイルディスクリプタ制限
sysctl -w fs.file-max=2097152
# プロセスごとの制限
ulimit -n 1048576
13. 実践クイズ
クイズ 1: TCP状態遷移
サーバーがクライアントのFINを受信した後、まだ送信するデータが残っている場合、サーバーはどのTCP状態にありますか?
正解:CLOSE_WAIT
サーバーがクライアントのFINを受信すると、ACKを送信してCLOSE_WAIT状態に遷移(せんい)します。この状態でサーバーはまだデータを送信できます。残りのデータをすべて送信した後、サーバーがFINを送信するとLAST_ACK状態に遷移します。
CLOSE_WAITソケットが大量に蓄積(ちくせき)する場合、アプリケーションがclose()を正しく呼び出していないバグです。
# CLOSE_WAITソケットの確認
ss -tnp state close-wait
クイズ 2: HTTP/2 vs HTTP/3のHOLブロッキング
HTTP/2で1つのTCPパケットが損失すると、同じ接続の他のストリームも影響を受けるのはなぜですか?
正解:TCPの順序保証特性のため
HTTP/2は1つのTCP接続で複数のストリームを多重化します。TCPはバイトストリームの順序を保証するため、中間パケットが損失すると、後続のすべてのバイト(他のストリームのデータを含む)がTCP受信バッファで待機しなければなりません。
HTTP/3(QUIC)はUDP上で各ストリームを独立して管理することで、この問題を解決します。ストリームAのパケット損失はストリームB、Cに影響しません。
クイズ 3: BBR vs Cubic
高いパケット損失率(例:衛星リンクの2%損失)環境でBBRがCubicより有利な理由は?
正解: Cubicは損失ベース(loss-based)アルゴリズムで、パケット損失を輻輳の信号として解釈し、cwndを急激に縮小します。2%損失環境では、実際の輻輳ではないにもかかわらず、継続的にcwndが減少します。
BBRはモデルベース(model-based)で、実際の帯域幅(BtlBw)と最小RTT(RTprop)を測定して最適な送信速度を決定します。パケット損失自体を輻輳信号として使用しないため、高損失率でも利用可能な帯域幅を効果的に活用します。
クイズ 4: TLS 1.3の0-RTTセキュリティ
TLS 1.3の0-RTT再接続がリプレイ攻撃に脆弱な理由と対策は?
正解: 0-RTTデータは前回セッションのPSKで暗号化されますが、サーバーのServerHelloを含まないため、鮮度(freshness)が保証されません。攻撃者が0-RTTパケットをキャプチャして再送すると、サーバーが同じリクエストを再処理する可能性があります。
対策:
- 0-RTTには冪等(べきとう)なリクエストのみ許可(GET、HEAD)
- サーバー側でアンチリプレイ機構を実装(Client Helloハッシュを保存)
- 金融取引などの機密操作では0-RTTを無効化
クイズ 5: epoll ETモード
epoll Edge-Triggeredモードでデータを一度に全部読まないとどのような問題が発生しますか?
正解: Edge-Triggeredモードは状態「変化」時にのみイベントを発生させます。読み取り可能状態への変化後、データを一部だけ読んでepoll_waitに戻ると、残りのデータに対するイベントは再発生しません。
したがって、ETモードではEAGAINが返されるまで繰り返しread()を呼び出して、すべてのデータを読み取る必要があります。これがETモードが必ずノンブロッキングソケットと一緒に使用されなければならない理由です。
Level-Triggeredモードでは、データが残っていれば継続的にイベントが発生するため、この問題は発生しません。
14. 参考資料
- TCP/IP Illustrated, Volume 1 - W. Richard Stevens(TCPのバイブル)
- Unix Network Programming - W. Richard Stevens(ソケットプログラミングの定番)
- High Performance Browser Networking - Ilya Grigorik(無料オンライン: hpbn.co)
- QUIC RFC 9000 - https://datatracker.ietf.org/doc/html/rfc9000
- TLS 1.3 RFC 8446 - https://datatracker.ietf.org/doc/html/rfc8446
- HTTP/3 RFC 9114 - https://datatracker.ietf.org/doc/html/rfc9114
- BBR Congestion Control - Google Research, https://research.google/pubs/bbr-congestion-based-congestion-control/
- Linux epoll(7) man page - https://man7.org/linux/man-pages/man7/epoll.7.html
- io_uring documentation - https://kernel.dk/io_uring.pdf
- Brendan Gregg - Network Performance - https://www.brendangregg.com/networking.html
- Cloudflare Blog - HTTP/3 - https://blog.cloudflare.com/http3-the-past-present-and-future/
- DNS Flag Day - https://dnsflagday.net/
- Certificate Transparency - https://certificate.transparency.dev/
- HPACK RFC 7541 - https://datatracker.ietf.org/doc/html/rfc7541
현재 단락 (1/599)
現代(げんだい)のソフトウェアエンジニアにとって、ネットワークはもはや「インフラチームに任(まか)せておけばいい領域(りょういき)」ではありません。マイクロサービス間(かん)の通信遅延(つうしんちえん...