401 lines
11 KiB
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()
|
|
}
|