Merge "[KA03] Support tcp keepalive offload"
am: 5f8ddc2eb1 Change-Id: I7715266c2c24d3f5dd65cd0e375d99b16be6aea6
This commit is contained in:
@@ -96,7 +96,7 @@ public class KeepalivePacketData implements Parcelable {
|
||||
out.writeByteArray(mPacket);
|
||||
}
|
||||
|
||||
private KeepalivePacketData(Parcel in) {
|
||||
protected KeepalivePacketData(Parcel in) {
|
||||
srcAddress = NetworkUtils.numericToInetAddress(in.readString());
|
||||
dstAddress = NetworkUtils.numericToInetAddress(in.readString());
|
||||
srcPort = in.readInt();
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package android.net;
|
||||
|
||||
import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
|
||||
import static android.net.SocketKeepalive.ERROR_INVALID_PORT;
|
||||
|
||||
import android.net.SocketKeepalive.InvalidPacketException;
|
||||
import android.net.util.IpUtils;
|
||||
import android.system.OsConstants;
|
||||
@@ -44,11 +47,11 @@ public final class NattKeepalivePacketData extends KeepalivePacketData {
|
||||
throws InvalidPacketException {
|
||||
|
||||
if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) {
|
||||
throw new InvalidPacketException(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
|
||||
throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
|
||||
}
|
||||
|
||||
if (dstPort != NattSocketKeepalive.NATT_PORT) {
|
||||
throw new InvalidPacketException(SocketKeepalive.ERROR_INVALID_PORT);
|
||||
throw new InvalidPacketException(ERROR_INVALID_PORT);
|
||||
}
|
||||
|
||||
int length = IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH + 1;
|
||||
|
||||
@@ -70,6 +70,18 @@ public class NetworkUtils {
|
||||
public native static void attachControlPacketFilter(FileDescriptor fd, int packetType)
|
||||
throws SocketException;
|
||||
|
||||
/**
|
||||
* Attaches a socket filter that drops all of incoming packets.
|
||||
* @param fd the socket's {@link FileDescriptor}.
|
||||
*/
|
||||
public static native void attachDropAllBPFFilter(FileDescriptor fd) throws SocketException;
|
||||
|
||||
/**
|
||||
* Detaches a socket filter.
|
||||
* @param fd the socket's {@link FileDescriptor}.
|
||||
*/
|
||||
public static native void detachBPFFilter(FileDescriptor fd) throws SocketException;
|
||||
|
||||
/**
|
||||
* Configures a socket for receiving ICMPv6 router solicitations and sending advertisements.
|
||||
* @param fd the socket's {@link FileDescriptor}.
|
||||
@@ -170,6 +182,16 @@ public class NetworkUtils {
|
||||
private static native void addArpEntry(byte[] ethAddr, byte[] netAddr, String ifname,
|
||||
FileDescriptor fd) throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Get the tcp repair window associated with the {@code fd}.
|
||||
*
|
||||
* @param fd the tcp socket's {@link FileDescriptor}.
|
||||
* @return a {@link TcpRepairWindow} object indicates tcp window size.
|
||||
*/
|
||||
public static native TcpRepairWindow getTcpRepairWindow(FileDescriptor fd)
|
||||
throws ErrnoException;
|
||||
|
||||
/**
|
||||
* @see Inet4AddressUtils#intToInet4AddressHTL(int)
|
||||
* @deprecated Use either {@link Inet4AddressUtils#intToInet4AddressHTH(int)}
|
||||
|
||||
@@ -109,15 +109,43 @@ public abstract class SocketKeepalive implements AutoCloseable {
|
||||
**/
|
||||
public static final int MAX_INTERVAL_SEC = 3600;
|
||||
|
||||
/**
|
||||
* An exception that embarks an error code.
|
||||
* @hide
|
||||
*/
|
||||
public static class ErrorCodeException extends Exception {
|
||||
public final int error;
|
||||
public ErrorCodeException(final int error, final Throwable e) {
|
||||
super(e);
|
||||
this.error = error;
|
||||
}
|
||||
public ErrorCodeException(final int error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This socket is invalid.
|
||||
* See the error code for details, and the optional cause.
|
||||
* @hide
|
||||
*/
|
||||
public static class InvalidSocketException extends ErrorCodeException {
|
||||
public InvalidSocketException(final int error, final Throwable e) {
|
||||
super(error, e);
|
||||
}
|
||||
public InvalidSocketException(final int error) {
|
||||
super(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This packet is invalid.
|
||||
* See the error code for details.
|
||||
* @hide
|
||||
*/
|
||||
public static class InvalidPacketException extends Exception {
|
||||
public final int error;
|
||||
public InvalidPacketException(int error) {
|
||||
this.error = error;
|
||||
public static class InvalidPacketException extends ErrorCodeException {
|
||||
public InvalidPacketException(final int error) {
|
||||
super(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
202
core/java/android/net/TcpKeepalivePacketData.java
Normal file
202
core/java/android/net/TcpKeepalivePacketData.java
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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;
|
||||
|
||||
import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.net.SocketKeepalive.InvalidPacketException;
|
||||
import android.net.util.IpUtils;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.system.OsConstants;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents the actual tcp keep alive packets which will be used for hardware offload.
|
||||
* @hide
|
||||
*/
|
||||
public class TcpKeepalivePacketData extends KeepalivePacketData implements Parcelable {
|
||||
private static final String TAG = "TcpKeepalivePacketData";
|
||||
|
||||
/** TCP sequence number. */
|
||||
public final int tcpSeq;
|
||||
|
||||
/** TCP ACK number. */
|
||||
public final int tcpAck;
|
||||
|
||||
/** TCP RCV window. */
|
||||
public final int tcpWnd;
|
||||
|
||||
/** TCP RCV window scale. */
|
||||
public final int tcpWndScale;
|
||||
|
||||
private static final int IPV4_HEADER_LENGTH = 20;
|
||||
private static final int IPV6_HEADER_LENGTH = 40;
|
||||
private static final int TCP_HEADER_LENGTH = 20;
|
||||
|
||||
// This should only be constructed via static factory methods, such as
|
||||
// tcpKeepalivePacket.
|
||||
private TcpKeepalivePacketData(TcpSocketInfo tcpDetails, byte[] data)
|
||||
throws InvalidPacketException {
|
||||
super(tcpDetails.srcAddress, tcpDetails.srcPort, tcpDetails.dstAddress,
|
||||
tcpDetails.dstPort, data);
|
||||
tcpSeq = tcpDetails.seq;
|
||||
tcpAck = tcpDetails.ack;
|
||||
// In the packet, the window is shifted right by the window scale.
|
||||
tcpWnd = tcpDetails.rcvWnd;
|
||||
tcpWndScale = tcpDetails.rcvWndScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create tcp keepalive packet structure.
|
||||
*/
|
||||
public static TcpKeepalivePacketData tcpKeepalivePacket(
|
||||
TcpSocketInfo tcpDetails) throws InvalidPacketException {
|
||||
final byte[] packet;
|
||||
if ((tcpDetails.srcAddress instanceof Inet4Address)
|
||||
&& (tcpDetails.dstAddress instanceof Inet4Address)) {
|
||||
packet = buildV4Packet(tcpDetails);
|
||||
} else {
|
||||
// TODO: support ipv6
|
||||
throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
|
||||
}
|
||||
|
||||
return new TcpKeepalivePacketData(tcpDetails, packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build ipv4 tcp keepalive packet, not including the link-layer header.
|
||||
*/
|
||||
// TODO : if this code is ever moved to the network stack, factorize constants with the ones
|
||||
// over there.
|
||||
private static byte[] buildV4Packet(TcpSocketInfo tcpDetails) {
|
||||
final int length = IPV4_HEADER_LENGTH + TCP_HEADER_LENGTH;
|
||||
ByteBuffer buf = ByteBuffer.allocate(length);
|
||||
buf.order(ByteOrder.BIG_ENDIAN);
|
||||
// IP version and TOS. TODO : fetch this from getsockopt(SOL_IP, IP_TOS)
|
||||
buf.putShort((short) 0x4500);
|
||||
buf.putShort((short) length);
|
||||
buf.putInt(0x4000); // ID, flags=DF, offset
|
||||
// TODO : fetch TTL from getsockopt(SOL_IP, IP_TTL)
|
||||
buf.put((byte) 64);
|
||||
buf.put((byte) OsConstants.IPPROTO_TCP);
|
||||
final int ipChecksumOffset = buf.position();
|
||||
buf.putShort((short) 0); // IP checksum
|
||||
buf.put(tcpDetails.srcAddress.getAddress());
|
||||
buf.put(tcpDetails.dstAddress.getAddress());
|
||||
buf.putShort((short) tcpDetails.srcPort);
|
||||
buf.putShort((short) tcpDetails.dstPort);
|
||||
buf.putInt(tcpDetails.seq); // Sequence Number
|
||||
buf.putInt(tcpDetails.ack); // ACK
|
||||
buf.putShort((short) 0x5010); // TCP length=5, flags=ACK
|
||||
buf.putShort((short) (tcpDetails.rcvWnd >> tcpDetails.rcvWndScale)); // Window size
|
||||
final int tcpChecksumOffset = buf.position();
|
||||
buf.putShort((short) 0); // TCP checksum
|
||||
// URG is not set therefore the urgent pointer is not included
|
||||
buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0));
|
||||
buf.putShort(tcpChecksumOffset, IpUtils.tcpChecksum(
|
||||
buf, 0, IPV4_HEADER_LENGTH, TCP_HEADER_LENGTH));
|
||||
|
||||
return buf.array();
|
||||
}
|
||||
|
||||
// TODO: add buildV6Packet.
|
||||
|
||||
/** Represents tcp/ip information. */
|
||||
public static class TcpSocketInfo {
|
||||
public final InetAddress srcAddress;
|
||||
public final InetAddress dstAddress;
|
||||
public final int srcPort;
|
||||
public final int dstPort;
|
||||
public final int seq;
|
||||
public final int ack;
|
||||
public final int rcvWnd;
|
||||
public final int rcvWndScale;
|
||||
|
||||
public TcpSocketInfo(InetAddress sAddr, int sPort, InetAddress dAddr,
|
||||
int dPort, int writeSeq, int readSeq, int rWnd, int rWndScale) {
|
||||
srcAddress = sAddr;
|
||||
dstAddress = dAddr;
|
||||
srcPort = sPort;
|
||||
dstPort = dPort;
|
||||
seq = writeSeq;
|
||||
ack = readSeq;
|
||||
rcvWnd = rWnd;
|
||||
rcvWndScale = rWndScale;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable final Object o) {
|
||||
if (!(o instanceof TcpKeepalivePacketData)) return false;
|
||||
final TcpKeepalivePacketData other = (TcpKeepalivePacketData) o;
|
||||
return this.srcAddress.equals(other.srcAddress)
|
||||
&& this.dstAddress.equals(other.dstAddress)
|
||||
&& this.srcPort == other.srcPort
|
||||
&& this.dstPort == other.dstPort
|
||||
&& this.tcpAck == other.tcpAck
|
||||
&& this.tcpSeq == other.tcpSeq
|
||||
&& this.tcpWnd == other.tcpWnd
|
||||
&& this.tcpWndScale == other.tcpWndScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(srcAddress, dstAddress, srcPort, dstPort, tcpAck, tcpSeq, tcpWnd,
|
||||
tcpWndScale);
|
||||
}
|
||||
|
||||
/* Parcelable Implementation. */
|
||||
/** No special parcel contents. */
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Write to parcel. */
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
super.writeToParcel(out, flags);
|
||||
out.writeInt(tcpSeq);
|
||||
out.writeInt(tcpAck);
|
||||
out.writeInt(tcpWnd);
|
||||
out.writeInt(tcpWndScale);
|
||||
}
|
||||
|
||||
private TcpKeepalivePacketData(Parcel in) {
|
||||
super(in);
|
||||
tcpSeq = in.readInt();
|
||||
tcpAck = in.readInt();
|
||||
tcpWnd = in.readInt();
|
||||
tcpWndScale = in.readInt();
|
||||
}
|
||||
|
||||
/** Parcelable Creator. */
|
||||
public static final Parcelable.Creator<TcpKeepalivePacketData> CREATOR =
|
||||
new Parcelable.Creator<TcpKeepalivePacketData>() {
|
||||
public TcpKeepalivePacketData createFromParcel(Parcel in) {
|
||||
return new TcpKeepalivePacketData(in);
|
||||
}
|
||||
|
||||
public TcpKeepalivePacketData[] newArray(int size) {
|
||||
return new TcpKeepalivePacketData[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
45
core/java/android/net/TcpRepairWindow.java
Normal file
45
core/java/android/net/TcpRepairWindow.java
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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;
|
||||
|
||||
/**
|
||||
* Corresponds to C's {@code struct tcp_repair_window} from
|
||||
* include/uapi/linux/tcp.h
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class TcpRepairWindow {
|
||||
public final int sndWl1;
|
||||
public final int sndWnd;
|
||||
public final int maxWindow;
|
||||
public final int rcvWnd;
|
||||
public final int rcvWup;
|
||||
public final int rcvWndScale;
|
||||
|
||||
/**
|
||||
* Constructs an instance with the given field values.
|
||||
*/
|
||||
public TcpRepairWindow(final int sndWl1, final int sndWnd, final int maxWindow,
|
||||
final int rcvWnd, final int rcvWup, final int rcvWndScale) {
|
||||
this.sndWl1 = sndWl1;
|
||||
this.sndWnd = sndWnd;
|
||||
this.maxWindow = maxWindow;
|
||||
this.rcvWnd = rcvWnd;
|
||||
this.rcvWup = rcvWup;
|
||||
this.rcvWndScale = rcvWndScale;
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <net/if.h>
|
||||
#include <linux/filter.h>
|
||||
#include <linux/if_arp.h>
|
||||
#include <linux/tcp.h>
|
||||
#include <netinet/ether.h>
|
||||
#include <netinet/icmp6.h>
|
||||
#include <netinet/ip.h>
|
||||
@@ -226,6 +227,34 @@ static void android_net_utils_attachControlPacketFilter(
|
||||
}
|
||||
}
|
||||
|
||||
static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
|
||||
{
|
||||
struct sock_filter filter_code[] = {
|
||||
// Reject all.
|
||||
BPF_STMT(BPF_RET | BPF_K, 0)
|
||||
};
|
||||
struct sock_fprog filter = {
|
||||
sizeof(filter_code) / sizeof(filter_code[0]),
|
||||
filter_code,
|
||||
};
|
||||
|
||||
int fd = jniGetFDFromFileDescriptor(env, javaFd);
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
|
||||
jniThrowExceptionFmt(env, "java/net/SocketException",
|
||||
"setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
static void android_net_utils_detachBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
|
||||
{
|
||||
int dummy = 0;
|
||||
int fd = jniGetFDFromFileDescriptor(env, javaFd);
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, &dummy, sizeof(dummy)) != 0) {
|
||||
jniThrowExceptionFmt(env, "java/net/SocketException",
|
||||
"setsockopt(SO_DETACH_FILTER): %s", strerror(errno));
|
||||
}
|
||||
|
||||
}
|
||||
static void android_net_utils_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd,
|
||||
jint ifIndex)
|
||||
{
|
||||
@@ -458,6 +487,41 @@ static jbyteArray android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz,
|
||||
return answer;
|
||||
}
|
||||
|
||||
static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) {
|
||||
if (javaFd == NULL) {
|
||||
jniThrowNullPointerException(env, NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int fd = jniGetFDFromFileDescriptor(env, javaFd);
|
||||
struct tcp_repair_window trw = {};
|
||||
socklen_t size = sizeof(trw);
|
||||
|
||||
// Obtain the parameters of the TCP repair window.
|
||||
int rc = getsockopt(fd, IPPROTO_TCP, TCP_REPAIR_WINDOW, &trw, &size);
|
||||
if (rc == -1) {
|
||||
throwErrnoException(env, "getsockopt : TCP_REPAIR_WINDOW", errno);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct tcp_info tcpinfo = {};
|
||||
socklen_t tcpinfo_size = sizeof(tcp_info);
|
||||
|
||||
// Obtain the window scale from the tcp info structure. This contains a scale factor that
|
||||
// should be applied to the window size.
|
||||
rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcpinfo, &tcpinfo_size);
|
||||
if (rc == -1) {
|
||||
throwErrnoException(env, "getsockopt : TCP_INFO", errno);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
jclass class_TcpRepairWindow = env->FindClass("android/net/TcpRepairWindow");
|
||||
jmethodID ctor = env->GetMethodID(class_TcpRepairWindow, "<init>", "(IIIIII)V");
|
||||
|
||||
return env->NewObject(class_TcpRepairWindow, ctor, trw.snd_wl1, trw.snd_wnd, trw.max_window,
|
||||
trw.rcv_wnd, trw.rcv_wup, tcpinfo.tcpi_rcv_wscale);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
@@ -475,6 +539,9 @@ static const JNINativeMethod gNetworkUtilMethods[] = {
|
||||
{ "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter },
|
||||
{ "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
|
||||
{ "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter },
|
||||
{ "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter },
|
||||
{ "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter },
|
||||
{ "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow },
|
||||
{ "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket },
|
||||
{ "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
|
||||
{ "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
|
||||
|
||||
@@ -381,8 +381,7 @@ public class KeepaliveTracker {
|
||||
}
|
||||
KeepaliveInfo ki = new KeepaliveInfo(messenger, binder, nai, packet, intervalSeconds);
|
||||
Log.d(TAG, "Created keepalive: " + ki.toString());
|
||||
mConnectivityServiceHandler.obtainMessage(
|
||||
CMD_START_SOCKET_KEEPALIVE, ki).sendToTarget();
|
||||
mConnectivityServiceHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, ki).sendToTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.NetworkAgent.EVENT_SOCKET_KEEPALIVE;
|
||||
import static android.net.SocketKeepalive.DATA_RECEIVED;
|
||||
import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
|
||||
import static android.net.SocketKeepalive.ERROR_SOCKET_NOT_IDLE;
|
||||
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
|
||||
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
|
||||
import static android.system.OsConstants.FIONREAD;
|
||||
import static android.system.OsConstants.IPPROTO_TCP;
|
||||
import static android.system.OsConstants.TIOCOUTQ;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.net.NetworkUtils;
|
||||
import android.net.SocketKeepalive.InvalidSocketException;
|
||||
import android.net.TcpKeepalivePacketData.TcpSocketInfo;
|
||||
import android.net.TcpRepairWindow;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.MessageQueue;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Int32Ref;
|
||||
import android.system.Os;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
|
||||
/**
|
||||
* Manage tcp socket which offloads tcp keepalive.
|
||||
*
|
||||
* The input socket will be changed to repair mode and the application
|
||||
* will not have permission to read/write data. If the application wants
|
||||
* to write data, it must stop tcp keepalive offload to leave repair mode
|
||||
* first. If a remote packet arrives, repair mode will be turned off and
|
||||
* offload will be stopped. The application will receive a callback to know
|
||||
* it can start reading data.
|
||||
*
|
||||
* {start,stop}SocketMonitor are thread-safe, but care must be taken in the
|
||||
* order in which they are called. Please note that while calling
|
||||
* {@link #startSocketMonitor(FileDescriptor, Messenger, int)} multiple times
|
||||
* with either the same slot or the same FileDescriptor without stopping it in
|
||||
* between will result in an exception, calling {@link #stopSocketMonitor(int)}
|
||||
* multiple times with the same int is explicitly a no-op.
|
||||
* Please also note that switching the socket to repair mode is not synchronized
|
||||
* with either of these operations and has to be done in an orderly fashion
|
||||
* with stopSocketMonitor. Take care in calling these in the right order.
|
||||
* @hide
|
||||
*/
|
||||
public class TcpKeepaliveController {
|
||||
private static final String TAG = "TcpKeepaliveController";
|
||||
private static final boolean DBG = false;
|
||||
|
||||
private final MessageQueue mFdHandlerQueue;
|
||||
|
||||
private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
|
||||
|
||||
// Reference include/uapi/linux/tcp.h
|
||||
private static final int TCP_REPAIR = 19;
|
||||
private static final int TCP_REPAIR_QUEUE = 20;
|
||||
private static final int TCP_QUEUE_SEQ = 21;
|
||||
private static final int TCP_NO_QUEUE = 0;
|
||||
private static final int TCP_RECV_QUEUE = 1;
|
||||
private static final int TCP_SEND_QUEUE = 2;
|
||||
private static final int TCP_REPAIR_OFF = 0;
|
||||
private static final int TCP_REPAIR_ON = 1;
|
||||
// Reference include/uapi/linux/sockios.h
|
||||
private static final int SIOCINQ = FIONREAD;
|
||||
private static final int SIOCOUTQ = TIOCOUTQ;
|
||||
|
||||
/**
|
||||
* Keeps track of packet listeners.
|
||||
* Key: slot number of keepalive offload.
|
||||
* Value: {@link FileDescriptor} being listened to.
|
||||
*/
|
||||
@GuardedBy("mListeners")
|
||||
private final SparseArray<FileDescriptor> mListeners = new SparseArray<>();
|
||||
|
||||
public TcpKeepaliveController(final Handler connectivityServiceHandler) {
|
||||
mFdHandlerQueue = connectivityServiceHandler.getLooper().getQueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the tcp socket to repair mode and query tcp socket information.
|
||||
*
|
||||
* @param fd the fd of socket on which to use keepalive offload
|
||||
* @return a {@link TcpKeepalivePacketData#TcpSocketInfo} object for current
|
||||
* tcp/ip information.
|
||||
*/
|
||||
// TODO : make this private. It's far too confusing that this gets called from outside
|
||||
// at a time that nobody can understand, but the switch out is in this class only.
|
||||
public static TcpSocketInfo switchToRepairMode(FileDescriptor fd)
|
||||
throws InvalidSocketException {
|
||||
if (DBG) Log.i(TAG, "switchToRepairMode to start tcp keepalive : " + fd);
|
||||
final SocketAddress srcSockAddr;
|
||||
final SocketAddress dstSockAddr;
|
||||
final InetAddress srcAddress;
|
||||
final InetAddress dstAddress;
|
||||
final int srcPort;
|
||||
final int dstPort;
|
||||
int seq;
|
||||
final int ack;
|
||||
final TcpRepairWindow trw;
|
||||
|
||||
// Query source address and port.
|
||||
try {
|
||||
srcSockAddr = Os.getsockname(fd);
|
||||
} catch (ErrnoException e) {
|
||||
Log.e(TAG, "Get sockname fail: ", e);
|
||||
throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
|
||||
}
|
||||
if (srcSockAddr instanceof InetSocketAddress) {
|
||||
srcAddress = getAddress((InetSocketAddress) srcSockAddr);
|
||||
srcPort = getPort((InetSocketAddress) srcSockAddr);
|
||||
} else {
|
||||
Log.e(TAG, "Invalid or mismatched SocketAddress");
|
||||
throw new InvalidSocketException(ERROR_INVALID_SOCKET);
|
||||
}
|
||||
// Query destination address and port.
|
||||
try {
|
||||
dstSockAddr = Os.getpeername(fd);
|
||||
} catch (ErrnoException e) {
|
||||
Log.e(TAG, "Get peername fail: ", e);
|
||||
throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
|
||||
}
|
||||
if (dstSockAddr instanceof InetSocketAddress) {
|
||||
dstAddress = getAddress((InetSocketAddress) dstSockAddr);
|
||||
dstPort = getPort((InetSocketAddress) dstSockAddr);
|
||||
} else {
|
||||
Log.e(TAG, "Invalid or mismatched peer SocketAddress");
|
||||
throw new InvalidSocketException(ERROR_INVALID_SOCKET);
|
||||
}
|
||||
|
||||
// Query sequence and ack number
|
||||
dropAllIncomingPackets(fd, true);
|
||||
try {
|
||||
// Enter tcp repair mode.
|
||||
Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_ON);
|
||||
// Check if socket is idle.
|
||||
if (!isSocketIdle(fd)) {
|
||||
throw new InvalidSocketException(ERROR_SOCKET_NOT_IDLE);
|
||||
}
|
||||
// Query write sequence number from SEND_QUEUE.
|
||||
Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_SEND_QUEUE);
|
||||
seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
|
||||
// Query read sequence number from RECV_QUEUE.
|
||||
Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_RECV_QUEUE);
|
||||
ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
|
||||
// Switch to NO_QUEUE to prevent illegal socket read/write in repair mode.
|
||||
Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_NO_QUEUE);
|
||||
// Finally, check if socket is still idle. TODO : this check needs to move to
|
||||
// after starting polling to prevent a race.
|
||||
if (!isSocketIdle(fd)) {
|
||||
throw new InvalidSocketException(ERROR_INVALID_SOCKET);
|
||||
}
|
||||
|
||||
// Query tcp window size.
|
||||
trw = NetworkUtils.getTcpRepairWindow(fd);
|
||||
} catch (ErrnoException e) {
|
||||
Log.e(TAG, "Exception reading TCP state from socket", e);
|
||||
try {
|
||||
Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_OFF);
|
||||
} catch (ErrnoException ex) {
|
||||
Log.e(TAG, "Exception while turning off repair mode due to exception", ex);
|
||||
}
|
||||
throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
|
||||
} finally {
|
||||
dropAllIncomingPackets(fd, false);
|
||||
}
|
||||
|
||||
// Keepalive sequence number is last sequence number - 1. If it couldn't be retrieved,
|
||||
// then it must be set to -1, so decrement in all cases.
|
||||
seq = seq - 1;
|
||||
|
||||
return new TcpSocketInfo(srcAddress, srcPort, dstAddress, dstPort, seq, ack, trw.rcvWnd,
|
||||
trw.rcvWndScale);
|
||||
}
|
||||
|
||||
private static void switchOutOfRepairMode(@NonNull final FileDescriptor fd)
|
||||
throws ErrnoException {
|
||||
Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_OFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start monitoring incoming packets.
|
||||
*
|
||||
* @param fd socket fd to monitor.
|
||||
* @param messenger a callback to notify socket status.
|
||||
* @param slot keepalive slot.
|
||||
*/
|
||||
public void startSocketMonitor(@NonNull final FileDescriptor fd,
|
||||
@NonNull final Messenger messenger, final int slot) {
|
||||
synchronized (mListeners) {
|
||||
if (null != mListeners.get(slot)) {
|
||||
throw new IllegalArgumentException("This slot is already taken");
|
||||
}
|
||||
for (int i = 0; i < mListeners.size(); ++i) {
|
||||
if (fd.equals(mListeners.valueAt(i))) {
|
||||
throw new IllegalArgumentException("This fd is already registered");
|
||||
}
|
||||
}
|
||||
mFdHandlerQueue.addOnFileDescriptorEventListener(fd, FD_EVENTS, (readyFd, events) -> {
|
||||
// This can't be called twice because the queue guarantees that once the listener
|
||||
// is unregistered it can't be called again, even for a message that arrived
|
||||
// before it was unregistered.
|
||||
int result;
|
||||
try {
|
||||
// First move the socket out of repair mode.
|
||||
if (DBG) Log.d(TAG, "Moving socket out of repair mode for event : " + readyFd);
|
||||
switchOutOfRepairMode(readyFd);
|
||||
result = (0 != (events & EVENT_ERROR)) ? ERROR_INVALID_SOCKET : DATA_RECEIVED;
|
||||
} catch (ErrnoException e) {
|
||||
// Could not move the socket out of repair mode. Still continue with notifying
|
||||
// the client
|
||||
Log.e(TAG, "Cannot switch socket out of repair mode", e);
|
||||
result = ERROR_INVALID_SOCKET;
|
||||
}
|
||||
// Prepare and send the message to the receiver.
|
||||
final Message message = Message.obtain();
|
||||
message.what = EVENT_SOCKET_KEEPALIVE;
|
||||
message.arg1 = slot;
|
||||
message.arg2 = result;
|
||||
try {
|
||||
messenger.send(message);
|
||||
} catch (RemoteException e) {
|
||||
// Remote process died
|
||||
}
|
||||
synchronized (mListeners) {
|
||||
mListeners.remove(slot);
|
||||
}
|
||||
// The listener returns the new set of events to listen to. Because 0 means no
|
||||
// event, the listener gets unregistered.
|
||||
return 0;
|
||||
});
|
||||
mListeners.put(slot, fd);
|
||||
}
|
||||
}
|
||||
|
||||
/** Stop socket monitor */
|
||||
// This slot may have been stopped automatically already because the socket received data,
|
||||
// was closed on the other end or otherwise suffered some error. In this case, this function
|
||||
// is a no-op.
|
||||
public void stopSocketMonitor(final int slot) {
|
||||
final FileDescriptor fd;
|
||||
synchronized (mListeners) {
|
||||
fd = mListeners.get(slot);
|
||||
if (null == fd) return;
|
||||
mListeners.remove(slot);
|
||||
}
|
||||
mFdHandlerQueue.removeOnFileDescriptorEventListener(fd);
|
||||
try {
|
||||
if (DBG) Log.d(TAG, "Moving socket out of repair mode for stop : " + fd);
|
||||
switchOutOfRepairMode(fd);
|
||||
} catch (ErrnoException e) {
|
||||
Log.e(TAG, "Cannot switch socket out of repair mode", e);
|
||||
// Well, there is not much to do here to recover
|
||||
}
|
||||
}
|
||||
|
||||
private static InetAddress getAddress(InetSocketAddress inetAddr) {
|
||||
return inetAddr.getAddress();
|
||||
}
|
||||
|
||||
private static int getPort(InetSocketAddress inetAddr) {
|
||||
return inetAddr.getPort();
|
||||
}
|
||||
|
||||
private static boolean isSocketIdle(FileDescriptor fd) throws ErrnoException {
|
||||
return isReceiveQueueEmpty(fd) && isSendQueueEmpty(fd);
|
||||
}
|
||||
|
||||
private static boolean isReceiveQueueEmpty(FileDescriptor fd)
|
||||
throws ErrnoException {
|
||||
Int32Ref result = new Int32Ref(-1);
|
||||
Os.ioctlInt(fd, SIOCINQ, result);
|
||||
if (result.value != 0) {
|
||||
Log.e(TAG, "Read queue has data");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isSendQueueEmpty(FileDescriptor fd)
|
||||
throws ErrnoException {
|
||||
Int32Ref result = new Int32Ref(-1);
|
||||
Os.ioctlInt(fd, SIOCOUTQ, result);
|
||||
if (result.value != 0) {
|
||||
Log.e(TAG, "Write queue has data");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void dropAllIncomingPackets(FileDescriptor fd, boolean enable)
|
||||
throws InvalidSocketException {
|
||||
try {
|
||||
if (enable) {
|
||||
NetworkUtils.attachDropAllBPFFilter(fd);
|
||||
} else {
|
||||
NetworkUtils.detachBPFFilter(fd);
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
Log.e(TAG, "Socket Exception: ", e);
|
||||
throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,18 +22,15 @@ import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.net.IpPrefix;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.LinkProperties.CompareResult;
|
||||
import android.net.LinkProperties.ProvisioningChange;
|
||||
import android.net.RouteInfo;
|
||||
import android.os.Parcel;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.system.OsConstants;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import com.android.internal.util.TestUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -849,18 +846,6 @@ public class LinkPropertiesTest {
|
||||
assertEquals(new ArraySet<>(expectRemoved), (new ArraySet<>(result.removed)));
|
||||
}
|
||||
|
||||
private void assertParcelingIsLossless(LinkProperties source) {
|
||||
Parcel p = Parcel.obtain();
|
||||
source.writeToParcel(p, /* flags */ 0);
|
||||
p.setDataPosition(0);
|
||||
final byte[] marshalled = p.marshall();
|
||||
p = Parcel.obtain();
|
||||
p.unmarshall(marshalled, 0, marshalled.length);
|
||||
p.setDataPosition(0);
|
||||
LinkProperties dest = LinkProperties.CREATOR.createFromParcel(p);
|
||||
assertEquals(source, dest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLinkPropertiesParcelable() throws Exception {
|
||||
LinkProperties source = new LinkProperties();
|
||||
@@ -882,12 +867,12 @@ public class LinkPropertiesTest {
|
||||
|
||||
source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96"));
|
||||
|
||||
assertParcelingIsLossless(source);
|
||||
TestUtils.assertParcelingIsLossless(source, LinkProperties.CREATOR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParcelUninitialized() throws Exception {
|
||||
LinkProperties empty = new LinkProperties();
|
||||
assertParcelingIsLossless(empty);
|
||||
TestUtils.assertParcelingIsLossless(empty, LinkProperties.CREATOR);
|
||||
}
|
||||
}
|
||||
|
||||
102
tests/net/java/android/net/TcpKeepalivePacketDataTest.java
Normal file
102
tests/net/java/android/net/TcpKeepalivePacketDataTest.java
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.net.SocketKeepalive.InvalidPacketException;
|
||||
import android.net.TcpKeepalivePacketData.TcpSocketInfo;
|
||||
|
||||
import com.android.internal.util.TestUtils;
|
||||
|
||||
import libcore.net.InetAddressUtils;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public final class TcpKeepalivePacketDataTest {
|
||||
|
||||
@Before
|
||||
public void setUp() {}
|
||||
|
||||
@Test
|
||||
public void testV4TcpKeepalivePacket() {
|
||||
final InetAddress srcAddr = InetAddressUtils.parseNumericAddress("192.168.0.1");
|
||||
final InetAddress dstAddr = InetAddressUtils.parseNumericAddress("192.168.0.10");
|
||||
final int srcPort = 1234;
|
||||
final int dstPort = 4321;
|
||||
final int seq = 0x11111111;
|
||||
final int ack = 0x22222222;
|
||||
final int wnd = 8000;
|
||||
final int wndScale = 2;
|
||||
TcpKeepalivePacketData resultData = null;
|
||||
TcpSocketInfo testInfo = new TcpSocketInfo(
|
||||
srcAddr, srcPort, dstAddr, dstPort, seq, ack, wnd, wndScale);
|
||||
try {
|
||||
resultData = TcpKeepalivePacketData.tcpKeepalivePacket(testInfo);
|
||||
} catch (InvalidPacketException e) {
|
||||
fail("InvalidPacketException: " + e);
|
||||
}
|
||||
|
||||
assertEquals(testInfo.srcAddress, resultData.srcAddress);
|
||||
assertEquals(testInfo.dstAddress, resultData.dstAddress);
|
||||
assertEquals(testInfo.srcPort, resultData.srcPort);
|
||||
assertEquals(testInfo.dstPort, resultData.dstPort);
|
||||
assertEquals(testInfo.seq, resultData.tcpSeq);
|
||||
assertEquals(testInfo.ack, resultData.tcpAck);
|
||||
assertEquals(testInfo.rcvWndScale, resultData.tcpWndScale);
|
||||
|
||||
TestUtils.assertParcelingIsLossless(resultData, TcpKeepalivePacketData.CREATOR);
|
||||
|
||||
final byte[] packet = resultData.getPacket();
|
||||
// IP version and TOS.
|
||||
ByteBuffer buf = ByteBuffer.wrap(packet);
|
||||
assertEquals(buf.getShort(), 0x4500);
|
||||
// Source IP address.
|
||||
byte[] ip = new byte[4];
|
||||
buf = ByteBuffer.wrap(packet, 12, 4);
|
||||
buf.get(ip);
|
||||
assertArrayEquals(ip, srcAddr.getAddress());
|
||||
// Destination IP address.
|
||||
buf = ByteBuffer.wrap(packet, 16, 4);
|
||||
buf.get(ip);
|
||||
assertArrayEquals(ip, dstAddr.getAddress());
|
||||
|
||||
buf = ByteBuffer.wrap(packet, 20, 12);
|
||||
// Source port.
|
||||
assertEquals(buf.getShort(), srcPort);
|
||||
// Destination port.
|
||||
assertEquals(buf.getShort(), dstPort);
|
||||
// Sequence number.
|
||||
assertEquals(buf.getInt(), seq);
|
||||
// Ack.
|
||||
assertEquals(buf.getInt(), ack);
|
||||
// Window size.
|
||||
buf = ByteBuffer.wrap(packet, 34, 2);
|
||||
assertEquals(buf.getShort(), wnd >> wndScale);
|
||||
}
|
||||
|
||||
//TODO: add ipv6 test when ipv6 supported
|
||||
}
|
||||
Reference in New Issue
Block a user