Sunday, June 7, 2020

Enabling IPv6 on Scaleway Dedibox using FreeBSD

First steps

There are two ways to configure IPv6 on a Dedibox:

  1. either your server supports SLAAC (see Dedibox' IPv6 SLAAC to see if it's the case)
  2. or using a DHCPv6 client

Either way, the first thing to do is to accept ICMPv6 Router Advertisements, this will get you a default route using a link-local IPv6 address:

# ifconfig bce0 inet6 accept_rtadv
# grep ^ifconfig_bce0_ipv6 /etc/rc.conf
ifconfig_bce0_ipv6="inet6 accept_rtadv"
# # netstat -rnf inet6 | grep ^default
default                           fe80::be16:65ff:fefb:d23f%bce0 UG        bce0

Also it doesn't hurt to start rtsold(8) (it looked like it worked without it though):

# grep ^rtsold /etc/rc.conf
rtsold_enable="YES"
# /etc/rc.d/rcsold start
Starting rtsold.

If your server supports SLAAC then I believe you can stop here. In my case I was unlucky and I had to use DHCPv6.

After some experimentation, I realized that you need to make you DHCP client request Prefix Delegation (PD), otherwise the router won't let you through. This is very loosely defined in Online´s documentation, because they use dhclient -P. But PD doesn't request a normal address, so you also need to requests a Non-temporary Address (NA) for this.

Unfortunately, FreeBSD's dhclient doesn't support IPv6, so you need another one. There are some references on Internet of people using net/dual-dhclient (GitHub) but I can't use this since my IPv4 setup is static.

Using ISC's dhclient from ports (failed)

Then I moved to net/isc-dhcp44-client because it supports IPv6. The first problem here is that it's not clear to me that FreeBSD's rc.conf(5) allows you to use DHCP only for IPv6. After glancing at /etc/network.subr it's not clear to me that the ifconfig_<if>_ipv6 actually support "DHCP" as a parameter but I may be wrong (and I didn't test that far, see below). Some post seems to indicate that's the case, since they use "DHCP" both in $ifconfig_em0 and ifconfig_em0_ipv6.

As explained in Dedibox' /48 IPv6 prefix page, you need to configure your DHCP unique identifier (DUID) in your dhclient.conf(5) file, which is /usr/local/etc/dhclient.conf (given you use dhclient(8) from the package). The default configuration contains some stuff, you can remove it all:

# cat /usr/local/etc/dhclient.conf
interface "bce0" {
        # DUID given by Dedibox
        # https://console.online.net/en/network/
        send dhcp6.client-id 01:23:45:67:89:AB:CD:EF:01:23;
}

Optionally, if you don't want dhclient-script(8) to overwrite resolv.conf(5), you need to create the following file (I hate the fact the path is hard coded but there are worse evils in the world):

# cat /etc/dhclient-enter-hooks 
make_resolv_conf() { :; }

Here is what you get:

# /usr/local/sbin/dhclient -6 -d -v bce0
Internet Systems Consortium DHCP Client 4.4.2
Copyright 2004-2020 Internet Systems Consortium.
All rights reserved.                                                  
For info, please visit https://www.isc.org/software/dhcp/ 
                                   
