using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using LiteNetLib.Utils;
//Some code parts taken from lidgren-network-gen3
namespace LiteNetLib
{
public interface INatPunchListener
{
void OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token);
void OnNatIntroductionSuccess(IPEndPoint targetEndPoint, string token);
}
public class EventBasedNatPunchListener : INatPunchListener
{
public delegate void OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token);
public delegate void OnNatIntroductionSuccess(IPEndPoint targetEndPoint, string token);
public event OnNatIntroductionRequest NatIntroductionRequest;
public event OnNatIntroductionSuccess NatIntroductionSuccess;
void INatPunchListener.OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token)
{
if(NatIntroductionRequest != null)
NatIntroductionRequest(localEndPoint, remoteEndPoint, token);
}
void INatPunchListener.OnNatIntroductionSuccess(IPEndPoint targetEndPoint, string token)
{
if (NatIntroductionSuccess != null)
NatIntroductionSuccess(targetEndPoint, token);
}
}
///
/// Module for UDP NAT Hole punching operations. Can be accessed from NetManager
///
public sealed class NatPunchModule
{
struct RequestEventData
{
public IPEndPoint LocalEndPoint;
public IPEndPoint RemoteEndPoint;
public string Token;
}
struct SuccessEventData
{
public IPEndPoint TargetEndPoint;
public string Token;
}
private readonly NetSocket _socket;
private readonly Queue _requestEvents;
private readonly Queue _successEvents;
private const byte HostByte = 1;
private const byte ClientByte = 0;
public const int MaxTokenLength = 256;
private INatPunchListener _natPunchListener;
internal NatPunchModule(NetSocket socket)
{
_socket = socket;
_requestEvents = new Queue();
_successEvents = new Queue();
}
public void Init(INatPunchListener listener)
{
_natPunchListener = listener;
}
public void NatIntroduce(
IPEndPoint hostInternal,
IPEndPoint hostExternal,
IPEndPoint clientInternal,
IPEndPoint clientExternal,
string additionalInfo)
{
NetDataWriter dw = new NetDataWriter();
//First packet (server)
//send to client
dw.Put((byte)PacketProperty.NatIntroduction);
dw.Put(ClientByte);
dw.Put(hostInternal);
dw.Put(hostExternal);
dw.Put(additionalInfo, MaxTokenLength);
SocketError errorCode = 0;
_socket.SendTo(dw.Data, 0, dw.Length, clientExternal, ref errorCode);
//Second packet (client)
//send to server
dw.Reset();
dw.Put((byte)PacketProperty.NatIntroduction);
dw.Put(HostByte);
dw.Put(clientInternal);
dw.Put(clientExternal);
dw.Put(additionalInfo, MaxTokenLength);
_socket.SendTo(dw.Data, 0, dw.Length, hostExternal, ref errorCode);
}
public void PollEvents()
{
if (_natPunchListener == null)
return;
lock (_successEvents)
{
while (_successEvents.Count > 0)
{
var evt = _successEvents.Dequeue();
_natPunchListener.OnNatIntroductionSuccess(evt.TargetEndPoint, evt.Token);
}
}
lock (_requestEvents)
{
while (_requestEvents.Count > 0)
{
var evt = _requestEvents.Dequeue();
_natPunchListener.OnNatIntroductionRequest(evt.LocalEndPoint, evt.RemoteEndPoint, evt.Token);
}
}
}
public void SendNatIntroduceRequest(IPEndPoint masterServerEndPoint, string additionalInfo)
{
//prepare outgoing data
NetDataWriter dw = new NetDataWriter();
string networkIp = NetUtils.GetLocalIp(LocalAddrType.IPv4);
if (string.IsNullOrEmpty(networkIp))
{
networkIp = NetUtils.GetLocalIp(LocalAddrType.IPv6);
}
IPEndPoint localEndPoint = NetUtils.MakeEndPoint(networkIp, _socket.LocalPort);
dw.Put((byte)PacketProperty.NatIntroductionRequest);
dw.Put(localEndPoint);
dw.Put(additionalInfo, MaxTokenLength);
//prepare packet
SocketError errorCode = 0;
_socket.SendTo(dw.Data, 0, dw.Length, masterServerEndPoint, ref errorCode);
}
private void HandleNatPunch(IPEndPoint senderEndPoint, NetDataReader dr)
{
byte fromHostByte = dr.GetByte();
if (fromHostByte != HostByte && fromHostByte != ClientByte)
{
//garbage
return;
}
//Read info
string additionalInfo = dr.GetString(MaxTokenLength);
NetDebug.Write(NetLogLevel.Trace, "[NAT] punch received from {0} - additional info: {1}", senderEndPoint, additionalInfo);
//Release punch success to client; enabling him to Connect() to msg.Sender if token is ok
lock (_successEvents)
{
_successEvents.Enqueue(new SuccessEventData { TargetEndPoint = senderEndPoint, Token = additionalInfo });
}
}
private void HandleNatIntroduction(NetDataReader dr)
{
// read intro
byte hostByte = dr.GetByte();
IPEndPoint remoteInternal = dr.GetNetEndPoint();
IPEndPoint remoteExternal = dr.GetNetEndPoint();
string token = dr.GetString(MaxTokenLength);
NetDebug.Write(NetLogLevel.Trace, "[NAT] introduction received; we are designated " + (hostByte == HostByte ? "host" : "client"));
NetDataWriter writer = new NetDataWriter();
// send internal punch
writer.Put((byte)PacketProperty.NatPunchMessage);
writer.Put(hostByte);
writer.Put(token);
SocketError errorCode = 0;
_socket.SendTo(writer.Data, 0, writer.Length, remoteInternal, ref errorCode);
NetDebug.Write(NetLogLevel.Trace, "[NAT] internal punch sent to " + remoteInternal);
// send external punch
writer.Reset();
writer.Put((byte)PacketProperty.NatPunchMessage);
writer.Put(hostByte);
writer.Put(token);
if (hostByte == HostByte)
{
_socket.Ttl = 2;
_socket.SendTo(writer.Data, 0, writer.Length, remoteExternal, ref errorCode);
_socket.Ttl = NetConstants.SocketTTL;
}
else
{
_socket.SendTo(writer.Data, 0, writer.Length, remoteExternal, ref errorCode);
}
NetDebug.Write(NetLogLevel.Trace, "[NAT] external punch sent to " + remoteExternal);
}
private void HandleNatIntroductionRequest(IPEndPoint senderEndPoint, NetDataReader dr)
{
IPEndPoint localEp = dr.GetNetEndPoint();
string token = dr.GetString(MaxTokenLength);
lock (_requestEvents)
{
_requestEvents.Enqueue(new RequestEventData
{
LocalEndPoint = localEp,
RemoteEndPoint = senderEndPoint,
Token = token
});
}
}
internal void ProcessMessage(IPEndPoint senderEndPoint, NetPacket packet)
{
var dr = new NetDataReader(packet.RawData, NetConstants.HeaderSize, packet.Size);
switch (packet.Property)
{
case PacketProperty.NatIntroductionRequest:
//We got request and must introduce
HandleNatIntroductionRequest(senderEndPoint, dr);
break;
case PacketProperty.NatIntroduction:
//We got introduce and must punch
HandleNatIntroduction(dr);
break;
case PacketProperty.NatPunchMessage:
//We got punch and can connect
HandleNatPunch(senderEndPoint, dr);
break;
}
}
}
}