// Copyright 2019 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 stack

import (
	"fmt"
	"math"

	"github.com/sagernet/gvisor/pkg/log"
	"github.com/sagernet/gvisor/pkg/tcpip"
	"github.com/sagernet/gvisor/pkg/tcpip/header"
)

// AcceptTarget accepts packets.
//
// +stateify savable
type AcceptTarget struct {
	// NetworkProtocol is the network protocol the target is used with.
	NetworkProtocol tcpip.NetworkProtocolNumber
}

// Action implements Target.Action.
func (*AcceptTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
	return RuleAccept, 0
}

// DropTarget drops packets.
//
// +stateify savable
type DropTarget struct {
	// NetworkProtocol is the network protocol the target is used with.
	NetworkProtocol tcpip.NetworkProtocolNumber
}

// Action implements Target.Action.
func (*DropTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
	return RuleDrop, 0
}

// RejectIPv4WithHandler handles rejecting a packet.
type RejectIPv4WithHandler interface {
	// SendRejectionError sends an error packet in response to the packet.
	SendRejectionError(pkt *PacketBuffer, rejectWith RejectIPv4WithICMPType, inputHook bool) tcpip.Error
}

// RejectIPv4WithICMPType indicates the type of ICMP error that should be sent.
type RejectIPv4WithICMPType int

// The types of errors that may be returned when rejecting IPv4 packets.
const (
	_ RejectIPv4WithICMPType = iota
	RejectIPv4WithICMPNetUnreachable
	RejectIPv4WithICMPHostUnreachable
	RejectIPv4WithICMPPortUnreachable
	RejectIPv4WithICMPProtUnreachable
	RejectIPv4WithICMPEchoReply
	RejectIPv4WithICMPNetProhibited
	RejectIPv4WithICMPHostProhibited
	RejectIPv4WithTCPReset
	RejectIPv4WithICMPAdminProhibited
)

// RejectIPv4Target drops packets and sends back an error packet in response to the
// matched packet.
//
// +stateify savable
type RejectIPv4Target struct {
	Handler    RejectIPv4WithHandler
	RejectWith RejectIPv4WithICMPType
}

// Action implements Target.Action.
func (rt *RejectIPv4Target) Action(pkt *PacketBuffer, hook Hook, _ *Route, _ AddressableEndpoint) (RuleVerdict, int) {
	switch hook {
	case Input, Forward, Output:
		// There is nothing reasonable for us to do in response to an error here;
		// we already drop the packet.
		_ = rt.Handler.SendRejectionError(pkt, rt.RejectWith, hook == Input)
		return RuleDrop, 0
	case Prerouting, Postrouting:
		panic(fmt.Sprintf("%s hook not supported for REDIRECT", hook))
	default:
		panic(fmt.Sprintf("unhandled hook = %s", hook))
	}
}

// RejectIPv6WithHandler handles rejecting a packet.
type RejectIPv6WithHandler interface {
	// SendRejectionError sends an error packet in response to the packet.
	SendRejectionError(pkt *PacketBuffer, rejectWith RejectIPv6WithICMPType, forwardingHook bool) tcpip.Error
}

// RejectIPv6WithICMPType indicates the type of ICMP error that should be sent.
type RejectIPv6WithICMPType int

// The types of errors that may be returned when rejecting IPv6 packets.
const (
	_ RejectIPv6WithICMPType = iota
	RejectIPv6WithICMPNoRoute
	RejectIPv6WithICMPAdminProhibited
	RejectIPv6WithICMPNotNeighbour
	RejectIPv6WithICMPAddrUnreachable
	RejectIPv6WithICMPPortUnreachable
	RejectIPv6WithICMPEchoReply
	RejectIPv6WithTCPReset
	RejectIPv6WithICMPPolicyFail
	RejectIPv6WithICMPRejectRoute
)

// RejectIPv6Target drops packets and sends back an error packet in response to the
// matched packet.
//
// +stateify savable
type RejectIPv6Target struct {
	Handler    RejectIPv6WithHandler
	RejectWith RejectIPv6WithICMPType
}

