There is Python code of tcp forwarder in both directions:

import socket
import threading
import time
import sys

def parse_speed_limit(speed_limit):
    """Parse the speed limit string and return bytes per second."""
    if speed_limit[-1].upper() not in {'K', 'M', 'G'}:
        raise ValueError(f"Invalid speed limit unit: {speed_limit[-1]}")
    count = int(speed_limit[:-1])
    units = {'K': 1024, 'M': 1024**2, 'G': 1024**3}
    return count * units[speed_limit[-1].upper()]

# Dictionary to track clients and their data transfer statistics
clients_stats = {}
clients_lock = threading.Lock()

def forward(source, destination, speed_limit_bps, client_id):
    """Forward data from source to destination at a limited speed, tracking stats."""
    buffer_size = 4096
    total_sent = 0
    total_received = 0

    start_time = time.time()  # Initialize start_time at the beginning of the forwarding
    while True:
        try:
            # Receive data from the source
            data = source.recv(buffer_size)
            if not data:
                break  # Exit if there's no data (connection closed)

            # Send data to the destination
            destination.sendall(data)
            total_sent += len(data)

            # Update the received data for the stats (from destination point of view)
            total_received += len(data)

            # Control the speed by sleeping as necessary
            if speed_limit_bps > 0:
                # Calculate current bytes per second
                elapsed_time = time.time() - start_time  # Use the defined start_time
                if elapsed_time > 0:
                    current_bps = total_sent / elapsed_time
                    if current_bps > speed_limit_bps:
                        excess_time = (total_sent / speed_limit_bps) - elapsed_time
                        if excess_time > 0:
                            time.sleep(excess_time)
        except KeyboardInterrupt:
            print("Shutting down the server...")
            exit()
        except ConnectionAbortedError:
            pass  # Ignore ConnectionAbortedError and continue the loop
        except Exception as e:
            print(f"Unexpected error: {e}")
            break  # Handle other exceptions by breaking out of the loop

    # Clean up on exit
    source.close()
    destination.close()

    # Update statistics
    with clients_lock:
        clients_stats[client_id]['sent'] += total_sent
        clients_stats[client_id]['received'] += total_received

def start_forwarder(local_host, local_port, remote_host, remote_port, speed_limit):
    speed_limit_bps = parse_speed_limit(speed_limit)

    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # Allow reuse of local address
    server.bind((local_host, local_port))
    server.listen(1)
    print(f"Listening on {local_host}:{local_port}")

    try:
        while True:
            try:
                client_socket, client_address = server.accept()
                #print(f"Accepted connection from client {client_address}")

                # Initialize stats for the client
                client_id = f"{client_address[0]}"
                with clients_lock:
                    if client_id not in clients_stats:
                        clients_stats[client_id] = {'sent': 0, 'received': 0}

                remote_socket = socket.create_connection((remote_host, remote_port))
                #print(f"Connected to remote server {remote_host}:{remote_port}")

                threading.Thread(target=forward, args=(client_socket, remote_socket, speed_limit_bps, client_id)).start()
                threading.Thread(target=forward, args=(remote_socket, client_socket, speed_limit_bps, client_id)).start()
            except KeyboardInterrupt:
                print("Shutting down the server...")
                break
            except Exception as e:
                print(f"Error accepting connections: {e}")

    finally:
        server.close()  # Ensure server socket is closed on exit

def update_clients_stats():
    while True:
        time.sleep(10)  # Update interval
        with clients_lock:
            print("\nClient Statistics (MB):")
            print("Client Address             | Sent          | Received      ")
            print("---------------------------|---------------|---------------")
            for client_id, stats in clients_stats.items():
                sent_mb = stats['sent'] / (1024 * 1024)  # Convert bytes to MB
                received_mb = stats['received'] / (1024 * 1024)  # Convert bytes to MB
                # Adjusted field widths for better spacing
                print(f"{client_id:<25} | {sent_mb:12.2f} | {received_mb:12.2f}")


def main():
    if len(sys.argv) != 6:
        print('Usage: tcp_forwarder.py <listen_ip> <listen_port> <target_ip> <target_port> <speed_limit>')
        exit(1)

    source_ip = sys.argv[1]
    source_port = int(sys.argv[2])
    target_ip = sys.argv[3]
    target_port = int(sys.argv[4])
    speed_limit = sys.argv[5]
    
    # Start the stats updating thread
    threading.Thread(target=update_clients_stats, daemon=True).start()

    start_forwarder(source_ip, source_port, target_ip, target_port, speed_limit)

if __name__ == "__main__":
    main()

line 28, in forward
    data = source.recv(buffer_size)
           ^^^^^^^^^^^^^^^^^^^^^^^^
OSError: [WinError 10038] an operation was attempted on something that is not a socket