blob: f863d3d4f14dba836143c8915f7f2ce0b4ea5891 [file] [log] [blame]
/*
* Copyright (C) 2017 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.
*/
package com.android.server.lowpan;
import android.annotation.NonNull;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
import android.net.NetworkFactory;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.ip.IIpClient;
import android.net.ip.IpClientCallbacks;
import android.net.ip.IpClientUtil;
import android.net.lowpan.ILowpanInterface;
import android.net.lowpan.LowpanException;
import android.net.lowpan.LowpanInterface;
import android.net.lowpan.LowpanRuntimeException;
import android.net.shared.InitialConfiguration;
import android.net.shared.ProvisioningConfiguration;
import android.os.ConditionVariable;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Log;
import com.android.internal.util.HexDump;
import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
/** Tracks connectivity of a LoWPAN interface. */
class LowpanInterfaceTracker extends StateMachine {
// Misc Constants
/** Network type string for NetworkInfo */
private static final String NETWORK_TYPE = "LoWPAN";
/** Tag used for logging */
private static final String TAG = "LowpanInterfaceTracker";
/**
* Maximum network score for LoWPAN networks.
*
* <p>TODO: Research if 30 is an appropriate value.
*/
private static final int NETWORK_SCORE = 30;
// TODO: create IpClient asynchronously
private static final int IPCLIENT_CREATE_TIMEOUT_MS = 10000;
/** Internal debugging flag. */
private static final boolean DBG = true;
/** Number of state machine log records. */
public static final short NUM_LOG_RECS_NORMAL = 100;
// Message Code Enumeration Constants
/** The base for LoWPAN message codes */
static final int BASE = Protocol.BASE_LOWPAN;
static final int CMD_START_NETWORK = BASE + 3;
static final int CMD_STOP_NETWORK = BASE + 4;
static final int CMD_STATE_CHANGE = BASE + 5;
static final int CMD_LINK_PROPERTIES_CHANGE = BASE + 6;
static final int CMD_UNWANTED = BASE + 7;
static final int CMD_PROVISIONING_SUCCESS = BASE + 8;
static final int CMD_PROVISIONING_FAILURE = BASE + 9;
// Services and interfaces
ILowpanInterface mILowpanInterface;
private LowpanInterface mLowpanInterface;
private NetworkAgent mNetworkAgent;
private NetworkFactory mNetworkFactory;
private volatile IIpClient mIpClient;
private final IpClientCallbackImpl mIpClientCallback = new IpClientCallbackImpl();
// Instance Variables
private String mInterfaceName;
private String mHwAddr;
private Context mContext;
private NetworkInfo mNetworkInfo;
private LinkProperties mLinkProperties;
private final NetworkCapabilities mNetworkCapabilities = new NetworkCapabilities();
private String mState = "";
// State machine state instances
final DefaultState mDefaultState = new DefaultState();
final NormalState mNormalState = new NormalState();
final OfflineState mOfflineState = new OfflineState();
final CommissioningState mCommissioningState = new CommissioningState();
final AttachingState mAttachingState = new AttachingState();
final AttachedState mAttachedState = new AttachedState();
final ObtainingIpState mObtainingIpState = new ObtainingIpState();
final FaultState mFaultState = new FaultState();
final ConnectedState mConnectedState = new ConnectedState();
private LocalLowpanCallback mLocalLowpanCallback = new LocalLowpanCallback();
// Misc Private Classes
private class LocalLowpanCallback extends LowpanInterface.Callback {
@Override
public void onEnabledChanged(boolean value) {}
@Override
public void onUpChanged(boolean value) {}
@Override
public void onConnectedChanged(boolean value) {}
@Override
public void onStateChanged(@NonNull String state) {
LowpanInterfaceTracker.this.sendMessage(CMD_STATE_CHANGE, state);
}
}
class IpClientCallbackImpl extends IpClientCallbacks {
private final ConditionVariable mObtainedCv = new ConditionVariable(false);
@Override
public void onIpClientCreated(IIpClient ipClient) {
mIpClient = ipClient;
mObtainedCv.open();
}
public boolean waitUntilCreated() {
return mObtainedCv.block(IPCLIENT_CREATE_TIMEOUT_MS);
}
@Override
public void onProvisioningSuccess(LinkProperties newLp) {
LowpanInterfaceTracker.this.sendMessage(CMD_PROVISIONING_SUCCESS, newLp);
}
@Override
public void onProvisioningFailure(LinkProperties newLp) {
LowpanInterfaceTracker.this.sendMessage(CMD_PROVISIONING_FAILURE, newLp);
}
@Override
public void onLinkPropertiesChange(LinkProperties newLp) {
LowpanInterfaceTracker.this.sendMessage(CMD_LINK_PROPERTIES_CHANGE, newLp);
}
}
// State Definitions
class DefaultState extends State {
@Override
public void enter() {
if (DBG) {
Log.i(TAG, "DefaultState.enter()");
}
mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_NONE, 0, NETWORK_TYPE, "");
mNetworkInfo.setIsAvailable(true);
mLowpanInterface.registerCallback(mLocalLowpanCallback);
mState = "";
sendMessage(CMD_STATE_CHANGE, mLowpanInterface.getState());
}
@Override
public boolean processMessage(Message message) {
boolean retValue = NOT_HANDLED;
switch (message.what) {
case CMD_START_NETWORK:
if (DBG) {
Log.i(TAG, "CMD_START_NETWORK");
}
try {
mLowpanInterface.setEnabled(true);
} catch (LowpanException | LowpanRuntimeException x) {
Log.e(TAG, "Exception while enabling: " + x);
transitionTo(mFaultState);
return HANDLED;
}
break;
case CMD_STOP_NETWORK:
if (DBG) {
Log.i(TAG, "CMD_STOP_NETWORK");
}
try {
mLowpanInterface.setEnabled(false);
} catch (LowpanException | LowpanRuntimeException x) {
Log.e(TAG, "Exception while disabling: " + x);
transitionTo(mFaultState);
return HANDLED;
}
break;
case CMD_STATE_CHANGE:
if (!mState.equals(message.obj)) {
if (DBG) {
Log.i(
TAG,
"LowpanInterface changed state from \""
+ mState
+ "\" to \""
+ message.obj
+ "\".");
}
mState = (String) message.obj;
switch (mState) {
case LowpanInterface.STATE_OFFLINE:
transitionTo(mOfflineState);
break;
case LowpanInterface.STATE_COMMISSIONING:
transitionTo(mCommissioningState);
break;
case LowpanInterface.STATE_ATTACHING:
transitionTo(mAttachingState);
break;
case LowpanInterface.STATE_ATTACHED:
transitionTo(mObtainingIpState);
break;
case LowpanInterface.STATE_FAULT:
transitionTo(mFaultState);
break;
}
}
retValue = HANDLED;
break;
}
return retValue;
}
@Override
public void exit() {
mLowpanInterface.unregisterCallback(mLocalLowpanCallback);
}
}
class NormalState extends State {
@Override
public void enter() {
if (DBG) {
Log.i(TAG, "NormalState.enter()");
}
IpClientUtil.makeIpClient(mContext, mInterfaceName, mIpClientCallback);
if (!mIpClientCallback.waitUntilCreated()) {
Log.e(TAG, "Failed to create IpClient");
transitionTo(mFaultState);
return;
}
if (mHwAddr == null) {
byte[] hwAddr = null;
try {
hwAddr = mLowpanInterface.getService().getMacAddress();
} catch (RemoteException | ServiceSpecificException x) {
// Don't let misbehavior of the interface service
// crash the system service.
Log.e(TAG, "Call to getMacAddress() failed: " + x);
transitionTo(mFaultState);
}
if (hwAddr != null) {
mHwAddr = HexDump.toHexString(hwAddr);
}
}
mNetworkFactory.register();
}
@Override
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_UNWANTED:
if (mNetworkAgent == message.obj) {
if (DBG) {
Log.i(TAG, "UNWANTED.");
}
try {
mLowpanInterface.setEnabled(false);
} catch (LowpanException | LowpanRuntimeException x) {
Log.e(TAG, "Exception while disabling: " + x);
transitionTo(mFaultState);
return HANDLED;
}
shutdownNetworkAgent();
}
break;
case CMD_LINK_PROPERTIES_CHANGE:
mLinkProperties = (LinkProperties) message.obj;
if (DBG) {
Log.i(TAG, "Got LinkProperties: " + mLinkProperties);
}
if (mNetworkAgent != null) {
mNetworkAgent.sendLinkProperties(mLinkProperties);
}
break;
case CMD_PROVISIONING_FAILURE:
Log.i(TAG, "Provisioning Failure: " + message.obj);
break;
}
return NOT_HANDLED;
}
@Override
public void exit() {
shutdownNetworkAgent();
mNetworkFactory.unregister();
if (mIpClient != null) {
try {
mIpClient.shutdown();
} catch (RemoteException e) {
Log.e(TAG, "Error shutting down IpClient", e);
}
}
mIpClient = null;
}
}
class OfflineState extends State {
@Override
public void enter() {
shutdownNetworkAgent();
mNetworkInfo.setIsAvailable(true);
try {
mIpClient.stop();
} catch (RemoteException e) {
Log.e(TAG, "Error stopping IpClient");
}
}
@Override
public boolean processMessage(Message message) {
return NOT_HANDLED;
}
@Override
public void exit() {}
}
class CommissioningState extends State {
@Override
public void enter() {}
@Override
public boolean processMessage(Message message) {
return NOT_HANDLED;
}
@Override
public void exit() {}
}
class AttachingState extends State {
@Override
public void enter() {
mNetworkInfo.setDetailedState(DetailedState.CONNECTING, null, mHwAddr);
mNetworkInfo.setIsAvailable(true);
bringUpNetworkAgent();
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
}
@Override
public boolean processMessage(Message message) {
return NOT_HANDLED;
}
@Override
public void exit() {}
}
class AttachedState extends State {
@Override
public void enter() {
bringUpNetworkAgent();
mNetworkInfo.setIsAvailable(true);
}
@Override
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_STATE_CHANGE:
if (!mState.equals(message.obj)
&& !LowpanInterface.STATE_ATTACHED.equals(message.obj)) {
return NOT_HANDLED;
}
return HANDLED;
default:
return NOT_HANDLED;
}
}
@Override
public void exit() {
mNetworkInfo.setIsAvailable(false);
}
}
class ObtainingIpState extends State {
@Override
public void enter() {
InitialConfiguration initialConfiguration = new InitialConfiguration();
try {
for (LinkAddress address : mLowpanInterface.getLinkAddresses()) {
if (DBG) {
Log.i(TAG, "Adding link address: " + address);
}
initialConfiguration.ipAddresses.add(address);
IpPrefix prefix = new IpPrefix(address.getAddress(), address.getPrefixLength());
initialConfiguration.directlyConnectedRoutes.add(prefix);
}
for (IpPrefix prefix : mLowpanInterface.getLinkNetworks()) {
if (DBG) {
Log.i(TAG, "Adding directly connected route: " + prefix);
}
initialConfiguration.directlyConnectedRoutes.add(prefix);
}
} catch (LowpanException | LowpanRuntimeException x) {
Log.e(TAG, "Exception while populating InitialConfiguration: " + x);
transitionTo(mFaultState);
return;
} catch (RuntimeException x) {
if (x.getCause() instanceof RemoteException) {
// Don't let misbehavior of an interface service
// crash the system service.
Log.e(TAG, "RuntimeException while populating InitialConfiguration: " + x);
transitionTo(mFaultState);
} else {
// This exception wasn't remote in origin, so we rethrow.
throw x;
}
}
if (!initialConfiguration.isValid()) {
Log.e(TAG, "Invalid initial configuration: " + initialConfiguration);
transitionTo(mFaultState);
return;
}
if (DBG) {
Log.d(TAG, "Using Initial configuration: " + initialConfiguration);
}
final ProvisioningConfiguration.Builder builder =
new ProvisioningConfiguration.Builder();
builder.withInitialConfiguration(initialConfiguration).withProvisioningTimeoutMs(0);
// LoWPAN networks generally don't have internet connectivity,
// so the reachability monitor would almost always fail.
builder.withoutIpReachabilityMonitor();
// We currently only support IPv6 on LoWPAN networks, although
// theoretically we could make this determination by examining
// the InitialConfiguration for any IPv4 addresses.
builder.withoutIPv4();
try {
mIpClient.startProvisioning(builder.build().toStableParcelable());
} catch (RemoteException e) {
Log.e(TAG, "Error starting IpClient provisioning", e);
transitionTo(mFaultState);
return;
}
mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, mHwAddr);
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
}
@Override
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_PROVISIONING_SUCCESS:
Log.i(TAG, "Provisioning Success: " + message.obj);
transitionTo(mConnectedState);
return HANDLED;
}
return NOT_HANDLED;
}
@Override
public void exit() {}
}
class ConnectedState extends State {
@Override
public void enter() {
mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddr);
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
mNetworkAgent.sendNetworkScore(NETWORK_SCORE);
}
@Override
public boolean processMessage(Message message) {
return NOT_HANDLED;
}
@Override
public void exit() {
if (mNetworkAgent != null) {
mNetworkAgent.sendNetworkScore(0);
}
}
}
class FaultState extends State {
@Override
public void enter() {}
@Override
public boolean processMessage(Message message) {
return NOT_HANDLED;
}
@Override
public void exit() {}
}
public LowpanInterfaceTracker(Context context, ILowpanInterface ifaceService, Looper looper) {
super(TAG, looper);
if (DBG) {
Log.i(TAG, "LowpanInterfaceTracker() begin");
}
setDbg(DBG);
setLogRecSize(NUM_LOG_RECS_NORMAL);
setLogOnlyTransitions(false);
mILowpanInterface = ifaceService;
mLowpanInterface = new LowpanInterface(context, ifaceService, looper);
mContext = context;
mInterfaceName = mLowpanInterface.getName();
mLinkProperties = new LinkProperties();
mLinkProperties.setInterfaceName(mInterfaceName);
// Initialize capabilities
mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_LOWPAN);
mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
mNetworkCapabilities.setLinkUpstreamBandwidthKbps(100);
mNetworkCapabilities.setLinkDownstreamBandwidthKbps(100);
// CHECKSTYLE:OFF IndentationCheck
addState(mDefaultState);
addState(mFaultState, mDefaultState);
addState(mNormalState, mDefaultState);
addState(mOfflineState, mNormalState);
addState(mCommissioningState, mNormalState);
addState(mAttachingState, mNormalState);
addState(mAttachedState, mNormalState);
addState(mObtainingIpState, mAttachedState);
addState(mConnectedState, mAttachedState);
// CHECKSTYLE:ON IndentationCheck
setInitialState(mDefaultState);
mNetworkFactory =
new NetworkFactory(looper, context, NETWORK_TYPE, mNetworkCapabilities) {
@Override
protected void startNetwork() {
LowpanInterfaceTracker.this.sendMessage(CMD_START_NETWORK);
}
@Override
protected void stopNetwork() {
LowpanInterfaceTracker.this.sendMessage(CMD_STOP_NETWORK);
}
};
if (DBG) {
Log.i(TAG, "LowpanInterfaceTracker() end");
}
}
private void shutdownNetworkAgent() {
mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr);
mNetworkInfo.setIsAvailable(false);
if (mNetworkAgent != null) {
mNetworkAgent.sendNetworkScore(0);
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
}
mNetworkAgent = null;
}
private void bringUpNetworkAgent() {
if (mNetworkAgent == null) {
mNetworkAgent =
new NetworkAgent(
mNetworkFactory.getLooper(),
mContext,
NETWORK_TYPE,
mNetworkInfo,
mNetworkCapabilities,
mLinkProperties,
NETWORK_SCORE) {
public void unwanted() {
LowpanInterfaceTracker.this.sendMessage(CMD_UNWANTED, this);
};
};
}
}
public void register() {
if (DBG) {
Log.i(TAG, "register()");
}
start();
}
public void unregister() {
if (DBG) {
Log.i(TAG, "unregister()");
}
quit();
}
}