Slide 1

Slide 1 text

Goで ネットワークプログラミングへ入門 〜Network programming in Go〜 Gopherdojo LT 2018/05/28

Slide 2

Slide 2 text

WHO Nao Yamaguchi Job: - Telecommunications business Github: - https://github.com/naoyamaguchi Interest: - Golang, Angular - HTTP2.0, grpc, TLS1.3, QUIC - GCP, Container

Slide 3

Slide 3 text

Today’s Contents!! Network Programming ? 01 Why did I choose Go ? What I wanted to make What I made Impressions 02 03 04 05

Slide 4

Slide 4 text

Network Programming ? ● Overview ○ 通信プロトコルそのものを作ったりsocketを利用してもぐもぐが一般的 ○ DPDK, eBPFのような本格派 ● Great Products ○ src/net, netstack, gobgp, zebra ● Great Developper ○ Mikio Hara ○ Matt Layher

Slide 5

Slide 5 text

Why did I choose Go ? 1. Precondition ○ C/C++は書きたくない ○ 1から全部設定したくないけど、なぜその処理になったかを知りたい ○ どこがボトルネックになっているか最低限アプリ被疑を晴らしたい 2. Do not guess. Measure it. ○ 標準libraryがGoで書かれているのでGoさえ読めれば中身を理解できるのではないか ○ Benchmark, pprofが標準で存在しており簡単に調査 /改善することができるのではない か 3. Community ○ Kubernetesやgrpc等の新しい技術や人と触れ合える ○ 会社の指示で仕方なく。。。みたいな人がいない(はず)

Slide 6

Slide 6 text

What I wanted to make ● PGW ○ LTEで利用されるキャリア終端設備で接続・課金・通信処理等を行う ○ キャリアとの接続はGTPというUDPベースのトンネリングプロトコル ■ 相互接続に関する技術的条件集 ○ Internet方向はRawSocketでencap/decap処理 ■ 現状goではEthernetを直接扱う標準ライブラリはなさそう UE eNB Carrier Equipment (SGW/MME/HSS) PGW The Internet GTP C/U (UDP Sock) Any Protocols (Raw Sock)

Slide 7

Slide 7 text

What I made ● UDP Proxy 1. 標準パッケージでUDP 2. Uplink / Downlinkの並行処理 3. context, x/time/rateを利用した帯域制御 4. Benchmark 5. (todo) pprofによるプロファイリング Uplink Downlink

Slide 8

Slide 8 text

(reference) UDP通信の基本システムコールフロー Server Client ソケット作成 socket() IPとPortを指定 ソケットに名前をつける bind() データ受信 recvfrom() 終了する close() ソケット作成 socket() IPとPortを指定 データ受信 sendto() 終了する close()

Slide 9

Slide 9 text

What I made ● 標準パッケージでUDP ○ どんな設定のどんなシステムコールがなされるのかを知りたい UdpAddr := &net.UDPAddr{IP:net.ParseIP("0.0.0.0"), Port:2152} UdpConn, _ := net.ListenUDP("udp", UdpAddr) buf := make([]byte, 1550) for { n, addr, _ := UpdConn.ReadFromUDP(buf) go func() { UdpConn.WriteTo(buf[:n], addr) }() } func ListenUDP(network string, laddr *UDPAddr) (*UDPConn, error) { -- snip -- c, err := listenUDP(context.Background(), network, laddr) -- snip -- } net/udpsock.go

Slide 10

Slide 10 text

