Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4fae111c authored by Erik Kline's avatar Erik Kline Committed by Android (Google) Code Review
Browse files

Merge "Support NAT keepalives" into oc-mr1-dev

parents b6a1c5f0 7a65bc62
Loading
Loading
Loading
Loading
+135 −14
Original line number Diff line number Diff line
@@ -30,6 +30,10 @@ import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkStats;
import android.net.RouteInfo;
import android.net.netlink.ConntrackMessage;
import android.net.netlink.NetlinkConstants;
import android.net.netlink.NetlinkSocket;
import android.net.util.IpUtils;
import android.net.util.SharedLog;
import android.os.Handler;
import android.os.Looper;
@@ -37,10 +41,12 @@ import android.os.INetworkManagementService;
import android.os.RemoteException;
import android.os.SystemClock;
import android.provider.Settings;
import android.system.ErrnoException;
import android.system.OsConstants;
import android.text.TextUtils;
import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;

import com.android.internal.util.IndentingPrintWriter;
import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;

import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -63,6 +69,7 @@ import java.util.concurrent.TimeUnit;
 */
public class OffloadController {
    private static final String TAG = OffloadController.class.getSimpleName();
    private static final boolean DBG = false;
    private static final String ANYIP = "0.0.0.0";
    private static final ForwardedStats EMPTY_STATS = new ForwardedStats();

@@ -96,6 +103,9 @@ public class OffloadController {
    // includes upstream interfaces that have a quota set.
    private HashMap<String, Long> mInterfaceQuotas = new HashMap<>();

    private int mNatUpdateCallbacksReceived;
    private int mNatUpdateNetlinkErrors;

    public OffloadController(Handler h, OffloadHardwareInterface hwi,
            ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) {
        mHandler = h;
@@ -115,12 +125,12 @@ public class OffloadController {
        }
    }

    public void start() {
        if (started()) return;
    public boolean start() {
        if (started()) return true;

        if (isOffloadDisabled()) {
            mLog.i("tethering offload disabled");
            return;
            return false;
        }

        if (!mConfigInitialized) {
@@ -128,11 +138,14 @@ public class OffloadController {
            if (!mConfigInitialized) {
                mLog.i("tethering offload config not supported");
                stop();
                return;
                return false;
            }
        }

        mControlInitialized = mHwInterface.initOffloadControl(
                // OffloadHardwareInterface guarantees that these callback
                // methods are called on the handler passed to it, which is the
                // same as mHandler, as coordinated by the setup in Tethering.
                new OffloadHardwareInterface.ControlCallback() {
                    @Override
                    public void onStarted() {
@@ -203,15 +216,20 @@ public class OffloadController {
                                                   String srcAddr, int srcPort,
                                                   String dstAddr, int dstPort) {
                        if (!started()) return;
                        mLog.log(String.format("NAT timeout update: %s (%s,%s) -> (%s,%s)",
                                proto, srcAddr, srcPort, dstAddr, dstPort));
                        updateNatTimeout(proto, srcAddr, srcPort, dstAddr, dstPort);
                    }
                });
        if (!mControlInitialized) {

        final boolean isStarted = started();
        if (!isStarted) {
            mLog.i("tethering offload control not supported");
            stop();
        }
        } else {
            mLog.log("tethering offload started");
            mNatUpdateCallbacksReceived = 0;
            mNatUpdateNetlinkErrors = 0;
        }
        return isStarted;
    }

    public void stop() {
@@ -227,6 +245,10 @@ public class OffloadController {
        if (wasStarted) mLog.log("tethering offload stopped");
    }

    private boolean started() {
        return mConfigInitialized && mControlInitialized;
    }

    private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub {
        @Override
        public NetworkStats getTetherStats(int how) {
@@ -402,10 +424,6 @@ public class OffloadController {
                mContentResolver, TETHER_OFFLOAD_DISABLED, defaultDisposition) != 0);
    }

    private boolean started() {
        return mConfigInitialized && mControlInitialized;
    }

    private boolean pushUpstreamParameters(String prevUpstream) {
        final String iface = currentUpstreamInterface();

@@ -516,10 +534,113 @@ public class OffloadController {
            pw.println("Offload disabled");
            return;
        }
        pw.println("Offload HALs " + (started() ? "started" : "not started"));
        final boolean isStarted = started();
        pw.println("Offload HALs " + (isStarted ? "started" : "not started"));
        LinkProperties lp = mUpstreamLinkProperties;
        String upstream = (lp != null) ? lp.getInterfaceName() : null;
        pw.println("Current upstream: " + upstream);
        pw.println("Exempt prefixes: " + mLastLocalPrefixStrs);
        pw.println("NAT timeout update callbacks received during the "
                + (isStarted ? "current" : "last")
                + " offload session: "
                + mNatUpdateCallbacksReceived);
        pw.println("NAT timeout update netlink errors during the "
                + (isStarted ? "current" : "last")
                + " offload session: "
                + mNatUpdateNetlinkErrors);
    }

    private void updateNatTimeout(
            int proto, String srcAddr, int srcPort, String dstAddr, int dstPort) {
        final String protoName = protoNameFor(proto);
        if (protoName == null) {
            mLog.e("Unknown NAT update callback protocol: " + proto);
            return;
        }

        final Inet4Address src = parseIPv4Address(srcAddr);
        if (src == null) {
            mLog.e("Failed to parse IPv4 address: " + srcAddr);
            return;
        }

        if (!IpUtils.isValidUdpOrTcpPort(srcPort)) {
            mLog.e("Invalid src port: " + srcPort);
            return;
        }

        final Inet4Address dst = parseIPv4Address(dstAddr);
        if (dst == null) {
            mLog.e("Failed to parse IPv4 address: " + dstAddr);
            return;
        }

        if (!IpUtils.isValidUdpOrTcpPort(dstPort)) {
            mLog.e("Invalid dst port: " + dstPort);
            return;
        }

        mNatUpdateCallbacksReceived++;
        if (DBG) {
            mLog.log(String.format("NAT timeout update: %s (%s, %s) -> (%s, %s)",
                     protoName, srcAddr, srcPort, dstAddr, dstPort));
        }

        final int timeoutSec = connectionTimeoutUpdateSecondsFor(proto);
        final byte[] msg = ConntrackMessage.newIPv4TimeoutUpdateRequest(
                proto, src, srcPort, dst, dstPort, timeoutSec);

        try {
            NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_NETFILTER, msg);
        } catch (ErrnoException e) {
            mNatUpdateNetlinkErrors++;
            mLog.e("Error updating NAT conntrack entry: " + e
                    + ", msg: " + NetlinkConstants.hexify(msg));
            mLog.log("NAT timeout update callbacks received: " + mNatUpdateCallbacksReceived);
            mLog.log("NAT timeout update netlink errors: " + mNatUpdateNetlinkErrors);
        }
    }

    private static Inet4Address parseIPv4Address(String addrString) {
        try {
            final InetAddress ip = InetAddress.parseNumericAddress(addrString);
            // TODO: Consider other sanitization steps here, including perhaps:
            //           not eql to 0.0.0.0
            //           not within 169.254.0.0/16
            //           not within ::ffff:0.0.0.0/96
            //           not within ::/96
            // et cetera.
            if (ip instanceof Inet4Address) {
                return (Inet4Address) ip;
            }
        } catch (IllegalArgumentException iae) {}
        return null;
    }

    private static String protoNameFor(int proto) {
        // OsConstants values are not constant expressions; no switch statement.
        if (proto == OsConstants.IPPROTO_UDP) {
            return "UDP";
        } else if (proto == OsConstants.IPPROTO_TCP) {
            return "TCP";
        }
        return null;
    }

    private static int connectionTimeoutUpdateSecondsFor(int proto) {
        // TODO: Replace this with more thoughtful work, perhaps reading from
        // and maybe writing to any required
        //
        //     /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_*
        //     /proc/sys/net/netfilter/nf_conntrack_udp_timeout{,_stream}
        //
        // entries.  TBD.
        if (proto == OsConstants.IPPROTO_TCP) {
            // Cf. /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established
            return 432000;
        } else {
            // Cf. /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream
            return 180;
        }
    }
}
+14 −1
Original line number Diff line number Diff line
@@ -21,10 +21,12 @@ import static com.android.internal.util.BitUtils.uint16;
import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
import android.os.Handler;
import android.os.RemoteException;
import android.net.util.SharedLog;
import android.system.OsConstants;

import java.util.ArrayList;

@@ -327,13 +329,24 @@ public class OffloadHardwareInterface {
        public void updateTimeout(NatTimeoutUpdate params) {
            handler.post(() -> {
                    controlCb.onNatTimeoutUpdate(
                        params.proto,
                        networkProtocolToOsConstant(params.proto),
                        params.src.addr, uint16(params.src.port),
                        params.dst.addr, uint16(params.dst.port));
            });
        }
    }

    private static int networkProtocolToOsConstant(int proto) {
        switch (proto) {
            case NetworkProtocol.TCP: return OsConstants.IPPROTO_TCP;
            case NetworkProtocol.UDP: return OsConstants.IPPROTO_UDP;
            default:
                // The caller checks this value and will log an error. Just make
                // sure it won't collide with valid OsContants.IPPROTO_* values.
                return -Math.abs(proto);
        }
    }

    private static class CbResults {
        boolean success;
        String errMsg;
+6 −36
Original line number Diff line number Diff line
@@ -183,44 +183,14 @@ public class IpReachabilityMonitor {
        final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
                1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);

        int errno = -OsConstants.EPROTO;
        try (NetlinkSocket nlSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE)) {
            final long IO_TIMEOUT = 300L;
            nlSocket.connectToKernel();
            nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT);
            final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT);
            // recvMessage() guaranteed to not return null if it did not throw.
            final NetlinkMessage response = NetlinkMessage.parse(bytes);
            if (response != null && response instanceof NetlinkErrorMessage &&
                    (((NetlinkErrorMessage) response).getNlMsgError() != null)) {
                errno = ((NetlinkErrorMessage) response).getNlMsgError().error;
                if (errno != 0) {
                    // TODO: consider ignoring EINVAL (-22), which appears to be
                    // normal when probing a neighbor for which the kernel does
                    // not already have / no longer has a link layer address.
                    Log.e(TAG, "Error " + msgSnippet + ", errmsg=" + response.toString());
                }
            } else {
                String errmsg;
                if (response == null) {
                    bytes.position(0);
                    errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
                } else {
                    errmsg = response.toString();
                }
                Log.e(TAG, "Error " + msgSnippet + ", errmsg=" + errmsg);
            }
        try {
            NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_ROUTE, msg);
        } catch (ErrnoException e) {
            Log.e(TAG, "Error " + msgSnippet, e);
            errno = -e.errno;
        } catch (InterruptedIOException e) {
            Log.e(TAG, "Error " + msgSnippet, e);
            errno = -OsConstants.ETIMEDOUT;
        } catch (SocketException e) {
            Log.e(TAG, "Error " + msgSnippet, e);
            errno = -OsConstants.EIO;
            Log.e(TAG, "Error " + msgSnippet + ": " + e);
            return -e.errno;
        }
        return errno;

        return 0;
    }

