How to Select Default IPv6 Source Address for Outbound Traffic with Netplan

edited December 2020 in Technical

This post is originally published on yoursunny.com blog https://yoursunny.com/t/2020/preferred-lft-netplan/

I bought a few Virtual Private Servers (VPS) on Black Friday, and have been busy setting them up.
Nowadays, most VPS comes with an IPv6 subnet that contains millions of possible addresses.
Initially, only one IPv6 address is assigned to the server, but the user can assign additional addresses as desired.
Given that I plan to run multiple services within a server, I added a few more IPv6 addresses so that each service can have a unique IPv6 address.

One of my servers is using KVM virtualization technology, in which I installed Ubuntu 20.04 operating system manually from an ISO image.
Unlike a template-based installation, an ISO-installed Ubuntu 20.04 system manages its networks using Netplan, a backend-agnostic network configuration utility that generates network configuration from YAML files.
Most VPS control panels, including SolusVM and Virtualizer, are unable to generate the YAML files needed by Netplan.
IPv4 works out of box via DHCP, but IPv6 has to be configured manually.
To assign two IPv6 addresses to my server, I need to write the following in /etc/netplan/01-netcfg.yaml:

network:
  version: 2
  ethernets:
    ens3:
      dhcp4: true
      addresses:
        - 2001:db8:30fa:5877::1/64
        - 2001:db8:30fa:5877::beef/64
      routes:
        - to: ::/0
          via: 2001:db8:30fa::1
          on-link: true
      nameservers:
        addresses:
        - 2001:4860:4860::8888
        - 2606:4700:4700::1111

I intend to host my secret beef recipes on its unique IPv6 address 2001:db8:30fa:5877::beef, and use the other address 2001:db8:30fa:5877::1 for outbound traffic such as pings and traceroutes.
However, I noticed that the wrong address is being selected for outgoing packets:

$ ping 2001:db8:57eb:8479::2

$ sudo tcpdump -n icmp6
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on venet0, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
00:44:48.704099 IP6 2001:db8:30fa:5877::beef > 2001:db8:57eb:8479::2: ICMP6, echo request, seq 1, length 64
00:44:48.704188 IP6 2001:db8:57eb:8479::2 > 2001:db8:30fa:5877::beef: ICMP6, echo reply, seq 1, length 64
00:44:49.704011 IP6 2001:db8:30fa:5877::beef > 2001:db8:57eb:8479::2: ICMP6, echo request, seq 2, length 64
00:44:49.704099 IP6 2001:db8:57eb:8479::2 > 2001:db8:30fa:5877::beef: ICMP6, echo reply, seq 2, length 64

I started searching for a solution, and learned that:

  • Default Address Selection for Internet Protocol version 6 (IPv6) is a very complicated topic.
  • An application can explicitly specify a source address.
    For example, I can invoke ping -I 2001:db8:30fa:5877::1 2001:db8:57eb:8479::2 to use the desired source address.

  • Each local IPv6 address can be either "preferred" or "deprecated".
    If the application does not specify a source address, the system would prefer to use a "preferred" address instead of a "deprecated" address.

Currently, both addresses are "preferred" on my server:

$ ip addr show dev ens3
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 92:b6:ab:eb:04:00 brd ff:ff:ff:ff:ff:ff
    inet 192.0.2.30/24 brd 192.0.2.255 scope global dynamic ens3
       valid_lft 21599977sec preferred_lft 21599977sec
    inet6 2001:db8:30fa:5877::1/64 scope global
       valid_lft forever preferred_lft forever
    inet6 2001:db8:30fa:5877::beef/64 scope global
       valid_lft forever preferred_lft forever

This means, both addresses are equally possible of being used as the default source address.
If I can make 2001:db8:30fa:5877::1 "preferred" and all other addresses "deprecated", I would achieve my goal of making 2001:db8:30fa:5877::1 the default source address for outbound traffic.

