// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package netstack

import (
	"fmt"
	"maps"
	"slices"

	"gvisor.dev/gvisor/pkg/abi/linux"
	"gvisor.dev/gvisor/pkg/context"
	"gvisor.dev/gvisor/pkg/errors/linuxerr"
	"gvisor.dev/gvisor/pkg/log"
	"gvisor.dev/gvisor/pkg/refs"
	"gvisor.dev/gvisor/pkg/sentry/inet"
	"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
	"gvisor.dev/gvisor/pkg/sentry/socket/netlink/nlmsg"
	"gvisor.dev/gvisor/pkg/syserr"
	"gvisor.dev/gvisor/pkg/tcpip"
	"gvisor.dev/gvisor/pkg/tcpip/header"
	"gvisor.dev/gvisor/pkg/tcpip/link/ethernet"
	"gvisor.dev/gvisor/pkg/tcpip/link/packetsocket"
	"gvisor.dev/gvisor/pkg/tcpip/link/veth"
	"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
	"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
	"gvisor.dev/gvisor/pkg/tcpip/stack"
	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
)

// Stack implements inet.Stack for netstack/tcpip/stack.Stack.
//
// +stateify savable
type Stack struct {
	Stack *stack.Stack `state:".(*stack.Stack)"`

	eventSubscriber inet.InterfaceEventSubscriber

	// linkMu serializes link creation, modification and deletion.
	// It is a rough parallel to the per-netns rtnl_mutex in Linux.
	linkMu netstackLinkMutex `state:"nosave"`

	// id is a unique identifier for this stack, it is currently only
	// used for a deterministic lock ordering.
	id uint64
}

// NewStack creates a new netstack.Stack which wraps the given tcpip.Stack.
func NewStack(s *stack.Stack, id uint64) *Stack {
	return &Stack{
		Stack: s,
		id:    id,
	}
}

// AddInterfaceEventSubscriber implements inet.InterfaceEventPublisher.AddInterfaceEventSubscriber.
func (s *Stack) AddInterfaceEventSubscriber(sub inet.InterfaceEventSubscriber) {
	if s.eventSubscriber != nil {
		panic("AddInterfaceEventSubscriber called twice: multiple subscribers yet to be supported")
	}
	s.eventSubscriber = sub
}

func makeInterfaceInfo(ni *stack.NICInfo) inet.Interface {
	return inet.Interface{
		Name:       ni.Name,
		Addr:       []byte(ni.LinkAddress),
		Flags:      uint32(nicStateFlagsToLinux(ni.Flags)),
		DeviceType: toLinuxARPHardwareType(ni.ARPHardwareType),
		MTU:        ni.MTU,
		Master:     uint32(ni.Primary),
		Kind:       ni.Kind,
	}
}

func (s *Stack) sendChangeEvent(ctx context.Context, id tcpip.NICID) {
	if s.eventSubscriber == nil {
		return
	}
	if nicInfo, ok := s.Stack.SingleNICInfo(id); ok {
		s.eventSubscriber.OnInterfaceChangeEvent(ctx, int32(id), makeInterfaceInfo(nicInfo))
	}
}

func (s *Stack) sendDeleteEvent(ctx context.Context, id tcpip.NICID, nicInfo *stack.NICInfo) {
	if s.eventSubscriber == nil {
		return
	}
	s.eventSubscriber.OnInterfaceDeleteEvent(ctx, int32(id), makeInterfaceInfo(nicInfo))
}

// Destroy implements inet.Stack.Destroy.
func (s *Stack) Destroy() {
	if s.Stack != nil {
		s.Stack.Close()
		refs.CleanupSync.Add(1)
		go func() {
			s.Stack.Wait()
			refs.CleanupSync.Done()
		}()
	}
}

// SupportsIPv6 implements Stack.SupportsIPv6.
func (s *Stack) SupportsIPv6() bool {
	return s.Stack.CheckNetworkProtocol(ipv6.ProtocolNumber)
}

// Converts Netstack's ARPHardwareType to equivalent linux constants.
func toLinuxARPHardwareType(t header.ARPHardwareType) uint16 {
	switch t {
	case header.ARPHardwareNone:
		return linux.ARPHRD_NONE
	case header.ARPHardwareLoopback:
		return linux.ARPHRD_LOOPBACK
	case header.ARPHardwareEther:
		return linux.ARPHRD_ETHER
	default:
		panic(fmt.Sprintf("unknown ARPHRD type: %d", t))
	}
}

// Interfaces implements inet.Stack.Interfaces.
func (s *Stack) Interfaces() map[int32]inet.Interface {
	is := make(map[int32]inet.Interface)
	for id, ni := range s.Stack.NICInfo() {
		is[int32(id)] = makeInterfaceInfo(&ni)
	}
	return is
}

// InterfaceIDs implements inet.Stack.InterfaceIDs.
func (s *Stack) InterfaceIDs() []int32 {
	// Since gVisor allocates NIC IDs monotonically (like Linux ifindex),
	// sorting by ID is equivalent to registration order.
	return slices.Sorted(maps.Keys(s.Interfaces()))
}

// RemoveInterface implements inet.Stack.RemoveInterface.
func (s *Stack) RemoveInterface(ctx context.Context, idx int32) error {
	s.linkMu.Lock()
	defer s.linkMu.Unlock()

	nic := tcpip.NICID(idx)

	nicInfo, ok := s.Stack.SingleNICInfo(nic)
	if !ok {
		return syserr.ErrUnknownNICID.ToError()
	}

	// Don't allow removing the loopback interface.
	if nicInfo.Flags.Loopback {
		return syserr.ErrNotSupported.ToError()
	}

	if err := syserr.TranslateNetstackError(s.Stack.RemoveNIC(nic)); err != nil {
		return err.ToError()
	}
	s.sendDeleteEvent(ctx, nic, nicInfo)
	return nil

}

