// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package iptables

import (
	"strings"
	"testing"

	"github.com/stretchr/testify/require"
)

func TestSetup(t *testing.T) {
	cases := []struct {
		name            string
		cfg             Config
		additionalRules [][]string
		expectedRules   []string
	}{
		{
			"no proxy outbound port provided",
			Config{
				ProxyUserID:      "123",
				ProxyInboundPort: 20000,
				IptablesProvider: &fakeIptablesProvider{},
			},
			nil,
			[]string{
				"iptables -t nat -N CONSUL_PROXY_INBOUND",
				"iptables -t nat -N CONSUL_PROXY_IN_REDIRECT",
				"iptables -t nat -N CONSUL_PROXY_OUTPUT",
				"iptables -t nat -N CONSUL_PROXY_REDIRECT",
				"iptables -t nat -N CONSUL_DNS_REDIRECT",
				"iptables -t nat -A CONSUL_PROXY_REDIRECT -p tcp -j REDIRECT --to-port 15001",
				"iptables -t nat -A OUTPUT -p tcp -j CONSUL_PROXY_OUTPUT",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -m owner --uid-owner 123 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -d 127.0.0.1/32 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -j CONSUL_PROXY_REDIRECT",
				"iptables -t nat -A CONSUL_PROXY_IN_REDIRECT -p tcp -j REDIRECT --to-port 20000",
				"iptables -t nat -A PREROUTING -p tcp -j CONSUL_PROXY_INBOUND",
				"iptables -t nat -A CONSUL_PROXY_INBOUND -p tcp -j CONSUL_PROXY_IN_REDIRECT",
			},
		},
		{
			"Consul DNS IP provided",
			Config{
				ProxyUserID:      "123",
				ProxyInboundPort: 20000,
				ConsulDNSIP:      "10.0.34.16",
				IptablesProvider: &fakeIptablesProvider{},
			},
			nil,
			[]string{
				"iptables -t nat -N CONSUL_PROXY_INBOUND",
				"iptables -t nat -N CONSUL_PROXY_IN_REDIRECT",
				"iptables -t nat -N CONSUL_PROXY_OUTPUT",
				"iptables -t nat -N CONSUL_PROXY_REDIRECT",
				"iptables -t nat -N CONSUL_DNS_REDIRECT",
				"iptables -t nat -A CONSUL_PROXY_REDIRECT -p tcp -j REDIRECT --to-port 15001",
				"iptables -t nat -A CONSUL_DNS_REDIRECT -p udp --dport 53 -j DNAT --to-destination 10.0.34.16",
				"iptables -t nat -A CONSUL_DNS_REDIRECT -p tcp --dport 53 -j DNAT --to-destination 10.0.34.16",
				"iptables -t nat -A OUTPUT -p udp --dport 53 -j CONSUL_DNS_REDIRECT",
				"iptables -t nat -A OUTPUT -p tcp --dport 53 -j CONSUL_DNS_REDIRECT",
				"iptables -t nat -A OUTPUT -p tcp -j CONSUL_PROXY_OUTPUT",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -m owner --uid-owner 123 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -d 127.0.0.1/32 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -j CONSUL_PROXY_REDIRECT",
				"iptables -t nat -A CONSUL_PROXY_IN_REDIRECT -p tcp -j REDIRECT --to-port 20000",
				"iptables -t nat -A PREROUTING -p tcp -j CONSUL_PROXY_INBOUND",
				"iptables -t nat -A CONSUL_PROXY_INBOUND -p tcp -j CONSUL_PROXY_IN_REDIRECT",
			},
		},
		{
			"Consul DNS port provided",
			Config{
				ProxyUserID:      "123",
				ProxyInboundPort: 20000,
				ConsulDNSPort:    8600,
				IptablesProvider: &fakeIptablesProvider{},
			},
			nil,
			[]string{
				"iptables -t nat -N CONSUL_PROXY_INBOUND",
				"iptables -t nat -N CONSUL_PROXY_IN_REDIRECT",
				"iptables -t nat -N CONSUL_PROXY_OUTPUT",
				"iptables -t nat -N CONSUL_PROXY_REDIRECT",
				"iptables -t nat -N CONSUL_DNS_REDIRECT",
				"iptables -t nat -A CONSUL_PROXY_REDIRECT -p tcp -j REDIRECT --to-port 15001",
				"iptables -t nat -A CONSUL_DNS_REDIRECT -p udp -d 127.0.0.1 --dport 53 -j DNAT --to-destination 127.0.0.1:8600",
				"iptables -t nat -A CONSUL_DNS_REDIRECT -p tcp -d 127.0.0.1 --dport 53 -j DNAT --to-destination 127.0.0.1:8600",
				"iptables -t nat -A OUTPUT -p udp -d 127.0.0.1 --dport 53 -j CONSUL_DNS_REDIRECT",
				"iptables -t nat -A OUTPUT -p tcp -d 127.0.0.1 --dport 53 -j CONSUL_DNS_REDIRECT",
				"iptables -t nat -A OUTPUT -p tcp -j CONSUL_PROXY_OUTPUT",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -m owner --uid-owner 123 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -d 127.0.0.1/32 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -j CONSUL_PROXY_REDIRECT",
				"iptables -t nat -A CONSUL_PROXY_IN_REDIRECT -p tcp -j REDIRECT --to-port 20000",
				"iptables -t nat -A PREROUTING -p tcp -j CONSUL_PROXY_INBOUND",
				"iptables -t nat -A CONSUL_PROXY_INBOUND -p tcp -j CONSUL_PROXY_IN_REDIRECT",
			},
		},
		{
			"Consul DNS IP and port provided",
			Config{
				ProxyUserID:      "123",
				ProxyInboundPort: 20000,
				ConsulDNSIP:      "10.0.34.16",
				ConsulDNSPort:    8600,
				IptablesProvider: &fakeIptablesProvider{},
			},
			nil,
			[]string{
				"iptables -t nat -N CONSUL_PROXY_INBOUND",
				"iptables -t nat -N CONSUL_PROXY_IN_REDIRECT",
				"iptables -t nat -N CONSUL_PROXY_OUTPUT",
				"iptables -t nat -N CONSUL_PROXY_REDIRECT",
				"iptables -t nat -N CONSUL_DNS_REDIRECT",
				"iptables -t nat -A CONSUL_PROXY_REDIRECT -p tcp -j REDIRECT --to-port 15001",
				"iptables -t nat -A CONSUL_DNS_REDIRECT -p udp -d 10.0.34.16 --dport 53 -j DNAT --to-destination 10.0.34.16:8600",
				"iptables -t nat -A CONSUL_DNS_REDIRECT -p tcp -d 10.0.34.16 --dport 53 -j DNAT --to-destination 10.0.34.16:8600",
				"iptables -t nat -A OUTPUT -p udp -d 10.0.34.16 --dport 53 -j CONSUL_DNS_REDIRECT",
				"iptables -t nat -A OUTPUT -p tcp -d 10.0.34.16 --dport 53 -j CONSUL_DNS_REDIRECT",
				"iptables -t nat -A OUTPUT -p tcp -j CONSUL_PROXY_OUTPUT",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -m owner --uid-owner 123 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -d 127.0.0.1/32 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -j CONSUL_PROXY_REDIRECT",
				"iptables -t nat -A CONSUL_PROXY_IN_REDIRECT -p tcp -j REDIRECT --to-port 20000",
				"iptables -t nat -A PREROUTING -p tcp -j CONSUL_PROXY_INBOUND",
				"iptables -t nat -A CONSUL_PROXY_INBOUND -p tcp -j CONSUL_PROXY_IN_REDIRECT",
			},
		},
		{
			"proxy outbound port is provided",
			Config{
				ProxyUserID:       "123",
				ProxyInboundPort:  20000,
				ProxyOutboundPort: 21000,
				IptablesProvider:  &fakeIptablesProvider{},
			},
			nil,
			[]string{
				"iptables -t nat -N CONSUL_PROXY_INBOUND",
				"iptables -t nat -N CONSUL_PROXY_IN_REDIRECT",
				"iptables -t nat -N CONSUL_PROXY_OUTPUT",
				"iptables -t nat -N CONSUL_PROXY_REDIRECT",
				"iptables -t nat -N CONSUL_DNS_REDIRECT",
				"iptables -t nat -A CONSUL_PROXY_REDIRECT -p tcp -j REDIRECT --to-port 21000",
				"iptables -t nat -A OUTPUT -p tcp -j CONSUL_PROXY_OUTPUT",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -m owner --uid-owner 123 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -d 127.0.0.1/32 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -j CONSUL_PROXY_REDIRECT",
				"iptables -t nat -A CONSUL_PROXY_IN_REDIRECT -p tcp -j REDIRECT --to-port 20000",
				"iptables -t nat -A PREROUTING -p tcp -j CONSUL_PROXY_INBOUND",
				"iptables -t nat -A CONSUL_PROXY_INBOUND -p tcp -j CONSUL_PROXY_IN_REDIRECT",
			},
		},
		{
			"exclude inbound ports is set",
			Config{
				ProxyUserID:         "123",
				ProxyInboundPort:    20000,
				ProxyOutboundPort:   21000,
				ExcludeInboundPorts: []string{"22000", "22500"},
				IptablesProvider:    &fakeIptablesProvider{},
			},
			nil,
			[]string{
				"iptables -t nat -N CONSUL_PROXY_INBOUND",
				"iptables -t nat -N CONSUL_PROXY_IN_REDIRECT",
				"iptables -t nat -N CONSUL_PROXY_OUTPUT",
				"iptables -t nat -N CONSUL_PROXY_REDIRECT",
				"iptables -t nat -N CONSUL_DNS_REDIRECT",
				"iptables -t nat -A CONSUL_PROXY_REDIRECT -p tcp -j REDIRECT --to-port 21000",
				"iptables -t nat -A OUTPUT -p tcp -j CONSUL_PROXY_OUTPUT",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -m owner --uid-owner 123 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -d 127.0.0.1/32 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -j CONSUL_PROXY_REDIRECT",
				"iptables -t nat -A CONSUL_PROXY_IN_REDIRECT -p tcp -j REDIRECT --to-port 20000",
				"iptables -t nat -A PREROUTING -p tcp -j CONSUL_PROXY_INBOUND",
				"iptables -t nat -A CONSUL_PROXY_INBOUND -p tcp -j CONSUL_PROXY_IN_REDIRECT",
				"iptables -t nat -I CONSUL_PROXY_INBOUND -p tcp --dport 22000 -j RETURN",
				"iptables -t nat -I CONSUL_PROXY_INBOUND -p tcp --dport 22500 -j RETURN",
			},
		},
		{
			"exclude outbound ports is set",
			Config{
				ProxyUserID:          "123",
				ProxyInboundPort:     20000,
				ProxyOutboundPort:    21000,
				ExcludeOutboundPorts: []string{"22000", "22500"},
				IptablesProvider:     &fakeIptablesProvider{},
			},
			nil,
			[]string{
				"iptables -t nat -N CONSUL_PROXY_INBOUND",
				"iptables -t nat -N CONSUL_PROXY_IN_REDIRECT",
				"iptables -t nat -N CONSUL_PROXY_OUTPUT",
				"iptables -t nat -N CONSUL_PROXY_REDIRECT",
				"iptables -t nat -N CONSUL_DNS_REDIRECT",
				"iptables -t nat -A CONSUL_PROXY_REDIRECT -p tcp -j REDIRECT --to-port 21000",
				"iptables -t nat -A OUTPUT -p tcp -j CONSUL_PROXY_OUTPUT",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -m owner --uid-owner 123 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -d 127.0.0.1/32 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -j CONSUL_PROXY_REDIRECT",
				"iptables -t nat -I CONSUL_PROXY_OUTPUT -p tcp --dport 22000 -j RETURN",
				"iptables -t nat -I CONSUL_PROXY_OUTPUT -p tcp --dport 22500 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_IN_REDIRECT -p tcp -j REDIRECT --to-port 20000",
				"iptables -t nat -A PREROUTING -p tcp -j CONSUL_PROXY_INBOUND",
				"iptables -t nat -A CONSUL_PROXY_INBOUND -p tcp -j CONSUL_PROXY_IN_REDIRECT",
			},
		},
		{
			"exclude outbound CIDRs is set",
			Config{
				ProxyUserID:          "123",
				ProxyInboundPort:     20000,
				ProxyOutboundPort:    21000,
				ExcludeOutboundCIDRs: []string{"1.1.1.1", "2.2.2.2/24"},
				IptablesProvider:     &fakeIptablesProvider{},
			},
			nil,
			[]string{
				"iptables -t nat -N CONSUL_PROXY_INBOUND",
				"iptables -t nat -N CONSUL_PROXY_IN_REDIRECT",
				"iptables -t nat -N CONSUL_PROXY_OUTPUT",
				"iptables -t nat -N CONSUL_PROXY_REDIRECT",
				"iptables -t nat -N CONSUL_DNS_REDIRECT",
				"iptables -t nat -A CONSUL_PROXY_REDIRECT -p tcp -j REDIRECT --to-port 21000",
				"iptables -t nat -A OUTPUT -p tcp -j CONSUL_PROXY_OUTPUT",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -m owner --uid-owner 123 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -d 127.0.0.1/32 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -j CONSUL_PROXY_REDIRECT",
				"iptables -t nat -I CONSUL_PROXY_OUTPUT -d 1.1.1.1 -j RETURN",
				"iptables -t nat -I CONSUL_PROXY_OUTPUT -d 2.2.2.2/24 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_IN_REDIRECT -p tcp -j REDIRECT --to-port 20000",
				"iptables -t nat -A PREROUTING -p tcp -j CONSUL_PROXY_INBOUND",
				"iptables -t nat -A CONSUL_PROXY_INBOUND -p tcp -j CONSUL_PROXY_IN_REDIRECT",
			},
		},
		{
			"exclude UIDs is set",
			Config{
				ProxyUserID:       "123",
				ProxyInboundPort:  20000,
				ProxyOutboundPort: 21000,
				ExcludeUIDs:       []string{"456", "789"},
				IptablesProvider:  &fakeIptablesProvider{},
			},
			nil,
			[]string{
				"iptables -t nat -N CONSUL_PROXY_INBOUND",
				"iptables -t nat -N CONSUL_PROXY_IN_REDIRECT",
				"iptables -t nat -N CONSUL_PROXY_OUTPUT",
				"iptables -t nat -N CONSUL_PROXY_REDIRECT",
				"iptables -t nat -N CONSUL_DNS_REDIRECT",
				"iptables -t nat -A CONSUL_PROXY_REDIRECT -p tcp -j REDIRECT --to-port 21000",
				"iptables -t nat -A OUTPUT -p tcp -j CONSUL_PROXY_OUTPUT",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -m owner --uid-owner 123 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -d 127.0.0.1/32 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -j CONSUL_PROXY_REDIRECT",
				"iptables -t nat -I CONSUL_PROXY_OUTPUT -m owner --uid-owner 456 -j RETURN",
				"iptables -t nat -I CONSUL_PROXY_OUTPUT -m owner --uid-owner 789 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_IN_REDIRECT -p tcp -j REDIRECT --to-port 20000",
				"iptables -t nat -A PREROUTING -p tcp -j CONSUL_PROXY_INBOUND",
				"iptables -t nat -A CONSUL_PROXY_INBOUND -p tcp -j CONSUL_PROXY_IN_REDIRECT",
			},
		},
		{
			"additional rules are passed",
			Config{
				ProxyUserID:       "123",
				ProxyInboundPort:  20000,
				ProxyOutboundPort: 21000,
				ExcludeUIDs:       []string{"456", "789"},
				IptablesProvider:  &fakeIptablesProvider{},
			},
			[][]string{
				{"iptables", "-t", "nat", "--policy", "POSTROUTING", "ACCEPT"},
				{"iptables", "-t", "nat", "--policy", "PREROUTING", "ACCEPT"},
			},
			[]string{
				"iptables -t nat -N CONSUL_PROXY_INBOUND",
				"iptables -t nat -N CONSUL_PROXY_IN_REDIRECT",
				"iptables -t nat -N CONSUL_PROXY_OUTPUT",
				"iptables -t nat -N CONSUL_PROXY_REDIRECT",
				"iptables -t nat -N CONSUL_DNS_REDIRECT",
				"iptables -t nat -A CONSUL_PROXY_REDIRECT -p tcp -j REDIRECT --to-port 21000",
				"iptables -t nat -A OUTPUT -p tcp -j CONSUL_PROXY_OUTPUT",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -m owner --uid-owner 123 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -d 127.0.0.1/32 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_OUTPUT -j CONSUL_PROXY_REDIRECT",
				"iptables -t nat -I CONSUL_PROXY_OUTPUT -m owner --uid-owner 456 -j RETURN",
				"iptables -t nat -I CONSUL_PROXY_OUTPUT -m owner --uid-owner 789 -j RETURN",
				"iptables -t nat -A CONSUL_PROXY_IN_REDIRECT -p tcp -j REDIRECT --to-port 20000",
				"iptables -t nat -A PREROUTING -p tcp -j CONSUL_PROXY_INBOUND",
				"iptables -t nat -A CONSUL_PROXY_INBOUND -p tcp -j CONSUL_PROXY_IN_REDIRECT",
				"iptables -t nat --policy POSTROUTING ACCEPT",
				"iptables -t nat --policy PREROUTING ACCEPT",
			},
		},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			var fn AdditionalRulesFn
			if c.additionalRules != nil {
				fn = func(provider Provider) {
					for _, rule := range c.additionalRules {
						provider.AddRule(rule[0], rule[1:]...)
					}
				}
			}

			err := SetupWithAdditionalRules(c.cfg, fn)
			require.NoError(t, err)
			require.Equal(t, c.expectedRules, c.cfg.IptablesProvider.Rules())
		})
	}
}

func TestSetup_errors(t *testing.T) {
	cases := []struct {
		name   string
		cfg    Config
		expErr string
	}{
		{
			"no proxy UID",
			Config{
				IptablesProvider: &iptablesExecutor{},
			},
			"ProxyUserID is required to set up traffic redirection",
		},
		{
			"no proxy inbound port",
			Config{
				ProxyUserID:       "123",
				ProxyOutboundPort: 21000,
				IptablesProvider:  &iptablesExecutor{},
			},
			"ProxyInboundPort is required to set up traffic redirection",
		},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			err := Setup(c.cfg)
			require.EqualError(t, err, c.expErr)
		})
	}
}

type fakeIptablesProvider struct {
	rules []string
}

func (f *fakeIptablesProvider) AddRule(name string, args ...string) {
	var rule []string
	rule = append(rule, name)
	rule = append(rule, args...)

	f.rules = append(f.rules, strings.Join(rule, " "))
}

func (f *fakeIptablesProvider) ApplyRules() error {
	return nil
}

func (f *fakeIptablesProvider) Rules() []string {
	return f.rules
}
