Thursday, August 22, 2013

ssh to ipv6 link-local host

What I wanted to be able to do was run my Raspberry Pi as a headless server, and just plug an ethernet cable into it from my laptop and ssh in. Is that so much to ask? I expected it to be automatic with ipv6, since it has automatic host discovery built in. No so much.
  1. ipv6 is not enabled on a Raspberry Pi by default. Edit /etc/modules.conf and add a line for "ipv6". Reboot.
  2. You need to know what address to connect to. ip neigh will show you your neighboring IPs, but it doesn't know about the link-local hosts; you need to ping them first. But you need their addresses to ping them, don't you? The secret is to ping the magic link-local address, ff02::1. Then ip neigh will tell you what you need to know.
    $ ping6 -c 1 -I eth0 ff02::1
    PING ff02::1(ff02::1) from fe80::226:2dff:fef9:3f85 eth0: 56 data bytes
    64 bytes from fe80::226:2dff:fef9:3f85: icmp_seq=1 ttl=64 time=0.050 ms
    
    --- ff02::1 ping statistics ---
    1 packets transmitted, 1 received, 0% packet loss, time 0ms
    rtt min/avg/max/mdev = 0.050/0.050/0.050/0.000 ms
    $ ip -6 neigh
    fe80::ba27:ebff:feb6:6647 dev eth0 lladdr b8:27:eb:b6:66:47 DELAY
    
  3. Ok, I've got the address, but ssh fails with a cryptic "Invalid argument" message.
    $ ssh -6 pi@fe80::ba27:ebff:feb6:6647
    ssh: connect to host fe80::ba27:ebff:feb6:6647 port 22: Invalid argument
    
    This is because ssh (actually, the kernel) doesn't know which interface to talk to. Adding a route for the link-local host doesn't seem to work:
    $ sudo route -A inet6 add fe80::ba27:ebff:feb6:6647/128 dev eth0
    $ route -A inet6 | grep eth0
    fe80::ba27:ebff:feb6:6647/128  ::                         UH   1   0     0 eth0
    fe80::/64                      ::                         U    256 0     0 eth0
    ff00::/8                       ::                         U    256 0     0 eth0
    $ ssh -6 pi@fe80::ba27:ebff:feb6:6647 
    ssh: connect to host fe80::ba27:ebff:feb6:6647 port 22: Invalid argument
    
    So instead, I specified the interface in the ssh hostname (by adding it after a '%'). Here's what that looks like:
    $ ssh -6 pi@fe80::ba27:ebff:feb6:6647%eth0
    pi@fe80::ba27:ebff:feb6:6647%eth0's password: 
    
    scp has a similar problem, and it also uses colons to separate hostname and filename, so you have to put the colon-laden ipv6 address inside square brackets
    $ scp -6 my_file.txt pi@fe80::ba27:ebff:feb6:6647%eth0:
    ssh: Could not resolve hostname fe80: Success
    lost connection
    $ scp -6 my_file.txt pi@'[fe80::ba27:ebff:feb6:6647%eth0]':
    pi@fe80::ba27:ebff:feb6:6647%eth0's password: 
    
I ended up writing a shell script to do all that setup for me:
#!/bin/bash

user=$1
test -n "$user" || user=pi

iface=eth0

# Find the ipv6 address for our link-local host
linkhost=`ip -6 neigh | cut -d ' ' -f 1`
if test -z "$linkhost" ; then
    ping6 -c 1 -I $iface ff02::1 >& /dev/null
    linkhost=`ip -6 neigh | cut -d ' ' -f 1`
fi
echo "Found link local host $linkhost"

echo "ssh -6 -X $user@$linkhost%$iface"
ssh -6 -X $user@$linkhost%$iface

No comments:

Post a Comment