Basic SOCKS5 TCP support

This commit is contained in:
M66B 2016-07-28 17:46:19 +02:00
parent 7860e15b65
commit aff4fb2308
9 changed files with 199 additions and 21 deletions

View File

@ -64,14 +64,6 @@
<sourceFolder url="file://$MODULE_DIR$/src/all/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/all/renderscript" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/all/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/testAll/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testAll/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testAll/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testAll/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testAll/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testAll/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testAll/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testAll/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestAll/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestAll/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestAll/assets" type="java-test-resource" />
@ -80,6 +72,14 @@
<sourceFolder url="file://$MODULE_DIR$/src/androidTestAll/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestAll/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestAll/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testAll/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testAll/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testAll/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testAll/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testAll/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testAll/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testAll/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testAll/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />

View File

@ -226,6 +226,10 @@ public class ActivitySettings extends AppCompatActivity implements SharedPrefere
pref_dns.getEditText().setHint(def_dns.get(0));
pref_dns.setTitle(getString(R.string.setting_dns, prefs.getString("dns", def_dns.get(0))));
// SOCKS5 parameters
screen.findPreference("socks5_addr").setTitle(getString(R.string.setting_socks5_addr, prefs.getString("socks5_addr", "-")));
screen.findPreference("socks5_port").setTitle(getString(R.string.setting_socks5_port, prefs.getString("socks5_port", "-")));
// PCAP parameters
screen.findPreference("pcap_record_size").setTitle(getString(R.string.setting_pcap_record_size, prefs.getString("pcap_record_size", "64")));
screen.findPreference("pcap_file_size").setTitle(getString(R.string.setting_pcap_file_size, prefs.getString("pcap_file_size", "2")));
@ -637,6 +641,24 @@ public class ActivitySettings extends AppCompatActivity implements SharedPrefere
getPreferenceScreen().findPreference(name).setTitle(
getString(R.string.setting_dns, prefs.getString("dns", Util.getDefaultDNS(this).get(0))));
} else if ("socks5_addr".equals(name)) {
String socks5 = prefs.getString("socks5_addr", null);
try {
checkAddress(socks5);
} catch (Throwable ex) {
prefs.edit().remove("socks5_addr").apply();
((EditTextPreference) getPreferenceScreen().findPreference(name)).setText(null);
if (!TextUtils.isEmpty(socks5))
Toast.makeText(ActivitySettings.this, ex.toString(), Toast.LENGTH_LONG).show();
}
ServiceSinkhole.reload("changed " + name, this);
getPreferenceScreen().findPreference(name).setTitle(
getString(R.string.setting_dns, prefs.getString("socks5_addr", "-")));
} else if ("socks5_port".equals(name)) {
getPreferenceScreen().findPreference(name).setTitle(getString(R.string.setting_socks5_port, prefs.getString(name, "-")));
ServiceSinkhole.reload("changed " + name, this);
} else if ("pcap_record_size".equals(name) || "pcap_file_size".equals(name)) {
if ("pcap_record_size".equals(name))
getPreferenceScreen().findPreference(name).setTitle(getString(R.string.setting_pcap_record_size, prefs.getString(name, "64")));

View File

@ -183,6 +183,8 @@ public class ServiceSinkhole extends VpnService implements SharedPreferences.OnS
private static native void jni_pcap(String name, int record_size, int file_size);
private native void jni_socks5(String ip, int port);
private native void jni_done();
public static void setPcap(boolean enabled, Context context) {
@ -1113,6 +1115,7 @@ public class ServiceSinkhole extends VpnService implements SharedPreferences.OnS
if (log || log_app || filter) {
int prio = Integer.parseInt(prefs.getString("loglevel", Integer.toString(Log.WARN)));
jni_socks5(prefs.getString("socks5_addr", ""), Integer.parseInt(prefs.getString("socks5_port", "0")));
jni_start(vpn.getFd(), mapForward.containsKey(53), prio);
}
}

View File

@ -28,6 +28,8 @@ JavaVM *jvm = NULL;
int pipefds[2];
pthread_t thread_id = 0;
pthread_mutex_t lock;
char socks5_addr[INET6_ADDRSTRLEN + 1];
int socks5_port = 0;
int loglevel = ANDROID_LOG_WARN;
extern int max_tun_msg;
@ -103,6 +105,8 @@ Java_eu_faircode_netguard_ServiceSinkhole_jni_1init(JNIEnv *env, jobject instanc
args.instance = instance;
init(&args);
*socks5_addr = 0;
socks5_port = 0;
pcap_file = NULL;
if (pthread_mutex_init(&lock, NULL))
@ -294,6 +298,22 @@ Java_eu_faircode_netguard_ServiceSinkhole_jni_1pcap(
log_android(ANDROID_LOG_ERROR, "pthread_mutex_unlock failed");
}
JNIEXPORT void JNICALL
Java_eu_faircode_netguard_ServiceSinkhole_jni_1socks5(JNIEnv *env,
jobject instance, jstring ip_, jint port) {
if (ip_ == NULL || port == 0) {
*socks5_addr = 0;
socks5_port = 0;
} else {
const char *ip = (*env)->GetStringUTFChars(env, ip_, 0);
strcpy(socks5_addr, ip);
socks5_port = port;
log_android(ANDROID_LOG_WARN, "SOCKS5 %s:%d", socks5_addr, socks5_port);
(*env)->ReleaseStringUTFChars(env, ip_, ip);
}
}
JNIEXPORT void JNICALL
Java_eu_faircode_netguard_ServiceSinkhole_jni_1done(JNIEnv *env, jobject instance) {

View File

@ -59,6 +59,11 @@
#define UID_DELAYTRY 10 // milliseconds
#define UID_MAXTRY 3
#define SOCKS5_NONE 1
#define SOCKS5_HELLO 2
#define SOCKS5_CONNECT 3
#define SOCKS5_CONNECTED 4
struct arguments {
JNIEnv *env;
jobject instance;
@ -163,6 +168,7 @@ struct tcp_session {
__be16 dest; // network notation
uint8_t state;
uint8_t socks5;
struct segment *forward;
};

View File

@ -20,6 +20,9 @@
#include "netguard.h"
extern struct ng_session *ng_session;
extern char socks5_addr[INET6_ADDRSTRLEN + 1];
extern int socks5_port;
extern FILE *pcap_file;
void clear_tcp_data(struct tcp_session *cur) {
@ -116,7 +119,10 @@ int monitor_tcp_session(const struct arguments *args, struct ng_session *s, int
if (s->tcp.state == TCP_LISTEN) {
// Check for connected = writable
events = events | EPOLLOUT;
if (s->tcp.socks5 == SOCKS5_NONE)
events = events | EPOLLOUT;
else
events = events | EPOLLIN;
}
else if (s->tcp.state == TCP_ESTABLISHED || s->tcp.state == TCP_CLOSE_WAIT) {
@ -257,9 +263,88 @@ void check_tcp_socket(const struct arguments *args,
// Assume socket okay
if (s->tcp.state == TCP_LISTEN) {
// Check socket connect
if (ev->events & EPOLLOUT) {
log_android(ANDROID_LOG_INFO, "%s connected", session);
if (s->tcp.socks5 == SOCKS5_NONE) {
if (ev->events & EPOLLOUT) {
log_android(ANDROID_LOG_INFO, "%s connected", session);
if (*socks5_addr && socks5_port) {
// https://en.wikipedia.org/wiki/SOCKS
s->tcp.socks5 = SOCKS5_HELLO;
uint8_t buffer[3] = {5, 1, 0};
char *h = hex(buffer, sizeof(buffer));
log_android(ANDROID_LOG_INFO, "%s sending SOCKS5 hello: %s",
session, h);
free(h);
ssize_t sent = send(s->socket, buffer, sizeof(buffer), MSG_NOSIGNAL);
if (sent < 0) {
log_android(ANDROID_LOG_ERROR, "%s send SOCKS5 hello error %d: %s",
session, errno, strerror(errno));
write_rst(args, &s->tcp);
}
} else
s->tcp.socks5 = SOCKS5_CONNECTED;
}
} else {
if (ev->events & EPOLLIN) {
uint8_t buffer[32];
ssize_t bytes = recv(s->socket, buffer, sizeof(buffer), 0);
if (bytes < 0) {
log_android(ANDROID_LOG_ERROR, "%s recv SOCKS5 error %d: %s",
session, errno, strerror(errno));
write_rst(args, &s->tcp);
}
else {
char *h = hex(buffer, (const size_t) bytes);
log_android(ANDROID_LOG_INFO, "%s recv SOCKS5 %s", session, h);
free(h);
if (s->tcp.socks5 == SOCKS5_HELLO &&
bytes == 2 && buffer[0] == 5 && buffer[1] == 0) {
s->tcp.socks5 = SOCKS5_CONNECT;
*(buffer + 0) = 5; // version
*(buffer + 1) = 1; // TCP/IP stream connection
*(buffer + 2) = 0; // reserved
*(buffer + 3) = (uint8_t) (s->tcp.version == 4 ? 1 : 4);
if (s->tcp.version == 4) {
memcpy(buffer + 4, &s->tcp.daddr.ip4, 4);
*((__be16 *) (buffer + 4 + 4)) = s->tcp.dest;
} else {
memcpy(buffer + 4, &s->tcp.daddr.ip6, 16);
*((__be16 *) (buffer + 4 + 16)) = s->tcp.dest;
}
size_t len = (s->tcp.version == 4 ? 10 : 22);
h = hex(buffer, len);
log_android(ANDROID_LOG_INFO, "%s sending SOCKS5 connect: %s",
session, h);
free(h);
ssize_t sent = send(s->socket, buffer, len, MSG_NOSIGNAL);
if (sent < 0) {
log_android(ANDROID_LOG_ERROR,
"%s send SOCKS5 connect error %d: %s",
session, errno, strerror(errno));
write_rst(args, &s->tcp);
}
} else if (s->tcp.socks5 == SOCKS5_CONNECT &&
bytes == 6 + (s->tcp.version == 4 ? 4 : 16) &&
buffer[0] == 5 && buffer[1] == 0) {
s->tcp.socks5 = SOCKS5_CONNECTED;
log_android(ANDROID_LOG_WARN, "%s SOCKS5 connected", session);
} else {
log_android(ANDROID_LOG_ERROR, "%s recv SOCKS5 state %d",
session, s->tcp.socks5);
write_rst(args, &s->tcp);
}
}
}
}
if (s->tcp.socks5 == SOCKS5_CONNECTED) {
s->tcp.remote_seq++; // remote SYN
if (write_syn_ack(args, &s->tcp) >= 0) {
s->tcp.time = time(NULL);
@ -551,6 +636,7 @@ jboolean handle_tcp(const struct arguments *args,
s->tcp.source = tcphdr->source;
s->tcp.dest = tcphdr->dest;
s->tcp.state = TCP_LISTEN;
s->tcp.socks5 = SOCKS5_NONE;
s->tcp.forward = NULL;
s->next = NULL;
@ -846,9 +932,12 @@ int open_tcp_socket(const struct arguments *args,
const struct tcp_session *cur, const struct allowed *redirect) {
int sock;
int version;
if (redirect == NULL)
version = cur->version;
else
if (redirect == NULL) {
if (*socks5_addr && socks5_port)
version = (strstr(socks5_addr, ":") == NULL ? 4 : 6);
else
version = cur->version;
} else
version = (strstr(redirect->raddr, ":") == NULL ? 4 : 6);
// Get TCP socket
@ -873,14 +962,30 @@ int open_tcp_socket(const struct arguments *args,
struct sockaddr_in addr4;
struct sockaddr_in6 addr6;
if (redirect == NULL) {
if (version == 4) {
addr4.sin_family = AF_INET;
addr4.sin_addr.s_addr = (__be32) cur->daddr.ip4;
addr4.sin_port = cur->dest;
if (*socks5_addr && socks5_port) {
log_android(ANDROID_LOG_WARN, "TCP%d SOCKS5 to %s/%u",
version, socks5_addr, socks5_port);
if (version == 4) {
addr4.sin_family = AF_INET;
inet_pton(AF_INET, socks5_addr, &addr4.sin_addr);
addr4.sin_port = htons(socks5_port);
}
else {
addr6.sin6_family = AF_INET6;
inet_pton(AF_INET6, socks5_addr, &addr6.sin6_addr);
addr6.sin6_port = htons(socks5_port);
}
} else {
addr6.sin6_family = AF_INET6;
memcpy(&addr6.sin6_addr, &cur->daddr.ip6, 16);
addr6.sin6_port = cur->dest;
if (version == 4) {
addr4.sin_family = AF_INET;
addr4.sin_addr.s_addr = (__be32) cur->daddr.ip4;
addr4.sin_port = cur->dest;
} else {
addr6.sin6_family = AF_INET6;
memcpy(&addr6.sin6_addr, &cur->daddr.ip6, 16);
addr6.sin6_port = cur->dest;
}
}
} else {
log_android(ANDROID_LOG_WARN, "TCP%d redirect to %s/%u",

View File

@ -92,6 +92,8 @@ however it is impossible to guarantee NetGuard will work correctly on every devi
<string name="setting_vpn4">VPN IPv4: %s</string>
<string name="setting_vpn6">VPN IPv6: %s</string>
<string name="setting_dns">VPN DNS: %s</string>
<string name="setting_socks5_addr">SOCKS5 address: %s</string>
<string name="setting_socks5_port">SOCKS5 port: %s</string>
<string name="setting_pcap_record_size">PCAP record size: %s B</string>
<string name="setting_pcap_file_size">PCAP max. file size: %s MB</string>

View File

@ -186,6 +186,16 @@
android:dependency="filter"
android:inputType="text"
android:key="dns" />
<EditTextPreference
android:dependency="filter"
android:hint="127.0.0.1"
android:inputType="text"
android:key="socks5_addr" />
<EditTextPreference
android:dependency="filter"
android:hint="1080"
android:inputType="text"
android:key="socks5_port" />
<EditTextPreference
android:defaultValue="64"
android:inputType="number"

View File

@ -186,6 +186,16 @@
android:dependency="filter"
android:inputType="text"
android:key="dns" />
<EditTextPreference
android:dependency="filter"
android:hint="127.0.0.1"
android:inputType="text"
android:key="socks5_addr" />
<EditTextPreference
android:dependency="filter"
android:hint="1080"
android:inputType="text"
android:key="socks5_port" />
<EditTextPreference
android:defaultValue="64"
android:inputType="number"