client hello (#1)
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
using Prospect.Unreal.Core;
|
||||
using Prospect.Unreal.Net;
|
||||
using Prospect.Unreal.Runtime;
|
||||
using Prospect.Unreal.Net.Packets.Control;
|
||||
using Prospect.Unreal.Net.Packets.Bunch;
|
||||
using Serilog;
|
||||
|
||||
namespace Prospect.Server.Game;
|
||||
|
||||
internal class Client
|
||||
{
|
||||
private const float TickRate = (1000.0f / 60.0f) / 1000.0f;
|
||||
private readonly PeriodicTimer ClientTick = new PeriodicTimer(TimeSpan.FromSeconds(TickRate));
|
||||
UNetConnection UnitConn;
|
||||
public async Task<UIpNetDriver> Connect(string ipAddr, int port, FUrl worldUrl)
|
||||
{
|
||||
var connection = new UIpNetDriver(System.Net.IPAddress.Parse(ipAddr), port, false);
|
||||
await using (var world = new ProspectWorld(worldUrl))
|
||||
connection.InitConnect(world, new FUrl { Host = System.Net.IPAddress.Parse(ipAddr), Port = port });
|
||||
UnitConn = connection.ServerConnection;
|
||||
connection.ServerConnection.Handler?.BeginHandshaking(SendInitialJoin);
|
||||
|
||||
while (await ClientTick.WaitForNextTickAsync())
|
||||
{
|
||||
if (connection != null)
|
||||
{
|
||||
connection.TickDispatch(TickRate);
|
||||
connection.PostTickDispatch();
|
||||
|
||||
connection.TickFlush(TickRate);
|
||||
connection.PostTickFlush();
|
||||
}
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
public void SendInitialJoin()
|
||||
//public static void SendInitialJoin(UNetConnection UnitConn)
|
||||
{
|
||||
var channel = UnitConn.CreateChannelByName(new Unreal.Core.Names.FName(Unreal.Core.Names.EName.Control), Unreal.Net.Channels.EChannelCreateFlags.None, 0);
|
||||
var BunchSequence = ++UnitConn.OutReliable[0];
|
||||
FOutBunch ControlChanBunch = new FOutBunch(channel, false);
|
||||
ControlChanBunch.Time = 0.0;
|
||||
ControlChanBunch.ReceivedAck = false;
|
||||
ControlChanBunch.PacketId = 0;
|
||||
//ControlChanBunch.Channel = nullptr;
|
||||
ControlChanBunch.ChIndex = 0;
|
||||
ControlChanBunch.ChName = new Unreal.Core.Names.FName(Unreal.Core.Names.EName.Control);
|
||||
ControlChanBunch.bReliable = true;
|
||||
ControlChanBunch.ChSequence = BunchSequence;
|
||||
ControlChanBunch.bOpen = true;
|
||||
// NOTE: Might not cover all bOpen or 'channel already open' cases
|
||||
if (UnitConn.Channels[0] != null && UnitConn.Channels[0].OpenPacketId.First == 0)
|
||||
{
|
||||
UnitConn.Channels[0].OpenPacketId = new FPacketIdRange(BunchSequence, BunchSequence);
|
||||
}
|
||||
|
||||
// Need to send 'NMT_Hello' to start off the connection (the challenge is not replied to)
|
||||
byte IsLittleEndian = 0;
|
||||
|
||||
// We need to construct the NMT_Hello packet manually, for the initial connection
|
||||
byte MessageType = (byte)NMT.Hello;
|
||||
|
||||
// Allow the bunch to resize, and be split into partial bunches in SendRawBunch - for Fortnite
|
||||
ControlChanBunch.SetAllowResize(true);
|
||||
ControlChanBunch.WriteByte(MessageType);
|
||||
ControlChanBunch.WriteByte(IsLittleEndian);
|
||||
ControlChanBunch.WriteInt32(0); //FNetworkVersion::GetLocalNetworkVersion();
|
||||
// TODO hello encryption token
|
||||
|
||||
/*bool bSkipControlJoin = !!(MinClientFlags & EMinClientFlags::SkipControlJoin);
|
||||
bool bBeaconConnect = !!(MinClientFlags & EMinClientFlags::BeaconConnect);
|
||||
|
||||
if (bBeaconConnect)
|
||||
{
|
||||
if (!bSkipControlJoin)
|
||||
{
|
||||
MessageType = NMT_BeaconJoin;
|
||||
*ControlChanBunch << MessageType;
|
||||
*ControlChanBunch << BeaconType;
|
||||
|
||||
uint8 EncType = 0;
|
||||
|
||||
*ControlChanBunch << EncType;
|
||||
*ControlChanBunch << JoinUID;
|
||||
|
||||
// Also immediately ack the beacon GUID setup; we're just going to let the server setup the client beacon,
|
||||
// through the actor channel
|
||||
MessageType = NMT_BeaconNetGUIDAck;
|
||||
*ControlChanBunch << MessageType;
|
||||
*ControlChanBunch << BeaconType;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Then send NMT_Login
|
||||
WriteControlLogin(ControlChanBunch);
|
||||
|
||||
// Now send NMT_Join, which will spawn the PlayerController, which should then trigger replication of basic actor channels
|
||||
if (!bSkipControlJoin)
|
||||
{
|
||||
MessageType = NMT_Join;
|
||||
*ControlChanBunch << MessageType;
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
UnitConn.SendRawBunch(ControlChanBunch, false);
|
||||
// Immediately flush, so that Fortnite doesn't trigger an overflow
|
||||
UnitConn.FlushNet();
|
||||
|
||||
|
||||
// At this point, fire of notification that we are connected
|
||||
//bConnected = true;
|
||||
|
||||
//ConnectedDel.ExecuteIfBound();
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ internal static class Program
|
||||
Map = "/Game/ThirdPersonCPP/Maps/ThirdPersonExampleMap"
|
||||
};
|
||||
|
||||
//Task.Run(() => { Thread.Sleep(100); new Client().Connect("127.0.0.1", 7777, worldUrl); });
|
||||
await using (var world = new ProspectWorld(worldUrl))
|
||||
{
|
||||
world.SetGameInstance(new UGameInstance());
|
||||
|
||||
@@ -70,7 +70,7 @@ public abstract class HandlerComponent
|
||||
|
||||
public abstract void Initialize();
|
||||
|
||||
public virtual void NotifiyHandshakeBegin()
|
||||
public virtual void NotifyHandshakeBegin()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ public class PacketHandler
|
||||
private FBitReader _incomingPacket;
|
||||
|
||||
private bool _bBeganHandshaking;
|
||||
private Action HandshakeCompleteAction;
|
||||
|
||||
public PacketHandler()
|
||||
{
|
||||
@@ -116,15 +117,17 @@ public class PacketHandler
|
||||
return Outgoing_Internal(packet, countBits, traits, false, EmptyAddress);
|
||||
}
|
||||
|
||||
public void BeginHandshaking()
|
||||
public void BeginHandshaking(Action handshakeComplete = null)
|
||||
{
|
||||
// bBeganHandshaking = true;
|
||||
_bBeganHandshaking = true;
|
||||
|
||||
HandshakeCompleteAction = handshakeComplete;
|
||||
|
||||
foreach (var component in _handlerComponents)
|
||||
{
|
||||
if (component.RequiresHandshake && !component.IsInitialized())
|
||||
{
|
||||
component.NotifiyHandshakeBegin();
|
||||
component.NotifyHandshakeBegin();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,6 +327,11 @@ public class PacketHandler
|
||||
}
|
||||
|
||||
SetState(HandlerState.Initialized);
|
||||
|
||||
if (_bBeganHandshaking)
|
||||
{
|
||||
HandshakeCompleteAction();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFullyInitialized()
|
||||
@@ -430,7 +438,7 @@ public class PacketHandler
|
||||
// (components closer to the Socket, perform their handshake first)
|
||||
if (_bBeganHandshaking && !curComponent.IsInitialized() && inComponent.RequiresHandshake && !bPassedHandshakeNotify && curComponent.RequiresHandshake)
|
||||
{
|
||||
curComponent.NotifiyHandshakeBegin();
|
||||
curComponent.NotifyHandshakeBegin();
|
||||
bPassedHandshakeNotify = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,36 @@ public class StatelessConnectHandlerComponent : HandlerComponent
|
||||
/// </summary>
|
||||
private int _lastClientSequence;
|
||||
|
||||
/// <summary>
|
||||
/// The last time a handshake packet was sent - used for detecting failed sends.
|
||||
/// </summary>
|
||||
private double _lastClientSendTimestamp;
|
||||
|
||||
/// <summary>
|
||||
/// The local (client) time at which the challenge was last updated
|
||||
/// </summary>
|
||||
private double _lastChallengeTimestamp;
|
||||
|
||||
/// <summary>
|
||||
/// The local (client) time at which the last restart handshake request was receive
|
||||
/// </summary>
|
||||
private double _lastRestartPacketTimestamp;
|
||||
|
||||
/// <summary>
|
||||
/// The SecretId value of the last challenge response sent
|
||||
/// </summary>
|
||||
private byte _lastSecretId;
|
||||
|
||||
/// <summary>
|
||||
/// The Timestamp value of the last challenge response sent
|
||||
/// </summary>
|
||||
private double _lastTimestamp;
|
||||
|
||||
/// <summary>
|
||||
/// The Cookie value of the last challenge response sent. Will differ from AuthorisedCookie, if a handshake retry is triggered.
|
||||
/// </summary>
|
||||
private byte[] _lastCookie = new byte[CookieByteSize];
|
||||
|
||||
/// <summary>
|
||||
/// Client: Whether or not we are in the middle of a restarted handshake.
|
||||
/// Server: Whether or not the last handshake was a restarted handshake.
|
||||
@@ -176,7 +206,136 @@ public class StatelessConnectHandlerComponent : HandlerComponent
|
||||
{
|
||||
if (Handler.Mode == HandlerMode.Client)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
if (State == HandlerComponentState.UnInitialized || State == HandlerComponentState.InitializedOnLocal)
|
||||
{
|
||||
if (bRestartHandshake)
|
||||
{
|
||||
//UE_LOG(LogHandshake, Log, TEXT("Ignoring restart handshake request, while already restarted."));
|
||||
}
|
||||
// Receiving challenge, verify the timestamp is > 0.0f
|
||||
else if (timestamp > 0.0)
|
||||
{
|
||||
_lastChallengeTimestamp = _driver?.GetElapsedTime() ?? 0.0;
|
||||
|
||||
SendChallengeResponse(secretId, timestamp, cookie);
|
||||
|
||||
// Utilize this state as an intermediary, indicating that the challenge response has been sent
|
||||
SetState(HandlerComponentState.InitializedOnLocal);
|
||||
}
|
||||
// Receiving challenge ack, verify the timestamp is < 0.0f
|
||||
else if (timestamp < 0.0)
|
||||
{
|
||||
if (!_bRestartedHandshake)
|
||||
{
|
||||
var ServerConn = _driver?.ServerConnection;
|
||||
|
||||
// Extract the initial packet sequence from the random Cookie data
|
||||
if (ServerConn != null)
|
||||
{
|
||||
_lastServerSequence = BinaryPrimitives.ReadInt16LittleEndian(cookie) & (UNetConnection.MaxPacketId - 1);
|
||||
_lastClientSequence = BinaryPrimitives.ReadInt16LittleEndian(cookie.Slice(2)) & (UNetConnection.MaxPacketId - 1);
|
||||
|
||||
ServerConn.InitSequence(_lastServerSequence, _lastClientSequence);
|
||||
}
|
||||
// Save the final authorized cookie
|
||||
cookie.CopyTo(_authorisedCookie);
|
||||
}
|
||||
|
||||
// Now finish initializing the handler - flushing the queued packet buffer in the process.
|
||||
SetState(HandlerComponentState.Initialized);
|
||||
Initialized();
|
||||
|
||||
_bRestartedHandshake = false;
|
||||
}
|
||||
}
|
||||
else if (bRestartHandshake)
|
||||
{
|
||||
var bValidAuthCookie = !_authorisedCookie.All(b => b == 0);
|
||||
|
||||
// The server has requested us to restart the handshake process - this is because
|
||||
// it has received traffic from us on a different address than before.
|
||||
if (bValidAuthCookie)
|
||||
{
|
||||
bool bPassedDelayCheck = false;
|
||||
bool bPassedDualIPCheck = false;
|
||||
double CurrentTime = FPlatformTime.Seconds();
|
||||
|
||||
if (!_bRestartedHandshake)
|
||||
{
|
||||
var ServerConn = _driver?.ServerConnection;
|
||||
// todo
|
||||
//double LastNetConnPacketTime = ServerConn?.lastre(ServerConn != nullptr ? ServerConn->LastReceiveRealtime : 0.0);
|
||||
|
||||
// The server may send multiple restart handshake packets, so have a 10 second delay between accepting them
|
||||
//bPassedDelayCheck = (CurrentTime - LastClientSendTimestamp) > 10.0;
|
||||
|
||||
// Some clients end up sending packets duplicated over multiple IP's, triggering the restart handshake.
|
||||
// Detect this by checking if any restart handshake requests have been received in roughly the last second
|
||||
// (Dual IP situations will make the server send them constantly) - and override the checks as a failsafe,
|
||||
// if no NetConnection packets have been received in the last second.
|
||||
//double LastRestartPacketTimeDiff = CurrentTime - _lastRestartPacketTimestamp;
|
||||
//double LastNetConnPacketTimeDiff = CurrentTime - LastNetConnPacketTime;
|
||||
|
||||
//bPassedDualIPCheck = _lastRestartPacketTimestamp == 0.0 || LastRestartPacketTimeDiff > 1.1 || LastNetConnPacketTimeDiff > 1.0;
|
||||
}
|
||||
|
||||
_lastRestartPacketTimestamp = CurrentTime;
|
||||
double LastLogStartTime = 0.0;
|
||||
int LogCounter = 0;
|
||||
Func<bool> WithinHandshakeLogLimit = new Func<bool>(() => {
|
||||
const double LogCountPeriod = 30.0;
|
||||
const byte MaxLogCount = 3;
|
||||
double CurTimeApprox = _driver.GetElapsedTime();
|
||||
bool bWithinLimit = false;
|
||||
if ((CurTimeApprox - LastLogStartTime) > LogCountPeriod)
|
||||
{
|
||||
LastLogStartTime = CurTimeApprox;
|
||||
LogCounter = 1;
|
||||
bWithinLimit = true;
|
||||
}
|
||||
else if (LogCounter < MaxLogCount)
|
||||
{
|
||||
LogCounter++;
|
||||
bWithinLimit = true;
|
||||
}
|
||||
|
||||
return bWithinLimit;
|
||||
});
|
||||
|
||||
if (!_bRestartedHandshake && bPassedDelayCheck && bPassedDualIPCheck)
|
||||
{
|
||||
Logger.Verbose("Beginning restart handshake process.");
|
||||
|
||||
_bRestartedHandshake = true;
|
||||
|
||||
SetState(HandlerComponentState.UnInitialized);
|
||||
NotifyHandshakeBegin();
|
||||
}
|
||||
else if (WithinHandshakeLogLimit())
|
||||
{
|
||||
if (_bRestartedHandshake)
|
||||
{
|
||||
Logger.Verbose("Ignoring restart handshake request, while already restarted (this is normal).");
|
||||
}
|
||||
else if (!bPassedDelayCheck)
|
||||
{
|
||||
Logger.Verbose("Ignoring restart handshake request, due to < 10 seconds since last handshake.");
|
||||
}
|
||||
else // if (!bPassedDualIPCheck)
|
||||
{
|
||||
Logger.Verbose("Ignoring restart handshake request, due to recent NetConnection packets.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Verbose("Server sent restart handshake request, when we don't have an authorised cookie.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ignore, could be a dupe/out-of-order challenge packet
|
||||
}
|
||||
}
|
||||
else if (Handler.Mode == HandlerMode.Server)
|
||||
{
|
||||
@@ -248,11 +407,7 @@ public class StatelessConnectHandlerComponent : HandlerComponent
|
||||
|
||||
if (bHandshakePacket)
|
||||
{
|
||||
if (Handler.Mode == HandlerMode.Client)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
else if (Handler.Mode == HandlerMode.Server)
|
||||
if (Handler.Mode == HandlerMode.Server)
|
||||
{
|
||||
var bInitialConnect = timestamp == 0.0;
|
||||
if (bInitialConnect)
|
||||
@@ -367,6 +522,48 @@ public class StatelessConnectHandlerComponent : HandlerComponent
|
||||
HMACSHA1.HashData(_handshakeSecret[secretId], writer.GetData().AsSpan((int)writer.GetNumBytes()), outCookie);
|
||||
}
|
||||
|
||||
public override void NotifyHandshakeBegin()
|
||||
{
|
||||
if (Handler.Mode != HandlerMode.Client) return;
|
||||
|
||||
var ServerConn = _driver?.ServerConnection;
|
||||
|
||||
if (_driver == null || ServerConn == null)
|
||||
{
|
||||
Logger.Warning("Tried to send handshake connect packet without a server connection.");
|
||||
return;
|
||||
}
|
||||
|
||||
using var ackPacket = new FBitWriter(GetAdjustedSizeBits(HandshakePacketSizeBits) + 1);
|
||||
var bHandshakePacket = (byte)1;
|
||||
var bRestartHandshake = (byte)(_bRestartedHandshake ? 1 : 0);
|
||||
var secretIdPad = (byte)0;
|
||||
|
||||
//if (MagicHeader.Num() > 0) challengePacket.SerializeBits(MagicHeader.GetData(), MagicHeader.Num());
|
||||
|
||||
ackPacket.WriteBit(bHandshakePacket);
|
||||
// In order to prevent DRDoS reflection amplification attacks, clients must pad the packet to match server packet size
|
||||
ackPacket.WriteBit(bRestartHandshake);
|
||||
ackPacket.WriteBit(secretIdPad);
|
||||
ackPacket.Serialize(new Byte[28], 28);
|
||||
|
||||
Logger.Verbose("NotifyHandshakeBegin");
|
||||
|
||||
CapHandshakePacket(ackPacket);
|
||||
|
||||
// Disable PacketHandler parsing, and send the raw packet
|
||||
ServerConn.Handler?.SetRawSend(true);
|
||||
|
||||
if (_driver.IsNetResourceValid())
|
||||
{
|
||||
ServerConn.LowLevelSend(ackPacket.GetData(), (int)ackPacket.GetNumBits(), new FOutPacketTraits());
|
||||
}
|
||||
|
||||
ServerConn.Handler?.SetRawSend(false);
|
||||
|
||||
_lastClientSendTimestamp = FPlatformTime.Seconds();
|
||||
}
|
||||
|
||||
private void SendConnectChallenge(IPEndPoint address)
|
||||
{
|
||||
if (_driver == null)
|
||||
@@ -410,6 +607,60 @@ public class StatelessConnectHandlerComponent : HandlerComponent
|
||||
connectionlessHandler?.SetRawSend(false);
|
||||
}
|
||||
|
||||
private void SendChallengeResponse(byte inSecretId, double timestamp, Span<byte> cookie)
|
||||
{
|
||||
var ServerConn = _driver?.ServerConnection;
|
||||
|
||||
if (ServerConn == null)
|
||||
{
|
||||
Logger.Warning("Tried to send connect challenge without driver");
|
||||
return;
|
||||
}
|
||||
using var challengePacket = new FBitWriter(GetAdjustedSizeBits(_bRestartedHandshake ? RestartResponseSizeBits : HandshakePacketSizeBits) + 1);
|
||||
var bHandshakePacket = (byte)1;
|
||||
var bRestartHandshake = (byte)(_bRestartedHandshake ? 1 : 0);
|
||||
|
||||
//if (MagicHeader.Num() > 0)
|
||||
{
|
||||
//challengePacket.SerializeBits(MagicHeader.GetData(), MagicHeader.Num());
|
||||
}
|
||||
|
||||
challengePacket.WriteBit(bHandshakePacket);
|
||||
challengePacket.WriteBit(bRestartHandshake);
|
||||
challengePacket.WriteBit(inSecretId);
|
||||
challengePacket.WriteDouble(timestamp);
|
||||
challengePacket.Serialize(cookie, cookie.Length);
|
||||
|
||||
if (_bRestartedHandshake)
|
||||
{
|
||||
//challengePacket.Serialize(AuthorisedCookie, COOKIE_BYTE_SIZE);
|
||||
}
|
||||
|
||||
Logger.Verbose("SendChallengeResponse. Timestamp: {Timestamp}, Cookie: {Cookie}", timestamp, Convert.ToHexString(cookie));
|
||||
|
||||
CapHandshakePacket(challengePacket);
|
||||
|
||||
ServerConn.Handler?.SetRawSend(true);
|
||||
|
||||
if (_driver.IsNetResourceValid())
|
||||
{
|
||||
ServerConn.LowLevelSend(challengePacket.GetData(), (int)challengePacket.GetNumBits(), new FOutPacketTraits());
|
||||
}
|
||||
|
||||
ServerConn.Handler?.SetRawSend(false);
|
||||
|
||||
var CurSequence = cookie;
|
||||
|
||||
_lastClientSendTimestamp = FPlatformTime.Seconds();
|
||||
_lastSecretId = inSecretId;
|
||||
_lastTimestamp = timestamp;
|
||||
|
||||
_lastServerSequence = BinaryPrimitives.ReadInt16LittleEndian(cookie) & (UNetConnection.MaxPacketId - 1);
|
||||
_lastClientSequence = BinaryPrimitives.ReadInt16LittleEndian(cookie.Slice(2)) & (UNetConnection.MaxPacketId - 1);
|
||||
|
||||
_lastCookie = cookie.ToArray();
|
||||
}
|
||||
|
||||
private void SendChallengeAck(IPEndPoint address, byte[] inCookie)
|
||||
{
|
||||
if (_driver == null)
|
||||
@@ -564,7 +815,33 @@ public class StatelessConnectHandlerComponent : HandlerComponent
|
||||
{
|
||||
if (Handler.Mode == HandlerMode.Client)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (State != HandlerComponentState.Initialized && _lastClientSendTimestamp != 0.0)
|
||||
{
|
||||
double LastSendTimeDiff = FPlatformTime.Seconds() - _lastClientSendTimestamp;
|
||||
|
||||
if (LastSendTimeDiff > 1.0)
|
||||
{
|
||||
var bRestartChallenge = _driver != null && ((_driver.GetElapsedTime() - _lastChallengeTimestamp) > MinCookieLifetime);
|
||||
|
||||
if (bRestartChallenge)
|
||||
{
|
||||
SetState(HandlerComponentState.UnInitialized);
|
||||
}
|
||||
|
||||
if (State == HandlerComponentState.UnInitialized)
|
||||
{
|
||||
Logger.Verbose("Initial handshake packet timeout - resending.");
|
||||
|
||||
NotifyHandshakeBegin();
|
||||
}
|
||||
else if (State == HandlerComponentState.InitializedOnLocal && _lastTimestamp != 0.0)
|
||||
{
|
||||
Logger.Verbose("Challenge response packet timeout - resending.");
|
||||
|
||||
SendChallengeResponse(_lastSecretId, _lastTimestamp, _lastCookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -51,7 +51,12 @@ public class UIpConnection : UNetConnection
|
||||
|
||||
public override void InitLocalConnection(UNetDriver inDriver, UdpClient inSocket, FUrl inURL, EConnectionState inState, int inMaxPacket = 0, int inPacketOverhead = 0)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
InitBase(inDriver, inSocket, inURL, inState,
|
||||
// Use the default packet size/overhead unless overridden by a child class
|
||||
(inMaxPacket == 0 || inMaxPacket > MaxPacketSize) ? MaxPacketSize : inMaxPacket,
|
||||
inPacketOverhead == 0 ? inMaxPacket : inPacketOverhead);
|
||||
//RemoteAddr = new IPEndPoint(inURL.Host, inURL.Port);
|
||||
InitSendBuffer();
|
||||
}
|
||||
|
||||
public override void LowLevelSend(byte[] data, int countBits, FOutPacketTraits traits)
|
||||
@@ -82,8 +87,13 @@ public class UIpConnection : UNetConnection
|
||||
{
|
||||
throw new UnrealNetException();
|
||||
}
|
||||
|
||||
Socket.Send(dataToSend, countBytes, RemoteAddr);
|
||||
|
||||
// can't send remote addr if client is already set. receive is called before now too, so need to establish socket before then.
|
||||
if (RemoteAddr.Address.GetAddressBytes().All(a => a == 0xff))
|
||||
Socket.Send(dataToSend, countBytes);
|
||||
|
||||
else
|
||||
Socket.Send(dataToSend, countBytes, RemoteAddr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,18 @@ public class UIpNetDriver : UNetDriver
|
||||
|
||||
private bool _isDisposed;
|
||||
|
||||
public UIpNetDriver(IPAddress host, int port)
|
||||
public UIpNetDriver(IPAddress host, int port, bool server)
|
||||
{
|
||||
ServerIp = new IPEndPoint(host, port);
|
||||
Socket = new UdpClient(ServerIp);
|
||||
if (server)
|
||||
{
|
||||
ServerIp = new IPEndPoint(host, port);
|
||||
Socket = new UdpClient(ServerIp); // binds to local
|
||||
}
|
||||
else
|
||||
{
|
||||
Socket = new UdpClient(host.ToString(), port); // binds to remote, establishes local port
|
||||
ServerIp = new IPEndPoint(host, ((IPEndPoint)Socket.Client.LocalEndPoint).Port);
|
||||
}
|
||||
ReceiveThread = new FReceiveThreadRunnable(this);
|
||||
}
|
||||
|
||||
@@ -22,9 +30,28 @@ public class UIpNetDriver : UNetDriver
|
||||
public UdpClient Socket { get; }
|
||||
public FReceiveThreadRunnable ReceiveThread { get; }
|
||||
|
||||
public override bool Init(FNetworkNotify notify)
|
||||
public bool InitConnect(FNetworkNotify InNotify, FUrl ConnectURL)
|
||||
{
|
||||
if (!base.Init(notify))
|
||||
if (!Init(InNotify))
|
||||
{
|
||||
Logger.Warning("Failed to init net driver ConnectURL: %s", ConnectURL.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create new connection.
|
||||
var ipConnection = new UIpConnection();
|
||||
ServerConnection = ipConnection;
|
||||
ServerConnection.InitLocalConnection(this, Socket, ConnectURL, EConnectionState.USOCK_Pending);
|
||||
int DestinationPort = ConnectURL.Port;
|
||||
Logger.Warning("Game client on port %i, rate %i", DestinationPort, ServerConnection.CurrentNetSpeed);
|
||||
CreateInitialClientChannels();
|
||||
ReceiveThread.Start();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool InitListen(FNetworkNotify notify)
|
||||
{
|
||||
if (!Init(notify))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -46,7 +73,7 @@ public class UIpNetDriver : UNetDriver
|
||||
{
|
||||
Logger.Information("Received from {Adress} data {Buffer}", packet.Address, packet.DataView.GetData());
|
||||
|
||||
UNetConnection? connection = null;
|
||||
UNetConnection? connection = ServerConnection;
|
||||
|
||||
if (Equals(packet.Address, ServerIp))
|
||||
{
|
||||
|
||||
@@ -1123,7 +1123,8 @@ public abstract class UNetConnection : UPlayer
|
||||
}
|
||||
|
||||
Handler = new PacketHandler();
|
||||
Handler.Initialize(HandlerMode.Server /* Mode */, UNetConnection.MaxPacketSize * 8 ,false);
|
||||
var mode = Driver.ServerConnection != null ? HandlerMode.Client : HandlerMode.Server;
|
||||
Handler.Initialize(mode, UNetConnection.MaxPacketSize * 8 ,false);
|
||||
|
||||
StatelessConnectComponent = (StatelessConnectHandlerComponent) Handler.AddHandler<StatelessConnectHandlerComponent>();
|
||||
StatelessConnectComponent.SetDriver(Driver);
|
||||
|
||||
@@ -59,6 +59,11 @@ public abstract class UNetDriver : IAsyncDisposable
|
||||
/// </summary>
|
||||
public Dictionary<FName, FChannelDefinition> ChannelDefinitionMap { get; }
|
||||
|
||||
/// <summary>
|
||||
/// AConnection to the server (this net driver is a client)
|
||||
/// </summary>
|
||||
public UNetConnection ServerConnection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Array of connections to clients (this net driver is a host) - unsorted, and ordering changes depending on actor replication
|
||||
/// </summary>
|
||||
@@ -184,7 +189,7 @@ public abstract class UNetDriver : IAsyncDisposable
|
||||
|
||||
public bool IsServer()
|
||||
{
|
||||
return true;
|
||||
return ServerConnection == null;
|
||||
}
|
||||
|
||||
public bool IsKnownChannelName(FName name)
|
||||
@@ -223,6 +228,17 @@ public abstract class UNetDriver : IAsyncDisposable
|
||||
// TODO: NetworkObjectList > HandleConnectionAdded
|
||||
}
|
||||
|
||||
protected void CreateInitialClientChannels()
|
||||
{
|
||||
foreach (var channelDef in ChannelDefinitions)
|
||||
{
|
||||
if (channelDef.InitialServer)
|
||||
{
|
||||
ServerConnection.CreateChannelByName(channelDef.Name, EChannelCreateFlags.OpenedLocally, channelDef.StaticChannelIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateInitialServerChannels(UNetConnection clientConnection)
|
||||
{
|
||||
foreach (var channelDef in ChannelDefinitions)
|
||||
|
||||
@@ -86,10 +86,10 @@ public abstract partial class UWorld : FNetworkNotify, IAsyncDisposable
|
||||
return false;
|
||||
}
|
||||
|
||||
NetDriver = new UIpNetDriver(Url.Host, Url.Port);
|
||||
NetDriver = new UIpNetDriver(Url.Host, Url.Port, IsServer());
|
||||
NetDriver.SetWorld(this);
|
||||
|
||||
if (!NetDriver.Init(this))
|
||||
if (!((UIpNetDriver)NetDriver).InitListen(this))
|
||||
{
|
||||
Logger.Error("Failed to listen");
|
||||
NetDriver = null;
|
||||
|
||||
Reference in New Issue
Block a user