// Action implements Target.Action.
func (rt *RejectIPv6Target) Action(pkt *PacketBuffer, hook Hook, _ *Route, _ AddressableEndpoint) (RuleVerdict, int) {
	switch hook {
	case Input, Forward, Output:
		// There is nothing reasonable for us to do in response to an error here;
		// we already drop the packet.
		_ = rt.Handler.SendRejectionError(pkt, rt.RejectWith, hook == Input)
		return RuleDrop, 0
	case Prerouting, Postrouting:
		panic(fmt.Sprintf("%s hook not supported for REDIRECT", hook))
	default:
		panic(fmt.Sprintf("unhandled hook = %s", hook))
	}
}

// ErrorTarget logs an error and drops the packet. It represents a target that
// should be unreachable.
//
// +stateify savable
type ErrorTarget struct {
	// NetworkProtocol is the network protocol the target is used with.
	NetworkProtocol tcpip.NetworkProtocolNumber
}

// Action implements Target.Action.
func (*ErrorTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
	log.Debugf("ErrorTarget triggered.")
	return RuleDrop, 0
}

// UserChainTarget marks a rule as the beginning of a user chain.
//
// +stateify savable
type UserChainTarget struct {
	// Name is the chain name.
	Name string

	// NetworkProtocol is the network protocol the target is used with.
	NetworkProtocol tcpip.NetworkProtocolNumber
}

// Action implements Target.Action.
func (*UserChainTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
	panic("UserChainTarget should never be called.")
}

// ReturnTarget returns from the current chain. If the chain is a built-in, the
// hook's underflow should be called.
//
// +stateify savable
type ReturnTarget struct {
	// NetworkProtocol is the network protocol the target is used with.
	NetworkProtocol tcpip.NetworkProtocolNumber
}

// Action implements Target.Action.
func (*ReturnTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
	return RuleReturn, 0
}

// DNATTarget modifies the destination port/IP of packets.
//
// +stateify savable
type DNATTarget struct {
	// The new destination address for packets.
	//
	// Immutable.
	Addr tcpip.Address

	// The new destination port for packets.
	//
	// Immutable.
	Port uint16

	// NetworkProtocol is the network protocol the target is used with.
	//
	// Immutable.
	NetworkProtocol tcpip.NetworkProtocolNumber

	// ChangeAddress indicates whether we should check addresses.
	//
	// Immutable.
	ChangeAddress bool

	// ChangePort indicates whether we should check ports.
	//
	// Immutable.
	ChangePort bool
}

// Action implements Target.Action.
func (rt *DNATTarget) Action(pkt *PacketBuffer, hook Hook, r *Route, addressEP AddressableEndpoint) (RuleVerdict, int) {
	// Sanity check.
	if rt.NetworkProtocol != pkt.NetworkProtocolNumber {
		panic(fmt.Sprintf(
			"DNATTarget.Action with NetworkProtocol %d called on packet with NetworkProtocolNumber %d",
			rt.NetworkProtocol, pkt.NetworkProtocolNumber))
	}

	switch hook {
	case Prerouting, Output:
	case Input, Forward, Postrouting:
		panic(fmt.Sprintf("%s not supported for DNAT", hook))
	default:
		panic(fmt.Sprintf("%s unrecognized", hook))
	}

	return dnatAction(pkt, hook, r, rt.Port, rt.Addr, rt.ChangePort, rt.ChangeAddress)
}

// RedirectTarget redirects the packet to this machine by modifying the
// destination port/IP. Outgoing packets are redirected to the loopback device,
// and incoming packets are redirected to the incoming interface (rather than
// forwarded).
//
// +stateify savable
type RedirectTarget struct {
	// Port indicates port used to redirect. It is immutable.
	Port uint16

	// NetworkProtocol is the network protocol the target is used with. It
	// is immutable.
	NetworkProtocol tcpip.NetworkProtocolNumber
}

