unifi-utilities / unifios-utilities

A collection of enhancements for UnifiOS based devices
GNU General Public License v3.0
3.9k stars 419 forks source link

ipv6 ULA Support via boot script #104

Open helbgd opened 3 years ago

helbgd commented 3 years ago

Hello, would it anyhow possible to add ipv6 ULA support to the UDM ?

The Problem is I like to provide a pihole container trough podman in a mgmt Network that is reachable via all VLAN separated networks.

My ISP changes my ipv6 prefix from time to time and therefore a fix configured ULA would help me in assigning this ipv6 IP of the pihole to my end client's via SLAAC from the UDM ...

I think the script should manually assign all bridged vlan interfaces on the UDM a own specified ipv6 ULA address then enable SLAAC Service to provide additional ULA adsresses to end clients in the VLAN's

Does anyone have a comment on this ?

the counterpart on the USG can be found under following topic: https://community.ui.com/questions/IPv6-Have-USG-assign-Unique-Local-Address-through-Router-Advertisement/e516521b-9f40-4a00-a0b6-60311790276d#comment/5cb60cbc-7bb4-46cd-a284-1d52db8b1173

peacey commented 3 years ago

I use IPv6 ULAs in the boot script already. They work fine but there are some caveats.

  1. You have to manually assign the ULA prefix (fd00::1/64 for example) to the bridge interface first.
  2. SLAAC is enabled and clients automatically configure addresses with the ULA prefix as long as Router Advertisement and Prefix Delegation is enabled in the Unifi Network LAN settings for that VLAN.
  3. The Unifi OS automatically deletes the ULA prefix from the bridge interfaces every few hours because it reconfigures them in response to prefix and lease changes. This means that you need a watcher daemon that watches the interfaces for when the ULA prefix is deleted and readds it if needed.

(1) and (2) are easy to solve. For (3), solving it is as simple as using a shell script with an infinite loop that checks the interface every second and re-adds it it's missing, then running the script with nohup in the background. Alternatively, you can code a go program that watches the netlink messages for when the interface is changed and act on that message. I've done both solutions and both work.

helbgd commented 3 years ago

okay ...

what if you leave the ipv6 completely disabled via Unifi gui for the networks and assign a prefix assigned ipv6 gua and ula to each bridge interface manually via the init.d scripts ?

and write custom config files to /var/run/dnsmasq.conf.d/ to provide the slaac config by ourself ? i hope adding just a single file into that directory should not get deleted by the udapi server binary the dnsmasq thenn simply can get reloaded with kill -HUP and something like this as it reloads each config file in that directory ... as per

ps a |grep dnsmasq
 1447 root     grep dnsmasq
27465 nobody   /usr/sbin/dnsmasq --conf-dir=/run/dnsmasq.conf.d/

the other question: not sure how this is normally beeing done:

Under a linux router how is a gua based on the prefix from the wan interface be built and kept refreshed once it changes ?

I hope the udapi server binary does not touch this ipv6 on the bridge interface at all once it is disabled in the gui for that network

helbgd commented 3 years ago
# ps auxwww |grep dhcp6c
 2166 root     /usr/sbin/odhcp6c -e -v -s /usr/share/ubios-udapi-server/ubios-odhcp6c-script -P 58 eth4
 4643 root     grep dhcp6c
# 

bad this is done by /usr/share/ubios-udapi-server/ubios-odhcp6c-script wich is getting triggered once the prefix changes this is a binary so once we want to do this we have to rewrite that ubios-odhcp6c-script ourself in a shell script or simmilar ...

not sure if this is worth the effort

helbgd commented 3 years ago

I use IPv6 ULAs in the boot script already. They work fine but there are some caveats.

  1. You have to manually assign the ULA prefix (fd00::1/64 for example) to the bridge interface first.
  2. SLAAC is enabled and clients automatically configure addresses with the ULA prefix as long as Router Advertisement and Prefix Delegation is enabled in the Unifi Network LAN settings for that VLAN.
  3. The Unifi OS automatically deletes the ULA prefix from the bridge interfaces every few hours because it reconfigures them in response to prefix and lease changes. This means that you need a watcher daemon that watches the interfaces for when the ULA prefix is deleted and readds it if needed.

(1) and (2) are easy to solve. For (3), solving it is as simple as using a shell script with an infinite loop that checks the interface every second and re-adds it it's missing, then running the script with nohup in the background. Alternatively, you can code a go program that watches the netlink messages for when the interface is changed and act on that message. I've done both solutions and both work.