// SetInterface implements inet.Stack.SetInterface.
func (s *Stack) SetInterface(ctx context.Context, msg *nlmsg.Message) *syserr.Error {
	var ifinfomsg linux.InterfaceInfoMessage
	attrsView, ok := msg.GetData(&ifinfomsg)
	if !ok {
		return syserr.ErrInvalidArgument
	}
	attrs, ok := attrsView.Parse()
	if !ok {
		return syserr.ErrInvalidArgument
	}
	ifname := ""
	for attr := range attrs {
		value := attrs[attr]
		switch attr {
		case linux.IFLA_IFNAME:
			if len(value) < 1 {
				return syserr.ErrInvalidArgument
			}
			if ifinfomsg.Index == 0 {
				ifname = value.String()
				for idx, ifa := range s.Interfaces() {
					if ifname == ifa.Name {
						ifinfomsg.Index = idx
						break
					}
				}
			}
		case linux.IFLA_MASTER:
		case linux.IFLA_LINKINFO:
		case linux.IFLA_ADDRESS:
		case linux.IFLA_MTU:
		case linux.IFLA_NET_NS_FD:
		case linux.IFLA_TXQLEN:
		default:
			ctx.Warningf("unexpected attribute: %x", attr)
			return syserr.ErrNotSupported
		}
	}
	flags := msg.Header().Flags
	if ifinfomsg.Index == 0 {
		if flags&linux.NLM_F_CREATE != 0 {
			return s.newInterface(ctx, msg, attrs)
		}
		return syserr.ErrNoDevice
	}

	if flags&(linux.NLM_F_EXCL|linux.NLM_F_REPLACE) != 0 {
		return syserr.ErrExists
	}
	if ifinfomsg.Flags != 0 || ifinfomsg.Change != 0 {
		if ifinfomsg.Change & ^uint32(linux.IFF_UP) != 0 {
			ctx.Warningf("Unsupported ifi_change flags: %x", ifinfomsg.Change)
			return syserr.ErrInvalidArgument
		}
		if ifinfomsg.Flags & ^uint32(linux.IFF_UP) != 0 {
			ctx.Warningf("Unsupported ifi_flags: %x", ifinfomsg.Flags)
			return syserr.ErrInvalidArgument
		}
		// Netstack interfaces are always up.
	}

	dstNs, err := s.lockSrcAndDst(ctx, attrs)
	if err != nil {
		return err
	}
	defer s.unlockSrcAndDst(ctx, dstNs)
	return s.setLinkLocked(ctx, tcpip.NICID(ifinfomsg.Index), attrs, dstNs)
}

// If the linkAttrs map contains IFLA_NET_NS_FD, locks the source and
// destination stacks and returns the destination netns, which must
// be passed into unlockSrcAndDst by the caller.
//
// If instead the linkAttrs map does not contain IFLA_NET_NS_FD, or if it
// points to the same netns as the source stack, only locks the source stack
// and returns nil.
//
// If the netns fd is invalid, or the calling context lacks CAP_NET_ADMIN,
// returns an error.
func (s *Stack) lockSrcAndDst(ctx context.Context, linkAttrs map[uint16]nlmsg.BytesView) (*inet.Namespace, *syserr.Error) {
	if linkAttrs == nil {
		s.linkMu.Lock()
		return nil, nil
	}
	v, ok := linkAttrs[linux.IFLA_NET_NS_FD]
	if !ok {
		s.linkMu.Lock()
		return nil, nil
	}

	fd, ok := v.Uint32()
	if !ok {
		return nil, syserr.ErrInvalidArgument
	}
	f := inet.NamespaceByFDFromContext(ctx)
	if f == nil {
		return nil, syserr.ErrInvalidArgument
	}
	ns, err := f(int32(fd))
	if err != nil {
		return nil, syserr.FromError(err)
	}
	if !auth.CredentialsFromContext(ctx).HasCapabilityIn(linux.CAP_NET_ADMIN, ns.UserNamespace()) {
		ns.DecRef(ctx)
		return nil, syserr.ErrNotPermittedNet
	}

	dst := ns.Stack().(*Stack)
	if s == dst {
		ns.DecRef(ctx)
		s.linkMu.Lock()
		return nil, nil
	}
	// No failures from this point on, ns.DecRef() is called in unlockSrcAndDst().

	if s.id < dst.id {
		s.linkMu.Lock()
		dst.linkMu.NestedLock(netstackLinkLockDeststack)
	} else {
		dst.linkMu.Lock()
		s.linkMu.NestedLock(netstackLinkLockDeststack)
	}
	return ns, nil
}

func (s *Stack) unlockSrcAndDst(ctx context.Context, ns *inet.Namespace) {
	if ns == nil {
		s.linkMu.Unlock()
		return
	}
	dst := ns.Stack().(*Stack)
	if s == dst {
		// This cannot happen because lockSrcAndDst() returns nil in this case.
		panic("unlockSrcAndDst called with dst == src")
	}

	if s.id < dst.id {
		dst.linkMu.NestedUnlock(netstackLinkLockDeststack)
		s.linkMu.Unlock()
	} else {
		s.linkMu.NestedUnlock(netstackLinkLockDeststack)
		dst.linkMu.Unlock()
	}
	ns.DecRef(ctx)
}

