blob: 0a4d10819ad892877a83fdb4bccaec1d7335c18f [file] [log] [blame]
Robert Greenwaltfc97b822011-11-02 16:48:36 -07001/*
2 * Copyright (C) 2008 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 <stdlib.h>
18#include <errno.h>
19#include <fcntl.h>
20#include <string.h>
21
22#include <sys/socket.h>
23#include <sys/stat.h>
24#include <sys/types.h>
25#include <sys/wait.h>
26
27#include <netinet/in.h>
28#include <arpa/inet.h>
29
30#define LOG_TAG "SecondaryTablController"
31#include <cutils/log.h>
32#include <cutils/properties.h>
Rom Lemarchand001f0a42013-01-31 12:41:03 -080033#include <logwrap/logwrap.h>
JP Abgrall9e5e0ce2011-12-14 15:20:59 -080034
Robert Greenwaltfc97b822011-11-02 16:48:36 -070035#include "ResponseCode.h"
Robert Greenwaltc4621772012-01-31 12:46:45 -080036#include "NetdConstants.h"
Robert Greenwaltfc97b822011-11-02 16:48:36 -070037#include "SecondaryTableController.h"
38
Chad Brubaker9a508892013-05-31 20:51:46 -070039const char* SecondaryTableController::LOCAL_MANGLE_OUTPUT = "st_mangle_OUTPUT";
Chad Brubaker2251c0f2013-06-27 17:20:39 -070040const char* SecondaryTableController::LOCAL_MANGLE_IFACE_FORMAT = "st_mangle_%s_OUTPUT";
Chad Brubaker7a6ce4b2013-06-06 21:42:53 -070041const char* SecondaryTableController::LOCAL_NAT_POSTROUTING = "st_nat_POSTROUTING";
Chad Brubaker2251c0f2013-06-27 17:20:39 -070042const char* SecondaryTableController::LOCAL_FILTER_OUTPUT = "st_filter_OUTPUT";
Chad Brubaker9a508892013-05-31 20:51:46 -070043
Chad Brubakerd2617932013-06-21 15:26:35 -070044SecondaryTableController::SecondaryTableController(UidMarkMap *map) : mUidMarkMap(map) {
Robert Greenwaltfc97b822011-11-02 16:48:36 -070045 int i;
46 for (i=0; i < INTERFACES_TRACKED; i++) {
47 mInterfaceTable[i][0] = 0;
48 // TODO - use a hashtable or other prebuilt container class
49 mInterfaceRuleCount[i] = 0;
50 }
51}
52
53SecondaryTableController::~SecondaryTableController() {
54}
55
Chad Brubaker2251c0f2013-06-27 17:20:39 -070056int SecondaryTableController::setupIptablesHooks() {
57 int res = execIptables(V4V6,
58 "-t",
59 "mangle",
60 "-F",
61 LOCAL_MANGLE_OUTPUT,
62 NULL);
63 //rule for skipping anything marked with the PROTECT_MARK
64 char protect_mark_str[11];
65 snprintf(protect_mark_str, sizeof(protect_mark_str), "%d", PROTECT_MARK);
66 res |= execIptables(V4V6,
67 "-t",
68 "mangle",
69 "-A",
70 LOCAL_MANGLE_OUTPUT,
71 "-m",
72 "mark",
73 "--mark",
74 protect_mark_str,
75 "-j",
76 "RETURN",
77 NULL);
78
79 //protect the legacy VPN daemons from routes.
80 //TODO: Remove this when legacy VPN's are removed.
81 res |= execIptables(V4V6,
82 "-t",
83 "mangle",
84 "-A",
85 LOCAL_MANGLE_OUTPUT,
86 "-m",
87 "owner",
88 "--uid-owner",
89 "vpn",
90 "-j",
91 "RETURN",
92 NULL);
93 return res;
94
95}
96
Robert Greenwaltfc97b822011-11-02 16:48:36 -070097int SecondaryTableController::findTableNumber(const char *iface) {
98 int i;
99 for (i = 0; i < INTERFACES_TRACKED; i++) {
Jaime A Lopez-Sollano3c207872012-01-11 16:29:28 -0800100 // compare through the final null, hence +1
101 if (strncmp(iface, mInterfaceTable[i], IFNAMSIZ + 1) == 0) {
Robert Greenwaltfc97b822011-11-02 16:48:36 -0700102 return i;
103 }
104 }
105 return -1;
106}
107
108int SecondaryTableController::addRoute(SocketClient *cli, char *iface, char *dest, int prefix,
109 char *gateway) {
Robert Greenwaltfc97b822011-11-02 16:48:36 -0700110 int tableIndex = findTableNumber(iface);
111 if (tableIndex == -1) {
112 tableIndex = findTableNumber(""); // look for an empty slot
113 if (tableIndex == -1) {
Steve Block5ea0c052012-01-06 19:18:11 +0000114 ALOGE("Max number of NATed interfaces reached");
Robert Greenwaltfc97b822011-11-02 16:48:36 -0700115 errno = ENODEV;
116 cli->sendMsg(ResponseCode::OperationFailed, "Max number NATed", true);
117 return -1;
118 }
Jaime A Lopez-Sollanod14fd4f2012-01-11 16:29:28 -0800119 strncpy(mInterfaceTable[tableIndex], iface, IFNAMSIZ);
120 // Ensure null termination even if truncation happened
121 mInterfaceTable[tableIndex][IFNAMSIZ] = 0;
Robert Greenwaltfc97b822011-11-02 16:48:36 -0700122 }
123
Robert Greenwalt063af322011-11-18 15:32:13 -0800124 return modifyRoute(cli, ADD, iface, dest, prefix, gateway, tableIndex);
125}
126
Robert Greenwaltc4621772012-01-31 12:46:45 -0800127int SecondaryTableController::modifyRoute(SocketClient *cli, const char *action, char *iface,
128 char *dest, int prefix, char *gateway, int tableIndex) {
Rom Lemarchand001f0a42013-01-31 12:41:03 -0800129 char dest_str[44]; // enough to store an IPv6 address + 3 character bitmask
130 char tableIndex_str[11];
131 int ret;
132
133 // IP tool doesn't like "::" - the equiv of 0.0.0.0 that it accepts for ipv4
134 snprintf(dest_str, sizeof(dest_str), "%s/%d", dest, prefix);
135 snprintf(tableIndex_str, sizeof(tableIndex_str), "%d", tableIndex + BASE_TABLE_NUMBER);
Robert Greenwalt063af322011-11-18 15:32:13 -0800136
137 if (strcmp("::", gateway) == 0) {
Rom Lemarchand001f0a42013-01-31 12:41:03 -0800138 const char *cmd[] = {
139 IP_PATH,
140 "route",
141 action,
142 dest_str,
143 "dev",
144 iface,
145 "table",
146 tableIndex_str
147 };
148 ret = runCmd(ARRAY_SIZE(cmd), cmd);
Robert Greenwalt063af322011-11-18 15:32:13 -0800149 } else {
Rom Lemarchand001f0a42013-01-31 12:41:03 -0800150 const char *cmd[] = {
151 IP_PATH,
152 "route",
153 action,
154 dest_str,
155 "via",
156 gateway,
157 "dev",
158 iface,
159 "table",
160 tableIndex_str
161 };
162 ret = runCmd(ARRAY_SIZE(cmd), cmd);
Robert Greenwalt063af322011-11-18 15:32:13 -0800163 }
164
Rom Lemarchand001f0a42013-01-31 12:41:03 -0800165 if (ret) {
Steve Block5ea0c052012-01-06 19:18:11 +0000166 ALOGE("ip route %s failed: %s route %s %s/%d via %s dev %s table %d", action,
Robert Greenwalt063af322011-11-18 15:32:13 -0800167 IP_PATH, action, dest, prefix, gateway, iface, tableIndex+BASE_TABLE_NUMBER);
Robert Greenwaltfc97b822011-11-02 16:48:36 -0700168 errno = ENODEV;
Robert Greenwalt063af322011-11-18 15:32:13 -0800169 cli->sendMsg(ResponseCode::OperationFailed, "ip route modification failed", true);
Robert Greenwaltfc97b822011-11-02 16:48:36 -0700170 return -1;
171 }
Robert Greenwalt063af322011-11-18 15:32:13 -0800172
173 if (strcmp(action, ADD) == 0) {
174 mInterfaceRuleCount[tableIndex]++;
175 } else {
176 if (--mInterfaceRuleCount[tableIndex] < 1) {
177 mInterfaceRuleCount[tableIndex] = 0;
178 mInterfaceTable[tableIndex][0] = 0;
179 }
180 }
Robert Greenwaltc4621772012-01-31 12:46:45 -0800181 modifyRuleCount(tableIndex, action);
Robert Greenwalt063af322011-11-18 15:32:13 -0800182 cli->sendMsg(ResponseCode::CommandOkay, "Route modified", false);
Robert Greenwaltfc97b822011-11-02 16:48:36 -0700183 return 0;
184}
185
Robert Greenwaltc4621772012-01-31 12:46:45 -0800186void SecondaryTableController::modifyRuleCount(int tableIndex, const char *action) {
187 if (strcmp(action, ADD) == 0) {
188 mInterfaceRuleCount[tableIndex]++;
189 } else {
190 if (--mInterfaceRuleCount[tableIndex] < 1) {
191 mInterfaceRuleCount[tableIndex] = 0;
192 mInterfaceTable[tableIndex][0] = 0;
193 }
194 }
195}
196
197int SecondaryTableController::verifyTableIndex(int tableIndex) {
198 if ((tableIndex < 0) ||
199 (tableIndex >= INTERFACES_TRACKED) ||
200 (mInterfaceTable[tableIndex][0] == 0)) {
201 return -1;
202 } else {
203 return 0;
204 }
205}
206
207const char *SecondaryTableController::getVersion(const char *addr) {
208 if (strchr(addr, ':') != NULL) {
209 return "-6";
210 } else {
211 return "-4";
212 }
213}
214
Chad Brubaker2251c0f2013-06-27 17:20:39 -0700215IptablesTarget SecondaryTableController::getIptablesTarget(const char *addr) {
216 if (strchr(addr, ':') != NULL) {
217 return V6;
218 } else {
219 return V4;
220 }
221}
222
Robert Greenwaltfc97b822011-11-02 16:48:36 -0700223int SecondaryTableController::removeRoute(SocketClient *cli, char *iface, char *dest, int prefix,
224 char *gateway) {
Robert Greenwaltfc97b822011-11-02 16:48:36 -0700225 int tableIndex = findTableNumber(iface);
226 if (tableIndex == -1) {
Steve Block5ea0c052012-01-06 19:18:11 +0000227 ALOGE("Interface not found");
Robert Greenwaltfc97b822011-11-02 16:48:36 -0700228 errno = ENODEV;
229 cli->sendMsg(ResponseCode::OperationFailed, "Interface not found", true);
230 return -1;
231 }
232
Robert Greenwalt063af322011-11-18 15:32:13 -0800233 return modifyRoute(cli, DEL, iface, dest, prefix, gateway, tableIndex);
Robert Greenwaltfc97b822011-11-02 16:48:36 -0700234}
235
Robert Greenwaltc4621772012-01-31 12:46:45 -0800236int SecondaryTableController::modifyFromRule(int tableIndex, const char *action,
237 const char *addr) {
Rom Lemarchand001f0a42013-01-31 12:41:03 -0800238 char tableIndex_str[11];
Robert Greenwaltc4621772012-01-31 12:46:45 -0800239
240 if (verifyTableIndex(tableIndex)) {
241 return -1;
242 }
Rom Lemarchand001f0a42013-01-31 12:41:03 -0800243
244 snprintf(tableIndex_str, sizeof(tableIndex_str), "%d", tableIndex +
245 BASE_TABLE_NUMBER);
246 const char *cmd[] = {
247 IP_PATH,
248 getVersion(addr),
249 "rule",
250 action,
251 "from",
252 addr,
253 "table",
254 tableIndex_str
255 };
256 if (runCmd(ARRAY_SIZE(cmd), cmd)) {
Robert Greenwaltc4621772012-01-31 12:46:45 -0800257 return -1;
258 }
259
260 modifyRuleCount(tableIndex, action);
261 return 0;
262}
263
264int SecondaryTableController::modifyLocalRoute(int tableIndex, const char *action,
265 const char *iface, const char *addr) {
Rom Lemarchand001f0a42013-01-31 12:41:03 -0800266 char tableIndex_str[11];
Robert Greenwaltc4621772012-01-31 12:46:45 -0800267
268 if (verifyTableIndex(tableIndex)) {
269 return -1;
270 }
271
272 modifyRuleCount(tableIndex, action); // some del's will fail as the iface is already gone.
273
Rom Lemarchand001f0a42013-01-31 12:41:03 -0800274 snprintf(tableIndex_str, sizeof(tableIndex_str), "%d", tableIndex +
275 BASE_TABLE_NUMBER);
276 const char *cmd[] = {
277 IP_PATH,
278 "route",
279 action,
280 addr,
281 "dev",
282 iface,
283 "table",
284 tableIndex_str
285 };
286
287 return runCmd(ARRAY_SIZE(cmd), cmd);
Robert Greenwaltc4621772012-01-31 12:46:45 -0800288}
Chad Brubaker7a6ce4b2013-06-06 21:42:53 -0700289int SecondaryTableController::addFwmarkRule(const char *iface) {
290 return setFwmarkRule(iface, true);
291}
292
293int SecondaryTableController::removeFwmarkRule(const char *iface) {
294 return setFwmarkRule(iface, false);
295}
296
297int SecondaryTableController::setFwmarkRule(const char *iface, bool add) {
Chad Brubaker7a6ce4b2013-06-06 21:42:53 -0700298 int tableIndex = findTableNumber(iface);
299 if (tableIndex == -1) {
300 tableIndex = findTableNumber(""); // look for an empty slot
301 if (tableIndex == -1) {
302 ALOGE("Max number of NATed interfaces reached");
303 errno = ENODEV;
304 return -1;
305 }
306 strncpy(mInterfaceTable[tableIndex], iface, IFNAMSIZ);
307 // Ensure null termination even if truncation happened
308 mInterfaceTable[tableIndex][IFNAMSIZ] = 0;
309 }
Chad Brubaker2251c0f2013-06-27 17:20:39 -0700310 int mark = tableIndex + BASE_TABLE_NUMBER;
311 char mark_str[11];
312 int ret;
313
314 //fail fast if any rules already exist for this interface
315 if (mUidMarkMap->anyRulesForMark(mark)) {
316 errno = EBUSY;
317 return -1;
318 }
319
320 snprintf(mark_str, sizeof(mark_str), "%d", mark);
321 //add the catch all route to the tun. Route rules will make sure the right packets hit the table
322 const char *route_cmd[] = {
323 IP_PATH,
324 "route",
325 add ? "add" : "del",
326 "default",
327 "dev",
328 iface,
329 "table",
330 mark_str
331 };
332 ret = runCmd(ARRAY_SIZE(route_cmd), route_cmd);
333
334 const char *fwmark_cmd[] = {
Chad Brubaker7a6ce4b2013-06-06 21:42:53 -0700335 IP_PATH,
336 "rule",
337 add ? "add" : "del",
338 "fwmark",
Chad Brubaker2251c0f2013-06-27 17:20:39 -0700339 mark_str,
Chad Brubaker7a6ce4b2013-06-06 21:42:53 -0700340 "table",
Chad Brubaker2251c0f2013-06-27 17:20:39 -0700341 mark_str
Chad Brubaker7a6ce4b2013-06-06 21:42:53 -0700342 };
Chad Brubaker2251c0f2013-06-27 17:20:39 -0700343 ret = runCmd(ARRAY_SIZE(fwmark_cmd), fwmark_cmd);
Chad Brubaker7a6ce4b2013-06-06 21:42:53 -0700344 if (ret) return ret;
345
Chad Brubaker2251c0f2013-06-27 17:20:39 -0700346 //add rules for v6
347 const char *route6_cmd[] = {
348 IP_PATH,
349 "-6",
350 "route",
351 add ? "add" : "del",
352 "default",
353 "dev",
354 iface,
355 "table",
356 mark_str
357 };
358 ret = runCmd(ARRAY_SIZE(route6_cmd), route6_cmd);
359
360 const char *fwmark6_cmd[] = {
361 IP_PATH,
362 "-6",
363 "rule",
364 add ? "add" : "del",
365 "fwmark",
366 mark_str,
367 "table",
368 mark_str
369 };
370 ret = runCmd(ARRAY_SIZE(fwmark6_cmd), fwmark6_cmd);
371
372
373 if (ret) return ret;
374
375 //create the route rule chain
376 char chain_str[IFNAMSIZ + 18];
377 snprintf(chain_str, sizeof(chain_str), LOCAL_MANGLE_IFACE_FORMAT, iface);
378 //code split due to ordering requirements
379 if (add) {
380 ret = execIptables(V4V6,
381 "-t",
382 "mangle",
383 "-N",
384 chain_str,
385 NULL);
386 //set up the rule for sending premarked packets to the VPN chain
387 //Insert these at the top of the chain so they trigger before any UID rules
388 ret |= execIptables(V4V6,
389 "-t",
390 "mangle",
391 "-I",
392 LOCAL_MANGLE_OUTPUT,
393 "3",
394 "-m",
395 "mark",
396 "--mark",
397 mark_str,
398 "-g",
399 chain_str,
400 NULL);
401 //add a rule to clear the mark in the VPN chain
402 //packets marked with SO_MARK already have the iface's mark set but unless they match a
403 //route they should hit the network instead of the VPN
404 ret |= execIptables(V4V6,
405 "-t",
406 "mangle",
407 "-A",
408 chain_str,
409 "-j",
410 "MARK",
411 "--set-mark",
412 "0",
413 NULL);
414
415 } else {
416 ret = execIptables(V4V6,
417 "-t",
418 "mangle",
419 "-D",
420 LOCAL_MANGLE_OUTPUT,
421 "-m",
422 "mark",
423 "--mark",
424 mark_str,
425 "-g",
426 chain_str,
427 NULL);
428
429 //clear and delete the chain
430 ret |= execIptables(V4V6,
431 "-t",
432 "mangle",
433 "-F",
434 chain_str,
435 NULL);
436
437 ret |= execIptables(V4V6,
438 "-t",
439 "mangle",
440 "-X",
441 chain_str,
442 NULL);
443 }
444
Chad Brubaker7a6ce4b2013-06-06 21:42:53 -0700445 //set up the needed source IP rewriting
446 //NOTE: Without ipv6 NAT in the kernel <3.7 only support V4 NAT
Chad Brubaker2251c0f2013-06-27 17:20:39 -0700447 ret = execIptables(V4,
Chad Brubaker7a6ce4b2013-06-06 21:42:53 -0700448 "-t",
449 "nat",
450 add ? "-A" : "-D",
451 LOCAL_NAT_POSTROUTING,
452 "-o",
453 iface,
454 "-m",
455 "mark",
456 "--mark",
Chad Brubaker2251c0f2013-06-27 17:20:39 -0700457 mark_str,
Chad Brubaker7a6ce4b2013-06-06 21:42:53 -0700458 "-j",
459 "MASQUERADE",
460 NULL);
461
Chad Brubaker2251c0f2013-06-27 17:20:39 -0700462 if (ret) return ret;
463
464 //try and set up for ipv6. ipv6 nat came in the kernel only in 3.7, so this can fail
465 ret = execIptables(V6,
466 "-t",
467 "nat",
468 add ? "-A" : "-D",
469 LOCAL_NAT_POSTROUTING,
470 "-o",
471 iface,
472 "-m",
473 "mark",
474 "--mark",
475 mark_str,
476 "-j",
477 "MASQUERADE",
478 NULL);
479 if (ret) {
480 //Without V6 NAT we can't do V6 over VPNs.
481 ret = execIptables(V6,
482 "-t",
483 "filter",
484 add ? "-A" : "-D",
485 LOCAL_FILTER_OUTPUT,
486 "-m",
487 "mark",
488 "--mark",
489 mark_str,
490 "-j",
491 "REJECT",
492 NULL);
493 }
494 return ret;
495
496}
497
498int SecondaryTableController::addFwmarkRoute(const char* iface, const char *dest, int prefix) {
499 return setFwmarkRoute(iface, dest, prefix, true);
500}
501
502int SecondaryTableController::removeFwmarkRoute(const char* iface, const char *dest, int prefix) {
503 return setFwmarkRoute(iface, dest, prefix, true);
504}
505
506int SecondaryTableController::setFwmarkRoute(const char* iface, const char *dest, int prefix,
507 bool add) {
508 int tableIndex = findTableNumber(iface);
509 if (tableIndex == -1) {
510 errno = EINVAL;
511 return -1;
512 }
513 int mark = tableIndex + BASE_TABLE_NUMBER;
514 char mark_str[11] = {0};
515 char chain_str[IFNAMSIZ + 18];
516 char dest_str[44]; // enough to store an IPv6 address + 3 character bitmask
517
518 snprintf(mark_str, sizeof(mark_str), "%d", mark);
519 snprintf(chain_str, sizeof(chain_str), LOCAL_MANGLE_IFACE_FORMAT, iface);
520 snprintf(dest_str, sizeof(dest_str), "%s/%d", dest, prefix);
521 return execIptables(getIptablesTarget(dest),
522 "-t",
523 "mangle",
524 add ? "-A" : "-D",
525 chain_str,
526 "-d",
527 dest_str,
528 "-j",
529 "MARK",
530 "--set-mark",
531 mark_str,
532 NULL);
Chad Brubaker7a6ce4b2013-06-06 21:42:53 -0700533}
Robert Greenwaltc4621772012-01-31 12:46:45 -0800534
Chad Brubaker8830b942013-06-12 10:51:55 -0700535int SecondaryTableController::addUidRule(const char *iface, int uid_start, int uid_end) {
536 return setUidRule(iface, uid_start, uid_end, true);
Chad Brubaker9a508892013-05-31 20:51:46 -0700537}
538
Chad Brubaker8830b942013-06-12 10:51:55 -0700539int SecondaryTableController::removeUidRule(const char *iface, int uid_start, int uid_end) {
540 return setUidRule(iface, uid_start, uid_end, false);
Chad Brubaker9a508892013-05-31 20:51:46 -0700541}
542
Chad Brubaker8830b942013-06-12 10:51:55 -0700543int SecondaryTableController::setUidRule(const char *iface, int uid_start, int uid_end, bool add) {
Chad Brubaker9a508892013-05-31 20:51:46 -0700544 int tableIndex = findTableNumber(iface);
545 if (tableIndex == -1) {
Chad Brubakerd2617932013-06-21 15:26:35 -0700546 errno = EINVAL;
Chad Brubaker9a508892013-05-31 20:51:46 -0700547 return -1;
548 }
Chad Brubakerd2617932013-06-21 15:26:35 -0700549 int mark = tableIndex + BASE_TABLE_NUMBER;
550 if (add) {
551 if (!mUidMarkMap->add(uid_start, uid_end, mark)) {
552 errno = EINVAL;
553 return -1;
554 }
555 } else {
556 if (!mUidMarkMap->remove(uid_start, uid_end, mark)) {
557 errno = EINVAL;
558 return -1;
559 }
560 }
Chad Brubaker8830b942013-06-12 10:51:55 -0700561 char uid_str[24] = {0};
Chad Brubaker2251c0f2013-06-27 17:20:39 -0700562 char chain_str[IFNAMSIZ + 18];
Chad Brubaker8830b942013-06-12 10:51:55 -0700563 snprintf(uid_str, sizeof(uid_str), "%d-%d", uid_start, uid_end);
Chad Brubaker2251c0f2013-06-27 17:20:39 -0700564 snprintf(chain_str, sizeof(chain_str), LOCAL_MANGLE_IFACE_FORMAT, iface);
Chad Brubaker9a508892013-05-31 20:51:46 -0700565 return execIptables(V4V6,
566 "-t",
567 "mangle",
568 add ? "-A" : "-D",
569 LOCAL_MANGLE_OUTPUT,
570 "-m",
571 "owner",
572 "--uid-owner",
Chad Brubaker8830b942013-06-12 10:51:55 -0700573 uid_str,
Chad Brubaker2251c0f2013-06-27 17:20:39 -0700574 "-g",
575 chain_str,
Chad Brubaker9a508892013-05-31 20:51:46 -0700576 NULL);
577}
578
Rom Lemarchand001f0a42013-01-31 12:41:03 -0800579int SecondaryTableController::runCmd(int argc, const char **argv) {
Robert Greenwaltfc97b822011-11-02 16:48:36 -0700580 int ret = 0;
Rom Lemarchand001f0a42013-01-31 12:41:03 -0800581
582 ret = android_fork_execvp(argc, (char **)argv, NULL, false, false);
Robert Greenwaltfc97b822011-11-02 16:48:36 -0700583 return ret;
584}