Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add monitor on table chain rule set setelem and obj events #250

Merged
merged 4 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,10 @@ func (cc *Conn) ListChainsOfTableFamily(family TableFamily) ([]*Chain, error) {
}

func chainFromMsg(msg netlink.Message) (*Chain, error) {
chainHeaderType := netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWCHAIN)
if got, want := msg.Header.Type, chainHeaderType; got != want {
return nil, fmt.Errorf("unexpected header type: got %v, want %v", got, want)
newChainHeaderType := netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_NEWCHAIN)
delChainHeaderType := netlink.HeaderType((unix.NFNL_SUBSYS_NFTABLES << 8) | unix.NFT_MSG_DELCHAIN)
if got, want1, want2 := msg.Header.Type, newChainHeaderType, delChainHeaderType; got != want1 && got != want2 {
return nil, fmt.Errorf("unexpected header type: got %v, want %v or %v", got, want1, want2)
}

var c Chain
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/google/nftables

go 1.17
go 1.18

require (
github.com/mdlayher/netlink v1.7.1
Expand Down
309 changes: 309 additions & 0 deletions monitor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
// Copyright 2018 Google LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package nftables

import (
"math"
"strings"
"sync"

"github.com/mdlayher/netlink"
"golang.org/x/sys/unix"
)

type MonitorAction uint8

// Possible MonitorAction values.
const (
MonitorActionNew MonitorAction = 1 << iota
MonitorActionDel
MonitorActionMask MonitorAction = (1 << iota) - 1
MonitorActionAny MonitorAction = MonitorActionMask
)

type MonitorObject uint32

// Possible MonitorObject values.
const (
MonitorObjectTables MonitorObject = 1 << iota
MonitorObjectChains
MonitorObjectSets
MonitorObjectRules
MonitorObjectElements
MonitorObjectRuleset
MonitorObjectMask MonitorObject = (1 << iota) - 1
MonitorObjectAny MonitorObject = MonitorObjectMask
)

var (
monitorFlags = map[MonitorAction]map[MonitorObject]uint32{
MonitorActionAny: {
MonitorObjectAny: 0xffffffff,
MonitorObjectTables: 1<<unix.NFT_MSG_NEWTABLE | 1<<unix.NFT_MSG_DELTABLE,
MonitorObjectChains: 1<<unix.NFT_MSG_NEWCHAIN | 1<<unix.NFT_MSG_DELCHAIN,
MonitorObjectRules: 1<<unix.NFT_MSG_NEWRULE | 1<<unix.NFT_MSG_DELRULE,
MonitorObjectSets: 1<<unix.NFT_MSG_NEWSET | 1<<unix.NFT_MSG_DELSET,
MonitorObjectElements: 1<<unix.NFT_MSG_NEWSETELEM | 1<<unix.NFT_MSG_DELSETELEM,
MonitorObjectRuleset: 1<<unix.NFT_MSG_NEWTABLE | 1<<unix.NFT_MSG_DELTABLE |
1<<unix.NFT_MSG_NEWCHAIN | 1<<unix.NFT_MSG_DELCHAIN |
1<<unix.NFT_MSG_NEWRULE | 1<<unix.NFT_MSG_DELRULE |
1<<unix.NFT_MSG_NEWSET | 1<<unix.NFT_MSG_DELSET |
1<<unix.NFT_MSG_NEWSETELEM | 1<<unix.NFT_MSG_DELSETELEM |
1<<unix.NFT_MSG_NEWOBJ | 1<<unix.NFT_MSG_DELOBJ,
},
MonitorActionNew: {
MonitorObjectAny: 1<<unix.NFT_MSG_NEWTABLE |
1<<unix.NFT_MSG_NEWCHAIN |
1<<unix.NFT_MSG_NEWRULE |
1<<unix.NFT_MSG_NEWSET |
1<<unix.NFT_MSG_NEWSETELEM,
MonitorObjectTables: 1 << unix.NFT_MSG_NEWTABLE,
MonitorObjectChains: 1 << unix.NFT_MSG_NEWCHAIN,
MonitorObjectRules: 1 << unix.NFT_MSG_NEWRULE,
MonitorObjectSets: 1 << unix.NFT_MSG_NEWSET,
MonitorObjectRuleset: 1<<unix.NFT_MSG_NEWTABLE |
1<<unix.NFT_MSG_NEWCHAIN |
1<<unix.NFT_MSG_NEWRULE |
1<<unix.NFT_MSG_NEWSET |
1<<unix.NFT_MSG_NEWSETELEM |
1<<unix.NFT_MSG_NEWOBJ,
},
MonitorActionDel: {
MonitorObjectAny: 1<<unix.NFT_MSG_DELTABLE |
1<<unix.NFT_MSG_DELCHAIN |
1<<unix.NFT_MSG_DELRULE |
1<<unix.NFT_MSG_DELSET |
1<<unix.NFT_MSG_DELSETELEM |
1<<unix.NFT_MSG_DELOBJ,
},
}
monitorFlagsInitOnce sync.Once
)

type MonitorEventType int

const (
MonitorEventTypeNewTable MonitorEventType = unix.NFT_MSG_NEWTABLE
MonitorEventTypeDelTable MonitorEventType = unix.NFT_MSG_DELTABLE
MonitorEventTypeNewChain MonitorEventType = unix.NFT_MSG_NEWCHAIN
MonitorEventTypeDelChain MonitorEventType = unix.NFT_MSG_DELCHAIN
MonitorEventTypeNewRule MonitorEventType = unix.NFT_MSG_NEWRULE
MonitorEventTypeDelRule MonitorEventType = unix.NFT_MSG_DELRULE
MonitorEventTypeNewSet MonitorEventType = unix.NFT_MSG_NEWSET
MonitorEventTypeDelSet MonitorEventType = unix.NFT_MSG_DELSET
MonitorEventTypeNewSetElem MonitorEventType = unix.NFT_MSG_NEWSETELEM
MonitorEventTypeDelSetElem MonitorEventType = unix.NFT_MSG_DELSETELEM
MonitorEventTypeNewObj MonitorEventType = unix.NFT_MSG_NEWOBJ
MonitorEventTypeDelObj MonitorEventType = unix.NFT_MSG_DELOBJ
MonitorEventTypeOOB MonitorEventType = math.MaxInt // out of band event
)

type MonitorEvent struct {
Type MonitorEventType
Data any
Error error
}

const (
monitorOK = iota
monitorClosed
)

// A Monitor to track actions on objects.
type Monitor struct {
action MonitorAction
object MonitorObject
monitorFlags uint32

conn *netlink.Conn
closer netlinkCloser

// mu covers eventCh and status
mu sync.Mutex
eventCh chan *MonitorEvent
status int
}

type MonitorOption func(*Monitor)

func WithMonitorEventBuffer(size int) MonitorOption {
return func(monitor *Monitor) {
monitor.eventCh = make(chan *MonitorEvent, size)
}
}

// WithMonitorAction to set monitor actions like new, del or any.
func WithMonitorAction(action MonitorAction) MonitorOption {
return func(monitor *Monitor) {
monitor.action = action
}
}

// WithMonitorObject to set monitor objects.
func WithMonitorObject(object MonitorObject) MonitorOption {
return func(monitor *Monitor) {
monitor.object = object
}
}

// NewMonitor returns a Monitor with options to be started.
func NewMonitor(opts ...MonitorOption) *Monitor {
monitor := &Monitor{
status: monitorOK,
}
for _, opt := range opts {
opt(monitor)
}
if monitor.eventCh == nil {
monitor.eventCh = make(chan *MonitorEvent)
}
objects, ok := monitorFlags[monitor.action]
if !ok {
objects = monitorFlags[MonitorActionAny]
}
flags, ok := objects[monitor.object]
if !ok {
flags = objects[MonitorObjectAny]
}
monitor.monitorFlags = flags
return monitor
}

func (monitor *Monitor) monitor() {
for {
msgs, err := monitor.conn.Receive()
if err != nil {
if strings.Contains(err.Error(), "use of closed file") {
// ignore the error that be closed
break
} else {
// any other errors will be send to user, and then to close eventCh
event := &MonitorEvent{
Type: MonitorEventTypeOOB,
Data: nil,
Error: err,
}
monitor.eventCh <- event
break
}
}
for _, msg := range msgs {
if msg.Header.Type&0xff00>>8 != netlink.HeaderType(unix.NFNL_SUBSYS_NFTABLES) {
continue
}
msgType := msg.Header.Type & 0x00ff
if monitor.monitorFlags&1<<msgType == 0 {
continue
}
switch msgType {
case unix.NFT_MSG_NEWTABLE, unix.NFT_MSG_DELTABLE:
table, err := tableFromMsg(msg)
event := &MonitorEvent{
Type: MonitorEventType(msgType),
Data: table,
Error: err,
}
monitor.eventCh <- event
case unix.NFT_MSG_NEWCHAIN, unix.NFT_MSG_DELCHAIN:
chain, err := chainFromMsg(msg)
event := &MonitorEvent{
Type: MonitorEventType(msgType),
Data: chain,
Error: err,
}
monitor.eventCh <- event
case unix.NFT_MSG_NEWRULE, unix.NFT_MSG_DELRULE:
rule, err := parseRuleFromMsg(msg)
event := &MonitorEvent{
Type: MonitorEventType(msgType),
Data: rule,
Error: err,
}
monitor.eventCh <- event
case unix.NFT_MSG_NEWSET, unix.NFT_MSG_DELSET:
set, err := setsFromMsg(msg)
event := &MonitorEvent{
Type: MonitorEventType(msgType),
Data: set,
Error: err,
}
monitor.eventCh <- event
case unix.NFT_MSG_NEWSETELEM, unix.NFT_MSG_DELSETELEM:
elems, err := elementsFromMsg(uint8(TableFamilyUnspecified), msg)
event := &MonitorEvent{
Type: MonitorEventType(msgType),
Data: elems,
Error: err,
}
monitor.eventCh <- event
case unix.NFT_MSG_NEWOBJ, unix.NFT_MSG_DELOBJ:
obj, err := objFromMsg(msg)
event := &MonitorEvent{
Type: MonitorEventType(msgType),
Data: obj,
Error: err,
}
monitor.eventCh <- event
}
}
}
monitor.mu.Lock()
defer monitor.mu.Unlock()

if monitor.status != monitorClosed {
monitor.status = monitorClosed
}
close(monitor.eventCh)
}

func (monitor *Monitor) Close() error {
monitor.mu.Lock()
defer monitor.mu.Unlock()

if monitor.status != monitorClosed {
monitor.status = monitorClosed
return monitor.closer()
}
return nil
}

// AddMonitor to perform the monitor immediately. The channel will be closed after
// calling Close on Monitor or encountering a netlink conn error while Receive.
// Caller may receive a MonitorEventTypeOOB event which contains an error we didn't
// handle, for now.
func (cc *Conn) AddMonitor(monitor *Monitor) (chan *MonitorEvent, error) {
conn, closer, err := cc.netlinkConn()
if err != nil {
return nil, err
}
monitor.conn = conn
monitor.closer = closer

if monitor.monitorFlags != 0 {
if err = conn.JoinGroup(uint32(unix.NFNLGRP_NFTABLES)); err != nil {
monitor.closer()
return nil, err
}
}

go monitor.monitor()
return monitor.eventCh, nil
}

func parseRuleFromMsg(msg netlink.Message) (*Rule, error) {
genmsg := &NFGenMsg{}
genmsg.Decode(msg.Data[:4])
return ruleFromMsg(TableFamily(genmsg.NFGenFamily), msg)
}
Loading