diff options
author | Geremy Condra <gcondra@google.com> | 2013-07-11 22:56:55 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2013-07-11 22:56:55 +0000 |
commit | 9d433316d408cd93b0c18dc0caf8e72dfc6179f0 (patch) | |
tree | ab83fd3cd0e55dfcbde11f82070aa3a05bb3a057 | |
parent | 9ba91b9045b2d760789c6854e574494686e0b64c (diff) | |
parent | 2251c0fbcf24a9c8fd77b23851f60304087bab2b (diff) | |
download | netd-9d433316d408cd93b0c18dc0caf8e72dfc6179f0.tar.gz |
Merge "Add support for fwmark split tunneling"
-rw-r--r-- | CommandListener.cpp | 65 | ||||
-rw-r--r-- | SecondaryTableController.cpp | 270 | ||||
-rw-r--r-- | SecondaryTableController.h | 10 | ||||
-rw-r--r-- | UidMarkMap.cpp | 12 | ||||
-rw-r--r-- | UidMarkMap.h | 1 |
5 files changed, 329 insertions, 29 deletions
diff --git a/CommandListener.cpp b/CommandListener.cpp index 26aa8cef..473b864a 100644 --- a/CommandListener.cpp +++ b/CommandListener.cpp @@ -77,6 +77,7 @@ static const char* FILTER_OUTPUT[] = { OEM_IPTABLES_FILTER_OUTPUT, FirewallController::LOCAL_OUTPUT, BandwidthController::LOCAL_OUTPUT, + SecondaryTableController::LOCAL_FILTER_OUTPUT, NULL, }; @@ -206,6 +207,8 @@ CommandListener::CommandListener(UidMarkMap *map) : sIdletimerCtrl->setupIptablesHooks(); sBandwidthCtrl->enableBandwidthControl(false); + + sSecondaryTableCtrl->setupIptablesHooks(); } CommandListener::InterfaceCmd::InterfaceCmd() : @@ -263,37 +266,65 @@ int CommandListener::InterfaceCmd::runCommand(SocketClient *cli, // 0 1 2 3 4 5 6 7 // interface route add/remove iface default/secondary dest prefix gateway - // interface route fwmark add/remove iface - // interface route uid add/remove iface uid_start uid_end - if (!strcmp(argv[1], "route")) { - int prefix_length = 0; - if (!strcmp(argv[2], "fwmark")) { + // interface fwmark rule add/remove iface + // interface fwmark route add/remove iface dest prefix + // interface fwmark uid add/remove iface uid_start uid_end + if (!strcmp(argv[1], "fwmark")) { + if (!strcmp(argv[2], "rule")) { if (argc < 5) { cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false); return 0; } if (!strcmp(argv[3], "add")) { if (!sSecondaryTableCtrl->addFwmarkRule(argv[4])) { - cli->sendMsg(ResponseCode::CommandOkay, "Fwmark rule successfully added", - false); + cli->sendMsg(ResponseCode::CommandOkay, + "Fwmark rule successfully added", false); } else { cli->sendMsg(ResponseCode::OperationFailed, "Failed to add fwmark rule", true); } } else if (!strcmp(argv[3], "remove")) { if (!sSecondaryTableCtrl->removeFwmarkRule(argv[4])) { - cli->sendMsg(ResponseCode::CommandOkay, "Fwmark rule successfully removed", - false); + cli->sendMsg(ResponseCode::CommandOkay, + "Fwmark rule successfully removed", false); } else { - cli->sendMsg(ResponseCode::OperationFailed, "Failed to remove fwmark rule", - true); + cli->sendMsg(ResponseCode::OperationFailed, + "Failed to remove fwmark rule", true); } } else { - cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown fwmark cmd", false); + cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown fwmark rule cmd", + false); } return 0; - } - if (!strcmp(argv[2], "uid")) { + } else if (!strcmp(argv[2], "route")) { + if (argc < 7) { + cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false); + return 0; + } + if (!strcmp(argv[3], "add")) { + if (!sSecondaryTableCtrl->addFwmarkRoute(argv[4], argv[5], atoi(argv[6]))) { + cli->sendMsg(ResponseCode::CommandOkay, + "Fwmark route successfully added", false); + } else { + cli->sendMsg(ResponseCode::OperationFailed, + "Failed to add fwmark route", true); + } + } else if (!strcmp(argv[3], "remove")) { + if (!sSecondaryTableCtrl->removeFwmarkRoute(argv[4], argv[5], + atoi(argv[6]))) { + cli->sendMsg(ResponseCode::CommandOkay, + "Fwmark route successfully removed", false); + } else { + cli->sendMsg(ResponseCode::OperationFailed, + "Failed to remove fwmark route", true); + } + } else { + cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown fwmark route cmd", + false); + } + return 0; + + } else if (!strcmp(argv[2], "uid")) { if (argc < 7) { cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false); return 0; @@ -318,7 +349,13 @@ int CommandListener::InterfaceCmd::runCommand(SocketClient *cli, cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown uid cmd", false); } return 0; + } else { + cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown fwmark cmd", false); + return 0; } + } + if (!strcmp(argv[1], "route")) { + int prefix_length = 0; if (argc < 8) { cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false); return 0; diff --git a/SecondaryTableController.cpp b/SecondaryTableController.cpp index 89a307b0..0a4d1081 100644 --- a/SecondaryTableController.cpp +++ b/SecondaryTableController.cpp @@ -37,7 +37,9 @@ #include "SecondaryTableController.h" const char* SecondaryTableController::LOCAL_MANGLE_OUTPUT = "st_mangle_OUTPUT"; +const char* SecondaryTableController::LOCAL_MANGLE_IFACE_FORMAT = "st_mangle_%s_OUTPUT"; const char* SecondaryTableController::LOCAL_NAT_POSTROUTING = "st_nat_POSTROUTING"; +const char* SecondaryTableController::LOCAL_FILTER_OUTPUT = "st_filter_OUTPUT"; SecondaryTableController::SecondaryTableController(UidMarkMap *map) : mUidMarkMap(map) { int i; @@ -51,6 +53,47 @@ SecondaryTableController::SecondaryTableController(UidMarkMap *map) : mUidMarkMa SecondaryTableController::~SecondaryTableController() { } +int SecondaryTableController::setupIptablesHooks() { + int res = execIptables(V4V6, + "-t", + "mangle", + "-F", + LOCAL_MANGLE_OUTPUT, + NULL); + //rule for skipping anything marked with the PROTECT_MARK + char protect_mark_str[11]; + snprintf(protect_mark_str, sizeof(protect_mark_str), "%d", PROTECT_MARK); + res |= execIptables(V4V6, + "-t", + "mangle", + "-A", + LOCAL_MANGLE_OUTPUT, + "-m", + "mark", + "--mark", + protect_mark_str, + "-j", + "RETURN", + NULL); + + //protect the legacy VPN daemons from routes. + //TODO: Remove this when legacy VPN's are removed. + res |= execIptables(V4V6, + "-t", + "mangle", + "-A", + LOCAL_MANGLE_OUTPUT, + "-m", + "owner", + "--uid-owner", + "vpn", + "-j", + "RETURN", + NULL); + return res; + +} + int SecondaryTableController::findTableNumber(const char *iface) { int i; for (i = 0; i < INTERFACES_TRACKED; i++) { @@ -169,6 +212,14 @@ const char *SecondaryTableController::getVersion(const char *addr) { } } +IptablesTarget SecondaryTableController::getIptablesTarget(const char *addr) { + if (strchr(addr, ':') != NULL) { + return V6; + } else { + return V4; + } +} + int SecondaryTableController::removeRoute(SocketClient *cli, char *iface, char *dest, int prefix, char *gateway) { int tableIndex = findTableNumber(iface); @@ -244,7 +295,6 @@ int SecondaryTableController::removeFwmarkRule(const char *iface) { } int SecondaryTableController::setFwmarkRule(const char *iface, bool add) { - char tableIndex_str[11]; int tableIndex = findTableNumber(iface); if (tableIndex == -1) { tableIndex = findTableNumber(""); // look for an empty slot @@ -257,23 +307,144 @@ int SecondaryTableController::setFwmarkRule(const char *iface, bool add) { // Ensure null termination even if truncation happened mInterfaceTable[tableIndex][IFNAMSIZ] = 0; } - snprintf(tableIndex_str, sizeof(tableIndex_str), "%d", tableIndex + - BASE_TABLE_NUMBER); - const char *cmd[] = { + int mark = tableIndex + BASE_TABLE_NUMBER; + char mark_str[11]; + int ret; + + //fail fast if any rules already exist for this interface + if (mUidMarkMap->anyRulesForMark(mark)) { + errno = EBUSY; + return -1; + } + + snprintf(mark_str, sizeof(mark_str), "%d", mark); + //add the catch all route to the tun. Route rules will make sure the right packets hit the table + const char *route_cmd[] = { + IP_PATH, + "route", + add ? "add" : "del", + "default", + "dev", + iface, + "table", + mark_str + }; + ret = runCmd(ARRAY_SIZE(route_cmd), route_cmd); + + const char *fwmark_cmd[] = { + IP_PATH, + "rule", + add ? "add" : "del", + "fwmark", + mark_str, + "table", + mark_str + }; + ret = runCmd(ARRAY_SIZE(fwmark_cmd), fwmark_cmd); + if (ret) return ret; + + //add rules for v6 + const char *route6_cmd[] = { + IP_PATH, + "-6", + "route", + add ? "add" : "del", + "default", + "dev", + iface, + "table", + mark_str + }; + ret = runCmd(ARRAY_SIZE(route6_cmd), route6_cmd); + + const char *fwmark6_cmd[] = { IP_PATH, + "-6", "rule", add ? "add" : "del", "fwmark", - tableIndex_str, + mark_str, "table", - tableIndex_str + mark_str }; - int ret = runCmd(ARRAY_SIZE(cmd), cmd); + ret = runCmd(ARRAY_SIZE(fwmark6_cmd), fwmark6_cmd); + + if (ret) return ret; + //create the route rule chain + char chain_str[IFNAMSIZ + 18]; + snprintf(chain_str, sizeof(chain_str), LOCAL_MANGLE_IFACE_FORMAT, iface); + //code split due to ordering requirements + if (add) { + ret = execIptables(V4V6, + "-t", + "mangle", + "-N", + chain_str, + NULL); + //set up the rule for sending premarked packets to the VPN chain + //Insert these at the top of the chain so they trigger before any UID rules + ret |= execIptables(V4V6, + "-t", + "mangle", + "-I", + LOCAL_MANGLE_OUTPUT, + "3", + "-m", + "mark", + "--mark", + mark_str, + "-g", + chain_str, + NULL); + //add a rule to clear the mark in the VPN chain + //packets marked with SO_MARK already have the iface's mark set but unless they match a + //route they should hit the network instead of the VPN + ret |= execIptables(V4V6, + "-t", + "mangle", + "-A", + chain_str, + "-j", + "MARK", + "--set-mark", + "0", + NULL); + + } else { + ret = execIptables(V4V6, + "-t", + "mangle", + "-D", + LOCAL_MANGLE_OUTPUT, + "-m", + "mark", + "--mark", + mark_str, + "-g", + chain_str, + NULL); + + //clear and delete the chain + ret |= execIptables(V4V6, + "-t", + "mangle", + "-F", + chain_str, + NULL); + + ret |= execIptables(V4V6, + "-t", + "mangle", + "-X", + chain_str, + NULL); + } + //set up the needed source IP rewriting //NOTE: Without ipv6 NAT in the kernel <3.7 only support V4 NAT - return execIptables(V4, + ret = execIptables(V4, "-t", "nat", add ? "-A" : "-D", @@ -283,11 +454,82 @@ int SecondaryTableController::setFwmarkRule(const char *iface, bool add) { "-m", "mark", "--mark", - tableIndex_str, + mark_str, "-j", "MASQUERADE", NULL); + if (ret) return ret; + + //try and set up for ipv6. ipv6 nat came in the kernel only in 3.7, so this can fail + ret = execIptables(V6, + "-t", + "nat", + add ? "-A" : "-D", + LOCAL_NAT_POSTROUTING, + "-o", + iface, + "-m", + "mark", + "--mark", + mark_str, + "-j", + "MASQUERADE", + NULL); + if (ret) { + //Without V6 NAT we can't do V6 over VPNs. + ret = execIptables(V6, + "-t", + "filter", + add ? "-A" : "-D", + LOCAL_FILTER_OUTPUT, + "-m", + "mark", + "--mark", + mark_str, + "-j", + "REJECT", + NULL); + } + return ret; + +} + +int SecondaryTableController::addFwmarkRoute(const char* iface, const char *dest, int prefix) { + return setFwmarkRoute(iface, dest, prefix, true); +} + +int SecondaryTableController::removeFwmarkRoute(const char* iface, const char *dest, int prefix) { + return setFwmarkRoute(iface, dest, prefix, true); +} + +int SecondaryTableController::setFwmarkRoute(const char* iface, const char *dest, int prefix, + bool add) { + int tableIndex = findTableNumber(iface); + if (tableIndex == -1) { + errno = EINVAL; + return -1; + } + int mark = tableIndex + BASE_TABLE_NUMBER; + char mark_str[11] = {0}; + char chain_str[IFNAMSIZ + 18]; + char dest_str[44]; // enough to store an IPv6 address + 3 character bitmask + + snprintf(mark_str, sizeof(mark_str), "%d", mark); + snprintf(chain_str, sizeof(chain_str), LOCAL_MANGLE_IFACE_FORMAT, iface); + snprintf(dest_str, sizeof(dest_str), "%s/%d", dest, prefix); + return execIptables(getIptablesTarget(dest), + "-t", + "mangle", + add ? "-A" : "-D", + chain_str, + "-d", + dest_str, + "-j", + "MARK", + "--set-mark", + mark_str, + NULL); } int SecondaryTableController::addUidRule(const char *iface, int uid_start, int uid_end) { @@ -316,10 +558,10 @@ int SecondaryTableController::setUidRule(const char *iface, int uid_start, int u return -1; } } - char mark_str[11] = {0}; - snprintf(mark_str, sizeof(mark_str), "%d", mark); char uid_str[24] = {0}; + char chain_str[IFNAMSIZ + 18]; snprintf(uid_str, sizeof(uid_str), "%d-%d", uid_start, uid_end); + snprintf(chain_str, sizeof(chain_str), LOCAL_MANGLE_IFACE_FORMAT, iface); return execIptables(V4V6, "-t", "mangle", @@ -329,10 +571,8 @@ int SecondaryTableController::setUidRule(const char *iface, int uid_start, int u "owner", "--uid-owner", uid_str, - "-j", - "MARK", - "--set-mark", - mark_str, + "-g", + chain_str, NULL); } diff --git a/SecondaryTableController.h b/SecondaryTableController.h index 48baa9f8..84995fa3 100644 --- a/SecondaryTableController.h +++ b/SecondaryTableController.h @@ -21,6 +21,7 @@ #include <net/if.h> #include "UidMarkMap.h" +#include "NetdConstants.h" #ifndef IFNAMSIZ #define IFNAMSIZ 16 @@ -29,6 +30,7 @@ static const int INTERFACES_TRACKED = 10; static const int BASE_TABLE_NUMBER = 60; static int MAX_TABLE_NUMBER = BASE_TABLE_NUMBER + INTERFACES_TRACKED; +static const int PROTECT_MARK = 0x1; class SecondaryTableController { @@ -45,9 +47,15 @@ public: int removeUidRule(const char *iface, int uid_start, int uid_end); int addFwmarkRule(const char *iface); int removeFwmarkRule(const char *iface); + int addFwmarkRoute(const char* iface, const char *dest, int prefix); + int removeFwmarkRoute(const char* iface, const char *dest, int prefix); + + int setupIptablesHooks(); static const char* LOCAL_MANGLE_OUTPUT; + static const char* LOCAL_MANGLE_IFACE_FORMAT; static const char* LOCAL_NAT_POSTROUTING; + static const char* LOCAL_FILTER_OUTPUT; private: @@ -55,6 +63,7 @@ private: int setUidRule(const char* iface, int uid_start, int uid_end, bool add); int setFwmarkRule(const char *iface, bool add); + int setFwmarkRoute(const char* iface, const char *dest, int prefix, bool add); int modifyRoute(SocketClient *cli, const char *action, char *iface, char *dest, int prefix, char *gateway, int tableIndex); @@ -63,6 +72,7 @@ private: void modifyRuleCount(int tableIndex, const char *action); int verifyTableIndex(int tableIndex); const char *getVersion(const char *addr); + IptablesTarget getIptablesTarget(const char *addr); int runCmd(int argc, const char **argv); }; diff --git a/UidMarkMap.cpp b/UidMarkMap.cpp index 34764cf6..d30ac53d 100644 --- a/UidMarkMap.cpp +++ b/UidMarkMap.cpp @@ -65,3 +65,15 @@ int UidMarkMap::getMark(int uid) { } return -1; }; + +bool UidMarkMap::anyRulesForMark(int mark) { + android::RWLock::AutoRLock lock(mRWLock); + android::netd::List<UidMarkEntry*>::iterator it; + for (it = mMap.begin(); it != mMap.end(); it++) { + UidMarkEntry *entry = *it; + if (entry->mark == mark) { + return true; + } + } + return false; +} diff --git a/UidMarkMap.h b/UidMarkMap.h index 52b9e835..43881bed 100644 --- a/UidMarkMap.h +++ b/UidMarkMap.h @@ -27,6 +27,7 @@ public: bool add(int uid_start, int uid_end, int mark); bool remove(int uid_start, int uid_end, int mark); int getMark(int uid); + bool anyRulesForMark(int mark); private: struct UidMarkEntry { |