Skip to content

Instantly share code, notes, and snippets.

@WangYihang
Last active April 3, 2024 06:13
Show Gist options
  • Save WangYihang/e7d36b744557e4673d2157499f6c6b5e to your computer and use it in GitHub Desktop.
Save WangYihang/e7d36b744557e4673d2157499f6c6b5e to your computer and use it in GitHub Desktop.
port forwarding via python socket
#!/usr/bin/env python3
# Tcp Port Forwarding (Reverse Proxy)
# Author : WangYihang <wangyihanger@gmail.com>
'''
+-----------------------------+ +---------------------------------------------+ +--------------------------------+
| My Laptop (Alice) | | Intermediary Server (Bob) | | Internal Server (Carol) |
+-----------------------------+ +----------------------+----------------------+ +--------------------------------+
| $ ssh -p 1022 carol@1.2.3.4 |<------->| IF 1: 1.2.3.4 | IF 2: 192.168.1.1 |<------->| IF 1: 192.168.1.2 |
| carol@1.2.3.4's password: | +----------------------+----------------------+ +--------------------------------+
| carol@hostname:~$ whoami | | $ python pf.py --listen-host 1.2.3.4 \ | | 192.168.1.2:22(OpenSSH Server) |
| carol | | --listen-port 1022 \ | +--------------------------------+
+-----------------------------+ | --connect-host 192.168.1.2 \ |
| --connect-port 22 |
+---------------------------------------------+
'''
import socket
import threading
import argparse
import logging
format = '%(asctime)s - %(filename)s:%(lineno)d - %(levelname)s: %(message)s'
logging.basicConfig(level=logging.INFO, format=format)
def handle(buffer, direction, src_address, src_port, dst_address, dst_port):
'''
intercept the data flows between local port and the target port
'''
if direction:
logging.debug(f"{src_address, src_port} -> {dst_address, dst_port} {len(buffer)} bytes")
else:
logging.debug(f"{src_address, src_port} <- {dst_address, dst_port} {len(buffer)} bytes")
return buffer
def transfer(src, dst, direction):
src_address, src_port = src.getsockname()
dst_address, dst_port = dst.getsockname()
while True:
try:
buffer = src.recv(4096)
if len(buffer) == 0:
break
dst.send(handle(buffer, direction, src_address, src_port, dst_address, dst_port))
except Exception as e:
logging.error(repr(e))
break
logging.warning(f"Closing connect {src_address, src_port}! ")
src.close()
logging.warning(f"Closing connect {dst_address, dst_port}! ")
dst.close()
def server(local_host, local_port, remote_host, remote_port):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((local_host, local_port))
server_socket.listen(0x40)
logging.info(f"Server started {local_host, local_port}")
logging.info(f"Connect to {local_host, local_port} to get the content of {remote_host, remote_port}")
while True:
src_socket, src_address = server_socket.accept()
logging.info(f"[Establishing] {src_address} -> {local_host, local_port} -> ? -> {remote_host, remote_port}")
try:
dst_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
dst_socket.connect((remote_host, remote_port))
logging.info(f"[OK] {src_address} -> {local_host, local_port} -> {dst_socket.getsockname()} -> {remote_host, remote_port}")
s = threading.Thread(target=transfer, args=(dst_socket, src_socket, False))
r = threading.Thread(target=transfer, args=(src_socket, dst_socket, True))
s.start()
r.start()
except Exception as e:
logging.error(repr(e))
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--listen-host", help="the host to listen", required=True)
parser.add_argument("--listen-port", type=int, help="the port to bind", required=True)
parser.add_argument("--connect-host", help="the target host to connect", required=True)
parser.add_argument("--connect-port", type=int, help="the target port to connect", required=True)
args = parser.parse_args()
server(args.listen_host, args.listen_port,
args.connect_host, args.connect_port)
if __name__ == "__main__":
main()
@WangYihang
Copy link
Author

WangYihang commented Aug 4, 2022

The program communicates with the base, there is no call to the "transfer" function. Why?

@kristow31

See line 70-71, the target function of threading.Thread is function transfer.

In addition, please refer to the document of the Python threading package.

target is the callable object to be invoked by the run() method. Defaults to None, meaning nothing is called.

@medram
Copy link

medram commented Sep 16, 2022

Hello everyone,
I really like port forwarding and what you can do with it.
Here is my Python port forwarding version that I've made recently, inspired by @wangyihan script :D

I hope you like it.

By the way, it support HTTP Custom & HTTP injection (for fake Websocket upgrade reply).

@iPurya
Copy link

iPurya commented Oct 4, 2022

what about udp ?

@AnemusDev
Copy link

usage: 213.py [-h] --listen-host LISTEN_HOST --listen-port LISTEN_PORT
--connect-host CONNECT_HOST --connect-port CONNECT_PORT
213.py: error: the following arguments are required: --listen-host, --listen-port, --connect-host, --connect-port
Help, how to fix that?

@ruanhao
Copy link

ruanhao commented Sep 9, 2023

line 45, if len(buffer) == 0, there would be endless loop

@WangYihang
Copy link
Author

@ruanhao thanks for pointing out this bug, fixed.

@ldelaprade
Copy link

Why do we need --listen-host parameter ? 127.0.0.1 won't always do ?

@ruanhao
Copy link

ruanhao commented Sep 28, 2023

Why do we need --listen-host parameter ? 127.0.0.1 won't always do ?

there is need for 0.0.0.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment