Proof-of-concept patch to add client hardware address as option in relay agent

Petrin, Benjamin b.petrin at WPI.EDU
Tue Sep 8 20:04:30 UTC 2009


Here's the completed proof-of-concept patch to the relay agent that adds the hardware address of the client onto the packet going up to the server. This should be useful for anyone operating a network that needs to regulate who can and cannot get an address based on MAC address.

Notes
- You need libnl installed on the system to compile.
- After patching, you must manually edit the relay/Makefile so that the "LIBS" line includes "-lnl" without quotes, I haven't yet learned how to properly edit the auto config files appropriately.
- The relay agent will normally be unable to add the hardware address on the first packet since the clients entry in the neighbor table is not yet present. This packet will be dropped but subsequent packets should go through. This seemed like a better idea than blocking while waiting for the neighbor table to be updated. In my tests this is fine because the client continually sends requests out.

To do
- The option should be stored in a different option space (I'm using a high, unassigned number in the dhcpv6_universe at the moment). I need some assistance on storing it somewhere else
- The handle opened in lookup_lladr should be cleaned up when the relay shuts down

Let me know if you run into any problems, have any suggestions, or can offer some help in improving this.

Thanks,
Benjamin Petrin

The patch follows:

diff -ur dhcp-4.1.1b2/common/tables.c dhcp-4.1.1b2.withpatch/common/tables.c
--- dhcp-4.1.1b2/common/tables.c	2009-07-24 17:12:27.000000000 -0400
+++ dhcp-4.1.1b2.withpatch/common/tables.c	2009-09-08 15:50:46.000000000 -0400
@@ -454,6 +454,9 @@
 	{ "lq-relay-data", "6X",		&dhcpv6_universe, 47, 1 },
 	{ "lq-client-link", "6A",		&dhcpv6_universe, 48, 1 },
 
+	/* BP - temporarily use code 254 for proof-of-concept */
+	{ "relay-observed-mac", "X",            &dhcpv6_universe, 254, 1 },
+
 	{ NULL, NULL, NULL, 0, 0 }
 };
 
diff -ur dhcp-4.1.1b2/includes/dhcp6.h dhcp-4.1.1b2.withpatch/includes/dhcp6.h
--- dhcp-4.1.1b2/includes/dhcp6.h	2009-07-24 17:22:56.000000000 -0400
+++ dhcp-4.1.1b2.withpatch/includes/dhcp6.h	2009-09-08 15:50:46.000000000 -0400
@@ -119,6 +119,8 @@
 #define DUID_EN		2
 #define DUID_LL		3
 
+#define ISC6_RELAY_OBSERVED_MAC 254
+
 /* Offsets into IA_*'s where Option spaces commence.  */
 #define IA_NA_OFFSET 12 /* IAID, T1, T2, all 4 octets each */
 #define IA_TA_OFFSET  4 /* IAID only, 4 octets */

diff -ur dhcp-4.1.1b2/relay/dhcrelay.c dhcp-4.1.1b2.withpatch/relay/dhcrelay.c
--- dhcp-4.1.1b2/relay/dhcrelay.c	2009-07-23 15:02:10.000000000 -0400
+++ dhcp-4.1.1b2.withpatch/relay/dhcrelay.c	2009-09-08 15:51:28.000000000 -0400
@@ -35,6 +35,8 @@
 #include "dhcpd.h"
 #include <syslog.h>
 #include <sys/time.h>
+#include <netlink/route/neighbour.h>
+#include <netlink/route/link.h>
 
 TIME default_lease_time = 43200; /* 12 hours... */
 TIME max_lease_time = 86400; /* 24 hours... */
@@ -1296,9 +1298,120 @@
 static const int required_forw_opts[] = {
 	D6O_INTERFACE_ID,
 	D6O_RELAY_MSG,
+	ISC6_RELAY_OBSERVED_MAC,
 	0
 };
 