What I made func listenUDP(ctx context.Context,network string,laddr *UDPAddr) (*UDPConn,error){ fd, err := internetSocket(ctx,network,laddr,nil,syscall.SOCK_DGRAM,0,"listen") -- snip -- net/udpsock_posix.go func internetSocket(ctx context.Context, net string, laddr, raddr sockaddr, sotype, proto int, mode string) (fd *netFD, err error) { -- snip -- return socket(ctx, net, family, sotype, proto, ipv6only, laddr, raddr) -- snip -- net/ipsock_posix.go func socket(ctx context.Context, net string, family, sotype, proto int, ipv6only bool, laddr, raddr sockaddr) (fd *netFD, err error) { s, err := sysSocket(family, sotype, proto) // システムコールsocket()を呼んでいる -- snip -- if err := fd.dial(ctx, laddr, raddr); // システムコールbind()を呼んでいる net/sock_posix.go

Slide 11

Slide 11 text

What I made ● 標準パッケージでUDP:straceで答え合わせ ○ go build udp.go && strace -e 'trace=!pselect6,futex,sched_yield' ./udp →システムコールにみる Go言語のnetパッケージの実装 でTCPの分析している --- net.ListenUDP() --- socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3 setsockopt(3, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0 bind(3, {sa_family=AF_INET, sin_port=htons(2152), sin_addr=inet_addr("127.0.0.1")}, 16) = 0 epoll_ctl(4, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=1772719872, u64=140515922775808}}) = 0 --- updConn.ReadFromUDP() --- recvfrom(3, 0xc42004f8ba, 1550, 0, 0xc42004f5d8, [112]) = -1 EAGAIN (Resource temporarily unavailable) epoll_pwait(4, [{EPOLLOUT, {u32=4207877888, u64=140097451138816}}], 128, 0, NULL, 140097451138816) = 1

Slide 12

Slide 12 text

What I made ● Uplink / Downlinkの並行処理

Slide 13

Slide 13 text

What I made ● Uplink / Downlinkの並行処理 ○ sync.WaitGroupを利用しDoneさせないことで複数loopを保持(Chanelで返したいものがない func main() { var wg sync.WaitGroup wg.Add(1) go uplink() go downlink() wg.Wait() } func uplink() { for { n, addr , _ := conn.ReadFromUDP(buf) go func() { conn.WriteTo(buf[:n], addr) }() -- snip -- func downlink() { for { n, addr , _ := conn.ReadFromUDP(buf) go func() { conn.WriteTo(buf[:n], addr) }() -- snip --

Slide 14

Slide 14 text

What I made ● context, x/time/rateを利用した帯域制御 ○ golang.org/x/time/rateで速度制限を行う という@lufiaさまの記事にてチャネルへの送信数制御を受信し たbyte数(bit数)に置き換えることでいわゆる Token Bucket Filterを実装できそう const ( M = 80000000 // 1秒あたりの処理制限 ) func udpProxy() { updConn, _ := net.ListenUDP("udp", udpAddr) ctx := context.Background() n := rate.Every(time.Second / M) // 1秒あたりのトークン補充数 l := rate.NewLimiter(n, M) // トークン補充上限値 for { n, raddr, _ := updLn.ReadFromUDP(buffer) if err := l.WaitN(ctx, n); err != nil { // n個のトークン(受信したバイト数)を消費(ゆえに bpsではない) log.Fatalln(err) } go func() { updLn.WriteTo(buffer[:n], raddr) }() } }

Slide 15

Slide 15 text

What I made ● Benchmark ○ httptestのようにserve自体をmock?してくれる標準ライブラリはなさそう func BenchmarkServer(b *testing.B) { conn, _ := net.Dial("udp", "127.0.0.1:2152") defer conn.Close() b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = conn.Write([]byte("0123456789")) recvBuf := make([]byte, 1500) n, _ := conn.Read(recvBuf) log.Printf("Received data: %s", string(recvBuf[:n])) } } 実行回数 1回あたりの実行時間 1回あたりの実行で確保した容量 1回あたりのアロケーション回数 300000 4225 ns/op 112 B/op 1 allocs/op

Slide 16

Slide 16 text

What I made ● (TODO) pprof ○ net/http/pprofを仕込むことでwebUIからコールスタックや実行時間が確認できる(らしい) ○ まだ意味はわからない。

Slide 17

Slide 17 text

Impressions ● Conclusion ○ 標準libraryがGoで書かれているので Goさえ読めれば中身を理解できるのではないか ■ できそう。標準ライブラリを読みたいと思える( pythonで生じなかった感情) ○ Benchmark, pprofが標準で存在しており簡単に調査 /改善することができるのではないか ■ できそう。pprofを理解したい。 ■ 特に何もチューニングせずともそこそこの速度。 ● TODO ○ pprofの理解 ○ 標準ライブラリとしての syscallの理解 / Raw socketを利用したGTPサーバの開発 ○ grpcを利用した サービス/課金情報の連携 ○ Docker-composeを脱却してk8sへ ○ 状態管理と分散処理 ● 謝辞

Slide 18

Slide 18 text

Thank you