]>
Commit | Line | Data |
---|---|---|
942bf97b | 1 | /* |
2 | * Zebra Policy Based Routing (PBR) interaction with the kernel using | |
3 | * netlink. | |
4 | * Copyright (C) 2018 Cumulus Networks, Inc. | |
5 | * | |
6 | * This file is part of FRR. | |
7 | * | |
8 | * FRR is free software; you can redistribute it and/or modify it | |
9 | * under the terms of the GNU General Public License as published by the | |
10 | * Free Software Foundation; either version 2, or (at your option) any | |
11 | * later version. | |
12 | * | |
13 | * FRR is distributed in the hope that it will be useful, but | |
14 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | * General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with FRR; see the file COPYING. If not, write to the Free | |
20 | * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA | |
21 | * 02111-1307, USA. | |
22 | */ | |
23 | ||
24 | #include <zebra.h> | |
25 | ||
26 | #ifdef HAVE_NETLINK | |
27 | ||
28 | #include "if.h" | |
29 | #include "prefix.h" | |
30 | #include "vrf.h" | |
31 | ||
32 | #include <linux/fib_rules.h> | |
33 | #include "zebra/zserv.h" | |
34 | #include "zebra/zebra_ns.h" | |
35 | #include "zebra/zebra_vrf.h" | |
36 | #include "zebra/rt.h" | |
37 | #include "zebra/interface.h" | |
38 | #include "zebra/debug.h" | |
39 | #include "zebra/rtadv.h" | |
40 | #include "zebra/kernel_netlink.h" | |
41 | #include "zebra/rule_netlink.h" | |
42 | #include "zebra/zebra_pbr.h" | |
9df414fe | 43 | #include "zebra/zebra_errors.h" |
942bf97b | 44 | |
45 | /* definitions */ | |
46 | ||
47 | /* static function declarations */ | |
48 | ||
49 | /* Private functions */ | |
50 | ||
51 | /* Install or uninstall specified rule for a specific interface. | |
52 | * Form netlink message and ship it. Currently, notify status after | |
53 | * waiting for netlink status. | |
54 | */ | |
a0321978 | 55 | static int netlink_rule_update(int cmd, struct zebra_pbr_rule *rule) |
942bf97b | 56 | { |
f3dbec60 | 57 | uint8_t protocol = RTPROT_ZEBRA; |
942bf97b | 58 | int family; |
59 | int bytelen; | |
60 | struct { | |
61 | struct nlmsghdr n; | |
62 | struct fib_rule_hdr frh; | |
63 | char buf[NL_PKT_BUF_SIZE]; | |
64 | } req; | |
65 | struct zebra_ns *zns = zebra_ns_lookup(NS_DEFAULT); | |
66 | struct sockaddr_nl snl; | |
67 | char buf1[PREFIX_STRLEN]; | |
68 | char buf2[PREFIX_STRLEN]; | |
69 | ||
fd71d73e | 70 | memset(&req, 0, sizeof(req) - NL_PKT_BUF_SIZE); |
5dd0722d | 71 | family = PREFIX_FAMILY(&rule->rule.filter.src_ip); |
942bf97b | 72 | bytelen = (family == AF_INET ? 4 : 16); |
73 | ||
74 | req.n.nlmsg_type = cmd; | |
75 | req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); | |
76 | req.n.nlmsg_flags = NLM_F_REQUEST; | |
77 | req.n.nlmsg_pid = zns->netlink_cmd.snl.nl_pid; | |
78 | ||
79 | req.frh.family = family; | |
80 | req.frh.action = FR_ACT_TO_TBL; | |
81 | ||
f3dbec60 DS |
82 | addattr_l(&req.n, sizeof(req), |
83 | FRA_PROTOCOL, &protocol, sizeof(protocol)); | |
84 | ||
942bf97b | 85 | /* rule's pref # */ |
5dd0722d | 86 | addattr32(&req.n, sizeof(req), FRA_PRIORITY, rule->rule.priority); |
942bf97b | 87 | |
88 | /* interface on which applied */ | |
b19d55d0 SW |
89 | addattr_l(&req.n, sizeof(req), FRA_IFNAME, rule->ifname, |
90 | strlen(rule->ifname) + 1); | |
942bf97b | 91 | |
92 | /* source IP, if specified */ | |
93 | if (IS_RULE_FILTERING_ON_SRC_IP(rule)) { | |
5dd0722d | 94 | req.frh.src_len = rule->rule.filter.src_ip.prefixlen; |
942bf97b | 95 | addattr_l(&req.n, sizeof(req), FRA_SRC, |
5dd0722d | 96 | &rule->rule.filter.src_ip.u.prefix, bytelen); |
942bf97b | 97 | } |
98 | /* destination IP, if specified */ | |
99 | if (IS_RULE_FILTERING_ON_DST_IP(rule)) { | |
5dd0722d | 100 | req.frh.dst_len = rule->rule.filter.dst_ip.prefixlen; |
942bf97b | 101 | addattr_l(&req.n, sizeof(req), FRA_DST, |
5dd0722d | 102 | &rule->rule.filter.dst_ip.u.prefix, bytelen); |
942bf97b | 103 | } |
104 | ||
2bee7aae PG |
105 | /* fwmark, if specified */ |
106 | if (IS_RULE_FILTERING_ON_FWMARK(rule)) { | |
107 | addattr32(&req.n, sizeof(req), FRA_FWMARK, | |
108 | rule->rule.filter.fwmark); | |
109 | } | |
110 | ||
942bf97b | 111 | /* Route table to use to forward, if filter criteria matches. */ |
5dd0722d PG |
112 | if (rule->rule.action.table < 256) |
113 | req.frh.table = rule->rule.action.table; | |
942bf97b | 114 | else { |
115 | req.frh.table = RT_TABLE_UNSPEC; | |
116 | addattr32(&req.n, sizeof(req), FRA_TABLE, | |
5dd0722d | 117 | rule->rule.action.table); |
942bf97b | 118 | } |
119 | ||
120 | if (IS_ZEBRA_DEBUG_KERNEL) | |
fd71d73e | 121 | zlog_debug( |
15e6eed4 | 122 | "Tx %s family %s IF %s(%u) Pref %u Fwmark %u Src %s Dst %s Table %u", |
fd71d73e | 123 | nl_msg_type_to_str(cmd), nl_family_to_str(family), |
b19d55d0 | 124 | rule->ifname, rule->rule.ifindex, rule->rule.priority, |
15e6eed4 | 125 | rule->rule.filter.fwmark, |
5dd0722d PG |
126 | prefix2str(&rule->rule.filter.src_ip, buf1, |
127 | sizeof(buf1)), | |
128 | prefix2str(&rule->rule.filter.dst_ip, buf2, | |
129 | sizeof(buf2)), | |
130 | rule->rule.action.table); | |
942bf97b | 131 | |
132 | /* Ship off the message. | |
133 | * Note: Currently, netlink_talk() is a blocking call which returns | |
134 | * back the status. | |
135 | */ | |
136 | memset(&snl, 0, sizeof(snl)); | |
137 | snl.nl_family = AF_NETLINK; | |
138 | return netlink_talk(netlink_talk_filter, &req.n, | |
139 | &zns->netlink_cmd, zns, 0); | |
140 | } | |
141 | ||
142 | ||
143 | /* Public functions */ | |
144 | /* | |
145 | * Install specified rule for a specific interface. The preference is what | |
146 | * goes in the rule to denote relative ordering; it may or may not be the | |
147 | * same as the rule's user-defined sequence number. | |
148 | */ | |
ea1c14f6 | 149 | enum zebra_dplane_result kernel_add_pbr_rule(struct zebra_pbr_rule *rule) |
942bf97b | 150 | { |
151 | int ret = 0; | |
152 | ||
a0321978 DS |
153 | ret = netlink_rule_update(RTM_NEWRULE, rule); |
154 | kernel_pbr_rule_add_del_status(rule, | |
ea1c14f6 MS |
155 | (!ret) ? ZEBRA_DPLANE_INSTALL_SUCCESS |
156 | : ZEBRA_DPLANE_INSTALL_FAILURE); | |
ebecd649 | 157 | |
ea1c14f6 | 158 | return ZEBRA_DPLANE_REQUEST_SUCCESS; |
942bf97b | 159 | } |
160 | ||
161 | /* | |
162 | * Uninstall specified rule for a specific interface. | |
163 | */ | |
ea1c14f6 | 164 | enum zebra_dplane_result kernel_del_pbr_rule(struct zebra_pbr_rule *rule) |
942bf97b | 165 | { |
166 | int ret = 0; | |
167 | ||
a0321978 DS |
168 | ret = netlink_rule_update(RTM_DELRULE, rule); |
169 | kernel_pbr_rule_add_del_status(rule, | |
ea1c14f6 MS |
170 | (!ret) ? ZEBRA_DPLANE_DELETE_SUCCESS |
171 | : ZEBRA_DPLANE_DELETE_FAILURE); | |
ebecd649 | 172 | |
ea1c14f6 | 173 | return ZEBRA_DPLANE_REQUEST_SUCCESS; |
942bf97b | 174 | } |
175 | ||
176 | /* | |
177 | * Handle netlink notification informing a rule add or delete. | |
178 | * Handling of an ADD is TBD. | |
179 | * DELs are notified up, if other attributes indicate it may be a | |
180 | * notification of interest. The expectation is that if this corresponds | |
181 | * to a PBR rule added by FRR, it will be readded. | |
182 | */ | |
2414abd3 | 183 | int netlink_rule_change(struct nlmsghdr *h, ns_id_t ns_id, int startup) |
942bf97b | 184 | { |
185 | struct zebra_ns *zns; | |
186 | struct fib_rule_hdr *frh; | |
187 | struct rtattr *tb[FRA_MAX + 1]; | |
188 | int len; | |
189 | char *ifname; | |
cc42104c | 190 | struct zebra_pbr_rule rule = {}; |
942bf97b | 191 | char buf1[PREFIX_STRLEN]; |
192 | char buf2[PREFIX_STRLEN]; | |
193 | ||
194 | /* Basic validation followed by extracting attributes. */ | |
195 | if (h->nlmsg_type != RTM_NEWRULE && h->nlmsg_type != RTM_DELRULE) | |
196 | return 0; | |
197 | ||
198 | /* TBD */ | |
199 | if (h->nlmsg_type == RTM_NEWRULE) | |
200 | return 0; | |
201 | ||
202 | len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct fib_rule_hdr)); | |
9bdf8618 DS |
203 | if (len < 0) { |
204 | zlog_err("%s: Message received from netlink is of a broken size: %d %zu", | |
205 | __PRETTY_FUNCTION__, h->nlmsg_len, | |
206 | (size_t)NLMSG_LENGTH(sizeof(struct fib_rule_hdr))); | |
942bf97b | 207 | return -1; |
9bdf8618 | 208 | } |
942bf97b | 209 | |
210 | frh = NLMSG_DATA(h); | |
8a1b681c | 211 | if (frh->family != AF_INET && frh->family != AF_INET6) { |
9df414fe | 212 | flog_warn( |
e914ccbe | 213 | EC_ZEBRA_NETLINK_INVALID_AF, |
81227874 | 214 | "Invalid address family: %u received from kernel rule change: %u", |
8a1b681c | 215 | frh->family, h->nlmsg_type); |
942bf97b | 216 | return 0; |
8a1b681c | 217 | } |
942bf97b | 218 | if (frh->action != FR_ACT_TO_TBL) |
219 | return 0; | |
220 | ||
221 | memset(tb, 0, sizeof(tb)); | |
222 | netlink_parse_rtattr(tb, FRA_MAX, RTM_RTA(frh), len); | |
223 | ||
224 | /* TBD: We don't care about rules not specifying an IIF. */ | |
225 | if (tb[FRA_IFNAME] == NULL) | |
226 | return 0; | |
227 | ||
942bf97b | 228 | ifname = (char *)RTA_DATA(tb[FRA_IFNAME]); |
229 | zns = zebra_ns_lookup(ns_id); | |
b19d55d0 SW |
230 | |
231 | /* If we don't know the interface, we don't care. */ | |
232 | if (!if_lookup_by_name_per_ns(zns, ifname)) | |
942bf97b | 233 | return 0; |
234 | ||
b19d55d0 SW |
235 | strlcpy(rule.ifname, ifname, sizeof(rule.ifname)); |
236 | ||
942bf97b | 237 | if (tb[FRA_PRIORITY]) |
5dd0722d | 238 | rule.rule.priority = *(uint32_t *)RTA_DATA(tb[FRA_PRIORITY]); |
942bf97b | 239 | |
240 | if (tb[FRA_SRC]) { | |
241 | if (frh->family == AF_INET) | |
5dd0722d | 242 | memcpy(&rule.rule.filter.src_ip.u.prefix4, |
942bf97b | 243 | RTA_DATA(tb[FRA_SRC]), 4); |
244 | else | |
5dd0722d | 245 | memcpy(&rule.rule.filter.src_ip.u.prefix6, |
942bf97b | 246 | RTA_DATA(tb[FRA_SRC]), 16); |
5dd0722d PG |
247 | rule.rule.filter.src_ip.prefixlen = frh->src_len; |
248 | rule.rule.filter.filter_bm |= PBR_FILTER_SRC_IP; | |
942bf97b | 249 | } |
250 | ||
251 | if (tb[FRA_DST]) { | |
252 | if (frh->family == AF_INET) | |
5dd0722d | 253 | memcpy(&rule.rule.filter.dst_ip.u.prefix4, |
942bf97b | 254 | RTA_DATA(tb[FRA_DST]), 4); |
255 | else | |
5dd0722d | 256 | memcpy(&rule.rule.filter.dst_ip.u.prefix6, |
942bf97b | 257 | RTA_DATA(tb[FRA_DST]), 16); |
5dd0722d PG |
258 | rule.rule.filter.dst_ip.prefixlen = frh->dst_len; |
259 | rule.rule.filter.filter_bm |= PBR_FILTER_DST_IP; | |
942bf97b | 260 | } |
261 | ||
262 | if (tb[FRA_TABLE]) | |
5dd0722d | 263 | rule.rule.action.table = *(uint32_t *)RTA_DATA(tb[FRA_TABLE]); |
942bf97b | 264 | else |
5dd0722d | 265 | rule.rule.action.table = frh->table; |
942bf97b | 266 | |
267 | if (IS_ZEBRA_DEBUG_KERNEL) | |
fd71d73e DS |
268 | zlog_debug( |
269 | "Rx %s family %s IF %s(%u) Pref %u Src %s Dst %s Table %u", | |
270 | nl_msg_type_to_str(h->nlmsg_type), | |
b19d55d0 SW |
271 | nl_family_to_str(frh->family), rule.ifname, |
272 | rule.rule.ifindex, rule.rule.priority, | |
5dd0722d PG |
273 | prefix2str(&rule.rule.filter.src_ip, buf1, |
274 | sizeof(buf1)), | |
275 | prefix2str(&rule.rule.filter.dst_ip, buf2, | |
276 | sizeof(buf2)), | |
277 | rule.rule.action.table); | |
fd71d73e | 278 | |
a0321978 | 279 | return kernel_pbr_rule_del(&rule); |
942bf97b | 280 | } |
281 | ||
282 | /* | |
283 | * Get to know existing PBR rules in the kernel - typically called at startup. | |
284 | * TBD. | |
285 | */ | |
286 | int netlink_rules_read(struct zebra_ns *zns) | |
287 | { | |
288 | return 0; | |
289 | } | |
290 | ||
291 | #endif /* HAVE_NETLINK */ |