Ipg: A simple IPv6 address generator for lazy netadmins

In July 2019, an AS I manage was born. As long as I worked on the deployment of the network and its services, I tried many ways to generate the IPv6 addresses for each services. SLAAC, simple IPv6 addresses, based on the hostname and random ones.

SLAAC addresses are OK but requires using EUI-64 to get a stable IPv6 address and it has the drawback of exposing the MAC address and potentially permits vendor-specific attacks. It came with another problem when virtualization or containers (LXC) is used. As I would like to push even further Infrastructure as a Code (IaC) on our infrastructure with tools like Terraform, it would require more work to get the IPv6 address of the VM/container.

Simple IPv6 addresses like 2001:db8:cafe::1 or 2001:db8:dc1::dead:beef are common addresses and are easily targeted by network scans and attacks. We mainly use this approach on border routers and for anycast routing, not for unicast services.

Random static IPv6 addresses or based on the hostname “solve” the problem I mentioned about IaC by assigning a static IPv6 address to a container or a VM. Generating an IPv6 address based on the hostname was for me a pretty nice and easy way to generate IPv6 addresses and also have the advantage of containing a fragment of the hostname in case we have an IPv6 address not in the IPAM or a reverse DNS record. With the time, I also used this method for the network at home.

2020, the premises

In 2020, I decided to write a small script in Python called v6gen that helped us to generate IPv6 addresses based on this approach. The way it worked was very simple, it took in parameter a IPv6 subnet and a FQDN. The script take the hostname part from the FQDN, convert it in hexadecimal. If the hostname doesn’t fit, it removes the vowels and progressively remove last characters to let the hostname fit.

$ v6gen 2001:db8:beef:cafe::/64 -n myserver.blah.net
2001:db8:beef:cafe:6d79:7365:7276:6572/128

If the script made pretty much the job, some features like generating the ARPA version of the IP was missing and unfortunately I never improved the script to expose this info. For the domain name of the AS, we use Gandi LiveDNS and so it’s possible for us to quickly create a record via the API. For our rDNS zones, we deployed Knot servers but we don’t have some sort of APIs to manage our zones from scripts and automation tools. Instead, we need to convert the IP generated by v6gen and add it to our zones and deploy them via Ansible.

2022, ipg is born!

By the end of 2021, I wanted to rewrite entirely the script to be more feature-complete and also to play with Go. Unfortunately, time and motivation was not there and I simply created a task on my ToDo list for one day. In August 2022, I wanted to rework on the project, motivated like never to finish it. I took what I started earlier and worked on what I called ipg for… IP generator. Great name heh?

ipg is built with few great libraries like Kong, a CLI parser, c-robinson/iplib for IP addresses parsing and manipulation and net from the Go standard library.

It’s released under MIT license and available on GitHub. In addition of releasing binaries for pretty much all operating systems and architectures, I also published a tiiiinyy Docker image that weight only 5.72MB, thanks to the usage of Busybox as base image! It can be pulled with the command:

docker pull ghcr.io/themimitoof/ipg

This time I wanted to make the tool more flexible, add missing features from v6gen and make it usable and understandable by machines. Let take a look on how it works:

$ ipg --help
Usage: ipg <subnet>

A simple IPv6 generator for lazy netadmins.

Arguments:
  <subnet>    IPv6 Subnet

Flags:
  -h, --help                Show context-sensitive help.
  -r, --random              Generate a random IPv6 address on the given subnet.
  -n, --name="hostname"     Specify the hostname of a machine, an IPv6 address will be generated based on it.
  -s, --silent              Only display values without their labels.
  -f, --format="console"    Specify the type of output. Possible values: console, json
  -a, --address             Display the generated IP address.
  -R, --reverse             Display the ARPA version of the IP address.
  -d, --dns                 Returns a DNS record ready to paste to a DNS zone.
  -x, --rrecord             Returns a rDNS record ready to paste to a DNS zone.
  -t, --ttl=86400           TTL value for DNS returned DNS records.

To generate an IPv6 address based on a hostname/FQDN, nothing changed compared to v6gen except returned information:

