Category Archives: Networking

Networking Stuff!

Configuring CenturyLink IPv6 (6rd) on a UniFi UXG-Pro

If you’re using CenturyLink Fiber with a UniFi device, you may have noticed that getting IPv6 working isn’t quite as straightforward as enabling it in the UniFi Network application.

While UniFi supports IPv6, CenturyLink’s legacy network still relies on a technology called 6rd (IPv6 Rapid Deployment) rather than native IPv6 in many areas. Unfortunately, the UXG-Pro doesn’t provide a native 6rd configuration option through its management interface.

After spending some time digging through various GitHub repositories, UniFi community posts, and the underlying Linux kernel configuration, I was able to get IPv6 working using a manually configured tunnel.

This blog post is more of a reflection of how I was able to get IPv6 working with CenturyLink, rather than a supported guide that will give you IPv6 and guarantee it will work indefinitely. I’ve tried to explain why and what is going on where applicable to capture a working solution at least short-term. Yay for access to IPv6-only resources now!

My network setup

For reference, here is the environment I’m working with at the time I’m writing this post:

ComponentConfiguration
Internet providerCenturyLink Fiber
ISP networkCENTURYLINK-US-LEGACY-QWEST
OrganizationCenturyLink Communications, LLC
Network topologyCenturyLink Fiber → ONT → UXG-Pro
GatewayUbiquiti UniFi UXG-Pro
UXG-Pro firmware5.1.26
UniFi Network applicationOfficial – 10.6.106
WAN authenticationPPPoE
WAN VLAN201
Public IPv4Static assignment via PPPoE
IPv6 technology6rd
CenturyLink 6rd prefix2602::/24
CenturyLink 6rd relay205.171.2.64

One important distinction is that I’m using a static public IPv4 address. Since CenturyLink’s 6rd implementation derives your IPv6 prefix directly from your public IPv4 address, having a static assignment makes the configuration considerably easier.

That said, most residential customers probably don’t have a static public IPv4 address. I have a script later in this guide that calculates the correct IPv6 prefix from your current WAN IPv4 address, so the tunnel configuration can work with either static or dynamic assignments, but there is one additional consideration with dynamic addresses: when your public IPv4 changes, your LAN’s IPv6 prefix also needs to change. I’ll explain that limitation later.

Before digging in, what is IPv6 6rd and how does it work?

6rd stands for IPv6 Rapid Deployment and is defined in RFC 5969.

The technology allows an ISP to deliver IPv6 connectivity across an existing IPv4 network without having to deploy native IPv6 throughout its entire infrastructure.

Instead of your router sending IPv6 packets directly over an IPv6-enabled WAN connection, the router encapsulates IPv6 packets inside IPv4 packets and sends them to a designated ISP relay.

The relay then forwards the traffic to the native IPv6 Internet.

Here’s roughly what that looks like:

LAN devices
    |
    | Native IPv6
    |
UniFi UXG-Pro
    |
    | IPv6 encapsulated in IPv4
    | (SIT tunnel / IPv4 protocol 41)
    |
CenturyLink IPv4 network
    |
    |
CenturyLink 6rd Border Relay
205.171.2.64
    |
    | Native IPv6
    |
IPv6 Internet

In my environment, CenturyLink uses the following settings:

ParameterValue
6rd IPv6 prefix2602::/24
IPv4 mask length0
Border relay IPv4205.171.2.64
Tunnel MTU1472

Note: these settings are specific to the CenturyLink legacy deployment I’m using and shouldn’t automatically be assumed for every CenturyLink or Quantum Fiber connection.

How CenturyLink calculates your IPv6 prefix

The interesting part is how CenturyLink assigns IPv6 addresses.

With a /24 6rd prefix and an IPv4 mask length of zero, the router’s entire 32-bit public IPv4 address is embedded directly into the IPv6 prefix.

That gives us:

24-bit IPv6 prefix
+ 32-bit public IPv4 address
----------------------------
56-bit IPv6 prefix

In other words, each public IPv4 address maps to its own /56 IPv6 prefix. A /56 provides 256 individual /64 subnets, which is more than enough for a typical home network.

For example, you could use one /64 for your default LAN, another for IoT devices, and additional subnets for guest or isolated networks.

It’s worth noting that 6rd isn’t NAT64 or an IPv4-to-IPv6 address translator. Your devices receive globally routable IPv6 addresses. The UXG-Pro simply transports their IPv6 packets across CenturyLink’s IPv4 infrastructure.

This also means you can host services over IPv6, provided you configure the appropriate firewall rules and the CenturyLink relay allows inbound connectivity.

Why the existing UniFi 6rd scripts didn’t work

While researching how to configure this, I came across the following GitHub repository: https://github.com/msimerson/unifi

The repository includes a script for configuring 6rd on UniFi gateways. Its README describes testing on a UXG-Max and indicates that the approach should work on the UXG-Pro and other UniFi gateways.

Unfortunately, that wasn’t the case with my UXG-Pro running firmware 5.1.26.

The repository relies on native Linux 6rd tunnel functionality, including a command similar to:

ip tunnel 6rd dev 6rd 6rd-prefix 2602::/24

When I ran the command, the router returned:

6rd: ioctl 89f9 failed: Invalid argument

To understand why, I checked the kernel configuration directly:

zcat /proc/config.gz 2>/dev/null | grep IPV6_SIT

The output was:

CONFIG_IPV6_SIT=y
# CONFIG_IPV6_SIT_6RD is not set

This tells us two important things:

  • CONFIG_IPV6_SIT=y means the Linux kernel supports IPv6-in-IPv4 tunneling using SIT interfaces.
  • CONFIG_IPV6_SIT_6RD is not set means the kernel was built without the specialized 6rd functionality.

This explains why the repository’s native 6rd command fails on my gateway, despite the README suggesting compatibility with the UXG-Pro.

The kernel supports the basic encapsulation needed to transport IPv6 inside IPv4, but it doesn’t support configuring the additional 6rd parameters with ip tunnel 6rd.

The workaround is to use a standard SIT tunnel with an explicitly configured CenturyLink relay.

Because CenturyLink provides a known border relay, we can manually create the tunnel, assign our derived IPv6 address, and configure routing without relying on the kernel’s native 6rd functionality.

Step 1: Connect to the UXG-Pro and identify your WAN interface

First, SSH into your UXG-Pro and access its debug shell using the Debug option from the UniFi Network console. Once you have shell access open, confirm that your PPPoE interface has an IPv4 address:

ip -4 addr show dev ppp0

In my environment, ppp0 is the PPPoE interface that receives my static public IPv4 address.

You should see something similar to:

inet YOUR_PUBLIC_IPV4 peer ... scope global ppp0

If you’re not using PPPoE, your IPv4 address may be assigned to a different interface.

You can list all IPv4 addresses using:

ip -4 addr show

Once you’ve identified the appropriate interface, you can extract its IPv4 address directly:

ip -4 -o addr show dev ppp0 |
    awk 'NR==1 {split($4,a,"/"); print a[1]}'

We’ll use this address when creating the tunnel.

Next, verify that the kernel supports SIT tunneling:

zcat /proc/config.gz 2>/dev/null | grep IPV6_SIT

As explained above, my UXG-Pro supports SIT but doesn’t have native 6rd functionality enabled.

Step 2: Calculate your CenturyLink IPv6 prefix

Before creating the tunnel, we need to determine which IPv6 addresses CenturyLink will route to us.

Rather than manually calculating these values every time, we can derive them from the current public IPv4 address.

For illustration, consider the reserved documentation IPv4 address:

192.0.2.1

Converting its individual octets into hexadecimal gives:

IPv4 octetHexadecimal
192C0
000
202
101

When combined with CenturyLink’s 2602::/24 prefix, this produces:

2602:00c0:0002:0100::/56

Which can be shortened to:

2602:c0:2:100::/56

From that /56, we can assign addresses to our tunnel and LAN networks.

PurposeExample
Customer 6rd prefix2602:c0:2:100::/56
Tunnel IPv6 address2602:c0:2:100::1/128
Default LAN subnet2602:c0:2:101::/64
Default LAN gateway2602:c0:2:101::1

The important thing to remember is that your IPv6 prefix isn’t arbitrary. It needs to be derived from your public IPv4 address.

Automatically calculate your IPv6 addresses

Instead of converting each octet manually, we can use Linux’s awk command.

First, obtain the current WAN IPv4 address:

WAN4=$(ip -4 -o addr show dev ppp0 |
    awk 'NR==1 {split($4,a,"/"); print a[1]}')

Next, calculate the /56 prefix:

PREFIX=$(echo "$WAN4" | awk -F. '{
    printf "2602:%x:%x:%x::/56\n",
        $1, ($2 * 256) + $3, $4 * 256
}')

echo "$PREFIX"

This produces the same /56 prefix that CenturyLink derives from your public IPv4 address.

However, because the 6rd prefix is /24, the precise bit placement matters. The first IPv4 octet occupies the low byte of the second IPv6 hextet, while the next two octets occupy the third hextet.

The final IPv4 octet occupies the high byte of the fourth hextet.

For our example, the result is:

2602:c0:2:100::/56

We can then select the first /64 for the tunnel and use additional /64 networks for our LANs and VLANs.

For example:

2602:c0:2:100::/64    Tunnel
2602:c0:2:101::/64    Default LAN
2602:c0:2:102::/64    IoT
2602:c0:2:103::/64    Guest
2602:c0:2:104::/64    Servers

We’ll automate these calculations in the restore script later in the article.

Step 3: Configure the IPv6 firewall

I strongly recommend configuring the tunnel manually before attempting to automate anything to prove the 6rd tunnel will work with your CenturyLink service. First I would start with the firewall configuration.

Unlike typical residential IPv4 networks, IPv6 doesn’t require NAT for devices to reach the Internet. Each client can receive its own globally routable IPv6 address. This means we need to ensure inbound firewall rules deny inbound traffic from the internet.

I added two rules to restrict unsolicited inbound IPv6 traffic arriving through the tunnel.

Block unsolicited inbound connections to LAN clients

ip6tables -I FORWARD 1 -i cl6rd \
    -m conntrack ! --ctstate ESTABLISHED,RELATED \
    -j DROP

Here’s what the command does:

ParameterDescription
-I FORWARD 1Inserts the rule at the beginning of the forwarding chain
-i cl6rdMatches packets arriving through the tunnel
-m conntrackEnables connection-state matching
! --ctstate ESTABLISHED,RELATEDMatches packets that aren’t part of an established or related connection
-j DROPDrops matching traffic

Essentially, devices on the LAN can initiate IPv6 connections, but unsolicited inbound connections arriving through the tunnel are dropped.

Protect the UXG-Pro itself

Next, add a similar rule for traffic destined directly to the gateway:

ip6tables -I INPUT 1 -i cl6rd \
    -m conntrack ! --ctstate ESTABLISHED,RELATED \
    -j DROP

This protects services running directly on the UXG-Pro.

You can inspect the rules using:

ip6tables -L INPUT -n -v
ip6tables -L FORWARD -n -v

These are deliberately restrictive inbound rules, not a complete IPv6 firewall policy. If you want to host publicly accessible services over IPv6, you’ll need to add specific inbound exceptions.

Next you should also make sure your overall IPv6 firewall policy doesn’t block required ICMPv6 functionality, particularly Packet Too Big messages used for Path MTU Discovery.

ip6tables -t mangle -I FORWARD 1 -o cl6rd -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
ip6tables -t mangle -I FORWARD 1 -i cl6rd -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1412

Why UniFi’s firewall doesn’t protect the tunnel

You might wonder whether these manual rules are really necessary, or whether you could just create an IPv6 firewall rule in the UniFi Network application. I tested this, and the short answer is that UniFi’s firewall never sees tunnel traffic.

UniFi builds its firewall from its own UBIOS_* chains, and the rules that apply your “Internet v6 In” and “Internet v6 Local” policies are only reached for traffic arriving on UniFi’s WAN interfaces (in my case ppp0 and eth2):

-A UBIOS_FORWARD_IN_USER -i ppp0 ... -j UBIOS_WAN_IN_USER
-A UBIOS_INPUT_USER_HOOK -i ppp0 ... -j UBIOS_WAN_LOCAL_USER

Traffic from the 6rd tunnel arrives on cl6rd, an interface UniFi doesn’t know about, so it skips those chains entirely. This includes UniFi’s default “drop all inbound” rule. The FORWARD and INPUT chains also default to ACCEPT, so without the two rules above, unsolicited inbound IPv6 traffic would reach both your LAN devices and the gateway itself.

To confirm, I created an “Internet v6 In” rule in the UniFi UI that drops all traffic to one of my LAN devices, then compared ip6tables-save output before and after. UniFi added the rule to UBIOS_WAN_IN_USER as expected, but that chain is only reachable from ppp0 and eth2, so the rule can never match traffic coming through the tunnel.

Bottom line: the two manual ip6tables rules are the only thing standing between the internet and your IPv6 devices.

