Skip to content

Commit

Permalink
Network: firewall controller nat rules
Browse files Browse the repository at this point in the history
  • Loading branch information
cheina97 committed Dec 11, 2023
1 parent 247c5f2 commit 2385597
Show file tree
Hide file tree
Showing 18 changed files with 910 additions and 161 deletions.
51 changes: 51 additions & 0 deletions apis/networking/v1alpha1/firewall/common_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2019-2023 The Liqo Authors
//
// 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 firewall

import (
"fmt"
"net"
)

// IPValueType is the type of the match value.
type IPValueType string

const (
// IPValueTypeIP is a string representing an ip.
IPValueTypeIP IPValueType = "ip"
// IPValueTypeSubnet is a string representing a subnet (eg. 10.0.0.0/24).
IPValueTypeSubnet IPValueType = "subnet"
// IPValueTypeInvalid is an invalid match value.
IPValueTypeInvalid IPValueType = "invalid"
)

// GetIPValueType parses the match value and returns the type of the value.
func GetIPValueType(value string) (IPValueType, error) {
if value == "" {
return IPValueTypeInvalid, fmt.Errorf("match value cannot be empty")
}

// Check if the value is a pool subnet.
if _, _, err := net.ParseCIDR(value); err == nil {
return IPValueTypeSubnet, nil
}

// Check if the value is an IP.
if net.ParseIP(value) != nil {
return IPValueTypeIP, nil
}

return IPValueTypeInvalid, fmt.Errorf("invalid match value %s", value)
}
24 changes: 24 additions & 0 deletions apis/networking/v1alpha1/firewall/filterrule_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2019-2023 The Liqo Authors
//
// 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 firewall

var _ Rule = &FilterRule{}

// FilterRule is a rule to be applied to a filter chain.
// +kubebuilder:object:generate=true
type FilterRule struct {
// Name is the name of the rule.
Name *string `json:"name,omitempty"`
}
39 changes: 39 additions & 0 deletions apis/networking/v1alpha1/firewall/filterule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2019-2023 The Liqo Authors
//
// 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 firewall

import "github.com/google/nftables"

// GetName returns the name of the rule.
func (fr *FilterRule) GetName() *string {
return fr.Name
}

// SetName sets the name of the rule.
func (fr *FilterRule) SetName(name string) {
fr.Name = &name
}

// Add adds the rule to the chain.
func (fr *FilterRule) Add(_ *nftables.Conn, _ *nftables.Chain) error {
// TODO: implement
return nil
}

// Equal checks if the rule is equal to the given one.
func (fr *FilterRule) Equal(_ *nftables.Rule) bool {
// TODO: implement
return true
}
167 changes: 167 additions & 0 deletions apis/networking/v1alpha1/firewall/match.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright 2019-2023 The Liqo Authors
//
// 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 firewall

import (
"fmt"
"net"

"github.com/google/nftables"
"github.com/google/nftables/expr"
)

func applyMatch(m *Match, rule *nftables.Rule) error {
op, err := getMatchCmpOp(m)
if err != nil {
return err
}

switch {
case m.IP != nil:
return applyMatchIP(m, rule, op)
case m.Dev != nil:
return applyMatchDev(m, rule, op)
}
return nil
}

func applyMatchIP(m *Match, rule *nftables.Rule, op expr.CmpOp) error {
matchIPValueType, err := GetIPValueType(m.IP.Value)
if err != nil {
return err
}

switch matchIPValueType {
case IPValueTypeIP:
return applyMatchIPSingleIP(m, rule, op)
case IPValueTypeSubnet:
return applyMatchIPPoolSubnet(m, rule, op)
default:
return fmt.Errorf("invalid match value type %s", matchIPValueType)
}
}

func applyMatchIPSingleIP(m *Match, rule *nftables.Rule, op expr.CmpOp) error {
posOffset, err := getMatchIPPositionOffset(m)
if err != nil {
return err
}

rule.Exprs = append(rule.Exprs,
&expr.Payload{
DestRegister: 1,
Base: expr.PayloadBaseNetworkHeader,
Offset: posOffset,
Len: 4,
},
&expr.Cmp{
Op: op,
Register: 1,
Data: net.ParseIP(m.IP.Value).To4(),
},
)
return nil
}

