OpenBSD Journal

Developer blog - reyk@: more faith in relayd(8)

Contributed by jason on from the faith-is-not-just-a-two-legged-dog dept.

Reyk Floeter (reyk@) recently committed some changes to relayd(8) which add support for IPv6-to-IPv4 and IPv4-to-IPv6 translation. This makes it very easy to relay IPv6 traffic to an IPv4-only host behind an OpenBSD PF router and perform application-layer magic on the headers (e.g. HTTP X-Forwarded-For).

Reyk was kind enough to write up a summary of this new functionality and describe how he came to integrate the capabilities from faithd(8). Please continue on to read Reyk's blog entry.

IPv6 CAT6 cableThe n2k8 network hackathon in Japan inspired me to do more work on IPv6; it might be the spirit of the KAME project or the IPv6-capable network cable that I bought in Tokio's Akihabara. And there is also an increasing demand in the real world - many governmental organizations are introducing IPv6 to their networks. Altogether I got interested and started doing a few minor IPv6 patches before I decided to make an experiment - run IPv6 or even IPv6-only in my internal networks.

First I needed a connection to the IPv6 internet. Since my ISP does not provide native v6 yet, I had to get a tunnel. I decided for a tunnel from the SixXS IPv6 Deployment & Tunnel Broker project because they're known for very reliable, good, and free tunnels. And they cooperate with various ISPs to provide many PoPs (Points of Presence) for the tunnel endpoints - my PoP is only 16ms and 8 IPv4 hops away.

After requesting the desired static tunnel at SixXS and configuring the gif(4) interface and pf(4) on my gateway I had the IPv6 connection running - but what to do next? Because I didn't have an IPv6 subnet yet, I decided to hook up an Unique Local Unicast network internally using a fd00::/64 prefix and to NAT it on the gateway to the tunnel IPv6 address. That might sound like a bad IPv4 habit but I found it kind of interesting that they introduced something like the good old private RFC1918 addresses for IPv6 with RFC4193 - wasn't IPv6 supposed to get global unicast IPs everywhere ;)?

The next thing was to connect some services to the IPv6 world: www, ssh, submission, and imaps. I requested a real IPv6 /48 network from SixXS but only attached it to the gateway itself and published a few AAAA DNS records. Because I didn't like to configure IPv6 in the DMZ - even if our httpd(8) finally supports IPv6 - I decided to cheat a little bit: relayd(8) is supposed to relay connections from A to B; it can also relay connections between IPv6 and IPv4. A relayd(8) on the gateway accepting IPv6 connections from the Internet to forward them to the local IPv4 services in the DMZ was doing the trick.

Actually I first had to fix a few bugs in relayd(8) regarding IPv6 support - binding to the IPv6 :: "any" address, IPv6 pf NAT lookups did not work in relayd(8), and the daemon did not support IPv6 scope IDs to listen on and forward to link local addresses.

But the IPv6-to-IPv4 configuration was still a little bit complicated because I had to add every single IPv6 address as an alias on the gateway and add an configuration block in relayd.conf(5) for all the translated services. There must have been a better solution to make it simplier and to magically relay the incoming IPv6 connections to the right IPv4 address with almost no configuration. The best solution was faith.

There is a daemon in our tree called faithd(8) from WIDE/KAME and Jun-ichiro "itojun" Hagino. The main purpose of faithd(8) is to be a dynamic IPv6-to-IPv4 relay by doing a clever trick: when faithd(8) receives TCPv6 traffic it will figure out the IPv4 destination by looking at the last 4 octets of the original IPv6 destination. For example, if the original IPv6 destination address is 2001:db8:7395:ffff::a01:101, the connection will be relayed to the IPv4 address 10.1.1.1 (a01:101). It can also be used to connect IPv6-only networks to the IPv4 worlds by embedding the complete IPv4 space in a simple /96 IPv6 network. itojun@ used it this way and he probably had the strongest faith in IPv6.

The problems with faithd(8) are that it depends on the faith(4) pseudo kernel-interface, it is not in the default GENERIC kernels, it does not support the reverse direction of IPv4-to-IPv6, and that it requires some legacy configuration with special sysctls, routes, and the faithd.conf(5) file. In contrast, relayd(8) could replace almost the complete faithd(8) functionality without the need for the faith interface, the special sysctls, and with some assistance of pf.

