From 5a78e12e2fd4cacdae24c2050be8cd9b489f31e8 Mon Sep 17 00:00:00 2001 From: Kevin Krakauer Date: Thu, 9 Nov 2023 17:45:22 -0800 Subject: [PATCH] netstack: put snat targets in their own file We're about to have several more of these, so split them out. PiperOrigin-RevId: 581077373 --- pkg/sentry/socket/netfilter/BUILD | 1 + pkg/sentry/socket/netfilter/snat.go | 190 +++++++++++++++++++++++++ pkg/sentry/socket/netfilter/targets.go | 166 --------------------- 3 files changed, 191 insertions(+), 166 deletions(-) create mode 100644 pkg/sentry/socket/netfilter/snat.go diff --git a/pkg/sentry/socket/netfilter/BUILD b/pkg/sentry/socket/netfilter/BUILD index edb5007b8..7729bad51 100644 --- a/pkg/sentry/socket/netfilter/BUILD +++ b/pkg/sentry/socket/netfilter/BUILD @@ -14,6 +14,7 @@ go_library( "netfilter.go", "owner_matcher.go", "owner_matcher_v1.go", + "snat.go", "targets.go", "tcp_matcher.go", "udp_matcher.go", diff --git a/pkg/sentry/socket/netfilter/snat.go b/pkg/sentry/socket/netfilter/snat.go new file mode 100644 index 000000000..213670403 --- /dev/null +++ b/pkg/sentry/socket/netfilter/snat.go @@ -0,0 +1,190 @@ +// Copyright 2023 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 netfilter + +import ( + "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/marshal" + "gvisor.dev/gvisor/pkg/syserr" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/stack" +) + +// SNATTargetName is used to mark targets as SNAT targets. SNAT targets should +// be reached for only NAT table. These targets will change the source port +// and/or IP for packets. +const SNATTargetName = "SNAT" + +type snatTarget struct { + stack.SNATTarget +} + +func (st *snatTarget) id() targetID { + return targetID{ + name: SNATTargetName, + networkProtocol: st.NetworkProtocol, + } +} + +type snatTargetMakerV4 struct { + NetworkProtocol tcpip.NetworkProtocolNumber +} + +func (st *snatTargetMakerV4) id() targetID { + return targetID{ + name: SNATTargetName, + networkProtocol: st.NetworkProtocol, + } +} + +func (*snatTargetMakerV4) marshal(target target) []byte { + st := target.(*snatTarget) + // This is a snat target named snat. + xt := linux.XTNATTargetV0{ + Target: linux.XTEntryTarget{ + TargetSize: linux.SizeOfXTNATTargetV0, + }, + } + copy(xt.Target.Name[:], SNATTargetName) + + xt.NfRange.RangeSize = 1 + xt.NfRange.RangeIPV4.Flags |= linux.NF_NAT_RANGE_MAP_IPS | linux.NF_NAT_RANGE_PROTO_SPECIFIED + xt.NfRange.RangeIPV4.MinPort = htons(st.Port) + xt.NfRange.RangeIPV4.MaxPort = xt.NfRange.RangeIPV4.MinPort + copy(xt.NfRange.RangeIPV4.MinIP[:], st.Addr.AsSlice()) + copy(xt.NfRange.RangeIPV4.MaxIP[:], st.Addr.AsSlice()) + return marshal.Marshal(&xt) +} + +func (*snatTargetMakerV4) unmarshal(buf []byte, filter stack.IPHeaderFilter) (target, *syserr.Error) { + if len(buf) < linux.SizeOfXTNATTargetV0 { + nflog("snatTargetMakerV4: buf has insufficient size for snat target %d", len(buf)) + return nil, syserr.ErrInvalidArgument + } + + if p := filter.Protocol; p != header.TCPProtocolNumber && p != header.UDPProtocolNumber { + nflog("snatTargetMakerV4: bad proto %d", p) + return nil, syserr.ErrInvalidArgument + } + + var st linux.XTNATTargetV0 + st.UnmarshalUnsafe(buf) + + // Copy linux.XTNATTargetV0 to stack.SNATTarget. + target := snatTarget{SNATTarget: stack.SNATTarget{ + NetworkProtocol: filter.NetworkProtocol(), + }} + + // RangeSize should be 1. + nfRange := st.NfRange + if nfRange.RangeSize != 1 { + nflog("snatTargetMakerV4: bad rangesize %d", nfRange.RangeSize) + return nil, syserr.ErrInvalidArgument + } + + // TODO(gvisor.dev/issue/5772): If the rule doesn't specify the source port, + // choose one automatically. + if nfRange.RangeIPV4.MinPort == 0 { + nflog("snatTargetMakerV4: snat target needs to specify a non-zero port") + return nil, syserr.ErrInvalidArgument + } + + if nfRange.RangeIPV4.MinPort != nfRange.RangeIPV4.MaxPort { + nflog("snatTargetMakerV4: MinPort != MaxPort (%d, %d)", nfRange.RangeIPV4.MinPort, nfRange.RangeIPV4.MaxPort) + return nil, syserr.ErrInvalidArgument + } + if nfRange.RangeIPV4.MinIP != nfRange.RangeIPV4.MaxIP { + nflog("snatTargetMakerV4: MinIP != MaxIP (%d, %d)", nfRange.RangeIPV4.MinPort, nfRange.RangeIPV4.MaxPort) + return nil, syserr.ErrInvalidArgument + } + + target.Addr = tcpip.AddrFrom4(nfRange.RangeIPV4.MinIP) + target.Port = ntohs(nfRange.RangeIPV4.MinPort) + + return &target, nil +} + +type snatTargetMakerV6 struct { + NetworkProtocol tcpip.NetworkProtocolNumber +} + +func (st *snatTargetMakerV6) id() targetID { + return targetID{ + name: SNATTargetName, + networkProtocol: st.NetworkProtocol, + revision: 1, + } +} + +func (*snatTargetMakerV6) marshal(target target) []byte { + st := target.(*snatTarget) + nt := linux.XTNATTargetV1{ + Target: linux.XTEntryTarget{ + TargetSize: linux.SizeOfXTNATTargetV1, + }, + Range: linux.NFNATRange{ + 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 (*snatTargetMakerV6) unmarshal(buf []byte, filter stack.IPHeaderFilter) (target, *syserr.Error) { + if size := linux.SizeOfXTNATTargetV1; len(buf) < size { + nflog("snatTargetMakerV6: buf has insufficient size (%d) for SNAT V6 target (%d)", len(buf), size) + return nil, syserr.ErrInvalidArgument + } + + if p := filter.Protocol; p != header.TCPProtocolNumber && p != header.UDPProtocolNumber { + nflog("snatTargetMakerV6: bad proto %d", p) + return nil, syserr.ErrInvalidArgument + } + + var natRange linux.NFNATRange + natRange.UnmarshalUnsafe(buf[linux.SizeOfXTEntryTarget:]) + + // TODO(gvisor.dev/issue/5697): Support port or address ranges. + if natRange.MinAddr != natRange.MaxAddr { + nflog("snatTargetMakerV6: MinAddr and MaxAddr are different") + return nil, syserr.ErrInvalidArgument + } + if natRange.MinProto != natRange.MaxProto { + nflog("snatTargetMakerV6: MinProto and MaxProto are different") + 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("snatTargetMakerV6: invalid range flags %d", natRange.Flags) + return nil, syserr.ErrInvalidArgument + } + + target := snatTarget{ + SNATTarget: stack.SNATTarget{ + NetworkProtocol: filter.NetworkProtocol(), + Addr: tcpip.AddrFrom16(natRange.MinAddr), + Port: ntohs(natRange.MinProto), + }, + } + + return &target, nil +} diff --git a/pkg/sentry/socket/netfilter/targets.go b/pkg/sentry/socket/netfilter/targets.go index 93e9c1061..2ed17346b 100644 --- a/pkg/sentry/socket/netfilter/targets.go +++ b/pkg/sentry/socket/netfilter/targets.go @@ -36,11 +36,6 @@ const ErrorTargetName = "ERROR" // change the destination port and/or IP for packets. const RedirectTargetName = "REDIRECT" -// SNATTargetName is used to mark targets as SNAT targets. SNAT targets should -// be reached for only NAT table. These targets will change the source port -// and/or IP for packets. -const SNATTargetName = "SNAT" - func init() { // Standard targets include ACCEPT, DROP, RETURN, and JUMP. registerTargetMaker(&standardTargetMaker{ @@ -144,17 +139,6 @@ func (rt *redirectTarget) id() targetID { } } -type snatTarget struct { - stack.SNATTarget -} - -func (st *snatTarget) id() targetID { - return targetID{ - name: SNATTargetName, - networkProtocol: st.NetworkProtocol, - } -} - type standardTargetMaker struct { NetworkProtocol tcpip.NetworkProtocolNumber } @@ -424,156 +408,6 @@ func (*nfNATTargetMaker) unmarshal(buf []byte, filter stack.IPHeaderFilter) (tar return &target, nil } -type snatTargetMakerV4 struct { - NetworkProtocol tcpip.NetworkProtocolNumber -} - -func (st *snatTargetMakerV4) id() targetID { - return targetID{ - name: SNATTargetName, - networkProtocol: st.NetworkProtocol, - } -} - -func (*snatTargetMakerV4) marshal(target target) []byte { - st := target.(*snatTarget) - // This is a snat target named snat. - xt := linux.XTNATTargetV0{ - Target: linux.XTEntryTarget{ - TargetSize: linux.SizeOfXTNATTargetV0, - }, - } - copy(xt.Target.Name[:], SNATTargetName) - - xt.NfRange.RangeSize = 1 - xt.NfRange.RangeIPV4.Flags |= linux.NF_NAT_RANGE_MAP_IPS | linux.NF_NAT_RANGE_PROTO_SPECIFIED - xt.NfRange.RangeIPV4.MinPort = htons(st.Port) - xt.NfRange.RangeIPV4.MaxPort = xt.NfRange.RangeIPV4.MinPort - copy(xt.NfRange.RangeIPV4.MinIP[:], st.Addr.AsSlice()) - copy(xt.NfRange.RangeIPV4.MaxIP[:], st.Addr.AsSlice()) - return marshal.Marshal(&xt) -} - -func (*snatTargetMakerV4) unmarshal(buf []byte, filter stack.IPHeaderFilter) (target, *syserr.Error) { - if len(buf) < linux.SizeOfXTNATTargetV0 { - nflog("snatTargetMakerV4: buf has insufficient size for snat target %d", len(buf)) - return nil, syserr.ErrInvalidArgument - } - - if p := filter.Protocol; p != header.TCPProtocolNumber && p != header.UDPProtocolNumber { - nflog("snatTargetMakerV4: bad proto %d", p) - return nil, syserr.ErrInvalidArgument - } - - var st linux.XTNATTargetV0 - st.UnmarshalUnsafe(buf) - - // Copy linux.XTNATTargetV0 to stack.SNATTarget. - target := snatTarget{SNATTarget: stack.SNATTarget{ - NetworkProtocol: filter.NetworkProtocol(), - }} - - // RangeSize should be 1. - nfRange := st.NfRange - if nfRange.RangeSize != 1 { - nflog("snatTargetMakerV4: bad rangesize %d", nfRange.RangeSize) - return nil, syserr.ErrInvalidArgument - } - - // TODO(gvisor.dev/issue/5772): If the rule doesn't specify the source port, - // choose one automatically. - if nfRange.RangeIPV4.MinPort == 0 { - nflog("snatTargetMakerV4: snat target needs to specify a non-zero port") - return nil, syserr.ErrInvalidArgument - } - - if nfRange.RangeIPV4.MinPort != nfRange.RangeIPV4.MaxPort { - nflog("snatTargetMakerV4: MinPort != MaxPort (%d, %d)", nfRange.RangeIPV4.MinPort, nfRange.RangeIPV4.MaxPort) - return nil, syserr.ErrInvalidArgument - } - if nfRange.RangeIPV4.MinIP != nfRange.RangeIPV4.MaxIP { - nflog("snatTargetMakerV4: MinIP != MaxIP (%d, %d)", nfRange.RangeIPV4.MinPort, nfRange.RangeIPV4.MaxPort) - return nil, syserr.ErrInvalidArgument - } - - target.Addr = tcpip.AddrFrom4(nfRange.RangeIPV4.MinIP) - target.Port = ntohs(nfRange.RangeIPV4.MinPort) - - return &target, nil -} - -type snatTargetMakerV6 struct { - NetworkProtocol tcpip.NetworkProtocolNumber -} - -func (st *snatTargetMakerV6) id() targetID { - return targetID{ - name: SNATTargetName, - networkProtocol: st.NetworkProtocol, - revision: 1, - } -} - -func (*snatTargetMakerV6) marshal(target target) []byte { - st := target.(*snatTarget) - nt := linux.XTNATTargetV1{ - Target: linux.XTEntryTarget{ - TargetSize: linux.SizeOfXTNATTargetV1, - }, - Range: linux.NFNATRange{ - 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 (*snatTargetMakerV6) unmarshal(buf []byte, filter stack.IPHeaderFilter) (target, *syserr.Error) { - if size := linux.SizeOfXTNATTargetV1; len(buf) < size { - nflog("snatTargetMakerV6: buf has insufficient size (%d) for SNAT V6 target (%d)", len(buf), size) - return nil, syserr.ErrInvalidArgument - } - - if p := filter.Protocol; p != header.TCPProtocolNumber && p != header.UDPProtocolNumber { - nflog("snatTargetMakerV6: bad proto %d", p) - return nil, syserr.ErrInvalidArgument - } - - var natRange linux.NFNATRange - natRange.UnmarshalUnsafe(buf[linux.SizeOfXTEntryTarget:]) - - // TODO(gvisor.dev/issue/5697): Support port or address ranges. - if natRange.MinAddr != natRange.MaxAddr { - nflog("snatTargetMakerV6: MinAddr and MaxAddr are different") - return nil, syserr.ErrInvalidArgument - } - if natRange.MinProto != natRange.MaxProto { - nflog("snatTargetMakerV6: MinProto and MaxProto are different") - 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("snatTargetMakerV6: invalid range flags %d", natRange.Flags) - return nil, syserr.ErrInvalidArgument - } - - target := snatTarget{ - SNATTarget: stack.SNATTarget{ - NetworkProtocol: filter.NetworkProtocol(), - Addr: tcpip.AddrFrom16(natRange.MinAddr), - Port: ntohs(natRange.MinProto), - }, - } - - return &target, nil -} - // translateToStandardTarget translates from the value in a // linux.XTStandardTarget to an stack.Verdict. func translateToStandardTarget(val int32, netProto tcpip.NetworkProtocolNumber) (target, *syserr.Error) {