+int lookup_lladdr(const char *addr, const char *interface_name, char *buff, int size)
+{
+	/*
+		Our goal is to lookup the address "addr" on interface
+		"interface_name" and place the corresponding hardware
+		address into "buff" whose size should be 6
+	*/
+
+        struct nl_addr *dst_addr = nl_addr_parse(addr, AF_INET6);
+
+	/*
+	we want to keep a handle around
+
+	TODO - this should be made global and cleanup when
+	shutting down by calling both nl_close(handle)
+	and nl_handle_destroy(handle)
+	*/
+	static struct nl_handle *handle = NULL;
+
+	if (handle == NULL) {
+	        if((handle = nl_handle_alloc()) == NULL) {
+        	        log_error("lookup_lladdr - Error getting handle\n");
+	                return 1;
+	        }
+
+
+		int ret;
+	        if ((ret = nl_connect(handle, NETLINK_ROUTE))) {
+	                log_error("lookup_lladdr - Error connecting handle: %d", ret);
+	                return 1;
+	        }
+	}
+
+        /*
+	The first step is to retrieve a list of all available interfaces within
+        the kernel and put them into a cache.
+	*/
+        struct nl_cache *cache;
+
+        if((cache = rtnl_link_alloc_cache(handle)) == NULL) {
+                log_error("Error getting link cache");
+                return 1;
+        }
+
+        int ifindex;
+	if ((ifindex = rtnl_link_name2i(cache, interface_name)) == RTNL_LINK_NOT_FOUND) {
+		log_error("lookup_lladdr - can't get index of interface '%s'", interface_name);
+		nl_cache_destroy_and_free(cache);
+		return 1;
+	}
+
+	/* Now that we are done with the cache, we need to destroy and free it */
+	nl_cache_destroy_and_free(cache);
+
+        /*
+	Retrieve a list of all available neighbors within
+        the kernel and put them into a cache. I will recycle
+	the cache variable
+	*/
+        cache = NULL;
+        if((cache = rtnl_neigh_alloc_cache(handle)) == NULL) {
+                log_error("lookup_lladdr - Error getting neigh cache");
+                return 1;
+        }
+
+        /*
+	Neighbours can then be looked up by the interface and destination
+        address:
+	*/
+        struct rtnl_neigh *neigh;
+        if((neigh = rtnl_neigh_get(cache, ifindex, dst_addr)) == NULL) {
+                log_error("lookup_lladdr - Unable to look up neigh");
+                return 1;
+        }
+        
+        /*
+	now extract the hardware address from the neigh
+	*/
+        struct nl_addr *nladdr;
+        if((nladdr = rtnl_neigh_get_lladdr(neigh)) == NULL) {
+		/*
+		this is informational since it will happen on the first packet 
+		from a client. This is because we do not yet know the hardware
+		address from our bogus packet that was sent out
+		*/
+                log_info("lookup_lladdr - Unable to get hardware address from neigh");
+                return 1;
+        }
+
+	int addr_length = nl_addr_get_len(nladdr);
+
+	if(addr_length > size)
+	{
+		/* we don't have enough room! */
+		log_error("lookup_lladdr - supplied buffer not large enough");
+	}
+
+	/* copy over the desired info to the user's buffer */
+	memcpy(buff, nl_addr_get_binary_addr(nladdr), addr_length);
+
+        /* 
+	After successful usage, the object must be given back to the cache 
+	Then we have to free the cache we allocated
+	*/
+        rtnl_neigh_put(neigh);
+	nl_cache_destroy_and_free(cache);
+
+        return 0;
+}
+
 /*
  * Process a packet upwards, i.e., from client to server.
  */
@@ -1405,6 +1518,45 @@
 		}
 	}
 
+	/* convert the cleint address to a hardware address and store in lladdr*/
+	char lladdr[6];
+	memset(lladdr, 0, 6);
+
+	/* before we lookup, we need to send a packet to force client into neighbor table */
+
+	struct sockaddr_in6 to;
+	memset(&to, 0, sizeof(to));
+	to.sin6_family = AF_INET6;
+#ifdef HAVE_SA_LEN
+	to.sin6_len = sizeof(to);
+#endif
+	to.sin6_port = htons(9);
+	memcpy(to.sin6_addr.s6_addr, packet->client_addr.iabuf, 16);
+	send_packet6(packet->interface, (unsigned char *) "WPI NetOps", (size_t) 11, &to);
+
+	/* send_packet will return an error, we don't care */
+
+	/* now we can lookup */
+	if(lookup_lladdr(piaddr(packet->client_addr), packet->interface->name, lladdr, 6)) {
+		log_error("lookup_lladdr returned an error, packet won't be forwarded");
+		return;
+	}
+
+	log_debug("lladdr is (dec) %d %d %d %d %d %d",lladdr[0], lladdr[1], lladdr[2], lladdr[3], lladdr[4], lladdr[5]);	
+
+	/* 
+	save the hardware address away as an option
+	TODO - put in different option space
+	*/
+	if (!save_option_buffer(&dhcpv6_universe, opts,
+				NULL, (unsigned char *) lladdr,
+				6,
+				ISC6_RELAY_OBSERVED_MAC, 0)) {
+		log_error("Can't save link layer address to packet going up.");
+		option_state_dereference(&opts, MDL);
+	 	return;
+	}
+
 	/* Add the relay-msg carrying the packet. */
 	if (!save_option_buffer(&dhcpv6_universe, opts,
 				NULL, (unsigned char *) packet->raw,



More information about the dhcp-workers mailing list