Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dhcpv6relay plugin. #551

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open

Conversation

jkroonza
Copy link
Contributor

@jkroonza jkroonza commented Mar 6, 2025

After the plugin is loaded, it just needs dhcpv6relay-server option to know where to relay to.

Server will (current planning) need to support option 135 which tells it to which port to send return responses (it will still send from port 547 typically).

@jkroonza jkroonza marked this pull request as draft March 6, 2025 11:50
@jkroonza
Copy link
Contributor Author

jkroonza commented Mar 6, 2025

This relates to #546

I'm hoping that seeing the binding work here explains why I think this is easier "in-pppd" than outside.

Turns out here exists an DHCPv6 "option 135" which allows us to specify the target port for return traffic from the server. I suspect KEA isn't going to like us sending from a port other than 546, not sure about dhcpd -6 (yet). So might need to bind two "client" sockets, one for sending from port 546 and one for receiving (on some other port). Will need to play a bit with that.

I will say that multicast is a pain in the backside.

@paulusmack if this provides you any insight/ideas, or raises questions, please let me know.

This is intended for pppd as a dial-in (server) service. Client side is easily manageable already and there are hordes of tools to deal with that side of the equation.

@Neustradamus
Copy link
Member

@jkroonza: Nice :)

@jkroonza
Copy link
Contributor Author

jkroonza commented Mar 7, 2025

@jkroonza: Nice :)

Conceptually perhaps, but not there yet.

@jkroonza
Copy link
Contributor Author

jkroonza commented Mar 7, 2025

@Neustradamus @paulusmack

sys-linux uses select(2) to wait for input, sys-solaris uses poll(2).

Would it be highly-objectionable to consolidate that into using the same mechanism for both linux and solaris, and into say pppd/event_handler.c which still presents the add_fd(), remove_fd() and wait_input() calls, but also adds the following additional call:

typedef int (*event_cb)(int fd, void* ctx);
add_fd_callback(int fd, event_cb cb, void* ctx);

remove_fd can still be used to remove that specific FD.

I think we have a very small set of file descriptors overall, and this PR will add maximum three more, so performance of select() vs poll() is probably irrelevant. select will be easier to code against in this case, from the man page on my system it looks like select() should be portable for this use case, but some level of confirmation would be appreciated as this is a significant change to current operation.

The "public" API calls would be add_fd_callback() and remove_fd(), as from what I can tell only core code should use add_fd() and wait_input().

@paulusmack
Copy link
Collaborator

Would it be highly-objectionable to consolidate that into using the same mechanism for both linux and solaris, and into say pppd/event_handler.c which still presents the add_fd(), remove_fd() and wait_input() calls, but also adds the following additional call:

typedef int (*event_cb)(int fd, void* ctx);
add_fd_callback(int fd, event_cb cb, void* ctx);

I wouldn't object, if that would be helpful. I assume the single implementation would use poll().

@jkroonza
Copy link
Contributor Author

jkroonza commented Mar 7, 2025

select() would be easier, but I can use poll() too if there's an objection to select?

This allows non-core modules to add file descriptors to the
event loop and have a call-back called when those file descriptors
won't block on read().

Signed-off-by: Jaco Kroon <[email protected]>
@jkroonza jkroonza force-pushed the dhcpv6relay branch 2 times, most recently from 49cd94c to 22be579 Compare March 8, 2025 01:04
@jkroonza
Copy link
Contributor Author