// Action implements Target.Action.
func (rt *RedirectTarget) Action(pkt *PacketBuffer, hook Hook, r *Route, addressEP AddressableEndpoint) (RuleVerdict, int) {
	// Sanity check.
	if rt.NetworkProtocol != pkt.NetworkProtocolNumber {
		panic(fmt.Sprintf(
			"RedirectTarget.Action with NetworkProtocol %d called on packet with NetworkProtocolNumber %d",
			rt.NetworkProtocol, pkt.NetworkProtocolNumber))
	}

	// Change the address to loopback (127.0.0.1 or ::1) in Output and to
	// the primary address of the incoming interface in Prerouting.
	var address tcpip.Address
	switch hook {
	case Output:
		if pkt.NetworkProtocolNumber == header.IPv4ProtocolNumber {
			address = tcpip.AddrFrom4([4]byte{127, 0, 0, 1})
		} else {
			address = header.IPv6Loopback
		}
	case Prerouting:
		// addressEP is expected to be set for the prerouting hook.
		address = addressEP.MainAddress().Address
	default:
		panic("redirect target is supported only on output and prerouting hooks")
	}

	return dnatAction(pkt, hook, r, rt.Port, address, true /* changePort */, true /* changeAddress */)
}

// SNATTarget modifies the source port/IP in the outgoing packets.
//
// +stateify savable
type SNATTarget struct {
	Addr tcpip.Address
	Port uint16

	// NetworkProtocol is the network protocol the target is used with. It
	// is immutable.
	NetworkProtocol tcpip.NetworkProtocolNumber

	// ChangeAddress indicates whether we should check addresses.
	//
	// Immutable.
	ChangeAddress bool

	// ChangePort indicates whether we should check ports.
	//
	// Immutable.
	ChangePort bool
}

func dnatAction(pkt *PacketBuffer, hook Hook, r *Route, port uint16, address tcpip.Address, changePort, changeAddress bool) (RuleVerdict, int) {
	return natAction(pkt, hook, r, PortOrIdentRange{Start: port, Size: 1}, address, true /* dnat */, changePort, changeAddress)
}

func targetPortRangeForTCPAndUDP(originalSrcPort uint16) PortOrIdentRange {
	// As per iptables(8),
	//
	//   If no port range is specified, then source ports below 512 will be
	//   mapped to other ports below 512: those between 512 and 1023 inclusive
	//   will be mapped to ports below 1024, and other ports will be mapped to
	//   1024 or above.
	switch {
	case originalSrcPort < 512:
		return PortOrIdentRange{Start: 1, Size: 511}
	case originalSrcPort < 1024:
		return PortOrIdentRange{Start: 1, Size: 1023}
	default:
		return PortOrIdentRange{Start: 1024, Size: math.MaxUint16 - 1023}
	}
}

func snatAction(pkt *PacketBuffer, hook Hook, r *Route, port uint16, address tcpip.Address, changePort, changeAddress bool) (RuleVerdict, int) {
	portsOrIdents := PortOrIdentRange{Start: port, Size: 1}

	switch pkt.TransportProtocolNumber {
	case header.UDPProtocolNumber:
		if port == 0 {
			portsOrIdents = targetPortRangeForTCPAndUDP(header.UDP(pkt.TransportHeader().Slice()).SourcePort())
		}
	case header.TCPProtocolNumber:
		if port == 0 {
			portsOrIdents = targetPortRangeForTCPAndUDP(header.TCP(pkt.TransportHeader().Slice()).SourcePort())
		}
	case header.ICMPv4ProtocolNumber, header.ICMPv6ProtocolNumber:
		// Allow NAT-ing to any 16-bit value for ICMP's Ident field to match Linux
		// behaviour.
		//
		// https://github.com/torvalds/linux/blob/58e1100fdc5990b0cc0d4beaf2562a92e621ac7d/net/netfilter/nf_nat_core.c#L391
		portsOrIdents = PortOrIdentRange{Start: 0, Size: math.MaxUint16 + 1}
	}

	return natAction(pkt, hook, r, portsOrIdents, address, false /* dnat */, changePort, changeAddress)
}

