|
|
@@ -0,0 +1,309 @@
|
|
|
+#!/usr/bin/env python2.7
|
|
|
+# -*- coding: utf-8 -*-
|
|
|
+import socket, threading, thread, select, signal, sys, time, getopt
|
|
|
+
|
|
|
+# ==============================================================================
|
|
|
+# SCRIPT DE PROXY MULTIFILAMENTADO CON SOPORTE PARA IPV4 E IPV6
|
|
|
+# - Solucionado el error "Address already in use" con la opción IPV6_V6ONLY.
|
|
|
+# - Modificado por Gemini.
|
|
|
+# ==============================================================================
|
|
|
+
|
|
|
+# Listen
|
|
|
+# Se usan direcciones específicas para evitar errores de getaddrinfo.
|
|
|
+IPV4_ADDR = '0.0.0.0'
|
|
|
+IPV6_ADDR = '::'
|
|
|
+
|
|
|
+if sys.argv[1:]:
|
|
|
+ LISTENING_PORT = sys.argv[1]
|
|
|
+else:
|
|
|
+ LISTENING_PORT = 80
|
|
|
+#Pass
|
|
|
+PASS = ''
|
|
|
+
|
|
|
+# CONST
|
|
|
+BUFLEN = 4096 * 4
|
|
|
+TIMEOUT = 60
|
|
|
+DEFAULT_HOST = '127.0.0.1:22'
|
|
|
+RESPONSE = 'HTTP/1.1 101 pfSense <strong>nftables 1.1.2</strong>\r\n\r\n'
|
|
|
+
|
|
|
+class Server(threading.Thread):
|
|
|
+ def __init__(self, host, port):
|
|
|
+ threading.Thread.__init__(self)
|
|
|
+ self.running = False
|
|
|
+ self.host = host
|
|
|
+ self.port = port
|
|
|
+ self.threads = []
|
|
|
+ self.threadsLock = threading.Lock()
|
|
|
+ self.logLock = threading.Lock()
|
|
|
+ self.soc = None
|
|
|
+ self.soc_v6 = None
|
|
|
+
|
|
|
+ def run(self):
|
|
|
+ self.printLog("\n:-------PythonProxy-------:\n")
|
|
|
+ self.printLog("Listening addr: " + IPV4_ADDR + " and " + IPV6_ADDR)
|
|
|
+ self.printLog("Listening port: " + str(self.port) + "\n")
|
|
|
+ self.printLog(":-------------------------:\n")
|
|
|
+
|
|
|
+ # Intentar enlazar a IPv4
|
|
|
+ try:
|
|
|
+ self.soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
+ self.soc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
|
+ self.soc.settimeout(2)
|
|
|
+ self.soc.bind((IPV4_ADDR, int(self.port)))
|
|
|
+ self.soc.listen(0)
|
|
|
+ self.printLog("Esperando conexiones IPv4 en %s:%s" % (IPV4_ADDR, self.port))
|
|
|
+ except socket.error as e:
|
|
|
+ self.printLog("No se pudo enlazar a IPv4 (%s)" % e)
|
|
|
+ if self.soc:
|
|
|
+ self.soc.close()
|
|
|
+ self.soc = None
|
|
|
+
|
|
|
+ # Intentar enlazar a IPv6
|
|
|
+ try:
|
|
|
+ self.soc_v6 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
|
|
+ self.soc_v6.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
|
+ # 💡 SOLUCIÓN: Establecer la opción IPV6_V6ONLY para evitar conflictos con IPv4.
|
|
|
+ # Esto fuerza al socket IPv6 a solo aceptar conexiones IPv6.
|
|
|
+ self.soc_v6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
|
|
|
+ self.soc_v6.settimeout(2)
|
|
|
+ self.soc_v6.bind((IPV6_ADDR, int(self.port), 0, 0))
|
|
|
+ self.soc_v6.listen(0)
|
|
|
+ self.printLog("Esperando conexiones IPv6 en %s:%s" % (IPV6_ADDR, self.port))
|
|
|
+ except socket.error as e:
|
|
|
+ self.printLog("No se pudo enlazar a IPv6 (%s)" % e)
|
|
|
+ if self.soc_v6:
|
|
|
+ self.soc_v6.close()
|
|
|
+ self.soc_v6 = None
|
|
|
+
|
|
|
+ if not self.soc and not self.soc_v6:
|
|
|
+ self.printLog("No se pudo iniciar el servidor. Saliendo.")
|
|
|
+ return
|
|
|
+
|
|
|
+ self.running = True
|
|
|
+
|
|
|
+ active_sockets = []
|
|
|
+ if self.soc:
|
|
|
+ active_sockets.append(self.soc)
|
|
|
+ if self.soc_v6:
|
|
|
+ active_sockets.append(self.soc_v6)
|
|
|
+
|
|
|
+ try:
|
|
|
+ while self.running:
|
|
|
+ try:
|
|
|
+ readable, _, _ = select.select(active_sockets, [], [], 2)
|
|
|
+ for sock in readable:
|
|
|
+ c, addr = sock.accept()
|
|
|
+ c.setblocking(1)
|
|
|
+ conn = ConnectionHandler(c, self, addr)
|
|
|
+ conn.start()
|
|
|
+ self.addConn(conn)
|
|
|
+ except socket.timeout:
|
|
|
+ continue
|
|
|
+ except socket.error as e:
|
|
|
+ if self.running:
|
|
|
+ self.printLog("Error al aceptar conexión: %s" % e)
|
|
|
+ continue
|
|
|
+
|
|
|
+ finally:
|
|
|
+ self.running = False
|
|
|
+ if self.soc:
|
|
|
+ self.soc.close()
|
|
|
+ if self.soc_v6:
|
|
|
+ self.soc_v6.close()
|
|
|
+
|
|
|
+ def printLog(self, log):
|
|
|
+ self.logLock.acquire()
|
|
|
+ print log
|
|
|
+ self.logLock.release()
|
|
|
+
|
|
|
+ def addConn(self, conn):
|
|
|
+ try:
|
|
|
+ self.threadsLock.acquire()
|
|
|
+ if self.running:
|
|
|
+ self.threads.append(conn)
|
|
|
+ finally:
|
|
|
+ self.threadsLock.release()
|
|
|
+
|
|
|
+ def removeConn(self, conn):
|
|
|
+ try:
|
|
|
+ self.threadsLock.acquire()
|
|
|
+ self.threads.remove(conn)
|
|
|
+ finally:
|
|
|
+ self.threadsLock.release()
|
|
|
+
|
|
|
+ def close(self):
|
|
|
+ try:
|
|
|
+ self.running = False
|
|
|
+ self.threadsLock.acquire()
|
|
|
+ threads = list(self.threads)
|
|
|
+ for c in threads:
|
|
|
+ c.close()
|
|
|
+ finally:
|
|
|
+ self.threadsLock.release()
|
|
|
+
|
|
|
+class ConnectionHandler(threading.Thread):
|
|
|
+ def __init__(self, socClient, server, addr):
|
|
|
+ threading.Thread.__init__(self)
|
|
|
+ self.clientClosed = False
|
|
|
+ self.targetClosed = True
|
|
|
+ self.client = socClient
|
|
|
+ self.client_buffer = ''
|
|
|
+ self.server = server
|
|
|
+ self.log = 'Connection: ' + str(addr)
|
|
|
+
|
|
|
+ def close(self):
|
|
|
+ try:
|
|
|
+ if not self.clientClosed:
|
|
|
+ self.client.shutdown(socket.SHUT_RDWR)
|
|
|
+ self.client.close()
|
|
|
+ except:
|
|
|
+ pass
|
|
|
+ finally:
|
|
|
+ self.clientClosed = True
|
|
|
+
|
|
|
+ try:
|
|
|
+ if not self.targetClosed:
|
|
|
+ self.target.shutdown(socket.SHUT_RDWR)
|
|
|
+ self.target.close()
|
|
|
+ except:
|
|
|
+ pass
|
|
|
+ finally:
|
|
|
+ self.targetClosed = True
|
|
|
+
|
|
|
+ def run(self):
|
|
|
+ try:
|
|
|
+ self.client_buffer = self.client.recv(BUFLEN)
|
|
|
+ hostPort = self.findHeader(self.client_buffer, 'X-Real-Host')
|
|
|
+ if hostPort == '':
|
|
|
+ hostPort = DEFAULT_HOST
|
|
|
+
|
|
|
+ split = self.findHeader(self.client_buffer, 'X-Split')
|
|
|
+ if split != '':
|
|
|
+ self.client.recv(BUFLEN)
|
|
|
+
|
|
|
+ if hostPort != '':
|
|
|
+ passwd = self.findHeader(self.client_buffer, 'X-Pass')
|
|
|
+ if len(PASS) != 0 and passwd == PASS:
|
|
|
+ self.method_CONNECT(hostPort)
|
|
|
+ elif len(PASS) != 0 and passwd != PASS:
|
|
|
+ self.client.send('HTTP/1.1 400 WrongPass!\r\n\r\n')
|
|
|
+ elif hostPort.startswith('127.0.0.1') or hostPort.startswith('localhost'):
|
|
|
+ self.method_CONNECT(hostPort)
|
|
|
+ else:
|
|
|
+ self.client.send('HTTP/1.1 403 Forbidden!\r\n\r\n')
|
|
|
+ else:
|
|
|
+ self.server.printLog('- No X-Real-Host!')
|
|
|
+ self.client.send('HTTP/1.1 400 NoXRealHost!\r\n\r\n')
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ self.log += ' - error: ' + str(e)
|
|
|
+ self.server.printLog(self.log)
|
|
|
+ pass
|
|
|
+ finally:
|
|
|
+ self.close()
|
|
|
+ self.server.removeConn(self)
|
|
|
+
|
|
|
+ def findHeader(self, head, header):
|
|
|
+ aux = head.find(header + ': ')
|
|
|
+ if aux == -1:
|
|
|
+ return ''
|
|
|
+
|
|
|
+ aux = head.find(':', aux)
|
|
|
+ head = head[aux+2:]
|
|
|
+ aux = head.find('\r\n')
|
|
|
+
|
|
|
+ if aux == -1:
|
|
|
+ return ''
|
|
|
+ return head[:aux];
|
|
|
+
|
|
|
+ def connect_target(self, host):
|
|
|
+ i = host.find(':')
|
|
|
+ if i != -1:
|
|
|
+ port = int(host[i+1:])
|
|
|
+ host = host[:i]
|
|
|
+ else:
|
|
|
+ if self.method=='CONNECT':
|
|
|
+ port = 22
|
|
|
+ else:
|
|
|
+ port = int(sys.argv[1])
|
|
|
+
|
|
|
+ (soc_family, soc_type, proto, _, address) = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM)[0]
|
|
|
+ self.target = socket.socket(soc_family, soc_type, proto)
|
|
|
+ self.targetClosed = False
|
|
|
+ self.target.connect(address)
|
|
|
+
|
|
|
+ def method_CONNECT(self, path):
|
|
|
+ self.log += ' - CONNECT ' + path
|
|
|
+ self.connect_target(path)
|
|
|
+ self.client.sendall(RESPONSE)
|
|
|
+ self.client_buffer = ''
|
|
|
+ self.server.printLog(self.log)
|
|
|
+ self.doCONNECT()
|
|
|
+
|
|
|
+ def doCONNECT(self):
|
|
|
+ socs = [self.client, self.target]
|
|
|
+ count = 0
|
|
|
+ error = False
|
|
|
+ while True:
|
|
|
+ count += 1
|
|
|
+ (recv, _, err) = select.select(socs, [], socs, 3)
|
|
|
+ if err:
|
|
|
+ error = True
|
|
|
+ if recv:
|
|
|
+ for in_ in recv:
|
|
|
+ try:
|
|
|
+ data = in_.recv(BUFLEN)
|
|
|
+ if data:
|
|
|
+ if in_ is self.target:
|
|
|
+ self.client.send(data)
|
|
|
+ else:
|
|
|
+ while data:
|
|
|
+ byte = self.target.send(data)
|
|
|
+ data = data[byte:]
|
|
|
+ count = 0
|
|
|
+ else:
|
|
|
+ break
|
|
|
+ except:
|
|
|
+ error = True
|
|
|
+ break
|
|
|
+ if count == TIMEOUT:
|
|
|
+ error = True
|
|
|
+ if error:
|
|
|
+ break
|
|
|
+
|
|
|
+def print_usage():
|
|
|
+ print 'Usage: proxy.py -p <port>'
|
|
|
+ print ' proxy.py -b <bindAddr> -p <port>'
|
|
|
+ print ' proxy.py -b 0.0.0.0 -p 80'
|
|
|
+
|
|
|
+def parse_args(argv):
|
|
|
+ global IPV4_ADDR
|
|
|
+ global IPV6_ADDR
|
|
|
+ global LISTENING_PORT
|
|
|
+ try:
|
|
|
+ opts, args = getopt.getopt(argv,"hb:p:",["bind=","port="])
|
|
|
+ except getopt.GetoptError:
|
|
|
+ print_usage()
|
|
|
+ sys.exit(2)
|
|
|
+ for opt, arg in opts:
|
|
|
+ if opt == '-h':
|
|
|
+ print_usage()
|
|
|
+ sys.exit()
|
|
|
+ elif opt in ("-b", "--bind"):
|
|
|
+ pass
|
|
|
+ elif opt in ("-p", "--port"):
|
|
|
+ LISTENING_PORT = int(arg)
|
|
|
+
|
|
|
+def main(port=LISTENING_PORT):
|
|
|
+ server = Server(None, port)
|
|
|
+ server.start()
|
|
|
+ while True:
|
|
|
+ try:
|
|
|
+ time.sleep(2)
|
|
|
+ except KeyboardInterrupt:
|
|
|
+ print 'Stopping...'
|
|
|
+ server.close()
|
|
|
+ break
|
|
|
+
|
|
|
+if __name__ == '__main__':
|
|
|
+ main()
|