jkroonza commented Mar 8, 2025

	0x0000:  86dd 0000 0000 0198 0200 0000 0000 0000  ................
	0x0010:  0000 0000 6000 0000 003a 1101 fe80 0000  ....`....:......
	0x0020:  0000 0000 0000 0000 0000 0014 ff02 0000  ................
	0x0030:  0000 0000 0000 0000 0001 0002 0222 0223  .............".#
	0x0040:  003a 67ec 0148 614a 0001 000a 0003 0001  .:g..HaJ........
	0x0050:  18fd 7415 9342 0006 0002 0017 0008 0002  ..t..B..........
	0x0060:  0000 000e 0000 0019 000c 0000 0014 0000  ................
	0x0070:  0708 0000 0b40                           .....@
03:02:42.499749 lo    In  IP6 (flowlabel 0xd8a5e, hlim 64, next-header UDP (17) payload length: 102) ::1.53032 > ::1.547: [bad udp cksum 0x0079 -> 0xbda4!] dhcp6 relay-fwd (linkaddr=:: peeraddr=fe80::14 (opt_135) (relay-message (dhcp6 solicit (xid=48614a (client-ID hwaddr type 1 18fd74159342) (option-request DNS-server) (elapsed-time 0) (rapid-commit) (IA_PD IAID:20 T1:1800 T2:2880))))
	0x0000:  86dd 0000 0000 0001 0304 0006 0000 0000  ................
	0x0010:  0000 0000 600d 8a5e 0066 1140 0000 0000  ....`..^.f.@....
	0x0020:  0000 0000 0000 0000 0000 0001 0000 0000  ................
	0x0030:  0000 0000 0000 0000 0000 0001 cf28 0223  .............(.#
	0x0040:  0066 0079 0c00 0000 0000 0000 0000 0000  .f.y............
	0x0050:  0000 0000 0000 fe80 0000 0000 0000 0000  ................
	0x0060:  0000 0000 0014 0087 0002 cf28 0009 0032  ...........(...2
	0x0070:  0148 614a 0001 000a 0003 0001 18fd 7415  .HaJ..........t.
	0x0080:  9342 0006 0002 0017 0008 0002 0000 000e  .B..............
	0x0090:  0000 0019 000c 0000 0014 0000 0708 0000  ................
	0x00a0:  0b40                                     .@

We can relay already towards the server already. This was with:

plugin dhcpv6relay.so
dhcpv6relay-server ::1

Other variations should work too. If option 135 ends up not working I have two ideas, the simplest of which is to implement a pure proxy that can action the relevant relay on our behalf that does support option 135. This should not be too difficult, and is definitely easier than implementing some "management agent" like previous suggestions.

Next up is to get KEA / ISC DHCPd to respond with a PD so that I can get working on:

  1. Decapsulating and forwarding the response.
  2. Adding relevant routes via sys-* (will need help on solaris side). Routing will need to support both PD as well as address allocation.
  3. Potentially need to keep track of multiple addresses/prefixes issued, and set expiry timers to remove the routes when valid_lifetime expires (assuming that the client doesn't renew the addresses).
  4. Add option 37 aka remote-id (REMOTENUMBER) and 38 aka subscriber-id (PEERNAME).

Is there a maximum length on either of those options or should I consider further complicating the iovec structure?

@jkroonza
Copy link
Contributor Author

jkroonza commented Mar 8, 2025

Option 37 + 38 added. Note the string values inside the hex dump of the packet makes a lot more sense than the tcpdump decoded values.

13:50:42.706067 lo    In  IP6 (flowlabel 0x29224, hlim 64, next-header UDP (17) payload length: 125) ::1.42205 > ::1.547: [bad udp cksum 0x0090 -> 0x9f53!] dhcp6 relay-fwd (linkaddr=:: peeraddr=fe80::14 (opt_135) (Remote-ID 825831982 3136382e312e31...) (Subscriber-ID 74657374...) (relay-message (dhcp6 solicit (xid=ef5a63 (client-ID hwaddr type 1 18fd74159342) (option-request DNS-server) (elapsed-time 300) (rapid-commit) (IA_PD IAID:20 T1:1800 T2:2880))))
	0x0000:  86dd 0000 0000 0001 0304 0006 0000 0000  ................
	0x0010:  0000 0000 6002 9224 007d 1140 0000 0000  ....`..$.}.@....
	0x0020:  0000 0000 0000 0000 0000 0001 0000 0000  ................
	0x0030:  0000 0000 0000 0000 0000 0001 a4dd 0223  ...............#
	0x0040:  007d 0090 0c00 0000 0000 0000 0000 0000  .}..............
	0x0050:  0000 0000 0000 fe80 0000 0000 0000 0000  ................
	0x0060:  0000 0000 0014 0087 0002 a4dd 0025 000b  .............%..
	0x0070:  3139 322e 3136 382e 312e 3100 2600 0474  192.168.1.1.&..t
	0x0080:  6573 7400 0900 3201 ef5a 6300 0100 0a00  est**...2..Zc.....
	0x0090:  0300 0118 fd74 1593 4200 0600 0200 1700  .....t..B.......
	0x00a0:  0800 0201 2c00 0e00 0000 1900 0c00 0000  ....,...........
	0x00b0:  1400 0007 0800 000b 40                   ........@

@jkroonza
Copy link
Contributor Author

jkroonza commented Mar 9, 2025

Tested working up to this point with kea on server side, bound to ::1, and Mikrotik router as client over incoming L2TP connection (via xl2tpd).

DHCP itself works, just need to add routing to resolve this section from the RFC:

https://datatracker.ietf.org/doc/html/rfc8415#section-19.1.3

For the moment I'm going to stick with the assumption that in the pppd use-case we're looking for a PD, and not an address assignment, as in my mind at least it doesn't make sense to merely request a single address on a ppp link, since the CPE device router will invariably want a prefix to assign to the LAN, I'm going to focus on that use-case. A single address request may make sense in VPN type scenarios where a host dials in from a remote location. That may well end up being better served using RAs though, but a DHCP configuration might make more sense, eg, to serve such requests from a single /64

I have spotted one case (ISC dhcpcd -6) where the client expects to first see an route-advertisement in response to a route-solicitation. This can be dealt with with a tool such as radvd or even frr. That said, it may be useful to have a ipv6ra module added to pppd as well in addition to this dhcpv6relay, just want to look into radvd - the problem is our use case is of such a nature that even if radvd will work with patterns it may well end up being in-discriminant, but frr should do the trick by disabling ra announce for the interface during net-pre-up, and then selectively enabling in ipv6-up. If that'll work for us, I'm obviously not going to dig too much further into this.

As an aside, due to the design of ISC dhcpd, it's unlikely going to work well alongside pppd in this configuration on the same host, and not at all on it's own (due to binding specific broadcast ethernet interfaces only during startup, whilst still having a [::]:547 bound unicast socket).

KEA is more configurable, but crashes when you try to bind it to a ppp interface, and having tried to engage with upstream, this doesn't seem likely to get fixed. So my recommendation is KEA bound to a specific address, eg ::1 if you're on a single-host, and then use this module to relay to that.

@jkroonza
Copy link
Contributor Author

jkroonza commented Mar 10, 2025

@paulusmack @Neustradamus

I think I'm going to pause here for a day or two just to give time for feedback. I suspect we can treat these two commits as:

  1. Core changes - need proper review, and ideally testing beyond just me. I'd also like confirmation that the select() implementation over poll() is acceptable. poll() is possible but probably a bit more involved to implement, but if that's the preference I'll put in the effort.
  2. The extra module - probably less of an issue, suspect not too many users will need this, and those that would would like me action their own full blown testing regardless. Not to say "be reckless", but this module will likely not be used by a great many number of people as it's focused very much on the ISP type industry, most of which probably use CISCO or some other highly paid commercial alternative (although I've started seeing a drive in the local industry to get away from those). It might (with some further modifications) be useful to VPN solutions building on top of ppp (eg, ppp over l2tp over ipsec)

This includes an implementation on Linux, and a placeholder on Solaris
(which will need to be implemented to make dhcpv6relay module
effective).

Signed-off-by: Jaco Kroon <[email protected]>
After the plugin is loaded, it just needs dhcpv6-server option to
know where to relay to.

dhcpv6-trusted / dhcpv6-untrusted can be used to mark the port as
trusted (will further forward relay-fwd messages) or unstrusted (will
only accept and forward client originated messages.

dhcpv6-metric - metric at which to install IPv6 routes into the kernel
routing table for prefix delegations.

Server needs to support fwd option 135 (aka relay-port) in order to
respond to pppd.  KEA (for one) does.

Signed-off-by: Jaco Kroon <[email protected]>
@jkroonza jkroonza marked this pull request as ready for review March 11, 2025 05:59
@jkroonza
Copy link
Contributor Author

My testing pans out:

  1. select() implementation hasn't failed in any way (on Linux at least). Happy to redo to poll() but implementation will be more complicated.
  2. changes to sys-linux was tested again with client with defaultroute and defaultroute6, this showed a bug in the changes which was rectified. sys-solaris needs an implementation but I've no way to test that, so would rather leave it with stub implementations that just generates and error indicating that it's non-functional.
  3. dhcpv6relay module covers my requirements. Things that could still be added (That I can think off):
    3.1. deal with IA_ADDR delegation (single /128 address assignment). This makes more sense for a VPN environment, and there I'm betting RAs are sufficient regardless. Shouldn't be hard to add.
    3.2. Multiple upstream DHCPv6 addresses (but you might just as well give this module a multicast address, plus logic of "send to all vs ).
    3.3. Specific bind() address for upstream socket, rather than determining by way of connect().
    3.4. External route manipulation scripts.

Possible change: Turns out I don't need to remember until when a route is valid courtesy of the way ppp_(un)timeout() works. So this field can be removed from the relevant structure, leaving just the prefix and length as the effective fields. We do still need to know what all the routes are, since if IPV6CP gets torn down, we do need to clean up all the routes still.

Where to add documentation? How are modules documented? And how do people know which man pages to get to? It seems the only plugin that's documented in the pppd man page is pppoe.so ... surely that's something we should improve? Either have a separate man page for each pppd plugin (eg, pppd-dhcpv6relay), or document all of them in the main man page?

Everything else (eg, setting DNS servers, NTP servers, or any other information providable by DHCP for that matter) can be handled on KEA side (or ISC dhcpd if you're that way inclined, KEA seems to be more manageable, especially in a clustered environment due to options for shared SQL back-ends - which itself can be clustered again). This module solves a few problems for us:

  1. DHCPv6 server not on the same host.
  2. KEA crashes when trying to bind to a ppp interface. Bug logged, no response for years now.
  3. Dynamic reconfiguration of DHCP servers is best avoided based on testing.
  4. Avoids complicated dhcpv6-proxy process which does what this module does, but for several interfaces, and monitoring for interface addition/removal as it turns out is very, very complicated (which is probably why existing DHCPv6 servers doesn't do it).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants