From b9fe44808b0cfcede29f6c37b7d5813538f23252 Mon Sep 17 00:00:00 2001 From: Kevin Krakauer Date: Fri, 10 Nov 2023 22:34:41 -0800 Subject: [PATCH] netstack: support snat revision 2 targets We now support all snat target versions. dnat targets will reuse the same structs in a later CL. PiperOrigin-RevId: 581455903 --- pkg/abi/linux/netfilter.go | 11 ++++ pkg/abi/linux/netfilter_ipv6.go | 17 ++++++ pkg/sentry/socket/netfilter/snat.go | 83 ++++++++++++++++++++++++++ pkg/sentry/socket/netfilter/targets.go | 6 ++ 4 files changed, 117 insertions(+) diff --git a/pkg/abi/linux/netfilter.go b/pkg/abi/linux/netfilter.go index 3c5710fad..0ac5d67df 100644 --- a/pkg/abi/linux/netfilter.go +++ b/pkg/abi/linux/netfilter.go @@ -413,6 +413,17 @@ type XTNATTargetV1 struct { // SizeOfXTNATTargetV1 is the size of an XTNATTargetV1. const SizeOfXTNATTargetV1 = SizeOfXTEntryTarget + SizeOfNFNATRange +// XTNATTargetV2 triggers NAT when reached. +// +// +marshal +type XTNATTargetV2 struct { + Target XTEntryTarget + Range NFNATRange2 +} + +// SizeOfXTNATTargetV2 is the size of an XTNATTargetV2. +const SizeOfXTNATTargetV2 = SizeOfXTEntryTarget + SizeOfNFNATRange2 + // IPTGetinfo is the argument for the IPT_SO_GET_INFO sockopt. It corresponds // to struct ipt_getinfo in include/uapi/linux/netfilter_ipv4/ip_tables.h. // diff --git a/pkg/abi/linux/netfilter_ipv6.go b/pkg/abi/linux/netfilter_ipv6.go index 280465d06..4d9c09402 100644 --- a/pkg/abi/linux/netfilter_ipv6.go +++ b/pkg/abi/linux/netfilter_ipv6.go @@ -275,3 +275,20 @@ type NFNATRange struct { // SizeOfNFNATRange is the size of NFNATRange. const SizeOfNFNATRange = 40 + +// NFNATRange2 corresponds to struct nf_nat_range2 in +// include/uapi/linux/netfilter/nf_nat.h. +// +// +marshal +type NFNATRange2 struct { + Flags uint32 + MinAddr Inet6Addr + MaxAddr Inet6Addr + MinProto uint16 // Network byte order. + MaxProto uint16 // Network byte order. + BaseProto uint16 // Network byte order. + _ [6]byte +} + +// SizeOfNFNATRange2 is the size of NFNATRange2. +const SizeOfNFNATRange2 = 48 diff --git a/pkg/sentry/socket/netfilter/snat.go b/pkg/sentry/socket/netfilter/snat.go index ea0eac7b5..92d672535 100644 --- a/pkg/sentry/socket/netfilter/snat.go +++ b/pkg/sentry/socket/netfilter/snat.go @@ -197,3 +197,86 @@ func (st *snatTargetMakerR1) unmarshal(buf []byte, filter stack.IPHeaderFilter) return &target, nil } + +type snatTargetMakerR2 struct { + NetworkProtocol tcpip.NetworkProtocolNumber +} + +func (st *snatTargetMakerR2) id() targetID { + return targetID{ + name: SNATTargetName, + networkProtocol: st.NetworkProtocol, + revision: 2, + } +} + +func (*snatTargetMakerR2) marshal(target target) []byte { + st := target.(*snatTarget) + nt := linux.XTNATTargetV2{ + Target: linux.XTEntryTarget{ + TargetSize: linux.SizeOfXTNATTargetV1, + }, + Range: linux.NFNATRange2{ + Flags: linux.NF_NAT_RANGE_MAP_IPS | linux.NF_NAT_RANGE_PROTO_SPECIFIED, + }, + } + copy(nt.Target.Name[:], SNATTargetName) + copy(nt.Range.MinAddr[:], st.Addr.AsSlice()) + copy(nt.Range.MaxAddr[:], st.Addr.AsSlice()) + nt.Range.MinProto = htons(st.Port) + nt.Range.MaxProto = nt.Range.MinProto + + return marshal.Marshal(&nt) +} + +func (st *snatTargetMakerR2) unmarshal(buf []byte, filter stack.IPHeaderFilter) (target, *syserr.Error) { + if size := linux.SizeOfXTNATTargetV2; len(buf) < size { + nflog("snatTargetMakerR2: buf has insufficient size (%d) for SNAT target (%d)", len(buf), size) + return nil, syserr.ErrInvalidArgument + } + + if p := filter.Protocol; p != header.TCPProtocolNumber && p != header.UDPProtocolNumber { + nflog("snatTargetMakerR2: bad proto %d", p) + return nil, syserr.ErrInvalidArgument + } + + var natRange linux.NFNATRange2 + natRange.UnmarshalUnsafe(buf[linux.SizeOfXTEntryTarget:]) + + // TODO(gvisor.dev/issue/5697): Support port or address ranges. + if natRange.MinAddr != natRange.MaxAddr { + nflog("snatTargetMakerR2: MinAddr and MaxAddr are different") + return nil, syserr.ErrInvalidArgument + } + if natRange.MinProto != natRange.MaxProto { + nflog("snatTargetMakerR2: MinProto and MaxProto are different") + return nil, syserr.ErrInvalidArgument + } + if natRange.BaseProto != 0 { + nflog("snatTargetMakerR2: BaseProto is nonzero") + return nil, syserr.ErrInvalidArgument + } + + // TODO(gvisor.dev/issue/5698): Support other NF_NAT_RANGE flags. + if natRange.Flags != linux.NF_NAT_RANGE_MAP_IPS|linux.NF_NAT_RANGE_PROTO_SPECIFIED { + nflog("snatTargetMakerR2: invalid range flags %d", natRange.Flags) + return nil, syserr.ErrInvalidArgument + } + + target := snatTarget{ + SNATTarget: stack.SNATTarget{ + NetworkProtocol: filter.NetworkProtocol(), + Port: ntohs(natRange.MinProto), + }, + } + switch st.NetworkProtocol { + case header.IPv4ProtocolNumber: + target.SNATTarget.Addr = tcpip.AddrFrom4Slice(natRange.MinAddr[:4]) + case header.IPv6ProtocolNumber: + target.SNATTarget.Addr = tcpip.AddrFrom16(natRange.MinAddr) + default: + panic(fmt.Sprintf("invalid protocol number: %d", st.NetworkProtocol)) + } + + return &target, nil +} diff --git a/pkg/sentry/socket/netfilter/targets.go b/pkg/sentry/socket/netfilter/targets.go index e62844f1c..d6ac101a9 100644 --- a/pkg/sentry/socket/netfilter/targets.go +++ b/pkg/sentry/socket/netfilter/targets.go @@ -70,6 +70,12 @@ func init() { registerTargetMaker(&snatTargetMakerR1{ NetworkProtocol: header.IPv6ProtocolNumber, }) + registerTargetMaker(&snatTargetMakerR2{ + NetworkProtocol: header.IPv4ProtocolNumber, + }) + registerTargetMaker(&snatTargetMakerR2{ + NetworkProtocol: header.IPv6ProtocolNumber, + }) } // The stack package provides some basic, useful targets for us. The following