• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 
17 package com.android.server.net;
18 
19 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
20 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
21 import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
22 
23 import static org.mockito.ArgumentMatchers.any;
24 import static org.mockito.ArgumentMatchers.argThat;
25 import static org.mockito.ArgumentMatchers.eq;
26 import static org.mockito.Mockito.clearInvocations;
27 import static org.mockito.Mockito.doReturn;
28 import static org.mockito.Mockito.never;
29 import static org.mockito.Mockito.verify;
30 
31 import android.app.Notification;
32 import android.app.NotificationManager;
33 import android.content.Context;
34 import android.content.ContextWrapper;
35 import android.net.ConnectivityManager;
36 import android.net.ConnectivityManager.NetworkCallback;
37 import android.net.LinkAddress;
38 import android.net.LinkProperties;
39 import android.net.Network;
40 import android.net.NetworkCapabilities;
41 import android.net.NetworkInfo;
42 import android.os.Handler;
43 import android.os.HandlerThread;
44 import android.text.TextUtils;
45 
46 import androidx.test.InstrumentationRegistry;
47 import androidx.test.ext.junit.runners.AndroidJUnit4;
48 import androidx.test.filters.SmallTest;
49 
50 import com.android.internal.R;
51 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
52 import com.android.internal.net.VpnConfig;
53 import com.android.internal.net.VpnProfile;
54 import com.android.server.connectivity.Vpn;
55 
56 import org.junit.After;
57 import org.junit.Before;
58 import org.junit.Test;
59 import org.junit.runner.RunWith;
60 import org.mockito.ArgumentCaptor;
61 import org.mockito.Mock;
62 import org.mockito.MockitoAnnotations;
63 
64 import java.util.ArrayList;
65 
66 @RunWith(AndroidJUnit4.class)
67 @SmallTest
68 public class LockdownVpnTrackerTest {
69     private static final NetworkCapabilities TEST_CELL_NC = new NetworkCapabilities.Builder()
70             .addTransportType(TRANSPORT_CELLULAR)
71             .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
72             .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
73             .build();
74     private static final LinkProperties TEST_CELL_LP = new LinkProperties();
75 
76     static {
77         TEST_CELL_LP.setInterfaceName("rmnet0");
TEST_CELL_LP.addLinkAddress(new LinkAddress("192.0.2.2/25"))78         TEST_CELL_LP.addLinkAddress(new LinkAddress("192.0.2.2/25"));
79     }
80 
81     // Use a context wrapper instead of a mock since LockdownVpnTracker builds notifications which
82     // is tedious and currently unnecessary to mock.
83     private final Context mContext = new ContextWrapper(InstrumentationRegistry.getContext()) {
84         @Override
85         public Object getSystemService(String name) {
86             if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
87             if (Context.NOTIFICATION_SERVICE.equals(name)) return mNotificationManager;
88 
89             return super.getSystemService(name);
90         }
91     };
92     @Mock private ConnectivityManager mCm;
93     @Mock private Vpn mVpn;
94     @Mock private NotificationManager mNotificationManager;
95     @Mock private NetworkInfo mVpnNetworkInfo;
96     @Mock private VpnConfig mVpnConfig;
97     @Mock private Network mNetwork;
98     @Mock private Network mNetwork2;
99     @Mock private Network mVpnNetwork;
100 
101     private HandlerThread mHandlerThread;
102     private Handler mHandler;
103     private VpnProfile mProfile;
104 
createTestVpnProfile()105     private VpnProfile createTestVpnProfile() {
106         final String profileName = "testVpnProfile";
107         final VpnProfile profile = new VpnProfile(profileName);
108         profile.name = "My VPN";
109         profile.server = "192.0.2.1";
110         profile.dnsServers = "8.8.8.8";
111         profile.ipsecIdentifier = "My ipsecIdentifier";
112         profile.ipsecSecret = "My PSK";
113         profile.type = VpnProfile.TYPE_IKEV2_IPSEC_PSK;
114 
115         return profile;
116     }
117 
getDefaultNetworkCallback()118     private NetworkCallback getDefaultNetworkCallback() {
119         final ArgumentCaptor<NetworkCallback> callbackCaptor =
120                 ArgumentCaptor.forClass(NetworkCallback.class);
121         verify(mCm).registerSystemDefaultNetworkCallback(callbackCaptor.capture(), eq(mHandler));
122         return callbackCaptor.getValue();
123     }
124 
getVpnNetworkCallback()125     private NetworkCallback getVpnNetworkCallback() {
126         final ArgumentCaptor<NetworkCallback> callbackCaptor =
127                 ArgumentCaptor.forClass(NetworkCallback.class);
128         verify(mCm).registerNetworkCallback(any(), callbackCaptor.capture(), eq(mHandler));
129         return callbackCaptor.getValue();
130     }
131 
132     @Before
setUp()133     public void setUp() throws Exception {
134         MockitoAnnotations.initMocks(this);
135 
136         mHandlerThread = new HandlerThread("LockdownVpnTrackerTest");
137         mHandlerThread.start();
138         mHandler = mHandlerThread.getThreadHandler();
139 
140         doReturn(mVpnNetworkInfo).when(mVpn).getNetworkInfo();
141         doReturn(false).when(mVpnNetworkInfo).isConnectedOrConnecting();
142         doReturn(mVpnConfig).when(mVpn).getLegacyVpnConfig();
143         // mVpnConfig is a mock but the production code will try to add addresses in this array
144         // assuming it's non-null, so it needs to be initialized.
145         mVpnConfig.addresses = new ArrayList<>();
146 
147         mProfile = createTestVpnProfile();
148     }
149 
150     @After
tearDown()151     public void tearDown() throws Exception {
152         if (mHandlerThread != null) {
153             mHandlerThread.quitSafely();
154             mHandlerThread.join();
155         }
156     }
157 
initAndVerifyLockdownVpnTracker()158     private LockdownVpnTracker initAndVerifyLockdownVpnTracker() {
159         final LockdownVpnTracker lockdownVpnTracker =
160                 new LockdownVpnTracker(mContext, mHandler, mVpn, mProfile);
161         lockdownVpnTracker.init();
162         verify(mVpn).setEnableTeardown(false);
163         verify(mVpn).setLockdown(true);
164         verify(mCm).setLegacyLockdownVpnEnabled(true);
165         verify(mVpn).stopVpnRunnerPrivileged();
166         verify(mNotificationManager).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS));
167 
168         return lockdownVpnTracker;
169     }
170 
callCallbacksForNetworkConnect(NetworkCallback callback, Network network, NetworkCapabilities nc, LinkProperties lp, boolean blocked)171     private void callCallbacksForNetworkConnect(NetworkCallback callback, Network network,
172             NetworkCapabilities nc, LinkProperties lp, boolean blocked) {
173         callback.onAvailable(network);
174         callback.onCapabilitiesChanged(network, nc);
175         callback.onLinkPropertiesChanged(network, lp);
176         callback.onBlockedStatusChanged(network, blocked);
177     }
178 
callCallbacksForNetworkConnect(NetworkCallback callback, Network network)179     private void callCallbacksForNetworkConnect(NetworkCallback callback, Network network) {
180         callCallbacksForNetworkConnect(
181                 callback, network, TEST_CELL_NC, TEST_CELL_LP, true /* blocked */);
182     }
183 
isExpectedNotification(Notification notification, int titleRes, int iconRes)184     private boolean isExpectedNotification(Notification notification, int titleRes, int iconRes) {
185         if (!NOTIFICATION_CHANNEL_VPN.equals(notification.getChannelId())) {
186             return false;
187         }
188         final CharSequence expectedTitle = mContext.getString(titleRes);
189         final CharSequence actualTitle = notification.extras.getCharSequence(
190                 Notification.EXTRA_TITLE);
191         if (!TextUtils.equals(expectedTitle, actualTitle)) {
192             return false;
193         }
194         return notification.getSmallIcon().getResId() == iconRes;
195     }
196 
197     @Test
testShutdown()198     public void testShutdown() {
199         final LockdownVpnTracker lockdownVpnTracker = initAndVerifyLockdownVpnTracker();
200         final NetworkCallback defaultCallback = getDefaultNetworkCallback();
201         final NetworkCallback vpnCallback = getVpnNetworkCallback();
202         clearInvocations(mVpn, mCm, mNotificationManager);
203 
204         lockdownVpnTracker.shutdown();
205         verify(mVpn).stopVpnRunnerPrivileged();
206         verify(mVpn).setLockdown(false);
207         verify(mCm).setLegacyLockdownVpnEnabled(false);
208         verify(mNotificationManager).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS));
209         verify(mVpn).setEnableTeardown(true);
210         verify(mCm).unregisterNetworkCallback(defaultCallback);
211         verify(mCm).unregisterNetworkCallback(vpnCallback);
212     }
213 
214     @Test
testDefaultNetworkConnected()215     public void testDefaultNetworkConnected() {
216         initAndVerifyLockdownVpnTracker();
217         final NetworkCallback defaultCallback = getDefaultNetworkCallback();
218         clearInvocations(mVpn, mCm, mNotificationManager);
219 
220         // mNetwork connected and available.
221         callCallbacksForNetworkConnect(defaultCallback, mNetwork);
222 
223         // Vpn is starting
224         verify(mVpn).startLegacyVpnPrivileged(mProfile);
225         verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS),
226                 argThat(notification -> isExpectedNotification(notification,
227                         R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected)));
228     }
229 
doTestDefaultLpChanged(LinkProperties startingLp, LinkProperties newLp)230     private void doTestDefaultLpChanged(LinkProperties startingLp, LinkProperties newLp) {
231         initAndVerifyLockdownVpnTracker();
232         final NetworkCallback defaultCallback = getDefaultNetworkCallback();
233         callCallbacksForNetworkConnect(
234                 defaultCallback, mNetwork, TEST_CELL_NC, startingLp, true /* blocked */);
235         clearInvocations(mVpn, mCm, mNotificationManager);
236 
237         // LockdownVpnTracker#handleStateChangedLocked() is not called on the same network even if
238         // the LinkProperties change.
239         defaultCallback.onLinkPropertiesChanged(mNetwork, newLp);
240 
241         // Ideally the VPN should start if it hasn't already, but it doesn't because nothing calls
242         // LockdownVpnTracker#handleStateChangedLocked. This is a bug.
243         // TODO: consider fixing this.
244         verify(mVpn, never()).stopVpnRunnerPrivileged();
245         verify(mVpn, never()).startLegacyVpnPrivileged(any());
246         verify(mNotificationManager, never()).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS));
247     }
248 
249     @Test
testDefaultLPChanged_V4AddLinkAddressV4()250     public void testDefaultLPChanged_V4AddLinkAddressV4() {
251         final LinkProperties lp = new LinkProperties(TEST_CELL_LP);
252         lp.setInterfaceName("rmnet0");
253         lp.addLinkAddress(new LinkAddress("192.0.2.3/25"));
254         doTestDefaultLpChanged(TEST_CELL_LP, lp);
255     }
256 
257     @Test
testDefaultLPChanged_V4AddLinkAddressV6()258     public void testDefaultLPChanged_V4AddLinkAddressV6() {
259         final LinkProperties lp = new LinkProperties();
260         lp.setInterfaceName("rmnet0");
261         lp.addLinkAddress(new LinkAddress("192.0.2.3/25"));
262         final LinkProperties newLp = new LinkProperties(lp);
263         newLp.addLinkAddress(new LinkAddress("2001:db8::1/64"));
264         doTestDefaultLpChanged(lp, newLp);
265     }
266 
267     @Test
testDefaultLPChanged_V6AddLinkAddressV4()268     public void testDefaultLPChanged_V6AddLinkAddressV4() {
269         final LinkProperties lp = new LinkProperties();
270         lp.setInterfaceName("rmnet0");
271         lp.addLinkAddress(new LinkAddress("2001:db8::1/64"));
272         final LinkProperties newLp = new LinkProperties(lp);
273         newLp.addLinkAddress(new LinkAddress("192.0.2.3/25"));
274         doTestDefaultLpChanged(lp, newLp);
275     }
276 
277     @Test
testDefaultLPChanged_AddLinkAddressV4()278     public void testDefaultLPChanged_AddLinkAddressV4() {
279         final LinkProperties lp = new LinkProperties();
280         lp.setInterfaceName("rmnet0");
281         doTestDefaultLpChanged(lp, TEST_CELL_LP);
282     }
283 
284     @Test
testDefaultNetworkChanged()285     public void testDefaultNetworkChanged() {
286         initAndVerifyLockdownVpnTracker();
287         final NetworkCallback defaultCallback = getDefaultNetworkCallback();
288         final NetworkCallback vpnCallback = getVpnNetworkCallback();
289         callCallbacksForNetworkConnect(defaultCallback, mNetwork);
290         clearInvocations(mVpn, mCm, mNotificationManager);
291 
292         // New network and LinkProperties received
293         final NetworkCapabilities wifiNc = new NetworkCapabilities.Builder()
294                 .addTransportType(TRANSPORT_WIFI)
295                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
296                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
297                 .build();
298         final LinkProperties wifiLp = new LinkProperties();
299         wifiLp.setInterfaceName("wlan0");
300         callCallbacksForNetworkConnect(
301                 defaultCallback, mNetwork2, wifiNc, wifiLp, true /* blocked */);
302 
303         // Vpn is restarted.
304         verify(mVpn).stopVpnRunnerPrivileged();
305         verify(mVpn).startLegacyVpnPrivileged(mProfile);
306         verify(mNotificationManager, never()).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS));
307         verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS),
308                 argThat(notification -> isExpectedNotification(notification,
309                         R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected)));
310 
311         // Vpn is Connected
312         doReturn(true).when(mVpnNetworkInfo).isConnectedOrConnecting();
313         doReturn(true).when(mVpnNetworkInfo).isConnected();
314         vpnCallback.onAvailable(mVpnNetwork);
315         verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS),
316                 argThat(notification -> isExpectedNotification(notification,
317                         R.string.vpn_lockdown_connected, R.drawable.vpn_connected)));
318 
319     }
320 
321     @Test
testSystemDefaultLost()322     public void testSystemDefaultLost() {
323         initAndVerifyLockdownVpnTracker();
324         final NetworkCallback defaultCallback = getDefaultNetworkCallback();
325         // mNetwork connected
326         callCallbacksForNetworkConnect(defaultCallback, mNetwork);
327         clearInvocations(mVpn, mCm, mNotificationManager);
328 
329         defaultCallback.onLost(mNetwork);
330 
331         // Vpn is stopped
332         verify(mVpn).stopVpnRunnerPrivileged();
333         verify(mNotificationManager).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS));
334     }
335 }
336