Do you have an example of the go and shell script ?

Thanks in advance

peacey commented 3 years ago

Do you have an example of the go and shell script ?

Sure, here is a simple shell script that checks every second and re-adds the ULAs if they're missing. Rename it to ula-watcher.sh (could only upload .txt), chmod +x ula-watcher.sh, then run it in the background with nohup ./ula-watcher.sh > ula-watcher.log &

boostchicken commented 3 years ago

@peacey @helbgd will you guys open a pull request and get this contributed to the repo?

Menschomat commented 3 years ago

@peacey I have tested your script. It works great. It's a shame that the UDM isn't capable to do this from the UI... Is it the right way, to use boostchicken/on-boot-script for this? I just copied my ula-watcher.sh to '/mnt/data/on_boot.d'. It works fine, but maybe you tell us, how you would deploy it in production?

peacey commented 3 years ago

@Menschomat, the script I wrote doesn't run in the background by default, so it will actually block other scripts from running if you use it as is in on_boot.d. You have to use the nohup ... & command to run it in the background.

I would place the ula-watcher.sh script in /mnt/data or /mnt/data/scripts (not in on_boot.d). Then make a boot script in /mnt/data/on_boot.d/run-ula-watcher.sh with these contents to run the script in the background:

#!/bin/sh
nohup /mnt/data/scripts/ula-watcher.sh > /mnt/data/scripts/ula-watcher.log &

Then run chmod +x on it. This way the script will run in the background. Confirm it is running in the background after you reboot by checking ps | grep ula-watcher.

The nohup is necessary to run things in the background because the on-boot-script opens an SSH connection to the UDM base OS to run the boot scripts, and after the SSH connection is closed, all programs, even those running in the background will be terminated because SIGHUP will be sent to the programs. To get around that, we use nohup which runs the command in the background and makes the program ignore SIGHUP so it doesn't get terminated after the on-boot-script SSH session closes.

Menschomat commented 3 years ago

@peacey thank you very much for the fast response. This was very helpful 👍 It works like a charm.

andrewaylett commented 3 years ago

Worth noting that we really should be careful in assigning prefixes -- note the MUST NOT in https://tools.ietf.org/html/rfc4193#section-3.2. Don't use the same hard-coded prefixes as everyone else :).

There's a tool to generate prefixes using the recommended algorithm here: https://cd34.com/rfc4193/

andrewaylett commented 3 years ago

An alternative implementation:

In /mnt/data/ula/ensure_ula, ensure you set ULA_PREFIX using a value from https://cd34.com/rfc4193/ and you probably want to match the subnet numbers with any existing global subnets for each bridge interface rather than copying mine.

#!/bin/sh

ULA_PREFIX="fd60:e0ce:679d"

add_ula () {
  if [ -z "`ip address show dev $1 to $ULA_PREFIX::/48`" ]
  then
    ip address add $ULA_PREFIX:$2::1/64 dev $1
  fi
}

add_ula br0 0
add_ula br2 1
add_ula br3 2
add_ula br30 3
add_ula br40 4

In /mnt/data/on_boot.d/17-ula.sh, sets things up to run ensure_ula every minute:

#!/bin/sh

echo "* * * * * sh /mnt/data/ula/ensure_ula" > /etc/cron.d/ula

Ideally we'd have something that can run without needing to be edited -- a script that generates a ULA prefix if none exists, save it in a file that'll be kept, and examines existing IPv6 addresses if there's any assigned to see which subnet to use.

ikkerus commented 3 years ago

Thanks for the script, but I need to bump this. Same issue here with German Telecom. They mostly offer PD with changing addresses unless you have a business account with fixed addresses. But still I would rather use NATv6 and do not assign a public addresses. ULA support is needed on all Ubiquiti Products.

Are you planning on implementing this into the udm-tools? Is there a way to help besides submitting a pull request and developing it?

helbgd commented 1 year ago

simple and dirty workaround if you want RA for a ULA prefix from dnsmasq enter following into on-state-change.sh after adding the ipv6 ula address to br0 device for example:

#!/bin/sh

ULA_PREFIX="fd60:e0ce:679d"
# on-state-change.sh 

add_ula () {
  if [ -z "`ip address show dev $1 to $ULA_PREFIX::/48`" ]
  then
    ip address add $ULA_PREFIX:$2::1/64 dev $1
  fi
}

