nftables/expr/ct.go

401 lines
11 KiB
Go

// 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 expr
import (
"encoding/binary"
"github.com/google/nftables/binaryutil"
"github.com/mdlayher/netlink"
"golang.org/x/sys/unix"
)
// CtKey specifies which piece of conntrack information should be loaded. See
// also https://wiki.nftables.org/wiki-nftables/index.php/Matching_connection_tracking_stateful_metainformation
type CtKey uint32
// Possible CtKey values.
const (
CtKeySTATE CtKey = unix.NFT_CT_STATE
CtKeyDIRECTION CtKey = unix.NFT_CT_DIRECTION
CtKeySTATUS CtKey = unix.NFT_CT_STATUS
CtKeyMARK CtKey = unix.NFT_CT_MARK
CtKeySECMARK CtKey = unix.NFT_CT_SECMARK
CtKeyEXPIRATION CtKey = unix.NFT_CT_EXPIRATION
CtKeyHELPER CtKey = unix.NFT_CT_HELPER
CtKeyL3PROTOCOL CtKey = unix.NFT_CT_L3PROTOCOL
CtKeySRC CtKey = unix.NFT_CT_SRC
CtKeyDST CtKey = unix.NFT_CT_DST
CtKeyPROTOCOL CtKey = unix.NFT_CT_PROTOCOL
CtKeyPROTOSRC CtKey = unix.NFT_CT_PROTO_SRC
CtKeyPROTODST CtKey = unix.NFT_CT_PROTO_DST
CtKeyLABELS CtKey = unix.NFT_CT_LABELS
CtKeyPKTS CtKey = unix.NFT_CT_PKTS
CtKeyBYTES CtKey = unix.NFT_CT_BYTES
CtKeyAVGPKT CtKey = unix.NFT_CT_AVGPKT
CtKeyZONE CtKey = unix.NFT_CT_ZONE
CtKeyEVENTMASK CtKey = unix.NFT_CT_EVENTMASK
// https://sources.debian.org/src//nftables/0.9.8-3/src/ct.c/?hl=39#L39
CtStateBitINVALID uint32 = 1
CtStateBitESTABLISHED uint32 = 2
CtStateBitRELATED uint32 = 4
CtStateBitNEW uint32 = 8
CtStateBitUNTRACKED uint32 = 64
)
// Missing ct timeout consts
// https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=be0bae0ad31b0adb506f96de083f52a2bd0d4fbf#n1592
const (
NFTA_CT_TIMEOUT_L3PROTO = 0x01
NFTA_CT_TIMEOUT_L4PROTO = 0x02
NFTA_CT_TIMEOUT_DATA = 0x03
)
type CtStatePolicyTimeout map[uint16]uint32
const (
// https://git.netfilter.org/libnftnl/tree/src/obj/ct_timeout.c?id=116e95aa7b6358c917de8c69f6f173874030b46b#n24
CtStateTCPSYNSENT = iota
CtStateTCPSYNRECV
CtStateTCPESTABLISHED
CtStateTCPFINWAIT
CtStateTCPCLOSEWAIT
CtStateTCPLASTACK
CtStateTCPTIMEWAIT
CtStateTCPCLOSE
CtStateTCPSYNSENT2
CtStateTCPRETRANS
CtStateTCPUNACK
)
// https://git.netfilter.org/libnftnl/tree/src/obj/ct_timeout.c?id=116e95aa7b6358c917de8c69f6f173874030b46b#n38
var CtStateTCPTimeoutDefaults CtStatePolicyTimeout = map[uint16]uint32{
CtStateTCPSYNSENT: 120,
CtStateTCPSYNRECV: 60,
CtStateTCPESTABLISHED: 43200,
CtStateTCPFINWAIT: 120,
CtStateTCPCLOSEWAIT: 60,
CtStateTCPLASTACK: 30,
CtStateTCPTIMEWAIT: 120,
CtStateTCPCLOSE: 10,
CtStateTCPSYNSENT2: 120,
CtStateTCPRETRANS: 300,
CtStateTCPUNACK: 300,
}
const (
// https://git.netfilter.org/libnftnl/tree/src/obj/ct_timeout.c?id=116e95aa7b6358c917de8c69f6f173874030b46b#n57
CtStateUDPUNREPLIED = iota
CtStateUDPREPLIED
)
// https://git.netfilter.org/libnftnl/tree/src/obj/ct_timeout.c?id=116e95aa7b6358c917de8c69f6f173874030b46b#n57
var CtStateUDPTimeoutDefaults CtStatePolicyTimeout = map[uint16]uint32{
CtStateUDPUNREPLIED: 30,
CtStateUDPREPLIED: 180,
}
// Ct defines type for NFT connection tracking
type Ct struct {
Register uint32
SourceRegister bool
Key CtKey
Direction uint32
}
func (e *Ct) marshal(fam byte) ([]byte, error) {
exprData, err := e.marshalData(fam)
if err != nil {
return nil, err
}
return netlink.MarshalAttributes([]netlink.Attribute{
{Type: unix.NFTA_EXPR_NAME, Data: []byte("ct\x00")},
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: exprData},
})
}
func (e *Ct) marshalData(fam byte) ([]byte, error) {
var regData []byte
exprData, err := netlink.MarshalAttributes(
[]netlink.Attribute{
{Type: unix.NFTA_CT_KEY, Data: binaryutil.BigEndian.PutUint32(uint32(e.Key))},
},
)
if err != nil {
return nil, err
}
if e.SourceRegister {
regData, err = netlink.MarshalAttributes(
[]netlink.Attribute{
{Type: unix.NFTA_CT_SREG, Data: binaryutil.BigEndian.PutUint32(e.Register)},
},
)
} else {
regData, err = netlink.MarshalAttributes(
[]netlink.Attribute{
{Type: unix.NFTA_CT_DREG, Data: binaryutil.BigEndian.PutUint32(e.Register)},
},
)
}
if err != nil {
return nil, err
}
exprData = append(exprData, regData...)
switch e.Key {
case CtKeySRC, CtKeyDST, CtKeyPROTOSRC, CtKeyPROTODST:
regData, err = netlink.MarshalAttributes(
[]netlink.Attribute{
{Type: unix.NFTA_CT_DIRECTION, Data: binaryutil.BigEndian.PutUint32(e.Direction)},
},
)
if err != nil {
return nil, err
}
exprData = append(exprData, regData...)
}
return exprData, nil
}
func (e *Ct) unmarshal(fam byte, data []byte) error {
ad, err := netlink.NewAttributeDecoder(data)
if err != nil {
return err
}
ad.ByteOrder = binary.BigEndian
for ad.Next() {
switch ad.Type() {
case unix.NFTA_CT_KEY:
e.Key = CtKey(ad.Uint32())
case unix.NFTA_CT_DREG:
e.Register = ad.Uint32()
case unix.NFTA_CT_DIRECTION:
e.Direction = ad.Uint32()
}
}
return ad.Err()
}
type CtHelper struct {
Name string
L3Proto uint16
L4Proto uint8
}
func (c *CtHelper) marshal(fam byte) ([]byte, error) {
exprData, err := c.marshalData(fam)
if err != nil {
return nil, err
}
return netlink.MarshalAttributes([]netlink.Attribute{
{Type: unix.NFTA_EXPR_NAME, Data: []byte("cthelper\x00")},
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: exprData},
})
}
func (c *CtHelper) marshalData(fam byte) ([]byte, error) {
exprData := []netlink.Attribute{
{Type: unix.NFTA_CT_HELPER_NAME, Data: []byte(c.Name)},
}
if c.L3Proto != 0 {
exprData = append(exprData, netlink.Attribute{
Type: unix.NFTA_CT_HELPER_L3PROTO, Data: binaryutil.BigEndian.PutUint16(c.L3Proto),
})
}
if c.L4Proto != 0 {
exprData = append(exprData, netlink.Attribute{
Type: unix.NFTA_CT_HELPER_L4PROTO, Data: []byte{c.L4Proto},
})
}
return netlink.MarshalAttributes(exprData)
}
func (c *CtHelper) unmarshal(fam byte, data []byte) error {
ad, err := netlink.NewAttributeDecoder(data)
if err != nil {
return err
}
ad.ByteOrder = binary.BigEndian
for ad.Next() {
switch ad.Type() {
case unix.NFTA_CT_HELPER_NAME:
c.Name = ad.String()
case unix.NFTA_CT_HELPER_L3PROTO:
c.L3Proto = ad.Uint16()
case unix.NFTA_CT_HELPER_L4PROTO:
c.L4Proto = ad.Uint8()
}
}
return ad.Err()
}
// From https://git.netfilter.org/libnftnl/tree/include/linux/netfilter/nf_tables.h?id=be0bae0ad31b0adb506f96de083f52a2bd0d4fbf#n1601
// Currently not available in sys/unix
const (
NFTA_CT_EXPECT_L3PROTO = 0x01
NFTA_CT_EXPECT_L4PROTO = 0x02
NFTA_CT_EXPECT_DPORT = 0x03
NFTA_CT_EXPECT_TIMEOUT = 0x04
NFTA_CT_EXPECT_SIZE = 0x05
)
type CtExpect struct {
L3Proto uint16
L4Proto uint8
DPort uint16
Timeout uint32
Size uint8
}
func (c *CtExpect) marshal(fam byte) ([]byte, error) {
exprData, err := c.marshalData(fam)
if err != nil {
return nil, err
}
return netlink.MarshalAttributes([]netlink.Attribute{
{Type: unix.NFTA_EXPR_NAME, Data: []byte("ctexpect\x00")},
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: exprData},
})
}
func (c *CtExpect) marshalData(fam byte) ([]byte, error) {
// all elements except l3proto must be defined
// per https://git.netfilter.org/nftables/tree/doc/stateful-objects.txt?id=db70959a5ccf2952b218f51c3d529e186a5a43bb#n119
// from man page: l3proto is derived from the table family by default
exprData := []netlink.Attribute{
{Type: NFTA_CT_EXPECT_L4PROTO, Data: []byte{c.L4Proto}},
{Type: NFTA_CT_EXPECT_DPORT, Data: binaryutil.BigEndian.PutUint16(c.DPort)},
{Type: NFTA_CT_EXPECT_TIMEOUT, Data: binaryutil.BigEndian.PutUint32(c.Timeout)},
{Type: NFTA_CT_EXPECT_SIZE, Data: []byte{c.Size}},
}
if c.L3Proto != 0 {
attr := netlink.Attribute{Type: NFTA_CT_EXPECT_L3PROTO, Data: binaryutil.BigEndian.PutUint16(c.L3Proto)}
exprData = append(exprData, attr)
}
return netlink.MarshalAttributes(exprData)
}
func (c *CtExpect) unmarshal(fam byte, data []byte) error {
ad, err := netlink.NewAttributeDecoder(data)
if err != nil {
return err
}
ad.ByteOrder = binary.BigEndian
for ad.Next() {
switch ad.Type() {
case NFTA_CT_EXPECT_L3PROTO:
c.L3Proto = ad.Uint16()
case NFTA_CT_EXPECT_L4PROTO:
c.L4Proto = ad.Uint8()
case NFTA_CT_EXPECT_DPORT:
c.DPort = ad.Uint16()
case NFTA_CT_EXPECT_TIMEOUT:
c.Timeout = ad.Uint32()
case NFTA_CT_EXPECT_SIZE:
c.Size = ad.Uint8()
}
}
return ad.Err()
}
type CtTimeout struct {
L3Proto uint16
L4Proto uint8
Policy CtStatePolicyTimeout
}
func (c *CtTimeout) marshal(fam byte) ([]byte, error) {
exprData, err := c.marshalData(fam)
if err != nil {
return nil, err
}
return netlink.MarshalAttributes([]netlink.Attribute{
{Type: unix.NFTA_EXPR_NAME, Data: []byte("cttimeout\x00")},
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: exprData},
})
}
func (c *CtTimeout) marshalData(fam byte) ([]byte, error) {
var policy CtStatePolicyTimeout
switch c.L4Proto {
case unix.IPPROTO_UDP:
policy = CtStateUDPTimeoutDefaults
default:
policy = CtStateTCPTimeoutDefaults
}
for k, v := range c.Policy {
policy[k] = v
}
var policyAttrs []netlink.Attribute
for k, v := range policy {
policyAttrs = append(policyAttrs, netlink.Attribute{Type: k + 1, Data: binaryutil.BigEndian.PutUint32(v)})
}
policyData, err := netlink.MarshalAttributes(policyAttrs)
if err != nil {
return nil, err
}
exprData := []netlink.Attribute{
{Type: NFTA_CT_TIMEOUT_L3PROTO, Data: binaryutil.BigEndian.PutUint16(c.L3Proto)},
{Type: NFTA_CT_TIMEOUT_L4PROTO, Data: []byte{c.L4Proto}},
{Type: unix.NLA_F_NESTED | NFTA_CT_TIMEOUT_DATA, Data: policyData},
}
return netlink.MarshalAttributes(exprData)
}
func (c *CtTimeout) unmarshal(fam byte, data []byte) error {
ad, err := netlink.NewAttributeDecoder(data)
if err != nil {
return err
}
ad.ByteOrder = binary.BigEndian
for ad.Next() {
switch ad.Type() {
case NFTA_CT_TIMEOUT_L3PROTO:
c.L3Proto = ad.Uint16()
case NFTA_CT_TIMEOUT_L4PROTO:
c.L4Proto = ad.Uint8()
case NFTA_CT_TIMEOUT_DATA:
decoder, err := netlink.NewAttributeDecoder(ad.Bytes())
decoder.ByteOrder = binary.BigEndian
if err != nil {
return err
}
for decoder.Next() {
switch c.L4Proto {
case unix.IPPROTO_UDP:
c.Policy = CtStateUDPTimeoutDefaults
default:
c.Policy = CtStateTCPTimeoutDefaults
}
c.Policy[decoder.Type()-1] = decoder.Uint32()
}
}
}
return ad.Err()
}