RouteController.cpp revision a561e121c724e9163b2e256e15eef660e3a326da
1/*
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
22#include <linux/rtnetlink.h>
23#include <logwrap/logwrap.h>
24#include <map>
25#include <net/if.h>
26
27namespace {
28
29const uint32_t RULE_PRIORITY_PRIVILEGED_LEGACY     = 11000;
30const uint32_t RULE_PRIORITY_PER_NETWORK_EXPLICIT  = 13000;
31const uint32_t RULE_PRIORITY_PER_NETWORK_INTERFACE = 14000;
32const uint32_t RULE_PRIORITY_LEGACY                = 16000;
33const uint32_t RULE_PRIORITY_PER_NETWORK_NORMAL    = 17000;
34const uint32_t RULE_PRIORITY_DEFAULT_NETWORK       = 19000;
35const uint32_t RULE_PRIORITY_MAIN                  = 20000;
36// TODO: Uncomment once we are sure everything works.
37#if 0
38const uint32_t RULE_PRIORITY_UNREACHABLE           = 21000;
39#endif
40
41// TODO: These should be turned into per-UID tables once the kernel supports UID-based routing.
42const int ROUTE_TABLE_PRIVILEGED_LEGACY = RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX - 901;
43const int ROUTE_TABLE_LEGACY            = RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX - 902;
44
45std::map<std::string, uint32_t> interfaceToIndex;
46
47uint32_t getRouteTableForInterface(const char* interface) {
48    uint32_t index = if_nametoindex(interface);
49    if (index) {
50        interfaceToIndex[interface] = index;
51    } else {
52        // If the interface goes away if_nametoindex() will return 0 but we still need to know
53        // the index so we can remove the rules and routes.
54        std::map<std::string, uint32_t>::iterator it = interfaceToIndex.find(interface);
55        if (it != interfaceToIndex.end())
56            index = it->second;
57    }
58    return index ? index + RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX : 0;
59}
60
61// Adds or removes a routing rule for IPv4 and IPv6.
62//
63// + If |table| is non-zero, the rule points at the specified routing table. Otherwise, the rule
64//   returns ENETUNREACH.
65// + If |mask| is non-zero, the rule matches the specified fwmark and mask. Otherwise, |fwmark| is
66//   ignored.
67// + If |interface| is non-NULL, the rule matches the specified outgoing interface.
68bool runIpRuleCommand(const char* action, uint32_t priority, uint32_t table, uint32_t fwmark,
69                      uint32_t mask, const char* interface) {
70    char priorityString[UINT32_STRLEN];
71    snprintf(priorityString, sizeof(priorityString), "%u", priority);
72
73    char tableString[UINT32_STRLEN];
74    snprintf(tableString, sizeof(tableString), "%u", table);
75
76    char fwmarkString[sizeof("0x12345678/0x12345678")];
77    snprintf(fwmarkString, sizeof(fwmarkString), "0x%x/0x%x", fwmark, mask);
78
79    const char* version[] = {"-4", "-6"};
80    for (size_t i = 0; i < ARRAY_SIZE(version); ++i) {
81        int argc = 0;
82        const char* argv[16];
83
84        argv[argc++] = IP_PATH;
85        argv[argc++] = version[i];
86        argv[argc++] = "rule";
87        argv[argc++] = action;
88        argv[argc++] = "priority";
89        argv[argc++] = priorityString;
90        if (table) {
91            argv[argc++] = "table";
92            argv[argc++] = tableString;
93        } else {
94            argv[argc++] = "unreachable";
95        }
96        if (mask) {
97            argv[argc++] = "fwmark";
98            argv[argc++] = fwmarkString;
99        }
100        if (interface) {
101            argv[argc++] = "oif";
102            argv[argc++] = interface;
103        }
104        if (android_fork_execvp(argc, const_cast<char**>(argv), NULL, false, false)) {
105            return false;
106        }
107    }
108
109    return true;
110}
111
112bool runIpRouteCommand(const char* action, uint32_t table, const char* interface,
113                       const char* destination, const char* nexthop) {
114    char tableString[UINT32_STRLEN];
115    snprintf(tableString, sizeof(tableString), "%u", table);
116
117    int argc = 0;
118    const char* argv[16];
119
120    argv[argc++] = IP_PATH;
121    argv[argc++] = "route";
122    argv[argc++] = action;
123    argv[argc++] = "table";
124    argv[argc++] = tableString;
125    if (destination) {
126        argv[argc++] = destination;
127        argv[argc++] = "dev";
128        argv[argc++] = interface;
129        if (nexthop) {
130            argv[argc++] = "via";
131            argv[argc++] = nexthop;
132        }
133    }
134
135    return !android_fork_execvp(argc, const_cast<char**>(argv), NULL, false, false);
136}
137
138bool modifyPerNetworkRules(unsigned netId, const char* interface, Permission permission, bool add,
139                           bool modifyIptables) {
140    uint32_t table = getRouteTableForInterface(interface);
141    if (!table) {
142        return false;
143    }
144
145    const char* action = add ? ADD : DEL;
146
147    Fwmark fwmark;
148    fwmark.permission = permission;
149
150    Fwmark mask;
151    mask.permission = permission;
152
153    // A rule to route traffic based on a chosen outgoing interface.
154    //
155    // Supports apps that use SO_BINDTODEVICE or IP_PKTINFO options and the kernel that already
156    // knows the outgoing interface (typically for link-local communications).
157    if (!runIpRuleCommand(action, RULE_PRIORITY_PER_NETWORK_INTERFACE, table, fwmark.intValue,
158                          mask.intValue, interface)) {
159        return false;
160    }
161
162    // A rule to route traffic based on the chosen network.
163    //
164    // This is for sockets that have not explicitly requested a particular network, but have been
165    // bound to one when they called connect(). This ensures that sockets connected on a particular
166    // network stay on that network even if the default network changes.
167    fwmark.netId = netId;
168    mask.netId = FWMARK_NET_ID_MASK;
169    if (!runIpRuleCommand(action, RULE_PRIORITY_PER_NETWORK_NORMAL, table, fwmark.intValue,
170                          mask.intValue, NULL)) {
171        return false;
172    }
173
174    // A rule to route traffic based on an explicitly chosen network.
175    //
176    // Supports apps that use the multinetwork APIs to restrict their traffic to a network.
177    //
178    // We don't really need to check the permission bits of the fwmark here, as they would've been
179    // checked at the time the netId was set into the fwmark, but we do so to be consistent.
180    fwmark.explicitlySelected = true;
181    mask.explicitlySelected = true;
182    if (!runIpRuleCommand(action, RULE_PRIORITY_PER_NETWORK_EXPLICIT, table, fwmark.intValue,
183                          mask.intValue, NULL)) {
184        return false;
185    }
186
187    // An iptables rule to mark incoming packets on a network with the netId of the network.
188    //
189    // This is so that the kernel can:
190    // + Use the right fwmark for (and thus correctly route) replies (e.g.: TCP RST, ICMP errors,
191    //   ping replies).
192    // + Mark sockets that accept connections from this interface so that the connection stays on
193    //   the same interface.
194    if (modifyIptables) {
195        action = add ? "-A" : "-D";
196        char markString[UINT32_HEX_STRLEN];
197        snprintf(markString, sizeof(markString), "0x%x", netId);
198        if (execIptables(V4V6, "-t", "mangle", action, "INPUT", "-i", interface, "-j", "MARK",
199                         "--set-mark", markString, NULL)) {
200            return false;
201        }
202    }
203
204    return true;
205}
206
207bool modifyDefaultNetworkRules(const char* interface, Permission permission, const char* action) {
208    uint32_t table = getRouteTableForInterface(interface);
209    if (!table) {
210        return false;
211    }
212
213    Fwmark fwmark;
214    fwmark.netId = 0;
215    fwmark.permission = permission;
216
217    Fwmark mask;
218    mask.netId = FWMARK_NET_ID_MASK;
219    mask.permission = permission;
220
221    return runIpRuleCommand(action, RULE_PRIORITY_DEFAULT_NETWORK, table, fwmark.intValue,
222                            mask.intValue, NULL);
223}
224
225bool modifyRoute(const char* interface, const char* destination, const char* nexthop,
226                 const char* action, RouteController::TableType tableType, unsigned /* uid */) {
227    uint32_t table = 0;
228    switch (tableType) {
229        case RouteController::INTERFACE: {
230            table = getRouteTableForInterface(interface);
231            break;
232        }
233        case RouteController::LEGACY: {
234            // TODO: Use the UID to assign a unique table per UID instead of this fixed table.
235            table = ROUTE_TABLE_LEGACY;
236            break;
237        }
238        case RouteController::PRIVILEGED_LEGACY: {
239            // TODO: Use the UID to assign a unique table per UID instead of this fixed table.
240            table = ROUTE_TABLE_PRIVILEGED_LEGACY;
241            break;
242        }
243    }
244    if (!table) {
245        return false;
246    }
247
248    if (!runIpRouteCommand(action, table, interface, destination, nexthop)) {
249        return false;
250    }
251
252    // If there's no nexthop, this is a directly connected route. Add it to the main table also, to
253    // let the kernel find it when validating nexthops when global routes are added. Don't do this
254    // for IPv6, since all directly-connected routes in v6 are link-local and should already be in
255    // the main table.
256    // TODO: A failure here typically means that the route already exists in the main table, so we
257    // ignore it. It's wrong to ignore other kinds of failures, but we have no way to distinguish
258    // them based on the return status of the 'ip' command. Fix this situation by ignoring errors
259    // only when action == ADD && error == EEXIST.
260    if (!nexthop && !strchr(destination, ':')) {
261        runIpRouteCommand(action, RT_TABLE_MAIN, interface, destination, NULL);
262    }
263
264    return true;
265}
266
267bool flushRoutes(const char* interface) {
268    uint32_t table = getRouteTableForInterface(interface);
269    if (!table) {
270        return false;
271    }
272    interfaceToIndex.erase(interface);
273
274    return runIpRouteCommand("flush", table, NULL, NULL, NULL);
275}
276
277}  // namespace
278
279void RouteController::Init() {
280    // Add a new rule to look up the 'main' table, with the same selectors as the "default network"
281    // rule, but with a lower priority. Since the default network rule points to a table with a
282    // default route, the rule we're adding will never be used for normal routing lookups. However,
283    // the kernel may fall-through to it to find directly-connected routes when it validates that a
284    // nexthop (in a route being added) is reachable.
285    Fwmark fwmark;
286    fwmark.netId = 0;
287
288    Fwmark mask;
289    mask.netId = FWMARK_NET_ID_MASK;
290
291    runIpRuleCommand(ADD, RULE_PRIORITY_MAIN, RT_TABLE_MAIN, fwmark.intValue, mask.intValue, NULL);
292
293    // Add rules to allow lookup of legacy routes.
294    //
295    // TODO: Remove these once the kernel supports UID-based routing. Instead, add them on demand
296    // when routes are added.
297    fwmark.netId = 0;
298    mask.netId = 0;
299
300    fwmark.explicitlySelected = false;
301    mask.explicitlySelected = true;
302
303    runIpRuleCommand(ADD, RULE_PRIORITY_LEGACY, ROUTE_TABLE_LEGACY, fwmark.intValue, mask.intValue,
304                     NULL);
305
306    fwmark.permission = PERMISSION_CONNECTIVITY_INTERNAL;
307    mask.permission = PERMISSION_CONNECTIVITY_INTERNAL;
308
309    runIpRuleCommand(ADD, RULE_PRIORITY_PRIVILEGED_LEGACY, ROUTE_TABLE_PRIVILEGED_LEGACY,
310                     fwmark.intValue, mask.intValue, NULL);
311
312// TODO: Uncomment once we are sure everything works.
313#if 0
314    // Add a rule to preempt the pre-defined "from all lookup main" rule. This ensures that packets
315    // that are already marked with a specific NetId don't fall-through to the main table.
316    runIpRuleCommand(ADD, RULE_PRIORITY_UNREACHABLE, 0, 0, 0, NULL);
317#endif
318}
319
320bool RouteController::addInterfaceToNetwork(unsigned netId, const char* interface,
321                                            Permission permission) {
322    return modifyPerNetworkRules(netId, interface, permission, true, true);
323}
324
325bool RouteController::removeInterfaceFromNetwork(unsigned netId, const char* interface,
326                                                 Permission permission) {
327    return modifyPerNetworkRules(netId, interface, permission, false, true) &&
328           flushRoutes(interface);
329}
330
331bool RouteController::modifyNetworkPermission(unsigned netId, const char* interface,
332                                              Permission oldPermission, Permission newPermission) {
333    // Add the new rules before deleting the old ones, to avoid race conditions.
334    return modifyPerNetworkRules(netId, interface, newPermission, true, false) &&
335           modifyPerNetworkRules(netId, interface, oldPermission, false, false);
336}
337
338bool RouteController::addToDefaultNetwork(const char* interface, Permission permission) {
339    return modifyDefaultNetworkRules(interface, permission, ADD);
340}
341
342bool RouteController::removeFromDefaultNetwork(const char* interface, Permission permission) {
343    return modifyDefaultNetworkRules(interface, permission, DEL);
344}
345
346bool RouteController::addRoute(const char* interface, const char* destination,
347                               const char* nexthop, TableType tableType, unsigned uid) {
348    return modifyRoute(interface, destination, nexthop, ADD, tableType, uid);
349}
350
351bool RouteController::removeRoute(const char* interface, const char* destination,
352                                  const char* nexthop, TableType tableType, unsigned uid) {
353    return modifyRoute(interface, destination, nexthop, DEL, tableType, uid);
354}
355