using EonaCat.Quic.Connections; using EonaCat.Quic.Constants; using EonaCat.Quic.Events; using EonaCat.Quic.Helpers; using EonaCat.Quic.Infrastructure; using EonaCat.Quic.Infrastructure.Frames; using EonaCat.Quic.Infrastructure.PacketProcessing; using EonaCat.Quic.Infrastructure.Packets; using EonaCat.Quic.Infrastructure.Settings; using EonaCat.Quic.InternalInfrastructure; using System; using System.Linq; using System.Net; using System.Net.Sockets; namespace EonaCat.Quic { // This file is part of the EonaCat project(s) which is released under the Apache License. // See the LICENSE file or go to https://EonaCat.com/License for full license details. /// /// Quic Server - a Quic server that processes incoming connections and if possible sends back data on it's peers. /// public class QuicServer : QuicTransport { private readonly Unpacker _unpacker; private readonly InitialPacketCreator _packetCreator; private PacketWireTransfer _pwt; private UdpClient _client; private readonly int _port; private readonly string _hostname; private bool _started; public event ClientConnectedEvent OnClientConnected; /// /// Create a new instance of QuicListener. /// /// The port that the server will listen on. public QuicServer(string hostName, int port) { _started = false; _port = port; _hostname = hostName; _unpacker = new Unpacker(); _packetCreator = new InitialPacketCreator(); } /// /// Starts the listener. /// public void Start() { var ipEntry = Uri.CheckHostName(_hostname); IPAddress ipAddress; if (ipEntry == UriHostNameType.Dns) { ipAddress = Dns.GetHostEntry(_hostname).AddressList?.FirstOrDefault(); } else { ipAddress = IPAddress.Parse(_hostname); } _client = new UdpClient(new IPEndPoint(ipAddress, _port)); _started = true; _pwt = new PacketWireTransfer(_client, null); while (true) { Packet packet = _pwt.ReadPacket(); if (packet is InitialPacket) { QuicConnection connection = ProcessInitialPacket(packet, _pwt.LastTransferEndpoint()); OnClientConnected?.Invoke(connection); } if (packet is ShortHeaderPacket) { ProcessShortHeaderPacket(packet); } } } /// /// Stops the listener. /// public void Close() { if (_started) { _client.Close(); } } /// /// Processes incomming initial packet and creates or halts a connection. /// /// Initial Packet /// Peer's endpoint /// private QuicConnection ProcessInitialPacket(Packet packet, IPEndPoint endPoint) { QuicConnection result = null; ulong availableConnectionId; byte[] data; // Unsupported version. Version negotiation packet is sent only on initial connection. All other packets are dropped. (5.2.2 / 16th draft) if (packet.Version != QuicVersion.CurrentVersion || !QuicVersion.SupportedVersions.Contains(packet.Version)) { VersionNegotiationPacket vnp = _packetCreator.CreateVersionNegotiationPacket(); data = vnp.Encode(); _client.Send(data, data.Length, endPoint); return null; } InitialPacket cast = packet as InitialPacket; InitialPacket ip = _packetCreator.CreateInitialPacket(0, cast.SourceConnectionId); // Protocol violation if the initial packet is smaller than the PMTU. (pt. 14 / 16th draft) if (cast.Encode().Length < QuicSettings.PMTU) { ip.AttachFrame(new ConnectionCloseFrame(ErrorCode.PROTOCOL_VIOLATION, 0x00, ErrorConstants.PMTUNotReached)); } else if (ConnectionPool.AddConnection(new ConnectionData(new PacketWireTransfer(_client, endPoint), cast.SourceConnectionId, 0), out availableConnectionId) == true) { // Tell the peer the available connection id ip.SourceConnectionId = (byte)availableConnectionId; // We're including the maximum possible stream id during the connection handshake. (4.5 / 16th draft) ip.AttachFrame(new MaxStreamsFrame(QuicSettings.MaximumStreamId, StreamType.ServerBidirectional)); // Set the return result result = ConnectionPool.Find(availableConnectionId); } else { // Not accepting connections. Send initial packet with CONNECTION_CLOSE frame. // Maximum buffer size should be set in QuicSettings. ip.AttachFrame(new ConnectionCloseFrame(ErrorCode.CONNECTION_REFUSED, 0x00, ErrorConstants.ServerTooBusy)); } data = ip.Encode(); int dataSent = _client.Send(data, data.Length, endPoint); if (dataSent > 0) { return result; } return null; } } }