Эх сурвалжийг харах

Add BSocksClient, a simple SOCKS5 client. Regenerate generated sources.

ambrop7 15 жил өмнө
parent
commit
e2e8a7c319

+ 1 - 0
CMakeLists.txt

@@ -82,6 +82,7 @@ add_subdirectory(predicate)
 add_subdirectory(nspr_support)
 add_subdirectory(server_connection)
 add_subdirectory(security)
+add_subdirectory(socksclient)
 if (NOT WIN32)
     add_subdirectory(ipc)
 endif ()

+ 1 - 0
blog_channels.txt

@@ -11,3 +11,4 @@ flooder 4
 Listener 4
 DataProto 4
 FrameDecider 4
+BSocksClient 4

+ 4 - 0
generated/blog_channel_BSocksClient.h

@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BSocksClient

+ 2 - 1
generated/blog_channels_defines.h

@@ -11,4 +11,5 @@
 #define BLOG_CHANNEL_Listener 10
 #define BLOG_CHANNEL_DataProto 11
 #define BLOG_CHANNEL_FrameDecider 12
-#define BLOG_NUM_CHANNELS 13
+#define BLOG_CHANNEL_BSocksClient 13
+#define BLOG_NUM_CHANNELS 14

+ 1 - 0
generated/blog_channels_list.h

@@ -11,3 +11,4 @@
 {.name = "Listener", .loglevel = 4},
 {.name = "DataProto", .loglevel = 4},
 {.name = "FrameDecider", .loglevel = 4},
+{.name = "BSocksClient", .loglevel = 4},

+ 24 - 20
generated/flex_BPredicate.c

@@ -54,7 +54,6 @@ typedef int flex_int32_t;
 typedef unsigned char flex_uint8_t; 
 typedef unsigned short int flex_uint16_t;
 typedef unsigned int flex_uint32_t;
-#endif /* ! C99 */
 
 /* Limits of integral types. */
 #ifndef INT8_MIN
@@ -85,6 +84,8 @@ typedef unsigned int flex_uint32_t;
 #define UINT32_MAX             (4294967295U)
 #endif
 
+#endif /* ! C99 */
+
 #endif /* ! FLEXINT_H */
 
 #ifdef __cplusplus
@@ -158,7 +159,15 @@ typedef void* yyscan_t;
 
 /* Size of default input buffer. */
 #ifndef YY_BUF_SIZE
+#ifdef __ia64__
+/* On IA-64, the buffer size is 16k, not 8k.
+ * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case.
+ * Ditto for the __ia64__ case accordingly.
+ */
+#define YY_BUF_SIZE 32768
+#else
 #define YY_BUF_SIZE 16384
+#endif /* __ia64__ */
 #endif
 
 /* The state buf must be large enough to hold one state per character in the main buffer.
@@ -492,7 +501,7 @@ static yyconst flex_int16_t yy_chk[70] =
     int bytes_read = LexMemoryBufferInput_Read(yyget_extra(yyscanner), buffer, max_size); \
     res = (bytes_read == 0 ? YY_NULL : bytes_read);
 
-#line 496 "generated//flex_BPredicate.c"
+#line 505 "generated//flex_BPredicate.c"
 
 #define INITIAL 0
 
@@ -635,7 +644,12 @@ static int input (yyscan_t yyscanner );
     
 /* Amount of stuff to slurp up with each read. */
 #ifndef YY_READ_BUF_SIZE
+#ifdef __ia64__
+/* On IA-64, the buffer size is 16k, not 8k */
+#define YY_READ_BUF_SIZE 16384
+#else
 #define YY_READ_BUF_SIZE 8192
+#endif /* __ia64__ */
 #endif
 
 /* Copy whatever the last rule matched to the standard output. */
@@ -643,7 +657,7 @@ static int input (yyscan_t yyscanner );
 /* This used to be an fputs(), but since the string might contain NUL's,
  * we now use fwrite().
  */