Listening on Socket/bce0                                              
Sending on   Socket/bce0                                              
PRC: Soliciting for leases (INIT).                                    
XMT: Forming Solicit, 0 ms elapsed.                                                                                                          
XMT:  X-- IA_NA 52:cd:78:96                                           
XMT:  | X-- Request renew in  +3600        
XMT:  | X-- Request rebind in +5400 
XMT: Solicit on bce0, interval 1090ms.
RCV: Advertise message on bce0 from fe80::be16:65ff:fefb:d23f.
RCV:  X-- Preference 255.
RCV:  X-- IA_NA 52:cd:78:96
RCV:  | X-- starts 1591003129
RCV:  | X-- t1 - renew  +10800
RCV:  | X-- t2 - rebind +172800
RCV:  | X-- [Options]
RCV:  | | X-- IAADDR 2001:aaaa:3bac::1
RCV:  | | | X-- Preferred lifetime 54000.
RCV:  | | | X-- Max lifetime 86400. 
RCV:  X-- Server ID: 00:01:00:01:1b:ac:bc:2d:10:60:4b:9b:0a:f4
RCV:  Advertisement immediately selected.
PRC: Selecting best advertised lease.
PRC: Considering best lease.
PRC:  X-- Initial candidate 00:01:00:01:1b:ac:bc:2d:10:60:4b:9b:0a:f4 (s: 10105, p: 255).
XMT: Forming Request, 0 ms elapsed. 
XMT:  X-- IA_NA 52:cd:78:96
XMT:  | X-- Requested renew  +3600
XMT:  | X-- Requested rebind +5400
XMT:  | | X-- IAADDR 2001:aaaa:3bac::1
XMT:  | | | X-- Preferred lifetime +7200
XMT:  | | | X-- Max lifetime +7500
XMT:  V IA_NA appended.
XMT: Request on bce0, interval 1040ms.
RCV: Reply message on bce0 from fe80::be16:65ff:fefb:d23f.
RCV:  X-- Preference 255.
RCV:  X-- IA_NA 52:cd:78:96
RCV:  | X-- starts 1591003129
RCV:  | X-- t1 - renew  +10800
RCV:  | X-- t2 - rebind +172800
RCV:  | X-- [Options]
RCV:  | | X-- IAADDR 2001:aaaa:3bac::1
RCV:  | | | X-- Preferred lifetime 7200.
RCV:  | | | X-- Max lifetime 86400. 
RCV:  X-- Server ID: 00:01:00:01:1b:ac:bc:2d:10:60:4b:9b:0a:f4
PRC: Bound to lease 00:01:00:01:1b:ac:bc:2d:10:60:4b:9b:0a:f4.
PRC: Renewal event scheduled in 10800 seconds, to run for 162000 seconds.
PRC: Depreference scheduled in 7200 seconds.
PRC: Expiration scheduled in 86400 seconds.

The interface will get an global IPv6 address but as I said above, Online's router won't let you unless request Prefix Delegation PD, so this is pretty useless. You do this with the -P flag but if you read the manual:

-P Enable IPv6 prefix delegation. This implies −6 and also disables the normal address query. See −N to restore it. Multiple prefixes can be requested with multiple −P flags. Note only one requested interface is allowed.

-N Restore normal address query for IPv6. This implies -6. It is used to restore normal operation after using -T or -P. Multiple addresses can be requested with multiple −N flags.

So that's what I did:

# /usr/local/sbin/dhclient  -d -v -6 bce0 -P -N
Internet Systems Consortium DHCP Client 4.4.2
Copyright 2004-2020 Internet Systems Consortium.
All rights reserved.
For info, please visit https://www.isc.org/software/dhcp/
Listening on Socket/bce0
Sending on   Socket/bce0
PRC: Confirming active lease (INIT-REBOOT).
XMT: Forming Rebind, 0 ms elapsed.
XMT:  X-- IA_NA 52:cd:78:96
XMT:  | X-- Request renew in  +3600
XMT:  | X-- Request rebind in +5400
XMT:  X-- IA_PD 52:cd:78:96
XMT:  | X-- Requested renew  +3600
XMT:  | X-- Requested rebind +5400
XMT:  | | X-- IAPREFIX 2001:aaaa:3bac::/48
XMT:  | | | X-- Preferred lifetime +7200
XMT:  | | | X-- Max lifetime +7500
XMT:  V IA_PD appended.
XMT: Rebind on bce0, interval 990ms.
RCV: Reply message on bce0 from fe80::be16:65ff:fefb:d23f.
RCV:  X-- Preference 255.
RCV:  X-- IA_NA 52:cd:78:96
RCV:  | X-- starts 1591559136
RCV:  | X-- t1 - renew  +10800
RCV:  | X-- t2 - rebind +172800
RCV:  | X-- [Options]
RCV:  | | X-- IAADDR 2001:aaaa:3bac::1
RCV:  | | | X-- Preferred lifetime 54000.
RCV:  | | | X-- Max lifetime 86400.
RCV:  X-- IA_PD 52:cd:78:96
RCV:  | X-- starts 1591559136
RCV:  | X-- t1 - renew  +10800
RCV:  | X-- t2 - rebind +172800
RCV:  | X-- [Options]
RCV:  | | X-- IAPREFIX 2001:aaaa:3bac::/48
RCV:  | | | X-- Preferred lifetime 7200.
RCV:  | | | X-- Max lifetime 86400. 
RCV:  X-- Server ID: 00:01:00:01:1b:ac:bc:2d:10:60:4b:9b:0a:f4
PRC: Bound to lease 00:01:00:01:1b:ac:bc:2d:10:60:4b:9b:0a:f4.
Prefix REBIND6 old=2001:aaaa:3bac::/48 new=2001:aaaa:3bac::/48
PRC: Renewal event scheduled in 10800 seconds, to run for 162000 seconds.
PRC: Depreference scheduled in 7200 seconds.
PRC: Expiration scheduled in 86400 seconds.