// precondition: s.linkLock is held. If dstNs is not nil, dstNs.Stack.linkMu is
// also held.
func (s *Stack) setLinkLocked(ctx context.Context, id tcpip.NICID, linkAttrs map[uint16]nlmsg.BytesView, dstNs *inet.Namespace) *syserr.Error {
	src := s
	changed := false
	nicInfo, ok := src.Stack.SingleNICInfo(id)
	if !ok {
		return syserr.ErrUnknownNICID
	}

	dst := src
	if dstNs != nil {
		dst = dstNs.Stack().(*Stack)
	}
	if dst != src {
		var err tcpip.Error
		oldID := id
		id, err = src.Stack.SetNICStack(id, dst.Stack)
		if err != nil {
			return syserr.TranslateNetstackError(err)
		}

		src.sendDeleteEvent(ctx, oldID, nicInfo) // inform about exit from old ns
		changed = true                           // inform about entry into new ns

		nicInfo, ok = dst.Stack.SingleNICInfo(id)
		if !ok {
			// Because we hold dst.linkMu, this should never happen.
			ctx.Warningf("Newly rehomed NIC %d not found in new stack %p", id, dst.Stack)
			return syserr.ErrUnknownNICID
		}
		src = dst

		// TODO (b/465141970): Once we support IFLA_LINK_NETNSID, we need to call sendChangeEvent on
		// the peer interface if this interface is part of a veth pair.
	}

	for t, v := range linkAttrs {
		switch t {
		case linux.IFLA_MASTER:
			master, ok := v.Uint32()
			if !ok {
				return syserr.ErrInvalidArgument
			}
			if mid, ok := src.Stack.GetNICCoordinatorID(id); ok && mid == tcpip.NICID(master) {
				continue
			}
			if master != 0 {
				if err := src.Stack.SetNICCoordinator(id, tcpip.NICID(master)); err != nil {
					return syserr.TranslateNetstackError(err)
				}
				changed = true
			}
		case linux.IFLA_ADDRESS:
			if len(v) != tcpip.LinkAddressSize {
				return syserr.ErrInvalidArgument
			}
			addr := tcpip.LinkAddress(v)
			if nicInfo.LinkAddress == addr {
				continue
			}
			if err := src.Stack.SetNICAddress(id, addr); err != nil {
				return syserr.TranslateNetstackError(err)
			}
			changed = true
		case linux.IFLA_IFNAME:
			if nicInfo.Name == v.String() {
				continue
			}
			if err := src.Stack.SetNICName(id, v.String()); err != nil {
				return syserr.TranslateNetstackError(err)
			}
			changed = true
		case linux.IFLA_MTU:
			mtu, ok := v.Uint32()
			if !ok {
				return syserr.ErrInvalidArgument
			}
			if nicInfo.MTU == mtu {
				continue
			}
			if err := src.Stack.SetNICMTU(id, mtu); err != nil {
				return syserr.TranslateNetstackError(err)
			}
			changed = true
		case linux.IFLA_TXQLEN:
			// TODO(b/340388892): support IFLA_TXQLEN.
		}
	}

	if changed {
		src.sendChangeEvent(ctx, id)
	}
	return nil
}

const defaultMTU = 1500

func (s *Stack) newVeth(ctx context.Context, linkAttrs map[uint16]nlmsg.BytesView, linkInfoAttrs map[uint16]nlmsg.BytesView) *syserr.Error {
	var (
		linkInfoData  map[uint16]nlmsg.BytesView
		ifinfomsg     linux.InterfaceInfoMessage
		peerLinkAttrs map[uint16]nlmsg.BytesView
	)

	peerStack := s
	peerName := ""
	ifname := ""

	if v, ok := linkAttrs[linux.IFLA_IFNAME]; ok {
		ifname = v.String()
	}
	if value, ok := linkInfoAttrs[linux.IFLA_INFO_DATA]; ok {
		linkInfoData, ok = nlmsg.AttrsView(value).Parse()
		if !ok {
			return syserr.ErrInvalidArgument
		}
		if v, ok := linkInfoData[linux.VETH_INFO_PEER]; ok {
			attrsView := nlmsg.AttrsView(v[ifinfomsg.SizeBytes():])
			if !ok {
				return syserr.ErrInvalidArgument
			}
			peerLinkAttrs, ok = attrsView.Parse()
			if !ok {
				return syserr.ErrInvalidArgument
			}
			if v, ok = peerLinkAttrs[linux.IFLA_IFNAME]; ok {
				peerName = v.String()
			}
			if v, ok = peerLinkAttrs[linux.IFLA_NET_NS_FD]; ok {
				fd, ok := v.Uint32()
				if !ok {
					return syserr.ErrInvalidArgument
				}
				f := inet.NamespaceByFDFromContext(ctx)
				if f == nil {
					return syserr.ErrInvalidArgument
				}
				ns, err := f(int32(fd))
				if err != nil {
					return syserr.FromError(err)
				}
				defer ns.DecRef(ctx)
				peerStack = ns.Stack().(*Stack)
			}
		}
	}

	dstNs, sysErr := s.lockSrcAndDst(ctx, linkAttrs)
	if sysErr != nil {
		return sysErr
	}
	ep, peerEP := veth.NewPair(defaultMTU, veth.DefaultBacklogSize)
	id := s.Stack.NextNICID()
	peerID := peerStack.Stack.NextNICID()
	if ifname == "" {
		ifname = fmt.Sprintf("veth%d", id)
	}
	err := s.Stack.CreateNICWithOptions(id, packetsocket.New(ethernet.New(ep)), stack.NICOptions{
		Name: ifname,
		Kind: "veth",
	})
	if err != nil {
		s.unlockSrcAndDst(ctx, dstNs)
		return syserr.TranslateNetstackError(err)
	}
	if err := s.setLinkLocked(ctx, id, linkAttrs, dstNs); err != nil {
		s.unlockSrcAndDst(ctx, dstNs)
		peerEP.Close()
		return err
	}
	s.unlockSrcAndDst(ctx, dstNs)

	if peerName == "" {
		peerName = fmt.Sprintf("veth%d", peerID)
	}
	peerDstNs, sysErr := peerStack.lockSrcAndDst(ctx, peerLinkAttrs)
	if sysErr != nil {
		return sysErr
	}
	defer peerStack.unlockSrcAndDst(ctx, peerDstNs)

	err = peerStack.Stack.CreateNICWithOptions(peerID, packetsocket.New(ethernet.New(peerEP)), stack.NICOptions{
		Name: peerName,
		Kind: "veth",
	})
	if err != nil {
		peerEP.Close()
		return syserr.TranslateNetstackError(err)
	}
	if peerLinkAttrs != nil {
		if err := peerStack.setLinkLocked(ctx, peerID, peerLinkAttrs, peerDstNs); err != nil {
			peerStack.Stack.RemoveNIC(peerID)
			peerEP.Close()
			return err
		}
	}

	return nil
}

