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 _peerPublicKeyRegistry; private IPEndPoint _serverEndPoint; private RSACng _serverPublicKey; private SocketAsyncEventArgs _saeReceive; private Guid _localId; private RSACng _localPrivateKey; 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(); _peerPublicKeyRegistry = Directory.GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PeerPublicKey")) .ToDictionary( s => new Guid(Path.GetFileNameWithoutExtension(s)), TransferCodec.LoadKey ); } //------------- ui event ------------- private void ExampleForm_Shown(object sender, EventArgs e) { _serverPublicKey = new RSACng(); _serverPublicKey.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 = _peerPublicKeyRegistry.Keys.ToArray(); } private void StartButton_Click(object sender, EventArgs e) { Log("Starting..."); _serverEndPoint = ServerIEndPointTextBox.Text.ParseToIpEndPointV4(); _localId = new Guid(PeerKetyDropDown.Text); var peerPrivateKeyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PrivateKey", PeerKetyDropDown.Text + ".txt"); _localPrivateKey = TransferCodec.LoadKey(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(); _localPrivateKey?.Dispose(); _localSocket = null; _saeReceive = null; _localPrivateKey = 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(_serverEndPoint, _localPrivateKey, _serverPublicKey, _localId, _keepAliveMsg); } 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(_serverEndPoint, _localPrivateKey, _serverPublicKey, _localId, msg); } 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(_localPrivateKey, _peerPublicKeyRegistry[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(_localPrivateKey, _serverPublicKey, _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 == _peerPublicKeyRegistry.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(msg.PeerEndPoint, _localPrivateKey, peerKey, _localId, connMsg); } _localSocket.SendExchangeMessageTo(_serverEndPoint, _localPrivateKey, _serverPublicKey, _localId, msgReply); 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(msg.PeerEndPoint, _localPrivateKey, _peerPublicKeyRegistry[msg.PeerId.Value], _localId, connMsg); } 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 == _peerPublicKeyRegistry.TryGetValue(peerId, out var peerPublicKey)) { throw new InvalidDataException("PEER ERROR: INVALID PEER ID"); } var msgData = TransferCodec.DecodeData(_localPrivateKey, peerPublicKey, _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(_saeReceive.RemoteEndPoint, _localPrivateKey, peerPublicKey, _localId, reply); } } 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(); } } }