| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // package tcputil contains functions commonly used to manipulate TCP |
| // connections. |
| package tcputil |
| |
| import ( |
| "net" |
| "time" |
| |
| "v.io/v23/context" |
| "v.io/v23/flow" |
| |
| "v.io/x/ref/runtime/protocols/lib/framer" |
| ) |
| |
| const keepAlivePeriod = 30 * time.Second |
| |
| // EnableTCPKeepAlive enabled the KeepAlive option on a TCP connection. |
| // |
| // Some cloud providers (like Google Compute Engine) blackhole inactive TCP |
| // connections, we need to set TCP keep alive option to prevent that. |
| // See: https://developers.google.com/compute/docs/troubleshooting#communicatewithinternet |
| // |
| // The same problem can happen when one end of a TCP connection dies and the |
| // TCP FIN or RST packet doesn't reach the other end, e.g. when the machine |
| // dies, falls off the network, or when there is packet loss. So, it is best to |
| // enable this option for all TCP connections. |
| func EnableTCPKeepAlive(conn net.Conn) error { |
| if tcpconn, ok := conn.(*net.TCPConn); ok { |
| if err := tcpconn.SetKeepAlivePeriod(keepAlivePeriod); err != nil { |
| return err |
| } |
| return tcpconn.SetKeepAlive(true) |
| } |
| return nil |
| } |
| |
| type TCP struct{} |
| |
| // Dial dials a net.Conn to a the specific address and adds framing to the connection. |
| func (TCP) Dial(ctx *context.T, network, address string, timeout time.Duration) (flow.Conn, error) { |
| conn, err := net.DialTimeout(network, address, timeout) |
| if err != nil { |
| return nil, err |
| } |
| if err := EnableTCPKeepAlive(conn); err != nil { |
| return nil, err |
| } |
| return NewTCPConn(conn), nil |
| } |
| |
| // Resolve performs a DNS resolution on the provided network and address. |
| func (TCP) Resolve(ctx *context.T, network, address string) (string, []string, error) { |
| addrs, err := TCPResolveAddrs(ctx, address) |
| return network, addrs, err |
| } |
| |
| // Listen returns a listener that sets KeepAlive on all accepted connections. |
| // Connections returned from the listener will be framed. |
| func (TCP) Listen(ctx *context.T, network, address string) (flow.Listener, error) { |
| ln, err := net.Listen(network, address) |
| if err != nil { |
| return nil, err |
| } |
| return &tcpListener{ln}, nil |
| } |
| |
| // tcpListener is a wrapper around net.Listener that sets KeepAlive on all |
| // accepted connections and returns framed flow.Conns. |
| type tcpListener struct { |
| netLn net.Listener |
| } |
| |
| func (ln *tcpListener) Accept(ctx *context.T) (flow.Conn, error) { |
| conn, err := ln.netLn.Accept() |
| if err != nil { |
| return nil, err |
| } |
| if err := EnableTCPKeepAlive(conn); err != nil { |
| return nil, err |
| } |
| return NewTCPConn(conn), nil |
| } |
| |
| func (ln *tcpListener) Addr() net.Addr { |
| return ln.netLn.Addr() |
| } |
| |
| func (ln *tcpListener) Close() error { |
| return ln.netLn.Close() |
| } |
| |
| func NewTCPConn(c net.Conn) flow.Conn { |
| return tcpConn{framer.New(c), c.LocalAddr()} |
| } |
| |
| type tcpConn struct { |
| flow.MsgReadWriteCloser |
| localAddr net.Addr |
| } |
| |
| func (c tcpConn) LocalAddr() net.Addr { |
| return c.localAddr |
| } |
| |
| func TCPResolveAddrs(ctx *context.T, address string) ([]string, error) { |
| host, port, err := net.SplitHostPort(address) |
| if err != nil { |
| return nil, err |
| } |
| var addrs []string |
| ips, err := net.LookupIP(host) |
| if err != nil { |
| return nil, err |
| } |
| s := ":" + string(port) |
| for _, ip := range ips { |
| addrs = append(addrs, ip.String()+s) |
| } |
| return addrs, nil |
| } |