o
    ;i                     @   s   d Z ddlmZ ddlmZ ddlmZ ddlmZ ddl	m
Z
 ddlmZ dd	lmZ dd
lmZmZmZmZ eddG dd dZedddddedededee dee f
ddZeeZdS )z=Client for Modal relay servers, allowing users to expose TLS.    )AsyncIterator)	dataclass)Optional)asynccontextmanager)api_pb2   )synchronize_api)_Client)AlreadyExistsErrorInvalidErrorRemoteErrorServiceErrorT)frozenc                   @   sx   e Zd ZU dZeed< eed< eed< eed< edefddZede	eef fd	d
Z
ede	eef fddZdS )TunnelzA port forwarded from within a running Modal container. Created by `modal.forward()`.

    **Important:** This is an experimental API which may change in the future.
    hostportunencrypted_hostunencrypted_portreturnc                 C   s*   d| j  }| jdkr|d| j 7 }|S )z/Get the public HTTPS URL of the forwarded port.zhttps://i  :r   r   )selfvalue r   A/home/ubuntu/.local/lib/python3.10/site-packages/modal/_tunnel.pyurl   s   
z
Tunnel.urlc                 C   s   | j | jfS )z2Get the public TLS socket as a (host, port) tuple.r   r   r   r   r   
tls_socket%   s   zTunnel.tls_socketc                 C   s   | j std| j | jfS )z2Get the public TCP socket as a (host, port) tuple.z_This tunnel is not configured for unencrypted TCP. Please use `forward(..., unencrypted=True)`.)r   r   r   r   r   r   r   
tcp_socket*   s
   zTunnel.tcp_socketN)__name__
__module____qualname____doc__str__annotations__intpropertyr   tupler   r   r   r   r   r   r      s   
 r   FN)unencrypted
h2_enabledclientr   r(   r)   r*   r   c                C  s@  t | tstd| | dk s| dkrtd|  |r$|r$td|s-t I dH }|jtjkr7td|r<tjntj	}z|j
tj| ||dI dH }W n$ tyd } ztd	|  d
d}~w tyt } ztd|d}~ww zt|j|j|j|jV  W |j
tj| dI dH  dS |j
tj| dI dH  w )a6  Expose a port publicly from inside a running Modal container, with TLS.

    If `unencrypted` is set, this also exposes the TCP socket without encryption on a random port
    number. This can be used to SSH into a container (see example below). Note that it is on the public Internet, so
    make sure you are using a secure protocol over TCP.

    If `h2_enabled` is set, the TLS server will advertise support for HTTP/2.

    **Important:** This is an experimental API which may change in the future.

    **Usage:**

    ```python notest
    import modal
    from flask import Flask

    app = modal.App(image=modal.Image.debian_slim().pip_install("Flask"))
    flask_app = Flask(__name__)


    @flask_app.route("/")
    def hello_world():
        return "Hello, World!"


    @app.function()
    def run_app():
        # Start a web server inside the container at port 8000. `modal.forward(8000)` lets us
        # expose that port to the world at a random HTTPS URL.
        with modal.forward(8000) as tunnel:
            print("Server listening at", tunnel.url)
            flask_app.run("0.0.0.0", 8000)

        # When the context manager exits, the port is no longer exposed.
    ```

    **Raw TCP usage:**

    ```python
    import socket
    import threading

    import modal


    def run_echo_server(port: int):
        """Run a TCP echo server listening on the given port."""
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.bind(("0.0.0.0", port))
        sock.listen(1)

        while True:
            conn, addr = sock.accept()
            print("Connection from:", addr)

            # Start a new thread to handle the connection
            def handle(conn):
                with conn:
                    while True:
                        data = conn.recv(1024)
                        if not data:
                            break
                        conn.sendall(data)

            threading.Thread(target=handle, args=(conn,)).start()


    app = modal.App()


    @app.function()
    def tcp_tunnel():
        # This exposes port 8000 to public Internet traffic over TCP.
        with modal.forward(8000, unencrypted=True) as tunnel:
            # You can connect to this TCP socket from outside the container, for example, using `nc`:
            #  nc <HOST> <PORT>
            print("TCP tunnel listening at:", tunnel.tcp_socket)
            run_echo_server(8000)
    ```

    **SSH example:**
    This assumes you have a rsa keypair in `~/.ssh/id_rsa{.pub}`, this is a bare-bones example
    letting you SSH into a Modal container.

    ```python
    import subprocess
    import time

    import modal

    app = modal.App()
    image = (
        modal.Image.debian_slim()
        .apt_install("openssh-server")
        .run_commands("mkdir /run/sshd")
        .add_local_file("~/.ssh/id_rsa.pub", "/root/.ssh/authorized_keys", copy=True)
    )


    @app.function(image=image, timeout=3600)
    def some_function():
        subprocess.Popen(["/usr/sbin/sshd", "-D", "-e"])
        with modal.forward(port=22, unencrypted=True) as tunnel:
            hostname, port = tunnel.tcp_socket
            connection_cmd = f'ssh -p {port} root@{hostname}'
            print(f"ssh into container using: {connection_cmd}")
            time.sleep(3600)  # keep alive for 1 hour or until killed
    ```

    If you intend to use this more generally, a suggestion is to put the subprocess and port
    forwarding code in an `@enter` lifecycle method of an @app.cls, to only make a single
    ssh server and port for each container (and not one for each input to the function).
    z(The port argument should be an int, not r   i  zInvalid port number z(H2 can only be used with encrypted portsNz4Forwarding ports only works inside a Modal container)r   r(   tunnel_typezPort z is already forwardedzRelay server is unavailable)r   )
isinstancer%   r   r	   from_envclient_typer   CLIENT_TYPE_CONTAINERTUNNEL_TYPE_H2TUNNEL_TYPE_UNSPECIFIEDstubTunnelStartTunnelStartRequestr
   r   r   r   r   r   r   r   
TunnelStopTunnelStopRequest)r   r(   r)   r*   r+   responseexcr   r   r   _forward4   s4   
v
:r9   )r"   collections.abcr   dataclassesr   typingr   synchronicity.async_wrapr   modal_protor   _utils.async_utilsr   r*   r	   	exceptionr
   r   r   r   r   r%   boolr9   forwardr   r   r   r   <module>   s4   " 