On the plus side, in my testing these rules survived a reprovision triggered by a configuration change in the Network app. UniFi rebuilt its own UBIOS_* chains but left my top-level rules alone. A reboot removed the rules, the tunnel, and the route together, so the gateway fails safe (no IPv6 exposure, just no IPv6) until you re-run the restore script (or until you follow Step 11, which sets up a service that restores everything automatically at boot 🙂 ). I haven’t tested a firmware update yet. If I remember to update this blog post after a new one comes out, I’ll let you know.

Step 4: Manually configure the 6rd tunnel

These steps will let us verify that the Linux kernel UniFi is using supports the workaround, CenturyLink’s relay is reachable, and IPv6 connectivity actually works. Once everything is functional, we can move on to the script.

Create the SIT tunnel

First, capture the current public IPv4 address:

WAN4=$(ip -4 -o addr show dev ppp0 |
    awk 'NR==1 {split($4,a,"/"); print a[1]}')

Next, create the tunnel:

ip tunnel add cl6rd mode sit \
    local "$WAN4" \
    remote 205.171.2.64 \
    ttl 64

Here’s what the command does:

ParameterDescription
ip tunnel add cl6rdCreates a tunnel interface named cl6rd
mode sitEnables IPv6 encapsulation inside IPv4
local "$WAN4"Uses the current public IPv4 address
remote 205.171.2.64Sets CenturyLink’s border relay
ttl 64Sets the encapsulating IPv4 packet’s time-to-live

Unlike native 6rd configuration, we’re explicitly specifying where the IPv6-in-IPv4 packets need to go.

Enable the tunnel interface

Next, bring the tunnel online and set its MTU:

ip link set dev cl6rd mtu 1472 up

The MTU is important because the tunnel introduces additional overhead. A typical Ethernet connection supports an MTU of 1500 bytes. PPPoE adds 8 bytes of overhead, leaving 1492 bytes for the IPv4 packet. SIT encapsulation adds another 20-byte IPv4 header.

That gives us:

1500 - 8 (PPPoE) - 20 (IPv4 tunnel) = 1472

This is the appropriate tunnel MTU for my PPPoE configuration. If you’re using a connection without PPPoE, the appropriate MTU may differ.

Assign an IPv6 address to the tunnel

Next, calculate the tunnel IPv6 address from the public IPv4 address:

TUNNEL_IP=$(echo "$WAN4" | awk -F. '{
    printf "2602:%x:%x:%x::1/128\n",
        $1, ($2 * 256) + $3, $4 * 256
}')

Then assign it:

ip -6 addr add "$TUNNEL_IP" dev cl6rd

The /128 assigns a single IPv6 address to the tunnel interface.

We’re not advertising a subnet over this interface. The tunnel serves as the gateway’s connection to CenturyLink’s IPv6 infrastructure.

Configure IPv6 routing

Next, tell Linux to send globally routable IPv6 traffic through the tunnel:

ip -6 route add 2000::/3 dev cl6rd

The 2000::/3 prefix covers the currently allocated global unicast IPv6 address space.

This provides a route for public IPv6 destinations without overriding local and link-local IPv6 routes.

You can confirm the route was added by running:

ip -6 route show

You should see something similar to:

2000::/3 dev cl6rd metric 1024 pref medium

At this point, the tunnel is configured and we should have connectivity!

Step 5: Verify IPv6 connectivity from the UXG-Pro

Before configuring the LAN, let’s confirm that the gateway can reach IPv6 destinations through CenturyLink.

Start by checking the tunnel interface:

ip -d link show cl6rd
ip -6 addr show dev cl6rd

Next, ping Cloudflare’s IPv6 DNS server:

ping -6 -c 3 2606:4700:4700::1111

If everything is working, you should receive replies.

You can also use curl to make an outbound IPv6 connection:

curl -6 --max-time 10 https://api64.ipify.org

This should return the public IPv6 address used for the request.

The -6 argument forces curl to use IPv6 rather than silently falling back to IPv4.

If both commands work, we’ve confirmed that the gateway can successfully transport IPv6 traffic over CenturyLink’s IPv4 infrastructure.

If these tests fail, I would stop here and troubleshoot the tunnel before making changes to the LAN.

Step 6: Configure IPv6 on the UniFi LAN

Now that the tunnel works, the next step is to configure UniFi to advertise IPv6 addresses to devices on our LAN.

Open the UniFi Network application and navigate to:

Settings → Networks → Default

Under IPv6 configuration, enter the following settings:

SettingValue
Interface TypeStatic
IPv6 AddressYour calculated LAN gateway address
Netmask64
Gateway IPAutomatically calculated by UniFi
Link Local IPLeave unchanged
AdvancedManual
Client Address AssignmentSLAAC
Auto DNS ServerUnchecked
Router Advertisement (RA)Enabled
RA PriorityMedium

For DNS, I am using Cloudflare’s public IPv6 DNS servers. Enter both a primary and secondary server address:

DNSIPv4IPv6
Primary1.1.1.12606:4700:4700::1111
Secondary1.0.0.12606:4700:4700::1001

Using the example /56 calculated earlier, our default LAN gateway would be:

2602:c0:2:101::1

With a netmask of 64, that gives us:

2602:c0:2:101::/64

The important thing is that the LAN uses a /64 within the /56 derived from our public IPv4 address.

What is SLAAC?

SLAAC stands for Stateless Address Autoconfiguration.

When enabled, devices connected to the network can automatically configure their own IPv6 addresses based on the prefix advertised by the router.

This means we don’t necessarily need a DHCPv6 server to hand out individual IPv6 addresses.

What are Router Advertisements?

Router Advertisements, or RAs, tell clients which IPv6 prefix they should use and which router serves as their default gateway.

With Router Advertisements enabled, devices on the default LAN should begin configuring IPv6 addresses within the subnet configured in UniFi.

Depending on the operating system, you may need to disconnect and reconnect a device or wait for the next Router Advertisement.

Step 7: Configure multiple LANs and VLANs

One advantage of CenturyLink’s /56 allocation is that we aren’t limited to a single IPv6 subnet.

A /56 provides 256 individual /64 networks, allowing us to use different IPv6 subnets for our default LAN, IoT devices, guest network, servers, or any other VLANs we’ve configured.

For example, using the fictional IPv4 address from earlier:

NetworkSubnet IDIPv6 subnet
Default LAN012602:c0:2:101::/64
IoT022602:c0:2:102::/64
Guest032602:c0:2:103::/64
Servers042602:c0:2:104::/64

Each network would use an IPv6 gateway address ending in ::1.

For example, the IoT network would use:

2602:c0:2:102::1/64

And the Servers network would use:

2602:c0:2:104::1/64

You can configure each of these networks in UniFi using the same Static IPv6, SLAAC, and Router Advertisement settings described above.

UniFi continues to manage the individual LAN interfaces and VLANs.

The 6rd tunnel itself doesn’t need a separate interface or route for each VLAN. The gateway simply forwards their outbound IPv6 traffic through the same tunnel.

Later in the article, we’ll configure the restore script with a list of LANs and VLANs so it can calculate and validate the expected gateway address for each network.

One important distinction: the script won’t create VLANs or change the IPv6 settings managed by UniFi. It will calculate the correct addresses and warn if the actual interface configuration doesn’t match.

Step 8: Test IPv6 from a LAN device

Now that the tunnel is configured and UniFi is advertising the IPv6 subnet, connect a device to your default LAN.

The device should receive a globally routable IPv6 address within the /64 assigned to that network.

You can check this on Windows using:

ipconfig /all

Or on Linux using:

ip -6 addr

Next, try pinging Cloudflare over IPv6:

ping -6 2606:4700:4700::1111

For a more comprehensive test, navigate to:

https://test-ipv6.com

This is one of my preferred ways of validating an IPv6 configuration because it checks whether your client can successfully communicate with IPv6 destinations and identifies common connectivity issues.

With the configuration described above, I’m receiving a perfect 10/10 score on test-ipv6.com.

This confirms that IPv6 connectivity is working properly from my LAN devices through the CenturyLink 6rd tunnel.

If your UXG-Pro can reach IPv6 destinations but your LAN devices cannot, double-check the LAN prefix, Router Advertisements, IPv6 forwarding, and firewall configuration.

A 10/10 score confirms client IPv6 connectivity, but it doesn’t independently validate unsolicited inbound traffic if you’re planning to host services.

Step 9: Create a script to automatically configure the tunnel

Now that we’ve confirmed the configuration works manually, we can combine the commands into a reusable script.

Rather than hardcoding the public IPv4 address or IPv6 tunnel address, the script will calculate everything from the IPv4 address assigned to the WAN interface.

This makes the tunnel configuration usable with both static and dynamic IPv4 assignments.

There are several things I wanted the script to handle:

  1. Wait for the WAN interface to receive an IPv4 address.
  2. Calculate the CenturyLink /56 IPv6 prefix automatically.
  3. Derive the appropriate tunnel IPv6 address.
  4. Install the IPv6 firewall rules without creating duplicates.
  5. Create the tunnel if it doesn’t exist.
  6. Recreate the tunnel if its IPv4 endpoints have changed.
  7. Restore the IPv6 route.
  8. Test IPv6 connectivity through the tunnel.
  9. Calculate the expected IPv6 addresses for multiple LANs and VLANs.
  10. Validate that the configured LAN interfaces use the correct IPv6 addresses.
  11. Display clear status messages and warnings.

For additional protection, the script also supports an optional expected IPv4 address.

If you have a static public IPv4 address, you can populate that setting to prevent unexpected tunnel changes.

If you have a dynamic assignment, leave it blank.

Identify your LAN and VLAN interface names

Before creating the script, we’ll need to identify the Linux interfaces associated with our UniFi networks.

You can list the available interfaces using:

ip -br link show

You can also view interfaces with IPv6 addresses:

ip -6 -br addr show

Depending on your UniFi configuration, the LAN interfaces may be named something like br0, br10, or another bridge or VLAN interface.

Don’t assume the interface name corresponds to the VLAN ID. Verify the actual interface name on your gateway.

For example, you might discover the following mapping:

UniFi networkLinux interfaceSubnet ID
Default LANbr01
IoTbr102
Guestbr203
Serversbr304

The interface names above are examples. You’ll need to use the interface names from your own gateway.

The subnet ID is the final eight bits of your assigned /56 and can range from 0 to 255.

I’ll reserve subnet ID 0 for the tunnel and use IDs 1 through 255 for LANs and VLANs.

Create the restore script

SSH into the UXG-Pro and run the following commands.

Before running the script, review the WAN_IF, EXPECTED_WAN4, and LAN_NETWORKS settings.

mkdir -p /mnt/data/centurylink6rd

cat > /mnt/data/centurylink6rd/restore.sh <<'EOF'
#!/bin/sh
set -eu

#######################################
# CenturyLink 6rd Configuration
#######################################

WAN_IF="ppp0"
RELAY="205.171.2.64"
TUNNEL="cl6rd"
MTU="1472"

# Optional static IPv4 protection.
# Leave empty for dynamic IPv4 assignments.
EXPECTED_WAN4=""

# LAN/VLAN configuration:
# Format: Interface Subnet_ID Network_Name
#
# Replace these examples with your actual
# Linux interface names and network IDs.
#
# Use subnet IDs 1-255.
# Subnet ID 0 is reserved for the tunnel.

LAN_NETWORKS="
br0 1 Default
br10 2 IoT
br20 3 Guest
br30 4 Servers
"

#######################################
# Wait for WAN IPv4
#######################################

n=0

while :; do
    WAN4=$(ip -4 -o addr show dev "$WAN_IF" 2>/dev/null |
        awk 'NR==1 {split($4,a,"/"); print a[1]}')

    [ -n "$WAN4" ] && break

    n=$((n + 1))

    [ "$n" -ge 60 ] && {
        echo "[ERROR] WAN IPv4 timeout" >&2
        exit 1
    }

    sleep 2
done

# Validate IPv4 octets.
echo "$WAN4" | awk -F. '
    NF != 4 { exit 1 }
    {
        for (i = 1; i <= 4; i++) {
            if ($i !~ /^[0-9]+$/ ||
                $i < 0 || $i > 255) exit 1
        }
    }
' || {
    echo "[ERROR] Invalid WAN IPv4" >&2
    exit 1
}

echo "[OK] WAN IPv4 detected"

# Optional static IPv4 check.
if [ -n "$EXPECTED_WAN4" ] &&
   [ "$WAN4" != "$EXPECTED_WAN4" ]; then

    echo "[ERROR] Unexpected WAN IPv4" >&2
    exit 1
fi

#######################################
# Calculate CenturyLink IPv6 Prefix
#######################################

# CenturyLink uses:
# 6rd prefix: 2602::/24
# IPv4 mask length: 0
#
# The 32-bit IPv4 address is embedded
# after the first 24 bits.

BASE=$(echo "$WAN4" | awk -F. '{
    printf "2602:%x:%x:%x",
        $1, ($2 * 256) + $3, $4 * 256
}')

PREFIX="${BASE}::/56"
TUNNEL_IP="${BASE}::1/128"

