関連記事
GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ
概要
以下、自分用のメモです。忘れないようにここにメモメモ。。。
通信処理を書いていると、たまにプロトコル仕様にて特定のタイミングでRSTを送信する必要があったりします。
C言語の場合だと SO_LINGER を 0 で設定して close システムコール を呼ぶとRSTが送信されます。
Goの場合、ソケットは抽象化された net.Conn (*net.TCPConn) という形になっています。
んで、そのものスバリのメソッドが *net.TCPConn.SetLinger という名前のメソッドで存在します。
$ go doc net.tcpconn.setlinger package net // import "net" func (c *TCPConn) SetLinger(sec int) error SetLinger sets the behavior of Close on a connection which still has data waiting to be sent or to be acknowledged. If sec < 0 (the default), the operating system finishes sending the data in the background. If sec == 0, the operating system discards any unsent or unacknowledged data. If sec > 0, the data is sent in the background as with sec < 0. On some operating systems including Linux, this may cause Close to block until all data has been sent or discarded. On some operating systems after sec seconds have elapsed any remaining unsent data may be discarded.
サンプル
いちいちファイル分けて実装するのが面倒だったので、サーバーとクライアント兼用です。
main.go
package main import ( "flag" "net" ) type ( Args struct { IsServer bool UseRst bool } ) var ( args Args ) func init() { flag.BoolVar(&args.IsServer, "server", false, "サーバモードで起動") flag.BoolVar(&args.UseRst, "rst", false, "RST送信による強制切断を使用") } func main() { flag.Parse() if err := run(); err != nil { panic(err) } } func run() error { var ( err error ) if args.IsServer { err = runServer() } else { err = runClient() } if err != nil { return err } return nil } func runServer() error { var ( l net.Listener err error ) l, err = net.Listen("tcp", ":8888") if err != nil { return err } defer l.Close() // サンプルなので1回のみ接続を受付 var ( conn net.Conn ) conn, err = l.Accept() if err != nil { return err } defer conn.Close() // 適当にデータを送信しクライアントが切ってきたら終わり _, err = conn.Write([]byte("hello")) if err != nil { return err } var ( buf = make([]byte, 10) n int ) for { clear(buf) n, err = conn.Read(buf) if n == 0 || err != nil { break } } if args.UseRst { // RST送信するために SO_LINGER を設定 // Goの場合 *net.TCPConn に SetLinger メソッドが用意されている。 var ( tcpConn *net.TCPConn ok bool lingerSec = 0 ) tcpConn, ok = conn.(*net.TCPConn) if ok { // $ go doc net.tcpconn.setlinger // // > SetLinger sets the behavior of Close on a connection which still has data waiting to be sent or to be acknowledged. // > If sec < 0 (the default), the operating system finishes sending the data in the background. // > If sec == 0, the operating system discards any unsent or unacknowledged data. // > If sec > 0, the data is sent in the background as with sec < 0. On some operating systems including Linux, // > this may cause Close to block until all data has been sent or discarded. // > On some operating systems after sec seconds have elapsed any remaining unsent data may be discarded. tcpConn.SetLinger(lingerSec) } } return nil } func runClient() error { var ( conn net.Conn err error ) conn, err = net.Dial("tcp", "localhost:8888") if err != nil { return err } defer func() { conn.Close() }() // データを受信したら切断 var ( buf = make([]byte, 10) ) _, err = conn.Read(buf) if err != nil { return err } if args.UseRst { tcpConn, ok := conn.(*net.TCPConn) if ok { tcpConn.SetLinger(0) } } return nil }
Taskfile.yml
tcpdumpコマンドでパケットを確認するようにして実行します。
# https://taskfile.dev version: '3' tasks: default: cmds: - task: build - task: run build: cmds: - go build -o app main.go run: cmds: - task: run-fin - sleep 2 - task: run-rst run-fin: cmds: - sudo tcpdump -i lo -n 'tcp port 8888' -S & - sleep 1 - ./app -server & - ./app - sleep 2 - sudo pkill tcpdump run-rst: cmds: - sudo tcpdump -i lo -n 'tcp port 8888' -S & - sleep 1 - ./app -server -rst & - ./app -rst - sleep 2 - sudo pkill tcpdump
shell
実行すると以下のように出力されます。
$ task task: [build] go build -o app main.go task: [run-fin] sudo tcpdump -i lo -n 'tcp port 8888' -S & task: [run-fin] sleep 1 tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes task: [run-fin] ./app -server & task: [run-fin] ./app task: [run-fin] sleep 2 09:22:02.504826 IP 127.0.0.1.32782 > 127.0.0.1.8888: Flags [S], seq 1561972627, win 43690, options [mss 65495,sackOK,TS val 3209706398 ecr 0,nop,wscale 7], length 0 09:22:02.504835 IP 127.0.0.1.8888 > 127.0.0.1.32782: Flags [S.], seq 2366941560, ack 1561972628, win 43690, options [mss 65495,sackOK,TS val 3209706398 ecr 3209706398,nop,wscale 7], length 0 09:22:02.504844 IP 127.0.0.1.32782 > 127.0.0.1.8888: Flags [.], ack 2366941561, win 342, options [nop,nop,TS val 3209706398 ecr 3209706398], length 0 09:22:02.504914 IP 127.0.0.1.8888 > 127.0.0.1.32782: Flags [P.], seq 2366941561:2366941566, ack 1561972628, win 342, options [nop,nop,TS val 3209706398 ecr 3209706398], length 5 09:22:02.504924 IP 127.0.0.1.32782 > 127.0.0.1.8888: Flags [.], ack 2366941566, win 342, options [nop,nop,TS val 3209706398 ecr 3209706398], length 0 09:22:02.504944 IP 127.0.0.1.32782 > 127.0.0.1.8888: Flags [F.], seq 1561972628, ack 2366941566, win 342, options [nop,nop,TS val 3209706398 ecr 3209706398], length 0 09:22:02.504982 IP 127.0.0.1.8888 > 127.0.0.1.32782: Flags [F.], seq 2366941566, ack 1561972629, win 342, options [nop,nop,TS val 3209706398 ecr 3209706398], length 0 09:22:02.504995 IP 127.0.0.1.32782 > 127.0.0.1.8888: Flags [.], ack 2366941567, win 342, options [nop,nop,TS val 3209706398 ecr 3209706398], length 0 task: [run-fin] sudo pkill tcpdump 8 packets captured 16 packets received by filter 0 packets dropped by kernel task: [run] sleep 2 task: [run-rst] sudo tcpdump -i lo -n 'tcp port 8888' -S & task: [run-rst] sleep 1 tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes task: [run-rst] ./app -server -rst & task: [run-rst] ./app -rst task: [run-rst] sleep 2 09:22:07.566462 IP 127.0.0.1.50180 > 127.0.0.1.8888: Flags [S], seq 3347564028, win 43690, options [mss 65495,sackOK,TS val 3209711459 ecr 0,nop,wscale 7], length 0 09:22:07.566476 IP 127.0.0.1.8888 > 127.0.0.1.50180: Flags [S.], seq 671550310, ack 3347564029, win 43690, options [mss 65495,sackOK,TS val 3209711459 ecr 3209711459,nop,wscale 7], length 0 09:22:07.566485 IP 127.0.0.1.50180 > 127.0.0.1.8888: Flags [.], ack 671550311, win 342, options [nop,nop,TS val 3209711459 ecr 3209711459], length 0 09:22:07.566556 IP 127.0.0.1.8888 > 127.0.0.1.50180: Flags [P.], seq 671550311:671550316, ack 3347564029, win 342, options [nop,nop,TS val 3209711459 ecr 3209711459], length 5 09:22:07.566566 IP 127.0.0.1.50180 > 127.0.0.1.8888: Flags [.], ack 671550316, win 342, options [nop,nop,TS val 3209711459 ecr 3209711459], length 0 09:22:07.566589 IP 127.0.0.1.50180 > 127.0.0.1.8888: Flags [R.], seq 3347564029, ack 671550316, win 342, options [nop,nop,TS val 3209711459 ecr 3209711459], length 0 task: [run-rst] sudo pkill tcpdump 6 packets captured 12 packets received by filter 0 packets dropped by kernel
最初の実行は通常通りのFIN送信で実行しています。パケットの Flags を見ると
127.0.0.1.32782 > 127.0.0.1.8888: Flags [F.] 127.0.0.1.8888 > 127.0.0.1.32782: Flags [F.] 127.0.0.1.32782 > 127.0.0.1.8888: Flags [.]
となっており、ちゃんとFIN (FがFINの意味) が飛んでますね。
2回目の実行はRST送信するように SO_LINGER を設定してから close しています。パケットの Flags を見ると
127.0.0.1.50180 > 127.0.0.1.8888: Flags [R.]
でRST (RがRSTの意味) が送信されて、それでプチっと終了していますね。
参考情報
net package - net - Go Packages
ソケットオプションの使い方(SO_LINGER編) - hana_shinのLinux技術ブログ
sockets - When is TCP option SO_LINGER (0) required? - Stack Overflow
C言語 close()でRST送信する #ソケットプログラミング - Qiita
Goのおすすめ書籍
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。





