An stunnel like program that utilizes the Noise protocol.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

151 lines
5.0 KiB

  1. # Copyright 2022 John-Mark Gurney.
  2. #
  3. # Redistribution and use in source and binary forms, with or without
  4. # modification, are permitted provided that the following conditions
  5. # are met:
  6. # 1. Redistributions of source code must retain the above copyright
  7. # notice, this list of conditions and the following disclaimer.
  8. # 2. Redistributions in binary form must reproduce the above copyright
  9. # notice, this list of conditions and the following disclaimer in the
  10. # documentation and/or other materials provided with the distribution.
  11. #
  12. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  13. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  14. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  15. # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  16. # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  17. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  18. # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  19. # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  20. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  21. # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  22. # SUCH DAMAGE.
  23. #
  24. import asyncio
  25. import unittest
  26. from . import parsesockstr, connectsockstr, listensockstr
  27. from aioquic.asyncio import QuicConnectionProtocol, serve
  28. from aioquic.asyncio.client import connect
  29. from aioquic.quic.configuration import QuicConfiguration
  30. # copied from wsfwd
  31. async def fwd_data(reader, writer):
  32. while True:
  33. data = await reader.read(16384)
  34. if data == b'':
  35. #_debprint('fwd_data eof', repr(reader), repr(writer))
  36. # XXX - aioquic doesn't implement close
  37. #writer.close()
  38. #await writer.wait_closed()
  39. #_debprint('fwd_data done', repr(reader), repr(writer))
  40. return
  41. #_debprint('fwd_data data', repr(reader), repr(writer), len(data))
  42. writer.write(data)
  43. # XXX - aioquic doesn't implement is_closing
  44. #await writer.drain()
  45. async def run_connect(dst, rdr, wrr):
  46. connrdr, connwrr = await connectsockstr(dst)
  47. await asyncio.gather(fwd_data(connrdr, wrr), fwd_data(rdr, connwrr))
  48. def cmd_quic_serv(args):
  49. privkey = args.servkey
  50. cert = args.cert
  51. quic_logger = None
  52. quic_conf = QuicConfiguration(
  53. alpn_protocols=["ntunnel-01"],
  54. is_client=False,
  55. quic_logger=quic_logger,
  56. )
  57. quic_conf.load_cert_chain(cert, privkey)
  58. proto, slargs = parsesockstr(args.servlisten)
  59. if proto != 'udp':
  60. raise ValueError('protocol for servlisten must be udp')
  61. def sh(rdr, wrr):
  62. task = run_connect(args.servtarget, rdr, wrr)
  63. asyncio.create_task(task)
  64. # XXX - await task
  65. print('foo', repr(slargs))
  66. loop = asyncio.get_event_loop()
  67. loop.run_until_complete(serve(slargs['host'], slargs['port'],
  68. configuration=quic_conf, retry=True, stream_handler=sh))
  69. try:
  70. loop.run_forever()
  71. except KeyboardInterrupt:
  72. pass
  73. async def client_run(conf, liststr, deststr):
  74. proto, slargs = parsesockstr(deststr)
  75. if proto != 'udp':
  76. raise ValueError('protocol for destination must be udp')
  77. # XXX - loop when server restarts?
  78. async with connect(slargs['host'], slargs['port'], configuration=conf) as \
  79. client:
  80. async def connmaker(rdr, wrr):
  81. connrdr, connwrr = await client.create_stream()
  82. await asyncio.gather(fwd_data(connrdr, wrr),
  83. fwd_data(rdr, connwrr))
  84. ssock = await listensockstr(liststr, connmaker)
  85. # XXX - how to break out when new connection needed?
  86. await ssock.serve_forever()
  87. def cmd_quic_client(args):
  88. quic_logger = None
  89. quic_conf = QuicConfiguration(
  90. alpn_protocols=["ntunnel-01"],
  91. is_client=True,
  92. quic_logger=quic_logger,
  93. )
  94. if args.ca_certs:
  95. quic_conf.load_verify_locations(args.ca_certs)
  96. cr = client_run(quic_conf, args.clientlisten, args.clienttarget)
  97. loop = asyncio.get_event_loop()
  98. loop.run_until_complete(cr)
  99. def quic_parsers(subparsers):
  100. parser_quic_serv = subparsers.add_parser('quic_serv', help='run a QUIC server')
  101. parser_quic_serv.add_argument("-k", "--servkey", type=str,
  102. help="load the TLS private key from the specified file")
  103. parser_quic_serv.add_argument("-c", "--cert", type=str,
  104. required=True,
  105. help="load the TLS certificate from the specified file")
  106. parser_quic_serv.add_argument('servlisten', type=str, help='Connection that the server listens on')
  107. parser_quic_serv.add_argument('servtarget', type=str, help='Connection that the server connects to')
  108. parser_quic_serv.set_defaults(func=cmd_quic_serv)
  109. parser_quic_client = subparsers.add_parser('quic_client', help='run a QUIC client')
  110. parser_quic_client.add_argument("--ca-certs", type=str,
  111. help="load CA certificates from the specified file")
  112. parser_quic_client.add_argument('clientlisten', type=str, help='Connection that the client listens on')
  113. parser_quic_client.add_argument('clienttarget', type=str, help='Connection that the client connects to')
  114. parser_quic_client.set_defaults(func=cmd_quic_client)
  115. class Tests(unittest.IsolatedAsyncioTestCase):
  116. pass