/// <summary> /// TODO: Documentation Constructor /// </summary> /// <param name="message"></param> public StunClassicRFC(StunMessage message) { this.boundMessage = message; }
/// <summary>查询</summary> /// <param name="request"></param> /// <param name="remoteEndPoint"></param> /// <returns></returns> public StunMessage Query(StunMessage request, IPEndPoint remoteEndPoint) { EnsureSocket(); return Query(Socket, request, remoteEndPoint); }
StunMessage Query(ISocketClient client, StunMessage request, IPEndPoint remoteEndPoint) { Byte[] buffer = null; try { if (client.Local.IsTcp) { // Tcp协议不支持更换IP或者端口 if (request.ChangeIP || request.ChangePort) return null; //if (!client.Connected) client.Connect(remoteEndPoint); //client.Send(request.ToArray()); } //else // client.SendTo(request.ToArray(), remoteEndPoint); WriteLog("查询 {0} =>{1}", request, remoteEndPoint); client.Remote.EndPoint = remoteEndPoint; client.Send(request.ToArray()); buffer = client.Receive(); if (buffer == null || buffer.Length == 0) return null; } catch { return null; } var rs = StunMessage.Read(new MemoryStream(buffer)); //if (rs != null && rs.Type != StunMessageType.BindingResponse) return null; if (rs == null) return null; // 不是同一个会话不要 if (rs.TransactionID.CompareTo(request.TransactionID) != 0) return null; // 不是期望的响应不要 if (rs.Type != (StunMessageType)((UInt16)request.Type | 0x0100)) return null; return rs; }
///// <summary>在指定服务器上执行查询</summary> ///// <param name="host"></param> ///// <param name="port"></param> ///// <returns></returns> //public StunResult QueryWithServer(String host, Int32 port = 3478) //{ // try // { // return QueryWithServer(NetHelper.ParseAddress(host), port); // } // catch { return null; } //} /// <summary>在指定服务器上执行查询</summary> /// <param name="address"></param> /// <param name="port"></param> /// <returns></returns> public StunResult QueryWithServer(IPAddress address, Int32 port) { EnsureSocket(); var client = Socket as ISocketClient; var remote = new IPEndPoint(address, port); // Test I // 测试网络是否畅通 var msg = new StunMessage(); msg.Type = StunMessageType.BindingRequest; var rs = Query(client, msg, remote); // UDP blocked. if (rs == null) return new StunResult(StunNetType.Blocked, null); WriteLog("服务器:{0}", rs.ServerName); WriteLog("映射地址:{0}", rs.MappedAddress); WriteLog("源地址:{0}", rs.SourceAddress); WriteLog("新地址:{0}", rs.ChangedAddress); var remote2 = rs.ChangedAddress; // Test II // 要求改变IP和端口 msg.ChangeIP = true; msg.ChangePort = true; msg.ResetTransactionID(); // 如果本地地址就是映射地址,表示没有NAT。这里的本地地址应该有问题,永远都会是0.0.0.0 //if (client.LocalEndPoint.Equals(test1response.MappedAddress)) var pub = rs.MappedAddress; if (pub != null && client.Local.Port == pub.Port && pub.Address.IsLocal()) { // 要求STUN服务器从另一个地址和端口向当前映射端口发送消息。如果收到,表明是完全开放网络;如果没收到,可能是防火墙阻止了。 rs = Query(client, msg, remote); // Open Internet. if (rs != null) return new StunResult(StunNetType.OpenInternet, pub); // Symmetric UDP firewall. return new StunResult(StunNetType.SymmetricUdpFirewall, pub); } else { rs = Query(client, msg, remote); if (rs != null && pub == null) pub = rs.MappedAddress; // Full cone NAT. if (rs != null) return new StunResult(StunNetType.FullCone, pub); // Test II msg.ChangeIP = false; msg.ChangePort = false; msg.ResetTransactionID(); // 如果是Tcp,这里需要准备第二个重用的Socket if (client.Local.IsTcp) { EnsureSocket2(); client = Socket2 as ISocketClient; } rs = Query(client, msg, remote2); // 如果第二服务器没响应,重试 if (rs == null) rs = Query(client, msg, remote2); if (rs != null && pub == null) pub = rs.MappedAddress; if (rs == null) return new StunResult(StunNetType.Blocked, pub); // 两次映射地址不一样,对称网络 if (!rs.MappedAddress.Equals(pub)) return new StunResult(StunNetType.Symmetric, pub); // Test III msg.ChangeIP = false; msg.ChangePort = true; msg.ResetTransactionID(); rs = Query(client, msg, remote2); if (rs != null && pub == null) pub = rs.MappedAddress; // 受限 if (rs != null) return new StunResult(StunNetType.AddressRestrictedCone, pub); // 端口受限 return new StunResult(StunNetType.PortRestrictedCone, pub); } }
/// <summary>获取公网地址</summary> /// <returns></returns> public IPEndPoint GetPublic() { EnsureSocket(); //var socket = Socket.Socket; var msg = new StunMessage(); msg.Type = StunMessageType.BindingRequest; IPEndPoint ep = null; foreach (var item in Servers) { try { ep = NetHelper.ParseEndPoint(item, 3478); } catch { continue; } //Int32 p = item.IndexOf(":"); //if (p > 0) // ep = new IPEndPoint(NetHelper.ParseAddress(item.Substring(0, p)), Int32.Parse(item.Substring(p + 1))); //else // ep = new IPEndPoint(NetHelper.ParseAddress(item), 3478); var rs = Query(Socket, msg, ep); if (rs != null && rs.MappedAddress != null) return rs.MappedAddress; } return null; }
/// <summary> /// TODO: Documentation Constructor /// </summary> /// <param name="message"></param> public TurnRFCs(StunMessage message) { this.boundMessage = message; }
/// <summary> /// TODO: Documentation Constructor /// </summary> /// <param name="message"></param> public StunRFC(StunMessage message) { this.boundMessage = message; }
/// <summary> /// Gets NAT info from STUN server. /// </summary> /// <param name="host">STUN server name or IP.</param> /// <param name="port">STUN server port. Default port is 3478.</param> /// <param name="socket">UDP socket to use.</param> /// <returns>Returns UDP network info.</returns> /// <exception cref="Exception">Throws exception if unexpected error happens.</exception> public static StunResult Query(string host, int port, Socket socket) { if (host == null) { throw new ArgumentNullException(nameof(host)); } if (socket == null) { throw new ArgumentNullException(nameof(socket)); } if (port < 1) { throw new ArgumentException(@"Port value must be >= 1 !"); } if (socket.ProtocolType != ProtocolType.Udp) { throw new ArgumentException(@"Socket must be UDP socket !"); } var remoteEndPoint = new IPEndPoint(Dns.GetHostAddresses(host)[0], port); /* * In test I, the client sends a STUN Binding Request to a server, without any flags set in the * CHANGE-REQUEST attribute, and without the RESPONSE-ADDRESS attribute. This causes the server * to send the response back to the address and port that the request came from. * * In test II, the client sends a Binding Request with both the "change IP" and "change port" flags * from the CHANGE-REQUEST attribute set. * * In test III, the client sends a Binding Request with only the "change port" flag set. * +--------+ | Test | | I | +--------+ | | | V | /\ /\ | N / \ Y / \ Y +--------+ | UDP <-------/Resp\--------->/ IP \------------->| Test | | Blocked \ ? / \Same/ | II | \ / \? / +--------+ \/ \/ | | N | | V | V /\ +--------+ Sym. N / \ | Test | UDP <---/Resp\ | II | Firewall \ ? / +--------+ \ / | \/ | V |Y | /\ /\ | | Symmetric N / \ +--------+ N / \ V | NAT <--- / IP \<-----| Test |<--- /Resp\ Open | \Same/ | I | \ ? / Internet \? / +--------+ \ / \/ \/ | |Y | | | V | Full | Cone | V /\ +--------+ / \ Y | Test |------>/Resp\---->Restricted | III | \ ? / +--------+ \ / \/ |N | Port +------>Restricted | */ try { // Test I var test1 = new StunMessage { Type = StunMessageType.BindingRequest }; var test1Response = DoTransaction(test1, socket, remoteEndPoint, 1600); // UDP blocked. if (test1Response == null) { return(new StunResult(NatType.UdpBlocked, null)); } else { // Test II var test2 = new StunMessage { Type = StunMessageType.BindingRequest, ChangeRequest = new StunChangeRequest(true, true) }; // No NAT. if (socket.LocalEndPoint.Equals(test1Response.MappedAddress)) { var test2Response = DoTransaction(test2, socket, remoteEndPoint, 1600); // Open Internet. if (test2Response != null) { return(new StunResult(NatType.OpenInternet, test1Response.MappedAddress)); } // Symmetric UDP firewall. else { return(new StunResult(NatType.SymmetricUdpFirewall, test1Response.MappedAddress)); } } // NAT else { var test2Response = DoTransaction(test2, socket, remoteEndPoint, 1600); // Full cone NAT. if (test2Response != null) { return(new StunResult(NatType.FullCone, test1Response.MappedAddress)); } else { /* * If no response is received, it performs test I again, but this time, does so to * the address and port from the CHANGED-ADDRESS attribute from the response to test I. */ // Test I(II) var test12 = new StunMessage { Type = StunMessageType.BindingRequest }; var test12Response = DoTransaction(test12, socket, test1Response.ChangedAddress, 1600); if (test12Response == null) { throw new Exception(@"STUN Test I(II) didn't get response !"); } else { // Symmetric NAT if (!test12Response.MappedAddress.Equals(test1Response.MappedAddress)) { return(new StunResult(NatType.Symmetric, test1Response.MappedAddress)); } else { // Test III var test3 = new StunMessage { Type = StunMessageType.BindingRequest, ChangeRequest = new StunChangeRequest(false, true) }; var test3Response = DoTransaction(test3, socket, test1Response.ChangedAddress, 1600); // Restricted if (test3Response != null) { return(new StunResult(NatType.RestrictedCone, test1Response.MappedAddress)); } // Port restricted else { return(new StunResult(NatType.PortRestrictedCone, test1Response.MappedAddress)); } } } } } } } finally { // Junk all late responses. var startTime = DateTime.Now; while (startTime.AddMilliseconds(200) > DateTime.Now) { // We got response. if (socket.Poll(1, SelectMode.SelectRead)) { var receiveBuffer = new byte[512]; socket.Receive(receiveBuffer); } } } }
/// <summary> /// TODO: Documentation turnManager_OnConnectionBindSucceed /// </summary> /// <param name="sender"></param> /// <param name="connectedSocket"></param> /// <param name="receivedMsg"></param> private void turnManager_OnConnectionBindSucceed(object sender, Socket connectedSocket, StunMessage receivedMsg) { if (this.OnTurnConnectionBindSucceed != null) { this.OnTurnConnectionBindSucceed(this, connectedSocket, this.StartingSessionSid, this.StartingSessionRecipient); } if (this.OnConnectionTryEnded != null) { this.OnConnectionTryEnded(this, this.StartingSessionSid); } this.CancelStartingSession(); }
/// <summary> /// TODO: Documentation turnManager_OnConnectionAttemptReceived /// </summary> /// <param name="sender"></param> /// <param name="receivedMsg"></param> private void turnManager_OnConnectionAttemptReceived(object sender, StunMessage receivedMsg) { (sender as TurnManager).ConnectionBind(receivedMsg.Turn.ConnectionId, this.TurnUsername, this.TurnPassword); }
/// <summary> /// TODO: Documentation turnManager_OnAllocateFailed /// </summary> /// <param name="sender"></param> /// <param name="receivedMsg"></param> /// <param name="sentMsg"></param> /// <param name="transactionObject"></param> private void turnManager_OnAllocateFailed(object sender, StunMessage receivedMsg, StunMessage sentMsg, object transactionObject) { if (this.StartingSessionRecipient != null) { if (this.OnConnectionTryEnded != null) { this.OnConnectionTryEnded(this, this.StartingSessionSid); } this.DestroyTurnSession(this.StartingSessionSid); this.CancelStartingSession(); } }
/// <summary> /// TODO: Documentation turnManager_OnAllocateSucceed /// </summary> /// <param name="sender"></param> /// <param name="allocation"></param> /// <param name="sentMsg"></param> /// <param name="receivedMsg"></param> private void turnManager_OnAllocateSucceed(object sender, TurnAllocation allocation, StunMessage sentMsg, StunMessage receivedMsg) { if (this.StartingSessionRecipient != null) { this.turnSessions[this.StartingSessionSid].TurnAllocation = allocation; XmlDocument doc = new XmlDocument(); // Jingle Transport JingleIce jingleIce = new JingleIce(doc) { Pwd = JingleUtilities.GenerateIcePwd, Ufrag = JingleUtilities.GenerateIceUfrag }; if (this.OnIceCandidatesGathering != null) { this.OnIceCandidatesGathering(this, jingleIce, (sender as TurnManager).HostEP, this.turnSessions[this.StartingSessionSid].UseTurnOnly, allocation); } this.localCandidates.Add(this.StartingSessionSid, jingleIce.GetCandidates()); JingleIQ jingleIq = null; // Jingle Description Element jingleDescription = null; String contentName = null; if (this.OnDescriptionGathering != null) { this.OnDescriptionGathering(this, doc, this.StartingSessionSid, ref jingleDescription, ref contentName); } jingleIq = this.JingleManager.SessionRequest(this.StartingSessionRecipient, this.StartingSessionAction, this.StartingSessionSid, contentName, jingleDescription, jingleIce); //this.jingleManager.FindSession(this.StartingSessionSid).Local = jingleIq.Instruction; this.Stream.Write(jingleIq); } }