''' @desc:内网穿透(映射)客户端 部署在内网 @author: Martin Huang @time: created on 2019/6/14 20:43 @修改记录: 2019/07/12 => 增加DEBUG选项 默认False 改为True可显示更多信息 ''' import select import socket import time from threading import Thread from Utils.ConversionUtils import ConversionUtils #pycharm #from src.main.Utils.IOUtils import * #调试参数 DEBUG = False class MappingClient: def __init__(self,fromIP,fromPort,type,remoteIp,remotePort): #远程VPS的IP地址 self.remoteIp = remoteIp #远程VPS数据监听端口 self.remotePort = remotePort #源/本地ip self.fromIP = fromIP #源/本地端口 self.fromPort = fromPort #clientA->连接内网App self.clientA = None #clientB->连接VPS self.clientB = None #select监听的可读列表、可写列表、错误列表 self.readableList = [] #协议类型 self.type = type #连接内网App def connectClientA(self): if not self.clientA: self.clientA = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.clientA.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) self.clientA.connect((self.fromIP,self.fromPort)) print('clientA Connected!') #将clientA添加进监听可读队列 self.readableList.append(self.clientA) #连接VPS def connectClientB(self): if not self.clientB: self.clientB = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.clientB.setsockopt(socket.SOL_SOCKET,socket.SO_KEEPALIVE,1) self.clientB.connect((self.remoteIp, self.remotePort)) print('clientB Connected!') # 将client添加进监听可读队列 self.readableList.append(self.clientB) #关闭clientA def closeClintA(self): #先将clientA从监听队列中移除,再关闭,否则会有异常,clientB同理 if self.clientA in self.readableList: self.readableList.remove(self.clientA) self.clientA.shutdown(2) self.clientA = None print('ClintA Closed!') def closeClintB(self): if self.clientB in self.readableList: self.readableList.remove(self.clientB) self.clientB.shutdown(2) self.clientB = None print('ClintB Closed!') #端口映射 def TCPMapping(self): #连接内网App和外网VPS self.connectClientA() self.connectClientB() while True: #使用select监听 rs, ws, es = select.select(self.readableList, [], []) for each in rs: #如果当前可读对象为clientA,将读取的数据转发到clientB,若遇到异常/无数据则关闭连接 if each == self.clientA: try: tdataA = each.recv(1024) self.clientB.send(tdataA) except ConnectionResetError as e: if DEBUG: print(e) self.closeClintA() return #print(tdataA) if not tdataA: if self.clientA is not None: self.closeClintA() self.closeClintB() return # 如果当前可读对象为clientB,将读取的数据转发到clientA,若遇到异常/无数据则关闭连接 elif each == self.clientB: try: tdataB = each.recv(1024) self.clientA.send(tdataB) except ConnectionResetError: self.closeClintA() return #print(tdataB) #若收到外部用户意外中断信息,关闭全部连接,结束 if tdataB == bytes('NODATA',encoding='utf-8'): self.closeClintA() self.closeClintB() return if not tdataB: self.closeClintA() self.closeClintB() return #主方法 def InternalMain(remoteIP,commonPort,remotePort,localIp,localPort): #remoteIP ->远程VPS的IP地址 #commonPort -> 心跳检测端口 #remotePort -> 远程VPS数据监听端口 #localIp -> 本地IP #localPort -> 本地端口 #clientC专门与远程VPS做心跳 clientC = socket.socket(socket.AF_INET, socket.SOCK_STREAM) clientC.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) clientC.connect((remoteIP, commonPort)) rl = [clientC] #监听 while True: rs, ws, es = select.select(rl, [], []) for each in rs: if each == clientC: tdataC = each.recv(1024) if not tdataC: rl.remove(clientC) clientC.close() clientC.connect((remoteIP, commonPort)) rl = [clientC] break if DEBUG: print(tdataC) #若远程VPS接收到用户访问请求,则激活一个线程用于处理 if tdataC == bytes('ACTIVATE',encoding='utf-8'): foo = MappingClient(localIp,localPort,'tcp',remoteIP,remotePort) t = Thread(target=foo.TCPMapping) t.setDaemon(True) t.start() #心跳检测 elif tdataC == bytes('IAMALIVE',encoding='utf-8'): b = bytes('OK', encoding='utf-8') each.send(b)