1/*
2 * Copyright (C) 2012 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 <errno.h>
18#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
21
22#define LOG_TAG "FirewallController"
23#define LOG_NDEBUG 0
24
25#include <cutils/log.h>
26#include <private/android_filesystem_config.h>
27
28#include "NetdConstants.h"
29#include "FirewallController.h"
30
31const char* FirewallController::TABLE = "filter";
32
33const char* FirewallController::LOCAL_INPUT = "fw_INPUT";
34const char* FirewallController::LOCAL_OUTPUT = "fw_OUTPUT";
35const char* FirewallController::LOCAL_FORWARD = "fw_FORWARD";
36
37const char* FirewallController::LOCAL_DOZABLE = "fw_dozable";
38const char* FirewallController::LOCAL_STANDBY = "fw_standby";
39
40// ICMPv6 types that are required for any form of IPv6 connectivity to work. Note that because the
41// fw_dozable chain is called from both INPUT and OUTPUT, this includes both packets that we need
42// to be able to send (e.g., RS, NS), and packets that we need to receive (e.g., RA, NA).
43const char* FirewallController::ICMPV6_TYPES[] = {
44    "packet-too-big",
45    "router-solicitation",
46    "router-advertisement",
47    "neighbour-solicitation",
48    "neighbour-advertisement",
49    "redirect",
50};
51
52FirewallController::FirewallController(void) {
53    // If no rules are set, it's in BLACKLIST mode
54    mFirewallType = BLACKLIST;
55}
56
57int FirewallController::setupIptablesHooks(void) {
58    int res = 0;
59    // child chains are created but not attached, they will be attached explicitly.
60    FirewallType firewallType = getFirewallType(DOZABLE);
61    res |= createChain(LOCAL_DOZABLE, LOCAL_INPUT, firewallType);
62
63    firewallType = getFirewallType(STANDBY);
64    res |= createChain(LOCAL_STANDBY, LOCAL_INPUT, firewallType);
65
66    return res;
67}
68
69int FirewallController::enableFirewall(FirewallType ftype) {
70    int res = 0;
71    if (mFirewallType != ftype) {
72        // flush any existing rules
73        disableFirewall();
74
75        if (ftype == WHITELIST) {
76            // create default rule to drop all traffic
77            res |= execIptables(V4V6, "-A", LOCAL_INPUT, "-j", "DROP", NULL);
78            res |= execIptables(V4V6, "-A", LOCAL_OUTPUT, "-j", "REJECT", NULL);
79            res |= execIptables(V4V6, "-A", LOCAL_FORWARD, "-j", "REJECT", NULL);
80        }
81
82        // Set this after calling disableFirewall(), since it defaults to WHITELIST there
83        mFirewallType = ftype;
84    }
85    return res;
86}
87
88int FirewallController::disableFirewall(void) {
89    int res = 0;
90
91    mFirewallType = WHITELIST;
92
93    // flush any existing rules
94    res |= execIptables(V4V6, "-F", LOCAL_INPUT, NULL);
95    res |= execIptables(V4V6, "-F", LOCAL_OUTPUT, NULL);
96    res |= execIptables(V4V6, "-F", LOCAL_FORWARD, NULL);
97
98    return res;
99}
100
101int FirewallController::enableChildChains(ChildChain chain, bool enable) {
102    int res = 0;
103    const char* name;
104    switch(chain) {
105        case DOZABLE:
106            name = LOCAL_DOZABLE;
107            break;
108        case STANDBY:
109            name = LOCAL_STANDBY;
110            break;
111        default:
112            return res;
113    }
114
115    if (enable) {
116        res |= attachChain(name, LOCAL_INPUT);
117        res |= attachChain(name, LOCAL_OUTPUT);
118    } else {
119        res |= detachChain(name, LOCAL_INPUT);
120        res |= detachChain(name, LOCAL_OUTPUT);
121    }
122    return res;
123}
124
125int FirewallController::isFirewallEnabled(void) {
126    // TODO: verify that rules are still in place near top
127    return -1;
128}
129
130int FirewallController::setInterfaceRule(const char* iface, FirewallRule rule) {
131    if (mFirewallType == BLACKLIST) {
132        // Unsupported in BLACKLIST mode
133        return -1;
134    }
135
136    if (!isIfaceName(iface)) {
137        errno = ENOENT;
138        return -1;
139    }
140
141    const char* op;
142    if (rule == ALLOW) {
143        op = "-I";
144    } else {
145        op = "-D";
146    }
147
148    int res = 0;
149    res |= execIptables(V4V6, op, LOCAL_INPUT, "-i", iface, "-j", "RETURN", NULL);
150    res |= execIptables(V4V6, op, LOCAL_OUTPUT, "-o", iface, "-j", "RETURN", NULL);
151    return res;
152}
153
154int FirewallController::setEgressSourceRule(const char* addr, FirewallRule rule) {
155    if (mFirewallType == BLACKLIST) {
156        // Unsupported in BLACKLIST mode
157        return -1;
158    }
159
160    IptablesTarget target = V4;
161    if (strchr(addr, ':')) {
162        target = V6;
163    }
164
165    const char* op;
166    if (rule == ALLOW) {
167        op = "-I";
168    } else {
169        op = "-D";
170    }
171
172    int res = 0;
173    res |= execIptables(target, op, LOCAL_INPUT, "-d", addr, "-j", "RETURN", NULL);
174    res |= execIptables(target, op, LOCAL_OUTPUT, "-s", addr, "-j", "RETURN", NULL);
175    return res;
176}
177
178int FirewallController::setEgressDestRule(const char* addr, int protocol, int port,
179        FirewallRule rule) {
180    if (mFirewallType == BLACKLIST) {
181        // Unsupported in BLACKLIST mode
182        return -1;
183    }
184
185    IptablesTarget target = V4;
186    if (strchr(addr, ':')) {
187        target = V6;
188    }
189
190    char protocolStr[16];
191    sprintf(protocolStr, "%d", protocol);
192
193    char portStr[16];
194    sprintf(portStr, "%d", port);
195
196    const char* op;
197    if (rule == ALLOW) {
198        op = "-I";
199    } else {
200        op = "-D";
201    }
202
203    int res = 0;
204    res |= execIptables(target, op, LOCAL_INPUT, "-s", addr, "-p", protocolStr,
205            "--sport", portStr, "-j", "RETURN", NULL);
206    res |= execIptables(target, op, LOCAL_OUTPUT, "-d", addr, "-p", protocolStr,
207            "--dport", portStr, "-j", "RETURN", NULL);
208    return res;
209}
210
211FirewallType FirewallController::getFirewallType(ChildChain chain) {
212    switch(chain) {
213        case DOZABLE:
214            return WHITELIST;
215        case STANDBY:
216            return BLACKLIST;
217        case NONE:
218            return mFirewallType;
219        default:
220            return BLACKLIST;
221    }
222}
223
224int FirewallController::setUidRule(ChildChain chain, int uid, FirewallRule rule) {
225    char uidStr[16];
226    sprintf(uidStr, "%d", uid);
227
228    const char* op;
229    const char* target;
230    FirewallType firewallType = getFirewallType(chain);
231    if (firewallType == WHITELIST) {
232        target = "RETURN";
233        op = (rule == ALLOW)? "-I" : "-D";
234    } else { // BLACKLIST mode
235        target = "DROP";
236        op = (rule == DENY)? "-I" : "-D";
237    }
238
239    int res = 0;
240    switch(chain) {
241        case DOZABLE:
242            res |= execIptables(V4V6, op, LOCAL_DOZABLE, "-m", "owner", "--uid-owner",
243                    uidStr, "-j", target, NULL);
244            break;
245        case STANDBY:
246            res |= execIptables(V4V6, op, LOCAL_STANDBY, "-m", "owner", "--uid-owner",
247                    uidStr, "-j", target, NULL);
248            break;
249        case NONE:
250            res |= execIptables(V4V6, op, LOCAL_INPUT, "-m", "owner", "--uid-owner", uidStr,
251                    "-j", target, NULL);
252            res |= execIptables(V4V6, op, LOCAL_OUTPUT, "-m", "owner", "--uid-owner", uidStr,
253                    "-j", target, NULL);
254            break;
255        default:
256            ALOGW("Unknown child chain: %d", chain);
257            break;
258    }
259    return res;
260}
261
262int FirewallController::attachChain(const char* childChain, const char* parentChain) {
263    return execIptables(V4V6, "-t", TABLE, "-A", parentChain, "-j", childChain, NULL);
264}
265
266int FirewallController::detachChain(const char* childChain, const char* parentChain) {
267    return execIptables(V4V6, "-t", TABLE, "-D", parentChain, "-j", childChain, NULL);
268}
269
270int FirewallController::createChain(const char* childChain,
271        const char* parentChain, FirewallType type) {
272    // Order is important, otherwise later steps may fail.
273    execIptablesSilently(V4V6, "-t", TABLE, "-D", parentChain, "-j", childChain, NULL);
274    execIptablesSilently(V4V6, "-t", TABLE, "-F", childChain, NULL);
275    execIptablesSilently(V4V6, "-t", TABLE, "-X", childChain, NULL);
276    int res = 0;
277    res |= execIptables(V4V6, "-t", TABLE, "-N", childChain, NULL);
278    if (type == WHITELIST) {
279        // Allow ICMPv6 packets necessary to make IPv6 connectivity work. http://b/23158230 .
280        for (size_t i = 0; i < ARRAY_SIZE(ICMPV6_TYPES); i++) {
281            res |= execIptables(V6, "-A", childChain, "-p", "icmpv6", "--icmpv6-type",
282                    ICMPV6_TYPES[i], "-j", "RETURN", NULL);
283        }
284
285        // create default white list for system uid range
286        char uidStr[16];
287        sprintf(uidStr, "0-%d", AID_APP - 1);
288        res |= execIptables(V4V6, "-A", childChain, "-m", "owner", "--uid-owner",
289                uidStr, "-j", "RETURN", NULL);
290
291        // create default rule to drop all traffic
292        res |= execIptables(V4V6, "-A", childChain, "-j", "DROP", NULL);
293    }
294    return res;
295}
296