From 0fe96f32fd271420fcb702f0f6948e4ba4e6f759 Mon Sep 17 00:00:00 2001 From: Aritra Basu Date: Wed, 21 Jan 2026 15:15:11 -0500 Subject: [PATCH] Add ip6tables rule for DHCPv6 hop limit configuration Configure ip6tables mangle rule to set hop limit to 2 for DHCPv6 OUTPUT traffic from client (sport 546) to server (dport 547). This prevents VPP from dropping DHCPv6 SOLICIT/REQUEST packets when it decrements hop-limit by 1 during forwarding. Since clients generate SOLICIT/REQUEST with hop-limit=1, without this rule VPP drops the packet (ip6 ttl <= 1) with ICMP time exceeded, causing DHCPv6 lease negotiation to fail. The rule is checked for existence before adding to prevent duplicates since ip6tables does not auto-dedupe rules. The rule is also cleaned up during configuration restoration. Signed-off-by: Aritra Basu --- vpp-manager/images/ubuntu/Dockerfile | 2 +- vpp-manager/vpp_runner.go | 48 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/vpp-manager/images/ubuntu/Dockerfile b/vpp-manager/images/ubuntu/Dockerfile index 6f39d183b..4a665343b 100644 --- a/vpp-manager/images/ubuntu/Dockerfile +++ b/vpp-manager/images/ubuntu/Dockerfile @@ -6,7 +6,7 @@ RUN apt-get update \ && apt-get install -y openssl libapr1 libnuma1 \ libmbedcrypto7 libmbedtls14 libmbedx509-1 libsubunit0 \ iproute2 ifupdown ethtool libnl-3-dev libnl-route-3-dev \ - libpcap0.8 libunwind8 \ + libpcap0.8 libunwind8 iptables \ gdb \ && rm -rf /var/lib/apt/lists/* diff --git a/vpp-manager/vpp_runner.go b/vpp-manager/vpp_runner.go index 0d4d702e2..555884d9d 100644 --- a/vpp-manager/vpp_runner.go +++ b/vpp-manager/vpp_runner.go @@ -864,6 +864,46 @@ func (v *VppRunner) AllocatePhysicalNetworkVRFs(phyNet string) (err error) { return nil } +func (v *VppRunner) configureDHCPv6HopLimit() { + log.Infof("Configuring ip6tables mangle OUTPUT rule for DHCPv6 hop limit on host") + + checkCmd := exec.Command("/usr/sbin/ip6tables", "-t", "mangle", "-C", "OUTPUT", + "-p", "udp", "--sport", "546", "--dport", "547", + "-j", "HL", "--hl-set", "2") + if err := checkCmd.Run(); err != nil { + outputCmd := exec.Command("/usr/sbin/ip6tables", "-t", "mangle", "-A", "OUTPUT", + "-p", "udp", "--sport", "546", "--dport", "547", + "-j", "HL", "--hl-set", "2") + outputCmd.Stdout = os.Stdout + outputCmd.Stderr = os.Stderr + if err := outputCmd.Run(); err != nil { + log.Warnf("Failed to configure ip6tables mangle OUTPUT rule for DHCPv6: %v", err) + } + } else { + log.Infof("ip6tables mangle OUTPUT rule for DHCPv6 already present") + } +} + +func (v *VppRunner) cleanupDHCPv6HopLimit() { + log.Infof("Cleaning up ip6tables mangle OUTPUT rule for DHCPv6 hop limit on host") + + checkCmd := exec.Command("/usr/sbin/ip6tables", "-t", "mangle", "-C", "OUTPUT", + "-p", "udp", "--sport", "546", "--dport", "547", + "-j", "HL", "--hl-set", "2") + if err := checkCmd.Run(); err == nil { + deleteCmd := exec.Command("/usr/sbin/ip6tables", "-t", "mangle", "-D", "OUTPUT", + "-p", "udp", "--sport", "546", "--dport", "547", + "-j", "HL", "--hl-set", "2") + deleteCmd.Stdout = os.Stdout + deleteCmd.Stderr = os.Stderr + if err := deleteCmd.Run(); err != nil { + log.Warnf("Failed to delete ip6tables mangle OUTPUT rule for DHCPv6: %v", err) + } + } else { + log.Infof("ip6tables mangle OUTPUT rule for DHCPv6 not present") + } +} + // Returns VPP exit code func (v *VppRunner) runVpp() (err error) { if !v.allInterfacesPhysical() { // use separate net namespace because linux deletes these interfaces when ns is deleted @@ -936,6 +976,13 @@ func (v *VppRunner) runVpp() (err error) { return errors.Wrap(err, "Error configuring VPP") } + // FIXME This is a temporary workaround using ip6tables to set the hop limit for DHCPv6. + // Ideally, VPP should have a dedicated node for handling this. + // Without this, when forwarding a DHCPv6 SOLICIT/REQUEST packet, VPP will decrement the + // hop-limit by 1. Since client generates DHCPv6 SOLICIT/REQUEST with hop-limit=1, VPP + // drops it (ip6 ttl <= 1) with ICMP time exceeded and DHCPv6 lease negotiation fails. + v.configureDHCPv6HopLimit() + // add main network that has the default VRF config.Info.PhysicalNets[config.DefaultPhysicalNetworkName] = config.PhysicalNetwork{VrfID: common.DefaultVRFIndex, PodVrfID: common.PodVRFIndex} @@ -1001,6 +1048,7 @@ func (v *VppRunner) runVpp() (err error) { func (v *VppRunner) restoreConfiguration(allInterfacesPhysical bool) { log.Infof("Restoring configuration") + v.cleanupDHCPv6HopLimit() err := utils.ClearVppManagerFiles() if err != nil { log.Errorf("Error clearing vpp manager files: %v", err)