Introduce basic firewall controls.

Creates a separate firewall chain that can be used to allow/deny
traffic based on rules.

Firewall is disabled by default.  When enabled, it supports four
types of rules: allowing traffic based on iface name, based on egress
source IP, based on egress destination IP and port, and based on
local UID.

Bug: 5756357
Change-Id: I97f894dca6bddb93b3c56478c5297f79d727cdab
diff --git a/Android.mk b/Android.mk
index cdaa184..4e6c6f3 100644
--- a/Android.mk
+++ b/Android.mk
@@ -6,8 +6,9 @@
                   BandwidthController.cpp              \
                   CommandListener.cpp                  \
                   DnsProxyListener.cpp                 \
-                  MDnsSdListener.cpp                   \
+                  FirewallController.cpp               \
                   IdletimerController.cpp              \
+                  MDnsSdListener.cpp                   \
                   NatController.cpp                    \
                   NetdCommand.cpp                      \
                   NetdConstants.cpp                    \
diff --git a/CommandListener.cpp b/CommandListener.cpp
index 510f09c..c07c2c1 100644
--- a/CommandListener.cpp
+++ b/CommandListener.cpp
@@ -41,6 +41,7 @@
 #include "SecondaryTableController.h"
 #include "oem_iptables_hook.h"
 #include "NetdConstants.h"
+#include "FirewallController.h"
 
 TetherController *CommandListener::sTetherCtrl = NULL;
 NatController *CommandListener::sNatCtrl = NULL;
@@ -51,18 +52,23 @@
 IdletimerController * CommandListener::sIdletimerCtrl = NULL;
 ResolverController *CommandListener::sResolverCtrl = NULL;
 SecondaryTableController *CommandListener::sSecondaryTableCtrl = NULL;
