Example #1
0
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())))
Example #2
0
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)
Example #3
0
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)