But then I check my interface but the IPv6 address hadn't been assigned. This is were I threw the towel as I considered I had been too deep in the rabbit whole already.

Theorically I could just have used -P to open the router's gate and then rely on static IPv6 configuration. That would have actually been my favorite solution but I'm not sure FreeBSD's rc.conf(5) would allow this. Actually, it maybe be possible by usng /etc/start_if.bce0; to start /usr/local/sbin/dhclient -6 -P bce0 (and probably remove the ifdisabled inet6 flag on the interface first), but I haven't tried.

Using KAME's dhcp6

As I was wondering about Prefix Delegation and how I could re-use the information I got from the DHCP server to assign IPv6 addresses to my hosts, I came across this post. It's the way it should be: simple and straightforward. dhcp6c requests both PD and NA and in the PD response handler, you tell it how to assign an IPv6 address to the other interfaces. Then rtadvd(8) does the rest.

But I digress and I'm not there yet, I just want an IPv6 for my host. My point is that it's neat. I created a super basic configuration file for dhcp6c:

# cat /usr/local/etc/dhcp6c.conf
interface bce0 {
        send ia-na 1;
        send ia-pd 1;
        send rapid-commit;
};

id-assoc pd 1 {
};

id-assoc na 1 {
};

The attentive reader will notice there's a problem here. Where is the DUID configured? Well it turns out that dhcp6c doesn't allow to configure it by hand... I found this on Google Books (page 469 of IPv6 Advanced Protocols Implementation):

A DHCPv6 client or server needs its DUID for the protocol operation. The user does not have to configure the ID by hand: dhcp6c and dhcp6s automatically generate their type 1 DUIDs on their first invocation, and them them in volatile files, /var/db/dhcp6c_duid and /var/db/dhcp6sduid.

So we need to generate /var/db/dhcp6c_duid. Fortunately someone already did the work:

echo 01:23:45:67:89:AB:CD:EF:01:23 | \
  awk '{ gsub(":"," "); printf "0: 0a 00 %s\n", $0 }' | \
  xxd -r > /var/db/dhcp6c_duid

Let's try it manually:

# dhcp6c -df -c /usr/local/etc/dhcp6c.conf bce0
Jun/07/2020 23:35:12: failed to open /usr/local/etc/dhcp6cctlkey: No such file or directory
Jun/07/2020 23:35:12: failed initialize control message authentication
Jun/07/2020 23:35:12: skip opening control port
Jun/07/2020 23:35:13: Sending Solicit
Jun/07/2020 23:35:13: unexpected advertise
Jun/07/2020 23:35:13: Sending Request
Jun/07/2020 23:35:13: dhcp6c Received REQUEST
Jun/07/2020 23:35:13: add an address 2001:aaaa:3bac::1/128 on bce0

# ping6 -qc 1 google.com
PING6(56=40+8+8 bytes) 2001:aaaa:3bac::1 --> 2a00:1450:4007:811::200e

--- google.com ping6 statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 2.047/2.047/2.047/0.000 ms

Let's just enable dhcp6c at boot time:

# grep ^dhcp6 /etc/rc.conf
dhcp6c_enable="YES"
dhcp6c_interfaces="bce0"

One last thing: if you're using a firewall, be sure to let IPv6 UDP packets on port 546 come in. This is how DHCPv6 works. With pf, this would look like:

# grep 'port 546' /etc/pf.conf 
pass in log inet6 proto udp to port 546

And voilĂ !

Friday, May 1, 2020

Using Lenovo X1 Carbon volume/brightness keys under a bare X11

This is going to be a quick post.

When you don't use a fancy Window Manager (I'm using i3), there are many things which don't come out of the box. Well i3 supports keyboard bindings, but I'm not sure about special keys like volume/brightness. You may ask "but what are you using this then?", well it's another topic but I'll just say that I like its lightweightness.

Anyway, I'm sure there's more than one way to do this but one pretty neat method I found and like is using xbindkeys(1). Here is the relevant snippet of my $HOME/.xbindkeyrc:

"xterm"
  control + b:2

# Increase backlight
"xbacklight -inc 5"
  XF86MonBrightnessUp

# Decrease backlight
"xbacklight -dec 5"
  XF86MonBrightnessDown

# Increase volume
"amixer set Master 5%+"
  XF86AudioRaiseVolume

# Decrease volume
"amixer set Master 5%-"
  XF86AudioLowerVolume

# Mute
"amixer set Master toggle"
  XF86AudioMute

I let you figure out what this does, but that shouldn't be too hard...

Note that of other XF86 symbols, you can visit this page: http://wiki.linuxquestions.org/wiki/XF86_keyboard_symbols. You can also use xev(1) to figure out which code is emitted when one key is pressed.

Finally, xbindkeys(1) is a daemon which needs to be started, so just add this to your $HOME/.xinitrc:

xbindkeys

Have fun!

Saturday, February 15, 2020

Using acme.sh to generate a Let's Encrypt certificate

Like almost any individual on Earth involved a little bit in open-source, I use Let's Encrypt to generate my web certificates.

The first client I used for this was acme-client. It was conveniently available as FreeBSD's security/acme-client port but it has been removed. The reason, as far as I understand, is a bit silly: acme-client has been imported in OpenBSD and is now maintained there. The code on the original website is not updated anymore so the port has been removed. (It's a bit more work to maintain a tool from OpenBSD as a port, as someone needs to manually extract it from time to time and store it somewhere, I guess that's the fundamental reason.)