func applyMatchIPPoolSubnet(m *Match, rule *nftables.Rule, op expr.CmpOp) error {
posOffset, err := getMatchIPPositionOffset(m)
if err != nil {
return err
}

ip, subnet, err := net.ParseCIDR(m.IP.Value)
if err != nil {
return err
}

rule.Exprs = append(rule.Exprs,
&expr.Payload{
DestRegister: 1,
Base: expr.PayloadBaseNetworkHeader,
Offset: posOffset,
Len: 4,
},
&expr.Bitwise{
SourceRegister: 1,
DestRegister: 1,
Len: 4,
Xor: []byte{0x0, 0x0, 0x0, 0x0},
Mask: subnet.Mask,
},
&expr.Cmp{
Op: op,
Register: 1,
Data: ip.To4(),
},
)
return nil
}

func applyMatchDev(m *Match, rule *nftables.Rule, op expr.CmpOp) error {
metakey, err := getMatchDevMetaKey(m)
if err != nil {
return err
}

rule.Exprs = append(rule.Exprs,
&expr.Meta{
Register: 1,
Key: metakey,
},
&expr.Cmp{
Op: op,
Register: 1,
Data: ifname(m.Dev.Value),
},
)

return nil
}

func getMatchCmpOp(m *Match) (expr.CmpOp, error) {
switch m.Op {
case MatchOperationEq:
return expr.CmpOpEq, nil
case MatchOperationNeq:
return expr.CmpOpNeq, nil
}
return expr.CmpOp(0), fmt.Errorf("invalid match operation %s", m.Op)
}

func getMatchIPPositionOffset(m *Match) (uint32, error) {
switch m.IP.Position {
case MatchIPPositionSrc:
return 12, nil
case MatchIPPositionDst:
return 16, nil
}
return 0, fmt.Errorf("invalid match IP position %s", m.Dev.Position)
}

func getMatchDevMetaKey(m *Match) (expr.MetaKey, error) {
switch m.Dev.Position {
case MatchDevPositionIn:
return expr.MetaKeyIIFNAME, nil
case MatchDevPositionOut:
return expr.MetaKeyOIFNAME, nil
}
return 0, fmt.Errorf("invalid match IP position %s", m.Dev.Position)
}

func ifname(n string) []byte {
b := make([]byte, 16)
copy(b, n+"\x00")
return b
}
79 changes: 79 additions & 0 deletions apis/networking/v1alpha1/firewall/match_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2019-2023 The Liqo Authors
//
// 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 firewall

// MatchOperation is the operation of the match.
type MatchOperation string

const (
// MatchOperationEq is the operation of the match.
MatchOperationEq MatchOperation = "eq"
// MatchOperationNeq is the operation of the match.
MatchOperationNeq MatchOperation = "neq"
)

// MatchIPPosition is the position of the IP in the packet.
type MatchIPPosition string

const (
// MatchIPPositionSrc is the position of the IP in the packet.
MatchIPPositionSrc MatchIPPosition = "src"
// MatchIPPositionDst is the position of the IP in the packet.
MatchIPPositionDst MatchIPPosition = "dst"
)

// MatchDevPosition is the position of the device in the packet.
type MatchDevPosition string

const (
// MatchDevPositionIn is the position of the device in the packet.
MatchDevPositionIn MatchDevPosition = "in"
// MatchDevPositionOut is the position of the device in the packet.
MatchDevPositionOut MatchDevPosition = "out"
)

// MatchIP is an IP to be matched.
// +kubebuilder:object:generate=true
type MatchIP struct {
// Value is the IP or a SUbnet to be matched.
Value string `json:"value"`
// Position is the position of the IP in the packet.
// +kubebuilder:validation:Enum=src;dst
Position MatchIPPosition `json:"position"`
}

// MatchDev is a device to be matched.
// +kubebuilder:object:generate=true
type MatchDev struct {
// Value is the name of the device to be matched.
Value string `json:"value"`
// Position is the source device of the packet.
// +kubebuilder:validation:Enum=in;out
Position MatchDevPosition `json:"position"`
}

// Match is a match to be applied to a rule.
// +kubebuilder:object:generate=true
// +kubebuilder:validation:MaxProperties=2
// +kubebuilder:validation:MinProperties=2
type Match struct {
// Op is the operation of the match.
// +kubebuilder:validation:Enum=eq;neq
Op MatchOperation `json:"op"`
// IP contains the options to match an IP or a Subnet.
IP *MatchIP `json:"ip,omitempty"`
// Dev contains the options to match a device.
Dev *MatchDev `json:"dev,omitempty"`
}
Loading

0 comments on commit 2385597

Please sign in to comment.