echo "[OK] 6rd prefix calculated"

#######################################
# Configure IPv6 Firewall
#######################################

# -w 30 waits up to 30 seconds for the
# xtables lock, which UniFi may hold while
# building its own rules during boot.

ip6tables -w 30 -C FORWARD -i "$TUNNEL" \
    -m conntrack ! --ctstate ESTABLISHED,RELATED \
    -j DROP 2>/dev/null ||
ip6tables -w 30 -I FORWARD 1 -i "$TUNNEL" \
    -m conntrack ! --ctstate ESTABLISHED,RELATED \
    -j DROP

ip6tables -w 30 -C INPUT -i "$TUNNEL" \
    -m conntrack ! --ctstate ESTABLISHED,RELATED \
    -j DROP 2>/dev/null ||
ip6tables -w 30 -I INPUT 1 -i "$TUNNEL" \
    -m conntrack ! --ctstate ESTABLISHED,RELATED \
    -j DROP

# Clamp TCP MSS on the tunnel
ip6tables -t mangle -I FORWARD 1 -o "$TUNNEL" -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
ip6tables -t mangle -I FORWARD 1 -i "$TUNNEL" -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1412

#######################################
# Create or Restore SIT Tunnel
#######################################

# Recreate if the IPv4 endpoints changed.
if ip link show dev "$TUNNEL" \
    >/dev/null 2>&1; then

    if ! ip tunnel show "$TUNNEL" |
        grep -Fq "remote $RELAY local $WAN4"; then

        echo "[WARN] Tunnel endpoints changed"
        echo "[INFO] Recreating tunnel"

        ip -6 route del 2000::/3 \
            dev "$TUNNEL" 2>/dev/null || true

        ip tunnel del "$TUNNEL"
    fi
fi

# Create the tunnel if missing.
if ! ip link show dev "$TUNNEL" \
    >/dev/null 2>&1; then

    ip tunnel add "$TUNNEL" mode sit \
        local "$WAN4" \
        remote "$RELAY" \
        ttl 64
fi

ip link set dev "$TUNNEL" mtu "$MTU" up

# Ensure the expected tunnel address exists.
ip -6 -o addr show dev "$TUNNEL" |
    awk '{print $4}' |
    grep -Fqx "$TUNNEL_IP" ||
    ip -6 addr add "$TUNNEL_IP" dev "$TUNNEL"

echo "[OK] Tunnel configured"

#######################################
# Restore IPv6 Routing
#######################################

ip -6 route replace 2000::/3 dev "$TUNNEL"

echo "[OK] IPv6 route installed"

#######################################
# Verify IPv6 Connectivity
#######################################

if ping -6 -I "$TUNNEL" -c 3 -W 3 \
    2606:4700:4700::1111 >/dev/null 2>&1; then

    echo "[OK] IPv6 connectivity verified"
else
    echo "[WARN] IPv6 connectivity test failed"
fi

#######################################
# Validate LAN/VLAN IPv6 Addresses
#######################################

echo ""
echo "LAN/VLAN validation:"

# Extract the first three IPv4 octets'
# embedded hextets once for subnet math.

IPV6_PART=$(echo "$WAN4" | awk -F. '{
    printf "2602:%x:%x",
        $1, ($2 * 256) + $3
}')

IPV6_FOURTH_BASE=$(echo "$WAN4" |
    awk -F. '{print $4 * 256}')

printf '%s\n' "$LAN_NETWORKS" |
while read -r IFACE SUBNET NAME; do

    [ -n "${IFACE:-}" ] || continue

    # Validate the subnet ID.
    case "$SUBNET" in
        *[!0-9]*|"")
            echo "[WARN] $NAME - Invalid subnet ID"
            continue
            ;;
    esac

    if [ "$SUBNET" -lt 1 ] ||
       [ "$SUBNET" -gt 255 ]; then

        echo "[WARN] $NAME - Subnet ID must be 1-255"
        continue
    fi

    # Calculate the expected LAN /64.
    FOURTH=$((IPV6_FOURTH_BASE + SUBNET))
    FOURTH_HEX=$(printf '%x' "$FOURTH")

    LAN_BASE="${IPV6_PART}:${FOURTH_HEX}"
    LAN_PREFIX="${LAN_BASE}::/64"
    LAN_GATEWAY="${LAN_BASE}::1"

    # Check whether the interface exists.
    if ! ip link show dev "$IFACE" \
        >/dev/null 2>&1; then

        echo "[WARN] $NAME ($IFACE) - Interface not found"
        echo "       Gateway: $LAN_GATEWAY/64"
        continue
    fi

    # Compare the configured gateway address.
    if ip -6 -o addr show dev "$IFACE" |
        awk '{print $4}' |
        grep -Fqx "${LAN_GATEWAY}/64"; then

        echo "[OK] $NAME ($IFACE) - gateway address matches"
    else
        echo "[WARN] $NAME ($IFACE) - gateway address does not match"
        echo "       Expected: $LAN_PREFIX"
        echo "       Gateway:  $LAN_GATEWAY/64"
    fi
done

#######################################
# Summary
#######################################

echo ""
echo "CenturyLink IPv6 tunnel configured."
echo ""
echo "WAN IPv4: $WAN4"
echo "6rd Prefix: $PREFIX"
echo "Tunnel IPv6: $TUNNEL_IP"
echo ""
echo "Note: LAN warnings do not"
echo "automatically modify UniFi settings."
EOF

chmod 700 /mnt/data/centurylink6rd/restore.sh

How the script works

There are a few important improvements over the individual commands used earlier.

Automatically detecting the WAN IPv4 address

Rather than hardcoding an IPv4 address, the script retrieves the current address directly from the WAN interface:

WAN4=$(ip -4 -o addr show dev "$WAN_IF" 2>/dev/null |
    awk 'NR==1 {split($4,a,"/"); print a[1]}')

This works with either static or dynamically assigned WAN IPv4 addresses, provided WAN_IF points to the correct interface.

The script also waits up to approximately two minutes for the interface to receive an address.

Automatically deriving the IPv6 prefix

The script converts the public IPv4 address into the appropriate hexadecimal values and combines them with CenturyLink’s 2602::/24 prefix.

This produces the correct /56 without requiring a manually entered IPv6 address.

It then calculates the tunnel address and the expected LAN/VLAN prefixes.

Handling static IPv4 assignments

If you have a static IPv4 address, you can optionally populate:

EXPECTED_WAN4="YOUR_STATIC_PUBLIC_IPV4"

This provides an additional safety check.

If the public IPv4 address changes unexpectedly, the script exits rather than creating the tunnel using an address you weren’t expecting.

Since I have a static IPv4 assignment, this is the setting I would use in my environment.

Handling dynamic IPv4 assignments

If your public IPv4 address is dynamically assigned, leave this variable empty:

EXPECTED_WAN4=""

Every time the script runs, it retrieves the current WAN IPv4 address and recalculates the appropriate IPv6 prefix.

If the tunnel already exists but its IPv4 endpoints no longer match, the script deletes and recreates it.

Deleting the old tunnel also removes the IPv6 address associated with that tunnel interface.

Keep in mind that this happens when the script executes. It doesn’t continuously monitor the WAN connection on its own.

Avoiding duplicate firewall rules

The script uses ip6tables -C to check whether the firewall rules already exist before adding them.

This allows the script to be executed multiple times without inserting duplicate copies of the same rules.

Waiting for the firewall lock

Every ip6tables command uses -w 30, which tells it to wait up to 30 seconds if another process is modifying the firewall. This matters at boot, when UniFi is building its own rules at the same time. Without it, the command fails immediately and the script exits.

Restoring the route

Instead of adding a new route every time, the script uses:

ip -6 route replace 2000::/3 dev "$TUNNEL"

This makes the routing configuration repeatable.

Testing IPv6 connectivity

The script also runs a connectivity test:

ping -6 -I "$TUNNEL" -c 3 -W 3 \
    2606:4700:4700::1111

This attempts to ping Cloudflare’s IPv6 DNS server through the tunnel.

If the test succeeds, the script displays:

[OK] IPv6 connectivity verified

If it fails, it displays:

[WARN] IPv6 connectivity test failed

A failed ping doesn’t necessarily mean the tunnel is configured incorrectly, so the script reports a warning rather than deleting or recreating the tunnel automatically.

Validating multiple LANs and VLANs

The script also includes a configurable list of LAN interfaces and subnet IDs.

For example:

LAN_NETWORKS="
br0 1 Default
br10 2 IoT
br20 3 Guest
br30 4 Servers
"

Each entry tells the script which Linux interface to check and which /64 subnet should be assigned to it. For each entry, the script calculates the expected IPv6 subnet and gateway address. It then checks whether that gateway address is actually configured on the corresponding Linux interface.

For example, if the IoT network is configured correctly, you’ll see:

[OK] IoT (br10) - gateway address matches

If the address doesn’t match, you’ll see something like:

[WARN] IoT (br10) - gateway address does not match
       Expected: 2602:c0:2:102::/64
       Gateway:  2602:c0:2:102::1/64

This is a read-only validation step.

The script doesn’t modify UniFi’s LAN configuration or automatically change the addresses assigned to your VLANs.

Also, this check verifies the expected gateway address on the interface. It doesn’t independently verify that clients are receiving the correct Router Advertisements.

Run the script

Once you’ve saved the script and updated the appropriate variables, execute:

/mnt/data/centurylink6rd/restore.sh

If everything is configured correctly, you’ll see output similar to:

[OK] WAN IPv4 detected
[OK] 6rd prefix calculated
[OK] Tunnel configured
[OK] IPv6 route installed
[OK] IPv6 connectivity verified

LAN/VLAN validation:
[OK] Default (br0) - gateway address matches
[OK] IoT (br10) - gateway address matches
[WARN] Guest (br20) - gateway address does not match
       Expected: 2602:c0:2:103::/64
       Gateway:  2602:c0:2:103::1/64
[OK] Servers (br30) - gateway address matches

CenturyLink IPv6 tunnel configured.

WAN IPv4: 192.0.2.1
6rd Prefix: 2602:c0:2:100::/56
Tunnel IPv6: 2602:c0:2:100::1/128

Note: LAN warnings do not
automatically modify UniFi settings.

The output uses documentation addresses for illustration. Your actual values will be derived from your public IPv4 address. The important thing is that the script distinguishes between configuring the tunnel and validating the LAN networks.

Note: a LAN mismatch won’t prevent the tunnel from being configured, but the warning will tell you which UniFi network needs attention.

Step 10: Handling dynamic IP changes and reboots

There are two separate problems to consider when automating the tunnel:

  1. Handling changes to the public IPv4 address.
  2. Restoring the configuration after a gateway reboot.

Static IPv4 configuration

For my environment, this is relatively straightforward.

Since CenturyLink provides me with a static public IPv4 address, my /56 IPv6 prefix remains consistent.

Once UniFi is configured with the appropriate static IPv6 addresses on the LAN and VLAN interfaces, those addresses shouldn’t need to change during normal operation.

The service in Step 11 restores the tunnel automatically after a reboot. You can also run the script manually at any time:

/mnt/data/centurylink6rd/restore.sh

The script will recreate the tunnel, restore the firewall rules and route, and validate the LAN configurations.

Dynamic IPv4 configuration

If you have a dynamic public IPv4 address, there is an additional consideration.

Since CenturyLink derives the /56 prefix directly from the public IPv4 address, a new IPv4 assignment results in a different IPv6 prefix.

The script already handles recalculating the prefix and rebuilding the tunnel when it’s executed.

However, changing the tunnel alone isn’t enough. UniFi also needs to update the IPv6 addresses and Router Advertisements for each configured LAN or VLAN. Otherwise, devices will continue using IPv6 addresses derived from the previous public IPv4 assignment.

For now, the script deliberately leaves these UniFi-managed settings alone.

Instead, it displays a warning for each LAN interface that doesn’t match the newly calculated prefix to provide a clear indication of which networks need to be updated.

A fully unattended solution for dynamic IPv4 assignments would require an additional mechanism to detect WAN changes, execute the script, and update the managed UniFi LAN configuration. That could potentially be accomplished using the UniFi Network API, but that’s outside the scope of this script.

Restoring the configuration after a gateway reboot

The script provides the configuration and restoration logic, but it doesn’t run on its own after a reboot. Step 11 adds a service that runs it automatically at boot.

Step 11: Run the script automatically at boot

As shown earlier, a reboot removes the tunnel, the route, and the firewall rules, but the script under /mnt/data survives. All that’s missing is something to run it when the gateway starts. UniFi OS uses systemd, so we can add a small service that runs the restore script at boot.

Create the service file

To keep everything in one persistent location, store the unit file alongside the script in /mnt/data:

cat > /mnt/data/centurylink6rd/centurylink6rd.service <<'EOF'
[Unit]
Description=CenturyLink 6rd IPv6 tunnel
Wants=network-online.target
After=network-online.target
RequiresMountsFor=/mnt/data

