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 package com.android.server;
17 
18 import static com.android.net.module.util.netlink.NetlinkUtils.SOCKET_RECV_BUFSIZE;
19 import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.IPPROTO_ESP;
20 import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.NETLINK_XFRM;
21 import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.XFRM_MSG_NEWSA;
22 
23 import android.annotation.TargetApi;
24 import android.os.Build;
25 import android.system.ErrnoException;
26 import android.util.Log;
27 
28 import androidx.annotation.NonNull;
29 import androidx.annotation.Nullable;
30 
31 import com.android.internal.annotations.GuardedBy;
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.net.module.util.HexDump;
34 import com.android.net.module.util.netlink.NetlinkConstants;
35 import com.android.net.module.util.netlink.NetlinkErrorMessage;
36 import com.android.net.module.util.netlink.NetlinkMessage;
37 import com.android.net.module.util.netlink.NetlinkUtils;
38 import com.android.net.module.util.netlink.xfrm.XfrmNetlinkGetSaMessage;
39 import com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage;
40 import com.android.net.module.util.netlink.xfrm.XfrmNetlinkNewSaMessage;
41 
42 import libcore.io.IoUtils;
43 
44 import java.io.FileDescriptor;
45 import java.io.IOException;
46 import java.io.InterruptedIOException;
47 import java.net.InetAddress;
48 import java.net.SocketException;
49 import java.nio.ByteBuffer;
50 
51 /**
52  * This class handles IPSec XFRM commands between IpSecService and the Linux kernel
53  *
54  * <p>Synchronization in IpSecXfrmController is done on all entrypoints due to potential race
55  * conditions at the kernel/xfrm level.
56  */
57 public class IpSecXfrmController {
58     private static final String TAG = IpSecXfrmController.class.getSimpleName();
59 
60     private static final boolean VDBG = false; // STOPSHIP: if true
61 
62     private static final int TIMEOUT_MS = 500;
63     private static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
64 
65     @NonNull private final Dependencies mDependencies;
66     @Nullable private FileDescriptor mNetlinkSocket;
67 
68     @VisibleForTesting
IpSecXfrmController(@onNull Dependencies dependencies)69     public IpSecXfrmController(@NonNull Dependencies dependencies) {
70         mDependencies = dependencies;
71     }
72 
IpSecXfrmController()73     public IpSecXfrmController() {
74         this(new Dependencies());
75     }
76 
77     /**
78      * Start the XfrmController
79      *
80      * <p>The method is idempotent
81      */
openNetlinkSocketIfNeeded()82     public synchronized void openNetlinkSocketIfNeeded() throws ErrnoException, SocketException {
83         if (mNetlinkSocket == null) {
84             mNetlinkSocket = mDependencies.newNetlinkSocket();
85         }
86     }
87 
88     /**
89      * Stop the XfrmController
90      *
91      * <p>The method is idempotent
92      */
closeNetlinkSocketIfNeeded()93     public synchronized void closeNetlinkSocketIfNeeded() {
94         if (mNetlinkSocket != null) {
95             mDependencies.releaseNetlinkSocket(mNetlinkSocket);
96             mNetlinkSocket = null;
97         }
98     }
99 
100     @VisibleForTesting
getNetlinkSocket()101     public synchronized FileDescriptor getNetlinkSocket() {
102         return mNetlinkSocket;
103     }
104 
105     /** Dependencies of IpSecXfrmController, for injection in tests. */
106     @VisibleForTesting
107     public static class Dependencies {
108         /** Get a new XFRM netlink socket and connect it */
newNetlinkSocket()109         public FileDescriptor newNetlinkSocket() throws ErrnoException, SocketException {
110             final FileDescriptor fd =
111                     NetlinkUtils.netlinkSocketForProto(NETLINK_XFRM, SOCKET_RECV_BUFSIZE);
112             NetlinkUtils.connectToKernel(fd);
113             return fd;
114         }
115 
116         /** Close the netlink socket */
117         // TODO: b/205923322 This annotation is to suppress the lint error complaining that
118         // #closeQuietly requires Android S. It can be removed when the infra supports setting
119         // service-connectivity min_sdk to 31
120         @TargetApi(Build.VERSION_CODES.S)
releaseNetlinkSocket(FileDescriptor fd)121         public void releaseNetlinkSocket(FileDescriptor fd) {
122             IoUtils.closeQuietly(fd);
123         }
124 
125         /** Send a netlink message to a socket */
sendMessage(FileDescriptor fd, byte[] bytes)126         public void sendMessage(FileDescriptor fd, byte[] bytes)
127                 throws ErrnoException, InterruptedIOException {
128             NetlinkUtils.sendMessage(fd, bytes, 0, bytes.length, TIMEOUT_MS);
129         }
130 
131         /** Receive a netlink message from a socket */
recvMessage(FileDescriptor fd)132         public ByteBuffer recvMessage(FileDescriptor fd)
133                 throws ErrnoException, InterruptedIOException {
134             return NetlinkUtils.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT_MS);
135         }
136     }
137 
138     @GuardedBy("IpSecXfrmController.this")
sendRequestAndGetResponse(String methodTag, byte[] req)139     private NetlinkMessage sendRequestAndGetResponse(String methodTag, byte[] req)
140             throws ErrnoException, InterruptedIOException, IOException {
141         openNetlinkSocketIfNeeded();
142 
143         logD(methodTag + ":  send request " + req.length + " bytes");
144         logV(HexDump.dumpHexString(req));
145         mDependencies.sendMessage(mNetlinkSocket, req);
146 
147         final ByteBuffer response = mDependencies.recvMessage(mNetlinkSocket);
148         logD(methodTag + ": receive response " + response.limit() + " bytes");
149         logV(HexDump.dumpHexString(response.array(), 0 /* offset */, response.limit()));
150 
151         final NetlinkMessage msg = XfrmNetlinkMessage.parse(response, NETLINK_XFRM);
152         if (msg == null) {
153             throw new IOException("Fail to parse the response message");
154         }
155 
156         final int msgType = msg.getHeader().nlmsg_type;
157         if (msgType == NetlinkConstants.NLMSG_ERROR) {
158             final NetlinkErrorMessage errorMsg = (NetlinkErrorMessage) msg;
159             final int errorCode = errorMsg.getNlMsgError().error;
160             throw new ErrnoException(methodTag, errorCode);
161         }
162 
163         return msg;
164     }
165 
166     /** Get the state of an IPsec SA */
167     @NonNull
ipSecGetSa( @onNull final InetAddress destAddress, long spi)168     public synchronized XfrmNetlinkNewSaMessage ipSecGetSa(
169             @NonNull final InetAddress destAddress, long spi)
170             throws ErrnoException, InterruptedIOException, IOException {
171         logD("ipSecGetSa: destAddress=" + destAddress + " spi=" + spi);
172 
173         final byte[] req =
174                 XfrmNetlinkGetSaMessage.newXfrmNetlinkGetSaMessage(
175                         destAddress, spi, (short) IPPROTO_ESP);
176         try {
177             final NetlinkMessage msg = sendRequestAndGetResponse("ipSecGetSa", req);
178 
179             final int messageType = msg.getHeader().nlmsg_type;
180             if (messageType != XFRM_MSG_NEWSA) {
181                 throw new IOException("unexpected response type " + messageType);
182             }
183 
184             return (XfrmNetlinkNewSaMessage) msg;
185         } catch (IllegalArgumentException exception) {
186             // Maybe thrown from Struct.parse
187             throw new IOException("Failed to parse the response " + exception);
188         }
189     }
190 
logV(String details)191     private static void logV(String details) {
192         if (VDBG) {
193             Log.v(TAG, details);
194         }
195     }
196 
logD(String details)197     private static void logD(String details) {
198         Log.d(TAG, details);
199     }
200 }
201