Scripting an automated backup for an AVM FritzBox router

My home server is set up to backup pretty much every device in my network automatically every day. So I figured why not also backup my AVM FritzBox router. In case it breaks or an update fails and it has to be reset, I can easily restore a backup with all settings, phone book entries, logs etc.

Fortunately, there is a very basic interface for automation, so it is possible to shell script the process of exporting the entire configuration. The result is identical to the manual export in the administration interface to export everything to a backup file. The necessary steps are as follows:

  1. Request a challenge
  2. Log in by responding to the challenge
  3. Extract session ID
  4. Export the backup file
  5. Log out

For sake of simplicity, I will assume that the router is available at https://fritz.box (which it is by default) and that the admin password is supersecret.

A challenge can be requested with the URL https://fritz.box/login_sid.lua:

curl -s -k https://fritz.box/login_sid.lua

The response is a SessionInfo XML, which looks like this:

<?xml version="1.0" encoding="utf-8"?>
<SessionInfo>
  <SID>0000000000000000</SID>
  <Challenge>1ma6cy9c</Challenge>
  <BlockTime>0</BlockTime>
  <Rights/>
</SessionInfo>

The challenge is a random string that changes every time the URL is called. Without providing the challenge response, the session ID (SID) will remain empty. To extract the challenge and store it in a variable, simply add a sed statement:

CHALLENGE=`curl -s -k https://fritz.box/login_sid.lua | \
sed 's/.*<Challenge>\([a-z0-9]*\)<.*/\1/'`

So now we have to construct the response and call the URL again. The response is <challenge>-md5(<challenge>-<password>), but it is important to make sure the encoding is UTF-16LE before calculating the hash. Otherwise it will differ and won’t be accepted. This can be done by including iconv:

PASSWORD=supersecret
RESPONSE=$CHALLENGE-`echo -n "$CHALLENGE-$PASSWORD" | iconv -t UTF-16LE | md5`

Call the login URL again, this time with the response:

DATA='response='$RESPONSE'&username=&lp='
curl -s -k --data $DATA https://fritz.box/login_sid.lua

In case you want to use a different user than admin, just provide the username here. The response is again the SessionInfo XML, but SID should be set. It also lists the rights the now logged in user has:

<?xml version="1.0" encoding="utf-8"?>
<SessionInfo>
  <SID>962dd5a4b3d75fb2</SID>
  <Challenge>e56b0c6e</Challenge>
  <BlockTime>0</BlockTime>
  <Rights>
    <Name>Dial</Name>
    <Access>2</Access>
    <Name>App</Name>
    <Access>2</Access>
    <Name>HomeAuto</Name>
    <Access>2</Access>
    <Name>BoxAdmin</Name>
    <Access>2</Access>
    <Name>Phone</Name>
    <Access>2</Access>
    <Name>NAS</Name>
    <Access>2</Access>
  </Rights>
</SessionInfo>

In case the SID remains empty, the login failed. If the password is correct, but it still doesn’t work, it may be an encoding issue with the MD5 hash. Keep in mind that the encoding may also depend on the shell used. Use the following example to check your script:

$ echo -n "test" | iconv -t UTF-16LE | md5
c8059e2ec7419f590e79d7f1b774bfe6

To extract the SID and store it in a variable, add the following sed statement:

SID=`curl -s -k --data $DATA https://fritz.box/login_sid.lua | \
sed 's/.*<SID>\([a-z0-9]*\)<.*/\1/'`

This SID can now be used with the call to export all settings for backup. To do this, a form has to be sent to https://fritz.box/cgi-bin/firmwarecfg, including the SID, the desired operation, as well as the password required to restore the backup in this case. I’ll just use the password backup. This is necessary, because the export contains sensitive information, like ISP and SIP accounts, which will be encrypted.

OUT=/tmp/fritzbox.export
BAKPWD=backup
curl -s -k -o $OUT --form sid=$SID --form ImportExportPassword=$BAKPWD \
--form ConfigExport= https://fritz.box/cgi-bin/firmwarecfg

Now the only thing left to do is log out, so the session doesn’t stay open unnecessarily:

DATA='sid='$SID'&logout=1'
curl -s -k --data $DATA https://fritz.box/login_sid.lua

That’s it! The exported file includes all information stored in the FritzBox, so in case anything goes wrong, it allows a complete restore.

IPv6 with Hurricane Electric 6in4 tunnel on DD-WRT

