|
| 1 | +package usbgadget |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "os/exec" |
| 6 | + |
| 7 | + "github.com/google/nftables" |
| 8 | + "github.com/google/nftables/expr" |
| 9 | + "golang.org/x/sys/unix" |
| 10 | +) |
| 11 | + |
| 12 | +const ( |
| 13 | + ncmFirewallTableName = "jetkvm" |
| 14 | + ncmFirewallChainName = "input_usb0" |
| 15 | +) |
| 16 | + |
| 17 | +// applyNcmFirewall installs (or replaces) an nftables table that drops all |
| 18 | +// inbound TCP and UDP arriving on usb0. ICMP/ICMPv6 are intentionally not |
| 19 | +// touched so NDP and ping continue to work — host isolation, not full |
| 20 | +// blackhole. Idempotent: deletes any pre-existing table of the same name |
| 21 | +// first so a stale ruleset from a previous run can't accumulate. |
| 22 | +// |
| 23 | +// Loads nf_tables.ko on first call via modprobe; the rv1106 rootfs ships |
| 24 | +// the module but does not auto-load it. |
| 25 | +func (u *UsbGadget) applyNcmFirewall() error { |
| 26 | + if out, err := exec.Command("modprobe", "nf_tables").CombinedOutput(); err != nil { |
| 27 | + return fmt.Errorf("modprobe nf_tables: %w: %s", err, out) |
| 28 | + } |
| 29 | + |
| 30 | + conn, err := nftables.New() |
| 31 | + if err != nil { |
| 32 | + return fmt.Errorf("open nftables conn: %w", err) |
| 33 | + } |
| 34 | + |
| 35 | + // Wipe any stale table from a previous run before building fresh. |
| 36 | + table := &nftables.Table{Name: ncmFirewallTableName, Family: nftables.TableFamilyINet} |
| 37 | + conn.DelTable(table) |
| 38 | + // Ignore error — table may not exist, which is fine. |
| 39 | + _ = conn.Flush() |
| 40 | + |
| 41 | + table = conn.AddTable(table) |
| 42 | + policy := nftables.ChainPolicyAccept |
| 43 | + chain := conn.AddChain(&nftables.Chain{ |
| 44 | + Name: ncmFirewallChainName, |
| 45 | + Table: table, |
| 46 | + Hooknum: nftables.ChainHookInput, |
| 47 | + Priority: nftables.ChainPriorityFilter, |
| 48 | + Type: nftables.ChainTypeFilter, |
| 49 | + Policy: &policy, |
| 50 | + }) |
| 51 | + |
| 52 | + // One drop rule per L4 protocol. Each rule matches: |
| 53 | + // iifname == usb0 AND l4proto == <tcp|udp> => drop |
| 54 | + for _, proto := range []byte{unix.IPPROTO_TCP, unix.IPPROTO_UDP} { |
| 55 | + conn.AddRule(&nftables.Rule{ |
| 56 | + Table: table, |
| 57 | + Chain: chain, |
| 58 | + Exprs: []expr.Any{ |
| 59 | + &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, |
| 60 | + &expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: ifnameBytes(ncmInterfaceName)}, |
| 61 | + &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, |
| 62 | + &expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{proto}}, |
| 63 | + &expr.Verdict{Kind: expr.VerdictDrop}, |
| 64 | + }, |
| 65 | + }) |
| 66 | + } |
| 67 | + |
| 68 | + if err := conn.Flush(); err != nil { |
| 69 | + return fmt.Errorf("commit nftables ruleset: %w", err) |
| 70 | + } |
| 71 | + return nil |
| 72 | +} |
| 73 | + |
| 74 | +// removeNcmFirewall deletes our nftables table. Best-effort: a missing table |
| 75 | +// is not an error (we may be called during teardown after a crash or during |
| 76 | +// rapid toggle off/on cycles). |
| 77 | +func (u *UsbGadget) removeNcmFirewall() { |
| 78 | + conn, err := nftables.New() |
| 79 | + if err != nil { |
| 80 | + u.log.Warn().Err(err).Msg("nftables open failed during teardown") |
| 81 | + return |
| 82 | + } |
| 83 | + conn.DelTable(&nftables.Table{Name: ncmFirewallTableName, Family: nftables.TableFamilyINet}) |
| 84 | + if err := conn.Flush(); err != nil { |
| 85 | + // Most likely cause is "table not found", which we don't care about. |
| 86 | + u.log.Debug().Err(err).Msg("nftables flush during teardown") |
| 87 | + } |
| 88 | +} |
| 89 | + |
| 90 | +// ifnameBytes pads or truncates name to IFNAMSIZ (16 bytes), the form nft |
| 91 | +// expects when comparing against the iifname meta key. A shorter slice |
| 92 | +// silently fails to match (the kernel memcmps the full register width). |
| 93 | +func ifnameBytes(name string) []byte { |
| 94 | + b := make([]byte, unix.IFNAMSIZ) |
| 95 | + copy(b, name) |
| 96 | + return b |
| 97 | +} |
0 commit comments