func (s *Stack) newBridge(ctx context.Context, linkAttrs map[uint16]nlmsg.BytesView, linkInfoAttrs map[uint16]nlmsg.BytesView) *syserr.Error {
	dstNs, sysErr := s.lockSrcAndDst(ctx, linkAttrs)
	if sysErr != nil {
		return sysErr
	}
	defer s.unlockSrcAndDst(ctx, dstNs)

	ifname := ""

	if v, ok := linkAttrs[linux.IFLA_IFNAME]; ok {
		ifname = v.String()
	}
	ep := stack.NewBridgeEndpoint(defaultMTU)
	id := s.Stack.NextNICID()
	err := s.Stack.CreateNICWithOptions(id, ep, stack.NICOptions{
		Name: ifname,
		Kind: "bridge",
	})
	if err != nil {
		return syserr.TranslateNetstackError(err)
	}
	if err := s.setLinkLocked(ctx, id, linkAttrs, dstNs); err != nil {
		return err
	}

	return nil
}

func (s *Stack) newInterface(ctx context.Context, msg *nlmsg.Message, linkAttrs map[uint16]nlmsg.BytesView) *syserr.Error {
	var (
		linkInfoAttrs map[uint16]nlmsg.BytesView
		kind          string
	)

	if v, ok := linkAttrs[linux.IFLA_LINKINFO]; ok {
		linkInfoAttrs, ok = nlmsg.AttrsView(v).Parse()
		if !ok {
			return syserr.ErrInvalidArgument
		}

		for attr := range linkInfoAttrs {
			value := linkInfoAttrs[attr]
			switch attr {
			case linux.IFLA_INFO_KIND:
				kind = value.String()
			case linux.IFLA_INFO_DATA:
			default:
				ctx.Warningf("unexpected link info attribute: %x", attr)
				return syserr.ErrNotSupported
			}
		}
	}
	switch kind {
	case "":
		return syserr.ErrInvalidArgument
	case "bridge":
		return s.newBridge(ctx, linkAttrs, linkInfoAttrs)
	case "veth":
		return s.newVeth(ctx, linkAttrs, linkInfoAttrs)
	}
	return syserr.ErrNotSupported
}

// InterfaceAddrs implements inet.Stack.InterfaceAddrs.
func (s *Stack) InterfaceAddrs() map[int32][]inet.InterfaceAddr {
	nicAddrs := make(map[int32][]inet.InterfaceAddr)
	for id, ni := range s.Stack.NICInfo() {
		var addrs []inet.InterfaceAddr
		for _, a := range ni.ProtocolAddresses {
			var family uint8
			switch a.Protocol {
			case ipv4.ProtocolNumber:
				family = linux.AF_INET
			case ipv6.ProtocolNumber:
				family = linux.AF_INET6
			default:
				log.Warningf("Unknown network protocol in %+v", a)
				continue
			}

			addrCopy := a.AddressWithPrefix.Address
			addrs = append(addrs, inet.InterfaceAddr{
				Family:    family,
				PrefixLen: uint8(a.AddressWithPrefix.PrefixLen),
				Addr:      addrCopy.AsSlice(),
				// TODO(b/68878065): Other fields.
			})
		}
		nicAddrs[int32(id)] = addrs
	}
	return nicAddrs
}

// convertAddr converts an InterfaceAddr to a ProtocolAddress.
func convertAddr(addr inet.InterfaceAddr) (tcpip.ProtocolAddress, error) {
	var (
		protocol        tcpip.NetworkProtocolNumber
		address         tcpip.Address
		protocolAddress tcpip.ProtocolAddress
	)
	switch addr.Family {
	case linux.AF_INET:
		if len(addr.Addr) != header.IPv4AddressSize {
			return protocolAddress, linuxerr.EINVAL
		}
		if addr.PrefixLen > header.IPv4AddressSize*8 {
			return protocolAddress, linuxerr.EINVAL
		}
		protocol = ipv4.ProtocolNumber
		address = tcpip.AddrFrom4Slice(addr.Addr)
	case linux.AF_INET6:
		if len(addr.Addr) != header.IPv6AddressSize {
			return protocolAddress, linuxerr.EINVAL
		}
		if addr.PrefixLen > header.IPv6AddressSize*8 {
			return protocolAddress, linuxerr.EINVAL
		}
		protocol = ipv6.ProtocolNumber
		address = tcpip.AddrFrom16Slice(addr.Addr)
	default:
		return protocolAddress, linuxerr.ENOTSUP
	}

	protocolAddress = tcpip.ProtocolAddress{
		Protocol: protocol,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   address,
			PrefixLen: int(addr.PrefixLen),
		},
	}
	return protocolAddress, nil
}

