nftables.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // TODO(#8502): add support for more architectures
  4. //go:build linux && (arm64 || amd64)
  5. package linuxfw
  6. import (
  7. "cmp"
  8. "fmt"
  9. "sort"
  10. "strings"
  11. "github.com/google/nftables"
  12. "github.com/google/nftables/expr"
  13. "github.com/google/nftables/xt"
  14. "github.com/josharian/native"
  15. "golang.org/x/sys/unix"
  16. "tailscale.com/types/logger"
  17. )
  18. // DebugNetfilter prints debug information about netfilter rules to the
  19. // provided log function.
  20. func DebugNetfilter(logf logger.Logf) error {
  21. conn, err := nftables.New()
  22. if err != nil {
  23. return err
  24. }
  25. chains, err := conn.ListChains()
  26. if err != nil {
  27. return fmt.Errorf("cannot list chains: %w", err)
  28. }
  29. if len(chains) == 0 {
  30. logf("netfilter: no chains")
  31. return nil
  32. }
  33. for _, chain := range chains {
  34. logf("netfilter: table=%s chain=%s", chain.Table.Name, chain.Name)
  35. rules, err := conn.GetRules(chain.Table, chain)
  36. if err != nil {
  37. continue
  38. }
  39. sort.Slice(rules, func(i, j int) bool {
  40. return rules[i].Position < rules[j].Position
  41. })
  42. for i, rule := range rules {
  43. logf("netfilter: rule[%d]: pos=%d flags=%d", i, rule.Position, rule.Flags)
  44. for _, ex := range rule.Exprs {
  45. switch v := ex.(type) {
  46. case *expr.Meta:
  47. key := cmp.Or(metaKeyNames[v.Key], "UNKNOWN")
  48. logf("netfilter: Meta: key=%s source_register=%v register=%d", key, v.SourceRegister, v.Register)
  49. case *expr.Cmp:
  50. op := cmp.Or(cmpOpNames[v.Op], "UNKNOWN")
  51. logf("netfilter: Cmp: op=%s register=%d data=%s", op, v.Register, formatMaybePrintable(v.Data))
  52. case *expr.Counter:
  53. // don't print
  54. case *expr.Verdict:
  55. kind := cmp.Or(verdictNames[v.Kind], "UNKNOWN")
  56. logf("netfilter: Verdict: kind=%s data=%s", kind, v.Chain)
  57. case *expr.Target:
  58. logf("netfilter: Target: name=%s info=%s", v.Name, printTargetInfo(v.Name, v.Info))
  59. case *expr.Match:
  60. logf("netfilter: Match: name=%s info=%+v", v.Name, printMatchInfo(v.Name, v.Info))
  61. case *expr.Payload:
  62. logf("netfilter: Payload: op=%s src=%d dst=%d base=%s offset=%d len=%d",
  63. payloadOperationTypeNames[v.OperationType],
  64. v.SourceRegister, v.DestRegister,
  65. payloadBaseNames[v.Base],
  66. v.Offset, v.Len)
  67. // TODO(andrew): csum
  68. case *expr.Bitwise:
  69. var xor string
  70. for _, b := range v.Xor {
  71. if b != 0 {
  72. xor = fmt.Sprintf(" xor=%v", v.Xor)
  73. break
  74. }
  75. }
  76. logf("netfilter: Bitwise: src=%d dst=%d len=%d mask=%v%s",
  77. v.SourceRegister, v.DestRegister, v.Len, v.Mask, xor)
  78. default:
  79. logf("netfilter: unknown %T: %+v", v, v)
  80. }
  81. }
  82. }
  83. }
  84. return nil
  85. }
  86. // detectNetfilter returns the number of nftables rules present in the system.
  87. func detectNetfilter() (int, error) {
  88. // Frist try creating a dummy postrouting chain. Emperically, we have
  89. // noticed that on some devices there is partial nftables support and the
  90. // kernel rejects some chains that are valid on other devices. This is a
  91. // workaround to detect that case.
  92. //
  93. // This specifically allows us to run in on GKE nodes using COS images which
  94. // have partial nftables support (as of 2023-10-18). When we try to create a
  95. // dummy postrouting chain, we get an error like:
  96. // add chain: conn.Receive: netlink receive: no such file or directory
  97. nft, err := newNfTablesRunner(logger.Discard)
  98. if err != nil {
  99. return 0, FWModeNotSupportedError{
  100. Mode: FirewallModeNfTables,
  101. Err: fmt.Errorf("cannot create nftables runner: %w", err),
  102. }
  103. }
  104. if err := nft.createDummyPostroutingChains(); err != nil {
  105. return 0, FWModeNotSupportedError{
  106. Mode: FirewallModeNfTables,
  107. Err: err,
  108. }
  109. }
  110. conn, err := nftables.New()
  111. if err != nil {
  112. return 0, FWModeNotSupportedError{
  113. Mode: FirewallModeNfTables,
  114. Err: err,
  115. }
  116. }
  117. chains, err := conn.ListChains()
  118. if err != nil {
  119. return 0, FWModeNotSupportedError{
  120. Mode: FirewallModeNfTables,
  121. Err: fmt.Errorf("cannot list chains: %w", err),
  122. }
  123. }
  124. var validRules int
  125. for _, chain := range chains {
  126. rules, err := conn.GetRules(chain.Table, chain)
  127. if err != nil {
  128. continue
  129. }
  130. validRules += len(rules)
  131. }
  132. return validRules, nil
  133. }
  134. func printMatchInfo(name string, info xt.InfoAny) string {
  135. var sb strings.Builder
  136. sb.WriteString(`{`)
  137. var handled bool = true
  138. switch v := info.(type) {
  139. // TODO(andrew): we should support these common types
  140. //case *xt.ConntrackMtinfo3:
  141. //case *xt.ConntrackMtinfo2:
  142. case *xt.Tcp:
  143. fmt.Fprintf(&sb, "Src:%s Dst:%s", formatPortRange(v.SrcPorts), formatPortRange(v.DstPorts))
  144. if v.Option != 0 {
  145. fmt.Fprintf(&sb, " Option:%d", v.Option)
  146. }
  147. if v.FlagsMask != 0 {
  148. fmt.Fprintf(&sb, " FlagsMask:%d", v.FlagsMask)
  149. }
  150. if v.FlagsCmp != 0 {
  151. fmt.Fprintf(&sb, " FlagsCmp:%d", v.FlagsCmp)
  152. }
  153. if v.InvFlags != 0 {
  154. fmt.Fprintf(&sb, " InvFlags:%d", v.InvFlags)
  155. }
  156. case *xt.Udp:
  157. fmt.Fprintf(&sb, "Src:%s Dst:%s", formatPortRange(v.SrcPorts), formatPortRange(v.DstPorts))
  158. if v.InvFlags != 0 {
  159. fmt.Fprintf(&sb, " InvFlags:%d", v.InvFlags)
  160. }
  161. case *xt.AddrType:
  162. var sprefix, dprefix string
  163. if v.InvertSource {
  164. sprefix = "!"
  165. }
  166. if v.InvertDest {
  167. dprefix = "!"
  168. }
  169. // TODO(andrew): translate source/dest
  170. fmt.Fprintf(&sb, "Source:%s%d Dest:%s%d", sprefix, v.Source, dprefix, v.Dest)
  171. case *xt.AddrTypeV1:
  172. // TODO(andrew): translate source/dest
  173. fmt.Fprintf(&sb, "Source:%d Dest:%d", v.Source, v.Dest)
  174. var flags []string
  175. for flag, name := range addrTypeFlagNames {
  176. if v.Flags&flag != 0 {
  177. flags = append(flags, name)
  178. }
  179. }
  180. if len(flags) > 0 {
  181. sort.Strings(flags)
  182. fmt.Fprintf(&sb, "Flags:%s", strings.Join(flags, ","))
  183. }
  184. default:
  185. handled = false
  186. }
  187. if handled {
  188. sb.WriteString(`}`)
  189. return sb.String()
  190. }
  191. unknown, ok := info.(*xt.Unknown)
  192. if !ok {
  193. return fmt.Sprintf("(%T)%+v", info, info)
  194. }
  195. data := []byte(*unknown)
  196. // Things where upstream has no type
  197. handled = true
  198. switch name {
  199. case "pkttype":
  200. if len(data) != 8 {
  201. handled = false
  202. break
  203. }
  204. pkttype := int(native.Endian.Uint32(data[0:4]))
  205. invert := int(native.Endian.Uint32(data[4:8]))
  206. var invertPrefix string
  207. if invert != 0 {
  208. invertPrefix = "!"
  209. }
  210. pkttypeName := packetTypeNames[pkttype]
  211. if pkttypeName != "" {
  212. fmt.Fprintf(&sb, "PktType:%s%s", invertPrefix, pkttypeName)
  213. } else {
  214. fmt.Fprintf(&sb, "PktType:%s%d", invertPrefix, pkttype)
  215. }
  216. default:
  217. handled = true
  218. }
  219. if !handled {
  220. return fmt.Sprintf("(%T)%+v", info, info)
  221. }
  222. sb.WriteString(`}`)
  223. return sb.String()
  224. }
  225. func printTargetInfo(name string, info xt.InfoAny) string {
  226. var sb strings.Builder
  227. sb.WriteString(`{`)
  228. unknown, ok := info.(*xt.Unknown)
  229. if !ok {
  230. return fmt.Sprintf("(%T)%+v", info, info)
  231. }
  232. data := []byte(*unknown)
  233. // Things where upstream has no type
  234. switch name {
  235. case "LOG":
  236. if len(data) != 32 {
  237. fmt.Fprintf(&sb, `Error:"bad size; want 32, got %d"`, len(data))
  238. break
  239. }
  240. level := data[0]
  241. logflags := data[1]
  242. prefix := unix.ByteSliceToString(data[2:])
  243. fmt.Fprintf(&sb, "Level:%d LogFlags:%d Prefix:%q", level, logflags, prefix)
  244. default:
  245. return fmt.Sprintf("(%T)%+v", info, info)
  246. }
  247. sb.WriteString(`}`)
  248. return sb.String()
  249. }