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

David W. Hankins dhankins at isc.org
Thu Sep 10 18:08:45 UTC 2009


Benjamin, this looks excellent at first glance.

I can rework the option the values are stuffed into and send you back a
patch.  I can't promise when I'll have time for that tho. :(

The basic idea is to use ISC's "vendor information option", called VSIO
in DHCPv6, so that we can use option code tags that ISC allocates that
are unique using ISC's enterprise-ID.

So it is probably better I do that anyway. :)

The library dependency may be the largest barrier to getting the patch
comitted, but I think at worst that means I will make it a ./configure
time switch.

I've opened a ticket at dhcp-suggest at isc.org to remind me to look at
this, so don't be surprised if you get mail from our ticketing system.

On Tue, Sep 08, 2009 at 04:04:30PM -0400, Petrin, Benjamin wrote:
> 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,
> _______________________________________________
> dhcp-workers mailing list
> dhcp-workers at lists.isc.org
> https://lists.isc.org/mailman/listinfo/dhcp-workers

-- 
David W. Hankins	"If you don't do it right the first time,
Software Engineer		     you'll just have to do it again."
Internet Systems Consortium, Inc.		-- Jack T. Hankins
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 197 bytes
Desc: not available
URL: <https://lists.isc.org/pipermail/dhcp-workers/attachments/20090910/28060949/attachment.bin>


More information about the dhcp-workers mailing list