From 53b661d60e50edd1079a2f55e0cb3e44e76553e2 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Thu, 24 Sep 2026 19:01:00 +0000 Subject: ppp: T9060: do not derive a link-local address from a synthetic MAC A PPP link has no hardware address. RFC 5072 defines the IPv6 interface identifier of such a link as the one negotiated via IPV6CP, and that negotiation is also what guarantees its uniqueness - which is why duplicate address detection is skipped for the link-local address on PPP links. VyOS additionally derived an EUI-64 link-local address from a synthetic MAC address and assigned it to PPPoE and SSTP client interfaces. The peer knows nothing about that address and its uniqueness was never verified. With two link-local addresses present, RFC 6724 source address selection falls through to rule 8 and may pick the synthetic one, so DHCPv6 prefix delegation and router solicitations leave from an address the BRAS never negotiated. ISPs validating the source address silently drop them and delegation stalls. Both interface types now carry only the address negotiated via IPV6CP. A leftover address on an established session is removed on commit. --- python/vyos/ifconfig/interface.py | 11 +++++++++-- python/vyos/ifconfig/pppoe.py | 17 +++++++++++++++++ python/vyos/ifconfig/sstpc.py | 4 ++++ 3 files changed, 30 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 73f7544dd..270e3db08 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -94,6 +94,11 @@ class Interface(Control): 'eternal': '', } + # T9060: Interfaces negotiating their IPv6 interface identifier with the + # remote end - PPP links via IPV6CP (RFC 5072) - must not receive an + # additional EUI-64 derived link-local address unknown to the peer. + _ipv6_default_link_local = True + _command_get = { 'admin_state': { 'shellcmd': 'ip -json link show dev {ifname}', @@ -983,7 +988,8 @@ class Interface(Control): with the given prefix to the interface. """ # T2863: only add a link-local IPv6 address if the interface returns - # a MAC address. This is not the case on e.g. WireGuard interfaces. + # a MAC address. Interface types without one either provide a + # synthetic MAC or opt out via _ipv6_default_link_local. mac = self.get_mac() if mac: eui64 = mac2eui64(mac, prefix) @@ -2011,7 +2017,8 @@ class Interface(Control): self.del_ipv6_eui64_address(addr) # Manage IPv6 link-local addresses - if dict_search('ipv6.address.no_default_link_local', config) != None: + if (not self._ipv6_default_link_local or + dict_search('ipv6.address.no_default_link_local', config) != None): self.del_ipv6_eui64_address(link_local_prefix) else: self.add_ipv6_eui64_address(link_local_prefix) diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py index 327c41719..eb217e12d 100644 --- a/python/vyos/ifconfig/pppoe.py +++ b/python/vyos/ifconfig/pppoe.py @@ -14,9 +14,11 @@ # License along with this library. If not, see . from vyos.ifconfig.interface import Interface +from vyos.template import is_ipv6 from vyos.utils.assertion import assert_range from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_config +from vyos.utils.network import mac2eui64 @Interface.register class PPPoEIf(Interface): @@ -28,6 +30,10 @@ class PPPoEIf(Interface): }, } + # T9060: the IPv6 interface identifier of a PPP link is negotiated with + # the peer via IPV6CP (RFC 5072) - see Interface._ipv6_default_link_local + _ipv6_default_link_local = False + _sysfs_get = { **Interface._sysfs_get,**{ 'accept_ra_defrtr': { @@ -80,6 +86,17 @@ class PPPoEIf(Interface): # we cannot create this interface as it is managed outside pass + def del_ipv6_eui64_address(self, prefix): + """ + del_addr() is a NOOP as the addresses are managed by pppd. The EUI-64 + link-local address was added by VyOS itself, so use the generic + implementation to clean it off an already established session. + """ + if is_ipv6(prefix): + eui64 = mac2eui64(self.get_mac(), prefix) + prefixlen = prefix.split('/')[1] + super().del_addr(f'{eui64}/{prefixlen}') + def get_mac(self): """ Get a synthetic MAC address. """ return self.get_mac_synthetic() diff --git a/python/vyos/ifconfig/sstpc.py b/python/vyos/ifconfig/sstpc.py index a84a44265..71c965784 100644 --- a/python/vyos/ifconfig/sstpc.py +++ b/python/vyos/ifconfig/sstpc.py @@ -26,6 +26,10 @@ class SSTPCIf(Interface): }, } + # T9060: the IPv6 interface identifier of a PPP link is negotiated with + # the peer via IPV6CP (RFC 5072) - see Interface._ipv6_default_link_local + _ipv6_default_link_local = False + def _create(self): # we cannot create this interface as it is managed outside pass -- cgit v1.2.3