├── lib ├── __pycache__ │ └── ConnTool.cpython-36.pyc └── ConnTool.py ├── readme.md ├── frpc.py └── frps.py /lib/__pycache__/ConnTool.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usualheart/frp-python/HEAD/lib/__pycache__/ConnTool.cpython-36.pyc -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # frp-python 2 | 3 | > frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。 4 | 5 | frp-python是基于frp原理实现的轻量级python版frp,frp-python具有非常简洁的设计,在速度方面甚至优于frp。如果你愿意,只需稍加修改frpc的代码,甚至可以在esp-32上部署一个frpc客户端! 6 | 7 | 关于frp原理,可以参考[内网穿透工具frp核心架构原理分析](https://blog.csdn.net/usualheart/article/details/123032372) 8 | 9 | ### frp-python 使用方法示例 10 | 11 | 假设服务端ip是110.110.110.1,要穿透本机的远程桌面端口3389,则分别在服务端和本机如下启动: 12 | 13 | **服务端** 14 | 15 | ```shell 16 | frps.py 7000 8001 17 | ``` 18 | 19 | **客户端** 20 | 21 | ``` 22 | frpc.py 110.110.110.1 7000 localhost 3389 23 | ``` 24 | 25 | 然后通过110.110.110.1:8001就可以远程访问本机了! -------------------------------------------------------------------------------- /lib/ConnTool.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # tcp mapping 3 | 4 | import sys 5 | import socket 6 | import logging 7 | import threading 8 | 9 | # 接收数据缓存大小 10 | PKT_BUFF_SIZE = 10240 11 | 12 | logger = logging.getLogger("Proxy Logging") 13 | formatter = logging.Formatter('%(name)-12s %(asctime)s %(levelname)-8s %(lineno)-4d %(message)s', '%Y %b %d %a %H:%M:%S',) 14 | 15 | stream_handler = logging.StreamHandler(sys.stderr) 16 | stream_handler.setFormatter(formatter) 17 | logger.addHandler(stream_handler) 18 | logger.setLevel(logging.DEBUG) 19 | 20 | # 单向流数据传递 21 | def tcp_mapping_worker(conn_receiver, conn_sender): 22 | logger.info("start ") 23 | while True: 24 | try: 25 | data = conn_receiver.recv(PKT_BUFF_SIZE) 26 | except Exception as e: 27 | logger.debug(e) 28 | logger.debug('Connection closed.') 29 | break 30 | 31 | if not data: 32 | logger.info('No more data is received.') # 正常情况下不会到这,一般是某个连接断了 33 | break 34 | 35 | try: 36 | conn_sender.sendall(data) 37 | except Exception: 38 | logger.error('Failed sending data.') 39 | break 40 | 41 | # logger.info('Mapping data > %s ' % repr(data)) 42 | # logger.info('Mapping > %s -> %s > %d bytes.' % (conn_receiver.getpeername(), conn_sender.getpeername(), len(data))) 43 | 44 | conn_receiver.close() 45 | conn_sender.close() 46 | 47 | return 48 | 49 | 50 | # 端口映射请求处理 51 | def join(connA, connB): 52 | threading.Thread(target=tcp_mapping_worker, args=(connA, connB)).start() 53 | threading.Thread(target=tcp_mapping_worker, args=(connB, connA)).start() 54 | return 55 | 56 | if __name__ == '__main__': 57 | # 测试 58 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 59 | sock.bind(('0.0.0.0', 8080)) 60 | # sock.setblocking(False) 61 | sock.listen(100) 62 | conn1,addr1 =sock.accept() 63 | 64 | sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 65 | sock2.bind(('0.0.0.0', 7000)) 66 | sock2.listen(100) 67 | 68 | # 类似frpc 69 | server_conn = socket.create_connection(('localhost', 7000)) 70 | 71 | conn2,addr2 =sock2.accept() 72 | join(conn1,conn2) 73 | 74 | target_conn = socket.create_connection(('192.168.1.101', 3389)) 75 | join(server_conn,target_conn) 76 | -------------------------------------------------------------------------------- /frpc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys, socket, time, threading 3 | import struct 4 | import selectors 5 | import lib.ConnTool as ConnTool 6 | sel = selectors.DefaultSelector() 7 | 8 | 9 | 10 | 11 | class Frpc(): 12 | def __init__(self, serverhost,serverport, targethost,targetport): 13 | self.targethost=targethost 14 | self.targetport=targetport 15 | self.serverhost=serverhost 16 | self.serverport=serverport 17 | self.server_fd = socket.create_connection((self.serverhost,self.serverport)) 18 | self.server_fd.sendall(struct.pack('i', 1)) # 启动时 首次发送心跳包 1 19 | self.server_fd.setblocking(False) 20 | sel.register(self.server_fd,selectors.EVENT_READ,self.handle_controller_data) 21 | 22 | self.workConnPool = [] 23 | threading.Thread(target=self.maitainConPool).start() 24 | threading.Thread(target=self.heartbeat).start() 25 | 26 | def heartbeat(self): 27 | while True: 28 | if self.server_fd is not None: 29 | self.server_fd.send(struct.pack('i', 1)) 30 | time.sleep(9) 31 | 32 | # 提前把 workConn和targetConn连接起来,需要的时候直接用workConn targetConn不需要缓存 33 | def maitainConPool(self): 34 | print("启动tcp连接池") 35 | pool_size = 0 36 | while True: 37 | if len(self.workConnPool)0: 54 | workConn = self.workConnPool.pop() 55 | else: 56 | targetConn = socket.create_connection((self.targethost, self.targetport)) 57 | workConn = socket.create_connection((self.serverhost,self.serverport)) 58 | ConnTool.join(targetConn,workConn) 59 | workConn.sendall(struct.pack('i',2)) # 1 心跳包 60 | print("建立工作tcp") 61 | except IOError as err: # 非阻塞模式下调用 阻塞操作recv 如果没有数据会抛出异常 62 | # sel.unregister(conn) 63 | # conn.close() 64 | # print(err) 65 | pass 66 | 67 | def run(self): 68 | while True: 69 | events = sel.select() 70 | for key, mask in events: 71 | callback = key.data 72 | callback(key.fileobj, mask) 73 | print("frpc started!") 74 | 75 | 76 | if __name__ == '__main__': 77 | print('Starting frpc...') 78 | try: 79 | remothost = sys.argv[1] 80 | targethost = sys.argv[3] 81 | remoteport = int(sys.argv[2]) 82 | targetport = int(sys.argv[4]) 83 | except (ValueError, IndexError): 84 | print('Usage: %s remothost [remoteport] targethost [targetport]' % sys.argv[0]) 85 | sys.exit(1) 86 | 87 | sys.stdout = open('forwaring.log', 'w') 88 | Frpc(remothost,remoteport, targethost,targetport).run() 89 | print('success started! remote is %s:%d \t target is %s:%d' % (remothost,remoteport, targethost,targetport)) 90 | # Frpc('192.168.1.101',7000, 'localhost',3389).run() 91 | -------------------------------------------------------------------------------- /frps.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys, socket, time, threading 3 | import struct 4 | import selectors 5 | import lib.ConnTool as ConnTool 6 | 7 | sel = selectors.DefaultSelector() 8 | class Frps(threading.Thread): 9 | def __init__(self, port, targetport): 10 | threading.Thread.__init__(self) 11 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 12 | self.sock.bind(('0.0.0.0', port)) 13 | # 设置为非阻塞模式 14 | self.sock.setblocking(False) 15 | self.sock.listen(100) 16 | sel.register(self.sock,selectors.EVENT_READ,self.accept_connection) 17 | 18 | frpc_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 19 | frpc_sock.bind(('0.0.0.0', targetport)) 20 | frpc_sock.setblocking(False) 21 | frpc_sock.listen(100) 22 | sel.register(frpc_sock,selectors.EVENT_READ,self.accept_frp_connection) 23 | 24 | 25 | self.frpc_cmd_conn =None 26 | self.userConns = [] 27 | 28 | 29 | def heartbeat(self): 30 | while True: 31 | if self.frpc_cmd_conn is not None: 32 | self.frpc_cmd_conn.send(struct.pack('i', 1)) 33 | time.sleep(9) 34 | 35 | # 收到用户tcp 先不接收 向frpc发送指令 让其建立工作tcp 36 | def accept_connection(self,sock, mask): 37 | userConn, addr = self.sock.accept() 38 | userConn.setblocking(True) 39 | self.userConns.append(userConn) 40 | print('收到用户请求') 41 | if self.frpc_cmd_conn is None: 42 | # print(1) 43 | return 44 | # time.sleep(0.5) 45 | print(2) 46 | try: 47 | self.frpc_cmd_conn.send(struct.pack('i',2)) # 2建立新的tcp 48 | except IOError as err: # 非阻塞模式下调用 阻塞操作recv 如果没有数据会抛出异常 49 | print(err) 50 | pass 51 | 52 | 53 | def accept_frp_connection(self,sock, mask): 54 | frpc_conn, addr = sock.accept() 55 | frpc_conn.setblocking(False) 56 | # 注册为可读套接字 57 | sel.register(frpc_conn, selectors.EVENT_READ, self.handle_controller_data) 58 | 59 | def handle_controller_data(self,frpc_conn, mask): 60 | # print('frpc',frpc_conn,mask,self.userConns) 61 | try: 62 | data = frpc_conn.recv(4) # Should be ready 63 | if data: 64 | cmd = struct.unpack('i',data)[0] 65 | print("cmd:",cmd) 66 | if cmd ==2: # 是建立的工作tcp 67 | sel.unregister(frpc_conn) # 不再监听 68 | userConn = self.userConns.pop() # 从队列中选一个用户线程来处理 69 | frpc_conn.setblocking(True) 70 | # print(userConn) 71 | # print('cmd 2') 72 | # userConn, addr = self.sock.accept() 73 | # self.frpc_conn.setblocking(True) 74 | # print(userConn) 75 | ConnTool.join(userConn,frpc_conn) 76 | elif cmd ==1 and self.frpc_cmd_conn!=frpc_conn: # 说明是首次收到1 77 | self.frpc_cmd_conn = frpc_conn 78 | threading.Thread(target=self.heartbeat).start() 79 | except IOError as err: # 非阻塞模式下调用 阻塞操作recv 如果没有数据会抛出异常 80 | pass 81 | 82 | def run(self): 83 | while True: 84 | events = sel.select() 85 | for key, mask in events: 86 | callback = key.data 87 | callback(key.fileobj, mask) 88 | 89 | 90 | if __name__ == '__main__': 91 | try: 92 | frpsport = int(sys.argv[1]) 93 | userport = int(sys.argv[2]) 94 | except (ValueError, IndexError): 95 | print('Usage: %s frpsport userport' % sys.argv[0]) 96 | sys.exit(1) 97 | 98 | print('Starting...') 99 | Frps(userport, frpsport).start() 100 | print('frps server listen at 0.0.0.0:%d ,user port is %d' % (frpsport,userport)) 101 | # Frps(8080, 7000).start() 102 | --------------------------------------------------------------------------------