summaryrefslogtreecommitdiff
path: root/src/rtnetlink.c
blob: d15ae13924162747d8398f8663d95ef4cb2e5816 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/**
 * Copyright (C) 2012 Steven Barth <steven@midlink.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License v2 as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <time.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <stdbool.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <linux/rtnetlink.h>

#include "odhcp6c.h"


static int sock = -1;
static unsigned seq = 0;


// Init rtnetlink socket
int init_rtnetlink(void)
{
	sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
	struct sockaddr_nl rtnl_kernel = { .nl_family = AF_NETLINK };
	if (connect(sock, (struct sockaddr*)&rtnl_kernel, sizeof(rtnl_kernel)))
		return -1;

	return 0;
}


// CRUD addresses to interface
int set_rtnetlink_addr(int ifindex, const struct in6_addr *addr,
		time_t pref, time_t valid)
{
	int flags = NLM_F_REQUEST | NLM_F_ACK;
	int cmd = RTM_DELADDR;

	if (valid > 0) {
		flags |= NLM_F_CREATE | NLM_F_REPLACE;
		cmd = RTM_NEWADDR;
	}

	struct {
		struct nlmsghdr nhm;
		struct ifaddrmsg ifa;
		struct rtattr rta_addr;
		struct in6_addr addr;
		struct rtattr rta_local;
		struct in6_addr local;
		struct rtattr rta_info;
		struct ifa_cacheinfo info;
	} req = {
		{sizeof(req), cmd, flags, ++seq, 0},
		{AF_INET6, 128, 0, RT_SCOPE_UNIVERSE, ifindex},
		{sizeof(req.rta_addr) + sizeof(req.addr), IFA_ADDRESS},
		*addr,
		{sizeof(req.rta_local) + sizeof(req.local), IFA_LOCAL},
		*addr,
		{sizeof(req.rta_info) + sizeof(req.info), IFA_CACHEINFO},
		{pref, valid, 0, 0}
	};
	send(sock, &req, sizeof(req), 0);

	struct {
		struct nlmsghdr nhm;
		struct nlmsgerr err;
	} reply;
	recv(sock, &reply, sizeof(reply), 0);

	char buf[INET6_ADDRSTRLEN];
	inet_ntop(AF_INET6, addr, buf, sizeof(buf));
	syslog(LOG_WARNING, "%s address %s/128 for iface %i: %s",
			(valid) ? "assigning" : "removing", buf,
			ifindex, strerror(-reply.err.error));

	if (reply.err.error < 0 || valid == 0)
		return reply.err.error;

	// Check for duplicate addresses
	struct timespec ts = {1, 0};
	nanosleep(&ts, NULL);

	req.nhm.nlmsg_type = RTM_GETADDR;
	req.nhm.nlmsg_seq = ++seq;
	req.nhm.nlmsg_flags = NLM_F_REQUEST;
	send(sock, &req, sizeof(req), 0);

	struct {
		struct nlmsghdr nhm;
		struct ifaddrmsg ifa;
		uint8_t buf[1024];
	} dad_reply;
	recv(sock, &dad_reply, sizeof(dad_reply), 0);

	if (dad_reply.nhm.nlmsg_type != RTM_NEWADDR ||
			(dad_reply.ifa.ifa_flags & IFA_F_DADFAILED)) {
		syslog(LOG_WARNING, "Removing duplicate address %s", buf);
		set_rtnetlink_addr(ifindex, addr, 0, 0);
		return -EADDRNOTAVAIL;
	}
	return 0;
}