// AddInterfaceAddr implements inet.Stack.AddInterfaceAddr.
func (s *Stack) AddInterfaceAddr(idx int32, addr inet.InterfaceAddr) error {
	protocolAddress, err := convertAddr(addr)
	if err != nil {
		return err
	}

	// Attach address to interface.
	nicID := tcpip.NICID(idx)
	if err := s.Stack.AddProtocolAddress(nicID, protocolAddress, stack.AddressProperties{}); err != nil {
		return syserr.TranslateNetstackError(err).ToError()
	}

	// Add route for local network if it doesn't exist already.
	localRoute := tcpip.Route{
		Destination: protocolAddress.AddressWithPrefix.Subnet(),
		Gateway:     tcpip.Address{}, // No gateway for local network.
		NIC:         nicID,
	}

	for _, rt := range s.Stack.GetRouteTable() {
		if rt.Equal(localRoute) {
			return nil
		}
	}

	// Local route does not exist yet. Add it.
	s.Stack.AddRoute(localRoute)

	return nil
}

// RemoveInterfaceAddr implements inet.Stack.RemoveInterfaceAddr.
func (s *Stack) RemoveInterfaceAddr(idx int32, addr inet.InterfaceAddr) error {
	protocolAddress, err := convertAddr(addr)
	if err != nil {
		return err
	}

	// Remove addresses matching the address and prefix.
	nicID := tcpip.NICID(idx)
	if err := s.Stack.RemoveAddress(nicID, protocolAddress.AddressWithPrefix.Address); err != nil {
		return syserr.TranslateNetstackError(err).ToError()
	}

	// Remove the corresponding local network route if it exists.
	localRoute := tcpip.Route{
		Destination: protocolAddress.AddressWithPrefix.Subnet(),
		Gateway:     tcpip.Address{}, // No gateway for local network.
		NIC:         nicID,
	}
	s.Stack.RemoveRoutes(func(rt tcpip.Route) bool {
		return rt.Equal(localRoute)
	})

	return nil
}

// TCPReceiveBufferSize implements inet.Stack.TCPReceiveBufferSize.
func (s *Stack) TCPReceiveBufferSize() (inet.TCPBufferSize, error) {
	var rs tcpip.TCPReceiveBufferSizeRangeOption
	err := s.Stack.TransportProtocolOption(tcp.ProtocolNumber, &rs)
	return inet.TCPBufferSize{
		Min:     rs.Min,
		Default: rs.Default,
		Max:     rs.Max,
	}, syserr.TranslateNetstackError(err).ToError()
}

// SetTCPReceiveBufferSize implements inet.Stack.SetTCPReceiveBufferSize.
func (s *Stack) SetTCPReceiveBufferSize(size inet.TCPBufferSize) error {
	rs := tcpip.TCPReceiveBufferSizeRangeOption{
		Min:     size.Min,
		Default: size.Default,
		Max:     size.Max,
	}
	return syserr.TranslateNetstackError(s.Stack.SetTransportProtocolOption(tcp.ProtocolNumber, &rs)).ToError()
}

// TCPSendBufferSize implements inet.Stack.TCPSendBufferSize.
func (s *Stack) TCPSendBufferSize() (inet.TCPBufferSize, error) {
	var ss tcpip.TCPSendBufferSizeRangeOption
	err := s.Stack.TransportProtocolOption(tcp.ProtocolNumber, &ss)
	return inet.TCPBufferSize{
		Min:     ss.Min,
		Default: ss.Default,
		Max:     ss.Max,
	}, syserr.TranslateNetstackError(err).ToError()
}

// SetTCPSendBufferSize implements inet.Stack.SetTCPSendBufferSize.
func (s *Stack) SetTCPSendBufferSize(size inet.TCPBufferSize) error {
	ss := tcpip.TCPSendBufferSizeRangeOption{
		Min:     size.Min,
		Default: size.Default,
		Max:     size.Max,
	}
	return syserr.TranslateNetstackError(s.Stack.SetTransportProtocolOption(tcp.ProtocolNumber, &ss)).ToError()
}

// TCPSACKEnabled implements inet.Stack.TCPSACKEnabled.
func (s *Stack) TCPSACKEnabled() (bool, error) {
	var sack tcpip.TCPSACKEnabled
	err := s.Stack.TransportProtocolOption(tcp.ProtocolNumber, &sack)
	return bool(sack), syserr.TranslateNetstackError(err).ToError()
}

// SetTCPSACKEnabled implements inet.Stack.SetTCPSACKEnabled.
func (s *Stack) SetTCPSACKEnabled(enabled bool) error {
	opt := tcpip.TCPSACKEnabled(enabled)
	return syserr.TranslateNetstackError(s.Stack.SetTransportProtocolOption(tcp.ProtocolNumber, &opt)).ToError()
}

