0

I'm trying to use Ngrok to connect two python scripts using the socket module. I've tried doing this with the ngrok.exe and with pyngrok. Trying to connect with the client using port as 12345 or 80 (everything else times out).

On the client s.connect((ip, port)) doesn't error, but the Python server.py and the ngrok terminal don't register anything. The URL definitely works as Ngrok registers a 502 bad gateway when I put the URL into a web browser.

I assume this is because the sockets aren't using HTTP, instead TCP, but when I start Ngrok with a TCP flag (ngrok.exe TCP 12345) the forwarding address looks like tcp://0.tcp.ngrok.io:xxxxx which when I put this into the client socket.connect() it returns socket.gaierror: [Errno 11001] getaddrinfo failed

So I guess my question is, how to get the Python client socket to accept this address or should I be doing something different?

Thanks

server.py

from pyngrok import ngrok
import socket

port = 12345

s = socket.socket()
s.bind(('', port))

public_url = ngrok.connect(port)
print("ngrok tunnel \"{}\" -> \"http://127.0.0.1:{}/\"".format(public_url, port))

s.listen(5)      
print("socket is listening" )

c, addr = s.accept()

print('Got connection from', addr )

c.send(str.encode(str("Yes")))

while True:
    c.recv(1024).decode("utf-8")

client.py

import socket
s = socket.socket()
url = "copied from server or ngrok"
ip = socket.gethostbyname(url)
port = 12345
s.connect((ip, 12345))
philnash
  • 70,667
  • 10
  • 60
  • 88
holmeswatson
  • 969
  • 3
  • 14
  • 39
  • gethostbyname expects a **hostname** and not some URL. This is [well documented](https://docs.python.org/3/library/socket.html#socket.gethostbyname). The name you need to use is the domain part from the URL and then you need to use the port from the URL to connect. – Steffen Ullrich May 16 '20 at 10:53
  • Just a friendly reminder to click the checkmark on answers to the benefit community members that helped you out. – alexdlaird Aug 27 '20 at 16:51

2 Answers2

1

I am the developer of pyngrok, which I see that you are using. There is actually a TCP socket/client integration example in the documentation, which can be found here.

But you're on the right track. The short of it is, you're not specifying a protocol, which means the default one (http) is being used. You need to tell pyngrok and ngrok that you want to setup a tcp tunnel. So instead do this:

ngrok_tunnel = ngrok.connect(port, "tcp", remote_addr="{}:{}".format(host, port))
print("ngrok tunnel \"{}\" -> \"tcp://127.0.0.1:{}/\"".format(ngrok_tunnel.public_url, port))

For the host and port, you'll need to have first setup a ngrok subdomain ( you can do that here), something like host="1.tcp.ngrok.io". Then you pass that same host/post to your client.py as well (what you're doing wrong there is including the protocol tcp://, leave that off).

alexdlaird
  • 1,174
  • 12
  • 34
0

Yet another example of secure Python sockets using ngrok. You can design your code ignoring ngrok and then inject pyngrok dependency (couple of lines) after testing.

ssl_client.py

# -*- coding: latin-1 -*-

from OpenSSL import SSL
import sys, os, select, socket

def verify_cb(conn, cert, errnum, depth, ok):
    # This obviously has to be updated
    print ('Got certificate: %s' % cert.get_subject())
    return ok

if len(sys.argv) < 3:
    print ('Usage: python[2] client.py HOST PORT')
    sys.exit(1)

dir = os.path.dirname(sys.argv[0])
if dir == '':
    dir = os.curdir

# Initialize context
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.set_verify(SSL.VERIFY_PEER, verify_cb) # Demand a certificate
ctx.use_privatekey_file (os.path.join(dir, 'simple','client.pkey'))
ctx.use_certificate_file(os.path.join(dir, 'simple','client.cert'))
ctx.load_verify_locations(os.path.join(dir, 'simple','CA.cert'))

# Set up client
sock = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
sock.connect((sys.argv[1], int(sys.argv[2])))

while 1:

    line = sys.stdin.readline()
    if line == '':
        break
    try:
        sock.send(line.encode())
    except SSL.Error:
        raise
        print ('Connection died unexpectedly')
        break


sock.shutdown()
sock.close()

ssl_server.py



from OpenSSL import SSL
import sys, os, select, socket


def verify_cb(conn, cert, errnum, depth, ok):
    # This obviously has to be updated
    print ('Got certificate: %s' % cert.get_subject())
    return ok

if len(sys.argv) < 2:
    print ('Usage: python[2] server.py PORT')
    sys.exit(1)

dir = os.path.dirname(sys.argv[0])
if dir == '':
    dir = os.curdir

# Initialize context
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.set_options(SSL.OP_NO_SSLv2)
ctx.set_verify(SSL.VERIFY_PEER|SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb) # Demand a certificate
ctx.use_privatekey_file (os.path.join(dir, 'simple', 'server.pkey'))
ctx.use_certificate_file(os.path.join(dir, 'simple', 'server.cert'))
ctx.load_verify_locations(os.path.join(dir, 'simple', 'CA.cert'))

# Set up server
server = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
server.bind(('', int(sys.argv[1])))
server.listen(3) 
server.setblocking(0)

clients = {}
writers = {}

def dropClient(cli, errors=None):
    if errors:
        print ('Client %s left unexpectedly:' % (clients[cli],))
        print ('  ', errors)
    else:
        print ('Client %s left politely' % (clients[cli],))
    del clients[cli]
    if cli in writers:
        del writers[cli]
    if not errors:
        cli.shutdown()
    cli.close()
from pyngrok import ngrok

port=6063 #make a param
public_url = ngrok.connect(port, "tcp").public_url
print(f"ngrok tunnel '{public_url}' -> 'tcp://127.0.0.1:{port}'")

while 1:

    try:
        r,w,_ = select.select([server]+list(clients.keys()), writers.keys(), [])
    except:
        
        raise

    for cli in r:
        if cli == server:
            cli,addr = server.accept()
            print ('Connection from %s' % (addr,))
            clients[cli] = addr

        else:
            try:
                ret = cli.recv(1024)
                print(555, ret)
            except (SSL.WantReadError, SSL.WantWriteError, SSL.WantX509LookupError):
                pass
            except SSL.ZeroReturnError:
                raise
                dropClient(cli)
            except SSL.Error as errors:
                raise
                dropClient(cli, errors)
            else:
                if not cli in writers:
                    writers[cli] = b''
                writers[cli] = writers[cli] + ret
    if 0:
        for cli in w:
            try:
                ret = cli.send(writers[cli])
            except (SSL.WantReadError, SSL.WantWriteError, SSL.WantX509LookupError):
                pass
            except SSL.ZeroReturnError:
                print(111)
                raise
                dropClient(cli)
            except SSL.Error as  errors:
                raise
                dropClient(cli, errors)
            else:
                writers[cli] = writers[cli][ret:]
                if writers[cli] == '':
                    del writers[cli]

for cli in clients.keys():
    cli.close()
server.close()

URL change at line #59

Details are here: secure-python-socket-using-ngrok

Alex B
  • 2,165
  • 2
  • 27
  • 37