flowtable.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. // Copyright 2018 Google LLC. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package nftables
  15. import (
  16. "encoding/binary"
  17. "fmt"
  18. "github.com/google/nftables/binaryutil"
  19. "github.com/mdlayher/netlink"
  20. "golang.org/x/sys/unix"
  21. )
  22. const (
  23. // not in ztypes_linux.go, added here
  24. // https://cs.opensource.google/go/x/sys/+/c6bc011c:unix/ztypes_linux.go;l=1870-1892
  25. NFT_MSG_NEWFLOWTABLE = 0x16
  26. NFT_MSG_GETFLOWTABLE = 0x17
  27. NFT_MSG_DELFLOWTABLE = 0x18
  28. )
  29. const (
  30. // not in ztypes_linux.go, added here
  31. // https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n1634
  32. _ = iota
  33. NFTA_FLOWTABLE_TABLE
  34. NFTA_FLOWTABLE_NAME
  35. NFTA_FLOWTABLE_HOOK
  36. NFTA_FLOWTABLE_USE
  37. NFTA_FLOWTABLE_HANDLE
  38. NFTA_FLOWTABLE_PAD
  39. NFTA_FLOWTABLE_FLAGS
  40. )
  41. const (
  42. // not in ztypes_linux.go, added here
  43. // https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n1657
  44. _ = iota
  45. NFTA_FLOWTABLE_HOOK_NUM
  46. NFTA_FLOWTABLE_PRIORITY
  47. NFTA_FLOWTABLE_DEVS
  48. )
  49. const (
  50. // not in ztypes_linux.go, added here, used for flowtable device name specification
  51. // https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n1709
  52. NFTA_DEVICE_NAME = 1
  53. )
  54. type FlowtableFlags uint32
  55. const (
  56. _ FlowtableFlags = iota
  57. FlowtableFlagsHWOffload
  58. FlowtableFlagsCounter
  59. FlowtableFlagsMask = (FlowtableFlagsHWOffload | FlowtableFlagsCounter)
  60. )
  61. type FlowtableHook uint32
  62. func FlowtableHookRef(h FlowtableHook) *FlowtableHook {
  63. return &h
  64. }
  65. var (
  66. // Only ingress is supported
  67. // https://github.com/torvalds/linux/blob/b72018ab8236c3ae427068adeb94bdd3f20454ec/net/netfilter/nf_tables_api.c#L7378-L7379
  68. FlowtableHookIngress *FlowtableHook = FlowtableHookRef(unix.NF_NETDEV_INGRESS)
  69. )
  70. type FlowtablePriority int32
  71. func FlowtablePriorityRef(p FlowtablePriority) *FlowtablePriority {
  72. return &p
  73. }
  74. var (
  75. // As per man page:
  76. // The priority can be a signed integer or filter which stands for 0. Addition and subtraction can be used to set relative priority, e.g. filter + 5 equals to 5.
  77. // https://git.netfilter.org/nftables/tree/doc/nft.txt?id=8c600a843b7c0c1cc275ecc0603bd1fc57773e98#n712
  78. FlowtablePriorityFilter *FlowtablePriority = FlowtablePriorityRef(0)
  79. )
  80. type Flowtable struct {
  81. Table *Table
  82. Name string
  83. Hooknum *FlowtableHook
  84. Priority *FlowtablePriority
  85. Devices []string
  86. Use uint32
  87. // Bitmask flags, can be HW_OFFLOAD or COUNTER
  88. // https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=84d12cfacf8ddd857a09435f3d982ab6250d250c#n1621
  89. Flags FlowtableFlags
  90. Handle uint64
  91. }
  92. func (cc *Conn) AddFlowtable(f *Flowtable) *Flowtable {
  93. cc.mu.Lock()
  94. defer cc.mu.Unlock()
  95. data := cc.marshalAttr([]netlink.Attribute{
  96. {Type: NFTA_FLOWTABLE_TABLE, Data: []byte(f.Table.Name)},
  97. {Type: NFTA_FLOWTABLE_NAME, Data: []byte(f.Name)},
  98. {Type: NFTA_FLOWTABLE_FLAGS, Data: binaryutil.BigEndian.PutUint32(uint32(f.Flags))},
  99. })
  100. if f.Hooknum == nil {
  101. f.Hooknum = FlowtableHookIngress
  102. }
  103. if f.Priority == nil {
  104. f.Priority = FlowtablePriorityFilter
  105. }
  106. hookAttr := []netlink.Attribute{
  107. {Type: NFTA_FLOWTABLE_HOOK_NUM, Data: binaryutil.BigEndian.PutUint32(uint32(*f.Hooknum))},
  108. {Type: NFTA_FLOWTABLE_PRIORITY, Data: binaryutil.BigEndian.PutUint32(uint32(*f.Priority))},
  109. }
  110. if len(f.Devices) > 0 {
  111. devs := make([]netlink.Attribute, len(f.Devices))
  112. for i, d := range f.Devices {
  113. devs[i] = netlink.Attribute{Type: NFTA_DEVICE_NAME, Data: []byte(d)}
  114. }
  115. hookAttr = append(hookAttr, netlink.Attribute{
  116. Type: unix.NLA_F_NESTED | NFTA_FLOWTABLE_DEVS,
  117. Data: cc.marshalAttr(devs),
  118. })
  119. }
  120. data = append(data, cc.marshalAttr([]netlink.Attribute{
  121. {Type: unix.NLA_F_NESTED | NFTA_FLOWTABLE_HOOK, Data: cc.marshalAttr(hookAttr)},
  122. })...)
  123. cc.messages = append(cc.messages, netlink.Message{
  124. Header: netlink.Header{
  125. Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWFLOWTABLE),
  126. Flags: netlink.Request | netlink.Acknowledge | netlink.Create,
  127. },
  128. Data: append(extraHeader(uint8(f.Table.Family), 0), data...),
  129. })
  130. return f
  131. }
  132. func (cc *Conn) DelFlowtable(f *Flowtable) {
  133. cc.mu.Lock()
  134. defer cc.mu.Unlock()
  135. data := cc.marshalAttr([]netlink.Attribute{
  136. {Type: NFTA_FLOWTABLE_TABLE, Data: []byte(f.Table.Name)},
  137. {Type: NFTA_FLOWTABLE_NAME, Data: []byte(f.Name)},
  138. })
  139. cc.messages = append(cc.messages, netlink.Message{
  140. Header: netlink.Header{
  141. Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_DELFLOWTABLE),
  142. Flags: netlink.Request | netlink.Acknowledge,
  143. },
  144. Data: append(extraHeader(uint8(f.Table.Family), 0), data...),
  145. })
  146. }
  147. func (cc *Conn) ListFlowtables(t *Table) ([]*Flowtable, error) {
  148. reply, err := cc.getFlowtables(t)
  149. if err != nil {
  150. return nil, err
  151. }
  152. var fts []*Flowtable
  153. for _, msg := range reply {
  154. f, err := ftsFromMsg(msg)
  155. if err != nil {
  156. return nil, err
  157. }
  158. f.Table = t
  159. fts = append(fts, f)
  160. }
  161. return fts, nil
  162. }
  163. func (cc *Conn) getFlowtables(t *Table) ([]netlink.Message, error) {
  164. conn, closer, err := cc.netlinkConn()
  165. if err != nil {
  166. return nil, err
  167. }
  168. defer func() { _ = closer() }()
  169. attrs := []netlink.Attribute{
  170. {Type: NFTA_FLOWTABLE_TABLE, Data: []byte(t.Name + "\x00")},
  171. }
  172. data, err := netlink.MarshalAttributes(attrs)
  173. if err != nil {
  174. return nil, err
  175. }
  176. message := netlink.Message{
  177. Header: netlink.Header{
  178. Type: netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_GETFLOWTABLE),
  179. Flags: netlink.Request | netlink.Acknowledge | netlink.Dump,
  180. },
  181. Data: append(extraHeader(uint8(t.Family), 0), data...),
  182. }
  183. if _, err := conn.SendMessages([]netlink.Message{message}); err != nil {
  184. return nil, fmt.Errorf("SendMessages: %v", err)
  185. }
  186. reply, err := receiveAckAware(conn, message.Header.Flags)
  187. if err != nil {
  188. return nil, fmt.Errorf("Receive: %v", err)
  189. }
  190. return reply, nil
  191. }
  192. func ftsFromMsg(msg netlink.Message) (*Flowtable, error) {
  193. flowHeaderType := netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWFLOWTABLE)
  194. if got, want := msg.Header.Type, flowHeaderType; got != want {
  195. return nil, fmt.Errorf("unexpected header type: got %v, want %v", got, want)
  196. }
  197. ad, err := netlink.NewAttributeDecoder(msg.Data[4:])
  198. if err != nil {
  199. return nil, err
  200. }
  201. ad.ByteOrder = binary.BigEndian
  202. var ft Flowtable
  203. for ad.Next() {
  204. switch ad.Type() {
  205. case NFTA_FLOWTABLE_NAME:
  206. ft.Name = ad.String()
  207. case NFTA_FLOWTABLE_USE:
  208. ft.Use = ad.Uint32()
  209. case NFTA_FLOWTABLE_HANDLE:
  210. ft.Handle = ad.Uint64()
  211. case NFTA_FLOWTABLE_FLAGS:
  212. ft.Flags = FlowtableFlags(ad.Uint32())
  213. case NFTA_FLOWTABLE_HOOK:
  214. ad.Do(func(b []byte) error {
  215. ft.Hooknum, ft.Priority, ft.Devices, err = ftsHookFromMsg(b)
  216. return err
  217. })
  218. }
  219. }
  220. return &ft, nil
  221. }
  222. func ftsHookFromMsg(b []byte) (*FlowtableHook, *FlowtablePriority, []string, error) {
  223. ad, err := netlink.NewAttributeDecoder(b)
  224. if err != nil {
  225. return nil, nil, nil, err
  226. }
  227. ad.ByteOrder = binary.BigEndian
  228. var hooknum FlowtableHook
  229. var prio FlowtablePriority
  230. var devices []string
  231. for ad.Next() {
  232. switch ad.Type() {
  233. case NFTA_FLOWTABLE_HOOK_NUM:
  234. hooknum = FlowtableHook(ad.Uint32())
  235. case NFTA_FLOWTABLE_PRIORITY:
  236. prio = FlowtablePriority(ad.Uint32())
  237. case NFTA_FLOWTABLE_DEVS:
  238. ad.Do(func(b []byte) error {
  239. devices, err = devsFromMsg(b)
  240. return err
  241. })
  242. }
  243. }
  244. return &hooknum, &prio, devices, nil
  245. }
  246. func devsFromMsg(b []byte) ([]string, error) {
  247. ad, err := netlink.NewAttributeDecoder(b)
  248. if err != nil {
  249. return nil, err
  250. }
  251. ad.ByteOrder = binary.BigEndian
  252. devs := make([]string, 0)
  253. for ad.Next() {
  254. switch ad.Type() {
  255. case NFTA_DEVICE_NAME:
  256. devs = append(devs, ad.String())
  257. }
  258. }
  259. return devs, nil
  260. }