From 797b85b03ac169535c634205a8f4a47ca4c7d68b Mon Sep 17 00:00:00 2001 From: Vladimir Avtsenov Date: Fri, 6 Sep 2024 16:50:56 +0300 Subject: [PATCH] catch netfilter.d event --- README.md | 2 +- kvas2.go | 50 +++++++++ netfilter-helper/interface-to-ipset.go | 147 ++++++++++++++----------- netfilter-helper/port-remap.go | 35 ++++-- opt/etc/ndm/netfilter.d/100-kvas2 | 6 + 5 files changed, 163 insertions(+), 77 deletions(-) create mode 100755 opt/etc/ndm/netfilter.d/100-kvas2 diff --git a/README.md b/README.md index a5b68fe..bc00d2f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Realized features: - [X] IP integration - [X] IPTables rules to IPSet - [X] Catch interface up/down -- [ ] Catch `netfilter.d` event +- [X] Catch `netfilter.d` event - [ ] Rule composer (CRUD) - [ ] GORM integration - [X] Listing of interfaces diff --git a/kvas2.go b/kvas2.go index 824c9d7..ce10901 100644 --- a/kvas2.go +++ b/kvas2.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net" + "strings" "sync" "time" @@ -103,6 +104,55 @@ func (a *App) Listen(ctx context.Context) []error { done := make(chan struct{}) netlink.LinkSubscribe(link, done) + exitListenerLoop := false + listener, err := net.Listen("unix", "/opt/var/run/kvas2-go.sock") + if err != nil { + handleError(fmt.Errorf("error while serve UNIX socket: %v", err)) + } + defer listener.Close() + + go func() { + for { + if exitListenerLoop { + break + } + + conn, err := listener.Accept() + if err != nil { + log.Error().Err(err).Msg("error while listening unix socket") + } + + go func(conn net.Conn) { + defer conn.Close() + + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + return + } + + args := strings.Split(string(buf[:n]), ":") + if len(args) == 3 && args[0] == "netfilter.d" { + log.Debug().Str("table", args[2]).Msg("netfilter.d event") + if a.dnsOverrider.Enabled { + err := a.dnsOverrider.PutIPTable(args[2]) + if err != nil { + log.Error().Err(err).Msg("error while fixing iptables after netfilter.d") + } + } + for _, group := range a.Groups { + if group.ifaceToIPSet.Enabled { + err := group.ifaceToIPSet.PutIPTable(args[2]) + if err != nil { + log.Error().Err(err).Msg("error while fixing iptables after netfilter.d") + } + } + } + } + }(conn) + } + }() + Loop: for { select { diff --git a/netfilter-helper/interface-to-ipset.go b/netfilter-helper/interface-to-ipset.go index 03ba3cc..d1284ce 100644 --- a/netfilter-helper/interface-to-ipset.go +++ b/netfilter-helper/interface-to-ipset.go @@ -25,6 +25,85 @@ type IfaceToIPSet struct { ipRoute *netlink.Route } +func (r *IfaceToIPSet) PutIPTable(table string) error { + var err error + + if !r.SoftwareMode { + if table == "all" || table == "mangle" { + err = r.IPTables.ClearChain("mangle", r.ChainName) + if err != nil { + return fmt.Errorf("failed to clear chain: %w", err) + } + + for _, iptablesArgs := range [][]string{ + // Source: https://github.com/qzeleza/kvas/blob/3fdbbd1ace7b57b11bf88d8db3882d94a1d6e01c/opt/etc/ndm/ndm#L194-L206 + {"-m", "set", "!", "--match-set", r.IPSetName, "dst", "-j", "RETURN"}, + {"-j", "CONNMARK", "--restore-mark"}, + {"-m", "mark", "--mark", strconv.Itoa(int(r.mark)), "-j", "RETURN"}, + // This command not working + // {"--syn", "-j", "MARK", "--set-mark", strconv.Itoa(int(mark))}, + {"-m", "conntrack", "--ctstate", "NEW", "-j", "MARK", "--set-mark", strconv.Itoa(int(r.mark))}, + {"-j", "CONNMARK", "--save-mark"}, + } { + err = r.IPTables.AppendUnique("mangle", r.ChainName, iptablesArgs...) + if err != nil { + return fmt.Errorf("failed to append rule: %w", err) + } + } + + err = r.IPTables.AppendUnique("mangle", "PREROUTING", "-m", "set", "--match-set", r.IPSetName, "dst", "-j", r.ChainName) + if err != nil { + return fmt.Errorf("failed to append rule to PREROUTING: %w", err) + } + + err = r.IPTables.AppendUnique("mangle", "OUTPUT", "-m", "set", "--match-set", r.IPSetName, "dst", "-j", r.ChainName) + if err != nil { + return fmt.Errorf("failed to append rule to OUTPUT: %w", err) + } + } + } else { + if table == "all" || table == "mangle" { + preroutingChainName := fmt.Sprintf("%s_PREROUTING", r.ChainName) + + err = r.IPTables.ClearChain("mangle", preroutingChainName) + if err != nil { + return fmt.Errorf("failed to clear chain: %w", err) + } + + err = r.IPTables.AppendUnique("mangle", preroutingChainName, "-m", "set", "--match-set", r.IPSetName, "dst", "-j", "MARK", "--set-mark", strconv.Itoa(int(r.mark))) + if err != nil { + return fmt.Errorf("failed to create rule: %w", err) + } + + err = r.IPTables.AppendUnique("mangle", "PREROUTING", "-j", preroutingChainName) + if err != nil { + return fmt.Errorf("failed to append rule to PREROUTING: %w", err) + } + } + } + + if table == "all" || table == "nat" { + postroutingChainName := fmt.Sprintf("%s_POSTROUTING", r.ChainName) + + err = r.IPTables.ClearChain("nat", postroutingChainName) + if err != nil { + return fmt.Errorf("failed to clear chain: %w", err) + } + + err = r.IPTables.AppendUnique("nat", postroutingChainName, "-o", r.IfaceName, "-j", "MASQUERADE") + if err != nil { + return fmt.Errorf("failed to create rule: %w", err) + } + + err = r.IPTables.AppendUnique("nat", "POSTROUTING", "-j", postroutingChainName) + if err != nil { + return fmt.Errorf("failed to append rule to POSTROUTING: %w", err) + } + } + + return nil +} + func (r *IfaceToIPSet) IfaceHandle() error { // Find interface iface, err := netlink.LinkByName(r.IfaceName) @@ -98,71 +177,9 @@ func (r *IfaceToIPSet) ForceEnable() error { } // IPTables rules - if !r.SoftwareMode { - err = r.IPTables.ClearChain("mangle", r.ChainName) - if err != nil { - return fmt.Errorf("failed to clear chain: %w", err) - } - - for _, iptablesArgs := range [][]string{ - // Source: https://github.com/qzeleza/kvas/blob/3fdbbd1ace7b57b11bf88d8db3882d94a1d6e01c/opt/etc/ndm/ndm#L194-L206 - {"-m", "set", "!", "--match-set", r.IPSetName, "dst", "-j", "RETURN"}, - {"-j", "CONNMARK", "--restore-mark"}, - {"-m", "mark", "--mark", strconv.Itoa(int(r.mark)), "-j", "RETURN"}, - // This command not working - // {"--syn", "-j", "MARK", "--set-mark", strconv.Itoa(int(mark))}, - {"-m", "conntrack", "--ctstate", "NEW", "-j", "MARK", "--set-mark", strconv.Itoa(int(r.mark))}, - {"-j", "CONNMARK", "--save-mark"}, - } { - err = r.IPTables.AppendUnique("mangle", r.ChainName, iptablesArgs...) - if err != nil { - return fmt.Errorf("failed to append rule: %w", err) - } - } - - err = r.IPTables.AppendUnique("mangle", "PREROUTING", "-m", "set", "--match-set", r.IPSetName, "dst", "-j", r.ChainName) - if err != nil { - return fmt.Errorf("failed to append rule to PREROUTING: %w", err) - } - - err = r.IPTables.AppendUnique("mangle", "OUTPUT", "-m", "set", "--match-set", r.IPSetName, "dst", "-j", r.ChainName) - if err != nil { - return fmt.Errorf("failed to append rule to OUTPUT: %w", err) - } - } else { - preroutingChainName := fmt.Sprintf("%s_PREROUTING", r.ChainName) - - err = r.IPTables.ClearChain("mangle", preroutingChainName) - if err != nil { - return fmt.Errorf("failed to clear chain: %w", err) - } - - err = r.IPTables.AppendUnique("mangle", preroutingChainName, "-m", "set", "--match-set", r.IPSetName, "dst", "-j", "MARK", "--set-mark", strconv.Itoa(int(r.mark))) - if err != nil { - return fmt.Errorf("failed to create rule: %w", err) - } - - err = r.IPTables.AppendUnique("mangle", "PREROUTING", "-j", preroutingChainName) - if err != nil { - return fmt.Errorf("failed to append rule to PREROUTING: %w", err) - } - } - - postroutingChainName := fmt.Sprintf("%s_POSTROUTING", r.ChainName) - - err = r.IPTables.ClearChain("nat", postroutingChainName) + err = r.PutIPTable("all") if err != nil { - return fmt.Errorf("failed to clear chain: %w", err) - } - - err = r.IPTables.AppendUnique("nat", postroutingChainName, "-o", r.IfaceName, "-j", "MASQUERADE") - if err != nil { - return fmt.Errorf("failed to create rule: %w", err) - } - - err = r.IPTables.AppendUnique("nat", "POSTROUTING", "-j", postroutingChainName) - if err != nil { - return fmt.Errorf("failed to append rule to POSTROUTING: %w", err) + return nil } // Mapping mark with table @@ -180,6 +197,7 @@ func (r *IfaceToIPSet) ForceEnable() error { return nil } + r.Enabled = true return nil } @@ -244,6 +262,7 @@ func (r *IfaceToIPSet) Disable() []error { r.ipRule = nil } + r.Enabled = false return errs } diff --git a/netfilter-helper/port-remap.go b/netfilter-helper/port-remap.go index 5d8729d..5e84150 100644 --- a/netfilter-helper/port-remap.go +++ b/netfilter-helper/port-remap.go @@ -15,20 +15,31 @@ type PortRemap struct { Enabled bool } +func (r *PortRemap) PutIPTable(table string) error { + if table == "all" || table == "nat" { + err := r.IPTables.ClearChain("nat", r.ChainName) + if err != nil { + return fmt.Errorf("failed to clear chain: %w", err) + } + + err = r.IPTables.AppendUnique("nat", r.ChainName, "-p", "udp", "--dport", strconv.Itoa(int(r.From)), "-j", "REDIRECT", "--to-port", strconv.Itoa(int(r.To))) + if err != nil { + return fmt.Errorf("failed to create rule: %w", err) + } + + err = r.IPTables.InsertUnique("nat", "PREROUTING", 1, "-j", r.ChainName) + if err != nil { + return fmt.Errorf("failed to linking chain: %w", err) + } + } + + return nil +} + func (r *PortRemap) ForceEnable() error { - err := r.IPTables.ClearChain("nat", r.ChainName) + err := r.PutIPTable("all") if err != nil { - return fmt.Errorf("failed to clear chain: %w", err) - } - - err = r.IPTables.AppendUnique("nat", r.ChainName, "-p", "udp", "--dport", strconv.Itoa(int(r.From)), "-j", "REDIRECT", "--to-port", strconv.Itoa(int(r.To))) - if err != nil { - return fmt.Errorf("failed to create rule: %w", err) - } - - err = r.IPTables.InsertUnique("nat", "PREROUTING", 1, "-j", r.ChainName) - if err != nil { - return fmt.Errorf("failed to linking chain: %w", err) + return err } r.Enabled = true diff --git a/opt/etc/ndm/netfilter.d/100-kvas2 b/opt/etc/ndm/netfilter.d/100-kvas2 new file mode 100755 index 0000000..0370e59 --- /dev/null +++ b/opt/etc/ndm/netfilter.d/100-kvas2 @@ -0,0 +1,6 @@ +#!/bin/sh +SOCKET_PATH="/opt/var/run/kvas2-go.sock" +if [ ! -S "$SOCKET_PATH" ]; then + exit +fi +echo -n "netfilter.d:${type}:${table}" | socat - UNIX-CONNECT:"${SOCKET_PATH}"