summaryrefslogtreecommitdiffstats
path: root/zebra/netconf_netlink.c
blob: 002d2c7bf65d5b8c55abedb7273ea46119158816 (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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * netconf_netlink.c - netconf interaction with the kernel using
 * netlink
 * Copyright (C) 2021  Nvidia, Inc.
 *                     Donald Sharp
 */
#include <zebra.h>
#include <fcntl.h>

#ifdef HAVE_NETLINK /* Netlink OSes only */

#include <ns.h>

#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include "linux/netconf.h"

#include "lib/lib_errors.h"
#include "zebra/zebra_ns.h"
#include "zebra/zebra_dplane.h"
#include "zebra/kernel_netlink.h"
#include "zebra/netconf_netlink.h"
#include "zebra/debug.h"

static struct rtattr *netconf_rta(struct netconfmsg *ncm)
{
	return (struct rtattr *)((char *)ncm +
				 NLMSG_ALIGN(sizeof(struct netconfmsg)));
}

/*
 * Handle netconf update about a single interface: create dplane
 * context, and enqueue for processing in the main zebra pthread.
 */
static int
netlink_netconf_dplane_update(ns_id_t ns_id, afi_t afi, ifindex_t ifindex,
			      enum dplane_netconf_status_e mpls_on,
			      enum dplane_netconf_status_e mcast_on,
			      enum dplane_netconf_status_e linkdown_on)
{
	struct zebra_dplane_ctx *ctx;

	ctx = dplane_ctx_alloc();
	dplane_ctx_set_op(ctx, DPLANE_OP_INTF_NETCONFIG);
	dplane_ctx_set_ns_id(ctx, ns_id);
	dplane_ctx_set_afi(ctx, afi);
	dplane_ctx_set_ifindex(ctx, ifindex);

	dplane_ctx_set_netconf_mpls(ctx, mpls_on);
	dplane_ctx_set_netconf_mcast(ctx, mcast_on);
	dplane_ctx_set_netconf_linkdown(ctx, linkdown_on);

	/* Enqueue ctx for main pthread to process */
	dplane_provider_enqueue_to_zebra(ctx);

	return 0;
}

/*
 * Parse and process an incoming netlink netconf update.
 */
int netlink_netconf_change(struct nlmsghdr *h, ns_id_t ns_id, int startup)
{
	struct netconfmsg *ncm;
	struct rtattr *tb[NETCONFA_MAX + 1] = {};
	int len;
	ifindex_t ifindex;
	uint32_t ival;
	afi_t afi;
	enum dplane_netconf_status_e mpls_on = DPLANE_NETCONF_STATUS_UNKNOWN;
	enum dplane_netconf_status_e mcast_on = DPLANE_NETCONF_STATUS_UNKNOWN;
	enum dplane_netconf_status_e linkdown_on =
		DPLANE_NETCONF_STATUS_UNKNOWN;

	if (h->nlmsg_type != RTM_NEWNETCONF && h->nlmsg_type != RTM_DELNETCONF)
		return 0;

	len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct netconfmsg));
	if (len < 0) {
		zlog_err("%s: Message received from netlink is of a broken size: %d, min %zu",
			 __func__, h->nlmsg_len,
			 (size_t)NLMSG_LENGTH(sizeof(struct netconfmsg)));
		return -1;
	}

	ncm = NLMSG_DATA(h);

	/*
	 * FRR does not have an internal representation of afi_t for
	 * the MPLS Address Family that the kernel has.  So let's
	 * just call it v4.  This is ok because the kernel appears
	 * to do a good job of not sending data that is mixed/matched
	 * across families
	 */
#ifdef AF_MPLS
	if (ncm->ncm_family == AF_MPLS)
		afi = AFI_IP;
	else
