| // 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. |
| |
| // +build !build_with_native_toolchain |
| |
| 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/HEAD/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/HEAD/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 := ep.SocketOptions().GetPassCred() |
| 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 := ep.SocketOptions().GetReuseAddress() |
| return boolToInt32(v), nil |
| |
| case C.SO_REUSEPORT: |
| v := ep.SocketOptions().GetReusePort() |
| 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 := ep.SocketOptions().GetBroadcast() |
| return boolToInt32(v), nil |
| |
| case C.SO_KEEPALIVE: |
| v := ep.SocketOptions().GetKeepAlive() |
| return boolToInt32(v), nil |
| |
| case C.SO_LINGER: |
| v := ep.SocketOptions().GetLinger() |
| 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: |
| v := ep.SocketOptions().GetOutOfBandInline() |
| return boolToInt32(v), nil |
| |
| case C.SO_NO_CHECK: |
| v := ep.SocketOptions().GetNoChecksum() |
| return boolToInt32(v), nil |
| |
| case C.SO_ACCEPTCONN: |
| var v bool |
| // From `man socket.7`, SO_ACCEPTCONN: |
| // |
| // Returns a value indicating whether or not this socket has been marked |
| // to accept connections with listen(2). |
| // |
| // And among the options here, `listen` only makes sense on TCP sockets. |
| if transProto == tcp.ProtocolNumber { |
| v = tcp.EndpointState(ep.State()) == tcp.StateListen |
| } |
| 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: |
| return boolToInt32(!ep.SocketOptions().GetDelayOption()), nil |
| |
| case C.TCP_CORK: |
| return boolToInt32(ep.SocketOptions().GetCorkOption()), nil |
| |
| case C.TCP_QUICKACK: |
| return boolToInt32(ep.SocketOptions().GetQuickAck()), 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: |
| return boolToInt32(ep.SocketOptions().GetV6Only()), 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: |
| return boolToInt32(ep.SocketOptions().GetMulticastLoop()), nil |
| |
| case C.IPV6_RECVTCLASS: |
| return boolToInt32(ep.SocketOptions().GetReceiveTClass()), 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: |
| return boolToInt32(ep.SocketOptions().GetMulticastLoop()), 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: |
| return boolToInt32(ep.SocketOptions().GetReceiveTOS()), nil |
| |
| case C.IP_PKTINFO: |
| return boolToInt32(ep.SocketOptions().GetReceivePacketInfo()), 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) |
| ep.SocketOptions().SetReuseAddress(v != 0) |
| return nil |
| |
| case C.SO_REUSEPORT: |
| if len(optVal) < sizeOfInt32 { |
| return tcpip.ErrInvalidOptionValue |
| } |
| |
| v := binary.LittleEndian.Uint32(optVal) |
| ep.SocketOptions().SetReusePort(v != 0) |
| return nil |
| |
| 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) |
| ep.SocketOptions().SetBroadcast(v != 0) |
| return nil |
| |
| case C.SO_PASSCRED: |
| if len(optVal) < sizeOfInt32 { |
| return tcpip.ErrInvalidOptionValue |
| } |
| |
| v := binary.LittleEndian.Uint32(optVal) |
| ep.SocketOptions().SetPassCred(v != 0) |
| return nil |
| |
| case C.SO_KEEPALIVE: |
| if len(optVal) < sizeOfInt32 { |
| return tcpip.ErrInvalidOptionValue |
| } |
| |
| v := binary.LittleEndian.Uint32(optVal) |
| ep.SocketOptions().SetKeepAlive(v != 0) |
| return nil |
| |
| case C.SO_LINGER: |
| var linger C.struct_linger |
| if err := linger.Unmarshal(optVal); err != nil { |
| return tcpip.ErrInvalidOptionValue |
| } |
| ep.SocketOptions().SetLinger(tcpip.LingerOption{ |
| Enabled: linger.l_onoff != 0, |
| Timeout: time.Second * time.Duration(linger.l_linger), |
| }) |
| return nil |
| |
| 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 |
| } |
| |
| v := binary.LittleEndian.Uint32(optVal) |
| ep.SocketOptions().SetOutOfBandInline(v != 0) |
| return nil |
| |
| case C.SO_NO_CHECK: |
| if len(optVal) < sizeOfInt32 { |
| return tcpip.ErrInvalidOptionValue |
| } |
| |
| v := binary.LittleEndian.Uint32(optVal) |
| ep.SocketOptions().SetNoChecksum(v != 0) |
| return nil |
| |
| 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 |
| } |
| |
| ep.SocketOptions().SetDelayOption(binary.LittleEndian.Uint32(optVal) == 0) |
| return nil |
| |
| case C.TCP_CORK: |
| if len(optVal) < sizeOfInt32 { |
| return tcpip.ErrInvalidOptionValue |
| } |
| |
| ep.SocketOptions().SetCorkOption(binary.LittleEndian.Uint32(optVal) != 0) |
| return nil |
| |
| case C.TCP_QUICKACK: |
| if len(optVal) < sizeOfInt32 { |
| return tcpip.ErrInvalidOptionValue |
| } |
| |
| ep.SocketOptions().SetQuickAck(binary.LittleEndian.Uint32(optVal) != 0) |
| return nil |
| |
| 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 |
| } |
| |
| ep.SocketOptions().SetV6Only(binary.LittleEndian.Uint32(optVal) != 0) |
| return nil |
| |
| 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 |
| } |
| ep.SocketOptions().SetMulticastLoop(v != 0) |
| return nil |
| |
| 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 |
| } |
| ep.SocketOptions().SetReceiveTClass(v != 0) |
| return nil |
| |
| 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 |
| } |
| |
| ep.SocketOptions().SetMulticastLoop(v != 0) |
| return nil |
| |
| 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 |
| } |
| ep.SocketOptions().SetReceiveTOS(v != 0) |
| return nil |
| |
| case C.IP_PKTINFO: |
| if len(optVal) == 0 { |
| return nil |
| } |
| v, err := parseIntOrChar(optVal) |
| if err != nil { |
| return err |
| } |
| ep.SocketOptions().SetReceivePacketInfo(v != 0) |
| return nil |
| |
| 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()) |
| } |
| } |