| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.connectivity; |
| |
| import static android.net.INetd.IF_STATE_UP; |
| import static android.net.INetd.PERMISSION_SYSTEM; |
| |
| import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.net.INetd; |
| import android.net.InterfaceConfigurationParcel; |
| import android.net.IpPrefix; |
| import android.os.ParcelFileDescriptor; |
| import android.os.RemoteException; |
| import android.os.ServiceSpecificException; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.net.module.util.InterfaceParams; |
| |
| import java.io.FileDescriptor; |
| import java.io.IOException; |
| import java.net.InetAddress; |
| import java.nio.ByteBuffer; |
| |
| /** |
| * This coordinator is responsible for providing clat relevant functionality. |
| * |
| * {@hide} |
| */ |
| public class ClatCoordinator { |
| private static final String TAG = ClatCoordinator.class.getSimpleName(); |
| |
| // Sync from external/android-clat/clatd.c |
| // 40 bytes IPv6 header - 20 bytes IPv4 header + 8 bytes fragment header. |
| @VisibleForTesting |
| static final int MTU_DELTA = 28; |
| @VisibleForTesting |
| static final int CLAT_MAX_MTU = 65536; |
| |
| // This must match the interface prefix in clatd.c. |
| private static final String CLAT_PREFIX = "v4-"; |
| |
| // For historical reasons, start with 192.0.0.4, and after that, use all subsequent addresses |
| // in 192.0.0.0/29 (RFC 7335). |
| @VisibleForTesting |
| static final String INIT_V4ADDR_STRING = "192.0.0.4"; |
| @VisibleForTesting |
| static final int INIT_V4ADDR_PREFIX_LEN = 29; |
| private static final InetAddress GOOGLE_DNS_4 = InetAddress.parseNumericAddress("8.8.8.8"); |
| |
| private static final int INVALID_IFINDEX = 0; |
| private static final int INVALID_PID = 0; |
| |
| @NonNull |
| private final INetd mNetd; |
| @NonNull |
| private final Dependencies mDeps; |
| @Nullable |
| private String mIface = null; |
| @Nullable |
| private String mNat64Prefix = null; |
| @Nullable |
| private String mXlatLocalAddress4 = null; |
| @Nullable |
| private String mXlatLocalAddress6 = null; |
| private int mPid = INVALID_PID; |
| |
| @VisibleForTesting |
| abstract static class Dependencies { |
| /** |
| * Get netd. |
| */ |
| @NonNull |
| public abstract INetd getNetd(); |
| |
| /** |
| * @see ParcelFileDescriptor#adoptFd(int). |
| */ |
| @NonNull |
| public ParcelFileDescriptor adoptFd(int fd) { |
| return ParcelFileDescriptor.adoptFd(fd); |
| } |
| |
| /** |
| * Get interface index for a given interface. |
| */ |
| public int getInterfaceIndex(String ifName) { |
| final InterfaceParams params = InterfaceParams.getByName(ifName); |
| return params != null ? params.index : INVALID_IFINDEX; |
| } |
| |
| /** |
| * Create tun interface for a given interface name. |
| */ |
| public int createTunInterface(@NonNull String tuniface) throws IOException { |
| return native_createTunInterface(tuniface); |
| } |
| |
| /** |
| * Pick an IPv4 address for clat. |
| */ |
| @NonNull |
| public String selectIpv4Address(@NonNull String v4addr, int prefixlen) |
| throws IOException { |
| return native_selectIpv4Address(v4addr, prefixlen); |
| } |
| |
| /** |
| * Generate a checksum-neutral IID. |
| */ |
| @NonNull |
| public String generateIpv6Address(@NonNull String iface, @NonNull String v4, |
| @NonNull String prefix64) throws IOException { |
| return native_generateIpv6Address(iface, v4, prefix64); |
| } |
| |
| /** |
| * Detect MTU. |
| */ |
| public int detectMtu(@NonNull String platSubnet, int platSuffix, int mark) |
| throws IOException { |
| return native_detectMtu(platSubnet, platSuffix, mark); |
| } |
| |
| /** |
| * Open packet socket. |
| */ |
| public int openPacketSocket() throws IOException { |
| return native_openPacketSocket(); |
| } |
| |
| /** |
| * Open IPv6 raw socket and set SO_MARK. |
| */ |
| public int openRawSocket6(int mark) throws IOException { |
| return native_openRawSocket6(mark); |
| } |
| |
| /** |
| * Add anycast setsockopt. |
| */ |
| public void addAnycastSetsockopt(@NonNull FileDescriptor sock, String v6, int ifindex) |
| throws IOException { |
| native_addAnycastSetsockopt(sock, v6, ifindex); |
| } |
| |
| /** |
| * Configure packet socket. |
| */ |
| public void configurePacketSocket(@NonNull FileDescriptor sock, String v6, int ifindex) |
| throws IOException { |
| native_configurePacketSocket(sock, v6, ifindex); |
| } |
| |
| /** |
| * Start clatd. |
| */ |
| public int startClatd(@NonNull FileDescriptor tunfd, @NonNull FileDescriptor readsock6, |
| @NonNull FileDescriptor writesock6, @NonNull String iface, @NonNull String pfx96, |
| @NonNull String v4, @NonNull String v6) throws IOException { |
| return native_startClatd(tunfd, readsock6, writesock6, iface, pfx96, v4, v6); |
| } |
| |
| /** |
| * Stop clatd. |
| */ |
| public void stopClatd(String iface, String pfx96, String v4, String v6, int pid) |
| throws IOException { |
| native_stopClatd(iface, pfx96, v4, v6, pid); |
| } |
| } |
| |
| @VisibleForTesting |
| static int getFwmark(int netId) { |
| // See union Fwmark in system/netd/include/Fwmark.h |
| return (netId & 0xffff) |
| | 0x1 << 16 // protectedFromVpn: true |
| | 0x1 << 17 // explicitlySelected: true |
| | (PERMISSION_SYSTEM & 0x3) << 18; |
| } |
| |
| @VisibleForTesting |
| static int adjustMtu(int mtu) { |
| // clamp to minimum ipv6 mtu - this probably cannot ever trigger |
| if (mtu < IPV6_MIN_MTU) mtu = IPV6_MIN_MTU; |
| // clamp to buffer size |
| if (mtu > CLAT_MAX_MTU) mtu = CLAT_MAX_MTU; |
| // decrease by ipv6(40) + ipv6 fragmentation header(8) vs ipv4(20) overhead of 28 bytes |
| mtu -= MTU_DELTA; |
| |
| return mtu; |
| } |
| |
| public ClatCoordinator(@NonNull Dependencies deps) { |
| mDeps = deps; |
| mNetd = mDeps.getNetd(); |
| } |
| |
| /** |
| * Start clatd for a given interface and NAT64 prefix. |
| */ |
| public String clatStart(final String iface, final int netId, |
| @NonNull final IpPrefix nat64Prefix) |
| throws IOException { |
| if (mIface != null || mPid != INVALID_PID) { |
| throw new IOException("Clatd is already running on " + mIface + " (pid " + mPid + ")"); |
| } |
| if (nat64Prefix.getPrefixLength() != 96) { |
| throw new IOException("Prefix must be 96 bits long: " + nat64Prefix); |
| } |
| |
| // [1] Pick an IPv4 address from 192.0.0.4, 192.0.0.5, 192.0.0.6 .. |
| final String v4; |
| try { |
| v4 = mDeps.selectIpv4Address(INIT_V4ADDR_STRING, INIT_V4ADDR_PREFIX_LEN); |
| } catch (IOException e) { |
| throw new IOException("no IPv4 addresses were available for clat: " + e); |
| } |
| |
| // [2] Generate a checksum-neutral IID. |
| final String pfx96 = nat64Prefix.getAddress().getHostAddress(); |
| final String v6; |
| try { |
| v6 = mDeps.generateIpv6Address(iface, v4, pfx96); |
| } catch (IOException e) { |
| throw new IOException("no IPv6 addresses were available for clat: " + e); |
| } |
| |
| // [3] Open, configure and bring up the tun interface. |
| // Create the v4-... tun interface. |
| final String tunIface = CLAT_PREFIX + iface; |
| final ParcelFileDescriptor tunFd; |
| try { |
| tunFd = mDeps.adoptFd(mDeps.createTunInterface(tunIface)); |
| } catch (IOException e) { |
| throw new IOException("Create tun interface " + tunIface + " failed: " + e); |
| } |
| |
| // disable IPv6 on it - failing to do so is not a critical error |
| try { |
| mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */); |
| } catch (RemoteException | ServiceSpecificException e) { |
| tunFd.close(); |
| Log.e(TAG, "Disable IPv6 on " + tunIface + " failed: " + e); |
| } |
| |
| // Detect ipv4 mtu. |
| final Integer fwmark = getFwmark(netId); |
| final int detectedMtu = mDeps.detectMtu(pfx96, |
| ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark); |
| final int mtu = adjustMtu(detectedMtu); |
| Log.i(TAG, "ipv4 mtu is " + mtu); |
| |
| // TODO: add setIptablesDropRule |
| |
| // Config tun interface mtu, address and bring up. |
| try { |
| mNetd.interfaceSetMtu(tunIface, mtu); |
| } catch (RemoteException | ServiceSpecificException e) { |
| tunFd.close(); |
| throw new IOException("Set MTU " + mtu + " on " + tunIface + " failed: " + e); |
| } |
| final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel(); |
| ifConfig.ifName = tunIface; |
| ifConfig.ipv4Addr = v4; |
| ifConfig.prefixLength = 32; |
| ifConfig.hwAddr = ""; |
| ifConfig.flags = new String[] {IF_STATE_UP}; |
| try { |
| mNetd.interfaceSetCfg(ifConfig); |
| } catch (RemoteException | ServiceSpecificException e) { |
| tunFd.close(); |
| throw new IOException("Setting IPv4 address to " + ifConfig.ipv4Addr + "/" |
| + ifConfig.prefixLength + " failed on " + ifConfig.ifName + ": " + e); |
| } |
| |
| // [4] Open and configure local 464xlat read/write sockets. |
| // Opens a packet socket to receive IPv6 packets in clatd. |
| final ParcelFileDescriptor readSock6; |
| try { |
| // Use a JNI call to get native file descriptor instead of Os.socket() because we would |
| // like to use ParcelFileDescriptor to manage file descriptor. But ctor |
| // ParcelFileDescriptor(FileDescriptor fd) is a @hide function. Need to use native file |
| // descriptor to initialize ParcelFileDescriptor object instead. |
| readSock6 = mDeps.adoptFd(mDeps.openPacketSocket()); |
| } catch (IOException e) { |
| tunFd.close(); |
| throw new IOException("Open packet socket failed: " + e); |
| } |
| |
| // Opens a raw socket with a given fwmark to send IPv6 packets in clatd. |
| final ParcelFileDescriptor writeSock6; |
| try { |
| // Use a JNI call to get native file descriptor instead of Os.socket(). See above |
| // reason why we use jniOpenPacketSocket6(). |
| writeSock6 = mDeps.adoptFd(mDeps.openRawSocket6(fwmark)); |
| } catch (IOException e) { |
| tunFd.close(); |
| readSock6.close(); |
| throw new IOException("Open raw socket failed: " + e); |
| } |
| |
| final int ifaceIndex = mDeps.getInterfaceIndex(iface); |
| if (ifaceIndex == INVALID_IFINDEX) { |
| tunFd.close(); |
| readSock6.close(); |
| writeSock6.close(); |
| throw new IOException("Fail to get interface index for interface " + iface); |
| } |
| |
| // Start translating packets to the new prefix. |
| try { |
| mDeps.addAnycastSetsockopt(writeSock6.getFileDescriptor(), v6, ifaceIndex); |
| } catch (IOException e) { |
| tunFd.close(); |
| readSock6.close(); |
| writeSock6.close(); |
| throw new IOException("add anycast sockopt failed: " + e); |
| } |
| |
| // Update our packet socket filter to reflect the new 464xlat IP address. |
| try { |
| mDeps.configurePacketSocket(readSock6.getFileDescriptor(), v6, ifaceIndex); |
| } catch (IOException e) { |
| tunFd.close(); |
| readSock6.close(); |
| writeSock6.close(); |
| throw new IOException("configure packet socket failed: " + e); |
| } |
| |
| // [5] Start clatd. |
| try { |
| mPid = mDeps.startClatd(tunFd.getFileDescriptor(), readSock6.getFileDescriptor(), |
| writeSock6.getFileDescriptor(), iface, pfx96, v4, v6); |
| mIface = iface; |
| mNat64Prefix = pfx96; |
| mXlatLocalAddress4 = v4; |
| mXlatLocalAddress6 = v6; |
| } catch (IOException e) { |
| throw new IOException("Error start clatd on " + iface + ": " + e); |
| } finally { |
| tunFd.close(); |
| readSock6.close(); |
| writeSock6.close(); |
| } |
| |
| return v6; |
| } |
| |
| /** |
| * Stop clatd |
| */ |
| public void clatStop() throws IOException { |
| if (mPid == INVALID_PID) { |
| throw new IOException("Clatd has not started"); |
| } |
| Log.i(TAG, "Stopping clatd pid=" + mPid + " on " + mIface); |
| |
| mDeps.stopClatd(mIface, mNat64Prefix, mXlatLocalAddress4, mXlatLocalAddress6, mPid); |
| // TODO: remove setIptablesDropRule |
| |
| Log.i(TAG, "clatd on " + mIface + " stopped"); |
| |
| mIface = null; |
| mNat64Prefix = null; |
| mXlatLocalAddress4 = null; |
| mXlatLocalAddress6 = null; |
| mPid = INVALID_PID; |
| } |
| |
| private static native String native_selectIpv4Address(String v4addr, int prefixlen) |
| throws IOException; |
| private static native String native_generateIpv6Address(String iface, String v4, |
| String prefix64) throws IOException; |
| private static native int native_createTunInterface(String tuniface) throws IOException; |
| private static native int native_detectMtu(String platSubnet, int platSuffix, int mark) |
| throws IOException; |
| private static native int native_openPacketSocket() throws IOException; |
| private static native int native_openRawSocket6(int mark) throws IOException; |
| private static native void native_addAnycastSetsockopt(FileDescriptor sock, String v6, |
| int ifindex) throws IOException; |
| private static native void native_configurePacketSocket(FileDescriptor sock, String v6, |
| int ifindex) throws IOException; |
| private static native int native_startClatd(FileDescriptor tunfd, FileDescriptor readsock6, |
| FileDescriptor writesock6, String iface, String pfx96, String v4, String v6) |
| throws IOException; |
| private static native void native_stopClatd(String iface, String pfx96, String v4, String v6, |
| int pid) throws IOException; |
| } |