android: Pin direct-flow sockets to the active network (2.13.10)
Android / pre-check (push) Failing after 6s
Android / android (push) Skipped
CIFuzz / pre-check (push) Failing after 4s
CIFuzz / fuzzing (address) (push) Skipped
CIFuzz / fuzzing (undefined) (push) Skipped
CodeQL / pre-check (push) Failing after 3s
CodeQL / analyze (python) (push) Skipped
CodeQL / analyze (ruby) (push) Skipped
CodeQL / analyze-cpp (push) Skipped
FreeBSD / pre-check (push) Failing after 3s
FreeBSD / freebsd (14.4) (push) Skipped
FreeBSD / freebsd (15.0) (push) Skipped
Linux / pre-check (push) Failing after 3s
Linux / latest (apidoc) (push) Skipped
Linux / latest (clang, no, all) (push) Skipped
Linux / latest (clang, no, default) (push) Skipped
Linux / latest (clang, no, printf-builtin) (push) Skipped
Linux / latest (clang, no-dbg) (push) Skipped
Linux / latest (clang, no-testable-ke) (push) Skipped
Linux / latest (clang, yes, all) (push) Skipped
Linux / latest (clang, yes, default) (push) Skipped
Linux / latest (clang, yes, fuzzing) (push) Skipped
Linux / latest (coverage) (push) Skipped
Linux / latest (dist) (push) Skipped
Linux / latest (gcc, no, all) (push) Skipped
Linux / latest (gcc, no, default) (push) Skipped
Linux / latest (gcc, no, printf-builtin) (push) Skipped
Linux / latest (gcc, yes, all) (push) Skipped
Linux / latest (gcc, yes, default) (push) Skipped
Linux / latest (nm) (push) Skipped
Linux / latest (no-dbg) (push) Skipped
Linux / latest (no-testable-ke) (push) Skipped
Linux / latest (yes, ld) (push) Skipped
Linux / crypto (ubuntu-22.04, openssl-sys) (push) Skipped
Linux / crypto (ubuntu-latest, botan) (push) Skipped
Linux / crypto (ubuntu-latest, openssl-3) (push) Skipped
Linux / crypto (ubuntu-latest, openssl-4) (push) Skipped
Linux / crypto (ubuntu-latest, openssl-awslc) (push) Skipped
Linux / crypto (ubuntu-latest, openssl-sys) (push) Skipped
Linux / crypto (ubuntu-latest, wolfssl) (push) Skipped
Linux / older (clang, ubuntu-22.04, all) (push) Skipped
Linux / older (gcc, ubuntu-22.04, all) (push) Skipped
Linux / older (gcc, ubuntu-22.04, nm) (push) Skipped
Linux / alpine (push) Skipped
macOS / pre-check (push) Failing after 5s
macOS / macos (macos-14) (push) Skipped
macOS / macos (macos-latest) (push) Skipped
SonarCloud / pre-check (push) Canceled after 3s
SonarCloud / sonarcloud (push) Canceled after 0s
TKM / pre-check (push) Canceled after 0s
TKM / tkm (push) Canceled after 0s
Windows / pre-check (push) Canceled after 1s
Windows / cross-compile (win32) (push) Canceled after 0s
Windows / cross-compile (win64) (push) Canceled after 0s
Windows / native (i686, mingw32, win32) (push) Canceled after 0s
Windows / native (x86_64, mingw64, win64) (push) Canceled after 0s
Android / pre-check (push) Failing after 6s
Android / android (push) Skipped
CIFuzz / pre-check (push) Failing after 4s
CIFuzz / fuzzing (address) (push) Skipped
CIFuzz / fuzzing (undefined) (push) Skipped
CodeQL / pre-check (push) Failing after 3s
CodeQL / analyze (python) (push) Skipped
CodeQL / analyze (ruby) (push) Skipped
CodeQL / analyze-cpp (push) Skipped
FreeBSD / pre-check (push) Failing after 3s
FreeBSD / freebsd (14.4) (push) Skipped
FreeBSD / freebsd (15.0) (push) Skipped
Linux / pre-check (push) Failing after 3s
Linux / latest (apidoc) (push) Skipped
Linux / latest (clang, no, all) (push) Skipped
Linux / latest (clang, no, default) (push) Skipped
Linux / latest (clang, no, printf-builtin) (push) Skipped
Linux / latest (clang, no-dbg) (push) Skipped
Linux / latest (clang, no-testable-ke) (push) Skipped
Linux / latest (clang, yes, all) (push) Skipped
Linux / latest (clang, yes, default) (push) Skipped
Linux / latest (clang, yes, fuzzing) (push) Skipped
Linux / latest (coverage) (push) Skipped
Linux / latest (dist) (push) Skipped
Linux / latest (gcc, no, all) (push) Skipped
Linux / latest (gcc, no, default) (push) Skipped
Linux / latest (gcc, no, printf-builtin) (push) Skipped
Linux / latest (gcc, yes, all) (push) Skipped
Linux / latest (gcc, yes, default) (push) Skipped
Linux / latest (nm) (push) Skipped
Linux / latest (no-dbg) (push) Skipped
Linux / latest (no-testable-ke) (push) Skipped
Linux / latest (yes, ld) (push) Skipped
Linux / crypto (ubuntu-22.04, openssl-sys) (push) Skipped
Linux / crypto (ubuntu-latest, botan) (push) Skipped
Linux / crypto (ubuntu-latest, openssl-3) (push) Skipped
Linux / crypto (ubuntu-latest, openssl-4) (push) Skipped
Linux / crypto (ubuntu-latest, openssl-awslc) (push) Skipped
Linux / crypto (ubuntu-latest, openssl-sys) (push) Skipped
Linux / crypto (ubuntu-latest, wolfssl) (push) Skipped
Linux / older (clang, ubuntu-22.04, all) (push) Skipped
Linux / older (gcc, ubuntu-22.04, all) (push) Skipped
Linux / older (gcc, ubuntu-22.04, nm) (push) Skipped
Linux / alpine (push) Skipped
macOS / pre-check (push) Failing after 5s
macOS / macos (macos-14) (push) Skipped
macOS / macos (macos-latest) (push) Skipped
SonarCloud / pre-check (push) Canceled after 3s
SonarCloud / sonarcloud (push) Canceled after 0s
TKM / pre-check (push) Canceled after 0s
TKM / tkm (push) Canceled after 0s
Windows / pre-check (push) Canceled after 1s
Windows / cross-compile (win32) (push) Canceled after 0s
Windows / cross-compile (win64) (push) Canceled after 0s
Windows / native (i686, mingw32, win32) (push) Canceled after 0s
Windows / native (x86_64, mingw64, win64) (push) Canceled after 0s
Diag/charon logs from 2026-09-30 16:45 showed that after a Wi-Fi->cellular roam every direct-TCP connect failed asynchronously with EHOSTUNREACH for minutes, while the IKE socket worked and a Java socket explicitly bound to the underlying network connected fine: a bare VpnService.protect() left fresh sockets routed over the vanished network. - CharonVpnService.protectDirect(fd): protect + Network.bindSocket to the active non-VPN network (API 23+, best-effort, adoptFd/detachFd) - charonservice_protect_direct_socket() JNI wrapper; relay and UDP flow sockets use it at creation - direct_tcp: one retry on a fresh socket when an async connect fails; reprotect aborts still-connecting relays (RST) instead of letting the kernel take ~90s to report the dead route; failures log dst=ip:port - direct_udp: reprotect closes all flows; they re-open per packet with a fresh, network-pinned socket - TUN reader: EBADF after a reauth re-establish is no longer logged Published as 2.13.10 (versionCode 147).
This commit is contained in:
1 parent
d12fb96dc2
commit
e9bdf60f82
7 files changed
+202
-35
No files matched your search
@@ -37,8 +37,8 @@ android {
|
|||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 36
|
targetSdkVersion 36
|
||||||
|
|
||||||
versionCode 146
|
versionCode 147
|
||||||
versionName "2.13.9"
|
versionName "2.13.10"
|
||||||
|
|
||||||
/* Base URL of the update storage, trailing slash included */
|
/* Base URL of the update storage, trailing slash included */
|
||||||
buildConfigField "String", "UPDATE_BASE_URL", "\"${updateBaseUrl}\""
|
buildConfigField "String", "UPDATE_BASE_URL", "\"${updateBaseUrl}\""
|
||||||
|
|||||||
+60
@@ -1284,6 +1284,66 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
|
|||||||
return KeyChain.getPrivateKey(getApplicationContext(), mCurrentUserCertificateAlias);
|
return KeyChain.getPrivateKey(getApplicationContext(), mCurrentUserCertificateAlias);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called via JNI for the sockets of direct (RU-split) flows: besides the
|
||||||
|
* regular VPN bypass this pins the socket to the currently active physical
|
||||||
|
* network. A bare protect() leaves the network choice to the system's
|
||||||
|
* default-network bookkeeping, which was observed to keep routing fresh
|
||||||
|
* sockets over a network that vanished during a Wi-Fi/cellular roam
|
||||||
|
* (async EHOSTUNREACH), while sockets explicitly bound to the underlying
|
||||||
|
* network connected fine on the same network.
|
||||||
|
*
|
||||||
|
* Must be called before connect(). Best effort: on any failure the socket
|
||||||
|
* keeps the plain protect() routing.
|
||||||
|
*
|
||||||
|
* @param fd socket file descriptor
|
||||||
|
* @return TRUE if protect() itself succeeded
|
||||||
|
*/
|
||||||
|
public boolean protectDirect(int fd)
|
||||||
|
{
|
||||||
|
if (!protect(fd))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||||
|
{
|
||||||
|
android.net.Network network = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ConnectivityManager manager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
network = manager != null ? manager.getActiveNetwork() : null;
|
||||||
|
if (network != null)
|
||||||
|
{
|
||||||
|
android.net.NetworkCapabilities caps = manager.getNetworkCapabilities(network);
|
||||||
|
if (caps == null || caps.hasTransport(android.net.NetworkCapabilities.TRANSPORT_VPN))
|
||||||
|
{ /* while the tunnel is up our own default is the physical
|
||||||
|
* network; fall back to the tracked set otherwise */
|
||||||
|
android.net.Network[] tracked = mUnderlyingTracker.getNetworks();
|
||||||
|
network = tracked != null && tracked.length > 0 ? tracked[0] : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (network != null)
|
||||||
|
{
|
||||||
|
ParcelFileDescriptor pfd = ParcelFileDescriptor.adoptFd(fd);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
network.bindSocket(pfd.getFileDescriptor());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
/* release ownership without closing the fd, native code owns it */
|
||||||
|
pfd.detachFd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
DiagLog.i(TAG, "direct socket network bind skipped: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialization of charon, provided by libandroidbridge.so
|
* Initialization of charon, provided by libandroidbridge.so
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -188,7 +188,13 @@ static job_requeue_t handle_plain(private_android_service_t *this)
|
|||||||
len = read(tunfd, raw.ptr, raw.len);
|
len = read(tunfd, raw.ptr, raw.len);
|
||||||
if (len < 0)
|
if (len < 0)
|
||||||
{
|
{
|
||||||
DBG1(DBG_DMN, "reading from TUN device failed: %s", strerror(errno));
|
if (errno != EBADF)
|
||||||
|
{
|
||||||
|
DBG1(DBG_DMN, "reading from TUN device failed: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
/* EBADF: the TUN got closed between the check above and the read
|
||||||
|
* (e.g. a reauth re-established it) - retry, the next round sees
|
||||||
|
* tunfd < 0 and quits */
|
||||||
chunk_free(&raw);
|
chunk_free(&raw);
|
||||||
return JOB_REQUEUE_FAIR;
|
return JOB_REQUEUE_FAIR;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,9 +235,10 @@ failed:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bypass a single socket
|
* Bypass a single socket, optionally pinning it to the active network
|
||||||
*/
|
*/
|
||||||
static bool bypass_single_socket(private_charonservice_t *this, int fd)
|
static bool bypass_single_socket(private_charonservice_t *this, int fd,
|
||||||
|
bool direct)
|
||||||
{
|
{
|
||||||
JNIEnv *env;
|
JNIEnv *env;
|
||||||
jmethodID method_id;
|
jmethodID method_id;
|
||||||
@@ -245,7 +246,8 @@ static bool bypass_single_socket(private_charonservice_t *this, int fd)
|
|||||||
androidjni_attach_thread(&env);
|
androidjni_attach_thread(&env);
|
||||||
|
|
||||||
method_id = (*env)->GetMethodID(env, android_charonvpnservice_class,
|
method_id = (*env)->GetMethodID(env, android_charonvpnservice_class,
|
||||||
"protect", "(I)Z");
|
direct ? "protectDirect" : "protect",
|
||||||
|
"(I)Z");
|
||||||
if (!method_id)
|
if (!method_id)
|
||||||
{
|
{
|
||||||
goto failed;
|
goto failed;
|
||||||
@@ -270,7 +272,7 @@ CALLBACK(bypass_single_socket_cb, void,
|
|||||||
private_charonservice_t *this;
|
private_charonservice_t *this;
|
||||||
|
|
||||||
VA_ARGS_VGET(args, this);
|
VA_ARGS_VGET(args, this);
|
||||||
bypass_single_socket(this, fd);
|
bypass_single_socket(this, fd, FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
METHOD(charonservice_t, bypass_socket, bool,
|
METHOD(charonservice_t, bypass_socket, bool,
|
||||||
@@ -282,7 +284,7 @@ METHOD(charonservice_t, bypass_socket, bool,
|
|||||||
{
|
{
|
||||||
this->sockets->insert_last(this->sockets, (void*)(intptr_t)fd);
|
this->sockets->insert_last(this->sockets, (void*)(intptr_t)fd);
|
||||||
}
|
}
|
||||||
return bypass_single_socket(this, fd);
|
return bypass_single_socket(this, fd, FALSE);
|
||||||
}
|
}
|
||||||
this->sockets->invoke_function(this->sockets, bypass_single_socket_cb, this);
|
this->sockets->invoke_function(this->sockets, bypass_single_socket_cb, this);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@@ -303,7 +305,25 @@ bool charonservice_protect_socket(int fd)
|
|||||||
{
|
{
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
return bypass_single_socket(this, fd);
|
return bypass_single_socket(this, fd, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as charonservice_protect_socket(), but additionally pins the socket to
|
||||||
|
* the currently active physical network (Java protectDirect()). Used when a
|
||||||
|
* direct-flow socket is created: after a Wi-Fi/cellular roam a bare protect()
|
||||||
|
* was observed to keep routing fresh sockets over the vanished network
|
||||||
|
* (async EHOSTUNREACH), while explicitly bound sockets connected fine.
|
||||||
|
*/
|
||||||
|
bool charonservice_protect_direct_socket(int fd)
|
||||||
|
{
|
||||||
|
private_charonservice_t *this = (private_charonservice_t*)charonservice;
|
||||||
|
|
||||||
|
if (!this || fd < 0)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return bypass_single_socket(this, fd, TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -173,4 +173,11 @@ extern charonservice_t *charonservice;
|
|||||||
*/
|
*/
|
||||||
bool charonservice_protect_socket(int fd);
|
bool charonservice_protect_socket(int fd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like charonservice_protect_socket(), but additionally pins the socket to
|
||||||
|
* the currently active physical network (Java protectDirect()); call before
|
||||||
|
* connect(). For freshly created direct-flow sockets.
|
||||||
|
*/
|
||||||
|
bool charonservice_protect_direct_socket(int fd);
|
||||||
|
|
||||||
#endif /** CHARONSERVICE_H_ @}*/
|
#endif /** CHARONSERVICE_H_ @}*/
|
||||||
@@ -48,6 +48,7 @@
|
|||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@@ -74,6 +75,9 @@ typedef struct {
|
|||||||
bool connected;
|
bool connected;
|
||||||
bool dead;
|
bool dead;
|
||||||
bool remote_eof;
|
bool remote_eof;
|
||||||
|
bool retried;
|
||||||
|
struct sockaddr_storage dest;
|
||||||
|
socklen_t dest_len;
|
||||||
uint8_t *to_sock;
|
uint8_t *to_sock;
|
||||||
size_t to_sock_len, to_sock_cap;
|
size_t to_sock_len, to_sock_cap;
|
||||||
uint8_t *to_pcb;
|
uint8_t *to_pcb;
|
||||||
@@ -265,6 +269,53 @@ static void relay_flush_to_sock(tcp_relay_t *relay)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "ip:port" of a relay destination for log lines
|
||||||
|
*/
|
||||||
|
static void format_dest(const struct sockaddr_storage *ss, char *buf, size_t len)
|
||||||
|
{
|
||||||
|
char host[INET6_ADDRSTRLEN];
|
||||||
|
in_port_t port = 0;
|
||||||
|
|
||||||
|
if (ss->ss_family == AF_INET6)
|
||||||
|
{
|
||||||
|
inet_ntop(AF_INET6, &((struct sockaddr_in6*)ss)->sin6_addr, host, sizeof(host));
|
||||||
|
port = ((struct sockaddr_in6*)ss)->sin6_port;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inet_ntop(AF_INET, &((struct sockaddr_in*)ss)->sin_addr, host, sizeof(host));
|
||||||
|
port = ((struct sockaddr_in*)ss)->sin_port;
|
||||||
|
}
|
||||||
|
snprintf(buf, len, "%s:%u", host, ntohs(port));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a protected, network-pinned non-blocking socket and start connecting
|
||||||
|
* it to the given destination. Returns -1 on failure.
|
||||||
|
*/
|
||||||
|
static int relay_connect_socket(const struct sockaddr_storage *ss, socklen_t len)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = socket(ss->ss_family, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!charonservice_protect_direct_socket(fd))
|
||||||
|
{
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (connect(fd, (struct sockaddr*)ss, len) < 0 && errno != EINPROGRESS)
|
||||||
|
{
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* lwIP accept callback: a connection arrived for some destination
|
* lwIP accept callback: a connection arrived for some destination
|
||||||
*/
|
*/
|
||||||
@@ -275,6 +326,8 @@ static lwip_err_t on_accept(void *arg, struct tcp_pcb *newpcb, lwip_err_t err)
|
|||||||
struct sockaddr_storage ss;
|
struct sockaddr_storage ss;
|
||||||
struct sockaddr_in *sin = (struct sockaddr_in*)&ss;
|
struct sockaddr_in *sin = (struct sockaddr_in*)&ss;
|
||||||
struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&ss;
|
struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&ss;
|
||||||
|
socklen_t ss_len;
|
||||||
|
char dest[INET6_ADDRSTRLEN + 8];
|
||||||
int fd;
|
int fd;
|
||||||
|
|
||||||
DIRECT_STATS_ADD(this->stats->tcp_accepts, 1);
|
DIRECT_STATS_ADD(this->stats->tcp_accepts, 1);
|
||||||
@@ -285,35 +338,23 @@ static lwip_err_t on_accept(void *arg, struct tcp_pcb *newpcb, lwip_err_t err)
|
|||||||
sin6->sin6_family = AF_INET6;
|
sin6->sin6_family = AF_INET6;
|
||||||
memcpy(&sin6->sin6_addr, &ip_2_ip6(&newpcb->local_ip)->addr, 16);
|
memcpy(&sin6->sin6_addr, &ip_2_ip6(&newpcb->local_ip)->addr, 16);
|
||||||
sin6->sin6_port = htons(newpcb->local_port);
|
sin6->sin6_port = htons(newpcb->local_port);
|
||||||
|
ss_len = sizeof(struct sockaddr_in6);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sin->sin_family = AF_INET;
|
sin->sin_family = AF_INET;
|
||||||
memcpy(&sin->sin_addr, &ip_2_ip4(&newpcb->local_ip)->addr, 4);
|
memcpy(&sin->sin_addr, &ip_2_ip4(&newpcb->local_ip)->addr, 4);
|
||||||
sin->sin_port = htons(newpcb->local_port);
|
sin->sin_port = htons(newpcb->local_port);
|
||||||
|
ss_len = sizeof(struct sockaddr_in);
|
||||||
}
|
}
|
||||||
|
format_dest(&ss, dest, sizeof(dest));
|
||||||
|
|
||||||
fd = socket(ss.ss_family, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
|
fd = relay_connect_socket(&ss, ss_len);
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
{
|
{
|
||||||
DIRECT_STATS_ADD(this->stats->relay_errors, 1);
|
DIRECT_STATS_ADD(this->stats->relay_errors, 1);
|
||||||
DBG1(DBG_NET, "direct tcp: accept socket failed: %s", strerror(errno));
|
DBG1(DBG_NET, "direct tcp: accept socket setup failed dst=%s: %s",
|
||||||
tcp_abort(newpcb);
|
dest, strerror(errno));
|
||||||
return ERR_ABRT;
|
|
||||||
}
|
|
||||||
if (!charonservice_protect_socket(fd))
|
|
||||||
{
|
|
||||||
DIRECT_STATS_ADD(this->stats->relay_errors, 1);
|
|
||||||
DBG1(DBG_NET, "direct tcp: accept protect failed fd=%d", fd);
|
|
||||||
close(fd);
|
|
||||||
tcp_abort(newpcb);
|
|
||||||
return ERR_ABRT;
|
|
||||||
}
|
|
||||||
if (connect(fd, (struct sockaddr*)&ss, sizeof(ss)) < 0 && errno != EINPROGRESS)
|
|
||||||
{
|
|
||||||
DIRECT_STATS_ADD(this->stats->relay_errors, 1);
|
|
||||||
DBG1(DBG_NET, "direct tcp: accept connect failed: %s", strerror(errno));
|
|
||||||
close(fd);
|
|
||||||
tcp_abort(newpcb);
|
tcp_abort(newpcb);
|
||||||
return ERR_ABRT;
|
return ERR_ABRT;
|
||||||
}
|
}
|
||||||
@@ -322,7 +363,9 @@ static lwip_err_t on_accept(void *arg, struct tcp_pcb *newpcb, lwip_err_t err)
|
|||||||
.m = this,
|
.m = this,
|
||||||
.pcb = newpcb,
|
.pcb = newpcb,
|
||||||
.fd = fd,
|
.fd = fd,
|
||||||
|
.dest_len = ss_len,
|
||||||
);
|
);
|
||||||
|
memcpy(&relay->dest, &ss, ss_len);
|
||||||
/* keep our own segments within the TUN MTU regardless of TCP_MSS */
|
/* keep our own segments within the TUN MTU regardless of TCP_MSS */
|
||||||
if (newpcb->mss > this->mtu - 72)
|
if (newpcb->mss > this->mtu - 72)
|
||||||
{
|
{
|
||||||
@@ -581,6 +624,15 @@ METHOD(direct_tcp_t, reprotect, void,
|
|||||||
enumerator = this->relays->create_enumerator(this->relays);
|
enumerator = this->relays->create_enumerator(this->relays);
|
||||||
while (enumerator->enumerate(enumerator, &relay))
|
while (enumerator->enumerate(enumerator, &relay))
|
||||||
{
|
{
|
||||||
|
if (!relay->connected)
|
||||||
|
{ /* SYN still in flight on the old network - it can no longer
|
||||||
|
* complete (the source/network it started on is gone, and the
|
||||||
|
* kernel may take over a minute to report EHOSTUNREACH). Abort
|
||||||
|
* now so the client re-SYNs at once and gets a fresh socket,
|
||||||
|
* pinned to the new active network. */
|
||||||
|
relay->dead = TRUE;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (relay->fd >= 0 && !charonservice_protect_socket(relay->fd))
|
if (relay->fd >= 0 && !charonservice_protect_socket(relay->fd))
|
||||||
{
|
{
|
||||||
DBG1(DBG_NET, "direct tcp: re-protect failed fd=%d", relay->fd);
|
DBG1(DBG_NET, "direct tcp: re-protect failed fd=%d", relay->fd);
|
||||||
@@ -641,6 +693,7 @@ METHOD(direct_tcp_t, process_poll, void,
|
|||||||
{
|
{
|
||||||
int err = 0;
|
int err = 0;
|
||||||
socklen_t len = sizeof(err);
|
socklen_t len = sizeof(err);
|
||||||
|
char dest[INET6_ADDRSTRLEN + 8];
|
||||||
|
|
||||||
getsockopt(relay->fd, SOL_SOCKET, SO_ERROR, &err, &len);
|
getsockopt(relay->fd, SOL_SOCKET, SO_ERROR, &err, &len);
|
||||||
if (err == 0)
|
if (err == 0)
|
||||||
@@ -649,8 +702,29 @@ METHOD(direct_tcp_t, process_poll, void,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
format_dest(&relay->dest, dest, sizeof(dest));
|
||||||
|
if (!relay->retried)
|
||||||
|
{ /* one retry on a fresh socket: the first attempt may have
|
||||||
|
* been pinned to a network that vanished mid-roam (its
|
||||||
|
* EHOSTUNREACH can take over a minute to surface, leaving
|
||||||
|
* the connection hanging) */
|
||||||
|
int nfd;
|
||||||
|
|
||||||
|
DBG1(DBG_NET, "direct tcp: connect failed dst=%s: %s, retrying on a fresh socket",
|
||||||
|
dest, strerror(err));
|
||||||
|
relay->retried = TRUE;
|
||||||
|
close(relay->fd);
|
||||||
|
nfd = relay_connect_socket(&relay->dest, relay->dest_len);
|
||||||
|
if (nfd >= 0)
|
||||||
|
{
|
||||||
|
relay->fd = nfd;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
err = errno;
|
||||||
|
}
|
||||||
DIRECT_STATS_ADD(this->stats->relay_errors, 1);
|
DIRECT_STATS_ADD(this->stats->relay_errors, 1);
|
||||||
DBG1(DBG_NET, "direct tcp: connect failed async: %s", strerror(err));
|
DBG1(DBG_NET, "direct tcp: connect failed dst=%s: %s",
|
||||||
|
dest, strerror(err));
|
||||||
this->relays->remove_at(this->relays, enumerator);
|
this->relays->remove_at(this->relays, enumerator);
|
||||||
relay_destroy(this, relay, TRUE);
|
relay_destroy(this, relay, TRUE);
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ static void flow_close(private_direct_udp_t *this, int slot)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a protected datagram socket connected to the remote end
|
* Open a protected, network-pinned datagram socket connected to the remote
|
||||||
*/
|
*/
|
||||||
static int open_flow_socket(host_t *remote)
|
static int open_flow_socket(host_t *remote)
|
||||||
{
|
{
|
||||||
@@ -102,7 +102,7 @@ static int open_flow_socket(host_t *remote)
|
|||||||
DBG1(DBG_NET, "direct udp: socket failed: %s", strerror(errno));
|
DBG1(DBG_NET, "direct udp: socket failed: %s", strerror(errno));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (!charonservice_protect_socket(fd))
|
if (!charonservice_protect_direct_socket(fd))
|
||||||
{
|
{
|
||||||
DBG1(DBG_NET, "direct udp: protecting socket failed");
|
DBG1(DBG_NET, "direct udp: protecting socket failed");
|
||||||
close(fd);
|
close(fd);
|
||||||
@@ -291,13 +291,13 @@ METHOD(direct_udp_t, reprotect, void,
|
|||||||
{
|
{
|
||||||
int slot;
|
int slot;
|
||||||
|
|
||||||
|
/* A connected datagram socket cannot be moved to another network; on a
|
||||||
|
* roam these flows are dead anyway. Close them all - the next packet of
|
||||||
|
* each flow re-opens it with a fresh socket, pinned to the new active
|
||||||
|
* network (see open_flow_socket). */
|
||||||
for (slot = 0; slot < FLOW_SLOTS; slot++)
|
for (slot = 0; slot < FLOW_SLOTS; slot++)
|
||||||
{
|
{
|
||||||
if (this->flows[slot].fd >= 0 &&
|
flow_close(this, slot);
|
||||||
!charonservice_protect_socket(this->flows[slot].fd))
|
|
||||||
{
|
|
||||||
DBG1(DBG_NET, "direct udp: re-protect failed on slot %d", slot);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user