nftables_runner_test.go 46 KB

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