diff options
author | 2015-10-02 04:26:47 +0000 | |
---|---|---|
committer | 2015-10-02 04:26:47 +0000 | |
commit | 43509a120f2aeb0a26f1a8ca43fb60460ab80687 (patch) | |
tree | 4f83eebf70586b5a95973b8457bf1121d5d81d62 | |
parent | mention these pathname calls are checked in namei (diff) | |
download | wireguard-openbsd-43509a120f2aeb0a26f1a8ca43fb60460ab80687.tar.xz wireguard-openbsd-43509a120f2aeb0a26f1a8ca43fb60460ab80687.zip |
Welcome eigrpd
The eigrpd daemon will support the Enhanced Interior Gateway Routing Protocol.
Built using the imsg/three process framework and heavily based on ospfd(8), ospf6d(8) and ldpd(8).
The current status of eigrpd(8) is as follows:
* Almost full compliance with the specification: DUAL FSM, RTP, CR mode, SIA, etc
* Support for both IPv4 and IPv6
* Support for multiple instances (different ASes/AFs) within the same process
* Support for rdomains (one process per rdomain)
* RIB/FIB synchronization
* Basic redistribution support
Not implemented features (yet):
* Configuration reload support (partially implemented)
* Route summarization
* Advanced route redistribution/filtering
* Carp integration
* Authentication (draft is missing information)
* Stub (not released by Cisco)
Not yet connected to the builds.
ok deraadt@ claudio@
29 files changed, 11539 insertions, 0 deletions
diff --git a/usr.sbin/eigrpd/Makefile b/usr.sbin/eigrpd/Makefile new file mode 100644 index 00000000000..9768067adae --- /dev/null +++ b/usr.sbin/eigrpd/Makefile @@ -0,0 +1,19 @@ +# $OpenBSD: Makefile,v 1.1 2015/10/02 04:26:47 renato Exp $ + +PROG= eigrpd +SRCS= control.c eigrpd.c eigrpe.c hello.c in_cksum.c interface.c \ + kroute.c log.c neighbor.c packet.c parse.y printconf.c query.c \ + rde.c rde_dual.c reply.c rtp.c tlv.c update.c util.c + +MAN= eigrpd.8 eigrpd.conf.5 + +CFLAGS+= -Wall -I${.CURDIR} +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare +YFLAGS= +LDADD+= -levent -lutil +DPADD+= ${LIBEVENT} ${LIBUTIL} + +.include <bsd.prog.mk> diff --git a/usr.sbin/eigrpd/control.c b/usr.sbin/eigrpd/control.c new file mode 100644 index 00000000000..a62a7d8518c --- /dev/null +++ b/usr.sbin/eigrpd/control.c @@ -0,0 +1,315 @@ +/* $OpenBSD: control.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@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/stat.h> +#include <sys/un.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "eigrpd.h" +#include "eigrp.h" +#include "eigrpe.h" +#include "log.h" +#include "control.h" + +#define CONTROL_BACKLOG 5 + +struct ctl_conn *control_connbyfd(int); +struct ctl_conn *control_connbypid(pid_t); +void control_close(int); + +int +control_init(char *path) +{ + struct sockaddr_un sun; + int fd; + mode_t old_umask; + + if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + 0)) == -1) { + log_warn("%s: socket", __func__); + return (-1); + } + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, path, sizeof(sun.sun_path)); + + if (unlink(path) == -1) + if (errno != ENOENT) { + log_warn("%s: unlink %s", __func__, path); + close(fd); + return (-1); + } + + old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); + if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) { + log_warn("%s: bind: %s", __func__, path); + close(fd); + umask(old_umask); + return (-1); + } + umask(old_umask); + + if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) { + log_warn("%s: chmod", __func__); + close(fd); + (void)unlink(path); + return (-1); + } + + control_state.fd = fd; + + return (0); +} + +int +control_listen(void) +{ + + if (listen(control_state.fd, CONTROL_BACKLOG) == -1) { + log_warn("%s: listen", __func__); + return (-1); + } + + event_set(&control_state.ev, control_state.fd, EV_READ, + control_accept, NULL); + event_add(&control_state.ev, NULL); + evtimer_set(&control_state.evt, control_accept, NULL); + + return (0); +} + +void +control_cleanup(char *path) +{ + if (path == NULL) + return; + event_del(&control_state.ev); + event_del(&control_state.evt); + unlink(path); +} + +/* ARGSUSED */ +void +control_accept(int listenfd, short event, void *bula) +{ + int connfd; + socklen_t len; + struct sockaddr_un sun; + struct ctl_conn *c; + + event_add(&control_state.ev, NULL); + if ((event & EV_TIMEOUT)) + return; + + len = sizeof(sun); + if ((connfd = accept4(listenfd, (struct sockaddr *)&sun, &len, + SOCK_CLOEXEC | SOCK_NONBLOCK)) == -1) { + /* + * Pause accept if we are out of file descriptors, or + * libevent will haunt us here too. + */ + if (errno == ENFILE || errno == EMFILE) { + struct timeval evtpause = { 1, 0 }; + + event_del(&control_state.ev); + evtimer_add(&control_state.evt, &evtpause); + } else if (errno != EWOULDBLOCK && errno != EINTR && + errno != ECONNABORTED) + log_warn("%s: accept4", __func__); + return; + } + + if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) { + log_warn("%s: calloc", __func__); + close(connfd); + return; + } + + imsg_init(&c->iev.ibuf, connfd); + c->iev.handler = control_dispatch_imsg; + c->iev.events = EV_READ; + event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events, + c->iev.handler, &c->iev); + event_add(&c->iev.ev, NULL); + + TAILQ_INSERT_TAIL(&ctl_conns, c, entry); +} + +struct ctl_conn * +control_connbyfd(int fd) +{ + struct ctl_conn *c; + + for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->iev.ibuf.fd != fd; + c = TAILQ_NEXT(c, entry)) + ; /* nothing */ + + return (c); +} + +struct ctl_conn * +control_connbypid(pid_t pid) +{ + struct ctl_conn *c; + + for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->iev.ibuf.pid != pid; + c = TAILQ_NEXT(c, entry)) + ; /* nothing */ + + return (c); +} + +void +control_close(int fd) +{ + struct ctl_conn *c; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warnx("%s: fd %d: not found", __func__, fd); + return; + } + + msgbuf_clear(&c->iev.ibuf.w); + TAILQ_REMOVE(&ctl_conns, c, entry); + + event_del(&c->iev.ev); + close(c->iev.ibuf.fd); + + /* Some file descriptors are available again. */ + if (evtimer_pending(&control_state.evt, NULL)) { + evtimer_del(&control_state.evt); + event_add(&control_state.ev, NULL); + } + + free(c); +} + +/* ARGSUSED */ +void +control_dispatch_imsg(int fd, short event, void *bula) +{ + struct ctl_conn *c; + struct imsg imsg; + ssize_t n; + unsigned int ifidx; + int verbose; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warnx("%s: fd %d: not found", __func__, fd); + return; + } + + if (event & EV_READ) { + if ((n = imsg_read(&c->iev.ibuf)) == -1 || n == 0) { + control_close(fd); + return; + } + } + if (event & EV_WRITE) { + if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) { + control_close(fd); + return; + } + } + + for (;;) { + if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) { + control_close(fd); + return; + } + + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_CTL_FIB_COUPLE: + case IMSG_CTL_FIB_DECOUPLE: + case IMSG_CTL_RELOAD: + c->iev.ibuf.pid = imsg.hdr.pid; + eigrpe_imsg_compose_parent(imsg.hdr.type, 0, NULL, 0); + break; + case IMSG_CTL_KROUTE: + case IMSG_CTL_IFINFO: + c->iev.ibuf.pid = imsg.hdr.pid; + eigrpe_imsg_compose_parent(imsg.hdr.type, imsg.hdr.pid, + imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE); + break; + case IMSG_CTL_SHOW_INTERFACE: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(ifidx)) + break; + + memcpy(&ifidx, imsg.data, sizeof(ifidx)); + eigrpe_iface_ctl(c, ifidx); + imsg_compose_event(&c->iev, IMSG_CTL_END, 0, + 0, -1, NULL, 0); + break; + case IMSG_CTL_SHOW_TOPOLOGY: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct ctl_show_topology_req)) + break; + + c->iev.ibuf.pid = imsg.hdr.pid; + eigrpe_imsg_compose_rde(imsg.hdr.type, 0, imsg.hdr.pid, + imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE); + break; + case IMSG_CTL_SHOW_NBR: + eigrpe_nbr_ctl(c); + break; + case IMSG_CTL_LOG_VERBOSE: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(verbose)) + break; + + /* forward to other processes */ + eigrpe_imsg_compose_parent(imsg.hdr.type, imsg.hdr.pid, + imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE); + eigrpe_imsg_compose_rde(imsg.hdr.type, 0, imsg.hdr.pid, + imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE); + + memcpy(&verbose, imsg.data, sizeof(verbose)); + log_verbose(verbose); + break; + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + + imsg_event_add(&c->iev); +} + +int +control_imsg_relay(struct imsg *imsg) +{ + struct ctl_conn *c; + + if ((c = control_connbypid(imsg->hdr.pid)) == NULL) + return (0); + + return (imsg_compose_event(&c->iev, imsg->hdr.type, 0, imsg->hdr.pid, + -1, imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE)); +} diff --git a/usr.sbin/eigrpd/control.h b/usr.sbin/eigrpd/control.h new file mode 100644 index 00000000000..608aedc0845 --- /dev/null +++ b/usr.sbin/eigrpd/control.h @@ -0,0 +1,44 @@ +/* $OpenBSD: control.h,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@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. + */ + +#ifndef _CONTROL_H_ +#define _CONTROL_H_ + +#include <sys/queue.h> +#include <sys/time.h> +#include <event.h> + +struct { + struct event ev; + struct event evt; + int fd; +} control_state; + +struct ctl_conn { + TAILQ_ENTRY(ctl_conn) entry; + struct imsgev iev; +}; + +int control_init(char *); +int control_listen(void); +void control_accept(int, short, void *); +void control_dispatch_imsg(int, short, void *); +int control_imsg_relay(struct imsg *); +void control_cleanup(char *); + +#endif /* _CONTROL_H_ */ diff --git a/usr.sbin/eigrpd/eigrp.h b/usr.sbin/eigrpd/eigrp.h new file mode 100644 index 00000000000..b6bd3cfd06e --- /dev/null +++ b/usr.sbin/eigrpd/eigrp.h @@ -0,0 +1,210 @@ +/* $OpenBSD: eigrp.h,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@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. + */ + +/* EIGRP protocol definitions */ + +#ifndef _EIGRP_H_ +#define _EIGRP_H_ + +#include <netinet/in.h> +#include <stddef.h> + +/* misc */ +#define EIGRP_VERSION 2 +#define IPPROTO_EIGRP 88 +#define AllEIGRPRouters_v4 0xa0000e0 /* network byte order */ +#define AllEIGRPRouters_v6 "ff02::a" +#define EIGRP_IP_TTL 2 + +#define EIGRP_INFINITE_METRIC ((uint32_t )(~0)) + +#define RTP_RTRNS_INTERVAL 5 +#define RTP_RTRNS_MAX_ATTEMPTS 16 + +#define RTP_ACK_TIMEOUT 100000 + +#define EIGRP_ACTIVE_TIMEOUT 180 + +#define EIGRP_VERSION_MAJOR 1 +#define EIGRP_VERSION_MINOR 2 + +#define EIGRP_MIN_AS 1 +#define EIGRP_MAX_AS 65535 + +#define DEFAULT_HELLO_INTERVAL 5 +#define MIN_HELLO_INTERVAL 1 +#define MAX_HELLO_INTERVAL 65535 + +#define DEFAULT_HELLO_HOLDTIME 15 +#define MIN_HELLO_HOLDTIME 1 +#define MAX_HELLO_HOLDTIME 65535 + +#define EIGRP_SCALING_FACTOR 256 + +#define DEFAULT_DELAY 10 +#define MIN_DELAY 1 +#define MAX_DELAY 16777215 + +#define DEFAULT_BANDWIDTH 100000 +#define MIN_BANDWIDTH 1 +#define MAX_BANDWIDTH 10000000 + +#define DEFAULT_RELIABILITY 255 +#define MIN_RELIABILITY 1 +#define MAX_RELIABILITY 255 + +#define DEFAULT_LOAD 1 +#define MIN_LOAD 1 +#define MAX_LOAD 255 + +#define MIN_MTU 1 +#define MAX_MTU 65535 + +#define MIN_KVALUE 0 +#define MAX_KVALUE 254 + +#define DEFAULT_MAXIMUM_HOPS 100 +#define MIN_MAXIMUM_HOPS 1 +#define MAX_MAXIMUM_HOPS 255 + +#define DEFAULT_MAXIMUM_PATHS 4 +#define MIN_MAXIMUM_PATHS 1 +#define MAX_MAXIMUM_PATHS 32 + +#define DEFAULT_VARIANCE 1 +#define MIN_VARIANCE 1 +#define MAX_VARIANCE 128 + +#define EIGRP_HEADER_VERSION 2 + +#define EIGRP_VRID_UNICAST_AF 0x0000 +#define EIGRP_VRID_MULTICAST_AF 0x0001 +#define EIGRP_VRID_UNICAST_SF 0x8000 + +/* EIGRP packet types */ +#define EIGRP_OPC_UPDATE 1 +#define EIGRP_OPC_REQUEST 2 +#define EIGRP_OPC_QUERY 3 +#define EIGRP_OPC_REPLY 4 +#define EIGRP_OPC_HELLO 5 +#define EIGRP_OPC_PROBE 7 +#define EIGRP_OPC_SIAQUERY 10 +#define EIGRP_OPC_SIAREPLY 11 + +struct eigrp_hdr { + uint8_t version; + uint8_t opcode; + uint16_t chksum; + uint32_t flags; + uint32_t seq_num; + uint32_t ack_num; + uint16_t vrid; + uint16_t as; +}; +/* EIGRP header flags */ +#define EIGRP_HDR_FLAG_INIT 0x01 +#define EIGRP_HDR_FLAG_CR 0x02 +#define EIGRP_HDR_FLAG_RS 0x04 +#define EIGRP_HDR_FLAG_EOT 0x08 + +/* TLV record */ +struct tlv { + uint16_t type; + uint16_t length; +}; +#define TLV_HDR_LEN 4 + +struct tlv_parameter { + uint16_t type; + uint16_t length; + uint8_t kvalues[6]; + uint16_t holdtime; +}; + +struct tlv_sw_version { + uint16_t type; + uint16_t length; + uint8_t vendor_os_major; + uint8_t vendor_os_minor; + uint8_t eigrp_major; + uint8_t eigrp_minor; +}; + +struct tlv_mcast_seq { + uint16_t type; + uint16_t length; + uint32_t seq; +}; + +struct classic_metric { + uint32_t delay; + uint32_t bandwidth; + uint8_t mtu[3]; /* 3 bytes, yeah... */ + uint8_t hop_count; + uint8_t reliability; + uint8_t load; + uint8_t tag; + uint8_t flags; +}; +#define F_METRIC_SRC_WITHDRAW 0x01 +#define F_METRIC_C_DEFAULT 0x02 +#define F_METRIC_ACTIVE 0x04 + +struct classic_emetric { + uint32_t routerid; + uint32_t as; + uint32_t tag; + uint32_t metric; + uint16_t reserved; + uint8_t protocol; + uint8_t flags; +}; + +#define EIGRP_EXT_PROTO_IGRP 1 +#define EIGRP_EXT_PROTO_EIGRP 2 +#define EIGRP_EXT_PROTO_STATIC 3 +#define EIGRP_EXT_PROTO_RIP 4 +#define EIGRP_EXT_PROTO_HELLO 5 +#define EIGRP_EXT_PROTO_OSPF 6 +#define EIGRP_EXT_PROTO_ISIS 7 +#define EIGRP_EXT_PROTO_EGP 8 +#define EIGRP_EXT_PROTO_BGP 9 +#define EIGRP_EXT_PROTO_IDRP 10 +#define EIGRP_EXT_PROTO_CONN 11 + +/* EIGRP TLV types */ +#define TLV_TYPE_PARAMETER 0x0001 +#define TLV_TYPE_AUTH 0x0002 +#define TLV_TYPE_SEQ 0x0003 +#define TLV_TYPE_SW_VERSION 0x0004 +#define TLV_TYPE_MCAST_SEQ 0x0005 +#define TLV_TYPE_PEER_TERM 0x0007 +#define TLV_TYPE_IPV4_INTERNAL 0x0102 +#define TLV_TYPE_IPV4_EXTERNAL 0x0103 +#define TLV_TYPE_IPV4_COMMUNITY 0x0104 +#define TLV_TYPE_IPV6_INTERNAL 0x0402 +#define TLV_TYPE_IPV6_EXTERNAL 0x0403 +#define TLV_TYPE_IPV6_COMMUNITY 0x0404 + +#define TLV_TYPE_PARAMETER_LEN 0x000C +#define TLV_TYPE_SW_VERSION_LEN 0x0008 +#define TLV_TYPE_MCAST_SEQ_LEN 0x0008 +#define TLV_TYPE_IPV4_INT_MIN_LEN 0x0019 +#define TLV_TYPE_IPV6_INT_MIN_LEN 0x0025 + +#endif /* _EIGRP_H_ */ diff --git a/usr.sbin/eigrpd/eigrpd.8 b/usr.sbin/eigrpd/eigrpd.8 new file mode 100644 index 00000000000..f157edd118d --- /dev/null +++ b/usr.sbin/eigrpd/eigrpd.8 @@ -0,0 +1,109 @@ +.\" $OpenBSD: eigrpd.8,v 1.1 2015/10/02 04:26:47 renato Exp $ +.\" +.\" Copyright (c) 2015 Renato Westphal <renato@openbsd.org> +.\" Copyright (c) 2004, 2005, 2007 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. +.\" +.Dd $Mdocdate: October 2 2015 $ +.Dt EIGRPD 8 +.Os +.Sh NAME +.Nm eigrpd +.Nd Enhanced Interior Gateway Routing Protocol daemon +.Sh SYNOPSIS +.Nm +.Op Fl dnv +.Op Fl D Ar macro Ns = Ns Ar value +.Op Fl f Ar file +.Op Fl s Ar socket +.Sh DESCRIPTION +.Nm +is an Enhanced Interior Gateway Routing Protocol +.Pq EIGRP +daemon which manages routing tables. +EIGRP is a routing protocol based on Distance Vector technology. +.Pp +.Nm +is usually started at boot time, and can be enabled by +setting the following in +.Pa /etc/rc.conf.local : +.Pp +.Dl eigrpd_flags=\&"\&" +.Pp +See +.Xr rc 8 +and +.Xr rc.conf 8 +for more information on the boot process +and enabling daemons. +.Pp +A running +.Nm +can be controlled with the +.Xr eigrpctl 8 +utility. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl D Ar macro Ns = Ns Ar value +Define +.Ar macro +to be set to +.Ar value +on the command line. +Overrides the definition of +.Ar macro +in the configuration file. +.It Fl d +Do not daemonize. +If this option is specified, +.Nm +will run in the foreground and log to +.Em stderr . +.It Fl f Ar file +Specify an alternative configuration file. +.It Fl n +Configtest mode. +Only check the configuration file for validity. +.It Fl s Ar socket +Use an alternate location for the default control socket. +.It Fl v +Produce more verbose output. +.El +.Sh FILES +.Bl -tag -width "/var/run/eigrpd.sockXX" -compact +.It Pa /etc/eigrpd.conf +Default +.Nm +configuration file. +.It Pa /var/run/eigrpd.sock +.Ux Ns -domain +socket used for communication with +.Xr eigrpctl 8 . +.El +.Sh SEE ALSO +.Xr eigrpd.conf 5 , +.Xr eigrpctl 8 +.Sh STANDARDS +.Rs +.%A Savage, et al. +.%D April, 2014 +.%R draft-savage-eigrp-02 +.%T Enhanced Interior Gateway Routing Protocol +.Re +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 5.8 . diff --git a/usr.sbin/eigrpd/eigrpd.c b/usr.sbin/eigrpd/eigrpd.c new file mode 100644 index 00000000000..6d4191bb003 --- /dev/null +++ b/usr.sbin/eigrpd/eigrpd.c @@ -0,0 +1,657 @@ +/* $OpenBSD: eigrpd.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@openbsd.org> + * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org> + * Copyright (c) 2004 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@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 <stdio.h> +#include <stdlib.h> +#include <sys/wait.h> +#include <sys/sysctl.h> +#include <arpa/inet.h> +#include <err.h> +#include <errno.h> +#include <pwd.h> +#include <string.h> +#include <signal.h> +#include <unistd.h> + +#include "eigrpd.h" +#include "eigrp.h" +#include "eigrpe.h" +#include "control.h" +#include "log.h" +#include "rde.h" + +void main_sig_handler(int, short, void *); +__dead void usage(void); +void eigrpd_shutdown(void); +int check_child(pid_t, const char *); + +void main_dispatch_eigrpe(int, short, void *); +void main_dispatch_rde(int, short, void *); + +int eigrp_reload(void); +int eigrp_sendboth(enum imsg_type, void *, uint16_t); +void merge_instances(struct eigrpd_conf *, struct eigrp *, struct eigrp *); + +int pipe_parent2eigrpe[2]; +int pipe_parent2rde[2]; +int pipe_eigrpe2rde[2]; + +struct eigrpd_conf *eigrpd_conf = NULL; +struct imsgev *iev_eigrpe; +struct imsgev *iev_rde; +char *conffile; + +pid_t eigrpe_pid = 0; +pid_t rde_pid = 0; + +/* ARGSUSED */ +void +main_sig_handler(int sig, short event, void *arg) +{ + /* + * signal handler rules don't apply, libevent decouples for us + */ + + int die = 0; + + switch (sig) { + case SIGTERM: + case SIGINT: + die = 1; + /* FALLTHROUGH */ + case SIGCHLD: + if (check_child(eigrpe_pid, "eigrp engine")) { + eigrpe_pid = 0; + die = 1; + } + if (check_child(rde_pid, "route decision engine")) { + rde_pid = 0; + die = 1; + } + if (die) + eigrpd_shutdown(); + break; + case SIGHUP: + if (eigrp_reload() == -1) + log_warnx("configuration reload failed"); + else + log_debug("configuration reloaded"); + break; + default: + fatalx("unexpected signal"); + /* NOTREACHED */ + } +} + +__dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-dnv] [-D macro=value]" + " [-f file] [-s socket]\n", + __progname); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + struct event ev_sigint, ev_sigterm, ev_sigchld, ev_sighup; + int ch, opts = 0; + int debug = 0; + int ipforwarding; + int mib[4]; + size_t len; + char *sockname; + + conffile = CONF_FILE; + eigrpd_process = PROC_MAIN; + sockname = EIGRPD_SOCKET; + + log_init(1); /* log to stderr until daemonized */ + log_verbose(1); + + while ((ch = getopt(argc, argv, "dD:f:ns:v")) != -1) { + switch (ch) { + case 'd': + debug = 1; + break; + case 'D': + if (cmdline_symset(optarg) < 0) + log_warnx("could not parse macro definition %s", + optarg); + break; + case 'f': + conffile = optarg; + break; + case 'n': + opts |= EIGRPD_OPT_NOACTION; + break; + case 's': + sockname = optarg; + break; + case 'v': + if (opts & EIGRPD_OPT_VERBOSE) + opts |= EIGRPD_OPT_VERBOSE2; + opts |= EIGRPD_OPT_VERBOSE; + break; + default: + usage(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + if (argc > 0) + usage(); + + mib[0] = CTL_NET; + mib[1] = PF_INET; + mib[2] = IPPROTO_IP; + mib[3] = IPCTL_FORWARDING; + len = sizeof(ipforwarding); + if (sysctl(mib, 4, &ipforwarding, &len, NULL, 0) == -1) + err(1, "sysctl"); + + if (ipforwarding != 1) + log_warnx("WARNING: IP forwarding NOT enabled"); + + /* fetch interfaces early */ + kif_init(); + + /* parse config file */ + if ((eigrpd_conf = parse_config(conffile, opts)) == NULL) { + kif_clear(); + exit(1); + } + eigrpd_conf->csock = sockname; + + if (eigrpd_conf->opts & EIGRPD_OPT_NOACTION) { + if (eigrpd_conf->opts & EIGRPD_OPT_VERBOSE) + print_config(eigrpd_conf); + else + fprintf(stderr, "configuration OK\n"); + kif_clear(); + exit(0); + } + + /* check for root privileges */ + if (geteuid()) + errx(1, "need root privileges"); + + /* check for eigrpd user */ + if (getpwnam(EIGRPD_USER) == NULL) + errx(1, "unknown user %s", EIGRPD_USER); + + log_init(debug); + log_verbose(eigrpd_conf->opts & EIGRPD_OPT_VERBOSE); + + if (!debug) + daemon(1, 0); + + log_info("startup"); + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + PF_UNSPEC, pipe_parent2eigrpe) == -1) + fatal("socketpair"); + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + PF_UNSPEC, pipe_parent2rde) == -1) + fatal("socketpair"); + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + PF_UNSPEC, pipe_eigrpe2rde) == -1) + fatal("socketpair"); + + /* start children */ + rde_pid = rde(eigrpd_conf, pipe_parent2rde, pipe_eigrpe2rde, + pipe_parent2eigrpe); + eigrpe_pid = eigrpe(eigrpd_conf, pipe_parent2eigrpe, pipe_eigrpe2rde, + pipe_parent2rde); + + /* show who we are */ + setproctitle("parent"); + + event_init(); + + /* setup signal handler */ + signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL); + signal_set(&ev_sigchld, SIGCHLD, main_sig_handler, NULL); + signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal_add(&ev_sigchld, NULL); + signal_add(&ev_sighup, NULL); + signal(SIGPIPE, SIG_IGN); + + /* setup pipes to children */ + close(pipe_parent2eigrpe[1]); + close(pipe_parent2rde[1]); + close(pipe_eigrpe2rde[0]); + close(pipe_eigrpe2rde[1]); + + if ((iev_eigrpe = malloc(sizeof(struct imsgev))) == NULL || + (iev_rde = malloc(sizeof(struct imsgev))) == NULL) + fatal(NULL); + imsg_init(&iev_eigrpe->ibuf, pipe_parent2eigrpe[0]); + iev_eigrpe->handler = main_dispatch_eigrpe; + imsg_init(&iev_rde->ibuf, pipe_parent2rde[0]); + iev_rde->handler = main_dispatch_rde; + + /* setup event handler */ + iev_eigrpe->events = EV_READ; + event_set(&iev_eigrpe->ev, iev_eigrpe->ibuf.fd, iev_eigrpe->events, + iev_eigrpe->handler, iev_eigrpe); + event_add(&iev_eigrpe->ev, NULL); + + iev_rde->events = EV_READ; + event_set(&iev_rde->ev, iev_rde->ibuf.fd, iev_rde->events, + iev_rde->handler, iev_rde); + event_add(&iev_rde->ev, NULL); + + /* notify eigrpe about existing interfaces and addresses */ + kif_redistribute(); + + if (kr_init(!(eigrpd_conf->flags & EIGRPD_FLAG_NO_FIB_UPDATE), + eigrpd_conf->rdomain) == -1) + fatalx("kr_init failed"); + + event_dispatch(); + + eigrpd_shutdown(); + /* NOTREACHED */ + return (0); +} + +void +eigrpd_shutdown(void) +{ + pid_t pid; + + if (eigrpe_pid) + kill(eigrpe_pid, SIGTERM); + + if (rde_pid) + kill(rde_pid, SIGTERM); + + kr_shutdown(); + + do { + if ((pid = wait(NULL)) == -1 && + errno != EINTR && errno != ECHILD) + fatal("wait"); + } while (pid != -1 || (pid == -1 && errno == EINTR)); + + config_clear(eigrpd_conf); + + msgbuf_clear(&iev_eigrpe->ibuf.w); + free(iev_eigrpe); + msgbuf_clear(&iev_rde->ibuf.w); + free(iev_rde); + + log_info("terminating"); + exit(0); +} + +int +check_child(pid_t pid, const char *pname) +{ + int status; + + if (waitpid(pid, &status, WNOHANG) > 0) { + if (WIFEXITED(status)) { + log_warnx("lost child: %s exited", pname); + return (1); + } + if (WIFSIGNALED(status)) { + log_warnx("lost child: %s terminated; signal %d", + pname, WTERMSIG(status)); + return (1); + } + } + + return (0); +} + +/* imsg handling */ +/* ARGSUSED */ +void +main_dispatch_eigrpe(int fd, short event, void *bula) +{ + struct imsgev *iev = bula; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + int shut = 0, verbose; + + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1) + fatal("imsg_read error"); + if (n == 0) /* connection closed */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* connection closed */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("imsg_get"); + + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_CTL_RELOAD: + if (eigrp_reload() == -1) + log_warnx("configuration reload failed"); + else + log_debug("configuration reloaded"); + break; + case IMSG_CTL_FIB_COUPLE: + kr_fib_couple(); + break; + case IMSG_CTL_FIB_DECOUPLE: + kr_fib_decouple(); + break; + case IMSG_CTL_KROUTE: + kr_show_route(&imsg); + break; + case IMSG_CTL_IFINFO: + if (imsg.hdr.len == IMSG_HEADER_SIZE) + kr_ifinfo(NULL, imsg.hdr.pid); + else if (imsg.hdr.len == IMSG_HEADER_SIZE + IFNAMSIZ) + kr_ifinfo(imsg.data, imsg.hdr.pid); + else + log_warnx("IFINFO request with wrong len"); + break; + case IMSG_CTL_LOG_VERBOSE: + /* already checked by eigrpe */ + memcpy(&verbose, imsg.data, sizeof(verbose)); + log_verbose(verbose); + break; + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +/* ARGSUSED */ +void +main_dispatch_rde(int fd, short event, void *bula) +{ + struct imsgev *iev = bula; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + int shut = 0; + + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1) + fatal("imsg_read error"); + if (n == 0) /* connection closed */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* connection closed */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("imsg_get"); + + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_KROUTE_CHANGE: + if (imsg.hdr.len - IMSG_HEADER_SIZE != + sizeof(struct kroute)) + fatalx("invalid size of IMSG_KROUTE_CHANGE"); + if (kr_change(imsg.data)) + log_warnx("%s: error changing route", __func__); + break; + case IMSG_KROUTE_DELETE: + if (imsg.hdr.len - IMSG_HEADER_SIZE != + sizeof(struct kroute)) + fatalx("invalid size of IMSG_KROUTE_DELETE"); + if (kr_delete(imsg.data)) + log_warnx("%s: error deleting route", __func__); + break; + + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +void +main_imsg_compose_eigrpe(int type, pid_t pid, void *data, uint16_t datalen) +{ + if (iev_eigrpe == NULL) + return; + imsg_compose_event(iev_eigrpe, type, 0, pid, -1, data, datalen); +} + +void +main_imsg_compose_rde(int type, pid_t pid, void *data, uint16_t datalen) +{ + imsg_compose_event(iev_rde, type, 0, pid, -1, data, datalen); +} + +void +imsg_event_add(struct imsgev *iev) +{ + iev->events = EV_READ; + if (iev->ibuf.w.queued) + iev->events |= EV_WRITE; + + event_del(&iev->ev); + event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev); + event_add(&iev->ev, NULL); +} + +int +imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid, + pid_t pid, int fd, void *data, uint16_t datalen) +{ + int ret; + + if ((ret = imsg_compose(&iev->ibuf, type, peerid, + pid, fd, data, datalen)) != -1) + imsg_event_add(iev); + return (ret); +} + +uint32_t +eigrp_router_id(struct eigrpd_conf *xconf) +{ + return (xconf->rtr_id.s_addr); +} + +struct eigrp * +eigrp_find(struct eigrpd_conf *xconf, int af, uint16_t as) +{ + struct eigrp *eigrp; + + TAILQ_FOREACH(eigrp, &xconf->instances, entry) + if (eigrp->af == af && eigrp->as == as) + return (eigrp); + + return (NULL); +} + +int +eigrp_reload(void) +{ + struct eigrp *eigrp; + struct eigrp_iface *ei; + struct eigrpd_conf *xconf; + + if ((xconf = parse_config(conffile, eigrpd_conf->opts)) == NULL) + return (-1); + + if (eigrp_sendboth(IMSG_RECONF_CONF, xconf, sizeof(*xconf)) == -1) + return (-1); + + TAILQ_FOREACH(eigrp, &xconf->instances, entry) { + if (eigrp_sendboth(IMSG_RECONF_INSTANCE, eigrp, + sizeof(*eigrp)) == -1) + return (-1); + + TAILQ_FOREACH(ei, &eigrp->ei_list, e_entry) { + if (eigrp_sendboth(IMSG_RECONF_IFACE, ei->iface, + sizeof(struct iface)) == -1) + return (-1); + + if (eigrp_sendboth(IMSG_RECONF_EIGRP_IFACE, ei, + sizeof(*ei)) == -1) + return (-1); + } + } + + if (eigrp_sendboth(IMSG_RECONF_END, NULL, 0) == -1) + return (-1); + + merge_config(eigrpd_conf, xconf); + + return (0); +} + +int +eigrp_sendboth(enum imsg_type type, void *buf, uint16_t len) +{ + if (imsg_compose_event(iev_eigrpe, type, 0, 0, -1, buf, len) == -1) + return (-1); + if (imsg_compose_event(iev_rde, type, 0, 0, -1, buf, len) == -1) + return (-1); + return (0); +} + +void +merge_config(struct eigrpd_conf *conf, struct eigrpd_conf *xconf) +{ + struct eigrp *eigrp, *etmp, *xe; + + /* change of rtr_id needs a restart */ + conf->flags = xconf->flags; + conf->rdomain= xconf->rdomain; + conf->fib_priority_internal = xconf->fib_priority_internal; + conf->fib_priority_external = xconf->fib_priority_external; + + /* merge instances */ + TAILQ_FOREACH_SAFE(eigrp, &conf->instances, entry, etmp) { + /* find deleted instances */ + if ((xe = eigrp_find(xconf, eigrp->af, eigrp->as)) == NULL) { + TAILQ_REMOVE(&conf->instances, eigrp, entry); + + switch (eigrpd_process) { + case PROC_RDE_ENGINE: + rde_instance_del(eigrp); + break; + case PROC_EIGRP_ENGINE: + eigrpe_instance_del(eigrp); + break; + case PROC_MAIN: + free(eigrp); + break; + } + } + } + TAILQ_FOREACH_SAFE(xe, &xconf->instances, entry, etmp) { + /* find new instances */ + if ((eigrp = eigrp_find(conf, xe->af, xe->as)) == NULL) { + TAILQ_REMOVE(&xconf->instances, xe, entry); + TAILQ_INSERT_TAIL(&conf->instances, xe, entry); + + switch (eigrpd_process) { + case PROC_RDE_ENGINE: + rde_instance_init(xe); + break; + case PROC_EIGRP_ENGINE: + eigrpe_instance_init(xe); + break; + case PROC_MAIN: + break; + } + continue; + } + + /* update existing instances */ + merge_instances(conf, eigrp, xe); + } + /* resend addresses to activate new interfaces */ + if (eigrpd_process == PROC_MAIN) + kif_redistribute(); + + free(xconf); +} + +void +merge_instances(struct eigrpd_conf *xconf, struct eigrp *eigrp, struct eigrp *xe) +{ + /* TODO */ +} + +void +config_clear(struct eigrpd_conf *conf) +{ + struct eigrpd_conf *xconf; + + /* merge current config with an empty config */ + xconf = malloc(sizeof(*xconf)); + memcpy(xconf, conf, sizeof(*xconf)); + TAILQ_INIT(&xconf->instances); + merge_config(conf, xconf); + + free(conf); +} diff --git a/usr.sbin/eigrpd/eigrpd.conf.5 b/usr.sbin/eigrpd/eigrpd.conf.5 new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/usr.sbin/eigrpd/eigrpd.conf.5 diff --git a/usr.sbin/eigrpd/eigrpd.h b/usr.sbin/eigrpd/eigrpd.h new file mode 100644 index 00000000000..8581d4cfd60 --- /dev/null +++ b/usr.sbin/eigrpd/eigrpd.h @@ -0,0 +1,447 @@ +/* $OpenBSD: eigrpd.h,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@openbsd.org> + * Copyright (c) 2004 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@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. + */ + +#ifndef _EIGRPD_H_ +#define _EIGRPD_H_ + +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/tree.h> +#include <net/route.h> +#include <net/if.h> +#include <netinet/in.h> +#include <event.h> + +#include <imsg.h> +#include "eigrp.h" + +#define CONF_FILE "/etc/eigrpd.conf" +#define EIGRPD_SOCKET "/var/run/eigrpd.sock" +#define EIGRPD_USER "_eigrpd" + +#define NBR_IDSELF 1 +#define NBR_CNTSTART (NBR_IDSELF + 1) + +#define READ_BUF_SIZE 65535 +#define PKG_DEF_SIZE 512 /* compromise */ +#define RT_BUF_SIZE 16384 +#define MAX_RTSOCK_BUF 128 * 1024 + +#define RTP_EIGRP 28 /* XXX move to sys/net/route.h */ + +#define F_EIGRPD_INSERTED 0x0001 +#define F_KERNEL 0x0002 +#define F_CONNECTED 0x0004 +#define F_STATIC 0x0008 +#define F_DYNAMIC 0x0010 +#define F_DOWN 0x0020 +#define F_REJECT 0x0040 +#define F_BLACKHOLE 0x0080 +#define F_REDISTRIBUTED 0x0100 +#define F_CTL_EXTERNAL 0x0200 /* only used by eigrpctl */ +#define F_CTL_ACTIVE 0x0400 +#define F_CTL_ALLLINKS 0x0800 + +struct imsgev { + struct imsgbuf ibuf; + void (*handler)(int, short, void *); + struct event ev; + void *data; + short events; +}; + +enum imsg_type { + IMSG_CTL_RELOAD, + IMSG_CTL_SHOW_INTERFACE, + IMSG_CTL_SHOW_NBR, + IMSG_CTL_SHOW_TOPOLOGY, + IMSG_CTL_FIB_COUPLE, + IMSG_CTL_FIB_DECOUPLE, + IMSG_CTL_IFACE, + IMSG_CTL_KROUTE, + IMSG_CTL_IFINFO, + IMSG_CTL_END, + IMSG_CTL_LOG_VERBOSE, + IMSG_KROUTE_CHANGE, + IMSG_KROUTE_DELETE, + IMSG_NONE, + IMSG_IFINFO, + IMSG_IFDOWN, + IMSG_NEWADDR, + IMSG_DELADDR, + IMSG_NETWORK_ADD, + IMSG_NETWORK_DEL, + IMSG_NEIGHBOR_UP, + IMSG_NEIGHBOR_DOWN, + IMSG_RECV_UPDATE_INIT, + IMSG_RECV_UPDATE, + IMSG_RECV_QUERY, + IMSG_RECV_REPLY, + IMSG_RECV_SIAQUERY, + IMSG_RECV_SIAREPLY, + IMSG_SEND_UPDATE, + IMSG_SEND_QUERY, + IMSG_SEND_REPLY, + IMSG_SEND_MUPDATE, + IMSG_SEND_MQUERY, + IMSG_SEND_UPDATE_END, + IMSG_SEND_REPLY_END, + IMSG_SEND_SIAQUERY_END, + IMSG_SEND_SIAREPLY_END, + IMSG_SEND_MUPDATE_END, + IMSG_SEND_MQUERY_END, + IMSG_RECONF_CONF, + IMSG_RECONF_IFACE, + IMSG_RECONF_INSTANCE, + IMSG_RECONF_EIGRP_IFACE, + IMSG_RECONF_END +}; + +union eigrpd_addr { + struct in_addr v4; + struct in6_addr v6; +}; + +/* interface types */ +enum iface_type { + IF_TYPE_POINTOPOINT, + IF_TYPE_BROADCAST, +}; + +struct if_addr { + TAILQ_ENTRY(if_addr) entry; + int af; + union eigrpd_addr addr; + uint8_t prefixlen; + union eigrpd_addr dstbrd; +}; +TAILQ_HEAD(if_addr_head, if_addr); + +struct iface { + TAILQ_ENTRY(iface) entry; + TAILQ_HEAD(, eigrp_iface) ei_list; + unsigned int ifindex; + char name[IF_NAMESIZE]; + struct if_addr_head addr_list; + struct in6_addr linklocal; + int mtu; + enum iface_type type; + uint8_t if_type; + uint64_t baudrate; + uint16_t flags; + uint8_t linkstate; + uint8_t group_count_v4; + uint8_t group_count_v6; +}; + +enum route_type { + EIGRP_ROUTE_INTERNAL, + EIGRP_ROUTE_EXTERNAL +}; + +/* routing information advertised by update/query/reply messages */ +struct rinfo { + int af; + enum route_type type; + union eigrpd_addr prefix; + uint8_t prefixlen; + union eigrpd_addr nexthop; + struct classic_metric metric; + struct classic_emetric emetric; +}; + +struct rinfo_entry { + TAILQ_ENTRY(rinfo_entry) entry; + struct rinfo rinfo; +}; +TAILQ_HEAD(rinfo_head, rinfo_entry); + +/* interface states */ +#define IF_STA_DOWN 0x01 +#define IF_STA_ACTIVE 0x02 + +struct eigrp_iface { + RB_ENTRY(eigrp_iface) id_tree; + TAILQ_ENTRY(eigrp_iface) e_entry; + TAILQ_ENTRY(eigrp_iface) i_entry; + struct eigrp *eigrp; + struct iface *iface; + int state; + uint32_t ifaceid; + struct event hello_timer; + uint32_t delay; + uint32_t bandwidth; + uint16_t hello_holdtime; + uint16_t hello_interval; + uint8_t splithorizon; + uint8_t passive; + time_t uptime; + TAILQ_HEAD(, nbr) nbr_list; + struct nbr *self; + struct rinfo_head update_list; /* multicast updates */ + struct rinfo_head query_list; /* multicast queries */ +}; + +#define INADDRSZ 4 +#define IN6ADDRSZ 16 + +struct seq_addr_entry { + TAILQ_ENTRY(seq_addr_entry) entry; + int af; + union eigrpd_addr addr; +}; +TAILQ_HEAD(seq_addr_head, seq_addr_entry); + +struct nbr; +RB_HEAD(nbr_addr_head, nbr); +RB_HEAD(nbr_pid_head, nbr); +struct rt_node; +RB_HEAD(rt_tree, rt_node); + +#define REDIST_STATIC 0x01 +#define REDIST_RIP 0x02 +#define REDIST_OSPF 0x04 +#define REDIST_CONNECTED 0x08 +#define REDIST_DEFAULT 0x10 +#define REDIST_ADDR 0x20 +#define REDIST_NO 0x40 + +struct redist_metric { + uint32_t bandwidth; + uint32_t delay; + uint8_t reliability; + uint8_t load; + uint16_t mtu; +}; + +struct redistribute { + SIMPLEQ_ENTRY(redistribute) entry; + uint8_t type; + int af; + union eigrpd_addr addr; + uint8_t prefixlen; + struct redist_metric *metric; + struct { + uint32_t as; + uint32_t metric; + uint32_t tag; + } emetric; +}; +SIMPLEQ_HEAD(redist_list, redistribute); + +/* eigrp instance */ +struct eigrp { + TAILQ_ENTRY(eigrp) entry; + int af; + uint16_t as; + uint8_t kvalues[6]; + uint8_t maximum_hops; + uint8_t maximum_paths; + uint8_t variance; + struct redist_metric *dflt_metric; + struct redist_list redist_list; + TAILQ_HEAD(, eigrp_iface) ei_list; + struct nbr_addr_head nbrs; + struct rde_nbr *rnbr_redist; + struct rde_nbr *rnbr_summary; + struct rt_tree topology; + uint32_t seq_num; +}; + +/* eigrp_conf */ +enum { + PROC_MAIN, + PROC_EIGRP_ENGINE, + PROC_RDE_ENGINE +} eigrpd_process; + +struct eigrpd_conf { + struct in_addr rtr_id; + + uint32_t opts; +#define EIGRPD_OPT_VERBOSE 0x00000001 +#define EIGRPD_OPT_VERBOSE2 0x00000002 +#define EIGRPD_OPT_NOACTION 0x00000004 + + int flags; +#define EIGRPD_FLAG_NO_FIB_UPDATE 0x0001 + + time_t uptime; + int eigrp_socket_v4; + int eigrp_socket_v6; + unsigned int rdomain; + uint8_t fib_priority_internal; + uint8_t fib_priority_external; + TAILQ_HEAD(, iface) iface_list; + TAILQ_HEAD(, eigrp) instances; + char *csock; +}; + +/* kroute */ +struct kroute { + int af; + union eigrpd_addr prefix; + uint8_t prefixlen; + union eigrpd_addr nexthop; + unsigned short ifindex; + uint8_t priority; + uint16_t flags; +}; + +struct kaddr { + unsigned short ifindex; + int af; + union eigrpd_addr addr; + uint8_t prefixlen; + union eigrpd_addr dstbrd; +}; + +struct kif { + char ifname[IF_NAMESIZE]; + unsigned short ifindex; + int flags; + uint8_t link_state; + int mtu; + uint8_t if_type; + uint64_t baudrate; + uint8_t nh_reachable; /* for nexthop verification */ +}; + +/* control data structures */ +struct ctl_iface { + int af; + uint16_t as; + char name[IF_NAMESIZE]; + unsigned int ifindex; + union eigrpd_addr addr; + uint8_t prefixlen; + uint16_t flags; + uint8_t linkstate; + int mtu; + enum iface_type type; + uint8_t if_type; + uint64_t baudrate; + uint32_t delay; + uint32_t bandwidth; + uint16_t hello_holdtime; + uint16_t hello_interval; + uint8_t splithorizon; + uint8_t passive; + time_t uptime; + int nbr_cnt; +}; + +struct ctl_nbr { + int af; + uint16_t as; + char ifname[IF_NAMESIZE]; + union eigrpd_addr addr; + uint16_t hello_holdtime; + time_t uptime; +}; + +struct ctl_rt { + int af; + uint16_t as; + union eigrpd_addr prefix; + uint8_t prefixlen; + enum route_type type; + union eigrpd_addr nexthop; + char ifname[IF_NAMESIZE]; + uint32_t distance; + uint32_t rdistance; + uint32_t fdistance; + int state; + uint8_t flags; + struct { + uint32_t delay; + uint32_t bandwidth; + uint32_t mtu; + uint8_t hop_count; + uint8_t reliability; + uint8_t load; + } metric; + struct classic_emetric emetric; +}; +#define F_CTL_RT_FIRST 0x01 +#define F_CTL_RT_SUCCESSOR 0x02 +#define F_CTL_RT_FSUCCESSOR 0x04 + +struct ctl_show_topology_req { + int af; + union eigrpd_addr prefix; + uint8_t prefixlen; + uint16_t flags; +}; + +/* parse.y */ +struct eigrpd_conf *parse_config(char *, int); +int cmdline_symset(char *); + +/* in_cksum.c */ +uint16_t in_cksum(void *, size_t); + +/* kroute.c */ +int kif_init(void); +void kif_redistribute(void); +int kr_init(int, unsigned int); +int kr_change(struct kroute *); +int kr_delete(struct kroute *); +void kif_clear(void); +void kr_shutdown(void); +void kr_fib_couple(void); +void kr_fib_decouple(void); +void kr_fib_reload(void); +void kr_dispatch_msg(int, short, void *); +void kr_show_route(struct imsg *); +void kr_ifinfo(char *, pid_t); +struct kif *kif_findname(char *); +void kr_reload(void); + +/* util.c */ +uint8_t mask2prefixlen(in_addr_t); +uint8_t mask2prefixlen6(struct sockaddr_in6 *); +in_addr_t prefixlen2mask(uint8_t); +struct in6_addr *prefixlen2mask6(uint8_t); +void eigrp_applymask(int, union eigrpd_addr *, + const union eigrpd_addr *, int); +int eigrp_addrcmp(int, union eigrpd_addr *, union eigrpd_addr *); +int eigrp_addrisset(int, union eigrpd_addr *); +void embedscope(struct sockaddr_in6 *); +void recoverscope(struct sockaddr_in6 *); +void addscope(struct sockaddr_in6 *, uint32_t); +void clearscope(struct in6_addr *); + +/* eigrpd.c */ +void main_imsg_compose_eigrpe(int, pid_t, void *, uint16_t); +void main_imsg_compose_rde(int, pid_t, void *, uint16_t); +void merge_config(struct eigrpd_conf *, struct eigrpd_conf *); +void config_clear(struct eigrpd_conf *); +void imsg_event_add(struct imsgev *); +int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, + pid_t, int, void *, uint16_t); +uint32_t eigrp_router_id(struct eigrpd_conf *); +struct eigrp *eigrp_find(struct eigrpd_conf *, int, uint16_t); + +/* printconf.c */ +void print_config(struct eigrpd_conf *); + +#endif /* _EIGRPD_H_ */ diff --git a/usr.sbin/eigrpd/eigrpe.c b/usr.sbin/eigrpd/eigrpe.c new file mode 100644 index 00000000000..a141eaefc43 --- /dev/null +++ b/usr.sbin/eigrpd/eigrpe.c @@ -0,0 +1,678 @@ +/* $OpenBSD: eigrpe.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@openbsd.org> + * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org> + * Copyright (c) 2004 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@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 <stdlib.h> +#include <arpa/inet.h> +#include <signal.h> +#include <string.h> +#include <fcntl.h> +#include <pwd.h> +#include <unistd.h> +#include <errno.h> + +#include "eigrp.h" +#include "eigrpd.h" +#include "eigrpe.h" +#include "rde.h" +#include "control.h" +#include "log.h" + +void eigrpe_sig_handler(int, short, void *); +void eigrpe_shutdown(void); + +static struct event ev4; +static struct event ev6; +struct eigrpd_conf *econf = NULL, *nconf; +struct imsgev *iev_main; +struct imsgev *iev_rde; + +extern struct iface_id_head ifaces_by_id; +RB_PROTOTYPE(iface_id_head, eigrp_iface, id_tree, iface_id_compare) + +extern struct nbr_addr_head nbrs_by_addr; +RB_PROTOTYPE(nbr_addr_head, nbr, addr_tree, nbr_compare) + +/* ARGSUSED */ +void +eigrpe_sig_handler(int sig, short event, void *bula) +{ + switch (sig) { + case SIGINT: + case SIGTERM: + eigrpe_shutdown(); + /* NOTREACHED */ + default: + fatalx("unexpected signal"); + } +} + +/* eigrp engine */ +pid_t +eigrpe(struct eigrpd_conf *xconf, int pipe_parent2eigrpe[2], int pipe_eigrpe2rde[2], + int pipe_parent2rde[2]) +{ + struct passwd *pw; + struct event ev_sigint, ev_sigterm; + pid_t pid; + struct iface *iface; + + switch (pid = fork()) { + case -1: + fatal("cannot fork"); + case 0: + break; + default: + return (pid); + } + + /* create eigrpd control socket outside chroot */ + if (control_init(xconf->csock) == -1) + fatalx("control socket setup failed"); + + /* create the raw ipv4 socket */ + if ((xconf->eigrp_socket_v4 = socket(AF_INET, + SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_EIGRP)) == -1) + fatal("error creating raw ipv4 socket"); + + /* set some defaults */ + if (if_set_ipv4_mcast_ttl(xconf->eigrp_socket_v4, EIGRP_IP_TTL) == -1) + fatal("if_set_ipv4_mcast_ttl"); + if (if_set_ipv4_mcast_loop(xconf->eigrp_socket_v4) == -1) + fatal("if_set_ipv4_mcast_loop"); + if (if_set_ipv4_recvif(xconf->eigrp_socket_v4, 1) == -1) + fatal("if_set_ipv4_recvif"); + if (if_set_ipv4_hdrincl(xconf->eigrp_socket_v4) == -1) + fatal("if_set_ipv4_hdrincl"); + if_set_sockbuf(xconf->eigrp_socket_v4); + + /* create the raw ipv6 socket */ + if ((xconf->eigrp_socket_v6 = socket(AF_INET6, + SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_EIGRP)) == -1) + fatal("error creating raw ipv6 socket"); + + /* set some defaults */ + if (if_set_ipv6_mcast_loop(xconf->eigrp_socket_v6) == -1) + fatal("if_set_ipv6_mcast_loop"); + if (if_set_ipv6_pktinfo(xconf->eigrp_socket_v6, 1) == -1) + fatal("if_set_ipv6_pktinfo"); + if (if_set_ipv6_dscp(xconf->eigrp_socket_v6, + IPTOS_PREC_NETCONTROL) == -1) + fatal("if_set_ipv6_dscp"); + if_set_sockbuf(xconf->eigrp_socket_v6); + + econf = xconf; + + if ((pw = getpwnam(EIGRPD_USER)) == NULL) + fatal("getpwnam"); + + if (chroot(pw->pw_dir) == -1) + fatal("chroot"); + if (chdir("/") == -1) + fatal("chdir(\"/\")"); + + setproctitle("eigrp engine"); + eigrpd_process = PROC_EIGRP_ENGINE; + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("can't drop privileges"); + + event_init(); + + /* setup signal handler */ + signal_set(&ev_sigint, SIGINT, eigrpe_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, eigrpe_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + /* setup pipes */ + close(pipe_parent2eigrpe[0]); + close(pipe_eigrpe2rde[1]); + close(pipe_parent2rde[0]); + close(pipe_parent2rde[1]); + + if ((iev_rde = malloc(sizeof(struct imsgev))) == NULL || + (iev_main = malloc(sizeof(struct imsgev))) == NULL) + fatal(NULL); + imsg_init(&iev_rde->ibuf, pipe_eigrpe2rde[0]); + iev_rde->handler = eigrpe_dispatch_rde; + imsg_init(&iev_main->ibuf, pipe_parent2eigrpe[1]); + iev_main->handler = eigrpe_dispatch_main; + + /* setup event handler */ + iev_rde->events = EV_READ; + event_set(&iev_rde->ev, iev_rde->ibuf.fd, iev_rde->events, + iev_rde->handler, iev_rde); + event_add(&iev_rde->ev, NULL); + + iev_main->events = EV_READ; + event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events, + iev_main->handler, iev_main); + event_add(&iev_main->ev, NULL); + + event_set(&ev4, econf->eigrp_socket_v4, EV_READ|EV_PERSIST, + recv_packet_v4, econf); + event_add(&ev4, NULL); + + event_set(&ev6, econf->eigrp_socket_v6, EV_READ|EV_PERSIST, + recv_packet_v6, econf); + event_add(&ev6, NULL); + + /* listen on eigrpd control socket */ + TAILQ_INIT(&ctl_conns); + control_listen(); + + if ((pkt_ptr = calloc(1, READ_BUF_SIZE)) == NULL) + fatal("eigrpe"); + + /* initialize interfaces */ + TAILQ_FOREACH(iface, &econf->iface_list, entry) + if_init(xconf, iface); + + event_dispatch(); + + eigrpe_shutdown(); + /* NOTREACHED */ + return (0); +} + +void +eigrpe_shutdown(void) +{ + control_cleanup(econf->csock); + + config_clear(econf); + + event_del(&ev4); + event_del(&ev6); + close(econf->eigrp_socket_v4); + close(econf->eigrp_socket_v6); + + /* clean up */ + msgbuf_write(&iev_rde->ibuf.w); + msgbuf_clear(&iev_rde->ibuf.w); + free(iev_rde); + msgbuf_write(&iev_main->ibuf.w); + msgbuf_clear(&iev_main->ibuf.w); + free(iev_main); + free(pkt_ptr); + + log_info("eigrp engine exiting"); + _exit(0); +} + +/* imesg */ +int +eigrpe_imsg_compose_parent(int type, pid_t pid, void *data, uint16_t datalen) +{ + return (imsg_compose_event(iev_main, type, 0, pid, -1, data, datalen)); +} + +int +eigrpe_imsg_compose_rde(int type, uint32_t peerid, pid_t pid, + void *data, uint16_t datalen) +{ + return (imsg_compose_event(iev_rde, type, peerid, pid, -1, + data, datalen)); +} + +/* ARGSUSED */ +void +eigrpe_dispatch_main(int fd, short event, void *bula) +{ + struct iface *niface; + static struct eigrp *neigrp; + struct eigrp_iface *nei; + struct imsg imsg; + struct imsgev *iev = bula; + struct imsgbuf *ibuf = &iev->ibuf; + struct iface *iface = NULL; + struct kif *kif; + struct kaddr *ka; + int n, shut = 0; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1) + fatal("imsg_read error"); + if (n == 0) /* connection closed */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* connection closed */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("eigrpe_dispatch_main: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_IFINFO: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct kif)) + fatalx("IFSTATUS imsg with wrong len"); + kif = imsg.data; + + iface = if_lookup(econf, kif->ifindex); + if (!iface) + break; + + iface->flags = kif->flags; + iface->linkstate = kif->link_state; + if_update(iface, AF_UNSPEC); + break; + case IMSG_NEWADDR: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct kaddr)) + fatalx("NEWADDR imsg with wrong len"); + ka = imsg.data; + + iface = if_lookup(econf, ka->ifindex); + if (iface == NULL) + break; + + if (ka->af == AF_INET6 && + IN6_IS_ADDR_LINKLOCAL(&ka->addr.v6)) { + memcpy(&iface->linklocal, &ka->addr.v6, + sizeof(iface->linklocal)); + break; + } + + if_addr_new(iface, ka); + break; + case IMSG_DELADDR: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct kaddr)) + fatalx("DELADDR imsg with wrong len"); + ka = imsg.data; + + iface = if_lookup(econf, ka->ifindex); + if (iface == NULL) + break; + + if (ka->af == AF_INET6 && + IN6_ARE_ADDR_EQUAL(&iface->linklocal, + &ka->addr.v6)) { + memset(&iface->linklocal, 0, + sizeof(iface->linklocal)); + break; + } + + if_addr_del(iface, ka); + break; + case IMSG_RECONF_CONF: + if ((nconf = malloc(sizeof(struct eigrpd_conf))) == + NULL) + fatal(NULL); + memcpy(nconf, imsg.data, sizeof(struct eigrpd_conf)); + + TAILQ_INIT(&nconf->iface_list); + TAILQ_INIT(&nconf->instances); + break; + case IMSG_RECONF_INSTANCE: + if ((neigrp = malloc(sizeof(struct eigrp))) == NULL) + fatal(NULL); + memcpy(neigrp, imsg.data, sizeof(struct eigrp)); + + SIMPLEQ_INIT(&neigrp->redist_list); + TAILQ_INIT(&neigrp->ei_list); + RB_INIT(&neigrp->nbrs); + RB_INIT(&neigrp->topology); + TAILQ_INSERT_TAIL(&nconf->instances, neigrp, entry); + break; + case IMSG_RECONF_IFACE: + niface = imsg.data; + niface = if_lookup(nconf, niface->ifindex); + if (niface) + break; + + if ((niface = malloc(sizeof(struct iface))) == NULL) + fatal(NULL); + memcpy(niface, imsg.data, sizeof(struct iface)); + + TAILQ_INIT(&niface->addr_list); + TAILQ_INSERT_TAIL(&nconf->iface_list, niface, entry); + break; + case IMSG_RECONF_EIGRP_IFACE: + if ((nei = malloc(sizeof(struct eigrp_iface))) == NULL) + fatal(NULL); + memcpy(nei, imsg.data, sizeof(struct eigrp_iface)); + + nei->iface = niface; + nei->eigrp = neigrp; + TAILQ_INIT(&nei->nbr_list); + TAILQ_INIT(&nei->update_list); + TAILQ_INIT(&nei->query_list); + TAILQ_INSERT_TAIL(&niface->ei_list, nei, i_entry); + TAILQ_INSERT_TAIL(&neigrp->ei_list, nei, e_entry); + if (RB_INSERT(iface_id_head, &ifaces_by_id, nei) != + NULL) + fatalx("rde_dispatch_parent: " + "RB_INSERT(ifaces_by_id) failed"); + break; + case IMSG_RECONF_END: + merge_config(econf, nconf); + nconf = NULL; + break; + case IMSG_CTL_KROUTE: + case IMSG_CTL_IFINFO: + case IMSG_CTL_END: + control_imsg_relay(&imsg); + break; + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +/* ARGSUSED */ +void +eigrpe_dispatch_rde(int fd, short event, void *bula) +{ + struct imsgev *iev = bula; + struct imsgbuf *ibuf = &iev->ibuf; + struct imsg imsg; + struct nbr *nbr; + struct eigrp_iface *ei; + struct rinfo rinfo; + int n, shut = 0; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1) + fatal("imsg_read error"); + if (n == 0) /* connection closed */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* connection closed */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("eigrpe_dispatch_rde: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_SEND_UPDATE: + case IMSG_SEND_QUERY: + case IMSG_SEND_REPLY: + if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(rinfo)) + fatalx("invalid size of rinfo"); + memcpy(&rinfo, imsg.data, sizeof(rinfo)); + + nbr = nbr_find_peerid(imsg.hdr.peerid); + if (nbr == NULL) { + log_debug("%s: cannot find rde neighbor", + __func__); + break; + } + + switch (imsg.hdr.type) { + case IMSG_SEND_UPDATE: + message_add(&nbr->update_list, &rinfo); + break; + case IMSG_SEND_QUERY: + message_add(&nbr->query_list, &rinfo); + break; + case IMSG_SEND_REPLY: + message_add(&nbr->reply_list, &rinfo); + break; + } + break; + case IMSG_SEND_MUPDATE: + case IMSG_SEND_MQUERY: + if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(rinfo)) + fatalx("invalid size of rinfo"); + memcpy(&rinfo, imsg.data, sizeof(rinfo)); + + ei = eigrp_iface_find_id(imsg.hdr.peerid); + if (ei == NULL) { + log_debug("%s: cannot find interface", + __func__); + break; + } + + switch (imsg.hdr.type) { + case IMSG_SEND_MUPDATE: + message_add(&ei->update_list, &rinfo); + break; + case IMSG_SEND_MQUERY: + message_add(&ei->query_list, &rinfo); + break; + } + break; + case IMSG_SEND_UPDATE_END: + case IMSG_SEND_REPLY_END: + case IMSG_SEND_SIAQUERY_END: + case IMSG_SEND_SIAREPLY_END: + nbr = nbr_find_peerid(imsg.hdr.peerid); + if (nbr == NULL) { + log_debug("%s: cannot find rde neighbor", + __func__); + break; + } + + switch (imsg.hdr.type) { + case IMSG_SEND_UPDATE_END: + send_update(nbr->ei, nbr, 0, 1, + &nbr->update_list); + message_list_clr(&nbr->update_list); + break; + case IMSG_SEND_REPLY_END: + send_reply(nbr, &nbr->reply_list, 0); + message_list_clr(&nbr->reply_list); + break; + case IMSG_SEND_SIAQUERY_END: + send_query(nbr->ei, nbr, &nbr->query_list, 1); + message_list_clr(&nbr->query_list); + break; + case IMSG_SEND_SIAREPLY_END: + send_reply(nbr, &nbr->reply_list, 1); + message_list_clr(&nbr->reply_list); + break; + } + break; + case IMSG_SEND_MUPDATE_END: + case IMSG_SEND_MQUERY_END: + ei = eigrp_iface_find_id(imsg.hdr.peerid); + if (ei == NULL) { + log_debug("%s: cannot find interface", + __func__); + break; + } + + switch (imsg.hdr.type) { + case IMSG_SEND_MUPDATE_END: + send_update(ei, NULL, 0, 0, &ei->update_list); + message_list_clr(&ei->update_list); + break; + case IMSG_SEND_MQUERY_END: + send_query(ei, NULL, &ei->query_list, 0); + message_list_clr(&ei->query_list); + break; + } + break; + case IMSG_CTL_SHOW_TOPOLOGY: + case IMSG_CTL_END: + control_imsg_relay(&imsg); + break; + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +void +eigrpe_instance_init(struct eigrp *eigrp) +{ + struct eigrp_iface *ei; + + TAILQ_FOREACH(ei, &eigrp->ei_list, e_entry) + if_init(econf, ei->iface); +} + +void +eigrpe_instance_del(struct eigrp *eigrp) +{ + struct eigrp_iface *ei; + + while ((ei = TAILQ_FIRST(&eigrp->ei_list)) != NULL) + eigrp_if_del(ei); + + free(eigrp); +} + +void +message_add(struct rinfo_head *rinfo_list, struct rinfo *rinfo) +{ + struct rinfo_entry *re; + + re = calloc(1, sizeof(*re)); + if (re == NULL) + fatal("message_add"); + memcpy(&re->rinfo, rinfo, sizeof(re->rinfo));; + + TAILQ_INSERT_TAIL(rinfo_list, re, entry); +} + +void +message_list_clr(struct rinfo_head *rinfo_list) +{ + struct rinfo_entry *re; + + while ((re = TAILQ_FIRST(rinfo_list)) != NULL) { + TAILQ_REMOVE(rinfo_list, re, entry); + free(re); + } +} + +void +seq_addr_list_clr(struct seq_addr_head *seq_addr_list) +{ + struct seq_addr_entry *sa; + + while ((sa = TAILQ_FIRST(seq_addr_list)) != NULL) { + TAILQ_REMOVE(seq_addr_list, sa, entry); + free(sa); + } +} + +void +eigrpe_orig_local_route(struct eigrp_iface *ei, struct if_addr *if_addr, + int withdraw) +{ + struct rinfo rinfo; + + memset(&rinfo, 0, sizeof(rinfo)); + rinfo.af = if_addr->af; + rinfo.type = EIGRP_ROUTE_INTERNAL; + memcpy(&rinfo.prefix, &if_addr->addr, sizeof(rinfo.prefix)); + rinfo.prefixlen = if_addr->prefixlen; + + eigrp_applymask(rinfo.af, &rinfo.prefix, &rinfo.prefix, + rinfo.prefixlen); + + if (withdraw) + rinfo.metric.delay = EIGRP_INFINITE_METRIC; + else + rinfo.metric.delay = eigrp_composite_delay(ei->delay); + rinfo.metric.bandwidth = eigrp_composite_bandwidth(ei->bandwidth); + metric_encode_mtu(rinfo.metric.mtu, ei->iface->mtu); + rinfo.metric.hop_count = 0; + rinfo.metric.reliability = DEFAULT_RELIABILITY; + rinfo.metric.load = DEFAULT_LOAD; + rinfo.metric.tag = 0; + rinfo.metric.flags = 0; + + eigrpe_imsg_compose_rde(IMSG_RECV_UPDATE, ei->self->peerid, 0, + &rinfo, sizeof(rinfo)); +} + +void +eigrpe_iface_ctl(struct ctl_conn *c, unsigned int idx) +{ + struct eigrp *eigrp; + struct eigrp_iface *ei; + struct ctl_iface *ictl; + + TAILQ_FOREACH(eigrp, &econf->instances, entry) { + TAILQ_FOREACH(ei, &eigrp->ei_list, e_entry) { + if (idx == 0 || idx == ei->iface->ifindex) { + ictl = if_to_ctl(ei); + imsg_compose_event(&c->iev, + IMSG_CTL_SHOW_INTERFACE, 0, 0, -1, + ictl, sizeof(struct ctl_iface)); + } + } + } +} + +void +eigrpe_nbr_ctl(struct ctl_conn *c) +{ + struct eigrp *eigrp; + struct nbr *nbr; + struct ctl_nbr *nctl; + + TAILQ_FOREACH(eigrp, &econf->instances, entry) { + RB_FOREACH(nbr, nbr_addr_head, &eigrp->nbrs) { + if (nbr->flags & (F_EIGRP_NBR_PENDING|F_EIGRP_NBR_SELF)) + continue; + + nctl = nbr_to_ctl(nbr); + imsg_compose_event(&c->iev, IMSG_CTL_SHOW_NBR, 0, + 0, -1, nctl, sizeof(struct ctl_nbr)); + } + } + + imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0); +} diff --git a/usr.sbin/eigrpd/eigrpe.h b/usr.sbin/eigrpd/eigrpe.h new file mode 100644 index 00000000000..b7e43547a73 --- /dev/null +++ b/usr.sbin/eigrpd/eigrpe.h @@ -0,0 +1,200 @@ +/* $OpenBSD: eigrpe.h,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@openbsd.org> + * 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. + */ + +#ifndef _EIGRPE_H_ +#define _EIGRPE_H_ + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/ip.h> + +TAILQ_HEAD(ctl_conns, ctl_conn) ctl_conns; + +struct pbuf { + struct ibuf *buf; + int refcnt; +}; + +struct packet { + TAILQ_ENTRY(packet) entry; + struct nbr *nbr; + uint32_t seq_num; + struct pbuf *pbuf; + struct event ev_timeout; + int attempts; +}; + +struct nbr { + RB_ENTRY(nbr) addr_tree, pid_tree; + TAILQ_ENTRY(nbr) entry; + struct event ev_ack; + struct event ev_hello_timeout; + struct eigrp_iface *ei; + union eigrpd_addr addr; + uint32_t peerid; + time_t uptime; + uint16_t hello_holdtime; + uint8_t flags; + + struct rinfo_head update_list; /* unicast updates */ + struct rinfo_head query_list; /* unicast queries */ + struct rinfo_head reply_list; /* unicast replies */ + + /* RTP */ + uint32_t recv_seq; + uint32_t next_mcast_seq; + TAILQ_HEAD(, packet) retrans_list; +}; +#define F_EIGRP_NBR_SELF 0x01 +#define F_EIGRP_NBR_PENDING 0x02 +#define F_EIGRP_NBR_CR_MODE 0x04 + +#define PREFIX_SIZE4(x) (((x - 1) / 8) + 1) +#define PREFIX_SIZE6(x) ((x == 128) ? 16 : ((x / 8) + 1)) + +/* eigrpe.c */ +pid_t eigrpe(struct eigrpd_conf *, int[2], int[2], int[2]); +void eigrpe_dispatch_main(int, short, void *); +void eigrpe_dispatch_rde(int, short, void *); +int eigrpe_imsg_compose_parent(int, pid_t, void *, uint16_t); +int eigrpe_imsg_compose_rde(int, uint32_t, pid_t, void *, + uint16_t); +void eigrpe_instance_init(struct eigrp *); +void eigrpe_instance_del(struct eigrp *); +void message_add(struct rinfo_head *, struct rinfo *); +void message_list_clr(struct rinfo_head *); +void seq_addr_list_clr(struct seq_addr_head *); +void eigrpe_orig_local_route(struct eigrp_iface *, + struct if_addr *, int); +void eigrpe_iface_ctl(struct ctl_conn *, unsigned int); +void eigrpe_nbr_ctl(struct ctl_conn *); + +/* interface.c */ +struct iface *if_new(struct eigrpd_conf *, struct kif *); +void if_del(struct iface *); +void if_init(struct eigrpd_conf *, struct iface *); +struct iface *if_lookup(struct eigrpd_conf *, unsigned int); +struct if_addr *if_addr_new(struct iface *, struct kaddr *); +void if_addr_del(struct iface *, struct kaddr *); +struct if_addr *if_addr_lookup(struct if_addr_head *, struct kaddr *); +in_addr_t if_primary_addr(struct iface *); +uint8_t if_primary_addr_prefixlen(struct iface *); +void if_update(struct iface *, int); +struct eigrp_iface *eigrp_if_new(struct eigrpd_conf *, struct eigrp *, + struct kif *); +void eigrp_if_del(struct eigrp_iface *); +void eigrp_if_start(struct eigrp_iface *); +void eigrp_if_reset(struct eigrp_iface *); +struct eigrp_iface *eigrp_if_lookup(struct iface *, int, uint16_t); +struct eigrp_iface *eigrp_iface_find_id(uint32_t); +struct ctl_iface *if_to_ctl(struct eigrp_iface *); +void if_set_sockbuf(int); +int if_join_ipv4_group(struct iface *, struct in_addr *); +int if_leave_ipv4_group(struct iface *, struct in_addr *); +int if_set_ipv4_mcast_ttl(int, uint8_t); +int if_set_ipv4_mcast(struct iface *); +int if_set_ipv4_mcast_loop(int); +int if_set_ipv4_recvif(int, int); +int if_set_ipv4_hdrincl(int); +int if_join_ipv6_group(struct iface *, struct in6_addr *); +int if_leave_ipv6_group(struct iface *, struct in6_addr *); +int if_set_ipv6_mcast(struct iface *); +int if_set_ipv6_mcast_loop(int); +int if_set_ipv6_pktinfo(int, int); +int if_set_ipv6_dscp(int, int); + +/* neighbor.c */ +struct nbr *nbr_new(struct eigrp_iface *, union eigrpd_addr *, + uint16_t, int); +void nbr_init(struct nbr *); +void nbr_del(struct nbr *); +void nbr_update_peerid(struct nbr *); +struct nbr *nbr_find(struct eigrp_iface *, union eigrpd_addr *); +struct nbr *nbr_find_peerid(uint32_t); +struct ctl_nbr *nbr_to_ctl(struct nbr *); +void nbr_timeout(int, short, void *); +void nbr_start_timeout(struct nbr *); +void nbr_stop_timeout(struct nbr *); + +/* rtp.c */ +struct pbuf *rtp_buf_new(struct ibuf *); +struct pbuf *rtp_buf_hold(struct pbuf *); +void rtp_buf_release(struct pbuf *); +struct packet *rtp_packet_new(struct nbr *, uint32_t, struct pbuf *); +void rtp_packet_del(struct packet *); +void rtp_process_ack(struct nbr *, uint32_t); +void rtp_send_packet(struct packet *); +void rtp_enqueue_packet(struct packet *); +void rtp_send_ucast(struct nbr *, struct ibuf *); +void rtp_send_mcast(struct eigrp_iface *, struct ibuf *); +void rtp_send(struct eigrp_iface *, struct nbr *, struct ibuf *); +void rtp_send_ack(struct nbr *); +void rtp_ack_timer(int, short, void *); +void rtp_ack_start_timer(struct nbr *); +void rtp_ack_stop_timer(struct nbr *); + +/* packet.c */ +int gen_eigrp_hdr(struct ibuf *, uint16_t, uint8_t, uint32_t, + uint16_t); +int send_packet(struct eigrp_iface *, struct nbr *, uint32_t, + struct ibuf *); +void recv_packet_v4(int, short, void *); +void recv_packet_v6(int, short, void *); + +/* tlv.c */ +int gen_parameter_tlv(struct ibuf *, struct eigrp_iface *); +int gen_sequence_tlv(struct ibuf *, + struct seq_addr_head *); +int gen_sw_version_tlv(struct ibuf *); +int gen_mcast_seq_tlv(struct ibuf *, uint32_t); +uint16_t len_route_tlv(struct rinfo *); +int gen_route_tlv(struct ibuf *, struct rinfo *); +struct tlv_parameter *tlv_decode_parameter(struct tlv *, char *); +int tlv_decode_seq(struct tlv *, char *, + struct seq_addr_head *); +struct tlv_sw_version *tlv_decode_sw_version(struct tlv *, char *); +struct tlv_mcast_seq *tlv_decode_mcast_seq(struct tlv *, char *); +int tlv_decode_route(int, enum route_type, struct tlv *, + char *, struct rinfo *); +void metric_encode_mtu(uint8_t *, int); +int metric_decode_mtu(uint8_t *); + +/* hello.c */ +void send_hello(struct eigrp_iface *, struct seq_addr_head *, uint32_t); +void recv_hello(struct eigrp_iface *, union eigrpd_addr *, struct nbr *, + struct tlv_parameter *); + +/* update.c */ +void send_update(struct eigrp_iface *, struct nbr *, uint32_t, int, + struct rinfo_head *); +void recv_update(struct nbr *, struct rinfo_head *, uint32_t); + +/* query.c */ +void send_query(struct eigrp_iface *, struct nbr *, struct rinfo_head *, + int); +void recv_query(struct nbr *, struct rinfo_head *, int); + +/* reply.c */ +void send_reply(struct nbr *, struct rinfo_head *, int); +void recv_reply(struct nbr *, struct rinfo_head *, int); + +char *pkt_ptr; /* packet buffer */ + +#endif /* _EIGRPE_H_ */ diff --git a/usr.sbin/eigrpd/hello.c b/usr.sbin/eigrpd/hello.c new file mode 100644 index 00000000000..401e5b4f6b2 --- /dev/null +++ b/usr.sbin/eigrpd/hello.c @@ -0,0 +1,102 @@ +/* $OpenBSD: hello.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@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 <string.h> +#include <arpa/inet.h> + +#include "eigrpd.h" +#include "eigrp.h" +#include "log.h" +#include "eigrpe.h" + +/* hello packet handling */ + +void +send_hello(struct eigrp_iface *ei, struct seq_addr_head *seq_addr_list, + uint32_t mcast_seq) +{ + struct eigrp *eigrp = ei->eigrp; + struct ibuf *buf; + uint8_t flags = 0; + + if ((buf = ibuf_dynamic(PKG_DEF_SIZE, + IP_MAXPACKET - sizeof(struct ip))) == NULL) + fatal("send_hello"); + + /* EIGRP header */ + if (gen_eigrp_hdr(buf, EIGRP_OPC_HELLO, flags, 0, eigrp->as)) + goto fail; + + if (gen_parameter_tlv(buf, ei)) + goto fail; + + if (gen_sw_version_tlv(buf)) + goto fail; + + if (seq_addr_list && !TAILQ_EMPTY(seq_addr_list) && + gen_sequence_tlv(buf, seq_addr_list)) + goto fail; + + if (mcast_seq && gen_mcast_seq_tlv(buf, mcast_seq)) + goto fail; + + /* send unreliably */ + send_packet(ei, NULL, 0, buf); + ibuf_free(buf); + return; +fail: + log_warnx("%s: failed to send message", __func__); + ibuf_free(buf); +} + +void +recv_hello(struct eigrp_iface *ei, union eigrpd_addr *src, struct nbr *nbr, + struct tlv_parameter *tp) +{ + /* + * Some old Cisco routers seems to use the "parameters tlv" with all + * K-values set to 255 (except k6) to inform that the neighbor has been + * reset. The "peer termination" tlv described in the draft for the same + * purpose is probably something introduced recently. Let's check for + * this case to maintain backward compatibility. + */ + if (tp->kvalues[0] == 255 && tp->kvalues[1] == 255 && + tp->kvalues[2] == 255 && tp->kvalues[3] == 255 && + tp->kvalues[4] == 255 && tp->kvalues[5] == 0) { + if (nbr) { + log_debug("%s: peer temination", __func__); + nbr_del(nbr); + } + return; + } + + if (nbr == NULL) { + if (memcmp(ei->eigrp->kvalues, tp->kvalues, 6) != 0) { + log_debug("%s: k-values don't match (nbr %s)", + __func__, log_addr(ei->eigrp->af, src)); + return; + } + + nbr = nbr_new(ei, src, ntohs(tp->holdtime), 0); + + /* send an expedited hello */ + send_hello(ei, NULL, 0); + + send_update(nbr->ei, nbr, EIGRP_HDR_FLAG_INIT, 0, NULL); + } +} diff --git a/usr.sbin/eigrpd/in_cksum.c b/usr.sbin/eigrpd/in_cksum.c new file mode 100644 index 00000000000..daeb3f92ea0 --- /dev/null +++ b/usr.sbin/eigrpd/in_cksum.c @@ -0,0 +1,82 @@ +/* $OpenBSD: in_cksum.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ +/* $NetBSD: in_cksum.c,v 1.3 1995/04/22 13:53:48 cgd Exp $ */ + +/* + * Copyright (c) 1992 Regents of the University of California. + * All rights reserved. + * + * This software was developed by the Computer Systems Engineering group + * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and + * contributed to Berkeley. + * + * 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. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 THE REGENTS 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. + * + * @(#) Header: in_cksum.c,v 1.1 92/09/11 01:15:55 leres Exp (LBL) + */ + +#include <sys/types.h> + +#include "eigrpd.h" +#include "log.h" + +/* + * Checksum routine for Internet Protocol family headers. + * This routine is very heavily used in the network + * code and should be modified for each CPU to be as fast as possible. + * In particular, it should not be this one. + */ +uint16_t +in_cksum(void *p, size_t l) +{ + unsigned int sum = 0; + int len; + unsigned char *cp = p; + + /* ensure that < 2^16 bytes being summed */ + if (l >= (1 << 16)) + fatalx("in_cksum: packet too big"); + len = (int)l; + + if (((long)cp & 1) == 0) { + while (len > 1) { + sum += htons(*(unsigned short *)cp); + cp += 2; + len -= 2; + } + } else { + while (len > 1) { + sum += *cp++ << 8; + sum += *cp++; + len -= 2; + } + } + if (len == 1) + sum += *cp << 8; + + sum = (sum >> 16) + (sum & 0xffff); /* add in accumulated carries */ + sum += sum >> 16; /* add potential last carry */ + sum = ntohs(sum); + return (0xffff & ~sum); +} diff --git a/usr.sbin/eigrpd/interface.c b/usr.sbin/eigrpd/interface.c new file mode 100644 index 00000000000..087dc764ba6 --- /dev/null +++ b/usr.sbin/eigrpd/interface.c @@ -0,0 +1,753 @@ +/* $OpenBSD: interface.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@openbsd.org> + * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org> + * 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 <stdlib.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <ctype.h> +#include <err.h> +#include <string.h> + +#include "eigrpd.h" +#include "eigrp.h" +#include "log.h" +#include "eigrpe.h" + +extern struct eigrpd_conf *econf; + +void eigrp_if_hello_timer(int, short, void *); +void eigrp_if_start_hello_timer(struct eigrp_iface *); +void eigrp_if_stop_hello_timer(struct eigrp_iface *); + +static __inline int iface_id_compare(struct eigrp_iface *, + struct eigrp_iface *); + +RB_HEAD(iface_id_head, eigrp_iface); +RB_PROTOTYPE(iface_id_head, eigrp_iface, id_tree, iface_id_compare) +RB_GENERATE(iface_id_head, eigrp_iface, id_tree, iface_id_compare) + +static __inline int +iface_id_compare(struct eigrp_iface *a, struct eigrp_iface *b) +{ + return (a->ifaceid - b->ifaceid); +} + +struct iface_id_head ifaces_by_id = RB_INITIALIZER(&ifaces_by_id); + +uint32_t ifacecnt = 1; + +struct iface * +if_new(struct eigrpd_conf *xconf, struct kif *kif) +{ + struct iface *iface; + + if ((iface = calloc(1, sizeof(*iface))) == NULL) + err(1, "if_new: calloc"); + + TAILQ_INIT(&iface->ei_list); + TAILQ_INIT(&iface->addr_list); + + strlcpy(iface->name, kif->ifname, sizeof(iface->name)); + + /* get type */ + if (kif->flags & IFF_POINTOPOINT) + iface->type = IF_TYPE_POINTOPOINT; + if (kif->flags & IFF_BROADCAST && + kif->flags & IFF_MULTICAST) + iface->type = IF_TYPE_BROADCAST; + if (kif->flags & IFF_LOOPBACK) + iface->type = IF_TYPE_POINTOPOINT; + + /* get index and flags */ + iface->mtu = kif->mtu; + iface->ifindex = kif->ifindex; + iface->flags = kif->flags; + iface->linkstate = kif->link_state; + iface->if_type = kif->if_type; + iface->baudrate = kif->baudrate; + + TAILQ_INSERT_TAIL(&xconf->iface_list, iface, entry); + + return (iface); +} + +void +if_del(struct iface *iface) +{ + struct eigrp_iface *ei; + struct if_addr *if_addr; + + log_debug("%s: interface %s", __func__, iface->name); + + while ((ei = TAILQ_FIRST(&iface->ei_list)) != NULL) + eigrp_if_del(ei); + + while ((if_addr = TAILQ_FIRST(&iface->addr_list)) != NULL) { + TAILQ_REMOVE(&iface->addr_list, if_addr, entry); + free(if_addr); + } + + TAILQ_REMOVE(&econf->iface_list, iface, entry); + free(iface); +} + +void +if_init(struct eigrpd_conf *xconf, struct iface *iface) +{ + struct ifreq ifr; + unsigned int rdomain; + struct eigrp_iface *ei; + union eigrpd_addr addr; + + memset(&addr, 0, sizeof(addr)); + TAILQ_FOREACH(ei, &iface->ei_list, i_entry) { + /* init the dummy self neighbor */ + ei->self = nbr_new(ei, &addr, 0, 1); + nbr_init(ei->self); + + /* set event handlers for interface */ + evtimer_set(&ei->hello_timer, eigrp_if_hello_timer, ei); + } + + /* set rdomain */ + strlcpy(ifr.ifr_name, iface->name, sizeof(ifr.ifr_name)); + if (ioctl(econf->eigrp_socket_v4, SIOCGIFRDOMAIN, (caddr_t)&ifr) == -1) + rdomain = 0; + else { + rdomain = ifr.ifr_rdomainid; + if (setsockopt(econf->eigrp_socket_v4, SOL_SOCKET, SO_RTABLE, + &rdomain, sizeof(rdomain)) == -1) + fatal("failed to set rdomain"); + } + if (rdomain != xconf->rdomain) + fatalx("interface rdomain mismatch"); +} + +struct iface * +if_lookup(struct eigrpd_conf *xconf, unsigned int ifindex) +{ + struct iface *iface; + + TAILQ_FOREACH(iface, &xconf->iface_list, entry) + if (iface->ifindex == ifindex) + return (iface); + + return (NULL); +} + +struct if_addr * +if_addr_new(struct iface *iface, struct kaddr *kaddr) +{ + struct if_addr *if_addr; + struct eigrp_iface *ei; + + if (if_addr_lookup(&iface->addr_list, kaddr) != NULL) + return (NULL); + + if ((if_addr = calloc(1, sizeof(*if_addr))) == NULL) + fatal("if_addr_new"); + + if_addr->af = kaddr->af; + memcpy(&if_addr->addr, &kaddr->addr, sizeof(if_addr->addr)); + if_addr->prefixlen = kaddr->prefixlen; + memcpy(&if_addr->dstbrd, &kaddr->dstbrd, sizeof(if_addr->dstbrd)); + + TAILQ_INSERT_TAIL(&iface->addr_list, if_addr, entry); + + TAILQ_FOREACH(ei, &iface->ei_list, i_entry) + if (ei->state == IF_STA_ACTIVE && ei->eigrp->af == if_addr->af) + eigrpe_orig_local_route(ei, if_addr, 0); + + if_update(iface, if_addr->af); + + return (if_addr); +} + +void +if_addr_del(struct iface *iface, struct kaddr *kaddr) +{ + struct if_addr *if_addr; + struct eigrp_iface *ei; + + if_addr = if_addr_lookup(&iface->addr_list, kaddr); + if (if_addr == NULL) + return; + + TAILQ_FOREACH(ei, &iface->ei_list, i_entry) + if (ei->state == IF_STA_ACTIVE && ei->eigrp->af == if_addr->af) + eigrpe_orig_local_route(ei, if_addr, 1); + + TAILQ_REMOVE(&iface->addr_list, if_addr, entry); + if_update(iface, if_addr->af); + free(if_addr); +} + +struct if_addr * +if_addr_lookup(struct if_addr_head *addr_list, struct kaddr *kaddr) +{ + struct if_addr *if_addr; + int af = kaddr->af; + + TAILQ_FOREACH(if_addr, addr_list, entry) + if (!eigrp_addrcmp(af, &if_addr->addr, &kaddr->addr) && + if_addr->prefixlen == kaddr->prefixlen && + !eigrp_addrcmp(af, &if_addr->dstbrd, &kaddr->dstbrd)) + return (if_addr); + + return (NULL); +} + +in_addr_t +if_primary_addr(struct iface *iface) +{ + struct if_addr *if_addr; + + TAILQ_FOREACH(if_addr, &iface->addr_list, entry) + if (if_addr->af == AF_INET) + return (if_addr->addr.v4.s_addr); + + return (INADDR_ANY); +} + +uint8_t +if_primary_addr_prefixlen(struct iface *iface) +{ + struct if_addr *if_addr; + + TAILQ_FOREACH(if_addr, &iface->addr_list, entry) + if (if_addr->af == AF_INET) + return (if_addr->prefixlen); + + return (0); +} + +/* up/down events */ +void +if_update(struct iface *iface, int af) +{ + struct eigrp_iface *ei; + int link_ok; + + link_ok = (iface->flags & IFF_UP) && + LINK_STATE_IS_UP(iface->linkstate); + + TAILQ_FOREACH(ei, &iface->ei_list, i_entry) { + if (af != AF_UNSPEC && ei->eigrp->af != af) + continue; + + if (ei->state == IF_STA_DOWN) { + if (!link_ok) + continue; + if (af == AF_INET && TAILQ_EMPTY(&iface->addr_list)) + continue; + ei->state = IF_STA_ACTIVE; + eigrp_if_start(ei); + } else { + if (link_ok) + continue; + if (!(af == AF_INET && TAILQ_EMPTY(&iface->addr_list))) + continue; + ei->state = IF_STA_DOWN; + eigrp_if_reset(ei); + } + } +} + +struct eigrp_iface * +eigrp_if_new(struct eigrpd_conf *xconf, struct eigrp *eigrp, struct kif *kif) +{ + struct iface *iface; + struct eigrp_iface *ei; + struct timeval now; + + iface = if_lookup(xconf, kif->ifindex); + if (iface == NULL) + iface = if_new(xconf, kif); + + if ((ei = calloc(1, sizeof(*ei))) == NULL) + err(1, "eigrp_if_new: calloc"); + + ei->state = IF_STA_DOWN; + /* get next unused ifaceid */ + while (eigrp_iface_find_id(ifacecnt++)) + ; + ei->ifaceid = ifacecnt; + ei->eigrp = eigrp; + ei->iface = iface; + if (ei->iface->flags & IFF_LOOPBACK) + ei->passive = 1; + + gettimeofday(&now, NULL); + ei->uptime = now.tv_sec; + + TAILQ_INIT(&ei->nbr_list); + TAILQ_INIT(&ei->update_list); + TAILQ_INIT(&ei->query_list); + TAILQ_INSERT_TAIL(&iface->ei_list, ei, i_entry); + TAILQ_INSERT_TAIL(&eigrp->ei_list, ei, e_entry); + if (RB_INSERT(iface_id_head, &ifaces_by_id, ei) != NULL) + fatalx("eigrp_if_new: RB_INSERT(ifaces_by_id) failed"); + + return (ei); +} + +void +eigrp_if_del(struct eigrp_iface *ei) +{ + struct nbr *nbr; + + RB_REMOVE(iface_id_head, &ifaces_by_id, ei); + TAILQ_REMOVE(&ei->eigrp->ei_list, ei, e_entry); + TAILQ_REMOVE(&ei->iface->ei_list, ei, i_entry); + + if (ei->state == IF_STA_ACTIVE) + eigrp_if_reset(ei); + + while ((nbr = TAILQ_FIRST(&ei->nbr_list)) != NULL) + nbr_del(nbr); + + if (TAILQ_EMPTY(&ei->iface->ei_list)) + if_del(ei->iface); + + free(ei); +} + +void +eigrp_if_start(struct eigrp_iface *ei) +{ + struct eigrp *eigrp = ei->eigrp; + struct if_addr *if_addr; + struct in_addr addr4; + struct in6_addr addr6; + + log_debug("%s: %s as %u family %s", __func__, ei->iface->name, + eigrp->as, af_name(eigrp->af)); + + TAILQ_FOREACH(if_addr, &ei->iface->addr_list, entry) { + if (if_addr->af != eigrp->af) + continue; + + eigrpe_orig_local_route(ei, if_addr, 0); + } + + if (ei->passive) + return; + + switch (eigrp->af) { + case AF_INET: + addr4.s_addr = AllEIGRPRouters_v4; + if (if_join_ipv4_group(ei->iface, &addr4)) + return; + break; + case AF_INET6: + inet_pton(AF_INET6, AllEIGRPRouters_v6, &addr6); + if (if_join_ipv6_group(ei->iface, &addr6)) + return; + break; + default: + break; + } + + eigrp_if_start_hello_timer(ei); +} + +void +eigrp_if_reset(struct eigrp_iface *ei) +{ + struct eigrp *eigrp = ei->eigrp; + struct in_addr addr4; + struct in6_addr addr6; + + log_debug("%s: %s as %u family %s", __func__, ei->iface->name, + eigrp->as, af_name(eigrp->af)); + + /* the rde will withdraw the connected route for us */ + + if (ei->passive) + return; + + /* try to cleanup */ + switch (eigrp->af) { + case AF_INET: + addr4.s_addr = AllEIGRPRouters_v4; + if_leave_ipv4_group(ei->iface, &addr4); + break; + case AF_INET6: + inet_pton(AF_INET6, AllEIGRPRouters_v6, &addr6); + if_leave_ipv6_group(ei->iface, &addr6); + break; + default: + break; + } + + eigrp_if_stop_hello_timer(ei); +} + +struct eigrp_iface * +eigrp_iface_find_id(uint32_t ifaceid) +{ + struct eigrp_iface e; + e.ifaceid = ifaceid; + return (RB_FIND(iface_id_head, &ifaces_by_id, &e)); +} + +struct eigrp_iface * +eigrp_if_lookup(struct iface *iface, int af, uint16_t as) +{ + struct eigrp_iface *ei; + + TAILQ_FOREACH(ei, &iface->ei_list, i_entry) + if (ei->eigrp->af == af && + ei->eigrp->as == as) + return (ei); + + return (NULL); +} + +/* timers */ +/* ARGSUSED */ +void +eigrp_if_hello_timer(int fd, short event, void *arg) +{ + struct eigrp_iface *ei = arg; + struct timeval tv; + + send_hello(ei, NULL, 0); + + /* reschedule hello_timer */ + timerclear(&tv); + tv.tv_sec = ei->hello_interval; + if (evtimer_add(&ei->hello_timer, &tv) == -1) + fatal("eigrp_if_hello_timer"); +} + +void +eigrp_if_start_hello_timer(struct eigrp_iface *ei) +{ + struct timeval tv; + + timerclear(&tv); + tv.tv_sec = ei->hello_interval; + if (evtimer_add(&ei->hello_timer, &tv) == -1) + fatal("eigrp_if_start_hello_timer"); +} + +void +eigrp_if_stop_hello_timer(struct eigrp_iface *ei) +{ + if (evtimer_pending(&ei->hello_timer, NULL) && + evtimer_del(&ei->hello_timer) == -1) + fatal("eigrp_if_stop_hello_timer"); +} + +struct ctl_iface * +if_to_ctl(struct eigrp_iface *ei) +{ + static struct ctl_iface ictl; + struct timeval now; + struct nbr *nbr; + + ictl.af = ei->eigrp->af; + ictl.as = ei->eigrp->as; + memcpy(ictl.name, ei->iface->name, sizeof(ictl.name)); + ictl.ifindex = ei->iface->ifindex; + switch (ei->eigrp->af) { + case AF_INET: + ictl.addr.v4.s_addr = if_primary_addr(ei->iface); + ictl.prefixlen = if_primary_addr_prefixlen(ei->iface); + break; + case AF_INET6: + memcpy(&ictl.addr.v6, &ei->iface->linklocal, + sizeof(ictl.addr.v6)); + ictl.prefixlen = 64; + break; + default: + break; + } + ictl.flags = ei->iface->flags; + ictl.linkstate = ei->iface->linkstate; + ictl.mtu = ei->iface->mtu; + ictl.type = ei->iface->type; + ictl.if_type = ei->iface->if_type; + ictl.baudrate = ei->iface->baudrate; + ictl.delay = ei->delay; + ictl.bandwidth = ei->bandwidth; + ictl.hello_holdtime = ei->hello_holdtime; + ictl.hello_interval = ei->hello_interval; + ictl.splithorizon = ei->splithorizon; + ictl.passive = ei->passive; + ictl.nbr_cnt = 0; + + gettimeofday(&now, NULL); + if (ei->state != IF_STA_DOWN && ei->uptime != 0) + ictl.uptime = now.tv_sec - ei->uptime; + else + ictl.uptime = 0; + + TAILQ_FOREACH(nbr, &ei->nbr_list, entry) + if (!(nbr->flags & (F_EIGRP_NBR_PENDING|F_EIGRP_NBR_SELF))) + ictl.nbr_cnt++; + + return (&ictl); +} + +/* misc */ +void +if_set_sockbuf(int fd) +{ + int bsize; + + bsize = 65535; + while (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bsize, + sizeof(bsize)) == -1) + bsize /= 2; + + if (bsize != 65535) + log_warnx("%s: recvbuf size only %d", __func__, bsize); + + bsize = 65535; + while (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &bsize, + sizeof(bsize)) == -1) + bsize /= 2; + + if (bsize != 65535) + log_warnx("%s: sendbuf size only %d", __func__, bsize); +} + +int +if_join_ipv4_group(struct iface *iface, struct in_addr *addr) +{ + struct ip_mreq mreq; + + if (iface->group_count_v4++ != 0) + /* already joined */ + return (0); + + log_debug("%s: interface %s addr %s", __func__, iface->name, + inet_ntoa(*addr)); + + mreq.imr_multiaddr.s_addr = addr->s_addr; + mreq.imr_interface.s_addr = if_primary_addr(iface); + + if (setsockopt(econf->eigrp_socket_v4, IPPROTO_IP, IP_ADD_MEMBERSHIP, + (void *)&mreq, sizeof(mreq)) < 0) { + log_warn("%s: error IP_ADD_MEMBERSHIP, interface %s address %s", + __func__, iface->name, inet_ntoa(*addr)); + return (-1); + } + + return (0); +} + +int +if_leave_ipv4_group(struct iface *iface, struct in_addr *addr) +{ + struct ip_mreq mreq; + + if (--iface->group_count_v4 != 0) + /* others still joined */ + return (0); + + log_debug("%s: interface %s addr %s", __func__, iface->name, + inet_ntoa(*addr)); + + mreq.imr_multiaddr.s_addr = addr->s_addr; + mreq.imr_interface.s_addr = if_primary_addr(iface); + + if (setsockopt(econf->eigrp_socket_v4, IPPROTO_IP, IP_DROP_MEMBERSHIP, + (void *)&mreq, sizeof(mreq)) < 0) { + log_warn("%s: error IP_DROP_MEMBERSHIP, interface %s " + "address %s", iface->name, __func__, inet_ntoa(*addr)); + return (-1); + } + + return (0); +} + +int +if_set_ipv4_mcast_ttl(int fd, uint8_t ttl) +{ + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, + (char *)&ttl, sizeof(ttl)) < 0) { + log_warn("%s: error setting IP_MULTICAST_TTL to %d", + __func__, ttl); + return (-1); + } + + return (0); +} + +int +if_set_ipv4_mcast(struct iface *iface) +{ + in_addr_t addr; + + addr = if_primary_addr(iface); + + if (setsockopt(econf->eigrp_socket_v4, IPPROTO_IP, IP_MULTICAST_IF, + &addr, sizeof(addr)) < 0) { + log_warn("%s: error setting IP_MULTICAST_IF, interface %s", + __func__, iface->name); + return (-1); + } + + return (0); +} + +int +if_set_ipv4_mcast_loop(int fd) +{ + uint8_t loop = 0; + + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, + (char *)&loop, sizeof(loop)) < 0) { + log_warn("%s: error setting IP_MULTICAST_LOOP", __func__); + return (-1); + } + + return (0); +} + +int +if_set_ipv4_recvif(int fd, int enable) +{ + if (setsockopt(fd, IPPROTO_IP, IP_RECVIF, &enable, + sizeof(enable)) < 0) { + log_warn("%s: error setting IP_RECVIF", __func__); + return (-1); + } + return (0); +} + +int +if_set_ipv4_hdrincl(int fd) +{ + int hincl = 1; + + if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &hincl, sizeof(hincl)) < 0) { + log_warn("%s: error setting IP_HDRINCL", __func__); + return (-1); + } + + return (0); +} + +int +if_join_ipv6_group(struct iface *iface, struct in6_addr *addr) +{ + struct ipv6_mreq mreq; + + if (iface->group_count_v6++ != 0) + /* already joined */ + return (0); + + log_debug("%s: interface %s addr %s", __func__, iface->name, + log_in6addr(addr)); + + mreq.ipv6mr_multiaddr = *addr; + mreq.ipv6mr_interface = iface->ifindex; + + if (setsockopt(econf->eigrp_socket_v6, IPPROTO_IPV6, IPV6_JOIN_GROUP, + &mreq, sizeof(mreq)) < 0) { + log_warn("%s: error IPV6_JOIN_GROUP, interface %s address %s", + __func__, iface->name, log_in6addr(addr)); + return (-1); + } + + return (0); +} + +int +if_leave_ipv6_group(struct iface *iface, struct in6_addr *addr) +{ + struct ipv6_mreq mreq; + + if (--iface->group_count_v6 != 0) + /* others still joined */ + return (0); + + log_debug("%s: interface %s addr %s", __func__, iface->name, + log_in6addr(addr)); + + mreq.ipv6mr_multiaddr = *addr; + mreq.ipv6mr_interface = iface->ifindex; + + if (setsockopt(econf->eigrp_socket_v6, IPPROTO_IPV6, IPV6_LEAVE_GROUP, + (void *)&mreq, sizeof(mreq)) < 0) { + log_warn("%s: error IPV6_LEAVE_GROUP, interface %s address %s", + __func__, iface->name, log_in6addr(addr)); + return (-1); + } + + return (0); +} + +int +if_set_ipv6_mcast(struct iface *iface) +{ + if (setsockopt(econf->eigrp_socket_v6, IPPROTO_IPV6, IPV6_MULTICAST_IF, + &iface->ifindex, sizeof(iface->ifindex)) < 0) { + log_debug("%s: error setting IPV6_MULTICAST_IF, interface %s", + __func__, iface->name); + return (-1); + } + + return (0); +} + +int +if_set_ipv6_mcast_loop(int fd) +{ + unsigned int loop = 0; + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, + (unsigned int *)&loop, sizeof(loop)) < 0) { + log_warn("%s: error setting IPV6_MULTICAST_LOOP", __func__); + return (-1); + } + + return (0); +} + +int +if_set_ipv6_pktinfo(int fd, int enable) +{ + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &enable, + sizeof(enable)) < 0) { + log_warn("%s: error setting IPV6_PKTINFO", __func__); + return (-1); + } + + return (0); +} + +int +if_set_ipv6_dscp(int fd, int dscp) +{ + if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &dscp, + sizeof(dscp)) < 0) { + log_warn("%s: error setting IPV6_TCLASS", __func__); + return (-1); + } + + return (0); +} diff --git a/usr.sbin/eigrpd/kroute.c b/usr.sbin/eigrpd/kroute.c new file mode 100644 index 00000000000..7d0c081b1eb --- /dev/null +++ b/usr.sbin/eigrpd/kroute.c @@ -0,0 +1,1588 @@ +/* $OpenBSD: kroute.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@openbsd.org> + * Copyright (c) 2004 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@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/sysctl.h> +#include <sys/tree.h> +#include <sys/uio.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> +#include <net/route.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +#include "eigrpd.h" +#include "log.h" + +extern struct eigrpd_conf *eigrpd_conf; + +struct { + uint32_t rtseq; + pid_t pid; + int fib_sync; + int fd; + struct event ev; + unsigned int rdomain; +} kr_state; + +struct kroute_node { + TAILQ_ENTRY(kroute_node) entry; + struct kroute_priority *kprio; /* back pointer */ + struct kroute r; +}; + +struct kroute_priority { + TAILQ_ENTRY(kroute_priority) entry; + struct kroute_prefix *kp; /* back pointer */ + uint8_t priority; + TAILQ_HEAD(, kroute_node) nexthops; +}; + +struct kroute_prefix { + RB_ENTRY(kroute_prefix) entry; + int af; + union eigrpd_addr prefix; + uint8_t prefixlen; + TAILQ_HEAD(plist, kroute_priority) priorities; +}; + +struct kif_addr { + TAILQ_ENTRY(kif_addr) entry; + struct kaddr a; +}; + +struct kif_node { + RB_ENTRY(kif_node) entry; + TAILQ_HEAD(, kif_addr) addrs; + struct kif k; +}; + +void kr_redist_remove(struct kroute *); +int kr_redist_eval(struct kroute *); +void kr_redistribute(struct kroute_prefix *); +int kroute_compare(struct kroute_prefix *, + struct kroute_prefix *); +struct kroute_prefix *kroute_find_prefix(int, union eigrpd_addr *, uint8_t); +struct kroute_priority *kroute_find_prio(struct kroute_prefix *, uint8_t); +struct kroute_node *kroute_find_gw(struct kroute_priority *, + union eigrpd_addr *); +struct kroute_node *kroute_insert(struct kroute *); +int kroute_remove(struct kroute *); +void kroute_clear(void); + +int kif_compare(struct kif_node *, struct kif_node *); +struct kif_node *kif_find(unsigned short); +struct kif_node *kif_insert(unsigned short); +int kif_remove(struct kif_node *); +struct kif *kif_update(unsigned short, int, struct if_data *, + struct sockaddr_dl *); +int kif_validate(unsigned short); + +void protect_lo(void); +uint8_t prefixlen_classful(in_addr_t); +void get_rtaddrs(int, struct sockaddr *, struct sockaddr **); +void if_change(unsigned short, int, struct if_data *, + struct sockaddr_dl *); +void if_newaddr(unsigned short, struct sockaddr *, struct sockaddr *, + struct sockaddr *); +void if_deladdr(unsigned short, struct sockaddr *, struct sockaddr *, + struct sockaddr *); +void if_announce(void *); + +int send_rtmsg(int, int, struct kroute *); +int dispatch_rtmsg(void); +int fetchtable(void); +int fetchifs(void); +int rtmsg_process(char *, size_t); +int rtmsg_process_route(struct rt_msghdr *, + struct sockaddr *[RTAX_MAX]); + +RB_HEAD(kroute_tree, kroute_prefix) krt; +RB_PROTOTYPE(kroute_tree, kroute_prefix, entry, kroute_compare) +RB_GENERATE(kroute_tree, kroute_prefix, entry, kroute_compare) + +RB_HEAD(kif_tree, kif_node) kit; +RB_PROTOTYPE(kif_tree, kif_node, entry, kif_compare) +RB_GENERATE(kif_tree, kif_node, entry, kif_compare) + +int +kif_init(void) +{ + RB_INIT(&kit); + kr_state.fib_sync = 0; /* decoupled */ + + if (fetchifs() == -1) + return (-1); + + return (0); +} + +int +kr_init(int fs, unsigned int rdomain) +{ + int opt = 0, rcvbuf, default_rcvbuf; + socklen_t optlen; + + kr_state.fib_sync = fs; + kr_state.rdomain = rdomain; + + if ((kr_state.fd = socket(AF_ROUTE, + SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) == -1) { + log_warn("%s: socket", __func__); + return (-1); + } + + /* not interested in my own messages */ + if (setsockopt(kr_state.fd, SOL_SOCKET, SO_USELOOPBACK, + &opt, sizeof(opt)) == -1) + log_warn("%s: setsockopt", __func__); /* not fatal */ + + /* grow receive buffer, don't wanna miss messages */ + optlen = sizeof(default_rcvbuf); + if (getsockopt(kr_state.fd, SOL_SOCKET, SO_RCVBUF, + &default_rcvbuf, &optlen) == -1) + log_warn("%s: getsockopt SOL_SOCKET SO_RCVBUF", __func__); + else + for (rcvbuf = MAX_RTSOCK_BUF; + rcvbuf > default_rcvbuf && + setsockopt(kr_state.fd, SOL_SOCKET, SO_RCVBUF, + &rcvbuf, sizeof(rcvbuf)) == -1 && errno == ENOBUFS; + rcvbuf /= 2) + ; /* nothing */ + + kr_state.pid = getpid(); + kr_state.rtseq = 1; + + if (fetchtable() == -1) + return (-1); + + protect_lo(); + + event_set(&kr_state.ev, kr_state.fd, EV_READ | EV_PERSIST, + kr_dispatch_msg, NULL); + event_add(&kr_state.ev, NULL); + + return (0); +} + +void +kif_redistribute(void) +{ + struct kif_node *kif; + struct kif_addr *ka; + + RB_FOREACH(kif, kif_tree, &kit) + TAILQ_FOREACH(ka, &kif->addrs, entry) + main_imsg_compose_eigrpe(IMSG_NEWADDR, 0, &ka->a, + sizeof(struct kaddr)); +} + +int +kr_change(struct kroute *kr) +{ + struct kroute_prefix *kp; + struct kroute_priority *kprio; + struct kroute_node *kn; + int action = RTM_ADD; + + kp = kroute_find_prefix(kr->af, &kr->prefix, kr->prefixlen); + if (kp == NULL) + kn = kroute_insert(kr); + else { + kprio = kroute_find_prio(kp, kr->priority); + if (kprio == NULL) + kn = kroute_insert(kr); + else { + kn = kroute_find_gw(kprio, &kr->nexthop); + if (kn == NULL) + kn = kroute_insert(kr); + else + action = RTM_CHANGE; + } + } + + /* send update */ + if (send_rtmsg(kr_state.fd, action, kr) == -1) + return (-1); + + kn->r.flags |= F_EIGRPD_INSERTED; + + return (0); +} + +int +kr_delete(struct kroute *kr) +{ + struct kroute_prefix *kp; + struct kroute_priority *kprio; + struct kroute_node *kn; + + kp = kroute_find_prefix(kr->af, &kr->prefix, kr->prefixlen); + if (kp == NULL) + return (0); + kprio = kroute_find_prio(kp, kr->priority); + if (kprio == NULL) + return (0); + kn = kroute_find_gw(kprio, &kr->nexthop); + if (kn == NULL) + return (0); + + if (!(kn->r.flags & F_EIGRPD_INSERTED)) + return (0); + + if (send_rtmsg(kr_state.fd, RTM_DELETE, &kn->r) == -1) + return (-1); + + if (kroute_remove(kr) == -1) + return (-1); + + return (0); +} + +void +kr_shutdown(void) +{ + kr_fib_decouple(); + kroute_clear(); + kif_clear(); +} + +void +kr_fib_couple(void) +{ + struct kroute_prefix *kp; + struct kroute_priority *kprio; + struct kroute_node *kn; + + if (kr_state.fib_sync == 1) /* already coupled */ + return; + + kr_state.fib_sync = 1; + + RB_FOREACH(kp, kroute_tree, &krt) + TAILQ_FOREACH(kprio, &kp->priorities, entry) + TAILQ_FOREACH(kn, &kprio->nexthops, entry) { + if (!(kn->r.flags & F_EIGRPD_INSERTED)) + continue; + send_rtmsg(kr_state.fd, RTM_ADD, &kn->r); + } + + log_info("kernel routing table coupled"); +} + +void +kr_fib_decouple(void) +{ + struct kroute_prefix *kp; + struct kroute_priority *kprio; + struct kroute_node *kn; + + if (kr_state.fib_sync == 0) /* already decoupled */ + return; + + RB_FOREACH(kp, kroute_tree, &krt) + TAILQ_FOREACH(kprio, &kp->priorities, entry) + TAILQ_FOREACH(kn, &kprio->nexthops, entry) { + if (!(kn->r.flags & F_EIGRPD_INSERTED)) + continue; + + send_rtmsg(kr_state.fd, RTM_DELETE, &kn->r); + } + + kr_state.fib_sync = 0; + + log_info("kernel routing table decoupled"); +} + +/* ARGSUSED */ +void +kr_dispatch_msg(int fd, short event, void *bula) +{ + if (dispatch_rtmsg() == -1) + event_loopexit(NULL); +} + +void +kr_show_route(struct imsg *imsg) +{ + struct kroute_prefix *kp; + struct kroute_priority *kprio; + struct kroute_node *kn; + struct kroute kr; + int flags; + + if (imsg->hdr.len != IMSG_HEADER_SIZE + sizeof(flags)) { + log_warnx("%s: wrong imsg len", __func__); + return; + } + memcpy(&flags, imsg->data, sizeof(flags)); + RB_FOREACH(kp, kroute_tree, &krt) + TAILQ_FOREACH(kprio, &kp->priorities, entry) + TAILQ_FOREACH(kn, &kprio->nexthops, entry) { + if (flags && !(kn->r.flags & flags)) + continue; + + memcpy(&kr, &kn->r, sizeof(kr)); + if (kr.priority == + eigrpd_conf->fib_priority_external) + kr.flags |= F_CTL_EXTERNAL; + main_imsg_compose_eigrpe(IMSG_CTL_KROUTE, + imsg->hdr.pid, &kr, sizeof(kr)); + } + + main_imsg_compose_eigrpe(IMSG_CTL_END, imsg->hdr.pid, NULL, 0); +} + +void +kr_ifinfo(char *ifname, pid_t pid) +{ + struct kif_node *kif; + + RB_FOREACH(kif, kif_tree, &kit) + if (ifname == NULL || !strcmp(ifname, kif->k.ifname)) { + main_imsg_compose_eigrpe(IMSG_CTL_IFINFO, + pid, &kif->k, sizeof(kif->k)); + } + + main_imsg_compose_eigrpe(IMSG_CTL_END, pid, NULL, 0); +} + +void +kr_redist_remove(struct kroute *kr) +{ + /* was the route redistributed? */ + if (!(kr->flags & F_REDISTRIBUTED)) + return; + + /* remove redistributed flag */ + kr->flags &= ~F_REDISTRIBUTED; + main_imsg_compose_rde(IMSG_NETWORK_DEL, 0, kr, sizeof(*kr)); +} + +int +kr_redist_eval(struct kroute *kr) +{ + in_addr_t a; + + /* Only non-eigrpd routes are considered for redistribution. */ + if (!(kr->flags & F_KERNEL)) + goto dont_redistribute; + + /* Dynamic routes are not redistributable. */ + if (kr->flags & F_DYNAMIC) + goto dont_redistribute; + + /* interface is not up and running so don't announce */ + if (kr->flags & F_DOWN) + goto dont_redistribute; + + /* filter-out non redistributable addresses */ + switch (kr->af) { + case AF_INET: + a = ntohl(kr->prefix.v4.s_addr); + if (IN_MULTICAST(a) || IN_BADCLASS(a) || + (a >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) + goto dont_redistribute; + + if (kr->nexthop.v4.s_addr == htonl(INADDR_LOOPBACK) && + !(kr->flags & (F_BLACKHOLE|F_REJECT))) + goto dont_redistribute; + break; + case AF_INET6: + if (IN6_IS_ADDR_LOOPBACK(&kr->prefix.v6) || + IN6_IS_ADDR_MULTICAST(&kr->prefix.v6) || + IN6_IS_ADDR_LINKLOCAL(&kr->prefix.v6) || + IN6_IS_ADDR_SITELOCAL(&kr->prefix.v6) || + IN6_IS_ADDR_V4MAPPED(&kr->prefix.v6) || + IN6_IS_ADDR_V4COMPAT(&kr->prefix.v6)) + goto dont_redistribute; + + if (IN6_IS_ADDR_LOOPBACK(&kr->nexthop.v6) && + !(kr->flags & (F_BLACKHOLE|F_REJECT))) + goto dont_redistribute; + break; + default: + log_debug("%s: unexpected address-family", __func__); + break; + } + + /* prefix should be redistributed */ + kr->flags |= F_REDISTRIBUTED; + main_imsg_compose_rde(IMSG_NETWORK_ADD, 0, kr, sizeof(*kr)); + return (1); + +dont_redistribute: + kr_redist_remove(kr); + return (0); +} + +void +kr_redistribute(struct kroute_prefix *kp) +{ + struct kroute_priority *kprio; + struct kroute_node *kn; + + /* only the highest prio route can be redistributed */ + TAILQ_FOREACH_REVERSE(kprio, &kp->priorities, plist, entry) { + if (kprio == TAILQ_FIRST(&kp->priorities)) { + TAILQ_FOREACH(kn, &kprio->nexthops, entry) + /* pick just one entry in case of multipath */ + if (kr_redist_eval(&kn->r)) + break; + } else { + TAILQ_FOREACH(kn, &kprio->nexthops, entry) + kr_redist_remove(&kn->r); + } + } +} + +int +kroute_compare(struct kroute_prefix *a, struct kroute_prefix *b) +{ + if (a->af < b->af) + return (-1); + if (a->af > b->af) + return (1); + + switch (a->af) { + case AF_INET: + if (ntohl(a->prefix.v4.s_addr) < + ntohl(b->prefix.v4.s_addr)) + return (-1); + if (ntohl(a->prefix.v4.s_addr) > + ntohl(b->prefix.v4.s_addr)) + return (1); + break; + case AF_INET6: + if (memcmp(a->prefix.v6.s6_addr, + b->prefix.v6.s6_addr, 16) < 0) + return (-1); + if (memcmp(a->prefix.v6.s6_addr, + b->prefix.v6.s6_addr, 16) > 0) + return (1); + break; + default: + log_debug("%s: unexpected address-family", __func__); + break; + } + + if (a->prefixlen < b->prefixlen) + return (-1); + if (a->prefixlen > b->prefixlen) + return (1); + + return (0); +} + +/* tree management */ +struct kroute_prefix * +kroute_find_prefix(int af, union eigrpd_addr *prefix, uint8_t prefixlen) +{ + struct kroute_prefix s; + + s.af = af; + memcpy(&s.prefix, prefix, sizeof(s.prefix)); + s.prefixlen = prefixlen; + + return (RB_FIND(kroute_tree, &krt, &s)); +} + +struct kroute_priority * +kroute_find_prio(struct kroute_prefix *kp, uint8_t prio) +{ + struct kroute_priority *kprio; + + /* RTP_ANY here picks the lowest priority node */ + if (prio == RTP_ANY) + return (TAILQ_FIRST(&kp->priorities)); + + TAILQ_FOREACH(kprio, &kp->priorities, entry) + if (kprio->priority == prio) + return (kprio); + + return (NULL); +} + +struct kroute_node * +kroute_find_gw(struct kroute_priority *kprio, union eigrpd_addr *nh) +{ + struct kroute_node *kn; + + TAILQ_FOREACH(kn, &kprio->nexthops, entry) + if (eigrp_addrcmp(kprio->kp->af, &kn->r.nexthop, nh) == 0) + return (kn); + + return (NULL); +} + +struct kroute_node * +kroute_insert(struct kroute *kr) +{ + struct kroute_prefix *kp; + struct kroute_priority *kprio, *tmp = NULL; + struct kroute_node *kn; + + kp = kroute_find_prefix(kr->af, &kr->prefix, kr->prefixlen); + if (kp == NULL) { + kp = calloc(1, sizeof((*kp))); + if (kp == NULL) + fatal("kroute_insert"); + kp->af = kr->af; + memcpy(&kp->prefix, &kr->prefix, sizeof(kp->prefix)); + kp->prefixlen = kr->prefixlen; + TAILQ_INIT(&kp->priorities); + RB_INSERT(kroute_tree, &krt, kp); + } + + kprio = kroute_find_prio(kp, kr->priority); + if (kprio == NULL) { + kprio = calloc(1, sizeof(*kprio)); + if (kprio == NULL) + fatal("kroute_insert"); + kprio->kp = kp; + kprio->priority = kr->priority; + TAILQ_INIT(&kprio->nexthops); + + /* lower priorities first */ + TAILQ_FOREACH(tmp, &kp->priorities, entry) + if (tmp->priority > kprio->priority) + break; + if (tmp) + TAILQ_INSERT_BEFORE(tmp, kprio, entry); + else + TAILQ_INSERT_TAIL(&kp->priorities, kprio, entry); + } + + kn = kroute_find_gw(kprio, &kr->nexthop); + if (kn == NULL) { + kn = calloc(1, sizeof(*kn)); + if (kn == NULL) + fatal("kroute_insert"); + kn->kprio = kprio; + memcpy(&kn->r, kr, sizeof(kn->r)); + TAILQ_INSERT_TAIL(&kprio->nexthops, kn, entry); + } + + if (!(kr->flags & F_KERNEL)) { + /* don't validate or redistribute eigrp route */ + kr->flags &= ~F_DOWN; + return (kn); + } + + if (kif_validate(kr->ifindex)) + kr->flags &= ~F_DOWN; + else + kr->flags |= F_DOWN; + + kr_redistribute(kp); + return (kn); +} + +int +kroute_remove(struct kroute *kr) +{ + struct kroute_prefix *kp; + struct kroute_priority *kprio; + struct kroute_node *kn; + + kp = kroute_find_prefix(kr->af, &kr->prefix, kr->prefixlen); + if (kp == NULL) + goto notfound; + kprio = kroute_find_prio(kp, kr->priority); + if (kprio == NULL) + goto notfound; + kn = kroute_find_gw(kprio, &kr->nexthop); + if (kn == NULL) + goto notfound; + + kr_redist_remove(&kn->r); + + TAILQ_REMOVE(&kprio->nexthops, kn, entry); + free(kn); + + if (TAILQ_EMPTY(&kprio->nexthops)) { + TAILQ_REMOVE(&kp->priorities, kprio, entry); + free(kprio); + } + + if (TAILQ_EMPTY(&kp->priorities)) { + if (RB_REMOVE(kroute_tree, &krt, kp) == NULL) + log_warnx("%s failed for %s/%u", __func__, + log_addr(kr->af, &kr->prefix), kp->prefixlen); + free(kp); + } else + kr_redistribute(kp); + + return (0); + +notfound: + log_warnx("%s failed to find %s/%u", __func__, + log_addr(kr->af, &kr->prefix), kr->prefixlen); + return (-1); +} + +void +kroute_clear(void) +{ + struct kroute_prefix *kp; + struct kroute_priority *kprio; + struct kroute_node *kn; + + while ((kp = RB_MIN(kroute_tree, &krt)) != NULL) { + while ((kprio = TAILQ_FIRST(&kp->priorities)) != NULL) { + while ((kn = TAILQ_FIRST(&kprio->nexthops)) != NULL) { + TAILQ_REMOVE(&kprio->nexthops, kn, entry); + free(kn); + } + TAILQ_REMOVE(&kp->priorities, kprio, entry); + free(kprio); + } + RB_REMOVE(kroute_tree, &krt, kp); + free(kp); + } +} + +int +kif_compare(struct kif_node *a, struct kif_node *b) +{ + return (b->k.ifindex - a->k.ifindex); +} + +/* tree management */ +struct kif_node * +kif_find(unsigned short ifindex) +{ + struct kif_node s; + + memset(&s, 0, sizeof(s)); + s.k.ifindex = ifindex; + + return (RB_FIND(kif_tree, &kit, &s)); +} + +struct kif * +kif_findname(char *ifname) +{ + struct kif_node *kif; + + RB_FOREACH(kif, kif_tree, &kit) + if (!strcmp(ifname, kif->k.ifname)) + return (&kif->k); + + return (NULL); +} + +struct kif_node * +kif_insert(unsigned short ifindex) +{ + struct kif_node *kif; + + if ((kif = calloc(1, sizeof(struct kif_node))) == NULL) + return (NULL); + + kif->k.ifindex = ifindex; + TAILQ_INIT(&kif->addrs); + + if (RB_INSERT(kif_tree, &kit, kif) != NULL) + fatalx("kif_insert: RB_INSERT"); + + return (kif); +} + +int +kif_remove(struct kif_node *kif) +{ + struct kif_addr *ka; + + if (RB_REMOVE(kif_tree, &kit, kif) == NULL) { + log_warnx("%s failed for inteface %s", __func__, kif->k.ifname); + return (-1); + } + + while ((ka = TAILQ_FIRST(&kif->addrs)) != NULL) { + TAILQ_REMOVE(&kif->addrs, ka, entry); + free(ka); + } + free(kif); + return (0); +} + +void +kif_clear(void) +{ + struct kif_node *kif; + + while ((kif = RB_MIN(kif_tree, &kit)) != NULL) + kif_remove(kif); +} + +struct kif * +kif_update(unsigned short ifindex, int flags, struct if_data *ifd, + struct sockaddr_dl *sdl) +{ + struct kif_node *kif; + + if ((kif = kif_find(ifindex)) == NULL) { + if ((kif = kif_insert(ifindex)) == NULL) + return (NULL); + kif->k.nh_reachable = (flags & IFF_UP) && + LINK_STATE_IS_UP(ifd->ifi_link_state); + } + + kif->k.flags = flags; + kif->k.link_state = ifd->ifi_link_state; + kif->k.if_type = ifd->ifi_type; + kif->k.baudrate = ifd->ifi_baudrate; + kif->k.mtu = ifd->ifi_mtu; + + if (sdl && sdl->sdl_family == AF_LINK) { + if (sdl->sdl_nlen >= sizeof(kif->k.ifname)) + memcpy(kif->k.ifname, sdl->sdl_data, + sizeof(kif->k.ifname) - 1); + else if (sdl->sdl_nlen > 0) + memcpy(kif->k.ifname, sdl->sdl_data, + sdl->sdl_nlen); + /* string already terminated via calloc() */ + } + + return (&kif->k); +} + +int +kif_validate(unsigned short ifindex) +{ + struct kif_node *kif; + + if ((kif = kif_find(ifindex)) == NULL) + return (0); + + return (kif->k.nh_reachable); +} + +/* misc */ +void +protect_lo(void) +{ + struct kroute kr4, kr6; + + /* special protection for 127/8 */ + memset(&kr4, 0, sizeof(kr4)); + kr4.af = AF_INET; + kr4.prefix.v4.s_addr = htonl(INADDR_LOOPBACK & IN_CLASSA_NET); + kr4.prefixlen = 8; + kr4.flags = F_KERNEL|F_CONNECTED; + kroute_insert(&kr4); + + /* special protection for ::1 */ + memset(&kr6, 0, sizeof(kr6)); + kr6.af = AF_INET6; + memcpy(&kr6.prefix.v6, &in6addr_loopback, sizeof(kr6.prefix.v6)); + kr6.prefixlen = 128; + kr6.flags = F_KERNEL|F_CONNECTED; + kroute_insert(&kr6); +} + +uint8_t +prefixlen_classful(in_addr_t ina) +{ + /* it hurt to write this. */ + + if (ina >= 0xf0000000U) /* class E */ + return (32); + else if (ina >= 0xe0000000U) /* class D */ + return (4); + else if (ina >= 0xc0000000U) /* class C */ + return (24); + else if (ina >= 0x80000000U) /* class B */ + return (16); + else /* class A */ + return (8); +} + +#define ROUNDUP(a) \ + (((a) & (sizeof(long) - 1)) ? (1 + ((a) | (sizeof(long) - 1))) : (a)) + +void +get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info) +{ + int i; + + for (i = 0; i < RTAX_MAX; i++) { + if (addrs & (1 << i)) { + rti_info[i] = sa; + sa = (struct sockaddr *)((char *)(sa) + + ROUNDUP(sa->sa_len)); + } else + rti_info[i] = NULL; + } +} + +void +if_change(unsigned short ifindex, int flags, struct if_data *ifd, + struct sockaddr_dl *sdl) +{ + struct kroute_prefix *kp; + struct kroute_priority *kprio; + struct kroute_node *kn; + struct kif *kif; + uint8_t reachable; + + if ((kif = kif_update(ifindex, flags, ifd, sdl)) == NULL) { + log_warn("%s: kif_update(%u)", __func__, ifindex); + return; + } + + reachable = (kif->flags & IFF_UP) && + LINK_STATE_IS_UP(kif->link_state); + + if (reachable == kif->nh_reachable) + return; /* nothing changed wrt nexthop validity */ + + kif->nh_reachable = reachable; + + /* notify eigrpe about link state */ + main_imsg_compose_eigrpe(IMSG_IFINFO, 0, kif, sizeof(struct kif)); + + /* notify rde about link going down */ + if (!kif->nh_reachable) + main_imsg_compose_rde(IMSG_IFDOWN, 0, kif, sizeof(struct kif)); + + /* update redistribute list */ + RB_FOREACH(kp, kroute_tree, &krt) { + TAILQ_FOREACH(kprio, &kp->priorities, entry) { + TAILQ_FOREACH(kn, &kprio->nexthops, entry) { + if (kn->r.ifindex != ifindex) + continue; + + if (reachable) + kn->r.flags &= ~F_DOWN; + else + kn->r.flags |= F_DOWN; + } + } + kr_redistribute(kp); + } +} + +void +if_newaddr(unsigned short ifindex, struct sockaddr *ifa, struct sockaddr *mask, + struct sockaddr *brd) +{ + struct kif_node *kif; + struct sockaddr_in *ifa4, *mask4, *brd4; + struct sockaddr_in6 *ifa6, *mask6, *brd6; + struct kif_addr *ka; + uint32_t a; + + if (ifa == NULL) + return; + if ((kif = kif_find(ifindex)) == NULL) { + log_warnx("%s: corresponding if %d not found", __func__, + ifindex); + return; + } + + switch (ifa->sa_family) { + case AF_INET: + ifa4 = (struct sockaddr_in *) ifa; + mask4 = (struct sockaddr_in *) mask; + brd4 = (struct sockaddr_in *) brd; + + /* filter out unwanted addresses */ + a = ntohl(ifa4->sin_addr.s_addr); + if (IN_MULTICAST(a) || IN_BADCLASS(a) || + (a >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) + return; + + if ((ka = calloc(1, sizeof(struct kif_addr))) == NULL) + fatal("if_newaddr"); + ka->a.addr.v4.s_addr = ifa4->sin_addr.s_addr; + if (mask4) + ka->a.prefixlen = + mask2prefixlen(mask4->sin_addr.s_addr); + if (brd4) + ka->a.dstbrd.v4.s_addr = brd4->sin_addr.s_addr; + break; + case AF_INET6: + ifa6 = (struct sockaddr_in6 *) ifa; + mask6 = (struct sockaddr_in6 *) mask; + brd6 = (struct sockaddr_in6 *) brd; + + /* We only care about link-local and global-scope. */ + if (IN6_IS_ADDR_UNSPECIFIED(&ifa6->sin6_addr) || + IN6_IS_ADDR_LOOPBACK(&ifa6->sin6_addr) || + IN6_IS_ADDR_MULTICAST(&ifa6->sin6_addr) || + IN6_IS_ADDR_SITELOCAL(&ifa6->sin6_addr) || + IN6_IS_ADDR_V4MAPPED(&ifa6->sin6_addr) || + IN6_IS_ADDR_V4COMPAT(&ifa6->sin6_addr)) + return; + + clearscope(&ifa6->sin6_addr); + + if ((ka = calloc(1, sizeof(struct kif_addr))) == NULL) + fatal("if_newaddr"); + ka->a.addr.v6 = ifa6->sin6_addr; + if (mask6) + ka->a.prefixlen = mask2prefixlen6(mask6); + if (brd6) + ka->a.dstbrd.v6 = brd6->sin6_addr; + break; + default: + return; + } + + ka->a.ifindex = ifindex; + ka->a.af = ifa->sa_family; + TAILQ_INSERT_TAIL(&kif->addrs, ka, entry); + + /* notify eigrpe about new address */ + main_imsg_compose_eigrpe(IMSG_NEWADDR, 0, &ka->a, + sizeof(struct kaddr)); +} + +void +if_deladdr(unsigned short ifindex, struct sockaddr *ifa, struct sockaddr *mask, + struct sockaddr *brd) +{ + struct kif_node *kif; + struct sockaddr_in *ifa4, *mask4, *brd4; + struct sockaddr_in6 *ifa6, *mask6, *brd6; + struct kaddr k; + struct kif_addr *ka, *nka; + + if (ifa == NULL) + return; + if ((kif = kif_find(ifindex)) == NULL) { + log_warnx("%s: corresponding if %d not found", __func__, + ifindex); + return; + } + + memset(&k, 0, sizeof(k)); + k.af = ifa->sa_family; + switch (ifa->sa_family) { + case AF_INET: + ifa4 = (struct sockaddr_in *) ifa; + mask4 = (struct sockaddr_in *) mask; + brd4 = (struct sockaddr_in *) brd; + + k.addr.v4.s_addr = ifa4->sin_addr.s_addr; + if (mask4) + k.prefixlen = mask2prefixlen(mask4->sin_addr.s_addr); + if (brd4) + k.dstbrd.v4.s_addr = brd4->sin_addr.s_addr; + break; + case AF_INET6: + ifa6 = (struct sockaddr_in6 *) ifa; + mask6 = (struct sockaddr_in6 *) mask; + brd6 = (struct sockaddr_in6 *) brd; + + /* We only care about link-local and global-scope. */ + if (IN6_IS_ADDR_UNSPECIFIED(&ifa6->sin6_addr) || + IN6_IS_ADDR_LOOPBACK(&ifa6->sin6_addr) || + IN6_IS_ADDR_MULTICAST(&ifa6->sin6_addr) || + IN6_IS_ADDR_SITELOCAL(&ifa6->sin6_addr) || + IN6_IS_ADDR_V4MAPPED(&ifa6->sin6_addr) || + IN6_IS_ADDR_V4COMPAT(&ifa6->sin6_addr)) + return; + + clearscope(&ifa6->sin6_addr); + + k.addr.v6 = ifa6->sin6_addr; + if (mask6) + k.prefixlen = mask2prefixlen6(mask6); + if (brd6) + k.dstbrd.v6 = brd6->sin6_addr; + break; + default: + return; + } + + for (ka = TAILQ_FIRST(&kif->addrs); ka != NULL; ka = nka) { + nka = TAILQ_NEXT(ka, entry); + + if (ka->a.af != k.af || + ka->a.prefixlen != k.prefixlen) + continue; + + switch (ifa->sa_family) { + case AF_INET: + if (ka->a.addr.v4.s_addr != k.addr.v4.s_addr || + ka->a.dstbrd.v4.s_addr != k.dstbrd.v4.s_addr) + continue; + break; + case AF_INET6: + if (!IN6_ARE_ADDR_EQUAL(&ka->a.addr.v6, &k.addr.v6) || + !IN6_ARE_ADDR_EQUAL(&ka->a.dstbrd.v6, &k.dstbrd.v6)) + continue; + break; + default: + break; + } + + /* notify eigrpe about removed address */ + main_imsg_compose_eigrpe(IMSG_DELADDR, 0, &ka->a, + sizeof(struct kaddr)); + TAILQ_REMOVE(&kif->addrs, ka, entry); + free(ka); + return; + } +} + +void +if_announce(void *msg) +{ + struct if_announcemsghdr *ifan; + struct kif_node *kif; + + ifan = msg; + + switch (ifan->ifan_what) { + case IFAN_ARRIVAL: + kif = kif_insert(ifan->ifan_index); + if (kif) + strlcpy(kif->k.ifname, ifan->ifan_name, + sizeof(kif->k.ifname)); + break; + case IFAN_DEPARTURE: + kif = kif_find(ifan->ifan_index); + if (kif) + kif_remove(kif); + break; + } +} + +/* rtsock */ +static int +send_rtmsg_v4(int fd, int action, struct kroute *kr) +{ + struct iovec iov[5]; + struct rt_msghdr hdr; + struct sockaddr_in prefix; + struct sockaddr_in nexthop; + struct sockaddr_in mask; + int iovcnt = 0; + + if (kr_state.fib_sync == 0) + return (0); + + /* initialize header */ + memset(&hdr, 0, sizeof(hdr)); + hdr.rtm_version = RTM_VERSION; + hdr.rtm_type = action; + hdr.rtm_priority = kr->priority; + hdr.rtm_tableid = kr_state.rdomain; /* rtableid */ + if (action == RTM_CHANGE) + hdr.rtm_fmask = RTF_REJECT|RTF_BLACKHOLE; + else + hdr.rtm_flags = RTF_MPATH; + hdr.rtm_seq = kr_state.rtseq++; /* overflow doesn't matter */ + hdr.rtm_msglen = sizeof(hdr); + /* adjust iovec */ + iov[iovcnt].iov_base = &hdr; + iov[iovcnt++].iov_len = sizeof(hdr); + + memset(&prefix, 0, sizeof(prefix)); + prefix.sin_len = sizeof(prefix); + prefix.sin_family = AF_INET; + prefix.sin_addr.s_addr = kr->prefix.v4.s_addr; + /* adjust header */ + hdr.rtm_addrs |= RTA_DST; + hdr.rtm_msglen += sizeof(prefix); + /* adjust iovec */ + iov[iovcnt].iov_base = &prefix; + iov[iovcnt++].iov_len = sizeof(prefix); + + if (kr->nexthop.v4.s_addr != 0) { + memset(&nexthop, 0, sizeof(nexthop)); + nexthop.sin_len = sizeof(nexthop); + nexthop.sin_family = AF_INET; + nexthop.sin_addr.s_addr = kr->nexthop.v4.s_addr; + /* adjust header */ + hdr.rtm_flags |= RTF_GATEWAY; + hdr.rtm_addrs |= RTA_GATEWAY; + hdr.rtm_msglen += sizeof(nexthop); + /* adjust iovec */ + iov[iovcnt].iov_base = &nexthop; + iov[iovcnt++].iov_len = sizeof(nexthop); + } + + memset(&mask, 0, sizeof(mask)); + mask.sin_len = sizeof(mask); + mask.sin_family = AF_INET; + mask.sin_addr.s_addr = prefixlen2mask(kr->prefixlen); + /* adjust header */ + hdr.rtm_addrs |= RTA_NETMASK; + hdr.rtm_msglen += sizeof(mask); + /* adjust iovec */ + iov[iovcnt].iov_base = &mask; + iov[iovcnt++].iov_len = sizeof(mask); + +retry: + if (writev(fd, iov, iovcnt) == -1) { + if (errno == ESRCH) { + if (hdr.rtm_type == RTM_CHANGE) { + hdr.rtm_type = RTM_ADD; + goto retry; + } else if (hdr.rtm_type == RTM_DELETE) { + log_info("route %s/%u vanished before delete", + inet_ntoa(kr->prefix.v4), + kr->prefixlen); + return (0); + } + } + log_warn("%s: action %u, prefix %s/%u", __func__, hdr.rtm_type, + inet_ntoa(kr->prefix.v4), kr->prefixlen); + return (0); + } + + return (0); +} + +static int +send_rtmsg_v6(int fd, int action, struct kroute *kr) +{ + struct iovec iov[5]; + struct rt_msghdr hdr; + struct pad { + struct sockaddr_in6 addr; + char pad[sizeof(long)]; /* thank you IPv6 */ + } prefix, nexthop, mask; + int iovcnt = 0; + + if (kr_state.fib_sync == 0) + return (0); + + /* initialize header */ + memset(&hdr, 0, sizeof(hdr)); + hdr.rtm_version = RTM_VERSION; + hdr.rtm_type = action; + hdr.rtm_priority = kr->priority; + hdr.rtm_tableid = kr_state.rdomain; /* rtableid */ + if (action == RTM_CHANGE) + hdr.rtm_fmask = RTF_REJECT|RTF_BLACKHOLE; + else + hdr.rtm_flags = RTF_MPATH; + hdr.rtm_seq = kr_state.rtseq++; /* overflow doesn't matter */ + hdr.rtm_msglen = sizeof(hdr); + /* adjust iovec */ + iov[iovcnt].iov_base = &hdr; + iov[iovcnt++].iov_len = sizeof(hdr); + + memset(&prefix, 0, sizeof(prefix)); + prefix.addr.sin6_len = sizeof(struct sockaddr_in6); + prefix.addr.sin6_family = AF_INET6; + prefix.addr.sin6_addr = kr->prefix.v6; + /* adjust header */ + hdr.rtm_addrs |= RTA_DST; + hdr.rtm_msglen += ROUNDUP(sizeof(struct sockaddr_in6)); + /* adjust iovec */ + iov[iovcnt].iov_base = &prefix; + iov[iovcnt++].iov_len = ROUNDUP(sizeof(struct sockaddr_in6)); + + if (!IN6_IS_ADDR_UNSPECIFIED(&kr->nexthop.v6)) { + memset(&nexthop, 0, sizeof(nexthop)); + nexthop.addr.sin6_len = sizeof(struct sockaddr_in6); + nexthop.addr.sin6_family = AF_INET6; + nexthop.addr.sin6_addr = kr->nexthop.v6; + nexthop.addr.sin6_scope_id = kr->ifindex; + embedscope(&nexthop.addr); + + /* adjust header */ + hdr.rtm_flags |= RTF_GATEWAY; + hdr.rtm_addrs |= RTA_GATEWAY; + hdr.rtm_msglen += ROUNDUP(sizeof(struct sockaddr_in6)); + /* adjust iovec */ + iov[iovcnt].iov_base = &nexthop; + iov[iovcnt++].iov_len = ROUNDUP(sizeof(struct sockaddr_in6)); + } + + memset(&mask, 0, sizeof(mask)); + mask.addr.sin6_len = sizeof(struct sockaddr_in6); + mask.addr.sin6_family = AF_INET6; + mask.addr.sin6_addr = *prefixlen2mask6(kr->prefixlen); + /* adjust header */ + if (kr->prefixlen == 128) + hdr.rtm_flags |= RTF_HOST; + hdr.rtm_addrs |= RTA_NETMASK; + hdr.rtm_msglen += ROUNDUP(sizeof(struct sockaddr_in6)); + /* adjust iovec */ + iov[iovcnt].iov_base = &mask; + iov[iovcnt++].iov_len = ROUNDUP(sizeof(struct sockaddr_in6)); + +retry: + if (writev(fd, iov, iovcnt) == -1) { + if (errno == ESRCH) { + if (hdr.rtm_type == RTM_CHANGE) { + hdr.rtm_type = RTM_ADD; + goto retry; + } else if (hdr.rtm_type == RTM_DELETE) { + log_info("route %s/%u vanished before delete", + log_in6addr(&kr->prefix.v6), + kr->prefixlen); + return (0); + } + } + log_warn("%s: action %u, prefix %s/%u", __func__, hdr.rtm_type, + log_in6addr(&kr->prefix.v6), kr->prefixlen); + return (0); + } + + return (0); +} + +int +send_rtmsg(int fd, int action, struct kroute *kr) +{ + switch (kr->af) { + case AF_INET: + return (send_rtmsg_v4(fd, action, kr)); + case AF_INET6: + return (send_rtmsg_v6(fd, action, kr)); + default: + break; + } + + return (-1); +} + +int +fetchtable(void) +{ + size_t len; + int mib[7]; + char *buf; + int rv; + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; + mib[3] = 0; + mib[4] = NET_RT_DUMP; + mib[5] = 0; + mib[6] = kr_state.rdomain; /* rtableid */ + + if (sysctl(mib, 7, NULL, &len, NULL, 0) == -1) { + log_warn("sysctl"); + return (-1); + } + if ((buf = malloc(len)) == NULL) { + log_warn("fetchtable"); + return (-1); + } + if (sysctl(mib, 7, buf, &len, NULL, 0) == -1) { + log_warn("sysctl"); + free(buf); + return (-1); + } + + rv = rtmsg_process(buf, len); + free(buf); + + return (rv); +} + +int +fetchifs(void) +{ + size_t len; + int mib[6]; + char *buf; + int rv; + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; + mib[3] = 0; /* wildcard */ + mib[4] = NET_RT_IFLIST; + mib[5] = 0; + + if (sysctl(mib, 6, NULL, &len, NULL, 0) == -1) { + log_warn("sysctl"); + return (-1); + } + if ((buf = malloc(len)) == NULL) { + log_warn("fetchifs"); + return (-1); + } + if (sysctl(mib, 6, buf, &len, NULL, 0) == -1) { + log_warn("sysctl"); + free(buf); + return (-1); + } + + rv = rtmsg_process(buf, len); + free(buf); + + return (rv); +} + +int +dispatch_rtmsg(void) +{ + char buf[RT_BUF_SIZE]; + ssize_t n; + + if ((n = read(kr_state.fd, &buf, sizeof(buf))) == -1) { + if (errno == EAGAIN || errno == EINTR) + return (0); + log_warn("%s: read error", __func__); + return (-1); + } + + if (n == 0) { + log_warnx("routing socket closed"); + return (-1); + } + + return (rtmsg_process(buf, n)); +} + +int +rtmsg_process(char *buf, size_t len) +{ + struct rt_msghdr *rtm; + struct if_msghdr ifm; + struct ifa_msghdr *ifam; + struct sockaddr *sa, *rti_info[RTAX_MAX]; + size_t offset; + char *next; + + for (offset = 0; offset < len; offset += rtm->rtm_msglen) { + next = buf + offset; + rtm = (struct rt_msghdr *)next; + if (len < offset + sizeof(unsigned short) || + len < offset + rtm->rtm_msglen) + fatalx("rtmsg_process: partial rtm in buffer"); + if (rtm->rtm_version != RTM_VERSION) + continue; + + sa = (struct sockaddr *)(next + rtm->rtm_hdrlen); + get_rtaddrs(rtm->rtm_addrs, sa, rti_info); + + switch (rtm->rtm_type) { + case RTM_ADD: + case RTM_GET: + case RTM_CHANGE: + case RTM_DELETE: + if (rtm->rtm_pid == kr_state.pid) + continue; + + if (rtm->rtm_errno) /* failed attempts... */ + continue; + + if (rtm->rtm_tableid != kr_state.rdomain) + continue; + + /* Skip ARP/ND cache and broadcast routes. */ + if (rtm->rtm_flags & (RTF_LLINFO|RTF_BROADCAST)) + continue; + + if (rtmsg_process_route(rtm, rti_info) == -1) + return (-1); + } + + switch (rtm->rtm_type) { + case RTM_IFINFO: + memcpy(&ifm, next, sizeof(ifm)); + if_change(ifm.ifm_index, ifm.ifm_flags, &ifm.ifm_data, + (struct sockaddr_dl *)rti_info[RTAX_IFP]); + break; + case RTM_NEWADDR: + ifam = (struct ifa_msghdr *)rtm; + if ((ifam->ifam_addrs & (RTA_NETMASK | RTA_IFA | + RTA_BRD)) == 0) + break; + + if_newaddr(ifam->ifam_index, + (struct sockaddr *)rti_info[RTAX_IFA], + (struct sockaddr *)rti_info[RTAX_NETMASK], + (struct sockaddr *)rti_info[RTAX_BRD]); + break; + case RTM_DELADDR: + ifam = (struct ifa_msghdr *)rtm; + if ((ifam->ifam_addrs & (RTA_NETMASK | RTA_IFA | + RTA_BRD)) == 0) + break; + + if_deladdr(ifam->ifam_index, + (struct sockaddr *)rti_info[RTAX_IFA], + (struct sockaddr *)rti_info[RTAX_NETMASK], + (struct sockaddr *)rti_info[RTAX_BRD]); + break; + case RTM_IFANNOUNCE: + if_announce(next); + break; + default: + /* ignore for now */ + break; + } + } + + return (offset); +} + +int +rtmsg_process_route(struct rt_msghdr *rtm, struct sockaddr *rti_info[RTAX_MAX]) +{ + struct sockaddr *sa; + struct sockaddr_in *sa_in; + struct sockaddr_in6 *sa_in6; + struct kroute kr; + struct kroute_prefix *kp; + struct kroute_priority *kprio; + struct kroute_node *kn; + + if ((sa = rti_info[RTAX_DST]) == NULL) + return (-1); + + memset(&kr, 0, sizeof(kr)); + kr.af = sa->sa_family; + switch (kr.af) { + case AF_INET: + kr.prefix.v4.s_addr = + ((struct sockaddr_in *)sa)->sin_addr.s_addr; + sa_in = (struct sockaddr_in *) rti_info[RTAX_NETMASK]; + if (sa_in != NULL && sa_in->sin_len != 0) + kr.prefixlen = mask2prefixlen(sa_in->sin_addr.s_addr); + else if (rtm->rtm_flags & RTF_HOST) + kr.prefixlen = 32; + else if (kr.prefix.v4.s_addr == INADDR_ANY) + kr.prefixlen = 0; + else + kr.prefixlen = prefixlen_classful(kr.prefix.v4.s_addr); + break; + case AF_INET6: + memcpy(&kr.prefix.v6, &((struct sockaddr_in6 *)sa)->sin6_addr, + sizeof(kr.prefix.v6)); + sa_in6 = (struct sockaddr_in6 *)rti_info[RTAX_NETMASK]; + if (sa_in6 != NULL && sa_in6->sin6_len != 0) + kr.prefixlen = mask2prefixlen6(sa_in6); + else if (rtm->rtm_flags & RTF_HOST) + kr.prefixlen = 128; + else if (IN6_IS_ADDR_UNSPECIFIED(&kr.prefix.v6)) + kr.prefixlen = 0; + else + fatalx("in6 net addr without netmask"); + break; + default: + return (0); + } + kr.ifindex = rtm->rtm_index; + if ((sa = rti_info[RTAX_GATEWAY]) != NULL) { + switch (sa->sa_family) { + case AF_INET: + kr.nexthop.v4.s_addr = + ((struct sockaddr_in *)sa)->sin_addr.s_addr; + break; + case AF_INET6: + sa_in6 = (struct sockaddr_in6 *)sa; + recoverscope(sa_in6); + kr.nexthop.v6 = sa_in6->sin6_addr; + if (sa_in6->sin6_scope_id) + kr.ifindex = sa_in6->sin6_scope_id; + break; + case AF_LINK: + kr.flags |= F_CONNECTED; + break; + } + } + kr.flags |= F_KERNEL; + if (rtm->rtm_flags & RTF_STATIC) + kr.flags |= F_STATIC; + if (rtm->rtm_flags & RTF_BLACKHOLE) + kr.flags |= F_BLACKHOLE; + if (rtm->rtm_flags & RTF_REJECT) + kr.flags |= F_REJECT; + if (rtm->rtm_flags & RTF_DYNAMIC) + kr.flags |= F_DYNAMIC; + kr.priority = rtm->rtm_priority; + + + if (rtm->rtm_type == RTM_CHANGE) { + /* + * The kernel doesn't allow RTM_CHANGE for multipath routes. + * If we got this message we know that the route has only one + * nexthop and we should remove it before installing the same + * route with the new nexthop. + */ + kp = kroute_find_prefix(kr.af, &kr.prefix, kr.prefixlen); + if (kp) { + kprio = kroute_find_prio(kp, kr.priority); + if (kprio) { + kn = TAILQ_FIRST(&kprio->nexthops); + if (kn && (kn->r.flags & F_KERNEL)) + kroute_remove(&kr); + } + } + } + + kn = NULL; + kp = kroute_find_prefix(kr.af, &kr.prefix, kr.prefixlen); + if (kp) { + kprio = kroute_find_prio(kp, kr.priority); + if (kprio) + kn = kroute_find_gw(kprio, &kr.nexthop); + } + + if (rtm->rtm_type == RTM_DELETE) { + if (kn == NULL || !(kn->r.flags & F_KERNEL)) + return (0); + return (kroute_remove(&kr)); + } + + if (!eigrp_addrisset(kr.af, &kr.nexthop) && !(kr.flags & F_CONNECTED)) { + log_warnx("%s: no nexthop for %s/%u", __func__, + log_addr(kr.af, &kr.prefix), kr.prefixlen); + return (-1); + } + + if (kr.priority == eigrpd_conf->fib_priority_internal || + kr.priority == eigrpd_conf->fib_priority_external) { + log_warnx("alien EIGRP route %s/%d", log_addr(kr.af, &kr.prefix), + kr.prefixlen); + return (send_rtmsg(kr_state.fd, RTM_DELETE, &kr)); + } + + if (kn != NULL) { + /* update route */ + memcpy(&kn->r, &kr, sizeof(kn->r)); + + if (kif_validate(kn->r.ifindex)) + kn->r.flags &= ~F_DOWN; + else + kn->r.flags |= F_DOWN; + + kr_redistribute(kp); + } else + kroute_insert(&kr); + + return (0); +} diff --git a/usr.sbin/eigrpd/log.c b/usr.sbin/eigrpd/log.c new file mode 100644 index 00000000000..ce8ad3998fc --- /dev/null +++ b/usr.sbin/eigrpd/log.c @@ -0,0 +1,353 @@ +/* $OpenBSD: log.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@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 <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include "eigrpd.h" +#include "rde.h" +#include "log.h" + +static const char * const procnames[] = { + "parent", + "eigrpe", + "rde" +}; + +int debug; +int verbose; + +void +log_init(int n_debug) +{ + extern char *__progname; + + debug = n_debug; + + if (!debug) + openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); + + tzset(); +} + +void +log_verbose(int v) +{ + verbose = v; +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char *nfmt; + + if (debug) { + /* best effort in out of mem situations */ + if (asprintf(&nfmt, "%s\n", fmt) == -1) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, nfmt, ap); + free(nfmt); + } + fflush(stderr); + } else + vsyslog(pri, fmt, ap); +} + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_CRIT, "%s", strerror(errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) { + /* we tried it... */ + vlog(LOG_CRIT, emsg, ap); + logit(LOG_CRIT, "%s", strerror(errno)); + } else { + vlog(LOG_CRIT, nfmt, ap); + free(nfmt); + } + va_end(ap); + } +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_CRIT, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + if (verbose) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +void +fatal(const char *emsg) +{ + if (emsg == NULL) + logit(LOG_CRIT, "fatal in %s: %s", procnames[eigrpd_process], + strerror(errno)); + else + if (errno) + logit(LOG_CRIT, "fatal in %s: %s: %s", + procnames[eigrpd_process], emsg, strerror(errno)); + else + logit(LOG_CRIT, "fatal in %s: %s", + procnames[eigrpd_process], emsg); + + if (eigrpd_process == PROC_MAIN) + exit(1); + else /* parent copes via SIGCHLD */ + _exit(1); +} + +void +fatalx(const char *emsg) +{ + errno = 0; + fatal(emsg); +} + +#define NUM_LOGS 4 +const char * +log_sockaddr(void *vp) +{ + static char buf[NUM_LOGS][NI_MAXHOST]; + static int round = 0; + struct sockaddr *sa = vp; + + round = (round + 1) % NUM_LOGS; + + if (getnameinfo(sa, sa->sa_len, buf[round], NI_MAXHOST, NULL, 0, + NI_NUMERICHOST)) + return ("(unknown)"); + else + return (buf[round]); +} + +const char * +log_in6addr(const struct in6_addr *addr) +{ + struct sockaddr_in6 sa_in6; + + memset(&sa_in6, 0, sizeof(sa_in6)); + sa_in6.sin6_len = sizeof(sa_in6); + sa_in6.sin6_family = AF_INET6; + memcpy(&sa_in6.sin6_addr, addr, sizeof(sa_in6.sin6_addr)); + + recoverscope(&sa_in6); + + return (log_sockaddr(&sa_in6)); +} + +const char * +log_in6addr_scope(const struct in6_addr *addr, unsigned int ifindex) +{ + struct sockaddr_in6 sa_in6; + + memset(&sa_in6, 0, sizeof(sa_in6)); + sa_in6.sin6_len = sizeof(sa_in6); + sa_in6.sin6_family = AF_INET6; + memcpy(&sa_in6.sin6_addr, addr, sizeof(sa_in6.sin6_addr)); + + addscope(&sa_in6, ifindex); + + return (log_sockaddr(&sa_in6)); +} + +const char * +log_addr(int af, union eigrpd_addr *addr) +{ + static char buf[NUM_LOGS][INET6_ADDRSTRLEN]; + static int round = 0; + + switch (af) { + case AF_INET: + round = (round + 1) % NUM_LOGS; + if (inet_ntop(AF_INET, &addr->v4, buf[round], + sizeof(buf[round])) == NULL) + return ("???"); + return (buf[round]); + case AF_INET6: + return (log_in6addr(&addr->v6)); + default: + break; + } + + return ("???"); +} + +const char * +log_prefix(struct rt_node *rn) +{ + static char buf[64]; + + if (snprintf(buf, sizeof(buf), "%s/%u", log_addr(rn->eigrp->af, + &rn->prefix), rn->prefixlen) == -1) + return ("???"); + + return (buf); +} + +const char * +opcode_name(uint8_t opcode) +{ + switch (opcode) { + case EIGRP_OPC_UPDATE: + return ("UPDATE"); + case EIGRP_OPC_REQUEST: + return ("REQUEST"); + case EIGRP_OPC_QUERY: + return ("QUERY"); + case EIGRP_OPC_REPLY: + return ("REPLY"); + case EIGRP_OPC_HELLO: + return ("HELLO"); + case EIGRP_OPC_PROBE: + return ("PROBE"); + case EIGRP_OPC_SIAQUERY: + return ("SIAQUERY"); + case EIGRP_OPC_SIAREPLY: + return ("SIAREPLY"); + default: + return ("UNKNOWN"); + } +} + +const char * +af_name(int af) +{ + switch (af) { + case AF_INET: + return ("ipv4"); + case AF_INET6: + return ("ipv6"); + default: + return ("UNKNOWN"); + } +} + +const char * +if_type_name(enum iface_type type) +{ + switch (type) { + case IF_TYPE_POINTOPOINT: + return ("POINTOPOINT"); + case IF_TYPE_BROADCAST: + return ("BROADCAST"); + } + /* NOTREACHED */ + return ("UNKNOWN"); +} + +const char * +dual_state_name(int state) +{ + switch (state) { + case DUAL_STA_PASSIVE: + return ("PASSIVE"); + case DUAL_STA_ACTIVE0: + return ("ACTIVE(Oij=0)"); + case DUAL_STA_ACTIVE1: + return ("ACTIVE(Oij=1)"); + case DUAL_STA_ACTIVE2: + return ("ACTIVE(Oij=2)"); + case DUAL_STA_ACTIVE3: + return ("ACTIVE(Oij=3)"); + default: + return ("UNKNOWN"); + } +} + +const char * +ext_proto_name(int proto) +{ + switch (proto) { + case EIGRP_EXT_PROTO_IGRP: + return ("IGRP"); + case EIGRP_EXT_PROTO_EIGRP: + return ("EIGRP"); + case EIGRP_EXT_PROTO_STATIC: + return ("Static"); + case EIGRP_EXT_PROTO_RIP: + return ("RIP"); + case EIGRP_EXT_PROTO_HELLO: + return ("HELLO"); + case EIGRP_EXT_PROTO_OSPF: + return ("OSPF"); + case EIGRP_EXT_PROTO_ISIS: + return ("ISIS"); + case EIGRP_EXT_PROTO_EGP: + return ("EGP"); + case EIGRP_EXT_PROTO_BGP: + return ("BGP"); + case EIGRP_EXT_PROTO_IDRP: + return ("IDRP"); + case EIGRP_EXT_PROTO_CONN: + return ("Connected"); + default: + return ("UNKNOWN"); + } +} diff --git a/usr.sbin/eigrpd/log.h b/usr.sbin/eigrpd/log.h new file mode 100644 index 00000000000..b50cc7f33a4 --- /dev/null +++ b/usr.sbin/eigrpd/log.h @@ -0,0 +1,46 @@ +/* $OpenBSD: log.h,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@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. + */ + +#ifndef _LOG_H_ +#define _LOG_H_ + +#include <stdarg.h> + +void log_init(int); +void log_verbose(int); +void logit(int, const char *, ...); +void vlog(int, const char *, va_list); +void log_warn(const char *, ...); +void log_warnx(const char *, ...); +void log_info(const char *, ...); +void log_debug(const char *, ...); +void fatal(const char *) __dead; +void fatalx(const char *) __dead; + +const char *log_in6addr(const struct in6_addr *); +const char *log_in6addr_scope(const struct in6_addr *, unsigned int); +const char *log_sockaddr(void *); +const char *log_addr(int, union eigrpd_addr *); +const char *log_prefix(struct rt_node *); +const char *opcode_name(uint8_t); +const char *af_name(int); +const char *if_type_name(enum iface_type); +const char *dual_state_name(int); +const char *ext_proto_name(int); + +#endif /* _LOG_H_ */ diff --git a/usr.sbin/eigrpd/neighbor.c b/usr.sbin/eigrpd/neighbor.c new file mode 100644 index 00000000000..bc61672105f --- /dev/null +++ b/usr.sbin/eigrpd/neighbor.c @@ -0,0 +1,269 @@ +/* $OpenBSD: neighbor.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@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 <stdlib.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <string.h> + +#include "eigrpd.h" +#include "eigrp.h" +#include "eigrpe.h" +#include "rde.h" +#include "log.h" + +static __inline int nbr_compare(struct nbr *, struct nbr *); +static __inline int nbr_pid_compare(struct nbr *, struct nbr *); + +RB_PROTOTYPE(nbr_addr_head, nbr, addr_tree, nbr_compare) +RB_GENERATE(nbr_addr_head, nbr, addr_tree, nbr_compare) +RB_PROTOTYPE(nbr_pid_head, nbr, pid_tree, nbr_pid_compare) +RB_GENERATE(nbr_pid_head, nbr, pid_tree, nbr_pid_compare) + +static __inline int +nbr_compare(struct nbr *a, struct nbr *b) +{ + int i; + + if (a->ei->eigrp->af < b->ei->eigrp->af) + return (-1); + if (a->ei->eigrp->af > b->ei->eigrp->af) + return (1); + if (a->ei->iface->ifindex < b->ei->iface->ifindex) + return (-1); + if (a->ei->iface->ifindex > b->ei->iface->ifindex) + return (1); + if (a->ei->eigrp->as < b->ei->eigrp->as) + return (-1); + if (a->ei->eigrp->as > b->ei->eigrp->as) + return (1); + + switch (a->ei->eigrp->af) { + case AF_INET: + if (ntohl(a->addr.v4.s_addr) < ntohl(b->addr.v4.s_addr)) + return (-1); + if (ntohl(a->addr.v4.s_addr) > ntohl(b->addr.v4.s_addr)) + return (1); + break; + case AF_INET6: + i = memcmp(&a->addr.v6, &b->addr.v6, sizeof(struct in6_addr)); + if (i > 0) + return (1); + if (i < 0) + return (-1); + break; + default: + log_debug("%s: unexpected address-family", __func__); + break; + } + + return (0); +} + +static __inline int +nbr_pid_compare(struct nbr *a, struct nbr *b) +{ + return (a->peerid - b->peerid); +} + +struct nbr_pid_head nbrs_by_pid = RB_INITIALIZER(&nbrs_by_pid); + +uint32_t peercnt = NBR_CNTSTART; + +extern struct eigrpd_conf *econf; + +struct nbr * +nbr_new(struct eigrp_iface *ei, union eigrpd_addr *addr, uint16_t holdtime, + int self) +{ + struct eigrp *eigrp = ei->eigrp; + struct nbr *nbr; + + if (!self) + log_debug("%s: interface %s addr %s as %u", __func__, + ei->iface->name, log_addr(eigrp->af, addr), eigrp->as); + + if ((nbr = calloc(1, sizeof(*nbr))) == NULL) + fatal("nbr_new"); + + nbr->ei = ei; + TAILQ_INSERT_TAIL(&ei->nbr_list, nbr, entry); + memcpy(&nbr->addr, addr, sizeof(nbr->addr)); + nbr->peerid = 0; + nbr->hello_holdtime = holdtime; + nbr->flags = F_EIGRP_NBR_PENDING; + if (self) + nbr->flags |= F_EIGRP_NBR_SELF; + TAILQ_INIT(&nbr->update_list); + TAILQ_INIT(&nbr->query_list); + TAILQ_INIT(&nbr->reply_list); + TAILQ_INIT(&nbr->retrans_list); + + if (RB_INSERT(nbr_addr_head, &eigrp->nbrs, nbr) != NULL) + fatalx("nbr_new: RB_INSERT(eigrp->nbrs) failed"); + + /* timeout handling */ + if (!self) { + evtimer_set(&nbr->ev_ack, rtp_ack_timer, nbr); + evtimer_set(&nbr->ev_hello_timeout, nbr_timeout, nbr); + nbr_start_timeout(nbr); + } + + return (nbr); +} + +void +nbr_init(struct nbr *nbr) +{ + struct timeval now; + struct rde_nbr rnbr; + + nbr->flags &= ~F_EIGRP_NBR_PENDING; + + gettimeofday(&now, NULL); + nbr->uptime = now.tv_sec; + + nbr_update_peerid(nbr); + + memset(&rnbr, 0, sizeof(rnbr)); + memcpy(&rnbr.addr, &nbr->addr, sizeof(rnbr.addr)); + rnbr.ifaceid = nbr->ei->ifaceid; + if (nbr->flags & F_EIGRP_NBR_SELF) + rnbr.flags = F_RDE_NBR_SELF; + + /* rde is not aware of pending nbrs */ + eigrpe_imsg_compose_rde(IMSG_NEIGHBOR_UP, nbr->peerid, 0, &rnbr, + sizeof(rnbr)); +} + +void +nbr_del(struct nbr *nbr) +{ + struct eigrp *eigrp = nbr->ei->eigrp; + struct packet *pkt; + + if (!(nbr->flags & F_EIGRP_NBR_SELF)) + log_debug("%s: addr %s", __func__, + log_addr(eigrp->af, &nbr->addr)); + + eigrpe_imsg_compose_rde(IMSG_NEIGHBOR_DOWN, nbr->peerid, 0, NULL, 0); + + nbr_stop_timeout(nbr); + + /* clear retransmission list */ + while ((pkt = TAILQ_FIRST(&nbr->retrans_list)) != NULL) + rtp_packet_del(pkt); + + if (nbr->peerid) + RB_REMOVE(nbr_pid_head, &nbrs_by_pid, nbr); + RB_REMOVE(nbr_addr_head, &eigrp->nbrs, nbr); + TAILQ_REMOVE(&nbr->ei->nbr_list, nbr, entry); + + free(nbr); +} + +void +nbr_update_peerid(struct nbr *nbr) +{ + if (nbr->peerid) + RB_REMOVE(nbr_pid_head, &nbrs_by_pid, nbr); + + /* get next unused peerid */ + while (nbr_find_peerid(++peercnt)) + ; + nbr->peerid = peercnt; + + if (RB_INSERT(nbr_pid_head, &nbrs_by_pid, nbr) != NULL) + fatalx("nbr_new: RB_INSERT(nbrs_by_pid) failed"); +} + +struct nbr * +nbr_find(struct eigrp_iface *ei, union eigrpd_addr *addr) +{ + struct nbr n; + struct eigrp_iface i; + struct eigrp e; + + e.af = ei->eigrp->af; + e.as = ei->eigrp->as; + i.eigrp = &e; + i.iface = ei->iface; + n.ei = &i; + memcpy(&n.addr, addr, sizeof(n.addr)); + + return (RB_FIND(nbr_addr_head, &ei->eigrp->nbrs, &n)); +} + +struct nbr * +nbr_find_peerid(uint32_t peerid) +{ + struct nbr n; + n.peerid = peerid; + return (RB_FIND(nbr_pid_head, &nbrs_by_pid, &n)); +} + +struct ctl_nbr * +nbr_to_ctl(struct nbr *nbr) +{ + static struct ctl_nbr nctl; + struct timeval now; + + nctl.af = nbr->ei->eigrp->af; + nctl.as = nbr->ei->eigrp->as; + memcpy(nctl.ifname, nbr->ei->iface->name, sizeof(nctl.ifname)); + memcpy(&nctl.addr, &nbr->addr, sizeof(nctl.addr)); + nctl.hello_holdtime = nbr->hello_holdtime; + gettimeofday(&now, NULL); + nctl.uptime = now.tv_sec - nbr->uptime; + + return (&nctl); +} + +/* timers */ + +/* ARGSUSED */ +void +nbr_timeout(int fd, short event, void *arg) +{ + struct nbr *nbr = arg; + struct eigrp *eigrp = nbr->ei->eigrp; + + log_debug("%s: neighbor %s", __func__, log_addr(eigrp->af, &nbr->addr)); + + nbr_del(nbr); +} + +void +nbr_start_timeout(struct nbr *nbr) +{ + struct timeval tv; + + timerclear(&tv); + tv.tv_sec = nbr->hello_holdtime; + + if (evtimer_add(&nbr->ev_hello_timeout, &tv) == -1) + fatal("nbr_start_timeout"); +} + +void +nbr_stop_timeout(struct nbr *nbr) +{ + if (evtimer_pending(&nbr->ev_hello_timeout, NULL) && + evtimer_del(&nbr->ev_hello_timeout) == -1) + fatal("nbr_stop_timeout"); +} diff --git a/usr.sbin/eigrpd/packet.c b/usr.sbin/eigrpd/packet.c new file mode 100644 index 00000000000..4b2670b13a5 --- /dev/null +++ b/usr.sbin/eigrpd/packet.c @@ -0,0 +1,729 @@ +/* $OpenBSD: packet.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@openbsd.org> + * 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 <stdlib.h> +#include <stdio.h> +#include <arpa/inet.h> +#include <net/if_dl.h> +#include <errno.h> +#include <string.h> + +#include "eigrpd.h" +#include "eigrp.h" +#include "log.h" +#include "eigrpe.h" + +extern struct eigrpd_conf *econf; + +int ip_hdr_sanity_check(const struct ip *, uint16_t); +int eigrp_hdr_sanity_check(int, union eigrpd_addr *, + struct eigrp_hdr *, uint16_t, const struct iface *); +struct iface *find_iface(unsigned int, int, union eigrpd_addr *); + +int +gen_eigrp_hdr(struct ibuf *buf, uint16_t opcode, uint8_t flags, + uint32_t seq_num, uint16_t as) +{ + struct eigrp_hdr eigrp_hdr; + + memset(&eigrp_hdr, 0, sizeof(eigrp_hdr)); + eigrp_hdr.version = EIGRP_VERSION; + eigrp_hdr.opcode = opcode; + /* chksum will be set later */ + eigrp_hdr.flags = htonl(flags); + eigrp_hdr.seq_num = htonl(seq_num); + /* ack_num will be set later */ + eigrp_hdr.vrid = htons(EIGRP_VRID_UNICAST_AF); + eigrp_hdr.as = htons(as); + + return (ibuf_add(buf, &eigrp_hdr, sizeof(eigrp_hdr))); +} + +/* send and receive packets */ +static int +send_packet_v4(struct iface *iface, struct nbr *nbr, struct ibuf *buf) +{ + struct sockaddr_in dst; + struct msghdr msg; + struct iovec iov[2]; + struct ip ip_hdr; + + /* setup sockaddr */ + dst.sin_family = AF_INET; + dst.sin_len = sizeof(struct sockaddr_in); + if (nbr) + dst.sin_addr.s_addr = nbr->addr.v4.s_addr; + else + dst.sin_addr.s_addr = AllEIGRPRouters_v4; + + /* setup IP hdr */ + memset(&ip_hdr, 0, 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(ibuf_size(buf) + sizeof(ip_hdr)); + ip_hdr.ip_id = 0; /* 0 means kernel set appropriate value */ + ip_hdr.ip_off = 0; + ip_hdr.ip_ttl = EIGRP_IP_TTL; + ip_hdr.ip_p = IPPROTO_EIGRP; + ip_hdr.ip_sum = 0; + ip_hdr.ip_src.s_addr = if_primary_addr(iface); + ip_hdr.ip_dst = dst.sin_addr; + + /* setup buffer */ + memset(&msg, 0, sizeof(msg)); + iov[0].iov_base = &ip_hdr; + iov[0].iov_len = sizeof(ip_hdr); + iov[1].iov_base = buf->buf; + iov[1].iov_len = ibuf_size(buf); + 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_ipv4_mcast(iface) == -1) { + log_warn("%s: error setting multicast interface, %s", + __func__, iface->name); + return (-1); + } + + if (sendmsg(econf->eigrp_socket_v4, &msg, 0) == -1) { + log_warn("%s: error sending packet on interface %s", + __func__, iface->name); + return (-1); + } + + return (0); +} + +static int +send_packet_v6(struct iface *iface, struct nbr *nbr, struct ibuf *buf) +{ + struct sockaddr_in6 sa6; + + /* setup sockaddr */ + memset(&sa6, 0, sizeof(sa6)); + sa6.sin6_family = AF_INET6; + sa6.sin6_len = sizeof(struct sockaddr_in6); + if (nbr) + sa6.sin6_addr = nbr->addr.v6; + else + inet_pton(AF_INET6, AllEIGRPRouters_v6, &sa6.sin6_addr); + addscope(&sa6, iface->ifindex); + + /* set outgoing interface for multicast traffic */ + if (IN6_IS_ADDR_MULTICAST(&sa6.sin6_addr)) + if (if_set_ipv6_mcast(iface) == -1) { + log_warn("%s: error setting multicast interface, %s", + __func__, iface->name); + return (-1); + } + + if (sendto(econf->eigrp_socket_v6, buf->buf, buf->wpos, 0, + (struct sockaddr *)&sa6, sizeof(sa6)) == -1) { + log_warn("%s: error sending packet on interface %s", + __func__, iface->name); + return (-1); + } + + return (0); +} + +int +send_packet(struct eigrp_iface *ei, struct nbr *nbr, uint32_t flags, + struct ibuf *buf) +{ + struct eigrp *eigrp = ei->eigrp; + struct iface *iface = ei->iface; + struct eigrp_hdr *eigrp_hdr; + + if (!(iface->flags & IFF_UP) || !LINK_STATE_IS_UP(iface->linkstate)) + return (-1); + + /* update ack number, flags and checksum */ + if ((eigrp_hdr = ibuf_seek(buf, 0, sizeof(*eigrp_hdr))) == NULL) + fatalx("send_packet: buf_seek failed"); + if (nbr) { + eigrp_hdr->ack_num = htonl(nbr->recv_seq); + rtp_ack_stop_timer(nbr); + } + if (flags) { + eigrp_hdr->flags = ntohl(eigrp_hdr->flags) | flags; + eigrp_hdr->flags = htonl(eigrp_hdr->flags); + } + eigrp_hdr->chksum = 0; + eigrp_hdr->chksum = in_cksum(buf->buf, ibuf_size(buf)); + + /* log packet being sent */ + if (eigrp_hdr->opcode != EIGRP_OPC_HELLO) { + char buffer[64]; + + if (nbr) + snprintf(buffer, sizeof(buffer), "nbr %s", + log_addr(eigrp->af, &nbr->addr)); + else + snprintf(buffer, sizeof(buffer), "(multicast)"); + + log_debug("%s: type %s iface %s %s AS %u seq %u ack %u", + __func__, opcode_name(eigrp_hdr->opcode), iface->name, + buffer, ntohs(eigrp_hdr->as), ntohl(eigrp_hdr->seq_num), + ntohl(eigrp_hdr->ack_num)); + } + + switch (eigrp->af) { + case AF_INET: + send_packet_v4(iface, nbr, buf); + break; + case AF_INET6: + send_packet_v6(iface, nbr, buf); + break; + default: + break; + } + + return (0); +} + +static int +recv_packet_nbr(struct nbr *nbr, struct eigrp_hdr *eigrp_hdr, + struct seq_addr_head *seq_addr_list, struct tlv_mcast_seq *tm) +{ + uint32_t seq, ack; + struct seq_addr_entry *sa; + + seq = ntohl(eigrp_hdr->seq_num); + ack = ntohl(eigrp_hdr->ack_num); + + /* + * draft-savage-eigrp-04 - Section 5.3.1: + * "In addition to the HELLO packet, if any packet is received within + * the hold time period, then the Hold Time period will be reset." + */ + nbr_start_timeout(nbr); + + /* ack processing */ + if (ack != 0) + rtp_process_ack(nbr, ack); + if (seq != 0) { + /* check for sequence wraparound */ + if (nbr->recv_seq >= seq && + !(nbr->recv_seq == UINT32_MAX && seq == 1)) { + log_debug("%s: duplicate packet", __func__); + rtp_send_ack(nbr); + return (-1); + } + nbr->recv_seq = seq; + } + + /* handle the sequence tlv */ + if (eigrp_hdr->opcode == EIGRP_OPC_HELLO && + !TAILQ_EMPTY(seq_addr_list)) { + nbr->flags |= F_EIGRP_NBR_CR_MODE; + + TAILQ_FOREACH(sa, seq_addr_list, entry) { + switch (sa->af) { + case AF_INET: + if (sa->addr.v4.s_addr == + if_primary_addr(nbr->ei->iface)) { + nbr->flags &= ~F_EIGRP_NBR_CR_MODE; + break; + } + break; + case AF_INET6: + if (IN6_ARE_ADDR_EQUAL(&sa->addr.v6, + &nbr->ei->iface->linklocal)) { + nbr->flags &= ~F_EIGRP_NBR_CR_MODE; + break; + } + break; + default: + break; + } + } + if (tm) + nbr->next_mcast_seq = tm->seq; + } + + if ((ntohl(eigrp_hdr->flags) & EIGRP_HDR_FLAG_CR)) { + if (!(nbr->flags & F_EIGRP_NBR_CR_MODE)) + return (-1); + if (ntohl(eigrp_hdr->seq_num) != nbr->next_mcast_seq) + return (-1); + } + + return (0); +} + +static void +recv_packet(int af, union eigrpd_addr *src, union eigrpd_addr *dest, + struct iface *iface, struct eigrp_hdr *eigrp_hdr, char *buf, uint16_t len) +{ + struct eigrp_iface *ei; + struct nbr *nbr; + struct tlv_parameter *tp; + struct tlv_sw_version *tv; + struct tlv_mcast_seq *tm; + struct rinfo ri; + struct rinfo_entry *re; + int route_af; + enum route_type route_type; + struct seq_addr_head seq_addr_list; + struct rinfo_head rinfo_list; + + /* EIGRP header sanity checks */ + if (eigrp_hdr_sanity_check(af, dest, eigrp_hdr, len, iface) == -1) + return; + + buf += sizeof(*eigrp_hdr); + len -= sizeof(*eigrp_hdr); + + TAILQ_INIT(&seq_addr_list); + TAILQ_INIT(&rinfo_list); + while (len > 0) { + struct tlv tlv; + + if (len < sizeof(tlv)) { + log_debug("%s: malformed packet (bad length)", + __func__); + goto error; + } + + memcpy(&tlv, buf, sizeof(tlv)); + if (ntohs(tlv.length) > len) { + log_debug("%s: malformed packet (bad length)", + __func__); + goto error; + } + + switch (ntohs(tlv.type)) { + case TLV_TYPE_PARAMETER: + if ((tp = tlv_decode_parameter(&tlv, buf)) == NULL) + goto error; + break; + case TLV_TYPE_SEQ: + if (tlv_decode_seq(&tlv, buf, &seq_addr_list) < 0) + goto error; + break; + case TLV_TYPE_SW_VERSION: + if ((tv = tlv_decode_sw_version(&tlv, buf)) == NULL) + goto error; + break; + case TLV_TYPE_MCAST_SEQ: + if ((tm = tlv_decode_mcast_seq(&tlv, buf)) == NULL) + goto error; + break; + case TLV_TYPE_IPV4_INTERNAL: + case TLV_TYPE_IPV4_EXTERNAL: + case TLV_TYPE_IPV6_INTERNAL: + case TLV_TYPE_IPV6_EXTERNAL: + switch (ntohs(tlv.type)) { + case TLV_TYPE_IPV4_INTERNAL: + route_af = AF_INET; + route_type = EIGRP_ROUTE_INTERNAL; + break; + case TLV_TYPE_IPV4_EXTERNAL: + route_af = AF_INET; + route_type = EIGRP_ROUTE_EXTERNAL; + break; + case TLV_TYPE_IPV6_INTERNAL: + route_af = AF_INET6; + route_type = EIGRP_ROUTE_INTERNAL; + break; + case TLV_TYPE_IPV6_EXTERNAL: + route_af = AF_INET6; + route_type = EIGRP_ROUTE_EXTERNAL; + break; + } + + if (tlv_decode_route(route_af, route_type, &tlv, buf, + &ri) < 0) + goto error; + if ((re = calloc(1, sizeof(*re))) == NULL) + fatal("recv_packet"); + memcpy(&re->rinfo, &ri, sizeof(re->rinfo)); + TAILQ_INSERT_TAIL(&rinfo_list, re, entry); + break; + case TLV_TYPE_AUTH: + case TLV_TYPE_PEER_TERM: + /* + * XXX There is no enough information in the draft + * to implement these TLVs properly. + */ + case TLV_TYPE_IPV4_COMMUNITY: + case TLV_TYPE_IPV6_COMMUNITY: + /* TODO */ + default: + /* ignore unknown tlv */ + break; + } + buf += ntohs(tlv.length); + len -= ntohs(tlv.length); + } + + ei = eigrp_if_lookup(iface, af, ntohs(eigrp_hdr->as)); + if (ei == NULL || ei->passive) + goto error; + + nbr = nbr_find(ei, src); + if (nbr == NULL && (eigrp_hdr->opcode != EIGRP_OPC_HELLO || + ntohl(eigrp_hdr->ack_num) != 0)) { + log_debug("%s: unknown neighbor", __func__); + goto error; + } else if (nbr && recv_packet_nbr(nbr, eigrp_hdr, &seq_addr_list, + tm) < 0) + goto error; + + /* log packet being received */ + if (eigrp_hdr->opcode != EIGRP_OPC_HELLO) + log_debug("%s: type %s nbr %s AS %u seq %u ack %u", __func__, + opcode_name(eigrp_hdr->opcode), log_addr(af, &nbr->addr), + ei->eigrp->as, ntohl(eigrp_hdr->seq_num), + ntohl(eigrp_hdr->ack_num)); + + /* switch EIGRP packet type */ + switch (eigrp_hdr->opcode) { + case EIGRP_OPC_HELLO: + if (ntohl(eigrp_hdr->ack_num) == 0) + recv_hello(ei, src, nbr, tp); + break; + case EIGRP_OPC_UPDATE: + recv_update(nbr, &rinfo_list, ntohl(eigrp_hdr->flags)); + break; + case EIGRP_OPC_QUERY: + recv_query(nbr, &rinfo_list, 0); + break; + case EIGRP_OPC_REPLY: + recv_reply(nbr, &rinfo_list, 0); + break; + case EIGRP_OPC_SIAQUERY: + recv_query(nbr, &rinfo_list, 1); + break; + case EIGRP_OPC_SIAREPLY: + recv_reply(nbr, &rinfo_list, 1); + break; + default: + log_debug("%s: unknown EIGRP packet type, interface %s", + __func__, iface->name); + } + +error: + /* free rinfo tlvs */ + message_list_clr(&rinfo_list); + /* free seq addresses tlvs */ + seq_addr_list_clr(&seq_addr_list); +} + +void +recv_packet_v4(int fd, short event, void *bula) +{ + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(struct sockaddr_dl))]; + } cmsgbuf; + struct msghdr msg; + struct iovec iov; + struct ip ip_hdr; + struct eigrp_hdr *eigrp_hdr; + struct iface *iface; + char *buf; + struct cmsghdr *cmsg; + ssize_t r; + uint16_t len; + int l; + unsigned int ifindex = 0; + union eigrpd_addr src, dest; + + if (event != EV_READ) + return; + + /* setup buffer */ + memset(&msg, 0, 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 = &cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + + if ((r = recvmsg(fd, &msg, 0)) == -1) { + if (errno != EAGAIN && errno != EINTR) + log_debug("%s: read error: %s", __func__, + 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 = (uint16_t)r; + + /* IP header sanity checks */ + if (len < sizeof(ip_hdr)) { + log_debug("%s: bad packet size", __func__); + return; + } + memcpy(&ip_hdr, buf, sizeof(ip_hdr)); + if ((l = ip_hdr_sanity_check(&ip_hdr, len)) == -1) + return; + src.v4.s_addr = ip_hdr.ip_src.s_addr; + dest.v4.s_addr = ip_hdr.ip_dst.s_addr; + + buf += l; + len -= l; + + /* find a matching interface */ + if ((iface = find_iface(ifindex, AF_INET, &src)) == NULL) + return; + + /* + * Packet needs to be sent to AllEIGRPRouters_v4 or to one + * of the interface addresses. + */ + if (ip_hdr.ip_dst.s_addr != AllEIGRPRouters_v4) { + struct if_addr *if_addr; + int found = 0; + + TAILQ_FOREACH(if_addr, &iface->addr_list, entry) + if (if_addr->af == AF_INET && + ip_hdr.ip_dst.s_addr == if_addr->addr.v4.s_addr) { + found = 1; + break; + } + if (found == 0) { + log_debug("%s: packet sent to wrong address %s, " + "interface %s", __func__, inet_ntoa(ip_hdr.ip_dst), + iface->name); + return; + } + } + + if (len < sizeof(*eigrp_hdr)) { + log_debug("%s: bad packet size", __func__); + return; + } + eigrp_hdr = (struct eigrp_hdr *)buf; + + recv_packet(AF_INET, &src, &dest, iface, eigrp_hdr, buf, len); +} + +void +recv_packet_v6(int fd, short event, void *bula) +{ + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } cmsgbuf; + struct msghdr msg; + struct iovec iov; + struct in6_addr maddr; + struct sockaddr_in6 sin6; + struct eigrp_hdr *eigrp_hdr; + struct iface *iface; + char *buf; + struct cmsghdr *cmsg; + ssize_t r; + uint16_t len; + unsigned int ifindex = 0; + union eigrpd_addr src, dest; + + if (event != EV_READ) + return; + + /* setup buffer */ + memset(&msg, 0, sizeof(msg)); + iov.iov_base = buf = pkt_ptr; + iov.iov_len = READ_BUF_SIZE; + msg.msg_name = &sin6; + msg.msg_namelen = sizeof(sin6); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = &cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + + if ((r = recvmsg(fd, &msg, 0)) == -1) { + if (errno != EAGAIN && errno != EINTR) + log_debug("%s: read error: %s", __func__, + strerror(errno)); + return; + } + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == IPPROTO_IPV6 && + cmsg->cmsg_type == IPV6_PKTINFO) { + ifindex = ((struct in6_pktinfo *) + CMSG_DATA(cmsg))->ipi6_ifindex; + dest.v6 = ((struct in6_pktinfo *) + CMSG_DATA(cmsg))->ipi6_addr; + break; + } + } + src.v6 = sin6.sin6_addr; + + /* find a matching interface */ + if ((iface = find_iface(ifindex, AF_INET6, &src)) == NULL) + return; + + /* + * Packet needs to be sent to AllEIGRPRouters_v6 or to the + * link local address of the interface. + */ + inet_pton(AF_INET6, AllEIGRPRouters_v6, &maddr); + + if (!IN6_ARE_ADDR_EQUAL(&dest.v6, &maddr) && + !IN6_ARE_ADDR_EQUAL(&dest.v6, &iface->linklocal)) { + log_debug("%s: packet sent to wrong address %s, interface %s", + __func__, log_in6addr(&dest.v6), iface->name); + return; + } + + len = (uint16_t)r; + if (len < sizeof(*eigrp_hdr)) { + log_debug("%s: bad packet size", __func__); + return; + } + eigrp_hdr = (struct eigrp_hdr *)buf; + + recv_packet(AF_INET6, &src, &dest, iface, eigrp_hdr, buf, len); +} + +int +ip_hdr_sanity_check(const struct ip *ip_hdr, uint16_t len) +{ + in_addr_t ipv4; + + if (ntohs(ip_hdr->ip_len) != len) { + log_debug("%s: invalid IP packet length %u", __func__, + ntohs(ip_hdr->ip_len)); + return (-1); + } + + ipv4 = ntohl(ip_hdr->ip_src.s_addr); + if (((ipv4 >> IN_CLASSA_NSHIFT) == 0) + || ((ipv4 >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) + || IN_MULTICAST(ipv4) || IN_BADCLASS(ipv4)) { + log_debug("%s: invalid IP source address %s", __func__, + inet_ntoa(ip_hdr->ip_src)); + return (-1); + } + + if (ip_hdr->ip_p != IPPROTO_EIGRP) + /* this is enforced by the socket itself */ + fatalx("ip_hdr_sanity_check: invalid IP proto"); + + return (ip_hdr->ip_hl << 2); +} + +int +eigrp_hdr_sanity_check(int af, union eigrpd_addr *addr, + struct eigrp_hdr *eigrp_hdr, uint16_t len, const struct iface *iface) +{ + if (in_cksum(eigrp_hdr, len)) { + log_debug("%s: invalid checksum, interface %s", __func__, + iface->name); + return (-1); + } + + if (eigrp_hdr->version != EIGRP_HEADER_VERSION) { + log_debug("%s: invalid EIGRP version %d, interface %s", + __func__, eigrp_hdr->version, iface->name); + return (-1); + } + + if (ntohs(eigrp_hdr->vrid) != EIGRP_VRID_UNICAST_AF) { + log_debug("%s: unknown or unsupported vrid %u, interface %s", + __func__, ntohs(eigrp_hdr->vrid), iface->name); + return (-1); + } + + if (eigrp_hdr->opcode == EIGRP_OPC_HELLO && + eigrp_hdr->ack_num != 0) { + switch (af) { + case AF_INET: + if (IN_MULTICAST(addr->v4.s_addr)) { + log_debug("%s: multicast ack (ipv4), " + "interface %s", __func__, iface->name); + return (-1); + } + break; + case AF_INET6: + if (IN6_IS_ADDR_MULTICAST(&addr->v6)) { + log_debug("%s: multicast ack (ipv6), " + "interface %s", __func__, iface->name); + return (-1); + } + break; + default: + break; + } + } + + return (0); +} + +struct iface * +find_iface(unsigned int ifindex, int af, union eigrpd_addr *src) +{ + struct iface *iface; + struct if_addr *if_addr; + uint32_t mask; + + iface = if_lookup(econf, ifindex); + if (iface == NULL) + return (NULL); + + switch (af) { + case AF_INET: + /* + * From CCNP ROUTE 642-902 OCG: + * "EIGRP's rules about neighbor IP addresses being in the same + * subnet are less exact than OSPF. OSPF requires matching + * subnet numbers and masks. EIGRP just asks the question of + * whether the neighbor's IP address is in the range of + * addresses for the subnet as known to the local router." + */ + TAILQ_FOREACH(if_addr, &iface->addr_list, entry) { + mask = prefixlen2mask(if_addr->prefixlen); + if ((if_addr->addr.v4.s_addr & mask) == + (src->v4.s_addr & mask)) + return (iface); + } + break; + case AF_INET6: + /* + * draft-savage-eigrp-04 - Section 10.1: + * "EIGRP IPv6 will check that a received HELLO contains a valid + * IPv6 link-local source address." + */ + if (IN6_IS_ADDR_LINKLOCAL(&src->v6)) + return (iface); + break; + default: + break; + } + + return (NULL); +} diff --git a/usr.sbin/eigrpd/parse.y b/usr.sbin/eigrpd/parse.y new file mode 100644 index 00000000000..1228399f2e7 --- /dev/null +++ b/usr.sbin/eigrpd/parse.y @@ -0,0 +1,1165 @@ +/* $OpenBSD: parse.y,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@openbsd.org> + * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org> + * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org> + * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * 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/stat.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <unistd.h> +#include <ifaddrs.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <syslog.h> + +#include "eigrp.h" +#include "eigrpd.h" +#include "eigrpe.h" +#include "log.h" + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *, int); +int popfile(void); +int check_file_secrecy(int, const char *); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...) + __attribute__((__format__ (printf, 1, 2))) + __attribute__((__nonnull__ (1))); +int kw_cmp(const void *, const void *); +int lookup(char *); +int lgetc(int); +int lungetc(int); +int findeol(void); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; +int symset(const char *, const char *, int); +char *symget(const char *); + +void clear_config(struct eigrpd_conf *xconf); +uint32_t get_rtr_id(void); +int host(const char *, union eigrpd_addr *, uint8_t *); + +static struct eigrpd_conf *conf; +static int errors = 0; + +int af = AF_UNSPEC; +struct eigrp *eigrp = NULL; +struct eigrp_iface *ei = NULL; + +struct config_defaults { + uint8_t kvalues[6]; + uint8_t maximum_hops; + uint8_t maximum_paths; + uint8_t variance; + struct redist_metric *dflt_metric; + uint16_t hello_interval; + uint16_t hello_holdtime; + uint32_t delay; + uint32_t bandwidth; + uint8_t splithorizon; +}; + +struct config_defaults globaldefs; +struct config_defaults afdefs; +struct config_defaults asdefs; +struct config_defaults ifacedefs; +struct config_defaults *defs; + +struct eigrp *conf_get_instance(uint16_t); +struct eigrp_iface *conf_get_if(struct kif *); + +typedef struct { + union { + int64_t number; + char *string; + struct redistribute *redist; + struct redist_metric *redist_metric; + } v; + int lineno; +} YYSTYPE; + +%} + +%token ROUTERID AS FIBUPDATE RDOMAIN REDISTRIBUTE METRIC DFLTMETRIC +%token MAXHOPS MAXPATHS VARIANCE FIBPRIORITY_INT FIBPRIORITY_EXT +%token AF IPV4 IPV6 HELLOINTERVAL HOLDTIME KVALUES +%token INTERFACE PASSIVE DELAY BANDWIDTH SPLITHORIZON +%token YES NO +%token INCLUDE +%token ERROR +%token <v.string> STRING +%token <v.number> NUMBER +%type <v.number> yesno no +%type <v.string> string +%type <v.number> eigrp_af +%type <v.redist> redistribute +%type <v.redist_metric> redist_metric opt_red_metric + +%% + +grammar : /* empty */ + | grammar include '\n' + | grammar '\n' + | grammar conf_main '\n' + | grammar varset '\n' + | grammar af '\n' + | grammar error '\n' { file->errors++; } + ; + +include : INCLUDE STRING { + struct file *nfile; + + if ((nfile = pushfile($2, 1)) == NULL) { + yyerror("failed to include file %s", $2); + free($2); + YYERROR; + } + free($2); + + file = nfile; + lungetc('\n'); + } + ; + +string : string STRING { + if (asprintf(&$$, "%s %s", $1, $2) == -1) { + free($1); + free($2); + yyerror("string: asprintf"); + YYERROR; + } + free($1); + free($2); + } + | STRING + ; + +yesno : YES { $$ = 1; } + | NO { $$ = 0; } + ; + +no : /* empty */ { $$ = 0; } + | NO { $$ = 1; } + ; + +eigrp_af : IPV4 { $$ = AF_INET; } + | IPV6 { $$ = AF_INET6; } + ; + +varset : STRING '=' string { + if (conf->opts & EIGRPD_OPT_VERBOSE) + printf("%s = \"%s\"\n", $1, $3); + if (symset($1, $3, 0) == -1) + fatal("cannot store variable"); + free($1); + free($3); + } + ; + +conf_main : ROUTERID STRING { + if (!inet_aton($2, &conf->rtr_id)) { + yyerror("error parsing router-id"); + free($2); + YYERROR; + } + free($2); + } + | FIBUPDATE yesno { + if ($2 == 0) + conf->flags |= EIGRPD_FLAG_NO_FIB_UPDATE; + else + conf->flags &= ~EIGRPD_FLAG_NO_FIB_UPDATE; + } + | RDOMAIN NUMBER { + if ($2 < 0 || $2 > RT_TABLEID_MAX) { + yyerror("invalid rdomain"); + YYERROR; + } + conf->rdomain = $2; + } + | FIBPRIORITY_INT NUMBER { + if ($2 <= RTP_NONE || $2 > RTP_MAX) { + yyerror("invalid fib-priority"); + YYERROR; + } + conf->fib_priority_internal = $2; + } + | FIBPRIORITY_EXT NUMBER { + if ($2 <= RTP_NONE || $2 > RTP_MAX) { + yyerror("invalid fib-priority"); + YYERROR; + } + conf->fib_priority_external = $2; + } + | defaults + ; + +redistribute : no REDISTRIBUTE STRING opt_red_metric { + struct redistribute *r; + + if ((r = calloc(1, sizeof(*r))) == NULL) + fatal(NULL); + if (!strcmp($3, "default")) + r->type = REDIST_DEFAULT; + else if (!strcmp($3, "static")) + r->type = REDIST_STATIC; + else if (!strcmp($3, "rip")) + r->type = REDIST_RIP; + else if (!strcmp($3, "ospf")) + r->type = REDIST_OSPF; + else if (!strcmp($3, "connected")) + r->type = REDIST_CONNECTED; + else if (host($3, &r->addr, &r->prefixlen) >= 0) + r->type = REDIST_ADDR; + else { + yyerror("invalid redistribute"); + free($3); + free(r); + YYERROR; + } + + r->af = af; + if ($1) + r->type |= REDIST_NO; + r->metric = $4; + free($3); + $$ = r; + } + ; + +redist_metric : NUMBER NUMBER NUMBER NUMBER NUMBER { + struct redist_metric *m; + + if ($1 < MIN_BANDWIDTH || $1 > MAX_BANDWIDTH) { + yyerror("bandwidth out of range (%d-%d)", + MIN_BANDWIDTH, MAX_BANDWIDTH); + YYERROR; + } + if ($2 < MIN_DELAY || $2 > MAX_DELAY) { + yyerror("delay out of range (%d-%d)", + MIN_DELAY, MAX_DELAY); + YYERROR; + } + if ($3 < MIN_RELIABILITY || $3 > MAX_RELIABILITY) { + yyerror("reliability out of range (%d-%d)", + MIN_RELIABILITY, MAX_RELIABILITY); + YYERROR; + } + if ($4 < MIN_LOAD || $4 > MAX_LOAD) { + yyerror("load out of range (%d-%d)", + MIN_LOAD, MAX_LOAD); + YYERROR; + } + if ($5 < MIN_MTU || $5 > MAX_MTU) { + yyerror("mtu out of range (%d-%d)", + MIN_MTU, MAX_MTU); + YYERROR; + } + + if ((m = calloc(1, sizeof(*m))) == NULL) + fatal(NULL); + m->bandwidth = $1; + m->delay = $2; + m->reliability = $3; + m->load = $4; + m->mtu = $5; + + $$ = m; + } + ; + +opt_red_metric : /* empty */ { $$ = NULL; } + | METRIC redist_metric { $$ = $2; } + ; + +defaults : KVALUES NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER { + if ($2 < MIN_KVALUE || $2 > MAX_KVALUE || + $3 < MIN_KVALUE || $3 > MAX_KVALUE || + $4 < MIN_KVALUE || $4 > MAX_KVALUE || + $5 < MIN_KVALUE || $5 > MAX_KVALUE || + $6 < MIN_KVALUE || $6 > MAX_KVALUE || + $7 < MIN_KVALUE || $7 > MAX_KVALUE) { + yyerror("k-value out of range (%d-%d)", + MIN_KVALUE, MAX_KVALUE); + YYERROR; + } + defs->kvalues[0] = $2; + defs->kvalues[1] = $3; + defs->kvalues[2] = $4; + defs->kvalues[3] = $5; + defs->kvalues[4] = $6; + defs->kvalues[5] = $7; + } + | MAXHOPS NUMBER { + if ($2 < MIN_MAXIMUM_HOPS || + $2 > MAX_MAXIMUM_HOPS) { + yyerror("maximum-hops out of range (%d-%d)", + MIN_MAXIMUM_HOPS, MAX_MAXIMUM_HOPS); + YYERROR; + } + defs->maximum_hops = $2; + } + | MAXPATHS NUMBER { + if ($2 < MIN_MAXIMUM_PATHS || + $2 > MAX_MAXIMUM_PATHS) { + yyerror("maximum-paths out of range (%d-%d)", + MIN_MAXIMUM_PATHS, MAX_MAXIMUM_PATHS); + YYERROR; + } + defs->maximum_paths = $2; + } + | VARIANCE NUMBER { + if ($2 < MIN_VARIANCE || + $2 > MAX_VARIANCE) { + yyerror("variance out of range (%d-%d)", + MIN_VARIANCE, MAX_VARIANCE); + YYERROR; + } + defs->variance = $2; + } + | DFLTMETRIC redist_metric { + defs->dflt_metric = $2; + } + | iface_defaults + ; + +iface_defaults : HELLOINTERVAL NUMBER { + if ($2 < MIN_HELLO_INTERVAL || + $2 > MAX_HELLO_INTERVAL) { + yyerror("hello-interval out of range (%d-%d)", + MIN_HELLO_INTERVAL, MAX_HELLO_INTERVAL); + YYERROR; + } + defs->hello_interval = $2; + } + | HOLDTIME NUMBER { + if ($2 < MIN_HELLO_HOLDTIME || + $2 > MAX_HELLO_HOLDTIME) { + yyerror("hold-timel out of range (%d-%d)", + MIN_HELLO_HOLDTIME, + MAX_HELLO_HOLDTIME); + YYERROR; + } + defs->hello_holdtime = $2; + } + | DELAY NUMBER { + if ($2 < MIN_DELAY || $2 > MAX_DELAY) { + yyerror("delay out of range (%d-%d)", + MIN_DELAY, MAX_DELAY); + YYERROR; + } + defs->delay = $2; + } + | BANDWIDTH NUMBER { + if ($2 < MIN_BANDWIDTH || $2 > MAX_BANDWIDTH) { + yyerror("bandwidth out of range (%d-%d)", + MIN_BANDWIDTH, MAX_BANDWIDTH); + YYERROR; + } + defs->bandwidth = $2; + } + | SPLITHORIZON yesno { + defs->splithorizon = $2; + } + ; + +optnl : '\n' optnl + | + ; + +nl : '\n' optnl /* one newline or more */ + ; + +af : AF eigrp_af { + af = $2; + memcpy(&afdefs, defs, sizeof(afdefs)); + defs = &afdefs; + } af_block { + af = AF_UNSPEC; + defs = &globaldefs; + } + ; + +af_block : '{' optnl afopts_l '}' + | '{' optnl '}' + | + ; + +afopts_l : afopts_l afoptsl nl + | afoptsl optnl + ; + +afoptsl : as + | defaults + ; + +as : AS NUMBER { + if ($2 < EIGRP_MIN_AS || $2 > EIGRP_MAX_AS) { + yyerror("invalid autonomous-system"); + YYERROR; + } + eigrp = conf_get_instance($2); + if (eigrp == NULL) + YYERROR; + TAILQ_INSERT_TAIL(&conf->instances, eigrp, entry); + + memcpy(&asdefs, defs, sizeof(asdefs)); + defs = &asdefs; + } as_block { + memcpy(eigrp->kvalues, defs->kvalues, + sizeof(eigrp->kvalues)); + eigrp->maximum_hops = defs->maximum_hops; + eigrp->maximum_paths = defs->maximum_paths; + eigrp->variance = defs->variance; + eigrp->dflt_metric = defs->dflt_metric; + eigrp = NULL; + defs = &afdefs; + } + ; + +as_block : '{' optnl asopts_l '}' + | '{' optnl '}' + | + ; + +asopts_l : asopts_l asoptsl nl + | asoptsl optnl + ; + +asoptsl : interface + | redistribute { + SIMPLEQ_INSERT_TAIL(&eigrp->redist_list, $1, entry); + } + | defaults + ; + +interface : INTERFACE STRING { + struct kif *kif; + + if ((kif = kif_findname($2)) == NULL) { + yyerror("unknown interface %s", $2); + free($2); + YYERROR; + } + free($2); + ei = conf_get_if(kif); + if (ei == NULL) + YYERROR; + + memcpy(&ifacedefs, defs, sizeof(ifacedefs)); + defs = &ifacedefs; + } interface_block { + ei->hello_holdtime = defs->hello_holdtime; + ei->hello_interval = defs->hello_interval; + ei->delay = defs->delay; + ei->bandwidth = defs->bandwidth; + ei->splithorizon = defs->splithorizon; + ei = NULL; + defs = &asdefs; + } + ; + +interface_block : '{' optnl interfaceopts_l '}' + | '{' optnl '}' + | + ; + +interfaceopts_l : interfaceopts_l interfaceoptsl nl + | interfaceoptsl optnl + ; + +interfaceoptsl : PASSIVE { ei->passive = 1; } + | iface_defaults + ; + +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + char *msg; + + file->errors++; + va_start(ap, fmt); + if (vasprintf(&msg, fmt, ap) == -1) + fatalx("yyerror vasprintf"); + va_end(ap); + logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); + free(msg); + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + {"address-family", AF}, + {"autonomous-system", AS}, + {"bandwidth", BANDWIDTH}, + {"default-metric", DFLTMETRIC}, + {"delay", DELAY}, + {"fib-priority-external", FIBPRIORITY_EXT}, + {"fib-priority-internal", FIBPRIORITY_INT}, + {"fib-update", FIBUPDATE}, + {"hello-interval", HELLOINTERVAL}, + {"holdtime", HOLDTIME}, + {"include", INCLUDE}, + {"interface", INTERFACE}, + {"ipv4", IPV4}, + {"ipv6", IPV6}, + {"k-values", KVALUES}, + {"maximum-hops", MAXHOPS}, + {"maximum-paths", MAXPATHS}, + {"metric", METRIC}, + {"no", NO}, + {"passive", PASSIVE}, + {"rdomain", RDOMAIN}, + {"redistribute", REDISTRIBUTE}, + {"router-id", ROUTERID}, + {"split-horizon", SPLITHORIZON}, + {"variance", VARIANCE}, + {"yes", YES} + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + else + return (STRING); +} + +#define MAXPUSHBACK 128 + +unsigned char *parsebuf; +int parseindex; +unsigned char pushback_buffer[MAXPUSHBACK]; +int pushback_index = 0; + +int +lgetc(int quotec) +{ + int c, next; + + if (parsebuf) { + /* Read character from the parsebuffer instead of input. */ + if (parseindex >= 0) { + c = parsebuf[parseindex++]; + if (c != '\0') + return (c); + parsebuf = NULL; + } else + parseindex++; + } + + if (pushback_index) + return (pushback_buffer[--pushback_index]); + + if (quotec) { + if ((c = getc(file->stream)) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = getc(file->stream)) == '\\') { + next = getc(file->stream); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return (EOF); + c = getc(file->stream); + } + return (c); +} + +int +lungetc(int c) +{ + if (c == EOF) + return (EOF); + if (parsebuf) { + parseindex--; + if (parseindex >= 0) + return (c); + } + if (pushback_index < MAXPUSHBACK-1) + return (pushback_buffer[pushback_index++] = c); + else + return (EOF); +} + +int +findeol(void) +{ + int c; + + parsebuf = NULL; + + /* skip to either EOF or the first real EOL */ + while (1) { + if (pushback_index) + c = pushback_buffer[--pushback_index]; + else + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + unsigned char buf[8096]; + unsigned char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && parsebuf == NULL) { + while (1) { + if ((c = lgetc(0)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + parsebuf = val; + parseindex = 0; + goto top; + } + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || c == ' ' || c == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return (findeol()); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "yylex: strdup"); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return (c); + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && \ + x != '!' && x != '=' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + err(1, "yylex: strdup"); + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +int +check_file_secrecy(int fd, const char *fname) +{ + struct stat st; + + if (fstat(fd, &st)) { + log_warn("cannot stat %s", fname); + return (-1); + } + if (st.st_uid != 0 && st.st_uid != getuid()) { + log_warnx("%s: owner not root or current user", fname); + return (-1); + } + if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { + log_warnx("%s: group writable or world read/writable", fname); + return (-1); + } + return (0); +} + +struct file * +pushfile(const char *name, int secret) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL) { + log_warn("malloc"); + return (NULL); + } + if ((nfile->name = strdup(name)) == NULL) { + log_warn("malloc"); + free(nfile); + return (NULL); + } + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + log_warn("%s", nfile->name); + free(nfile->name); + free(nfile); + return (NULL); + } else if (secret && + check_file_secrecy(fileno(nfile->stream), nfile->name)) { + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + nfile->lineno = 1; + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file); + file = prev; + return (file ? 0 : EOF); +} + +struct eigrpd_conf * +parse_config(char *filename, int opts) +{ + struct sym *sym, *next; + + if ((conf = calloc(1, sizeof(struct eigrpd_conf))) == NULL) + fatal("parse_config"); + conf->opts = opts; + conf->rdomain = 0; + conf->fib_priority_internal = RTP_EIGRP; + conf->fib_priority_external = RTP_EIGRP; + + memset(&globaldefs, 0, sizeof(globaldefs)); + defs = &globaldefs; + defs->kvalues[0] = defs->kvalues[2] = 1; + defs->maximum_hops = DEFAULT_MAXIMUM_HOPS; + defs->maximum_paths = DEFAULT_MAXIMUM_PATHS; + defs->variance = DEFAULT_VARIANCE; + defs->hello_holdtime = DEFAULT_HELLO_HOLDTIME; + defs->hello_interval = DEFAULT_HELLO_INTERVAL; + defs->delay = DEFAULT_DELAY; + defs->bandwidth = DEFAULT_BANDWIDTH; + defs->splithorizon = 1; + + if ((file = pushfile(filename, !(conf->opts & EIGRPD_OPT_NOACTION))) == NULL) { + free(conf); + return (NULL); + } + topfile = file; + + TAILQ_INIT(&conf->iface_list); + TAILQ_INIT(&conf->instances); + + yyparse(); + errors = file->errors; + popfile(); + + /* Free macros and check which have not been used. */ + for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) { + next = TAILQ_NEXT(sym, entry); + if ((conf->opts & EIGRPD_OPT_VERBOSE2) && !sym->used) + fprintf(stderr, "warning: macro '%s' not " + "used\n", sym->nam); + if (!sym->persist) { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + + if (errors) { + clear_config(conf); + return (NULL); + } + + if (conf->rtr_id.s_addr == 0) + conf->rtr_id.s_addr = get_rtr_id(); + + return (conf); +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); + sym = TAILQ_NEXT(sym, entry)) + ; /* nothing */ + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entry); + return (0); +} + +int +cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + size_t len; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + + len = strlen(s) - strlen(val) + 1; + if ((sym = malloc(len)) == NULL) + errx(1, "cmdline_symset: malloc"); + + strlcpy(sym, s, len); + + ret = symset(sym, val + 1, 1); + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + return (NULL); +} + +struct eigrp * +conf_get_instance(uint16_t as) +{ + struct eigrp *e; + + if (eigrp_find(conf, af, as)) { + yyerror("autonomous-system %u already configured" + "for address-family %s", as, af_name(af)); + return (NULL); + } + + e = calloc(1, sizeof(struct eigrp)); + e->af = af; + e->as = as; + SIMPLEQ_INIT(&e->redist_list); + TAILQ_INIT(&e->ei_list); + RB_INIT(&e->nbrs); + RB_INIT(&e->topology); + + /* start local sequence number used by RTP */ + e->seq_num = 1; + + return (e); +} + +struct eigrp_iface * +conf_get_if(struct kif *kif) +{ + struct eigrp_iface *e; + + TAILQ_FOREACH(e, &eigrp->ei_list, e_entry) + if (e->iface->ifindex == kif->ifindex) { + yyerror("interface %s already configured " + "for address-family %s and " + "autonomous-system %u", kif->ifname, + af_name(af), eigrp->as); + return (NULL); + } + + e = eigrp_if_new(conf, eigrp, kif); + + return (e); +} + +void +clear_config(struct eigrpd_conf *xconf) +{ + free(xconf); +} + +uint32_t +get_rtr_id(void) +{ + struct ifaddrs *ifap, *ifa; + uint32_t ip = 0, cur, localnet; + + localnet = htonl(INADDR_LOOPBACK & IN_CLASSA_NET); + + if (getifaddrs(&ifap) == -1) + fatal("getifaddrs"); + + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (strncmp(ifa->ifa_name, "carp", 4) == 0) + continue; + if (ifa->ifa_addr->sa_family != AF_INET) + continue; + cur = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr; + if ((cur & localnet) == localnet) /* skip 127/8 */ + continue; + if (ntohl(cur) < ntohl(ip) || ip == 0) + ip = cur; + } + freeifaddrs(ifap); + + if (ip == 0) + fatal("router-id is 0.0.0.0"); + + return (ip); +} + +int +host(const char *s, union eigrpd_addr *addr, uint8_t *plen) +{ + char *p, *ps; + const char *errstr; + int maxplen; + + switch (af) { + case AF_INET: + maxplen = 32; + break; + case AF_INET6: + maxplen = 128; + break; + default: + return (-1); + } + + if ((p = strrchr(s, '/')) != NULL) { + *plen = strtonum(p + 1, 0, maxplen, &errstr); + if (errstr) { + log_warnx("prefixlen is %s: %s", errstr, p + 1); + return (-1); + } + if ((ps = malloc(strlen(s) - strlen(p) + 1)) == NULL) + fatal("host: malloc"); + strlcpy(ps, s, strlen(s) - strlen(p) + 1); + } else { + if ((ps = strdup(s)) == NULL) + fatal("host: strdup"); + *plen = maxplen; + } + + memset(addr, 0, sizeof(union eigrpd_addr)); + switch (af) { + case AF_INET: + if (inet_pton(AF_INET, ps, &addr->v4) != 1) + return (-1); + break; + case AF_INET6: + if (inet_pton(AF_INET6, ps, &addr->v6) != 1) + return (-1); + break; + default: + return (-1); + } + eigrp_applymask(af, addr, addr, *plen); + free(ps); + + return (0); +} diff --git a/usr.sbin/eigrpd/printconf.c b/usr.sbin/eigrpd/printconf.c new file mode 100644 index 00000000000..acdbeba07f6 --- /dev/null +++ b/usr.sbin/eigrpd/printconf.c @@ -0,0 +1,162 @@ +/* $OpenBSD: printconf.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@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 <stdio.h> +#include <arpa/inet.h> + +#include "eigrp.h" +#include "eigrpd.h" +#include "eigrpe.h" +#include "log.h" + +void print_mainconf(struct eigrpd_conf *); +const char *print_no(uint16_t); +void print_redist_metric(struct redist_metric *); +void print_redistribute(struct eigrp *); +void print_iface(struct eigrp_iface *); +void print_as(struct eigrp *); +void print_af(struct eigrpd_conf *, int); + +void +print_mainconf(struct eigrpd_conf *conf) +{ + printf("router-id %s\n", inet_ntoa(conf->rtr_id)); + + if (conf->flags & EIGRPD_FLAG_NO_FIB_UPDATE) + printf("fib-update no\n"); + else + printf("fib-update yes\n"); + + printf("rdomain %u\n", conf->rdomain); + printf("fib-priority-internal %u\n", conf->fib_priority_internal); + printf("fib-priority-external %u\n", conf->fib_priority_external); +} + +const char * +print_no(uint16_t type) +{ + if (type & REDIST_NO) + return ("no "); + else + return (""); +} + +void +print_redist_metric(struct redist_metric *metric) +{ + printf(" %u %u %u %u %u", metric->bandwidth, metric->delay, + metric->reliability, metric->load, metric->mtu); +} + +void +print_redistribute(struct eigrp *eigrp) +{ + struct redistribute *r; + + if (eigrp->dflt_metric) { + printf("\t\tdefault-metric"); + print_redist_metric(eigrp->dflt_metric); + printf("\n"); + } + + SIMPLEQ_FOREACH(r, &eigrp->redist_list, entry) { + switch (r->type & ~REDIST_NO) { + case REDIST_STATIC: + printf("\t\t%sredistribute static", print_no(r->type)); + break; + case REDIST_RIP: + printf("\t\t%sredistribute rip", print_no(r->type)); + break; + case REDIST_OSPF: + printf("\t\t%sredistribute ospf", print_no(r->type)); + break; + case REDIST_CONNECTED: + printf("\t\t%sredistribute connected", + print_no(r->type)); + break; + case REDIST_DEFAULT: + printf("\t\t%sredistribute default", print_no(r->type)); + break; + case REDIST_ADDR: + printf("\t\t%sredistribute %s/%u", + print_no(r->type), log_addr(r->af, &r->addr), + r->prefixlen); + break; + } + + if (r->metric) + print_redist_metric(r->metric); + printf("\n"); + } +} + +void +print_iface(struct eigrp_iface *ei) +{ + printf("\t\tinterface %s {\n", ei->iface->name); + printf("\t\t\thello-interval %u\n", ei->hello_interval); + printf("\t\t\tholdtime %u\n", ei->hello_holdtime); + printf("\t\t\tdelay %u\n", ei->delay); + printf("\t\t\tbandwidth %u\n", ei->bandwidth); + printf("\t\t\tsplit-horizon %s\n", (ei->splithorizon) ? "yes" : "no"); + if (ei->passive) + printf("\t\t\tpassive\n"); + printf("\t\t}\n"); +} + +void +print_as(struct eigrp *eigrp) +{ + struct eigrp_iface *ei; + + printf("\tautonomous-system %u {\n", eigrp->as); + printf("\t\tk-values %u %u %u %u %u %u\n", eigrp->kvalues[0], + eigrp->kvalues[1], eigrp->kvalues[2], eigrp->kvalues[3], + eigrp->kvalues[4], eigrp->kvalues[5]); + printf("\t\tmaximum-hops %u\n", eigrp->maximum_hops); + printf("\t\tmaximum-paths %u\n", eigrp->maximum_paths); + printf("\t\tvariance %u\n", eigrp->variance); + print_redistribute(eigrp); + printf("\n"); + TAILQ_FOREACH(ei, &eigrp->ei_list, e_entry) + print_iface(ei); + printf("\t}\n"); +} + +void +print_af(struct eigrpd_conf *conf, int af) +{ + struct eigrp *eigrp; + + printf("address-family %s {\n", af_name(af)); + TAILQ_FOREACH(eigrp, &conf->instances, entry) + if (eigrp->af == af) + print_as(eigrp); + printf("}\n\n"); +} + +void +print_config(struct eigrpd_conf *conf) +{ + printf("\n"); + print_mainconf(conf); + printf("\n"); + + print_af(conf, AF_INET); + print_af(conf, AF_INET6); +} diff --git a/usr.sbin/eigrpd/query.c b/usr.sbin/eigrpd/query.c new file mode 100644 index 00000000000..c6b06c65c97 --- /dev/null +++ b/usr.sbin/eigrpd/query.c @@ -0,0 +1,114 @@ +/* $OpenBSD: query.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@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 <stdlib.h> +#include <netinet/in.h> +#include <netinet/ip6.h> + +#include "eigrpd.h" +#include "eigrp.h" +#include "log.h" +#include "eigrpe.h" + +extern struct eigrpd_conf *econf; + +/* query packet handling */ + +void +send_query(struct eigrp_iface *ei, struct nbr *nbr, + struct rinfo_head *rinfo_list, int siaquery) +{ + struct eigrp *eigrp = ei->eigrp; + struct ibuf *buf; + uint16_t opcode; + struct rinfo_entry *re; + int size; + int route_len; + + if (rinfo_list == NULL || TAILQ_EMPTY(rinfo_list)) + return; + + /* don't exceed the interface's mtu */ + do { + if ((buf = ibuf_dynamic(PKG_DEF_SIZE, + IP_MAXPACKET - sizeof(struct ip))) == NULL) + fatal("send_query"); + + if (!siaquery) + opcode = EIGRP_OPC_QUERY; + else + opcode = EIGRP_OPC_SIAQUERY; + + /* EIGRP header */ + if (gen_eigrp_hdr(buf, opcode, 0, eigrp->seq_num, eigrp->as)) + goto fail; + + switch (eigrp->af) { + case AF_INET: + size = sizeof(struct ip); + break; + case AF_INET6: + size = sizeof(struct ip6_hdr); + break; + default: + break; + } + size += sizeof(struct eigrp_hdr); + + while ((re = TAILQ_FIRST(rinfo_list)) != NULL) { + route_len = len_route_tlv(&re->rinfo); + if (size + route_len > ei->iface->mtu) { + rtp_send(ei, nbr, buf); + break; + } + size += route_len; + + if (gen_route_tlv(buf, &re->rinfo)) + goto fail; + TAILQ_REMOVE(rinfo_list, re, entry); + free(re); + } + } while (!TAILQ_EMPTY(rinfo_list)); + + rtp_send(ei, nbr, buf); + return; +fail: + log_warnx("%s: failed to send message", __func__); + if (rinfo_list) + message_list_clr(rinfo_list); + ibuf_free(buf); + return; +} + +void +recv_query(struct nbr *nbr, struct rinfo_head *rinfo_list, int siaquery) +{ + int type; + struct rinfo_entry *re; + + rtp_ack_start_timer(nbr); + + if (!siaquery) + type = IMSG_RECV_QUERY; + else + type = IMSG_RECV_SIAQUERY; + + TAILQ_FOREACH(re, rinfo_list, entry) + eigrpe_imsg_compose_rde(type, nbr->peerid, 0, &re->rinfo, + sizeof(re->rinfo)); +} diff --git a/usr.sbin/eigrpd/rde.c b/usr.sbin/eigrpd/rde.c new file mode 100644 index 00000000000..3b543648f35 --- /dev/null +++ b/usr.sbin/eigrpd/rde.c @@ -0,0 +1,764 @@ +/* $OpenBSD: rde.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@openbsd.org> + * Copyright (c) 2004, 2005 Claudio Jeker <claudio@openbsd.org> + * Copyright (c) 2004 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@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 <stdlib.h> +#include <unistd.h> +#include <arpa/inet.h> +#include <errno.h> +#include <signal.h> +#include <string.h> +#include <pwd.h> + +#include "eigrp.h" +#include "eigrpd.h" +#include "eigrpe.h" +#include "log.h" +#include "rde.h" + +void rde_sig_handler(int sig, short, void *); +void rde_shutdown(void); +void rde_dispatch_imsg(int, short, void *); +void rde_dispatch_parent(int, short, void *); + +struct eigrpd_conf *rdeconf = NULL, *nconf; +struct imsgev *iev_eigrpe; +struct imsgev *iev_main; + +extern struct iface_id_head ifaces_by_id; +RB_PROTOTYPE(iface_id_head, eigrp_iface, id_tree, iface_id_compare) + +RB_PROTOTYPE(rt_tree, rt_node, entry, rt_compare) + +extern struct rde_nbr_head rde_nbrs; +RB_PROTOTYPE(rde_nbr_head, rde_nbr, entry, rde_nbr_compare) + +/* ARGSUSED */ +void +rde_sig_handler(int sig, short event, void *arg) +{ + /* + * signal handler rules don't apply, libevent decouples for us + */ + + switch (sig) { + case SIGINT: + case SIGTERM: + rde_shutdown(); + /* NOTREACHED */ + default: + fatalx("unexpected signal"); + } +} + +/* route decision engine */ +pid_t +rde(struct eigrpd_conf *xconf, int pipe_parent2rde[2], int pipe_eigrpe2rde[2], + int pipe_parent2eigrpe[2]) +{ + struct event ev_sigint, ev_sigterm; + struct timeval now; + struct passwd *pw; + pid_t pid; + struct eigrp *eigrp; + + switch (pid = fork()) { + case -1: + fatal("cannot fork"); + /* NOTREACHED */ + case 0: + break; + default: + return (pid); + } + + rdeconf = xconf; + + if ((pw = getpwnam(EIGRPD_USER)) == NULL) + fatal("getpwnam"); + + if (chroot(pw->pw_dir) == -1) + fatal("chroot"); + if (chdir("/") == -1) + fatal("chdir(\"/\")"); + + setproctitle("route decision engine"); + eigrpd_process = PROC_RDE_ENGINE; + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("can't drop privileges"); + + event_init(); + + /* setup signal handler */ + signal_set(&ev_sigint, SIGINT, rde_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, rde_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + /* setup pipes */ + close(pipe_eigrpe2rde[0]); + close(pipe_parent2rde[0]); + close(pipe_parent2eigrpe[0]); + close(pipe_parent2eigrpe[1]); + + if ((iev_eigrpe = malloc(sizeof(struct imsgev))) == NULL || + (iev_main = malloc(sizeof(struct imsgev))) == NULL) + fatal(NULL); + imsg_init(&iev_eigrpe->ibuf, pipe_eigrpe2rde[1]); + iev_eigrpe->handler = rde_dispatch_imsg; + imsg_init(&iev_main->ibuf, pipe_parent2rde[1]); + iev_main->handler = rde_dispatch_parent; + + /* setup event handler */ + iev_eigrpe->events = EV_READ; + event_set(&iev_eigrpe->ev, iev_eigrpe->ibuf.fd, iev_eigrpe->events, + iev_eigrpe->handler, iev_eigrpe); + event_add(&iev_eigrpe->ev, NULL); + + iev_main->events = EV_READ; + event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events, + iev_main->handler, iev_main); + event_add(&iev_main->ev, NULL); + + gettimeofday(&now, NULL); + rdeconf->uptime = now.tv_sec; + + TAILQ_FOREACH(eigrp, &rdeconf->instances, entry) + rde_instance_init(eigrp); + + event_dispatch(); + + rde_shutdown(); + /* NOTREACHED */ + + return (0); +} + +void +rde_shutdown(void) +{ + config_clear(rdeconf); + + msgbuf_clear(&iev_eigrpe->ibuf.w); + free(iev_eigrpe); + msgbuf_clear(&iev_main->ibuf.w); + free(iev_main); + + log_info("route decision engine exiting"); + _exit(0); +} + +int +rde_imsg_compose_parent(int type, pid_t pid, void *data, uint16_t datalen) +{ + return (imsg_compose_event(iev_main, type, 0, pid, -1, + data, datalen)); +} + +int +rde_imsg_compose_eigrpe(int type, uint32_t peerid, pid_t pid, void *data, + uint16_t datalen) +{ + return (imsg_compose_event(iev_eigrpe, type, peerid, pid, -1, + data, datalen)); +} + +/* ARGSUSED */ +void +rde_dispatch_imsg(int fd, short event, void *bula) +{ + struct imsgev *iev = bula; + struct imsgbuf *ibuf; + struct imsg imsg; + struct rde_nbr *nbr; + struct rde_nbr new; + struct rinfo rinfo; + ssize_t n; + int shut = 0, verbose; + + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1) + fatal("imsg_read error"); + if (n == 0) /* connection closed */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* connection closed */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("rde_dispatch_imsg: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_NEIGHBOR_UP: + if (imsg.hdr.len - IMSG_HEADER_SIZE != + sizeof(struct rde_nbr)) + fatalx("invalid size of neighbor request"); + memcpy(&new, imsg.data, sizeof(new)); + + if (rde_nbr_find(imsg.hdr.peerid)) + fatalx("rde_dispatch_imsg: " + "neighbor already exists"); + rde_nbr_new(imsg.hdr.peerid, &new); + break; + case IMSG_NEIGHBOR_DOWN: + nbr = rde_nbr_find(imsg.hdr.peerid); + if (nbr == NULL) { + log_debug("%s: cannot find rde neighbor", + __func__); + break; + } + + rde_check_link_down_nbr(nbr); + rde_flush_queries(); + rde_nbr_del(rde_nbr_find(imsg.hdr.peerid)); + break; + case IMSG_RECV_UPDATE_INIT: + nbr = rde_nbr_find(imsg.hdr.peerid); + if (nbr == NULL) { + log_debug("%s: cannot find rde neighbor", + __func__); + break; + } + + rt_snap(nbr); + break; + case IMSG_RECV_UPDATE: + case IMSG_RECV_QUERY: + case IMSG_RECV_REPLY: + case IMSG_RECV_SIAQUERY: + case IMSG_RECV_SIAREPLY: + nbr = rde_nbr_find(imsg.hdr.peerid); + if (nbr == NULL) { + log_debug("%s: cannot find rde neighbor", + __func__); + break; + } + + if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(rinfo)) + fatalx("invalid size of rinfo"); + memcpy(&rinfo, imsg.data, sizeof(rinfo)); + + switch (imsg.hdr.type) { + case IMSG_RECV_UPDATE: + rde_check_update(nbr, &rinfo); + break; + case IMSG_RECV_QUERY: + rde_check_query(nbr, &rinfo, 0); + break; + case IMSG_RECV_REPLY: + rde_check_reply(nbr, &rinfo, 0); + break; + case IMSG_RECV_SIAQUERY: + rde_check_query(nbr, &rinfo, 1); + break; + case IMSG_RECV_SIAREPLY: + rde_check_reply(nbr, &rinfo, 1); + break; + } + break; + case IMSG_CTL_SHOW_TOPOLOGY: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct ctl_show_topology_req)) { + log_warnx("%s: wrong imsg len", __func__); + break; + } + + rt_dump(imsg.data, imsg.hdr.pid); + rde_imsg_compose_eigrpe(IMSG_CTL_END, 0, imsg.hdr.pid, + NULL, 0); + break; + case IMSG_CTL_LOG_VERBOSE: + /* already checked by eigrpe */ + memcpy(&verbose, imsg.data, sizeof(verbose)); + log_verbose(verbose); + break; + default: + log_debug("rde_dispatch_imsg: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +/* ARGSUSED */ +void +rde_dispatch_parent(int fd, short event, void *bula) +{ + struct iface *niface; + static struct eigrp *neigrp; + struct eigrp_iface *nei; + struct imsg imsg; + struct imsgev *iev = bula; + struct imsgbuf *ibuf; + struct kif *kif; + ssize_t n; + int shut = 0; + + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1) + fatal("imsg_read error"); + if (n == 0) /* connection closed */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* connection closed */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("rde_dispatch_parent: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_IFDOWN: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct kif)) + fatalx("IFDOWN imsg with wrong len"); + kif = imsg.data; + rde_check_link_down(kif->ifindex); + break; + case IMSG_NETWORK_ADD: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct kroute)) + fatalx("IMSG_NETWORK_ADD imsg with wrong len"); + rt_redist_set(imsg.data, 0); + break; + case IMSG_NETWORK_DEL: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct kroute)) + fatalx("IMSG_NETWORK_DEL imsg with wrong len"); + rt_redist_set(imsg.data, 1); + break; + case IMSG_RECONF_CONF: + if ((nconf = malloc(sizeof(struct eigrpd_conf))) == + NULL) + fatal(NULL); + memcpy(nconf, imsg.data, sizeof(struct eigrpd_conf)); + + TAILQ_INIT(&nconf->iface_list); + TAILQ_INIT(&nconf->instances); + break; + case IMSG_RECONF_INSTANCE: + if ((neigrp = malloc(sizeof(struct eigrp))) == NULL) + fatal(NULL); + memcpy(neigrp, imsg.data, sizeof(struct eigrp)); + + SIMPLEQ_INIT(&neigrp->redist_list); + TAILQ_INIT(&neigrp->ei_list); + RB_INIT(&neigrp->nbrs); + RB_INIT(&neigrp->topology); + TAILQ_INSERT_TAIL(&nconf->instances, neigrp, entry); + break; + case IMSG_RECONF_IFACE: + niface = imsg.data; + niface = if_lookup(nconf, niface->ifindex); + if (niface) + break; + + if ((niface = malloc(sizeof(struct iface))) == NULL) + fatal(NULL); + memcpy(niface, imsg.data, sizeof(struct iface)); + + TAILQ_INIT(&niface->addr_list); + TAILQ_INSERT_TAIL(&nconf->iface_list, niface, entry); + break; + case IMSG_RECONF_EIGRP_IFACE: + if ((nei = malloc(sizeof(struct eigrp_iface))) == NULL) + fatal(NULL); + memcpy(nei, imsg.data, sizeof(struct eigrp_iface)); + + nei->iface = niface; + nei->eigrp = neigrp; + TAILQ_INIT(&nei->nbr_list); + TAILQ_INIT(&nei->update_list); + TAILQ_INIT(&nei->query_list); + TAILQ_INSERT_TAIL(&niface->ei_list, nei, i_entry); + TAILQ_INSERT_TAIL(&neigrp->ei_list, nei, e_entry); + if (RB_INSERT(iface_id_head, &ifaces_by_id, nei) != + NULL) + fatalx("rde_dispatch_parent: " + "RB_INSERT(ifaces_by_id) failed"); + break; + case IMSG_RECONF_END: + merge_config(rdeconf, nconf); + nconf = NULL; + break; + default: + log_debug("%s: unexpected imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +void +rde_instance_init(struct eigrp *eigrp) +{ + struct rde_nbr nbr; + + memset(&nbr, 0, sizeof(nbr)); + nbr.flags = F_RDE_NBR_SELF | F_RDE_NBR_REDIST; + eigrp->rnbr_redist = rde_nbr_new(NBR_IDSELF, &nbr); + eigrp->rnbr_redist->eigrp = eigrp; + nbr.flags = F_RDE_NBR_SELF | F_RDE_NBR_SUMMARY; + eigrp->rnbr_summary = rde_nbr_new(NBR_IDSELF, &nbr); + eigrp->rnbr_summary->eigrp = eigrp; +} + +void +rde_instance_del(struct eigrp *eigrp) +{ + struct rde_nbr *nbr, *safe; + struct rt_node *rn; + + /* clear topology */ + while((rn = RB_MIN(rt_tree, &eigrp->topology)) != NULL) + rt_del(rn); + + /* clear nbrs */ + RB_FOREACH_SAFE(nbr, rde_nbr_head, &rde_nbrs, safe) + if (nbr->eigrp == eigrp) + rde_nbr_del(nbr); + rde_nbr_del(eigrp->rnbr_redist); + rde_nbr_del(eigrp->rnbr_summary); + + free(eigrp); +} + +void +rde_send_change_kroute(struct rt_node *rn, struct eigrp_route *route) +{ + struct eigrp *eigrp = route->nbr->eigrp; + struct kroute kr; + + log_debug("%s: %s nbr %s", __func__, log_prefix(rn), + log_addr(eigrp->af, &route->nbr->addr)); + + memset(&kr, 0, sizeof(kr)); + kr.af = eigrp->af; + memcpy(&kr.prefix, &rn->prefix, sizeof(kr.prefix)); + kr.prefixlen = rn->prefixlen; + if (eigrp_addrisset(eigrp->af, &route->nexthop)) + memcpy(&kr.nexthop, &route->nexthop, sizeof(kr.nexthop)); + else + memcpy(&kr.nexthop, &route->nbr->addr, sizeof(kr.nexthop)); + kr.ifindex = route->nbr->ei->iface->ifindex; + if (route->type == EIGRP_ROUTE_EXTERNAL) + kr.priority = rdeconf->fib_priority_external; + else + kr.priority = rdeconf->fib_priority_internal; + + rde_imsg_compose_parent(IMSG_KROUTE_CHANGE, 0, &kr, sizeof(kr)); + + route->flags |= F_EIGRP_ROUTE_INSTALLED; +} + +void +rde_send_delete_kroute(struct rt_node *rn, struct eigrp_route *route) +{ + struct eigrp *eigrp = route->nbr->eigrp; + struct kroute kr; + + log_debug("%s: %s nbr %s", __func__, log_prefix(rn), + log_addr(eigrp->af, &route->nbr->addr)); + + memset(&kr, 0, sizeof(kr)); + kr.af = eigrp->af; + memcpy(&kr.prefix, &rn->prefix, sizeof(kr.prefix)); + kr.prefixlen = rn->prefixlen; + if (eigrp_addrisset(eigrp->af, &route->nexthop)) + memcpy(&kr.nexthop, &route->nexthop, sizeof(kr.nexthop)); + else + memcpy(&kr.nexthop, &route->nbr->addr, sizeof(kr.nexthop)); + kr.ifindex = route->nbr->ei->iface->ifindex; + if (route->type == EIGRP_ROUTE_EXTERNAL) + kr.priority = rdeconf->fib_priority_external; + else + kr.priority = rdeconf->fib_priority_internal; + + rde_imsg_compose_parent(IMSG_KROUTE_DELETE, 0, &kr, sizeof(kr)); + + route->flags &= ~F_EIGRP_ROUTE_INSTALLED; +} + +static struct redistribute * +eigrp_redistribute(struct eigrp *eigrp, struct kroute *kr) +{ + struct redistribute *r; + uint8_t is_default = 0; + union eigrpd_addr addr; + + /* only allow the default route via REDIST_DEFAULT */ + if (!eigrp_addrisset(kr->af, &kr->prefix) && kr->prefixlen == 0) + is_default = 1; + + SIMPLEQ_FOREACH(r, &eigrp->redist_list, entry) { + switch (r->type & ~REDIST_NO) { + case REDIST_STATIC: + if (is_default) + continue; + if (kr->flags & F_STATIC) + return (r->type & REDIST_NO ? NULL : r); + break; + case REDIST_RIP: + if (is_default) + continue; + if (kr->priority == RTP_RIP) + return (r->type & REDIST_NO ? NULL : r); + break; + case REDIST_OSPF: + if (is_default) + continue; + if (kr->priority == RTP_OSPF) + return (r->type & REDIST_NO ? NULL : r); + break; + case REDIST_CONNECTED: + if (is_default) + continue; + if (kr->flags & F_CONNECTED) + return (r->type & REDIST_NO ? NULL : r); + break; + case REDIST_ADDR: + if (eigrp_addrisset(r->af, &r->addr) && + r->prefixlen == 0) { + if (is_default) + return (r->type & REDIST_NO ? NULL : r); + else + return (0); + } + + eigrp_applymask(kr->af, &addr, &kr->prefix, + r->prefixlen); + if (eigrp_addrcmp(kr->af, &addr, &r->addr) == 0 && + kr->prefixlen >= r->prefixlen) + return (r->type & REDIST_NO ? NULL : r); + break; + case REDIST_DEFAULT: + if (is_default) + return (r->type & REDIST_NO ? NULL : r); + break; + } + } + + return (NULL); +} + +void +rt_redist_set(struct kroute *kr, int withdraw) +{ + struct eigrp *eigrp; + struct redistribute *r; + struct redist_metric *rmetric; + struct rinfo ri; + + TAILQ_FOREACH(eigrp, &rdeconf->instances, entry) { + if (eigrp->af != kr->af) + continue; + + r = eigrp_redistribute(eigrp, kr); + if (r == NULL) + continue; + + if (r->metric) + rmetric = r->metric; + else if (eigrp->dflt_metric) + rmetric = eigrp->dflt_metric; + else + continue; + + memset(&ri, 0, sizeof(ri)); + ri.af = kr->af; + ri.type = EIGRP_ROUTE_EXTERNAL; + memcpy(&ri.prefix, &kr->prefix, sizeof(ri.prefix)); + ri.prefixlen = kr->prefixlen; + + /* metric */ + if (withdraw) + ri.metric.delay = EIGRP_INFINITE_METRIC; + else + ri.metric.delay = eigrp_composite_delay(rmetric->delay); + ri.metric.bandwidth = + eigrp_composite_bandwidth(rmetric->bandwidth); + metric_encode_mtu(ri.metric.mtu, rmetric->mtu); + ri.metric.hop_count = 0; + ri.metric.reliability = rmetric->reliability; + ri.metric.load = rmetric->load; + ri.metric.tag = 0; + ri.metric.flags = 0; + + /* external metric */ + ri.emetric.routerid = htonl(eigrp_router_id(rdeconf)); + ri.emetric.as = r->emetric.as; + ri.emetric.tag = r->emetric.tag; + ri.emetric.metric = r->emetric.metric; + if (kr->priority == rdeconf->fib_priority_internal) + ri.emetric.protocol = EIGRP_EXT_PROTO_EIGRP; + else if (kr->priority == RTP_STATIC) + ri.emetric.protocol = EIGRP_EXT_PROTO_STATIC; + else if (kr->priority == RTP_RIP) + ri.emetric.protocol = EIGRP_EXT_PROTO_RIP; + else if (kr->priority == RTP_OSPF) + ri.emetric.protocol = EIGRP_EXT_PROTO_OSPF; + else + ri.emetric.protocol = EIGRP_EXT_PROTO_CONN; + ri.emetric.flags = 0; + + rde_check_update(eigrp->rnbr_redist, &ri); + } +} + +/* send all known routing information to new neighbor */ +void +rt_snap(struct rde_nbr *nbr) +{ + struct eigrp *eigrp = nbr->eigrp; + struct rt_node *rn; + struct rinfo ri; + + RB_FOREACH(rn, rt_tree, &eigrp->topology) + if (rn->state == DUAL_STA_PASSIVE) { + rinfo_fill_successor(rn, &ri); + rde_imsg_compose_eigrpe(IMSG_SEND_UPDATE, + nbr->peerid, 0, &ri, sizeof(ri)); + } + + rde_imsg_compose_eigrpe(IMSG_SEND_UPDATE_END, nbr->peerid, 0, + NULL, 0); +} + +struct ctl_rt * +rt_to_ctl(struct rt_node *rn, struct eigrp_route *route) +{ + static struct ctl_rt rtctl; + + memset(&rtctl, 0, sizeof(rtctl)); + rtctl.af = route->nbr->eigrp->af; + rtctl.as = route->nbr->eigrp->as; + memcpy(&rtctl.prefix, &rn->prefix, sizeof(rtctl.prefix)); + rtctl.prefixlen = rn->prefixlen; + rtctl.type = route->type; + memcpy(&rtctl.nexthop, &route->nbr->addr, sizeof(rtctl.nexthop)); + if (route->nbr->flags & F_RDE_NBR_REDIST) + strlcpy(rtctl.ifname, "redistribute", sizeof(rtctl.ifname)); + else if (route->nbr->flags & F_RDE_NBR_SUMMARY) + strlcpy(rtctl.ifname, "summary", sizeof(rtctl.ifname)); + else + memcpy(rtctl.ifname, route->nbr->ei->iface->name, + sizeof(rtctl.ifname)); + rtctl.distance = route->distance; + rtctl.rdistance = route->rdistance; + rtctl.fdistance = rn->successor.fdistance; + rtctl.state = rn->state; + /* metric */ + rtctl.metric.delay = eigrp_real_delay(route->metric.delay); + /* translate to microseconds */ + rtctl.metric.delay *= 10; + rtctl.metric.bandwidth = eigrp_real_bandwidth(route->metric.bandwidth); + rtctl.metric.mtu = metric_decode_mtu(route->metric.mtu); + rtctl.metric.hop_count = route->metric.hop_count; + rtctl.metric.reliability = route->metric.reliability; + rtctl.metric.load = route->metric.load; + /* external metric */ + memcpy(&rtctl.emetric, &route->emetric, sizeof(rtctl.emetric)); + + if (route->nbr == rn->successor.nbr) + rtctl.flags |= F_CTL_RT_SUCCESSOR; + else if (route->rdistance < rn->successor.fdistance) + rtctl.flags |= F_CTL_RT_FSUCCESSOR; + + return (&rtctl); +} + +void +rt_dump(struct ctl_show_topology_req *treq, pid_t pid) +{ + struct eigrp *eigrp; + struct rt_node *rn; + struct eigrp_route *route; + struct ctl_rt *rtctl; + int first = 1; + + TAILQ_FOREACH(eigrp, &rdeconf->instances, entry) { + RB_FOREACH(rn, rt_tree, &eigrp->topology) { + if (eigrp_addrisset(treq->af, &treq->prefix) && + eigrp_addrcmp(treq->af, &treq->prefix, + &rn->prefix)) + continue; + + if (treq->prefixlen && + (treq->prefixlen != rn->prefixlen)) + continue; + + first = 1; + TAILQ_FOREACH(route, &rn->routes, entry) { + if (treq->flags & F_CTL_ACTIVE && + !(rn->state & DUAL_STA_ACTIVE_ALL)) + continue; + if (!(treq->flags & F_CTL_ALLLINKS) && + route->rdistance >= rn->successor.fdistance) + continue; + + rtctl = rt_to_ctl(rn, route); + if (first) { + rtctl->flags |= F_CTL_RT_FIRST; + first = 0; + } + rde_imsg_compose_eigrpe(IMSG_CTL_SHOW_TOPOLOGY, + 0, pid, rtctl, sizeof(*rtctl)); + } + } + } +} diff --git a/usr.sbin/eigrpd/rde.h b/usr.sbin/eigrpd/rde.h new file mode 100644 index 00000000000..ba491a53142 --- /dev/null +++ b/usr.sbin/eigrpd/rde.h @@ -0,0 +1,196 @@ +/* $OpenBSD: rde.h,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@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. + */ + +#ifndef _RDE_H_ +#define _RDE_H_ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/tree.h> +#include <sys/queue.h> +#include <event.h> +#include <limits.h> + +#define min(x,y) ((x) <= (y) ? (x) : (y)) + +/* just the info RDE needs */ +struct rde_nbr { + RB_ENTRY(rde_nbr) entry; + uint32_t peerid; + uint32_t ifaceid; + union eigrpd_addr addr; + struct eigrp_iface *ei; + struct eigrp *eigrp; + TAILQ_HEAD(,reply_node) rijk; /* outstanding replies */ + uint8_t flags; +}; +/* + * We have one "self" neighbor for each interface on which EIGRP is configured. + * This way we can inject local routes into the DUAL FSM just like any other + * route received from a remote neighbor. For each instance, we also have two + * additional special neighbors used to inject redistributed and summarized + * routes. + */ +#define F_RDE_NBR_SELF 0x01 +#define F_RDE_NBR_REDIST 0x02 +#define F_RDE_NBR_SUMMARY 0x04 + +struct reply_node { + TAILQ_ENTRY(reply_node) rn_entry; + TAILQ_ENTRY(reply_node) nbr_entry; + struct event ev_active_timeout; + struct event ev_sia_timeout; + int siaquery_sent; + int siareply_recv; + struct rt_node *rn; + struct rde_nbr *nbr; +}; + +struct eigrp_route { + TAILQ_ENTRY(eigrp_route) entry; + struct rde_nbr *nbr; /* advertising nbr */ + enum route_type type; + union eigrpd_addr nexthop; + uint32_t distance; /* local distance */ + uint32_t rdistance; /* reported distance */ + struct classic_metric metric; /* metric */ + struct classic_emetric emetric; /* external metric */ + uint8_t flags; +}; +#define F_EIGRP_ROUTE_INSTALLED 0x01 +#define F_EIGRP_ROUTE_M_CHANGED 0x02 + +struct rt_node { + RB_ENTRY(rt_node) entry; + struct eigrp *eigrp; + union eigrpd_addr prefix; + uint8_t prefixlen; + int state; + TAILQ_HEAD(,eigrp_route) routes; + TAILQ_HEAD(,reply_node) rijk; /* outstanding replies */ + + struct { + struct rde_nbr *nbr; + enum route_type type; + uint32_t fdistance; + uint32_t rdistance; + struct classic_metric metric; + struct classic_emetric emetric; + } successor; +}; + +/* DUAL states */ +#define DUAL_STA_PASSIVE 0x0001 +#define DUAL_STA_ACTIVE0 0x0002 +#define DUAL_STA_ACTIVE1 0x0004 +#define DUAL_STA_ACTIVE2 0x0008 +#define DUAL_STA_ACTIVE3 0x0010 +#define DUAL_STA_ACTIVE_ALL (DUAL_STA_ACTIVE0 | DUAL_STA_ACTIVE1 | \ + DUAL_STA_ACTIVE2 | DUAL_STA_ACTIVE3) + +enum dual_event { + DUAL_EVT_1, + DUAL_EVT_2, + DUAL_EVT_3, + DUAL_EVT_4, + DUAL_EVT_5, + DUAL_EVT_6, + DUAL_EVT_7, + DUAL_EVT_8, + DUAL_EVT_9, + DUAL_EVT_10, + DUAL_EVT_11, + DUAL_EVT_12, + DUAL_EVT_13, + DUAL_EVT_14, + DUAL_EVT_15, + DUAL_EVT_16 +}; + +/* rde.c */ +pid_t rde(struct eigrpd_conf *, int [2], int [2], int [2]); +int rde_imsg_compose_parent(int, pid_t, void *, uint16_t); +int rde_imsg_compose_eigrpe(int, uint32_t, pid_t, void *, + uint16_t); + +void rde_instance_init(struct eigrp *); +void rde_instance_del(struct eigrp *); +void rde_send_change_kroute(struct rt_node *, struct eigrp_route *); +void rde_send_delete_kroute(struct rt_node *, struct eigrp_route *); +void rt_redist_set(struct kroute *, int); +void rt_snap(struct rde_nbr *); +struct ctl_rt *rt_to_ctl(struct rt_node *, struct eigrp_route *); +void rt_dump(struct ctl_show_topology_req *, pid_t); + +/* rde_nbr.c */ +struct rde_nbr *rde_nbr_find(uint32_t); +struct rde_nbr *rde_nbr_new(uint32_t, struct rde_nbr *); +void rde_nbr_del(struct rde_nbr *); + +/* rde_dual.c */ +int dual_fsm(struct rt_node *, enum dual_event); +struct rt_node *rt_find(struct eigrp *, struct rinfo *); +struct rt_node *rt_new(struct eigrp *, struct rinfo *); +void rt_del(struct rt_node *); +struct eigrp_route *route_find(struct rde_nbr *, struct rt_node *); +struct eigrp_route *route_new(struct rt_node *, struct rde_nbr *, + struct rinfo *); +void route_del(struct rt_node *, struct eigrp_route *); +uint32_t safe_sum_uint32(uint32_t, uint32_t); +uint32_t eigrp_composite_delay(uint32_t); +uint32_t eigrp_real_delay(uint32_t); +uint32_t eigrp_composite_bandwidth(uint32_t); +uint32_t eigrp_real_bandwidth(uint32_t); +void route_update_metrics(struct eigrp_route *, + struct rinfo *); +void reply_outstanding_add(struct rt_node *, + struct rde_nbr *); +struct reply_node *reply_outstanding_find(struct rt_node *, + struct rde_nbr *); +void reply_outstanding_remove(struct reply_node *); +void rinfo_fill_successor(struct rt_node *, struct rinfo *); +void rinfo_fill_infinite(struct rt_node *, enum route_type, + struct rinfo *); +void rt_update_fib(struct rt_node *); +void rt_set_successor(struct rt_node *, + struct eigrp_route *); +struct eigrp_route *rt_get_successor_fc(struct rt_node *); + +void rde_send_ack(struct rde_nbr *); +void rde_send_update(struct eigrp_iface *, struct rinfo *); +void rde_send_update_all(struct rt_node *, struct rinfo *); +void rde_send_query(struct eigrp_iface *, struct rinfo *, + int); +void rde_send_siaquery(struct rde_nbr *, struct rinfo *); +void rde_send_query_all(struct eigrp *, struct rt_node *, + int); +void rde_flush_queries(void); +void rde_send_reply(struct rde_nbr *, struct rinfo *, int); +void rde_check_update(struct rde_nbr *, struct rinfo *); +void rde_check_query(struct rde_nbr *, struct rinfo *, int); +void rde_last_reply(struct rt_node *); +void rde_check_reply(struct rde_nbr *, struct rinfo *, int); +void rde_check_link_down_rn(struct rde_nbr *, + struct rt_node *, struct eigrp_route *); +void rde_check_link_down_nbr(struct rde_nbr *); +void rde_check_link_down(unsigned int); +struct eigrp_interface; +void rde_check_link_cost_change(struct rde_nbr *, + struct eigrp_interface *); + +#endif /* _RDE_H_ */ diff --git a/usr.sbin/eigrpd/rde_dual.c b/usr.sbin/eigrpd/rde_dual.c new file mode 100644 index 00000000000..6af2dc2e90b --- /dev/null +++ b/usr.sbin/eigrpd/rde_dual.c @@ -0,0 +1,1245 @@ +/* $OpenBSD: rde_dual.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@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 <stdlib.h> +#include <string.h> +#include <arpa/inet.h> + +#include "eigrp.h" +#include "eigrpd.h" +#include "eigrpe.h" +#include "log.h" +#include "rde.h" + +void reply_active_timer(int, short, void *); +void reply_active_start_timer(struct reply_node *); +void reply_active_stop_timer(struct reply_node *); +void reply_sia_timer(int, short, void *); +void reply_sia_start_timer(struct reply_node *); +void reply_sia_stop_timer(struct reply_node *); + +extern struct eigrpd_conf *rdeconf; + +static int rt_compare(struct rt_node *, struct rt_node *); +RB_PROTOTYPE(rt_tree, rt_node, entry, rt_compare) +RB_GENERATE(rt_tree, rt_node, entry, rt_compare) + +static __inline int rde_nbr_compare(struct rde_nbr *, struct rde_nbr *); +RB_HEAD(rde_nbr_head, rde_nbr); +RB_PROTOTYPE(rde_nbr_head, rde_nbr, entry, rde_nbr_compare) +RB_GENERATE(rde_nbr_head, rde_nbr, entry, rde_nbr_compare) + +struct rde_nbr_head rde_nbrs = RB_INITIALIZER(&rde_nbrs); + +/* + * NOTE: events that don't cause a state transition aren't triggered to avoid + * too much verbosity and are here mostly for illustration purposes. + */ +struct { + int state; + enum dual_event event; + int new_state; +} dual_fsm_tbl[] = { + /* current state event resulting state */ +/* Passive */ + {DUAL_STA_PASSIVE, DUAL_EVT_1, 0}, + {DUAL_STA_PASSIVE, DUAL_EVT_2, 0}, + {DUAL_STA_PASSIVE, DUAL_EVT_3, DUAL_STA_ACTIVE3}, + {DUAL_STA_PASSIVE, DUAL_EVT_4, DUAL_STA_ACTIVE1}, +/* Active Oij=0 */ + {DUAL_STA_ACTIVE0, DUAL_EVT_5, DUAL_STA_ACTIVE2}, + {DUAL_STA_ACTIVE0, DUAL_EVT_11, DUAL_STA_ACTIVE1}, + {DUAL_STA_ACTIVE0, DUAL_EVT_14, DUAL_STA_PASSIVE}, +/* Active Oij=1 */ + {DUAL_STA_ACTIVE1, DUAL_EVT_5, DUAL_STA_ACTIVE2}, + {DUAL_STA_ACTIVE1, DUAL_EVT_9, DUAL_STA_ACTIVE0}, + {DUAL_STA_ACTIVE1, DUAL_EVT_15, DUAL_STA_PASSIVE}, +/* Active Oij=2 */ + {DUAL_STA_ACTIVE2, DUAL_EVT_12, DUAL_STA_ACTIVE3}, + {DUAL_STA_ACTIVE2, DUAL_EVT_16, DUAL_STA_PASSIVE}, +/* Active Oij=3 */ + {DUAL_STA_ACTIVE3, DUAL_EVT_10, DUAL_STA_ACTIVE2}, + {DUAL_STA_ACTIVE3, DUAL_EVT_13, DUAL_STA_PASSIVE}, +/* Active (all) */ + {DUAL_STA_ACTIVE_ALL, DUAL_EVT_6, 0}, + {DUAL_STA_ACTIVE_ALL, DUAL_EVT_7, 0}, + {DUAL_STA_ACTIVE_ALL, DUAL_EVT_8, 0}, +/* sentinel */ + {-1, 0, 0}, +}; + +const char * const dual_event_names[] = { + "DUAL_EVT_1", + "DUAL_EVT_2", + "DUAL_EVT_3", + "DUAL_EVT_4", + "DUAL_EVT_5", + "DUAL_EVT_6", + "DUAL_EVT_7", + "DUAL_EVT_8", + "DUAL_EVT_9", + "DUAL_EVT_10", + "DUAL_EVT_11", + "DUAL_EVT_12", + "DUAL_EVT_13", + "DUAL_EVT_14", + "DUAL_EVT_15", + "DUAL_EVT_16" +}; + +int +dual_fsm(struct rt_node *rn, enum dual_event event) +{ + int old_state; + int new_state = 0; + int i; + + old_state = rn->state; + for (i = 0; dual_fsm_tbl[i].state != -1; i++) + if ((dual_fsm_tbl[i].state & old_state) && + (dual_fsm_tbl[i].event == event)) { + new_state = dual_fsm_tbl[i].new_state; + break; + } + + if (dual_fsm_tbl[i].state == -1) { + /* event outside of the defined fsm, ignore it. */ + log_warnx("%s: route %s, event %s not expected in state %s", + __func__, log_prefix(rn), dual_event_names[event], + dual_state_name(old_state)); + return (0); + } + + if (new_state != 0) + rn->state = new_state; + + if (old_state != rn->state) { + log_debug("%s: event %s changing state for prefix %s " + "from %s to %s", __func__, dual_event_names[event], + log_prefix(rn), dual_state_name(old_state), + dual_state_name(rn->state)); + + if (old_state == DUAL_STA_PASSIVE || + new_state == DUAL_STA_PASSIVE) + rt_update_fib(rn); + } + + return (0); +} + +static int +rt_compare(struct rt_node *a, struct rt_node *b) +{ + if (a->eigrp->af < b->eigrp->af) + return (-1); + if (a->eigrp->af > b->eigrp->af) + return (1); + + switch (a->eigrp->af) { + case AF_INET: + if (ntohl(a->prefix.v4.s_addr) < + ntohl(b->prefix.v4.s_addr)) + return (-1); + if (ntohl(a->prefix.v4.s_addr) > + ntohl(b->prefix.v4.s_addr)) + return (1); + break; + case AF_INET6: + if (memcmp(a->prefix.v6.s6_addr, + b->prefix.v6.s6_addr, 16) < 0) + return (-1); + if (memcmp(a->prefix.v6.s6_addr, + b->prefix.v6.s6_addr, 16) > 0) + return (1); + break; + default: + log_debug("%s: unexpected address-family", __func__); + break; + } + + if (a->prefixlen < b->prefixlen) + return (-1); + if (a->prefixlen > b->prefixlen) + return (1); + + return (0); +} + +struct rt_node * +rt_find(struct eigrp *eigrp, struct rinfo *ri) +{ + struct rt_node rn; + + rn.eigrp = eigrp; + memcpy(&rn.prefix, &ri->prefix, sizeof(rn.prefix)); + rn.prefixlen = ri->prefixlen; + + return (RB_FIND(rt_tree, &eigrp->topology, &rn)); +} + +struct rt_node * +rt_new(struct eigrp *eigrp, struct rinfo *ri) +{ + struct rt_node *rn; + + if ((rn = calloc(1, sizeof(*rn))) == NULL) + fatal("rt_new"); + + rn->eigrp = eigrp; + memcpy(&rn->prefix, &ri->prefix, sizeof(rn->prefix)); + rn->prefixlen = ri->prefixlen; + rn->state = DUAL_STA_PASSIVE; + TAILQ_INIT(&rn->routes); + TAILQ_INIT(&rn->rijk); + rt_set_successor(rn, NULL); + + if (RB_INSERT(rt_tree, &eigrp->topology, rn) != NULL) { + log_warnx("%s failed for %s", __func__, log_prefix(rn)); + free(rn); + return (NULL); + } + + log_debug("%s: prefix %s", __func__, log_prefix(rn)); + + return (rn); +} + +void +rt_del(struct rt_node *rn) +{ + struct eigrp_route *route; + struct reply_node *reply; + + log_debug("%s: prefix %s", __func__, log_prefix(rn)); + + while ((reply = TAILQ_FIRST(&rn->rijk)) != NULL) + reply_outstanding_remove(reply); + while ((route = TAILQ_FIRST(&rn->routes)) != NULL) + route_del(rn, route); + RB_REMOVE(rt_tree, &rn->eigrp->topology, rn); + free(rn); +} + +struct eigrp_route * +route_find(struct rde_nbr *nbr, struct rt_node *rn) +{ + struct eigrp_route *route; + + TAILQ_FOREACH(route, &rn->routes, entry) + if (route->nbr == nbr) + return (route); + + return (NULL); +} + +static const char * +route_print_origin(int af, struct rde_nbr *nbr) +{ + if (nbr->flags & F_RDE_NBR_SELF) { + if (nbr->flags & F_RDE_NBR_REDIST) + return ("redistribute"); + if (nbr->flags & F_RDE_NBR_SUMMARY) + return ("summary"); + else + return ("connected"); + } + + return (log_addr(af, &nbr->addr)); +} + +struct eigrp_route * +route_new(struct rt_node *rn, struct rde_nbr *nbr, struct rinfo *ri) +{ + struct eigrp *eigrp = rn->eigrp; + struct eigrp_route *route; + + if ((route = calloc(1, sizeof(*route))) == NULL) + fatal("route_new"); + + route->nbr = nbr; + route->type = ri->type; + memcpy(&route->nexthop, &ri->nexthop, sizeof(route->nexthop)); + route_update_metrics(route, ri); + TAILQ_INSERT_TAIL(&rn->routes, route, entry); + + log_debug("%s: prefix %s via %s distance (%u/%u)", __func__, + log_prefix(rn), route_print_origin(eigrp->af, route->nbr), + route->distance, route->rdistance); + + return (route); +} + +void +route_del(struct rt_node *rn, struct eigrp_route *route) +{ + struct eigrp *eigrp = rn->eigrp; + + log_debug("%s: prefix %s via %s", __func__, log_prefix(rn), + route_print_origin(eigrp->af, route->nbr)); + + if (route->flags & F_EIGRP_ROUTE_INSTALLED) + rde_send_delete_kroute(rn, route); + + TAILQ_REMOVE(&rn->routes, route, entry); + free(route); +} + +uint32_t +safe_sum_uint32(uint32_t a, uint32_t b) +{ + uint64_t total; + + total = (uint64_t) a + (uint64_t) b; + + if (total >> 32) + return ((uint32_t )(~0)); + + return (total & 0xFFFFFFFF); +} + +uint32_t +eigrp_composite_delay(uint32_t delay) +{ + /* + * NOTE: the multiplication below has no risk of overflow + * because of the maximum configurable delay. + */ + return (delay * EIGRP_SCALING_FACTOR); +} + +uint32_t +eigrp_real_delay(uint32_t delay) +{ + return (delay / EIGRP_SCALING_FACTOR); +} + +uint32_t +eigrp_composite_bandwidth(uint32_t bandwidth) +{ + return ((EIGRP_SCALING_FACTOR * (uint32_t)10000000) / bandwidth); +} + +/* the formula is the same but let's focus on keeping the code readable */ +uint32_t +eigrp_real_bandwidth(uint32_t bandwidth) +{ + return ((EIGRP_SCALING_FACTOR * (uint32_t)10000000) / bandwidth); +} + +void +route_update_metrics(struct eigrp_route *route, struct rinfo *ri) +{ + uint32_t bandwidth; + int mtu; + + if (route->nbr->flags & F_RDE_NBR_SELF) { + memcpy(&route->metric, &ri->metric, sizeof(route->metric)); + memcpy(&route->emetric, &ri->emetric, sizeof(route->emetric)); + route->rdistance = 0; + + /* no need to update the local metric */ + } else { + memcpy(&route->metric, &ri->metric, sizeof(route->metric)); + memcpy(&route->emetric, &ri->emetric, sizeof(route->emetric)); + route->rdistance = safe_sum_uint32(ri->metric.delay, + ri->metric.bandwidth); + + /* update delay. */ + route->metric.delay = safe_sum_uint32(route->metric.delay, + eigrp_composite_delay(route->nbr->ei->delay)); + + /* update bandwidth */ + bandwidth = min(route->nbr->ei->bandwidth, + eigrp_real_bandwidth(route->metric.bandwidth)); + route->metric.bandwidth = eigrp_composite_bandwidth(bandwidth); + + /* update mtu */ + mtu = min(metric_decode_mtu(route->metric.mtu), + route->nbr->ei->iface->mtu); + metric_encode_mtu(route->metric.mtu, mtu); + + /* update hop count */ + if (route->metric.hop_count < UINT8_MAX) + route->metric.hop_count++; + } + + route->distance = route->metric.delay + route->metric.bandwidth; + route->flags |= F_EIGRP_ROUTE_M_CHANGED; +} + +void +reply_outstanding_add(struct rt_node *rn, struct rde_nbr *nbr) +{ + struct reply_node *reply; + + if ((reply = calloc(1, sizeof(*reply))) == NULL) + fatal("reply_outstanding_add"); + + evtimer_set(&reply->ev_active_timeout, reply_active_timer, reply); + evtimer_set(&reply->ev_sia_timeout, reply_sia_timer, reply); + reply->siaquery_sent = 0; + reply->siareply_recv = 0; + reply->rn = rn; + reply->nbr = nbr; + TAILQ_INSERT_TAIL(&rn->rijk, reply, rn_entry); + TAILQ_INSERT_TAIL(&nbr->rijk, reply, nbr_entry); + + reply_active_start_timer(reply); + reply_sia_start_timer(reply); +} + +struct reply_node * +reply_outstanding_find(struct rt_node *rn, struct rde_nbr *nbr) +{ + struct reply_node *reply; + + TAILQ_FOREACH(reply, &rn->rijk, rn_entry) + if (reply->nbr == nbr) + return (reply); + + return (NULL); +} + +void +reply_outstanding_remove(struct reply_node *reply) +{ + reply_active_stop_timer(reply); + reply_sia_stop_timer(reply); + TAILQ_REMOVE(&reply->rn->rijk, reply, rn_entry); + TAILQ_REMOVE(&reply->nbr->rijk, reply, nbr_entry); + free(reply); +} + +/* ARGSUSED */ +void +reply_active_timer(int fd, short event, void *arg) +{ + struct reply_node *reply = arg; + struct rde_nbr *nbr = reply->nbr; + + log_debug("%s: neighbor %s is stuck in active", + log_addr(nbr->eigrp->af, &nbr->addr)); + + rde_nbr_del(reply->nbr); +} + +void +reply_active_start_timer(struct reply_node *reply) +{ + struct timeval tv; + + timerclear(&tv); + tv.tv_sec = EIGRP_ACTIVE_TIMEOUT; + if (evtimer_add(&reply->ev_active_timeout, &tv) == -1) + fatal("reply_active_start_timer"); +} + +void +reply_active_stop_timer(struct reply_node *reply) +{ + if (evtimer_pending(&reply->ev_active_timeout, NULL) && + evtimer_del(&reply->ev_active_timeout) == -1) + fatal("reply_active_stop_timer"); +} + +/* ARGSUSED */ +void +reply_sia_timer(int fd, short event, void *arg) +{ + struct reply_node *reply = arg; + struct rde_nbr *nbr = reply->nbr; + struct rt_node *rn = reply->rn; + struct rinfo ri; + + log_debug("%s: nbr %s prefix %s", __func__, log_addr(nbr->eigrp->af, + &nbr->addr), log_prefix(rn)); + + if (reply->siaquery_sent > 0 && reply->siareply_recv == 0) { + log_debug("%s: neighbor %s is stuck in active", + log_addr(nbr->eigrp->af, &nbr->addr)); + rde_nbr_del(nbr); + return; + } + + /* restart active timeout */ + reply_active_start_timer(reply); + + /* send an sia-query */ + rinfo_fill_successor(rn, &ri); + ri.metric.flags |= F_METRIC_ACTIVE; + rde_send_siaquery(nbr, &ri); + + /* + * draft-savage-eigrp-04 - Section 4.4.1.1: + * "Up to three SIA-QUERY packets for a specific destination may + * be sent, each at a value of one-half the ACTIVE time, so long + * as each are successfully acknowledged and met with an SIA-REPLY". + */ + if (reply->siaquery_sent < 3) { + reply->siaquery_sent++; + reply->siareply_recv = 0; + reply_sia_start_timer(reply); + } +} + +void +reply_sia_start_timer(struct reply_node *reply) +{ + struct timeval tv; + + /* + * draft-savage-eigrp-04 - Section 4.4.1.1: + * "The SIA-QUERY packet SHOULD be sent on a per-destination basis + * at one-half of the ACTIVE timeout period." + */ + timerclear(&tv); + tv.tv_sec = EIGRP_ACTIVE_TIMEOUT / 2; + if (evtimer_add(&reply->ev_sia_timeout, &tv) == -1) + fatal("reply_sia_start_timer"); +} + +void +reply_sia_stop_timer(struct reply_node *reply) +{ + if (evtimer_pending(&reply->ev_sia_timeout, NULL) && + evtimer_del(&reply->ev_sia_timeout) == -1) + fatal("reply_sia_stop_timer"); +} + +void +rinfo_fill_successor(struct rt_node *rn, struct rinfo *ri) +{ + if (rn->successor.nbr == NULL) { + rinfo_fill_infinite(rn, EIGRP_ROUTE_INTERNAL, ri); + return; + } + + memset(ri, 0, sizeof(*ri)); + ri->af = rn->eigrp->af; + ri->type = rn->successor.type; + memcpy(&ri->prefix, &rn->prefix, sizeof(ri->prefix)); + ri->prefixlen = rn->prefixlen; + memcpy(&ri->metric, &rn->successor.metric, sizeof(ri->metric)); + if (ri->type == EIGRP_ROUTE_EXTERNAL) + memcpy(&ri->emetric, &rn->successor.emetric, + sizeof(ri->emetric)); +} + +void +rinfo_fill_infinite(struct rt_node *rn, enum route_type type, struct rinfo *ri) +{ + memset(ri, 0, sizeof(*ri)); + ri->af = rn->eigrp->af; + ri->type = type; + memcpy(&ri->prefix, &rn->prefix, sizeof(ri->prefix)); + ri->prefixlen = rn->prefixlen; + ri->metric.delay = EIGRP_INFINITE_METRIC; + ri->metric.bandwidth = 0; + ri->metric.mtu[0] = 0; + ri->metric.mtu[1] = 0; + ri->metric.mtu[2] = 0; + ri->metric.hop_count = 0; + ri->metric.reliability = 0; + ri->metric.load = 0; + ri->metric.tag = 0; + ri->metric.flags = 0; + if (ri->type == EIGRP_ROUTE_EXTERNAL) { + ri->emetric.routerid = 0; + ri->emetric.as = 0; + ri->emetric.tag = 0; + ri->emetric.metric = 0; + ri->emetric.protocol = 0; + ri->emetric.flags = 0; + } +} + +void +rt_update_fib(struct rt_node *rn) +{ + uint8_t maximum_paths = rn->eigrp->maximum_paths; + uint8_t variance = rn->eigrp->variance; + int installed = 0; + struct eigrp_route *route; + + if (rn->state == DUAL_STA_PASSIVE) { + TAILQ_FOREACH(route, &rn->routes, entry) { + if (route->nbr->flags & F_RDE_NBR_SELF) + continue; + + /* + * only feasible successors and the successor itself + * are elegible to be installed. + */ + if (route->rdistance > rn->successor.fdistance) + goto uninstall; + + /* no multipath for attached networks. */ + if (rn->successor.rdistance == 0 && + route->distance > 0) + goto uninstall; + + if (route->distance > + (rn->successor.fdistance * variance)) + goto uninstall; + + if (installed >= maximum_paths) + goto uninstall; + + installed++; + + if (route->flags & (F_EIGRP_ROUTE_INSTALLED | + !F_EIGRP_ROUTE_M_CHANGED)) + continue; + + rde_send_change_kroute(rn, route); + continue; + +uninstall: + if (route->flags & F_EIGRP_ROUTE_INSTALLED) + rde_send_delete_kroute(rn, route); + } + } else { + TAILQ_FOREACH(route, &rn->routes, entry) + if (route->flags & F_EIGRP_ROUTE_INSTALLED) + rde_send_delete_kroute(rn, route); + } +} + +void +rt_set_successor(struct rt_node *rn, struct eigrp_route *successor) +{ + if (successor == NULL) { + rn->successor.nbr = NULL; + rn->successor.type = 0; + rn->successor.fdistance = EIGRP_INFINITE_METRIC; + rn->successor.rdistance = EIGRP_INFINITE_METRIC; + memset(&rn->successor.metric, 0, + sizeof(rn->successor.metric)); + memset(&rn->successor.emetric, 0, + sizeof(rn->successor.emetric)); + return; + } + + rn->successor.nbr = successor->nbr; + rn->successor.type = successor->type; + rn->successor.fdistance = successor->distance; + rn->successor.rdistance = successor->rdistance; + memcpy(&rn->successor.metric, &successor->metric, + sizeof(rn->successor.metric)); + memcpy(&rn->successor.emetric, &successor->emetric, + sizeof(rn->successor.emetric)); +} + +struct eigrp_route * +rt_get_successor_fc(struct rt_node *rn) +{ + struct eigrp_route *route, *successor = NULL; + uint32_t distance = EIGRP_INFINITE_METRIC; + int external_only = 1; + + TAILQ_FOREACH(route, &rn->routes, entry) + if (route->type == EIGRP_ROUTE_INTERNAL) { + /* + * connected routes should always be prefered over + * received routes independent of the metric. + */ + if (route->rdistance == 0) + return (route); + + external_only = 0; + } + + TAILQ_FOREACH(route, &rn->routes, entry) { + /* + * draft-savage-eigrp-04 - Section 5.4.7: + * "Internal routes MUST be prefered over external routes + * independent of the metric." + */ + if (route->type == EIGRP_ROUTE_EXTERNAL && !external_only) + continue; + + /* pick the best route that meets the feasibility condition */ + if (route->rdistance < rn->successor.fdistance && + route->distance < distance) { + distance = route->distance; + successor = route; + } + } + + return (successor); +} + +void +rde_send_update(struct eigrp_iface *ei, struct rinfo *ri) +{ + if (ri->metric.hop_count >= ei->eigrp->maximum_hops) + ri->metric.delay = EIGRP_INFINITE_METRIC; + + rde_imsg_compose_eigrpe(IMSG_SEND_MUPDATE, ei->ifaceid, 0, + ri, sizeof(*ri)); + rde_imsg_compose_eigrpe(IMSG_SEND_MUPDATE_END, ei->ifaceid, 0, + NULL, 0); +} + +void +rde_send_update_all(struct rt_node *rn, struct rinfo *ri) +{ + struct eigrp *eigrp = rn->eigrp; + struct eigrp_iface *ei; + + TAILQ_FOREACH(ei, &eigrp->ei_list, e_entry) { + /* respect split-horizon configuration */ + if (rn->successor.nbr && rn->successor.nbr->ei == ei && + ei->splithorizon) + continue; + rde_send_update(ei, ri); + } +} + +void +rde_send_query(struct eigrp_iface *ei, struct rinfo *ri, int push) +{ + rde_imsg_compose_eigrpe(IMSG_SEND_MQUERY, ei->ifaceid, 0, + ri, sizeof(*ri)); + if (push) + rde_imsg_compose_eigrpe(IMSG_SEND_MQUERY_END, ei->ifaceid, + 0, NULL, 0); +} + +void +rde_send_siaquery(struct rde_nbr *nbr, struct rinfo *ri) +{ + rde_imsg_compose_eigrpe(IMSG_SEND_QUERY, nbr->peerid, 0, + ri, sizeof(*ri)); + rde_imsg_compose_eigrpe(IMSG_SEND_SIAQUERY_END, nbr->peerid, 0, + NULL, 0); +} + +void +rde_send_query_all(struct eigrp *eigrp, struct rt_node *rn, int push) +{ + struct eigrp_iface *ei; + struct rde_nbr *nbr; + struct rinfo ri; + + rinfo_fill_successor(rn, &ri); + ri.metric.flags |= F_METRIC_ACTIVE; + + TAILQ_FOREACH(ei, &eigrp->ei_list, e_entry) { + /* respect split-horizon configuration */ + if (rn->successor.nbr && rn->successor.nbr->ei == ei && + ei->splithorizon) + continue; + + rde_send_query(ei, &ri, push); + } + + RB_FOREACH(nbr, rde_nbr_head, &rde_nbrs) + if (nbr->ei->eigrp == eigrp && !(nbr->flags & F_RDE_NBR_SELF)) { + /* respect split-horizon configuration */ + if (rn->successor.nbr && + rn->successor.nbr->ei == nbr->ei && + nbr->ei->splithorizon) + continue; + + reply_outstanding_add(rn, nbr); + } +} + +void +rde_flush_queries(void) +{ + struct eigrp *eigrp; + struct eigrp_iface *ei; + + TAILQ_FOREACH(eigrp, &rdeconf->instances, entry) + TAILQ_FOREACH(ei, &eigrp->ei_list, e_entry) + rde_imsg_compose_eigrpe(IMSG_SEND_MQUERY_END, + ei->ifaceid, 0, NULL, 0); +} + +void +rde_send_reply(struct rde_nbr *nbr, struct rinfo *ri, int siareply) +{ + int type; + + if (ri->metric.hop_count >= nbr->eigrp->maximum_hops) + ri->metric.delay = EIGRP_INFINITE_METRIC; + + if (!siareply) + type = IMSG_SEND_REPLY_END; + else + type = IMSG_SEND_SIAREPLY_END; + + rde_imsg_compose_eigrpe(IMSG_SEND_REPLY, nbr->peerid, 0, + ri, sizeof(*ri)); + rde_imsg_compose_eigrpe(type, nbr->peerid, 0, NULL, 0); +} + +void +rde_check_update(struct rde_nbr *nbr, struct rinfo *ri) +{ + struct eigrp *eigrp = nbr->eigrp; + struct rt_node *rn; + struct eigrp_route *route, *successor; + uint32_t old_fdistance; + struct rinfo sri; + + rn = rt_find(eigrp, ri); + if (rn == NULL) { + if (ri->metric.delay == EIGRP_INFINITE_METRIC) + return; + + rn = rt_new(eigrp, ri); + route = route_new(rn, nbr, ri); + + old_fdistance = EIGRP_INFINITE_METRIC; + } else { + old_fdistance = rn->successor.fdistance; + + if (ri->metric.delay == EIGRP_INFINITE_METRIC) { + route = route_find(nbr, rn); + if (route) { + route_del(rn, route); + } + } else { + route = route_find(nbr, rn); + if (route == NULL) + route = route_new(rn, nbr, ri); + else + route_update_metrics(route, ri); + } + } + + switch (rn->state) { + case DUAL_STA_PASSIVE: + successor = rt_get_successor_fc(rn); + + /* + * go active if the successor was affected and no feasible + * successor exist. + */ + if (successor == NULL) { + rde_send_query_all(eigrp, rn, 1); + + dual_fsm(rn, DUAL_EVT_4); + } else { + rt_set_successor(rn, successor); + rt_update_fib(rn); + + /* send update with new metric if necessary */ + rinfo_fill_successor(rn, &sri); + if (rn->successor.fdistance != old_fdistance) + rde_send_update_all(rn, &sri); + } + break; + case DUAL_STA_ACTIVE1: + /* XXX event 9 if cost increase? */ + break; + case DUAL_STA_ACTIVE3: + /* XXX event 10 if cost increase? */ + break; + } + + if ((rn->state & DUAL_STA_ACTIVE_ALL) && TAILQ_EMPTY(&rn->rijk)) + rde_last_reply(rn); +} + +void +rde_check_query(struct rde_nbr *nbr, struct rinfo *ri, int siaquery) +{ + struct eigrp *eigrp = nbr->eigrp; + struct rt_node *rn; + struct eigrp_route *route, *successor; + uint32_t old_fdistance; + struct rinfo sri; + int reply_sent = 0; + + /* + * draft-savage-eigrp-02 - Section 4.3: + * "When a query is received for a route that doesn't exist in our + * topology table, a reply with infinite metric is sent and an entry + * in the topology table is added with the metric in the QUERY if + * the metric is not an infinite value". + */ + rn = rt_find(eigrp, ri); + if (rn == NULL) { + memcpy(&sri, ri, sizeof(sri)); + sri.metric.delay = EIGRP_INFINITE_METRIC; + rde_send_reply(nbr, &sri, 0); + + if (ri->metric.delay == EIGRP_INFINITE_METRIC) + return; + + rn = rt_new(eigrp, ri); + route = route_new(rn, nbr, ri); + rt_set_successor(rn, route); + return; + } + + old_fdistance = rn->successor.fdistance; + + if (ri->metric.delay == EIGRP_INFINITE_METRIC) { + route = route_find(nbr, rn); + if (route) + route_del(rn, route); + } else { + route = route_find(nbr, rn); + if (route == NULL) + route = route_new(rn, nbr, ri); + else + route_update_metrics(route, ri); + } + + switch (rn->state) { + case DUAL_STA_PASSIVE: + successor = rt_get_successor_fc(rn); + + /* + * go active if the successor was affected and no feasible + * successor exist. + */ + if (successor == NULL) { + rde_send_query_all(eigrp, rn, 1); + dual_fsm(rn, DUAL_EVT_3); + } else { + rt_set_successor(rn, successor); + rt_update_fib(rn); + + /* send reply */ + rinfo_fill_successor(rn, &sri); + rde_send_reply(nbr, &sri, 0); + reply_sent = 1; + + /* send update with new metric if necessary */ + if (rn->successor.fdistance != old_fdistance) + rde_send_update_all(rn, &sri); + } + break; + case DUAL_STA_ACTIVE0: + case DUAL_STA_ACTIVE1: + if (nbr == rn->successor.nbr) + dual_fsm(rn, DUAL_EVT_5); + else { + dual_fsm(rn, DUAL_EVT_6); + rinfo_fill_successor(rn, &sri); + sri.metric.flags |= F_METRIC_ACTIVE; + rde_send_reply(nbr, &sri, 0); + reply_sent = 1; + } + break; + case DUAL_STA_ACTIVE2: + case DUAL_STA_ACTIVE3: + if (nbr == rn->successor.nbr) { + /* XXX not defined in the spec, do nothing? */ + } else { + dual_fsm(rn, DUAL_EVT_6); + rinfo_fill_successor(rn, &sri); + sri.metric.flags |= F_METRIC_ACTIVE; + rde_send_reply(nbr, &sri, 0); + reply_sent = 1; + } + break; + } + + if ((rn->state & DUAL_STA_ACTIVE_ALL) && TAILQ_EMPTY(&rn->rijk)) + rde_last_reply(rn); + + if (siaquery && !reply_sent) { + rinfo_fill_successor(rn, &sri); + sri.metric.flags |= F_METRIC_ACTIVE; + rde_send_reply(nbr, &sri, 1); + } +} + +void +rde_last_reply(struct rt_node *rn) +{ + struct eigrp *eigrp = rn->eigrp; + struct eigrp_route *successor; + struct rde_nbr *old_successor; + uint32_t old_fdistance; + struct rinfo ri; + + old_successor = rn->successor.nbr; + old_fdistance = rn->successor.fdistance; + + switch (rn->state) { + case DUAL_STA_ACTIVE0: + successor = rt_get_successor_fc(rn); + if (successor == NULL) { + /* feasibility condition is not met */ + rde_send_query_all(eigrp, rn, 1); + dual_fsm(rn, DUAL_EVT_11); + break; + } + + /* update successor - feasibility condition is met */ + rt_set_successor(rn, successor); + + /* advertise new successor to neighbors */ + rinfo_fill_successor(rn, &ri); + rde_send_update_all(rn, &ri); + + dual_fsm(rn, DUAL_EVT_14); + break; + case DUAL_STA_ACTIVE1: + /* update successor */ + rn->successor.fdistance = EIGRP_INFINITE_METRIC; + successor = rt_get_successor_fc(rn); + rt_set_successor(rn, successor); + + /* advertise new successor to neighbors */ + rinfo_fill_successor(rn, &ri); + rde_send_update_all(rn, &ri); + + dual_fsm(rn, DUAL_EVT_15); + break; + case DUAL_STA_ACTIVE2: + successor = rt_get_successor_fc(rn); + if (successor == NULL) { + /* feasibility condition is not met */ + rde_send_query_all(eigrp, rn, 1); + dual_fsm(rn, DUAL_EVT_12); + break; + } + + /* update successor - feasibility condition is met */ + rt_set_successor(rn, successor); + + /* send a reply to the old successor */ + rinfo_fill_successor(rn, &ri); + ri.metric.flags |= F_METRIC_ACTIVE; + if (old_successor) + rde_send_reply(old_successor, &ri, 0); + + /* advertise new successor to neighbors */ + rde_send_update_all(rn, &ri); + + dual_fsm(rn, DUAL_EVT_16); + break; + case DUAL_STA_ACTIVE3: + /* update successor */ + rn->successor.fdistance = EIGRP_INFINITE_METRIC; + successor = rt_get_successor_fc(rn); + rt_set_successor(rn, successor); + + /* send a reply to the old successor */ + rinfo_fill_successor(rn, &ri); + ri.metric.flags |= F_METRIC_ACTIVE; + if (old_successor) + rde_send_reply(old_successor, &ri, 0); + + /* advertise new successor to neighbors */ + rde_send_update_all(rn, &ri); + + dual_fsm(rn, DUAL_EVT_13); + break; + } + + if (rn->state == DUAL_STA_PASSIVE && rn->successor.nbr == NULL) + rt_del(rn); +} + +void +rde_check_reply(struct rde_nbr *nbr, struct rinfo *ri, int siareply) +{ + struct eigrp *eigrp = nbr->eigrp; + struct rt_node *rn; + struct reply_node *reply; + struct eigrp_route *route; + + rn = rt_find(eigrp, ri); + if (rn == NULL) + return; + + /* XXX ignore reply when the state is passive? */ + if (rn->state == DUAL_STA_PASSIVE) + return; + + reply = reply_outstanding_find(rn, nbr); + if (reply == NULL) + return; + + if (siareply) { + reply->siareply_recv = 1; + reply_active_start_timer(reply); + return; + } + + if (ri->metric.delay == EIGRP_INFINITE_METRIC) { + route = route_find(nbr, rn); + if (route) + route_del(rn, route); + } else { + route = route_find(nbr, rn); + if (route == NULL) + route = route_new(rn, nbr, ri); + else + route_update_metrics(route, ri); + } + + reply_outstanding_remove(reply); + if (!TAILQ_EMPTY(&rn->rijk)) + /* not last reply */ + return; + + rde_last_reply(rn); +} + +void +rde_check_link_down_rn(struct rde_nbr *nbr, struct rt_node *rn, + struct eigrp_route *route) +{ + struct eigrp *eigrp = nbr->eigrp; + struct reply_node *reply; + struct eigrp_route *successor; + uint32_t old_fdistance; + struct rinfo ri; + + old_fdistance = rn->successor.fdistance; + + route_del(rn, route); + + switch (rn->state) { + case DUAL_STA_PASSIVE: + successor = rt_get_successor_fc(rn); + + /* + * go active if the successor was affected and no feasible + * successor exist. + */ + if (successor == NULL) { + rde_send_query_all(eigrp, rn, 0); + + dual_fsm(rn, DUAL_EVT_4); + } else { + rt_set_successor(rn, successor); + rt_update_fib(rn); + + /* send update with new metric if necessary */ + rinfo_fill_successor(rn, &ri); + if (rn->successor.fdistance != old_fdistance) + rde_send_update_all(rn, &ri); + } + break; + case DUAL_STA_ACTIVE1: + if (nbr == rn->successor.nbr) + dual_fsm(rn, DUAL_EVT_9); + break; + case DUAL_STA_ACTIVE3: + if (nbr == rn->successor.nbr) + dual_fsm(rn, DUAL_EVT_10); + break; + } + + if (rn->state & DUAL_STA_ACTIVE_ALL) { + reply = reply_outstanding_find(rn, nbr); + if (reply) { + rinfo_fill_infinite(rn, route->type, &ri); + rde_check_reply(nbr, &ri, 0); + } + } +} + +void +rde_check_link_down_nbr(struct rde_nbr *nbr) +{ + struct eigrp *eigrp = nbr->eigrp; + struct rt_node *rn, *safe; + struct eigrp_route *route; + + RB_FOREACH_SAFE(rn, rt_tree, &eigrp->topology, safe) { + route = route_find(nbr, rn); + if (route) { + rde_check_link_down_rn(nbr, rn, route); + if (rn->successor.nbr == nbr) + rn->successor.nbr = NULL; + } + } +} + +void +rde_check_link_down(unsigned int ifindex) +{ + struct rde_nbr *nbr; + + RB_FOREACH(nbr, rde_nbr_head, &rde_nbrs) + if (nbr->ei->iface->ifindex == ifindex) + rde_check_link_down_nbr(nbr); + + rde_flush_queries(); +} + +void +rde_check_link_cost_change(struct rde_nbr *nbr, struct eigrp_interface *ei) +{ +} + +static __inline int +rde_nbr_compare(struct rde_nbr *a, struct rde_nbr *b) +{ + return (a->peerid - b->peerid); +} + +struct rde_nbr * +rde_nbr_find(uint32_t peerid) +{ + struct rde_nbr n; + + n.peerid = peerid; + + return (RB_FIND(rde_nbr_head, &rde_nbrs, &n)); +} + +struct rde_nbr * +rde_nbr_new(uint32_t peerid, struct rde_nbr *new) +{ + struct rde_nbr *nbr; + + if ((nbr = calloc(1, sizeof(*nbr))) == NULL) + fatal("rde_nbr_new"); + + nbr->peerid = peerid; + nbr->ifaceid = new->ifaceid; + memcpy(&nbr->addr, &new->addr, sizeof(nbr->addr)); + nbr->ei = eigrp_iface_find_id(nbr->ifaceid); + if (nbr->ei) + nbr->eigrp = nbr->ei->eigrp; + TAILQ_INIT(&nbr->rijk); + nbr->flags = new->flags; + + if (nbr->peerid != NBR_IDSELF && + RB_INSERT(rde_nbr_head, &rde_nbrs, nbr) != NULL) + fatalx("rde_nbr_new: RB_INSERT failed"); + + return (nbr); +} + +void +rde_nbr_del(struct rde_nbr *nbr) +{ + struct reply_node *reply; + + while((reply = TAILQ_FIRST(&nbr->rijk)) != NULL) + reply_outstanding_remove(reply); + + if (nbr->peerid != NBR_IDSELF) + RB_REMOVE(rde_nbr_head, &rde_nbrs, nbr); + free(nbr); +} diff --git a/usr.sbin/eigrpd/reply.c b/usr.sbin/eigrpd/reply.c new file mode 100644 index 00000000000..98dab8d54cf --- /dev/null +++ b/usr.sbin/eigrpd/reply.c @@ -0,0 +1,120 @@ +/* $OpenBSD: reply.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@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 <stdlib.h> +#include <netinet/in.h> +#include <netinet/ip6.h> + +#include "eigrpd.h" +#include "eigrp.h" +#include "log.h" +#include "eigrpe.h" + +extern struct eigrpd_conf *econf; + +/* reply packet handling */ + +void +send_reply(struct nbr *nbr, struct rinfo_head *rinfo_list, int siareply) +{ + struct eigrp *eigrp = nbr->ei->eigrp; + struct ibuf *buf; + uint16_t opcode; + struct rinfo_entry *re; + int size; + int route_len; + + if (rinfo_list == NULL || TAILQ_EMPTY(rinfo_list)) + return; + + /* don't exceed the interface's mtu */ + do { + if ((buf = ibuf_dynamic(PKG_DEF_SIZE, + IP_MAXPACKET - sizeof(struct ip))) == NULL) + fatal("send_reply"); + + if (!siareply) + opcode = EIGRP_OPC_REPLY; + else + opcode = EIGRP_OPC_SIAREPLY; + + /* EIGRP header */ + if (gen_eigrp_hdr(buf, opcode, 0, eigrp->seq_num, eigrp->as)) + goto fail; + + switch (eigrp->af) { + case AF_INET: + size = sizeof(struct ip); + break; + case AF_INET6: + size = sizeof(struct ip6_hdr); + break; + default: + break; + } + size += sizeof(struct eigrp_hdr); + + while ((re = TAILQ_FIRST(rinfo_list)) != NULL) { + route_len = len_route_tlv(&re->rinfo); + if (size + route_len > nbr->ei->iface->mtu) { + rtp_send_ucast(nbr, buf); + break; + } + size += route_len; + + if (gen_route_tlv(buf, &re->rinfo)) + goto fail; + TAILQ_REMOVE(rinfo_list, re, entry); + free(re); + } + } while (!TAILQ_EMPTY(rinfo_list)); + + /* reply packets are always unicast */ + rtp_send_ucast(nbr, buf); + return; +fail: + log_warnx("%s: failed to send message", __func__); + if (rinfo_list) + message_list_clr(rinfo_list); + ibuf_free(buf); + return; +} + +void +recv_reply(struct nbr *nbr, struct rinfo_head *rinfo_list, int siareply) +{ + int type; + struct rinfo_entry *re; + + /* + * draft-savage-eigrp-02 - Section 4.3: + * "When a REPLY packet is received, there is no reason to process + * the packet before an acknowledgment is sent. Therefore, an Ack + * packet is sent immediately and then the packet is processed." + */ + rtp_send_ack(nbr); + + if (!siareply) + type = IMSG_RECV_REPLY; + else + type = IMSG_RECV_SIAREPLY; + + TAILQ_FOREACH(re, rinfo_list, entry) + eigrpe_imsg_compose_rde(type, nbr->peerid, 0, &re->rinfo, + sizeof(re->rinfo)); +} diff --git a/usr.sbin/eigrpd/rtp.c b/usr.sbin/eigrpd/rtp.c new file mode 100644 index 00000000000..556da6bb6f9 --- /dev/null +++ b/usr.sbin/eigrpd/rtp.c @@ -0,0 +1,309 @@ +/* $OpenBSD: rtp.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@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 <stdlib.h> +#include <string.h> +#include <arpa/inet.h> + +#include "eigrpd.h" +#include "eigrp.h" +#include "eigrpe.h" +#include "rde.h" +#include "log.h" + +void rtp_retrans_timer(int, short, void *); +void rtp_retrans_start_timer(struct packet *); +void rtp_retrans_stop_timer(struct packet *); + +struct pbuf * +rtp_buf_new(struct ibuf *buf) +{ + struct pbuf *pbuf; + + if ((pbuf = calloc(1, sizeof(*pbuf))) == NULL) + fatal("rtp_buf_new"); + pbuf->buf = buf; + + return (pbuf); +} + +struct pbuf * +rtp_buf_hold(struct pbuf *pbuf) +{ + pbuf->refcnt++; + return (pbuf); +} + +void +rtp_buf_release(struct pbuf *pbuf) +{ + if (--pbuf->refcnt == 0) { + ibuf_free(pbuf->buf); + free(pbuf); + } +} + +struct packet * +rtp_packet_new(struct nbr *nbr, uint32_t seq_num, struct pbuf *pbuf) +{ + struct packet *pkt; + + if ((pkt = calloc(1, sizeof(struct packet))) == NULL) + fatal("rtp_packet_new"); + + pkt->nbr = nbr; + pkt->seq_num = seq_num; + pkt->pbuf = rtp_buf_hold(pbuf); + pkt->attempts = 1; + evtimer_set(&pkt->ev_timeout, rtp_retrans_timer, pkt); + + return (pkt); +} + +void +rtp_packet_del(struct packet *pkt) +{ + TAILQ_REMOVE(&pkt->nbr->retrans_list, pkt, entry); + rtp_retrans_stop_timer(pkt); + rtp_buf_release(pkt->pbuf); + free(pkt); +} + +void +rtp_process_ack(struct nbr *nbr, uint32_t ack_num) +{ + struct eigrp *eigrp = nbr->ei->eigrp; + struct packet *pkt; + + /* window size is one */ + pkt = TAILQ_FIRST(&nbr->retrans_list); + if (pkt && pkt->seq_num == ack_num) { + log_debug("%s: nbr %s ack %u", __func__, + log_addr(eigrp->af, &nbr->addr), ack_num); + + /* dequeue packet from retransmission queue */ + rtp_packet_del(pkt); + + /* enqueue next packet from retransmission queue */ + pkt = TAILQ_FIRST(&nbr->retrans_list); + if (pkt) + rtp_send_packet(pkt); + } +} + +void +rtp_send_packet(struct packet *pkt) +{ + rtp_retrans_start_timer(pkt); + send_packet(pkt->nbr->ei, pkt->nbr, 0, pkt->pbuf->buf); +} + +void +rtp_enqueue_packet(struct packet *pkt) +{ + /* only send packet if transmission queue is empty */ + if (TAILQ_EMPTY(&pkt->nbr->retrans_list)) + rtp_send_packet(pkt); + + TAILQ_INSERT_TAIL(&pkt->nbr->retrans_list, pkt, entry); +} + +static void +rtp_seq_inc(struct eigrp *eigrp) +{ + /* automatic wraparound with unsigned arithmetic */ + eigrp->seq_num++; + + /* sequence number 0 is reserved for unreliably transmission */ + if (eigrp->seq_num == 0) + eigrp->seq_num = 1; +} + +void +rtp_send_ucast(struct nbr *nbr, struct ibuf *buf) +{ + struct eigrp *eigrp = nbr->ei->eigrp; + struct packet *pkt; + struct pbuf *pbuf; + + pbuf = rtp_buf_new(buf); + pkt = rtp_packet_new(nbr, eigrp->seq_num, pbuf); + rtp_enqueue_packet(pkt); + rtp_seq_inc(eigrp); +} + +void +rtp_send_mcast(struct eigrp_iface *ei, struct ibuf *buf) +{ + struct eigrp *eigrp = ei->eigrp; + struct nbr *nbr; + int total = 0, pending = 0; + struct packet *pkt; + struct pbuf *pbuf; + uint32_t flags = 0; + struct seq_addr_entry *sa; + struct seq_addr_head seq_addr_list; + + TAILQ_FOREACH(nbr, &ei->nbr_list, entry) { + if (nbr->flags & F_EIGRP_NBR_SELF) + continue; + if (!TAILQ_EMPTY(&nbr->retrans_list)) + pending++; + total++; + } + if (total == 0) + return; + + /* + * send a multicast if there's at least one neighbor with an empty + * queue on the interface. + */ + if (pending < total) { + /* + * build a hello packet with a seq tlv indicating all the + * neighbors that have full queues. + */ + if (pending > 0) { + flags |= EIGRP_HDR_FLAG_CR; + TAILQ_INIT(&seq_addr_list); + + TAILQ_FOREACH(nbr, &ei->nbr_list, entry) { + if (TAILQ_EMPTY(&nbr->retrans_list)) + continue; + if ((sa = calloc(1, sizeof(*sa))) == NULL) + fatal("rtp_send_mcast"); + sa->af = eigrp->af; + memcpy(&sa->addr, &nbr->addr, sizeof(sa->addr)); + TAILQ_INSERT_TAIL(&seq_addr_list, sa, entry); + } + + send_hello(ei, &seq_addr_list, eigrp->seq_num); + seq_addr_list_clr(&seq_addr_list); + } + send_packet(ei, NULL, flags, buf); + } + + /* schedule an unicast retransmission for each neighbor */ + pbuf = rtp_buf_new(buf); + TAILQ_FOREACH(nbr, &ei->nbr_list, entry) { + pkt = rtp_packet_new(nbr, eigrp->seq_num, pbuf); + TAILQ_INSERT_TAIL(&nbr->retrans_list, pkt, entry); + } + + rtp_seq_inc(eigrp); +} + +void +rtp_send(struct eigrp_iface *ei, struct nbr *nbr, struct ibuf *buf) +{ + if (nbr) + rtp_send_ucast(nbr, buf); + else + rtp_send_mcast(ei, buf); +} + +void +rtp_send_ack(struct nbr *nbr) +{ + struct eigrp *eigrp = nbr->ei->eigrp; + struct ibuf *buf; + + if ((buf = ibuf_dynamic(PKG_DEF_SIZE, + IP_MAXPACKET - sizeof(struct ip))) == NULL) + fatal("rtp_send_ack"); + + /* EIGRP header */ + if (gen_eigrp_hdr(buf, EIGRP_OPC_HELLO, 0, 0, eigrp->as)) + goto fail; + + /* send unreliably */ + send_packet(nbr->ei, nbr, 0, buf); + ibuf_free(buf); + return; +fail: + log_warnx("%s: failed to send message", __func__); + ibuf_free(buf); +} + +/* timers */ + +/* ARGSUSED */ +void +rtp_retrans_timer(int fd, short event, void *arg) +{ + struct packet *pkt = arg; + struct eigrp *eigrp = pkt->nbr->ei->eigrp; + + pkt->attempts++; + + if (pkt->attempts > RTP_RTRNS_MAX_ATTEMPTS) { + log_warnx("%s: retry limit exceeded, nbr %s", __func__, + log_addr(eigrp->af, &pkt->nbr->addr)); + nbr_del(pkt->nbr); + return; + } + + rtp_send_packet(pkt); +} + +void +rtp_retrans_start_timer(struct packet *pkt) +{ + struct timeval tv; + + timerclear(&tv); + tv.tv_sec = RTP_RTRNS_INTERVAL; + if (evtimer_add(&pkt->ev_timeout, &tv) == -1) + fatal("rtp_retrans_start_timer"); +} + +void +rtp_retrans_stop_timer(struct packet *pkt) +{ + if (evtimer_pending(&pkt->ev_timeout, NULL) && + evtimer_del(&pkt->ev_timeout) == -1) + fatal("rtp_retrans_stop_timer"); +} + +/* ARGSUSED */ +void +rtp_ack_timer(int fd, short event, void *arg) +{ + struct nbr *nbr = arg; + + rtp_send_ack(nbr); +} + +void +rtp_ack_start_timer(struct nbr *nbr) +{ + struct timeval tv; + + timerclear(&tv); + tv.tv_usec = RTP_ACK_TIMEOUT; + if (evtimer_add(&nbr->ev_ack, &tv) == -1) + fatal("rtp_ack_start_timer"); +} + +void +rtp_ack_stop_timer(struct nbr *nbr) +{ + if (evtimer_pending(&nbr->ev_ack, NULL) && + evtimer_del(&nbr->ev_ack) == -1) + fatal("rtp_ack_stop_timer"); +} diff --git a/usr.sbin/eigrpd/tlv.c b/usr.sbin/eigrpd/tlv.c new file mode 100644 index 00000000000..a871fcdd583 --- /dev/null +++ b/usr.sbin/eigrpd/tlv.c @@ -0,0 +1,480 @@ +/* $OpenBSD: tlv.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@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 <stdlib.h> +#include <stdio.h> +#include <inttypes.h> +#include <string.h> +#include <sys/uio.h> +#include <sys/utsname.h> + +#include "eigrpd.h" +#include "eigrp.h" +#include "log.h" +#include "eigrpe.h" + +int +gen_parameter_tlv(struct ibuf *buf, struct eigrp_iface *ei) +{ + struct tlv_parameter tp; + + tp.type = htons(TLV_TYPE_PARAMETER); + tp.length = htons(TLV_TYPE_PARAMETER_LEN); + memcpy(tp.kvalues, ei->eigrp->kvalues, 6); + tp.holdtime = htons(ei->hello_holdtime); + + return (ibuf_add(buf, &tp, sizeof(tp))); +} + +int +gen_sequence_tlv(struct ibuf *buf, struct seq_addr_head *seq_addr_list) +{ + struct tlv tlv, *tlvp; + struct seq_addr_entry *sa; + uint8_t alen; + uint16_t len = TLV_HDR_LEN; + size_t original_size = ibuf_size(buf); + + tlv.type = htons(TLV_TYPE_SEQ); + if (ibuf_add(buf, &tlv, sizeof(tlv))) { + log_warn("%s: ibuf_add failed", __func__); + return (-1); + } + + TAILQ_FOREACH(sa, seq_addr_list, entry) { + switch (sa->af) { + case AF_INET: + alen = INADDRSZ; + if (ibuf_add(buf, &alen, sizeof(alen))) + return (-1); + if (ibuf_add(buf, &sa->addr.v4, sizeof(sa->addr.v4))) { + log_warn("%s: ibuf_add failed", __func__); + return (-1); + } + break; + case AF_INET6: + alen = IN6ADDRSZ; + if (ibuf_add(buf, &alen, sizeof(alen))) + return (-1); + if (ibuf_add(buf, &sa->addr.v6, sizeof(sa->addr.v6))) { + log_warn("%s: ibuf_add failed", __func__); + return (-1); + } + break; + default: + log_debug("%s: unkown address family", __func__); + return (-1); + } + len += (sizeof(alen) + alen); + } + + /* adjust tlv length */ + if ((tlvp = ibuf_seek(buf, original_size, sizeof(*tlvp))) == NULL) + fatalx("gen_sequence_tlv: buf_seek failed"); + tlvp->length = htons(len); + + return (0); +} + +int +gen_sw_version_tlv(struct ibuf *buf) +{ + struct tlv_sw_version ts; + struct utsname u; + unsigned int vendor_os_major; + unsigned int vendor_os_minor; + + memset(&ts, 0, sizeof(ts)); + ts.type = htons(TLV_TYPE_SW_VERSION); + ts.length = htons(TLV_TYPE_SW_VERSION_LEN); + if (uname(&u) == 0) { + if (sscanf(u.release, "%u.%u", &vendor_os_major, + &vendor_os_minor) == 2) { + ts.vendor_os_major = (uint8_t) vendor_os_major; + ts.vendor_os_minor = (uint8_t) vendor_os_minor; + } + } + ts.eigrp_major = EIGRP_VERSION_MAJOR; + ts.eigrp_minor = EIGRP_VERSION_MINOR; + + return (ibuf_add(buf, &ts, sizeof(ts))); +} + +int +gen_mcast_seq_tlv(struct ibuf *buf, uint32_t seq) +{ + struct tlv_mcast_seq tm; + + tm.type = htons(TLV_TYPE_MCAST_SEQ); + tm.length = htons(TLV_TYPE_MCAST_SEQ_LEN); + tm.seq = htonl(seq); + + return (ibuf_add(buf, &tm, sizeof(tm))); +} + +uint16_t +len_route_tlv(struct rinfo *ri) +{ + uint16_t len = TLV_HDR_LEN; + + switch (ri->af) { + case AF_INET: + len += sizeof(ri->nexthop.v4); + len += PREFIX_SIZE4(ri->prefixlen); + break; + case AF_INET6: + len += sizeof(ri->nexthop.v6); + len += PREFIX_SIZE6(ri->prefixlen); + break; + default: + break; + } + + len += sizeof(ri->metric); + if (ri->type == EIGRP_ROUTE_EXTERNAL) + len += sizeof(ri->emetric); + + len += sizeof(ri->prefixlen); + + return (len); +} + +int +gen_route_tlv(struct ibuf *buf, struct rinfo *ri) +{ + struct tlv tlv, *tlvp; + struct in_addr addr; + struct classic_metric metric; + struct classic_emetric emetric; + uint16_t tlvlen; + uint8_t pflen; + size_t original_size = ibuf_size(buf); + + switch (ri->af) { + case AF_INET: + switch (ri->type) { + case EIGRP_ROUTE_INTERNAL: + tlv.type = htons(TLV_TYPE_IPV4_INTERNAL); + break; + case EIGRP_ROUTE_EXTERNAL: + tlv.type = htons(TLV_TYPE_IPV4_EXTERNAL); + break; + } + break; + case AF_INET6: + switch (ri->type) { + case EIGRP_ROUTE_INTERNAL: + tlv.type = htons(TLV_TYPE_IPV6_INTERNAL); + break; + case EIGRP_ROUTE_EXTERNAL: + tlv.type = htons(TLV_TYPE_IPV6_EXTERNAL); + break; + } + break; + default: + break; + } + if (ibuf_add(buf, &tlv, sizeof(tlv))) + return (-1); + tlvlen = TLV_HDR_LEN; + + /* nexthop */ + switch (ri->af) { + case AF_INET: + addr.s_addr = htonl(ri->nexthop.v4.s_addr); + if (ibuf_add(buf, &addr, sizeof(addr))) + return (-1); + tlvlen += sizeof(ri->nexthop.v4); + break; + case AF_INET6: + if (ibuf_add(buf, &ri->nexthop.v6, sizeof(ri->nexthop.v6))) + return (-1); + tlvlen += sizeof(ri->nexthop.v6); + break; + default: + break; + } + + /* exterior metric */ + if (ri->type == EIGRP_ROUTE_EXTERNAL) { + memcpy(&emetric, &ri->emetric, sizeof(emetric)); + emetric.routerid = htonl(emetric.routerid); + emetric.as = htonl(emetric.as); + emetric.tag = htonl(emetric.tag); + emetric.metric = htonl(emetric.metric); + emetric.reserved = htons(emetric.reserved); + if (ibuf_add(buf, &emetric, sizeof(emetric))) + return (-1); + tlvlen += sizeof(emetric); + } + + /* metric */ + memcpy(&metric, &ri->metric, sizeof(metric)); + metric.delay = htonl(metric.delay); + metric.bandwidth = htonl(metric.bandwidth); + if (ibuf_add(buf, &metric, sizeof(metric))) + return (-1); + tlvlen += sizeof(metric); + + /* destination */ + if (ibuf_add(buf, &ri->prefixlen, sizeof(ri->prefixlen))) + return (-1); + switch (ri->af) { + case AF_INET: + pflen = PREFIX_SIZE4(ri->prefixlen); + if (ibuf_add(buf, &ri->prefix.v4, pflen)) + return (-1); + break; + case AF_INET6: + pflen = PREFIX_SIZE6(ri->prefixlen); + if (ibuf_add(buf, &ri->prefix.v6, pflen)) + return (-1); + break; + default: + break; + } + tlvlen += sizeof(pflen) + pflen; + + /* adjust tlv length */ + if ((tlvp = ibuf_seek(buf, original_size, sizeof(*tlvp))) == NULL) + fatalx("gen_ipv4_internal_tlv: buf_seek failed"); + tlvp->length = htons(tlvlen); + + return (0); +} + +struct tlv_parameter * +tlv_decode_parameter(struct tlv *tlv, char *buf) +{ + struct tlv_parameter *tp; + + if (ntohs(tlv->length) != TLV_TYPE_PARAMETER_LEN) { + log_debug("%s: malformed tlv (bad length)", __func__); + return (NULL); + } + tp = (struct tlv_parameter *)buf; + return (tp); +} + +int +tlv_decode_seq(struct tlv *tlv, char *buf, + struct seq_addr_head *seq_addr_list) +{ + uint16_t len; + uint8_t alen; + struct seq_addr_entry *sa; + + len = ntohs(tlv->length); + if (len < TLV_HDR_LEN) { + log_debug("%s: malformed tlv (bad length)", __func__); + return (-1); + } + buf += TLV_HDR_LEN; + len -= TLV_HDR_LEN; + + while (len > 0) { + memcpy(&alen, buf, sizeof(alen)); + buf += sizeof(alen); + len -= sizeof(alen); + if (alen > len) { + log_debug("%s: malformed tlv (bad length)", __func__); + return (-1); + } + + if ((sa = calloc(1, sizeof(*sa))) == NULL) + fatal("tlv_decode_seq"); + switch (alen) { + case INADDRSZ: + sa->af = AF_INET; + memcpy(&sa->addr.v4, buf, sizeof(struct in_addr)); + break; + case IN6ADDRSZ: + sa->af = AF_INET6; + memcpy(&sa->addr.v6, buf, sizeof(struct in6_addr)); + break; + default: + log_debug("%s: unknown address length", __func__); + free(sa); + return (-1); + } + buf += alen; + len -= alen; + TAILQ_INSERT_TAIL(seq_addr_list, sa, entry); + } + + return (0); +} + +struct tlv_sw_version * +tlv_decode_sw_version(struct tlv *tlv, char *buf) +{ + struct tlv_sw_version *tv; + + if (ntohs(tlv->length) != TLV_TYPE_SW_VERSION_LEN) { + log_debug("%s: malformed tlv (bad length)", __func__); + return (NULL); + } + tv = (struct tlv_sw_version *)buf; + return (tv); +} + +struct tlv_mcast_seq * +tlv_decode_mcast_seq(struct tlv *tlv, char *buf) +{ + struct tlv_mcast_seq *tm; + + if (ntohs(tlv->length) != TLV_TYPE_MCAST_SEQ_LEN) { + log_debug("%s: malformed tlv (bad length)", __func__); + return (NULL); + } + tm = (struct tlv_mcast_seq *)buf; + return (tm); +} + +int +tlv_decode_route(int af, enum route_type type, struct tlv *tlv, char *buf, + struct rinfo *ri) +{ + int tlv_len, min_len, plen, offset; + in_addr_t ipv4; + + tlv_len = ntohs(tlv->length); + switch (af) { + case AF_INET: + min_len = TLV_TYPE_IPV4_INT_MIN_LEN; + break; + case AF_INET6: + min_len = TLV_TYPE_IPV6_INT_MIN_LEN; + break; + default: + break; + } + if (type == EIGRP_ROUTE_EXTERNAL) + min_len += sizeof(struct classic_emetric); + + if (tlv_len < min_len) { + log_debug("%s: malformed tlv (bad length)", __func__); + return (-1); + } + + ri->af = af; + ri->type = type; + + /* nexthop */ + offset = TLV_HDR_LEN; + switch (af) { + case AF_INET: + memcpy(&ri->nexthop.v4, buf + offset, sizeof(ri->nexthop.v4)); + offset += sizeof(ri->nexthop.v4); + break; + case AF_INET6: + memcpy(&ri->nexthop.v6, buf + offset, sizeof(ri->nexthop.v6)); + offset += sizeof(ri->nexthop.v6); + default: + break; + } + + /* exterior metric */ + if (type == EIGRP_ROUTE_EXTERNAL) { + memcpy(&ri->emetric, buf + offset, sizeof(ri->emetric)); + ri->emetric.routerid = ntohl(ri->emetric.routerid); + ri->emetric.as = ntohl(ri->emetric.as); + ri->emetric.tag = ntohl(ri->emetric.tag); + ri->emetric.metric = ntohl(ri->emetric.metric); + ri->emetric.reserved = ntohs(ri->emetric.reserved); + offset += sizeof(ri->emetric); + } + + /* metric */ + memcpy(&ri->metric, buf + offset, sizeof(ri->metric)); + ri->metric.delay = ntohl(ri->metric.delay); + ri->metric.bandwidth = ntohl(ri->metric.bandwidth); + offset += sizeof(ri->metric); + + /* prefixlen */ + memcpy(&ri->prefixlen, buf + offset, sizeof(ri->prefixlen)); + offset += sizeof(ri->prefixlen); + + switch (af) { + case AF_INET: + plen = PREFIX_SIZE4(ri->prefixlen); + break; + case AF_INET6: + plen = PREFIX_SIZE6(ri->prefixlen); + break; + default: + break; + } + + /* safety check */ + if (plen != (tlv_len - min_len)) { + log_debug("%s: malformed tlv (invalid prefix length)", + __func__); + return (-1); + } + + /* destination */ + switch (af) { + case AF_INET: + memset(&ri->prefix.v4, 0, sizeof(ri->prefix.v4)); + memcpy(&ri->prefix.v4, buf + offset, plen); + + /* check if the network is valid */ + ipv4 = ntohl(ri->prefix.v4.s_addr); + if (((ipv4 >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) || + IN_MULTICAST(ipv4) || IN_BADCLASS(ipv4)) { + log_debug("%s: malformed tlv (invalid ipv4 prefix)", + __func__); + return (-1); + } + break; + case AF_INET6: + memset(&ri->prefix.v6, 0, sizeof(ri->prefix.v6)); + memcpy(&ri->prefix.v6, buf + offset, plen); + + /* check if the network is valid */ + if (IN6_IS_ADDR_LOOPBACK(&ri->prefix.v6) || + IN6_IS_ADDR_MULTICAST(&ri->prefix.v6)) { + log_debug("%s: malformed tlv (invalid ipv6 prefix)", + __func__); + return (-1); + } + break; + default: + break; + } + + /* just in case... */ + eigrp_applymask(af, &ri->prefix, &ri->prefix, ri->prefixlen); + + return (0); +} + +void +metric_encode_mtu(uint8_t *dst, int mtu) +{ + dst[0] = (mtu & 0x00FF0000) >> 16; + dst[1] = (mtu & 0x0000FF00) >> 8; + dst[2] = (mtu & 0x000000FF); +} + +int +metric_decode_mtu(uint8_t *mtu) +{ + return ((mtu[0] << 16) + (mtu[1] << 8) + mtu[2]); +} diff --git a/usr.sbin/eigrpd/update.c b/usr.sbin/eigrpd/update.c new file mode 100644 index 00000000000..1b7b8b5d4a2 --- /dev/null +++ b/usr.sbin/eigrpd/update.c @@ -0,0 +1,145 @@ +/* $OpenBSD: update.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@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 <stdlib.h> +#include <netinet/in.h> +#include <netinet/ip6.h> + +#include "eigrpd.h" +#include "eigrp.h" +#include "log.h" +#include "eigrpe.h" + +extern struct eigrpd_conf *econf; + +/* update packet handling */ + +void +send_update(struct eigrp_iface *ei, struct nbr *nbr, uint32_t flags, + int startup, struct rinfo_head *rinfo_list) +{ + struct eigrp *eigrp = ei->eigrp; + struct ibuf *buf; + struct rinfo_entry *re; + int size; + int route_len; + struct eigrp_hdr *eigrp_hdr; + + /* don't exceed the interface's mtu */ + do { + if ((buf = ibuf_dynamic(PKG_DEF_SIZE, + IP_MAXPACKET - sizeof(struct ip))) == NULL) + fatal("send_update"); + + /* EIGRP header */ + if (gen_eigrp_hdr(buf, EIGRP_OPC_UPDATE, flags, + eigrp->seq_num, eigrp->as)) + goto fail; + + if (rinfo_list == NULL) + break; + + switch (eigrp->af) { + case AF_INET: + size = sizeof(struct ip); + break; + case AF_INET6: + size = sizeof(struct ip6_hdr); + break; + default: + break; + } + size += sizeof(struct eigrp_hdr); + + while ((re = TAILQ_FIRST(rinfo_list)) != NULL) { + route_len = len_route_tlv(&re->rinfo); + if (size + route_len > ei->iface->mtu) { + rtp_send(ei, nbr, buf); + break; + } + size += route_len; + + if (gen_route_tlv(buf, &re->rinfo)) + goto fail; + TAILQ_REMOVE(rinfo_list, re, entry); + free(re); + } + } while (!TAILQ_EMPTY(rinfo_list)); + + /* set the EOT flag in the last startup update */ + if (startup) { + if ((eigrp_hdr = ibuf_seek(buf, 0, sizeof(*eigrp_hdr))) == NULL) + fatalx("send_update: buf_seek failed"); + eigrp_hdr->flags = ntohl(eigrp_hdr->flags) | EIGRP_HDR_FLAG_EOT; + eigrp_hdr->flags = htonl(eigrp_hdr->flags); + } + + rtp_send(ei, nbr, buf); + return; +fail: + log_warnx("%s: failed to send message", __func__); + if (rinfo_list) + message_list_clr(rinfo_list); + ibuf_free(buf); + return; +} + +void +recv_update(struct nbr *nbr, struct rinfo_head *rinfo_list, uint32_t flags) +{ + struct rinfo_entry *re; + + rtp_ack_start_timer(nbr); + + if (flags & EIGRP_HDR_FLAG_INIT) { + log_debug("%s: INIT flag is set", __func__); + + if (nbr->flags & F_EIGRP_NBR_PENDING) + nbr_init(nbr); + else if (!(flags & EIGRP_HDR_FLAG_RS)) + /* + * This is not in the draft, but apparently if a Cisco + * device sends an INIT Update it expects to receive + * an INIT Update as well, otherwise it triggers the + * "stuck in INIT state" error and discards subsequent + * packets. However, there is an exception: when the + * "clear ip eigrp neighbors soft" command is issued + * on a Cisco device, the "Restart Flag" is also set + * in the EIGRP header. In this case the Cisco device + * doesn't expect to receive an INIT Update otherwise + * the adjacency will flap. Unfortunately it looks + * like that there is some kind of initialization + * FSM implemented in the Cisco devices that is not + * documented in the draft. + */ + send_update(nbr->ei, nbr, EIGRP_HDR_FLAG_INIT, + 0, NULL); + + /* + * The INIT flag instructs us to advertise all of our routes, + * even if the neighbor is not pending. + */ + eigrpe_imsg_compose_rde(IMSG_RECV_UPDATE_INIT, nbr->peerid, + 0, NULL, 0); + return; + } + + TAILQ_FOREACH(re, rinfo_list, entry) + eigrpe_imsg_compose_rde(IMSG_RECV_UPDATE, nbr->peerid, + 0, &re->rinfo, sizeof(re->rinfo)); +} diff --git a/usr.sbin/eigrpd/util.c b/usr.sbin/eigrpd/util.c new file mode 100644 index 00000000000..90099c558b2 --- /dev/null +++ b/usr.sbin/eigrpd/util.c @@ -0,0 +1,238 @@ +/* $OpenBSD: util.c,v 1.1 2015/10/02 04:26:47 renato Exp $ */ + +/* + * Copyright (c) 2015 Renato Westphal <renato@openbsd.org> + * Copyright (c) 2012 Alexander Bluhm <bluhm@openbsd.org> + * Copyright (c) 2004 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@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 <netinet/in.h> +#include <string.h> + +#include "eigrpd.h" +#include "log.h" + +uint8_t +mask2prefixlen(in_addr_t ina) +{ + if (ina == 0) + return (0); + else + return (33 - ffs(ntohl(ina))); +} + +uint8_t +mask2prefixlen6(struct sockaddr_in6 *sa_in6) +{ + uint8_t l = 0, *ap, *ep; + + /* + * sin6_len is the size of the sockaddr so substract the offset of + * the possibly truncated sin6_addr struct. + */ + ap = (uint8_t *)&sa_in6->sin6_addr; + ep = (uint8_t *)sa_in6 + sa_in6->sin6_len; + for (; ap < ep; ap++) { + /* this "beauty" is adopted from sbin/route/show.c ... */ + switch (*ap) { + case 0xff: + l += 8; + break; + case 0xfe: + l += 7; + return (l); + case 0xfc: + l += 6; + return (l); + case 0xf8: + l += 5; + return (l); + case 0xf0: + l += 4; + return (l); + case 0xe0: + l += 3; + return (l); + case 0xc0: + l += 2; + return (l); + case 0x80: + l += 1; + return (l); + case 0x00: + return (l); + default: + fatalx("non contiguous inet6 netmask"); + } + } + + return (l); +} + +in_addr_t +prefixlen2mask(uint8_t prefixlen) +{ + if (prefixlen == 0) + return (0); + + return (htonl(0xffffffff << (32 - prefixlen))); +} + +struct in6_addr * +prefixlen2mask6(uint8_t prefixlen) +{ + static struct in6_addr mask; + int i; + + memset(&mask, 0, sizeof(mask)); + for (i = 0; i < prefixlen / 8; i++) + mask.s6_addr[i] = 0xff; + i = prefixlen % 8; + if (i) + mask.s6_addr[prefixlen / 8] = 0xff00 >> i; + + return (&mask); +} + +void +eigrp_applymask(int af, union eigrpd_addr *dest, const union eigrpd_addr *src, + int prefixlen) +{ + struct in6_addr mask; + int i; + + switch (af) { + case AF_INET: + dest->v4.s_addr = src->v4.s_addr & prefixlen2mask(prefixlen); + break; + case AF_INET6: + memset(&mask, 0, sizeof(mask)); + for (i = 0; i < prefixlen / 8; i++) + mask.s6_addr[i] = 0xff; + i = prefixlen % 8; + if (i) + mask.s6_addr[prefixlen / 8] = 0xff00 >> i; + + for (i = 0; i < 16; i++) + dest->v6.s6_addr[i] = src->v6.s6_addr[i] & + mask.s6_addr[i]; + break; + default: + break; + } +} + +int +eigrp_addrcmp(int af, union eigrpd_addr *a, union eigrpd_addr *b) +{ + switch (af) { + case AF_INET: + if (a->v4.s_addr != b->v4.s_addr) + return (1); + break; + case AF_INET6: + if (!IN6_ARE_ADDR_EQUAL(&a->v6, &b->v6)) + return (1); + break; + default: + break; + } + + return (0); +} + +int +eigrp_addrisset(int af, union eigrpd_addr *addr) +{ + switch (af) { + case AF_INET: + if (addr->v4.s_addr != INADDR_ANY) + return (1); + break; + case AF_INET6: + if (!IN6_IS_ADDR_UNSPECIFIED(&addr->v6)) + return (1); + break; + default: + break; + } + + return (0); +} + +#define IN6_IS_SCOPE_EMBED(a) \ + ((IN6_IS_ADDR_LINKLOCAL(a)) || \ + (IN6_IS_ADDR_MC_LINKLOCAL(a)) || \ + (IN6_IS_ADDR_MC_INTFACELOCAL(a))) + +void +embedscope(struct sockaddr_in6 *sin6) +{ + uint16_t tmp16; + + if (IN6_IS_SCOPE_EMBED(&sin6->sin6_addr)) { + memcpy(&tmp16, &sin6->sin6_addr.s6_addr[2], sizeof(tmp16)); + if (tmp16 != 0) { + log_warnx("%s: address %s already has embeded scope %u", + __func__, log_sockaddr(sin6), ntohs(tmp16)); + } + tmp16 = htons(sin6->sin6_scope_id); + memcpy(&sin6->sin6_addr.s6_addr[2], &tmp16, sizeof(tmp16)); + sin6->sin6_scope_id = 0; + } +} + +void +recoverscope(struct sockaddr_in6 *sin6) +{ + uint16_t tmp16; + + if (sin6->sin6_scope_id != 0) { + log_warnx("%s: address %s already has scope id %u", + __func__, log_sockaddr(sin6), sin6->sin6_scope_id); + } + + if (IN6_IS_SCOPE_EMBED(&sin6->sin6_addr)) { + memcpy(&tmp16, &sin6->sin6_addr.s6_addr[2], sizeof(tmp16)); + sin6->sin6_scope_id = ntohs(tmp16); + sin6->sin6_addr.s6_addr[2] = 0; + sin6->sin6_addr.s6_addr[3] = 0; + } +} + +void +addscope(struct sockaddr_in6 *sin6, uint32_t id) +{ + if (sin6->sin6_scope_id != 0) { + log_warnx("%s: address %s already has scope id %u", __func__, + log_sockaddr(sin6), sin6->sin6_scope_id); + } + + if (IN6_IS_SCOPE_EMBED(&sin6->sin6_addr)) { + sin6->sin6_scope_id = id; + } +} + +void +clearscope(struct in6_addr *in6) +{ + if (IN6_IS_SCOPE_EMBED(in6)) { + in6->s6_addr[2] = 0; + in6->s6_addr[3] = 0; + } +} + +#undef IN6_IS_SCOPE_EMBED |