$ ipg -n hello 2001:db8:beef::/64
IP address: 2001:db8:beef::68:656c:6c6f
Reverse IP address: f.6.c.6.c.6.5.6.8.6.0.0.0.0.0.0.0.0.0.0.f.e.e.b.8.b.d.0.1.0.0.2.ip6.arpa
DNS record: hello    86400    IN    AAAA    2001:db8:beef::68:656c:6c6f
ARPA DNS record: f.6.c.6.c.6.5.6.8.6.0.0.0.0.0.0.0.0.0.0.f.e.e.b.8.b.d.0.1.0.0.2.ip6.arpa.    86400    IN    PTR    hello

In case you don’t want to generate an IPv6 address based on a hostname/FQDN, you can also generate a random one with the -r flag:

$ ipg -r 2001:db8:beef::/64
IP address: 2001:db8:beef:0:7ca:edea:8ae4:65e9
Reverse IP address: 9.e.5.6.4.e.a.8.a.e.d.e.a.c.7.0.0.0.0.0.f.e.e.b.8.b.d.0.1.0.0.2.ip6.arpa
DNS record: hostname    86400    IN    AAAA    2001:db8:beef:0:7ca:edea:8ae4:65e9
ARPA DNS record: 9.e.5.6.4.e.a.8.a.e.d.e.a.c.7.0.0.0.0.0.f.e.e.b.8.b.d.0.1.0.0.2.ip6.arpa.    86400    IN    PTR    hostname

By default, the IPv6 address, the ARPA version and DNS records are returned. In case you only want to get the IPv6 address and the PTR record, you can use filters:

$ ipg -axn hello 2001:db8:beef::/64
IP address: 2001:db8:beef::68:656c:6c6f
ARPA DNS record: f.6.c.6.c.6.5.6.8.6.0.0.0.0.0.0.0.0.0.0.f.e.e.b.8.b.d.0.1.0.0.2.ip6.arpa.    86400    IN    PTR    hello

There are few filters available:

The -s filter can be useful if you want to use ipg in a bash script, like a VM deployment script.

$ ipg -san hello 2001:db8:beef::/64
2001:db8:beef::68:656c:6c6f

If you want a random IPv6 address and have ready to copy-pasta DNS records, you can mix the -r flag and the -n one:

$ ipg -rn toto.foo.bar 2001:db8:beef::/64
IP address: 2001:db8:beef:0:9962:7058:c2a7:996
Reverse IP address: 6.9.9.0.7.a.2.c.8.5.0.7.2.6.9.9.0.0.0.0.f.e.e.b.8.b.d.0.1.0.0.2.ip6.arpa
DNS record: toto.foo.bar.    86400    IN    AAAA    2001:db8:beef:0:9962:7058:c2a7:996
ARPA DNS record: 6.9.9.0.7.a.2.c.8.5.0.7.2.6.9.9.0.0.0.0.f.e.e.b.8.b.d.0.1.0.0.2.ip6.arpa.    86400    IN    PTR    toto.foo.bar

In case you would like to use other languages like Python, Ruby, for scripting purposes or for example creating an Ansible or Puppet module, you change the format of the command to output in a JSON format with the --format json or -f json argument:

$ ipg -f json -n hello 2001:db8:beef::/64 | jq
{
  "hostname": "hello",
  "ip_addr": "2001:db8:beef::68:656c:6c6f",
  "arpa_addr": "f.6.c.6.c.6.5.6.8.6.0.0.0.0.0.0.0.0.0.0.f.e.e.b.8.b.d.0.1.0.0.2.ip6.arpa",
  "dns_record": "hello    86400    IN    AAAA    2001:db8:beef::68:656c:6c6f",
  "ptr_record": "f.6.c.6.c.6.5.6.8.6.0.0.0.0.0.0.0.0.0.0.f.e.e.b.8.b.d.0.1.0.0.2.ip6.arpa.    86400    IN    PTR    hello"
}

Note: Using the JSON format don’t take in consideration the given filters.

For Go projects, you can even import ipg and use the exposed methods on your projects. The API documentation is available on pkg.go.dev.

Conclusion

ipg probably will not fit the needs of any other network in the world than the AS I maintain and my home network. But in case you start using it on your network, star the project on GitHub and shoot me a message on Twitter, I will be happy to know that ipg is used elsewhere 😉.

If you see a potential on ipg and you would like to see new features that match with your needs and you think that can be used more broadly, feel free to contribute to the code or discuss with me by opening a discussion on GitHub.