// TCPRecovery implements inet.Stack.TCPRecovery.
func (s *Stack) TCPRecovery() (inet.TCPLossRecovery, error) {
	var recovery tcpip.TCPRecovery
	if err := s.Stack.TransportProtocolOption(tcp.ProtocolNumber, &recovery); err != nil {
		return 0, syserr.TranslateNetstackError(err).ToError()
	}
	return inet.TCPLossRecovery(recovery), nil
}

// SetTCPRecovery implements inet.Stack.SetTCPRecovery.
func (s *Stack) SetTCPRecovery(recovery inet.TCPLossRecovery) error {
	opt := tcpip.TCPRecovery(recovery)
	return syserr.TranslateNetstackError(s.Stack.SetTransportProtocolOption(tcp.ProtocolNumber, &opt)).ToError()
}

// Statistics implements inet.Stack.Statistics.
func (s *Stack) Statistics(stat any, arg string) error {
	netStats := s.Stats()
	switch stats := stat.(type) {
	case *inet.StatDev:
		for _, ni := range s.Stack.NICInfo() {
			if ni.Name != arg {
				continue
			}
			// TODO(gvisor.dev/issue/2103) Support stubbed stats.
			*stats = inet.StatDev{
				// Receive section.
				ni.Stats.Rx.Bytes.Value(),   // bytes.
				ni.Stats.Rx.Packets.Value(), // packets.
				0,                           // errs.
				0,                           // drop.
				0,                           // fifo.
				0,                           // frame.
				0,                           // compressed.
				0,                           // multicast.
				// Transmit section.
				ni.Stats.Tx.Bytes.Value(),   // bytes.
				ni.Stats.Tx.Packets.Value(), // packets.
				0,                           // errs.
				0,                           // drop.
				0,                           // fifo.
				0,                           // colls.
				0,                           // carrier.
				0,                           // compressed.
			}
			break
		}
	case *inet.StatSNMPIP:
		ip := netStats.IP
		// TODO(gvisor.dev/issue/969) Support stubbed stats.
		*stats = inet.StatSNMPIP{
			0,                          // Ip/Forwarding.
			0,                          // Ip/DefaultTTL.
			ip.PacketsReceived.Value(), // InReceives.
			0,                          // Ip/InHdrErrors.
			ip.InvalidDestinationAddressesReceived.Value(), // InAddrErrors.
			0,                               // Ip/ForwDatagrams.
			0,                               // Ip/InUnknownProtos.
			0,                               // Ip/InDiscards.
			ip.PacketsDelivered.Value(),     // InDelivers.
			ip.PacketsSent.Value(),          // OutRequests.
			ip.OutgoingPacketErrors.Value(), // OutDiscards.
			0,                               // Ip/OutNoRoutes.
			0,                               // Support Ip/ReasmTimeout.
			0,                               // Support Ip/ReasmReqds.
			0,                               // Support Ip/ReasmOKs.
			0,                               // Support Ip/ReasmFails.
			0,                               // Support Ip/FragOKs.
			0,                               // Support Ip/FragFails.
			0,                               // Support Ip/FragCreates.
		}
	case *inet.StatSNMPICMP:
		in := netStats.ICMP.V4.PacketsReceived.ICMPv4PacketStats
		out := netStats.ICMP.V4.PacketsSent.ICMPv4PacketStats
		// TODO(gvisor.dev/issue/969) Support stubbed stats.
		*stats = inet.StatSNMPICMP{
			0, // Icmp/InMsgs.
			Metrics.ICMP.V4.PacketsSent.Dropped.Value(), // InErrors.
			0,                         // Icmp/InCsumErrors.
			in.DstUnreachable.Value(), // InDestUnreachs.
			in.TimeExceeded.Value(),   // InTimeExcds.
			in.ParamProblem.Value(),   // InParmProbs.
			in.SrcQuench.Value(),      // InSrcQuenchs.
			in.Redirect.Value(),       // InRedirects.
			in.EchoRequest.Value(),    // InEchos.
			in.EchoReply.Value(),      // InEchoReps.
			in.Timestamp.Value(),      // InTimestamps.
			in.TimestampReply.Value(), // InTimestampReps.
			in.InfoRequest.Value(),    // InAddrMasks.
			in.InfoReply.Value(),      // InAddrMaskReps.
			0,                         // Icmp/OutMsgs.
			Metrics.ICMP.V4.PacketsReceived.Invalid.Value(), // OutErrors.
			out.DstUnreachable.Value(),                      // OutDestUnreachs.
			out.TimeExceeded.Value(),                        // OutTimeExcds.
			out.ParamProblem.Value(),                        // OutParmProbs.
			out.SrcQuench.Value(),                           // OutSrcQuenchs.
			out.Redirect.Value(),                            // OutRedirects.
			out.EchoRequest.Value(),                         // OutEchos.
			out.EchoReply.Value(),                           // OutEchoReps.
			out.Timestamp.Value(),                           // OutTimestamps.
			out.TimestampReply.Value(),                      // OutTimestampReps.
			out.InfoRequest.Value(),                         // OutAddrMasks.
			out.InfoReply.Value(),                           // OutAddrMaskReps.
		}
	case *inet.StatSNMPTCP:
		tcp := netStats.TCP
		// RFC 2012 (updates 1213):  SNMPv2-MIB-TCP.
		*stats = inet.StatSNMPTCP{
			1,                                     // RtoAlgorithm.
			200,                                   // RtoMin.
			120000,                                // RtoMax.
			(1<<64 - 1),                           // MaxConn.
			tcp.ActiveConnectionOpenings.Value(),  // ActiveOpens.
			tcp.PassiveConnectionOpenings.Value(), // PassiveOpens.
			tcp.FailedConnectionAttempts.Value(),  // AttemptFails.
			tcp.EstablishedResets.Value(),         // EstabResets.
			tcp.CurrentEstablished.Value(),        // CurrEstab.
			tcp.ValidSegmentsReceived.Value(),     // InSegs.
			tcp.SegmentsSent.Value(),              // OutSegs.
			tcp.Retransmits.Value(),               // RetransSegs.
			tcp.InvalidSegmentsReceived.Value(),   // InErrs.
			tcp.ResetsSent.Value(),                // OutRsts.
			tcp.ChecksumErrors.Value(),            // InCsumErrors.
		}
	case *inet.StatSNMPUDP:
		udp := netStats.UDP
		// TODO(gvisor.dev/issue/969) Support stubbed stats.
		*stats = inet.StatSNMPUDP{
			udp.PacketsReceived.Value(),     // InDatagrams.
			udp.UnknownPortErrors.Value(),   // NoPorts.
			0,                               // Udp/InErrors.
			udp.PacketsSent.Value(),         // OutDatagrams.
			udp.ReceiveBufferErrors.Value(), // RcvbufErrors.
			0,                               // Udp/SndbufErrors.
			udp.ChecksumErrors.Value(),      // Udp/InCsumErrors.
			0,                               // Udp/IgnoredMulti.
		}
	default:
		return syserr.ErrEndpointOperation.ToError()
	}
	return nil
}