-#define ECHO fwrite( yytext, yyleng, 1, yyout )
+#define ECHO do { if (fwrite( yytext, yyleng, 1, yyout )) {} } while (0)
 #endif
 
 /* Gets input and stuffs it into "buf".  number of characters read, or YY_NULL,
@@ -654,7 +668,7 @@ static int input (yyscan_t yyscanner );
 	if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \
 		{ \
 		int c = '*'; \
-		int n; \
+		size_t n; \
 		for ( n = 0; n < max_size && \
 			     (c = getc( yyin )) != EOF && c != '\n'; ++n ) \
 			buf[n] = (char) c; \
@@ -741,7 +755,7 @@ YY_DECL
 
 #line 47 "predicate/BPredicate.l"
 
-#line 745 "generated//flex_BPredicate.c"
+#line 759 "generated//flex_BPredicate.c"
 
     yylval = yylval_param;
 
@@ -913,7 +927,7 @@ YY_RULE_SETUP
 #line 78 "predicate/BPredicate.l"
 ECHO;
 	YY_BREAK
-#line 917 "generated//flex_BPredicate.c"
+#line 931 "generated//flex_BPredicate.c"
 case YY_STATE_EOF(INITIAL):
 	yyterminate();
 
@@ -1472,19 +1486,9 @@ static void yy_load_buffer_state  (yyscan_t yyscanner)
 	yyfree((void *) b ,yyscanner );
 }
 
-#ifndef _UNISTD_H /* assume unistd.h has isatty() for us */
-#ifdef __cplusplus
-extern "C" {
-#endif
-#ifdef __THROW /* this is a gnuism */
-extern int isatty (int ) __THROW;
-#else
+#ifndef __cplusplus
 extern int isatty (int );
-#endif
-#ifdef __cplusplus
-}
-#endif
-#endif
+#endif /* __cplusplus */
     
 /* Initializes or reinitializes a buffer.
  * This function is sometimes called more than once on the same buffer,
@@ -1695,8 +1699,8 @@ YY_BUFFER_STATE yy_scan_string (yyconst char * yystr , yyscan_t yyscanner)
 
 /** Setup the input buffer state to scan the given bytes. The next call to yylex() will
  * scan from a @e copy of @a bytes.
- * @param bytes the byte buffer to scan
- * @param len the number of bytes in the buffer pointed to by @a bytes.
+ * @param yybytes the byte buffer to scan
+ * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes.
  * @param yyscanner The scanner object.
  * @return the newly allocated buffer state object.
  */

+ 16 - 2
generated/flex_BPredicate.h

@@ -58,7 +58,6 @@ typedef int flex_int32_t;
 typedef unsigned char flex_uint8_t; 
 typedef unsigned short int flex_uint16_t;
 typedef unsigned int flex_uint32_t;
-#endif /* ! C99 */
 
 /* Limits of integral types. */
 #ifndef INT8_MIN
@@ -89,6 +88,8 @@ typedef unsigned int flex_uint32_t;
 #define UINT32_MAX             (4294967295U)
 #endif
 
+#endif /* ! C99 */
+
 #endif /* ! FLEXINT_H */
 
 #ifdef __cplusplus
@@ -131,7 +132,15 @@ typedef void* yyscan_t;
 
 /* Size of default input buffer. */
 #ifndef YY_BUF_SIZE
+#ifdef __ia64__
+/* On IA-64, the buffer size is 16k, not 8k.
+ * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case.
+ * Ditto for the __ia64__ case accordingly.
+ */
+#define YY_BUF_SIZE 32768
+#else
 #define YY_BUF_SIZE 16384
+#endif /* __ia64__ */
 #endif
 
 #ifndef YY_TYPEDEF_YY_BUFFER_STATE
@@ -302,7 +311,12 @@ static int yy_flex_strlen (yyconst char * ,yyscan_t yyscanner);
 
 /* Amount of stuff to slurp up with each read. */
 #ifndef YY_READ_BUF_SIZE
+#ifdef __ia64__
+/* On IA-64, the buffer size is 16k, not 8k */
+#define YY_READ_BUF_SIZE 16384
+#else
 #define YY_READ_BUF_SIZE 8192
+#endif /* __ia64__ */
 #endif
 
 /* Number of entries by which start-condition stack grows. */
@@ -340,6 +354,6 @@ extern int yylex \
 #line 78 "predicate/BPredicate.l"
 
 
-#line 344 "generated//flex_BPredicate.h"
+#line 358 "generated//flex_BPredicate.h"
 #undef yyIN_HEADER
 #endif /* yyHEADER_H */

+ 95 - 0
misc/socks_proto.h