add_ula br0 0
#add_ula br2 1
#add_ula br3 2
#add_ula br30 3
#add_ula br40 4

# only add entry to dnsmasq config if it does not exist
add_dnsmasq () {
 range=dhcp-range=set:ula6_$1,$ULA_PREFIX:$2::2,$ULA_PREFIX:$2::7d1,constructor:$1,64,86400
 conf=$(find /run/dnsmasq.conf.d/ -type f -name "*$1*IPV6.conf")
   if [ -z "`grep ula6_$1 $conf`" ]
   then
     echo $range >> $conf
     # reload dnsmasq config
     pkill -u 0 --signal HUP dnsmasq
   fi
}

add_dnsmasq br0 0

# the same can be done for ip6tables firewall rules:
ip6tables -S | grep custom-80-443-tcp || ip6tables -I UBIOS_WAN_IN_USER -d ::xx:xxxx:xxxx:x/::ffff:ffff:ffff:ffff -p tcp -m comment --comment custom-80-443-tcp -m multiport --dports 80,443 -j RETURN
ip6tables -S | grep custom-80-443-udp || ip6tables -I UBIOS_WAN_IN_USER -d ::xx:xxxx:xxxx:x/::ffff:ffff:ffff:ffff -p udp -m comment --comment custom-80-443-udp -m multiport --dports 80,443 -j RETURN
# the above for example expose port 443 and 80 tcp for example in your ipv6 subnet to internet even with changing prefixes from provider because the firewall entry matches only the host part of the ipv6 GUA (the part after /64) and the provider prefix part of the address can change at any time ::ffff:ffff:ffff:ffff hostmask for masking the IPv6 Address ensures exactly that
# notice the GUA of the host should be always the same as it is calculated via the MAC address of the host
# with the above in place you can fully utilize customize everything you want
# ip6tables -S gives the iptables output in a way you can feed it back via commands to ip6tables for example
xrh0905 commented 1 year ago

An alternative implementation:

In /mnt/data/ula/ensure_ula, ensure you set ULA_PREFIX using a value from https://cd34.com/rfc4193/ and you probably want to match the subnet numbers with any existing global subnets for each bridge interface rather than copying mine.

#!/bin/sh

ULA_PREFIX="fd60:e0ce:679d"

add_ula () {
  if [ -z "`ip address show dev $1 to $ULA_PREFIX::/48`" ]
  then
    ip address add $ULA_PREFIX:$2::1/64 dev $1
  fi
}

add_ula br0 0
add_ula br2 1
add_ula br3 2
add_ula br30 3
add_ula br40 4

In /mnt/data/on_boot.d/17-ula.sh, sets things up to run ensure_ula every minute:

#!/bin/sh

echo "* * * * * sh /mnt/data/ula/ensure_ula" > /etc/cron.d/ula

Ideally we'd have something that can run without needing to be edited -- a script that generates a ULA prefix if none exists, save it in a file that'll be kept, and examines existing IPv6 addresses if there's any assigned to see which subnet to use.

Works for me, Thanks!

EdwardCooke commented 9 months ago

I was able to use what @helbgd built on my UDM SE, with a couple of modifications.

  1. The subnet mask check for whether the IP was already assigned needed to be $ULA_PREFIX:$2::/64
  2. The persistent data is mounted on /data/ not /mnt/data
  3. I made it so it only restarted dnsmasq if it was required

Here's the new script that works on my Ubiquiti Dream Machine SE

#!/bin/bash

ULA_PREFIX="fded"

add_ula () {
  if [ -z "`ip address show dev $1 to $ULA_PREFIX:$2::/64`" ]
  then
    ip address add $ULA_PREFIX:$2::1/64 dev $1
  fi
}

add_ula br0 0
add_ula br2 2
add_ula br20 20
add_ula br100 100
add_ula br101 101
add_ula br102 102

ADDEDDNSMASQ=0

# only add entry to dnsmasq config if it does not exist
add_dnsmasq () {
  range=dhcp-range=set:ula6_$1,$ULA_PREFIX:$2::2,$ULA_PREFIX:$2::7d1,constructor:$1,64,86400
  conf=$(find /run/dnsmasq.conf.d/ -type f -name "*$1*IPV6.conf")
  if [ -z "`grep ula6_$1 $conf`" ]
  then
    echo $range >> $conf
    echo "Added $range"
    ADDEDDNSMASQ=1
    # reload dnsmasq config
    # pkill -u 0 --signal HUP dnsmasq
  fi
}