[Service]
Type=oneshot
ExecStart=/mnt/data/centurylink6rd/restore.sh
TimeoutStartSec=300
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

Here’s what the important settings do:

SettingDescription
After=network-online.targetStarts the service after the network stack is up
RequiresMountsFor=/mnt/dataWaits until /mnt/data is mounted, since the script lives there
Type=oneshotRuns the script once and exits, rather than running continuously
TimeoutStartSec=300Gives PPPoE time to connect. The script itself waits up to two minutes for a WAN IPv4 address
StandardOutput=journalSends the script’s [OK] / [WARN] output to the system journal

Install and enable the service

Next, create a small install script. I’m copying the unit file into /etc/systemd/system rather than symlinking it, because systemd reads unit files early in boot and /mnt/data might not be mounted yet.

cat > /mnt/data/centurylink6rd/install-service.sh <<'EOF'
#!/bin/sh
set -eu

cp /mnt/data/centurylink6rd/centurylink6rd.service \
    /etc/systemd/system/centurylink6rd.service

systemctl daemon-reload
systemctl enable centurylink6rd.service

echo "[OK] centurylink6rd.service installed and enabled"
EOF

chmod 700 /mnt/data/centurylink6rd/install-service.sh
/mnt/data/centurylink6rd/install-service.sh

Keeping the install steps in a script also makes it easy to reinstall the service later. More on that below.

Test the service

Before rebooting, confirm that the service runs correctly:

systemctl start centurylink6rd.service
systemctl status centurylink6rd.service --no-pager
journalctl -u centurylink6rd.service --no-pager

The journal should show the same [OK] output you see when running the script manually. A oneshot service shows inactive (dead) after it finishes. That’s normal, as long as the exit status shows success.

Verify after a reboot

Now reboot the gateway. Once it’s back up, confirm that the service ran and that everything was restored:

uptime
systemctl status centurylink6rd.service --no-pager
journalctl -b -u centurylink6rd.service --no-pager
ip6tables -C FORWARD -i cl6rd -m conntrack ! --ctstate ESTABLISHED,RELATED -j DROP && echo "FORWARD rule OK"
ip6tables -C INPUT -i cl6rd -m conntrack ! --ctstate ESTABLISHED,RELATED -j DROP && echo "INPUT rule OK"
ip link show cl6rd

The -b flag limits the journal output to the current boot, so you’re only seeing what happened after the reboot.

For whatever it is worth, in my testing, the tunnel, route, and firewall rules were restored automatically within about a minute of the gateway coming back online.

After firmware updates

/mnt/data survives reboots, but I wouldn’t assume /etc/systemd/system survives a UniFi OS firmware update. After updating, check whether the service is still installed:

systemctl is-enabled centurylink6rd.service

If it reports that the unit doesn’t exist, re-run the install script:

/mnt/data/centurylink6rd/install-service.sh

Until you do, the gateway behaves the same as after any reboot without the service: no tunnel and no firewall rules, so it fails safe with no IPv6 exposure, just no IPv6.

Removing the service

If you want to remove the automatic startup:

systemctl disable centurylink6rd.service
rm /etc/systemd/system/centurylink6rd.service
systemctl daemon-reload

This only stops the script from running at boot. It doesn’t remove the current tunnel or firewall rules. To remove those, see “Remove the tunnel and revert the changes” in the troubleshooting section.

Troubleshooting common problems

Here are a few things I’d check if you’re having trouble getting this working.

The native 6rd command returns “Invalid argument”

If you’re following another guide and receive:

6rd: ioctl 89f9 failed: Invalid argument

Check the kernel configuration:

zcat /proc/config.gz 2>/dev/null | grep IPV6_SIT

If you see:

CONFIG_IPV6_SIT=y
# CONFIG_IPV6_SIT_6RD is not set

Your kernel supports standard SIT tunneling but not the specialized 6rd functionality.

Use the fixed-relay SIT approach described in this article rather than the native ip tunnel 6rd commands.

The tunnel exists, but IPv6 doesn’t work

Start by checking the tunnel:

ip tunnel show cl6rd

Then verify the IPv6 address and route:

ip -6 addr show dev cl6rd
ip -6 route show

Make sure the tunnel’s local IPv4 endpoint matches the current public IPv4 assigned to your WAN interface.

Also verify that the remote endpoint is CenturyLink’s relay:

205.171.2.64

You can inspect the tunnel’s packet counters:

ip -s link show dev cl6rd

If needed, use tcpdump to check whether encapsulated traffic is passing through the WAN:

tcpdump -ni ppp0 'ip proto 41'

SIT uses IPv4 protocol 41.

Seeing outbound traffic but no inbound responses may indicate a problem with upstream routing, filtering, or relay connectivity.

IPv6 works on the UXG-Pro but not LAN devices

First, verify that your LAN clients are receiving IPv6 addresses within the expected /64.

Next, confirm that Router Advertisements are enabled in UniFi Network.

You can also check whether IPv6 forwarding is enabled:

sysctl net.ipv6.conf.all.forwarding

A value of 1 indicates that forwarding is enabled.

The restore script’s LAN validation output should also help identify whether the configured IPv6 addresses match the prefixes derived from the current public IPv4 address.

If the gateway is working but clients aren’t, focus on the LAN prefix, Router Advertisements, and firewall configuration.

The script reports a gateway address mismatch

For example:

[WARN] IoT (br10) - gateway address does not match

This means the script didn’t find the expected IPv6 gateway address on the configured Linux interface.

NOTE: The script expects the ::1 gateway address used in Step 7. If you’ve deliberately used a different gateway address, this warning is expected and can be ignored.

First, verify that you’ve entered the correct interface name in the LAN_NETWORKS configuration.

You can list the interfaces again using:

ip -br link show

Next, inspect the IPv6 addresses assigned to that interface:

ip -6 addr show dev br10

If the interface name is correct but the address doesn’t match, check the IPv6 configuration for that network in UniFi.

Keep in mind that the script doesn’t modify the LAN address automatically. It only reports the expected configuration.

The centurylink6rd service fails at boot with an “xtables lock” error

If IPv6 doesn’t come back after a reboot, check the boot log for the centurylink6rd.service systemd service from Step 11:

journalctl -b -u centurylink6rd.service --no-pager

If you see “Another app is currently holding the xtables lock” followed by “Failed to start CenturyLink 6rd IPv6 tunnel”, the restore script’s ip6tables commands are missing the -w option. UniFi builds its own firewall rules during boot, and without -w the script fails immediately instead of waiting its turn.

Make sure each ip6tables command in /mnt/data/centurylink6rd/restore.sh includes -w 30, as shown in Step 9, then start the service manually to restore IPv6:

systemctl start centurylink6rd.service

IPv6 works, but some websites time out

This may indicate an MTU or Path MTU Discovery problem.

Verify that the tunnel is configured with an MTU of 1472:

ip link show dev cl6rd

Also make sure that your overall IPv6 firewall configuration isn’t blocking ICMPv6 Packet Too Big messages.

IPv6 relies on these messages to determine the maximum usable packet size along a network path.

Can I host services over IPv6?

Yes. The IPv6 addresses assigned to your LAN clients are globally routable.

Unlike a typical IPv4 residential network using NAT, you don’t need IPv6 NAT or traditional port forwarding to make a service publicly accessible.

However, the firewall rules in this guide deliberately block unsolicited inbound connections.

If you want to host a web server, VPN server, or another service over IPv6, you’ll need to configure an explicit inbound firewall exception for that destination and port.

I’d also recommend assigning servers stable IPv6 addresses rather than relying on temporary privacy addresses.

Since the IPv6 prefix is derived from your public IPv4 address, having a static public IPv4 assignment makes maintaining DNS records and inbound firewall rules much easier.

Remove the tunnel and revert the changes

If you want to remove the tunnel configuration, complete the following:

First, remove the boot service in Step 11 so the tunnel isn’t recreated on the next reboot:

systemctl disable centurylink6rd.service
rm /etc/systemd/system/centurylink6rd.service
systemctl daemon-reload

Then delete the IPv6 route:

ip -6 route del 2000::/3 dev cl6rd

Next, delete the tunnel:

ip tunnel del cl6rd

Remove the associated firewall rules:

ip6tables -D FORWARD -i cl6rd \
    -m conntrack ! --ctstate ESTABLISHED,RELATED \
    -j DROP
ip6tables -D INPUT -i cl6rd \
    -m conntrack ! --ctstate ESTABLISHED,RELATED \
    -j DROP

Finally, disable or revert the static IPv6 configuration under your LANs and VLANs in UniFi Network.

Final thoughts

It’s a bit surprising that in 2026 we’re still dealing with 6rd to get IPv6 connectivity through an ISP’s fiber connection.

The UXG-Pro supports IPv6, but the lack of a native 6rd configuration option combined with the missing CONFIG_IPV6_SIT_6RD kernel functionality makes this more complicated than it probably should be.

Fortunately, at least with my CenturyLink configuration, it’s possible to work around the issue using standard Linux SIT tunneling, an explicitly configured relay, and some IPv6 routing. With my static IPv4 assignment, UXG-Pro firmware 5.1.26, and UniFi Network 10.6.106, I’ve confirmed that the manually configured tunnel works and produces a 10/10 score on test-ipv6.com.

The restore script, combined with the systemd service in Step 11, brings IPv6 back automatically after a reboot and removes the need to manually calculate IPv6 addresses, making it easier to configure the tunnel for either static or dynamically assigned IPv4 addresses. It also supports calculating and validating multiple LAN or VLAN prefixes, which is helpful if your UniFi environment has separate networks for devices, IoT, guests, or servers and you want to quickly add a new segment to your network.

For users with dynamic IPv4 assignments, there’s still additional work required to automate LAN prefix updates whenever the WAN IPv4 address changes. The script handles the tunnel configuration and provides diagnostic warnings, but it doesn’t modify UniFi-managed LAN settings in favor of making the configurations through UniFi’s supported interfaces.

Hopefully this helps anyone else trying to get CenturyLink IPv6 working on a UniFi UXG-Pro without spending hours troubleshooting the same missing kernel functionality.

Aside on IPv6 and IPv4 resources. With IPv6 becoming more widely available, your devices may begin using it automatically when connecting to websites and services. But what happens when both IPv4 and IPv6 are available? That’s where Happy Eyeballs comes in. Yes, “Happy Eyeballs” is its real name!

Happy Eyeballs is an official IETF standards-track algorithm defined in RFC 8305, designed to make IPv4 and IPv6 connectivity work together seamlessly. Happy Eyeballs allows applications to attempt connections over both IPv6 and IPv4, using timed attempts to establish a connection quickly without leaving users waiting on a broken or slow network path.

What does this mean for you? Websites that support IPv6 will increasingly use it automatically, while IPv4-only services will continue working as usual. For most users, the transition from v4 to v6 will feel seamless, with more internet traffic moving to IPv6 without any noticeable difference.

References

Establishing a GCP VPN Tunnel to Azure Virtual WAN; Active/Active BPG Configuration

This is a quick reflection of the steps I took to establish two IPSec tunnels between GCP’s VPC and Azure’s Virtual WAN VPN Gateway, propagating routes dynamically via BPG and ensuring High Availability. The design is fairly straightforward since both GCP and Azure offer the ability to established multiple connections to remote peers. When everything is said and done, you’ll end up with a diagram that conceptually looks something like this:

Note: It is recommended to complete the steps in this document in the outlined order to complete the least amount of steps. In this case, provide Azure Virtual WAN first, then configure GCP, then create the Azure Virtual WAN Site Links / Connections to GCP.

Note 2: If you have followed my previous guide on establishing an AWS VPN tunnel to Azure Virtual WAN , this guide will co-exist both connections and can skip the Create Azure Virtual WAN and Virtual WAN Hub sections.

Create Azure Virtual WAN and Virtual WAN Hub

On the Azure side, first we need to create a Virtual WAN resource and a Virtual WAN Hub, which will contain our VPN Gateway. If you have already created these, you can skip to the next session.

First, click the “Hamburger” icon and select Create a resource

Search for Virtual WAN and select it from the list in the marketplace.

Select Create

Specify the resource group and region you wish to deploy the Virtual WAN resource to. Specify a name for your Virtual WAN resource and click Review + Create

Click Create to start provisioning the Virtual WAN resource.

Once the resource is created, click Go to resource to navigate to your Virtual WAN resource.

On the Virtual WAN resource, select New Hub from the top menu.

Specify the name of the Hub and an address space that can be used for all the networking components Virtual WAN will deploy into the Virtual Hub. Click Next : Site to Site >

On the Site to Site tab, toggle Yes that you want to provision a VPN Gateway, and specify the scale units you need. Click the Review + create button when done.

Click the Create button to start provisioning the Hub and VPN Gateway. Please note this can take up to 30 minutes to complete.

Once the Virtual WAN Hub has been created, click the Menu icon and select All services (note: if you click the Go to resource button after the Virtual WAN Hub resource is created, it’ll take you the properties of the Hub, which isn’t where we want to be).

Search for Virtual WAN and select Virtual WANs.