@@ -0,0 +1,95 @@
+/**
+ * @file socks_proto.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Definitions for the SOCKS protocol.
+ */
+
+#ifndef BADVPN_MISC_SOCKS_PROTO_H
+#define BADVPN_MISC_SOCKS_PROTO_H
+
+#include <stdint.h>
+
+#define SOCKS_VERSION 0x05
+
+#define SOCKS_METHOD_NO_AUTHENTICATION_REQUIRED 0x00
+#define SOCKS_METHOD_GSSAPI 0x01
+#define SOCKS_METHOD_USERNAME_PASSWORD 0x02
+#define SOCKS_METHOD_NO_ACCEPTABLE_METHODS 0xFF
+
+#define SOCKS_CMD_CONNECT 0x01
+#define SOCKS_CMD_BIND 0x02
+#define SOCKS_CMD_UDP_ASSOCIATE 0x03
+
+#define SOCKS_ATYP_IPV4 0x01
+#define SOCKS_ATYP_DOMAINNAME 0x03
+#define SOCKS_ATYP_IPV6 0x04
+
+#define SOCKS_REP_SUCCEEDED 0x00
+#define SOCKS_REP_GENERAL_FAILURE 0x01
+#define SOCKS_REP_CONNECTION_NOT_ALLOWED 0x02
+#define SOCKS_REP_NETWORK_UNREACHABLE 0x03
+#define SOCKS_REP_HOST_UNREACHABLE 0x04
+#define SOCKS_REP_CONNECTION_REFUSED 0x05
+#define SOCKS_REP_TTL_EXPIRED 0x06
+#define SOCKS_REP_COMMAND_NOT_SUPPORTED 0x07
+#define SOCKS_REP_ADDRESS_TYPE_NOT_SUPPORTED 0x08
+
+struct socks_client_hello_header {
+    uint8_t ver;
+    uint8_t nmethods;
+} __attribute__((packed));
+
+struct socks_client_hello_method {
+    uint8_t method;
+} __attribute__((packed));
+
+struct socks_server_hello {
+    uint8_t ver;
+    uint8_t method;
+} __attribute__((packed));
+
+struct socks_request_header {
+    uint8_t ver;
+    uint8_t cmd;
+    uint8_t rsv;
+    uint8_t atyp;
+} __attribute__((packed));
+
+struct socks_reply_header {
+    uint8_t ver;
+    uint8_t rep;
+    uint8_t rsv;
+    uint8_t atyp;
+} __attribute__((packed));
+
+struct socks_addr_ipv4 {
+    uint32_t addr;
+    uint16_t port;
+} __attribute__((packed));
+
+struct socks_addr_ipv6 {
+    uint8_t addr[16];
+    uint16_t port;
+} __attribute__((packed));    
+
+#endif

+ 420 - 0
socksclient/BSocksClient.c

