2021-08-23 09:14:53 -04:00
|
|
|
//go:build linux
|
2021-05-28 14:53:49 -04:00
|
|
|
// +build linux
|
|
|
|
|
2015-05-06 19:47:41 -04:00
|
|
|
package iptables
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net"
|
|
|
|
"os/exec"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
2015-05-19 00:56:36 -04:00
|
|
|
|
2021-05-27 20:15:56 -04:00
|
|
|
"golang.org/x/sync/errgroup"
|
2015-05-06 19:47:41 -04:00
|
|
|
)
|
|
|
|
|
2015-05-14 17:56:15 -04:00
|
|
|
const chainName = "DOCKEREST"
|
2015-05-06 19:47:41 -04:00
|
|
|
|
2015-06-11 21:12:00 -04:00
|
|
|
var natChain *ChainInfo
|
|
|
|
var filterChain *ChainInfo
|
|
|
|
var bridgeName string
|
2015-05-06 19:47:41 -04:00
|
|
|
|
|
|
|
func TestNewChain(t *testing.T) {
|
|
|
|
var err error
|
|
|
|
|
2015-06-11 21:12:00 -04:00
|
|
|
bridgeName = "lo"
|
2017-11-28 16:15:55 -05:00
|
|
|
iptable := GetIptable(IPv4)
|
|
|
|
|
|
|
|
natChain, err = iptable.NewChain(chainName, Nat, false)
|
2017-06-12 14:30:30 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-11-28 16:15:55 -05:00
|
|
|
err = iptable.ProgramChain(natChain, bridgeName, false, true)
|
2015-05-06 19:47:41 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2017-11-28 16:15:55 -05:00
|
|
|
filterChain, err = iptable.NewChain(chainName, Filter, false)
|
2017-06-12 14:30:30 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-11-28 16:15:55 -05:00
|
|
|
err = iptable.ProgramChain(filterChain, bridgeName, false, true)
|
2015-05-06 19:47:41 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestForward(t *testing.T) {
|
|
|
|
ip := net.ParseIP("192.168.1.1")
|
|
|
|
port := 1234
|
|
|
|
dstAddr := "172.17.0.1"
|
|
|
|
dstPort := 4321
|
|
|
|
proto := "tcp"
|
|
|
|
|
2015-06-11 21:12:00 -04:00
|
|
|
bridgeName := "lo"
|
2017-11-28 16:15:55 -05:00
|
|
|
iptable := GetIptable(IPv4)
|
|
|
|
|
2015-06-11 21:12:00 -04:00
|
|
|
err := natChain.Forward(Insert, ip, port, proto, dstAddr, dstPort, bridgeName)
|
2015-05-06 19:47:41 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
dnatRule := []string{
|
|
|
|
"-d", ip.String(),
|
|
|
|
"-p", proto,
|
|
|
|
"--dport", strconv.Itoa(port),
|
|
|
|
"-j", "DNAT",
|
|
|
|
"--to-destination", dstAddr + ":" + strconv.Itoa(dstPort),
|
2015-06-11 21:12:00 -04:00
|
|
|
"!", "-i", bridgeName,
|
2015-05-06 19:47:41 -04:00
|
|
|
}
|
|
|
|
|
2017-11-28 16:15:55 -05:00
|
|
|
if !iptable.Exists(natChain.Table, natChain.Name, dnatRule...) {
|
2016-11-21 20:29:53 -05:00
|
|
|
t.Fatal("DNAT rule does not exist")
|
2015-05-06 19:47:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
filterRule := []string{
|
2015-06-11 21:12:00 -04:00
|
|
|
"!", "-i", bridgeName,
|
|
|
|
"-o", bridgeName,
|
2015-05-06 19:47:41 -04:00
|
|
|
"-d", dstAddr,
|
|
|
|
"-p", proto,
|
|
|
|
"--dport", strconv.Itoa(dstPort),
|
|
|
|
"-j", "ACCEPT",
|
|
|
|
}
|
|
|
|
|
2017-11-28 16:15:55 -05:00
|
|
|
if !iptable.Exists(filterChain.Table, filterChain.Name, filterRule...) {
|
2016-11-21 20:29:53 -05:00
|
|
|
t.Fatal("filter rule does not exist")
|
2015-05-06 19:47:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
masqRule := []string{
|
|
|
|
"-d", dstAddr,
|
|
|
|
"-s", dstAddr,
|
|
|
|
"-p", proto,
|
|
|
|
"--dport", strconv.Itoa(dstPort),
|
|
|
|
"-j", "MASQUERADE",
|
|
|
|
}
|
|
|
|
|
2017-11-28 16:15:55 -05:00
|
|
|
if !iptable.Exists(natChain.Table, "POSTROUTING", masqRule...) {
|
2016-11-21 20:29:53 -05:00
|
|
|
t.Fatal("MASQUERADE rule does not exist")
|
2015-05-06 19:47:41 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestLink(t *testing.T) {
|
|
|
|
var err error
|
|
|
|
|
2015-06-11 21:12:00 -04:00
|
|
|
bridgeName := "lo"
|
2017-11-28 16:15:55 -05:00
|
|
|
iptable := GetIptable(IPv4)
|
2015-05-06 19:47:41 -04:00
|
|
|
ip1 := net.ParseIP("192.168.1.1")
|
|
|
|
ip2 := net.ParseIP("192.168.1.2")
|
|
|
|
port := 1234
|
|
|
|
proto := "tcp"
|
|
|
|
|
2015-06-11 21:12:00 -04:00
|
|
|
err = filterChain.Link(Append, ip1, ip2, port, proto, bridgeName)
|
2015-05-06 19:47:41 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
rule1 := []string{
|
2015-06-11 21:12:00 -04:00
|
|
|
"-i", bridgeName,
|
|
|
|
"-o", bridgeName,
|
2015-05-06 19:47:41 -04:00
|
|
|
"-p", proto,
|
|
|
|
"-s", ip1.String(),
|
|
|
|
"-d", ip2.String(),
|
|
|
|
"--dport", strconv.Itoa(port),
|
|
|
|
"-j", "ACCEPT"}
|
|
|
|
|
2017-11-28 16:15:55 -05:00
|
|
|
if !iptable.Exists(filterChain.Table, filterChain.Name, rule1...) {
|
2016-11-21 20:29:53 -05:00
|
|
|
t.Fatal("rule1 does not exist")
|
2015-05-06 19:47:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
rule2 := []string{
|
2015-06-11 21:12:00 -04:00
|
|
|
"-i", bridgeName,
|
|
|
|
"-o", bridgeName,
|
2015-05-06 19:47:41 -04:00
|
|
|
"-p", proto,
|
|
|
|
"-s", ip2.String(),
|
|
|
|
"-d", ip1.String(),
|
|
|
|
"--sport", strconv.Itoa(port),
|
|
|
|
"-j", "ACCEPT"}
|
|
|
|
|
2017-11-28 16:15:55 -05:00
|
|
|
if !iptable.Exists(filterChain.Table, filterChain.Name, rule2...) {
|
2016-11-21 20:29:53 -05:00
|
|
|
t.Fatal("rule2 does not exist")
|
2015-05-06 19:47:41 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPrerouting(t *testing.T) {
|
|
|
|
args := []string{
|
|
|
|
"-i", "lo",
|
|
|
|
"-d", "192.168.1.1"}
|
2017-11-28 16:15:55 -05:00
|
|
|
iptable := GetIptable(IPv4)
|
2015-05-06 19:47:41 -04:00
|
|
|
|
|
|
|
err := natChain.Prerouting(Insert, args...)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2017-11-28 16:15:55 -05:00
|
|
|
if !iptable.Exists(natChain.Table, "PREROUTING", args...) {
|
2016-11-21 20:29:53 -05:00
|
|
|
t.Fatal("rule does not exist")
|
2015-05-06 19:47:41 -04:00
|
|
|
}
|
|
|
|
|
2015-06-16 10:07:51 -04:00
|
|
|
delRule := append([]string{"-D", "PREROUTING", "-t", string(Nat)}, args...)
|
2017-11-28 16:15:55 -05:00
|
|
|
if _, err = iptable.Raw(delRule...); err != nil {
|
2015-05-06 19:47:41 -04:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestOutput(t *testing.T) {
|
|
|
|
args := []string{
|
|
|
|
"-o", "lo",
|
|
|
|
"-d", "192.168.1.1"}
|
2017-11-28 16:15:55 -05:00
|
|
|
iptable := GetIptable(IPv4)
|
2015-05-06 19:47:41 -04:00
|
|
|
|
|
|
|
err := natChain.Output(Insert, args...)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2017-11-28 16:15:55 -05:00
|
|
|
if !iptable.Exists(natChain.Table, "OUTPUT", args...) {
|
2016-11-21 20:29:53 -05:00
|
|
|
t.Fatal("rule does not exist")
|
2015-05-06 19:47:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
delRule := append([]string{"-D", "OUTPUT", "-t",
|
2015-06-16 10:07:51 -04:00
|
|
|
string(natChain.Table)}, args...)
|
2017-11-28 16:15:55 -05:00
|
|
|
if _, err = iptable.Raw(delRule...); err != nil {
|
2015-05-06 19:47:41 -04:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-12 23:27:22 -04:00
|
|
|
func TestConcurrencyWithWait(t *testing.T) {
|
|
|
|
RunConcurrencyTest(t, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestConcurrencyNoWait(t *testing.T) {
|
|
|
|
RunConcurrencyTest(t, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Runs 10 concurrent rule additions. This will fail if iptables
|
|
|
|
// is actually invoked simultaneously without --wait.
|
|
|
|
// Note that if iptables does not support the xtable lock on this
|
|
|
|
// system, then allowXlock has no effect -- it will always be off.
|
|
|
|
func RunConcurrencyTest(t *testing.T, allowXlock bool) {
|
|
|
|
if !allowXlock && supportsXlock {
|
|
|
|
supportsXlock = false
|
|
|
|
defer func() { supportsXlock = true }()
|
|
|
|
}
|
|
|
|
|
|
|
|
ip := net.ParseIP("192.168.1.1")
|
|
|
|
port := 1234
|
|
|
|
dstAddr := "172.17.0.1"
|
|
|
|
dstPort := 4321
|
|
|
|
proto := "tcp"
|
|
|
|
|
2021-05-27 20:15:56 -04:00
|
|
|
group := new(errgroup.Group)
|
2015-05-12 23:27:22 -04:00
|
|
|
for i := 0; i < 10; i++ {
|
2021-05-27 20:15:56 -04:00
|
|
|
group.Go(func() error {
|
|
|
|
return natChain.Forward(Append, ip, port, proto, dstAddr, dstPort, "lo")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if err := group.Wait(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2015-05-12 23:27:22 -04:00
|
|
|
}
|
|
|
|
|
2015-05-06 19:47:41 -04:00
|
|
|
func TestCleanup(t *testing.T) {
|
|
|
|
var err error
|
|
|
|
var rules []byte
|
|
|
|
|
|
|
|
// Cleanup filter/FORWARD first otherwise output of iptables-save is dirty
|
|
|
|
link := []string{"-t", string(filterChain.Table),
|
|
|
|
string(Delete), "FORWARD",
|
2015-06-11 21:12:00 -04:00
|
|
|
"-o", bridgeName,
|
2015-05-06 19:47:41 -04:00
|
|
|
"-j", filterChain.Name}
|
2017-11-28 16:15:55 -05:00
|
|
|
iptable := GetIptable(IPv4)
|
|
|
|
|
|
|
|
if _, err = iptable.Raw(link...); err != nil {
|
2015-05-06 19:47:41 -04:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
filterChain.Remove()
|
|
|
|
|
2017-11-28 16:15:55 -05:00
|
|
|
err = iptable.RemoveExistingChain(chainName, Nat)
|
2015-05-06 19:47:41 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
rules, err = exec.Command("iptables-save").Output()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if strings.Contains(string(rules), chainName) {
|
|
|
|
t.Fatalf("Removing chain failed. %s found in iptables-save", chainName)
|
|
|
|
}
|
|
|
|
}
|
2016-02-02 15:52:34 -05:00
|
|
|
|
|
|
|
func TestExistsRaw(t *testing.T) {
|
|
|
|
testChain1 := "ABCD"
|
|
|
|
testChain2 := "EFGH"
|
|
|
|
|
2017-11-28 16:15:55 -05:00
|
|
|
iptable := GetIptable(IPv4)
|
|
|
|
|
|
|
|
_, err := iptable.NewChain(testChain1, Filter, false)
|
2016-02-02 15:52:34 -05:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer func() {
|
2017-11-28 16:15:55 -05:00
|
|
|
iptable.RemoveExistingChain(testChain1, Filter)
|
2016-02-02 15:52:34 -05:00
|
|
|
}()
|
|
|
|
|
2017-11-28 16:15:55 -05:00
|
|
|
_, err = iptable.NewChain(testChain2, Filter, false)
|
2016-02-02 15:52:34 -05:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer func() {
|
2017-11-28 16:15:55 -05:00
|
|
|
iptable.RemoveExistingChain(testChain2, Filter)
|
2016-02-02 15:52:34 -05:00
|
|
|
}()
|
|
|
|
|
|
|
|
// Test detection over full and truncated rule string
|
|
|
|
input := []struct{ rule []string }{
|
|
|
|
{[]string{"-s", "172.8.9.9/32", "-j", "ACCEPT"}},
|
|
|
|
{[]string{"-d", "172.8.9.0/24", "-j", "DROP"}},
|
|
|
|
{[]string{"-s", "172.0.3.0/24", "-d", "172.17.0.0/24", "-p", "tcp", "-m", "tcp", "--dport", "80", "-j", testChain2}},
|
|
|
|
{[]string{"-j", "RETURN"}},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, r := range input {
|
|
|
|
ruleAdd := append([]string{"-t", string(Filter), "-A", testChain1}, r.rule...)
|
2017-11-28 16:15:55 -05:00
|
|
|
err = iptable.RawCombinedOutput(ruleAdd...)
|
2016-02-02 15:52:34 -05:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("i=%d, err: %v", i, err)
|
|
|
|
}
|
2017-11-28 16:15:55 -05:00
|
|
|
if !iptable.existsRaw(Filter, testChain1, r.rule...) {
|
2016-02-02 15:52:34 -05:00
|
|
|
t.Fatalf("Failed to detect rule. i=%d", i)
|
|
|
|
}
|
|
|
|
// Truncate the rule
|
|
|
|
trg := r.rule[len(r.rule)-1]
|
|
|
|
trg = trg[:len(trg)-2]
|
|
|
|
r.rule[len(r.rule)-1] = trg
|
2017-11-28 16:15:55 -05:00
|
|
|
if iptable.existsRaw(Filter, testChain1, r.rule...) {
|
2016-02-02 15:52:34 -05:00
|
|
|
t.Fatalf("Invalid detection. i=%d", i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGetVersion(t *testing.T) {
|
|
|
|
mj, mn, mc := parseVersionNumbers("iptables v1.4.19.1-alpha")
|
|
|
|
if mj != 1 || mn != 4 || mc != 19 {
|
2016-11-21 20:29:53 -05:00
|
|
|
t.Fatal("Failed to parse version numbers")
|
2016-02-02 15:52:34 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSupportsCOption(t *testing.T) {
|
|
|
|
input := []struct {
|
|
|
|
mj int
|
|
|
|
mn int
|
|
|
|
mc int
|
|
|
|
ok bool
|
|
|
|
}{
|
|
|
|
{1, 4, 11, true},
|
|
|
|
{1, 4, 12, true},
|
|
|
|
{1, 5, 0, true},
|
|
|
|
{0, 4, 11, false},
|
|
|
|
{0, 5, 12, false},
|
|
|
|
{1, 3, 12, false},
|
|
|
|
{1, 4, 10, false},
|
|
|
|
}
|
|
|
|
for ind, inp := range input {
|
|
|
|
if inp.ok != supportsCOption(inp.mj, inp.mn, inp.mc) {
|
|
|
|
t.Fatalf("Incorrect check: %d", ind)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|