+FirewallController *CommandListener::sFirewallCtrl = NULL;
 
 /**
  * List of module chains to be created, along with explicit ordering. ORDERING
  * IS CRITICAL, AND SHOULD BE TRIPLE-CHECKED WITH EACH CHANGE.
  */
 static const char* FILTER_INPUT[] = {
+        // Bandwidth should always be early in input chain, to make sure we
+        // correctly count incoming traffic against data plan.
         BandwidthController::LOCAL_INPUT,
+        FirewallController::LOCAL_INPUT,
         NULL,
 };
 
 static const char* FILTER_FORWARD[] = {
         OEM_IPTABLES_FILTER_FORWARD,
+        FirewallController::LOCAL_FORWARD,
         BandwidthController::LOCAL_FORWARD,
         NatController::LOCAL_FORWARD,
         NULL,
@@ -70,6 +76,7 @@
 
 static const char* FILTER_OUTPUT[] = {
         OEM_IPTABLES_FILTER_OUTPUT,
+        FirewallController::LOCAL_OUTPUT,
         BandwidthController::LOCAL_OUTPUT,
         NULL,
 };
@@ -129,6 +136,7 @@
     registerCmd(new BandwidthControlCmd());
     registerCmd(new IdletimerControlCmd());
     registerCmd(new ResolverCmd());
+    registerCmd(new FirewallCmd());
 
     if (!sSecondaryTableCtrl)
         sSecondaryTableCtrl = new SecondaryTableController();
@@ -148,6 +156,8 @@
         sIdletimerCtrl = new IdletimerController();
     if (!sResolverCtrl)
         sResolverCtrl = new ResolverController();
+    if (!sFirewallCtrl)
+        sFirewallCtrl = new FirewallController();
 
     /*
      * This is the only time we touch top-level chains in iptables; controllers
@@ -171,6 +181,9 @@
     // Let each module setup their child chains
     setupOemIptablesHook();
 
+    /* When enabled, DROPs all packets except those matching rules. */
+    sFirewallCtrl->setupIptablesHooks();
+
     /* Does DROPs in FORWARD by default */
     sNatCtrl->setupIptablesHooks();
     /*
@@ -1337,3 +1350,110 @@
     cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown idletimer cmd", false);
     return 0;
 }
+
+CommandListener::FirewallCmd::FirewallCmd() :
+    NetdCommand("firewall") {
+}
+
+int CommandListener::FirewallCmd::sendGenericOkFail(SocketClient *cli, int cond) {
+    if (!cond) {
+        cli->sendMsg(ResponseCode::CommandOkay, "Firewall command succeeded", false);
+    } else {
+        cli->sendMsg(ResponseCode::OperationFailed, "Firewall command failed", false);
+    }
+    return 0;
+}
+
+FirewallRule CommandListener::FirewallCmd::parseRule(const char* arg) {
+    if (!strcmp(arg, "allow")) {
+        return ALLOW;
+    } else {
+        return DENY;
+    }
+}
+
+int CommandListener::FirewallCmd::runCommand(SocketClient *cli, int argc,
+        char **argv) {
+    if (argc < 2) {
+        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing command", false);
+        return 0;
+    }
+
+    if (!strcmp(argv[1], "enable")) {
+        int res = sFirewallCtrl->enableFirewall();
+        return sendGenericOkFail(cli, res);
+    }
+    if (!strcmp(argv[1], "disable")) {
+        int res = sFirewallCtrl->disableFirewall();
+        return sendGenericOkFail(cli, res);
+    }
+    if (!strcmp(argv[1], "is_enabled")) {
+        int res = sFirewallCtrl->isFirewallEnabled();
+        return sendGenericOkFail(cli, res);
+    }
+
+    if (!strcmp(argv[1], "set_interface_rule")) {
+        if (argc != 4) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                         "Usage: firewall set_interface_rule <rmnet0> <allow|deny>", false);
+            return 0;
+        }
+
+        const char* iface = argv[2];
+        FirewallRule rule = parseRule(argv[3]);
+
+        int res = sFirewallCtrl->setInterfaceRule(iface, rule);
+        return sendGenericOkFail(cli, res);
+    }
+
+    if (!strcmp(argv[1], "set_egress_source_rule")) {
+        if (argc != 4) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                         "Usage: firewall set_egress_source_rule <192.168.0.1> <allow|deny>",
+                         false);
+            return 0;
+        }
+
+        const char* addr = argv[2];
+        FirewallRule rule = parseRule(argv[3]);
+
+        int res = sFirewallCtrl->setEgressSourceRule(addr, rule);
+        return sendGenericOkFail(cli, res);
+    }
+
+    if (!strcmp(argv[1], "set_egress_dest_rule")) {
+        if (argc != 5) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                         "Usage: firewall set_egress_dest_rule <192.168.0.1> <80> <allow|deny>",
+                         false);
+            return 0;
+        }
+
+        const char* addr = argv[2];
+        int port = atoi(argv[3]);
+        FirewallRule rule = parseRule(argv[4]);
+
+        int res = 0;
+        res |= sFirewallCtrl->setEgressDestRule(addr, PROTOCOL_TCP, port, rule);
+        res |= sFirewallCtrl->setEgressDestRule(addr, PROTOCOL_UDP, port, rule);
+        return sendGenericOkFail(cli, res);
+    }
+
+    if (!strcmp(argv[1], "set_uid_rule")) {
+        if (argc != 4) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError,
+                         "Usage: firewall set_uid_rule <1000> <allow|deny>",
+                         false);
+            return 0;
+        }
+
+        int uid = atoi(argv[2]);
+        FirewallRule rule = parseRule(argv[3]);
+
+        int res = sFirewallCtrl->setUidRule(uid, rule);
+        return sendGenericOkFail(cli, res);
+    }
+
+    cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown command", false);
+    return 0;
+}
diff --git a/CommandListener.h b/CommandListener.h
index a9da6d7..e52b440 100644
--- a/CommandListener.h
+++ b/CommandListener.h
@@ -29,6 +29,7 @@
 #include "IdletimerController.h"
 #include "ResolverController.h"
 #include "SecondaryTableController.h"
+#include "FirewallController.h"
 
 class CommandListener : public FrameworkListener {
     static TetherController *sTetherCtrl;
@@ -40,6 +41,7 @@
     static IdletimerController *sIdletimerCtrl;
     static ResolverController *sResolverCtrl;
     static SecondaryTableController *sSecondaryTableCtrl;
+    static FirewallController *sFirewallCtrl;
 
 public:
     CommandListener();
@@ -131,6 +133,16 @@
         virtual ~ResolverCmd() {}
         int runCommand(SocketClient *c, int argc, char ** argv);
     };
+
+    class FirewallCmd: public NetdCommand {
+    public:
+        FirewallCmd();
+        virtual ~FirewallCmd() {}
+        int runCommand(SocketClient *c, int argc, char ** argv);
+    protected:
+        int sendGenericOkFail(SocketClient *cli, int cond);
+        static FirewallRule parseRule(const char* arg);
+    };
 };
 
 #endif
diff --git a/FirewallController.cpp b/FirewallController.cpp
new file mode 100644
index 0000000..0746316
--- /dev/null
+++ b/FirewallController.cpp
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define LOG_TAG "FirewallController"
+#define LOG_NDEBUG 0
+
+#include <cutils/log.h>
+
+#include "NetdConstants.h"
+#include "FirewallController.h"
+
+const char* FirewallController::LOCAL_INPUT = "fw_INPUT";
+const char* FirewallController::LOCAL_OUTPUT = "fw_OUTPUT";
+const char* FirewallController::LOCAL_FORWARD = "fw_FORWARD";
+
+FirewallController::FirewallController(void) {
+}
+
+int FirewallController::setupIptablesHooks(void) {
+    return 0;
+}
+
+int FirewallController::enableFirewall(void) {
+    int res = 0;
+
+    // flush any existing rules
+    disableFirewall();
+
+    // create default rule to drop all traffic
+    res |= execIptables(V4V6, "-A", LOCAL_INPUT, "-j", "DROP", NULL);
+    res |= execIptables(V4V6, "-A", LOCAL_OUTPUT, "-j", "REJECT", NULL);
+    res |= execIptables(V4V6, "-A", LOCAL_FORWARD, "-j", "REJECT", NULL);
+
+    return res;
+}
+
+int FirewallController::disableFirewall(void) {
+    int res = 0;
+
+    // flush any existing rules
+    res |= execIptables(V4V6, "-F", LOCAL_INPUT, NULL);
+    res |= execIptables(V4V6, "-F", LOCAL_OUTPUT, NULL);
+    res |= execIptables(V4V6, "-F", LOCAL_FORWARD, NULL);
+
+    return res;
+}
+
+int FirewallController::isFirewallEnabled(void) {
+    // TODO: verify that rules are still in place near top
+    return -1;
+}
+
+int FirewallController::setInterfaceRule(const char* iface, FirewallRule rule) {
+    const char* op;
+    if (rule == ALLOW) {
+        op = "-I";
+    } else {
+        op = "-D";
+    }
+
+    int res = 0;
+    res |= execIptables(V4V6, op, LOCAL_INPUT, "-i", iface, "-j", "RETURN", NULL);
+    res |= execIptables(V4V6, op, LOCAL_OUTPUT, "-o", iface, "-j", "RETURN", NULL);
+    return res;
+}
+
+int FirewallController::setEgressSourceRule(const char* addr, FirewallRule rule) {
+    IptablesTarget target = V4;
+    if (strchr(addr, ':')) {
+        target = V6;
+    }
+
+    const char* op;
+    if (rule == ALLOW) {
+        op = "-I";
+    } else {
+        op = "-D";
+    }
+
+    int res = 0;
+    res |= execIptables(target, op, LOCAL_INPUT, "-d", addr, "-j", "RETURN", NULL);
+    res |= execIptables(target, op, LOCAL_OUTPUT, "-s", addr, "-j", "RETURN", NULL);
+    return res;
+}
+
+int FirewallController::setEgressDestRule(const char* addr, int protocol, int port,
+        FirewallRule rule) {
+    IptablesTarget target = V4;
+    if (strchr(addr, ':')) {
+        target = V6;
+    }
+
+    char protocolStr[16];
+    sprintf(protocolStr, "%d", protocol);
+
+    char portStr[16];
+    sprintf(portStr, "%d", port);
+
+    const char* op;
+    if (rule == ALLOW) {
+        op = "-I";
+    } else {
+        op = "-D";
+    }
+
+    int res = 0;
+    res |= execIptables(target, op, LOCAL_INPUT, "-s", addr, "-p", protocolStr,
+            "--sport", portStr, "-j", "RETURN", NULL);
+    res |= execIptables(target, op, LOCAL_OUTPUT, "-d", addr, "-p", protocolStr,
+            "--dport", portStr, "-j", "RETURN", NULL);
+    return res;
+}
+
+int FirewallController::setUidRule(int uid, FirewallRule rule) {
+    char uidStr[16];
+    sprintf(uidStr, "%d", uid);
+
+    const char* op;
+    if (rule == ALLOW) {
+        op = "-I";
+    } else {
+        op = "-D";
+    }
+
+    int res = 0;
+    res |= execIptables(V4V6, op, LOCAL_INPUT, "-m", "owner", "--uid-owner", uidStr,
+            "-j", "RETURN", NULL);
+    res |= execIptables(V4V6, op, LOCAL_OUTPUT, "-m", "owner", "--uid-owner", uidStr,
+            "-j", "RETURN", NULL);
+    return res;
+}
diff --git a/FirewallController.h b/FirewallController.h
new file mode 100644
index 0000000..158e0fa
--- /dev/null
+++ b/FirewallController.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _FIREWALL_CONTROLLER_H
+#define _FIREWALL_CONTROLLER_H
+
+#include <string>
+
+enum FirewallRule { ALLOW, DENY };
+
+#define PROTOCOL_TCP 6
+#define PROTOCOL_UDP 17
+
+/*
+ * Simple firewall that drops all packets except those matching explicitly
+ * defined ALLOW rules.
+ */
+class FirewallController {
+public:
+    FirewallController();
+
+    int setupIptablesHooks(void);
+
+    int enableFirewall(void);
+    int disableFirewall(void);
+    int isFirewallEnabled(void);
+
+    /* Match traffic going in/out over the given iface. */
+    int setInterfaceRule(const char*, FirewallRule);
+    /* Match traffic coming-in-to or going-out-from given address. */
+    int setEgressSourceRule(const char*, FirewallRule);
+    /* Match traffic coming-in-from or going-out-to given address, port, and protocol. */
+    int setEgressDestRule(const char*, int, int, FirewallRule);
+    /* Match traffic owned by given UID. */
+    int setUidRule(int, FirewallRule);
+
+    static const char* LOCAL_INPUT;
+    static const char* LOCAL_OUTPUT;
+    static const char* LOCAL_FORWARD;
+
+};
+
+#endif