I implemented the dynamic IPv6-to-IPv4 mapping in relayd(8) and also added the reverse IPv4-to-IPv6 mapping. The former works exactly like the described approach in faithd(8), but the configuration is a little bit different and it needs a pf redirection to replace the faith(4) interface. The latter allows relayd(8) to accept IPv4 connections and to forward them to IPv6 destinations by setting the last 4 octets of a pre-configured IPv6 address-prefix to the 4 octets of the original IPv4 destination.

After getting more faith in relayd(8) I upgraded my own setup using the following 3 steps to connect my services to the IPv6 world:

  1. New AAAA DNS records using IPv6 addresses embedding the IPv4 addresses of the original A records in the last 4 octets. The following example embeds 208.77.188.166 as d04d:bca6 in the AAAA record:
    $ host -t A www.my.example.com    
    www.my.example.com has address 208.77.188.166
    host -t AAAA www.my.example.com   
    www.my.example.com has IPv6 address 2001:db8::ffff:64:d04d:bca6
    
  2. Redirections in pf.conf(5) to pass incoming IPv6 traffic for the desired target addresses to relayd(8). There is a special case for HTTP in the following example that will be explained later:
    rdr pass inet6 proto tcp to  port { imaps ssh submission } \
            -> ($ip6_if:0) port 8081
    rdr pass inet6 proto tcp to  port www \
            -> ($ip6_if:0) port 8080
    
  3. A simple relay in relayd.conf(5) handling all the TCP connections that get redirected from pf. The new IPv6-to-IPv4 configuration is enabled by simply adding the keyword "inet" to the forwarding directive - it will tell relayd(8) to forward to an IPv4 destination no matter where the source is coming from:
    tcp protocol tcpgeneric {
            tcp { backlog 128, nodelay, sack, socket buffer 131072 }
    }
    
    relay tcp6to4 {
            listen on :: port 8081
            forward to nat lookup inet
            protocol tcpgeneric
    }
    
    I added a special case for HTTP to add a X-Forwarded-For header to the client's request to see the original IPv6 source address in the httpd(8) logs instead of the IPv4 address of the relaying gateway (the transparent relay mode does not work with two different address families). The Apache httpd(8) webserver can extract the client address from the X-Forwarded-For header by using a module like mod_extract_forwarded (unfortunately the 1.3 version of the module is not online anymore and nobody officially ported it to OpenBSD yet):
    http protocol httpgeneric {
            header append "$REMOTE_ADDR" to "X-Forwarded-For"
            header append "$SERVER_ADDR:$SERVER_PORT" to \
                    "X-Forwarded-By"
            header change "Connection" to "close"
    
            tcp { backlog 128, nodelay, sack, socket buffer 131072 }
    
    }
    
    relay http6to4 {
            listen on :: port 8080
            forward to nat lookup inet
            protocol httpgeneric
    }
    
But there is even more, what about connecting an IPv6-only system to network and using relayd(8) to provide access to the legacy IPv4 world? The configuration was also very simple:
  1. A new configuration entry in pf.conf(5) redirecting the outgoing traffic from the internal network to the IPv4-mapped IPv6 subnet to relayd(8):
    rdr pass on $int_if inet6 proto tcp to 2001:db8::ffff:64::/96
            -> ($ip6_if:0) port 8081
    
  2. No additional configuration in relayd.conf(5) - the existing tcpgeneric block above will already do the right thing.
  3. A special DNS proxy server that translates AAAA requests to IPv4-mapped IPv6 addresses if no native AAAA record is available. There is net/totd in the ports tree that does this trick. It can simply be used with a configuration like the following example (where the forwarders are the addresses of real DNS servers):
    forwarder 192.168.1.1 port 53
    forwarder 192.168.1.2 port 53
    interfaces sis0
    prefix 2001:db8::ffff:64::
    retry 300 
    
  4. And the configuration on the client to become an IPv6-only host. This might vary on different client systems, I did the following on my laptop running OpenBSD:
    • I disabled IPv4 in the hostname.em0 file,
      #dhcp NONE NONE NONE 
      rtsol
      
    • changed the resolv.conf(5) DNS client configuration to use the IPv6 address of the totd proxy,
      search lan.example.com
      nameserver fd00:172:23:61::1
      lookup file bind
      
    • and added a rule to the local pf.conf(5) file to reject any IPv4 traffic. This is more like a workaround but it seems to fix an issue with several websites in Firefox where it tries to load something via IPv4 even if the IPv6 AAAA record is available. The "return" option tells pf to send a TCP RST instead of just dropping the IPv4 packets, this helps firefox to remember that it should really use IPv6.
      # Gracefully reject any IPv4 attempts
      block return quick inet
      
    Static non-local IPv4 entries in the hosts(5) file and in your finger memory should be removed, using a local DNS zone for the LAN services which allows to enter AAAA entries directly or to use the translation service by totd.

