class MeekSession(object): def __init__(self, sessionid, socksip, socksport, timeout, sessionmap): self.sessionid = sessionid self.socksip = socksip self.socksport = socksport self.timeout = timeout self.sessionmap = sessionmap self.sessionmap[self.sessionid] = self self.udpsock = None self.udp_associate = None self.socksconn = None self.allsocks = [] self.status = SESSION_WAIT_INIT self.initialized = False self.in_queue = Queue() self.in_notifier = Event() self.in_notifier.clear() self.out_queue = Queue() self.timer = SharedTimer(self.timeout) self.finish = Event() self.finish.clear() self.threads = [] def meeks_clean_thread(self): while not self.finish.is_set(): gevent.sleep(SERVER_TURNAROUND_MAX) [t.join() for t in self.threads] self.clean() def write_to_socks(self, data): if self.udpsock: self.udpsock.sendto(data, self.udp_associate) else: self.socksconn.sendall(data) def meeks_write_to_socks_thread(self): while not self.finish.is_set(): try: hasdata = self.in_notifier.wait(timeout=CLIENT_MAX_POLL_INTERVAL) self.in_notifier.clear() if not hasdata: self.timer.count(CLIENT_MAX_POLL_INTERVAL) if self.timer.timeout(): break self.timer.reset() while not self.in_queue.empty(): data = self.in_queue.get() log.debug("%s: RELAY-UP %d bytes" % (self.sessionid, len(data))) self.write_to_socks(data) except Exception as ex: log.error("[Exception][meeks_write_to_socks_thread] %s: %s" % (self.sessionid, str(ex))) break self.finish.set() def meeks_read_from_socks_thread(self): while not self.finish.is_set(): try: readable, _, _ = select.select(self.allsocks, [], [], CLIENT_MAX_POLL_INTERVAL) if not readable: self.timer.count(CLIENT_MAX_POLL_INTERVAL) if self.timer.timeout(): break else: self.timer.reset() if self.socksconn in readable: if self.udpsock: raise RelaySessionError("unexcepted read-event from tcp socket in UDP session") data = self.socksconn.recv(MAX_PAYLOAD_LENGTH) if not data: raise RelaySessionError("peer closed") self.out_queue.put(data) continue if self.udpsock and self.udpsock in readable: data, _ = self.udpsock.recvfrom(MAX_PAYLOAD_LENGTH) if data: self.out_queue.put(data) except Exception as ex: log.error("[Exception][meeks_read_from_socks_thread] %s:%s" % (self.sessionid, str(ex))) break self.finish.set() def initialize(self): self.socksconn = socket.create_connection((self.socksip, self.socksport), self.timeout) self.allsocks = [self.socksconn] self.socksconn.sendall(InitRequest().pack()) read_init_reply(self.socksconn) self.status = SESSION_WAIT_REQUEST self.initialized = True def cmd_connect(self, req): self.socksconn.sendall(req.pack()) reply = read_reply(self.socksconn) resp = reply.pack() headers = [ (HEADER_SESSION_ID, self.sessionid), (HEADER_MSGTYPE, MSGTYPE_DATA) ] self.threads.append(gevent.spawn(self.meeks_write_to_socks_thread)) self.threads.append(gevent.spawn(self.meeks_read_from_socks_thread)) # clean_thread will join the other two threads, then clean resources gevent.spawn(self.meeks_clean_thread) self.status = SESSION_TCP return resp, headers def cmd_udp_associate(self, req): self.udpsock = bind_local_udp(self.socksconn) self.allsocks.append(self.udpsock) addrtype, ip, port = sock_addr_info(self.udpsock) self.socksconn.sendall(Request(cmd=UDP_ASSOCIATE, addrtype=addrtype, dstaddr=ip, dstport=port).pack()) reply = read_reply(self.socksconn) resp = reply.pack() headers = [ (HEADER_SESSION_ID, self.sessionid), (HEADER_MSGTYPE, MSGTYPE_DATA) ] self.udp_associate = (reply.bndaddr, reply.bndport) self.threads.append(gevent.spawn(self.meeks_write_to_socks_thread)) self.threads.append(gevent.spawn(self.meeks_read_from_socks_thread)) # clean_thread will join the other two threads, then clean resources gevent.spawn(self.meeks_clean_thread) self.status = SESSION_UDP return resp, headers def cmd_bind(self, req): resp = "" headers = [ (HEADER_SESSION_ID, self.sessionid), (HEADER_ERROR, "Not Supported") ] return resp, headers def sync_socks_request(self, data, env): req = Request() req.unpack(data) return { CONNECT: self.cmd_connect, BIND: self.cmd_bind, UDP_ASSOCIATE : self.cmd_udp_associate }[req.cmd](req) def _fetch_resp(self): data = [] totalsize = 0 while True: while not self.out_queue.empty() and totalsize < MAX_PAYLOAD_LENGTH: pkt = self.out_queue.get() data.append(pkt) totalsize += len(pkt) if data: return data, totalsize else: try: self.out_queue.peek(block=True, timeout=SERVER_TURNAROUND_TIMEOUT) except Empty: break return data, totalsize def fetch_resp(self): data, _ = self._fetch_resp() resp = "".join(data) headers = [ (HEADER_SESSION_ID, self.sessionid), (HEADER_MSGTYPE, MSGTYPE_DATA), ] if self.status == SESSION_UDP and data: headers.append((HEADER_UDP_PKTS, ",".join([str(len(d)) for d in data]))) return resp, headers def process_tcp(self, data, env): if data: self.in_queue.put(data) self.in_notifier.set() return self.fetch_resp() def process_udp(self, data, env): if data: lengths = env[header_to_env(HEADER_UDP_PKTS)].split(",") pos = 0 for length in lengths: nxt = pos + int(length) self.in_queue.put(data[pos:nxt]) pos = nxt self.in_notifier.set() return self.fetch_resp() def process(self, data, env): if not self.initialized: self.initialize() return { SESSION_WAIT_REQUEST: self.sync_socks_request, SESSION_TCP: self.process_tcp, SESSION_UDP: self.process_udp, }[self.status](data, env) def alive(self): return not self.finish.is_set() def clean(self): self.finish.set() for sock in self.allsocks: sock.close() self.in_queue.queue.clear() self.out_queue.queue.clear() if self.sessionid in self.sessionmap: del self.sessionmap[self.sessionid] log.info("%s: quit, %d sessions left" % (self.sessionid, len(self.sessionmap.keys())))
class MeekSession(RelaySession): conn_pool = HTTPClientPool() def __init__(self, socksconn, meek, timeout): super(MeekSession, self).__init__(socksconn) self.sessionid = session_id() self.meek = meek self.meektimeout = timeout self.relay = self.meek.select_relay() self.ca_certs = self.meek.ca_certs self.httpclient = self.conn_pool.get(self.relay, self.ca_certs, self.meektimeout) self.udpsock = None self.allsocks = [self.socksconn] self.l2m_queue = Queue() self.m2l_queue = Queue() self.m_notifier = Event() self.l_notifier = Event() self.finish = Event() self.m_notifier.clear() self.l_notifier.clear() self.finish.clear() self.timer = SharedTimer(self.meektimeout) def _stream_response(self, response): try: chunk = response.read(MAX_PAYLOAD_LENGTH) while chunk: log.debug("%s streaming DOWN %d bytes" % (self.sessionid, len(chunk))) yield chunk, "" chunk = response.read(MAX_PAYLOAD_LENGTH) except GeneratorExit: response.release() raise StopIteration def meek_response(self, response, stream): if stream: return self._stream_response(response) data = response.read() response.release() if not data: return [("", "")] if not self.udpsock: return [(data, "")] # parse UDP packets log.debug("%s DOWN %d bytes" % (self.sessionid, len(data))) lengths = get_meek_meta(response.headers, HEADER_UDP_PKTS).split(",") pos = 0 pkts = [] for length in lengths: nxt = pos + int(length) pkts.append((data[pos:nxt], "")) pos = nxt return pkts def meek_roundtrip(self, pkts): headers = { HEADER_SESSION_ID: self.sessionid, HEADER_MSGTYPE: MSGTYPE_DATA, 'Host': self.relay.hostname, 'Content-Type': "application/octet-stream", 'Connection': "Keep-Alive", } stream = False if not self.udpsock and "stream" in self.relay.properties: stream = True headers[HEADER_MODE] = MODE_STREAM if pkts and self.udpsock: lengths = str(",".join([str(len(p)) for p in pkts])) headers[HEADER_UDP_PKTS] = lengths data = "".join(pkts) headers['Content-Length'] = str(len(data)) for _ in range(CLIENT_MAX_TRIES): try: log.debug("%s UP %d bytes" % (self.sessionid, len(data))) resp = self.httpclient.post("/", body=data, headers=headers) if resp.status_code != 200: # meek server always give 200, so all non-200s mean external issues. continue err = get_meek_meta(resp.headers, HEADER_ERROR) if err: return [("", err)] else: try: return self.meek_response(resp, stream) except Exception as ex: log.error("[Exception][meek_roundtrip - meek_response]: %s" % str(ex)) resp.release() return [("", "Data Format Error")] except socket.timeout: # @UndefinedVariable return [("", "timeout")] except Exception as ex: log.error("[Exception][meek_roundtrip]: %s" % str(ex)) gevent.sleep(CLIENT_RETRY_DELAY) self.relay.failure += 1 return [("", "Max Retry (%d) Exceeded" % CLIENT_MAX_TRIES)] def meek_sendrecv(self): pkts = [] datalen = 0 while not self.l2m_queue.empty(): pkt = self.l2m_queue.get() pkts.append(pkt) datalen += len(pkt) if datalen >= MAX_PAYLOAD_LENGTH: for (resp, err) in self.meek_roundtrip(pkts): yield (resp, err) if err or not resp: return pkts = [] datalen = 0 for (resp, err) in self.meek_roundtrip(pkts): yield (resp, err) if err or not resp: return def meek_relay(self): for (resp, err) in self.meek_sendrecv(): if err: return err if resp: self.m2l_queue.put(resp) self.l_notifier.set() return "" def meek_relay_thread(self): interval = CLIENT_INITIAL_POLL_INTERVAL while not self.finish.is_set(): try: hasdata = self.m_notifier.wait(timeout=interval) self.m_notifier.clear() err = self.meek_relay() if err: break if not hasdata: interval *= CLIENT_POLL_INTERVAL_MULTIPLIER if interval > CLIENT_MAX_POLL_INTERVAL: interval = CLIENT_MAX_POLL_INTERVAL except Exception as ex: log.error("[Exception][meek_relay_thread]: %s" % str(ex)) break self.finish.set() def write_to_client(self, data): if self.udpsock: self.udpsock.sendto(data, self.last_clientaddr) else: self.socksconn.sendall(data) def meek_write_to_client_thread(self): while not self.finish.is_set(): try: hasdata = self.l_notifier.wait(timeout=CLIENT_MAX_POLL_INTERVAL) self.l_notifier.clear() if not hasdata: self.timer.count(CLIENT_MAX_POLL_INTERVAL) if self.timer.timeout(): break else: self.timer.reset() while not self.m2l_queue.empty(): data = self.m2l_queue.get() if data: self.write_to_client(data) except Exception as ex: log.error("[Exception][meek_write_to_client_thread]: %s" % str(ex)) break self.finish.set() def read_from_client(self, timeout): readable, _, _ = select.select(self.allsocks, [], [], CLIENT_MAX_POLL_INTERVAL) if not readable: return None if self.socksconn in readable: if self.udpsock: raise RelaySessionError("unexcepted read-event from tcp socket in UDP session") data = self.socksconn.recv(MAX_PAYLOAD_LENGTH) if not data: raise RelaySessionError("peer closed") return data if self.udpsock and self.udpsock in readable: data, addr = self.udpsock.recvfrom(MAX_PAYLOAD_LENGTH) if not self.valid_udp_client(addr): return None else: self.last_clientaddr = addr return data def meek_read_from_client_thread(self): while not self.finish.is_set(): try: data = self.read_from_client(CLIENT_MAX_POLL_INTERVAL) if not data: self.timer.count(CLIENT_MAX_POLL_INTERVAL) if self.timer.timeout(): break else: self.timer.reset() self.l2m_queue.put(data) self.m_notifier.set() except Exception as ex: log.error("[Exception][meek_read_from_client_thread]: %s" % str(ex)) break self.finish.set() def proc_tcp_request(self, req): self.l2m_queue.put(req.pack()) def relay_tcp(self): read_thread = gevent.spawn(self.meek_read_from_client_thread) write_thread = gevent.spawn(self.meek_write_to_client_thread) relay_thread = gevent.spawn(self.meek_relay_thread) # notify relay to send request self.m_notifier.set() [t.join() for t in (read_thread, write_thread, relay_thread)] log.info("Session %s Ended" % self.sessionid) def valid_udp_client(self, addr): if self.client_associate[0] == "0.0.0.0" or \ self.client_associate[0] == "::": return True if self.client_associate == addr: return True return False def cmd_udp_associate(self, req): self.client_associate = (req.dstaddr, req.dstport) self.last_clientaddr = self.client_associate for (resp, err) in self.meek_roundtrip([req.pack()]): if err: return if resp: Reply(resp) self.udpsock = bind_local_udp(self.socksconn) if not self.udpsock: request_fail(self.socksconn, req, GENERAL_SOCKS_SERVER_FAILURE) return self.track_sock(self.udpsock) read_thread = gevent.spawn(self.meek_read_from_client_thread) write_thread = gevent.spawn(self.meek_write_to_client_thread) relay_thread = gevent.spawn(self.meek_relay_thread) request_success(self.socksconn, *sock_addr_info(self.udpsock)) [t.join() for t in (read_thread, write_thread, relay_thread)] log.info("Session %s Ended" % self.sessionid) def meek_terminate(self): headers = { HEADER_SESSION_ID: self.sessionid, HEADER_MSGTYPE: MSGTYPE_TERMINATE, #'Content-Type': "application/octet-stream", 'Content-Length': "0", 'Connection': "Keep-Alive", 'Host': self.relay.hostname, } try: self.httpclient.post("/", data="", headers=headers) except: pass def clean(self): self.meek_terminate() for sock in self.allsocks: sock.close() #self.httpclient.close() self.conn_pool.release(self.relay, self.httpclient)
class MeekSession(RelaySession): conn_pool = HTTPClientPool() def __init__(self, socksconn, meek, timeout): super(MeekSession, self).__init__(socksconn) self.sessionid = session_id() self.meek = meek self.meektimeout = timeout self.relay = self.meek.select_relay() self.ca_certs = self.meek.ca_certs self.httpclient = self.conn_pool.get(self.relay, self.ca_certs, self.meektimeout) self.udpsock = None self.allsocks = [self.socksconn] self.l2m_queue = Queue() self.m2l_queue = Queue() self.m_notifier = Event() self.l_notifier = Event() self.finish = Event() self.m_notifier.clear() self.l_notifier.clear() self.finish.clear() self.timer = SharedTimer(self.meektimeout) def _stream_response(self, response): try: chunk = response.read(MAX_PAYLOAD_LENGTH) while chunk: log.debug("%s streaming DOWN %d bytes" % (self.sessionid, len(chunk))) yield chunk, "" chunk = response.read(MAX_PAYLOAD_LENGTH) except GeneratorExit: response.release() raise StopIteration def meek_response(self, response, stream): if stream: return self._stream_response(response) data = response.read() response.release() if not data: return [("", "")] if not self.udpsock: return [(data, "")] # parse UDP packets log.debug("%s DOWN %d bytes" % (self.sessionid, len(data))) lengths = get_meek_meta(response.headers, HEADER_UDP_PKTS).split(",") pos = 0 pkts = [] for length in lengths: nxt = pos + int(length) pkts.append((data[pos:nxt], "")) pos = nxt return pkts def meek_roundtrip(self, pkts): headers = { HEADER_SESSION_ID: self.sessionid, HEADER_MSGTYPE: MSGTYPE_DATA, 'Host': self.relay.hostname, 'Content-Type': "application/octet-stream", 'Connection': "Keep-Alive", } stream = False if not self.udpsock and "stream" in self.relay.properties: stream = True headers[HEADER_MODE] = MODE_STREAM if pkts and self.udpsock: lengths = str(",".join([str(len(p)) for p in pkts])) headers[HEADER_UDP_PKTS] = lengths data = "".join(pkts) headers['Content-Length'] = str(len(data)) for _ in range(CLIENT_MAX_TRIES): try: log.debug("%s UP %d bytes" % (self.sessionid, len(data))) resp = self.httpclient.post("/", body=data, headers=headers) if resp.status_code != 200: # meek server always give 200, so all non-200s mean external issues. continue err = get_meek_meta(resp.headers, HEADER_ERROR) if err: return [("", err)] else: try: return self.meek_response(resp, stream) except Exception as ex: log.error( "[Exception][meek_roundtrip - meek_response]: %s" % str(ex)) resp.release() return [("", "Data Format Error")] except socket.timeout: # @UndefinedVariable return [("", "timeout")] except Exception as ex: log.error("[Exception][meek_roundtrip]: %s" % str(ex)) gevent.sleep(CLIENT_RETRY_DELAY) self.relay.failure += 1 return [("", "Max Retry (%d) Exceeded" % CLIENT_MAX_TRIES)] def meek_sendrecv(self): pkts = [] datalen = 0 while not self.l2m_queue.empty(): pkt = self.l2m_queue.get() pkts.append(pkt) datalen += len(pkt) if datalen >= MAX_PAYLOAD_LENGTH: for (resp, err) in self.meek_roundtrip(pkts): yield (resp, err) if err or not resp: return pkts = [] datalen = 0 for (resp, err) in self.meek_roundtrip(pkts): yield (resp, err) if err or not resp: return def meek_relay(self): for (resp, err) in self.meek_sendrecv(): if err: return err if resp: self.m2l_queue.put(resp) self.l_notifier.set() return "" def meek_relay_thread(self): interval = CLIENT_INITIAL_POLL_INTERVAL while not self.finish.is_set(): try: hasdata = self.m_notifier.wait(timeout=interval) self.m_notifier.clear() err = self.meek_relay() if err: break if not hasdata: interval *= CLIENT_POLL_INTERVAL_MULTIPLIER if interval > CLIENT_MAX_POLL_INTERVAL: interval = CLIENT_MAX_POLL_INTERVAL except Exception as ex: log.error("[Exception][meek_relay_thread]: %s" % str(ex)) break self.finish.set() def write_to_client(self, data): if self.udpsock: self.udpsock.sendto(data, self.last_clientaddr) else: self.socksconn.sendall(data) def meek_write_to_client_thread(self): while not self.finish.is_set(): try: hasdata = self.l_notifier.wait( timeout=CLIENT_MAX_POLL_INTERVAL) self.l_notifier.clear() if not hasdata: self.timer.count(CLIENT_MAX_POLL_INTERVAL) if self.timer.timeout(): break else: self.timer.reset() while not self.m2l_queue.empty(): data = self.m2l_queue.get() if data: self.write_to_client(data) except Exception as ex: log.error("[Exception][meek_write_to_client_thread]: %s" % str(ex)) break self.finish.set() def read_from_client(self, timeout): readable, _, _ = select.select(self.allsocks, [], [], CLIENT_MAX_POLL_INTERVAL) if not readable: return None if self.socksconn in readable: if self.udpsock: raise RelaySessionError( "unexcepted read-event from tcp socket in UDP session") data = self.socksconn.recv(MAX_PAYLOAD_LENGTH) if not data: raise RelaySessionError("peer closed") return data if self.udpsock and self.udpsock in readable: data, addr = self.udpsock.recvfrom(MAX_PAYLOAD_LENGTH) if not self.valid_udp_client(addr): return None else: self.last_clientaddr = addr return data def meek_read_from_client_thread(self): while not self.finish.is_set(): try: data = self.read_from_client(CLIENT_MAX_POLL_INTERVAL) if not data: self.timer.count(CLIENT_MAX_POLL_INTERVAL) if self.timer.timeout(): break else: self.timer.reset() self.l2m_queue.put(data) self.m_notifier.set() except Exception as ex: log.error("[Exception][meek_read_from_client_thread]: %s" % str(ex)) break self.finish.set() def proc_tcp_request(self, req): self.l2m_queue.put(req.pack()) def relay_tcp(self): read_thread = gevent.spawn(self.meek_read_from_client_thread) write_thread = gevent.spawn(self.meek_write_to_client_thread) relay_thread = gevent.spawn(self.meek_relay_thread) # notify relay to send request self.m_notifier.set() [t.join() for t in (read_thread, write_thread, relay_thread)] log.info("Session %s Ended" % self.sessionid) def valid_udp_client(self, addr): if self.client_associate[0] == "0.0.0.0" or \ self.client_associate[0] == "::": return True if self.client_associate == addr: return True return False def cmd_udp_associate(self, req): self.client_associate = (req.dstaddr, req.dstport) self.last_clientaddr = self.client_associate for (resp, err) in self.meek_roundtrip([req.pack()]): if err: return if resp: Reply(resp) self.udpsock = bind_local_udp(self.socksconn) if not self.udpsock: request_fail(self.socksconn, req, GENERAL_SOCKS_SERVER_FAILURE) return self.track_sock(self.udpsock) read_thread = gevent.spawn(self.meek_read_from_client_thread) write_thread = gevent.spawn(self.meek_write_to_client_thread) relay_thread = gevent.spawn(self.meek_relay_thread) request_success(self.socksconn, *sock_addr_info(self.udpsock)) [t.join() for t in (read_thread, write_thread, relay_thread)] log.info("Session %s Ended" % self.sessionid) def meek_terminate(self): headers = { HEADER_SESSION_ID: self.sessionid, HEADER_MSGTYPE: MSGTYPE_TERMINATE, #'Content-Type': "application/octet-stream", 'Content-Length': "0", 'Connection': "Keep-Alive", 'Host': self.relay.hostname, } try: self.httpclient.post("/", data="", headers=headers) except: pass def clean(self): self.meek_terminate() for sock in self.allsocks: sock.close() #self.httpclient.close() self.conn_pool.release(self.relay, self.httpclient)