netstack: add iptables DNAT revision 0 support

PiperOrigin-RevId: 581468023
This commit is contained in:
Kevin Krakauer
2023-11-10 23:51:22 -08:00
committed by gVisor bot
parent b9fe44808b
commit b4bf726395
6 changed files with 158 additions and 1 deletions
+1
View File
@@ -8,6 +8,7 @@ package(
go_library(
name = "netfilter",
srcs = [
"dnat.go",
"extensions.go",
"ipv4.go",
"ipv6.go",
+118
View File
@@ -0,0 +1,118 @@
// 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"
)
// DNATTargetName is used to mark targets as DNAT targets. DNAT targets should
// be reached for only NAT table. These targets will change the source port
// and/or IP for packets.
const DNATTargetName = "DNAT"
type dnatTarget struct {
stack.DNATTarget
}
func (st *dnatTarget) id() targetID {
return targetID{
name: DNATTargetName,
networkProtocol: st.NetworkProtocol,
}
}
type dnatTargetMakerV4 struct {
NetworkProtocol tcpip.NetworkProtocolNumber
}
func (st *dnatTargetMakerV4) id() targetID {
return targetID{
name: DNATTargetName,
networkProtocol: st.NetworkProtocol,
}
}
func (*dnatTargetMakerV4) marshal(target target) []byte {
st := target.(*dnatTarget)
// This is a dnat target named dnat.
xt := linux.XTNATTargetV0{
Target: linux.XTEntryTarget{
TargetSize: linux.SizeOfXTNATTargetV0,
},
}
copy(xt.Target.Name[:], DNATTargetName)
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 (*dnatTargetMakerV4) unmarshal(buf []byte, filter stack.IPHeaderFilter) (target, *syserr.Error) {
if len(buf) < linux.SizeOfXTNATTargetV0 {
nflog("dnatTargetMakerV4: buf has insufficient size for dnat target %d", len(buf))
return nil, syserr.ErrInvalidArgument
}
if p := filter.Protocol; p != header.TCPProtocolNumber && p != header.UDPProtocolNumber {
nflog("dnatTargetMakerV4: bad proto %d", p)
return nil, syserr.ErrInvalidArgument
}
var st linux.XTNATTargetV0
st.UnmarshalUnsafe(buf)
// Copy linux.XTNATTargetV0 to stack.DNATTarget.
target := dnatTarget{DNATTarget: stack.DNATTarget{
NetworkProtocol: filter.NetworkProtocol(),
}}
// RangeSize should be 1.
nfRange := st.NfRange
if nfRange.RangeSize != 1 {
nflog("dnatTargetMakerV4: 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("dnatTargetMakerV4: dnat target needs to specify a non-zero port")
return nil, syserr.ErrInvalidArgument
}
if nfRange.RangeIPV4.MinPort != nfRange.RangeIPV4.MaxPort {
nflog("dnatTargetMakerV4: MinPort != MaxPort (%d, %d)", nfRange.RangeIPV4.MinPort, nfRange.RangeIPV4.MaxPort)
return nil, syserr.ErrInvalidArgument
}
if nfRange.RangeIPV4.MinIP != nfRange.RangeIPV4.MaxIP {
nflog("dnatTargetMakerV4: 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
}
+1 -1
View File
@@ -207,7 +207,7 @@ func unmarshalTarget(target linux.XTEntryTarget, filter stack.IPHeaderFilter, bu
}
targetMaker, ok := targetMakers[tid]
if !ok {
nflog("unsupported target with name %q", target.Name.String())
nflog("unsupported target with name %q, proto %d, and revision %d", target.Name.String(), tid.networkProtocol, tid.revision)
return nil, syserr.ErrInvalidArgument
}
return targetMaker.unmarshal(buf, filter)
+7
View File
@@ -54,6 +54,7 @@ func init() {
NetworkProtocol: header.IPv6ProtocolNumber,
})
// REDIRECT targets.
registerTargetMaker(&redirectTargetMaker{
NetworkProtocol: header.IPv4ProtocolNumber,
})
@@ -61,6 +62,7 @@ func init() {
NetworkProtocol: header.IPv6ProtocolNumber,
})
// SNAT targets.
registerTargetMaker(&snatTargetMakerV4{
NetworkProtocol: header.IPv4ProtocolNumber,
})
@@ -76,6 +78,11 @@ func init() {
registerTargetMaker(&snatTargetMakerR2{
NetworkProtocol: header.IPv6ProtocolNumber,
})
// DNAT targets.
registerTargetMaker(&dnatTargetMakerV4{
NetworkProtocol: header.IPv4ProtocolNumber,
})
}
// The stack package provides some basic, useful targets for us. The following
+4
View File
@@ -364,6 +364,10 @@ func TestNATOutRedirectInvert(t *testing.T) {
singleTest(t, &NATOutRedirectInvert{})
}
func TestNATOutDNAT(t *testing.T) {
singleTest(t, &NATOutDNAT{})
}
func TestNATPreRedirectIP(t *testing.T) {
singleTest(t, &NATPreRedirectIP{})
}
+27
View File
@@ -51,6 +51,7 @@ func init() {
RegisterTestCase(&NATOutRECVORIGDSTADDR{})
RegisterTestCase(&NATPostSNATUDP{})
RegisterTestCase(&NATPostSNATTCP{})
RegisterTestCase(&NATOutDNAT{})
}
// NATPreRedirectUDPPort tests that packets are redirected to different port.
@@ -1035,3 +1036,29 @@ func (*NATPostSNATTCP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) er
}
return nil
}
// NATOutDNAT tests that the source port/IP in the packets are modified as
// expected.
type NATOutDNAT struct{ containerCase }
var _ TestCase = (*NATOutDNAT)(nil)
// Name implements TestCase.Name.
func (*NATOutDNAT) Name() string {
return "NATOutDNAT"
}
// ContainerAction implements TestCase.ContainerAction.
func (*NATOutDNAT) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
dst := nowhereIP(ipv6)
return loopbackTest(ctx, ipv6, net.ParseIP(dst),
"-A", "OUTPUT",
"-d", dst,
"-p", "udp", "-m", "udp",
"-j", "DNAT", "--to-destination", fmt.Sprintf("127.0.0.1:%d", acceptPort))
}
// LocalAction implements TestCase.LocalAction.
func (*NATOutDNAT) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
return nil
}