blob: 3cfbf83fe79cc44abd175d76792f8eefc797718c [file] [log] [blame]
Yan Yan4df36192023-11-15 19:44:05 +00001/*
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 */
16package com.android.server;
17
Yang Sun79342412024-01-28 16:58:12 +080018import static com.android.net.module.util.netlink.NetlinkUtils.SOCKET_RECV_BUFSIZE;
Yan Yan4df36192023-11-15 19:44:05 +000019import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.IPPROTO_ESP;
20import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.NETLINK_XFRM;
21import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.XFRM_MSG_NEWSA;
22
23import android.annotation.TargetApi;
24import android.os.Build;
25import android.system.ErrnoException;
26import android.util.Log;
27
28import androidx.annotation.NonNull;
29import androidx.annotation.Nullable;
30
31import com.android.internal.annotations.GuardedBy;
32import com.android.internal.annotations.VisibleForTesting;
33import com.android.net.module.util.HexDump;
34import com.android.net.module.util.netlink.NetlinkConstants;
35import com.android.net.module.util.netlink.NetlinkErrorMessage;
36import com.android.net.module.util.netlink.NetlinkMessage;
37import com.android.net.module.util.netlink.NetlinkUtils;
38import com.android.net.module.util.netlink.xfrm.XfrmNetlinkGetSaMessage;
39import com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage;
40import com.android.net.module.util.netlink.xfrm.XfrmNetlinkNewSaMessage;
41
42import libcore.io.IoUtils;
43
44import java.io.FileDescriptor;
45import java.io.IOException;
46import java.io.InterruptedIOException;
47import java.net.InetAddress;
48import java.net.SocketException;
49import 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 */
57public 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
69 public IpSecXfrmController(@NonNull Dependencies dependencies) {
70 mDependencies = dependencies;
71 }
72
73 public IpSecXfrmController() {
74 this(new Dependencies());
75 }
76
77 /**
78 * Start the XfrmController
79 *
80 * <p>The method is idempotent
81 */
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 */
93 public synchronized void closeNetlinkSocketIfNeeded() {
94 if (mNetlinkSocket != null) {
95 mDependencies.releaseNetlinkSocket(mNetlinkSocket);
96 mNetlinkSocket = null;
97 }
98 }
99
100 @VisibleForTesting
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 */
109 public FileDescriptor newNetlinkSocket() throws ErrnoException, SocketException {
Yang Sun79342412024-01-28 16:58:12 +0800110 final FileDescriptor fd =
111 NetlinkUtils.netlinkSocketForProto(NETLINK_XFRM, SOCKET_RECV_BUFSIZE);
Yan Yan4df36192023-11-15 19:44:05 +0000112 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)
121 public void releaseNetlinkSocket(FileDescriptor fd) {
122 IoUtils.closeQuietly(fd);
123 }
124
125 /** Send a netlink message to a socket */
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 */
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")
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
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
191 private static void logV(String details) {
192 if (VDBG) {
193 Log.v(TAG, details);
194 }
195 }
196
197 private static void logD(String details) {
198 Log.d(TAG, details);
199 }
200}