// Stats implements inet.Stack.Stats.
func (s *Stack) Stats() tcpip.Stats {
	return s.Stack.Stats()
}

// RouteTable implements inet.Stack.RouteTable.
func (s *Stack) RouteTable() []inet.Route {
	var routeTable []inet.Route

	for _, rt := range s.Stack.GetRouteTable() {
		var family uint8
		switch rt.Destination.ID().BitLen() {
		case header.IPv4AddressSizeBits:
			family = linux.AF_INET
		case header.IPv6AddressSizeBits:
			family = linux.AF_INET6
		default:
			log.Warningf("Unknown network protocol in route %+v", rt)
			continue
		}

		dstAddr := rt.Destination.ID()
		routeTable = append(routeTable, inet.Route{
			Family: family,
			DstLen: uint8(rt.Destination.Prefix()), // The CIDR prefix for the destination.

			// Always return unspecified protocol since we have no notion of
			// protocol for routes.
			Protocol: linux.RTPROT_UNSPEC,
			// Set statically to LINK scope for now.
			//
			// TODO(gvisor.dev/issue/595): Set scope for routes.
			Scope: linux.RT_SCOPE_LINK,
			Type:  linux.RTN_UNICAST,

			DstAddr:         dstAddr.AsSlice(),
			OutputInterface: int32(rt.NIC),
			GatewayAddr:     rt.Gateway.AsSlice(),
		})
	}

	return routeTable
}

// localRoute constructs a local route from the netlink message.
func (s *Stack) localRoute(msg *nlmsg.Message) (tcpip.Route, *syserr.Error) {
	var rtMsg linux.RouteMessage
	attrs, ok := msg.GetData(&rtMsg)
	if !ok {
		return tcpip.Route{}, syserr.ErrInvalidArgument
	}

	route := inet.Route{
		Family:   rtMsg.Family,
		DstLen:   rtMsg.DstLen,
		SrcLen:   rtMsg.SrcLen,
		TOS:      rtMsg.TOS,
		Table:    rtMsg.Table,
		Protocol: rtMsg.Protocol,
		Scope:    rtMsg.Scope,
		Type:     rtMsg.Type,
		Flags:    rtMsg.Flags,
	}

	for !attrs.Empty() {
		ahdr, value, rest, ok := attrs.ParseFirst()
		if !ok {
			return tcpip.Route{}, syserr.ErrInvalidArgument
		}
		attrs = rest

		switch ahdr.Type {
		case linux.RTA_DST:
			if len(value) < 1 {
				return tcpip.Route{}, syserr.ErrInvalidArgument
			}
			route.DstAddr = value
		case linux.RTA_SRC:
			if len(value) < 1 {
				return tcpip.Route{}, syserr.ErrInvalidArgument
			}
			route.SrcAddr = value
		case linux.RTA_OIF:
			oif := nlmsg.BytesView(value)
			outputInterface, ok := oif.Int32()
			if !ok {
				return tcpip.Route{}, syserr.ErrInvalidArgument
			}
			if _, exist := s.Interfaces()[outputInterface]; !exist {
				return tcpip.Route{}, syserr.ErrNoDevice
			}
			route.OutputInterface = outputInterface
		case linux.RTA_GATEWAY:
			if len(value) < 1 {
				return tcpip.Route{}, syserr.ErrInvalidArgument
			}
			route.GatewayAddr = value
		case linux.RTA_PRIORITY:
		default:
			log.Warningf("Unknown attribute: %v", ahdr.Type)
			return tcpip.Route{}, syserr.ErrNotSupported
		}
	}
	var dest tcpip.Subnet
	// When no destination address is provided, the new route might be the default route.
	if route.DstAddr == nil {
		if route.GatewayAddr == nil {
			return tcpip.Route{}, syserr.ErrInvalidArgument
		}
		switch len(route.GatewayAddr) {
		case header.IPv4AddressSize:
			subnet, err := tcpip.NewSubnet(tcpip.AddrFromSlice(tcpip.IPv4Zero), tcpip.MaskFromBytes(tcpip.IPv4Zero))
			if err != nil {
				return tcpip.Route{}, syserr.ErrInvalidArgument
			}
			dest = subnet
		case header.IPv6AddressSize:
			subnet, err := tcpip.NewSubnet(tcpip.AddrFromSlice(tcpip.IPv6Zero), tcpip.MaskFromBytes(tcpip.IPv6Zero))
			if err != nil {
				return tcpip.Route{}, syserr.ErrInvalidArgument
			}
			dest = subnet
		default:
			return tcpip.Route{}, syserr.ErrInvalidArgument
		}
	} else {
		dest = tcpip.AddressWithPrefix{
			Address:   tcpip.AddrFromSlice(route.DstAddr),
			PrefixLen: int(route.DstLen)}.Subnet()
	}

	localRoute := tcpip.Route{
		Destination: dest,
		Gateway:     tcpip.AddrFromSlice(route.GatewayAddr),
		NIC:         tcpip.NICID(route.OutputInterface),
	}

	if len(route.SrcAddr) != 0 {
		localRoute.SourceHint = tcpip.AddrFromSlice(route.SrcAddr)
	}

	return localRoute, nil
}