[ "$ADDDNSMASQ" == "1" ] && pkill -u 0 --signal HUP dnsmasq

Don't forget to chmod +x that script so it will work.

felipejfc commented 5 months ago

The scripts here works great for setting up ULA, however, did anyone get reverse name lookups on the ULA ipv6 addresses working? I'm trying to overcome a "problem" where adguard won't properly assign client names when queries where sent from ipv6 addr

Gandalf1783 commented 4 months ago

Hm, so while my devices do infact get the ULA addresses, I cannot use them to send/receive packets across the ULA networks (or more accurately across VLANs). The UDM SE can ping into both ULA v6 networks, but the devices cannot ping each other...

sgreadly commented 2 months ago

Ok, THANK YOU for this solution! It worked, but needed a few tweaks & requisites.

1) /mnt/data/onboot.d/ does not exist. Actually, /mnt is empty. So we need to install onboot.d and it'll now be in /data/ instead.

2) the script, at least in my UDM SE v4.0.6 did not like the sigle [ ] brackets in the last line. I had to do it the long way of if [ ... ] fi

3) running cron with 'sh' did not work either. I had to run bash -c 'sh ...' via root.

To summarise,

1) Install onboot,

curl -fsL "https://raw.githubusercontent.com/unifi-utilities/unifios-utilities/HEAD/on-boot-script-2.x/remote_install.sh" | /bin/bash

Reference from here.

cd /data/onboot.d/
chmod -x 05-install-cni-plugins.sh 06-cni-bridge.sh

2) Get your prefix:

Enter your UDM SE interface's MAC address and get the prefix to use.

https://cd34.com/rfc4193/

for this example, we'll use fdaa:abcd:1234.

3) Add the ULA script:

You can put it anywhere in /data/. I made a directory called ipv6-ula for consistency.

mkdir /data/ipv6-ula
vim /data/ipv6-ula/ensure_ula.sh

Add the following. Uncomment the interfaces you need.

#!/bin/bash

ULA_PREFIX="fdaa:abcd:1234"

add_ula () {
  if [ -z "`ip address show dev $1 to $ULA_PREFIX:$2::/64`" ]
  then
    ip address add $ULA_PREFIX:$2::1/64 dev $1
  fi
}

add_ula br0 0
#add_ula br2 2
#add_ula br20 20
#add_ula br100 100
#add_ula br101 101
#add_ula br102 102

ADDEDDNSMASQ=0

# only add entry to dnsmasq config if it does not exist
add_dnsmasq () {
  range=dhcp-range=set:ula6_$1,$ULA_PREFIX:$2::2,$ULA_PREFIX:$2::7d1,constructor:$1,64,86400
  conf=$(find /run/dnsmasq.conf.d/ -type f -name "*$1*IPV6.conf")
  if [ -z "`grep ula6_$1 $conf`" ]
  then
    echo $range >> $conf
    echo "Added $range"
    ADDEDDNSMASQ=1
  fi
}

# Use single brackets for compatibility
if [ "$ADDEDDNSMASQ" = "1" ]; then
  pkill -u 0 --signal HUP dnsmasq
fi

Then,

chmod a+x /data/ipv6-ula/ensure_ula.sh

4) Setup on_boot script:

Then, in /data/on_boot.d/17-ula.sh, sets things up to run ensure_ula every minute:

vim /data/on_boot.d/17-ula.sh

add:

#!/bin/sh

echo "* * * * * root /bin/bash -c 'sh /data/ipv6-ula/ensure_ula.sh'" > /etc/cron.d/ula

and then chmod a+x /data/on_boot.d/17-ula.sh

That did the trick! And I got this working with my pihole too.

TheKigen commented 1 month ago

The add_dnsmasq function currently requires IPv6 be enabled with Prefix Delegation or Static in Unifi Network App. It also seems to currently break DNSMASQ in Unifi with the following error.

2024 Aug 20 05:05:03 UDM-SE prefix must be zero with "constructor:" argument at line 13 of /run/dnsmasq.conf.d//dhcp.dhcpServers-net_Internet_of_Things_br2_192-168-2-0-24_IPV6.conf
2024 Aug 20 05:05:03 UDM-SE FAILED to start up

This error apparently prevents the DNS server from starting.