Защитите сокет в VpnService
Я изучаю возможности Android VpnService. В настоящее время я построил очень простой перенаправитель запросов, по сути, перестраивая стек IP в пространстве пользователя: я читаю IP-пакеты из входного потока VpnService, анализирую их, а для соединений, которые не хочу пересылать, я пытаюсь воссоздать их сокетные соединения вне VPN-соединения.
Я понял, что этому последнему моменту способствует VpnService.protect()
и попытался реализовать его следующим образом:
Socket socket = new Socket();
vpnService.protect(socket);
socket.connect(new InetSocketAddress(
header.getDestinationAddress(), // From my IP datagram header
body.getDestinationPort())); // From the TCP datagram header
К сожалению, этот подход вызывает обратную петлю в интерфейсе VPN.
Принимая во внимание, что приведенный выше код будет просто блокировать и в конечном итоге время ожидания, я наблюдаю обратную петлю Socket.connect(InetSocketAddress)
из отдельной ветки; соединение возвращается обратно во входной поток моего VpnService, и процесс повторяется.
Излишне говорить, что это вызывает петлю. У меня такое ощущение, что причина этого в том, что во время создания сокета (а затем VpnService.protect(Socket)
), Я еще не установил IP-адрес и порт назначения.
Похоже, что это действительно так, поскольку VpnService.protect(Socket)
а также VpnService.protect(int)
в моей реализации VpnService и при вызове supers в обоих случаях возвращается false.
Как правильно защитить сокетное соединение?
4 ответа
Следующий код работает.
Socket socket = SocketChannel.open().socket();
if ((null != socket) && (null != vpnService)) {
vpnService.protect(socket);
}
socket.connect(...);
new Socket () не имеет допустимого дескриптора файла, поэтому он не может быть защищен.
Вы должны связать свою розетку перед защитой. Это работает для меня:
Socket socket = new Socket();
//bind to any address
socket.bind(new InetSocketAddress(0));
vpnService.protect(socket);
socket.connect(...);
Я обнаружил, что альтернативным решением было написать его на C/C++.
Джава:
public native int createSocket();
public native int connectSocket(int fd);
C++:
// For sockets
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// For error codes
#include <errno.h>
extern "C" {
JNIEXPORT jint JNICALL
Java_com_pixplicity_example_jni_VpnInterface_createSocket(
JNIEnv * env, jobject thiz) {
// Create the socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
int err = errno;
// Return the file descriptor
return sockfd;
}
JNIEXPORT jint JNICALL
Java_com_pixplicity_example_jni_VpnInterface_connectSocket(
JNIEnv * env, jobject thiz, jint sockFd) {
// Host & port are hard-coded here
char* host = "74.125.136.113"; // google.com
int port = 80;
struct sockaddr_in peerAddr;
int ret;
peerAddr.sin_family = AF_INET;
peerAddr.sin_port = htons(port);
peerAddr.sin_addr.s_addr = inet_addr(host);
// Connect to host
ret = connect((int) sockFd, (struct sockaddr *) &peerAddr,
sizeof(peerAddr));
if (ret != 0) {
perror("connect failed");
close(sockFd);
}
// Return the error code
return ret;
}
}
Мне нужно было защитить розетки в
OkHttpClient
. Он создает неподключенные сокеты, которые еще нельзя защитить (это означает, что
service.protect()
возвращается
false
), а когда они подключены, очевидно, слишком поздно (например, когда я пытался защитить их внутри
networkInterceptor
). Классическое непонятное поведение Android.
Во всяком случае, ответ Май Куок Хай был правильным для меня, и весь код выглядит так.
val protectedHttpClient = OkHttpClient.Builder()
.socketFactory(object : SocketFactory() {
override fun createSocket(): Socket = Socket().apply {
bind(InetSocketAddress(0))
val result = service?.protect(this)
}
override fun createSocket(host: String?, port: Int) = unsupported()
override fun createSocket(host: String?, port: Int, localHost: InetAddress?, localPort: Int) = unsupported()
override fun createSocket(host: InetAddress?, port: Int) = unsupported()
override fun createSocket(address: InetAddress?, port: Int, localAddress: InetAddress?, localPort: Int) = unsupported()
private fun unsupported(): Nothing = throw UnsupportedOperationException("This factory can only create unconnected sockets for OkHttp")
})
.build()
Вы можете исключить использование сокетов вашего приложения (и, следовательно, трафика, проходящего через него) из VPN, добавив ваше приложение в vpnService.builder.addDisallowedApplication("your package name")
Я попробовал это и проверил это с запуском tcpdump
как на туннельном интерфейсе VPN, так и на исходящем интернет-интерфейсе. Пакеты из моих приложений не зацикливаются в интерфейсе vpn и отправляются через передний интернет-интерфейс телефона.