m/n/c/network: make SNAT generic

This changes the SNAT/Masquerade rule from being a thing set up per
interface which was only implemented by the dynamic network runnable to
a generic rule set up by the general network service part shared between
the static and dynamic implementations. It also tries to avoid NATing
host-originated traffic. Matching on interface names is argubly ugly but
the alternative is patching CNI plugins, which is also ugly.

Change-Id: I7ec40fc244ae4689b6f96ab87dbebe9a6c43dd70
Reviewed-on: https://review.monogon.dev/c/monogon/+/1844
Reviewed-by: Serge Bazanski <serge@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/node/core/network/main.go b/metropolis/node/core/network/main.go
index 956bb02..04f0afd 100644
--- a/metropolis/node/core/network/main.go
+++ b/metropolis/node/core/network/main.go
@@ -191,25 +191,6 @@
 		return err
 	}
 
-	// Masquerade/SNAT all traffic going out of the external interface
-	s.nftConn.AddRule(&nftables.Rule{
-		Table: s.natTable,
-		Chain: s.natPostroutingChain,
-		Exprs: []expr.Any{
-			&expr.Meta{Key: expr.MetaKeyOIFNAME, Register: 1},
-			&expr.Cmp{
-				Op:       expr.CmpOpEq,
-				Register: 1,
-				Data:     nfifname(iface.Attrs().Name),
-			},
-			&expr.Masq{},
-		},
-	})
-
-	if err := s.nftConn.Flush(); err != nil {
-		panic(err)
-	}
-
 	return nil
 }
 
@@ -280,8 +261,45 @@
 		Table:    s.natTable,
 		Type:     nftables.ChainTypeNAT,
 	})
+	// SNAT/Masquerade all traffic coming from interfaces starting with
+	// veth going to interfaces not starting with veth.
+	// This NATs all container traffic going out of the host without
+	// affecting anything else and without needing to care about specific
+	// interfaces. Will need to be changed when we support L3 attachments
+	// (BGP, ...).
+	s.nftConn.AddRule(&nftables.Rule{
+		Table: s.natTable,
+		Chain: s.natPostroutingChain,
+		Exprs: []expr.Any{
+			&expr.Meta{
+				Key:      expr.MetaKeyIIFNAME,
+				Register: 8, // covers registers 8-12 (16 bytes/4 regs)
+			},
+			// Check if incoming interface starts with veth
+			&expr.Cmp{
+				Op:       expr.CmpOpEq,
+				Register: 8,
+				Data:     []byte{'v', 'e', 't', 'h'},
+			},
+			&expr.Meta{
+				Key:      expr.MetaKeyOIFNAME,
+				Register: 8, // covers registers 8-12
+			},
+			// Check if outgoing interface doesn't start with veth
+			&expr.Cmp{
+				Op:       expr.CmpOpNeq,
+				Register: 8,
+				Data:     []byte{'v', 'e', 't', 'h'},
+			},
+			&expr.Masq{
+				FullyRandom: true,
+				Persistent:  true,
+			},
+		},
+	})
+
 	if err := s.nftConn.Flush(); err != nil {
-		logger.Fatalf("Failed to set up nftables base chains: %v", err)
+		logger.Fatalf("Failed to set up nftables nat chain: %v", err)
 	}
 
 	sysctlOpts := sysctlOptions{