EonaCat.Network/EonaCat.Network/System/Quic/QuicServer.cs

158 lines
5.6 KiB
C#

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.
/// <summary>
/// Quic Server - a Quic server that processes incoming connections and if possible sends back data on it's peers.
/// </summary>
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;
/// <summary>
/// Create a new instance of QuicListener.
/// </summary>
/// <param name="port">The port that the server will listen on.</param>
public QuicServer(string hostName, int port)
{
_started = false;
_port = port;
_hostname = hostName;
_unpacker = new Unpacker();
_packetCreator = new InitialPacketCreator();
}
/// <summary>
/// Starts the listener.
/// </summary>
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);
}
}
}
/// <summary>
/// Stops the listener.
/// </summary>
public void Close()
{
if (_started)
{
_client.Close();
}
}
/// <summary>
/// Processes incomming initial packet and creates or halts a connection.
/// </summary>
/// <param name="packet">Initial Packet</param>
/// <param name="endPoint">Peer's endpoint</param>
/// <returns></returns>
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;
}
}
}