// RemoveRoute implements inte.Stack.RemoveRoute.
func (s *Stack) RemoveRoute(ctx context.Context, msg *nlmsg.Message) *syserr.Error {
	localRoute, err := s.localRoute(msg)
	if err != nil {
		return err
	}
	if removed := s.Stack.RemoveRoutes(func(rt tcpip.Route) bool {
		// Both gateway and NIC are compared with existing routes
		// only when they are present in the netlink message.
		if localRoute.Gateway.Len() > 0 && !localRoute.Gateway.Equal(rt.Gateway) {
			return false
		}
		if localRoute.NIC > 0 && localRoute.NIC != rt.NIC {
			return false
		}
		return rt.Destination.Equal(localRoute.Destination)
	}); removed == 0 {
		return syserr.ErrNoProcess
	}
	return nil
}

// NewRoute implements inet.Stack.NewRoute.
func (s *Stack) NewRoute(ctx context.Context, msg *nlmsg.Message) *syserr.Error {
	localRoute, err := s.localRoute(msg)
	if err != nil {
		return err
	}
	found := false
	for _, rt := range s.Stack.GetRouteTable() {
		if localRoute.Equal(rt) {
			found = true
			break
		}
	}
	flags := msg.Header().Flags
	switch {
	case !found && flags&linux.NLM_F_CREATE == linux.NLM_F_CREATE:
		s.Stack.AddRoute(localRoute)
	case found && flags&linux.NLM_F_REPLACE != linux.NLM_F_REPLACE:
		return syserr.ErrExists
	}
	if flags&linux.NLM_F_REPLACE == linux.NLM_F_REPLACE {
		s.Stack.ReplaceRoute(localRoute)
	}
	return nil
}

// IPTables returns the stack's iptables.
func (s *Stack) IPTables() (*stack.IPTables, error) {
	return s.Stack.IPTables(), nil
}

// Pause implements inet.Stack.Pause.
func (s *Stack) Pause() {
	s.Stack.Pause()
}

// Restore implements inet.Stack.Restore.
func (s *Stack) Restore() {
	s.Stack.Restore()
}

// ResetConfig implements inet.Stack.ResetConfig.
func (s *Stack) ResetConfig() {
	s.Stack.ResetConfig()
}

// ReplaceConfig replaces config in the loaded stack.
func (s *Stack) ReplaceConfig(st inet.Stack) {
	if _, ok := st.(*Stack); !ok {
		panic("netstack.Stack cannot be nil when netstack s/r is enabled")
	}
	s.Stack.ReplaceConfig(st.(*Stack).Stack)
}

// Resume implements inet.Stack.Resume.
func (s *Stack) Resume() {
	s.Stack.Resume()
}

// RegisteredEndpoints implements inet.Stack.RegisteredEndpoints.
func (s *Stack) RegisteredEndpoints() []stack.TransportEndpoint {
	return s.Stack.RegisteredEndpoints()
}

// CleanupEndpoints implements inet.Stack.CleanupEndpoints.
func (s *Stack) CleanupEndpoints() []stack.TransportEndpoint {
	return s.Stack.CleanupEndpoints()
}

// RestoreCleanupEndpoints implements inet.Stack.RestoreCleanupEndpoints.
func (s *Stack) RestoreCleanupEndpoints(es []stack.TransportEndpoint) {
	s.Stack.RestoreCleanupEndpoints(es)
}

// SetForwarding implements inet.Stack.SetForwarding.
func (s *Stack) SetForwarding(protocol tcpip.NetworkProtocolNumber, enable bool) error {
	if err := s.Stack.SetForwardingDefaultAndAllNICs(protocol, enable); err != nil {
		return fmt.Errorf("SetForwardingDefaultAndAllNICs(%d, %t): %s", protocol, enable, err)
	}
	return nil
}

// PortRange implements inet.Stack.PortRange.
func (s *Stack) PortRange() (uint16, uint16) {
	return s.Stack.PortRange()
}

// SetPortRange implements inet.Stack.SetPortRange.
func (s *Stack) SetPortRange(start uint16, end uint16) error {
	return syserr.TranslateNetstackError(s.Stack.SetPortRange(start, end)).ToError()
}

// SetRemoveConf implements inet.Stack.SetRemoveConf.
func (s *Stack) SetRemoveConf(removeConf bool) {
	s.Stack.SetRemoveConf(removeConf)
}

// GetRemoveConf implements inet.Stack.GetRemoveConf.
func (s *Stack) GetRemoveConf() bool {
	return s.Stack.GetRemoveConf()
}
