1/* $Id: getroute.c,v 1.4 2013/02/06 10:50:04 nanard Exp $ */
2/* MiniUPnP project
3 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
4 * (c) 2006-2015 Thomas Bernard
5 * This software is subject to the conditions detailed
6 * in the LICENCE file provided within the distribution */
7
8#include <stdio.h>
9#include <string.h>
10#include <unistd.h>
11#include <errno.h>
12#include <syslog.h>
13#include <sys/types.h>
14#include <sys/socket.h>
15#include <netinet/in.h>
16/*#include <linux/in_route.h>*/
17#include <linux/netlink.h>
18#include <linux/rtnetlink.h>
19#ifdef USE_LIBNFNETLINK
20/* define USE_LIBNFNETLINK in order to use libnfnetlink
21 * instead of custom code
22 * see https://github.com/miniupnp/miniupnp/issues/110 */
23#include <libnfnetlink/libnfnetlink.h>
24#endif /* USE_LIBNFNETLINK */
25
26#include "../getroute.h"
27#include "../upnputils.h"
28
29int
30get_src_for_route_to(const struct sockaddr * dst,
31                     void * src, size_t * src_len,
32                     int * index)
33{
34	int fd = -1;
35	struct nlmsghdr *h;
36	int status;
37	struct {
38		struct nlmsghdr n;
39		struct rtmsg r;
40		char buf[1024];
41	} req;
42	struct sockaddr_nl nladdr;
43	struct iovec iov = {
44		.iov_base = (void*) &req.n,
45	};
46	struct msghdr msg = {
47		.msg_name = &nladdr,
48		.msg_namelen = sizeof(nladdr),
49		.msg_iov = &iov,
50		.msg_iovlen = 1,
51	};
52	const struct sockaddr_in * dst4;
53	const struct sockaddr_in6 * dst6;
54#ifndef USE_LIBNFNETLINK
55	struct rtattr * rta;
56#endif /* USE_LIBNFNETLINK */
57
58	memset(&req, 0, sizeof(req));
59	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
60	req.n.nlmsg_flags = NLM_F_REQUEST;
61	req.n.nlmsg_type = RTM_GETROUTE;
62	req.r.rtm_family = dst->sa_family;
63	req.r.rtm_table = 0;
64	req.r.rtm_protocol = 0;
65	req.r.rtm_scope = 0;
66	req.r.rtm_type = 0;
67	req.r.rtm_src_len = 0;
68	req.r.rtm_dst_len = 0;
69	req.r.rtm_tos = 0;
70
71	{
72		char dst_str[128];
73		sockaddr_to_string(dst, dst_str, sizeof(dst_str));
74		syslog(LOG_DEBUG, "get_src_for_route_to (%s)", dst_str);
75	}
76	/* add address */
77#ifndef USE_LIBNFNETLINK
78	rta = (struct rtattr *)(((char*)&req) + NLMSG_ALIGN(req.n.nlmsg_len));
79	rta->rta_type = RTA_DST;
80#endif /* USE_LIBNFNETLINK */
81	if(dst->sa_family == AF_INET) {
82		dst4 = (const struct sockaddr_in *)dst;
83#ifdef USE_LIBNFNETLINK
84		nfnl_addattr_l(&req.n, sizeof(req), RTA_DST, &dst4->sin_addr, 4);
85#else
86		rta->rta_len = RTA_SPACE(sizeof(dst4->sin_addr));
87		memcpy(RTA_DATA(rta), &dst4->sin_addr, sizeof(dst4->sin_addr));
88#endif /* USE_LIBNFNETLINK */
89		req.r.rtm_dst_len = 32;
90	} else {
91		dst6 = (const struct sockaddr_in6 *)dst;
92#ifdef USE_LIBNFNETLINK
93		nfnl_addattr_l(&req.n, sizeof(req), RTA_DST, &dst6->sin6_addr, 16);
94#else
95		rta->rta_len = RTA_SPACE(sizeof(dst6->sin6_addr));
96		memcpy(RTA_DATA(rta), &dst6->sin6_addr, sizeof(dst6->sin6_addr));
97#endif /* USE_LIBNFNETLINK */
98		req.r.rtm_dst_len = 128;
99	}
100#ifndef USE_LIBNFNETLINK
101	req.n.nlmsg_len += rta->rta_len;
102#endif /* USE_LIBNFNETLINK */
103
104	fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
105	if (fd < 0) {
106		syslog(LOG_ERR, "socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) : %m");
107		return -1;
108	}
109
110	memset(&nladdr, 0, sizeof(nladdr));
111	nladdr.nl_family = AF_NETLINK;
112
113	req.n.nlmsg_seq = 1;
114	iov.iov_len = req.n.nlmsg_len;
115
116	status = sendmsg(fd, &msg, 0);
117
118	if (status < 0) {
119		syslog(LOG_ERR, "sendmsg(rtnetlink) : %m");
120		goto error;
121	}
122
123	memset(&req, 0, sizeof(req));
124
125	for(;;) {
126		iov.iov_len = sizeof(req);
127		status = recvmsg(fd, &msg, 0);
128		if(status < 0) {
129			if (errno == EINTR || errno == EAGAIN)
130				continue;
131			syslog(LOG_ERR, "recvmsg(rtnetlink) %m");
132			goto error;
133		}
134		if(status == 0) {
135			syslog(LOG_ERR, "recvmsg(rtnetlink) EOF");
136			goto error;
137		}
138		for (h = (struct nlmsghdr*)&req.n; status >= (int)sizeof(*h); ) {
139			int len = h->nlmsg_len;
140			int l = len - sizeof(*h);
141
142			if (l<0 || len>status) {
143				if (msg.msg_flags & MSG_TRUNC) {
144					syslog(LOG_ERR, "Truncated message");
145				}
146				syslog(LOG_ERR, "malformed message: len=%d", len);
147				goto error;
148			}
149
150			if(nladdr.nl_pid != 0 || h->nlmsg_seq != 1/*seq*/) {
151				syslog(LOG_ERR, "wrong seq = %d\n", h->nlmsg_seq);
152				/* Don't forget to skip that message. */
153				status -= NLMSG_ALIGN(len);
154				h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len));
155				continue;
156			}
157
158			if(h->nlmsg_type == NLMSG_ERROR) {
159				struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h);
160				syslog(LOG_ERR, "NLMSG_ERROR %d : %s", err->error, strerror(-err->error));
161				goto error;
162			}
163			if(h->nlmsg_type == RTM_NEWROUTE) {
164				struct rtattr * rta;
165				int len = h->nlmsg_len;
166				len -= NLMSG_LENGTH(sizeof(struct rtmsg));
167				for(rta = RTM_RTA(NLMSG_DATA((h))); RTA_OK(rta, len); rta = RTA_NEXT(rta,len)) {
168					unsigned char * data = RTA_DATA(rta);
169					if(rta->rta_type == RTA_PREFSRC) {
170						if(src_len && src) {
171							if(*src_len < RTA_PAYLOAD(rta)) {
172								syslog(LOG_WARNING, "cannot copy src: %u<%lu",
173								       (unsigned)*src_len, (unsigned long)RTA_PAYLOAD(rta));
174								goto error;
175							}
176							*src_len = RTA_PAYLOAD(rta);
177							memcpy(src, data, RTA_PAYLOAD(rta));
178						}
179					} else if(rta->rta_type == RTA_OIF) {
180						if(index)
181							memcpy(index, data, sizeof(int));
182					}
183				}
184				close(fd);
185				return 0;
186			}
187			status -= NLMSG_ALIGN(len);
188			h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len));
189		}
190	}
191	syslog(LOG_WARNING, "get_src_for_route_to() : src not found");
192error:
193	if(fd >= 0)
194		close(fd);
195	return -1;
196}
197
198