简易HTTP 隧道技术

之前用钉钉的内网穿透服务,觉得很不错。但可惜只支持http协议。我想把内网主机的ssh映射到公网上。很可惜失败了。但比较奇怪的是在本地运行没什么问题。ssh流量可以封装成http数据包。到另外一边也可以成功拆包。但是通过钉钉的服务器转发后就有问题。一般刚开始可以成功登录ssh,并且也比较流畅,但打几个命令后就卡死了。刚开始觉得是粘包的问题,后来分析原因后觉得可能http协议是一个请求一个响应。应该是请求响应对不上的问题,用了点小技巧把两边的请求响应数量补齐成一样后,比之前稍微好一点,但还是会卡死。最后我觉的应该是TCP连接的问题。http连接和tcp连接没有一一对应的关系。最终我放弃了。选择用Python直接在服务器上直接做个端口转发。项目地址

总之之前的技术难用,还麻烦。下面介绍个实现简易HTTP隧道的方法。

HTTP代理原理

通过HTTP协议与代理服务器建立连接,协议信令中包含要连接到的远程主机的IP和端口号,如果有需要身份验证的话还需要加上授权信息,服务器收到信令后首先进行身份验证,通过后便与远程主机建立连接,连接成功之后会返回给客户端200,表示验证通过,HTTP tunnel是HTTP/1.1中引入的一个功能,主要为了解决明文的HTTP proxy无法代理跑在TLS中的流量(也就是https)的问题,同时提供了作为任意流量的TCP通道的能力。

CONNECT xxx.xxx.xxx.xx:443 HTTP/1.1 //建立http隧道要443端口
Proxy-Connection: Keep-Alive     //客户端到服务器端的连接持续有效
Content-Length: 0
Host: xxx.xxx.xxx.xx         //主机地址
Proxy-Authorization:Basic YTph    //身份验证信息
User-Agent: OpenFetion         //可以标识请求者的信息,如什么浏览器类型和版本、操作系统、使用语言等信息

下面是实现代码

import socket
from threading import Thread

class Header:
    """
    用于读取和解析头信息
    """

    def __init__(self, conn):
        self._method = None
        self._data = b''
        try:
            while 1:
                data = conn.recv(4096)
                self._data = b"%s%s" % (self._data, data)
                if self._data.find(b'\r\n\r\n') or (not data):
                    break
        except:
            pass
        self._header = self._data.split(b'\r\n\r\n')[0]
        self.header_list = self._header.split(b'\r\n')
        self._host = None
        self._port = None

    def get_method(self):
        """
        获取请求方式
        :return:
        """
        if self._method is None:
            self._method = self._header[:self._header.index(b' ')]
        return self._method

    def get_host_info(self):
        """
        获取目标主机的ip和端口
        :return:
        """
        if self._host is None:
            method = self.get_method()
            line = self.header_list[0].decode('utf8')
            if method == b"CONNECT":
                host = line.split(' ')[1]
                if ':' in host:
                    host, port = host.split(':')
                else:
                    port = 443
            else:
                for i in self.header_list:
                    if i.startswith(b"Host:"):
                        host = i.split(b" ")
                        if len(host) < 2:
                            continue
                        host = host[1].decode('utf8')
                        break
                else:
                    host = line.split('/')[2]
                if ':' in host:
                    host, port = host.split(':')
                else:
                    port = 80
            self._host = host
            self._port = int(port)
        return self._host, self._port

    @property
    def data(self):
        """
        返回数据
        :return:
        """
        return self._data

    def is_ssl(self):
        """
        判断是否为 https协议
        :return:
        """
        if self.get_method() == b'CONNECT':
            return True
        return False

    def __repr__(self):
        return str(self._header.decode("utf8"))

def communicate(sock1, sock2):
    """
    socket之间的数据交换
    :param sock1:
    :param sock2:
    :return:
    """
    try:
        while 1:
            data = sock1.recv(1024)
            if not data:
                sock2.close()
                return
            sock2.sendall(data)
    except:
        pass

def handle(client,addr):
    """
    处理连接进来的客户端
    :param client:
    :return:
    """
    timeout = 3
    client.settimeout(timeout)
    header = Header(client)
    if not header.data:
        client.close()
        return
    print(*header.get_host_info(), header.get_method())
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        server.connect(header.get_host_info())
        server.settimeout(timeout)
        if header.is_ssl():
            data = b"HTTP/1.0 200 Connection Established\r\n\r\n"
            client.sendall(data)
        else:
            server.sendall(header.data)
        Thread(name=f'B {
      
      addr}',target=communicate, args=(client, server)).start()
        communicate(server, client)
    except:
        server.close()
        client.close()

def serve(ip, port):
    """
    代理服务
    :param ip:
    :param port:
    :return:
    """
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind((ip, port))
    s.listen(10)
    print('proxy start...')
    while True:
        conn, addr = s.accept()
        Thread(name=f'A {
      
      addr}',target=handle, args=(conn,addr)).start()


if __name__ == '__main__':
    IP = "127.0.0.1"
    PORT = 5000
    serve(IP, PORT)

猜你喜欢

转载自blog.csdn.net/qq_45256489/article/details/125381280