@@ -0,0 +1,420 @@
+/**
+ * @file BSocksClient.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <misc/byteorder.h>
+#include <system/BLog.h>
+
+#include <socksclient/BSocksClient.h>
+
+#include <generated/blog_channel_BSocksClient.h>
+
+#define STATE_CONNECTING 1
+#define STATE_SENDING_HELLO 2
+#define STATE_SENT_HELLO 3
+#define STATE_SENDING_REQUEST 4
+#define STATE_SENT_REQUEST 5
+#define STATE_RECEIVED_REPLY_HEADER 6
+#define STATE_UP 7
+
+#define COMPONENT_SOURCE 1
+#define COMPONENT_SINK 2
+
+static void report_error (BSocksClient *o, int error);
+static void init_control_io (BSocksClient *o);
+static void free_control_io (BSocksClient *o);
+static void init_up_io (BSocksClient *o);
+static void free_up_io (BSocksClient *o);
+static void start_receive (BSocksClient *o, uint8_t *dest, int total);
+static void do_receive (BSocksClient *o);
+static void error_handler (BSocksClient *o, int component, const void *data);
+static void socket_error_handler (BSocksClient *o, int event);
+static void connect_handler (BSocksClient *o, int event);
+static void recv_handler_done (BSocksClient *o, int data_len);
+static void send_handler_done (BSocksClient *o);
+
+void report_error (BSocksClient *o, int error)
+{
+    #ifndef NDEBUG
+    DEAD_ENTER(o->d_dead)
+    #endif
+    
+    o->handler(o->user, error);
+    
+    #ifndef NDEBUG
+    ASSERT(DEAD_KILLED)
+    DEAD_LEAVE(o->d_dead);
+    #endif
+}
+
+void init_control_io (BSocksClient *o)
+{
+    // init receiving
+    StreamSocketSource_Init(&o->control.recv_source, FlowErrorReporter_Create(&o->domain, COMPONENT_SOURCE), &o->sock, BReactor_PendingGroup(o->reactor));
+    o->control.recv_if = StreamSocketSource_GetOutput(&o->control.recv_source);
+    StreamRecvInterface_Receiver_Init(o->control.recv_if, (StreamRecvInterface_handler_done)recv_handler_done, o);
+    
+    // init sending
+    StreamSocketSink_Init(&o->control.send_sink, FlowErrorReporter_Create(&o->domain, COMPONENT_SINK), &o->sock, BReactor_PendingGroup(o->reactor));
+    PacketStreamSender_Init(&o->control.send_sender, StreamSocketSink_GetInput(&o->control.send_sink), sizeof(o->control.msg), BReactor_PendingGroup(o->reactor));
+    o->control.send_if = PacketStreamSender_GetInput(&o->control.send_sender);
+    PacketPassInterface_Sender_Init(o->control.send_if, (PacketPassInterface_handler_done)send_handler_done, o);
+}
+
+void free_control_io (BSocksClient *o)
+{
+    // free sending
+    PacketStreamSender_Free(&o->control.send_sender);
+    StreamSocketSink_Free(&o->control.send_sink);
+    
+    // free receiving
+    StreamSocketSource_Free(&o->control.recv_source);
+}
+
+void init_up_io (BSocksClient *o)
+{
+    // init receiving
+    StreamSocketSource_Init(&o->up.recv_source, FlowErrorReporter_Create(&o->domain, COMPONENT_SOURCE), &o->sock, BReactor_PendingGroup(o->reactor));
+    
+    // init sending
+    StreamSocketSink_Init(&o->up.send_sink, FlowErrorReporter_Create(&o->domain, COMPONENT_SINK), &o->sock, BReactor_PendingGroup(o->reactor));
+}
+
+void free_up_io (BSocksClient *o)
+{
+    // free sending
+    StreamSocketSink_Free(&o->up.send_sink);
+    
+    // free receiving
+    StreamSocketSource_Free(&o->up.recv_source);
+}
+
+void start_receive (BSocksClient *o, uint8_t *dest, int total)
+{
+    ASSERT(total > 0)
+    
+    o->control.recv_dest = dest;
+    o->control.recv_len = 0;
+    o->control.recv_total = total;
+    
+    do_receive(o);
+}
+
+void do_receive (BSocksClient *o)
+{
+    ASSERT(o->control.recv_len < o->control.recv_total)
+    
+    StreamRecvInterface_Receiver_Recv(o->control.recv_if, o->control.recv_dest + o->control.recv_len, o->control.recv_total - o->control.recv_len);
+}
+
+void error_handler (BSocksClient *o, int component, const void *data)
+{
+    ASSERT(component == COMPONENT_SOURCE || component == COMPONENT_SINK)
+    DebugObject_Access(&o->d_obj);
+    
+    if (o->state == STATE_UP && component == COMPONENT_SOURCE && *((int *)data) == STREAMSOCKETSOURCE_ERROR_CLOSED) {
+        BLog(BLOG_DEBUG, "connection closed");
+        
+        report_error(o, BSOCKSCLIENT_EVENT_ERROR_CLOSED);
+        return;
+    }
+    
+    BLog(BLOG_NOTICE, "socket error (%d)", BSocket_GetError(&o->sock));
+    
+    report_error(o, BSOCKSCLIENT_EVENT_ERROR);
+    return;
+}
+
+void socket_error_handler (BSocksClient *o, int event)
+{
+    ASSERT(event == BSOCKET_ERROR)
+    DebugObject_Access(&o->d_obj);
+    
+    BLog(BLOG_NOTICE, "socket error event");
+    
+    report_error(o, BSOCKSCLIENT_EVENT_ERROR);
+    return;
+}
+
+void connect_handler (BSocksClient *o, int event)
+{
+    ASSERT(event == BSOCKET_CONNECT)
+    ASSERT(o->state == STATE_CONNECTING)
+    DebugObject_Access(&o->d_obj);
+    
+    // remove event handler
+    BSocket_RemoveEventHandler(&o->sock, BSOCKET_CONNECT);
+    
+    // check connect result
+    int res = BSocket_GetConnectResult(&o->sock);
+    if (res != 0) {
+        BLog(BLOG_NOTICE, "connection failed (%d)", res);
+        report_error(o, BSOCKSCLIENT_EVENT_ERROR);
+        return;
+    }
+    
+    BLog(BLOG_DEBUG, "connected");
+    
+    // init control I/O
+    init_control_io(o);
+    
+    // send hello
+    o->control.msg.client_hello.header.ver = hton8(SOCKS_VERSION);
+    o->control.msg.client_hello.header.nmethods = hton8(1);
+    o->control.msg.client_hello.method.method = hton8(SOCKS_METHOD_NO_AUTHENTICATION_REQUIRED);
+    PacketPassInterface_Sender_Send(o->control.send_if, (uint8_t *)&o->control.msg.client_hello, sizeof(o->control.msg.client_hello));
+    
+    // set state
+    o->state = STATE_SENDING_HELLO;
+}
+
+void recv_handler_done (BSocksClient *o, int data_len)
+{
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= o->control.recv_total - o->control.recv_len)
+    DebugObject_Access(&o->d_obj);
+    
+    o->control.recv_len += data_len;
+    
+    if (o->control.recv_len < o->control.recv_total) {
+        do_receive(o);
+        return;
+    }
+    
+    switch (o->state) {
+        case STATE_SENT_HELLO: {
+            BLog(BLOG_DEBUG, "received hello");
+            
+            if (ntoh8(o->control.msg.server_hello.ver != SOCKS_VERSION)) {
+                BLog(BLOG_NOTICE, "wrong version");
+                report_error(o, BSOCKSCLIENT_EVENT_ERROR);
+                return;
+            }
+            
+            if (ntoh8(o->control.msg.server_hello.method != SOCKS_METHOD_NO_AUTHENTICATION_REQUIRED)) {
+                BLog(BLOG_NOTICE, "wrong method");
+                report_error(o, BSOCKSCLIENT_EVENT_ERROR);
+                return;
+            }
+            
+            // send request
+            o->control.msg.request.header.ver = hton8(SOCKS_VERSION);
+            o->control.msg.request.header.cmd = hton8(SOCKS_CMD_CONNECT);
+            o->control.msg.request.header.rsv = hton8(0);
+            int len = sizeof(o->control.msg.request.header);
+            switch (o->dest_addr.type) {
+                case BADDR_TYPE_IPV4:
+                    o->control.msg.request.header.atyp = hton8(SOCKS_ATYP_IPV4);
+                    o->control.msg.request.addr.ipv4.addr = o->dest_addr.ipv4.ip;
+                    o->control.msg.request.addr.ipv4.port = o->dest_addr.ipv4.port;
+                    len += sizeof(o->control.msg.request.addr.ipv4);
+                    break;
+                case BADDR_TYPE_IPV6:
+                    o->control.msg.request.header.atyp = hton8(SOCKS_ATYP_IPV6);
+                    memcpy(o->control.msg.request.addr.ipv6.addr, o->dest_addr.ipv6.ip, sizeof(o->dest_addr.ipv6.ip));
+                    o->control.msg.request.addr.ipv6.port = o->dest_addr.ipv6.port;
+                    len += sizeof(o->control.msg.request.addr.ipv6);
+                    break;
+                default:
+                    ASSERT(0);
+            }
+            PacketPassInterface_Sender_Send(o->control.send_if, (uint8_t *)&o->control.msg.request, len);
+            
+            // set state
+            o->state = STATE_SENDING_REQUEST;
+        } break;
+        
+        case STATE_SENT_REQUEST: {
+            BLog(BLOG_DEBUG, "received reply header");
+            
+            if (ntoh8(o->control.msg.reply.header.ver) != SOCKS_VERSION) {
+                BLog(BLOG_NOTICE, "wrong version");
+                report_error(o, BSOCKSCLIENT_EVENT_ERROR);
+                return;
+            }
+            
+            if (ntoh8(o->control.msg.reply.header.rep) != SOCKS_REP_SUCCEEDED) {
+                BLog(BLOG_NOTICE, "reply not successful");
+                report_error(o, BSOCKSCLIENT_EVENT_ERROR);
+                return;
+            }
+            
+            int addr_len;
+            switch (ntoh8(o->control.msg.reply.header.atyp)) {
+                case SOCKS_ATYP_IPV4:
+                    addr_len = sizeof(o->control.msg.reply.addr.ipv4);
+                    break;
+                case SOCKS_ATYP_IPV6:
+                    addr_len = sizeof(o->control.msg.reply.addr.ipv6);
+                    break;
+                default:
+                    BLog(BLOG_NOTICE, "reply has unknown address type");
+                    report_error(o, BSOCKSCLIENT_EVENT_ERROR);
+                    return;
+            }
+            
+            // receive the rest of the reply
+            start_receive(o, (uint8_t *)&o->control.msg.reply.addr, addr_len);
+            
+            // set state
+            o->state = STATE_RECEIVED_REPLY_HEADER;
+        } break;
+        
+        case STATE_RECEIVED_REPLY_HEADER: {
+            BLog(BLOG_DEBUG, "received reply rest");
+            
+            // free control I/O
+            free_control_io(o);
+            
+            // init up I/O
+            init_up_io(o);
+            
+            // set state
+            o->state = STATE_UP;
+            
+            // call handler
+            o->handler(o->user, BSOCKSCLIENT_EVENT_UP);
+            return;
+        } break;
+        
+        default:
+            ASSERT(0);
+    }
+}
+
+void send_handler_done (BSocksClient *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    switch (o->state) {
+        case STATE_SENDING_HELLO: {
+            BLog(BLOG_DEBUG, "sent hello");
+            
+            // receive hello
+            start_receive(o, (uint8_t *)&o->control.msg.server_hello, sizeof(o->control.msg.server_hello));
+            
+            // set state
+            o->state = STATE_SENT_HELLO;
+        } break;
+        
+        case STATE_SENDING_REQUEST: {
+            BLog(BLOG_DEBUG, "sent request");
+            
+            // receive reply header
+            start_receive(o, (uint8_t *)&o->control.msg.reply.header, sizeof(o->control.msg.reply.header));
+            
+            // set state
+            o->state = STATE_SENT_REQUEST;
+        } break;
+        
+        default:
+            ASSERT(0);
+    }
+}
+
+int BSocksClient_Init (BSocksClient *o, BAddr server_addr, BAddr dest_addr, BSocksClient_handler handler, void *user, BReactor *reactor)
+{
+    ASSERT(dest_addr.type == BADDR_TYPE_IPV4 || dest_addr.type == BADDR_TYPE_IPV6)
+    
+    // init arguments
+    o->dest_addr = dest_addr;
+    o->handler = handler;
+    o->user = user;
+    o->reactor = reactor;
+    
+    // init error domain
+    FlowErrorDomain_Init(&o->domain, (FlowErrorDomain_handler)error_handler, o);
+    
+    // init socket
+    if (BSocket_Init(&o->sock, o->reactor, server_addr.type, BSOCKET_TYPE_STREAM) < 0) {
+        BLog(BLOG_NOTICE, "BSocket_Init failed");
+        goto fail0;
+    }
+    
+    // connect socket
+    if (BSocket_Connect(&o->sock, &server_addr) >= 0 || BSocket_GetError(&o->sock) != BSOCKET_ERROR_IN_PROGRESS) {
+        BLog(BLOG_NOTICE, "BSocket_Connect failed");
+        goto fail1;
+    }
+    
+    // setup error event
+    BSocket_AddEventHandler(&o->sock, BSOCKET_ERROR, (BSocket_handler)socket_error_handler, o);
+    BSocket_EnableEvent(&o->sock, BSOCKET_ERROR);
+    
+    // setup connect event
+    BSocket_AddEventHandler(&o->sock, BSOCKET_CONNECT, (BSocket_handler)connect_handler, o);
+    BSocket_EnableEvent(&o->sock, BSOCKET_CONNECT);
+    
+    // set state
+    o->state = STATE_CONNECTING;
+    
+    DebugObject_Init(&o->d_obj);
+    #ifndef NDEBUG
+    DEAD_INIT(o->d_dead);
+    #endif
+    
+    return 1;
+    
+fail1:
+    BSocket_Free(&o->sock);
+fail0:
+    return 0;
+}
+
+void BSocksClient_Free (BSocksClient *o)
+{
+    #ifndef NDEBUG
+    DEAD_KILL(o->d_dead);
+    #endif
+    DebugObject_Free(&o->d_obj);
+    
+    if (o->state == STATE_UP) {
+        // free up I/O
+        free_up_io(o);
+    }
+    else if (o->state != STATE_CONNECTING) {
+        ASSERT(o->state == STATE_SENDING_HELLO || o->state == STATE_SENT_HELLO ||
+               o->state == STATE_SENDING_REQUEST || o->state == STATE_SENT_REQUEST ||
+               o->state == STATE_RECEIVED_REPLY_HEADER
+        )
+        // free control I/O
+        free_control_io(o);
+    }
+    
+    // free socket
+    BSocket_Free(&o->sock);
+}
+
+StreamPassInterface * BSocksClient_GetSendInterface (BSocksClient *o)
+{
+    ASSERT(o->state == STATE_UP)
+    DebugObject_Access(&o->d_obj);
+    
+    return StreamSocketSink_GetInput(&o->up.send_sink);
+}
+
+StreamRecvInterface * BSocksClient_GetRecvInterface (BSocksClient *o)
+{
+    ASSERT(o->state == STATE_UP)
+    DebugObject_Access(&o->d_obj);
+    
+    return StreamSocketSource_GetOutput(&o->up.recv_source);
+}

+ 103 - 0
socksclient/BSocksClient.h

@@ -0,0 +1,103 @@
+/**
+ * @file BSocksClient.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * @section DESCRIPTION
+ * 
+ * SOCKS5 client. TCP only, no authentication.
+ */
+
+#ifndef BADVPN_SOCKS_BSOCKSCLIENT_H
+#define BADVPN_SOCKS_BSOCKSCLIENT_H
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <misc/dead.h>
+#include <misc/socks_proto.h>
+#include <system/DebugObject.h>
+#include <system/BSocket.h>
+#include <flow/StreamSocketSink.h>
+#include <flow/StreamSocketSource.h>
+#include <flow/PacketStreamSender.h>
+
+#define BSOCKSCLIENT_EVENT_ERROR 1
+#define BSOCKSCLIENT_EVENT_UP 2
+#define BSOCKSCLIENT_EVENT_ERROR_CLOSED 3
+
+typedef void (*BSocksClient_handler) (void *user, int event);
+
+typedef struct {
+    BAddr dest_addr;
+    BSocksClient_handler handler;
+    void *user;
+    BReactor *reactor;
+    int state;
+    FlowErrorDomain domain;
+    BSocket sock;
+    union {
+        struct {
+            PacketPassInterface *send_if;
+            PacketStreamSender send_sender;
+            StreamSocketSink send_sink;
+            StreamSocketSource recv_source;
+            StreamRecvInterface *recv_if;
+            union {
+                struct {
+                    struct socks_client_hello_header header;
+                    struct socks_client_hello_method method;
+                } __attribute__((packed)) client_hello;
+                struct socks_server_hello server_hello;
+                struct {
+                    struct socks_request_header header;
+                    union {
+                        struct socks_addr_ipv4 ipv4;
+                        struct socks_addr_ipv6 ipv6;
+                    } addr;
+                } __attribute__((packed)) request;
+                struct {
+                    struct socks_reply_header header;
+                    union {
+                        struct socks_addr_ipv4 ipv4;
+                        struct socks_addr_ipv6 ipv6;
+                    } addr;
+                } __attribute__((packed)) reply;
+            } msg;
+            uint8_t *recv_dest;
+            int recv_len;
+            int recv_total;
+        } control;
+        struct {
+            StreamSocketSink send_sink;
+            StreamSocketSource recv_source;
+        } up;
+    };
+    DebugObject d_obj;
+    #ifndef NDEBUG
+    dead_t d_dead;
+    #endif
+} BSocksClient;
+
+int BSocksClient_Init (BSocksClient *o, BAddr server_addr, BAddr dest_addr, BSocksClient_handler handler, void *user, BReactor *reactor) WARN_UNUSED;
+void BSocksClient_Free (BSocksClient *o);
+StreamPassInterface * BSocksClient_GetSendInterface (BSocksClient *o);
+StreamRecvInterface * BSocksClient_GetRecvInterface (BSocksClient *o);
+
+#endif

+ 4 - 0
socksclient/CMakeLists.txt

@@ -0,0 +1,4 @@
+add_library(socksclient
+    BSocksClient.c
+)
+target_link_libraries(socksclient system flow)