[BACK]Return to packet.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / ospfd

File: [local] / src / usr.sbin / ospfd / packet.c (download)

Revision 1.22, Fri Nov 17 08:55:31 2006 UTC (17 years, 6 months ago) by claudio
Branch: MAIN
CVS Tags: OPENBSD_4_3_BASE, OPENBSD_4_3, OPENBSD_4_2_BASE, OPENBSD_4_2, OPENBSD_4_1_BASE, OPENBSD_4_1
Changes since 1.21: +33 -6 lines

Support for multiple networks on one interface. Until now only the main
address of a interface could be used. Now it is possible to specify a
interface more than once if multiple networks are configured. An alternative
network can be specified via e.g. interface em0:10.0.5.1. The old interface
syntax without the IP still works and uses the main/first configured IP
address.
ospfd now needs to include the IP header on outgoing messages as it is not
possible to specifiy the source address in sendto(2). Additionally all
multicast joins and leaves have to be tracked.
OK norby@

/*	$OpenBSD: packet.c,v 1.22 2006/11/17 08:55:31 claudio Exp $ */

/*
 * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <net/if_dl.h>

#include <errno.h>
#include <event.h>
#include <stdlib.h>
#include <string.h>

#include "ospfd.h"
#include "ospf.h"
#include "log.h"
#include "ospfe.h"

int		 ip_hdr_sanity_check(const struct ip *, u_int16_t);
int		 ospf_hdr_sanity_check(const struct ip *,
		    struct ospf_hdr *, u_int16_t, const struct iface *);
struct iface	*find_iface(struct ospfd_conf *, unsigned int, struct in_addr);

int
gen_ospf_hdr(struct buf *buf, struct iface *iface, u_int8_t type)
{
	struct ospf_hdr	ospf_hdr;

	bzero(&ospf_hdr, sizeof(ospf_hdr));
	ospf_hdr.version = OSPF_VERSION;
	ospf_hdr.type = type;
	ospf_hdr.rtr_id = ospfe_router_id();
	if (iface->type != IF_TYPE_VIRTUALLINK)
		ospf_hdr.area_id = iface->area->id.s_addr;
	ospf_hdr.auth_type = htons(iface->auth_type);

	return (buf_add(buf, &ospf_hdr, sizeof(ospf_hdr)));
}

/* send and receive packets */
int
send_packet(struct iface *iface, void *pkt, size_t len, struct sockaddr_in *dst)
{
	struct msghdr		 msg;
	struct iovec		 iov[2];
	struct ip		 ip_hdr;

	/* setup IP hdr */
	bzero(&ip_hdr, sizeof(ip_hdr));
	ip_hdr.ip_v = IPVERSION;
	ip_hdr.ip_hl = sizeof(ip_hdr) >> 2;
	ip_hdr.ip_tos = IPTOS_PREC_INTERNETCONTROL;
	ip_hdr.ip_len = htons(len + sizeof(ip_hdr));
	ip_hdr.ip_id = 0;  /* 0 means kernel set appropriate value */
	ip_hdr.ip_off = 0;
	ip_hdr.ip_ttl = iface->type != IF_TYPE_VIRTUALLINK ?
	    IP_DEFAULT_MULTICAST_TTL : MAXTTL;
	ip_hdr.ip_p = IPPROTO_OSPF;
	ip_hdr.ip_sum = 0;
	ip_hdr.ip_src = iface->addr;
	ip_hdr.ip_dst = dst->sin_addr;

	/* setup buffer */
	bzero(&msg, sizeof(msg));
	iov[0].iov_base = &ip_hdr;
	iov[0].iov_len = sizeof(ip_hdr);
	iov[1].iov_base = pkt;
	iov[1].iov_len = len;
	msg.msg_name = dst;
	msg.msg_namelen = sizeof(*dst);
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;

	/* set outgoing interface for multicast traffic */
	if (IN_MULTICAST(ntohl(dst->sin_addr.s_addr)))
		if (if_set_mcast(iface) == -1) {
			log_warn("send_packet: error setting multicast "
			    "interface, %s", iface->name);
			return (-1);
		}

	if (sendmsg(iface->fd, &msg, 0) == -1) {
		log_warn("send_packet: error sending packet on interface %s",
		    iface->name);
		return (-1);
	}

	return (0);
}

void
recv_packet(int fd, short event, void *bula)
{
	char			 cbuf[CMSG_SPACE(sizeof(struct sockaddr_dl))];
	struct msghdr		 msg;
	struct iovec		 iov;
	struct ip		 ip_hdr;
	struct in_addr		 addr;
	struct ospfd_conf	*xconf = bula;
	struct ospf_hdr		*ospf_hdr;
	struct iface		*iface;
	struct nbr		*nbr = NULL;
	char			*buf;
	struct cmsghdr		*cmsg;
	ssize_t			 r;
	u_int16_t		 len;
	int			 l;
	unsigned int		 ifindex = 0;

	if (event != EV_READ)
		return;

	/* setup buffer */
	bzero(&msg, sizeof(msg));
	iov.iov_base = buf = pkt_ptr;
	iov.iov_len = READ_BUF_SIZE;
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_control = cbuf;
	msg.msg_controllen = sizeof(cbuf);

	if ((r = recvmsg(fd, &msg, 0)) == -1) {
		if (errno != EAGAIN && errno != EINTR)
			log_debug("recv_packet: read error: %s",
			    strerror(errno));
		return;
	}
	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
	    cmsg = CMSG_NXTHDR(&msg, cmsg)) {
		if (cmsg->cmsg_level == IPPROTO_IP &&
		    cmsg->cmsg_type == IP_RECVIF) {
			ifindex = ((struct sockaddr_dl *)
			    CMSG_DATA(cmsg))->sdl_index;
			break;
		}
	}

	len = (u_int16_t)r;

	/* IP header sanity checks */
	if (len < sizeof(ip_hdr)) {
		log_warnx("recv_packet: bad packet size");
		return;
	}
	memcpy(&ip_hdr, buf, sizeof(ip_hdr));
	if ((l = ip_hdr_sanity_check(&ip_hdr, len)) == -1)
		return;
	buf += l;
	len -= l;

	/* find a matching interface */
	if ((iface = find_iface(xconf, ifindex, ip_hdr.ip_src)) == NULL) {
		/* XXX add a counter here */
		return;
	}

	/*
	 * Packet needs to be sent to AllSPFRouters or AllDRouters
	 * or to the address of the interface itself.
	 * AllDRouters is only valid for DR and BDR but this is checked later.
	 */
	inet_aton(AllSPFRouters, &addr);
	if (ip_hdr.ip_dst.s_addr != addr.s_addr) {
		inet_aton(AllDRouters, &addr);
		if (ip_hdr.ip_dst.s_addr != addr.s_addr) {
			if (ip_hdr.ip_dst.s_addr != iface->addr.s_addr) {
				log_debug("recv_packet: packet sent to wrong "
				    "address %s, interface %s",
				    inet_ntoa(ip_hdr.ip_dst), iface->name);
				return;
			}
		}
	}

	/* OSPF header sanity checks */
	if (len < sizeof(*ospf_hdr)) {
		log_debug("recv_packet: bad packet size");
		return;
	}
	ospf_hdr = (struct ospf_hdr *)buf;

	if ((l = ospf_hdr_sanity_check(&ip_hdr, ospf_hdr, len, iface)) == -1)
		return;

	nbr = nbr_find_id(iface, ospf_hdr->rtr_id);
	if (ospf_hdr->type != PACKET_TYPE_HELLO && nbr == NULL) {
		log_debug("recv_packet: unknown neighbor ID");
		return;
	}

	if (auth_validate(buf, len, iface, nbr)) {
		log_warnx("recv_packet: authentication error, "
		    "interface %s", iface->name);
		return;
	}

	buf += sizeof(*ospf_hdr);
	len = l - sizeof(*ospf_hdr);

	/* switch OSPF packet type */
	switch (ospf_hdr->type) {
	case PACKET_TYPE_HELLO:
		inet_aton(AllDRouters, &addr);
		if (ip_hdr.ip_dst.s_addr == addr.s_addr) {
			log_debug("recv_packet: invalid destination IP "
			     "address");
			break;
		}

		recv_hello(iface, ip_hdr.ip_src, ospf_hdr->rtr_id, buf, len);
		break;
	case PACKET_TYPE_DD:
		recv_db_description(nbr, buf, len);
		break;
	case PACKET_TYPE_LS_REQUEST:
		recv_ls_req(nbr, buf, len);
		break;
	case PACKET_TYPE_LS_UPDATE:
		recv_ls_update(nbr, buf, len);
		break;
	case PACKET_TYPE_LS_ACK:
		recv_ls_ack(nbr, buf, len);
		break;
	default:
		log_debug("recv_packet: unknown OSPF packet type, interface %s",
		    iface->name);
	}
}

int
ip_hdr_sanity_check(const struct ip *ip_hdr, u_int16_t len)
{
	if (ntohs(ip_hdr->ip_len) != len) {
		log_debug("recv_packet: invalid IP packet length %u",
		    ntohs(ip_hdr->ip_len));
		return (-1);
	}

	if (ip_hdr->ip_p != IPPROTO_OSPF)
		/* this is enforced by the socket itself */
		fatalx("recv_packet: invalid IP proto");

	return (ip_hdr->ip_hl << 2);
}

int
ospf_hdr_sanity_check(const struct ip *ip_hdr, struct ospf_hdr *ospf_hdr,
    u_int16_t len, const struct iface *iface)
{
	struct in_addr		 addr;

	if (ospf_hdr->version != OSPF_VERSION) {
		log_debug("recv_packet: invalid OSPF version %d",
		    ospf_hdr->version);
		return (-1);
	}

	if (ntohs(ospf_hdr->len) > len ||
	    len <= sizeof(struct ospf_hdr)) {
		log_debug("recv_packet: invalid OSPF packet length %d",
		    ntohs(ospf_hdr->len));
		return (-1);
	}

	if (iface->type != IF_TYPE_VIRTUALLINK) {
		if (ospf_hdr->area_id != iface->area->id.s_addr) {
			addr.s_addr = ospf_hdr->area_id;
			log_debug("recv_packet: invalid area ID %s, "
			    "interface %s", inet_ntoa(addr), iface->name);
			return (-1);
		}
	} else {
		if (ospf_hdr->area_id != 0) {
			addr.s_addr = ospf_hdr->area_id;
			log_debug("recv_packet: invalid area ID %s, "
			    "interface %s", inet_ntoa(addr), iface->name);
			return (-1);
		}
	}

	if (iface->type == IF_TYPE_BROADCAST || iface->type == IF_TYPE_NBMA) {
		if (inet_aton(AllDRouters, &addr) == 0)
			fatalx("recv_packet: inet_aton");
		if (ip_hdr->ip_dst.s_addr == addr.s_addr &&
		    (iface->state & IF_STA_DRORBDR) == 0) {
			log_debug("recv_packet: invalid destination IP in "
			    "state %s, interface %s",
			    if_state_name(iface->state), iface->name);
			return (-1);
		}
	}

	return (ntohs(ospf_hdr->len));
}

struct iface *
find_iface(struct ospfd_conf *xconf, unsigned int ifindex, struct in_addr src)
{
	struct area	*area = NULL;
	struct iface	*iface = NULL;

	/* returned interface needs to be active */
	LIST_FOREACH(area, &xconf->area_list, entry) {
		LIST_FOREACH(iface, &area->iface_list, entry) {
			switch (iface->type) {
			case IF_TYPE_VIRTUALLINK:
				if ((src.s_addr == iface->dst.s_addr) &&
				    !iface->passive)
					return (iface);
				break;
			case IF_TYPE_POINTOPOINT:
				if (ifindex == iface->ifindex &&
				    iface->dst.s_addr == src.s_addr &&
				    !iface->passive)
					return (iface);
				break;
			default:
				if (ifindex == iface->ifindex &&
				    (iface->addr.s_addr & iface->mask.s_addr) ==
				    (src.s_addr & iface->mask.s_addr) &&
				    !iface->passive)
					return (iface);
				break;
			}
		}
	}

	return (NULL);
}