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