#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int tunfd, poller; struct sockaddr_in socks_server; struct connection { int fd; #define TCPCON 0 #define UDPCON 1 unsigned int type : 1; int status : 4; unsigned int fin : 2; unsigned int reserved : 1; uint32_t seq, ack_seq, echo; uint16_t id, mss; char * buffer; size_t buflen; char orig[68]; struct sockaddr_in local, remote; struct connection * prev, * next; } * first_connection = NULL; struct connection * find_connection (struct in_addr src, uint16_t srcport, struct in_addr dst, uint16_t dstport, int type) { struct connection * now; for (now = first_connection; now != NULL; now = now->next) { if (((now->local.sin_addr.s_addr == src.s_addr && now->local.sin_port == srcport && now->remote.sin_addr.s_addr == dst.s_addr && now->remote.sin_port == dstport) || (now->remote.sin_addr.s_addr == src.s_addr && now->remote.sin_port == srcport && now->local.sin_addr.s_addr == dst.s_addr && now->local.sin_port == dstport)) && (!type || type == now->type)) return now; } return NULL; } void remove_connection (struct connection * conn) { if (conn->buffer != NULL) free (conn->buffer); if (conn->next != NULL) conn->next->prev = conn->prev; if (conn->prev != NULL) conn->prev->next = conn->next; if (conn == first_connection) first_connection = conn->next; free (conn); } #define Econnrefused -1 #define Enetunreach -2 #define Ehostunreach -3 #define Ettlexpired -4 int socks_connect () { int sock; unsigned char buf[280]; sock = socket (AF_INET, SOCK_STREAM, 0); if (sock < 0) return Enetunreach; if (connect (sock, (struct sockaddr *) &socks_server, sizeof (socks_server))) goto close_netunreach; buf[0] = 5; buf[1] = 1; buf[2] = 0; if (write (sock, buf, 3) < 3) goto close_netunreach; return sock; close_netunreach: close (sock); return Enetunreach; } int socks_connect2 (struct connection * conn) { unsigned char buf[280]; size_t toread; int err; if (conn->status == 0) { if (read (conn->fd, buf, 2) < 2) goto close_netunreach; if (buf[0] != 0x05 && buf[1] != 0x00) goto close_netunreach; buf[0] = 5; buf[1] = conn->type == TCPCON ? 1 : 3; buf[2] = 0; buf[3] = 1; if (conn->type == TCPCON) { memcpy (buf+4, &conn->remote.sin_addr, 4); memcpy (buf+8, &conn->remote.sin_port, 2); } else memset (buf+4, 0, 6); if (write (conn->fd, buf, 10) < 10) goto close_netunreach; return 1; } else if (conn->status == 1) { if (read (conn->fd, buf, 5) < 5) goto close_netunreach; if (buf[0] != 5 || buf[2] != 0) goto close_netunreach; switch (buf[1]) { case 0: err = 0; break; case 4: err = Ehostunreach; break; case 5: err = Econnrefused; break; case 6: err = Ettlexpired; break; default: err = Enetunreach; } if (err) { close (conn->fd); return err; } if (buf[3] == 1) toread = 6-1; else if (buf[3] == 3) toread = buf[4] + 2; else if (buf[3] == 4) toread = 18-1; else goto close_netunreach; if (read (conn->fd, buf, toread) < toread) goto close_netunreach; return 2; } close_netunreach: close (conn->fd); return Enetunreach; } int tun_alloc (char * dev) { struct ifreq ifr; int fd, err; if ((fd = open ("/dev/net/tun", O_RDWR)) < 0) return (-1); memset (&ifr, 0, sizeof (ifr)); ifr.ifr_flags = IFF_TUN | IFF_NO_PI; if (*dev) strncpy (ifr.ifr_name, dev, IFNAMSIZ); if ((err = ioctl (fd, TUNSETIFF, (void *) &ifr)) < 0) { close (fd); return err; } strcpy (dev, ifr.ifr_name); return fd; } static inline int16_t csum_add (int16_t sum, int16_t addend) { uint32_t res = (uint32_t) sum; res += (uint32_t) addend; return (int16_t) (res + (res < (uint32_t) addend)); } static inline int16_t ip_tcpudp_csum (struct in_addr * src, struct in_addr * dst) { int16_t csum = 0; csum = csum_add (csum, ntohs (*((int16_t *) src))); csum = csum_add (csum, ntohs (*(((int16_t *) src)+1))); csum = csum_add (csum, ntohs (*((int16_t *) dst))); csum = csum_add (csum, ntohs (*(((int16_t *) dst)+1))); return csum; } void parse_tcp_opts (char * buf, size_t len, uint16_t * mss, uint8_t * wscale, int * sack, uint32_t * timestamp, uint32_t * echo) { size_t now = 0; if (mss != NULL) *mss = 0; if (wscale != NULL) *wscale = 0; if (sack != NULL) *sack = 0; if (timestamp != NULL && echo != NULL) *timestamp = *echo = 0; while (now < len) { if (buf[now] == 0) break; else if (buf[now] == 1) now ++; else if (buf[now] == 2 && buf[now+1] == 4) { if (mss != NULL) *mss = (buf[now+2] << 8) | (buf[now+3] & 0xff); now += 4; } else if (buf[now] == 3 && buf[now+1] == 3) { if (wscale != NULL) *wscale = buf[now+2]; now += 3; } else if (buf[now] == 4 && buf[now+1] == 2) { if (sack != NULL) *sack = 1; now += 2; } else if (buf[now] == 8 && buf[now+1] == 10) { if (timestamp != NULL && echo != NULL) { *timestamp = ntohl (*((uint32_t *) (buf + now + 2))); *echo = ntohl (*((uint32_t *) (buf + now + 6))); } now += 10; } } } size_t build_tcp (char * buf, int16_t csum, uint16_t source, uint16_t dest, uint32_t seq, uint32_t ack_seq, int syn, int ack, int psh, int rst, int fin, uint16_t window, uint16_t mss, uint8_t wscale, int sack, uint32_t timestamp, uint32_t echo, char * data, size_t len) { struct tcphdr * h = (struct tcphdr *) buf; size_t hlen = 20; int i; memset (buf, 0, hlen); h->source = htons (source); h->dest = htons (dest); h->seq = htonl (seq); h->ack_seq = htonl (ack_seq); if (syn) h->syn = 1; if (ack) h->ack = 1; if (psh) h->psh = 1; if (rst) h->rst = 1; if (fin) h->fin = 1; h->window = htons (window); h->urg_ptr = 0; if (mss) { buf[hlen++] = 2; buf[hlen++] = 4; buf[hlen++] = (mss >> 8) & 0xff; buf[hlen++] = mss & 0xff; } if (sack) { buf[hlen++] = 4; buf[hlen++] = 2; } if (timestamp) { buf[hlen++] = 8; buf[hlen++] = 10; *((uint32_t *) (buf + hlen)) = htonl (timestamp); *((uint32_t *) (buf + hlen + 4)) = htonl (echo); hlen += 8; } if (wscale) { if ((hlen >> 1) & 1) { buf[hlen++] = 1; buf[hlen++] = 1; } buf[hlen++] = 1; buf[hlen++] = 3; buf[hlen++] = 3; buf[hlen++] = wscale; } while (hlen % 4) buf[hlen++] = 1; h->doff = hlen/4; if (data != NULL && len > 0) memcpy (buf + hlen, data, len); csum = csum_add (csum, IPPROTO_TCP); csum = csum_add (csum, hlen+len); for (i = 0; i < hlen+len; i += 2) csum = csum_add (csum, ntohs (*(int16_t *) (buf + i))); if (len & 1) csum = csum_add (csum, buf[hlen+len-1] << 8); h->check = htons (~csum); return (hlen+len); } size_t build_udp (char * buf, int16_t csum, uint16_t source, uint16_t dest, char * data, size_t len) { struct udphdr * h = (struct udphdr *) buf; int i; h->source = htons (source); h->dest = htons (dest); h->len = htons (8+len); h->check = 0; csum = csum_add (csum, IPPROTO_UDP); csum = csum_add (csum, 8+len); for (i = 0; i < 8+len; i += 2) csum = csum_add (csum, ntohs (*(int16_t *) (buf + i))); if (len & 1) csum = csum_add (csum, buf[8+len-1] << 8); h->check = htons (~csum); return (8+len); } size_t build_icmp (char * buf, uint8_t type, uint8_t code, struct ip * orig) { struct icmphdr * h = (struct icmphdr *) buf; int16_t csum = 0; size_t origlen; int i; memset (buf, 0, 8); h->type = type; h->code = code; for (i = 0; i < 8; i += 2) csum = csum_add (csum, ntohs (*(int16_t *) (buf + i))); h->checksum = htons (~csum); #define MIN(a,b) ((a)<(b)?(a):(b)) origlen = MIN(orig->ip_hl*4 + 8, orig->ip_len); if (orig != NULL) memcpy (buf+8, orig, origlen); return (8+origlen); } void build_ip (char * buf, uint16_t len, uint16_t id, uint16_t off, uint8_t ttl, uint8_t prot, struct in_addr src, struct in_addr dst) { struct ip * h = (struct ip *) buf; uint16_t csum = 0; int i; memset (buf, 0, 20); h->ip_v = 4; h->ip_hl = 5; h->ip_len = htons (len); h->ip_id = htons (id); h->ip_off = htons (off); h->ip_ttl = ttl; h->ip_p = prot; h->ip_src = src; h->ip_dst = dst; for (i = 0; i < 20; i += 2) csum = csum_add (csum, ntohs (*(int16_t *) (buf + i))); h->ip_sum = htons (~csum); } void polled (pollerdata_t data, pollerev_t events) { if (data.ptr == NULL) { unsigned char buf[65536]; struct ip * iph; read (tunfd, buf, 65536); iph = (struct ip *) buf; if (iph->ip_v == 4) { struct connection * conn; char reply[100]; int16_t csum; size_t datalen; if (iph->ip_p == IPPROTO_TCP) { struct tcphdr * tcph; uint32_t nullecho; size_t tcplen; tcph = (struct tcphdr *) (buf + iph->ip_hl*4); conn = find_connection (iph->ip_src, tcph->source, iph->ip_dst, tcph->dest, TCPCON); if (tcph->syn && !tcph->ack && !tcph->ack_seq && conn == NULL) { int connfd; connfd = socks_connect (); if (connfd == Econnrefused) { csum = ip_tcpudp_csum (&iph->ip_src, &iph->ip_dst); tcplen = build_tcp (reply + 20, csum, ntohs (tcph->dest), ntohs (tcph->source), 0, ntohl (tcph->seq) + 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, NULL, 0); build_ip (reply, 20 + tcplen, 0, IP_DF, 64, IPPROTO_TCP, iph->ip_dst, iph->ip_src); write (tunfd, reply, 20 + tcplen); } else if (connfd < 0) { uint8_t _type, _code; size_t icmplen; if (connfd == Ettlexpired) _type = 11, _code = 0; else if (connfd == Ehostunreach) _type = 3, _code = 1; else _type = 3, _code = 0; icmplen = build_icmp (reply + 20, _type, _code, (struct ip *) buf); build_ip (reply, 20 + icmplen, 0, 0, 64, IPPROTO_ICMP, iph->ip_dst, iph->ip_src); write (tunfd, reply, 20 + icmplen); } else { conn = malloc (sizeof (struct connection)); conn->local.sin_family = AF_INET; conn->local.sin_addr = iph->ip_src; conn->local.sin_port = tcph->source; conn->remote.sin_family = AF_INET; conn->remote.sin_addr = iph->ip_dst; conn->remote.sin_port = tcph->dest; conn->type = TCPCON; conn->fd = connfd; conn->status = 0; conn->prev = NULL; if (first_connection) first_connection->prev = conn; conn->next = first_connection; first_connection = conn; conn->buffer = NULL; conn->buflen = 0; parse_tcp_opts (buf + 40, (tcph->doff-5)*4, &conn->mss, NULL, NULL, &conn->echo, &nullecho); conn->seq = rand (); conn->ack_seq = ntohl (tcph->seq) + 1; conn->fin = 0; conn->id = rand (); memcpy (conn->orig, buf, 68); data.ptr = (void *) conn; poller_add (poller, connfd, POLLERIN, data); } } else if (conn != NULL && conn->status == 2) { if (tcph->rst) goto connterm; parse_tcp_opts (buf + 40, (tcph->doff-5)*4, NULL, NULL, NULL, &conn->echo, &nullecho); datalen = ntohs (iph->ip_len) - 20 - tcph->doff*4; if (datalen > 0 && !(conn->fin & 1)) { conn->buffer = realloc (conn->buffer, conn->buflen + datalen); memcpy (conn->buffer + conn->buflen, ((char *) tcph) + tcph->doff*4, datalen); conn->buflen += datalen; conn->ack_seq += datalen; } if (tcph->psh && !(conn->fin & 1)) { if (write (conn->fd, conn->buffer, conn->buflen) < conn->buflen) fprintf (stderr, "Could not write everything!\n"); free (conn->buffer); conn->buffer = NULL; conn->buflen = 0; } if (tcph->fin) { conn->fin |= 1; if (conn->fin < 3) shutdown (conn->fd, SHUT_WR); conn->ack_seq ++; } if (tcph->ack && ((!tcph->fin && datalen > 0) || (tcph->fin && conn->fin == 3))) { csum = ip_tcpudp_csum (&iph->ip_src, &iph->ip_dst); tcplen = build_tcp (reply + 20, csum, ntohs (tcph->dest), ntohs (tcph->source), conn->seq, conn->ack_seq, 0, 1, 0, 0, 0, 65535, 0, 0, 0, time (NULL), conn->echo, NULL, 0); build_ip (reply, 20 + tcplen, conn->id++, IP_DF, 64, IPPROTO_TCP, iph->ip_dst, iph->ip_src); write (tunfd, reply, 20 + tcplen); } if (conn->fin == 3) { connterm: close (conn->fd); poller_remove (poller, conn->fd); remove_connection (conn); } } } else if (iph->ip_p == IPPROTO_UDP) { struct udphdr * udph; size_t udplen; udph = (struct udphdr *) (buf + iph->ip_hl*4); conn = find_connection (iph->ip_src, udph->source, (struct in_addr) {INADDR_ANY}, 0, UDPCON); if (conn == NULL) { int connfd; connfd = socks_connect (); if (connfd < 0) { uint8_t _type, _code; size_t icmplen; if (connfd == Ettlexpired) _type = 11, _code = 0; else if (connfd == Econnrefused) _type = 3, _code = 3; else if (connfd == Ehostunreach) _type = 3, _code = 1; else _type = 3, _code = 0; icmplen = build_icmp (reply + 20, _type, _code, (struct ip *) buf); build_ip (reply, 20 + icmplen, 0, 0, 64, IPPROTO_ICMP, iph->ip_dst, iph->ip_src); write (tunfd, reply, 20 + icmplen); } else { conn = malloc (sizeof (struct connection)); conn->local.sin_family = AF_INET; conn->local.sin_addr = iph->ip_src; conn->local.sin_port = udph->source; conn->remote.sin_family = AF_INET; conn->remote.sin_addr.s_addr = INADDR_ANY; conn->remote.sin_port = 0; conn->type = UDPCON; conn->fd = connfd; conn->status = 0; conn->prev = NULL; if (first_connection) first_connection->prev = conn; conn->next = first_connection; first_connection = conn; datalen = ntohs (udph->len) - 8; conn->buffer = malloc (datalen + 10); conn->buflen = datalen + 10; memcpy (conn->buffer + 10, buf + iph->ip_hl*4 + 8, conn->buflen - 10); conn->buffer[0] = 0; conn->buffer[1] = 0; conn->buffer[2] = 0; conn->buffer[3] = 1; memcpy (conn->buffer + 4, &iph->ip_dst, 4); memcpy (conn->buffer + 8, &udph->dest, 2); conn->id = rand (); memcpy (conn->orig, buf, 68); data.ptr = (void *) conn; poller_add (poller, connfd, POLLERIN, data); } } else { datalen = ntohs (udph->len) - 8; if (datalen) { conn->buffer = realloc (conn->buffer, conn->buflen + datalen); memcpy (conn->buffer + conn->buflen, ((char *) udph) + 8, datalen); conn->buflen += datalen; } if (conn->status == 2 && conn->buffer != NULL && conn->buflen > 0) { memcpy (conn->buffer + 4, &iph->ip_dst, 4); memcpy (conn->buffer + 8, &udph->dest, 2); if (write (conn->fd, conn->buffer, conn->buflen) < conn->buflen) { fprintf (stderr, "Could not write everything!\n"); } conn->buffer = realloc (conn->buffer, 10); conn->buflen = 10; } } } } else if (iph->ip_v == 6) { /* IPv6 zatial nepodporujeme */ struct ip6_hdr * ip6h; } } else { struct connection * conn = (struct connection *) data.ptr; char buf[65536], reply[65536]; size_t plen, icmplen; int16_t csum; ssize_t nr; if (conn->status < 2) { conn->status = socks_connect2 (conn); if (conn->status == Econnrefused && conn->type == TCPCON) { csum = ip_tcpudp_csum (&conn->local.sin_addr, &conn->remote.sin_addr); plen = build_tcp (reply + 20, csum, ntohs (conn->remote.sin_port), ntohs (conn->local.sin_port), 0, conn->ack_seq, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, NULL, 0); build_ip (reply, 20 + plen, 0, IP_DF, 64, IPPROTO_TCP, conn->remote.sin_addr, conn->local.sin_addr); write (tunfd, reply, 20 + plen); } else if (conn->status < 0) { uint8_t _type, _code; size_t icmplen; if (conn->status == Ettlexpired) _type = 11, _code = 0; else if (conn->status == Econnrefused) _type = 3, _code = 3; else if (conn->status == Ehostunreach) _type = 3, _code = 1; else _type = 3, _code = 0; icmplen = build_icmp (reply + 20, _type, _code, (struct ip *) conn->orig); build_ip (reply, 20 + icmplen, 0, 0, 64, IPPROTO_ICMP, conn->remote.sin_addr, conn->local.sin_addr); write (tunfd, reply, 20 + icmplen); } else if (conn->status == 2 && conn->type == TCPCON) { csum = ip_tcpudp_csum (&conn->local.sin_addr, &conn->remote.sin_addr); plen = build_tcp (reply + 20, csum, ntohs (conn->remote.sin_port), ntohs (conn->local.sin_port), conn->seq - 1, conn->ack_seq, 1, 1, 0, 0, 0, 65535, conn->mss, 0, 1, time (NULL), conn->echo, NULL, 0); build_ip (reply, 20 + plen, 0, IP_DF, 64, IPPROTO_TCP, conn->remote.sin_addr, conn->local.sin_addr); write (tunfd, reply, 20 + plen); return; } else if (conn->status == 2 && conn->type == UDPCON && conn->buflen) { if (write (conn->fd, conn->buffer, conn->buflen) < conn->buflen) fprintf (stderr, "Could not write everything!\n"); conn->buffer = realloc (conn->buffer, 10); conn->buflen = 10; return; } else return; poller_remove (poller, conn->fd); remove_connection (conn); return; } nr = read (conn->fd, buf, 65536); if (nr < 1) { close (conn->fd); poller_remove (poller, conn->fd); if (conn->type == UDPCON) { remove_connection (conn); return; } conn->fin |= 2; csum = ip_tcpudp_csum (&conn->local.sin_addr, &conn->remote.sin_addr); plen = build_tcp (reply + 20, csum, ntohs (conn->remote.sin_port), ntohs (conn->local.sin_port), conn->seq, conn->ack_seq, 0, 1, 0, 0, 1, 65535, 0, 0, 0, time (NULL), conn->echo, NULL, 0); build_ip (reply, 20 + plen, conn->id++, IP_DF, 64, IPPROTO_TCP, conn->remote.sin_addr, conn->local.sin_addr); write (tunfd, reply, 20 + plen); conn->seq ++; if (conn->fin == 3) remove_connection (conn); } else if (conn->type == TCPCON || (conn->type == UDPCON && conn->buflen > 10 && !memcmp (conn->buffer, (char[]) {0, 0, 0, 1}, 4))) { csum = ip_tcpudp_csum (&conn->local.sin_addr, &conn->remote.sin_addr); if (conn->type == TCPCON) plen = build_tcp (reply + 20, csum, ntohs (conn->remote.sin_port), ntohs (conn->local.sin_port), conn->seq, conn->ack_seq, 0, 1, 1, 0, 0, 65535, 0, 0, 0, time (NULL), conn->echo, buf, nr); else plen = build_udp (reply + 20, csum, ntohs (*(uint16_t *) (conn->buffer + 8)), ntohs (conn->local.sin_port), buf + 10, nr - 10); build_ip (reply, 20 + plen, conn->id++, IP_DF, 64, conn->type == TCPCON ? IPPROTO_TCP : IPPROTO_UDP, conn->type == TCPCON ? conn->remote.sin_addr : (*(struct in_addr *) (conn->buffer + 4)), conn->local.sin_addr); conn->seq += nr; write (tunfd, reply, 20 + plen); } } } int main (int argc, char ** argv) { char ifnam[IFNAMSIZ], * tmp; long int port; pollerdata_t data; struct connection * conn, * nextconn; srand (time (NULL) ^ getpid ()); if (argc < 4) { fprintf (stderr, "usage: %s " "\n\n", argv[0]); exit (EXIT_FAILURE); } socks_server.sin_family = AF_INET; if (!inet_pton (AF_INET, argv[2], &socks_server.sin_addr.s_addr)) { fprintf (stderr, "Invalid SOCKS server IP address!\n\n"); exit (EXIT_FAILURE); } port = strtol (argv[3], &tmp, 10); if (*tmp != '\0' || port < 1 || port > 65535) { fprintf (stderr, "Invalid SOCKS server port!\n\n"); exit (EXIT_FAILURE); } socks_server.sin_port = htons (port); strcpy (ifnam, argv[1]); tunfd = tun_alloc (ifnam); if (tunfd < 0) { fprintf (stderr, "Cannot allocate tun interface %s!\n\n", ifnam); exit (EXIT_FAILURE); } poller = poller_create (); if (poller < 0) { fprintf (stderr, "Cannot create poller!\n\n"); close (tunfd); exit (EXIT_FAILURE); } data.ptr = NULL; poller_add (poller, tunfd, POLLERIN, data); while (poller_poll (poller, -1, polled) > 0) ; for (conn = first_connection; conn != NULL; conn = nextconn) { nextconn = conn->next; poller_remove (poller, conn->fd); close (conn->fd); free (conn); } poller_destroy (poller); close (tunfd); exit (0); }