opensnitch/daemon/firewall/nftables/nftables.go
Gustavo Iñiguez Goia 6622df9d38
allow to configure nfqueue bypass flag
Nfqueue bypass option skips the enqueue of packets to userspace
if no application is listening to the queue.
https://wiki.nftables.org/wiki-nftables/index.php/Queueing_to_userspace

If this flag is not specified, and for example the daemon dies
unexpectedly, all the outbound traffic will be blocked.

Up until now we've been using this flag by default not to block network
traffic if the daemon dies or is killed for some reason. But some users
want to use precisely this behaviour (#884, #1183, #1201).

Now you can configure it, to block connections if the daemon
unexpectedly dies.

The option is on by default in the configuration (QueueBypass: true).
If this item is not present in the daemon config file, then it'll be
false.
2024-10-19 10:51:40 +02:00

199 lines
5.4 KiB
Go

package nftables
import (
"bytes"
"encoding/json"
"strings"
"sync"
"github.com/evilsocket/opensnitch/daemon/firewall/common"
"github.com/evilsocket/opensnitch/daemon/firewall/config"
"github.com/evilsocket/opensnitch/daemon/firewall/iptables"
"github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
"github.com/evilsocket/opensnitch/daemon/log"
"github.com/evilsocket/opensnitch/daemon/ui/protocol"
"github.com/golang/protobuf/jsonpb"
"github.com/google/nftables"
)
// Action is the modifier we apply to a rule.
type Action string
// Actions we apply to the firewall.
const (
fwKey = "opensnitch-key"
InterceptionRuleKey = fwKey + "-interception"
SystemRuleKey = fwKey + "-system"
Name = "nftables"
)
var (
filterTable = &nftables.Table{
Family: nftables.TableFamilyINet,
Name: exprs.NFT_CHAIN_FILTER,
}
mangleTable = &nftables.Table{
Family: nftables.TableFamilyINet,
Name: exprs.NFT_CHAIN_FILTER,
}
)
// Nft holds the fields of our nftables firewall
type Nft struct {
Conn *nftables.Conn
chains iptables.SystemChains
bypassQueue bool
common.Common
config.Config
sync.Mutex
}
// NewNft creates a new nftables object
func NewNft() *nftables.Conn {
return &nftables.Conn{}
}
// Fw initializes a new nftables object
func Fw() (*Nft, error) {
n := &Nft{
chains: iptables.SystemChains{
Rules: make(map[string]*iptables.SystemRule),
},
}
return n, nil
}
// Name returns the name of the firewall
func (n *Nft) Name() string {
return Name
}
// Init inserts the firewall rules and starts monitoring for firewall
// changes.
func (n *Nft) Init(qNum uint16, configPath, monitorInterval string, bypassQueue bool) {
if n.IsRunning() {
return
}
n.bypassQueue = bypassQueue
n.Conn = NewNft()
n.ErrChan = make(chan string, 100)
InitMapsStore()
n.SetQueueNum(qNum)
n.SetRulesCheckerInterval(monitorInterval)
// In order to clean up any existing firewall rule before start,
// we need to load the fw configuration first to know what rules
// were configured.
n.NewSystemFwConfig(configPath, n.PreloadConfCallback, n.ReloadConfCallback)
n.LoadDiskConfiguration(!common.ReloadConf)
// start from a clean state
// The daemon may have exited unexpectedly, leaving residual fw rules, so we
// need to clean them up to avoid duplicated rules.
n.DelInterceptionRules()
n.AddSystemRules(!common.ReloadRules, common.BackupChains)
n.EnableInterception()
n.Running = true
}
// Stop deletes the firewall rules, allowing network traffic.
func (n *Nft) Stop() {
n.ErrChan = make(chan string, 100)
if n.IsRunning() == false {
return
}
n.StopConfigWatcher()
n.StopCheckingRules()
n.CleanRules(log.GetLogLevel() == log.DEBUG)
n.Lock()
n.Running = false
n.Unlock()
}
// EnableInterception adds firewall rules to intercept connections
func (n *Nft) EnableInterception() {
if err := n.AddInterceptionTables(); err != nil {
log.Error("Error while adding interception tables: %s", err)
return
}
if err := n.AddInterceptionChains(); err != nil {
log.Error("Error while adding interception chains: %s", err)
return
}
if err, _ := n.QueueDNSResponses(common.EnableRule, common.EnableRule); err != nil {
log.Error("Error while running DNS nftables rule: %s", err)
}
if err, _ := n.QueueConnections(common.EnableRule, common.EnableRule); err != nil {
log.Error("Error while running conntrack nftables rule: %s", err)
}
// start monitoring firewall rules to intercept network traffic.
n.NewRulesChecker(n.AreRulesLoaded, n.ReloadRulesCallback)
}
// DisableInterception removes firewall rules to intercept outbound connections.
func (n *Nft) DisableInterception(logErrors bool) {
n.StopCheckingRules()
n.DelInterceptionRules()
}
// CleanRules deletes the rules we added.
func (n *Nft) CleanRules(logErrors bool) {
n.DisableInterception(logErrors)
n.DeleteSystemRules(common.ForcedDelRules, common.RestoreChains, logErrors)
}
// Commit applies the queued changes, creating new objects (tables, chains, etc).
// You add rules, chains or tables, and after calling to Flush() they're added to the system.
// NOTE: it's very important not to call Flush() without queued tasks.
func (n *Nft) Commit() bool {
if err := n.Conn.Flush(); err != nil {
log.Warning("%s error applying changes: %s", logTag, err)
return false
}
return true
}
// Serialize converts the configuration from json to protobuf
func (n *Nft) Serialize() (*protocol.SysFirewall, error) {
sysfw := &protocol.SysFirewall{}
jun := jsonpb.Unmarshaler{
AllowUnknownFields: true,
}
rawConfig, err := json.Marshal(&n.SysConfig)
if err != nil {
log.Error("nftables.Serialize() struct to string error: %s", err)
return nil, err
}
// string to proto
if err := jun.Unmarshal(strings.NewReader(string(rawConfig)), sysfw); err != nil {
log.Error("nftables.Serialize() string to protobuf error: %s", err)
return nil, err
}
return sysfw, nil
}
// Deserialize converts a protocolbuffer structure to byte array.
func (n *Nft) Deserialize(sysfw *protocol.SysFirewall) ([]byte, error) {
jun := jsonpb.Marshaler{
OrigName: true,
EmitDefaults: true,
Indent: " ",
}
// NOTE: '<' and '>' characters are encoded to unicode (\u003c).
// This has no effect on adding rules to nftables.
// Users can still write "<" if they want to, rules are added ok.
var b bytes.Buffer
if err := jun.Marshal(&b, sysfw); err != nil {
log.Error("nfables.Deserialize() error 2: %s", err)
return nil, err
}
return b.Bytes(), nil
}