My Asus WL500g Deluxe router is running an Eko build of DD-WRT, which comes with IPv6 support. They haven’t yet included it in the official releases for some reason. However, there is still no ISP offering native IPv6 here. It’s a shame. So some sort of tunnel is needed to finally equip my LAN with an IPv6 subnet.

I didn’t really trust those tunnel brokers, so I tried a 6to4 tunnel. There is a good tutorial and only a few scripts are needed to get it running. The problem was that my IPv4 address is dynamic and changes with every reconnect, which is also enforced every 24h. Unfortunately with 6to4 this also changes the IPv6 prefix since it includes the v4 address. That’s no fun and it also messes up the routing and clients using automatic address assignment. Ok, I need a tunnel broker.

My first try was SIXXS, which took like forever to get everything approved. After finishing the ‘paperwork’, my very own /48 subnet was ready. Some hours later I figured out how to run their proprietary aiccu client on my router. It basically worked, but the connection was slow and the client crashed again and again. The port for MIPS-based routers was from 2007 and I couldn’t find a more recent version. It was very disappointing. So hopefully Hurricane Electric will do better.

The tunnel request is pretty easy and instant. You don’t have to wait a week or two until everything is approved. There is no special software, so you have to rely on the tunneling features of your operating system, that is good. DD-WRT has no GUI to configure tunnels, so some scripts are necessary. The IPv6 tutorial for HE tunnels on DD-WRT is not very good, but I finally figured out how it works. To my surprise this solution is very stable and also very fast. The latency with IPv6 is sometimes even lower than with native IPv4. They must have a really good routing.

So here’s what needs to be done:

  • Get an Eko build running for IPv6 support. For my Asus router it’s the VINT build in V24_TNG.
  • Go to Administration and Management and under IPv6 Support, enable IPv6, Radvd and use the following for Radvd config:
    interface br0 {
       MinRtrAdvInterval 3;
       MaxRtrAdvInterval 10;
       AdvSendAdvert on;
       prefix <Your subnet prefix>::/64 {
          AdvOnLink on;
          AdvAutonomous on;
       };
    };

    This enables Router advertisements, so all the clients can assign an address automatically. Note that this only works with /64 subnets. If you have an /48 subnet you can only use one /64 part of it. Make sure that br0 really is your LAN interface and replace it with the correct identifier otherwise.

  • Go to Commands under Administration and add the following Startup Script:
    /usr/bin/wget 'http://ipv4.tunnelbroker.net/ipv4_end.php?
    ipv4b=AUTO&pass=<your password as MD5 hash>&
    user_id=<your user id>&tunnel_id=<your tunnel id>' -O -
    
    if [ -n `ifconfig | grep 'he-ipv6'` ]
    then
       insmod ipv6
       sleep 3
       ip tunnel add he-ipv6 mode sit remote <HE IPv4 tunnel endpoint> ttl 64
       ip link set he-ipv6 up
       ip addr add <your IPv6 tunnel endpoint>/64 dev he-ipv6
       ip route add ::/0 dev he-ipv6 metric 1
       ip -6 addr add <your router's IPv6 address>/64 dev br0
    fi
    
    radvd -C /tmp/radvd.conf &

    I have an USB stick attached, and placed everything above in a script file. Makes it a little clearer. Make sure the wget call has no line breaks like displayed here (due to limited space).

  • Add the following Firewall rules on the same page:
    iptables -I INPUT 2 -p ipv6 -i br0 -j ACCEPT
    iptables -I INPUT 2 -p ipv6 -i ppp0 -j ACCEPT

That’s it already. Reboot the router and IPv6 should work fine on the router itself and on clients which automatically assigned themselves addresses of the subnet announced by radvd.

For increased reliability, I also added a script which is run as a cron job every couple of minutes:

IP=<HE IPv6 tunnel endpoint>

RESULT=`ping6 -c 3 $IP | grep '64 bytes'`

if [ -n $RESULT ]
then
  /usr/bin/wget 'http://ipv4.tunnelbroker.net/ipv4_end.php?
  ipv4b=AUTO&pass=<your password as MD5 hash>&
  user_id=<your user id&tunnel_id=<your tunnel id>' -O -
fi

This tries to ping the tunnel endpoint of HE three times. If it fails, the website to update the IP address is called, because probably the IPv4 address changed without the Startup Script being run.