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