😺
goのtcp serverでkeepaliveを調べてみた
概要
TCP/IPの通信をしているとkeepaliveという機能がでてきます。これが有効になってるのかそもそも論どっちがデフォルトなんだとかよくわからないので追ってみました。
KeepAliveとは?
TCPの機能でRFC793 に記述されている(ただこのRFC改訂版が出るらしくて新しい番号がつくらしい)。こちらに書かれているkeepaliveの実装を概ねこんな感じで、長い間送受信がない状態というアイドル状態の定義、このアイドル接続がアクティブかどうかをprobe segment
を送信して確認する(SEG.SEQ = SND.NXT-1
が含まれる)。この記述はlinuxだと以下に現れる。
-
git@github.com:torvalds/linux.git
- net/ipv4/tcp_output.c
- net/netfilter/nf_synproxy_core.c
アプリケーションはTCPコネクションに対してオンまたはオフにできなければいけない。これはsocketに対してオプションを渡すことによって達成される。他にも方法はありこちらが詳しい。
KeepAliveはSO_KEEPALIVEを設定することによりオンにできる。 デフォルトはオフでなければならない(OS自身の設定)。
SEG.SEQ = SND.NXT-1 について
linuxのコードを見ると以下のように存在しているが、他のOSでもどうなっているか調べたが少なくともgrepなどでは引っかからなかった。
ag 'SEG.SEQ'
net/ipv4/tcp_output.c
3990: * one is with SEG.SEQ=SND.UNA to deliver urgent pointer, another is
net/netfilter/nf_synproxy_core.c
696: /* Keep-Alives are sent with SEG.SEQ = SND.NXT-1,
1119: /* Keep-Alives are sent with SEG.SEQ = SND.NXT-1,
golangでtcp serverの場合
- goでkeepaliveでのTCP通信をして検証してみる
package main
import (
"io"
"log"
"net"
)
func main() {
l, err := net.Listen("tcp", ":2000")
if err != nil {
panic(err)
}
defer l.Close()
for {
conn, err := l.Accept()
if err != nil {
log.Fatal(err)
}
go func(c net.Conn) {
io.Copy(c, c)
c.Close()
}(conn)
}
}
nc -v 127.0.0.1 2000
- localで通信をキャプチャするとKeepAliveの通信しているのがわかります
No. Time Source Destination Protocol Length Info
23 76.389967682 127.0.0.1 127.0.0.1 TCP 66 [TCP Keep-Alive] 2000 → 50212 [ACK] Seq=0 Ack=1 Win=65536 Len=0 TSval=409283988 TSecr=409268883
RFCの方を見るとMUST default to off (MUST-25) と書かれていますがこちらはOSの方の設定をさしています。GoのTCPServerでは特に何かを設定したわけではないですがKeepAliveが効いています。ドキュメントのListenConfig見るとosやプロトコルでサポートされていれば有効になるようです(特に何かを設定しない限り0になるはず)。
socketへのオプション
- 実際にどこでkeepaliveがオンになっているかを見ていく
conn, err := l.Accept()
func (l *TCPListener) Accept() (Conn, error) {
if !l.ok() {
return nil, syscall.EINVAL
}
c, err := l.accept()
if err != nil {
return nil, &OpError{Op: "accept", Net: l.fd.net, Source: nil, Addr: l.fd.laddr, Err: err}
}
return c, nil
}
- Acceptからaccept()を呼び出しているln.lc.KeepAliveはデフォルトで0なんで
- setKeepAlive() を呼び出している
func (ln *TCPListener) accept() (*TCPConn, error) {
fd, err := ln.fd.accept()
if err != nil {
return nil, err
}
tc := newTCPConn(fd)
if ln.lc.KeepAlive >= 0 {
setKeepAlive(fd, true)
ka := ln.lc.KeepAlive
if ln.lc.KeepAlive == 0 {
ka = defaultTCPKeepAlive
}
setKeepAlivePeriod(fd, ka)
}
return tc, nil
}
- setKeepAliveメソッドでsyscall.SOL_SOCKET syscall.SO_KEEPALIVE を渡している
- SOL_SOCKET はソケットレベルでオプションを指定するときに必要
- SO_KEEPALIVE はKeepAliveを有効にする
- これによりソケットでKeepAliveが有効になる
func setKeepAlive(fd *netFD, keepalive bool) error {
err := fd.pfd.SetsockoptInt(syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, boolint(keepalive))
runtime.KeepAlive(fd)
return wrapSyscallError("setsockopt", err)
}
- この状態でアクセスを受け付けるのでKeepAliveが有効な状態で通信が行われる
- KeepAliveが何をしているかは最初に書いたが詳細はこちらを見てもらったほうがいい
まとめ
- KeepAliveとはTCPの機能でRFC793 に記述されている
- 長い間送受信がない状態の通信がアクティブかどうかセグメント送信して確認する
- keepaliveの機能はOn/OffができてデフォルトはOff(あーこれOSレベルの方の話か)
- GoのTCP Serverの場合デフォルトでkeepaliveの機能は有効化されている
- 関係ないがOSの中身追うのとQUICが気になったので追ってみます
Discussion