#endif /* AF_MPLS */
		afi = family2afi(ncm->ncm_family);

	netlink_parse_rtattr(tb, NETCONFA_MAX, netconf_rta(ncm), len);

	if (!tb[NETCONFA_IFINDEX]) {
		zlog_err("NETCONF message received from netlink without an ifindex");
		return 0;
	}

	ifindex = *(ifindex_t *)RTA_DATA(tb[NETCONFA_IFINDEX]);

	if (tb[NETCONFA_INPUT]) {
		ival = *(uint32_t *)RTA_DATA(tb[NETCONFA_INPUT]);
		if (ival != 0)
			mpls_on = DPLANE_NETCONF_STATUS_ENABLED;
		else
			mpls_on = DPLANE_NETCONF_STATUS_DISABLED;
	}

	if (tb[NETCONFA_MC_FORWARDING]) {
		ival = *(uint32_t *)RTA_DATA(tb[NETCONFA_MC_FORWARDING]);
		if (ival != 0)
			mcast_on = DPLANE_NETCONF_STATUS_ENABLED;
		else
			mcast_on = DPLANE_NETCONF_STATUS_DISABLED;
	}

	if (tb[NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN]) {
		ival = *(uint32_t *)RTA_DATA(
			tb[NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN]);
		if (ival != 0)
			linkdown_on = DPLANE_NETCONF_STATUS_ENABLED;
		else
			linkdown_on = DPLANE_NETCONF_STATUS_DISABLED;
	}

	if (IS_ZEBRA_DEBUG_KERNEL)
		zlog_debug(
			"%s: interface %u is mpls on: %d multicast on: %d linkdown: %d",
			__func__, ifindex, mpls_on, mcast_on, linkdown_on);

	/* Create a dplane context and pass it along for processing */
	netlink_netconf_dplane_update(ns_id, afi, ifindex, mpls_on, mcast_on,
				      linkdown_on);

	return 0;
}

/*
 * Request info from the host OS. This only sends the request; any replies
 * are processed asynchronously.
 */
int netlink_request_netconf(int sockfd)
{
	struct nlsock *nls;
	struct {
		struct nlmsghdr n;
		struct netconfmsg ncm;
		char buf[1024];
	} req = {};

	nls = kernel_netlink_nlsock_lookup(sockfd);

	if (IS_ZEBRA_DEBUG_KERNEL)
		zlog_debug("%s: nlsock %s", __func__, nls ? nls->name : "NULL");

	if (nls == NULL)
		return -1;

	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct netconfmsg));
	req.n.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
	req.n.nlmsg_type = RTM_GETNETCONF;
	req.ncm.ncm_family = AF_UNSPEC;

	return netlink_request(nls, &req);
}

extern struct zebra_privs_t zserv_privs;
/*
 * Currently netconf has no ability to set from netlink.
 * So we've received a request to do this work in the data plane.
 * as such we need to set the value via the /proc system
 */
enum netlink_msg_status netlink_put_intf_netconfig(struct nl_batch *bth,
						   struct zebra_dplane_ctx *ctx)
{
	const char *ifname = dplane_ctx_get_ifname(ctx);
	enum dplane_netconf_status_e mpls_on = dplane_ctx_get_netconf_mpls(ctx);
	char set[64];
	char mpls_proc[PATH_MAX];
	int fd, ret = FRR_NETLINK_ERROR;

	snprintf(mpls_proc, sizeof(mpls_proc),
		 "/proc/sys/net/mpls/conf/%s/input", ifname);

	if (mpls_on == DPLANE_NETCONF_STATUS_ENABLED)
		snprintf(set, sizeof(set), "1\n");
	else if (mpls_on == DPLANE_NETCONF_STATUS_DISABLED)
		snprintf(set, sizeof(set), "0\n");
	else {
		flog_err_sys(
			EC_LIB_DEVELOPMENT,
			"%s: Expected interface %s to be set to ENABLED or DISABLED was %d",
			__func__, ifname, mpls_on);
		return ret;
	}

	frr_with_privs (&zserv_privs) {
		fd = open(mpls_proc, O_WRONLY);
		if (fd < 0) {
			flog_err_sys(
				EC_LIB_SOCKET,
				"%s: Unable to open %s for writing: %s(%d)",
				__func__, mpls_proc, safe_strerror(errno),
				errno);
			return ret;
		}
		if (write(fd, set, 2) == 2)
			ret = FRR_NETLINK_SUCCESS;
		else
			flog_err_sys(EC_LIB_SOCKET,
				     "%s: Unsuccessful write to %s: %s(%d)",
				     __func__, mpls_proc, safe_strerror(errno),
				     errno);
		close(fd);
	}
	return ret;
}

#endif	/* HAVE_NETLINK */