    public IpReachabilityMonitor(Context context, String ifName, SharedLog log, Callback callback) {
+117 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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 android.net.netlink;

import static android.net.netlink.NetlinkConstants.alignedLengthOf;
import static android.net.netlink.StructNlAttr.makeNestedType;
import static android.net.netlink.StructNlAttr.NLA_HEADERLEN;
import static android.net.netlink.StructNlMsgHdr.NLM_F_ACK;
import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
import static android.net.netlink.StructNlMsgHdr.NLM_F_REPLACE;
import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
import static android.net.util.NetworkConstants.IPV4_ADDR_LEN;
import static java.nio.ByteOrder.BIG_ENDIAN;

import android.system.OsConstants;
import android.util.Log;
import libcore.io.SizeOf;

import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;


/**
 * A NetlinkMessage subclass for netlink conntrack messages.
 *
 * see also: &lt;linux_src&gt;/include/uapi/linux/netfilter/nfnetlink_conntrack.h
 *
 * @hide
 */
public class ConntrackMessage extends NetlinkMessage {
    public static final int STRUCT_SIZE = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;

    public static final short NFNL_SUBSYS_CTNETLINK = 1;
    public static final short IPCTNL_MSG_CT_NEW = 0;

    // enum ctattr_type
    public static final short CTA_TUPLE_ORIG  = 1;
    public static final short CTA_TUPLE_REPLY = 2;
    public static final short CTA_TIMEOUT     = 7;

    // enum ctattr_tuple
    public static final short CTA_TUPLE_IP    = 1;
    public static final short CTA_TUPLE_PROTO = 2;

    // enum ctattr_ip
    public static final short CTA_IP_V4_SRC = 1;
    public static final short CTA_IP_V4_DST = 2;

    // enum ctattr_l4proto
    public static final short CTA_PROTO_NUM      = 1;
    public static final short CTA_PROTO_SRC_PORT = 2;
    public static final short CTA_PROTO_DST_PORT = 3;

    public static byte[] newIPv4TimeoutUpdateRequest(
            int proto, Inet4Address src, int sport, Inet4Address dst, int dport, int timeoutSec) {
        // *** STYLE WARNING ***
        //
        // Code below this point uses extra block indentation to highlight the
        // packing of nested tuple netlink attribute types.
        final StructNlAttr ctaTupleOrig = new StructNlAttr(CTA_TUPLE_ORIG,
                new StructNlAttr(CTA_TUPLE_IP,
                        new StructNlAttr(CTA_IP_V4_SRC, src),
                        new StructNlAttr(CTA_IP_V4_DST, dst)),
                new StructNlAttr(CTA_TUPLE_PROTO,
                        new StructNlAttr(CTA_PROTO_NUM, (byte) proto),
                        new StructNlAttr(CTA_PROTO_SRC_PORT, (short) sport, BIG_ENDIAN),
                        new StructNlAttr(CTA_PROTO_DST_PORT, (short) dport, BIG_ENDIAN)));

        final StructNlAttr ctaTimeout = new StructNlAttr(CTA_TIMEOUT, timeoutSec, BIG_ENDIAN);

        final int payloadLength = ctaTupleOrig.getAlignedLength() + ctaTimeout.getAlignedLength();
        final byte[] bytes = new byte[STRUCT_SIZE + payloadLength];
        final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
        byteBuffer.order(ByteOrder.nativeOrder());

        final ConntrackMessage ctmsg = new ConntrackMessage();
        ctmsg.mHeader.nlmsg_len = bytes.length;
        ctmsg.mHeader.nlmsg_type = (NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_NEW;
        ctmsg.mHeader.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE;
        ctmsg.mHeader.nlmsg_seq = 1;
        ctmsg.pack(byteBuffer);

        ctaTupleOrig.pack(byteBuffer);
        ctaTimeout.pack(byteBuffer);

        return bytes;
    }

    protected StructNfGenMsg mNfGenMsg;

    private ConntrackMessage() {
        super(new StructNlMsgHdr());
        mNfGenMsg = new StructNfGenMsg((byte) OsConstants.AF_INET);
    }

    public void pack(ByteBuffer byteBuffer) {
        mHeader.pack(byteBuffer);
        mNfGenMsg.pack(byteBuffer);
    }
}
+41 −0
Original line number Diff line number Diff line
@@ -51,6 +51,47 @@ public class NetlinkSocket implements Closeable {
    private long mLastRecvTimeoutMs;
    private long mLastSendTimeoutMs;

    public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException {
        final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage";

        try (NetlinkSocket nlSocket = new NetlinkSocket(nlProto)) {
            final long IO_TIMEOUT = 300L;
            nlSocket.connectToKernel();
            nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT);
            final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT);
            // recvMessage() guaranteed to not return null if it did not throw.
            final NetlinkMessage response = NetlinkMessage.parse(bytes);
            if (response != null && response instanceof NetlinkErrorMessage &&
                    (((NetlinkErrorMessage) response).getNlMsgError() != null)) {
                final int errno = ((NetlinkErrorMessage) response).getNlMsgError().error;
                if (errno != 0) {
                    // TODO: consider ignoring EINVAL (-22), which appears to be
                    // normal when probing a neighbor for which the kernel does
                    // not already have / no longer has a link layer address.
                    Log.e(TAG, errPrefix + ", errmsg=" + response.toString());
                    // Note: convert kernel errnos (negative) into userspace errnos (positive).
                    throw new ErrnoException(response.toString(), Math.abs(errno));
                }
            } else {
                final String errmsg;
                if (response == null) {
                    bytes.position(0);
                    errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
                } else {
                    errmsg = response.toString();
                }
                Log.e(TAG, errPrefix + ", errmsg=" + errmsg);
                throw new ErrnoException(errmsg, OsConstants.EPROTO);
            }
        } catch (InterruptedIOException e) {
            Log.e(TAG, errPrefix, e);
            throw new ErrnoException(errPrefix, OsConstants.ETIMEDOUT, e);
        } catch (SocketException e) {
            Log.e(TAG, errPrefix, e);
            throw new ErrnoException(errPrefix, OsConstants.EIO, e);
        }
    }

    public NetlinkSocket(int nlProto) throws ErrnoException {
        mDescriptor = Os.socket(
                OsConstants.AF_NETLINK, OsConstants.SOCK_DGRAM, nlProto);
Loading