|
|
@@ -0,0 +1,150 @@ |
|
|
|
# Copyright 2022 John-Mark Gurney. |
|
|
|
# |
|
|
|
# Redistribution and use in source and binary forms, with or without |
|
|
|
# modification, are permitted provided that the following conditions |
|
|
|
# are met: |
|
|
|
# 1. Redistributions of source code must retain the above copyright |
|
|
|
# notice, this list of conditions and the following disclaimer. |
|
|
|
# 2. Redistributions in binary form must reproduce the above copyright |
|
|
|
# notice, this list of conditions and the following disclaimer in the |
|
|
|
# documentation and/or other materials provided with the distribution. |
|
|
|
# |
|
|
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
|
|
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
|
|
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|
|
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
|
|
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
|
|
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
|
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
|
|
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
|
|
|
# SUCH DAMAGE. |
|
|
|
# |
|
|
|
|
|
|
|
import asyncio |
|
|
|
import unittest |
|
|
|
|
|
|
|
from . import parsesockstr, connectsockstr, listensockstr |
|
|
|
|
|
|
|
from aioquic.asyncio import QuicConnectionProtocol, serve |
|
|
|
from aioquic.asyncio.client import connect |
|
|
|
from aioquic.quic.configuration import QuicConfiguration |
|
|
|
|
|
|
|
# copied from wsfwd |
|
|
|
async def fwd_data(reader, writer): |
|
|
|
while True: |
|
|
|
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() |
|
|
|
#_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() |
|
|
|
|
|
|
|
async def run_connect(dst, rdr, wrr): |
|
|
|
connrdr, connwrr = await connectsockstr(dst) |
|
|
|
|
|
|
|
await asyncio.gather(fwd_data(connrdr, wrr), fwd_data(rdr, connwrr)) |
|
|
|
|
|
|
|
def cmd_quic_serv(args): |
|
|
|
privkey = args.servkey |
|
|
|
cert = args.cert |
|
|
|
|
|
|
|
quic_logger = None |
|
|
|
|
|
|
|
quic_conf = QuicConfiguration( |
|
|
|
alpn_protocols=["ntunnel-01"], |
|
|
|
is_client=False, |
|
|
|
quic_logger=quic_logger, |
|
|
|
) |
|
|
|
|
|
|
|
quic_conf.load_cert_chain(cert, privkey) |
|
|
|
|
|
|
|
proto, slargs = parsesockstr(args.servlisten) |
|
|
|
|
|
|
|
if proto != 'udp': |
|
|
|
raise ValueError('protocol for servlisten must be udp') |
|
|
|
|
|
|
|
def sh(rdr, wrr): |
|
|
|
task = run_connect(args.servtarget, rdr, wrr) |
|
|
|
asyncio.create_task(task) |
|
|
|
|
|
|
|
# XXX - await task |
|
|
|
|
|
|
|
print('foo', repr(slargs)) |
|
|
|
|
|
|
|
loop = asyncio.get_event_loop() |
|
|
|
loop.run_until_complete(serve(slargs['host'], slargs['port'], |
|
|
|
configuration=quic_conf, retry=True, stream_handler=sh)) |
|
|
|
|
|
|
|
try: |
|
|
|
loop.run_forever() |
|
|
|
except KeyboardInterrupt: |
|
|
|
pass |
|
|
|
|
|
|
|
async def client_run(conf, liststr, deststr): |
|
|
|
proto, slargs = parsesockstr(deststr) |
|
|
|
|
|
|
|
if proto != 'udp': |
|
|
|
raise ValueError('protocol for destination must be udp') |
|
|
|
|
|
|
|
# XXX - loop when server restarts? |
|
|
|
async with connect(slargs['host'], slargs['port'], configuration=conf) as \ |
|
|
|
client: |
|
|
|
async def connmaker(rdr, wrr): |
|
|
|
connrdr, connwrr = await client.create_stream() |
|
|
|
|
|
|
|
await asyncio.gather(fwd_data(connrdr, wrr), |
|
|
|
fwd_data(rdr, connwrr)) |
|
|
|
|
|
|
|
ssock = await listensockstr(liststr, connmaker) |
|
|
|
|
|
|
|
# XXX - how to break out when new connection needed? |
|
|
|
await ssock.serve_forever() |
|
|
|
|
|
|
|
def cmd_quic_client(args): |
|
|
|
quic_logger = None |
|
|
|
|
|
|
|
quic_conf = QuicConfiguration( |
|
|
|
alpn_protocols=["ntunnel-01"], |
|
|
|
is_client=True, |
|
|
|
quic_logger=quic_logger, |
|
|
|
) |
|
|
|
|
|
|
|
if args.ca_certs: |
|
|
|
quic_conf.load_verify_locations(args.ca_certs) |
|
|
|
|
|
|
|
cr = client_run(quic_conf, args.clientlisten, args.clienttarget) |
|
|
|
|
|
|
|
loop = asyncio.get_event_loop() |
|
|
|
|
|
|
|
loop.run_until_complete(cr) |
|
|
|
|
|
|
|
|
|
|
|
def quic_parsers(subparsers): |
|
|
|
parser_quic_serv = subparsers.add_parser('quic_serv', help='run a QUIC server') |
|
|
|
parser_quic_serv.add_argument("-k", "--servkey", type=str, |
|
|
|
help="load the TLS private key from the specified file") |
|
|
|
parser_quic_serv.add_argument("-c", "--cert", type=str, |
|
|
|
required=True, |
|
|
|
help="load the TLS certificate from the specified file") |
|
|
|
parser_quic_serv.add_argument('servlisten', type=str, help='Connection that the server listens on') |
|
|
|
parser_quic_serv.add_argument('servtarget', type=str, help='Connection that the server connects to') |
|
|
|
parser_quic_serv.set_defaults(func=cmd_quic_serv) |
|
|
|
|
|
|
|
parser_quic_client = subparsers.add_parser('quic_client', help='run a QUIC client') |
|
|
|
parser_quic_client.add_argument("--ca-certs", type=str, |
|
|
|
help="load CA certificates from the specified file") |
|
|
|
parser_quic_client.add_argument('clientlisten', type=str, help='Connection that the client listens on') |
|
|
|
parser_quic_client.add_argument('clienttarget', type=str, help='Connection that the client connects to') |
|
|
|
parser_quic_client.set_defaults(func=cmd_quic_client) |
|
|
|
|
|
|
|
class Tests(unittest.IsolatedAsyncioTestCase): |
|
|
|
pass |