2020-01-10 17:05:25 +00:00
|
|
|
// Copyright 2020 The gVisor Authors.
|
2020-01-09 23:38:28 +00:00
|
|
|
//
|
|
|
|
// 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 iptables
|
|
|
|
|
|
|
|
import (
|
2020-07-24 22:54:16 +00:00
|
|
|
"context"
|
2020-02-15 01:19:32 +00:00
|
|
|
"errors"
|
2020-01-10 17:05:25 +00:00
|
|
|
"fmt"
|
|
|
|
"net"
|
2020-06-12 03:33:56 +00:00
|
|
|
"syscall"
|
2020-01-09 23:38:28 +00:00
|
|
|
)
|
|
|
|
|
2020-06-12 03:33:56 +00:00
|
|
|
const redirectPort = 42
|
2020-01-09 23:38:28 +00:00
|
|
|
|
|
|
|
func init() {
|
2020-03-27 19:18:45 +00:00
|
|
|
RegisterTestCase(NATPreRedirectUDPPort{})
|
|
|
|
RegisterTestCase(NATPreRedirectTCPPort{})
|
2020-06-05 18:22:44 +00:00
|
|
|
RegisterTestCase(NATPreRedirectTCPOutgoing{})
|
|
|
|
RegisterTestCase(NATOutRedirectTCPIncoming{})
|
2020-03-27 19:18:45 +00:00
|
|
|
RegisterTestCase(NATOutRedirectUDPPort{})
|
|
|
|
RegisterTestCase(NATOutRedirectTCPPort{})
|
2020-01-10 17:05:25 +00:00
|
|
|
RegisterTestCase(NATDropUDP{})
|
2020-02-18 19:30:42 +00:00
|
|
|
RegisterTestCase(NATAcceptAll{})
|
2020-02-15 01:19:32 +00:00
|
|
|
RegisterTestCase(NATPreRedirectIP{})
|
|
|
|
RegisterTestCase(NATPreDontRedirectIP{})
|
|
|
|
RegisterTestCase(NATPreRedirectInvert{})
|
|
|
|
RegisterTestCase(NATOutRedirectIP{})
|
|
|
|
RegisterTestCase(NATOutDontRedirectIP{})
|
|
|
|
RegisterTestCase(NATOutRedirectInvert{})
|
|
|
|
RegisterTestCase(NATRedirectRequiresProtocol{})
|
2020-06-04 02:57:39 +00:00
|
|
|
RegisterTestCase(NATLoopbackSkipsPrerouting{})
|
2020-06-12 03:33:56 +00:00
|
|
|
RegisterTestCase(NATPreOriginalDst{})
|
|
|
|
RegisterTestCase(NATOutOriginalDst{})
|
2020-01-09 23:38:28 +00:00
|
|
|
}
|
|
|
|
|
2020-03-27 19:18:45 +00:00
|
|
|
// NATPreRedirectUDPPort tests that packets are redirected to different port.
|
2020-07-24 22:54:16 +00:00
|
|
|
type NATPreRedirectUDPPort struct{ containerCase }
|
2020-01-09 23:38:28 +00:00
|
|
|
|
|
|
|
// Name implements TestCase.Name.
|
2020-03-27 19:18:45 +00:00
|
|
|
func (NATPreRedirectUDPPort) Name() string {
|
|
|
|
return "NATPreRedirectUDPPort"
|
2020-01-09 23:38:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerAction implements TestCase.ContainerAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATPreRedirectUDPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-06-15 05:40:46 +00:00
|
|
|
if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil {
|
2020-01-09 23:38:28 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-07-24 22:54:16 +00:00
|
|
|
if err := listenUDP(ctx, redirectPort); err != nil {
|
2020-01-10 17:05:25 +00:00
|
|
|
return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %v", redirectPort, err)
|
2020-01-09 23:38:28 +00:00
|
|
|
}
|
2020-02-18 19:30:42 +00:00
|
|
|
|
2020-01-09 23:38:28 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// LocalAction implements TestCase.LocalAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATPreRedirectUDPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
|
|
|
return sendUDPLoop(ctx, ip, acceptPort)
|
2020-01-09 23:38:28 +00:00
|
|
|
}
|
|
|
|
|
2020-03-27 19:18:45 +00:00
|
|
|
// NATPreRedirectTCPPort tests that connections are redirected on specified ports.
|
2020-07-24 22:54:16 +00:00
|
|
|
type NATPreRedirectTCPPort struct{ baseCase }
|
2020-02-18 19:30:42 +00:00
|
|
|
|
|
|
|
// Name implements TestCase.Name.
|
2020-03-27 19:18:45 +00:00
|
|
|
func (NATPreRedirectTCPPort) Name() string {
|
|
|
|
return "NATPreRedirectTCPPort"
|
2020-02-18 19:30:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerAction implements TestCase.ContainerAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATPreRedirectTCPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-06-15 05:40:46 +00:00
|
|
|
if err := natTable(ipv6, "-A", "PREROUTING", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil {
|
2020-02-18 19:30:42 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Listen for TCP packets on redirect port.
|
2020-07-24 22:54:16 +00:00
|
|
|
return listenTCP(ctx, acceptPort)
|
2020-02-18 19:30:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// LocalAction implements TestCase.LocalAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATPreRedirectTCPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
|
|
|
return connectTCP(ctx, ip, dropPort)
|
2020-02-18 19:30:42 +00:00
|
|
|
}
|
|
|
|
|
2020-06-05 18:22:44 +00:00
|
|
|
// NATPreRedirectTCPOutgoing verifies that outgoing TCP connections aren't
|
|
|
|
// affected by PREROUTING connection tracking.
|
2020-07-24 22:54:16 +00:00
|
|
|
type NATPreRedirectTCPOutgoing struct{ baseCase }
|
2020-06-05 18:22:44 +00:00
|
|
|
|
|
|
|
// Name implements TestCase.Name.
|
|
|
|
func (NATPreRedirectTCPOutgoing) Name() string {
|
|
|
|
return "NATPreRedirectTCPOutgoing"
|
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerAction implements TestCase.ContainerAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATPreRedirectTCPOutgoing) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-06-05 18:22:44 +00:00
|
|
|
// Redirect all incoming TCP traffic to a closed port.
|
2020-06-15 05:40:46 +00:00
|
|
|
if err := natTable(ipv6, "-A", "PREROUTING", "-p", "tcp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", dropPort)); err != nil {
|
2020-06-05 18:22:44 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Establish a connection to the host process.
|
2020-07-24 22:54:16 +00:00
|
|
|
return connectTCP(ctx, ip, acceptPort)
|
2020-06-05 18:22:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// LocalAction implements TestCase.LocalAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATPreRedirectTCPOutgoing) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
|
|
|
return listenTCP(ctx, acceptPort)
|
2020-06-05 18:22:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NATOutRedirectTCPIncoming verifies that incoming TCP connections aren't
|
|
|
|
// affected by OUTPUT connection tracking.
|
2020-07-24 22:54:16 +00:00
|
|
|
type NATOutRedirectTCPIncoming struct{ baseCase }
|
2020-06-05 18:22:44 +00:00
|
|
|
|
|
|
|
// Name implements TestCase.Name.
|
|
|
|
func (NATOutRedirectTCPIncoming) Name() string {
|
|
|
|
return "NATOutRedirectTCPIncoming"
|
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerAction implements TestCase.ContainerAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATOutRedirectTCPIncoming) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-06-05 18:22:44 +00:00
|
|
|
// Redirect all outgoing TCP traffic to a closed port.
|
2020-06-15 05:40:46 +00:00
|
|
|
if err := natTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", dropPort)); err != nil {
|
2020-06-05 18:22:44 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Establish a connection to the host process.
|
2020-07-24 22:54:16 +00:00
|
|
|
return listenTCP(ctx, acceptPort)
|
2020-06-05 18:22:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// LocalAction implements TestCase.LocalAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATOutRedirectTCPIncoming) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
|
|
|
return connectTCP(ctx, ip, acceptPort)
|
2020-06-05 18:22:44 +00:00
|
|
|
}
|
|
|
|
|
2020-03-27 19:18:45 +00:00
|
|
|
// NATOutRedirectUDPPort tests that packets are redirected to different port.
|
2020-07-24 22:54:16 +00:00
|
|
|
type NATOutRedirectUDPPort struct{ containerCase }
|
2020-03-27 19:18:45 +00:00
|
|
|
|
|
|
|
// Name implements TestCase.Name.
|
|
|
|
func (NATOutRedirectUDPPort) Name() string {
|
|
|
|
return "NATOutRedirectUDPPort"
|
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerAction implements TestCase.ContainerAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATOutRedirectUDPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
|
|
|
return loopbackTest(ctx, ipv6, net.ParseIP(nowhereIP(ipv6)), "-A", "OUTPUT", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort))
|
2020-03-27 19:18:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// LocalAction implements TestCase.LocalAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATOutRedirectUDPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-03-27 19:18:45 +00:00
|
|
|
// No-op.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-02-15 01:19:32 +00:00
|
|
|
// NATDropUDP tests that packets are not received in ports other than redirect
|
|
|
|
// port.
|
2020-07-24 22:54:16 +00:00
|
|
|
type NATDropUDP struct{ containerCase }
|
2020-01-09 23:38:28 +00:00
|
|
|
|
|
|
|
// Name implements TestCase.Name.
|
2020-01-10 17:05:25 +00:00
|
|
|
func (NATDropUDP) Name() string {
|
|
|
|
return "NATDropUDP"
|
2020-01-09 23:38:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerAction implements TestCase.ContainerAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATDropUDP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-06-15 05:40:46 +00:00
|
|
|
if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil {
|
2020-01-09 23:38:28 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-07-24 22:54:16 +00:00
|
|
|
timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout)
|
|
|
|
defer cancel()
|
|
|
|
if err := listenUDP(timedCtx, acceptPort); err == nil {
|
2020-01-09 23:38:28 +00:00
|
|
|
return fmt.Errorf("packets on port %d should have been redirected to port %d", acceptPort, redirectPort)
|
2020-07-24 22:54:16 +00:00
|
|
|
} else if !errors.Is(err, context.DeadlineExceeded) {
|
|
|
|
return fmt.Errorf("error reading: %v", err)
|
2020-01-09 23:38:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// LocalAction implements TestCase.LocalAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATDropUDP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
|
|
|
return sendUDPLoop(ctx, ip, acceptPort)
|
2020-01-09 23:38:28 +00:00
|
|
|
}
|
2020-02-18 19:30:42 +00:00
|
|
|
|
|
|
|
// NATAcceptAll tests that all UDP packets are accepted.
|
2020-07-24 22:54:16 +00:00
|
|
|
type NATAcceptAll struct{ containerCase }
|
2020-02-18 19:30:42 +00:00
|
|
|
|
|
|
|
// Name implements TestCase.Name.
|
|
|
|
func (NATAcceptAll) Name() string {
|
|
|
|
return "NATAcceptAll"
|
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerAction implements TestCase.ContainerAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATAcceptAll) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-06-15 05:40:46 +00:00
|
|
|
if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-j", "ACCEPT"); err != nil {
|
2020-02-18 19:30:42 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-07-24 22:54:16 +00:00
|
|
|
if err := listenUDP(ctx, acceptPort); err != nil {
|
2020-02-18 19:30:42 +00:00
|
|
|
return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %v", acceptPort, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// LocalAction implements TestCase.LocalAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATAcceptAll) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
|
|
|
return sendUDPLoop(ctx, ip, acceptPort)
|
2020-02-18 19:30:42 +00:00
|
|
|
}
|
2020-03-16 18:13:14 +00:00
|
|
|
|
2020-02-15 01:19:32 +00:00
|
|
|
// NATOutRedirectIP uses iptables to select packets based on destination IP and
|
|
|
|
// redirects them.
|
2020-07-24 22:54:16 +00:00
|
|
|
type NATOutRedirectIP struct{ baseCase }
|
2020-02-15 01:19:32 +00:00
|
|
|
|
|
|
|
// Name implements TestCase.Name.
|
|
|
|
func (NATOutRedirectIP) Name() string {
|
|
|
|
return "NATOutRedirectIP"
|
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerAction implements TestCase.ContainerAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATOutRedirectIP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-02-15 01:19:32 +00:00
|
|
|
// Redirect OUTPUT packets to a listening localhost port.
|
2020-07-24 22:54:16 +00:00
|
|
|
return loopbackTest(ctx, ipv6, net.ParseIP(nowhereIP(ipv6)),
|
2020-06-15 05:40:46 +00:00
|
|
|
"-A", "OUTPUT",
|
|
|
|
"-d", nowhereIP(ipv6),
|
|
|
|
"-p", "udp",
|
|
|
|
"-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort))
|
2020-02-15 01:19:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// LocalAction implements TestCase.LocalAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATOutRedirectIP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-02-15 01:19:32 +00:00
|
|
|
// No-op.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NATOutDontRedirectIP tests that iptables matching with "-d" does not match
|
|
|
|
// packets it shouldn't.
|
2020-07-24 22:54:16 +00:00
|
|
|
type NATOutDontRedirectIP struct{ localCase }
|
2020-02-15 01:19:32 +00:00
|
|
|
|
|
|
|
// Name implements TestCase.Name.
|
|
|
|
func (NATOutDontRedirectIP) Name() string {
|
|
|
|
return "NATOutDontRedirectIP"
|
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerAction implements TestCase.ContainerAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATOutDontRedirectIP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-06-15 05:40:46 +00:00
|
|
|
if err := natTable(ipv6, "-A", "OUTPUT", "-d", localIP(ipv6), "-p", "udp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", dropPort)); err != nil {
|
2020-02-15 01:19:32 +00:00
|
|
|
return err
|
|
|
|
}
|
2020-07-24 22:54:16 +00:00
|
|
|
return sendUDPLoop(ctx, ip, acceptPort)
|
2020-02-15 01:19:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// LocalAction implements TestCase.LocalAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATOutDontRedirectIP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
|
|
|
return listenUDP(ctx, acceptPort)
|
2020-02-15 01:19:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NATOutRedirectInvert tests that iptables can match with "! -d".
|
2020-07-24 22:54:16 +00:00
|
|
|
type NATOutRedirectInvert struct{ baseCase }
|
2020-02-15 01:19:32 +00:00
|
|
|
|
|
|
|
// Name implements TestCase.Name.
|
|
|
|
func (NATOutRedirectInvert) Name() string {
|
|
|
|
return "NATOutRedirectInvert"
|
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerAction implements TestCase.ContainerAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATOutRedirectInvert) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-02-15 01:19:32 +00:00
|
|
|
// Redirect OUTPUT packets to a listening localhost port.
|
2020-06-15 05:40:46 +00:00
|
|
|
dest := "192.0.2.2"
|
|
|
|
if ipv6 {
|
|
|
|
dest = "2001:db8::2"
|
|
|
|
}
|
2020-07-24 22:54:16 +00:00
|
|
|
return loopbackTest(ctx, ipv6, net.ParseIP(nowhereIP(ipv6)),
|
2020-06-15 05:40:46 +00:00
|
|
|
"-A", "OUTPUT",
|
|
|
|
"!", "-d", dest,
|
|
|
|
"-p", "udp",
|
|
|
|
"-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort))
|
2020-02-15 01:19:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// LocalAction implements TestCase.LocalAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATOutRedirectInvert) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-02-15 01:19:32 +00:00
|
|
|
// No-op.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NATPreRedirectIP tests that we can use iptables to select packets based on
|
|
|
|
// destination IP and redirect them.
|
2020-07-24 22:54:16 +00:00
|
|
|
type NATPreRedirectIP struct{ containerCase }
|
2020-02-15 01:19:32 +00:00
|
|
|
|
|
|
|
// Name implements TestCase.Name.
|
|
|
|
func (NATPreRedirectIP) Name() string {
|
|
|
|
return "NATPreRedirectIP"
|
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerAction implements TestCase.ContainerAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATPreRedirectIP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-06-15 05:40:46 +00:00
|
|
|
addrs, err := localAddrs(ipv6)
|
2020-02-15 01:19:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var rules [][]string
|
|
|
|
for _, addr := range addrs {
|
|
|
|
rules = append(rules, []string{"-A", "PREROUTING", "-p", "udp", "-d", addr, "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)})
|
|
|
|
}
|
2020-06-15 05:40:46 +00:00
|
|
|
if err := natTableRules(ipv6, rules); err != nil {
|
2020-02-15 01:19:32 +00:00
|
|
|
return err
|
|
|
|
}
|
2020-07-24 22:54:16 +00:00
|
|
|
return listenUDP(ctx, acceptPort)
|
2020-02-15 01:19:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// LocalAction implements TestCase.LocalAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATPreRedirectIP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
|
|
|
return sendUDPLoop(ctx, ip, dropPort)
|
2020-02-15 01:19:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NATPreDontRedirectIP tests that iptables matching with "-d" does not match
|
|
|
|
// packets it shouldn't.
|
2020-07-24 22:54:16 +00:00
|
|
|
type NATPreDontRedirectIP struct{ containerCase }
|
2020-02-15 01:19:32 +00:00
|
|
|
|
|
|
|
// Name implements TestCase.Name.
|
|
|
|
func (NATPreDontRedirectIP) Name() string {
|
|
|
|
return "NATPreDontRedirectIP"
|
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerAction implements TestCase.ContainerAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATPreDontRedirectIP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-06-15 05:40:46 +00:00
|
|
|
if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-d", localIP(ipv6), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", dropPort)); err != nil {
|
2020-02-15 01:19:32 +00:00
|
|
|
return err
|
|
|
|
}
|
2020-07-24 22:54:16 +00:00
|
|
|
return listenUDP(ctx, acceptPort)
|
2020-02-15 01:19:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// LocalAction implements TestCase.LocalAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATPreDontRedirectIP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
|
|
|
return sendUDPLoop(ctx, ip, acceptPort)
|
2020-02-15 01:19:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NATPreRedirectInvert tests that iptables can match with "! -d".
|
2020-07-24 22:54:16 +00:00
|
|
|
type NATPreRedirectInvert struct{ containerCase }
|
2020-02-15 01:19:32 +00:00
|
|
|
|
|
|
|
// Name implements TestCase.Name.
|
|
|
|
func (NATPreRedirectInvert) Name() string {
|
|
|
|
return "NATPreRedirectInvert"
|
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerAction implements TestCase.ContainerAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATPreRedirectInvert) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-06-15 05:40:46 +00:00
|
|
|
if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "!", "-d", localIP(ipv6), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil {
|
2020-02-15 01:19:32 +00:00
|
|
|
return err
|
|
|
|
}
|
2020-07-24 22:54:16 +00:00
|
|
|
return listenUDP(ctx, acceptPort)
|
2020-02-15 01:19:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// LocalAction implements TestCase.LocalAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATPreRedirectInvert) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
|
|
|
return sendUDPLoop(ctx, ip, dropPort)
|
2020-02-15 01:19:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NATRedirectRequiresProtocol tests that use of the --to-ports flag requires a
|
|
|
|
// protocol to be specified with -p.
|
2020-07-24 22:54:16 +00:00
|
|
|
type NATRedirectRequiresProtocol struct{ baseCase }
|
2020-02-15 01:19:32 +00:00
|
|
|
|
|
|
|
// Name implements TestCase.Name.
|
|
|
|
func (NATRedirectRequiresProtocol) Name() string {
|
|
|
|
return "NATRedirectRequiresProtocol"
|
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerAction implements TestCase.ContainerAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATRedirectRequiresProtocol) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-06-15 05:40:46 +00:00
|
|
|
if err := natTable(ipv6, "-A", "PREROUTING", "-d", localIP(ipv6), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err == nil {
|
2020-02-15 01:19:32 +00:00
|
|
|
return errors.New("expected an error using REDIRECT --to-ports without a protocol")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// LocalAction implements TestCase.LocalAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATRedirectRequiresProtocol) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-02-15 01:19:32 +00:00
|
|
|
// No-op.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-27 19:18:45 +00:00
|
|
|
// NATOutRedirectTCPPort tests that connections are redirected on specified ports.
|
2020-07-24 22:54:16 +00:00
|
|
|
type NATOutRedirectTCPPort struct{ baseCase }
|
2020-03-27 19:18:45 +00:00
|
|
|
|
|
|
|
// Name implements TestCase.Name.
|
|
|
|
func (NATOutRedirectTCPPort) Name() string {
|
|
|
|
return "NATOutRedirectTCPPort"
|
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerAction implements TestCase.ContainerAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATOutRedirectTCPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-06-15 05:40:46 +00:00
|
|
|
if err := natTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil {
|
2020-03-27 19:18:45 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
localAddr := net.TCPAddr{
|
2020-06-15 05:40:46 +00:00
|
|
|
IP: net.ParseIP(localIP(ipv6)),
|
2020-03-27 19:18:45 +00:00
|
|
|
Port: acceptPort,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Starts listening on port.
|
|
|
|
lConn, err := net.ListenTCP("tcp", &localAddr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer lConn.Close()
|
|
|
|
|
|
|
|
// Accept connections on port.
|
2020-07-24 22:54:16 +00:00
|
|
|
if err := connectTCP(ctx, ip, dropPort); err != nil {
|
2020-03-27 19:18:45 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
conn, err := lConn.AcceptTCP()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
conn.Close()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// LocalAction implements TestCase.LocalAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATOutRedirectTCPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-03-27 19:18:45 +00:00
|
|
|
return nil
|
|
|
|
}
|
2020-06-04 02:57:39 +00:00
|
|
|
|
|
|
|
// NATLoopbackSkipsPrerouting tests that packets sent via loopback aren't
|
|
|
|
// affected by PREROUTING rules.
|
2020-07-24 22:54:16 +00:00
|
|
|
type NATLoopbackSkipsPrerouting struct{ baseCase }
|
2020-06-04 02:57:39 +00:00
|
|
|
|
|
|
|
// Name implements TestCase.Name.
|
|
|
|
func (NATLoopbackSkipsPrerouting) Name() string {
|
|
|
|
return "NATLoopbackSkipsPrerouting"
|
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerAction implements TestCase.ContainerAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATLoopbackSkipsPrerouting) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-06-04 02:57:39 +00:00
|
|
|
// Redirect anything sent to localhost to an unused port.
|
|
|
|
dest := []byte{127, 0, 0, 1}
|
2020-06-15 05:40:46 +00:00
|
|
|
if err := natTable(ipv6, "-A", "PREROUTING", "-p", "tcp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", dropPort)); err != nil {
|
2020-06-04 02:57:39 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Establish a connection via localhost. If the PREROUTING rule did apply to
|
|
|
|
// loopback traffic, the connection would fail.
|
|
|
|
sendCh := make(chan error)
|
|
|
|
go func() {
|
2020-07-24 22:54:16 +00:00
|
|
|
sendCh <- connectTCP(ctx, dest, acceptPort)
|
2020-06-04 02:57:39 +00:00
|
|
|
}()
|
|
|
|
|
2020-07-24 22:54:16 +00:00
|
|
|
if err := listenTCP(ctx, acceptPort); err != nil {
|
2020-06-04 02:57:39 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return <-sendCh
|
|
|
|
}
|
|
|
|
|
|
|
|
// LocalAction implements TestCase.LocalAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATLoopbackSkipsPrerouting) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-06-04 02:57:39 +00:00
|
|
|
// No-op.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-06-12 03:33:56 +00:00
|
|
|
// NATPreOriginalDst tests that SO_ORIGINAL_DST returns the pre-NAT destination
|
|
|
|
// of PREROUTING NATted packets.
|
2020-07-24 22:54:16 +00:00
|
|
|
type NATPreOriginalDst struct{ baseCase }
|
2020-06-12 03:33:56 +00:00
|
|
|
|
|
|
|
// Name implements TestCase.Name.
|
|
|
|
func (NATPreOriginalDst) Name() string {
|
|
|
|
return "NATPreOriginalDst"
|
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerAction implements TestCase.ContainerAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATPreOriginalDst) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-06-12 03:33:56 +00:00
|
|
|
// Redirect incoming TCP connections to acceptPort.
|
|
|
|
if err := natTable(ipv6, "-A", "PREROUTING",
|
|
|
|
"-p", "tcp",
|
|
|
|
"--destination-port", fmt.Sprintf("%d", dropPort),
|
|
|
|
"-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
addrs, err := getInterfaceAddrs(ipv6)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-07-24 22:54:16 +00:00
|
|
|
return listenForRedirectedConn(ctx, ipv6, addrs)
|
2020-06-12 03:33:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// LocalAction implements TestCase.LocalAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATPreOriginalDst) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
|
|
|
return connectTCP(ctx, ip, dropPort)
|
2020-06-12 03:33:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NATOutOriginalDst tests that SO_ORIGINAL_DST returns the pre-NAT destination
|
|
|
|
// of OUTBOUND NATted packets.
|
2020-07-24 22:54:16 +00:00
|
|
|
type NATOutOriginalDst struct{ baseCase }
|
2020-06-12 03:33:56 +00:00
|
|
|
|
|
|
|
// Name implements TestCase.Name.
|
|
|
|
func (NATOutOriginalDst) Name() string {
|
|
|
|
return "NATOutOriginalDst"
|
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerAction implements TestCase.ContainerAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATOutOriginalDst) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-06-12 03:33:56 +00:00
|
|
|
// Redirect incoming TCP connections to acceptPort.
|
|
|
|
if err := natTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
connCh := make(chan error)
|
|
|
|
go func() {
|
2020-07-24 22:54:16 +00:00
|
|
|
connCh <- connectTCP(ctx, ip, dropPort)
|
2020-06-12 03:33:56 +00:00
|
|
|
}()
|
|
|
|
|
2020-07-24 22:54:16 +00:00
|
|
|
if err := listenForRedirectedConn(ctx, ipv6, []net.IP{ip}); err != nil {
|
2020-06-12 03:33:56 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return <-connCh
|
|
|
|
}
|
|
|
|
|
|
|
|
// LocalAction implements TestCase.LocalAction.
|
2020-07-24 22:54:16 +00:00
|
|
|
func (NATOutOriginalDst) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
|
2020-06-12 03:33:56 +00:00
|
|
|
// No-op.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-07-24 22:54:16 +00:00
|
|
|
func listenForRedirectedConn(ctx context.Context, ipv6 bool, originalDsts []net.IP) error {
|
2020-06-12 03:33:56 +00:00
|
|
|
// The net package doesn't give guarantee access to the connection's
|
|
|
|
// underlying FD, and thus we cannot call getsockopt. We have to use
|
|
|
|
// traditional syscalls for SO_ORIGINAL_DST.
|
|
|
|
|
|
|
|
// Create the listening socket, bind, listen, and accept.
|
|
|
|
family := syscall.AF_INET
|
|
|
|
if ipv6 {
|
|
|
|
family = syscall.AF_INET6
|
|
|
|
}
|
|
|
|
sockfd, err := syscall.Socket(family, syscall.SOCK_STREAM, 0)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer syscall.Close(sockfd)
|
|
|
|
|
|
|
|
var bindAddr syscall.Sockaddr
|
|
|
|
if ipv6 {
|
|
|
|
bindAddr = &syscall.SockaddrInet6{
|
|
|
|
Port: acceptPort,
|
|
|
|
Addr: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // in6addr_any
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
bindAddr = &syscall.SockaddrInet4{
|
|
|
|
Port: acceptPort,
|
|
|
|
Addr: [4]byte{0, 0, 0, 0}, // INADDR_ANY
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := syscall.Bind(sockfd, bindAddr); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := syscall.Listen(sockfd, 1); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-07-24 22:54:16 +00:00
|
|
|
// Block on accept() in another goroutine.
|
|
|
|
connCh := make(chan int)
|
|
|
|
errCh := make(chan error)
|
|
|
|
go func() {
|
|
|
|
connFD, _, err := syscall.Accept(sockfd)
|
|
|
|
if err != nil {
|
|
|
|
errCh <- err
|
|
|
|
}
|
|
|
|
connCh <- connFD
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Wait for accept() to return or for the context to finish.
|
|
|
|
var connFD int
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return ctx.Err()
|
|
|
|
case err := <-errCh:
|
2020-06-12 03:33:56 +00:00
|
|
|
return err
|
2020-07-24 22:54:16 +00:00
|
|
|
case connFD = <-connCh:
|
2020-06-12 03:33:56 +00:00
|
|
|
}
|
2020-07-24 22:54:16 +00:00
|
|
|
defer syscall.Close(connFD)
|
2020-06-12 03:33:56 +00:00
|
|
|
|
|
|
|
// Verify that, despite listening on acceptPort, SO_ORIGINAL_DST
|
|
|
|
// indicates the packet was sent to originalDst:dropPort.
|
|
|
|
if ipv6 {
|
2020-07-24 22:54:16 +00:00
|
|
|
got, err := originalDestination6(connFD)
|
2020-06-12 03:33:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// The original destination could be any of our IPs.
|
|
|
|
for _, dst := range originalDsts {
|
|
|
|
want := syscall.RawSockaddrInet6{
|
|
|
|
Family: syscall.AF_INET6,
|
|
|
|
Port: htons(dropPort),
|
|
|
|
}
|
|
|
|
copy(want.Addr[:], dst.To16())
|
|
|
|
if got == want {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fmt.Errorf("SO_ORIGINAL_DST returned %+v, but wanted one of %+v (note: port numbers are in network byte order)", got, originalDsts)
|
|
|
|
} else {
|
2020-07-24 22:54:16 +00:00
|
|
|
got, err := originalDestination4(connFD)
|
2020-06-12 03:33:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// The original destination could be any of our IPs.
|
|
|
|
for _, dst := range originalDsts {
|
|
|
|
want := syscall.RawSockaddrInet4{
|
|
|
|
Family: syscall.AF_INET,
|
|
|
|
Port: htons(dropPort),
|
|
|
|
}
|
|
|
|
copy(want.Addr[:], dst.To4())
|
|
|
|
if got == want {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fmt.Errorf("SO_ORIGINAL_DST returned %+v, but wanted one of %+v (note: port numbers are in network byte order)", got, originalDsts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-04 02:57:39 +00:00
|
|
|
// loopbackTests runs an iptables rule and ensures that packets sent to
|
|
|
|
// dest:dropPort are received by localhost:acceptPort.
|
2020-07-24 22:54:16 +00:00
|
|
|
func loopbackTest(ctx context.Context, ipv6 bool, dest net.IP, args ...string) error {
|
2020-06-15 05:40:46 +00:00
|
|
|
if err := natTable(ipv6, args...); err != nil {
|
2020-06-04 02:57:39 +00:00
|
|
|
return err
|
|
|
|
}
|
2020-07-24 22:54:16 +00:00
|
|
|
sendCh := make(chan error, 1)
|
|
|
|
listenCh := make(chan error, 1)
|
2020-06-04 02:57:39 +00:00
|
|
|
go func() {
|
2020-07-24 22:54:16 +00:00
|
|
|
sendCh <- sendUDPLoop(ctx, dest, dropPort)
|
2020-06-04 02:57:39 +00:00
|
|
|
}()
|
|
|
|
go func() {
|
2020-07-24 22:54:16 +00:00
|
|
|
listenCh <- listenUDP(ctx, acceptPort)
|
2020-06-04 02:57:39 +00:00
|
|
|
}()
|
|
|
|
select {
|
|
|
|
case err := <-listenCh:
|
2020-07-24 22:54:16 +00:00
|
|
|
return err
|
|
|
|
case err := <-sendCh:
|
|
|
|
return err
|
2020-06-04 02:57:39 +00:00
|
|
|
}
|
|
|
|
}
|