blob: a58f02dfbbcb20244b53811da6ca69c8cc48a17d [file] [log] [blame]
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -07001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "RouteController.h"
18
19#include "Fwmark.h"
20#include "NetdConstants.h"
21
Sreeram Ramachandran8fe9c8e2014-04-16 12:08:05 -070022#include <linux/rtnetlink.h>
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -070023#include <logwrap/logwrap.h>
24#include <net/if.h>
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -070025
26namespace {
27
Sreeram Ramachandran8fe9c8e2014-04-16 12:08:05 -070028const uint32_t RULE_PRIORITY_PER_NETWORK_EXPLICIT = 13000;
29const uint32_t RULE_PRIORITY_PER_NETWORK_INTERFACE = 14000;
30const uint32_t RULE_PRIORITY_PER_NETWORK_NORMAL = 17000;
31const uint32_t RULE_PRIORITY_DEFAULT_NETWORK = 19000;
32const uint32_t RULE_PRIORITY_MAIN = 20000;
33const uint32_t RULE_PRIORITY_UNREACHABLE = 21000;
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -070034
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -070035uint32_t getRouteTableForInterface(const char* interface) {
Sreeram Ramachandrana4811802014-04-10 12:10:24 -070036 uint32_t index = if_nametoindex(interface);
37 return index ? index + RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX : 0;
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -070038}
39
Sreeram Ramachandran8fe9c8e2014-04-16 12:08:05 -070040// Adds or removes a routing rule for IPv4 and IPv6.
41//
42// + If |table| is non-zero, the rule points at the specified routing table. Otherwise, the rule
43// returns ENETUNREACH.
44// + If |mask| is non-zero, the rule matches the specified fwmark and mask. Otherwise, |fwmark| is
45// ignored.
46// + If |interface| is non-NULL, the rule matches the specified outgoing interface.
47bool runIpRuleCommand(const char* action, uint32_t priority, uint32_t table, uint32_t fwmark,
48 uint32_t mask, const char* interface) {
Lorenzo Colittia10ac322014-04-11 18:26:17 +090049 char priorityString[UINT32_STRLEN];
Lorenzo Colittia10ac322014-04-11 18:26:17 +090050 snprintf(priorityString, sizeof(priorityString), "%u", priority);
Sreeram Ramachandran7619e1b2014-04-15 14:23:08 -070051
52 char tableString[UINT32_STRLEN];
Lorenzo Colittia10ac322014-04-11 18:26:17 +090053 snprintf(tableString, sizeof(tableString), "%u", table);
54
55 char fwmarkString[sizeof("0x12345678/0x12345678")];
56 snprintf(fwmarkString, sizeof(fwmarkString), "0x%x/0x%x", fwmark, mask);
57
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -070058 const char* version[] = {"-4", "-6"};
59 for (size_t i = 0; i < ARRAY_SIZE(version); ++i) {
60 int argc = 0;
61 const char* argv[16];
62
63 argv[argc++] = IP_PATH;
64 argv[argc++] = version[i];
65 argv[argc++] = "rule";
66 argv[argc++] = action;
67 argv[argc++] = "priority";
Lorenzo Colittia10ac322014-04-11 18:26:17 +090068 argv[argc++] = priorityString;
Sreeram Ramachandran8fe9c8e2014-04-16 12:08:05 -070069 if (table) {
70 argv[argc++] = "table";
71 argv[argc++] = tableString;
72 } else {
73 argv[argc++] = "unreachable";
74 }
Lorenzo Colittia10ac322014-04-11 18:26:17 +090075 if (mask) {
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -070076 argv[argc++] = "fwmark";
Lorenzo Colittia10ac322014-04-11 18:26:17 +090077 argv[argc++] = fwmarkString;
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -070078 }
Sreeram Ramachandran9c0d3132014-04-10 20:35:04 -070079 if (interface) {
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -070080 argv[argc++] = "oif";
Sreeram Ramachandran9c0d3132014-04-10 20:35:04 -070081 argv[argc++] = interface;
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -070082 }
83 if (android_fork_execvp(argc, const_cast<char**>(argv), NULL, false, false)) {
84 return false;
85 }
86 }
87
88 return true;
89}
90
Sreeram Ramachandran7619e1b2014-04-15 14:23:08 -070091bool runIpRouteCommand(const char* action, uint32_t table, const char* interface,
92 const char* destination, const char* nexthop) {
93 char tableString[UINT32_STRLEN];
94 snprintf(tableString, sizeof(tableString), "%u", table);
95
96 int argc = 0;
97 const char* argv[16];
98
99 argv[argc++] = IP_PATH;
100 argv[argc++] = "route";
101 argv[argc++] = action;
102 argv[argc++] = "table";
103 argv[argc++] = tableString;
104 if (destination) {
105 argv[argc++] = destination;
106 argv[argc++] = "dev";
107 argv[argc++] = interface;
108 if (nexthop) {
109 argv[argc++] = "via";
110 argv[argc++] = nexthop;
111 }
112 }
113
Sreeram Ramachandrana79f6182014-04-24 15:55:26 -0700114 return !android_fork_execvp(argc, const_cast<char**>(argv), NULL, false, false);
Sreeram Ramachandran7619e1b2014-04-15 14:23:08 -0700115}
116
Sreeram Ramachandran9c0d3132014-04-10 20:35:04 -0700117bool modifyPerNetworkRules(unsigned netId, const char* interface, Permission permission, bool add,
118 bool modifyIptables) {
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -0700119 uint32_t table = getRouteTableForInterface(interface);
120 if (!table) {
121 return false;
122 }
123
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -0700124 const char* action = add ? ADD : DEL;
125
Sreeram Ramachandran122f5812014-05-11 20:29:49 -0700126 Fwmark fwmark;
127 fwmark.permission = permission;
128
129 Fwmark mask;
130 mask.permission = permission;
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -0700131
132 // A rule to route traffic based on a chosen outgoing interface.
133 //
134 // Supports apps that use SO_BINDTODEVICE or IP_PKTINFO options and the kernel that already
135 // knows the outgoing interface (typically for link-local communications).
Sreeram Ramachandran122f5812014-05-11 20:29:49 -0700136 if (!runIpRuleCommand(action, RULE_PRIORITY_PER_NETWORK_INTERFACE, table, fwmark.intValue,
137 mask.intValue, interface)) {
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -0700138 return false;
139 }
140
141 // A rule to route traffic based on the chosen network.
142 //
143 // This is for sockets that have not explicitly requested a particular network, but have been
144 // bound to one when they called connect(). This ensures that sockets connected on a particular
145 // network stay on that network even if the default network changes.
Sreeram Ramachandran122f5812014-05-11 20:29:49 -0700146 fwmark.netId = netId;
147 mask.netId = FWMARK_NET_ID_MASK;
148 if (!runIpRuleCommand(action, RULE_PRIORITY_PER_NETWORK_NORMAL, table, fwmark.intValue,
149 mask.intValue, NULL)) {
150 return false;
151 }
152
153 // A rule to route traffic based on an explicitly chosen network.
154 //
155 // Supports apps that use the multinetwork APIs to restrict their traffic to a network.
156 //
157 // We don't really need to check the permission bits of the fwmark here, as they would've been
158 // checked at the time the netId was set into the fwmark, but we do so to be consistent.
159 fwmark.explicitlySelected = true;
160 mask.explicitlySelected = true;
161 if (!runIpRuleCommand(action, RULE_PRIORITY_PER_NETWORK_EXPLICIT, table, fwmark.intValue,
162 mask.intValue, NULL)) {
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -0700163 return false;
164 }
165
166 // An iptables rule to mark incoming packets on a network with the netId of the network.
167 //
168 // This is so that the kernel can:
169 // + Use the right fwmark for (and thus correctly route) replies (e.g.: TCP RST, ICMP errors,
Sreeram Ramachandrana4811802014-04-10 12:10:24 -0700170 // ping replies).
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -0700171 // + Mark sockets that accept connections from this interface so that the connection stays on
172 // the same interface.
Sreeram Ramachandran379bd332014-04-10 19:58:06 -0700173 if (modifyIptables) {
174 action = add ? "-A" : "-D";
175 char markString[UINT32_HEX_STRLEN];
176 snprintf(markString, sizeof(markString), "0x%x", netId);
177 if (execIptables(V4V6, "-t", "mangle", action, "INPUT", "-i", interface, "-j", "MARK",
178 "--set-mark", markString, NULL)) {
179 return false;
180 }
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -0700181 }
182
183 return true;
184}
185
Sreeram Ramachandran9c0d3132014-04-10 20:35:04 -0700186bool modifyDefaultNetworkRules(const char* interface, Permission permission, const char* action) {
187 uint32_t table = getRouteTableForInterface(interface);
188 if (!table) {
189 return false;
190 }
191
Sreeram Ramachandran122f5812014-05-11 20:29:49 -0700192 Fwmark fwmark;
193 fwmark.netId = 0;
194 fwmark.permission = permission;
Sreeram Ramachandran9c0d3132014-04-10 20:35:04 -0700195
Sreeram Ramachandran122f5812014-05-11 20:29:49 -0700196 Fwmark mask;
197 mask.netId = FWMARK_NET_ID_MASK;
198 mask.permission = permission;
199
200 return runIpRuleCommand(action, RULE_PRIORITY_DEFAULT_NETWORK, table, fwmark.intValue,
201 mask.intValue, NULL);
Sreeram Ramachandran7619e1b2014-04-15 14:23:08 -0700202}
203
Sreeram Ramachandranc9213372014-04-16 12:32:18 -0700204bool modifyRoute(const char* interface, const char* destination, const char* nexthop,
205 const char* action) {
Sreeram Ramachandran7619e1b2014-04-15 14:23:08 -0700206 uint32_t table = getRouteTableForInterface(interface);
207 if (!table) {
Sreeram Ramachandran9c0d3132014-04-10 20:35:04 -0700208 return false;
209 }
210
Sreeram Ramachandranc9213372014-04-16 12:32:18 -0700211 if (!runIpRouteCommand(action, table, interface, destination, nexthop)) {
212 return false;
213 }
214
215 // If there's no nexthop, this is a directly connected route. Add it to the main table also, to
216 // let the kernel find it when validating nexthops when global routes are added. Don't do this
217 // for IPv6, since all directly-connected routes in v6 are link-local and should already be in
218 // the main table.
Sreeram Ramachandranfd6424f2014-04-24 16:23:35 -0700219 // TODO: A failure here typically means that the route already exists in the main table, so we
220 // ignore it. It's wrong to ignore other kinds of failures, but we have no way to distinguish
221 // them based on the return status of the 'ip' command. Fix this situation by ignoring errors
222 // only when action == ADD && error == EEXIST.
223 if (!nexthop && !strchr(destination, ':')) {
224 runIpRouteCommand(action, RT_TABLE_MAIN, interface, destination, NULL);
Sreeram Ramachandranc9213372014-04-16 12:32:18 -0700225 }
226
227 return true;
Sreeram Ramachandran9c0d3132014-04-10 20:35:04 -0700228}
229
Sreeram Ramachandran92b66c42014-04-15 14:28:55 -0700230bool flushRoutes(const char* interface) {
231 uint32_t table = getRouteTableForInterface(interface);
232 if (!table) {
233 return false;
234 }
235
236 return runIpRouteCommand("flush", table, NULL, NULL, NULL);
237}
238
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -0700239} // namespace
240
Sreeram Ramachandran8fe9c8e2014-04-16 12:08:05 -0700241void RouteController::Init() {
242 // Add a new rule to look up the 'main' table, with the same selectors as the "default network"
243 // rule, but with a lower priority. Since the default network rule points to a table with a
244 // default route, the rule we're adding will never be used for normal routing lookups. However,
245 // the kernel may fall-through to it to find directly-connected routes when it validates that a
246 // nexthop (in a route being added) is reachable.
Sreeram Ramachandran122f5812014-05-11 20:29:49 -0700247 Fwmark fwmark;
248 fwmark.netId = 0;
249
250 Fwmark mask;
251 mask.netId = FWMARK_NET_ID_MASK;
252
253 runIpRuleCommand(ADD, RULE_PRIORITY_MAIN, RT_TABLE_MAIN, fwmark.intValue, mask.intValue, NULL);
Sreeram Ramachandran8fe9c8e2014-04-16 12:08:05 -0700254
255// TODO: Uncomment once we are sure everything works.
256#if 0
257 // Add a rule to preempt the pre-defined "from all lookup main" rule. This ensures that packets
258 // that are already marked with a specific NetId don't fall-through to the main table.
259 runIpRuleCommand(ADD, RULE_PRIORITY_UNREACHABLE, 0, 0, 0, NULL);
260#endif
261}
262
Paul Jensenae37e8a2014-04-28 10:35:51 -0400263bool RouteController::addInterfaceToNetwork(unsigned netId, const char* interface,
264 Permission permission) {
Sreeram Ramachandran9c0d3132014-04-10 20:35:04 -0700265 return modifyPerNetworkRules(netId, interface, permission, true, true);
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -0700266}
267
Paul Jensenae37e8a2014-04-28 10:35:51 -0400268bool RouteController::removeInterfaceFromNetwork(unsigned netId, const char* interface,
269 Permission permission) {
Sreeram Ramachandran92b66c42014-04-15 14:28:55 -0700270 return modifyPerNetworkRules(netId, interface, permission, false, true) &&
271 flushRoutes(interface);
Sreeram Ramachandran379bd332014-04-10 19:58:06 -0700272}
273
274bool RouteController::modifyNetworkPermission(unsigned netId, const char* interface,
275 Permission oldPermission, Permission newPermission) {
276 // Add the new rules before deleting the old ones, to avoid race conditions.
Sreeram Ramachandran9c0d3132014-04-10 20:35:04 -0700277 return modifyPerNetworkRules(netId, interface, newPermission, true, false) &&
278 modifyPerNetworkRules(netId, interface, oldPermission, false, false);
279}
280
281bool RouteController::addDefaultNetwork(const char* interface, Permission permission) {
282 return modifyDefaultNetworkRules(interface, permission, ADD);
283}
284
285bool RouteController::removeDefaultNetwork(const char* interface, Permission permission) {
286 return modifyDefaultNetworkRules(interface, permission, DEL);
Sreeram Ramachandran5c181bf2014-04-07 14:10:04 -0700287}
Sreeram Ramachandran7619e1b2014-04-15 14:23:08 -0700288
289bool RouteController::addRoute(const char* interface, const char* destination,
290 const char* nexthop) {
Sreeram Ramachandranc9213372014-04-16 12:32:18 -0700291 return modifyRoute(interface, destination, nexthop, ADD);
Sreeram Ramachandran7619e1b2014-04-15 14:23:08 -0700292}
293
294bool RouteController::removeRoute(const char* interface, const char* destination,
295 const char* nexthop) {
Sreeram Ramachandranc9213372014-04-16 12:32:18 -0700296 return modifyRoute(interface, destination, nexthop, DEL);
Sreeram Ramachandran7619e1b2014-04-15 14:23:08 -0700297}