blob: 5693a6b5170ef9e76acac39af38f0d909820decf [file] [log] [blame]
// 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 (
"encoding/binary"
"log"
"math"
"time"
"github.com/google/netstack/tcpip"
"github.com/google/netstack/tcpip/transport/tcp"
"github.com/google/netstack/tcpip/transport/udp"
)
// #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
// github.com/google/gvisor/pkg/sentry/socket/epsocket/epsocket.go.
//
// At the time of writing, this command produces a reasonable diff:
//
// curl -sfSL https://raw.githubusercontent.com/google/gvisor/master/pkg/sentry/socket/epsocket/epsocket.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 - go/src/netstack/socket_conv.go
const sizeOfInt32 int = 4
func GetSockOpt(ep tcpip.Endpoint, transProto tcpip.TransportProtocolNumber, level, name int16, legacy bool) (interface{}, *tcpip.Error) {
switch level {
case C.SOL_SOCKET:
return getSockOptSocket(ep, transProto, name, legacy)
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:
log.Printf("unimplemented getsockopt: level=%d name=%d", level, name)
}
return nil, tcpip.ErrUnknownProtocol
}
func getSockOptSocket(ep tcpip.Endpoint, transProto tcpip.TransportProtocolNumber, name int16, legacy bool) (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_ERROR:
// Get the last error and convert it.
err := ep.GetSockOpt(tcpip.ErrorOption{})
if err == nil {
return int32(0), nil
}
if legacy {
return int32(zxNetError(err)), nil
} else {
return int32(tcpipErrorToCode(err)), nil
}
case C.SO_PEERCRED:
return nil, tcpip.ErrNotSupported
case C.SO_PASSCRED:
var v tcpip.PasscredOption
if err := ep.GetSockOpt(&v); err != nil {
return nil, err
}
return int32(v), nil
case C.SO_SNDBUF:
var size tcpip.SendBufferSizeOption
if err := ep.GetSockOpt(&size); err != nil {
return nil, err
}
if size > math.MaxInt32 {
size = math.MaxInt32
}
return int32(size), nil
case C.SO_RCVBUF:
var size tcpip.ReceiveBufferSizeOption
if err := ep.GetSockOpt(&size); err != nil {
return nil, err
}
if size > math.MaxInt32 {
size = math.MaxInt32
}
return int32(size), nil
case C.SO_REUSEADDR:
var v tcpip.ReuseAddressOption
if err := ep.GetSockOpt(&v); err != nil {
return nil, err
}
return int32(v), nil
case C.SO_REUSEPORT:
var v tcpip.ReusePortOption
if err := ep.GetSockOpt(&v); err != nil {
return nil, err
}
return int32(v), nil
case C.SO_KEEPALIVE:
var v tcpip.KeepaliveEnabledOption
if err := ep.GetSockOpt(&v); err != nil {
return nil, err
}
return int32(v), nil
case C.SO_LINGER:
return C.struct_linger{}, nil
case C.SO_SNDTIMEO:
return nil, tcpip.ErrNotSupported
case C.SO_RCVTIMEO:
return nil, tcpip.ErrNotSupported
case C.SO_TIMESTAMP:
var v tcpip.TimestampOption
if err := ep.GetSockOpt(&v); err != nil {
return nil, err
}
return int32(v), nil
case C.SO_OOBINLINE:
var v tcpip.OutOfBandInlineOption
if err := ep.GetSockOpt(&v); err != nil {
return nil, err
}
return int32(v), nil
default:
log.Printf("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:
var v tcpip.DelayOption
if err := ep.GetSockOpt(&v); err != nil {
return nil, err
}
if v == 0 {
return int32(1), nil
}
return int32(0), nil
case C.TCP_CORK:
var v tcpip.CorkOption
if err := ep.GetSockOpt(&v); err != nil {
return nil, err
}
return int32(v), nil
case C.TCP_QUICKACK:
var v tcpip.QuickAckOption
if err := ep.GetSockOpt(&v); 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) / time.Second), nil
case C.TCP_KEEPINTVL:
var v tcpip.KeepaliveIntervalOption
if err := ep.GetSockOpt(&v); err != nil {
return nil, err
}
return int32(time.Duration(v) / time.Second), 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_CC_INFO,
C.TCP_NOTSENT_LOWAT:
default:
log.Printf("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:
var v tcpip.V6OnlyOption
if err := ep.GetSockOpt(&v); err != nil {
return nil, err
}
return int32(v), nil
case C.IPV6_PATHMTU:
default:
log.Printf("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_MULTICAST_TTL:
var v tcpip.MulticastTTLOption
if err := ep.GetSockOpt(&v); err != nil {
return nil, err
}
return int32(v), nil
default:
log.Printf("unimplemented getsockopt: SOL_IP name=%d", name)
}
return nil, tcpip.ErrUnknownProtocolOption
}
func SetSockOpt(ep tcpip.Endpoint, level, name int16, optVal []uint8) *tcpip.Error {
switch level {
case C.SOL_SOCKET:
return setSockOptSocket(ep, 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:
log.Printf("unimplemented setsockopt: level=%d name=%d optVal=%x", level, name, optVal)
}
return ep.SetSockOpt(struct{}{})
}
func setSockOptSocket(ep tcpip.Endpoint, 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.SetSockOpt(tcpip.SendBufferSizeOption(v))
case C.SO_RCVBUF:
if len(optVal) < sizeOfInt32 {
return tcpip.ErrInvalidOptionValue
}
v := binary.LittleEndian.Uint32(optVal)
return ep.SetSockOpt(tcpip.ReceiveBufferSizeOption(v))
case C.SO_REUSEADDR:
if len(optVal) < sizeOfInt32 {
return tcpip.ErrInvalidOptionValue
}
v := binary.LittleEndian.Uint32(optVal)
return ep.SetSockOpt(tcpip.ReuseAddressOption(v))
case C.SO_REUSEPORT:
if len(optVal) < sizeOfInt32 {
return tcpip.ErrInvalidOptionValue
}
v := binary.LittleEndian.Uint32(optVal)
return ep.SetSockOpt(tcpip.ReusePortOption(v))
case C.SO_PASSCRED:
if len(optVal) < sizeOfInt32 {
return tcpip.ErrInvalidOptionValue
}
v := binary.LittleEndian.Uint32(optVal)
return ep.SetSockOpt(tcpip.PasscredOption(v))
case C.SO_KEEPALIVE:
if len(optVal) < sizeOfInt32 {
return tcpip.ErrInvalidOptionValue
}
v := binary.LittleEndian.Uint32(optVal)
return ep.SetSockOpt(tcpip.KeepaliveEnabledOption(v))
case C.SO_SNDTIMEO:
return tcpip.ErrNotSupported
case C.SO_RCVTIMEO:
return tcpip.ErrNotSupported
case C.SO_TIMESTAMP:
if len(optVal) < sizeOfInt32 {
return tcpip.ErrInvalidOptionValue
}
v := binary.LittleEndian.Uint32(optVal)
return ep.SetSockOpt(tcpip.TimestampOption(v))
default:
log.Printf("unimplemented setsockopt: SOL_SOCKET name=%d optVal=%x", name, optVal)
}
return ep.SetSockOpt(struct{}{})
}
// 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)
var o tcpip.DelayOption
if v == 0 {
o = 1
}
return ep.SetSockOpt(o)
case C.TCP_CORK:
if len(optVal) < sizeOfInt32 {
return tcpip.ErrInvalidOptionValue
}
v := binary.LittleEndian.Uint32(optVal)
return ep.SetSockOpt(tcpip.CorkOption(v))
case C.TCP_QUICKACK:
if len(optVal) < sizeOfInt32 {
return tcpip.ErrInvalidOptionValue
}
v := binary.LittleEndian.Uint32(optVal)
return ep.SetSockOpt(tcpip.QuickAckOption(v))
case C.TCP_KEEPIDLE:
if len(optVal) < sizeOfInt32 {
return tcpip.ErrInvalidOptionValue
}
v := binary.LittleEndian.Uint32(optVal)
return ep.SetSockOpt(tcpip.KeepaliveIdleOption(time.Second * time.Duration(v)))
case C.TCP_KEEPINTVL:
if len(optVal) < sizeOfInt32 {
return tcpip.ErrInvalidOptionValue
}
v := binary.LittleEndian.Uint32(optVal)
return ep.SetSockOpt(tcpip.KeepaliveIntervalOption(time.Second * time.Duration(v)))
case C.TCP_REPAIR_OPTIONS:
default:
log.Printf("unimplemented setsockopt: SOL_TCP name=%d optVal=%x", name, optVal)
}
return ep.SetSockOpt(struct{}{})
}
// 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.SetSockOpt(tcpip.V6OnlyOption(v))
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:
return ep.SetSockOpt(tcpip.AddMembershipOption(o))
case C.IPV6_DROP_MEMBERSHIP:
return ep.SetSockOpt(tcpip.RemoveMembershipOption(o))
default:
panic("unreachable")
}
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:
default:
log.Printf("unimplemented setsockopt: SOL_IPV6 name=%d optVal=%x", name, optVal)
}
return ep.SetSockOpt(struct{}{})
}
// 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:
if len(optVal) == 0 {
return tcpip.ErrInvalidOptionValue
}
if len(optVal) > sizeOfInt32 {
optVal = optVal[:sizeOfInt32]
}
var v int32
for i, b := range optVal {
v += int32(b) << uint(i*8)
}
if v == -1 {
// Linux translates -1 to 1.
v = 1
}
return ep.SetSockOpt(tcpip.MulticastTTLOption(v))
case C.IP_ADD_MEMBERSHIP, C.IP_DROP_MEMBERSHIP:
var o tcpip.MembershipOption
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
}
o = tcpip.MembershipOption{
MulticastAddr: tcpip.Address(mreq.imr_multiaddr.Bytes()),
InterfaceAddr: tcpip.Address(mreq.imr_interface.Bytes()),
}
case C.sizeof_struct_ip_mreqn:
var mreqn C.struct_ip_mreqn
if err := mreqn.Unmarshal(optVal); err != nil {
return tcpip.ErrInvalidOptionValue
}
o = tcpip.MembershipOption{
NIC: tcpip.NICID(mreqn.imr_ifindex),
MulticastAddr: tcpip.Address(mreqn.imr_multiaddr.Bytes()),
InterfaceAddr: tcpip.Address(mreqn.imr_address.Bytes()),
}
default:
return tcpip.ErrInvalidOptionValue
}
switch name {
case C.IP_ADD_MEMBERSHIP:
return ep.SetSockOpt(tcpip.AddMembershipOption(o))
case C.IP_DROP_MEMBERSHIP:
return ep.SetSockOpt(tcpip.RemoveMembershipOption(o))
default:
panic("unreachable")
}
case C.MCAST_JOIN_GROUP, C.IP_MULTICAST_IF:
// 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_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_MULTICAST_LOOP,
C.IP_NODEFRAG,
C.IP_OPTIONS,
C.IP_PASSSEC,
C.IP_PKTINFO,
C.IP_RECVERR,
C.IP_RECVOPTS,
C.IP_RECVORIGDSTADDR,
C.IP_RECVTOS,
C.IP_RECVTTL,
C.IP_RETOPTS,
C.IP_TOS,
C.IP_TRANSPARENT,
C.IP_TTL,
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:
log.Printf("unimplemented setsockopt: SOL_IP name=%d optVal=%x", name, optVal)
}
return ep.SetSockOpt(struct{}{})
}
// 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 tcpip.Address) bool {
return len(addr) >= 2 && addr[0] == 0xfe && addr[1]&0xc0 == 0x80
}