mirror of
https://github.com/safing/portmaster
synced 2025-09-01 18:19:12 +00:00
317 lines
8.2 KiB
Go
317 lines
8.2 KiB
Go
package interception
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/coreos/go-iptables/iptables"
|
|
"github.com/hashicorp/go-multierror"
|
|
|
|
"github.com/safing/portbase/log"
|
|
"github.com/safing/portmaster/firewall/interception/nfqexp"
|
|
"github.com/safing/portmaster/firewall/interception/nfqueue"
|
|
"github.com/safing/portmaster/network/packet"
|
|
)
|
|
|
|
// iptables -A OUTPUT -p icmp -j", "NFQUEUE", "--queue-num", "1", "--queue-bypass
|
|
|
|
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, "use experimental nfqueue packet")
|
|
}
|
|
|
|
// nfQueueFactoryFunc creates a new nfQueue with qid as the queue number.
|
|
type nfQueueFactoryFunc func(qid uint16, v6 bool) (nfQueue, error)
|
|
|
|
// nfQueue encapsulates nfQueue providers
|
|
type nfQueue interface {
|
|
PacketChannel() <-chan packet.Packet
|
|
Destroy()
|
|
}
|
|
|
|
func init() {
|
|
|
|
v4chains = []string{
|
|
"mangle C170",
|
|
"mangle C171",
|
|
"filter C17",
|
|
}
|
|
|
|
v4rules = []string{
|
|
"mangle C170 -j CONNMARK --restore-mark",
|
|
"mangle C170 -m mark --mark 0 -j NFQUEUE --queue-num 17040 --queue-bypass",
|
|
|
|
"mangle C171 -j CONNMARK --restore-mark",
|
|
"mangle C171 -m mark --mark 0 -j NFQUEUE --queue-num 17140 --queue-bypass",
|
|
|
|
"filter C17 -m mark --mark 0 -j DROP",
|
|
"filter C17 -m mark --mark 1700 -j RETURN",
|
|
"filter C17 -m mark --mark 1701 -j REJECT --reject-with icmp-host-prohibited",
|
|
"filter C17 -m mark --mark 1702 -j DROP",
|
|
"filter C17 -j CONNMARK --save-mark",
|
|
"filter C17 -m mark --mark 1710 -j RETURN",
|
|
"filter C17 -m mark --mark 1711 -j REJECT --reject-with icmp-host-prohibited",
|
|
"filter C17 -m mark --mark 1712 -j DROP",
|
|
"filter C17 -m mark --mark 1717 -j RETURN",
|
|
}
|
|
|
|
v4once = []string{
|
|
"mangle OUTPUT -j C170",
|
|
"mangle INPUT -j C171",
|
|
"filter OUTPUT -j C17",
|
|
"filter INPUT -j C17",
|
|
"nat OUTPUT -m mark --mark 1799 -p udp -j DNAT --to 127.0.0.1:53",
|
|
"nat OUTPUT -m mark --mark 1717 -p tcp -j DNAT --to 127.0.0.17:717",
|
|
"nat OUTPUT -m mark --mark 1717 -p udp -j DNAT --to 127.0.0.17:717",
|
|
// "nat OUTPUT -m mark --mark 1717 ! -p tcp ! -p udp -j DNAT --to 127.0.0.17",
|
|
}
|
|
|
|
v6chains = []string{
|
|
"mangle C170",
|
|
"mangle C171",
|
|
"filter C17",
|
|
}
|
|
|
|
v6rules = []string{
|
|
"mangle C170 -j CONNMARK --restore-mark",
|
|
"mangle C170 -m mark --mark 0 -j NFQUEUE --queue-num 17060 --queue-bypass",
|
|
|
|
"mangle C171 -j CONNMARK --restore-mark",
|
|
"mangle C171 -m mark --mark 0 -j NFQUEUE --queue-num 17160 --queue-bypass",
|
|
|
|
"filter C17 -m mark --mark 0 -j DROP",
|
|
"filter C17 -m mark --mark 1700 -j RETURN",
|
|
"filter C17 -m mark --mark 1701 -j REJECT --reject-with icmp6-adm-prohibited",
|
|
"filter C17 -m mark --mark 1702 -j DROP",
|
|
"filter C17 -j CONNMARK --save-mark",
|
|
"filter C17 -m mark --mark 1710 -j RETURN",
|
|
"filter C17 -m mark --mark 1711 -j REJECT --reject-with icmp6-adm-prohibited",
|
|
"filter C17 -m mark --mark 1712 -j DROP",
|
|
"filter C17 -m mark --mark 1717 -j RETURN",
|
|
}
|
|
|
|
v6once = []string{
|
|
"mangle OUTPUT -j C170",
|
|
"mangle INPUT -j C171",
|
|
"filter OUTPUT -j C17",
|
|
"filter INPUT -j C17",
|
|
"nat OUTPUT -m mark --mark 1799 -p udp -j DNAT --to [::1]:53",
|
|
"nat OUTPUT -m mark --mark 1717 -p tcp -j DNAT --to [fd17::17]:717",
|
|
"nat OUTPUT -m mark --mark 1717 -p udp -j DNAT --to [fd17::17]:717",
|
|
// "nat OUTPUT -m mark --mark 1717 ! -p tcp ! -p udp -j DNAT --to [fd17::17]",
|
|
}
|
|
|
|
// 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 err := activateIPTables(iptables.ProtocolIPv6, v6rules, v6once, v6chains); err != nil {
|
|
return err
|
|
}
|
|
|
|
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 err := deactivateIPTables(iptables.ProtocolIPv6, v6once, v6chains); err != nil {
|
|
result = multierror.Append(result, err)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// StartNfqueueInterception starts the nfqueue interception.
|
|
func StartNfqueueInterception() (err error) {
|
|
var nfQueueFactory nfQueueFactoryFunc = func(qid uint16, v6 bool) (nfQueue, error) {
|
|
return nfqueue.NewNFQueue(qid)
|
|
}
|
|
|
|
if experimentalNfqueueBackend {
|
|
log.Infof("nfqueue: using experimental nfqueue backend")
|
|
nfQueueFactory = func(qid uint16, v6 bool) (nfQueue, error) {
|
|
return nfqexp.New(qid, v6)
|
|
}
|
|
}
|
|
|
|
err = activateNfqueueFirewall()
|
|
if err != nil {
|
|
_ = Stop()
|
|
return fmt.Errorf("could not initialize nfqueue: %s", err)
|
|
}
|
|
|
|
out4Queue, err = nfQueueFactory(17040, false)
|
|
if err != nil {
|
|
_ = Stop()
|
|
return fmt.Errorf("nfqueue(IPv4, out): %w", err)
|
|
}
|
|
in4Queue, err = nfQueueFactory(17140, false)
|
|
if err != nil {
|
|
_ = Stop()
|
|
return fmt.Errorf("nfqueue(IPv4, in): %w", err)
|
|
}
|
|
out6Queue, err = nfQueueFactory(17060, true)
|
|
if err != nil {
|
|
_ = Stop()
|
|
return fmt.Errorf("nfqueue(IPv6, out): %w", err)
|
|
}
|
|
in6Queue, err = nfQueueFactory(17160, true)
|
|
if err != nil {
|
|
_ = Stop()
|
|
return fmt.Errorf("nfqueue(IPv6, in): %w", err)
|
|
}
|
|
|
|
go handleInterception()
|
|
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: %s", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func handleInterception() {
|
|
for {
|
|
select {
|
|
case <-shutdownSignal:
|
|
return
|
|
case pkt := <-out4Queue.PacketChannel():
|
|
pkt.SetOutbound()
|
|
Packets <- pkt
|
|
case pkt := <-in4Queue.PacketChannel():
|
|
pkt.SetInbound()
|
|
Packets <- pkt
|
|
case pkt := <-out6Queue.PacketChannel():
|
|
pkt.SetOutbound()
|
|
Packets <- pkt
|
|
case pkt := <-in6Queue.PacketChannel():
|
|
pkt.SetInbound()
|
|
Packets <- pkt
|
|
}
|
|
}
|
|
}
|