Support adding multiple tethering upstreams.
Currently, when the first tethering interface pair is added, we
add MASQUERADE rules for the upstream and IPv6 counting rules.
Continue to add IPv6 counting rules when the first interface pair
is added, but change the code to add MASQUERADE rules every time
a new upstream is added.
This will allow us to support adding, say, v4-rmnet_data0 as an
upstream once we're already using rmnet_data0 as an upstream.
Bug: 38218697
Bug: 64382985
Bug: 64976379
Bug: 64995262
Bug: 64380515
Test: netd_unit_test passes, manual: IPv4 or 464xlat tethering works
(cherry-pick of aosp I84078b2241214b3b993ccaf2e590406018df00ff)
Merged-In: I7747c81c62cc9ce26768849873a76a7e7478ea66
Merged-In: If80af6b3225316c47f11ab39e7544cfb7e095d37
Change-Id: I984ccf335fbc0c630fb21e0edc752a3ebbcd7706
diff --git a/server/TetherController.cpp b/server/TetherController.cpp
index c1d7308..6e805f5 100644
--- a/server/TetherController.cpp
+++ b/server/TetherController.cpp
@@ -138,7 +138,7 @@
mInterfaces.clear();
mDnsForwarders.clear();
mForwardingRequests.clear();
- ifacePairList.clear();
+ mFwdIfaces.clear();
}
bool TetherController::setIpFwdEnabled() {
@@ -439,7 +439,7 @@
return res;
}
- ifacePairList.clear();
+ mFwdIfaces.clear();
return 0;
}
@@ -472,8 +472,6 @@
return res;
}
- natCount = 0;
-
return 0;
}
@@ -492,48 +490,127 @@
return -1;
}
- // add this if we are the first added nat
- if (natCount == 0) {
+ if (isForwardingPairEnabled(intIface, extIface)) {
+ return 0;
+ }
+
+ // add this if we are the first enabled nat for this upstream
+ if (!isAnyForwardingEnabledOnUpstream(extIface)) {
std::vector<std::string> v4Cmds = {
"*nat",
StringPrintf("-A %s -o %s -j MASQUERADE", LOCAL_NAT_POSTROUTING, extIface),
"COMMIT\n"
};
- /*
- * IPv6 tethering doesn't need the state-based conntrack rules, so
- * it unconditionally jumps to the tether counters chain all the time.
- */
- std::vector<std::string> v6Cmds = {
- "*filter",
- StringPrintf("-A %s -g %s", LOCAL_FORWARD, LOCAL_TETHER_COUNTERS_CHAIN),
- "COMMIT\n"
- };
-
if (iptablesRestoreFunction(V4, Join(v4Cmds, '\n'), nullptr) ||
- iptablesRestoreFunction(V6, Join(v6Cmds, '\n'), nullptr)) {
+ setupIPv6CountersChain()) {
ALOGE("Error setting postroute rule: iface=%s", extIface);
- // unwind what's been done, but don't care about success - what more could we do?
- setDefaults();
+ if (!isAnyForwardingPairEnabled()) {
+ // unwind what's been done, but don't care about success - what more could we do?
+ setDefaults();
+ }
return -1;
}
}
if (setForwardRules(true, intIface, extIface) != 0) {
ALOGE("Error setting forward rules");
- if (natCount == 0) {
+ if (!isAnyForwardingPairEnabled()) {
setDefaults();
}
errno = ENODEV;
return -1;
}
- natCount++;
return 0;
}
-bool TetherController::checkTetherCountingRuleExist(const std::string& pair_name) {
- return std::find(ifacePairList.begin(), ifacePairList.end(), pair_name) != ifacePairList.end();
+int TetherController::setupIPv6CountersChain() {
+ // Only add this if we are the first enabled nat
+ if (isAnyForwardingPairEnabled()) {
+ return 0;
+ }
+
+ /*
+ * IPv6 tethering doesn't need the state-based conntrack rules, so
+ * it unconditionally jumps to the tether counters chain all the time.
+ */
+ std::vector<std::string> v6Cmds = {
+ "*filter",
+ StringPrintf("-A %s -g %s", LOCAL_FORWARD, LOCAL_TETHER_COUNTERS_CHAIN),
+ "COMMIT\n"
+ };
+
+ return iptablesRestoreFunction(V6, Join(v6Cmds, '\n'), nullptr);
+}
+
+// Gets a pointer to the ForwardingDownstream for an interface pair in the map, or nullptr
+TetherController::ForwardingDownstream* TetherController::findForwardingDownstream(
+ const std::string& intIface, const std::string& extIface) {
+ auto extIfaceMatches = mFwdIfaces.equal_range(extIface);
+ for (auto it = extIfaceMatches.first; it != extIfaceMatches.second; ++it) {
+ if (it->second.iface == intIface) {
+ return &(it->second);
+ }
+ }
+ return nullptr;
+}
+
+void TetherController::addForwardingPair(const std::string& intIface, const std::string& extIface) {
+ ForwardingDownstream* existingEntry = findForwardingDownstream(intIface, extIface);
+ if (existingEntry != nullptr) {
+ existingEntry->active = true;
+ return;
+ }
+
+ mFwdIfaces.insert(std::pair<std::string, ForwardingDownstream>(extIface, {
+ .iface = intIface,
+ .active = true
+ }));
+}
+
+void TetherController::markForwardingPairDisabled(
+ const std::string& intIface, const std::string& extIface) {
+ ForwardingDownstream* existingEntry = findForwardingDownstream(intIface, extIface);
+ if (existingEntry == nullptr) {
+ return;
+ }
+
+ existingEntry->active = false;
+}
+
+bool TetherController::isForwardingPairEnabled(
+ const std::string& intIface, const std::string& extIface) {
+ ForwardingDownstream* existingEntry = findForwardingDownstream(intIface, extIface);
+ return existingEntry != nullptr && existingEntry->active;
+}
+
+bool TetherController::isAnyForwardingEnabledOnUpstream(const std::string& extIface) {
+ auto extIfaceMatches = mFwdIfaces.equal_range(extIface);
+ for (auto it = extIfaceMatches.first; it != extIfaceMatches.second; ++it) {
+ if (it->second.active) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool TetherController::isAnyForwardingPairEnabled() {
+ for (auto& it : mFwdIfaces) {
+ if (it.second.active) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool TetherController::tetherCountingRuleExists(
+ const std::string& iface1, const std::string& iface2) {
+ // A counting rule exists if NAT was ever enabled for this interface pair, so if the pair
+ // is in the map regardless of its active status. Rules are added both ways so we check with
+ // the 2 combinations.
+ return findForwardingDownstream(iface1, iface2) != nullptr
+ || findForwardingDownstream(iface2, iface1) != nullptr;
}
/* static */
@@ -566,15 +643,11 @@
"*filter",
};
- /* We only ever add tethering quota rules so that they stick. */
- std::string pair1 = StringPrintf("%s_%s", intIface, extIface);
- if (add && !checkTetherCountingRuleExist(pair1)) {
+ // We only ever add tethering quota rules so that they stick.
+ if (add && !tetherCountingRuleExists(intIface, extIface)) {
v4.push_back(makeTetherCountingRule(intIface, extIface));
- v6.push_back(makeTetherCountingRule(intIface, extIface));
- }
- std::string pair2 = StringPrintf("%s_%s", extIface, intIface);
- if (add && !checkTetherCountingRuleExist(pair2)) {
v4.push_back(makeTetherCountingRule(extIface, intIface));
+ v6.push_back(makeTetherCountingRule(intIface, extIface));
v6.push_back(makeTetherCountingRule(extIface, intIface));
}
@@ -599,11 +672,10 @@
return -1;
}
- if (add && !checkTetherCountingRuleExist(pair1)) {
- ifacePairList.push_front(pair1);
- }
- if (add && !checkTetherCountingRuleExist(pair2)) {
- ifacePairList.push_front(pair2);
+ if (add) {
+ addForwardingPair(intIface, extIface);
+ } else {
+ markForwardingPairDisabled(intIface, extIface);
}
return 0;
@@ -616,8 +688,7 @@
}
setForwardRules(false, intIface, extIface);
- if (--natCount <= 0) {
- // handle decrement to 0 case (do reset to defaults) and erroneous dec below 0
+ if (!isAnyForwardingPairEnabled()) {
setDefaults();
}
return 0;
diff --git a/server/TetherController.h b/server/TetherController.h
index df43a7b..a34b5b7 100644
--- a/server/TetherController.h
+++ b/server/TetherController.h
@@ -33,8 +33,17 @@
class TetherController {
private:
+ struct ForwardingDownstream {
+ std::string iface;
+ bool active;
+ };
+
std::list<std::string> mInterfaces;
+ // Map upstream iface -> downstream iface. A pair is in the map if forwarding was enabled at
+ // some point since the controller was initialized.
+ std::multimap<std::string, ForwardingDownstream> mFwdIfaces;
+
// NetId to use for forwarded DNS queries. This may not be the default
// network, e.g., in the case where we are tethering to a DUN APN.
unsigned mDnsNetId;
@@ -48,10 +57,6 @@
TetherController();
virtual ~TetherController();
- // List of strings of interface pairs. Public because it's used by CommandListener.
- // TODO: merge with mInterfaces, and make private.
- std::list<std::string> ifacePairList;
-
bool enableForwarding(const char* requester);
bool disableForwarding(const char* requester);
size_t forwardingRequestCount();
@@ -126,10 +131,17 @@
private:
bool setIpFwdEnabled();
- int natCount;
-
+ int setupIPv6CountersChain();
static std::string makeTetherCountingRule(const char *if1, const char *if2);
- bool checkTetherCountingRuleExist(const std::string& pair_name);
+ ForwardingDownstream* findForwardingDownstream(const std::string& intIface,
+ const std::string& extIface);
+ void addForwardingPair(const std::string& intIface, const std::string& extIface);
+ void markForwardingPairDisabled(const std::string& intIface, const std::string& extIface);
+
+ bool isForwardingPairEnabled(const std::string& intIface, const std::string& extIface);
+ bool isAnyForwardingEnabledOnUpstream(const std::string& extIface);
+ bool isAnyForwardingPairEnabled();
+ bool tetherCountingRuleExists(const std::string& iface1, const std::string& iface2);
int setDefaults();
int setForwardRules(bool set, const char *intIface, const char *extIface);
diff --git a/server/TetherControllerTest.cpp b/server/TetherControllerTest.cpp
index ba101fd..bcdb106 100644
--- a/server/TetherControllerTest.cpp
+++ b/server/TetherControllerTest.cpp
@@ -116,9 +116,7 @@
template<typename T>
void appendAll(std::vector<T>& cmds, const std::vector<T>& appendCmds) {
- for (auto& cmd : appendCmds) {
- cmds.push_back(cmd);
- }
+ cmds.insert(cmds.end(), appendCmds.begin(), appendCmds.end());
}
ExpectedIptablesCommands startNatCommands(const char *intIf, const char *extIf,
@@ -167,6 +165,29 @@
};
}
+ constexpr static const bool WITH_COUNTERS = true;
+ constexpr static const bool NO_COUNTERS = false;
+ constexpr static const bool WITH_IPV6 = true;
+ constexpr static const bool NO_IPV6 = false;
+ ExpectedIptablesCommands allNewNatCommands(
+ const char *intIf, const char *extIf, bool withCounterChainRules,
+ bool withIPv6Upstream) {
+
+ ExpectedIptablesCommands commands;
+ ExpectedIptablesCommands setupFirstIPv4Commands = firstIPv4UpstreamCommands(extIf);
+ ExpectedIptablesCommands startFirstNatCommands = startNatCommands(intIf, extIf,
+ withCounterChainRules);
+
+ appendAll(commands, setupFirstIPv4Commands);
+ if (withIPv6Upstream) {
+ ExpectedIptablesCommands setupFirstIPv6Commands = firstIPv6UpstreamCommands();
+ appendAll(commands, setupFirstIPv6Commands);
+ }
+ appendAll(commands, startFirstNatCommands);
+
+ return commands;
+ }
+
ExpectedIptablesCommands stopNatCommands(const char *intIf, const char *extIf) {
std::string rpfilterCmd = StringPrintf(
"*raw\n"
@@ -204,18 +225,14 @@
TEST_F(TetherControllerTest, TestAddAndRemoveNat) {
// Start first NAT on first upstream interface. Expect the upstream and NAT rules to be created.
- ExpectedIptablesCommands firstNat;
- ExpectedIptablesCommands setupFirstIPv4Commands = firstIPv4UpstreamCommands("rmnet0");
- ExpectedIptablesCommands setupFirstIPv6Commands = firstIPv6UpstreamCommands();
- ExpectedIptablesCommands startFirstNatCommands = startNatCommands("wlan0", "rmnet0", true);
- appendAll(firstNat, setupFirstIPv4Commands);
- appendAll(firstNat, setupFirstIPv6Commands);
- appendAll(firstNat, startFirstNatCommands);
+ ExpectedIptablesCommands firstNat = allNewNatCommands(
+ "wlan0", "rmnet0", WITH_COUNTERS, WITH_IPV6);
mTetherCtrl.enableNat("wlan0", "rmnet0");
expectIptablesRestoreCommands(firstNat);
// Start second NAT on same upstream. Expect only the counter rules to be created.
- ExpectedIptablesCommands startOtherNatOnSameUpstream = startNatCommands("usb0", "rmnet0", true);
+ ExpectedIptablesCommands startOtherNatOnSameUpstream = startNatCommands(
+ "usb0", "rmnet0", WITH_COUNTERS);
mTetherCtrl.enableNat("usb0", "rmnet0");
expectIptablesRestoreCommands(startOtherNatOnSameUpstream);
@@ -231,13 +248,8 @@
mTetherCtrl.disableNat("usb0", "rmnet0");
expectIptablesRestoreCommands(stopLastNat);
- // Re-add a NAT removed previously
- firstNat = {};
- // tetherctrl_counters chain rules are not re-added
- startFirstNatCommands = startNatCommands("wlan0", "rmnet0", false);
- appendAll(firstNat, setupFirstIPv4Commands);
- appendAll(firstNat, setupFirstIPv6Commands);
- appendAll(firstNat, startFirstNatCommands);
+ // Re-add a NAT removed previously: tetherctrl_counters chain rules are not re-added
+ firstNat = allNewNatCommands("wlan0", "rmnet0", NO_COUNTERS, WITH_IPV6);
mTetherCtrl.enableNat("wlan0", "rmnet0");
expectIptablesRestoreCommands(firstNat);
@@ -248,6 +260,38 @@
expectIptablesRestoreCommands(stopLastNat);
}
+TEST_F(TetherControllerTest, TestMultipleUpstreams) {
+ // Start first NAT on first upstream interface. Expect the upstream and NAT rules to be created.
+ ExpectedIptablesCommands firstNat = allNewNatCommands(
+ "wlan0", "rmnet0", WITH_COUNTERS, WITH_IPV6);
+ mTetherCtrl.enableNat("wlan0", "rmnet0");
+ expectIptablesRestoreCommands(firstNat);
+
+ // Start second NAT, on new upstream. Expect the upstream and NAT rules to be created for IPv4,
+ // but no counter rules for IPv6.
+ ExpectedIptablesCommands secondNat = allNewNatCommands(
+ "wlan0", "v4-rmnet0", WITH_COUNTERS, NO_IPV6);
+ mTetherCtrl.enableNat("wlan0", "v4-rmnet0");
+ expectIptablesRestoreCommands(secondNat);
+
+ // Pretend that the caller has forgotten that it set up the second NAT, and asks us to do so
+ // again. Expect that we take no action.
+ const ExpectedIptablesCommands NONE = {};
+ mTetherCtrl.enableNat("wlan0", "v4-rmnet0");
+ expectIptablesRestoreCommands(NONE);
+
+ // Remove the second NAT.
+ ExpectedIptablesCommands stopSecondNat = stopNatCommands("wlan0", "v4-rmnet0");
+ mTetherCtrl.disableNat("wlan0", "v4-rmnet0");
+ expectIptablesRestoreCommands(stopSecondNat);
+
+ // Remove the first NAT. Expect rules to be cleared.
+ ExpectedIptablesCommands stopFirstNat = stopNatCommands("wlan0", "rmnet0");
+ appendAll(stopFirstNat, FLUSH_COMMANDS);
+ mTetherCtrl.disableNat("wlan0", "rmnet0");
+ expectIptablesRestoreCommands(stopFirstNat);
+}
+
std::string kTetherCounterHeaders = Join(std::vector<std::string> {
"Chain tetherctrl_counters (4 references)",
" pkts bytes target prot opt in out source destination",