| // Copyright 2017 The Fuchsia 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 netstack | |
| import ( | |
| "bytes" | |
| "encoding/binary" | |
| "fmt" | |
| "math" | |
| "net" | |
| "time" | |
| syslog "go.fuchsia.dev/fuchsia/src/lib/syslog/go" | |
| fidlnet "fidl/fuchsia/net" | |
| "gvisor.dev/gvisor/pkg/tcpip" | |
| "gvisor.dev/gvisor/pkg/tcpip/header" | |
| "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" | |
| "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" | |
| "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" | |
| "gvisor.dev/gvisor/pkg/tcpip/transport/udp" | |
| ) | |
| // TODO(fxbug.dev/44347) We shouldn't need any of this includes after we remove | |
| // C structs from the wire. | |
| // #cgo CFLAGS: -D_GNU_SOURCE | |
| // #cgo CFLAGS: -I${SRCDIR}/../../../../zircon/third_party/ulib/musl/include/ | |
| // #include <netinet/in.h> | |
| // #include <netinet/tcp.h> | |
| // #include <netinet/udp.h> | |
| import "C" | |
| // Functions below are adapted from | |
| // https://github.com/google/gvisor/blob/master/pkg/sentry/socket/netstack/netstack.go | |
| // | |
| // At the time of writing, this command produces a reasonable diff: | |
| // | |
| /* | |
| curl -sfSL https://raw.githubusercontent.com/google/gvisor/master/pkg/sentry/socket/netstack/netstack.go | | |
| sed s/linux/C/g | \ | |
| sed 's/, outLen)/)/g' | \ | |
| sed 's/(t, /(/g' | \ | |
| sed 's/(s, /(/g' | \ | |
| sed 's/, family,/,/g' | \ | |
| sed 's/, skType,/, transProto,/g' | \ | |
| diff --color --ignore-all-space --unified - src/connectivity/network/netstack/socket_conv.go | |
| */ | |
| const ( | |
| // DefaultTTL is linux's default TTL. All network protocols in all stacks used | |
| // with this package must have this value set as their default TTL. | |
| DefaultTTL = 64 | |
| sizeOfInt32 int = 4 | |
| // Max values for sockopt TCP_KEEPIDLE and TCP_KEEPINTVL in Linux. | |
| // | |
| // https://github.com/torvalds/linux/blob/f2850dd5ee015bd7b77043f731632888887689c7/include/net/tcp.h#L156-L158 | |
| maxTCPKeepIdle = 32767 | |
| maxTCPKeepIntvl = 32767 | |
| maxTCPKeepCnt = 127 | |
| ) | |
| func boolToInt32(v bool) int32 { | |
| if v { | |
| return 1 | |
| } | |
| return 0 | |
| } | |
| func GetSockOpt(ep tcpip.Endpoint, ns *Netstack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, level, name int16) (interface{}, *tcpip.Error) { | |
| switch level { | |
| case C.SOL_SOCKET: | |
| return getSockOptSocket(ep, ns, netProto, transProto, name) | |
| case C.SOL_TCP: | |
| return getSockOptTCP(ep, name) | |
| case C.SOL_IPV6: | |
| return getSockOptIPv6(ep, name) | |
| case C.SOL_IP: | |
| return getSockOptIP(ep, name) | |
| case | |
| C.SOL_UDP, | |
| C.SOL_ICMPV6, | |
| C.SOL_RAW, | |
| C.SOL_PACKET: | |
| default: | |
| syslog.Infof("unimplemented getsockopt: level=%d name=%d", level, name) | |
| } | |
| return nil, tcpip.ErrUnknownProtocol | |
| } | |
| func getSockOptSocket(ep tcpip.Endpoint, ns *Netstack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, name int16) (interface{}, *tcpip.Error) { | |
| switch name { | |
| case C.SO_TYPE: | |
| switch transProto { | |
| case tcp.ProtocolNumber: | |
| return int32(C.SOCK_STREAM), nil | |
| case udp.ProtocolNumber: | |
| return int32(C.SOCK_DGRAM), nil | |
| default: | |
| return 0, tcpip.ErrNotSupported | |
| } | |
| case C.SO_DOMAIN: | |
| switch netProto { | |
| case ipv4.ProtocolNumber: | |
| return int32(C.AF_INET), nil | |
| case ipv6.ProtocolNumber: | |
| return int32(C.AF_INET6), nil | |
| default: | |
| return 0, tcpip.ErrNotSupported | |
| } | |
| case C.SO_PROTOCOL: | |
| switch transProto { | |
| case tcp.ProtocolNumber: | |
| return int32(C.IPPROTO_TCP), nil | |
| case udp.ProtocolNumber: | |
| return int32(C.IPPROTO_UDP), nil | |
| case header.ICMPv4ProtocolNumber: | |
| return int32(C.IPPROTO_ICMP), nil | |
| case header.ICMPv6ProtocolNumber: | |
| return int32(C.IPPROTO_ICMPV6), nil | |
| default: | |
| return 0, tcpip.ErrNotSupported | |
| } | |
| case C.SO_ERROR: | |
| // Get the last error and convert it. | |
| err := ep.LastError() | |
| if err == nil { | |
| return int32(0), nil | |
| } | |
| return int32(tcpipErrorToCode(err)), nil | |
| case C.SO_PEERCRED: | |
| return nil, tcpip.ErrNotSupported | |
| case C.SO_PASSCRED: | |
| v, err := ep.GetSockOptBool(tcpip.PasscredOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return boolToInt32(v), nil | |
| case C.SO_SNDBUF: | |
| size, err := ep.GetSockOptInt(tcpip.SendBufferSizeOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if size > math.MaxInt32 { | |
| size = math.MaxInt32 | |
| } | |
| return int32(size), nil | |
| case C.SO_RCVBUF: | |
| size, err := ep.GetSockOptInt(tcpip.ReceiveBufferSizeOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if size > math.MaxInt32 { | |
| size = math.MaxInt32 | |
| } | |
| return int32(size), nil | |
| case C.SO_REUSEADDR: | |
| v, err := ep.GetSockOptBool(tcpip.ReuseAddressOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return boolToInt32(v), nil | |
| case C.SO_REUSEPORT: | |
| v, err := ep.GetSockOptBool(tcpip.ReusePortOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return boolToInt32(v), nil | |
| case C.SO_BINDTODEVICE: | |
| var v tcpip.BindToDeviceOption | |
| if err := ep.GetSockOpt(&v); err != nil { | |
| return nil, err | |
| } | |
| if v == tcpip.BindToDeviceOption(0) { | |
| return []byte(nil), nil | |
| } | |
| nicInfos := ns.stack.NICInfo() | |
| for id, info := range nicInfos { | |
| if tcpip.BindToDeviceOption(id) == v { | |
| return append([]byte(info.Name), 0), nil | |
| } | |
| } | |
| return nil, tcpip.ErrUnknownDevice | |
| case C.SO_BROADCAST: | |
| v, err := ep.GetSockOptBool(tcpip.BroadcastOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return boolToInt32(v), nil | |
| case C.SO_KEEPALIVE: | |
| v, err := ep.GetSockOptBool(tcpip.KeepaliveEnabledOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return boolToInt32(v), nil | |
| case C.SO_LINGER: | |
| var v tcpip.LingerOption | |
| if err := ep.GetSockOpt(&v); err != nil { | |
| return nil, err | |
| } | |
| linger := C.struct_linger{ | |
| l_linger: C.int(v.Timeout.Seconds()), | |
| } | |
| if v.Enabled { | |
| linger.l_onoff = 1 | |
| } | |
| return linger, nil | |
| case C.SO_SNDTIMEO: | |
| return nil, tcpip.ErrNotSupported | |
| case C.SO_RCVTIMEO: | |
| return nil, tcpip.ErrNotSupported | |
| case C.SO_OOBINLINE: | |
| var v tcpip.OutOfBandInlineOption | |
| if err := ep.GetSockOpt(&v); err != nil { | |
| return nil, err | |
| } | |
| return int32(v), nil | |
| case C.SO_NO_CHECK: | |
| v, err := ep.GetSockOptBool(tcpip.NoChecksumOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return boolToInt32(v), nil | |
| default: | |
| syslog.Infof("unimplemented getsockopt: SOL_SOCKET name=%d", name) | |
| } | |
| return nil, tcpip.ErrUnknownProtocolOption | |
| } | |
| func getSockOptTCP(ep tcpip.Endpoint, name int16) (interface{}, *tcpip.Error) { | |
| switch name { | |
| case C.TCP_NODELAY: | |
| v, err := ep.GetSockOptBool(tcpip.DelayOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return boolToInt32(!v), nil | |
| case C.TCP_CORK: | |
| v, err := ep.GetSockOptBool(tcpip.CorkOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return boolToInt32(v), nil | |
| case C.TCP_QUICKACK: | |
| v, err := ep.GetSockOptBool(tcpip.QuickAckOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return boolToInt32(v), nil | |
| case C.TCP_MAXSEG: | |
| v, err := ep.GetSockOptInt(tcpip.MaxSegOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return int32(v), nil | |
| case C.TCP_KEEPIDLE: | |
| var v tcpip.KeepaliveIdleOption | |
| if err := ep.GetSockOpt(&v); err != nil { | |
| return nil, err | |
| } | |
| return int32(time.Duration(v).Seconds()), nil | |
| case C.TCP_KEEPINTVL: | |
| var v tcpip.KeepaliveIntervalOption | |
| if err := ep.GetSockOpt(&v); err != nil { | |
| return nil, err | |
| } | |
| return int32(time.Duration(v).Seconds()), nil | |
| case C.TCP_KEEPCNT: | |
| v, err := ep.GetSockOptInt(tcpip.KeepaliveCountOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return int32(v), nil | |
| case C.TCP_USER_TIMEOUT: | |
| var v tcpip.TCPUserTimeoutOption | |
| if err := ep.GetSockOpt(&v); err != nil { | |
| return nil, err | |
| } | |
| return int32(time.Duration(v).Milliseconds()), nil | |
| case C.TCP_CONGESTION: | |
| var v tcpip.CongestionControlOption | |
| if err := ep.GetSockOpt(&v); err != nil { | |
| return nil, err | |
| } | |
| // https://github.com/torvalds/linux/blob/f2850dd5ee015bd7b77043f731632888887689c7/include/net/tcp.h#L1012 | |
| const tcpCANameMax = 16 | |
| // Always send back the maximum length; truncation happens in the client. | |
| b := make([]byte, tcpCANameMax) | |
| _ = copy(b, v) | |
| return b, nil | |
| case C.TCP_DEFER_ACCEPT: | |
| var v tcpip.TCPDeferAcceptOption | |
| if err := ep.GetSockOpt(&v); err != nil { | |
| return nil, err | |
| } | |
| return int32(time.Duration(v).Seconds()), nil | |
| case C.TCP_INFO: | |
| var v tcpip.TCPInfoOption | |
| if err := ep.GetSockOpt(&v); err != nil { | |
| return nil, err | |
| } | |
| return C.struct_tcp_info{ | |
| // Microseconds. | |
| tcpi_rtt: C.uint(v.RTT.Nanoseconds() / 1000), | |
| tcpi_rttvar: C.uint(v.RTTVar.Nanoseconds() / 1000), | |
| }, nil | |
| case C.TCP_SYNCNT: | |
| v, err := ep.GetSockOptInt(tcpip.TCPSynCountOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return int32(v), nil | |
| case C.TCP_WINDOW_CLAMP: | |
| v, err := ep.GetSockOptInt(tcpip.TCPWindowClampOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return int32(v), nil | |
| case C.TCP_LINGER2: | |
| var v tcpip.TCPLingerTimeoutOption | |
| if err := ep.GetSockOpt(&v); err != nil { | |
| return nil, err | |
| } | |
| // Match Linux by clamping to -1. | |
| // | |
| // https://github.com/torvalds/linux/blob/15bc20c/net/ipv4/tcp.c#L3216-L3218 | |
| if v < 0 { | |
| return int32(-1), nil | |
| } | |
| // Linux uses this socket option to override `tcp_fin_timeout`, which is in | |
| // seconds. | |
| // | |
| // See the man page for details: https://man7.org/linux/man-pages/man7/tcp.7.html | |
| return int32(time.Duration(v) / time.Second), nil | |
| case | |
| C.TCP_CC_INFO, | |
| C.TCP_NOTSENT_LOWAT: | |
| default: | |
| syslog.Infof("unimplemented getsockopt: SOL_TCP name=%d", name) | |
| } | |
| return nil, tcpip.ErrUnknownProtocolOption | |
| } | |
| func getSockOptIPv6(ep tcpip.Endpoint, name int16) (interface{}, *tcpip.Error) { | |
| switch name { | |
| case C.IPV6_V6ONLY: | |
| v, err := ep.GetSockOptBool(tcpip.V6OnlyOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return boolToInt32(v), nil | |
| case C.IPV6_PATHMTU: | |
| case C.IPV6_TCLASS: | |
| v, err := ep.GetSockOptInt(tcpip.IPv6TrafficClassOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return int32(v), nil | |
| case C.IPV6_MULTICAST_IF: | |
| var v tcpip.MulticastInterfaceOption | |
| if err := ep.GetSockOpt(&v); err != nil { | |
| return nil, err | |
| } | |
| return int32(v.NIC), nil | |
| case C.IPV6_MULTICAST_HOPS: | |
| v, err := ep.GetSockOptInt(tcpip.MulticastTTLOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return int32(v), nil | |
| case C.IPV6_MULTICAST_LOOP: | |
| v, err := ep.GetSockOptBool(tcpip.MulticastLoopOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return boolToInt32(v), nil | |
| case C.IPV6_RECVTCLASS: | |
| v, err := ep.GetSockOptBool(tcpip.ReceiveTClassOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return boolToInt32(v), nil | |
| default: | |
| syslog.Infof("unimplemented getsockopt: SOL_IPV6 name=%d", name) | |
| } | |
| return nil, tcpip.ErrUnknownProtocolOption | |
| } | |
| func getSockOptIP(ep tcpip.Endpoint, name int16) (interface{}, *tcpip.Error) { | |
| switch name { | |
| case C.IP_TTL: | |
| v, err := ep.GetSockOptInt(tcpip.TTLOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| // Fill in default value, if needed. | |
| if v == 0 { | |
| v = DefaultTTL | |
| } | |
| return int32(v), nil | |
| case C.IP_MULTICAST_TTL: | |
| v, err := ep.GetSockOptInt(tcpip.MulticastTTLOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return int32(v), nil | |
| case C.IP_MULTICAST_IF: | |
| var v tcpip.MulticastInterfaceOption | |
| if err := ep.GetSockOpt(&v); err != nil { | |
| return nil, err | |
| } | |
| if len(v.InterfaceAddr) == 0 { | |
| return []byte(net.IPv4zero.To4()), nil | |
| } | |
| return []byte((v.InterfaceAddr)), nil | |
| case C.IP_MULTICAST_LOOP: | |
| v, err := ep.GetSockOptBool(tcpip.MulticastLoopOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return boolToInt32(v), nil | |
| case C.IP_TOS: | |
| v, err := ep.GetSockOptInt(tcpip.IPv4TOSOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return int32(v), nil | |
| case C.IP_RECVTOS: | |
| v, err := ep.GetSockOptBool(tcpip.ReceiveTOSOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return boolToInt32(v), nil | |
| case C.IP_PKTINFO: | |
| v, err := ep.GetSockOptBool(tcpip.ReceiveIPPacketInfoOption) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return boolToInt32(v), nil | |
| default: | |
| syslog.Infof("unimplemented getsockopt: SOL_IP name=%d", name) | |
| } | |
| return nil, tcpip.ErrUnknownProtocolOption | |
| } | |
| func SetSockOpt(ep tcpip.Endpoint, ns *Netstack, level, name int16, optVal []uint8) *tcpip.Error { | |
| switch level { | |
| case C.SOL_SOCKET: | |
| return setSockOptSocket(ep, ns, name, optVal) | |
| case C.SOL_TCP: | |
| return setSockOptTCP(ep, name, optVal) | |
| case C.SOL_IPV6: | |
| return setSockOptIPv6(ep, name, optVal) | |
| case C.SOL_IP: | |
| return setSockOptIP(ep, name, optVal) | |
| case C.SOL_UDP, | |
| C.SOL_ICMPV6, | |
| C.SOL_RAW, | |
| C.SOL_PACKET: | |
| default: | |
| syslog.Infof("unimplemented setsockopt: level=%d name=%d optVal=%x", level, name, optVal) | |
| } | |
| return tcpip.ErrUnknownProtocolOption | |
| } | |
| func setSockOptSocket(ep tcpip.Endpoint, ns *Netstack, name int16, optVal []byte) *tcpip.Error { | |
| switch name { | |
| case C.SO_SNDBUF: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := binary.LittleEndian.Uint32(optVal) | |
| return ep.SetSockOptInt(tcpip.SendBufferSizeOption, int(v)) | |
| case C.SO_RCVBUF: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := binary.LittleEndian.Uint32(optVal) | |
| return ep.SetSockOptInt(tcpip.ReceiveBufferSizeOption, int(v)) | |
| case C.SO_REUSEADDR: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := binary.LittleEndian.Uint32(optVal) | |
| return ep.SetSockOptBool(tcpip.ReuseAddressOption, v != 0) | |
| case C.SO_REUSEPORT: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := binary.LittleEndian.Uint32(optVal) | |
| return ep.SetSockOptBool(tcpip.ReusePortOption, v != 0) | |
| case C.SO_BINDTODEVICE: | |
| n := bytes.IndexByte(optVal, 0) | |
| if n == -1 { | |
| n = len(optVal) | |
| } | |
| if n == 0 { | |
| var opt tcpip.BindToDeviceOption | |
| return ep.SetSockOpt(&opt) | |
| } | |
| name := string(optVal[:n]) | |
| nicInfos := ns.stack.NICInfo() | |
| for id, info := range nicInfos { | |
| if name == info.Name { | |
| opt := tcpip.BindToDeviceOption(id) | |
| return ep.SetSockOpt(&opt) | |
| } | |
| } | |
| return tcpip.ErrUnknownDevice | |
| case C.SO_BROADCAST: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := binary.LittleEndian.Uint32(optVal) | |
| return ep.SetSockOptBool(tcpip.BroadcastOption, v != 0) | |
| case C.SO_PASSCRED: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := binary.LittleEndian.Uint32(optVal) | |
| return ep.SetSockOptBool(tcpip.PasscredOption, v != 0) | |
| case C.SO_KEEPALIVE: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := binary.LittleEndian.Uint32(optVal) | |
| return ep.SetSockOptBool(tcpip.KeepaliveEnabledOption, v != 0) | |
| case C.SO_LINGER: | |
| var linger C.struct_linger | |
| if err := linger.Unmarshal(optVal); err != nil { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| return ep.SetSockOpt(&tcpip.LingerOption{ | |
| Enabled: linger.l_onoff != 0, | |
| Timeout: time.Second * time.Duration(linger.l_linger), | |
| }) | |
| case C.SO_SNDTIMEO: | |
| return tcpip.ErrNotSupported | |
| case C.SO_RCVTIMEO: | |
| return tcpip.ErrNotSupported | |
| case C.SO_OOBINLINE: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| opt := tcpip.OutOfBandInlineOption(binary.LittleEndian.Uint32(optVal)) | |
| return ep.SetSockOpt(&opt) | |
| case C.SO_NO_CHECK: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := binary.LittleEndian.Uint32(optVal) | |
| return ep.SetSockOptBool(tcpip.NoChecksumOption, v != 0) | |
| default: | |
| syslog.Infof("unimplemented setsockopt: SOL_SOCKET name=%d optVal=%x", name, optVal) | |
| } | |
| return tcpip.ErrUnknownProtocolOption | |
| } | |
| // setSockOptTCP implements SetSockOpt when level is SOL_TCP. | |
| func setSockOptTCP(ep tcpip.Endpoint, name int16, optVal []byte) *tcpip.Error { | |
| switch name { | |
| case C.TCP_NODELAY: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := binary.LittleEndian.Uint32(optVal) | |
| return ep.SetSockOptBool(tcpip.DelayOption, v == 0) | |
| case C.TCP_CORK: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := binary.LittleEndian.Uint32(optVal) | |
| return ep.SetSockOptBool(tcpip.CorkOption, v != 0) | |
| case C.TCP_QUICKACK: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := binary.LittleEndian.Uint32(optVal) | |
| return ep.SetSockOptBool(tcpip.QuickAckOption, v != 0) | |
| case C.TCP_MAXSEG: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := binary.LittleEndian.Uint32(optVal) | |
| return ep.SetSockOptInt(tcpip.MaxSegOption, int(v)) | |
| case C.TCP_KEEPIDLE: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := binary.LittleEndian.Uint32(optVal) | |
| // https://github.com/torvalds/linux/blob/f2850dd5ee015bd7b77043f731632888887689c7/net/ipv4/tcp.c#L2991 | |
| if v < 1 || v > maxTCPKeepIdle { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| opt := tcpip.KeepaliveIdleOption(time.Second * time.Duration(v)) | |
| return ep.SetSockOpt(&opt) | |
| case C.TCP_KEEPINTVL: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := binary.LittleEndian.Uint32(optVal) | |
| // https://github.com/torvalds/linux/blob/f2850dd5ee015bd7b77043f731632888887689c7/net/ipv4/tcp.c#L3008 | |
| if v < 1 || v > maxTCPKeepIntvl { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| opt := tcpip.KeepaliveIntervalOption(time.Second * time.Duration(v)) | |
| return ep.SetSockOpt(&opt) | |
| case C.TCP_KEEPCNT: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := binary.LittleEndian.Uint32(optVal) | |
| // https://github.com/torvalds/linux/blob/f2850dd5ee015bd7b77043f731632888887689c7/net/ipv4/tcp.c#L3014 | |
| if v < 1 || v > maxTCPKeepCnt { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| return ep.SetSockOptInt(tcpip.KeepaliveCountOption, int(v)) | |
| case C.TCP_USER_TIMEOUT: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := int32(binary.LittleEndian.Uint32(optVal)) | |
| // https://github.com/torvalds/linux/blob/33b40134e5cfbbccad7f3040d1919889537a3df7/net/ipv4/tcp.c#L3086-L3094 | |
| if v < 0 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| opt := tcpip.TCPUserTimeoutOption(time.Millisecond * time.Duration(v)) | |
| return ep.SetSockOpt(&opt) | |
| case C.TCP_CONGESTION: | |
| opt := tcpip.CongestionControlOption(optVal) | |
| return ep.SetSockOpt(&opt) | |
| case C.TCP_DEFER_ACCEPT: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := int32(binary.LittleEndian.Uint32(optVal)) | |
| // Use 0 if negative to match Linux. | |
| // | |
| // https://github.com/torvalds/linux/blob/33b40134e5cfbbccad7f3040d1919889537a3df7/net/ipv4/tcp.c#L3045 | |
| if v < 0 { | |
| v = 0 | |
| } | |
| opt := tcpip.TCPDeferAcceptOption(time.Second * time.Duration(v)) | |
| return ep.SetSockOpt(&opt) | |
| case C.TCP_SYNCNT: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| return ep.SetSockOptInt(tcpip.TCPSynCountOption, int(binary.LittleEndian.Uint32(optVal))) | |
| case C.TCP_WINDOW_CLAMP: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| return ep.SetSockOptInt(tcpip.TCPWindowClampOption, int(binary.LittleEndian.Uint32(optVal))) | |
| case C.TCP_LINGER2: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| // Linux uses this socket option to override `tcp_fin_timeout`, which is in | |
| // seconds. | |
| // | |
| // See the man page for details: https://man7.org/linux/man-pages/man7/tcp.7.html | |
| opt := tcpip.TCPLingerTimeoutOption(time.Second * time.Duration( | |
| int32(binary.LittleEndian.Uint32(optVal)), | |
| )) | |
| return ep.SetSockOpt(&opt) | |
| case C.TCP_REPAIR_OPTIONS: | |
| default: | |
| syslog.Infof("unimplemented setsockopt: SOL_TCP name=%d optVal=%x", name, optVal) | |
| } | |
| return tcpip.ErrUnknownProtocolOption | |
| } | |
| // setSockOptIPv6 implements SetSockOpt when level is SOL_IPV6. | |
| func setSockOptIPv6(ep tcpip.Endpoint, name int16, optVal []byte) *tcpip.Error { | |
| switch name { | |
| case C.IPV6_V6ONLY: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := binary.LittleEndian.Uint32(optVal) | |
| return ep.SetSockOptBool(tcpip.V6OnlyOption, v != 0) | |
| case C.IPV6_ADD_MEMBERSHIP, C.IPV6_DROP_MEMBERSHIP: | |
| var ipv6_mreq C.struct_ipv6_mreq | |
| if err := ipv6_mreq.Unmarshal(optVal); err != nil { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| o := tcpip.MembershipOption{ | |
| NIC: tcpip.NICID(ipv6_mreq.ipv6mr_interface), | |
| MulticastAddr: tcpip.Address(ipv6_mreq.ipv6mr_multiaddr.Bytes()), | |
| } | |
| switch name { | |
| case C.IPV6_ADD_MEMBERSHIP: | |
| opt := tcpip.AddMembershipOption(o) | |
| return ep.SetSockOpt(&opt) | |
| case C.IPV6_DROP_MEMBERSHIP: | |
| opt := tcpip.RemoveMembershipOption(o) | |
| return ep.SetSockOpt(&opt) | |
| default: | |
| panic("unreachable") | |
| } | |
| case C.IPV6_MULTICAST_IF: | |
| v, err := parseIntOrChar(optVal) | |
| if err != nil { | |
| return err | |
| } | |
| opt := tcpip.MulticastInterfaceOption{ | |
| NIC: tcpip.NICID(v), | |
| } | |
| return ep.SetSockOpt(&opt) | |
| case C.IPV6_MULTICAST_HOPS: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v, err := parseIntOrChar(optVal) | |
| if err != nil { | |
| return err | |
| } | |
| if v == -1 { | |
| // Linux translates -1 to 1. | |
| v = 1 | |
| } | |
| if v < 0 || v > 255 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| return ep.SetSockOptInt(tcpip.MulticastTTLOption, int(v)) | |
| case C.IPV6_MULTICAST_LOOP: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v, err := parseIntOrChar(optVal) | |
| if err != nil { | |
| return err | |
| } | |
| return ep.SetSockOptBool(tcpip.MulticastLoopOption, v != 0) | |
| case | |
| C.IPV6_IPSEC_POLICY, | |
| C.IPV6_JOIN_ANYCAST, | |
| C.IPV6_LEAVE_ANYCAST, | |
| C.IPV6_PKTINFO, | |
| C.IPV6_ROUTER_ALERT, | |
| C.IPV6_XFRM_POLICY, | |
| C.MCAST_BLOCK_SOURCE, | |
| C.MCAST_JOIN_GROUP, | |
| C.MCAST_JOIN_SOURCE_GROUP, | |
| C.MCAST_LEAVE_GROUP, | |
| C.MCAST_LEAVE_SOURCE_GROUP, | |
| C.MCAST_UNBLOCK_SOURCE: | |
| case C.IPV6_TCLASS: | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v := int32(binary.LittleEndian.Uint32(optVal)) | |
| if v < -1 || v > 255 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| if v == -1 { | |
| v = 0 | |
| } | |
| return ep.SetSockOptInt(tcpip.IPv6TrafficClassOption, int(v)) | |
| case C.IPV6_RECVTCLASS: | |
| // Although this is a boolean int flag, linux enforces that it is not | |
| // a char. This is a departure for how this is handled for the | |
| // comparable IPv4 option. | |
| // https://github.com/torvalds/linux/blob/f2850dd5ee015bd7b77043f731632888887689c7/net/ipv6/ipv6_sockglue.c#L345 | |
| if len(optVal) < sizeOfInt32 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| v, err := parseIntOrChar(optVal) | |
| if err != nil { | |
| return err | |
| } | |
| return ep.SetSockOptBool(tcpip.ReceiveTClassOption, v != 0) | |
| default: | |
| func() { | |
| var optName string | |
| switch name { | |
| case C.IPV6_UNICAST_HOPS: | |
| optName = "IPV6_UNICAST_HOPS" | |
| case C.IPV6_HOPLIMIT: | |
| optName = "IPV6_HOPLIMIT" | |
| default: | |
| syslog.Infof("unimplemented setsockopt: SOL_IPV6 name=%d optVal=%x", name, optVal) | |
| return | |
| } | |
| v, _ := parseIntOrChar(optVal) | |
| syslog.Infof("unimplemented setsockopt(SOL_IPV6,%s,%d)", optName, v) | |
| }() | |
| } | |
| return tcpip.ErrUnknownProtocolOption | |
| } | |
| // parseIntOrChar copies either a 32-bit int or an 8-bit uint out of buf. | |
| // | |
| // net/ipv4/ip_sockglue.c:do_ip_setsockopt does this for its socket options. | |
| func parseIntOrChar(buf []byte) (int32, *tcpip.Error) { | |
| if len(buf) == 0 { | |
| return 0, tcpip.ErrInvalidOptionValue | |
| } | |
| if len(buf) >= sizeOfInt32 { | |
| return int32(binary.LittleEndian.Uint32(buf)), nil | |
| } | |
| return int32(buf[0]), nil | |
| } | |
| // setSockOptIP implements SetSockOpt when level is SOL_IP. | |
| func setSockOptIP(ep tcpip.Endpoint, name int16, optVal []byte) *tcpip.Error { | |
| switch name { | |
| case C.IP_MULTICAST_TTL: | |
| v, err := parseIntOrChar(optVal) | |
| if err != nil { | |
| return err | |
| } | |
| if v == -1 { | |
| // Linux translates -1 to 1. | |
| v = 1 | |
| } | |
| if v < 0 || v > 255 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| return ep.SetSockOptInt(tcpip.MulticastTTLOption, int(v)) | |
| case C.IP_ADD_MEMBERSHIP, C.IP_DROP_MEMBERSHIP, C.IP_MULTICAST_IF: | |
| var mreqn C.struct_ip_mreqn | |
| switch len(optVal) { | |
| case C.sizeof_struct_ip_mreq: | |
| var mreq C.struct_ip_mreq | |
| if err := mreq.Unmarshal(optVal); err != nil { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| mreqn.imr_multiaddr = mreq.imr_multiaddr | |
| mreqn.imr_address = mreq.imr_interface | |
| case C.sizeof_struct_ip_mreqn: | |
| if err := mreqn.Unmarshal(optVal); err != nil { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| case C.sizeof_struct_in_addr: | |
| if name == C.IP_MULTICAST_IF { | |
| copy(mreqn.imr_address.Bytes(), optVal) | |
| break | |
| } | |
| fallthrough | |
| default: | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| switch name { | |
| case C.IP_ADD_MEMBERSHIP, C.IP_DROP_MEMBERSHIP: | |
| o := tcpip.MembershipOption{ | |
| NIC: tcpip.NICID(mreqn.imr_ifindex), | |
| MulticastAddr: tcpip.Address(mreqn.imr_multiaddr.Bytes()), | |
| InterfaceAddr: tcpip.Address(mreqn.imr_address.Bytes()), | |
| } | |
| switch name { | |
| case C.IP_ADD_MEMBERSHIP: | |
| opt := tcpip.AddMembershipOption(o) | |
| return ep.SetSockOpt(&opt) | |
| case C.IP_DROP_MEMBERSHIP: | |
| opt := tcpip.RemoveMembershipOption(o) | |
| return ep.SetSockOpt(&opt) | |
| default: | |
| panic("unreachable") | |
| } | |
| case C.IP_MULTICAST_IF: | |
| interfaceAddr := mreqn.imr_address.Bytes() | |
| if isZeros(interfaceAddr) { | |
| interfaceAddr = nil | |
| } | |
| return ep.SetSockOpt(&tcpip.MulticastInterfaceOption{ | |
| NIC: tcpip.NICID(mreqn.imr_ifindex), | |
| InterfaceAddr: tcpip.Address(interfaceAddr), | |
| }) | |
| default: | |
| panic("unreachable") | |
| } | |
| case C.IP_MULTICAST_LOOP: | |
| v, err := parseIntOrChar(optVal) | |
| if err != nil { | |
| return err | |
| } | |
| return ep.SetSockOptBool(tcpip.MulticastLoopOption, v != 0) | |
| case C.MCAST_JOIN_GROUP: | |
| // FIXME: Disallow IP-level multicast group options by | |
| // default. These will need to be supported by appropriately plumbing | |
| // the level through to the network stack (if at all). However, we | |
| // still allow setting TTL, and multicast-enable/disable type options. | |
| return tcpip.ErrNotSupported | |
| case C.IP_TTL: | |
| v, err := parseIntOrChar(optVal) | |
| if err != nil { | |
| return err | |
| } | |
| // -1 means default TTL. | |
| if v == -1 { | |
| v = 0 | |
| } else if v < 1 || v > 255 { | |
| return tcpip.ErrInvalidOptionValue | |
| } | |
| return ep.SetSockOptInt(tcpip.TTLOption, int(v)) | |
| case C.IP_TOS: | |
| if len(optVal) == 0 { | |
| return nil | |
| } | |
| v, err := parseIntOrChar(optVal) | |
| if err != nil { | |
| return err | |
| } | |
| return ep.SetSockOptInt(tcpip.IPv4TOSOption, int(v)) | |
| case C.IP_RECVTOS: | |
| v, err := parseIntOrChar(optVal) | |
| if err != nil { | |
| return err | |
| } | |
| return ep.SetSockOptBool(tcpip.ReceiveTOSOption, v != 0) | |
| case C.IP_PKTINFO: | |
| if len(optVal) == 0 { | |
| return nil | |
| } | |
| v, err := parseIntOrChar(optVal) | |
| if err != nil { | |
| return err | |
| } | |
| return ep.SetSockOptBool(tcpip.ReceiveIPPacketInfoOption, v != 0) | |
| case | |
| C.IP_ADD_SOURCE_MEMBERSHIP, | |
| C.IP_BIND_ADDRESS_NO_PORT, | |
| C.IP_BLOCK_SOURCE, | |
| C.IP_CHECKSUM, | |
| C.IP_DROP_SOURCE_MEMBERSHIP, | |
| C.IP_FREEBIND, | |
| C.IP_HDRINCL, | |
| C.IP_IPSEC_POLICY, | |
| C.IP_MINTTL, | |
| C.IP_MSFILTER, | |
| C.IP_MTU_DISCOVER, | |
| C.IP_MULTICAST_ALL, | |
| C.IP_NODEFRAG, | |
| C.IP_OPTIONS, | |
| C.IP_PASSSEC, | |
| C.IP_RECVERR, | |
| C.IP_RECVOPTS, | |
| C.IP_RECVORIGDSTADDR, | |
| C.IP_RECVTTL, | |
| C.IP_RETOPTS, | |
| C.IP_TRANSPARENT, | |
| C.IP_UNBLOCK_SOURCE, | |
| C.IP_UNICAST_IF, | |
| C.IP_XFRM_POLICY, | |
| C.MCAST_BLOCK_SOURCE, | |
| C.MCAST_JOIN_SOURCE_GROUP, | |
| C.MCAST_LEAVE_GROUP, | |
| C.MCAST_LEAVE_SOURCE_GROUP, | |
| C.MCAST_MSFILTER, | |
| C.MCAST_UNBLOCK_SOURCE: | |
| default: | |
| syslog.Infof("unimplemented setsockopt: SOL_IP name=%d optVal=%x", name, optVal) | |
| } | |
| return tcpip.ErrUnknownProtocolOption | |
| } | |
| // isLinkLocal determines if the given IPv6 address is link-local. This is the | |
| // case when it has the fe80::/10 prefix. This check is used to determine when | |
| // the NICID is relevant for a given IPv6 address. | |
| func isLinkLocal(addr fidlnet.Ipv6Address) bool { | |
| return addr.Addr[0] == 0xfe && addr.Addr[1]&0xc0 == 0x80 | |
| } | |
| // toNetSocketAddress converts a tcpip.FullAddress into a fidlnet.SocketAddress | |
| // taking the protocol into consideration. If addr is unspecified, the | |
| // unspecified address for the provided protocol is returned. | |
| // | |
| // Panics if protocol is neither IPv4 nor IPv6. | |
| func toNetSocketAddress(protocol tcpip.NetworkProtocolNumber, addr tcpip.FullAddress) fidlnet.SocketAddress { | |
| switch protocol { | |
| case ipv4.ProtocolNumber: | |
| out := fidlnet.Ipv4SocketAddress{ | |
| Port: addr.Port, | |
| } | |
| copy(out.Address.Addr[:], addr.Addr) | |
| return fidlnet.SocketAddressWithIpv4(out) | |
| case ipv6.ProtocolNumber: | |
| out := fidlnet.Ipv6SocketAddress{ | |
| Port: addr.Port, | |
| } | |
| if len(addr.Addr) == header.IPv4AddressSize { | |
| // Copy address in v4-mapped format. | |
| copy(out.Address.Addr[header.IPv6AddressSize-header.IPv4AddressSize:], addr.Addr) | |
| out.Address.Addr[header.IPv6AddressSize-header.IPv4AddressSize-1] = 0xff | |
| out.Address.Addr[header.IPv6AddressSize-header.IPv4AddressSize-2] = 0xff | |
| } else { | |
| copy(out.Address.Addr[:], addr.Addr) | |
| if isLinkLocal(out.Address) { | |
| out.ZoneIndex = uint64(addr.NIC) | |
| } | |
| } | |
| return fidlnet.SocketAddressWithIpv6(out) | |
| default: | |
| panic(fmt.Sprintf("invalid protocol for conversion: %d", protocol)) | |
| } | |
| } | |
| func toTCPIPFullAddress(addr fidlnet.SocketAddress) (tcpip.FullAddress, error) { | |
| skipZeros := func(b []uint8) tcpip.Address { | |
| if isZeros(b) { | |
| return "" | |
| } | |
| return tcpip.Address(b) | |
| } | |
| switch addr.Which() { | |
| case fidlnet.SocketAddressIpv4: | |
| return tcpip.FullAddress{ | |
| NIC: 0, | |
| Addr: skipZeros(addr.Ipv4.Address.Addr[:]), | |
| Port: addr.Ipv4.Port, | |
| }, nil | |
| case fidlnet.SocketAddressIpv6: | |
| return tcpip.FullAddress{ | |
| NIC: tcpip.NICID(addr.Ipv6.ZoneIndex), | |
| Addr: skipZeros(addr.Ipv6.Address.Addr[:]), | |
| Port: addr.Ipv6.Port, | |
| }, nil | |
| default: | |
| return tcpip.FullAddress{}, fmt.Errorf("invalid fuchsia.net/SocketAddress variant: %d", addr.Which()) | |
| } | |
| } |