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

Commit 6c7c5520 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Support NAT keepalives"

parents 9c57accc fc105bb6
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
@@ -205,44 +205,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