/// <summary>Stops allowing traversal of routers/firewalls to this host.</summary> /// <remarks>This will stop this NAT traversal session.</remarks> public void Disable() { if (!disabled) { // stop notification thread by sending a message from this local endpoint to itself byte[] emptyDatagram = new byte[0]; PortSpoofing.Send(LocalEndPoint, LocalEndPoint, emptyDatagram); disabled = true; } }
/// <summary>Notifies NAT traversal service that a player has joined (for statistical purpose).</summary> /// <param name="serverPrivatePort">The private port of the host.</param> /// <param name="clientIp">The IP address of the client.</param> /// <param name="clientPort">The port of the client.</param> public unsafe static void NotifyPlayerHasJoined(INatTraversalSession session, IPAddress clientIp, int clientPort) { try { // send "player_has_joined" message, from the same port as DirectPlay (spoofed) // connect to service to determine end points using (Socket dummySocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) { dummySocket.Connect(natTraversalServiceUrl, natTraversalServicePort); IPEndPoint serviceEndPoint = (IPEndPoint)dummySocket.RemoteEndPoint; IPEndPoint localEndPoint = (IPEndPoint)dummySocket.LocalEndPoint; IPEndPoint spoofedEndPoint = new IPEndPoint(localEndPoint.Address, ((NatTraversalSession)session).PrivatePort); MsgPlayerHasJoined msgPlayerHasJoined = new MsgPlayerHasJoined(session.SessionId, clientIp, (ushort)clientPort); byte[] datagram = new byte[sizeof(MsgPlayerHasJoined)]; for (int i = 0; i < datagram.Length; ++i) { datagram[i] = ((byte *)&msgPlayerHasJoined)[i]; } PortSpoofing.Send(spoofedEndPoint, serviceEndPoint, datagram); } } catch { } }
private static void keepAlive(object state) { KeepAliveSettings settings = (KeepAliveSettings)state; PortSpoofing.Send(settings.SpoofedLocalEndPoint, settings.ServiceEndPoint, settings.Datagram); }
private static unsafe void notificationLoop(object natTraversalSession) { NatTraversalSession session = (NatTraversalSession)natTraversalSession; Timer keepAliveTimer = null; try { IPEndPoint serviceEndPoint = null; // connect to service to open a port on the NAT devices using (Socket notificationSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) { notificationSocket.Connect(natTraversalServiceUrl, natTraversalServicePort); serviceEndPoint = (IPEndPoint)notificationSocket.RemoteEndPoint; session.LocalEndPoint = (IPEndPoint)notificationSocket.LocalEndPoint; } // create a new socket in listening mode using the port just opened using (Socket notificationSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) { notificationSocket.ReceiveTimeout = 1000; notificationSocket.Bind(session.LocalEndPoint); // send "open notification channel" message { MsgOpenNotificationChannel msg = new MsgOpenNotificationChannel(session.SessionId); byte[] datagram = new byte[sizeof(MsgOpenNotificationChannel)]; for (int i = 0; i < datagram.Length; ++i) { datagram[i] = ((byte *)&msg)[i]; } notificationSocket.SendTo(datagram, serviceEndPoint); } while (true) { // block until a message is received or 1 second has elapsed EndPoint any = new IPEndPoint(IPAddress.Any, 0); byte[] bytesReceived = new byte[1024]; int bytesReceivedLength = notificationSocket.ReceiveFrom(bytesReceived, ref any); IPEndPoint remoteEndPoint = (IPEndPoint)any; // test for stop condition: a message send from this local endpoint to itself if (remoteEndPoint.Equals(session.LocalEndPoint)) { break; } else if (remoteEndPoint.Equals(serviceEndPoint)) { // handle the message if (bytesReceivedLength >= 3 && bytesReceived[0] == 'z' && bytesReceived[1] == 't') { switch (bytesReceived[2]) { case (byte)MessageType.AckOpenNotificationChannel: if (bytesReceivedLength == sizeof(MsgAckOpenNotificationChannel)) { MsgAckOpenNotificationChannel msg = new MsgAckOpenNotificationChannel(); for (int i = 0; i < sizeof(MsgAckOpenNotificationChannel); ++i) { ((byte *)&msg)[i] = bytesReceived[i]; } // handle MsgAckOpenNotificationChannel // send "host" message, from the same port as DirectPlay (spoofed) IPEndPoint spoofedEndPoint = new IPEndPoint(session.LocalEndPoint.Address, session.PrivatePort); MsgHost msgHost = new MsgHost(session.SessionId, session.LocalEndPoint.Address, (ushort)session.PrivatePort); byte[] datagram = new byte[sizeof(MsgHost)]; for (int i = 0; i < datagram.Length; ++i) { datagram[i] = ((byte *)&msgHost)[i]; } PortSpoofing.Send(spoofedEndPoint, serviceEndPoint, datagram); } break; case (byte)MessageType.AckHost: if (bytesReceivedLength == sizeof(MsgAckHost)) { if (keepAliveTimer == null) // ignore if already handled { MsgAckHost msg = new MsgAckHost(); for (int i = 0; i < sizeof(MsgAckHost); ++i) { ((byte *)&msg)[i] = bytesReceived[i]; } // handle MsgAckHost // record public address/port session.PublicIpAddress = new IPAddress((uint)IPAddress.HostToNetworkOrder((int)msg.HostPublicIp)).ToString(); session.PublicPort = msg.HostPublicPort; // notify main thread that NAT traversal is enabled session.Enabled = true; if (session.NatTraversalEnabled != null) { session.NatTraversalEnabled.Set(); } // set reception so it can't time-out notificationSocket.ReceiveTimeout = 0; // send a keep alive message every 19 seconds KeepAliveSettings settings = new KeepAliveSettings(); settings.SpoofedLocalEndPoint = session.LocalEndPoint; settings.ServiceEndPoint = serviceEndPoint; MsgKeepAlive msgKeepAlive = new MsgKeepAlive(session.SessionId); settings.Datagram = new byte[sizeof(MsgKeepAlive)]; for (int i = 0; i < settings.Datagram.Length; ++i) { settings.Datagram[i] = ((byte *)&msgKeepAlive)[i]; } keepAliveTimer = new Timer(new TimerCallback(keepAlive), settings, 19000, 19000); } } break; case (byte)MessageType.Connect: if (bytesReceivedLength == sizeof(MsgConnect)) { MsgConnect msg = new MsgConnect(); for (int i = 0; i < sizeof(MsgConnect); ++i) { ((byte *)&msg)[i] = bytesReceived[i]; } // handle MsgConnect // punch hole targeting client's public and private addresses IPAddress publicClientAddress = new IPAddress((uint)IPAddress.HostToNetworkOrder((int)msg.ClientPublicIp)); IPAddress privateClientAddress = new IPAddress((uint)IPAddress.HostToNetworkOrder((int)msg.ClientPrivateIp)); IPEndPoint spoofedEndPoint = new IPEndPoint(session.LocalEndPoint.Address, session.PrivatePort); byte[] emptyDatagram = new byte[0]; PortSpoofing.Send(spoofedEndPoint, new IPEndPoint(publicClientAddress, msg.ClientPublicPort), emptyDatagram); PortSpoofing.Send(spoofedEndPoint, new IPEndPoint(privateClientAddress, msg.ClientPrivatePort), emptyDatagram); // send acknowledgement through notification channel MsgAckConnect msgAckConnect = new MsgAckConnect(session.SessionId, publicClientAddress, msg.ClientPublicPort); byte[] datagram = new byte[sizeof(MsgAckConnect)]; for (int i = 0; i < datagram.Length; ++i) { datagram[i] = ((byte *)&msgAckConnect)[i]; } notificationSocket.SendTo(datagram, serviceEndPoint); } break; } } } } } } catch { if (!session.Enabled && session.NatTraversalEnabled != null) { session.NatTraversalEnabled.Set(); } } finally { if (keepAliveTimer != null) { keepAliveTimer.Change(Timeout.Infinite, Timeout.Infinite); keepAliveTimer.Dispose(); } } }