Select your Virtual WAN resource.

Click on Hubs under Connectivity and select your Virtual WAN Hub.

Select VPN (Site to Site) under Connectivity and then click on the View/Configure link.

Set the Custom BGP IP addresses for each instance. Use the values below:

  • VPN Gateway Instance 0: 169.254.21.2
  • VPN Gateway Instance 1: 169.254.22.2

Click Edit once completed.

Configure GCP

Prerequisites

This guide assumes you have a VPC already (in my case, mine is called GCP-VPC with an address space of 10.60.0.0/16) and corresponding set of subnets for your servers.

Note: A GCP VPC is the equivalent of a VNet in Azure. One thing that is different between GCP and Azure is that in GCP you do not need to specify a subnet for your Gateways (i.e. “GatewaySubnet”).

Within the GCP Console, select Hybrid Connectivity -> VPN

Click Create VPN Connection

Select High-availability (HA) VPN and select Continue

Enter a name, select your VPC, and specify a region. Click Create & Continue.

Write down your Interface public IPs (we’ll use these later) and check On-prem or Non Google Cloud for Peer VPN Gateway. Click Create & continue.

Select two interfaces and enter your Instance 0 and Instance 1 Public IP addresses from your Virtual WAN Hub’s VPN Gateway. Click Create.

Click the dropdown for Cloud Router and select Create a new router

Enter a name and description for your router. For the ASN, enter a unique ASN to use (I used 64700 to differentiate from Azure as well as the ASN I used in the AWS example (which was 64512)). You can specify any supported ASN for this, however I would recommend against using 65515 specifically as this is reserved by Azure’s VPN Gateways.

Note: Google ASN must be an integer between 64512 and 65534 or between 4200000000 and 4294967294 or 16550

Click the pencil icon to modify the first VPN tunnel.

Select the instance 0 VPN Gateway interface, enter a name, set the IKE version to IKEv2, enter a pre-shared key, and click Done.

Repeat the same steps for the second VPN tunnel, specifying instance 1 VPN Gateway interface, enter a name, set the IKE version to IKEv2, enter a pre-shared key, and click Done and then Create & continue.

Click the Configure button for the first BPG session.

Enter a name for the first BGP peer connecting to instance 0 gateway on Virtual WAN.

Specify Peer ASN of 65515 (this is Azure VWAN’s BGP ASN), specify 169.254.21.1 for Cloud Router BGP IP and 169.254.21.2 as BGP peer IP (Azure VWAN’s BGP Peer IP). Click the Save and continue button.

Click the Configure button and enter a name for the first BGP peer connecting to instance 1 gateway on Virtual WAN.

Specify Peer ASN of 65515 (this is Azure VWAN’s BGP ASN), specify 169.254.22.1 for Cloud Router BGP IP and 169.254.22.2 as BGP peer IP (Azure VWAN’s BGP Peer IP).

Click the Save BGP configuration button.

Configure Azure Virtual WAN VPN Site

On the Virtual WAN hub, select VPN (Site to site) and click + Create new VPN site

Specify a name for the VPN connection, enter GCP for vendor, and click Next : Links >

Specify the following values to define each VPN tunnel that should be created to connect to GCP’s VPN interfaces.

Note: I entered 1000 for the link speed as a placeholder, but that doesn’t mean the connection will be throttled down to 1Gbps.

  • First Link:
    • Link Name: gcp-east1-vpc-vpn-int0
    • Link Speed: 1000
    • Link provider name: GCP
    • Link IP address: <GCP VPN Interface 0 Public IP>
    • Link ASN: 64700
  • Second Link:
    • Link Name: gcp-east1-vpc-vpn-int1
    • Link Speed: 1000
    • Link provider name: GCP
    • Link IP address: <GCP VPN Interface 1 Public IP>
    • Link ASN: 64700

Click Create

Configure Virtual WAN VPN Connection

Once the Virtual WAN Hub has been created, click the Menu icon and select All services.

Search for Virtual WAN and select Virtual WANs.

Select your Virtual WAN resource.

Click on Hubs under Connectivity and select your Virtual WAN Hub.

Select VPN (Site to Site) under Connectivity and then click on the X to remove the Hub association filter.

Check the box for your VPN site and click Connect VPN sites

Specify the following information:

  • Pre-shared key (PSK): <use the same one you specified in GCP>
  • Protocol: IKEv2
  • IPsec: Custom
    • SA Lifetime in seconds: 36000
    • Phase 1 (IKE):
      • Encryption: GCMAES256
      • Integrity/PRF: SHA384
      • DH Group: ECP256
    • Phase 2 (IPSec):
      • Encryption: GCMAES256
      • IPSec Integrity: GCMAES256
      • PFS Gropu: ECP384
  • Propagate Default Route: Disable
  • Use policy based traffic selector: Disable

Click Connect.

Note: Here is the link of Supported IKE ciphers for GCP.

Verify Connectivity

From the Azure Side, we will review three different areas to validate connectivity and propagation of routes via BGP.

Note: I connected a virtual network to the Virtual WAN Hub to show further configuration. In this case, you’ll see an additional IP address space of 10.51.0.0/16, which defines my connected VNet.

On the Azure Side, you should see the VPN Site’s Connectivity status change to Connected on the VPN (Site to site) blade of your Virtual WAN hub.

On the Routing blade, the Effective Routes will show you the learned VPC address space from GCP (10.60.0.0/16)

On a virtual machine in a connected VNet to the Virtual WAN Hub, you can pull the Effective Routes. Here I see the 10.60.0.0/16 route learned from both Instance 0 and Instance 1 gateways from the Virtual WAN Hub.

From the GCP Side, we can see the VPN tunnel status as well as Bgp session status now Established and Green on the Hybrid Connectivity -> VPN -> Cloud VPN Tunnels section.

If we switch over to Hybrid Connectivity -> Cloud Routers -> and select View on the logs column

Further, if creating a VM (instance) in GCP, you can view the Firewall and Route details to confirm you see the learned routes from the gateway (in our case, we see 10.51.0.0/16 and 10.50.0.0/24 learned from both BGP Peers):

Huzzah! Traffic! 🙂

Establishing an AWS VPN Tunnel to Azure Virtual WAN; Active/Active BPG Configuration

This is a quick reflection of the steps I took to establish two IPSec tunnels between AWS’ VPG and Azure’s Virtual WAN VPN Gateway, propagating routes dynamically via BPG and ensuring High Availability. The design itself is a bit interesting since AWS and Azure differ on how connections are established to remote peers. When everything is said and done, you’ll end up with a diagram that conceptually looks something like this:

Note: It is recommended to start with the Virtual WAN side first since you cannot modify the IP address of a Customer Gateway in AWS

Create Azure Virtual WAN and Virtual WAN Hub

On the Azure side, first we need to create a Virtual WAN resource and a Virtual WAN Hub, which will contain our VPN Gateway. If you have already created these, you can skip to the next session.

First, click the “Hamburger” icon and select Create a resource

Search for Virtual WAN and select it from the list in the marketplace.

Select Create

Specify the resource group and region you wish to deploy the Virtual WAN resource to. Specify a name for your Virtual WAN resource and click Review + Create

Click Create to start provisioning the Virtual WAN resource.

Once the resource is created, click Go to resource to navigate to your Virtual WAN resource.

On the Virtual WAN resource, select New Hub from the top menu.

Specify the name of the Hub and an address space that can be used for all the networking components Virtual WAN will deploy into the Virtual Hub. Click Next : Site to Site >

On the Site to Site tab, toggle Yes that you want to provision a VPN Gateway, and specify the scale units you need. Click the Review + create button when done.

Click the Create button to start provisioning the Hub and VPN Gateway. Please note this can take up to 30 minutes to complete.

Configure customer BGP IP Address for Virtual WAN VPN Gateway Instances

Once provisioning is completed, navigate back to the Virtual WAN resource. You can do this by clicking the “Hamburger” icon and searching for Virtual WAN

Select your Virtual WAN resource.

You should now see your Virtual WAN Hub resource you provisioned. Select the Virtual WAN Hub.

On the Virtual WAN Hub, click on the View/Configure link.

On the View/Configure Gateway Configuration blade, specify 169.254.21.2 as the Custom BGP IP address for Instance 0 and 169.254.22.2 as the Custom BGP IP address for Instance 1. Notate the Public IP address uses for Instance 0 and 1 and then click Edit and Confirm to apply the changes.

Create Virtual WAN VPN Site

On the Virtual WAN Hub, click Create new VPN Site

Specify a name for your VPN Site to define the connection connecting to AWS. Click Next : Links >

On the Links tab, add two entries with the following values (to tell VWAN how to connect to each of the AWS Site-to-Site connections). Note: this is very similar to AWS’ Customer Gateway section.

Link 1:

  • Link Name; AWS_Tunnel1
  • Link Speed: 1000
  • Link Provider Name: AWS
  • Link IP address: 1.1.1.1 (this is a placeholder value until we configure the AWS side)
  • Link BGP address: 169.254.21.1
  • Link ASN: 64512

Link 2:

  • Link Name; AWS_Tunnel2
  • Link Speed: 1000
  • Link Provider Name: AWS
  • Link IP address: 1.1.1.2 (this is placeholder value until we configure the AWS side)
  • Link BGP address: 169.254.22.1
  • Link ASN: 64512

Click Next: Review + Create >

Click Create

Click Go to resource once the links have finished being created.

Configure Phase 1/2 Proposals

Select your Virtual WAN hub on the Virtual WAN Overview blade.

Check the box for the new VPN Site Name and click the Connect VPN sites button

Specify the following configuration:

  • Pre-shared key (PSK): YourSecretKeyWithNumb3rs
    • Must be a 8-64 character string with alphanumeric, underscore(_), and dot(.). It cannot start with 0.
  • Protocol: IKEv2
  • IPSec: Custom
  • IKE Phase 1:
    • Encryption: GCMAES256
      • GCM algorithm is more efficient and can improve throughput on the Azure Gateways
    • Integrity/PRF: SHA256
    • DH Group: DHGroup14
  • IKE Phase 2 (ipsec):
    • IPSec Encryption: AES256
      • AWS does not support GCM algorithm for IPSec integrity at time of writing this, but if it is available, you may want to opt for that
    • IPSec Integrity: SHA256
    • PFS Group: PFS14

Click Connect

Configure AWS

Prerequisites

This guide assumes you have a VPC already (in my case, mine is called AWS-OHIO-VPC), a corresponding set of subnets for your servers, and a route table associated to your VPC.

Note: An AWS VPC is the equivalent of a VNet in Azure. One thing that is different between AWS and Azure is that in AWS you do not need to specify a subnet for your Gateways (i.e. “GatewaySubnet”).

Create the Customer Gateways

Customer Gateways in AWS are the equivalent of a local network gateway that you’d associate to a connection for a traditional VPN Gateway in Azure. It is also the equivalent of a defined Site Link for Azure’s Virtual WAN VPN configuration.

In this section, you will need to create two Customer Gateways. Specify the corresponding instance value obtained from the Configure Customer BPG IP address section. When creating the Customer Gateways ensure Dynamic routing is enabled and the BGP ASN is specified as 65515.

Configuration for the second Customer Gateway using the Instance 1 Gateway Public IP address.

Create a Virtual Private Gateway

Next we need to create an AWS Virtual Private Gateway. This is the equivalent of Azure’s VPN Gateway.

Create VPN Connections

We need to create two VPN Connections, each VPN Connection linked to its corresponding Customer Gateway and VPC.

On the Inside IPv4 CIDR for Tunnel 1 on the first VPN Connection, ensure you use 169.254.21.0/30 as the BGP Peer addresses and 169.254.21.4/30 for the second tunnel. Due to the way that the VPN Connection works, we are using a placeholder value of 169.254.21.4/30 tunnel, which will never be used in practice since we cannot point it to leverage Azure’s secondary VPN Gateway instance. This value must be specified as if we define the secondary BGP Peer address that will be created for the secondary instance in VWAN, you will receive an error that overlapping address space exists between this VPN Connection and the secondary VPN connection we create in AWS. Add the pre-shared key value you specified in Azure during this time as well.

When creating the second VPN connection, ensure 169.254.22.0/30 is specified for Inside IPv4 CIDR for Tunnel 1 and 169.254.22.4/30 is specified for Inside IPv4 CIDR for Tunnel 2 (which is again a placeholder value that won’t be used).

Configure Route Table to Propagate Routes

To allow the learned routes from BGP propagate to the VPC, you need to enable route propagation on your Route Table.

Navigate to Route Tables and select your Route Table and click the Route Propagation tab and select Edit route propagation

Check the Propagate box and click Save

Update Azure

Update Azure Site Link IP addresses

As per the Configure Phase 1/2 Proposals section for Azure Virtual WAN, you specified 1.1.1.1 and 1.1.1.2 as a placeholder value for the Public IP addresses of the AWS VPN Gateway instances. We will need to update these addresses with the proper values.

Naviate to your Virtual WAN instance and select your Virtual WAN hub

