Browse Source

add tests to verify quic functionality. document quic...

This also points to my own repo of quic as it contains a bug fix
for drain...
main
John-Mark Gurney 1 year ago
parent
commit
cc3b33a169
3 changed files with 163 additions and 11 deletions
  1. +57
    -4
      README.md
  2. +105
    -6
      ntunnel/quic.py
  3. +1
    -1
      setup.py

+ 57
- 4
README.md View File

@@ -7,6 +7,26 @@ be secure and simple to use and setup. Due to the flexibility, it can
forward any standard stream socket to another stream socket, including
TCP sockets.

ntunnel also supports using QUIC instead of Noise. The advantage of
QUIC is that you it operates over UDP and allows setting congestion
control parameters. The disadvantage is that it using TLS 1.3 like
handshake (instead of the stronger Noise), and channel binding is not
available.

Installing
----------

```
python3 -m venv p
. ./p/bin/activate
pip install git+https://www.funkthat.com/gitea/jmg/ntunnel.git
```

and if you want to install the QUIC variant:
```
pip install 'ntunnel [quic] @ git+https://www.funkthat.com/gitea/jmg/ntunnel.git'
```

Example
-------

@@ -15,8 +35,8 @@ Note: If you have installed the package, there is also the program

Generate the keys:
```
python -m ntunnel genkey serverkey
python -m ntunnel genkey clientkey
ntunnel genkey serverkey
ntunnel genkey clientkey
```

Create the target for the pass through:
@@ -26,8 +46,8 @@ nc -lU finalsock

Start the server and client:
```
python -m ntunnel server serverkey --clientkey clientkey.pub unix:$(pwd)/servsock unix:$(pwd)/finalsock
python -m ntunnel client clientkey serverkey.pub unix:$(pwd)/clientsock unix:$(pwd)/servsock
ntunnel server serverkey --clientkey clientkey.pub unix:$(pwd)/servsock unix:$(pwd)/finalsock
ntunnel client clientkey serverkey.pub unix:$(pwd)/clientsock unix:$(pwd)/servsock
```

Attach to the client:
@@ -38,6 +58,39 @@ nc -U clientsock
Now when you type text into either of the nc windows, you should see
the same text come out the other side.

Example for QUIC
----------------

Generate a self-signed server key:
```
tmp=$(mktemp)
cat > "$tmp" << EOF
[req]
distinguished_name=req
[san]
subjectAltName=DNS:localhost,server.example.com
EOF
openssl req -x509 -newkey rsa:4096 -sha256 -days 3560 -nodes \
-keyout example.key -out example.crt \
-subj '/CN=ntunnel example cert' -config "$tmp"
rm "$tmp"
```

Note: as QUIC uses standard TLS certificates, instead of a self-signed
certificate as generated above, a certificate signed by a CA may be used
instead. This allows the client to not need the server certificate and uses
the normal CA root store.

Run the server:
```
ntunnel quic_serv -k example.key -c example.crt udp:192.0.2.5:12322 tcp:127.0.0.1:22
```

Run client:
```
ntunnel quic_client --ca-certs funkthat.crt tcp:127.0.0.1:42720 udp:192.0.2.5:12322
```

Running Tests
-------------



+ 105
- 6
ntunnel/quic.py View File

@@ -23,9 +23,15 @@
#

import asyncio
import os
import random
import shutil
import subprocess
import tempfile
import unittest

from . import parsesockstr, connectsockstr, listensockstr
from . import async_test, _awaitfile

from aioquic.asyncio import QuicConnectionProtocol, serve
from aioquic.asyncio.client import connect
@@ -37,16 +43,14 @@ async def fwd_data(reader, writer):
data = await reader.read(16384)
if data == b'':
#_debprint('fwd_data eof', repr(reader), repr(writer))
# XXX - aioquic doesn't implement close
#writer.close()
#await writer.wait_closed()
writer.close()
await writer.wait_closed()
#_debprint('fwd_data done', repr(reader), repr(writer))
return

#_debprint('fwd_data data', repr(reader), repr(writer), len(data))
writer.write(data)
# XXX - aioquic doesn't implement is_closing
#await writer.drain()
await writer.drain()

async def run_connect(dst, rdr, wrr):
connrdr, connwrr = await connectsockstr(dst)
@@ -147,4 +151,99 @@ def quic_parsers(subparsers):
parser_quic_client.set_defaults(func=cmd_quic_client)

class Tests(unittest.IsolatedAsyncioTestCase):
pass
def setUp(self):
# setup temporary directory
d = os.path.realpath(tempfile.mkdtemp())
self.basetempdir = d
self.tempdir = os.path.join(d, 'subdir')
os.mkdir(self.tempdir)

# Generate key
self.privkey = os.path.join(self.tempdir, 'example.key')
self.cert = os.path.join(self.tempdir, 'example.crt')
conf = '''
[req]
distinguished_name=req
[san]
subjectAltName=DNS:localhost,server.example.com
'''.encode('utf-8')
k = subprocess.run(['openssl', 'req', '-x509',
'-newkey', 'rsa:4096', '-sha256', '-days', '3560',
'-nodes', '-keyout', self.privkey, '-out', self.cert,
'-subj', '/CN=ntunnel example cert',
'-config', '/dev/stdin'], input=conf,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

self.assertEqual(k.returncode, 0)

self.assertTrue(os.path.exists(self.privkey))
self.assertTrue(os.path.exists(self.cert))

def tearDown(self):
shutil.rmtree(self.basetempdir)
self.tempdir = None

@async_test
async def test_e2e(self):
unixservsock = os.path.join(self.tempdir, 'unix.serv.sock')
unixclientsock = os.path.join(self.tempdir, 'unix.client.sock')

port = random.randint(2000, 65000)

async def echofun(rdr, wrr):
while True:
d = await rdr.read(16384)
if d:
wrr.write(d)
await wrr.drain()
else:
wrr.close()
await wrr.wait_closed()
return

# start the destination server
servsock = await asyncio.start_unix_server(echofun, path=unixservsock)

# start up ntunnel quic processes
serv = await asyncio.create_subprocess_exec('ntunnel', 'quic_serv', '-k', self.privkey, '-c', self.cert, 'udp:127.0.0.1:%d' % port, 'unix:' + unixservsock)

client = await asyncio.create_subprocess_exec('ntunnel', 'quic_client', '--ca-certs', self.cert, 'unix:' + unixclientsock, 'udp:127.0.0.1:%d' % port)

# make sure everything has started
await _awaitfile(unixservsock)
await _awaitfile(unixclientsock)

# run tests
rdr, wrr = await asyncio.open_unix_connection(unixclientsock)

data = [ b'asldkfj', b'asldkjfasdklj', b'asdlfkjadsf' ]

for i in data:
wrr.write(i)
await wrr.drain()

d = await rdr.read(16384)

self.assertEqual(d, i)

# make sure close hasn't happened yet
self.assertFalse(rdr.at_eof())

# close the writer
wrr.write_eof()
wrr.close()
await wrr.wait_closed()

# make sure the reader sees that the client closed
self.assertFalse(await rdr.read())

# Done terminate daemons
serv.terminate()
client.terminate()

await serv.wait()
await client.wait()

# termiante unix server
servsock.close()
await servsock.wait_closed()

+ 1
- 1
setup.py View File

@@ -37,7 +37,7 @@ setup(name='ntunnel',
],
extras_require = {
'dev': [ 'coverage' ],
'quic': [ 'aioquic' ],
'quic': [ 'aioquic @ git+https://github.com/jmgurney/aioquic.git' ],
},
entry_points={
'console_scripts': [


Loading…
Cancel
Save