Skip to content

Instantly share code, notes, and snippets.

@Camerash
Created April 20, 2021 08:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Camerash/f3ca4a930d537306c040ec3555865a76 to your computer and use it in GitHub Desktop.
Save Camerash/f3ca4a930d537306c040ec3555865a76 to your computer and use it in GitHub Desktop.
Enable TCP Keepalive per application in Android using OkHttp
import okhttp3.OkHttpClient;
public class OkHttpUtil {
public static OkHttpClient getOkHttpClient() {
return new OkHttpClient.Builder()
.socketFactory(new TCPKeepaliveSocketFactory(60, 60, 5))
.build();
}
public static OkHttpClient getSSLOkHttpClient() {
return new OkHttpClient.Builder()
.sslSocketFactory(new TCPKeepaliveSSLSocketFactory(getSSLSocketFactory(), 60, 60, 5), getX509TrustManager())
.build();
}
}
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.net.SocketFactory;
public class TCPKeepaliveSocketFactory extends SocketFactory {
private final int idleTimeout;
private final int interval;
private final int count;
public TCPKeepaliveSocketFactory(int idleTimeout, int interval, int count) {
this.idleTimeout = idleTimeout;
this.interval = interval;
this.count = count;
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
Socket socket = new Socket(host, port);
TCPKeepaliveSocketUtil.setTCPKeepAlive(socket, idleTimeout, interval, count);
return socket;
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
Socket socket = new Socket(host, port, localHost, localPort);
TCPKeepaliveSocketUtil.setTCPKeepAlive(socket, idleTimeout, interval, count);
return socket;
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
Socket socket = new Socket(host, port);
TCPKeepaliveSocketUtil.setTCPKeepAlive(socket, idleTimeout, interval, count);
return socket;
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
Socket socket = new Socket(address, port, localAddress, localPort);
TCPKeepaliveSocketUtil.setTCPKeepAlive(socket, idleTimeout, interval, count);
return socket;
}
}
import android.system.Os;
import java.io.FileDescriptor;
import java.lang.reflect.Field;
import java.net.Socket;
public class TCPKeepaliveSocketUtil {
/**
* See https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
*/
private final static int SOL_TCP_LEVEL = 6;
/**
* See https://cs.android.com/android/platform/superproject/+/android-10.0.0_r30:bionic/libc/kernel/uapi/linux/tcp.h
*/
private final static int TCP_KEEPIDLE_OPTION = 4;
private final static int TCP_KEEPINTVL_OPTION = 5;
private final static int TCP_KEEPCNT_OPTION = 6;
/**
* Enable and configure TCP Keep Alive on provided socket
* Notice the socket should not be an SSLSocket as it's file descriptor will be unrecognisable by the OS
* To use an SSLSocket, pass a normal socket to this function first, then construct an SSLSocket using the normal socket.
*
* @param socket Socket to be configured.
* @param idleTimeout The interval between the last data packet sent (simple ACKs are not considered data) and the first keepalive probe in seconds
* @param interval The interval between subsequential keepalive probes in seconds, regardless of what the connection has exchanged in the meantime
* @param count The number of unacknowledged probes to send before considering the connection dead and notifying the application layer
*/
@SuppressWarnings("JavaReflectionMemberAccess")
public static void setTCPKeepAlive(Socket socket, int idleTimeout, int interval, int count) {
try {
socket.setKeepAlive(true);
Field socketImplField = Class.forName("java.net.Socket").getDeclaredField("impl");
if (socketImplField != null) {
socketImplField.setAccessible(true);
Object socketImpl = socketImplField.get(socket);
Field fileDescriptorField = Class.forName("java.net.SocketImpl").getDeclaredField("fd");
if (fileDescriptorField != null) {
fileDescriptorField.setAccessible(true);
final FileDescriptor fileDescriptor = (FileDescriptor) fileDescriptorField.get(socketImpl);
Os.setsockoptInt(fileDescriptor, SOL_TCP_LEVEL, TCP_KEEPIDLE_OPTION, idleTimeout);
Os.setsockoptInt(fileDescriptor, SOL_TCP_LEVEL, TCP_KEEPINTVL_OPTION, interval);
Os.setsockoptInt(fileDescriptor, SOL_TCP_LEVEL, TCP_KEEPCNT_OPTION, count);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.net.ssl.SSLSocketFactory;
public class TCPKeepaliveSSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory internalSSLSocketFactory;
private int idleTimeout = 50;
private int interval = 50;
private int count = 3;
public TCPKeepaliveSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
this.internalSSLSocketFactory = sslSocketFactory;
}
public TCPKeepaliveSSLSocketFactory(SSLSocketFactory sslSocketFactory, int idleTimeout, int interval, int count) {
this.internalSSLSocketFactory = sslSocketFactory;
this.idleTimeout = idleTimeout;
this.interval = interval;
this.count = count;
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
Socket socket = internalSSLSocketFactory.createSocket(host, port);
TCPKeepaliveSocketUtil.setTCPKeepAlive(socket, idleTimeout, interval, count);
return socket;
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
Socket socket = internalSSLSocketFactory.createSocket(host, port, localHost, localPort);
TCPKeepaliveSocketUtil.setTCPKeepAlive(socket, idleTimeout, interval, count);
return socket;
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
Socket socket = internalSSLSocketFactory.createSocket(host, port);
TCPKeepaliveSocketUtil.setTCPKeepAlive(socket, idleTimeout, interval, count);
return socket;
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
Socket socket = internalSSLSocketFactory.createSocket(address, port, localAddress, localPort);
TCPKeepaliveSocketUtil.setTCPKeepAlive(socket, idleTimeout, interval, count);
return socket;
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
TCPKeepaliveSocketUtil.setTCPKeepAlive(s, idleTimeout, interval, count); // Use underlying real socket to set TCP keep alive instead of wrapped SSLSocket
return internalSSLSocketFactory.createSocket(s, host, port, autoClose);
}
@Override
public String[] getDefaultCipherSuites() {
return internalSSLSocketFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return internalSSLSocketFactory.getSupportedCipherSuites();
}
}
@Camerash
Copy link
Author

Camerash commented Apr 20, 2021

Based on this fantastic answer on StackOverflow, updated with new Android API and adapted to OkHttp
Tested working on both Android 9 and 10

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment