safing-portmaster/service/firewall/interception/nfqueue_linux.go

354 lines
10 KiB
Go

package interception
import (
"context"
"flag"
"fmt"
"sort"
"strings"
"github.com/coreos/go-iptables/iptables"
"github.com/hashicorp/go-multierror"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/service/firewall/interception/nfq"
"github.com/safing/portmaster/service/netenv"
"github.com/safing/portmaster/service/network/packet"
)
var (
v4chains []string
v4rules []string
v4once []string
v6chains []string
v6rules []string
v6once []string
out4Queue nfQueue
in4Queue nfQueue
out6Queue nfQueue
in6Queue nfQueue
shutdownSignal = make(chan struct{})
experimentalNfqueueBackend bool
)
func init() {
flag.BoolVar(&experimentalNfqueueBackend, "experimental-nfqueue", false, "(deprecated flag; always used)")
}
// nfQueue encapsulates nfQueue providers.
type nfQueue interface {
PacketChannel() <-chan packet.Packet
Destroy()
}
func init() {
v4chains = []string{
"mangle PORTMASTER-INGEST-OUTPUT",
"mangle PORTMASTER-INGEST-INPUT",
"filter PORTMASTER-FILTER",
"nat PORTMASTER-REDIRECT",
}
v4rules = []string{
"mangle PORTMASTER-INGEST-OUTPUT -j CONNMARK --restore-mark",
"mangle PORTMASTER-INGEST-OUTPUT -m mark --mark 0 -j NFQUEUE --queue-num 17040 --queue-bypass",
"mangle PORTMASTER-INGEST-INPUT -j CONNMARK --restore-mark",
"mangle PORTMASTER-INGEST-INPUT -m mark --mark 0 -j NFQUEUE --queue-num 17140 --queue-bypass",
"filter PORTMASTER-FILTER -m mark --mark 0 -j DROP",
"filter PORTMASTER-FILTER -m mark --mark 1700 -j RETURN",
// Accepting ICMP packets with mark 1701 is required for rejecting to work,
// as the rejection ICMP packet will have the same mark. Blocked ICMP
// packets will always result in a drop within the Portmaster.
"filter PORTMASTER-FILTER -m mark --mark 1701 -p icmp -j RETURN",
"filter PORTMASTER-FILTER -m mark --mark 1701 -j REJECT --reject-with icmp-admin-prohibited",
"filter PORTMASTER-FILTER -m mark --mark 1702 -j DROP",
"filter PORTMASTER-FILTER -j CONNMARK --save-mark",
"filter PORTMASTER-FILTER -m mark --mark 1710 -j RETURN",
// Accepting ICMP packets with mark 1711 is required for rejecting to work,
// as the rejection ICMP packet will have the same mark. Blocked ICMP
// packets will always result in a drop within the Portmaster.
"filter PORTMASTER-FILTER -m mark --mark 1711 -p icmp -j RETURN",
"filter PORTMASTER-FILTER -m mark --mark 1711 -j REJECT --reject-with icmp-admin-prohibited",
"filter PORTMASTER-FILTER -m mark --mark 1712 -j DROP",
"filter PORTMASTER-FILTER -m mark --mark 1717 -j RETURN",
"nat PORTMASTER-REDIRECT -m mark --mark 1799 -p udp -j DNAT --to 127.0.0.17:53",
"nat PORTMASTER-REDIRECT -m mark --mark 1717 -p tcp -j DNAT --to 127.0.0.17:717",
"nat PORTMASTER-REDIRECT -m mark --mark 1717 -p udp -j DNAT --to 127.0.0.17:717",
// "nat PORTMASTER-REDIRECT -m mark --mark 1717 ! -p tcp ! -p udp -j DNAT --to 127.0.0.17",
}
v4once = []string{
"mangle OUTPUT -j PORTMASTER-INGEST-OUTPUT",
"mangle INPUT -j PORTMASTER-INGEST-INPUT",
"filter OUTPUT -j PORTMASTER-FILTER",
"filter INPUT -j PORTMASTER-FILTER",
"nat OUTPUT -j PORTMASTER-REDIRECT",
}
v6chains = []string{
"mangle PORTMASTER-INGEST-OUTPUT",
"mangle PORTMASTER-INGEST-INPUT",
"filter PORTMASTER-FILTER",
"nat PORTMASTER-REDIRECT",
}
v6rules = []string{
"mangle PORTMASTER-INGEST-OUTPUT -j CONNMARK --restore-mark",
"mangle PORTMASTER-INGEST-OUTPUT -m mark --mark 0 -j NFQUEUE --queue-num 17060 --queue-bypass",
"mangle PORTMASTER-INGEST-INPUT -j CONNMARK --restore-mark",
"mangle PORTMASTER-INGEST-INPUT -m mark --mark 0 -j NFQUEUE --queue-num 17160 --queue-bypass",
"filter PORTMASTER-FILTER -m mark --mark 0 -j DROP",
"filter PORTMASTER-FILTER -m mark --mark 1700 -j RETURN",
"filter PORTMASTER-FILTER -m mark --mark 1701 -p icmpv6 -j RETURN",
"filter PORTMASTER-FILTER -m mark --mark 1701 -j REJECT --reject-with icmp6-adm-prohibited",
"filter PORTMASTER-FILTER -m mark --mark 1702 -j DROP",
"filter PORTMASTER-FILTER -j CONNMARK --save-mark",
"filter PORTMASTER-FILTER -m mark --mark 1710 -j RETURN",
"filter PORTMASTER-FILTER -m mark --mark 1711 -p icmpv6 -j RETURN",
"filter PORTMASTER-FILTER -m mark --mark 1711 -j REJECT --reject-with icmp6-adm-prohibited",
"filter PORTMASTER-FILTER -m mark --mark 1712 -j DROP",
"filter PORTMASTER-FILTER -m mark --mark 1717 -j RETURN",
"nat PORTMASTER-REDIRECT -m mark --mark 1799 -p udp -j DNAT --to [::1]:53",
"nat PORTMASTER-REDIRECT -m mark --mark 1717 -p tcp -j DNAT --to [::1]:717",
"nat PORTMASTER-REDIRECT -m mark --mark 1717 -p udp -j DNAT --to [::1]:717",
// "nat PORTMASTER-REDIRECT -m mark --mark 1717 ! -p tcp ! -p udp -j DNAT --to [::1]",
}
v6once = []string{
"mangle OUTPUT -j PORTMASTER-INGEST-OUTPUT",
"mangle INPUT -j PORTMASTER-INGEST-INPUT",
"filter OUTPUT -j PORTMASTER-FILTER",
"filter INPUT -j PORTMASTER-FILTER",
"nat OUTPUT -j PORTMASTER-REDIRECT",
}
// Reverse because we'd like to insert in a loop
_ = sort.Reverse(sort.StringSlice(v4once)) // silence vet (sort is used just like in the docs)
_ = sort.Reverse(sort.StringSlice(v6once)) // silence vet (sort is used just like in the docs)
}
func activateNfqueueFirewall() error {
if err := activateIPTables(iptables.ProtocolIPv4, v4rules, v4once, v4chains); err != nil {
return err
}
if netenv.IPv6Enabled() {
if err := activateIPTables(iptables.ProtocolIPv6, v6rules, v6once, v6chains); err != nil {
return err
}
}
if err := nfq.InitNFCT(); err != nil {
return err
}
_ = nfq.DeleteAllMarkedConnection()
return nil
}
// DeactivateNfqueueFirewall drops portmaster related IP tables rules.
// Any errors encountered accumulated into a *multierror.Error.
func DeactivateNfqueueFirewall() error {
// IPv4
var result *multierror.Error
if err := deactivateIPTables(iptables.ProtocolIPv4, v4once, v4chains); err != nil {
result = multierror.Append(result, err)
}
// IPv6
if netenv.IPv6Enabled() {
if err := deactivateIPTables(iptables.ProtocolIPv6, v6once, v6chains); err != nil {
result = multierror.Append(result, err)
}
}
_ = nfq.DeleteAllMarkedConnection()
nfq.TeardownNFCT()
return result.ErrorOrNil()
}
func activateIPTables(protocol iptables.Protocol, rules, once, chains []string) error {
tbls, err := iptables.NewWithProtocol(protocol)
if err != nil {
return err
}
for _, chain := range chains {
splittedRule := strings.Split(chain, " ")
if err = tbls.ClearChain(splittedRule[0], splittedRule[1]); err != nil {
return err
}
}
for _, rule := range rules {
splittedRule := strings.Split(rule, " ")
if err = tbls.Append(splittedRule[0], splittedRule[1], splittedRule[2:]...); err != nil {
return err
}
}
for _, rule := range once {
splittedRule := strings.Split(rule, " ")
ok, err := tbls.Exists(splittedRule[0], splittedRule[1], splittedRule[2:]...)
if err != nil {
return err
}
if !ok {
if err = tbls.Insert(splittedRule[0], splittedRule[1], 1, splittedRule[2:]...); err != nil {
return err
}
}
}
return nil
}
func deactivateIPTables(protocol iptables.Protocol, rules, chains []string) error {
tbls, err := iptables.NewWithProtocol(protocol)
if err != nil {
return err
}
var multierr *multierror.Error
for _, rule := range rules {
splittedRule := strings.Split(rule, " ")
ok, err := tbls.Exists(splittedRule[0], splittedRule[1], splittedRule[2:]...)
if err != nil {
multierr = multierror.Append(multierr, err)
}
if ok {
if err = tbls.Delete(splittedRule[0], splittedRule[1], splittedRule[2:]...); err != nil {
multierr = multierror.Append(multierr, err)
}
}
}
for _, chain := range chains {
splittedRule := strings.Split(chain, " ")
if err = tbls.ClearChain(splittedRule[0], splittedRule[1]); err != nil {
multierr = multierror.Append(multierr, err)
}
if err = tbls.DeleteChain(splittedRule[0], splittedRule[1]); err != nil {
multierr = multierror.Append(multierr, err)
}
}
return multierr.ErrorOrNil()
}
// StartNfqueueInterception starts the nfqueue interception.
func StartNfqueueInterception(packets chan<- packet.Packet) (err error) {
// @deprecated, remove in v1
if experimentalNfqueueBackend {
log.Warningf("[DEPRECATED] --experimental-nfqueue has been deprecated as the backend is now used by default")
log.Warningf("[DEPRECATED] please remove the flag from your configuration!")
}
err = activateNfqueueFirewall()
if err != nil {
_ = StopNfqueueInterception()
return fmt.Errorf("could not initialize nfqueue: %w", err)
}
out4Queue, err = nfq.New(17040, false)
if err != nil {
_ = StopNfqueueInterception()
return fmt.Errorf("nfqueue(IPv4, out): %w", err)
}
in4Queue, err = nfq.New(17140, false)
if err != nil {
_ = StopNfqueueInterception()
return fmt.Errorf("nfqueue(IPv4, in): %w", err)
}
if netenv.IPv6Enabled() {
out6Queue, err = nfq.New(17060, true)
if err != nil {
_ = StopNfqueueInterception()
return fmt.Errorf("nfqueue(IPv6, out): %w", err)
}
in6Queue, err = nfq.New(17160, true)
if err != nil {
_ = StopNfqueueInterception()
return fmt.Errorf("nfqueue(IPv6, in): %w", err)
}
} else {
log.Warningf("interception: no IPv6 stack detected, disabling IPv6 network integration")
out6Queue = &disabledNfQueue{}
in6Queue = &disabledNfQueue{}
}
module.StartServiceWorker("nfqueue packet handler", 0, func(_ context.Context) error {
return handleInterception(packets)
})
return nil
}
// StopNfqueueInterception stops the nfqueue interception.
func StopNfqueueInterception() error {
defer close(shutdownSignal)
if out4Queue != nil {
out4Queue.Destroy()
}
if in4Queue != nil {
in4Queue.Destroy()
}
if out6Queue != nil {
out6Queue.Destroy()
}
if in6Queue != nil {
in6Queue.Destroy()
}
err := DeactivateNfqueueFirewall()
if err != nil {
return fmt.Errorf("interception: error while deactivating nfqueue: %w", err)
}
return nil
}
func handleInterception(packets chan<- packet.Packet) error {
for {
var pkt packet.Packet
select {
case <-shutdownSignal:
return nil
case pkt = <-out4Queue.PacketChannel():
pkt.SetOutbound()
case pkt = <-in4Queue.PacketChannel():
pkt.SetInbound()
case pkt = <-out6Queue.PacketChannel():
pkt.SetOutbound()
case pkt = <-in6Queue.PacketChannel():
pkt.SetInbound()
}
select {
case packets <- pkt:
case <-shutdownSignal:
return nil
}
}
}
type disabledNfQueue struct{}
func (dnfq *disabledNfQueue) PacketChannel() <-chan packet.Packet {
return nil
}
func (dnfq *disabledNfQueue) Destroy() {}