Skip to content
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
11 changes: 6 additions & 5 deletions agent/app/dto/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,12 @@ type FirewallBackendOption struct {
}

type FirewallBackendFamilyStatus struct {
Available bool `json:"available"`
Initialized bool `json:"initialized"`
Bound bool `json:"bound"`
Reason string `json:"reason,omitempty"`
ForwardPolicy string `json:"forwardPolicy,omitempty"`
Available bool `json:"available"`
Initialized bool `json:"initialized"`
Bound bool `json:"bound"`
Reason string `json:"reason,omitempty"`
ForwardPolicy string `json:"forwardPolicy,omitempty"`
RAInterfaces []string `json:"raInterfaces,omitempty"`
}

type FirewallBackendGroup struct {
Expand Down
29 changes: 17 additions & 12 deletions agent/app/service/firewall_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2413,18 +2413,23 @@ func firewallDeleteChange(current filter.ObservedRule, desired filter.DesiredRul
func loadForwardingFirewallOverview(manager forwarding.Adapter) (dto.FirewallSubsystemStatus, error) {
ipv4, ipv4Err := loadForwardingFamilyInfo(manager, manager.Name(), constant.FirewallFamilyIPv4)
ipv6, ipv6Err := loadForwardingFamilyInfo(manager, manager.Name(), constant.FirewallFamilyIPv6)
initialized, bound, statusErr := ipv4.Initialized, ipv4.Bound, ipv4Err
if manager.Name() == constant.FirewallProviderNftables {
if statusErr == nil && initialized && bound {
initialized, bound, statusErr = ipv6.Initialized, ipv6.Bound, ipv6Err
}
} else {
statusErr = errors.Join(statusErr, ipv6Err)
if ipv6.Available {
initialized, bound = initialized && ipv6.Initialized, bound && ipv6.Bound
}
}
return dto.FirewallSubsystemStatus{IsInit: initialized, IsBind: bound, IPv4: ipv4, IPv6: ipv6}, statusErr
if ipv6.Available {
interfaces, err := forwarding.IPv6RAInterfaces(os.ReadFile)
var pathError *os.PathError
switch {
case errors.As(err, &pathError) && errors.Is(err, os.ErrNotExist) && pathError.Path == "/proc/net/if_inet6":
ipv6.Available, ipv6.Initialized, ipv6.Bound = false, false, false
case err != nil:
ipv6.Reason = "ipv6_ra_check_failed"
ipv6.Bound = false
case len(interfaces) > 0:
ipv6.Reason, ipv6.RAInterfaces = "ipv6_ra_required", interfaces
ipv6.Bound = false
case !ipv6.Bound:
ipv6.Reason = "ipv6_forwarding_not_enabled"
}
}
return dto.FirewallSubsystemStatus{IsInit: ipv4.Initialized || ipv6.Initialized, IsBind: ipv4.Bound || ipv6.Bound, IPv4: ipv4, IPv6: ipv6}, errors.Join(ipv4Err, ipv6Err)
}

func loadForwardingFamilyInfo(manager forwarding.Adapter, backend, family string) (dto.FirewallBackendFamilyStatus, error) {
Expand Down
14 changes: 14 additions & 0 deletions agent/app/service/forward.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"os"
"strconv"
"strings"
"sync"
Expand Down Expand Up @@ -459,6 +460,19 @@ func (s *ForwardingService) operateRules(ctx context.Context, request dto.Forwar
}
return nil
}
for _, rule := range groups[1].rules {
if rule.Family != forwarding.FamilyIPv6 {
continue
}
interfaces, err := forwarding.IPv6RAInterfaces(os.ReadFile)
if err != nil {
return fmt.Errorf("check IPv6 Router Advertisement: %w", err)
}
if len(interfaces) > 0 {
return fmt.Errorf("IPv6 forwarding blocked: interfaces %s may depend on RA/SLAAC with accept_ra=1; persist accept_ra=2 on interfaces that require RA before retrying", strings.Join(interfaces, ", "))
}
break
}
}
var client forwarding.Adapter
var failures []error
Expand Down
60 changes: 39 additions & 21 deletions agent/utils/firewall/forwarding/iptables.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ func (systemIptablesBackend) LoadIPv6RulesFromFile(table, chain, fileName string
type forwardingSystem interface {
ReadFile(name string) ([]byte, error)
WriteFile(name string, data []byte, perm os.FileMode) error
RunWithOptionalSudo(name string, args ...string) error
}

type defaultForwardingSystem struct{}
Expand All @@ -103,10 +102,6 @@ func (defaultForwardingSystem) WriteFile(name string, data []byte, perm os.FileM
return cmd.WriteFileWithOptionalSudo(name, data, perm)
}

func (defaultForwardingSystem) RunWithOptionalSudo(name string, args ...string) error {
return cmd.NewCommandMgr().RunWithOptionalSudo(name, args...)
}

type Iptables struct {
provider string
backend iptablesBackend
Expand Down Expand Up @@ -151,15 +146,33 @@ func (l *Iptables) ReplaceRules(rules []Rule) error {
if err != nil {
return err
}
if normalized.Family == FamilyIPv6 && !l.backend.IPv6Available() {
return fmt.Errorf("ip6tables command family is unavailable")
}
byFamily[normalized.Family] = append(byFamily[normalized.Family], normalized)
}
var failures []error
for _, family := range []string{FamilyIPv4, FamilyIPv6} {
if family == FamilyIPv6 && !l.backend.IPv6Available() {
if len(byFamily[family]) > 0 {
failures = append(failures, fmt.Errorf("ip6tables command family is unavailable"))
}
continue
}
if family == FamilyIPv6 {
if len(byFamily[family]) > 0 {
if err := ensureForwardingSysctls(l.system, true); err != nil {
failures = append(failures, err)
continue
}
} else {
initialized, _, err := l.familyInitStatus(family)
if err != nil {
failures = append(failures, err)
continue
}
if !initialized {
continue
}
}
}
if err := l.batchEnsureChains(family); err != nil {
return err
}
Expand All @@ -171,7 +184,7 @@ func (l *Iptables) ReplaceRules(rules []Rule) error {
return fmt.Errorf("restore %s forwarding rules: %w", family, err)
}
}
return nil
return errors.Join(failures...)
}

func (l *Iptables) CreateRules(ctx context.Context, rules []Rule) error {
Expand All @@ -186,6 +199,17 @@ func (l *Iptables) CreateRules(ctx context.Context, rules []Rule) error {
if family == "" {
family = FamilyIPv4
}
if family == FamilyIPv6 {
if !l.backend.IPv6Available() {
return fmt.Errorf("ip6tables command family is unavailable")
}
if err := ensureForwardingSysctls(l.system, true); err != nil {
return err
}
if err := l.batchEnsureChains(family); err != nil {
return err
}
}
return l.backend.Restore(ctx, family, script)
}

Expand Down Expand Up @@ -287,19 +311,10 @@ func isRemoteTarget(family, target string) bool {
}

func (l *Iptables) Enable() error {
if err := ensureForwardingSysctls(l.system, l.backend.IPv6Available()); err != nil {
if err := ensureForwardingSysctls(l.system, false); err != nil {
return err
}

for _, family := range []string{FamilyIPv4, FamilyIPv6} {
if family == FamilyIPv6 && !l.backend.IPv6Available() {
continue
}
if err := l.batchEnsureChains(family); err != nil {
return err
}
}
return nil
return l.batchEnsureChains(FamilyIPv4)
}

func (l *Iptables) batchEnsureChains(family string) error {
Expand Down Expand Up @@ -506,7 +521,7 @@ func (l *Iptables) InitStatus() (bool, bool, error) {
if err != nil {
return false, false, err
}
return ipv4Init && ipv6Init, ipv4Bind && ipv6Bind, nil
return ipv4Init || ipv6Init, ipv4Bind || ipv6Bind, nil
}

func (l *Iptables) familyInitStatus(family string) (bool, bool, error) {
Expand All @@ -519,6 +534,9 @@ func (l *Iptables) familyInitStatus(family string) (bool, bool, error) {
list = l.backend.RunIPv6WithStd
}
data, err := l.system.ReadFile(sysctlPath)
if family == FamilyIPv6 && errors.Is(err, os.ErrNotExist) {
return false, false, nil
}
if err != nil {
return false, false, fmt.Errorf("read %s forwarding status: %w", label, err)
}
Expand Down
82 changes: 64 additions & 18 deletions agent/utils/firewall/forwarding/nftables.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,45 @@ func (n *Nftables) List() ([]Rule, error) {
}

func (n *Nftables) ReplaceRules(rules []Rule) error {
if err := ensureNftForwardTables(); err != nil {
return fmt.Errorf("initialize nftables forwarding table: %w", err)
byFamily := map[string][]Rule{FamilyIPv4: nil, FamilyIPv6: nil}
for _, rule := range rules {
normalized, err := NormalizeRule(rule)
if err != nil {
return err
}
byFamily[normalized.Family] = append(byFamily[normalized.Family], normalized)
}
commands, err := rebuildNftForwardCommands(rules)
if err != nil {
return err
var failures []error
for _, family := range []string{FamilyIPv4, FamilyIPv6} {
if family == FamilyIPv6 {
if len(byFamily[family]) > 0 {
if err := ensureForwardingSysctls(n.system, true); err != nil {
failures = append(failures, err)
continue
}
} else {
_, exists, err := nftables_helper.ReadTable(nftRun, nftTableFamily(family), nftForwardTable)
if err != nil {
failures = append(failures, err)
continue
}
if !exists {
continue
}
}
}
if err := ensureNftForwardTables(family); err != nil {
return fmt.Errorf("initialize nftables forwarding table: %w", err)
}
commands, err := rebuildNftForwardCommands(byFamily[family], family)
if err != nil {
return err
}
if err := nftRunCommands(context.Background(), commands); err != nil {
return err
}
}
return nftRunCommands(context.Background(), commands)
return errors.Join(failures...)
}

func (n *Nftables) CreateRules(ctx context.Context, rules []Rule) error {
Expand All @@ -66,6 +97,17 @@ func (n *Nftables) CreateRules(ctx context.Context, rules []Rule) error {
if err != nil {
return err
}
for _, rule := range rules {
if strings.EqualFold(strings.TrimSpace(rule.Family), FamilyIPv6) {
if err := ensureForwardingSysctls(n.system, true); err != nil {
return err
}
if err := ensureNftForwardTables(FamilyIPv6); err != nil {
return err
}
break
}
}
return nftRunCommands(ctx, commands)
}

Expand Down Expand Up @@ -113,10 +155,10 @@ func (n *Nftables) DeleteRules(ctx context.Context, rules []Rule) error {
}

func (n *Nftables) Enable() error {
if err := ensureForwardingSysctls(n.system, true); err != nil {
if err := ensureForwardingSysctls(n.system, false); err != nil {
return err
}
if err := ensureNftForwardTables(); err != nil {
if err := ensureNftForwardTables(FamilyIPv4); err != nil {
return fmt.Errorf("initialize nftables forwarding table: %w", err)
}
return nil
Expand Down Expand Up @@ -144,13 +186,16 @@ func (n *Nftables) Cleanup() error {
}

func (n *Nftables) InitStatus() (bool, bool, error) {
var anyInitialized, anyBound bool
for _, family := range []string{FamilyIPv4, FamilyIPv6} {
initialized, bound, err := n.FamilyStatus(family)
if err != nil || !initialized || !bound {
return initialized, bound, err
if err != nil {
return false, false, err
}
anyInitialized = anyInitialized || initialized
anyBound = anyBound || bound
}
return true, true, nil
return anyInitialized, anyBound, nil
}

func (n *Nftables) FamilyStatus(family string) (bool, bool, error) {
Expand All @@ -159,6 +204,9 @@ func (n *Nftables) FamilyStatus(family string) (bool, bool, error) {
sysctlPath = "/proc/sys/net/ipv6/conf/all/forwarding"
}
data, err := n.system.ReadFile(sysctlPath)
if family == FamilyIPv6 && errors.Is(err, os.ErrNotExist) {
return false, false, nil
}
if err != nil {
return false, false, fmt.Errorf("read %s forwarding status: %w", family, err)
}
Expand Down Expand Up @@ -197,9 +245,9 @@ func (n *Nftables) Replay() error {
return nftRunCommand("-f", file)
}

func ensureNftForwardTables() error {
func ensureNftForwardTables(families ...string) error {
commands := make([][]string, 0, 8)
for _, family := range []string{FamilyIPv4, FamilyIPv6} {
for _, family := range families {
tableFamily := nftTableFamily(family)
output, tableExists, err := nftables_helper.ReadTable(nftRun, tableFamily, nftForwardTable)
if err != nil {
Expand Down Expand Up @@ -232,12 +280,10 @@ func ensureNftForwardTables() error {
return nftRunCommands(context.Background(), commands)
}

func rebuildNftForwardCommands(rules []Rule) ([][]string, error) {
func rebuildNftForwardCommands(rules []Rule, family string) ([][]string, error) {
commands := make([][]string, 0, 6+len(rules)*4)
for _, family := range []string{FamilyIPv4, FamilyIPv6} {
for _, chain := range []string{ChainPreRouting, ChainPostRouting, ChainForward} {
commands = append(commands, []string{"flush", "chain", nftTableFamily(family), nftForwardTable, "NFT_" + chain})
}
for _, chain := range []string{ChainPreRouting, ChainPostRouting, ChainForward} {
commands = append(commands, []string{"flush", "chain", nftTableFamily(family), nftForwardTable, "NFT_" + chain})
}
additions, err := createNftForwardCommands(rules)
return append(commands, additions...), err
Expand Down
Loading
Loading