1/*
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#define LOG_NDEBUG 0
18
19#include <stdlib.h>
20#include <errno.h>
21#include <sys/socket.h>
22#include <sys/stat.h>
23#include <sys/wait.h>
24#include <fcntl.h>
25#include <netinet/in.h>
26#include <arpa/inet.h>
27#include <string.h>
28#include <cutils/properties.h>
29
30#define LOG_TAG "NatController"
31#include <cutils/log.h>
32#include <logwrap/logwrap.h>
33
34#include "NatController.h"
35#include "SecondaryTableController.h"
36#include "NetdConstants.h"
37
38const char* NatController::LOCAL_FORWARD = "natctrl_FORWARD";
39const char* NatController::LOCAL_NAT_POSTROUTING = "natctrl_nat_POSTROUTING";
40const char* NatController::LOCAL_TETHER_COUNTERS_CHAIN = "natctrl_tether_counters";
41
42NatController::NatController(SecondaryTableController *ctrl) {
43    secondaryTableCtrl = ctrl;
44}
45
46NatController::~NatController() {
47}
48
49struct CommandsAndArgs {
50    /* The array size doesn't really matter as the compiler will barf if too many initializers are specified. */
51    const char *cmd[32];
52    bool checkRes;
53};
54
55int NatController::runCmd(int argc, const char **argv) {
56    int res;
57
58    res = android_fork_execvp(argc, (char **)argv, NULL, false, false);
59
60#if !LOG_NDEBUG
61    std::string full_cmd = argv[0];
62    argc--; argv++;
63    /*
64     * HACK: Sometimes runCmd() is called with a ridcously large value (32)
65     * and it works because the argv[] contains a NULL after the last
66     * true argv. So here we use the NULL argv[] to terminate when the argc
67     * is horribly wrong, and argc for the normal cases.
68     */
69    for (; argc && argv[0]; argc--, argv++) {
70        full_cmd += " ";
71        full_cmd += argv[0];
72    }
73    ALOGV("runCmd(%s) res=%d", full_cmd.c_str(), res);
74#endif
75    return res;
76}
77
78int NatController::setupIptablesHooks() {
79    int res;
80    res = setDefaults();
81    if (res < 0) {
82        return res;
83    }
84
85    struct CommandsAndArgs defaultCommands[] = {
86        /*
87         * Chain for tethering counters.
88         * This chain is reached via --goto, and then RETURNS.
89         */
90        {{IPTABLES_PATH, "-F", LOCAL_TETHER_COUNTERS_CHAIN,}, 0},
91        {{IPTABLES_PATH, "-X", LOCAL_TETHER_COUNTERS_CHAIN,}, 0},
92        {{IPTABLES_PATH, "-N", LOCAL_TETHER_COUNTERS_CHAIN,}, 1},
93    };
94    for (unsigned int cmdNum = 0; cmdNum < ARRAY_SIZE(defaultCommands); cmdNum++) {
95        if (runCmd(ARRAY_SIZE(defaultCommands[cmdNum].cmd), defaultCommands[cmdNum].cmd) &&
96            defaultCommands[cmdNum].checkRes) {
97                return -1;
98        }
99    }
100
101    return 0;
102}
103
104int NatController::setDefaults() {
105    /*
106     * The following only works because:
107     *  - the defaultsCommands[].cmd array is padded with NULL, and
108     *  - the 1st argc of runCmd() will just be the max for the CommandsAndArgs[].cmd, and
109     *  - internally it will be memcopied to an array and terminated with a NULL.
110     */
111    struct CommandsAndArgs defaultCommands[] = {
112        {{IPTABLES_PATH, "-F", LOCAL_FORWARD,}, 1},
113        {{IPTABLES_PATH, "-A", LOCAL_FORWARD, "-j", "DROP"}, 1},
114        {{IPTABLES_PATH, "-t", "nat", "-F", LOCAL_NAT_POSTROUTING}, 1},
115        {{IP_PATH, "rule", "flush"}, 0},
116        {{IP_PATH, "-6", "rule", "flush"}, 0},
117        {{IP_PATH, "rule", "add", "from", "all", "lookup", "default", "prio", "32767"}, 0},
118        {{IP_PATH, "rule", "add", "from", "all", "lookup", "main", "prio", "32766"}, 0},
119        {{IP_PATH, "-6", "rule", "add", "from", "all", "lookup", "default", "prio", "32767"}, 0},
120        {{IP_PATH, "-6", "rule", "add", "from", "all", "lookup", "main", "prio", "32766"}, 0},
121        {{IP_PATH, "route", "flush", "cache"}, 0},
122    };
123    for (unsigned int cmdNum = 0; cmdNum < ARRAY_SIZE(defaultCommands); cmdNum++) {
124        if (runCmd(ARRAY_SIZE(defaultCommands[cmdNum].cmd), defaultCommands[cmdNum].cmd) &&
125            defaultCommands[cmdNum].checkRes) {
126                return -1;
127        }
128    }
129
130    natCount = 0;
131
132    return 0;
133}
134
135bool NatController::checkInterface(const char *iface) {
136    if (strlen(iface) > IFNAMSIZ) return false;
137    return true;
138}
139
140int NatController::routesOp(bool add, const char *intIface, const char *extIface, char **argv, int addrCount) {
141    int tableNumber = secondaryTableCtrl->findTableNumber(extIface);
142    int ret = 0;
143
144    if (tableNumber != -1) {
145        for (int i = 0; i < addrCount; i++) {
146            if (add) {
147                ret |= secondaryTableCtrl->modifyFromRule(tableNumber, ADD, argv[5+i]);
148                ret |= secondaryTableCtrl->modifyLocalRoute(tableNumber, ADD, intIface, argv[5+i]);
149            } else {
150                ret |= secondaryTableCtrl->modifyLocalRoute(tableNumber, DEL, intIface, argv[5+i]);
151                ret |= secondaryTableCtrl->modifyFromRule(tableNumber, DEL, argv[5+i]);
152            }
153        }
154        const char *cmd[] = {
155                IP_PATH,
156                "route",
157                "flush",
158                "cache"
159        };
160        runCmd(ARRAY_SIZE(cmd), cmd);
161    }
162    return ret;
163}
164
165//  0    1       2       3       4            5
166// nat enable intface extface addrcnt nated-ipaddr/prelength
167int NatController::enableNat(const int argc, char **argv) {
168    int i;
169    int addrCount = atoi(argv[4]);
170    const char *intIface = argv[2];
171    const char *extIface = argv[3];
172    int tableNumber;
173
174    ALOGV("enableNat(intIface=<%s>, extIface=<%s>)",intIface, extIface);
175
176    if (!checkInterface(intIface) || !checkInterface(extIface)) {
177        ALOGE("Invalid interface specified");
178        errno = ENODEV;
179        return -1;
180    }
181
182    /* Bug: b/9565268. "enableNat wlan0 wlan0". For now we fail until java-land is fixed */
183    if (!strcmp(intIface, extIface)) {
184        ALOGE("Duplicate interface specified: %s %s", intIface, extIface);
185        errno = EINVAL;
186        return -1;
187    }
188
189    if (argc < 5 + addrCount) {
190        ALOGE("Missing Argument");
191        errno = EINVAL;
192        return -1;
193    }
194    if (routesOp(true, intIface, extIface, argv, addrCount)) {
195        ALOGE("Error setting route rules");
196        routesOp(false, intIface, extIface, argv, addrCount);
197        errno = ENODEV;
198        return -1;
199    }
200
201    // add this if we are the first added nat
202    if (natCount == 0) {
203        const char *cmd[] = {
204                IPTABLES_PATH,
205                "-t",
206                "nat",
207                "-A",
208                LOCAL_NAT_POSTROUTING,
209                "-o",
210                extIface,
211                "-j",
212                "MASQUERADE"
213        };
214        if (runCmd(ARRAY_SIZE(cmd), cmd)) {
215            ALOGE("Error seting postroute rule: iface=%s", extIface);
216            // unwind what's been done, but don't care about success - what more could we do?
217            routesOp(false, intIface, extIface, argv, addrCount);
218            setDefaults();
219            return -1;
220        }
221    }
222
223
224    if (setForwardRules(true, intIface, extIface) != 0) {
225        ALOGE("Error setting forward rules");
226        routesOp(false, intIface, extIface, argv, addrCount);
227        if (natCount == 0) {
228            setDefaults();
229        }
230        errno = ENODEV;
231        return -1;
232    }
233
234    /* Always make sure the drop rule is at the end */
235    const char *cmd1[] = {
236            IPTABLES_PATH,
237            "-D",
238            LOCAL_FORWARD,
239            "-j",
240            "DROP"
241    };
242    runCmd(ARRAY_SIZE(cmd1), cmd1);
243    const char *cmd2[] = {
244            IPTABLES_PATH,
245            "-A",
246            LOCAL_FORWARD,
247            "-j",
248            "DROP"
249    };
250    runCmd(ARRAY_SIZE(cmd2), cmd2);
251
252    natCount++;
253    return 0;
254}
255
256int NatController::setTetherCountingRules(bool add, const char *intIface, const char *extIface) {
257
258    /* We only ever add tethering quota rules so that they stick. */
259    if (!add) {
260        return 0;
261    }
262    char *quota_name, *proc_path;
263    int quota_fd;
264    asprintf(&quota_name, "%s_%s", intIface, extIface);
265
266    asprintf(&proc_path, "/proc/net/xt_quota/%s", quota_name);
267    quota_fd = open(proc_path, O_RDONLY);
268    if (quota_fd >= 0) {
269        /* quota for iface pair already exists */
270        free(proc_path);
271        free(quota_name);
272        return 0;
273    }
274    close(quota_fd);
275    free(proc_path);
276
277    const char *cmd2b[] = {
278            IPTABLES_PATH,
279            "-A",
280            LOCAL_TETHER_COUNTERS_CHAIN,
281            "-i",
282            intIface,
283            "-o",
284            extIface,
285            "-m",
286            "quota2",
287            "--name",
288            quota_name,
289            "--grow",
290            "-j",
291          "RETURN"
292    };
293
294    if (runCmd(ARRAY_SIZE(cmd2b), cmd2b) && add) {
295        free(quota_name);
296        return -1;
297    }
298    free(quota_name);
299
300    asprintf(&quota_name, "%s_%s", extIface, intIface);
301    asprintf(&proc_path, "/proc/net/xt_quota/%s", quota_name);
302    quota_fd = open(proc_path, O_RDONLY);
303    if (quota_fd >= 0) {
304        /* quota for iface pair already exists */
305        free(proc_path);
306        free(quota_name);
307        return 0;
308    }
309    close(quota_fd);
310    free(proc_path);
311
312    const char *cmd3b[] = {
313            IPTABLES_PATH,
314            "-A",
315            LOCAL_TETHER_COUNTERS_CHAIN,
316            "-i",
317            extIface,
318            "-o",
319            intIface,
320            "-m",
321            "quota2",
322            "--name",
323            quota_name,
324            "--grow",
325            "-j",
326            "RETURN"
327    };
328
329    if (runCmd(ARRAY_SIZE(cmd3b), cmd3b) && add) {
330        // unwind what's been done, but don't care about success - what more could we do?
331        free(quota_name);
332        return -1;
333    }
334    free(quota_name);
335    return 0;
336}
337
338int NatController::setForwardRules(bool add, const char *intIface, const char *extIface) {
339    const char *cmd1[] = {
340            IPTABLES_PATH,
341            add ? "-A" : "-D",
342            LOCAL_FORWARD,
343            "-i",
344            extIface,
345            "-o",
346            intIface,
347            "-m",
348            "state",
349            "--state",
350            "ESTABLISHED,RELATED",
351            "-g",
352            LOCAL_TETHER_COUNTERS_CHAIN
353    };
354    int rc = 0;
355
356    if (runCmd(ARRAY_SIZE(cmd1), cmd1) && add) {
357        return -1;
358    }
359
360    const char *cmd2[] = {
361            IPTABLES_PATH,
362            add ? "-A" : "-D",
363            LOCAL_FORWARD,
364            "-i",
365            intIface,
366            "-o",
367            extIface,
368            "-m",
369            "state",
370            "--state",
371            "INVALID",
372            "-j",
373            "DROP"
374    };
375
376    const char *cmd3[] = {
377            IPTABLES_PATH,
378            add ? "-A" : "-D",
379            LOCAL_FORWARD,
380            "-i",
381            intIface,
382            "-o",
383            extIface,
384            "-g",
385            LOCAL_TETHER_COUNTERS_CHAIN
386    };
387
388    if (runCmd(ARRAY_SIZE(cmd2), cmd2) && add) {
389        // bail on error, but only if adding
390        rc = -1;
391        goto err_invalid_drop;
392    }
393
394    if (runCmd(ARRAY_SIZE(cmd3), cmd3) && add) {
395        // unwind what's been done, but don't care about success - what more could we do?
396        rc = -1;
397        goto err_return;
398    }
399
400    if (setTetherCountingRules(add, intIface, extIface) && add) {
401        rc = -1;
402        goto err_return;
403    }
404
405    return 0;
406
407err_return:
408    cmd2[1] = "-D";
409    runCmd(ARRAY_SIZE(cmd2), cmd2);
410err_invalid_drop:
411    cmd1[1] = "-D";
412    runCmd(ARRAY_SIZE(cmd1), cmd1);
413    return rc;
414}
415
416// nat disable intface extface
417//  0    1       2       3       4            5
418// nat enable intface extface addrcnt nated-ipaddr/prelength
419int NatController::disableNat(const int argc, char **argv) {
420    int i;
421    int addrCount = atoi(argv[4]);
422    const char *intIface = argv[2];
423    const char *extIface = argv[3];
424    int tableNumber;
425
426    if (!checkInterface(intIface) || !checkInterface(extIface)) {
427        ALOGE("Invalid interface specified");
428        errno = ENODEV;
429        return -1;
430    }
431
432    if (argc < 5 + addrCount) {
433        ALOGE("Missing Argument");
434        errno = EINVAL;
435        return -1;
436    }
437
438    setForwardRules(false, intIface, extIface);
439    routesOp(false, intIface, extIface, argv, addrCount);
440    if (--natCount <= 0) {
441        // handle decrement to 0 case (do reset to defaults) and erroneous dec below 0
442        setDefaults();
443    }
444    return 0;
445}
446