Anyway I had to find something else and after a bit of googling, I found a replacement: acme.sh, which is packaged as security/acme.sh. At first this is a bit scary (7000 lines of shell script, but well people have reported it works well and it's simple, so I gave it a try.

So here is how set it up (note that the ordering is important):

  1. First you need to configure you web server so that Let's Encrypt's service can read the challenge and verify the domain you claim is indeed yours. In my case, with Nginx:
        # This host only exist on port 443. Just accept Let's Encrypt challenges
        # in plain text and redirect anything else to the port 443.
        server {
            listen       80;
            server_name  foo.chchile.org;
            access_log   /var/log/nginx/foo-access.log;
            error_log    /var/log/nginx/foo-error.log info;
            allow all;
    
            root   /var/empty;
    
            location ^~ /.well-known/acme-challenge/ {
                alias /usr/local/www/acme/.well-known/acme-challenge/;
            }
    
            location / {
                return  301 https://foo.chchile.org$request_uri;
            }
        }
        
  2. Then you can run acme.sh to issue the certificate. It's really straightforward:
    # /usr/local/sbin/acme.sh  --issue -d foo.chchile.org -w /usr/local/www/acme
                            
    [Sat Feb 15 21:40:18 UTC 2020] Create account key ok.
    [Sat Feb 15 21:40:18 UTC 2020] Registering account                                                                                           
    [Sat Feb 15 21:40:19 UTC 2020] Registered                                                                                                    
    [Sat Feb 15 21:40:19 UTC 2020] ACCOUNT_THUMBPRINT='...'
    [Sat Feb 15 21:40:19 UTC 2020] Creating domain key
    [Sat Feb 15 21:40:19 UTC 2020] The domain key is here: /root/.acme.sh/foo.chchile.org/foo.chchile.org.key                        [Sat Feb 15 21:40:19 UTC 2020] Single domain='foo.chchile.org'                                                                         [Sat Feb 15 21:40:19 UTC 2020] Getting domain auth token for each domain                             
    [Sat Feb 15 21:40:20 UTC 2020] Getting webroot for domain='foo.chchile.org'
    [Sat Feb 15 21:40:20 UTC 2020] Verifying: foo.chchile.org
    [Sat Feb 15 21:40:24 UTC 2020] Success
    [Sat Feb 15 21:40:24 UTC 2020] Verify finished, start to sign.
    [Sat Feb 15 21:40:24 UTC 2020] Lets finalize the order, Le_OrderFinalize: https://acme-v02.api.letsencrypt.org/acme/finalize/78264375/2343222
    453
    [Sat Feb 15 21:40:25 UTC 2020] Download cert, Le_LinkCert: https://acme-v02.api.letsencrypt.org/acme/cert/04236a2f1bd00075bda7c3ed8bb9d2953b0
    0
    [Sat Feb 15 21:40:25 UTC 2020] Cert success.
    -----BEGIN CERTIFICATE-----
    [...]
    -----END CERTIFICATE-----
    [Sat Feb 15 21:40:25 UTC 2020] Your cert is in  /root/.acme.sh/foo.chchile.org/foo.chchile.org.cer 
    [Sat Feb 15 21:40:25 UTC 2020] Your cert key is in  /root/.acme.sh/foo.chchile.org/foo.chchile.org.key 
    [Sat Feb 15 21:40:25 UTC 2020] The intermediate CA cert is in  /root/.acme.sh/foo.chchile.org/ca.cer 
    [Sat Feb 15 21:40:25 UTC 2020] And the full chain certs is there:  /root/.acme.sh/foo.chchile.org/fullchain.cer 
        
  3. Now, let's configure the desired certificate paths in Nginx (there's not there yet, this will be the next step):
       server {
            listen       443 ssl;
            server_name  foo.chchile.org;
            access_log   /var/log/nginx/foo-access.log;
            error_log    /var/log/nginx/foo-error.log debug;
    
            ssl_certificate      /usr/local/etc/ssl/acme/foo.chchile.org/fullchain.pem;
            ssl_certificate_key  /usr/local/etc/ssl/acme/private/foo.chchile.org/privkey.pem;
    
            ssl_session_timeout  5m;
    
            ssl_protocols  TLSv1.1 TLSv1.2;
            ssl_ciphers  HIGH:!aNULL:!MD5;
            ssl_prefer_server_ciphers   on;
    
            [...]
        }
        
  4. It's almost done. acme.sh uses /root/.acme.sh as its work space. The certificate and the key are there but it's not advised to use them as is since the internal layout may chance in the future. So you need to install (read "copy") the certificate:
    # /usr/local/sbin/acme.sh --install-cert -d foo.chchile.org \
      --key-file /usr/local/etc/ssl/acme/private/foo.chchile.org/privkey.pem \
      --cert-file /usr/local/etc/ssl/acme/foo.chchile.org/cert.pem \
      --fullchain-file /usr/local/etc/ssl/acme/foo.chchile.org/fullchain.pem \
      --reloadcmd "service nginx restart"
    [Sat Feb 15 21:42:24 UTC 2020] Installing cert to:/usr/local/etc/ssl/acme/foo.chchile.org/cert.pem
    [Sat Feb 15 21:42:24 UTC 2020] Installing key to:/usr/local/etc/ssl/acme/private/foo.chchile.org/privkey.pem
    [Sat Feb 15 21:42:24 UTC 2020] Installing full chain to:/usr/local/etc/ssl/acme/foo.chchile.org/fullchain.pem
    [Sat Feb 15 21:42:24 UTC 2020] Run reload cmd: service nginx restart
    Performing sanity check on nginx configuration:
    nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful
    Stopping nginx.
    Waiting for PIDS: 29714.
    Performing sanity check on nginx configuration:
    nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful
    Starting nginx.
    [Sat Feb 15 21:42:24 UTC 2020] Reload success
        
  5. And finally, set up a cron job to renew the certificate whenever it's needed. That's where acme.sh is neat, everything you typed above has been recorded in /root/acme.sh, so the crontab(5) line just looks like:
    0       0       *       *       *       root    /usr/local/sbin/acme.sh --cron > /dev/null
        

Blog re-purpose

It's a been a long time since my last post.

My life has changed a lot since then and I don't have much time anymore to play with my computer for fun, even less to write articles to share my knowledge and learning. Hence from now on, when I will post something this will be mostly as a memo for myself for future use. Since it's public, I will still try to make the posts look decent but the style will be much more terse.

So why keep posting at all? I doubt I have any reader anyway. But maybe sometimes someone will be desperate enough to go to Google's 10th result page and will come across my blog. If this can help this soul, I'll be more than happy!