I finally got a useable IPv6-only setup with a few drawbacks: no access to UDPv4, no ICMP, no other IPv4 protocols like ESP. But this can easilly be ignored with enough faith in relayd(8)... In the future I will probably have to run an IPv4-to-IPv6 relay to connect the remaining legacy IPv4 hosts to a subset of the IPv6 internet after the global flag day of switching everything to the new protocol (hahaha) which is of course already possible with relayd(8):

relay tcp4to6 {
        listen on 127.0.0.1 port 8081
        forward to nat lookup inet6 2001:db8::ffff:64::
}

#EOT

I'd like to thank Reyk for all the time he spent putting together this blog entry. This is a very interesting re-use of functionality from an older IPv6 daemon. Please email us if you find creative uses for this feature, we'd love to hear about it.

(Comments are closed)


Comments
  1. By jirib (89.176.154.98) on

    great, thanks for your time!

  2. By Anonymous Coward (76.117.79.177) on

    Maybe some day the developers find the time to improve the network stack. :)

    but these improvements are very very welcome! thanks a lot!

    Comments
    1. By Anonymous Coward (70.173.52.51) on

      > Maybe some day the developers find the time to improve the network stack. :)
      >
      > but these improvements are very very welcome! thanks a lot!
      >

      I'm sure diffs from you to improve wherever you feel it is lacking will be given due consideration.

    2. By henning (80.86.183.226) on

      > Maybe some day the developers find the time to improve the network stack. :)

      crawl out from under your rock?

  3. By De Ganseman Amaury (195.13.31.240) on

    Do you know if there's a BIS implementation for OpenBSD(or other) ?
    It's a nice solution for IPv4-only client to access IPv6 networks.

    Comments
    1. By Reyk Floeter (2a01:198:295:0:ffff:ffff:ffff:ffff) reyk@openbsd.org on http://team.vantronix.net/~reyk/

      > Do you know if there's a BIS implementation for OpenBSD(or other) ?
      > It's a nice solution for IPv4-only client to access IPv6 networks.

      Bump-In-The-Stack is weird (RFC 2767), the proposed network stack implementation is something that can be done in user space on a gateway (eg. with relayd) or in the kernel if we would support NAT-PT (protocol NAT). I don't like the idea of something new that magically snoops off packets between network stack and NIC driver... *shudder*. Shouldn't it be done by the OS's packet filter?

      The only interesting section is the "Extension Name Resolver" which is the DNS service. Instead of using a fixed 4 to 6 mapping is looks up an AAAA IPv6 record and temporary assigns an IPv4 address for the IPV4-only client like a 32bit query Id. Maybe this could be done by patching the net/totd port, but I didn't think about the implications yet.

      Comments
      1. By Anonymous Coward (195.13.31.240) on


        > The only interesting section is the "Extension Name Resolver" which is the DNS service. Instead of using a fixed 4 to 6 mapping is looks up an AAAA IPv6 record and temporary assigns an IPv4 address for the IPV4-only client like a 32bit query Id. Maybe this could be done by patching the net/totd port, but I didn't think about the implications yet.
        >

        Yes I'm ok with that (it's quite similar to BIS). But today there isn't any implementation for that (ipv4-only-->v6 world) but I think it's an important thing during the transition and ther's nothing.

        Ok now there's a lot of dual stack (*BSD,Linux,vista,...) but there will be a lot of clients with older system and so IPv4 only.

  4. By Seth (71.30.208.239) on

    "I decided to hook up an Unique Local Unicast network internally using a fd00::"
    From RFC 4193, (section 3.1) isn't the Unique Local Unicast prefix supposed to be fc00::?

    Maybe I'm missing something....

    Comments
    1. By Reyk Floeter (2a01:198:295:0:ffff:ffff:ffff:ffff) reyk@vantronix.net on http://team.vantronix.net/~reyk/

      > "I decided to hook up an Unique Local Unicast network internally using a fd00::" > > From RFC 4193, (section 3.1) isn't the Unique Local Unicast prefix supposed to be fc00::? > > > Maybe I'm missing something....

      The 8th bit 'L' indicates that the prefix is locally assigned. In fact, it should always be fd00:: because "Set to 0 may be defined in the future".
            | 7 bits |1|  40 bits   |  16 bits  |          64 bits           |
            +--------+-+------------+-----------+----------------------------+
            | Prefix |L| Global ID  | Subnet ID |        Interface ID        |
            +--------+-+------------+-----------+----------------------------+
      
         Where:
      
            Prefix            FC00::/7 prefix to identify Local IPv6 unicast
                              addresses.
      
            L                 Set to 1 if the prefix is locally assigned.
                              Set to 0 may be defined in the future.  See
                              Section 3.2 for additional information.
      
            Global ID         40-bit global identifier used to create a
                              globally unique prefix.  See Section 3.2 for
                              additional information.
      
            Subnet ID         16-bit Subnet ID is an identifier of a subnet
                              within the site.
      
            Interface ID      64-bit Interface ID as defined in [ADDARCH].
      

  5. By Lennie (2001:610:612:0:217:31ff:fe75:76a5) on

    So what do we do with automatic configuration of DNS-setting ?

    I mean you could use DHCPv6, but we didn't need it for the address, so why use it for the rest ?

    I heared they are working on an extension to Router Solicitation to allow for it, but I've not seen any implementations.

    I know there is avahi-daemon which implements zero-conf, but that also might not be what people want.

    Or do we just set it to fe80::1%if statically everywhere ?

    What do you think will be the new fancy way to configure a IPv6-only network ?

    Because if can have automatic IPv6-configuration behind the firewall... why configure IPv4 on your desktop(s) ?

    Comments
    1. By Paul 'WEiRD' de Weerd (weerd) on http://www.weirdnet.nl/

      > So what do we do with automatic configuration of DNS-setting ?
      >
      > I mean you could use DHCPv6, but we didn't need it for the address, so why use it for the rest ?

      Because the address came from router solicitation, further Dynamic Host Configuration can be done with DHCP. What's the problem with that ?

      > Or do we just set it to fe80::1%if statically everywhere ?

      Sounds like a very bad plan to me...

      > What do you think will be the new fancy way to configure a IPv6-only network ?

      DHCPv6 seems like a viable option, but we'll have to see what we get.

      > Because if can have automatic IPv6-configuration behind the firewall... why configure IPv4 on your desktop(s) ?

      That's the spirit ;)

      Comments
      1. By Anonymous Coward (2a01:348:108:155:216:41ff:fe53:6a45) on

        > > Or do we just set it to fe80::1%if statically everywhere ?
        > Sounds like a very bad plan to me...

        Whatever happened to draft-ietf-ipv6-dns-discovery-07?

      2. By Lennie (2001:610:612:0:230:1bff:fe46:a618) on

        > > So what do we do with automatic configuration of DNS-setting ?
        > >
        > > I mean you could use DHCPv6, but we didn't need it for the address, so why use it for the rest ?
        >
        > Because the address came from router solicitation, further Dynamic Host Configuration can be done with DHCP. What's the problem with that ?

        Well, it seems double to me.

        You first have one protocol, then an other.

  6. By Anonymous Coward (216.75.164.158) on

    Ok, I've got to ask, why not 6to4?

    It sucks that Im going to have to use NetBSD for my ipv6 needs, I'd rather have OpenBSD, but really, is there any other better way of doing Ipv6 short of 6to4 with an ISP that only does IPv4, and an internet that is largely ipv4 based???

    With Windows server 2008 & Vista being IPv6 primary stack machines, the time is now.

    So, what am I missing?

    What is better than 6to4 on a ipv4 internet connection??

    Comments
    1. By Paul 'WEiRD' de Weerd (weerd) on http://www.weirdnet.nl/openbsd/

      > Ok, I've got to ask, why not 6to4?
      >
      > It sucks that Im going to have to use NetBSD for my ipv6 needs, I'd rather have OpenBSD, but really, is there any other better way of doing Ipv6 short of 6to4 with an ISP that only does IPv4, and an internet that is largely ipv4 based???

      Ask your ISP to provide native IPv6. Get them to at least provide tunneling services until native IPv6 is available. Look into switching to a provider that does offer IPv6. Use a tunnel provider (such as http://www.sixxs.net/) until you have ISP provided IPv6.

      > With Windows server 2008 & Vista being IPv6 primary stack machines, the time is now.

      Yes, the time is now. Make your ISP aware of customer demand for IPv6. If you don't ask, they'll never switch. Ask, so that they are at least aware.

      > So, what am I missing?
      >
      > What is better than 6to4 on a ipv4 internet connection??

      Actually sending out IPv6 traffic to the world.

  7. By Todd T. Fries (2001:240:58a:1:203:93ff:fed1:3670) todd@openbsd.org on http://todd.fries.net/

    As a brief followup, here is how to make the last example work that Reyk posted:
    /etc/relayd.conf:
    relay tcp4to6 {
            listen on 127.0.0.1 port 8081
            forward to nat lookup inet6 2001:db8::ffff:64::
    }
    
    /etc/pf.conf:
    rdr pass inet proto tcp to port 80 -> 127.0.0.1 port 8081
    

    With those lines, anything passing through pf on this system from an external system to port 80 will end up going through the remote v6 system as advertised.

    This is fine if you are at an office with an IPv4 router with IPv6 connectivity and want to relay some IPv4 traffic over IPv6. However, if you are a single laptop wanting to relay some IPv4 traffic over IPv6, you'll need a slightly different scenario. The relayd.conf stays the same, but the pf.conf changes to this:

    /etc/pf.conf
    rdr inet proto tcp to port 80 -> 127.0.0.1 port 8081
    pass out route-to (lo0 127.0.0.1) inet proto tcp to port 80
    

    Suddenly you can access any IPv4 website from your laptop if you have an IPv6 only laptop and an IPv6 router running relayd to reverse the process. (Well, technically, IPv6 only is a stretch, since 127.0.0.1 is an IPv4 address on lo0, but no IPv4 on the wire, I guess is more accurate...)

    Comments
    1. By Todd T. Fries (todd) todd@openbsd.org on http://todd.fries.net/

      An update to this most useful article. With IPv6 Day (June 8, 2011) rapidly approaching some out there might like to have an updated pf.conf(5) syntax given the syntax has changed since the above was written. The full config for a router should now be something like:
       /etc/pf.conf bit:
        4in6="2001:db8::ffff:64::/96"
        ip6_ip="2001:db8::1" # ($ip6_if:0) gets link local which won't work
        pass in quick on !egress inet6 proto tcp to $4in6 port 80 rdr-to $ip6_ip port 8080
        pass in quick on !egress inet6 proto tcp to $4in6 rdr-to $ip6_ip port 8081
       /etc/relayd.conf bit:
        tcp protocol tcpgeneric {
              tcp { backlog 128, nodelay, sack, socket buffer 131072 }
        }
        relay tcp6to4 {
              listen on :: port 8081
              forward to nat lookup inet
              protocol tcpgeneric
        }
        http protocol httpgeneric {
              header append "$REMOTE_ADDR" to "X-Forwarded-For"
              header append "$SERVER_ADDR:$SERVER_PORT" to \
                      "X-Forwarded-By"
              header change "Connection" to "close"
              tcp { backlog 128, nodelay, sack, socket buffer 131072 }
        }
        relay http6to4 {
              listen on :: port 8080
              forward to nat lookup inet
              protocol httpgeneric
        }
      
      The full config for an IPv6 only client (with 127.0.0.1 as the ONLY IPv4 address) would be something like the following:
       /etc/pf.conf bits:
        pass on lo
        match out inet proto tcp rdr-to 127.0.0.1 port 8081
        pass out quick inet proto tcp 
        block return out quick inet proto tcp
       /etc/mygate bits:
        127.0.0.1
       /etc/relayd.conf bits:
        protocol tcpgeneric {
              tcp { backlog 128, nodelay, sack, socket buffer 131072 }
        }
        relay tcp4to6 {
              listen on 127.0.0.1 port 8081
              protocol tcpgeneric
              forward to nat lookup inet6 2001:db8::ffff:64::
        }
      

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.]