Manage Forwarding rules

This commit is contained in:
Zoltán Papp
2025-01-24 12:48:52 +01:00
parent 69f48db0a3
commit 4ad5c55795
9 changed files with 649 additions and 281 deletions
+7
View File
@@ -99,6 +99,13 @@ type Manager interface {
// Flush the changes to firewall controller
Flush() error
// AddDNATRule adds a DNAT rule
AddDNATRule(ForwardRule) (Rule, error)
// DeleteDNATRule deletes a DNAT rule
// todo: do you need a string ID or the complete rule?
DeleteDNATRule(Rule) error
}
func GenKey(format string, pair RouterPair) string {
+26
View File
@@ -0,0 +1,26 @@
package manager
import (
"fmt"
"net"
)
// ForwardRule todo figure out better place to this to avoid circular imports
type ForwardRule struct {
Protocol Protocol
DestinationPort Port
TranslatedAddress net.IP
TranslatedPort Port
}
func (r ForwardRule) GetRuleID() string {
return fmt.Sprintf("%s-%s-%s-%s",
r.Protocol,
r.DestinationPort,
r.TranslatedAddress.String(),
r.TranslatedPort)
}
func (r ForwardRule) String() string {
return r.GetRuleID()
}
+1 -20
View File
@@ -4,27 +4,8 @@ import (
"strconv"
)
// Protocol is the protocol of the port
type Protocol string
const (
// ProtocolTCP is the TCP protocol
ProtocolTCP Protocol = "tcp"
// ProtocolUDP is the UDP protocol
ProtocolUDP Protocol = "udp"
// ProtocolICMP is the ICMP protocol
ProtocolICMP Protocol = "icmp"
// ProtocolALL cover all supported protocols
ProtocolALL Protocol = "all"
// ProtocolUnknown unknown protocol
ProtocolUnknown Protocol = "unknown"
)
// Port of the address for firewall rule
// todo Move Protocol and Port and RouterPair to the Firwall package or a separate package
type Port struct {
// IsRange is true Values contains two values, the first is the start port, the second is the end port
IsRange bool
+22
View File
@@ -0,0 +1,22 @@
package manager
// Protocol is the protocol of the port
// todo Move Protocol and Port and RouterPair to the Firwall package or a separate package
type Protocol string
const (
// ProtocolTCP is the TCP protocol
ProtocolTCP Protocol = "tcp"
// ProtocolUDP is the UDP protocol
ProtocolUDP Protocol = "udp"
// ProtocolICMP is the ICMP protocol
ProtocolICMP Protocol = "icmp"
// ProtocolALL cover all supported protocols
ProtocolALL Protocol = "all"
// ProtocolUnknown unknown protocol
ProtocolUnknown Protocol = "unknown"
)
+81 -10
View File
@@ -25,7 +25,7 @@ import (
nberrors "github.com/netbirdio/netbird/client/errors"
"github.com/netbirdio/netbird/client/firewall"
"github.com/netbirdio/netbird/client/firewall/manager"
firewallManager "github.com/netbirdio/netbird/client/firewall/manager"
"github.com/netbirdio/netbird/client/iface"
"github.com/netbirdio/netbird/client/iface/bind"
"github.com/netbirdio/netbird/client/iface/device"
@@ -33,6 +33,7 @@ import (
"github.com/netbirdio/netbird/client/internal/acl"
"github.com/netbirdio/netbird/client/internal/dns"
"github.com/netbirdio/netbird/client/internal/dnsfwd"
"github.com/netbirdio/netbird/client/internal/ingressgw"
"github.com/netbirdio/netbird/client/internal/networkmonitor"
"github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/client/internal/peer/guard"
@@ -168,10 +169,11 @@ type Engine struct {
statusRecorder *peer.Status
firewall manager.Manager
routeManager routemanager.Manager
acl acl.Manager
dnsForwardMgr *dnsfwd.Manager
firewall firewallManager.Manager
routeManager routemanager.Manager
acl acl.Manager
dnsForwardMgr *dnsfwd.Manager
ingressGatewayMgr *ingressgw.Manager
dnsServer dns.Server
@@ -291,6 +293,13 @@ func (e *Engine) Stop() error {
// stop/restore DNS first so dbus and friends don't complain because of a missing interface
e.stopDNSServer()
if e.ingressGatewayMgr != nil {
if err := e.ingressGatewayMgr.Close(); err != nil {
log.Warnf("failed to cleanup forward rules: %v", err)
}
e.ingressGatewayMgr = nil
}
if e.routeManager != nil {
e.routeManager.Stop(e.stateManager)
}
@@ -495,15 +504,15 @@ func (e *Engine) initFirewall() error {
}
rosenpassPort := e.rpManager.GetAddress().Port
port := manager.Port{Values: []int{rosenpassPort}}
port := firewallManager.Port{Values: []int{rosenpassPort}}
// this rule is static and will be torn down on engine down by the firewall manager
if _, err := e.firewall.AddPeerFiltering(
net.IP{0, 0, 0, 0},
manager.ProtocolUDP,
firewallManager.ProtocolUDP,
nil,
&port,
manager.ActionAccept,
firewallManager.ActionAccept,
"",
"",
); err != nil {
@@ -531,10 +540,10 @@ func (e *Engine) blockLanAccess() {
if _, err := e.firewall.AddRouteFiltering(
[]netip.Prefix{v4},
network,
manager.ProtocolALL,
firewallManager.ProtocolALL,
nil,
nil,
manager.ActionDrop,
firewallManager.ActionDrop,
); err != nil {
merr = multierror.Append(merr, fmt.Errorf("add fw rule for network %s: %w", network, err))
}
@@ -925,6 +934,9 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
log.Errorf("failed to update clientRoutes, err: %v", err)
}
// Ingress forward rules
e.updateForwardRules(networkMap.GetForwardingRules())
log.Debugf("got peers update from Management Service, total peers to connect to = %d", len(networkMap.GetRemotePeers()))
e.updateOfflinePeers(networkMap.GetOfflinePeers())
@@ -1737,6 +1749,65 @@ func (e *Engine) updateDNSForwarder(enabled bool, domains []string) {
}
}
func (e *Engine) updateForwardRules(rules []*mgmProto.ForwardingRule) error {
if e.firewall == nil {
log.Warn("firewall is disabled, not updating forwarding rules")
return nil
}
if len(rules) == 0 && e.ingressGatewayMgr != nil {
return e.ingressGatewayMgr.Close()
}
if e.ingressGatewayMgr == nil {
e.ingressGatewayMgr = ingressgw.NewManager(e.firewall)
}
var merr *multierror.Error
forwardingRules := make([]firewallManager.ForwardRule, 0, len(rules))
for _, rule := range rules {
proto, err := convertToFirewallProtocol(rule.GetProtocol())
if err != nil {
merr = multierror.Append(merr, fmt.Errorf("failed to convert protocol '%s': %w", rule.GetProtocol(), err))
continue
}
dstPortInfo := convertPortInfo(rule.GetDestinationPort())
if dstPortInfo == nil {
merr = multierror.Append(merr, fmt.Errorf("dstPort is nil"))
continue
}
translateIP, err := convertToIP(rule.GetTranslatedAddress())
if err != nil {
merr = multierror.Append(merr, fmt.Errorf("failed to convert translated address '%s': %w", rule.GetTranslatedAddress(), err))
continue
}
translatePort := convertPortInfo(rule.GetTranslatedPort())
if translatePort == nil {
merr = multierror.Append(merr, fmt.Errorf("translatePort is nil"))
continue
}
forwardRule := firewallManager.ForwardRule{
Protocol: proto,
DestinationPort: *dstPortInfo,
TranslatedAddress: translateIP,
TranslatedPort: *translatePort,
}
forwardingRules = append(forwardingRules, forwardRule)
}
log.Infof("updating forwarding rules: %d", len(forwardingRules))
if err := e.ingressGatewayMgr.Update(forwardingRules); err != nil {
log.Errorf("failed to update forwarding rules: %v", err)
}
return nberrors.FormatErrorOrNil(merr)
}
// isChecksEqual checks if two slices of checks are equal.
func isChecksEqual(checks []*mgmProto.Checks, oChecks []*mgmProto.Checks) bool {
for _, check := range checks {
+65
View File
@@ -0,0 +1,65 @@
package ingressgw
import (
"fmt"
"github.com/hashicorp/go-multierror"
nberrors "github.com/netbirdio/netbird/client/errors"
firewallManager "github.com/netbirdio/netbird/client/firewall/manager"
)
type Manager struct {
firewallManager firewallManager.Manager
fwRules map[string]firewallManager.Rule
}
func NewManager(firewall firewallManager.Manager) *Manager {
return &Manager{
firewallManager: firewall,
fwRules: make(map[string]firewallManager.Rule),
}
}
func (h *Manager) Update(forwardRules []firewallManager.ForwardRule) error {
var mErr *multierror.Error
toDelete := make(map[string]firewallManager.Rule)
for id, r := range h.fwRules {
toDelete[id] = r
}
// Process new/updated rules
for _, rule := range forwardRules {
id := rule.GetRuleID()
if _, ok := h.fwRules[id]; ok {
delete(toDelete, id)
continue
}
rule, err := h.firewallManager.AddDNATRule(rule)
if err != nil {
mErr = multierror.Append(mErr, fmt.Errorf("failed to add forward rule '%s': %v", rule, err))
}
h.fwRules[id] = rule
}
// Remove deleted rules
for id, rule := range toDelete {
if err := h.firewallManager.DeleteDNATRule(rule); err != nil {
mErr = multierror.Append(mErr, fmt.Errorf("failed to delete forward rule '%s': %v", rule, err))
}
delete(h.fwRules, id)
}
return nberrors.FormatErrorOrNil(mErr)
}
func (h *Manager) Close() error {
var mErr *multierror.Error
for _, rule := range h.fwRules {
if err := h.firewallManager.DeleteDNATRule(rule); err != nil {
mErr = multierror.Append(mErr, fmt.Errorf("failed to delete forward rule '%s': %v", rule, err))
}
}
return nberrors.FormatErrorOrNil(mErr)
}
+59
View File
@@ -0,0 +1,59 @@
package internal
import (
"errors"
"fmt"
"net"
firewallManager "github.com/netbirdio/netbird/client/firewall/manager"
mgmProto "github.com/netbirdio/netbird/management/proto"
)
func convertToFirewallProtocol(protocol mgmProto.RuleProtocol) (firewallManager.Protocol, error) {
switch protocol {
case mgmProto.RuleProtocol_TCP:
return firewallManager.ProtocolTCP, nil
case mgmProto.RuleProtocol_UDP:
return firewallManager.ProtocolUDP, nil
case mgmProto.RuleProtocol_ICMP:
return firewallManager.ProtocolICMP, nil
case mgmProto.RuleProtocol_ALL:
return firewallManager.ProtocolALL, nil
default:
return firewallManager.ProtocolALL, fmt.Errorf("invalid protocol type: %s", protocol.String())
}
}
// convertPortInfo todo: write validation for portInfo
func convertPortInfo(portInfo *mgmProto.PortInfo) *firewallManager.Port {
if portInfo == nil {
return nil
}
if portInfo.GetPort() != 0 {
return &firewallManager.Port{
Values: []int{int(portInfo.GetPort())},
}
}
if portInfo.GetRange() != nil {
return &firewallManager.Port{
IsRange: true,
Values: []int{int(portInfo.GetRange().Start), int(portInfo.GetRange().End)},
}
}
return nil
}
func convertToIP(rawIP []byte) (net.IP, error) {
if rawIP == nil {
return nil, errors.New("input bytes cannot be nil")
}
if len(rawIP) != net.IPv4len && len(rawIP) != net.IPv6len {
return nil, fmt.Errorf("invalid IP length: %d", len(rawIP))
}
return rawIP, nil
}