| /** @file |
| |
| A brief file description |
| |
| @section license License |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| /* |
| This implements SOCKS server. We intecept the http traffic and send it |
| through HTTP. Others are tunneled through directly to the socks server. |
| |
| |
| */ |
| #include "tscore/ink_platform.h" |
| #include "P_Net.h" |
| #include "I_OneWayTunnel.h" |
| #include "HttpSessionAccept.h" |
| |
| enum { |
| socksproxy_http_connections_stat, |
| socksproxy_tunneled_connections_stat, |
| |
| socksproxy_stat_count |
| }; |
| static RecRawStatBlock *socksproxy_stat_block; |
| |
| #define SOCKSPROXY_INC_STAT(x) RecIncrRawStat(socksproxy_stat_block, mutex->thread_holding, x) |
| |
| struct SocksProxy : public Continuation { |
| using EventHandler = int (SocksProxy::*)(int, void *); |
| |
| enum { |
| SOCKS_INIT = 1, |
| SOCKS_ACCEPT, |
| AUTH_DONE, |
| SERVER_TUNNEL, |
| HTTP_REQ, |
| RESP_TO_CLIENT, |
| ALL_DONE, |
| SOCKS_ERROR, |
| }; |
| |
| ~SocksProxy() override {} |
| |
| int mainEvent(int event, void *data); |
| int setupHttpRequest(unsigned char *p); |
| |
| int sendResp(bool granted); |
| |
| void init(NetVConnection *netVC); |
| void free(); |
| |
| private: |
| NetVConnection *clientVC = nullptr; |
| VIO *clientVIO = nullptr; |
| |
| MIOBuffer *buf = nullptr; |
| IOBufferReader *reader = nullptr; |
| Event *timeout = nullptr; |
| |
| SocksAuthHandler auth_handler = nullptr; |
| Action *pending_action = nullptr; |
| |
| unsigned char version = 0; |
| int state = SOCKS_INIT; |
| int recursion = 0; |
| }; |
| |
| ClassAllocator<SocksProxy> socksProxyAllocator("socksProxyAllocator"); |
| |
| void |
| SocksProxy::init(NetVConnection *netVC) |
| { |
| mutex = new_ProxyMutex(); |
| buf = new_MIOBuffer(); |
| reader = buf->alloc_reader(); |
| |
| SCOPED_MUTEX_LOCK(lock, mutex, this_ethread()); |
| |
| SET_HANDLER((EventHandler)&SocksProxy::mainEvent); |
| |
| mainEvent(NET_EVENT_ACCEPT, netVC); |
| } |
| |
| void |
| SocksProxy::free() |
| { |
| if (buf) { |
| free_MIOBuffer(buf); |
| } |
| |
| mutex = nullptr; |
| |
| socksProxyAllocator.free(this); |
| } |
| |
| int |
| SocksProxy::mainEvent(int event, void *data) |
| { |
| int ret = EVENT_DONE; |
| unsigned char *p; |
| |
| VIO *vio; |
| int64_t n_read_avail; |
| |
| recursion++; |
| |
| switch (event) { |
| case NET_EVENT_ACCEPT: |
| state = SOCKS_ACCEPT; |
| Debug("SocksProxy", "Proxy got accept event"); |
| |
| clientVC = (NetVConnection *)data; |
| clientVC->socks_addr.reset(); |
| // fallthrough |
| |
| case VC_EVENT_WRITE_COMPLETE: |
| |
| switch (state) { |
| case HTTP_REQ: { |
| HttpSessionAccept::Options ha_opt; |
| // This is a WRITE_COMPLETE. vio->nbytes == vio->ndone is true |
| |
| SOCKSPROXY_INC_STAT(socksproxy_http_connections_stat); |
| Debug("SocksProxy", "Handing over the HTTP request"); |
| |
| ha_opt.transport_type = clientVC->attributes; |
| HttpSessionAccept http_accept(ha_opt); |
| http_accept.mainEvent(NET_EVENT_ACCEPT, clientVC); |
| state = ALL_DONE; |
| break; |
| } |
| |
| case RESP_TO_CLIENT: |
| state = SOCKS_ERROR; |
| break; |
| |
| default: |
| buf->reset(); |
| timeout = this_ethread()->schedule_in(this, HRTIME_SECONDS(netProcessor.socks_conf_stuff->socks_timeout)); |
| clientVC->do_io_read(this, INT64_MAX, buf); |
| } |
| |
| break; |
| |
| case VC_EVENT_WRITE_READY: |
| Debug("SocksProxy", "Received unexpected write_ready"); |
| break; |
| |
| case VC_EVENT_READ_COMPLETE: |
| Debug("SocksProxy", "Oops! We should never get Read_Complete."); |
| // FALLTHROUGH |
| case VC_EVENT_READ_READY: { |
| unsigned char *port_ptr = nullptr; |
| |
| ret = EVENT_CONT; |
| vio = (VIO *)data; |
| |
| n_read_avail = reader->block_read_avail(); |
| ink_assert(n_read_avail == reader->read_avail()); |
| p = (unsigned char *)reader->start(); |
| |
| if (n_read_avail >= 2) { |
| Debug(state == SOCKS_ACCEPT ? "SocksProxy" : "", "Accepted connection from a version %d client", (int)p[0]); |
| |
| // Most of the time request is just a single packet |
| switch (p[0]) { |
| case SOCKS4_VERSION: |
| ink_assert(state == SOCKS_ACCEPT); |
| |
| if (n_read_avail > 8) { |
| // read the user name |
| int i = 8; |
| while (p[i] != 0 && n_read_avail > i) { |
| i++; |
| } |
| |
| if (p[i] == 0) { |
| port_ptr = &p[2]; |
| clientVC->socks_addr.type = SOCKS_ATYPE_IPV4; |
| reader->consume(i + 1); |
| ret = EVENT_DONE; |
| } |
| } |
| break; |
| |
| case SOCKS5_VERSION: |
| |
| if (state == SOCKS_ACCEPT) { |
| if (n_read_avail >= 2 + p[1]) { |
| auth_handler = &socks5ServerAuthHandler; |
| ret = EVENT_DONE; |
| } |
| } else { |
| ink_assert(state == AUTH_DONE); |
| |
| if (n_read_avail >= 5) { |
| int req_len; |
| |
| switch (p[3]) { |
| case SOCKS_ATYPE_IPV4: |
| req_len = 10; |
| break; |
| case SOCKS_ATYPE_FQHN: |
| req_len = 7 + p[4]; |
| break; |
| case SOCKS_ATYPE_IPV6: |
| req_len = 22; |
| break; |
| default: |
| req_len = INT_MAX; |
| Debug("SocksProxy", "Illegal address type(%d)", (int)p[3]); |
| } |
| |
| if (n_read_avail >= req_len) { |
| port_ptr = &p[req_len - 2]; |
| clientVC->socks_addr.type = p[3]; |
| auth_handler = nullptr; |
| reader->consume(req_len); |
| ret = EVENT_DONE; |
| } |
| } |
| } |
| break; |
| |
| default: |
| Warning("Wrong version for Socks: %d\n", p[0]); |
| state = SOCKS_ERROR; |
| } |
| } |
| |
| if (ret == EVENT_DONE) { |
| timeout->cancel(this); |
| timeout = nullptr; |
| |
| if (auth_handler) { |
| /* disable further reads */ |
| vio->nbytes = vio->ndone; |
| |
| // There is some auth stuff left. |
| if (invokeSocksAuthHandler(auth_handler, SOCKS_AUTH_READ_COMPLETE, p) >= 0) { |
| buf->reset(); |
| p = (unsigned char *)buf->start(); |
| |
| int n_bytes = invokeSocksAuthHandler(auth_handler, SOCKS_AUTH_FILL_WRITE_BUF, p); |
| ink_assert(n_bytes > 0); |
| |
| buf->fill(n_bytes); |
| |
| clientVC->do_io_write(this, n_bytes, reader, false); |
| |
| state = AUTH_DONE; |
| } else { |
| Debug("SocksProxy", "Auth_handler returned error"); |
| state = SOCKS_ERROR; |
| } |
| |
| } else { |
| int port = port_ptr[0] * 256 + port_ptr[1]; |
| version = p[0]; |
| |
| if (port == netProcessor.socks_conf_stuff->http_port && p[1] == SOCKS_CONNECT) { |
| /* disable further reads */ |
| vio->nbytes = vio->ndone; |
| |
| ret = setupHttpRequest(p); |
| sendResp(true); |
| state = HTTP_REQ; |
| |
| } else { |
| SOCKSPROXY_INC_STAT(socksproxy_tunneled_connections_stat); |
| Debug("SocksProxy", "Tunnelling the connection for port %d", port); |
| |
| if (clientVC->socks_addr.type != SOCKS_ATYPE_IPV4) { |
| // We dont support other kinds of addresses for tunnelling |
| // if this is a hostname we could do host look up here |
| mainEvent(NET_EVENT_OPEN_FAILED, nullptr); |
| break; |
| } |
| |
| uint32_t ip; |
| struct sockaddr_in addr; |
| |
| memcpy(&ip, &p[4], 4); |
| ats_ip4_set(&addr, ip, htons(port)); |
| |
| state = SERVER_TUNNEL; |
| clientVIO = vio; // used in the tunnel |
| |
| // tunnel the connection. |
| |
| NetVCOptions vc_options; |
| vc_options.socks_support = p[1]; |
| vc_options.socks_version = version; |
| |
| Action *action = netProcessor.connect_re(this, ats_ip_sa_cast(&addr), &vc_options); |
| if (action != ACTION_RESULT_DONE) { |
| ink_assert(pending_action == nullptr); |
| pending_action = action; |
| } |
| } |
| } |
| } // if (ret == EVENT_DONE) |
| |
| break; |
| } |
| |
| case NET_EVENT_OPEN: { |
| pending_action = nullptr; |
| ink_assert(state == SERVER_TUNNEL); |
| Debug("SocksProxy", "open to Socks server succeeded"); |
| |
| NetVConnection *serverVC; |
| serverVC = (NetVConnection *)data; |
| |
| OneWayTunnel *c_to_s = OneWayTunnel::OneWayTunnel_alloc(); |
| OneWayTunnel *s_to_c = OneWayTunnel::OneWayTunnel_alloc(); |
| |
| c_to_s->init(clientVC, serverVC, nullptr, clientVIO, reader); |
| s_to_c->init(serverVC, clientVC, /*aCont = */ nullptr, 0 /*best guess */, c_to_s->mutex.get()); |
| |
| OneWayTunnel::SetupTwoWayTunnel(c_to_s, s_to_c); |
| |
| buf = nullptr; // do not free buf. Tunnel will do that. |
| state = ALL_DONE; |
| break; |
| } |
| |
| case NET_EVENT_OPEN_FAILED: |
| pending_action = nullptr; |
| sendResp(false); |
| state = RESP_TO_CLIENT; |
| Debug("SocksProxy", "open to Socks server failed"); |
| break; |
| |
| case EVENT_INTERVAL: |
| timeout = nullptr; |
| Debug("SocksProxy", "SocksProxy timeout, state = %d", state); |
| state = SOCKS_ERROR; |
| break; |
| |
| case VC_EVENT_ERROR: |
| case VC_EVENT_INACTIVITY_TIMEOUT: |
| case VC_EVENT_ACTIVE_TIMEOUT: |
| case VC_EVENT_EOS: |
| Debug("SocksProxy", "VC_EVENT (state: %d error: %s)", state, get_vc_event_name(event)); |
| state = SOCKS_ERROR; |
| break; |
| |
| default: |
| ink_assert(!"bad case value\n"); |
| state = SOCKS_ERROR; |
| } |
| |
| if (state == SOCKS_ERROR) { |
| if (pending_action) { |
| pending_action->cancel(); |
| } |
| |
| if (timeout) { |
| timeout->cancel(this); |
| } |
| |
| if (clientVC) { |
| Debug("SocksProxy", "Closing clientVC on error"); |
| clientVC->do_io_close(); |
| clientVC = nullptr; |
| } |
| |
| state = ALL_DONE; |
| } |
| |
| recursion--; |
| |
| if (state == ALL_DONE && recursion == 0) { |
| free(); |
| } |
| |
| return ret; |
| } |
| |
| int |
| SocksProxy::sendResp(bool granted) |
| { |
| int n_bytes; |
| |
| // In SOCKS 4, IP addr and Dest Port fields are ignored. |
| // In SOCKS 5, IP addr and Dest Port are the ones we use to connect to the |
| // real host. In our case, it does not make sense, since we may not |
| // connect at all. Set these feilds to zeros. Any socks client which uses |
| // these breaks caching. |
| |
| buf->reset(); |
| unsigned char *p = (unsigned char *)buf->start(); |
| |
| if (version == SOCKS4_VERSION) { |
| p[0] = 0; |
| p[1] = (granted) ? SOCKS4_REQ_GRANTED : SOCKS4_CONN_FAILED; |
| n_bytes = 8; |
| } else { |
| p[0] = SOCKS5_VERSION; |
| p[1] = (granted) ? SOCKS5_REQ_GRANTED : SOCKS5_CONN_FAILED; |
| p[2] = 0; |
| p[3] = SOCKS_ATYPE_IPV4; |
| p[4] = p[5] = p[6] = p[7] = p[8] = p[9] = 0; |
| n_bytes = 10; |
| } |
| |
| buf->fill(n_bytes); |
| clientVC->do_io_write(this, n_bytes, reader, false); |
| |
| return n_bytes; |
| } |
| |
| int |
| SocksProxy::setupHttpRequest(unsigned char *p) |
| { |
| int ret = EVENT_DONE; |
| |
| SocksAddrType *a = &clientVC->socks_addr; |
| |
| // read the ip addr buf |
| // In both SOCKS4 and SOCKS5 addr starts after 4 octets |
| switch (a->type) { |
| case SOCKS_ATYPE_IPV4: |
| a->addr.ipv4[0] = p[4]; |
| a->addr.ipv4[1] = p[5]; |
| a->addr.ipv4[2] = p[6]; |
| a->addr.ipv4[3] = p[7]; |
| break; |
| |
| case SOCKS_ATYPE_FQHN: |
| // This is stored as a zero terminicated string |
| a->addr.buf = (unsigned char *)ats_malloc(p[4] + 1); |
| memcpy(a->addr.buf, &p[5], p[4]); |
| a->addr.buf[p[4]] = 0; |
| break; |
| case SOCKS_ATYPE_IPV6: |
| // a->addr.buf = (unsigned char *)ats_malloc(16); |
| // memcpy(a->addr.buf, &p[4], 16); |
| // dont think we will use "proper" IPv6 addr anytime soon. |
| // just use the last 4 octets as IPv4 addr: |
| a->type = SOCKS_ATYPE_IPV4; |
| a->addr.ipv4[0] = p[16]; |
| a->addr.ipv4[1] = p[17]; |
| a->addr.ipv4[2] = p[18]; |
| a->addr.ipv4[3] = p[19]; |
| |
| break; |
| default: |
| ink_assert(!"bad case value"); |
| } |
| |
| return ret; |
| } |
| |
| static void |
| new_SocksProxy(NetVConnection *netVC) |
| { |
| SocksProxy *proxy = socksProxyAllocator.alloc(); |
| proxy->init(netVC); |
| } |
| |
| struct SocksAccepter : public Continuation { |
| using SocksAccepterHandler = int (SocksAccepter::*)(int, void *); |
| |
| int |
| mainEvent(int event, NetVConnection *netVC) |
| { |
| ink_assert(event == NET_EVENT_ACCEPT); |
| // Debug("Socks", "Accepter got ACCEPT event"); |
| |
| new_SocksProxy(netVC); |
| |
| return EVENT_CONT; |
| } |
| |
| // There is no state used we dont need a mutex |
| SocksAccepter() : Continuation(nullptr) { SET_HANDLER((SocksAccepterHandler)&SocksAccepter::mainEvent); } |
| }; |
| |
| void |
| start_SocksProxy(int port) |
| { |
| Debug("SocksProxy", "Accepting SocksProxy connections on port %d", port); |
| NetProcessor::AcceptOptions opt; |
| opt.local_port = port; |
| netProcessor.main_accept(new SocksAccepter(), NO_FD, opt); |
| |
| socksproxy_stat_block = RecAllocateRawStatBlock(socksproxy_stat_count); |
| |
| if (socksproxy_stat_block) { |
| RecRegisterRawStat(socksproxy_stat_block, RECT_PROCESS, "proxy.process.socks.proxy.http_connections", RECD_INT, RECP_PERSISTENT, |
| socksproxy_http_connections_stat, RecRawStatSyncCount); |
| |
| RecRegisterRawStat(socksproxy_stat_block, RECT_PROCESS, "proxy.process.socks.proxy.tunneled_connections", RECD_INT, |
| RECP_PERSISTENT, socksproxy_tunneled_connections_stat, RecRawStatSyncCount); |
| } |
| } |
| |
| int |
| socks5ServerAuthHandler(int event, unsigned char *p, void (**h_ptr)(void)) |
| { |
| int ret = 0; |
| |
| switch (event) { |
| case SOCKS_AUTH_READ_COMPLETE: |
| |
| ink_assert(p[0] == SOCKS5_VERSION); |
| Debug("SocksProxy", "Socks read initial auth info"); |
| // do nothing |
| break; |
| |
| case SOCKS_AUTH_FILL_WRITE_BUF: |
| Debug("SocksProxy", "No authentication is required"); |
| p[0] = SOCKS5_VERSION; |
| p[1] = 0; // no authentication necessary |
| ret = 2; |
| // FALLTHROUGH |
| case SOCKS_AUTH_WRITE_COMPLETE: |
| // nothing to do |
| *h_ptr = nullptr; |
| break; |
| |
| default: |
| ink_assert(!"bad case value"); |
| ret = -1; |
| } |
| |
| return ret; |
| } |