nftables.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  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. "encoding/binary"
  9. "fmt"
  10. "sort"
  11. "strings"
  12. "github.com/google/nftables"
  13. "github.com/google/nftables/expr"
  14. "github.com/google/nftables/xt"
  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. func init() {
  87. hookDetectNetfilter.Set(detectNetfilter)
  88. }
  89. // detectNetfilter returns the number of nftables rules present in the system.
  90. func detectNetfilter() (int, error) {
  91. // Frist try creating a dummy postrouting chain. Emperically, we have
  92. // noticed that on some devices there is partial nftables support and the
  93. // kernel rejects some chains that are valid on other devices. This is a
  94. // workaround to detect that case.
  95. //
  96. // This specifically allows us to run in on GKE nodes using COS images which
  97. // have partial nftables support (as of 2023-10-18). When we try to create a
  98. // dummy postrouting chain, we get an error like:
  99. // add chain: conn.Receive: netlink receive: no such file or directory
  100. nft, err := newNfTablesRunner(logger.Discard)
  101. if err != nil {
  102. return 0, FWModeNotSupportedError{
  103. Mode: FirewallModeNfTables,
  104. Err: fmt.Errorf("cannot create nftables runner: %w", err),
  105. }
  106. }
  107. if err := nft.createDummyPostroutingChains(); err != nil {
  108. return 0, FWModeNotSupportedError{
  109. Mode: FirewallModeNfTables,
  110. Err: err,
  111. }
  112. }
  113. conn, err := nftables.New()
  114. if err != nil {
  115. return 0, FWModeNotSupportedError{
  116. Mode: FirewallModeNfTables,
  117. Err: err,
  118. }
  119. }
  120. chains, err := conn.ListChains()
  121. if err != nil {
  122. return 0, FWModeNotSupportedError{
  123. Mode: FirewallModeNfTables,
  124. Err: fmt.Errorf("cannot list chains: %w", err),
  125. }
  126. }
  127. var validRules int
  128. for _, chain := range chains {
  129. rules, err := conn.GetRules(chain.Table, chain)
  130. if err != nil {
  131. continue
  132. }
  133. validRules += len(rules)
  134. }
  135. return validRules, nil
  136. }
  137. func printMatchInfo(name string, info xt.InfoAny) string {
  138. var sb strings.Builder
  139. sb.WriteString(`{`)
  140. var handled bool = true
  141. switch v := info.(type) {
  142. // TODO(andrew): we should support these common types
  143. //case *xt.ConntrackMtinfo3:
  144. //case *xt.ConntrackMtinfo2:
  145. case *xt.Tcp:
  146. fmt.Fprintf(&sb, "Src:%s Dst:%s", formatPortRange(v.SrcPorts), formatPortRange(v.DstPorts))
  147. if v.Option != 0 {
  148. fmt.Fprintf(&sb, " Option:%d", v.Option)
  149. }
  150. if v.FlagsMask != 0 {
  151. fmt.Fprintf(&sb, " FlagsMask:%d", v.FlagsMask)
  152. }
  153. if v.FlagsCmp != 0 {
  154. fmt.Fprintf(&sb, " FlagsCmp:%d", v.FlagsCmp)
  155. }
  156. if v.InvFlags != 0 {
  157. fmt.Fprintf(&sb, " InvFlags:%d", v.InvFlags)
  158. }
  159. case *xt.Udp:
  160. fmt.Fprintf(&sb, "Src:%s Dst:%s", formatPortRange(v.SrcPorts), formatPortRange(v.DstPorts))
  161. if v.InvFlags != 0 {
  162. fmt.Fprintf(&sb, " InvFlags:%d", v.InvFlags)
  163. }
  164. case *xt.AddrType:
  165. var sprefix, dprefix string
  166. if v.InvertSource {
  167. sprefix = "!"
  168. }
  169. if v.InvertDest {
  170. dprefix = "!"
  171. }
  172. // TODO(andrew): translate source/dest
  173. fmt.Fprintf(&sb, "Source:%s%d Dest:%s%d", sprefix, v.Source, dprefix, v.Dest)
  174. case *xt.AddrTypeV1:
  175. // TODO(andrew): translate source/dest
  176. fmt.Fprintf(&sb, "Source:%d Dest:%d", v.Source, v.Dest)
  177. var flags []string
  178. for flag, name := range addrTypeFlagNames {
  179. if v.Flags&flag != 0 {
  180. flags = append(flags, name)
  181. }
  182. }
  183. if len(flags) > 0 {
  184. sort.Strings(flags)
  185. fmt.Fprintf(&sb, "Flags:%s", strings.Join(flags, ","))
  186. }
  187. default:
  188. handled = false
  189. }
  190. if handled {
  191. sb.WriteString(`}`)
  192. return sb.String()
  193. }
  194. unknown, ok := info.(*xt.Unknown)
  195. if !ok {
  196. return fmt.Sprintf("(%T)%+v", info, info)
  197. }
  198. data := []byte(*unknown)
  199. // Things where upstream has no type
  200. handled = true
  201. switch name {
  202. case "pkttype":
  203. if len(data) != 8 {
  204. handled = false
  205. break
  206. }
  207. pkttype := int(binary.NativeEndian.Uint32(data[0:4]))
  208. invert := int(binary.NativeEndian.Uint32(data[4:8]))
  209. var invertPrefix string
  210. if invert != 0 {
  211. invertPrefix = "!"
  212. }
  213. pkttypeName := packetTypeNames[pkttype]
  214. if pkttypeName != "" {
  215. fmt.Fprintf(&sb, "PktType:%s%s", invertPrefix, pkttypeName)
  216. } else {
  217. fmt.Fprintf(&sb, "PktType:%s%d", invertPrefix, pkttype)
  218. }
  219. default:
  220. handled = true
  221. }
  222. if !handled {
  223. return fmt.Sprintf("(%T)%+v", info, info)
  224. }
  225. sb.WriteString(`}`)
  226. return sb.String()
  227. }
  228. func printTargetInfo(name string, info xt.InfoAny) string {
  229. var sb strings.Builder
  230. sb.WriteString(`{`)
  231. unknown, ok := info.(*xt.Unknown)
  232. if !ok {
  233. return fmt.Sprintf("(%T)%+v", info, info)
  234. }
  235. data := []byte(*unknown)
  236. // Things where upstream has no type
  237. switch name {
  238. case "LOG":
  239. if len(data) != 32 {
  240. fmt.Fprintf(&sb, `Error:"bad size; want 32, got %d"`, len(data))
  241. break
  242. }
  243. level := data[0]
  244. logflags := data[1]
  245. prefix := unix.ByteSliceToString(data[2:])
  246. fmt.Fprintf(&sb, "Level:%d LogFlags:%d Prefix:%q", level, logflags, prefix)
  247. default:
  248. return fmt.Sprintf("(%T)%+v", info, info)
  249. }
  250. sb.WriteString(`}`)
  251. return sb.String()
  252. }