Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 10 Apr 2004 09:44:25 +0100
From:      Bruce M Simpson <bms@spc.org>
To:        Vlad GALU <dudu@diaspar.rdsnet.ro>
Cc:        hackers@freebsd.org
Subject:   Re: passing messages to routing sockets
Message-ID:  <20040410084425.GT710@empiric.dek.spc.org>
In-Reply-To: <20040409195541.I31321@qvnfcne.eqfarg.eb>
References:  <20040409195541.I31321@qvnfcne.eqfarg.eb>

next in thread | previous in thread | raw e-mail | index | archive | help

--7vAdt9JsdkkzRPKN
Content-Type: multipart/mixed; boundary="OOq1TgGhe8eTwFBO"
Content-Disposition: inline


--OOq1TgGhe8eTwFBO
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

On Fri, Apr 09, 2004 at 08:02:30PM +0300, Vlad GALU wrote:
> 	I understand that in order to add a new route to the routing
> table, one must fill a buffer with an ifa_msghdr header, followed by three
> socked adddress structures, representing the destination net/ip, the
> netmask and the gateway.
[routing table suckiness snipped]
> 	Could anyone provide a quick/small example on how to fill this
> member ? I don't really understand what I should write into it.

It's a sucky one and I had to stare at src/sbin/route/* for a while til
the penny dropped. The netmasks in particular are packed in in a format
which isn't immediately obvious.

Have a look at the attached code, in particular, reply_rtmsg_resolve().

Also have a look at Section 19.11 of TCP/IP Illustrated Volume 2, and
Section 18.9 of UNIX Network Programming 3e Vol 1.

The API sucks, and it's something we'd like to change in future.

Regards,
BMS

--OOq1TgGhe8eTwFBO
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="rtmhack.c"

/*      $FreeBSD$ */

/*
 * This is a hack to demonstrate the concept of hooking for the
 * RTM_RESOLVE message being sent from the FreeBSD routing code,
 * as a means of looking up routes on demand using a routing protocol
 * such as AODV.
 * This code will probably be vastly cleaned up and tested more thoroughly
 * before being used as the basis for a user-space BSD AODV implementation.
 */

/*
 * Copyright (c) 2003 Bruce M. Simpson <bms@spc.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *        This product includes software developed by Bruce M. Simpson.
 * 4. Neither the name of Bruce M. Simpson nor the names of co-
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Bruce M. Simpson AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL Bruce M. Simpson OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/ioctl.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/signal.h>

#include <net/if.h>
#include <net/if_var.h>
#include <net/if_mib.h>
#include <net/if_types.h>
#include <net/if_dl.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <paths.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
#include <ifaddrs.h>

union sockunion {
	struct	sockaddr sa;
	struct	sockaddr_in sin;
	struct	sockaddr_dl sdl;
	struct	sockaddr_storage ss;
};
typedef union sockunion sockunion_t;

void usage(void);
int add_xresolve_route(char *ifname, struct sockaddr_in *sin, int bits);
int inet_cidr_aton(char *s, struct in_addr *pin, int *bits);
int create_if(char *ifname);
int destroy_if(char *ifname);
int if2sockaddr(char *ifname, struct sockaddr_dl *sdl);
int handle_rtmsg(struct rt_msghdr *rtm, int msglen);
int handle_rtmsg_resolve(struct rt_msghdr *rtm, int msglen);
int reply_rtmsg_resolve(struct sockaddr_in *sin);

/*
 * We check for the existence of ifname.
 */
#if 1
#define _IFNAME "disc1"
#else
#define _IFNAME "lo0"
#endif

int rtsock = -1;
int created = 0;
char *ifname = _IFNAME;

void
sighand_term(int sig)
{
	/*
	 * Destroying an interface is sufficient to delete the routes
	 * pointing to it.
	 */
	if (created)
		destroy_if(ifname);

	exit(EXIT_SUCCESS);
}

void
setup_signals(void)
{
	struct sigaction sa, osa;

	sa.sa_handler = sighand_term;
	sa.sa_flags = 0;
	sigemptyset(&sa.sa_mask);

	sigaction(SIGTERM, &sa, &osa);
	sigaction(SIGINT, &sa, &osa);
	sigaction(SIGQUIT, &sa, &osa);
	sigaction(SIGKILL, &sa, &osa);
}

int
main(int argc, char *argv[])
{
	int n;
	int bits;
	char msg[2048];
	struct sockaddr_in sin;

	if (geteuid() != 0)
		errx(1, "must be root to alter routing table");

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_len = sizeof(sin);

	/* Parse network argument */
	if ((argc != 2)
	    || (inet_cidr_aton(argv[1], &sin.sin_addr, &bits) != 1))
		usage();

	setup_signals();

	/* Open routing socket */
	rtsock = socket(PF_ROUTE, SOCK_RAW, 0);
	if (rtsock == -1)
		err(EX_OSERR, "socket");

	/* Check that the target interface exists; create it if it doesn't. */
	if (if_nametoindex(ifname) == 0) {
		warnx("interface %s does not exist, creating.", ifname);
		create_if(ifname);
		created = 1;
		add_xresolve_route(ifname, &sin, bits);
	}

	/* Routing event loop */
	for (;;) {
		n = read(rtsock, msg, sizeof(msg));
		handle_rtmsg((struct rt_msghdr *)msg, n);
	}

	if (rtsock != -1)
		close(rtsock);

	exit (EXIT_SUCCESS);
}

void
usage(void)
{
	fprintf(stderr, "usage: rtmhack <testnet>\n"
		"<testnet> specifies the test network in CIDR notation\n");
	exit(EXIT_FAILURE);
}

/*
 * Like inet_aton(), but handle an optional CIDR prefix.
 */
int
inet_cidr_aton(char *s, struct in_addr *pin, int *bits)
{
	char *q;

	q = NULL;
	*bits = 32;

	if ((q = strchr(s, '/')) != NULL) {
		*bits = strtoul(q+1, 0, 0);
		*q = '\0';
	}

	return (inet_aton(s, pin));
}

/*
 * create an instance of a named clonable interface.
 * Return 0 if successful, or -1 if an error occurred.
 */
int
create_if(char *ifname)
{
	int s, retval;
	struct ifreq ifr;

	retval = 0;

	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s == -1)
		err(1, "socket");

	memset(&ifr, 0, sizeof(ifr));
	(void) strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
	if (ioctl(s, SIOCIFCREATE, &ifr) < 0) {
		retval = -1;
		warn("SIOCIFCREATE");
	}

	close(s);
	return (retval);
}

/*
 * destroy an instance of a named clonable interface.
 * Return 0 if successful, or -1 if an error occurred.
 */
int
destroy_if(char *ifname)
{
	int s, retval;
	struct ifreq ifr;

	retval = 0;

	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s == -1)
		err(1, "socket");

	memset(&ifr, 0, sizeof(ifr));
	(void) strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
	if (ioctl(s, SIOCIFDESTROY, &ifr) < 0) {
		retval = -1;
		warn("SIOCIFDESTROY");
	}

	close(s);
	return (retval);
}

/*
 * Copy the sockaddr_dl structure corresponding to the named interface
 * into the structure pointed to by sdl.
 * Returns 0 if successful, or -1 if the structure found was not valid.
 */
int
if2sockaddr(char *ifname, struct sockaddr_dl *sdl)
{
	struct ifaddrs *ifap, *ifa;
	struct sockaddr_dl *isdl;

	if (getifaddrs(&ifap))
		err(1, "getifaddrs");

	isdl = NULL;

	for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
		if (ifa->ifa_addr->sa_family != AF_LINK)
			continue;

		if (strcmp(ifname, ifa->ifa_name))
			continue;

		isdl = (struct sockaddr_dl *)ifa->ifa_addr;
	}

	if (isdl)
		memcpy(sdl, isdl, isdl->sdl_len);

	return ((isdl != NULL) ? 0 : -1);
}

/*
 * Given the prefix length of an IPv4 CIDR network address,
 * fill out a sockaddr_in structure accordingly for use with
 * BSD routing code.
 *
 * Return the value of the sin_len member as a hint.
 */
int
inet_makenetmask(int bits, struct sockaddr_in *so_mask)
{
	char *cp;
	unsigned long mask;
	int len;
	const int maxbits = 32;

	memset(so_mask, 0, sizeof(struct sockaddr_in));

	mask = 0xFFFFFFFF << (maxbits - bits);
	so_mask->sin_addr.s_addr = htonl(mask);

	/* count number of bytes in mask containing set bits */
	cp = (char *)(&so_mask->sin_addr + 1);
	while (*--cp == 0 && cp > (char *)&so_mask)
		;
	so_mask->sin_len = len = 1 + cp - (char *)&so_mask;

	return (len);
}

/*
 * Bind an cloning XRESOLVE route, for the given network/host,
 * to a named interface.
 *
 * The packing of the rtm message is all important. The kernel
 * expects it in a certain way. This routine seems to work but
 * 'route monitor' is reporting some junk at the end of the netmask.
 *
 * Return 0 if successful, or -1 if an error occurred.
 */

int
add_xresolve_route(char *ifname, struct sockaddr_in *so_dst, int bits)
{
#define ROUNDUP(a) \
	((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
#define NEXTADDR(cp, l, u) \
	do {						\
		l = (u)->sa.sa_len;			\
		(l) = ROUNDUP(l);			\
		memmove((cp), (u), (l));		\
		(cp) += (l);				\
	} while (0)
	/* */
	struct {
		struct rt_msghdr rtm;
		sockunion_t addrs[RTAX_MAX];
	} r;
	sockunion_t so_iface;
	sockunion_t so_mask;
	int rlen, len, masklen;
	char *cp;

	cp = (char *)&r.addrs[0];
	masklen = rlen = len = 0;
	memset(&r, 0, sizeof(r));
	memset(&so_iface, 0, sizeof(so_iface));
	memset(&so_mask, 0, sizeof(so_mask));

	r.rtm.rtm_version = RTM_VERSION;
	r.rtm.rtm_type = RTM_ADD;
	r.rtm.rtm_pid = getpid();
	r.rtm.rtm_seq = 0;
	r.rtm.rtm_flags = RTF_XRESOLVE | RTF_CLONING | RTF_UP;
	r.rtm.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;

	if2sockaddr(ifname, &so_iface.sdl);
	masklen = inet_makenetmask(bits, &so_mask.sin);

	NEXTADDR(cp, len, (sockunion_t *)so_dst);
	NEXTADDR(cp, len, &so_iface);
	NEXTADDR(cp, len, &so_mask);

	r.rtm.rtm_msglen = len = cp - (char *)&r;
	rlen = write(rtsock, &r, len);
	if (rlen < 0)
		warn("write");

	return ((rlen > 0) ? 0 : -1);
#undef NEXTADDR
#undef ROUNDUP
}

/*
 * routing socket message dispatcher
 */
int
handle_rtmsg(struct rt_msghdr *rtm, int msglen)
{
	if (rtm->rtm_version != RTM_VERSION) {
		(void) printf("bad routing message version %d\n",
			rtm->rtm_version);
		return (-1);
	}

	switch (rtm->rtm_type) {
	case RTM_RESOLVE:
		(void) printf("rtm_type %d: RTM_RESOLVE\n", rtm->rtm_type);
		handle_rtmsg_resolve(rtm, msglen);
		break;
	default:
		(void) printf("rtm_type %d: ignored\n", rtm->rtm_type);
	}

	return (0);
}

/*
 * Dispatch routine for RTM_RESOLVE routing messages.
 * Return 0 if successful; otherwise, return -1 if an error occurred.
 */
int
handle_rtmsg_resolve(struct rt_msghdr *rtm, int msglen)
{
	struct sockaddr_in *sin;
	struct sockaddr *sa;
	void *sp;

	/*
	 * ignore messages from ourselves
	 */
	if (rtm->rtm_pid == getpid()) {
		printf("heard own message, ignoring\n");
		return (0);
	}

	printf("rtm_index: %04x rtm_addrs: %08x\n",
		rtm->rtm_index, rtm->rtm_addrs);

	/*
	 * The message must contain the address for which a route is
	 * being requested, otherwise it is invalid.
	 */
	if (!(rtm->rtm_addrs & RTA_DST)) {
		warnx("RTM_RESOLVE message does not contain destination");
		return (-1);
	}

	sa = sp = (rtm + 1);
	if (sa->sa_family != AF_INET) {
		warnx("RTM_RESOLVE contains non-AF_INET destination %d",
			sa->sa_family);
		return (-1);
	}
	sin = (struct sockaddr_in *)sa;
	printf("route requested for %s\n", inet_ntoa(sin->sin_addr));

	/*
	 * XXX: Should check if the requested destination is within the
	 * network prefix specified on the command line.
	 */
	reply_rtmsg_resolve(sin);
	printf("route resolved for %s\n", inet_ntoa(sin->sin_addr));

	return (0);
}

/*
 * Modify a given route in response to an RTM_RESOLVE message from the kernel.
 * Return 0 if successful; otherwise, return -1.
 */
int
reply_rtmsg_resolve(struct sockaddr_in *sin)
{
	struct {
		struct rt_msghdr rtm;
		struct sockaddr addrs[RTAX_MAX];
	} r;
	struct sockaddr_dl sdl;
	int len;

	memset(&r, 0, sizeof(r));
	r.rtm.rtm_version = RTM_VERSION;
	r.rtm.rtm_type = RTM_CHANGE;
	r.rtm.rtm_pid = getpid();
	r.rtm.rtm_seq = 0;

	if2sockaddr("lo0", &sdl);
	memcpy(&r.addrs[RTAX_DST], sin, sin->sin_len);
	memcpy(&r.addrs[RTAX_GATEWAY], &sdl, sdl.sdl_len);
	memset(&r.addrs[RTAX_IFP], 0, sizeof(r.addrs[RTAX_IFP]));
	memset(&r.addrs[RTAX_IFA], 0, sizeof(r.addrs[RTAX_IFA]));
	r.rtm.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_IFP | RTA_IFA;

	r.rtm.rtm_flags = RTF_DONE;
	r.rtm.rtm_msglen = sizeof(r);

	len = write(rtsock, &r, r.rtm.rtm_msglen);
	if (len != r.rtm.rtm_msglen)
		warn("write");

	return ((len > 0) ? 0 : -1);
}

--OOq1TgGhe8eTwFBO--

--7vAdt9JsdkkzRPKN
Content-Type: application/pgp-signature
Content-Disposition: inline

-----BEGIN PGP SIGNATURE-----
Comment: ''

iD8DBQFAd7PpueUpAYYNtTsRAvYjAJ9Yr+IRLqOTkpkTyQl4CY047WRY+gCgnUiO
kMMDaCrFJwyKefYUOObxsjM=
=np2+
-----END PGP SIGNATURE-----

--7vAdt9JsdkkzRPKN--



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20040410084425.GT710>