How can I set an IPv6 address as "deprecated"?
After some digging, I found that it is controlled by the preferred_lft (preferred lifetime) attribute.
This attribute indicates the remaining time an IP address is to remain "preferred".
Unless it is set to "forever", preferred_lft counts down every second, and the IP address becomes "deprecated" when it reaches zero.
If the IP address was added with preferred_lft set to zero, it would be "deprecated" since the beginning.

Netplan gained support for preferred_lft setting very recently since version 0.100-0ubuntu4.
The syntax to specify preferred_lft of an IPv6 address is:

addresses:
  - 2001:db8:30fa:5877::1/64
  - 2001:db8:30fa:5877::beef/64:
      lifetime: 0

Notice that there is a colon (:) after the IPv6 address 2001:db8:30fa:5877::beef/64, because it is syntactically a map key instead of a string value.
The equivalent structure in JSON looks like:

{
  "addresses": [
    "2001:db8:30fa:5877::1/64",
    {
      "2001:db8:30fa:5877::beef/64": {
        "lifetime": 0
      }
    }
  ]
}

After applying this change, the IPv6 address 2001:db8:30fa:5877::beef is correctly marked as "deprecated" and no longer selected as the default source address.
Now I can securely host my secret beef recipes on 2001:db8:30fa:5877::beef without worrying about others discovering this "deprecated" IPv6 address through my outbound network traffic.

$ sudo netplan apply

$ ip addr show dev ens3
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 92:b6:ab:eb:04:00 brd ff:ff:ff:ff:ff:ff
    inet 192.0.2.30/24 brd 192.0.2.255 scope global dynamic ens3
       valid_lft 21598263sec preferred_lft 21598263sec
    inet6 2001:db8:30fa:5877::1/64 scope global
       valid_lft forever preferred_lft forever
    inet6 2001:db8:30fa:5877::beef/64 scope global deprecated
       valid_lft forever preferred_lft 0sec

$ ping 2001:db8:57eb:8479::2

$ sudo tcpdump -n icmp6
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on venet0, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
00:44:48.704099 IP6 2001:db8:30fa:5877::1 > 2001:db8:57eb:8479::2: ICMP6, echo request, seq 1, length 64
00:44:48.704188 IP6 2001:db8:57eb:8479::2 > 2001:db8:30fa:5877::1: ICMP6, echo reply, seq 1, length 64
00:44:49.704011 IP6 2001:db8:30fa:5877::1 > 2001:db8:57eb:8479::2: ICMP6, echo request, seq 2, length 64
00:44:49.704099 IP6 2001:db8:57eb:8479::2 > 2001:db8:30fa:5877::1: ICMP6, echo reply, seq 2, length 64

This article explained how to change default IPv6 source address selection by marking an IPv6 address "deprecated" via Netplan.
The described technique works in KVM and Ubuntu 20.04, and has been tested in a VPS provided by Spartan Host.
If you are using a OpenVZ 7 container, check out How to Select Default IPv6 Source Address for Outbound Traffic in OpenVZ 7.

Comments

  • Nice a good article about IPv6.

    Thanked by (1)Not_Oles

    A simple uptime dashboard using UptimeRobot API https://upy.duo.ovh
    Currently using VPS from BuyVM, GreenCloudVPS, Gullo's, Hetzner, HostHatch, InceptionHosting, LetBox, MaxKVM, MrVM, VirMach.

  • Also, for those of you (us!) who lwant to do things on demand, via cli, here's the equivalent to add additional addresses manually:

    ADD:ip -6 addr add IPV6::ADDRESS dev IFACE preferred_lft 0
    DEL: ip -6 addr del IPV6::ADDRESS dev IFACE preferred_lft 0

    This way you can just bring up/down address as you need them for testing etc.

    Just in case it helps anyone...

  • Nice write-up. Anything that helps simplify the dark magic of IPv6 is always good :)

    Get the best deal on your next VPS or Shared/Reseller hosting from RacknerdTracker.com - The original aff garden.

  • Good post, thank you.

Sign In or Register to comment.