nftables_runner_test.go 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072
  1. // Copyright (c) Tailscale Inc & contributors
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build linux
  4. package linuxfw
  5. import (
  6. "bytes"
  7. "errors"
  8. "fmt"
  9. "net/netip"
  10. "os"
  11. "runtime"
  12. "slices"
  13. "strings"
  14. "testing"
  15. "github.com/google/nftables"
  16. "github.com/google/nftables/expr"
  17. "github.com/mdlayher/netlink"
  18. "github.com/vishvananda/netns"
  19. "tailscale.com/net/tsaddr"
  20. "tailscale.com/tstest"
  21. "tailscale.com/types/logger"
  22. )
  23. func toAnySlice[T any](s []T) []any {
  24. out := make([]any, len(s))
  25. for i, v := range s {
  26. out[i] = v
  27. }
  28. return out
  29. }
  30. // nfdump returns a hexdump of 4 bytes per line (like nft --debug=all), allowing
  31. // users to make sense of large byte literals more easily.
  32. func nfdump(b []byte) string {
  33. var buf bytes.Buffer
  34. for c := range slices.Chunk(b, 4) {
  35. format := strings.Repeat("%02x ", len(c))
  36. fmt.Fprintf(&buf, format+"\n", toAnySlice(c)...)
  37. }
  38. return buf.String()
  39. }
  40. func TestMaskof(t *testing.T) {
  41. pfx, err := netip.ParsePrefix("192.168.1.0/24")
  42. if err != nil {
  43. t.Fatal(err)
  44. }
  45. want := []byte{0xff, 0xff, 0xff, 0x00}
  46. if got := maskof(pfx); !bytes.Equal(got, want) {
  47. t.Errorf("got %v; want %v", got, want)
  48. }
  49. }
  50. // linediff returns a side-by-side diff of two nfdump() return values, flagging
  51. // lines which are not equal with an exclamation point prefix.
  52. func linediff(a, b string) string {
  53. var buf bytes.Buffer
  54. fmt.Fprintf(&buf, "got -- want\n")
  55. linesA := strings.Split(a, "\n")
  56. linesB := strings.Split(b, "\n")
  57. for idx, lineA := range linesA {
  58. if idx >= len(linesB) {
  59. break
  60. }
  61. lineB := linesB[idx]
  62. prefix := "! "
  63. if lineA == lineB {
  64. prefix = " "
  65. }
  66. fmt.Fprintf(&buf, "%s%s -- %s\n", prefix, lineA, lineB)
  67. }
  68. return buf.String()
  69. }
  70. func newTestConn(t *testing.T, want [][]byte, reply [][]netlink.Message) *nftables.Conn {
  71. conn, err := nftables.New(nftables.WithTestDial(
  72. func(req []netlink.Message) ([]netlink.Message, error) {
  73. for idx, msg := range req {
  74. b, err := msg.MarshalBinary()
  75. if err != nil {
  76. t.Fatal(err)
  77. }
  78. if len(b) < 16 {
  79. continue
  80. }
  81. b = b[16:]
  82. if len(want) == 0 {
  83. t.Errorf("no want entry for message %d: %x", idx, b)
  84. continue
  85. }
  86. if got, want := b, want[0]; !bytes.Equal(got, want) {
  87. t.Errorf("message %d: %s", idx, linediff(nfdump(got), nfdump(want)))
  88. }
  89. want = want[1:]
  90. }
  91. // no reply for batch end message
  92. if len(want) == 0 {
  93. return nil, nil
  94. }
  95. rep := reply[0]
  96. reply = reply[1:]
  97. return rep, nil
  98. }))
  99. if err != nil {
  100. t.Fatal(err)
  101. }
  102. return conn
  103. }
  104. func TestInsertHookRule(t *testing.T) {
  105. proto := nftables.TableFamilyIPv4
  106. want := [][]byte{
  107. // batch begin
  108. []byte("\x00\x00\x00\x0a"),
  109. // nft add table ip ts-filter-test
  110. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
  111. // nft add chain ip ts-filter-test ts-input-test { type filter hook input priority 0 \; }
  112. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x03\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
  113. // nft add chain ip ts-filter-test ts-jumpto
  114. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x0e\x00\x03\x00\x74\x73\x2d\x6a\x75\x6d\x70\x74\x6f\x00\x00\x00"),
  115. // nft add rule ip ts-filter-test ts-input-test counter jump ts-jumptp
  116. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x02\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x70\x00\x04\x80\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x2c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x20\x00\x02\x80\x1c\x00\x02\x80\x08\x00\x01\x00\xff\xff\xff\xfd\x0e\x00\x02\x00\x74\x73\x2d\x6a\x75\x6d\x70\x74\x6f\x00\x00\x00"),
  117. // batch end
  118. []byte("\x00\x00\x00\x0a"),
  119. }
  120. testConn := newTestConn(t, want, nil)
  121. table := testConn.AddTable(&nftables.Table{
  122. Family: proto,
  123. Name: "ts-filter-test",
  124. })
  125. fromchain := testConn.AddChain(&nftables.Chain{
  126. Name: "ts-input-test",
  127. Table: table,
  128. Type: nftables.ChainTypeFilter,
  129. Hooknum: nftables.ChainHookInput,
  130. Priority: nftables.ChainPriorityFilter,
  131. })
  132. tochain := testConn.AddChain(&nftables.Chain{
  133. Name: "ts-jumpto",
  134. Table: table,
  135. })
  136. err := addHookRule(testConn, table, fromchain, tochain.Name)
  137. if err != nil {
  138. t.Fatal(err)
  139. }
  140. }
  141. func TestInsertLoopbackRule(t *testing.T) {
  142. proto := nftables.TableFamilyIPv4
  143. want := [][]byte{
  144. // batch begin
  145. []byte("\x00\x00\x00\x0a"),
  146. // nft add table ip ts-filter-test
  147. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
  148. // nft add chain ip ts-filter-test ts-input-test { type filter hook input priority 0 \; }
  149. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x03\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
  150. // nft add rule ip ts-filter-test ts-input-test iifname "lo" ip saddr 192.168.0.2 counter accept
  151. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x02\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x10\x01\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x06\x08\x00\x01\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x06\x00\x01\x00\x6c\x6f\x00\x00\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x0c\x08\x00\x04\x00\x00\x00\x00\x04\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x08\x00\x01\x00\xc0\xa8\x00\x02\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01"),
  152. // batch end
  153. []byte("\x00\x00\x00\x0a"),
  154. }
  155. testConn := newTestConn(t, want, nil)
  156. table := testConn.AddTable(&nftables.Table{
  157. Family: proto,
  158. Name: "ts-filter-test",
  159. })
  160. chain := testConn.AddChain(&nftables.Chain{
  161. Name: "ts-input-test",
  162. Table: table,
  163. Type: nftables.ChainTypeFilter,
  164. Hooknum: nftables.ChainHookInput,
  165. Priority: nftables.ChainPriorityFilter,
  166. })
  167. addr := netip.MustParseAddr("192.168.0.2")
  168. err := insertLoopbackRule(testConn, proto, table, chain, addr)
  169. if err != nil {
  170. t.Fatal(err)
  171. }
  172. }
  173. func TestInsertLoopbackRuleV6(t *testing.T) {
  174. protoV6 := nftables.TableFamilyIPv6
  175. want := [][]byte{
  176. // batch begin
  177. []byte("\x00\x00\x00\x0a"),
  178. // nft add table ip6 ts-filter-test
  179. []byte("\x0a\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
  180. // nft add chain ip6 ts-filter-test ts-input-test { type filter hook input priority 0\; }
  181. []byte("\x0a\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x03\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
  182. // nft add rule ip6 ts-filter-test ts-input-test iifname "lo" ip6 addr 2001:db8::1 counter accept
  183. []byte("\x0a\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x02\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x1c\x01\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x06\x08\x00\x01\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x06\x00\x01\x00\x6c\x6f\x00\x00\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x08\x08\x00\x04\x00\x00\x00\x00\x10\x38\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x2c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x18\x00\x03\x80\x14\x00\x01\x00\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01"),
  184. // batch end
  185. []byte("\x00\x00\x00\x0a"),
  186. }
  187. testConn := newTestConn(t, want, nil)
  188. tableV6 := testConn.AddTable(&nftables.Table{
  189. Family: protoV6,
  190. Name: "ts-filter-test",
  191. })
  192. chainV6 := testConn.AddChain(&nftables.Chain{
  193. Name: "ts-input-test",
  194. Table: tableV6,
  195. Type: nftables.ChainTypeFilter,
  196. Hooknum: nftables.ChainHookInput,
  197. Priority: nftables.ChainPriorityFilter,
  198. })
  199. addrV6 := netip.MustParseAddr("2001:db8::1")
  200. err := insertLoopbackRule(testConn, protoV6, tableV6, chainV6, addrV6)
  201. if err != nil {
  202. t.Fatal(err)
  203. }
  204. }
  205. func TestAddReturnChromeOSVMRangeRule(t *testing.T) {
  206. proto := nftables.TableFamilyIPv4
  207. want := [][]byte{
  208. // batch begin
  209. []byte("\x00\x00\x00\x0a"),
  210. // nft add table ip ts-filter-test
  211. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
  212. // nft add chain ip ts-filter-test ts-input-test { type filter hook input priority 0\; }
  213. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x03\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
  214. // nft add rule ip ts-filter-test ts-input-test iifname != "testTunn" ip saddr 100.115.92.0/23 counter return
  215. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x02\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x58\x01\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x06\x08\x00\x01\x00\x00\x00\x00\x01\x30\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x10\x00\x03\x80\x0c\x00\x01\x00\x74\x65\x73\x74\x54\x75\x6e\x6e\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x0c\x08\x00\x04\x00\x00\x00\x00\x04\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\xff\xff\xfe\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x08\x00\x01\x00\x64\x73\x5c\x00\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\xff\xff\xff\xfb"),
  216. // batch end
  217. []byte("\x00\x00\x00\x0a"),
  218. }
  219. testConn := newTestConn(t, want, nil)
  220. table := testConn.AddTable(&nftables.Table{
  221. Family: proto,
  222. Name: "ts-filter-test",
  223. })
  224. chain := testConn.AddChain(&nftables.Chain{
  225. Name: "ts-input-test",
  226. Table: table,
  227. Type: nftables.ChainTypeFilter,
  228. Hooknum: nftables.ChainHookInput,
  229. Priority: nftables.ChainPriorityFilter,
  230. })
  231. err := addReturnChromeOSVMRangeRule(testConn, table, chain, "testTunn")
  232. if err != nil {
  233. t.Fatal(err)
  234. }
  235. }
  236. func TestAddDropCGNATRangeRule(t *testing.T) {
  237. proto := nftables.TableFamilyIPv4
  238. want := [][]byte{
  239. // batch begin
  240. []byte("\x00\x00\x00\x0a"),
  241. // nft add table ip ts-filter-test
  242. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
  243. // nft add chain ip ts-filter-test ts-input-test { type filter hook input priority filter; }
  244. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x03\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
  245. // nft add rule ip ts-filter-test ts-input-test iifname != "testTunn" ip saddr 100.64.0.0/10 counter drop
  246. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x02\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x58\x01\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x06\x08\x00\x01\x00\x00\x00\x00\x01\x30\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x10\x00\x03\x80\x0c\x00\x01\x00\x74\x65\x73\x74\x54\x75\x6e\x6e\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x0c\x08\x00\x04\x00\x00\x00\x00\x04\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\xff\xc0\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x08\x00\x01\x00\x64\x40\x00\x00\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00"),
  247. // batch end
  248. []byte("\x00\x00\x00\x0a"),
  249. }
  250. testConn := newTestConn(t, want, nil)
  251. table := testConn.AddTable(&nftables.Table{
  252. Family: proto,
  253. Name: "ts-filter-test",
  254. })
  255. chain := testConn.AddChain(&nftables.Chain{
  256. Name: "ts-input-test",
  257. Table: table,
  258. Type: nftables.ChainTypeFilter,
  259. Hooknum: nftables.ChainHookInput,
  260. Priority: nftables.ChainPriorityFilter,
  261. })
  262. err := addDropCGNATRangeRule(testConn, table, chain, "testTunn")
  263. if err != nil {
  264. t.Fatal(err)
  265. }
  266. }
  267. func TestAddSetSubnetRouteMarkRule(t *testing.T) {
  268. proto := nftables.TableFamilyIPv4
  269. want := [][]byte{
  270. // batch begin
  271. []byte("\x00\x00\x00\x0a"),
  272. // nft add table ip ts-filter-test
  273. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
  274. // nft add chain ip ts-filter-test ts-forward-test { type filter hook forward priority 0\; }
  275. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x14\x00\x03\x00\x74\x73\x2d\x66\x6f\x72\x77\x61\x72\x64\x2d\x74\x65\x73\x74\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x02\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
  276. // nft add rule ip ts-filter-test ts-forward-test iifname "testTunn" counter meta mark set mark and 0xff00ffff xor 0x40000
  277. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x14\x00\x02\x00\x74\x73\x2d\x66\x6f\x72\x77\x61\x72\x64\x2d\x74\x65\x73\x74\x00\x10\x01\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x06\x08\x00\x01\x00\x00\x00\x00\x01\x30\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x10\x00\x03\x80\x0c\x00\x01\x00\x74\x65\x73\x74\x54\x75\x6e\x6e\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x01\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\xff\x00\xff\xff\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x04\x00\x00\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x03\x00\x00\x00\x00\x01"),
  278. // batch end
  279. []byte("\x00\x00\x00\x0a"),
  280. }
  281. testConn := newTestConn(t, want, nil)
  282. table := testConn.AddTable(&nftables.Table{
  283. Family: proto,
  284. Name: "ts-filter-test",
  285. })
  286. chain := testConn.AddChain(&nftables.Chain{
  287. Name: "ts-forward-test",
  288. Table: table,
  289. Type: nftables.ChainTypeFilter,
  290. Hooknum: nftables.ChainHookForward,
  291. Priority: nftables.ChainPriorityFilter,
  292. })
  293. err := addSetSubnetRouteMarkRule(testConn, table, chain, "testTunn")
  294. if err != nil {
  295. t.Fatal(err)
  296. }
  297. }
  298. func TestAddDropOutgoingPacketFromCGNATRangeRuleWithTunname(t *testing.T) {
  299. proto := nftables.TableFamilyIPv4
  300. want := [][]byte{
  301. // batch begin
  302. []byte("\x00\x00\x00\x0a"),
  303. // nft add table ip ts-filter-test
  304. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
  305. // nft add chain ip ts-filter-test ts-forward-test { type filter hook forward priority 0\; }
  306. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x14\x00\x03\x00\x74\x73\x2d\x66\x6f\x72\x77\x61\x72\x64\x2d\x74\x65\x73\x74\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x02\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
  307. // nft add rule ip ts-filter-test ts-forward-test oifname "testTunn" ip saddr 100.64.0.0/10 counter drop
  308. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x14\x00\x02\x00\x74\x73\x2d\x66\x6f\x72\x77\x61\x72\x64\x2d\x74\x65\x73\x74\x00\x58\x01\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x07\x08\x00\x01\x00\x00\x00\x00\x01\x30\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x10\x00\x03\x80\x0c\x00\x01\x00\x74\x65\x73\x74\x54\x75\x6e\x6e\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x0c\x08\x00\x04\x00\x00\x00\x00\x04\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\xff\xc0\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x08\x00\x01\x00\x64\x40\x00\x00\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00"),
  309. // batch end
  310. []byte("\x00\x00\x00\x0a"),
  311. }
  312. testConn := newTestConn(t, want, nil)
  313. table := testConn.AddTable(&nftables.Table{
  314. Family: proto,
  315. Name: "ts-filter-test",
  316. })
  317. chain := testConn.AddChain(&nftables.Chain{
  318. Name: "ts-forward-test",
  319. Table: table,
  320. Type: nftables.ChainTypeFilter,
  321. Hooknum: nftables.ChainHookForward,
  322. Priority: nftables.ChainPriorityFilter,
  323. })
  324. err := addDropOutgoingPacketFromCGNATRangeRuleWithTunname(testConn, table, chain, "testTunn")
  325. if err != nil {
  326. t.Fatal(err)
  327. }
  328. }
  329. func TestAddAcceptOutgoingPacketRule(t *testing.T) {
  330. proto := nftables.TableFamilyIPv4
  331. want := [][]byte{
  332. // batch begin
  333. []byte("\x00\x00\x00\x0a"),
  334. // nft add table ip ts-filter-test
  335. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
  336. // nft add chain ip ts-filter-test ts-forward-test { type filter hook forward priority 0\; }
  337. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x14\x00\x03\x00\x74\x73\x2d\x66\x6f\x72\x77\x61\x72\x64\x2d\x74\x65\x73\x74\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x02\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
  338. // nft add rule ip ts-filter-test ts-forward-test oifname "testTunn" counter accept
  339. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x14\x00\x02\x00\x74\x73\x2d\x66\x6f\x72\x77\x61\x72\x64\x2d\x74\x65\x73\x74\x00\xb4\x00\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x07\x08\x00\x01\x00\x00\x00\x00\x01\x30\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x10\x00\x03\x80\x0c\x00\x01\x00\x74\x65\x73\x74\x54\x75\x6e\x6e\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01"),
  340. // batch end
  341. []byte("\x00\x00\x00\x0a"),
  342. }
  343. testConn := newTestConn(t, want, nil)
  344. table := testConn.AddTable(&nftables.Table{
  345. Family: proto,
  346. Name: "ts-filter-test",
  347. })
  348. chain := testConn.AddChain(&nftables.Chain{
  349. Name: "ts-forward-test",
  350. Table: table,
  351. Type: nftables.ChainTypeFilter,
  352. Hooknum: nftables.ChainHookForward,
  353. Priority: nftables.ChainPriorityFilter,
  354. })
  355. err := addAcceptOutgoingPacketRule(testConn, table, chain, "testTunn")
  356. if err != nil {
  357. t.Fatal(err)
  358. }
  359. }
  360. func TestAddAcceptIncomingPacketRule(t *testing.T) {
  361. proto := nftables.TableFamilyIPv4
  362. want := [][]byte{
  363. // batch begin
  364. []byte("\x00\x00\x00\x0a"),
  365. // nft add table ip ts-filter-test
  366. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
  367. // nft add chain ip ts-filter-test ts-input-test { type filter hook input priority 0\; }
  368. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x03\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
  369. // nft add rule ip ts-filter-test ts-input-test iifname "testTunn" counter accept
  370. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x12\x00\x02\x00\x74\x73\x2d\x69\x6e\x70\x75\x74\x2d\x74\x65\x73\x74\x00\x00\x00\xb4\x00\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x06\x08\x00\x01\x00\x00\x00\x00\x01\x30\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x10\x00\x03\x80\x0c\x00\x01\x00\x74\x65\x73\x74\x54\x75\x6e\x6e\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01"),
  371. // batch end
  372. []byte("\x00\x00\x00\x0a"),
  373. }
  374. testConn := newTestConn(t, want, nil)
  375. table := testConn.AddTable(&nftables.Table{
  376. Family: proto,
  377. Name: "ts-filter-test",
  378. })
  379. chain := testConn.AddChain(&nftables.Chain{
  380. Name: "ts-input-test",
  381. Table: table,
  382. Type: nftables.ChainTypeFilter,
  383. Hooknum: nftables.ChainHookInput,
  384. Priority: nftables.ChainPriorityFilter,
  385. })
  386. err := addAcceptIncomingPacketRule(testConn, table, chain, "testTunn")
  387. if err != nil {
  388. t.Fatal(err)
  389. }
  390. }
  391. func TestAddMatchSubnetRouteMarkRuleMasq(t *testing.T) {
  392. proto := nftables.TableFamilyIPv4
  393. want := [][]byte{
  394. // batch begin
  395. []byte("\x00\x00\x00\x0a"),
  396. // nft add table ip ts-nat-test
  397. []byte("\x02\x00\x00\x00\x10\x00\x01\x00\x74\x73\x2d\x6e\x61\x74\x2d\x74\x65\x73\x74\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
  398. // nft add chain ip ts-nat-test ts-postrouting-test { type nat hook postrouting priority 100; }
  399. []byte("\x02\x00\x00\x00\x10\x00\x01\x00\x74\x73\x2d\x6e\x61\x74\x2d\x74\x65\x73\x74\x00\x18\x00\x03\x00\x74\x73\x2d\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\x2d\x74\x65\x73\x74\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x04\x08\x00\x02\x00\x00\x00\x00\x64\x08\x00\x07\x00\x6e\x61\x74\x00"),
  400. // nft add rule ip ts-nat-test ts-postrouting-test meta mark & 0x00ff0000 == 0x00040000 counter masquerade
  401. []byte("\x02\x00\x00\x00\x10\x00\x01\x00\x74\x73\x2d\x6e\x61\x74\x2d\x74\x65\x73\x74\x00\x18\x00\x02\x00\x74\x73\x2d\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\x2d\x74\x65\x73\x74\x00\xd8\x00\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x01\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\x00\xff\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x08\x00\x01\x00\x00\x04\x00\x00\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x01\x80\x09\x00\x01\x00\x6d\x61\x73\x71\x00\x00\x00\x00\x04\x00\x02\x80"),
  402. // batch end
  403. []byte("\x00\x00\x00\x0a"),
  404. }
  405. testConn := newTestConn(t, want, nil)
  406. table := testConn.AddTable(&nftables.Table{
  407. Family: proto,
  408. Name: "ts-nat-test",
  409. })
  410. chain := testConn.AddChain(&nftables.Chain{
  411. Name: "ts-postrouting-test",
  412. Table: table,
  413. Type: nftables.ChainTypeNAT,
  414. Hooknum: nftables.ChainHookPostrouting,
  415. Priority: nftables.ChainPriorityNATSource,
  416. })
  417. err := addMatchSubnetRouteMarkRule(testConn, table, chain, Masq)
  418. if err != nil {
  419. t.Fatal(err)
  420. }
  421. }
  422. func TestDelMatchSubnetRouteMarkMasqRule(t *testing.T) {
  423. proto := nftables.TableFamilyIPv4
  424. reply := [][]netlink.Message{
  425. nil,
  426. {{Header: netlink.Header{Length: 0x128, Type: 0xa06, Flags: 0x802, Sequence: 0xa213d55d, PID: 0x11e79}, Data: []uint8{0x2, 0x0, 0x0, 0x8c, 0xd, 0x0, 0x1, 0x0, 0x6e, 0x61, 0x74, 0x2d, 0x74, 0x65, 0x73, 0x74, 0x0, 0x0, 0x0, 0x0, 0x18, 0x0, 0x2, 0x0, 0x74, 0x73, 0x2d, 0x70, 0x6f, 0x73, 0x74, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x65, 0x73, 0x74, 0x0, 0xc, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0xe0, 0x0, 0x4, 0x0, 0x24, 0x0, 0x1, 0x0, 0x9, 0x0, 0x1, 0x0, 0x6d, 0x65, 0x74, 0x61, 0x0, 0x0, 0x0, 0x0, 0x14, 0x0, 0x2, 0x0, 0x8, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x3, 0x8, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4c, 0x0, 0x1, 0x0, 0xc, 0x0, 0x1, 0x0, 0x62, 0x69, 0x74, 0x77, 0x69, 0x73, 0x65, 0x0, 0x3c, 0x0, 0x2, 0x0, 0x8, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x8, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x1, 0x8, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x4, 0x8, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x4, 0x0, 0x8, 0x0, 0x1, 0x0, 0x0, 0xff, 0x0, 0x0, 0xc, 0x0, 0x5, 0x0, 0x8, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2c, 0x0, 0x1, 0x0, 0x8, 0x0, 0x1, 0x0, 0x63, 0x6d, 0x70, 0x0, 0x20, 0x0, 0x2, 0x0, 0x8, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x8, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x3, 0x0, 0x8, 0x0, 0x1, 0x0, 0x0, 0x4, 0x0, 0x0, 0x2c, 0x0, 0x1, 0x0, 0xc, 0x0, 0x1, 0x0, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x0, 0x1c, 0x0, 0x2, 0x0, 0xc, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x0, 0x1, 0x0, 0x9, 0x0, 0x1, 0x0, 0x6d, 0x61, 0x73, 0x71, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x2, 0x0}}},
  427. {{Header: netlink.Header{Length: 0x14, Type: 0x3, Flags: 0x2, Sequence: 0x311fdccb, PID: 0x11e79}, Data: []uint8{0x0, 0x0, 0x0, 0x0}}},
  428. {{Header: netlink.Header{Length: 0x24, Type: 0x2, Flags: 0x100, Sequence: 0x311fdccb, PID: 0x11e79}, Data: []uint8{0x0, 0x0, 0x0, 0x0, 0x48, 0x0, 0x0, 0x0, 0x8, 0xa, 0x5, 0x0, 0xcb, 0xdc, 0x1f, 0x31, 0x79, 0x1e, 0x1, 0x0}}},
  429. }
  430. want := [][]byte{
  431. // get rules in nat-test table ts-postrouting-test chain
  432. []byte("\x02\x00\x00\x00\x0d\x00\x01\x00\x6e\x61\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x00\x18\x00\x02\x00\x74\x73\x2d\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\x2d\x74\x65\x73\x74\x00"),
  433. // batch begin
  434. []byte("\x00\x00\x00\x0a"),
  435. // nft delete rule ip nat-test ts-postrouting-test handle 4
  436. []byte("\x02\x00\x00\x00\x0d\x00\x01\x00\x6e\x61\x74\x2d\x74\x65\x73\x74\x00\x00\x00\x00\x18\x00\x02\x00\x74\x73\x2d\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\x2d\x74\x65\x73\x74\x00\x0c\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x04"),
  437. // batch end
  438. []byte("\x00\x00\x00\x0a"),
  439. }
  440. conn := newTestConn(t, want, reply)
  441. table := &nftables.Table{
  442. Family: proto,
  443. Name: "nat-test",
  444. }
  445. chain := &nftables.Chain{
  446. Name: "ts-postrouting-test",
  447. Table: table,
  448. Type: nftables.ChainTypeNAT,
  449. Hooknum: nftables.ChainHookPostrouting,
  450. Priority: nftables.ChainPriorityNATSource,
  451. }
  452. err := delMatchSubnetRouteMarkMasqRule(conn, table, chain)
  453. if err != nil {
  454. t.Fatal(err)
  455. }
  456. }
  457. func TestAddMatchSubnetRouteMarkRuleAccept(t *testing.T) {
  458. proto := nftables.TableFamilyIPv4
  459. want := [][]byte{
  460. // batch begin
  461. []byte("\x00\x00\x00\x0a"),
  462. // nft add table ip ts-filter-test
  463. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"),
  464. // nft add chain ip ts-filter-test ts-forward-test { type filter hook forward priority 0\; }
  465. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x14\x00\x03\x00\x74\x73\x2d\x66\x6f\x72\x77\x61\x72\x64\x2d\x74\x65\x73\x74\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x02\x08\x00\x02\x00\x00\x00\x00\x00\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"),
  466. // nft add rule ip ts-filter-test ts-forward-test meta mark and 0x00ff0000 eq 0x00040000 counter accept
  467. []byte("\x02\x00\x00\x00\x13\x00\x01\x00\x74\x73\x2d\x66\x69\x6c\x74\x65\x72\x2d\x74\x65\x73\x74\x00\x00\x14\x00\x02\x00\x74\x73\x2d\x66\x6f\x72\x77\x61\x72\x64\x2d\x74\x65\x73\x74\x00\xf4\x00\x04\x80\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x01\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\x00\xff\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x03\x80\x08\x00\x01\x00\x00\x04\x00\x00\x2c\x00\x01\x80\x0c\x00\x01\x00\x63\x6f\x75\x6e\x74\x65\x72\x00\x1c\x00\x02\x80\x0c\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x01\x80\x0e\x00\x01\x00\x69\x6d\x6d\x65\x64\x69\x61\x74\x65\x00\x00\x00\x1c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x00\x10\x00\x02\x80\x0c\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01"),
  468. // batch end
  469. []byte("\x00\x00\x00\x0a"),
  470. }
  471. testConn := newTestConn(t, want, nil)
  472. table := testConn.AddTable(&nftables.Table{
  473. Family: proto,
  474. Name: "ts-filter-test",
  475. })
  476. chain := testConn.AddChain(&nftables.Chain{
  477. Name: "ts-forward-test",
  478. Table: table,
  479. Type: nftables.ChainTypeFilter,
  480. Hooknum: nftables.ChainHookForward,
  481. Priority: nftables.ChainPriorityFilter,
  482. })
  483. err := addMatchSubnetRouteMarkRule(testConn, table, chain, Accept)
  484. if err != nil {
  485. t.Fatal(err)
  486. }
  487. }
  488. func newSysConn(t *testing.T) *nftables.Conn {
  489. t.Helper()
  490. if os.Geteuid() != 0 {
  491. t.Skip(t.Name(), " requires privileges to create a namespace in order to run")
  492. return nil
  493. }
  494. runtime.LockOSThread()
  495. ns, err := netns.New()
  496. if err != nil {
  497. t.Fatalf("netns.New() failed: %v", err)
  498. }
  499. c, err := nftables.New(nftables.WithNetNSFd(int(ns)))
  500. if err != nil {
  501. t.Fatalf("nftables.New() failed: %v", err)
  502. }
  503. t.Cleanup(func() { cleanupSysConn(t, ns) })
  504. return c
  505. }
  506. func cleanupSysConn(t *testing.T, ns netns.NsHandle) {
  507. defer runtime.UnlockOSThread()
  508. if err := ns.Close(); err != nil {
  509. t.Fatalf("newNS.Close() failed: %v", err)
  510. }
  511. }
  512. func checkChains(t *testing.T, conn *nftables.Conn, fam nftables.TableFamily, wantCount int) {
  513. t.Helper()
  514. got, err := conn.ListChainsOfTableFamily(fam)
  515. if err != nil {
  516. t.Fatalf("conn.ListChainsOfTableFamily(%v) failed: %v", fam, err)
  517. }
  518. if len(got) != wantCount {
  519. t.Fatalf("len(got) = %d, want %d", len(got), wantCount)
  520. }
  521. }
  522. func checkTables(t *testing.T, conn *nftables.Conn, fam nftables.TableFamily, wantCount int) {
  523. t.Helper()
  524. got, err := conn.ListTablesOfFamily(fam)
  525. if err != nil {
  526. t.Fatalf("conn.ListTablesOfFamily(%v) failed: %v", fam, err)
  527. }
  528. if len(got) != wantCount {
  529. t.Fatalf("len(got) = %d, want %d", len(got), wantCount)
  530. }
  531. }
  532. func TestAddAndDelNetfilterChains(t *testing.T) {
  533. type test struct {
  534. hostHasIPv6 bool
  535. initIPv4ChainCount int
  536. initIPv6ChainCount int
  537. ipv4TableCount int
  538. ipv6TableCount int
  539. ipv4ChainCount int
  540. ipv6ChainCount int
  541. ipv4ChainCountPostDelete int
  542. ipv6ChainCountPostDelete int
  543. }
  544. tests := []test{
  545. {
  546. hostHasIPv6: true,
  547. initIPv4ChainCount: 0,
  548. initIPv6ChainCount: 0,
  549. ipv4TableCount: 2,
  550. ipv6TableCount: 2,
  551. ipv4ChainCount: 6,
  552. ipv6ChainCount: 6,
  553. ipv4ChainCountPostDelete: 3,
  554. ipv6ChainCountPostDelete: 3,
  555. },
  556. { // host without IPv6 support
  557. ipv4TableCount: 2,
  558. ipv4ChainCount: 6,
  559. ipv4ChainCountPostDelete: 3,
  560. }}
  561. for _, tt := range tests {
  562. t.Logf("running a test case for IPv6 support: %v", tt.hostHasIPv6)
  563. conn := newSysConn(t)
  564. runner := newFakeNftablesRunnerWithConn(t, conn, tt.hostHasIPv6)
  565. // Check that we start off with no chains.
  566. checkChains(t, conn, nftables.TableFamilyIPv4, tt.initIPv4ChainCount)
  567. checkChains(t, conn, nftables.TableFamilyIPv6, tt.initIPv6ChainCount)
  568. if err := runner.AddChains(); err != nil {
  569. t.Fatalf("runner.AddChains() failed: %v", err)
  570. }
  571. // Check that the amount of tables for each IP family is as expected.
  572. checkTables(t, conn, nftables.TableFamilyIPv4, tt.ipv4TableCount)
  573. checkTables(t, conn, nftables.TableFamilyIPv6, tt.ipv6TableCount)
  574. // Check that the amount of chains for each IP family is as expected.
  575. checkChains(t, conn, nftables.TableFamilyIPv4, tt.ipv4ChainCount)
  576. checkChains(t, conn, nftables.TableFamilyIPv6, tt.ipv6ChainCount)
  577. if err := runner.DelChains(); err != nil {
  578. t.Fatalf("runner.DelChains() failed: %v", err)
  579. }
  580. // Test that the tables as well as the default chains are still present.
  581. checkChains(t, conn, nftables.TableFamilyIPv4, tt.ipv4ChainCountPostDelete)
  582. checkChains(t, conn, nftables.TableFamilyIPv6, tt.ipv6ChainCountPostDelete)
  583. checkTables(t, conn, nftables.TableFamilyIPv4, tt.ipv4TableCount)
  584. checkTables(t, conn, nftables.TableFamilyIPv6, tt.ipv6TableCount)
  585. }
  586. }
  587. func getTsChains(
  588. conn *nftables.Conn,
  589. proto nftables.TableFamily) (*nftables.Chain, *nftables.Chain, *nftables.Chain, error) {
  590. chains, err := conn.ListChainsOfTableFamily(nftables.TableFamilyIPv4)
  591. if err != nil {
  592. return nil, nil, nil, fmt.Errorf("list chains failed: %w", err)
  593. }
  594. var chainInput, chainForward, chainPostrouting *nftables.Chain
  595. for _, chain := range chains {
  596. switch chain.Name {
  597. case "ts-input":
  598. chainInput = chain
  599. case "ts-forward":
  600. chainForward = chain
  601. case "ts-postrouting":
  602. chainPostrouting = chain
  603. }
  604. }
  605. return chainInput, chainForward, chainPostrouting, nil
  606. }
  607. // findV4BaseRules verifies that the base rules are present in the input and forward chains.
  608. func findV4BaseRules(
  609. conn *nftables.Conn,
  610. inpChain *nftables.Chain,
  611. forwChain *nftables.Chain,
  612. tunname string) ([]*nftables.Rule, error) {
  613. want := []*nftables.Rule{}
  614. rule, err := createRangeRule(inpChain.Table, inpChain, tunname, tsaddr.ChromeOSVMRange(), expr.VerdictReturn)
  615. if err != nil {
  616. return nil, fmt.Errorf("create rule: %w", err)
  617. }
  618. want = append(want, rule)
  619. rule, err = createRangeRule(inpChain.Table, inpChain, tunname, tsaddr.CGNATRange(), expr.VerdictDrop)
  620. if err != nil {
  621. return nil, fmt.Errorf("create rule: %w", err)
  622. }
  623. want = append(want, rule)
  624. rule, err = createDropOutgoingPacketFromCGNATRangeRuleWithTunname(forwChain.Table, forwChain, tunname)
  625. if err != nil {
  626. return nil, fmt.Errorf("create rule: %w", err)
  627. }
  628. want = append(want, rule)
  629. get := []*nftables.Rule{}
  630. for _, rule := range want {
  631. getRule, err := findRule(conn, rule)
  632. if err != nil {
  633. return nil, fmt.Errorf("find rule: %w", err)
  634. }
  635. get = append(get, getRule)
  636. }
  637. return get, nil
  638. }
  639. func findCommonBaseRules(
  640. conn *nftables.Conn,
  641. forwChain *nftables.Chain,
  642. tunname string) ([]*nftables.Rule, error) {
  643. want := []*nftables.Rule{}
  644. rule, err := createSetSubnetRouteMarkRule(forwChain.Table, forwChain, tunname)
  645. if err != nil {
  646. return nil, fmt.Errorf("create rule: %w", err)
  647. }
  648. want = append(want, rule)
  649. rule, err = createMatchSubnetRouteMarkRule(forwChain.Table, forwChain, Accept)
  650. if err != nil {
  651. return nil, fmt.Errorf("create rule: %w", err)
  652. }
  653. want = append(want, rule)
  654. rule = createAcceptOutgoingPacketRule(forwChain.Table, forwChain, tunname)
  655. want = append(want, rule)
  656. get := []*nftables.Rule{}
  657. for _, rule := range want {
  658. getRule, err := findRule(conn, rule)
  659. if err != nil {
  660. return nil, fmt.Errorf("find rule: %w", err)
  661. }
  662. get = append(get, getRule)
  663. }
  664. return get, nil
  665. }
  666. // checkChainRules verifies that the chain has the expected number of rules.
  667. func checkChainRules(t *testing.T, conn *nftables.Conn, chain *nftables.Chain, wantCount int) {
  668. t.Helper()
  669. got, err := conn.GetRules(chain.Table, chain)
  670. if err != nil {
  671. t.Fatalf("conn.GetRules() failed: %v", err)
  672. }
  673. if len(got) != wantCount {
  674. t.Fatalf("got = %d, want %d", len(got), wantCount)
  675. }
  676. }
  677. func TestNFTAddAndDelNetfilterBase(t *testing.T) {
  678. conn := newSysConn(t)
  679. runner := newFakeNftablesRunnerWithConn(t, conn, true)
  680. if err := runner.AddChains(); err != nil {
  681. t.Fatalf("AddChains() failed: %v", err)
  682. }
  683. defer runner.DelChains()
  684. if err := runner.AddBase("testTunn"); err != nil {
  685. t.Fatalf("AddBase() failed: %v", err)
  686. }
  687. // check number of rules in each IPv4 TS chain
  688. inputV4, forwardV4, postroutingV4, err := getTsChains(conn, nftables.TableFamilyIPv4)
  689. if err != nil {
  690. t.Fatalf("getTsChains() failed: %v", err)
  691. }
  692. checkChainRules(t, conn, inputV4, 3)
  693. checkChainRules(t, conn, forwardV4, 4)
  694. checkChainRules(t, conn, postroutingV4, 0)
  695. _, err = findV4BaseRules(conn, inputV4, forwardV4, "testTunn")
  696. if err != nil {
  697. t.Fatalf("missing v4 base rule: %v", err)
  698. }
  699. _, err = findCommonBaseRules(conn, forwardV4, "testTunn")
  700. if err != nil {
  701. t.Fatalf("missing v4 base rule: %v", err)
  702. }
  703. // Check number of rules in each IPv6 TS chain.
  704. inputV6, forwardV6, postroutingV6, err := getTsChains(conn, nftables.TableFamilyIPv6)
  705. if err != nil {
  706. t.Fatalf("getTsChains() failed: %v", err)
  707. }
  708. checkChainRules(t, conn, inputV6, 3)
  709. checkChainRules(t, conn, forwardV6, 4)
  710. checkChainRules(t, conn, postroutingV6, 0)
  711. _, err = findCommonBaseRules(conn, forwardV6, "testTunn")
  712. if err != nil {
  713. t.Fatalf("missing v6 base rule: %v", err)
  714. }
  715. runner.DelBase()
  716. chains, err := conn.ListChains()
  717. if err != nil {
  718. t.Fatalf("conn.ListChains() failed: %v", err)
  719. }
  720. for _, chain := range chains {
  721. checkChainRules(t, conn, chain, 0)
  722. }
  723. }
  724. func findLoopBackRule(conn *nftables.Conn, proto nftables.TableFamily, table *nftables.Table, chain *nftables.Chain, addr netip.Addr) (*nftables.Rule, error) {
  725. matchingAddr := addr.AsSlice()
  726. saddrExpr, err := newLoadSaddrExpr(proto, 1)
  727. if err != nil {
  728. return nil, fmt.Errorf("get expr: %w", err)
  729. }
  730. loopBackRule := &nftables.Rule{
  731. Table: table,
  732. Chain: chain,
  733. Exprs: []expr.Any{
  734. &expr.Meta{
  735. Key: expr.MetaKeyIIFNAME,
  736. Register: 1,
  737. },
  738. &expr.Cmp{
  739. Op: expr.CmpOpEq,
  740. Register: 1,
  741. Data: []byte("lo"),
  742. },
  743. saddrExpr,
  744. &expr.Cmp{
  745. Op: expr.CmpOpEq,
  746. Register: 1,
  747. Data: matchingAddr,
  748. },
  749. &expr.Counter{},
  750. &expr.Verdict{
  751. Kind: expr.VerdictAccept,
  752. },
  753. },
  754. }
  755. existingLoopBackRule, err := findRule(conn, loopBackRule)
  756. if err != nil {
  757. return nil, fmt.Errorf("find loop back rule: %w", err)
  758. }
  759. return existingLoopBackRule, nil
  760. }
  761. func TestNFTAddAndDelLoopbackRule(t *testing.T) {
  762. conn := newSysConn(t)
  763. runner := newFakeNftablesRunnerWithConn(t, conn, true)
  764. if err := runner.AddChains(); err != nil {
  765. t.Fatalf("AddChains() failed: %v", err)
  766. }
  767. defer runner.DelChains()
  768. inputV4, _, _, err := getTsChains(conn, nftables.TableFamilyIPv4)
  769. if err != nil {
  770. t.Fatalf("getTsChains() failed: %v", err)
  771. }
  772. inputV6, _, _, err := getTsChains(conn, nftables.TableFamilyIPv6)
  773. if err != nil {
  774. t.Fatalf("getTsChains() failed: %v", err)
  775. }
  776. checkChainRules(t, conn, inputV4, 0)
  777. checkChainRules(t, conn, inputV6, 0)
  778. runner.AddBase("testTunn")
  779. defer runner.DelBase()
  780. checkChainRules(t, conn, inputV4, 3)
  781. checkChainRules(t, conn, inputV6, 3)
  782. addr := netip.MustParseAddr("192.168.0.2")
  783. addrV6 := netip.MustParseAddr("2001:db8::2")
  784. runner.AddLoopbackRule(addr)
  785. runner.AddLoopbackRule(addrV6)
  786. checkChainRules(t, conn, inputV4, 4)
  787. checkChainRules(t, conn, inputV6, 4)
  788. existingLoopBackRule, err := findLoopBackRule(conn, nftables.TableFamilyIPv4, runner.nft4.Filter, inputV4, addr)
  789. if err != nil {
  790. t.Fatalf("findLoopBackRule() failed: %v", err)
  791. }
  792. if existingLoopBackRule.Position != 0 {
  793. t.Fatalf("existingLoopBackRule.Handle = %d, want 0", existingLoopBackRule.Handle)
  794. }
  795. existingLoopBackRuleV6, err := findLoopBackRule(conn, nftables.TableFamilyIPv6, runner.nft6.Filter, inputV6, addrV6)
  796. if err != nil {
  797. t.Fatalf("findLoopBackRule() failed: %v", err)
  798. }
  799. if existingLoopBackRuleV6.Position != 0 {
  800. t.Fatalf("existingLoopBackRule.Handle = %d, want 0", existingLoopBackRule.Handle)
  801. }
  802. runner.DelLoopbackRule(addr)
  803. runner.DelLoopbackRule(addrV6)
  804. checkChainRules(t, conn, inputV4, 3)
  805. checkChainRules(t, conn, inputV6, 3)
  806. }
  807. func TestNFTAddAndDelHookRule(t *testing.T) {
  808. conn := newSysConn(t)
  809. runner := newFakeNftablesRunnerWithConn(t, conn, true)
  810. if err := runner.AddChains(); err != nil {
  811. t.Fatalf("AddChains() failed: %v", err)
  812. }
  813. defer runner.DelChains()
  814. if err := runner.AddHooks(); err != nil {
  815. t.Fatalf("AddHooks() failed: %v", err)
  816. }
  817. forwardChain, err := getChainFromTable(conn, runner.nft4.Filter, "FORWARD")
  818. if err != nil {
  819. t.Fatalf("failed to get forwardChain: %v", err)
  820. }
  821. inputChain, err := getChainFromTable(conn, runner.nft4.Filter, "INPUT")
  822. if err != nil {
  823. t.Fatalf("failed to get inputChain: %v", err)
  824. }
  825. postroutingChain, err := getChainFromTable(conn, runner.nft4.Nat, "POSTROUTING")
  826. if err != nil {
  827. t.Fatalf("failed to get postroutingChain: %v", err)
  828. }
  829. checkChainRules(t, conn, forwardChain, 1)
  830. checkChainRules(t, conn, inputChain, 1)
  831. checkChainRules(t, conn, postroutingChain, 1)
  832. runner.DelHooks(t.Logf)
  833. checkChainRules(t, conn, forwardChain, 0)
  834. checkChainRules(t, conn, inputChain, 0)
  835. checkChainRules(t, conn, postroutingChain, 0)
  836. }
  837. type testFWDetector struct {
  838. iptRuleCount, nftRuleCount int
  839. iptErr, nftErr error
  840. }
  841. func (t *testFWDetector) iptDetect() (int, error) {
  842. return t.iptRuleCount, t.iptErr
  843. }
  844. func (t *testFWDetector) nftDetect() (int, error) {
  845. return t.nftRuleCount, t.nftErr
  846. }
  847. // TestCreateDummyPostroutingChains tests that on a system with nftables
  848. // available, the function does not return an error and that the dummy
  849. // postrouting chains are cleaned up.
  850. func TestCreateDummyPostroutingChains(t *testing.T) {
  851. conn := newSysConn(t)
  852. runner := newFakeNftablesRunnerWithConn(t, conn, true)
  853. if err := runner.createDummyPostroutingChains(); err != nil {
  854. t.Fatalf("createDummyPostroutingChains() failed: %v", err)
  855. }
  856. for _, table := range runner.getTables() {
  857. nt, err := getTableIfExists(conn, table.Proto, tsDummyTableName)
  858. if err != nil {
  859. t.Fatalf("getTableIfExists() failed: %v", err)
  860. }
  861. if nt != nil {
  862. t.Fatalf("expected table to be nil, got %v", nt)
  863. }
  864. }
  865. }
  866. func TestPickFirewallModeFromInstalledRules(t *testing.T) {
  867. tests := []struct {
  868. name string
  869. det *testFWDetector
  870. want FirewallMode
  871. }{
  872. {
  873. name: "using iptables legacy",
  874. det: &testFWDetector{iptRuleCount: 1},
  875. want: FirewallModeIPTables,
  876. },
  877. {
  878. name: "using nftables",
  879. det: &testFWDetector{nftRuleCount: 1},
  880. want: FirewallModeNfTables,
  881. },
  882. {
  883. name: "using both iptables and nftables",
  884. det: &testFWDetector{iptRuleCount: 2, nftRuleCount: 2},
  885. want: FirewallModeNfTables,
  886. },
  887. {
  888. name: "not using any firewall, both available",
  889. det: &testFWDetector{},
  890. want: FirewallModeNfTables,
  891. },
  892. {
  893. name: "not using any firewall, iptables available only",
  894. det: &testFWDetector{iptRuleCount: 1, nftErr: errors.New("nft error")},
  895. want: FirewallModeIPTables,
  896. },
  897. {
  898. name: "not using any firewall, nftables available only",
  899. det: &testFWDetector{iptErr: errors.New("iptables error"), nftRuleCount: 1},
  900. want: FirewallModeNfTables,
  901. },
  902. }
  903. for _, tt := range tests {
  904. t.Run(tt.name, func(t *testing.T) {
  905. got := pickFirewallModeFromInstalledRules(t.Logf, tt.det)
  906. if got != tt.want {
  907. t.Errorf("chooseFireWallMode() = %v, want %v", got, tt.want)
  908. }
  909. })
  910. }
  911. }
  912. // This test creates a temporary network namespace for the nftables rules being
  913. // set up, so it needs to run in a privileged mode. Locally it needs to be run
  914. // by root, else it will be silently skipped. In CI it runs in a privileged
  915. // container.
  916. func TestEnsureSNATForDst_nftables(t *testing.T) {
  917. conn := newSysConn(t)
  918. runner := newFakeNftablesRunnerWithConn(t, conn, true)
  919. ip1, ip2, ip3 := netip.MustParseAddr("100.99.99.99"), netip.MustParseAddr("100.88.88.88"), netip.MustParseAddr("100.77.77.77")
  920. // 1. A new rule gets added
  921. mustCreateSNATRule_nft(t, runner, ip1, ip2)
  922. chainRuleCount(t, "POSTROUTING", 1, conn, nftables.TableFamilyIPv4)
  923. checkSNATRule_nft(t, runner, runner.nft4.Proto, ip1, ip2)
  924. // 2. Another call to EnsureSNATForDst with the same src and dst does not result in another rule being added.
  925. mustCreateSNATRule_nft(t, runner, ip1, ip2)
  926. chainRuleCount(t, "POSTROUTING", 1, conn, nftables.TableFamilyIPv4) // still just one rule
  927. checkSNATRule_nft(t, runner, runner.nft4.Proto, ip1, ip2)
  928. // 3. Another call to EnsureSNATForDst with a different src and the same dst results in the earlier rule being
  929. // deleted.
  930. mustCreateSNATRule_nft(t, runner, ip3, ip2)
  931. chainRuleCount(t, "POSTROUTING", 1, conn, nftables.TableFamilyIPv4) // still just one rule
  932. checkSNATRule_nft(t, runner, runner.nft4.Proto, ip3, ip2)
  933. // 4. Another call to EnsureSNATForDst with a different dst should not get the earlier rule deleted.
  934. mustCreateSNATRule_nft(t, runner, ip3, ip1)
  935. chainRuleCount(t, "POSTROUTING", 2, conn, nftables.TableFamilyIPv4) // now two rules
  936. checkSNATRule_nft(t, runner, runner.nft4.Proto, ip3, ip1)
  937. }
  938. func newFakeNftablesRunnerWithConn(t *testing.T, conn *nftables.Conn, hasIPv6 bool) *nftablesRunner {
  939. t.Helper()
  940. if !hasIPv6 {
  941. tstest.Replace(t, &checkIPv6ForTest, func(logger.Logf) error {
  942. return errors.New("test: no IPv6")
  943. })
  944. }
  945. return newNfTablesRunnerWithConn(t.Logf, conn)
  946. }
  947. func mustCreateSNATRule_nft(t *testing.T, runner *nftablesRunner, src, dst netip.Addr) {
  948. t.Helper()
  949. if err := runner.EnsureSNATForDst(src, dst); err != nil {
  950. t.Fatalf("error ensuring SNAT rule: %v", err)
  951. }
  952. }
  953. // checkSNATRule_nft verifies that a SNAT rule for the given destination and source exists.
  954. func checkSNATRule_nft(t *testing.T, runner *nftablesRunner, fam nftables.TableFamily, src, dst netip.Addr) {
  955. t.Helper()
  956. chains, err := runner.conn.ListChainsOfTableFamily(fam)
  957. if err != nil {
  958. t.Fatalf("error listing chains: %v", err)
  959. }
  960. var chain *nftables.Chain
  961. for _, ch := range chains {
  962. if ch.Name == "POSTROUTING" {
  963. chain = ch
  964. break
  965. }
  966. }
  967. if chain == nil {
  968. t.Fatal("POSTROUTING chain does not exist")
  969. }
  970. meta := []byte(fmt.Sprintf("dst:%s,src:%s", dst.String(), src.String()))
  971. wantsRule := snatRule(chain.Table, chain, src, dst, meta)
  972. checkRule(t, wantsRule, runner.conn)
  973. }