-
-
Save WangYihang/e7d36b744557e4673d2157499f6c6b5e to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3 | |
# Tcp Port Forwarding (Reverse Proxy) | |
# Author : WangYihang <[email protected]> | |
''' | |
+-----------------------------+ +---------------------------------------------+ +--------------------------------+ | |
| My Laptop (Alice) | | Intermediary Server (Bob) | | Internal Server (Carol) | | |
+-----------------------------+ +----------------------+----------------------+ +--------------------------------+ | |
| $ ssh -p 1022 [email protected] |<------->| IF 1: 1.2.3.4 | IF 2: 192.168.1.1 |<------->| IF 1: 192.168.1.2 | | |
| [email protected]'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() |
@sudocpMATHdotPY
After running python port-forwarding.py 0.0.0.0 1022 192.168.1.1 22
, the script will listen on 0.0.0.0:1022
.
Suppose the script is running on some machine that has a public IP address (1.2.3.4), then if you try to connect 1.2.3.4:1022
as if you communicating with 192.168.1.1:22
.
@WangYihang Please change print into function so that it can also work with python3 version.
@shubham2110 thanks for your advice, done.
@WangYihang
which ip address is public and which is private?
This diagram should be able to make it easy to understand. @sudocpMATHdotPY
hello.
Tried to use mssql to port forwarding the connection. The connection is in progress. The program communicates with the base, there is no call to the "transfer" function. Why?
I wanted to see queries to the database, and see the response from the database. Tell me how can this be done?
I wanted to see queries to the database, and see the response from the database. Tell me how can this be done?
Modify function handle
to meet your requirements.
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.info(f"{src_address, src_port} -> {dst_address, dst_port} {len(buffer)} bytes")
logging.info(buffer)
else:
logging.info(f"{src_address, src_port} <- {dst_address, dst_port} {len(buffer)} bytes")
logging.info(buffer)
return buffer
As the following image shows.
Or you can use the Python package hexdump to view the hexadecimal data, just like the following image shows.
pip install hexdump
21a22
> import hexdump
33c34,35
< logging.debug(f"{src_address, src_port} -> {dst_address, dst_port} {len(buffer)} bytes")
---
> logging.info(f"{src_address, src_port} -> {dst_address, dst_port} {len(buffer)} bytes")
> hexdump.hexdump(buffer)
35c37,38
< logging.debug(f"{src_address, src_port} <- {dst_address, dst_port} {len(buffer)} bytes")
---
> logging.info(f"{src_address, src_port} <- {dst_address, dst_port} {len(buffer)} bytes")
> hexdump.hexdump(buffer)
91d93
<
The program communicates with the base, there is no call to the "transfer" function. Why?
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.
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
- Using socket & Asyncio:
https://github.com/medram/port-forwarding/blob/with-asyncio/port_forwarding.py - Using sockets & Selectors:
https://github.com/medram/port-forwarding/blob/with-selectors/port_forwarding.py
I hope you like it.
By the way, it support HTTP Custom & HTTP injection (for fake Websocket upgrade reply).
what about udp ?
line 45, if len(buffer) == 0, there would be endless loop
@ruanhao thanks for pointing out this bug, fixed.
Why do we need --listen-host parameter ? 127.0.0.1 won't always do ?
Why do we need --listen-host parameter ? 127.0.0.1 won't always do ?
there is need for 0.0.0.0
Sorry I have little experience with sockets, what would I put for L_HOST L_PORT R_HOST R_PORT?