Select VPN (Site to site) and choose click on the Site name you created

Click on the three dots (ellipsis) for AWS_Tunnel1 and click Edit Link.

Specify the proper IP address for Tunnel 1 on AWS Site-to-Site connection 1. Click Confirm.

Click on the three dots (ellipsis) for AWS_Tunnel2 and click Edit Link.

Specify the proper IP address for Tunnel 1 on AWS Site-to-Site connection 2. Click Confirm.

Verify connectivity

On the Azure Side, you should see the VPN Site’s Connectivity status change to Connected

You can also select a Virtual Machine that may have it’s virtual network attached to the VWAN Hub and validate you see learned routes from the VWAN Hub (and AWS) propagated into the VNet.

Tip: You can see the same route twice as we have both VPN Gateway instance BGP Peers actively connected to AWS. In the event you lose a peer, you would only see one route to one gateway listed.

On the AWS side, you can validate for each Site to Site VPN connection that you see Tunnel 1‘s status as UP and Tunnel 2‘s status as DOWN (remember, Tunnel 2 will always be listed as down because a fictitious BGP is specified).

Here you can see the secondary Site-to-Site connection with the same status: UP for Tunnel 1, DOWN for Tunnel 2

Cheat sheet on Azure Subnetting

Here’s a quick cheat sheet on recommended subnet sizing for Azure. Items in bold are subnet names reserved by the platform for their corresponding service.

GatewaySubnet – /27 – https://docs.microsoft.com/en-us/azure/vpn-gateway/vpn-gateway-about-vpn-gateway-settings#gwsub

Point-to-Site (P2S) addressing (VPN or VWAN) – Requires a non-vnet address space – depends on how many P2S clients – https://docs.microsoft.com/en-us/azure/vpn-gateway/point-to-site-about#gwsku

AzureBastionSubnet – /26 (as of Nov, 2021; previously was /27) – https://docs.microsoft.com/en-us/azure/bastion/bastion-create-host-portal#createhost

Azure Virtual WAN Hub – /24 – https://docs.microsoft.com/en-us/azure/virtual-wan/virtual-wan-site-to-site-portal#hub

AzureFirewallSubnet – /26 – https://docs.microsoft.com/en-us/azure/firewall/tutorial-firewall-deploy-portal#create-a-vnet

AzureFirewallManagementSubnet – /26 – Azure Firewall forced tunneling | Microsoft Docs

RouteServerSubnet – /27 – Quickstart: Create and configure Route Server using Azure PowerShell | Microsoft Docs

Application Gateway – min /27 per deployment – https://docs.microsoft.com/en-us/azure/application-gateway/configuration-overview#size-of-the-subnet

Azure AD Domain Services (AADDS) – min /28 – Network planning and connections for Azure AD Domain Services | Microsoft Docs

Azure SQL Managed Instance (SQL MI) – min /27 – https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance-determine-size-vnet-subnet

App Services (Web Apps, Functions, API Apps) – min /27 – https://docs.microsoft.com/en-us/azure/app-service/web-sites-integrate-with-vnet

App Service Environment – /24 – https://docs.microsoft.com/en-us/azure/app-service/environment/network-info

Logic Apps integration service – /27 – https://docs.microsoft.com/en-us/azure/logic-apps/connect-virtual-network-vnet-isolated-environment#set-up-network-ports

API Management – min /29 – https://docs.microsoft.com/en-us/azure/api-management/api-management-using-with-vnet#–subnet-size-requirement

Azure Kubernetes Service (AKS) – depends on node count –  https://docs.microsoft.com/en-us/azure/aks/configure-azure-cni#plan-ip-addressing-for-your-cluster

Azure Container Instances (ACI) – /29 – https://docs.microsoft.com/en-us/azure/container-instances/container-instances-vnet

Azure Databricks – Requires 2 subnets (Public/Private) – min of two /26 – https://docs.azuredatabricks.net/administration-guide/cloud-configurations/azure/vnet-inject.html#virtual-network-requirements

Azure NetApp Files – /28 – https://docs.microsoft.com/en-us/azure/azure-netapp-files/azure-netapp-files-delegate-subnet

Azure Dedicated HSM – /28 – https://docs.microsoft.com/en-us/azure/dedicated-hsm/networking#subnets

Azure VMware Solutions – /22 – https://docs.microsoft.com/en-us/azure/azure-vmware/tutorial-network-checklist#routing-and-subnet-considerations

Azure Spring Cloud – /28 – Deploy Azure Spring Cloud in a virtual network | Microsoft Docs

Notes

Microsoft has added a list of services that can be injected into Virtual Networks as well here: https://docs.microsoft.com/en-us/azure/virtual-network/virtual-network-for-azure-services#services-that-can-be-deployed-into-a-virtual-network

Deploying FortiGate Virtual Appliances (FortiGate-VM) on Azure

Here is a recap of some of the reflections I have with deploying Fortinet’s FortiGate appliance on Azure. This is more of a reflection of the steps I took rather than a guide, but you can use the information below as you see fit.  At a high level, you will need to deploy the device on Azure and then configure the internal “guts” of the device to allow it to route traffic properly on your Virtual Network (VNet) in Azure. While Fortinet does have some documentation on deploying their appliance, I found it very confusing, so I hope this helps walk through deployment. At the time of writing this, v6.2 was the latest version; however I recommend using at least version 6.0 or greater as it provides support for auto-scaling, which is what we will be looking at for this guide.

First, just want to provide a quick overview of the different options you can take and a rough overview of each architecture:

Deploy the Appliance in Azure

As part of this tutorial, we will look at FortiGate’s Autoscaling deployment as this will allow us to dynamically scale up or down depending on load. In addition, this deployment will provide us high availability, so in the event we lose a VM, network traffic will automatically failover to another appliance.

Architecture

A high level overview of what resources are deployed

Deployment

  1. Login to the Azure Portal
    1. https://portal.azure.com
  2. Create two new Resource Groups
    1. Navigate to All services -> Resource Groups
    2. Click Add 
    3. Create two new resource groups with the following names (they can be different if you wish, but you will need at least 2)
      1. Fortigate-Handler-RG
      2. Fortigate-VMSS-RG
  3. Create a Service Principal
    1. Navigate to All services -> Azure Active Directory
    2. Select App registrations
    3. Click New Registration
      1. Name: Fortigate-NVA
      2. Supported account types: Accounts in this organizational directory only
      3. Redirect URI: leave blank
    4. Click Register
    5. Write down the Application (client) ID, Directory (tenant) ID, and Object ID.
    6. Click on Certificates & secrets
    7. Click on the New client secret button and set the description to Fortigate-NVA, set the password expiry to your preference and click Add
    8. Write down the value of your client secret
      1. Note: once you navigate away from the blade you won’t be able to retrieve it again
  4. Delegate the Service Principal
    1. Navigate to All services -> Subscriptions -> select your subscription -> and select Access control (IAM)
    2. Click Add, Add role assignment, and use the following configuration
      1. Role: Owner
      2. Assign access to: Azure AD user, group, or service principal
      3. Select: Search for Fortigate-NVA and select it
    3. Click Save

      1. Note: I didn’t have a chance to test, but I think these permissions could likely be delegated down at the resource group level vs subscription.  If someone could confirm, please leave a comment below.
  5. Deploy the Fortigate Handler (CosmosDB and Function App)
    1. Once you click the button above to deploy the template, use the following configuration
      1. Function App Name
        1. This is the name of the Azure Function resource that gets created.  This must be globally unique across all customers within Azure.
      2. Cosmos DB Name
        1. Name of the Cosmos DB that will be created. This field must be between 3 and 31 characters and can contain only lowercase letters, numbers and -.  This value should be globally unique across all customers within Azure.
      3. Storage Account Type: Standard_LRS
      4. Tenant ID
        1. Use the Directory (tenant) ID from the Service Principal we created earlier.
      5. Subscription ID
        1. Enter the subscription ID to the Azure Subscription you wish to deploy to.  You can find your subscription ID by navigating to All services -> Subscriptions and selecting your subscription.
      6. Rest App ID
        1. Use the Application (client) ID from the Service Principal we created earlier.
      7. Rest App Secret: iW8gS………………………pMX
        1. Use the value you wrote down when generating the Client Secret when creating the Service Principal.
      8. Heart Beat Loss Count: 3
        1. Number of consecutively lost heartbeats. When the heartbeat loss count has been reached, the VM is deemed unhealthy and failover activities commence.
      9. Scaling Group Resource Group Name: Fortigate-VMSS-RG
        1. This is the value of the secret Resource Group you created at the beginning of this guide.  This Resource Group will contain the VM Scale Set and it’s corresponding resources.
      10. Script Timeout: 230
        1. This is the timeout for the Function App script to run.  By default this is 230 seconds.
      11. Election Wait Time: 90
        1. This is the maximum time (in seconds) to wait for a master election for the FortiGate’s to complete.
      12. PSK Secret: mysupersecretpassphrase
        1. This is a random string of characters used by the FortiGates in the scale set to synchronize configuration items.
      13. Package Res URL: https://github.com/fortinet/fortigate-autoscale/releases/download/1.0.3/fortigate-autoscale-azure-funcapp.zip
        1. Grab the latest version of the package for the Azure Function App from GitHub.  You can find the latest compiled versions here: https://github.com/fortinet/fortigate-autoscale/releases
  6. Deploy the VM Scale Set
    1. Once you click the button above to deploy the template, use the following configuration
      1. Instance Type: Standard_F2
      2. FOS Version: 6.2.1
      3. VNet New Or Existing: new
        1. Select whether you wish to use an existing or new Virtual Network
      4. VNet Name: AzureHubVNet
        1. The name of the VNet to be used or created.
      5. Subnet Address Prefix: 10.0.0.0/16
        1. The address space of the VNet to be used or created.
      6. Subnet1Name: Untrust
        1. The name of the subnet that will be public facing to the internet.
      7. Subnet1Prefix: 10.0.1.0/24
        1. The address space of the subnet to be created for the public facing zone.
      8. Subnet2Name: Trust
        1. The name of the subnet that will contain the private NICs of the FortiGate’s.
      9. Subnet2Prefix: 10.0.2.0/24
        1. The address space of the subnet to be created for the private facing zone.
      10. Subnet2Load Balancer IP: 10.0.2.10
        1. The IP address of the load balancer in the private zone.
      11. Subnet3Name: Private
        1. The name of the subnet that will contain the private machines that are behind the FortiGate appliance.
      12. Subnet3Prefix: 10.0.3.0/24
        1. The address space of the subnet that will contain the private machines that are behind the FortiGate.  Note: this is more of a place holder in FortiGate’s template, you can create additional subnets later on/use a different subnet for your private resources.
      13. Public IP New or Existing: new
        1. The Public IP address to be associated as the VIP of the Azure Load Balancer for incoming traffic.
      14. Scaling Group Name Prefix: fgtasg
        1. The prefix each VMSS Name is given when deploying the FortiGate autoscale template. The value of this parameter should be the same as for deploy_funcapp.json. The prefix cannot contain special characters \/””[]:|<>+=;,?*@& or begin with ‘_’ or end with ‘.’ or ‘-‘.
      15. Initial Capacity: 2
        1. How many FortiGate’s should be deployed.  Default value is 1, however I recommend at least 2 for high availability.
      16. Min Capacity: 2
        1. The smallest amount of FortiGate’s that should be running.  Default value is 1, however I recommend at least 2 for high availability.
      17. Max Capacity: 3
        1. The max amount of FortiGate’s that should be deployed.
      18. Scale Out Threshold: 80
        1. Percentage of CPU utilization at which scale-out should occur.
      19. Scale In Threshold: 20
        1. Percentage of CPU utilization at which scale-in should occur.
      20. Admin Username: azureadmin
        1. FortiGate administrator username on all VMs.
      21. Admin Password: azurepassword
        1. FortiGate administrator password on all VMs. This field must be between 11 and 26 characters and must include at least one uppercase letter, one lowercase letter, one digit, and one special character such as (! @ # $ %).
      22. Endpoint URL: https://yourfunctionappurl.azurewebsites.net
        1. This can be found by navigating to All services -> Function App -> YourFunctionApp -> URL on the overview blade.

At this point, your FortiGate deployment should be completed. When a FortiGate appliance comes up, it will reach out to the Azure Function to pull down its base configuration. Any changes to the primary FortiGate will be synchronized to any additional FortiGates deployed as well.

For those using a hub/spoke network, you will want to associate a UDR to each of your subnets to force traffic back to the internal load balancer’s VIP. You can do this by creating a new Route Table, add a Route, set the next hop type to Virtual Appliance, and set the IP address to the IP address you specified for the “Subnet2Load Balancer IP”.

You can connect to the primary FortiGate for management via web console on Port 8443 (https://IP.AD.DR.ESS:8443) or via SSH on Port 22.

References

https://docs.fortinet.com/vm/azure/fortigate/6.2/azure-cookbook/6.2.0/128029/about-fortigate-vm-for-azure

Deploying Cisco Virtual Appliances (NGFWv) on Azure

Here is a recap of some of the reflections I have with deploying Cisco NGFWv (Next Generation Firewall Virtual) on Azure. This is more of a reflection of the steps I took rather than a guide, but you can use the information below as you see fit.  At a high level, you will need to deploy the device on Azure and then configure the internal “guts” of the Cisco device to allow it to route traffic properly on your Virtual Network (VNet) in Azure. While Cisco does have decent documentation on deploying a single appliance, the primary purpose of this document is to look at HA/Scale out deployments.

First, just want to provide a quick overview of some of Cisco’s offerings today for Azure:

  • Cisco CSR
  • Cisco Meraki
    • In Cisco’s words:
      • Virtual MX is a virtual instance of a Meraki security & SD-WAN appliance, dedicated specifically to providing the simple configuration benefits of site-to-site Auto VPN for customers running or migrating IT services to an Amazon Web Services or Microsoft Azure Virtual Private Cloud (VPC).
    • Source: https://meraki.cisco.com/products/appliances/vmx100
  • Cisco ASAv
    • In Cisco’s words:
      • The ASAv is a virtualized network security solution that provides policy enforcement and threat inspection across heterogeneous, multisite environments.
      • ASA firewall and VPN capabilities help safeguard traffic and multitenant architectures. Available in most hypervisor environments, the Cisco ASAv can be deployed exactly where it is needed to protect users and workloads on-premises or in the cloud.
    • Source: https://github.com/cisco/asav
  • Cisco Firepower NGFW (Threat Defense Virtual)
    • In Cisco’s words:
      • The Cisco Firepower® NGFW (next-generation firewall) is the industry’s first fully integrated, threat-focused next-gen firewall with unified management. It uniquely provides advanced threat protection before, during, and after attacks.
      • The Firepower Threat Defense Virtual (FTDv) is the virtualized component of the Cisco NGFW solution. Organizations employing SDN can rapidly provision and orchestrate flexible network protection with Firepower NGFWv. As well, organizations using NFV can further lower costs utilizing Firepower FTDv.
    • Source: https://github.com/cisco/firepower-ngfw

Deploy the Appliance in Azure

In deploying the Cisco appliances, you’ll notice you can deploy from the Azure Marketplace: https://azuremarketplace.microsoft.com/en-us/marketplace/apps/cisco.cisco-firepower-threat-defense-appliance?tab=Overview).  Personally, I’m not a big fan of deploying the appliance this way as I don’t have as much control over naming conventions, don’t have the ability to deploy more than one appliance for scale, cannot specify my availability set, etc. While Cisco does offer an ARM template, it doesn’t allow flexibility for more than two devices, nor configures anything from a load balancer perspective. In this case, I’ve written a custom ARM template that leverages managed disks, availability sets, consistent naming nomenclature, proper VM sizing, and most importantly, let you define how many virtual instances you’d like to deploy for scaling.

Note: this article doesn’t cover deployment of Cisco’s Firepower Management Center, which is what is used to centrally manage each of the scale-out instances in a “single pane of glass”.

With the above said, this article will cover what Cisco calls their “scalable design” model. Here is an example of what this visually looks like (taken from one of their slide decks listed in the notes section at the bottom of this article):

Scalable design model as per Cisco’s Reference Architecture

Below is a link to the ARM template I use.

Cisco-NGFWv-HA.json

Deployment of this template can be done by navigating to the Azure Portal (portal.azure.com), select Create a resource, type Template Deployment in the Azure Marketplace, click Create,  select Build your own template in the editor, and paste the code into the editor.

Alternatively, you can click this button here:

Here are some notes on what the parameters mean in the template:

VMsize: Per Cisco, the recommend VM sizes should be D3v2, D4v2, or D5v2.  Interestingly, they don’t call out the use of Premium storage anywhere, which I would highly recommend using if this was a single instance machine (to get at least some sort of SLA by Azure).

CiscoSku: Here is where you can select to use bring-your-own-license or pay-as-you-go.  Plans are should be outlined in the following link, but oddly enough the BYOL image is only available via PowerShell and their plans don’t show it:  https://azuremarketplace.microsoft.com/en-us/marketplace/apps/cisco.cisco-firepower-threat-defense-appliance?tab=PlansAndPrice

CiscoVersion: The version of the Cisco appliance to deploy.

CiscoCount: This defines how many virtual instances you want deployed and placed behind load balancers.

VNetName: The name of your virtual network you have created.

VNetRG: The name of the resource group your virtual network is in.  This may be the same as the Resource Group you are placing the Cisco devices in, but this is a needed configurable option to prevent errors referencing a VNet in a different resource group.

envPrefix: All of the resources that get created (load balancer, virtual machines, public IPs, NICs, etc.) will use this naming nomenclature.

manPrivateIPPrefix, diagPrivateIPPrefix, trustPrivateIPPrefix, untrustPrivateIPPrefix: Corresponding subnet address range.  These should be the first 3 octets of the range followed by a period.  For example, 10.5.6. would be a valid value.

manPrivateIPFirst, diagPrivateIPFirst, trustPrivateIPFirst, untrustPrivateIPFirst: The first usable IP address on the subnet specified.  For example, if my subnet is 10.4.255.0/24, I would need to specify 4 as my first usable address.

Username: this is the name of the privileged account that should be used to ssh and login to the PanOS web portal.

NewStorageAccountName: this is the name of the storage account that will store boot diagnostics for the Cisco appliances. This will give you the ability to see what the serial console shows. This value should be alphanumeric and 3-24 characters.

Password: Password to the privileged account used to ssh and login to the device.

Configure the Appliance

Complete these steps for both devices.

  1. SSH to the device via it’s public or private IP address of the management interface
    1. Please note, SSH may not come up for another 10+ minutes after deployment has finished, even though the VMs show running. There are several tasks within the Cisco appliance that run post-provisioning which take awhile to complete before the ability to SSH works.
  2. Login using the following credentials
    1. Note: Even though we specified credentials within our template, cisco has a default set of admin credentials “baked” into the image and they should be specified during first login (which prompts you to immediately change). Please login using the default admin credentials.
    2. Username: admin
    3. Password: Admin123
      1. The password is case sensitive, you should use a capital A on Admin123.
  3. Change your password once prompted
  4. Enter y to configure IPv4
  5. Enter n to not configure IPv6
    1. As of 6/1/2019, Azure only has preview support for IPv6, so this article won’t cover any IPv6 specific items
  6. Enter dhcp to configure IPv4 with DHCP
    1. All addresses in Azure should be DHCP, static addresses are set within Azure, which essentially give the appliance a DHCP reservation
    2. Important Note: Once you configure this option, you’ll get an awkward “If your networking information has changed, you will need to reconnect” message and things will appears to be stuck. Be patient, it appears a script runs in the background, you’ll see it eventually prompt for the next question.
  7. Leave your SSH connection open for the next step

Configure NGFWv to use FirePower Management Center

Once you have gone through the initial configuration on both devices, you will need to register the sensor to a Firepower Management Center instance. To do this, you will need to run the configure manager command on both appliances. Please note I’ve listed the command below with the parameters it will accept, you will need to use the applicable values for your environment.

configure manager add {hostname | IPv4_address | IPv6_address | DONTRESOLVE} reg_key [nat_id]

Per Cisco’s documentation:

  • The registration key is a user-defined one-time use key that must not exceed 37 characters. Valid characters include alphanumeric characters (A–Z, a–z, 0–9) and the hyphen (-). You will need to remember this registration key when you add the device to the Firepower Management Center.
  • If the Firepower Management Center is not directly addressable, use DONTRESOLVE.
  • The NAT ID is an optional user-defined alphanumeric string that follows the same conventions as the registration key described above. It is required if the hostname is set to DONTRESOLVE. You will need to
    remember this NAT ID when you add the device to the Firepower Management Center

Add the appliances into FirePower Management Center

Repeat the following steps for each of the appliances you deployed

  1. Login to FirePower Management Center
  2. Select the Devices tab, click Device Management, and then click the Add button
  3. Enter the following
    1. Host: ManagementIP
    2. Device Name: FriendlyDeviceNameOrHostName
    3. Registration Key: KeyYouUsedWhenRunningConfigureManagerCommandAbove
    4. Access Control Panel
      1. Specify a Name, select Network Discovery
    5. Smart Licensing
      1. Check the following you are licensed for
        1. Malware
        2. Threat
        3. URL Filtering
    6. If you used NAT, configure NAT and specify the NAT ID
    7. Click Register

Initialize the interfaces on your appliances

Repeat the following steps for each of the appliances you deployed

  1. Select the Devices tab, click Device Management, and select the edit button (Pencil Icon) for your appliance
  2. Click the edit button (Pencil Icon) for GigabitEthernet 0/0
    1. Name: Untrust
    2. Check the Enabled checkbox
    3. Security Zone: Create a new zone called Untrusted
    4. Click the IPv4 tab
      1. IP Type: Use Static
      2. IP Address: IPAddressOfYourAppliance/SubnetSize
    1. Click OK
  3. Click the edit button (Pencil Icon) for GigabitEthernet 0/1
    1. Name: Trust
    2. Check the Enabled checkbox
    3. Security Zone: Create a new zone called Trusted
    4. Click the IPv4 tab
      1. P Type: Use Static
      2. IP Address: IPAddressOfYourAppliance/SubnetSize
    5. Click OK
  4. Click the Save button

Once you have completed the steps above, click Deploy, select each of your appliances, and click Deploy to push the configuration to the device

Configure static routes on your device

In this section, we will create several routes to handle the flow of traffic to and to/from your trusted subnets, traffic destined towards the internal, traffic destined towards the management interface (we’ll need this to help handle the health probes from the azure load balancer later on), and a specific route to define the Azure Health Probes themselves.

Repeat the following steps for each of the appliances you deployed.

  1. Select the Devices tab, click Device Management, and select the edit button (Pencil Icon) for your appliance
  2. Select the Routing tab and click Static Route
  3. Click the Add Route button
    1. Type: IPv4
    2. Interface: Trust
    3. Create new network objects
      1. Add network objects that represent each of the subnets you have in Azure that the device will need to return traffic to
        1. For example, you’d repeat these steps for each private subnet
          1. Name: DBServers
          2. Network: 10.3.5.0/24
          3. Click Save
      2. Add network object for the appliance’s management interface
        1. Name: YourAppliance-mgmt
        2. Network: IPAddressOfManagementInterface
          1. Use the private IP of your management interface
        3. Click Save
      3. Add network object for Azure Health Probes
        1. Name: Azure-LB-Probe
        2. Network: 168.63.129.16
        3. Click Save
    4. Add the defined network objects above to Selected Network box
    5. Gateway: Use the IP address of the default gateway of your subnet the Trust interface is deployed on
      1. Note: To find this, navigate to the Azure Portal (portal.azure.com) and select All Services -> Virtual Networks -> Your Virtual Network -> Subnets and use the first IP address of your subnet the trusted interface is on.  For example, if the address range of my subnet is 10.5.15.0/24, I would use 10.5.15.1 as my IP address.  If my subnet was 10.5.15.128/25, I would use 129 10.5.15.129 as my IP address
    6. Metric: 3
    7. Click OK
  4. Click the Add Route button
    1. Type: IPv4
    2. Interface: Untrust
    3. Add the any-ipv4 object to Selected Network box
      1. This will allow us to force all internet bound traffic through our Untrust interface
    4. Add the Azure-LB-Probe object to the Selected Network box
      1. This will allow health probes from the external azure load balancer probes to flow properly
    5. Add the YourAppliance-mgmt object to the Selected Network box
    6. Gateway: Use the IP address of the default gateway of your subnet the Untrust interface is deployed on
      1. Note: To find this, navigate to the Azure Portal (portal.azure.com) and select All Services -> Virtual Networks -> Your Virtual Network -> Subnets and use the first IP address of your subnet the untrusted interface is on.  For example, is the address range of my subnet is 10.5.15.0/24, I would use 10.5.15.1 as my IP address.  If my subnet was 10.5.15.128/25, I would use 129 10.5.15.129 as my IP address
    7. Metric: 2
    8. Click OK
  5. Click the Save button

Once you have completed the steps above, click Deploy, select each of your appliances, and click Deploy to push the configuration to the device

Configure NAT Policies

First create a NAT rule that will SNAT any traffic from our trusted zone to the Untrust interface. This is needed so Azure understands to return traffic through the external interface of your device for inspection.

  1. Select the Devices tab, click NAT, and select the Threat Defense NAT Policy link (or New Policy button)
  2. Select your first appliance, click the Add to Policy button, and click Save
  3. Click the Add Rule button
    1. NAT Rule: Auto NAT Rule
    2. Type: Dynamic
    3. Interface Objects Tab
      1. Select the Trusted Interface Object and click the Add to Source button
      2. Select the Untrusted Interface object and click the Add to Destination button
    4. Translation Tab
      1. Click the green button to add a new network object under Original Packet
        1. Name: any-ipv4
        2. Network: 0.0.0.0/0
        3. Click Save
      2. Original Source: any-ipv4
      3. Translated Source: Destination Interface IP
    5. Click OK

Next, we need to create a new NAT statement to handle traffic for our load balancer probes. We will need to configure two statements since we will receive health probes from the same IP address (168.63.129.16) to both NICs. On the same appliance, continue the following steps.

  1. Click the Add Rule button
    1. NAT Rule: Manual NAT Rule
    2. Type: Static
    3. Interface Objects Tab
      1. Select the Trusted Interface Object and click the Add to Source button
      2. Select the Untrusted Interface object and click the Add to Destination button
    4. Translation Tab
      1. Original Source: Azure-LB-Probe
      2. Original Destination: Source Interface IP
      3. Original Destination Port: SSH
      4. Translated Source: Destination Interface IP
      5. Translated Destination: YourAppliance-mgmt
      6. Translated Destination Port: SSH
    5. Click OK
  2. Click the Add Rule button
    1. NAT Rule: Manual NAT Rule
    2. Type: Static
    3. Interface Objects Tab
      1. Select the Untrusted Interface Object and click the Add to Source button
      2. Select the Trusted Interface object and click the Add to Destination button
    1. Translation Tab
      1. Original Source: Azure-LB-Probe
      2. Original Destination: Source Interface IP
      3. Original Destination Port: SSH
      4. Translated Source: Destination Interface IP
      5. Translated Destination: YourAppliance-mgmt
      6. Translated Destination Port: SSH
  3. Click OK

Optional Step: If you are using the appliances to front applications to the internet, you will also need to configure a NAT rule for ingress traffic. This is an optional step, but will show you how to configure traffic to let’s say a web server (which the ALB is configured to listen for per the template). If you do complete this step, make sure you add an access policy (Policies -> Access Control -> Select your policy -> click Add Rule).

  1. Click the Add Rule button
    1. NAT Rule: Manual NAT Rule
    2. Type: Static
    3. Interface Objects Tab
      1. Select the Untrusted Interface Object and click the Add to Source button
      2. Select the Trusted Interface object and click the Add to Destination button
    4. Translation Tab
      1. Original Source: any-ipv4
      2. Original Destination: Source Interface IP
      3. Original Destination Port: HTTP
      4. Translated Source: Destination Interface IP
      5. Translated Destination: webserver
        1. Click the green add button to create a new network object to define the private IP address of your web server.
      6. Translated Destination Port: HTTP
    1. Click OK

Click Save once you have finished adding the rules.

At this point, you will need to repeat the same steps above. The reason why we cannot apply the policy to both devices is when you configure the rule for the Azure Health Probes, you’ll need to specify the correct Translated Destination (I.e. Appliance1 should use the network object that resolves to appliance 1; Appliance2 should use the network object that resolves to appliance 2)

Once you have completed the steps above, click Deploy, select each of your appliances, and click Deploy to push the configuration to the device

Finalize the environment

Now that the environment is configured, there are two steps you will want to check back on.

  1. Add Route Tables to each subnet to force traffic to the Cisco appliances
    1. You will need to leverage route tables with custom routes to force traffic to the Cisco appliance. I’d highly recommend giving this a read to familiarize yourself with how Route Tables work in Azure: https://docs.microsoft.com/en-us/azure/virtual-network/virtual-networks-udr-overview
  2. Ensure there is a Network Security Group (NSG) on the Untrust subnet
    1. As per Azure Load Balancer’s documentation, you will need an NSG associated to the NICs or subnet to allow traffic in from the internet. https://docs.microsoft.com/en-us/azure/load-balancer/load-balancer-standard-overview#securebydefault
  3. Remove the public IP from your management interface
    1. Considering at this point you’ve configured the device and have private connectivity via VPN or ExpressRoute, I’d remove the public IP from your management interface to prevent the public internet from accessing this interface
  4. Adjust NSG rules
    1. Similar to above, I’d scope down who/what network segments can pass traffic to the device. Go back and modify the NSG on the management interfaces to only allow traffic from specific source addresses.

References

[How-To] Upgrade the firmware on a Dell PowerConnect N2000/3000 series switch

  1. Download the latest firmware from Dell’s website
    1. Navigate to http://www.dell.com/support/ and enter in your service tag.  You should see downloads for this product, grab the latest firmware that is in a zipped folder.
  2. Extract the .zip folder of the firmware
    N2000 Firmware
  3. Console into the switch via SSH or direct console
  4. Copy the current configuration to startup
    1. console#> copy running-config startup-config
      N2000-3000 - copy running-config startup-config
  5. Transfer the firmware to the switch
    1. TFTP Method
      1. console#> tftp://N3000_2000v6.1.2.4.stk backup
    2. USB Method (Directly attached to switch)
      1. console#> usb://N3000_2000v6.1.2.4.stk backup
        N2000-3000 - usb transfer - backup
  6. Verify the backup version is the new build
    1. console#> show version
      N2000-3000 - backup - version
  7. Activate the new firmware
    1. console#> boot system backup
      N2000-3000 - backup - boot system backup
  8. Reboot the switch
    1. console#> reload
      N2000-3000 - update bootcode - reload
  9. Verify the build is now up-to-date
    1. console#> show version
      N2000-3000 - show version - active - 6_1_2_4
  10. Update the boot code
    1. console#> update bootcode
      N2000-3000 - show version - active - 6_1_2_4 - update bootcode
  11. Reboot the switch
    1. console#> reload
      N2000-3000 - update bootcode - reload

That should do it!

[Tutorial] Upgrading the firmware on a Cisco 5508 Wireless LAN Controller

This guide will show you what steps are needed to get your Cisco 5508 Wireless LAN Controller to the latest and greatest state.

  1. Download and install a TFTP Server program
    1. TFTPD is the recommend program to be used by Cisco.  It is a free and can be obtained from here: http://tftpd32.jounin.net/tftpd32_download.html
  2. Ensure your TFTP server instance is running and pointed to a directory of your choice.
    In this tutorial, I will be using C:\TFTP-Root as my directory for hosting firmware.
    Tftpd32
  3. Ensure you have an inbound firewall created to allow incoming connections to your machine on UDP port 69 if you will be using the TFTP option.
    UDP 69 - TFTP - Windows Firewall with Advanced Security
  4. Copy the firmware you want to transfer to the WLC to the TFTP server’s directory
    TFTP-Server Firmware Directory
  5. Login to your Cisco WLC and select the Commands tab
    Cisco WLC 5508 - Commands Tab
  6. Ensure the following settings are entered and then click the Download button
    1. File Type: Code
      Transfer Mode: TFTP
      IP Address: xxx.xxx.xxx.xxx (IP Address to your machine)
      File Path: / (Use a relative file path; for example, if your firmware was located at c:\tftp-server\cisco5508\AIR-CT5500-K9-7-6-110-0.aes, use /cisco5508/)
      File Name: AIR-CT5500-K9-7-6-110-0.aes (or whatever your firmware is called)
      TFTP File transfer is successful
  7. Click OK when prompted to transfer the firmware
    Please confirm that you want to initiated the Code download process
  8. Once the firmware has finished updating, click on the Click Here link to reboot the WLC.
    TFTP File transfer is successful
  9. On the System Reboot page, hit the Save and Reboot button.
    Cisco WLC 5508 - Commands - Save and Reboot
  10. Click OK on the Configuration will be saved and the controller will be rebooted prompt.
    Configuration will be saved and the controller will be rebooted - Click ok to confirm
  11. Once the wireless LAN controller reboots, you should now be on the firmware version you provided.  You can verify on the Monitor page.
    Latest WLC firmware with outdated FUS
  12. At this point, you can can be done with your upgrade, however, it is highly recommended you also upgrade to the latest (or compatibile), version of the Field Upgrade Software (FUS) in additional to the WLC firmware (provided Cisco has a new version). The same steps to upgrade the FUS are of steps 6-10.
    1. Additional note, the FUS takes a considerable amount of time to upgrade the WLC.  It is normal for the FUS to take 30-50 minutes to upgrade after applying the firmware.  If you are not busy or intersted, you can watch the FUS upgrade various components if you console into the WLC during boot to keep an eye on things.
  13. Once the WLC and FUS firmware versions have been upgraded to their compatbile versions, you should be good to go! 🙂

Pushing firmware through CLI

If you wish to push the firmware manually via TFTP or FTP, you can use the following commands below (order doesn’t matter as long as transfer download start is entered last).  The process is the same for uploading the firmware to the WLC, you only need to swap out the filename for either the FUS firmware or WLC firmware.

(Cisco Controller) > transfer download datatype code
(Cisco Controller) > transfer download mode tftp (can use ftp as well)
(Cisco Controller) > transfer download username user (only needed if using ftp)
(Cisco Controller) > transfer download password password (only needed if using ftp)
(Cisco Controller) > transfer download filename AIR-CT5500-K9-1-9-0-0-FUS.aes
(Cisco Controller) > transfer download path /
(Cisco Controller) > 
transfer download start


As of 4/14/2014, here are the latest firmware versions:

Release 1.9.0.0 for the Field Upgrade Software

Release 7.6.110.0ED for the Wireless LAN Controller


Notes: While upgrading our WLC from stock firmware, I received a strange error stating % Error: Code file transfer failed – Error while writing output file.  Please see my other blog post in regards to upgrading really old firmware on this device to the latest version: http://jackstromberg.com/2014/04/cisco-wlc-firmware-upgrade-error-code-file-transfer-failed-error-while-writing-output-file/

Cisco WLC Firmware Upgrade – % Error: Code file transfer failed – Error while writing output file

Symptom: When trying to upgrade your 5508 Wireless LAN Controller from an older firmware version (6.0.199.4 in my case), you receive the following error:

% Error: Code file transfer failed – Error while writing output fileError Code file transfer failed - Error while writing output file

Solution: When upgrading the firmware on the 5508, greater versions need to be applied incrementally.  Stock 5508 WLCs appear to be shipping with Software Version 6.0.199.4, the following firmware versions should be applied to reach the latest and greatest versions.

I applied the following upgrades to reach higher versions:

6.0.x  to 7.2.x ED

7.2.x ED to 7.x

Upgrading Network Policy Server from Server 2008 R2 to Server 2012 R2

Synopsis: This tutorial will cover a basic “upgrade” path to go from Server 2008 R2 to Server 2012 R2.  This tutorial assumes you have a single Network Policy Server and you are wishing to reuse the same machine name, IP, and settings.  In environments needing high availability, you will need to complete each of the steps below, adding/removing each server being upgraded from your network load balancer.

In a standalone instance, you will experience some downtime as you will have to retire the old machine and setup a new one.

Tutorial

  1. Login to your Server 2008 R2 NPS server
  2. Open up a command prompt with Administrative Privileges
  3. Execute the following command
    1. netsh nps export filename=”c:\users\YOURUSERNAME\Desktop\NPS.xml” exportPSK=YES
      netsh nps export
  4. Copy the NPS.xml file to your local machine
  5. Disjoin the NPS server from the domain
  6. Retire the machine
  7. Recreate a new Server 2012 R2 machine with the same name and IP address
  8. Join the Server 2012 R2 machine to the domain
  9. Open up Server Manager and select Add Roles and Features
    Server 2012 - Manage - Add Roles and Features
  10. Click Next > on the Before You Begin screen
    Add Roles and Features Wizard - Before you begin
  11. Click Next > on the Installation Type screen
    Add Roles and Features Wizard - Select installation type
  12. Click Next > on the Server Selection screen
    Add Roles and Features Wizard - Select destination server
  13. Check Network Policy and Access Services (click Add Features when the screen pops up)
    Add Roles and Features Wizard - Network Policy and Access Services
    Add Roles and Features Wizard - Server Roles - Network Policy and Access Services
  14. Click Next > on the Features screen
    Add Roles and Features Wizard - Default - Network Policy and Access Services
  15. Click Next > on the Network Policy and Access Services screen
    Add Roles and Features Wizard - Network Policy and Access Services Welcome
  16. Check Network Policy Server and click Next >
    Add Roles and Features Wizard - Role Services - Network Policy Server
  17. Click Install
    Add Roles and Features Wizard - Network Policy and Access Services - Confirmation
  18. Click Close once the installation has successfully completed
    Add Roles and Features Wizard - Network Policy and Access Services - Results
  19. Copy over your XML file from the old NPS server to your new Server 2012 R2 NPS server.
  20. Open up an administrative powershell prompt
    Server 2012 - PowerShell - Run as Administrator
  21. Execute the following command
    1. Import-NpsConfiguration -Path c:\users\YOURUSERNAME\Desktop\NPS.xml
      Server 2012 R2 - Powershell - Import-npsconfiguration
  22. Head over to Server Manager and select Tools -> Network Policy Server
    Server Manager - Tools - Network Policy Server
  23. Verify the rules imported (I selected RADIUS Clients and Servers -> RADIUS Clients to see if it imported my WLAN controller)
    Network Policy Server - RADIUS Clients
  24. Connect your machine to your wireless network! 🙂