1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.internal.net.ipsec.ike;
17 
18 import static android.net.ipsec.ike.IkeManager.getIkeLog;
19 import static android.net.ipsec.ike.IkeManager.getIkeMetrics;
20 
21 import android.net.ipsec.ike.exceptions.IkeException;
22 import android.os.Message;
23 import android.util.SparseArray;
24 
25 import com.android.internal.annotations.VisibleForTesting;
26 import com.android.internal.net.ipsec.ike.net.IkeConnectionController;
27 import com.android.internal.net.ipsec.ike.utils.IkeMetrics;
28 import com.android.internal.util.IState;
29 import com.android.internal.util.State;
30 import com.android.internal.util.StateMachine;
31 
32 import java.util.concurrent.Executor;
33 import java.util.concurrent.TimeUnit;
34 
35 /**
36  * This class represents the common information of both IkeSessionStateMachine and
37  * ChildSessionStateMachine
38  */
39 abstract class AbstractSessionStateMachine extends StateMachine {
40     private static final int CMD_SHARED_BASE = 0;
41     protected static final int CMD_CATEGORY_SIZE = 100;
42 
43     /**
44      * Commands of Child local request that will be used in both IkeSessionStateMachine and
45      * ChildSessionStateMachine.
46      */
47     protected static final int CMD_CHILD_LOCAL_REQUEST_BASE = CMD_SHARED_BASE;
48 
49     @VisibleForTesting
50     static final int CMD_LOCAL_REQUEST_CREATE_CHILD = CMD_CHILD_LOCAL_REQUEST_BASE + 1;
51 
52     @VisibleForTesting
53     static final int CMD_LOCAL_REQUEST_DELETE_CHILD = CMD_CHILD_LOCAL_REQUEST_BASE + 2;
54 
55     @VisibleForTesting
56     static final int CMD_LOCAL_REQUEST_REKEY_CHILD = CMD_CHILD_LOCAL_REQUEST_BASE + 3;
57 
58     @VisibleForTesting
59     static final int CMD_LOCAL_REQUEST_REKEY_CHILD_MOBIKE = CMD_CHILD_LOCAL_REQUEST_BASE + 4;
60 
61     @VisibleForTesting
62     static final int CMD_LOCAL_REQUEST_MIGRATE_CHILD = CMD_CHILD_LOCAL_REQUEST_BASE + 5;
63 
64     static final int CMD_LOCAL_REQUEST_MIN = CMD_LOCAL_REQUEST_CREATE_CHILD;
65     static final int CMD_LOCAL_REQUEST_MAX = CMD_LOCAL_REQUEST_MIGRATE_CHILD;
66 
67     /** Timeout commands. */
68     protected static final int CMD_TIMEOUT_BASE = CMD_SHARED_BASE + CMD_CATEGORY_SIZE;
69     /** Timeout when the remote side fails to send a Rekey-Delete request. */
70     @VisibleForTesting static final int TIMEOUT_REKEY_REMOTE_DELETE = CMD_TIMEOUT_BASE + 1;
71 
72     /** Commands for generic usages */
73     protected static final int CMD_GENERIC_BASE = CMD_SHARED_BASE + 2 * CMD_CATEGORY_SIZE;
74     /** Force state machine to a target state for testing purposes. */
75     @VisibleForTesting static final int CMD_FORCE_TRANSITION = CMD_GENERIC_BASE + 1;
76     /** Force close the session. */
77     @VisibleForTesting static final int CMD_KILL_SESSION = CMD_GENERIC_BASE + 2;
78 
79     /** Private commands for subclasses */
80     protected static final int CMD_PRIVATE_BASE = CMD_SHARED_BASE + 3 * CMD_CATEGORY_SIZE;
81 
82     protected static final SparseArray<String> SHARED_CMD_TO_STR;
83 
84     static {
85         SHARED_CMD_TO_STR = new SparseArray<>();
SHARED_CMD_TO_STR.put(CMD_LOCAL_REQUEST_CREATE_CHILD, "Create Child")86         SHARED_CMD_TO_STR.put(CMD_LOCAL_REQUEST_CREATE_CHILD, "Create Child");
SHARED_CMD_TO_STR.put(CMD_LOCAL_REQUEST_DELETE_CHILD, "Delete Child")87         SHARED_CMD_TO_STR.put(CMD_LOCAL_REQUEST_DELETE_CHILD, "Delete Child");
SHARED_CMD_TO_STR.put(CMD_LOCAL_REQUEST_REKEY_CHILD, "Rekey Child")88         SHARED_CMD_TO_STR.put(CMD_LOCAL_REQUEST_REKEY_CHILD, "Rekey Child");
SHARED_CMD_TO_STR.put(CMD_LOCAL_REQUEST_MIGRATE_CHILD, "Migrate Child SA")89         SHARED_CMD_TO_STR.put(CMD_LOCAL_REQUEST_MIGRATE_CHILD, "Migrate Child SA");
SHARED_CMD_TO_STR.put(CMD_LOCAL_REQUEST_REKEY_CHILD_MOBIKE, "Rekey Child (MOBIKE)")90         SHARED_CMD_TO_STR.put(CMD_LOCAL_REQUEST_REKEY_CHILD_MOBIKE, "Rekey Child (MOBIKE)");
SHARED_CMD_TO_STR.put(CMD_KILL_SESSION, "Kill session")91         SHARED_CMD_TO_STR.put(CMD_KILL_SESSION, "Kill session");
SHARED_CMD_TO_STR.put(TIMEOUT_REKEY_REMOTE_DELETE, "Timout rekey remote delete")92         SHARED_CMD_TO_STR.put(TIMEOUT_REKEY_REMOTE_DELETE, "Timout rekey remote delete");
SHARED_CMD_TO_STR.put(CMD_FORCE_TRANSITION, "Force transition")93         SHARED_CMD_TO_STR.put(CMD_FORCE_TRANSITION, "Force transition");
94     }
95 
96     // Use a value greater than the retransmit-failure timeout.
97     static final long REKEY_DELETE_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(180L);
98 
99     // Default delay time for retrying a request
100     static final long RETRY_INTERVAL_MS = TimeUnit.SECONDS.toMillis(15L);
101 
102     @VisibleForTesting final IkeContext mIkeContext;
103 
104     protected final Executor mUserCbExecutor;
105     private final String mLogTag;
106 
107     protected volatile boolean mIsClosing = false;
108 
AbstractSessionStateMachine( String name, IkeContext ikeContext, Executor userCbExecutor)109     protected AbstractSessionStateMachine(
110             String name, IkeContext ikeContext, Executor userCbExecutor) {
111         super(name, ikeContext.getLooper());
112 
113         mIkeContext = ikeContext;
114         mLogTag = name;
115         mUserCbExecutor = userCbExecutor;
116     }
117 
118     /**
119      * Top level state for handling uncaught exceptions for all subclasses.
120      *
121      * <p>All other state in SessionStateMachine MUST extend this state.
122      *
123      * <p>Only errors this state should catch are unexpected internal failures. Since this may be
124      * run in critical processes, it must never take down the process if it fails
125      */
126     protected abstract class ExceptionHandlerBase extends State {
127         @Override
enter()128         public final void enter() {
129             try {
130                 enterState();
131             } catch (RuntimeException e) {
132                 cleanUpAndQuit(e);
133             }
134         }
135 
getCmdStr(int cmd)136         private String getCmdStr(int cmd) {
137             String cmdName = SHARED_CMD_TO_STR.get(cmd);
138             if (cmdName != null) {
139                 return cmdName;
140             }
141 
142             cmdName = getCmdString(cmd);
143             if (cmdName != null) {
144                 return cmdName;
145             }
146 
147             // Unrecognized message
148             return Integer.toString(cmd);
149         }
150 
151         @Override
processMessage(Message message)152         public final boolean processMessage(Message message) {
153             try {
154                 if (mIsClosing && message.what != CMD_KILL_SESSION) {
155                     logd(
156                             "Ignore "
157                                     + getCmdStr(message.what)
158                                     + " since this session is going to be closed");
159                     return HANDLED;
160                 } else {
161                     logd("processStateMessage: " + getCmdStr(message.what));
162                     return processStateMessage(message);
163                 }
164             } catch (RuntimeException e) {
165                 cleanUpAndQuit(e);
166                 return HANDLED;
167             }
168         }
169 
170         @Override
exit()171         public final void exit() {
172             try {
173                 exitState();
174             } catch (RuntimeException e) {
175                 cleanUpAndQuit(e);
176             }
177         }
178 
enterState()179         protected void enterState() {
180             // Do nothing. Subclasses MUST override it if they care.
181         }
182 
processStateMessage(Message message)183         protected boolean processStateMessage(Message message) {
184             return NOT_HANDLED;
185         }
186 
exitState()187         protected void exitState() {
188             // Do nothing. Subclasses MUST override it if they care.
189         }
190 
cleanUpAndQuit(RuntimeException e)191         protected abstract void cleanUpAndQuit(RuntimeException e);
192 
getCmdString(int cmd)193         protected abstract String getCmdString(int cmd);
194 
getMetricsStateCode()195         protected abstract @IkeMetrics.IkeState int getMetricsStateCode();
196     }
197 
executeUserCallback(Runnable r)198     protected void executeUserCallback(Runnable r) {
199         try {
200             mUserCbExecutor.execute(r);
201         } catch (Exception e) {
202             logd("Callback execution failed", e);
203         }
204     }
205 
206     /** Forcibly close this session. */
killSession()207     public void killSession() {
208         log("killSession");
209 
210         mIsClosing = true;
211         sendMessage(CMD_KILL_SESSION);
212     }
213 
214     /**
215      * Quit SessionStateMachine immediately.
216      *
217      * <p>This method pushes SM_QUIT_CMD in front of the message queue and mark mIsClosing as true.
218      * All currently queued messages will be discarded.
219      *
220      * <p>Subclasses MUST call this method instead of quitNow()
221      */
222     // quitNow() is a public final method in the base class. Thus there is no good way to prevent
223     // caller from calling it within the current inheritance structure.
quitSessionNow()224     protected void quitSessionNow() {
225         mIsClosing = true;
226         quitNow();
227     }
228 
getCurrentStateName()229     protected String getCurrentStateName() {
230         final IState state = getCurrentState();
231         if (state != null) {
232             return state.getName();
233         }
234 
235         return "Null State";
236     }
237 
getMetricsIkeStateCode()238     private @IkeMetrics.IkeState int getMetricsIkeStateCode() {
239         final IState currentState = getCurrentState();
240         return currentState instanceof ExceptionHandlerBase
241                 ? ((ExceptionHandlerBase) currentState).getMetricsStateCode()
242                 : IkeMetrics.IKE_STATE_UNKNOWN;
243     }
244 
recordMetricsEvent_sessionTerminated(IkeException exception)245     protected void recordMetricsEvent_sessionTerminated(IkeException exception) {
246         final @IkeMetrics.IkeError int exceptionCode =
247                 exception == null ? IkeMetrics.IKE_ERROR_NONE : exception.getMetricsErrorCode();
248 
249         getIkeMetrics()
250                 .logSessionTerminated(
251                         mIkeContext.getIkeCaller(),
252                         getMetricsSessionType(),
253                         getMetricsIkeStateCode(),
254                         exceptionCode);
255     }
256 
recordMetricsEvent_LivenssCheckCompletion( IkeConnectionController connectionController, int elapsedTimeInMillis, int numberOfOnGoing, boolean resultSuccess)257     protected void recordMetricsEvent_LivenssCheckCompletion(
258             IkeConnectionController connectionController,
259             int elapsedTimeInMillis,
260             int numberOfOnGoing,
261             boolean resultSuccess) {
262         getIkeMetrics()
263                 .logLivenessCheckCompleted(
264                         mIkeContext.getIkeCaller(),
265                         getMetricsIkeStateCode(),
266                         connectionController.getMetricsNetworkType(),
267                         elapsedTimeInMillis,
268                         numberOfOnGoing,
269                         resultSuccess);
270     }
271 
recordMetricsEvent_SaNegotiation( int dhGroup, int encryptionAlgorithm, int keyLength, int integrityAlgorithm, int prfAlgorithm, IkeException exception)272     protected void recordMetricsEvent_SaNegotiation(
273             int dhGroup,
274             int encryptionAlgorithm,
275             int keyLength,
276             int integrityAlgorithm,
277             int prfAlgorithm,
278             IkeException exception) {
279         final @IkeMetrics.IkeError int exceptionCode =
280                 exception == null ? IkeMetrics.IKE_ERROR_NONE : exception.getMetricsErrorCode();
281         getIkeMetrics()
282                 .logSaNegotiation(
283                         mIkeContext.getIkeCaller(),
284                         getMetricsSessionType(),
285                         getMetricsIkeStateCode(),
286                         dhGroup,
287                         encryptionAlgorithm,
288                         keyLength,
289                         integrityAlgorithm,
290                         prfAlgorithm,
291                         exceptionCode);
292     }
293 
getMetricsSessionType()294     protected abstract @IkeMetrics.IkeSessionType int getMetricsSessionType();
295 
296     @Override
log(String s)297     protected void log(String s) {
298         getIkeLog().d(mLogTag, s);
299     }
300 
301     @Override
logd(String s)302     protected void logd(String s) {
303         getIkeLog().d(mLogTag, s);
304     }
305 
logd(String s, Throwable e)306     protected void logd(String s, Throwable e) {
307         getIkeLog().d(mLogTag, s, e);
308     }
309 
310     @Override
logv(String s)311     protected void logv(String s) {
312         getIkeLog().v(mLogTag, s);
313     }
314 
315     @Override
logi(String s)316     protected void logi(String s) {
317         getIkeLog().i(mLogTag, s);
318     }
319 
logi(String s, Throwable cause)320     protected void logi(String s, Throwable cause) {
321         getIkeLog().i(mLogTag, s, cause);
322     }
323 
324     @Override
logw(String s)325     protected void logw(String s) {
326         getIkeLog().w(mLogTag, s);
327     }
328 
329     @Override
loge(String s)330     protected void loge(String s) {
331         getIkeLog().e(mLogTag, s);
332     }
333 
334     @Override
loge(String s, Throwable e)335     protected void loge(String s, Throwable e) {
336         getIkeLog().e(mLogTag, s, e);
337     }
338 
logWtf(String s)339     protected void logWtf(String s) {
340         getIkeLog().wtf(mLogTag, s);
341     }
342 
logWtf(String s, Throwable e)343     protected void logWtf(String s, Throwable e) {
344         getIkeLog().wtf(mLogTag, s, e);
345     }
346 }
347