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 3 years 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 forward any standard stream socket to another stream socket, including
TCP sockets. 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 Example
------- -------


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


Generate the keys: 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: Create the target for the pass through:
@@ -26,8 +46,8 @@ nc -lU finalsock


Start the server and client: 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: 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 Now when you type text into either of the nc windows, you should see
the same text come out the other side. 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 Running Tests
------------- -------------




+ 105
- 6
ntunnel/quic.py View File

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


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


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


from aioquic.asyncio import QuicConnectionProtocol, serve from aioquic.asyncio import QuicConnectionProtocol, serve
from aioquic.asyncio.client import connect from aioquic.asyncio.client import connect
@@ -37,16 +43,14 @@ async def fwd_data(reader, writer):
data = await reader.read(16384) data = await reader.read(16384)
if data == b'': if data == b'':
#_debprint('fwd_data eof', repr(reader), repr(writer)) #_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)) #_debprint('fwd_data done', repr(reader), repr(writer))
return return


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


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


class Tests(unittest.IsolatedAsyncioTestCase): 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 = { extras_require = {
'dev': [ 'coverage' ], 'dev': [ 'coverage' ],
'quic': [ 'aioquic' ],
'quic': [ 'aioquic @ git+https://github.com/jmgurney/aioquic.git' ],
}, },
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [


Loading…
Cancel
Save