OpenBSD Journal

developer blog: todd: ipsecctl hacking

Contributed by todd on from the trenches-of-the-hackathon dept.

As can be seen on source-changes, the utility ipsecadm(8) has been removed from the OpenBSD source tree.

Part of the reason for this removal is because people (like me) have not used the new ipsecctl(8) utility. Personally, I could not use it because it did not support IPv6 . Now that ipsecadm(8) is gone, there was only one choice left, add IPv6 code to ipsecctl(8).

The initial implementation of my diff seemed to work, but then hostnames did not resolve properly. After a few reminders in logic, I ended up realizing that I needed to obliterate host_v4() and host_dns() and just merge them into one function inside parse.y. After that, things went much more smoothly!

Since ipsecctl(8) borrowed its parser from bgpd(8), and bgpd(8) supports IPv6, it seemed logical to to go read bgpd(8)'s parser for any hints on how a working IPv6 parser worked. Early on I stumbled across an ERANGE check that was missing, easy commit #1!

The fun with developing a utility that someone else is also developing is that you get to play catchup with others when they commit work, and resolve merge conflicts. Hans is busy working up some nifty new tricks for the parser. Cool stuff! 'Tis ok I get to play catchup everytime he commits something, I get to learn more of the code that way ;-)

After a few rounds, Hans told me it would be fine to commit one part of my work. However, he has plans for the parser so my dif fs in that file will just have to wait.

It was a good thing they did wait, because Naddy was testing my v6 diff to ipsecctl, and observed a curious thing. When actually setting flows in the kernel, that the peer address that was inserted into the kernel was the local address, which is wrong. After a few minutes of debugging, it seems that this was, as can be expected, a reversal of src and dst in the code.

Success! We had static-keyed ipsec working with IPv6 and ipsecctl(8)!

Next I went to try using ipsecctl(8) with isakmpd(8). The way this works is isakmpd(8) is started with no configuration file, and then a rule such as this:

    ike esp from 3ffe::1 to 3ffe::2

