Add nat table support for iptables.
- commit the changes for the comments.
This commit is contained in:
parent
b30b7f3422
commit
acc405ba60
|
@ -250,8 +250,24 @@ type XTErrorTarget struct {
|
|||
// SizeOfXTErrorTarget is the size of an XTErrorTarget.
|
||||
const SizeOfXTErrorTarget = 64
|
||||
|
||||
// Flag values for NfNATIPV4Range. The values indicate whether to map
|
||||
// protocol specific part(ports) or IPs. It corresponds to values in
|
||||
// include/uapi/linux/netfilter/nf_nat.h.
|
||||
const (
|
||||
NF_NAT_RANGE_MAP_IPS = 1 << 0
|
||||
NF_NAT_RANGE_PROTO_SPECIFIED = 1 << 1
|
||||
NF_NAT_RANGE_PROTO_RANDOM = 1 << 2
|
||||
NF_NAT_RANGE_PERSISTENT = 1 << 3
|
||||
NF_NAT_RANGE_PROTO_RANDOM_FULLY = 1 << 4
|
||||
NF_NAT_RANGE_PROTO_RANDOM_ALL = (NF_NAT_RANGE_PROTO_RANDOM | NF_NAT_RANGE_PROTO_RANDOM_FULLY)
|
||||
NF_NAT_RANGE_MASK = (NF_NAT_RANGE_MAP_IPS |
|
||||
NF_NAT_RANGE_PROTO_SPECIFIED | NF_NAT_RANGE_PROTO_RANDOM |
|
||||
NF_NAT_RANGE_PERSISTENT | NF_NAT_RANGE_PROTO_RANDOM_FULLY)
|
||||
)
|
||||
|
||||
// NfNATIPV4Range. It corresponds to struct nf_nat_ipv4_range
|
||||
// in include/uapi/linux/netfilter/nf_nat.h.
|
||||
// in include/uapi/linux/netfilter/nf_nat.h. The fields are in
|
||||
// network byte order.
|
||||
type NfNATIPV4Range struct {
|
||||
Flags uint32
|
||||
MinIP [4]byte
|
||||
|
@ -263,11 +279,12 @@ type NfNATIPV4Range struct {
|
|||
// NfNATIPV4MultiRangeCompat. It corresponds to struct
|
||||
// nf_nat_ipv4_multi_range_compat in include/uapi/linux/netfilter/nf_nat.h.
|
||||
type NfNATIPV4MultiRangeCompat struct {
|
||||
Rangesize uint32
|
||||
RangeIPV4 [1]NfNATIPV4Range
|
||||
RangeSize uint32
|
||||
RangeIPV4 NfNATIPV4Range
|
||||
}
|
||||
|
||||
// XTRedirectTarget triggers a redirect when reached.
|
||||
// Adding 4 bytes of padding to make the struct 8 byte aligned.
|
||||
type XTRedirectTarget struct {
|
||||
Target XTEntryTarget
|
||||
NfRange NfNATIPV4MultiRangeCompat
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/syserr"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/iptables"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
|
@ -288,6 +289,7 @@ func marshalRedirectTarget() []byte {
|
|||
TargetSize: linux.SizeOfXTRedirectTarget,
|
||||
},
|
||||
}
|
||||
copy(target.Target.Name[:], redirectTargetName)
|
||||
|
||||
ret := make([]byte, 0, linux.SizeOfXTRedirectTarget)
|
||||
return binary.Marshal(ret, usermem.ByteOrder, target)
|
||||
|
@ -405,7 +407,7 @@ func SetEntries(stack *stack.Stack, optVal []byte) *syserr.Error {
|
|||
nflog("entry doesn't have enough room for its target (only %d bytes remain)", len(optVal))
|
||||
return syserr.ErrInvalidArgument
|
||||
}
|
||||
target, err := parseTarget(optVal[:targetSize])
|
||||
target, err := parseTarget(filter, optVal[:targetSize])
|
||||
if err != nil {
|
||||
nflog("failed to parse target: %v", err)
|
||||
return syserr.ErrInvalidArgument
|
||||
|
@ -552,7 +554,7 @@ func parseMatchers(filter iptables.IPHeaderFilter, optVal []byte) ([]iptables.Ma
|
|||
|
||||
// parseTarget parses a target from optVal. optVal should contain only the
|
||||
// target.
|
||||
func parseTarget(optVal []byte) (iptables.Target, error) {
|
||||
func parseTarget(filter iptables.IPHeaderFilter, optVal []byte) (iptables.Target, error) {
|
||||
nflog("set entries: parsing target of size %d", len(optVal))
|
||||
if len(optVal) < linux.SizeOfXTEntryTarget {
|
||||
return nil, fmt.Errorf("optVal has insufficient size for entry target %d", len(optVal))
|
||||
|
@ -604,6 +606,10 @@ func parseTarget(optVal []byte) (iptables.Target, error) {
|
|||
return nil, fmt.Errorf("netfilter.SetEntries: optVal has insufficient size for redirect target %d", len(optVal))
|
||||
}
|
||||
|
||||
if filter.Protocol != header.TCPProtocolNumber && filter.Protocol != header.UDPProtocolNumber {
|
||||
return nil, fmt.Errorf("netfilter.SetEntries: invalid argument")
|
||||
}
|
||||
|
||||
var redirectTarget linux.XTRedirectTarget
|
||||
buf = optVal[:linux.SizeOfXTRedirectTarget]
|
||||
binary.Unmarshal(buf, usermem.ByteOrder, &redirectTarget)
|
||||
|
@ -612,21 +618,30 @@ func parseTarget(optVal []byte) (iptables.Target, error) {
|
|||
var target iptables.RedirectTarget
|
||||
nfRange := redirectTarget.NfRange
|
||||
|
||||
target.RangeSize = nfRange.Rangesize
|
||||
target.Flags = nfRange.RangeIPV4[0].Flags
|
||||
// RangeSize should be 1.
|
||||
if nfRange.RangeSize != 1 {
|
||||
return nil, fmt.Errorf("netfilter.SetEntries: invalid argument")
|
||||
}
|
||||
|
||||
target.MinIP = tcpip.Address(nfRange.RangeIPV4[0].MinIP[:])
|
||||
target.MaxIP = tcpip.Address(nfRange.RangeIPV4[0].MaxIP[:])
|
||||
// TODO(gvisor.dev/issue/170): Check if the flags are valid.
|
||||
// Also check if we need to map ports or IP.
|
||||
// For now, redirect target only supports dest port change.
|
||||
if nfRange.RangeIPV4.Flags&linux.NF_NAT_RANGE_PROTO_SPECIFIED == 0 {
|
||||
return nil, fmt.Errorf("netfilter.SetEntries: invalid argument.")
|
||||
}
|
||||
target.Flags = nfRange.RangeIPV4.Flags
|
||||
|
||||
target.MinIP = tcpip.Address(nfRange.RangeIPV4.MinIP[:])
|
||||
target.MaxIP = tcpip.Address(nfRange.RangeIPV4.MaxIP[:])
|
||||
|
||||
// Convert port from big endian to little endian.
|
||||
port := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(port, nfRange.RangeIPV4[0].MinPort)
|
||||
binary.BigEndian.PutUint16(port, nfRange.RangeIPV4.MinPort)
|
||||
target.MinPort = binary.LittleEndian.Uint16(port)
|
||||
|
||||
binary.BigEndian.PutUint16(port, nfRange.RangeIPV4[0].MaxPort)
|
||||
binary.BigEndian.PutUint16(port, nfRange.RangeIPV4.MaxPort)
|
||||
target.MaxPort = binary.LittleEndian.Uint16(port)
|
||||
return target, nil
|
||||
|
||||
}
|
||||
|
||||
// Unknown target.
|
||||
|
|
|
@ -207,7 +207,7 @@ func (it *IPTables) checkTable(hook Hook, pkt tcpip.PacketBuffer, tablename stri
|
|||
underflow := table.Rules[table.Underflows[hook]]
|
||||
// Underflow is guaranteed to be an unconditional
|
||||
// ACCEPT or DROP.
|
||||
switch v, _ := underflow.Target.Action(pkt); v {
|
||||
switch v, _ := underflow.Target.Action(pkt, underflow.Filter); v {
|
||||
case RuleAccept:
|
||||
return TableAccept
|
||||
case RuleDrop:
|
||||
|
@ -233,6 +233,12 @@ func (it *IPTables) checkTable(hook Hook, pkt tcpip.PacketBuffer, tablename stri
|
|||
func (it *IPTables) checkRule(hook Hook, pkt tcpip.PacketBuffer, table Table, ruleIdx int) RuleVerdict {
|
||||
rule := table.Rules[ruleIdx]
|
||||
|
||||
// If pkt.NetworkHeader hasn't been set yet, it will be contained in
|
||||
// pkt.Data.First().
|
||||
if pkt.NetworkHeader == nil {
|
||||
pkt.NetworkHeader = pkt.Data.First()
|
||||
}
|
||||
|
||||
// First check whether the packet matches the IP header filter.
|
||||
// TODO(gvisor.dev/issue/170): Support other fields of the filter.
|
||||
if rule.Filter.Protocol != 0 && rule.Filter.Protocol != header.IPv4(pkt.NetworkHeader).TransportProtocol() {
|
||||
|
@ -252,6 +258,6 @@ func (it *IPTables) checkRule(hook Hook, pkt tcpip.PacketBuffer, table Table, ru
|
|||
}
|
||||
|
||||
// All the matchers matched, so run the target.
|
||||
verdict, _ := rule.Target.Action(pkt)
|
||||
verdict, _ := rule.Target.Action(pkt, rule.Filter)
|
||||
return verdict
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
type AcceptTarget struct{}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (AcceptTarget) Action(packet tcpip.PacketBuffer) (RuleVerdict, string) {
|
||||
func (AcceptTarget) Action(packet tcpip.PacketBuffer, filter IPHeaderFilter) (RuleVerdict, string) {
|
||||
return RuleAccept, ""
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ func (AcceptTarget) Action(packet tcpip.PacketBuffer) (RuleVerdict, string) {
|
|||
type DropTarget struct{}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (DropTarget) Action(packet tcpip.PacketBuffer) (RuleVerdict, string) {
|
||||
func (DropTarget) Action(packet tcpip.PacketBuffer, filter IPHeaderFilter) (RuleVerdict, string) {
|
||||
return RuleDrop, ""
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ func (DropTarget) Action(packet tcpip.PacketBuffer) (RuleVerdict, string) {
|
|||
type ErrorTarget struct{}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (ErrorTarget) Action(packet tcpip.PacketBuffer) (RuleVerdict, string) {
|
||||
func (ErrorTarget) Action(packet tcpip.PacketBuffer, filter IPHeaderFilter) (RuleVerdict, string) {
|
||||
log.Debugf("ErrorTarget triggered.")
|
||||
return RuleDrop, ""
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ type UserChainTarget struct {
|
|||
}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (UserChainTarget) Action(tcpip.PacketBuffer) (RuleVerdict, string) {
|
||||
func (UserChainTarget) Action(tcpip.PacketBuffer, IPHeaderFilter) (RuleVerdict, string) {
|
||||
panic("UserChainTarget should never be called.")
|
||||
}
|
||||
|
||||
|
@ -63,29 +63,55 @@ func (UserChainTarget) Action(tcpip.PacketBuffer) (RuleVerdict, string) {
|
|||
type ReturnTarget struct{}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (ReturnTarget) Action(tcpip.PacketBuffer) (RuleVerdict, string) {
|
||||
func (ReturnTarget) Action(tcpip.PacketBuffer, IPHeaderFilter) (RuleVerdict, string) {
|
||||
return RuleReturn, ""
|
||||
}
|
||||
|
||||
// RedirectTarget redirects the packet by modifying the destination port/IP.
|
||||
// Min and Max values for IP and Ports in the struct indicate the range of
|
||||
// values which can be used to redirect.
|
||||
type RedirectTarget struct {
|
||||
RangeSize uint32
|
||||
Flags uint32
|
||||
MinIP tcpip.Address
|
||||
MaxIP tcpip.Address
|
||||
MinPort uint16
|
||||
MaxPort uint16
|
||||
// Flags to check if the redirect is for address or ports.
|
||||
Flags uint32
|
||||
|
||||
// Min address used to redirect.
|
||||
MinIP tcpip.Address
|
||||
|
||||
// Max address used to redirect.
|
||||
MaxIP tcpip.Address
|
||||
|
||||
// Min port used to redirect.
|
||||
MinPort uint16
|
||||
|
||||
// Max port used to redirect.
|
||||
MaxPort uint16
|
||||
}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (rt RedirectTarget) Action(packet tcpip.PacketBuffer) (RuleVerdict, string) {
|
||||
log.Infof("RedirectTarget triggered.")
|
||||
func (rt RedirectTarget) Action(pkt tcpip.PacketBuffer, filter IPHeaderFilter) (RuleVerdict, string) {
|
||||
headerView := pkt.Data.First()
|
||||
|
||||
// TODO(gvisor.dev/issue/170): Checking only for UDP protocol.
|
||||
// We're yet to support for TCP protocol.
|
||||
headerView := packet.Data.First()
|
||||
h := header.UDP(headerView)
|
||||
h.SetDestinationPort(rt.MinPort)
|
||||
// Network header should be set.
|
||||
netHeader := header.IPv4(headerView)
|
||||
if netHeader == nil {
|
||||
return RuleDrop, ""
|
||||
}
|
||||
|
||||
// TODO(gvisor.dev/issue/170): Check Flags in RedirectTarget if
|
||||
// we need to change dest address (for OUTPUT chain) or ports.
|
||||
hlen := int(netHeader.HeaderLength())
|
||||
|
||||
switch protocol := filter.Protocol; protocol {
|
||||
case header.UDPProtocolNumber:
|
||||
udp := header.UDP(headerView[hlen:])
|
||||
udp.SetDestinationPort(rt.MinPort)
|
||||
case header.TCPProtocolNumber:
|
||||
// TODO(gvisor.dev/issue/170): Need to recompute checksum
|
||||
// and implement nat connection tracking to support TCP.
|
||||
tcp := header.TCP(headerView[hlen:])
|
||||
tcp.SetDestinationPort(rt.MinPort)
|
||||
default:
|
||||
return RuleDrop, ""
|
||||
}
|
||||
return RuleAccept, ""
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ const (
|
|||
// TableAccept indicates the packet should continue through netstack.
|
||||
TableAccept TableVerdict = iota
|
||||
|
||||
// TableAccept indicates the packet should be dropped.
|
||||
// TableDrop indicates the packet should be dropped.
|
||||
TableDrop
|
||||
)
|
||||
|
||||
|
@ -175,5 +175,5 @@ type Target interface {
|
|||
// Action takes an action on the packet and returns a verdict on how
|
||||
// traversal should (or should not) continue. If the return value is
|
||||
// Jump, it also returns the name of the chain to jump to.
|
||||
Action(packet tcpip.PacketBuffer) (RuleVerdict, string)
|
||||
Action(packet tcpip.PacketBuffer, filter IPHeaderFilter) (RuleVerdict, string)
|
||||
}
|
||||
|
|
|
@ -1087,19 +1087,8 @@ func (n *NIC) DeliverNetworkPacket(linkEP LinkEndpoint, remote, local tcpip.Link
|
|||
|
||||
// TODO(gvisor.dev/issue/170): Not supporting iptables for IPv6 yet.
|
||||
if protocol == header.IPv4ProtocolNumber {
|
||||
newPkt := pkt.Clone()
|
||||
|
||||
headerView := newPkt.Data.First()
|
||||
h := header.IPv4(headerView)
|
||||
newPkt.NetworkHeader = headerView[:h.HeaderLength()]
|
||||
|
||||
hlen := int(h.HeaderLength())
|
||||
tlen := int(h.TotalLength())
|
||||
newPkt.Data.TrimFront(hlen)
|
||||
newPkt.Data.CapLength(tlen - hlen)
|
||||
|
||||
ipt := n.stack.IPTables()
|
||||
if ok := ipt.Check(iptables.Prerouting, newPkt); !ok {
|
||||
if ok := ipt.Check(iptables.Prerouting, pkt); !ok {
|
||||
// iptables is telling us to drop the packet.
|
||||
return
|
||||
}
|
||||
|
|
|
@ -71,20 +71,12 @@ func (NATRedirectTCPPort) ContainerAction(ip net.IP) error {
|
|||
}
|
||||
|
||||
// Listen for TCP packets on redirect port.
|
||||
if err := listenTCP(redirectPort, sendloopDuration); err != nil {
|
||||
return fmt.Errorf("connection on port %d should be accepted, but got error %v", redirectPort, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return listenTCP(redirectPort, sendloopDuration)
|
||||
}
|
||||
|
||||
// LocalAction implements TestCase.LocalAction.
|
||||
func (NATRedirectTCPPort) LocalAction(ip net.IP) error {
|
||||
if err := connectTCP(ip, dropPort, acceptPort, sendloopDuration); err != nil {
|
||||
return fmt.Errorf("connection destined to port %d should be accepted, but got error %v", dropPort, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return connectTCP(ip, dropPort, acceptPort, sendloopDuration)
|
||||
}
|
||||
|
||||
// NATDropUDP tests that packets are not received in ports other than redirect port.
|
||||
|
|
Loading…
Reference in New Issue