using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Security.Cryptography; using System.Text; using System.Windows.Forms; namespace UdPunching.ExampleW { public partial class ExampleForm : Form { private const int ReceiveBufferSize = 1500; private static readonly IPEndPoint AnyEndPoint = new IPEndPoint(IPAddress.Any, 0); private readonly IReadOnlyDictionary _peerKeyRegister; private IPEndPoint _serverEndPoint; private RSACryptoServiceProvider _serverKey; private SocketAsyncEventArgs _saeReceive; private Guid _localId; private RSACryptoServiceProvider _localKey; private Socket _localSocket; private IPEndPoint _localPublicEndPoint; private readonly byte[] _keepAliveBuf = new byte[7];// 1flag,1count,1section,4timestamp private readonly ExchangeMessage _keepAliveMsg = new ExchangeMessage { Id = ExchangeMessageId.KeepAliveReq }; //------------- ctor ------------- public ExampleForm() { InitializeComponent(); _peerKeyRegister = Directory.GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PeerPublicKey")) .ToDictionary( s => new Guid(Path.GetFileNameWithoutExtension(s)), p => { var rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(File.ReadAllText(p)); return rsa; } ); } //------------- ui event ------------- private void ExampleForm_Shown(object sender, EventArgs e) { _serverKey = new RSACryptoServiceProvider(); _serverKey.FromXmlString(File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ServerPublicKey.txt"))); var privateKeys = Directory .GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PrivateKey")) .Select(Path.GetFileNameWithoutExtension) .ToArray(); PeerKetyDropDown.DataSource = privateKeys; PeerToKnockDropDown.DataSource = _peerKeyRegister.Keys.ToArray(); } private void StartButton_Click(object sender, EventArgs e) { Log("Starting..."); _serverEndPoint = ServerIEndPointTextBox.Text.ParseToIpEndPointV4(); _localId = new Guid(PeerKetyDropDown.Text); _localKey = new RSACryptoServiceProvider(); var peerPrivateKeyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PrivateKey", PeerKetyDropDown.Text + ".txt"); _localKey.FromXmlString( File.ReadAllText( peerPrivateKeyPath ) ); _localSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); _localSocket.Bind(AnyEndPoint); _saeReceive = new SocketAsyncEventArgs(); _saeReceive.SetBuffer(new byte[ReceiveBufferSize], 0, ReceiveBufferSize); _saeReceive.Completed += ReceiveCompleted; BeginRecv(); KeepAliveTimer.Start(); KeepAliveTimer_Tick(null, null); PeerKetyDropDown.Enabled = false; StartButton.Enabled = false; StopButton.Enabled = true; KnockButton.Enabled = true; Log("Started..."); } private void StopButton_Click(object sender, EventArgs e) { Log("Stopping..."); KeepAliveTimer.Stop(); _localSocket?.Dispose(); _saeReceive?.Dispose(); _localKey?.Dispose(); _localSocket = null; _saeReceive = null; _localKey = null; PeerKetyDropDown.Enabled = true; StartButton.Enabled = true; StopButton.Enabled = false; KnockButton.Enabled = false; SendButton.Enabled = false; Log("Stopped"); } private void KeepAliveTimer_Tick(object sender, EventArgs e) { _keepAliveMsg.TimeStamp = DateTime.Now; _keepAliveMsg.WriteToBuffer(_keepAliveBuf); _localSocket.SendExchangeMessageTo(_localId, _keepAliveMsg, _serverEndPoint, _serverKey); } private void KnockButton_Click(object sender, EventArgs e) { var idToKnock = new Guid(PeerToKnockDropDown.Text); if (idToKnock == _localId) { Log("KNOCK YOUR SELF ???"); return; } var msg = new ExchangeMessage(ExchangeMessageId.PeerKnockReq) { PeerId = idToKnock }; _localSocket.SendExchangeMessageTo(_localId, msg, _serverEndPoint, _serverKey); } private void SendButton_Click(object sender, EventArgs e) { var to = SendToEndPointTextBox.Text.ParseToIpEndPointV4(); var sendMsg = new ExchangeMessage(ExchangeMessageId.DataTransfer); sendMsg.PayloadBytes = Encoding.UTF8.GetBytes(SendContentTextBox.Text); var sendBytes = TransferCodec.Encode(_peerKeyRegister[new Guid(PeerToKnockDropDown.Text)], _localId, sendMsg.ToBytes()); var sent = _localSocket.SendTo(sendBytes, to); } //------------- logic ------------- private void ProcessPacket() { var peerId = TransferCodec.ReadId(_saeReceive.Buffer); if (_saeReceive.RemoteEndPoint.IpEndPointEqualsTo(_serverEndPoint)) { if (BuildInPeerId.Invalid == peerId) throw new InvalidDataException("SERVER ERROR: FAILURE"); if (Guid.Empty != peerId) throw new InvalidDataException("SERVER ERROR: INVALID SERVER PEER ID"); var msgData = TransferCodec.DecodeData(_localKey, _saeReceive.Buffer); var msg = new ExchangeMessage(msgData); switch (msg.Id) { case ExchangeMessageId.KeepAliveAckSessionCreated: _localPublicEndPoint = msg.PeerEndPoint; Log($"Session Created, public endpoint {_localPublicEndPoint}"); Invoke(new Action(() => { PublicEndPointTextBox.Text = msg.PeerEndPoint.ToString(); })); break; case ExchangeMessageId.KeepAliveAckNoChg: break; case ExchangeMessageId.PeerKnockReqRelay: if (false == msg.PeerId.HasValue) { Log($"INVALID {msg.Id} was IGNORED: peer id is required"); break; } ExchangeMessage msgReply; if (false == _peerKeyRegister.TryGetValue(msg.PeerId.Value, out var peerKey)) { Log($"DENIED {msg.Id}: peer id {msg.PeerId.Value}"); msgReply = new ExchangeMessage(ExchangeMessageId.PeerKnockDenied); } else { Log($"ACCEPT {msg.Id}: peer id {msg.PeerId.Value} @ {msg.PeerEndPoint}"); msgReply = new ExchangeMessage(ExchangeMessageId.PeerKnockAck) { PeerId = msg.PeerId }; Log($"SENDING CONNECTION REQ to {msg.PeerId} @ {msg.PeerEndPoint}"); var connMsg = new ExchangeMessage(ExchangeMessageId.PeerKnockConnectionReq); _localSocket.SendExchangeMessageTo(_localId, connMsg, msg.PeerEndPoint, peerKey); } _localSocket.SendExchangeMessageTo(_localId, msgReply, _serverEndPoint, _serverKey); break; case ExchangeMessageId.PeerKnockAckRelay: if (false == msg.PeerId.HasValue) { Log($"INVALID RESPONSE {msg.Id} was IGNORED: peer id is required"); break; } Log($"KNOCK SUCCESS by {msg.PeerId} peer endpont is {msg.PeerEndPoint}"); Invoke(new Action(() => { SendToEndPointTextBox.Text = msg.PeerEndPoint.ToString(); SendButton.Enabled = true; })); { Log($"SENDING CONNECTION REQ to {msg.PeerId} @ {msg.PeerEndPoint}"); var connMsg = new ExchangeMessage(ExchangeMessageId.PeerKnockConnectionReq); _localSocket.SendExchangeMessageTo(_localId, connMsg, msg.PeerEndPoint, _peerKeyRegister[msg.PeerId.Value]); } break; case ExchangeMessageId.PeerKnockAckRelayed: Log($"ACCEPT SENT {msg.PeerId}"); break; case ExchangeMessageId.PeerKnockReqErrPeerNoAvailable: Log($"KNOCK FAIL: {msg.PeerId}"); break; case ExchangeMessageId.PeerKnockReqRelayed: Log($"KNOCK SENT {msg.PeerId}"); break; case ExchangeMessageId.PeerKnockDeniedRelay: Log($"KNOCK DENIED by {msg.PeerId}"); break; default: throw new ArgumentOutOfRangeException("msg.Id", "SERVER ERROR: NO EXCEPTED MESSAGE FROM SERVER," + msg.Id); } } else { if (BuildInPeerId.Invalid == peerId || BuildInPeerId.Server == peerId || false == _peerKeyRegister.TryGetValue(peerId, out var peerKey)) { throw new InvalidDataException("PEER ERROR: INVALID PEER ID"); } var msgData = TransferCodec.DecodeData(_localKey, _saeReceive.Buffer); var msg = new ExchangeMessage(msgData); var reply = new ExchangeMessage { TimeStamp = DateTime.Now }; if (false == msg.TimeStamp.HasValue || Math.Abs((DateTime.Now - msg.TimeStamp.Value).TotalSeconds) > 10) { Log($"TIMESTAMP ERROR from peer {peerId}"); reply.Id = ExchangeMessageId.ErrTimeStamp; } else { switch (msg.Id) { case ExchangeMessageId.PeerKnockConnectionReq: reply.Id = ExchangeMessageId.PeerKnockConnectionAck; break; case ExchangeMessageId.DataTransfer: reply.Id = ExchangeMessageId.DataTransferAck; var payloadString = Encoding.UTF8.GetString(msg.PayloadBytes); Log($"DATA FROM {peerId}:{payloadString}"); reply.PayloadBytes = payloadString.Length.ToLeInt16Bytes(); break; case ExchangeMessageId.DataTransferAck: Log($"DATA ACK LEN:{msg.PayloadBytes.ReadLeInt16()} FROM {peerId}"); return; default: Log($"RECV {msg.Id} FROM {peerId} @ {_saeReceive.RemoteEndPoint}"); return; } } _localSocket.SendExchangeMessageTo(_localId, reply, _saeReceive.RemoteEndPoint, peerKey); } } private void ReceiveCompleted(object sender, SocketAsyncEventArgs e) { if (null == _saeReceive) return; if (_saeReceive.SocketError == SocketError.Success) { try { ProcessPacket(); } catch (InvalidDataException exception) { Log($"ERROR ProcessPacket:{exception.Message}"); } catch (Exception exception) { Log($"ERROR ProcessPacket:{exception}"); } } else { Log($"ERROR SOCKET:{_saeReceive.SocketError}"); } if (null == _localSocket) return; if (false == _localSocket.ReceiveFromAsync(_saeReceive)) ReceiveCompleted(null, null); } private void BeginRecv() { _saeReceive.RemoteEndPoint = AnyEndPoint; if (false == _localSocket.ReceiveFromAsync(_saeReceive)) { ReceiveCompleted(null, _saeReceive); } } //------------- util ------------- private void Log(string content) { if (InvokeRequired) { Invoke(new Action(Log), content); return; } RecvTextBox.Text = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} {content}" + $"{Environment.NewLine}" + RecvTextBox.Text; RecvTextBox.Refresh(); } } }