is placed in /etc/ipsec.conf. When parsing this file, ipsecctl(8) converts the simple statement into the complex grammar required by isakmpd(8), and further into a syntax that can be written to the isakmpd(8) fifo:

    C set [Phase 1]:3ffe::2=peer-3ffe::2 force
    C set [peer-3ffe::2]:Phase=1 force
    C set [peer-3ffe::2]:Address=3ffe::2 force
    C set [peer-3ffe::2]:Configuration=mm-3ffe::2 force
    C set [mm-3ffe::2]:EXCHANGE_TYPE=ID_PROT force
    C add [mm-3ffe::2]:Transforms=AES-SHA-GRP15-RSA_SIG force
    C set [IPsec-3ffe::1-3ffe::2]:Phase=2 force
    C set [IPsec-3ffe::1-3ffe::2]:ISAKMP-peer=peer-3ffe::2 force
    C set [IPsec-3ffe::1-3ffe::2]:Configuration=qm-3ffe::1-3ffe::2 force
    C set [IPsec-3ffe::1-3ffe::2:Local-ID=lid-3ffe::1 force
    C set [IPsec-3ffe::1-3ffe::2]:Remote-ID=rid-3ffe::2 force
    C set [qm-3ffe::1-3ffe::2]:EXCHANGE_TYPE=QUICK_MODE force
    C set [qm-3ffe::1-3ffe::2]:Suites=QM-ESP-AES-SHA2-256-PFS-GRP15-SUITE force
    C set [lid-3ffe::1]:ID-type=IPV6_ADDR force
    C set [lid-3ffe::1]:Address=3ffe::1 force
    C set [rid-3ffe::2]:ID-type=IPV6_ADDR force
    C set [rid-3ffe::2]:Address=3ffe::2 force
    C add [Phase 2]:Connections=IPsec-3ffe::1-3ffe::2

For those astute readers, there is a missing bracket before the 'Local-ID' above. I did not typo in this article, at this point in my development of the v6 diff, I realized this error. isakmpd(8) was helpfu l by logging the error message in /var/log/messages for me:

    isakmpd[15311]: ui_config: command
       "C set [IPsec-3ffe::1-3ffe::2:Local-ID=lid-3ffe::1 force" malformed

After finding this error, Hans was nowhere to be found. At OpenBSD, the 'ok' rule exists so that when you have to go find someone else to 'ok' your commit who is familiar with the code you are working on. Your commit is more likely to be right if you have to seek others approval. I finally found a willing soul, and an easy commit followed 'ok dlg@'.

So, we had a working ipsecctl(8), right? Well, not really. It turns out a recent commit by Hans broke compatibility between th e default configurations of isakmpd(8) prior and after that commit. I was testing between my 386 laptop and my zaurus pda. This only showed up because I happened to compile the latest isakmpd(8) code on my i 386 laptop, but did not for my zaurus pda. As is possible at such an event, the developer to talk to was just across the table from me. After a few head scratching and debugging sessions, Matthieu (msf@) realized it was the recently added default of group15/modp3072 that was at fault. It took a short bit to figure out how to correct this, but it turns out a couple more pieces to a still one-line ipsec.conf(5) file did the trick:

ike esp from ::/0 to 3ffe:41::2b \
    local 1.2.0.24 peer 1.2.0.11 \
    main auth hmac-sha1 enc aes group modp1024 \
    quick auth hmac-sha2-256 enc aes group modp1024 \
    srcid blue.fries.net dstid z.fries.net

With this success, I then decided to replace the 50 line `very simple' isakmpd.conf(5) file:

[General]
Policy-File=/etc/isakmpd/isakmpd.policy
Retransmits=490
Exchange-max-time=9000
Check-interval=30

[Phase 2]
Connections=compaq-default

[compaq]
ID-type=FQDN
Name=compaq.fries.net

[puffy]
ID-type=FQDN
Name=puffy.freedaemonhosting.com

[compaq-IPv6]
ID-type=IPV6_ADDR_SUBNET
Network=2001:240:58a:c286::
Netmask=ffff:ffff:ffff:ffff::

[default-IPv6]
ID-type=IPV6_ADDR_SUBNET
Network=::
Netmask=::

[puffyv4]
Phase=1
Address=66.210.104.251
Configuration=rsa-main-mode
ID=compaq
Remote-ID=puffy

[compaq-default]
Phase=2
ISAKMP-peer=puffyv4
Configuration=quick-mode
Local-ID=compaq-IPv6
Remote-ID=default-IPv6

[rsa-main-mode]
DOI=IPSEC
EXCHANGE_TYPE=ID_PROT
Transforms=3DES-SHA-RSA_SIG

[quick-mode]
DOI=IPSEC
EXCHANGE_TYPE=QUICK_MODE
Suites=QM-ESP-3DES-SHA-PFS-SUITE

With an even simpler 1 line ipsec.conf(5) file:

ike esp from 2001:240:58a:c286::/64 to ::/0 \
        peer 66.210.104.251 \
        main auth hmac-sha1 enc 3des group modp1024 \
        quick auth hmac-sha2-256 enc 3des group modp1024 \
        srcid compaq.fries.net dstid puffy.freedaemonhosting.com

And... it did not work. Ok, what is going on with those odd prefixlens in the output of netstat(8) (netstat -nr -f encap)? Hmm, a v6 subnet with a prefixlen of 32? I don't think so! Turns out I had missed the code to format the Netmask line for isakmpd(8) in ipsecctl(8) when making sure the utility supported v6. After fixing this, the IPsec tunn el works perfectly!

Naddy, again testing my v6 diff to ipsecctl, noticed that there was an annoying thing going on. Whenever he inserted his addresses into the kernel, and printed them back with `ipsecctl -sf', 'proto igmp' was showing up in the output of one of the IPsec flows. Even more curiously, route(8) also exhibited this behavior, numeric format. Obviously som ething is up with this.

If you were an encap sysctl, where would you be?

... chasing down the rabbit hole that is sys/net/pfkeyv2*, I learned a lot. Like how to hex-dump the data the kernel passes to userland and then manually parse it looking at the data structures in src/sys/net/pfkeyv2.h. It turns out that the sockaddr_union structure was abused.

So a fix for the sockaddr_union ensued after talking with several developers, Claudio clearly comes to mind as the one who repeatedly stated in no uncertain terms that after hex decoding the data from the kernel and having it be wrong that the kernel must be accessing the wrong member of the sockaddr_union. Within 5 minutes I had found the spot here and resulted in this commit.

Now all proto's display as zero as they should, great! Wait, always showing zero? Oops, in pfkeyv2*c, there is a value SENT_IP4 and SENT_IP6 instead of AF_INET and AF_INET6. Another commit follows.

Back to ipsecctl. Ultimately, I developed an acceptable diff, after producing several too-large diffs that broke behavior, finally looked in pfctl's parse.y file and found ingenious logic in the parser. It literally pays no attention to whether or not the v4 and v6 addresses match in the source or destination cases when creating the rules, but when preparing to expand the lists of addresses it simply iterates over several different potential lists and does a 'continue' on the inner most loop when things do not make sense. With ipsecctl, there were only two nested loops, and these were for src and dst, exactly what would be useful.

Commit! Let the v6 invasion begin!

Next on the invasion list was expanding the 'any' keyword to v6, easy enough, worked fine, etc. Commit!

On a roll now.. it is useful to teach ike.c how to tell isakmpd.fifo about v6 addresses. Commit!

As I leave Calgary, I need to fix the isakmpd.fifo Netmask syntax output for IPv6, since IPv6 addresses were never intended to be parsed as netmasks. Specific bits, when enabled, end up being treated as link local, which adds a prefix. So the current code produces these gems:

$ ipsecctl -vnf -
ike esp from 3ffe::1/64 to 3ffe:1::1/64 peer 1.2.3.4
C set [Phase 1]:1.2.3.4=peer-1.2.3.4 force
C set [peer-1.2.3.4]:Phase=1 force
C set [peer-1.2.3.4]:Address=1.2.3.4 force
C set [peer-1.2.3.4]:Configuration=mm-1.2.3.4 force
C set [mm-1.2.3.4]:EXCHANGE_TYPE=ID_PROT force
C add [mm-1.2.3.4]:Transforms=AES-SHA-GRP15-RSA_SIG force
C set [IPsec-3ffe::1/64-3ffe:1::1/64]:Phase=2 force
C set [IPsec-3ffe::1/64-3ffe:1::1/64]:ISAKMP-peer=peer-1.2.3.4 force
C set [IPsec-3ffe::1/64-3ffe:1::1/64]:Configuration=qm-3ffe::1/64-3ffe:1::1/64 force
C set [IPsec-3ffe::1/64-3ffe:1::1/64]:Local-ID=lid-3ffe::1/64 force
C set [IPsec-3ffe::1/64-3ffe:1::1/64]:Remote-ID=rid-3ffe:1::1/64 force
C set [qm-3ffe::1/64-3ffe:1::1/64]:EXCHANGE_TYPE=QUICK_MODE force
C set [qm-3ffe::1/64-3ffe:1::1/64]:Suites=QM-ESP-AES-SHA2-256-PFS-GRP15-SUITE force
C set [lid-3ffe::1/64]:ID-type=IPV6_ADDR_SUBNET force
C set [lid-3ffe::1/64]:Network=3ffe::1 force
C set [lid-3ffe::1/64]:Netmask=ffff:ffff:ffff:ffff::%1717975654 force
C set [rid-3ffe:1::1/64]:ID-type=IPV6_ADDR_SUBNET force
C set [rid-3ffe:1::1/64]:Network=3ffe:1::1 force
C set [rid-3ffe:1::1/64]:Netmask=ffff:ffff:ffff:ffff::%1717975654 force
C add [Phase 2]:Connections=IPsec-3ffe::1/64-3ffe:1::1/64
Note the '%1717975654' ? That's the IPv6 behavior of getnameinfo(3) when fed netmasks to do address to string conversion to be `fixed'.

(Comments are closed)


Comments
  1. By Massimo Lusetti (83.211.174.86) on

    I've built a 11 node IPsec VPN with OpenBSD without any gotcha. Amazing!

    OpenBSD as a whole and these kind of tools has made my day work life
    easier and funnier.
    Thanks guys!

  2. By DS (68.104.220.48) on

    This kind of simplification for IPsec is a great thing overall and helps normalize some of the overengineering in the protocol from an implementation standpoint. I like how more and more utilities are taking on the same configuration syntax and control format that made PF so sensible. Great work.

  3. By Anonymous Coward (203.113.233.160) on

    Can anyone elaborate on what they think the value of the ipsecctl tool is?

    Comments
    1. By Todd T. Fries (66.210.111.62) todd@openbsd.org on http://FreeDaemonConsulting.com/

      > Can anyone elaborate on what they think the value of the ipsecctl tool is?

      In a word: simplicity.

      To elaborate, people have stayed away from IPsec because of the brutal and difficult path to get it working. Making things simple and easily understandable is the path ipsecctl is taking. It's predecessor, if it can even be called that (ipsecadm) was removed because it was a barrier and not a useful tool anymore.

      I don't think anybody is going to say that a one line IPsec configuration is going to get any simpler.

Credits

Copyright © - Daniel Hartmeier. All rights reserved. Articles and comments are copyright their respective authors, submission implies license to publish on this web site. Contents of the archive prior to as well as images and HTML templates were copied from the fabulous original deadly.org with Jose's and Jim's kind permission. This journal runs as CGI with httpd(8) on OpenBSD, the source code is BSD licensed. undeadly \Un*dead"ly\, a. Not subject to death; immortal. [Obs.]