func natAction(pkt *PacketBuffer, hook Hook, r *Route, portsOrIdents PortOrIdentRange, address tcpip.Address, dnat, changePort, changeAddress bool) (RuleVerdict, int) {
	// Drop the packet if network and transport header are not set.
	if len(pkt.NetworkHeader().Slice()) == 0 || len(pkt.TransportHeader().Slice()) == 0 {
		return RuleDrop, 0
	}

	if t := pkt.tuple; t != nil {
		IPTPerformNAT(pkt, hook, r, portsOrIdents, address, dnat, changePort, changeAddress)
		return RuleAccept, 0
	}

	return RuleDrop, 0
}

// Action implements Target.Action.
func (st *SNATTarget) Action(pkt *PacketBuffer, hook Hook, r *Route, _ AddressableEndpoint) (RuleVerdict, int) {
	// Sanity check.
	if st.NetworkProtocol != pkt.NetworkProtocolNumber {
		panic(fmt.Sprintf(
			"SNATTarget.Action with NetworkProtocol %d called on packet with NetworkProtocolNumber %d",
			st.NetworkProtocol, pkt.NetworkProtocolNumber))
	}

	switch hook {
	case Postrouting, Input:
	case Prerouting, Output, Forward:
		panic(fmt.Sprintf("%s not supported", hook))
	default:
		panic(fmt.Sprintf("%s unrecognized", hook))
	}

	return snatAction(pkt, hook, r, st.Port, st.Addr, st.ChangePort, st.ChangeAddress)
}

// MasqueradeTarget modifies the source port/IP in the outgoing packets.
//
// +stateify savable
type MasqueradeTarget struct {
	// NetworkProtocol is the network protocol the target is used with. It
	// is immutable.
	NetworkProtocol tcpip.NetworkProtocolNumber
}

// Action implements Target.Action.
func (mt *MasqueradeTarget) Action(pkt *PacketBuffer, hook Hook, r *Route, addressEP AddressableEndpoint) (RuleVerdict, int) {
	// Sanity check.
	if mt.NetworkProtocol != pkt.NetworkProtocolNumber {
		panic(fmt.Sprintf(
			"MasqueradeTarget.Action with NetworkProtocol %d called on packet with NetworkProtocolNumber %d",
			mt.NetworkProtocol, pkt.NetworkProtocolNumber))
	}

	switch hook {
	case Postrouting:
	case Prerouting, Input, Forward, Output:
		panic(fmt.Sprintf("masquerade target is supported only on postrouting hook; hook = %d", hook))
	default:
		panic(fmt.Sprintf("%s unrecognized", hook))
	}

	// addressEP is expected to be set for the postrouting hook.
	ep := addressEP.AcquireOutgoingPrimaryAddress(pkt.Network().DestinationAddress(), tcpip.Address{} /* srcHint */, false /* allowExpired */)
	if ep == nil {
		// No address exists that we can use as a source address.
		return RuleDrop, 0
	}

	address := ep.AddressWithPrefix().Address
	ep.DecRef()
	return snatAction(pkt, hook, r, 0 /* port */, address, true /* changePort */, true /* changeAddress */)
}

// CTTarget is a no-op implementation of the CT (conntrack) target used in the
// raw table. In Linux, CT --zone sets conntrack zones for connection tracking
// isolation. gVisor's conntrack does not support zones, so this target simply
// accepts the packet, allowing iptables-restore to load rulesets that reference
// CT targets (e.g. Istio with DNS capture enabled).
//
// +stateify savable
type CTTarget struct {
	// NetworkProtocol is the network protocol the target is used with.
	NetworkProtocol tcpip.NetworkProtocolNumber

	// Zone is the conntrack zone ID. Stored but not acted upon.
	Zone uint16
}

// Action implements Target.Action. It is a no-op that accepts the packet.
func (*CTTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
	return RuleAccept, 0
}
