Merge changes I0613c23f,Ibeab8d0a,I8bd668ad,I7d3a16be am: a1a975e149
am: 0d6211b065
Change-Id: Idca7b76f4e638aaa88ededcbfc890492152211e9
This commit is contained in:
@@ -925,6 +925,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
return IIpConnectivityMetrics.Stub.asInterface(
|
return IIpConnectivityMetrics.Stub.asInterface(
|
||||||
ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
|
ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IBatteryStats getBatteryStatsService() {
|
||||||
|
return BatteryStatsService.getService();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConnectivityService(Context context, INetworkManagementService netManager,
|
public ConnectivityService(Context context, INetworkManagementService netManager,
|
||||||
@@ -2144,7 +2148,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.M);
|
opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.M);
|
||||||
options = opts.toBundle();
|
options = opts.toBundle();
|
||||||
}
|
}
|
||||||
final IBatteryStats bs = BatteryStatsService.getService();
|
final IBatteryStats bs = mDeps.getBatteryStatsService();
|
||||||
try {
|
try {
|
||||||
bs.noteConnectivityChanged(intent.getIntExtra(
|
bs.noteConnectivityChanged(intent.getIntExtra(
|
||||||
ConnectivityManager.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_NONE),
|
ConnectivityManager.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_NONE),
|
||||||
@@ -5628,7 +5632,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
// are accurate.
|
// are accurate.
|
||||||
networkAgent.clatd.fixupLinkProperties(oldLp, newLp);
|
networkAgent.clatd.fixupLinkProperties(oldLp, newLp);
|
||||||
|
|
||||||
updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities);
|
updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities,
|
||||||
|
networkAgent.networkInfo.getType());
|
||||||
|
|
||||||
// update filtering rules, need to happen after the interface update so netd knows about the
|
// update filtering rules, need to happen after the interface update so netd knows about the
|
||||||
// new interface (the interface name -> index map becomes initialized)
|
// new interface (the interface name -> index map becomes initialized)
|
||||||
@@ -5707,21 +5712,26 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateInterfaces(LinkProperties newLp, LinkProperties oldLp, int netId,
|
private void updateInterfaces(final @Nullable LinkProperties newLp,
|
||||||
NetworkCapabilities caps) {
|
final @Nullable LinkProperties oldLp, final int netId,
|
||||||
CompareResult<String> interfaceDiff = new CompareResult<>(
|
final @Nullable NetworkCapabilities caps, final int legacyType) {
|
||||||
|
final CompareResult<String> interfaceDiff = new CompareResult<>(
|
||||||
oldLp != null ? oldLp.getAllInterfaceNames() : null,
|
oldLp != null ? oldLp.getAllInterfaceNames() : null,
|
||||||
newLp != null ? newLp.getAllInterfaceNames() : null);
|
newLp != null ? newLp.getAllInterfaceNames() : null);
|
||||||
for (String iface : interfaceDiff.added) {
|
if (!interfaceDiff.added.isEmpty()) {
|
||||||
try {
|
final IBatteryStats bs = mDeps.getBatteryStatsService();
|
||||||
if (DBG) log("Adding iface " + iface + " to network " + netId);
|
for (final String iface : interfaceDiff.added) {
|
||||||
mNMS.addInterfaceToNetwork(iface, netId);
|
try {
|
||||||
wakeupModifyInterface(iface, caps, true);
|
if (DBG) log("Adding iface " + iface + " to network " + netId);
|
||||||
} catch (Exception e) {
|
mNMS.addInterfaceToNetwork(iface, netId);
|
||||||
loge("Exception adding interface: " + e);
|
wakeupModifyInterface(iface, caps, true);
|
||||||
|
bs.noteNetworkInterfaceType(iface, legacyType);
|
||||||
|
} catch (Exception e) {
|
||||||
|
loge("Exception adding interface: " + e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (String iface : interfaceDiff.removed) {
|
for (final String iface : interfaceDiff.removed) {
|
||||||
try {
|
try {
|
||||||
if (DBG) log("Removing iface " + iface + " from network " + netId);
|
if (DBG) log("Removing iface " + iface + " from network " + netId);
|
||||||
wakeupModifyInterface(iface, caps, false);
|
wakeupModifyInterface(iface, caps, false);
|
||||||
@@ -6480,54 +6490,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
mLegacyTypeTracker.add(newNetwork.networkInfo.getType(), newNetwork);
|
mLegacyTypeTracker.add(newNetwork.networkInfo.getType(), newNetwork);
|
||||||
notifyLockdownVpn(newNetwork);
|
notifyLockdownVpn(newNetwork);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reassignedRequests.containsValue(newNetwork) || newNetwork.isVPN()) {
|
|
||||||
// Notify battery stats service about this network, both the normal
|
|
||||||
// interface and any stacked links.
|
|
||||||
// TODO: Avoid redoing this; this must only be done once when a network comes online.
|
|
||||||
try {
|
|
||||||
final IBatteryStats bs = BatteryStatsService.getService();
|
|
||||||
final int type = newNetwork.networkInfo.getType();
|
|
||||||
|
|
||||||
final String baseIface = newNetwork.linkProperties.getInterfaceName();
|
|
||||||
bs.noteNetworkInterfaceType(baseIface, type);
|
|
||||||
for (LinkProperties stacked : newNetwork.linkProperties.getStackedLinks()) {
|
|
||||||
final String stackedIface = stacked.getInterfaceName();
|
|
||||||
bs.noteNetworkInterfaceType(stackedIface, type);
|
|
||||||
}
|
|
||||||
} catch (RemoteException ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// This has to happen after the notifyNetworkCallbacks as that tickles each
|
|
||||||
// ConnectivityManager instance so that legacy requests correctly bind dns
|
|
||||||
// requests to this network. The legacy users are listening for this broadcast
|
|
||||||
// and will generally do a dns request so they can ensureRouteToHost and if
|
|
||||||
// they do that before the callbacks happen they'll use the default network.
|
|
||||||
//
|
|
||||||
// TODO: Is there still a race here? We send the broadcast
|
|
||||||
// after sending the callback, but if the app can receive the
|
|
||||||
// broadcast before the callback, it might still break.
|
|
||||||
//
|
|
||||||
// This *does* introduce a race where if the user uses the new api
|
|
||||||
// (notification callbacks) and then uses the old api (getNetworkInfo(type))
|
|
||||||
// they may get old info. Reverse this after the old startUsing api is removed.
|
|
||||||
// This is on top of the multiple intent sequencing referenced in the todo above.
|
|
||||||
for (int i = 0; i < newNetwork.numNetworkRequests(); i++) {
|
|
||||||
NetworkRequest nr = newNetwork.requestAt(i);
|
|
||||||
if (nr.legacyType != TYPE_NONE && nr.isRequest()) {
|
|
||||||
// legacy type tracker filters out repeat adds
|
|
||||||
mLegacyTypeTracker.add(nr.legacyType, newNetwork);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A VPN generally won't get added to the legacy tracker in the "for (nri)" loop above,
|
|
||||||
// because usually there are no NetworkRequests it satisfies (e.g., mDefaultRequest
|
|
||||||
// wants the NOT_VPN capability, so it will never be satisfied by a VPN). So, add the
|
|
||||||
// newNetwork to the tracker explicitly (it's a no-op if it has already been added).
|
|
||||||
if (newNetwork.isVPN()) {
|
|
||||||
mLegacyTypeTracker.add(TYPE_VPN, newNetwork);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -6550,6 +6512,26 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
for (NetworkAgentInfo nai : nais) {
|
for (NetworkAgentInfo nai : nais) {
|
||||||
rematchNetworkAndRequests(nai, now);
|
rematchNetworkAndRequests(nai, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now that all the callbacks have been sent, send the legacy network broadcasts
|
||||||
|
// as needed. This is necessary so that legacy requests correctly bind dns
|
||||||
|
// requests to this network. The legacy users are listening for this broadcast
|
||||||
|
// and will generally do a dns request so they can ensureRouteToHost and if
|
||||||
|
// they do that before the callbacks happen they'll use the default network.
|
||||||
|
//
|
||||||
|
// TODO: Is there still a race here? The legacy broadcast will be sent after sending
|
||||||
|
// callbacks, but if apps can receive the broadcast before the callback, they still might
|
||||||
|
// have an inconsistent view of networking.
|
||||||
|
//
|
||||||
|
// This *does* introduce a race where if the user uses the new api
|
||||||
|
// (notification callbacks) and then uses the old api (getNetworkInfo(type))
|
||||||
|
// they may get old info. Reverse this after the old startUsing api is removed.
|
||||||
|
// This is on top of the multiple intent sequencing referenced in the todo above.
|
||||||
|
for (NetworkAgentInfo nai : nais) {
|
||||||
|
addNetworkToLegacyTypeTracker(nai);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tear down all unneeded networks.
|
||||||
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
|
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
|
||||||
if (unneeded(nai, UnneededFor.TEARDOWN)) {
|
if (unneeded(nai, UnneededFor.TEARDOWN)) {
|
||||||
if (nai.getLingerExpiry() > 0) {
|
if (nai.getLingerExpiry() > 0) {
|
||||||
@@ -6569,6 +6551,24 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addNetworkToLegacyTypeTracker(@NonNull final NetworkAgentInfo nai) {
|
||||||
|
for (int i = 0; i < nai.numNetworkRequests(); i++) {
|
||||||
|
NetworkRequest nr = nai.requestAt(i);
|
||||||
|
if (nr.legacyType != TYPE_NONE && nr.isRequest()) {
|
||||||
|
// legacy type tracker filters out repeat adds
|
||||||
|
mLegacyTypeTracker.add(nr.legacyType, nai);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A VPN generally won't get added to the legacy tracker in the "for (nri)" loop above,
|
||||||
|
// because usually there are no NetworkRequests it satisfies (e.g., mDefaultRequest
|
||||||
|
// wants the NOT_VPN capability, so it will never be satisfied by a VPN). So, add the
|
||||||
|
// newNetwork to the tracker explicitly (it's a no-op if it has already been added).
|
||||||
|
if (nai.isVPN()) {
|
||||||
|
mLegacyTypeTracker.add(TYPE_VPN, nai);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updateInetCondition(NetworkAgentInfo nai) {
|
private void updateInetCondition(NetworkAgentInfo nai) {
|
||||||
// Don't bother updating until we've graduated to validated at least once.
|
// Don't bother updating until we've graduated to validated at least once.
|
||||||
if (!nai.everValidated) return;
|
if (!nai.everValidated) return;
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ import static org.junit.Assert.fail;
|
|||||||
import static org.mockito.ArgumentMatchers.anyLong;
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.ArgumentMatchers.startsWith;
|
||||||
import static org.mockito.Matchers.anyInt;
|
import static org.mockito.Matchers.anyInt;
|
||||||
import static org.mockito.Mockito.any;
|
import static org.mockito.Mockito.any;
|
||||||
import static org.mockito.Mockito.atLeastOnce;
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
@@ -198,6 +199,7 @@ import androidx.test.filters.FlakyTest;
|
|||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.android.internal.app.IBatteryStats;
|
||||||
import com.android.internal.net.VpnConfig;
|
import com.android.internal.net.VpnConfig;
|
||||||
import com.android.internal.net.VpnInfo;
|
import com.android.internal.net.VpnInfo;
|
||||||
import com.android.internal.util.ArrayUtils;
|
import com.android.internal.util.ArrayUtils;
|
||||||
@@ -305,6 +307,7 @@ public class ConnectivityServiceTest {
|
|||||||
@Mock DefaultNetworkMetrics mDefaultNetworkMetrics;
|
@Mock DefaultNetworkMetrics mDefaultNetworkMetrics;
|
||||||
@Mock INetworkManagementService mNetworkManagementService;
|
@Mock INetworkManagementService mNetworkManagementService;
|
||||||
@Mock INetworkStatsService mStatsService;
|
@Mock INetworkStatsService mStatsService;
|
||||||
|
@Mock IBatteryStats mBatteryStatsService;
|
||||||
@Mock INetworkPolicyManager mNpm;
|
@Mock INetworkPolicyManager mNpm;
|
||||||
@Mock IDnsResolver mMockDnsResolver;
|
@Mock IDnsResolver mMockDnsResolver;
|
||||||
@Mock INetd mMockNetd;
|
@Mock INetd mMockNetd;
|
||||||
@@ -1135,6 +1138,7 @@ public class ConnectivityServiceTest {
|
|||||||
doReturn(mMetricsService).when(deps).getMetricsLogger();
|
doReturn(mMetricsService).when(deps).getMetricsLogger();
|
||||||
doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt());
|
doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt());
|
||||||
doReturn(mIpConnectivityMetrics).when(deps).getIpConnectivityMetrics();
|
doReturn(mIpConnectivityMetrics).when(deps).getIpConnectivityMetrics();
|
||||||
|
doReturn(mBatteryStatsService).when(deps).getBatteryStatsService();
|
||||||
doReturn(true).when(deps).hasService(Context.ETHERNET_SERVICE);
|
doReturn(true).when(deps).hasService(Context.ETHERNET_SERVICE);
|
||||||
doAnswer(inv -> {
|
doAnswer(inv -> {
|
||||||
mPolicyTracker = new WrappedMultinetworkPolicyTracker(
|
mPolicyTracker = new WrappedMultinetworkPolicyTracker(
|
||||||
@@ -5640,6 +5644,36 @@ public class ConnectivityServiceTest {
|
|||||||
mCm.unregisterNetworkCallback(defaultCallback);
|
mCm.unregisterNetworkCallback(defaultCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public final void testBatteryStatsNetworkType() throws Exception {
|
||||||
|
final LinkProperties cellLp = new LinkProperties();
|
||||||
|
cellLp.setInterfaceName("cell0");
|
||||||
|
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
|
||||||
|
mCellNetworkAgent.connect(true);
|
||||||
|
waitForIdle();
|
||||||
|
verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(),
|
||||||
|
TYPE_MOBILE);
|
||||||
|
reset(mBatteryStatsService);
|
||||||
|
|
||||||
|
final LinkProperties wifiLp = new LinkProperties();
|
||||||
|
wifiLp.setInterfaceName("wifi0");
|
||||||
|
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
|
||||||
|
mWiFiNetworkAgent.connect(true);
|
||||||
|
waitForIdle();
|
||||||
|
verify(mBatteryStatsService).noteNetworkInterfaceType(wifiLp.getInterfaceName(),
|
||||||
|
TYPE_WIFI);
|
||||||
|
reset(mBatteryStatsService);
|
||||||
|
|
||||||
|
mCellNetworkAgent.disconnect();
|
||||||
|
|
||||||
|
cellLp.setInterfaceName("wifi0");
|
||||||
|
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
|
||||||
|
mCellNetworkAgent.connect(true);
|
||||||
|
waitForIdle();
|
||||||
|
verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(),
|
||||||
|
TYPE_MOBILE);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make simulated InterfaceConfig for Nat464Xlat to query clat lower layer info.
|
* Make simulated InterfaceConfig for Nat464Xlat to query clat lower layer info.
|
||||||
*/
|
*/
|
||||||
@@ -5680,25 +5714,28 @@ public class ConnectivityServiceTest {
|
|||||||
mCm.registerNetworkCallback(networkRequest, networkCallback);
|
mCm.registerNetworkCallback(networkRequest, networkCallback);
|
||||||
|
|
||||||
// Prepare ipv6 only link properties.
|
// Prepare ipv6 only link properties.
|
||||||
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
|
|
||||||
final int cellNetId = mCellNetworkAgent.getNetwork().netId;
|
|
||||||
final LinkProperties cellLp = new LinkProperties();
|
final LinkProperties cellLp = new LinkProperties();
|
||||||
cellLp.setInterfaceName(MOBILE_IFNAME);
|
cellLp.setInterfaceName(MOBILE_IFNAME);
|
||||||
cellLp.addLinkAddress(myIpv6);
|
cellLp.addLinkAddress(myIpv6);
|
||||||
cellLp.addRoute(new RouteInfo((IpPrefix) null, myIpv6.getAddress(), MOBILE_IFNAME));
|
cellLp.addRoute(new RouteInfo((IpPrefix) null, myIpv6.getAddress(), MOBILE_IFNAME));
|
||||||
cellLp.addRoute(new RouteInfo(myIpv6, null, MOBILE_IFNAME));
|
cellLp.addRoute(new RouteInfo(myIpv6, null, MOBILE_IFNAME));
|
||||||
|
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
|
||||||
reset(mNetworkManagementService);
|
reset(mNetworkManagementService);
|
||||||
reset(mMockDnsResolver);
|
reset(mMockDnsResolver);
|
||||||
reset(mMockNetd);
|
reset(mMockNetd);
|
||||||
|
reset(mBatteryStatsService);
|
||||||
when(mNetworkManagementService.getInterfaceConfig(CLAT_PREFIX + MOBILE_IFNAME))
|
when(mNetworkManagementService.getInterfaceConfig(CLAT_PREFIX + MOBILE_IFNAME))
|
||||||
.thenReturn(getClatInterfaceConfig(myIpv4));
|
.thenReturn(getClatInterfaceConfig(myIpv4));
|
||||||
|
|
||||||
// Connect with ipv6 link properties. Expect prefix discovery to be started.
|
// Connect with ipv6 link properties. Expect prefix discovery to be started.
|
||||||
mCellNetworkAgent.sendLinkProperties(cellLp);
|
|
||||||
mCellNetworkAgent.connect(true);
|
mCellNetworkAgent.connect(true);
|
||||||
|
final int cellNetId = mCellNetworkAgent.getNetwork().netId;
|
||||||
|
waitForIdle();
|
||||||
|
|
||||||
verify(mMockNetd, times(1)).networkCreatePhysical(eq(cellNetId), anyInt());
|
verify(mMockNetd, times(1)).networkCreatePhysical(eq(cellNetId), anyInt());
|
||||||
verify(mMockDnsResolver, times(1)).createNetworkCache(eq(cellNetId));
|
verify(mMockDnsResolver, times(1)).createNetworkCache(eq(cellNetId));
|
||||||
|
verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(),
|
||||||
|
TYPE_MOBILE);
|
||||||
|
|
||||||
networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
|
networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
|
||||||
verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId);
|
verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId);
|
||||||
@@ -5714,6 +5751,11 @@ public class ConnectivityServiceTest {
|
|||||||
verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId);
|
verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId);
|
||||||
verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(any());
|
verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(any());
|
||||||
|
|
||||||
|
// Make sure BatteryStats was not told about any v4- interfaces, as none should have
|
||||||
|
// come online yet.
|
||||||
|
waitForIdle();
|
||||||
|
verify(mBatteryStatsService, never()).noteNetworkInterfaceType(startsWith("v4-"), anyInt());
|
||||||
|
|
||||||
verifyNoMoreInteractions(mMockNetd);
|
verifyNoMoreInteractions(mMockNetd);
|
||||||
verifyNoMoreInteractions(mMockDnsResolver);
|
verifyNoMoreInteractions(mMockDnsResolver);
|
||||||
reset(mMockNetd);
|
reset(mMockNetd);
|
||||||
@@ -5760,6 +5802,11 @@ public class ConnectivityServiceTest {
|
|||||||
assertEquals(1, resolvrParams.servers.length);
|
assertEquals(1, resolvrParams.servers.length);
|
||||||
assertTrue(ArrayUtils.contains(resolvrParams.servers, "8.8.8.8"));
|
assertTrue(ArrayUtils.contains(resolvrParams.servers, "8.8.8.8"));
|
||||||
|
|
||||||
|
for (final LinkProperties stackedLp : stackedLpsAfterChange) {
|
||||||
|
verify(mBatteryStatsService).noteNetworkInterfaceType(stackedLp.getInterfaceName(),
|
||||||
|
TYPE_MOBILE);
|
||||||
|
}
|
||||||
|
|
||||||
// Add ipv4 address, expect that clatd and prefix discovery are stopped and stacked
|
// Add ipv4 address, expect that clatd and prefix discovery are stopped and stacked
|
||||||
// linkproperties are cleaned up.
|
// linkproperties are cleaned up.
|
||||||
cellLp.addLinkAddress(myIpv4);
|
cellLp.addLinkAddress(myIpv4);
|
||||||
|
|||||||
Reference in New Issue
Block a user