From d242cbffa4f20c9f7472f79b3a9e47008b6fe77c Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Thu, 30 Dec 2021 21:20:37 +0000 Subject: Add snooping of DHCPv6 prefix delegation to the DHCP-relay function. --- CHANGELOG | 2 ++ man/dnsmasq.8 | 21 +++++++++++++--- src/dhcp6-protocol.h | 2 ++ src/dhcp6.c | 3 +-- src/dnsmasq.c | 15 +++++++++++- src/dnsmasq.h | 19 ++++++++++++++- src/helper.c | 57 ++++++++++++++++++++++++++++++++++++++++--- src/rfc3315.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 8 files changed, 175 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4a012de..aabeb5a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,6 +19,8 @@ version 2.87 now supported for both IPv4 and IPv6 and the configuration syntax made easier (but backwards compatible). + Add snooping of IPv6 prefix-delegations to the DHCP-relay system. + version 2.86 Handle DHCPREBIND requests in the DHCPv6 server code. diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 index 8bdcaa3..4de8969 100644 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -1356,6 +1356,11 @@ supported: the relay function will take precedence. Both DHCPv4 and DHCPv6 relay is supported. It's not possible to relay DHCPv4 to a DHCPv6 server or vice-versa. + +The DHCP relay function for IPv6 includes the ability to snoop +prefix-delegation from relayed DHCP transactions. See +.B --dhcp-script +for details. .TP .B \-U, --dhcp-vendorclass=set:,[enterprise:,] Map from a vendor-class string to a tag. Most DHCP clients provide a @@ -1766,15 +1771,25 @@ receives a HUP signal, the script will be invoked for existing leases with an "old" event. -There are four further actions which may appear as the first argument -to the script, "init", "arp-add", "arp-del" and "tftp". More may be added in the future, so +There are five further actions which may appear as the first argument +to the script, "init", "arp-add", "arp-del", "relay-snoop" and "tftp". +More may be added in the future, so scripts should be written to ignore unknown actions. "init" is described below in .B --leasefile-ro + The "tftp" action is invoked when a TFTP file transfer completes: the arguments are the file size in bytes, the address to which the file was sent, and the complete pathname of the file. - + +The "relay-snoop" action is invoked when dnsmasq is configured as a DHCP +relay for DHCPv6 and it relays a prefx delegation to a client. The arguments +are the name of the interface where the client is conected, its (link-local) +address on that interface and the delegated prefix. This information is +sufficient to install routes to the delegated prefix of a router. See +.B --dhcp-relay +for more details on configuring DHCP relay. + The "arp-add" and "arp-del" actions are only called if enabled with .B --script-arp They are are supplied with a MAC address and IP address as arguments. "arp-add" indicates diff --git a/src/dhcp6-protocol.h b/src/dhcp6-protocol.h index ebe8dfd..f1d0991 100644 --- a/src/dhcp6-protocol.h +++ b/src/dhcp6-protocol.h @@ -55,6 +55,8 @@ #define OPTION6_RECONF_ACCEPT 20 #define OPTION6_DNS_SERVER 23 #define OPTION6_DOMAIN_SEARCH 24 +#define OPTION6_IA_PD 25 +#define OPTION6_IAPREFIX 26 #define OPTION6_REFRESH_TIME 32 #define OPTION6_REMOTE_ID 37 #define OPTION6_SUBSCRIBER_ID 38 diff --git a/src/dhcp6.c b/src/dhcp6.c index ae1f5c1..c061879 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -135,9 +135,8 @@ void dhcp6_packet(time_t now) if (!indextoname(daemon->dhcp6fd, if_index, ifr.ifr_name)) return; - if ((port = relay_reply6(&from, sz, ifr.ifr_name)) != 0) + if (relay_reply6(&from, sz, ifr.ifr_name)) { - from.sin6_port = htons(port); while (retry_send(sendto(daemon->dhcp6fd, daemon->outpacket.iov_base, save_counter(-1), 0, (struct sockaddr *)&from, sizeof(from)))); diff --git a/src/dnsmasq.c b/src/dnsmasq.c index 2fe9808..422e40f 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -734,7 +734,11 @@ int main (int argc, char **argv) /* if we are to run scripts, we need to fork a helper before dropping root. */ daemon->helperfd = -1; #ifdef HAVE_SCRIPT - if ((daemon->dhcp || daemon->dhcp6 || option_bool(OPT_TFTP) || option_bool(OPT_SCRIPT_ARP)) && + if ((daemon->dhcp || + daemon->dhcp6 || + daemon->relay6 || + option_bool(OPT_TFTP) || + option_bool(OPT_SCRIPT_ARP)) && (daemon->lease_change_command || daemon->luascript)) daemon->helperfd = create_helper(pipewrite, err_pipe[1], script_uid, script_gid, max_fd); #endif @@ -1139,6 +1143,10 @@ int main (int argc, char **argv) while (helper_buf_empty() && do_tftp_script_run()); # endif +# ifdef HAVE_DHCP6 + while (helper_buf_empty() && do_snoop_script_run()); +# endif + if (!helper_buf_empty()) poll_listen(daemon->helperfd, POLLOUT); #else @@ -1153,6 +1161,11 @@ int main (int argc, char **argv) while (do_tftp_script_run()); # endif +# ifdef HAVE_DHCP6 + while (helper_buf_empty() && do_snoop_script_run()); +# endif + + #endif diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 05c1743..ba09bd0 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -778,6 +778,7 @@ struct frec { #define ACTION_TFTP 5 #define ACTION_ARP 6 #define ACTION_ARP_DEL 7 +#define ACTION_RELAY_SNOOP 8 #define LEASE_NEW 1 /* newly created */ #define LEASE_CHANGED 2 /* modified */ @@ -1076,6 +1077,13 @@ struct dhcp_relay { union all_addr local, server; char *interface; /* Allowable interface for replies from server, and dest for IPv6 multicast */ int iface_index; /* working - interface in which requests arrived, for return */ +#ifdef HAVE_SCRIPT + struct snoop_record { + struct in6_addr client, prefix; + int prefix_len; + struct snoop_record *next; + } *snoop_records; +#endif struct dhcp_relay *current, *next; }; @@ -1227,13 +1235,18 @@ extern struct daemon { unsigned char *duid; struct iovec outpacket; int dhcp6fd, icmp6fd; +# ifdef HAVE_SCRIPT + struct snoop_record *free_snoops; +# endif #endif + /* DBus stuff */ /* void * here to avoid depending on dbus headers outside dbus.c */ void *dbus; #ifdef HAVE_DBUS struct watch *watches; #endif + /* UBus stuff */ #ifdef HAVE_UBUS /* void * here to avoid depending on ubus headers outside ubus.c */ @@ -1619,6 +1632,9 @@ void queue_tftp(off_t file_len, char *filename, union mysockaddr *peer); void queue_arp(int action, unsigned char *mac, int maclen, int family, union all_addr *addr); int helper_buf_empty(void); +#ifdef HAVE_DHCP6 +void queue_relay_snoop(struct in6_addr *client, int if_index, struct in6_addr *prefix, int prefix_len); +#endif #endif /* tftp.c */ @@ -1664,7 +1680,8 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *if void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer_address, u32 scope_id, time_t now); -unsigned short relay_reply6( struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface); +int relay_reply6( struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface); +int do_snoop_script_run(void); #endif /* dhcp-common.c */ diff --git a/src/helper.c b/src/helper.c index 455a68c..d7c7dbd 100644 --- a/src/helper.c +++ b/src/helper.c @@ -233,8 +233,13 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) is6 = (data.flags != AF_INET); data.action = ACTION_ARP; } - else - continue; + else if (data.action == ACTION_RELAY_SNOOP) + { + is6 = 1; + action_str = "relay-snoop"; + } + else + continue; /* stringify MAC into dhcp_buff */ p = daemon->dhcp_buff; @@ -286,7 +291,7 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) char *dot; hostname = (char *)buf; hostname[data.hostname_len - 1] = 0; - if (data.action != ACTION_TFTP) + if (data.action != ACTION_TFTP && data.action != ACTION_RELAY_SNOOP) { if (!legal_hostname(hostname)) hostname = NULL; @@ -332,6 +337,24 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) lua_call(lua, 2, 0); /* pass 2 values, expect 0 */ } } + else if (data.action == ACTION_RELAY_SNOOP) + { + lua_getglobal(lua, "snoop"); + if (lua_type(lua, -1) != LUA_TFUNCTION) + lua_pop(lua, 1); /* tftp function optional */ + else + { + lua_pushstring(lua, action_str); /* arg1 - action */ + lua_newtable(lua); /* arg2 - data table */ + lua_pushstring(lua, daemon->addrbuff); + lua_setfield(lua, -2, "client_address"); + lua_pushstring(lua, hostname); + lua_setfield(lua, -2, "prefix"); + lua_pushstring(lua, data.interface); + lua_setfield(lua, -2, "client_interface"); + lua_call(lua, 2, 0); /* pass 2 values, expect 0 */ + } + } else if (data.action == ACTION_ARP) { lua_getglobal(lua, "arp"); @@ -553,7 +576,7 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) close(pipeout[1]); } - if (data.action != ACTION_TFTP && data.action != ACTION_ARP) + if (data.action != ACTION_TFTP && data.action != ACTION_ARP && data.action != ACTION_RELAY_SNOOP) { #ifdef HAVE_DHCP6 my_setenv("DNSMASQ_IAID", is6 ? daemon->dhcp_buff3 : NULL, &err); @@ -640,6 +663,9 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) fcntl(event_fd, F_SETFD, i | FD_CLOEXEC); close(pipefd[0]); + if (data.action == ACTION_RELAY_SNOOP) + strcpy(daemon->packet, data.interface); + p = strrchr(daemon->lease_change_command, '/'); if (err == 0) { @@ -810,6 +836,29 @@ void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t n bytes_in_buf = p - (unsigned char *)buf; } +#ifdef HAVE_DHCP6 +void queue_relay_snoop(struct in6_addr *client, int if_index, struct in6_addr *prefix, int prefix_len) +{ + /* no script */ + if (daemon->helperfd == -1) + return; + + inet_ntop(AF_INET6, prefix, daemon->addrbuff, ADDRSTRLEN); + + /* 5 for /nnn and zero on the end of the prefix. */ + buff_alloc(sizeof(struct script_data) + ADDRSTRLEN + 5); + memset(buf, 0, sizeof(struct script_data)); + + buf->action = ACTION_RELAY_SNOOP; + buf->addr6 = *client; + buf->hostname_len = sprintf((char *)(buf+1), "%s/%u", daemon->addrbuff, prefix_len) + 1; + + indextoname(daemon->dhcp6fd, if_index, buf->interface); + + bytes_in_buf = sizeof(struct script_data) + buf->hostname_len; +} +#endif + #ifdef HAVE_TFTP /* This nastily re-uses DHCP-fields for TFTP stuff */ void queue_tftp(off_t file_len, char *filename, union mysockaddr *peer) diff --git a/src/rfc3315.c b/src/rfc3315.c index f54fb78..fb387a4 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -2194,7 +2194,7 @@ void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, } } -unsigned short relay_reply6(struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface) +int relay_reply6(struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface) { struct dhcp_relay *relay; struct in6_addr link; @@ -2226,10 +2226,75 @@ unsigned short relay_reply6(struct sockaddr_in6 *peer, ssize_t sz, char *arrival put_opt6(opt6_ptr(opt, 0), opt6_len(opt)); memcpy(&peer->sin6_addr, &inbuff[18], IN6ADDRSZ); peer->sin6_scope_id = relay->iface_index; - return encap_type == DHCP6RELAYREPL ? DHCPV6_SERVER_PORT : DHCPV6_CLIENT_PORT; + + if (encap_type == DHCP6RELAYREPL) + { + peer->sin6_port = ntohs(DHCPV6_SERVER_PORT); + return 1; + } + + peer->sin6_port = ntohs(DHCPV6_CLIENT_PORT); + +#ifdef HAVE_SCRIPT + if (daemon->lease_change_command && encap_type == DHCP6REPLY) + { + /* decapsulate relayed message */ + opts = opt6_ptr(opt, 4); + end = opt6_ptr(opt, opt6_len(opt)); + + for (opt = opts; opt; opt = opt6_next(opt, end)) + if (opt6_type(opt) == OPTION6_IA_PD && opt6_len(opt) > 12) + { + void *ia_opts = opt6_ptr(opt, 12); + void *ia_end = opt6_ptr(opt, opt6_len(opt)); + void *ia_opt; + + for (ia_opt = ia_opts; ia_opt; ia_opt = opt6_next(ia_opt, ia_end)) + /* valid lifetime must not be zero. */ + if (opt6_type(ia_opt) == OPTION6_IAPREFIX && opt6_len(ia_opt) >= 25 && opt6_uint(ia_opt, 4, 4) != 0) + { + if (daemon->free_snoops || + (daemon->free_snoops = whine_malloc(sizeof(struct snoop_record)))) + { + struct snoop_record *snoop = daemon->free_snoops; + + daemon->free_snoops = snoop->next; + snoop->client = peer->sin6_addr; + snoop->prefix_len = opt6_uint(ia_opt, 8, 1); + memcpy(&snoop->prefix, opt6_ptr(ia_opt, 9), IN6ADDRSZ); + snoop->next = relay->snoop_records; + relay->snoop_records = snoop; + } + } + } + } +#endif + return 1; } + } + + return 0; +} +int do_snoop_script_run(void) +{ +#ifdef HAVE_SCRIPT + struct dhcp_relay *relay; + struct snoop_record *snoop; + + for (relay = daemon->relay6; relay; relay = relay->next) + if ((snoop = relay->snoop_records)) + { + relay->snoop_records = snoop->next; + snoop->next = daemon->free_snoops; + daemon->free_snoops = snoop; + + queue_relay_snoop(&snoop->client, relay->iface_index, &snoop->prefix, snoop->prefix_len); + return 1; + } +#endif + return 0; } -- cgit v1.2.1