Here's an updated diff with the following changes:
- Send the ifidx of the configured 'iface' instead of ifidx 0 to prevent
name collisions
- Cache the first received DNS server locally for cleanup/resending.
- Handle RTP_PROPOSAL_SOLICIT by resending the cached server.
- Remove the cached server from resolvd on cleanup.
There is no easy way to support multiple DNS servers from different peers
at the moment. For now, iked will always propose the first DNS server it
receives and simply ignore the rest. There is room for improvement here,
but i would rather do this in a follow-up diff.
ok?
Index: config.c
===================================================================
RCS file: /cvs/src/sbin/iked/config.c,v
retrieving revision 1.79
diff -u -p -r1.79 config.c
--- config.c 13 May 2021 15:20:48 -0000 1.79
+++ config.c 1 Sep 2021 11:26:51 -0000
@@ -174,6 +174,7 @@ config_free_sa(struct iked *env, struct
free(sa->sa_cp_addr);
free(sa->sa_cp_addr6);
+ free(sa->sa_cp_dns);
free(sa->sa_tag);
free(sa);
Index: iked.c
===================================================================
RCS file: /cvs/src/sbin/iked/iked.c,v
retrieving revision 1.57
diff -u -p -r1.57 iked.c
--- iked.c 13 May 2021 15:20:48 -0000 1.57
+++ iked.c 1 Sep 2021 11:26:51 -0000
@@ -459,6 +459,9 @@ parent_dispatch_ikev2(int fd, struct pri
case IMSG_IF_ADDADDR:
case IMSG_IF_DELADDR:
return (vroute_getaddr(env, imsg));
+ case IMSG_VDNS_ADD:
+ case IMSG_VDNS_DEL:
+ return (vroute_getdns(env, imsg));
case IMSG_VROUTE_ADD:
case IMSG_VROUTE_DEL:
return (vroute_getroute(env, imsg));
Index: iked.h
===================================================================
RCS file: /cvs/src/sbin/iked/iked.h,v
retrieving revision 1.192
diff -u -p -r1.192 iked.h
--- iked.h 23 Jun 2021 12:11:40 -0000 1.192
+++ iked.h 1 Sep 2021 11:26:51 -0000
@@ -429,6 +429,7 @@ struct iked_sa {
int sa_cp; /* XXX */
struct iked_addr *sa_cp_addr; /* requested address */
struct iked_addr *sa_cp_addr6; /* requested address */
+ struct iked_addr *sa_cp_dns; /* requested dns */
struct iked_policy *sa_policy;
struct timeval sa_timecreated;
@@ -611,6 +612,7 @@ struct iked_message {
int msg_cp;
struct iked_addr *msg_cp_addr; /* requested address */
struct iked_addr *msg_cp_addr6; /* requested address */
+ struct iked_addr *msg_cp_dns; /* requested dns */
/* MOBIKE */
int msg_update_sa_addresses;
@@ -752,6 +754,7 @@ struct iked {
int sc_pfkey; /* ike process */
struct event sc_pfkeyev;
+ struct event sc_routeev;
uint8_t sc_certreqtype;
struct ibuf *sc_certreq;
void *sc_vroute;
@@ -975,6 +978,8 @@ void vroute_init(struct iked *);
int vroute_setaddr(struct iked *, int, struct sockaddr *, int, unsigned int);
void vroute_cleanup(struct iked *);
int vroute_getaddr(struct iked *, struct imsg *);
+int vroute_setdns(struct iked *, int, struct sockaddr *, unsigned int);
+int vroute_getdns(struct iked *, struct imsg *);
int vroute_setaddroute(struct iked *, uint8_t, struct sockaddr *,
uint8_t, struct sockaddr *);
int vroute_setcloneroute(struct iked *, uint8_t, struct sockaddr *,
Index: ikev2.c
===================================================================
RCS file: /cvs/src/sbin/iked/ikev2.c,v
retrieving revision 1.325
diff -u -p -r1.325 ikev2.c
--- ikev2.c 29 Jun 2021 15:39:20 -0000 1.325
+++ ikev2.c 1 Sep 2021 11:26:51 -0000
@@ -998,6 +998,13 @@ ikev2_ike_auth_recv(struct iked *env, st
log_info("%s: obtained lease: %s", SPI_SA(sa, __func__),
print_host((struct sockaddr
*)&sa->sa_cp_addr6->addr, NULL, 0));
}
+ if (msg->msg_cp_dns) {
+ sa->sa_cp_dns = msg->msg_cp_dns;
+ msg->msg_cp_dns = NULL;
+ log_debug("%s: DNS: %s", __func__,
+ print_host((struct sockaddr *)&sa->sa_cp_dns->addr,
+ NULL, 0));
+ }
sa->sa_cp = msg->msg_cp;
}
@@ -4508,6 +4515,8 @@ ikev2_ikesa_enable(struct iked *env, str
sa->sa_cp_addr = NULL;
nsa->sa_cp_addr6 = sa->sa_cp_addr6;
sa->sa_cp_addr6 = NULL;
+ nsa->sa_cp_dns = sa->sa_cp_dns;
+ sa->sa_cp_dns = NULL;
/* Transfer other attributes */
if (sa->sa_dstid_entry_valid) {
sa_dstid_remove(env, sa);
Index: ikev2_msg.c
===================================================================
RCS file: /cvs/src/sbin/iked/ikev2_msg.c,v
retrieving revision 1.77
diff -u -p -r1.77 ikev2_msg.c
--- ikev2_msg.c 29 Oct 2020 21:49:58 -0000 1.77
+++ ikev2_msg.c 1 Sep 2021 11:26:51 -0000
@@ -197,6 +197,7 @@ ikev2_msg_cleanup(struct iked *env, stru
free(msg->msg_eap.eam_user);
free(msg->msg_cp_addr);
free(msg->msg_cp_addr6);
+ free(msg->msg_cp_dns);
msg->msg_nonce = NULL;
msg->msg_ke = NULL;
@@ -209,6 +210,7 @@ ikev2_msg_cleanup(struct iked *env, stru
msg->msg_eap.eam_user = NULL;
msg->msg_cp_addr = NULL;
msg->msg_cp_addr6 = NULL;
+ msg->msg_cp_dns = NULL;
config_free_proposals(&msg->msg_proposals, 0);
while ((cr = SIMPLEQ_FIRST(&msg->msg_certreqs))) {
Index: ikev2_pld.c
===================================================================
RCS file: /cvs/src/sbin/iked/ikev2_pld.c,v
retrieving revision 1.117
diff -u -p -r1.117 ikev2_pld.c
--- ikev2_pld.c 19 Feb 2021 21:52:53 -0000 1.117
+++ ikev2_pld.c 1 Sep 2021 11:26:51 -0000
@@ -1842,6 +1842,7 @@ ikev2_pld_cp(struct iked *env, struct ik
uint8_t *ptr;
size_t len;
uint8_t buf[128];
+ int cfg_type;
if (ikev2_validate_cp(msg, offset, left, &cp))
return (-1);
@@ -1878,8 +1879,10 @@ ikev2_pld_cp(struct iked *env, struct ik
print_hex(ptr, sizeof(*cfg), betoh16(cfg->cfg_length));
- switch (betoh16(cfg->cfg_type)) {
+ cfg_type = betoh16(cfg->cfg_type);
+ switch (cfg_type) {
case IKEV2_CFG_INTERNAL_IP4_ADDRESS:
+ case IKEV2_CFG_INTERNAL_IP4_DNS:
if (!ikev2_msg_frompeer(msg))
break;
if (betoh16(cfg->cfg_length) == 0)
@@ -1891,8 +1894,20 @@ ikev2_pld_cp(struct iked *env, struct ik
__func__, betoh16(cfg->cfg_length), 4);
return (-1);
}
- if (msg->msg_parent->msg_cp_addr != NULL) {
- log_debug("%s: address already set", __func__);
+ switch(cfg_type) {
+ case IKEV2_CFG_INTERNAL_IP4_ADDRESS:
+ if (msg->msg_parent->msg_cp_addr != NULL) {
+ log_debug("%s: address already set",
__func__);
+ goto skip;
+ }
+ break;
+ case IKEV2_CFG_INTERNAL_IP4_DNS:
+ if (msg->msg_parent->msg_cp_dns != NULL) {
+ log_debug("%s: dns already set",
__func__);
+ goto skip;
+ }
+ break;
+ default:
break;
}
if ((addr = calloc(1, sizeof(*addr))) == NULL) {
@@ -1907,22 +1922,42 @@ ikev2_pld_cp(struct iked *env, struct ik
print_host((struct sockaddr *)in4, (char *)buf,
sizeof(buf));
log_debug("%s: cfg %s", __func__, buf);
- msg->msg_parent->msg_cp_addr = addr;
+ switch(cfg_type) {
+ case IKEV2_CFG_INTERNAL_IP4_ADDRESS:
+ msg->msg_parent->msg_cp_addr = addr;
+ log_debug("%s: IP4_ADDRESS %s", __func__, buf);
+ break;
+ case IKEV2_CFG_INTERNAL_IP4_DNS:
+ msg->msg_parent->msg_cp_dns = addr;
+ log_debug("%s: IP4_DNS %s", __func__, buf);
+ break;
+ }
break;
case IKEV2_CFG_INTERNAL_IP6_ADDRESS:
+ case IKEV2_CFG_INTERNAL_IP6_DNS:
if (!ikev2_msg_frompeer(msg))
break;
if (betoh16(cfg->cfg_length) == 0)
break;
/* XXX multiple-valued */
- if (betoh16(cfg->cfg_length) < 16 + 1) {
+ if (betoh16(cfg->cfg_length) < 16) {
log_debug("%s: malformed payload: too short "
"for ipv6 addr w/prefixlen (%u < %u)",
- __func__, betoh16(cfg->cfg_length), 16 + 1);
+ __func__, betoh16(cfg->cfg_length), 16);
return (-1);
}
- if (msg->msg_parent->msg_cp_addr6 != NULL) {
- log_debug("%s: address already set", __func__);
+ switch(cfg_type) {
+ case IKEV2_CFG_INTERNAL_IP6_ADDRESS:
+ if (msg->msg_parent->msg_cp_addr6 != NULL) {
+ log_debug("%s: address6 already set",
__func__);
+ goto skip;
+ }
+ break;
+ case IKEV2_CFG_INTERNAL_IP6_DNS:
+ if (msg->msg_parent->msg_cp_dns != NULL) {
+ log_debug("%s: dns already set",
__func__);
+ goto skip;
+ }
break;
}
if ((addr = calloc(1, sizeof(*addr))) == NULL) {
@@ -1937,10 +1972,20 @@ ikev2_pld_cp(struct iked *env, struct ik
print_host((struct sockaddr *)in6, (char *)buf,
sizeof(buf));
log_debug("%s: cfg %s/%d", __func__, buf, ptr[16]);
- msg->msg_parent->msg_cp_addr6 = addr;
+ switch(cfg_type) {
+ case IKEV2_CFG_INTERNAL_IP6_ADDRESS:
+ msg->msg_parent->msg_cp_addr6 = addr;
+ log_debug("%s: IP6_ADDRESS %s", __func__, buf);
+ break;
+ case IKEV2_CFG_INTERNAL_IP6_DNS:
+ msg->msg_parent->msg_cp_dns = addr;
+ log_debug("%s: IP6_DNS %s", __func__, buf);
+ break;
+ }
break;
}
+ skip:
ptr += betoh16(cfg->cfg_length);
len -= betoh16(cfg->cfg_length);
}
Index: policy.c
===================================================================
RCS file: /cvs/src/sbin/iked/policy.c,v
retrieving revision 1.82
diff -u -p -r1.82 policy.c
--- policy.c 23 Jun 2021 12:11:40 -0000 1.82
+++ policy.c 1 Sep 2021 11:26:51 -0000
@@ -680,6 +680,13 @@ sa_configure_iface(struct iked *env, str
if (sa->sa_policy == NULL || sa->sa_policy->pol_iface == 0)
return (0);
+ if (sa->sa_cp_dns) {
+ if (vroute_setdns(env, add,
+ (struct sockaddr *)&sa->sa_cp_dns->addr,
+ sa->sa_policy->pol_iface) != 0)
+ return (-1);
+ }
+
if (!sa->sa_cp_addr && !sa->sa_cp_addr6)
return (0);
Index: types.h
===================================================================
RCS file: /cvs/src/sbin/iked/types.h,v
retrieving revision 1.44
diff -u -p -r1.44 types.h
--- types.h 3 Aug 2021 12:46:30 -0000 1.44
+++ types.h 1 Sep 2021 11:26:51 -0000
@@ -119,6 +119,8 @@ enum imsg_type {
IMSG_VROUTE_ADD,
IMSG_VROUTE_DEL,
IMSG_VROUTE_CLONE,
+ IMSG_VDNS_ADD,
+ IMSG_VDNS_DEL,
IMSG_OCSP_FD,
IMSG_OCSP_CFG,
IMSG_AUTH,
Index: vroute.c
===================================================================
RCS file: /cvs/src/sbin/iked/vroute.c,v
retrieving revision 1.12
diff -u -p -r1.12 vroute.c
--- vroute.c 23 Jun 2021 12:21:23 -0000 1.12
+++ vroute.c 1 Sep 2021 11:26:51 -0000
@@ -35,6 +35,7 @@
#include <iked.h>
+#define ROUTE_SOCKET_BUF_SIZE 16384
#define IKED_VROUTE_PRIO 6
#define ROUNDUP(a) (a>0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) :
sizeof(long))
@@ -44,10 +45,14 @@ int vroute_setroute(struct iked *, uint8
int vroute_doroute(struct iked *, int, int, int, uint8_t, struct sockaddr *,
struct sockaddr *, struct sockaddr *, int *);
int vroute_doaddr(struct iked *, char *, struct sockaddr *, struct sockaddr *,
int);
+int vroute_dodns(struct iked *, struct sockaddr *, int, unsigned int);
void vroute_cleanup(struct iked *);
+void vroute_rtmsg_cb(int, short, void *);
void vroute_insertaddr(struct iked *, int, struct sockaddr *, struct sockaddr
*);
void vroute_removeaddr(struct iked *, int, struct sockaddr *, struct sockaddr
*);
+void vroute_insertdns(struct iked *, int, struct sockaddr *);
+void vroute_removedns(struct iked *, int, struct sockaddr *);
void vroute_insertroute(struct iked *, int, struct sockaddr *, struct sockaddr
*);
void vroute_removeroute(struct iked *, int, struct sockaddr *, struct sockaddr
*);
@@ -68,14 +73,21 @@ struct vroute_route {
};
TAILQ_HEAD(vroute_routes, vroute_route);
+struct vroute_dns {
+ struct sockaddr_storage vd_addr;
+ int vd_ifidx;
+};
+
struct iked_vroute_sc {
- struct vroute_addrs ivr_addrs;
- struct vroute_routes ivr_routes;
- int ivr_iosock;
- int ivr_iosock6;
- int ivr_rtsock;
- int ivr_rtseq;
- pid_t ivr_pid;
+ struct vroute_addrs ivr_addrs;
+ struct vroute_routes ivr_routes;
+ struct vroute_dns *ivr_dns;
+ struct event ivr_routeev;
+ int ivr_iosock;
+ int ivr_iosock6;
+ int ivr_rtsock;
+ int ivr_rtseq;
+ pid_t ivr_pid;
};
struct vroute_msg {
@@ -87,9 +99,57 @@ int vroute_process(struct iked *, int ms
struct sockaddr *, struct sockaddr *, struct sockaddr *, int *);
void
+vroute_rtmsg_cb(int fd, short events, void *arg)
+{
+ struct iked *env = (struct iked *) arg;
+ struct iked_vroute_sc *ivr = env->sc_vroute;
+ static uint8_t *buf;
+ struct rt_msghdr *rtm;
+ ssize_t n;
+
+ if (buf == NULL) {
+ buf = malloc(ROUTE_SOCKET_BUF_SIZE);
+ if (buf == NULL)
+ fatal("malloc");
+ }
+ rtm = (struct rt_msghdr *)buf;
+ if ((n = read(fd, buf, ROUTE_SOCKET_BUF_SIZE)) == -1) {
+ if (errno == EAGAIN || errno == EINTR)
+ return;
+ log_warn("%s: read error", __func__);
+ return;
+ }
+
+ if (n == 0)
+ fatal("routing socket closed");
+
+ if (n < (ssize_t)sizeof(rtm->rtm_msglen) || n < rtm->rtm_msglen) {
+ log_warnx("partial rtm of %zd in buffer", n);
+ return;
+ }
+
+ if (rtm->rtm_version != RTM_VERSION)
+ return;
+
+ switch(rtm->rtm_type) {
+ case RTM_PROPOSAL:
+ if (rtm->rtm_priority == RTP_PROPOSAL_SOLICIT) {
+ log_debug("%s: got solicit", __func__);
+ vroute_dodns(env, (struct sockaddr *)
&ivr->ivr_dns->vd_addr, 1,
+ ivr->ivr_dns->vd_ifidx);
+ }
+ break;
+ default:
+ log_debug("%s: unexpected RTM: %d", __func__, rtm->rtm_type);
+ break;
+ }
+}
+
+void
vroute_init(struct iked *env)
{
struct iked_vroute_sc *ivr;
+ int rtfilter;
ivr = calloc(1, sizeof(*ivr));
if (ivr == NULL)
@@ -104,12 +164,21 @@ vroute_init(struct iked *env)
if ((ivr->ivr_rtsock = socket(AF_ROUTE, SOCK_RAW, AF_UNSPEC)) == -1)
fatal("%s: failed to create routing socket", __func__);
+ rtfilter = ROUTE_FILTER(RTM_GET) | ROUTE_FILTER(RTM_PROPOSAL);
+ if (setsockopt(ivr->ivr_rtsock, AF_ROUTE, ROUTE_MSGFILTER, &rtfilter,
+ sizeof(rtfilter)) == -1)
+ fatal("%s: setsockopt(ROUTE_MSGFILTER)", __func__);
+
TAILQ_INIT(&ivr->ivr_addrs);
TAILQ_INIT(&ivr->ivr_routes);
ivr->ivr_pid = getpid();
env->sc_vroute = ivr;
+
+ event_set(&ivr->ivr_routeev, ivr->ivr_rtsock, EV_READ | EV_PERSIST,
+ vroute_rtmsg_cb, env);
+ event_add(&ivr->ivr_routeev, NULL);
}
void
@@ -138,6 +207,12 @@ vroute_cleanup(struct iked *env)
TAILQ_REMOVE(&ivr->ivr_routes, route, vr_entry);
free(route);
}
+
+ if (ivr->ivr_dns) {
+ vroute_dodns(env, (struct sockaddr *) &ivr->ivr_dns->vd_addr, 0,
+ ivr->ivr_dns->vd_ifidx);
+ free(ivr->ivr_dns);
+ }
}
int
@@ -239,6 +314,64 @@ vroute_getaddr(struct iked *env, struct
return (vroute_doaddr(env, ifname, addr, mask, add));
}
+int
+vroute_setdns(struct iked *env, int add, struct sockaddr *addr,
+ unsigned int ifidx)
+{
+ struct iovec iov[2];
+
+ iov[0].iov_base = addr;
+ iov[0].iov_len = addr->sa_len;
+
+ iov[1].iov_base = &ifidx;
+ iov[1].iov_len = sizeof(ifidx);
+
+ return (proc_composev(&env->sc_ps, PROC_PARENT,
+ add ? IMSG_VDNS_ADD: IMSG_VDNS_DEL, iov, 2));
+}
+
+int
+vroute_getdns(struct iked *env, struct imsg *imsg)
+{
+ struct iked_vroute_sc *ivr = env->sc_vroute;
+ struct sockaddr *dns;
+ uint8_t *ptr;
+ size_t left;
+ int add;
+ unsigned int ifidx;
+
+ ptr = imsg->data;
+ left = IMSG_DATA_SIZE(imsg);
+
+ if (left < sizeof(*dns))
+ fatalx("bad length imsg received");
+
+ dns = (struct sockaddr *) ptr;
+ if (left < dns->sa_len)
+ fatalx("bad length imsg received");
+ ptr += dns->sa_len;
+ left -= dns->sa_len;
+
+ if (left != sizeof(ifidx))
+ fatalx("bad length imsg received");
+ memcpy(&ifidx, ptr, sizeof(ifidx));
+ ptr += sizeof(ifidx);
+ left -= sizeof(ifidx);
+
+ add = (imsg->hdr.type == IMSG_VDNS_ADD);
+ if (add) {
+ if (ivr->ivr_dns != NULL)
+ return (0);
+ vroute_insertdns(env, ifidx, dns);
+ } else {
+ if (ivr->ivr_dns == NULL)
+ return (0);
+ vroute_removedns(env, ifidx, dns);
+ }
+
+ return (vroute_dodns(env, dns, add, ifidx));
+}
+
void
vroute_insertroute(struct iked *env, int rdomain, struct sockaddr *dest,
struct sockaddr *mask)
@@ -285,6 +418,35 @@ vroute_removeroute(struct iked *env, int
}
void
+vroute_insertdns(struct iked *env, int ifidx, struct sockaddr *addr)
+{
+ struct iked_vroute_sc *ivr = env->sc_vroute;
+ struct vroute_dns *dns;
+
+ dns = calloc(1, sizeof(*dns));
+ if (dns == NULL)
+ fatalx("%s: calloc.", __func__);
+
+ memcpy(&dns->vd_addr, addr, addr->sa_len);
+ dns->vd_ifidx = ifidx;
+
+ ivr->ivr_dns = dns;
+}
+
+void
+vroute_removedns(struct iked *env, int ifidx, struct sockaddr *addr)
+{
+ struct iked_vroute_sc *ivr = env->sc_vroute;
+
+ if (ifidx == ivr->ivr_dns->vd_ifidx &&
+ sockaddr_cmp(addr, (struct sockaddr *)
+ &ivr->ivr_dns->vd_addr, -1) == 0) {
+ free(ivr->ivr_dns);
+ ivr->ivr_dns = NULL;
+ }
+}
+
+void
vroute_insertaddr(struct iked *env, int ifidx, struct sockaddr *addr,
struct sockaddr *mask)
{
@@ -525,6 +687,73 @@ vroute_getcloneroute(struct iked *env, s
return (vroute_doroute(env, flags, addrs, rdomain, RTM_ADD,
(struct sockaddr *)&dest, (struct sockaddr *)&mask,
(struct sockaddr *)&addr, NULL));
+}
+
+int
+vroute_dodns(struct iked *env, struct sockaddr *dns, int add,
+ unsigned int ifidx)
+{
+ struct vroute_msg m_rtmsg;
+ struct sockaddr_in *in;
+ struct sockaddr_in6 *in6;
+ struct sockaddr_rtdns rtdns;
+ struct iked_vroute_sc *ivr = env->sc_vroute;
+ struct iovec iov[3];
+ int i;
+ long pad = 0;
+ int iovcnt = 0, padlen;
+
+ bzero(&m_rtmsg, sizeof(m_rtmsg));
+#define rtm m_rtmsg.vm_rtm
+ rtm.rtm_version = RTM_VERSION;
+ rtm.rtm_type = RTM_PROPOSAL;
+ rtm.rtm_seq = ++ivr->ivr_rtseq;
+ rtm.rtm_priority = RTP_PROPOSAL_STATIC;
+ rtm.rtm_flags = RTF_UP;
+ rtm.rtm_addrs = RTA_DNS;
+ rtm.rtm_index = ifidx;
+
+ iov[iovcnt].iov_base = &rtm;
+ iov[iovcnt].iov_len = sizeof(rtm);
+ iovcnt++;
+
+ bzero(&rtdns, sizeof(rtdns));
+ rtdns.sr_family = dns->sa_family;
+ rtdns.sr_len = 2;
+ if (add) {
+ switch(dns->sa_family) {
+ case AF_INET:
+ rtdns.sr_family = AF_INET;
+ rtdns.sr_len += sizeof(struct in_addr);
+ in = (struct sockaddr_in *)dns;
+ memcpy(rtdns.sr_dns, &in->sin_addr, sizeof(struct
in_addr));
+ break;
+ case AF_INET6:
+ rtdns.sr_family = AF_INET6;
+ rtdns.sr_len += sizeof(struct in6_addr);
+ in6 = (struct sockaddr_in6 *)dns;
+ memcpy(rtdns.sr_dns, &in6->sin6_addr, sizeof(struct
in6_addr));
+ break;
+ default:
+ return (-1);
+ }
+ }
+ iov[iovcnt].iov_base = &rtdns;
+ iov[iovcnt++].iov_len = sizeof(rtdns);
+ padlen = ROUNDUP(sizeof(rtdns)) - sizeof(rtdns);
+ if (padlen > 0) {
+ iov[iovcnt].iov_base = &pad;
+ iov[iovcnt++].iov_len = padlen;
+ }
+
+ for (i = 0; i < iovcnt; i++)
+ rtm.rtm_msglen += iov[i].iov_len;
+#undef rtm
+
+ if (writev(ivr->ivr_rtsock, iov, iovcnt) == -1)
+ log_warn("failed to send route message");
+
+ return (0);
}
int