From 6622df9d38967951320367ffe2b117f6c3efce3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20I=C3=B1iguez=20Goia?= Date: Sat, 19 Oct 2024 10:51:40 +0200 Subject: [PATCH] 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. --- daemon/default-config.json | 3 ++- daemon/firewall/iptables/iptables.go | 6 ++++-- daemon/firewall/iptables/rules.go | 19 +++++++++++++++++-- daemon/firewall/nftables/nftables.go | 8 +++++--- daemon/firewall/nftables/rules.go | 6 +++--- daemon/firewall/nftables/utils.go | 9 +++++++++ daemon/firewall/rules.go | 10 +++++----- daemon/main.go | 1 + daemon/ui/config/config.go | 2 +- daemon/ui/config_utils.go | 4 +++- 10 files changed, 50 insertions(+), 18 deletions(-) diff --git a/daemon/default-config.json b/daemon/default-config.json index c79cea6b54..143ee9cf09 100644 --- a/daemon/default-config.json +++ b/daemon/default-config.json @@ -17,7 +17,8 @@ "Firewall": "nftables", "FwOptions": { "ConfigPath": "/etc/opensnitchd/system-fw.json", - "MonitorInterval": "15s" + "MonitorInterval": "15s", + "BypassQueue": true }, "Rules": { "Path": "/etc/opensnitchd/rules/", diff --git a/daemon/firewall/iptables/iptables.go b/daemon/firewall/iptables/iptables.go index 5e64ab59bf..7e4a827902 100644 --- a/daemon/firewall/iptables/iptables.go +++ b/daemon/firewall/iptables/iptables.go @@ -59,6 +59,7 @@ type Iptables struct { bin string bin6 string chains SystemChains + bypassQueue bool common.Common config.Config @@ -71,7 +72,7 @@ func Fw() (*Iptables, error) { return nil, err } - reRulesQuery, _ := regexp.Compile(`NFQUEUE.*ctstate NEW,RELATED.*NFQUEUE num.*bypass`) + reRulesQuery, _ := regexp.Compile(`NFQUEUE.*ctstate NEW,RELATED.*NFQUEUE num.*`) reSystemRulesQuery, _ := regexp.Compile(SystemRulePrefix + ".*") ipt := &Iptables{ @@ -93,10 +94,11 @@ func (ipt *Iptables) Name() string { // Init inserts the firewall rules and starts monitoring for firewall // changes. -func (ipt *Iptables) Init(qNum uint16, configPath, monitorInterval string) { +func (ipt *Iptables) Init(qNum uint16, configPath, monitorInterval string, bypassQueue bool) { if ipt.IsRunning() { return } + ipt.bypassQueue = bypassQueue ipt.SetQueueNum(qNum) ipt.SetRulesCheckerInterval(monitorInterval) ipt.ErrChan = make(chan string, 100) diff --git a/daemon/firewall/iptables/rules.go b/daemon/firewall/iptables/rules.go index 6eed8422ca..2cc77d2188 100644 --- a/daemon/firewall/iptables/rules.go +++ b/daemon/firewall/iptables/rules.go @@ -8,12 +8,27 @@ import ( "github.com/vishvananda/netlink" ) +func (ipt *Iptables) getBypassQueue() string { + if !ipt.bypassQueue { + return "" + } + + return "--queue-bypass" +} + // RunRule inserts or deletes a firewall rule. func (ipt *Iptables) RunRule(action Action, enable bool, logError bool, rule []string) (err4, err6 error) { if enable == false { action = "-D" } + // If the last argument of the rule is "", the iptables command fails. + // So if the user selects "QueueBypass: false", delete the last token of the rule, + // which will be "". + args := len(rule) + if rule[args-1] == "" { + rule = append(rule[:args-1], rule[args:]...) + } rule = append([]string{string(action)}, rule...) ipt.Lock() @@ -49,7 +64,7 @@ func (ipt *Iptables) QueueDNSResponses(enable bool, logError bool) (err4, err6 e "--sport", "53", "-j", "NFQUEUE", "--queue-num", fmt.Sprintf("%d", ipt.QueueNum), - "--queue-bypass", + ipt.getBypassQueue(), }) } @@ -64,7 +79,7 @@ func (ipt *Iptables) QueueConnections(enable bool, logError bool) (error, error) "--ctstate", "NEW,RELATED", "-j", "NFQUEUE", "--queue-num", fmt.Sprintf("%d", ipt.QueueNum), - "--queue-bypass", + ipt.getBypassQueue(), }) if enable { // flush conntrack as soon as netfilter rule is set. This ensures that already-established diff --git a/daemon/firewall/nftables/nftables.go b/daemon/firewall/nftables/nftables.go index c0277b18f1..cd9316fe95 100644 --- a/daemon/firewall/nftables/nftables.go +++ b/daemon/firewall/nftables/nftables.go @@ -41,8 +41,9 @@ var ( // Nft holds the fields of our nftables firewall type Nft struct { - Conn *nftables.Conn - chains iptables.SystemChains + Conn *nftables.Conn + chains iptables.SystemChains + bypassQueue bool common.Common config.Config @@ -71,10 +72,11 @@ func (n *Nft) Name() string { // Init inserts the firewall rules and starts monitoring for firewall // changes. -func (n *Nft) Init(qNum uint16, configPath, monitorInterval string) { +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() diff --git a/daemon/firewall/nftables/rules.go b/daemon/firewall/nftables/rules.go index 1595a7d91f..791366ee66 100644 --- a/daemon/firewall/nftables/rules.go +++ b/daemon/firewall/nftables/rules.go @@ -57,7 +57,7 @@ func (n *Nft) QueueDNSResponses(enable, logError bool) (error, error) { }, &expr.Queue{ Num: n.QueueNum, - Flag: expr.QueueFlagBypass, + Flag: n.getBypassFlag(), }, }, // rule key, to allow get it later by key @@ -112,7 +112,7 @@ func (n *Nft) QueueConnections(enable, logError bool) (error, error) { &expr.Cmp{Op: expr.CmpOpNeq, Register: 1, Data: []byte{0, 0, 0, 0}}, &expr.Queue{ Num: n.QueueNum, - Flag: expr.QueueFlagBypass, + Flag: n.getBypassFlag(), }, }, // rule key, to allow get it later by key @@ -163,7 +163,7 @@ func (n *Nft) QueueConnections(enable, logError bool) (error, error) { }, &expr.Queue{ Num: n.QueueNum, - Flag: expr.QueueFlagBypass, + Flag: n.getBypassFlag(), }, }, // rule key, to allow get it later by key diff --git a/daemon/firewall/nftables/utils.go b/daemon/firewall/nftables/utils.go index dbb3d19efc..50ad50cf52 100644 --- a/daemon/firewall/nftables/utils.go +++ b/daemon/firewall/nftables/utils.go @@ -6,8 +6,17 @@ import ( "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs" "github.com/evilsocket/opensnitch/daemon/log" "github.com/google/nftables" + "github.com/google/nftables/expr" ) +func (n *Nft) getBypassFlag() expr.QueueFlag { + if n.bypassQueue { + return expr.QueueFlagBypass + } + + return 0x0 +} + func GetFamilyCode(family string) nftables.TableFamily { famCode := nftables.TableFamilyINet switch family { diff --git a/daemon/firewall/rules.go b/daemon/firewall/rules.go index 6eaacf2277..80dc222eb9 100644 --- a/daemon/firewall/rules.go +++ b/daemon/firewall/rules.go @@ -13,7 +13,7 @@ import ( // Firewall is the interface that all firewalls (iptables, nftables) must implement. type Firewall interface { - Init(uint16, string, string) + Init(uint16, string, string, bool) Stop() Name() string IsRunning() bool @@ -46,7 +46,7 @@ var ( // We'll try to use the firewall configured in the configuration (iptables/nftables). // If iptables is not installed, we can add nftables rules directly to the kernel, // without relying on any binaries. -func Init(fwType, configPath, monitorInterval string, qNum uint16) (err error) { +func Init(fwType, configPath, monitorInterval string, bypassQueue bool, qNum uint16) (err error) { if fwType == iptables.Name { fw, err = iptables.Fw() if err != nil { @@ -72,7 +72,7 @@ func Init(fwType, configPath, monitorInterval string, qNum uint16) (err error) { configPath = config.DefaultConfigFile } fw.Stop() - fw.Init(qNum, configPath, monitorInterval) + fw.Init(qNum, configPath, monitorInterval, bypassQueue) queueNum = qNum log.Info("Using %s firewall", fw.Name()) @@ -104,9 +104,9 @@ func CleanRules(logErrors bool) { } // Reload stops current firewall and initializes a new one. -func Reload(fwtype, configPath, monitorInterval string, queueNum uint16) (err error) { +func Reload(fwtype, configPath, monitorInterval string, bypassQueue bool, queueNum uint16) (err error) { Stop() - err = Init(fwtype, configPath, monitorInterval, queueNum) + err = Init(fwtype, configPath, monitorInterval, bypassQueue, queueNum) return } diff --git a/daemon/main.go b/daemon/main.go index f4fd449ce0..2b29676ece 100644 --- a/daemon/main.go +++ b/daemon/main.go @@ -157,6 +157,7 @@ func overwriteFw(cfg *config.Config, qNum uint16, fwCfg string) { cfg.Firewall, fwCfg, cfg.FwOptions.MonitorInterval, + cfg.FwOptions.QueueBypass, qNum, ) // TODO: Close() closes the daemon if closing the queue timeouts diff --git a/daemon/ui/config/config.go b/daemon/ui/config/config.go index 673132b75d..adce9ce065 100644 --- a/daemon/ui/config/config.go +++ b/daemon/ui/config/config.go @@ -57,9 +57,9 @@ type ( FwOptions struct { Firewall string `json:"Firewall"` ConfigPath string `json:"ConfigPath"` - BypassQueue string `json:"BypassQueue"` MonitorInterval string `json:"MonitorInterval"` QueueNum uint16 `json:"QueueNum"` + QueueBypass bool `json:"QueueBypass"` } // InternalOptions struct diff --git a/daemon/ui/config_utils.go b/daemon/ui/config_utils.go index 81ea569f53..d59bc6c85f 100644 --- a/daemon/ui/config_utils.go +++ b/daemon/ui/config_utils.go @@ -204,7 +204,8 @@ func (c *Client) reloadConfiguration(reload bool, newConfig config.Config) *moni if c.GetFirewallType() != newConfig.Firewall || newConfig.FwOptions.ConfigPath != c.config.FwOptions.ConfigPath || newConfig.FwOptions.QueueNum != c.config.FwOptions.QueueNum || - newConfig.FwOptions.MonitorInterval != c.config.FwOptions.MonitorInterval { + newConfig.FwOptions.MonitorInterval != c.config.FwOptions.MonitorInterval || + newConfig.FwOptions.QueueBypass != c.config.FwOptions.QueueBypass { log.Debug("[config] reloading config.firewall") reloadFw = true @@ -212,6 +213,7 @@ func (c *Client) reloadConfiguration(reload bool, newConfig config.Config) *moni newConfig.Firewall, newConfig.FwOptions.ConfigPath, newConfig.FwOptions.MonitorInterval, + newConfig.FwOptions.QueueBypass, newConfig.FwOptions.QueueNum, ) } else {