/// <summary> /// Take care of a packet of UDP data /// </summary> /// <param name="payload"></param> internal static void HandlePacket(byte[] payload) { ushort ipHeaderLength = (ushort)((payload[14] & 0x0f) * 4); byte[] SourceIP = Utility.ExtractRangeFromArray(payload, 26, 4); byte[] DestinationIP = Utility.ExtractRangeFromArray(payload, 30, 4); byte[] SourcePort = Utility.ExtractRangeFromArray(payload, 34, 2); byte[] DestinationPort = Utility.ExtractRangeFromArray(payload, 36, 2); var socket = new Connection() { RemoteIP = SourceIP, RemotePort = SourcePort.ToShort(), LocalPort = DestinationPort.ToShort() }; ushort udpDataLength = (ushort)((new byte[2] { payload[38], payload[39] }).ToShort() - 8); //We got some data!? if (udpDataLength > 0) Networking.Adapter.FireUdpPacketEvent(Utility.ExtractRangeFromArray(payload, 42, udpDataLength), socket); }
internal static void HandlePacket(byte[] payload) { bool SYN = (payload[47] & (1<<1)) != 0; // Synchronize bool ACK = (payload[47] & (1<<4)) != 0; // Acknoledge bool FIN = (payload[47] & (1<<0)) != 0; // Finish bool PSH = (payload[47] & (1<<3)) != 0; // Push bool RST = (payload[47] & (1<<2)) != 0; // Reset byte[] SourceIP = Utility.ExtractRangeFromArray(payload, 26, 4); byte[] SourcePort = Utility.ExtractRangeFromArray(payload, 34, 2); byte[] LocalPort = Utility.ExtractRangeFromArray(payload, 36, 2); ulong connectionID = GenerateConnectionID(payload); Connection con = null; //TODO: Validate TCP checksum and reject packet if invalid! uint packetSeqNumber = Utility.ExtractRangeFromArray(payload, 38, 4).ToInt(); // Handle a new request // if this is a new connection SYN, add to the Connections collection if (SYN && !ACK) // new connection { //Debug.WriteLine("SYN"); if (Connections.Contains(connectionID) && (Connections[connectionID] as Connection).IsOpen) Connections.Remove(connectionID); // remove old connection? // Prune old Connections var keys = new ulong[Connections.Count]; Connections.Keys.CopyTo(keys, 0); foreach (var key in keys) { con = Connections[key] as Connection; if (Microsoft.SPOT.Hardware.Utility.GetMachineTime().Subtract((Connections[key] as Connection).LastActivity).Seconds > ConnectionIdleLimit) { con.isClosing = true; //Adapter.StopListeningToPort(con.LocalPort); //Debug.WriteLine("FIN -- Closing idle connection"); con.SeqNumber++; con.SendAck(false, true); // SendAck Fin/Ack Connections.Remove(key); } } if (!Connections.Contains(connectionID)) Connections.Add(connectionID, new Connection()); con = Connections[connectionID] as Connection; con.RemoteIP = SourceIP; con.RemotePort = SourcePort.ToShort(); con.RemoteMac = Utility.ExtractRangeFromArray(payload, 6, 6); con.LocalPort = LocalPort.ToShort(); con.SeqNumber = packetSeqNumber + 1; con.StartSeqNumber = packetSeqNumber; con.AckNumber = 2380; //Utility.ExtractRangeFromArray(payload, 42, 4).ToInt(), //TODO: this should be a random number initially? con.WindowSize = 1024; //Utility.ExtractRangeFromArray(payload, 48, 2).ToShort() con.ReadyForRequest = true; con.SendAck(true); // Syn/Ack con.AckNumber++; con.IsOpen = true; // This needs to be last because a call to the Connection.Open() may be blocked until this property gets set! } else if (Connections.Contains(connectionID) && (ACK || FIN || PSH || RST)) { con = Connections[connectionID] as Connection; ushort totalLength = Utility.ExtractRangeFromArray(payload, 16, 2).ToShort(); ushort ipHeaderLength = (ushort)((payload[14] & 0x0f) * 4); ushort tcpHeaderLength = (ushort)((payload[26 + ipHeaderLength] >> 4) * 4); if (totalLength + 14 > payload.Length) { // No Good -- Does not account for 0 padding? Debug.WriteLine("Bad packet size detected? " + totalLength.ToString() + "/" + payload.Length.ToString()); return; } //Debug.WriteLine("1 - con.seqnum = " + con.SeqNumber); con.SeqNumber += (uint)(totalLength - (tcpHeaderLength + ipHeaderLength)); con.WindowSize -= (ushort)(totalLength - (tcpHeaderLength + ipHeaderLength)); //Debug.WriteLine("2 - con.seqnum = " + con.SeqNumber); if (PSH) { //Debug.WriteLine("PSH"); con.SendAck(); // PSH indicates we want an ACK after this packet? } else if (SYN && ACK) { con.SeqNumber = packetSeqNumber + 1; con.StartSeqNumber = packetSeqNumber; con.AckNumber++; con.SendAck(); con.IsOpen = true; return; } else if ((FIN || RST) && ACK) { // Debug.WriteLine("FIN/RST + ACK"); con.isClosing = true; //Adapter.StopListeningToPort(con.LocalPort); con.SeqNumber++; con.SendAck(); // This is an ACKnowledgement that the connection is now closed, so delete it. Connections.Remove(connectionID); return; } else if (FIN) { con.isClosing = true; //Adapter.StopListeningToPort(con.LocalPort); // Debug.WriteLine("FIN"); con.SeqNumber++; con.SendAck(false, true); return; } else if (RST) { con.isClosing = true; //Adapter.StopListeningToPort(con.LocalPort); // Debug.WriteLine("FIN"); con.SeqNumber++; //con.SendAck(false, true); return; } else if (ACK && con.isClosing) { // Debug.WriteLine("ACK + Closing"); // This is an ACKnowledgement that the connection is now closed, so delete it. //Adapter.StopListeningToPort(con.LocalPort); Connections.Remove(connectionID); return; } if (Adapter.VerboseDebugging) Debug.WriteLine("Check for data"); //We got some data!? if ((totalLength - (tcpHeaderLength + ipHeaderLength)) > 0) { byte[] segment = Utility.ExtractRangeFromArray(payload, (14 + ipHeaderLength + tcpHeaderLength), (totalLength - (tcpHeaderLength + ipHeaderLength))); if (Adapter.VerboseDebugging) Debug.WriteLine("got some data, psn: " + packetSeqNumber.ToString() + ", ssn: " + con.StartSeqNumber.ToString() + ", header delim: " + segment.Locate(HttpRequest.HeaderDelimiter).ToString()); Networking.Adapter.FireTcpPacketEvent(segment, packetSeqNumber - con.StartSeqNumber, con); // TCP events always fire con.FireOnConnectionPacketReceived(new Packet(PacketType.TCP) { SequenceNumber = packetSeqNumber - con.StartSeqNumber, Content = segment, Socket = con }); // Filters out anything that is not a GET or POST Http VERB (I did a byte comparison to avoid utf decoding exceptions, since we don't know that we actually have text yet) //if (packetSeqNumber - con.StartSeqNumber == 1) if (segment.Length < 10 || !(segment[0] == 0x47 && segment[1] == 0x45 && segment[2] == 0x54) && !(segment[0] == 0x50 && segment[1] == 0x4F && segment[2] == 0x53 && segment[3] == 0x54)) return; // if it is not a get, then we won't handle it through the HTTP Request Handler //if (packetSeqNumber - con.StartSeqNumber == 1) // && segment.Locate(HttpRequest.HeaderDelimiter) > -1) if (con.ReadyForRequest) { // get the TCP checksum from the current packet and make sure it does not match the last request for we start... byte[] lrc = Utility.ExtractRangeFromArray(payload, (30 + ipHeaderLength), 2); if (con.LastRequestChecksum.BytesEqual(lrc)) { if (Adapter.VerboseDebugging) Debug.WriteLine("Retransmission of Request Ignored!"); } else { con.LastRequestChecksum = lrc; con.ReadyForRequest = false; Networking.Adapter.FireHttpPacketEvent(segment, con); } } } } else if ((FIN || RST) && ACK) { //Debug.WriteLine("Handling RST for a connection that no longer exists!!!!!!!!!"); con = new Connection(); con.RemoteIP = SourceIP; con.RemotePort = SourcePort.ToShort(); con.RemoteMac = Utility.ExtractRangeFromArray(payload, 6, 6); con.LocalPort = LocalPort.ToShort(); con.SeqNumber = Utility.ExtractRangeFromArray(payload, 38, 4).ToInt(); con.AckNumber = Utility.ExtractRangeFromArray(payload, 42, 4).ToInt(); con.isClosing = true; //Adapter.StopListeningToPort(con.LocalPort); con.SendAck(); return; } }
public void SendAsync() { // if _con is null, create and open a new connection. with a DNS lookup of host if necessary if (_con == null) { var remoteIp = DNS.Lookup(Host); _con = new Connection() { RemoteIP = remoteIp }; } var r = AssembleRequest(); _con.SendAsync(r, 0, (short)r.Length); }
/// <summary> /// A synchronous call to send an HTTP Request and Receive a Response -- this call will block (until the timeout) while trying to get the result /// </summary> /// <param name="timeout">Time out in seconds</param> /// <param name="RetrieveHeaderOnly">Set this to true if you don't need the content of the response (which can consume a lot of memory)</param> /// <returns>And HttpResponse object OR a null if it timeout happened</returns> public HttpResponse Send(ushort timeout = 5, bool ReturnHeaderOnly = false) { this.omitContent = ReturnHeaderOnly; // if _con is null, create and open a new connection. with a DNS lookup of host if necessary if (_con == null) { var remoteIp = DNS.Lookup(Host); _con = new Connection() { RemoteIP = remoteIp }; } var r = AssembleRequest(); _responseToSend = null; _con.OnConnectionPacketReceived += _con_OnConnectionPacketReceived; responseWaitHandle.Reset(); // This will release an Open() call waiting for the connection! _con.SendAsync(r, 0, (short)r.Length); //Wait for response or timeout responseWaitHandle.WaitOne(timeout * 1000, true); _con.OnConnectionPacketReceived -= _con_OnConnectionPacketReceived; return _responseToSend; }
/// <summary> /// A New Http Request /// </summary> /// <param name="tcpContent">The data content of the TCP packet</param> /// <param name="socket"></param> internal HttpRequest(byte[] tcpContent, Connection socket) { // Parse the packet into the various properties... _con = socket; Headers = new Hashtable(); // VERY worrisome. I have seen the last 4 bytes be wrong twice now. The second time I could see the packet was transmitted correctly in Wireshark, but the last 4 bytes were wrong as produced by the ReceiveFrame() method... if (!Utility.ExtractRangeFromArray(tcpContent, tcpContent.Length - 4, 4).BytesEqual(HeaderDelimiter)) Debug.WriteLine("This should never happen! "); tcpContent.Overwrite(tcpContent.Length - 4, HeaderDelimiter); // ugly workaround for possible bug... should not be necessary var delimiterLocation = tcpContent.Locate(HeaderDelimiter); var firstLineLocation = tcpContent.Locate(CrLf); if (firstLineLocation < 12) throw new Exception("Malformed HTTP Request."); var firstLine = new string(Encoding.UTF8.GetChars(tcpContent, 0, firstLineLocation)); RequestType = firstLine.Split(' ')[0].Trim().ToUpper(); Path = HttpUtility.UrlDecode(firstLine.Split(' ')[1].Trim(), false); int colonLocation = -1; int start = firstLineLocation; bool malformed = false; // Parse all the header keys and values for (int i = firstLineLocation; i <= delimiterLocation; i++) { if (tcpContent[i] == 0x3A && colonLocation == -1) colonLocation = i; if (tcpContent[i] > 0x7E || tcpContent[i] < 0x09) malformed = true; if (tcpContent[i] == 0x0D || tcpContent[i] == 0x0A) { if (colonLocation > start && !malformed) { // By handling the exception at each parameter, we can salvage the other headers if one contains an invalid character // Although, we should be avoiding any exceptions with the malformed flag. try { Headers.Add(new string(Encoding.UTF8.GetChars(tcpContent, start, colonLocation - start)).Trim(), new string(Encoding.UTF8.GetChars(tcpContent, colonLocation + 1, i - colonLocation)).Trim()); } catch { } } colonLocation = -1; start = i + 1; malformed = false; } } // Parse all the header keys and values //foreach (var aLine in new string(Encoding.UTF8.GetChars(tcpContent, firstLineLocation, (delimiterLocation - firstLineLocation))).Split('\r', '\n')) // if (aLine.IndexOf(':') > 0) Headers.Add(aLine.Substring(0, aLine.IndexOf(':')), aLine.Substring(aLine.IndexOf(':') + 1).TrimStart()); if (RequestType != "GET") Content = new string(Encoding.UTF8.GetChars(tcpContent, delimiterLocation + 4, tcpContent.Length - (delimiterLocation + 4))); else Content = string.Empty; // Assume HTTP 1.1 if cannot parse protocol... Protocol = (firstLine.IndexOf("HTTP") > 5) ? firstLine.Split(' ')[2] : "HTTP/1.1"; if (Headers.Contains("Host")) this.Host = Headers["Host"] as string; }
/// <summary> /// Sends an HTTP Request to the specified URL. If no content is specified a GET is sent, otherwise a POST is sent. /// </summary> /// <param name="url">URL address with path, such as http://odata.netflix.com/Catalog/Titles%28%27BVIuO%27%29/Synopsis/$value</param> /// <param name="content">The body of a POST</param> /// <param name="connection">Specify a connection object when you want to get the response event from ConnectionResponseReceived</param> public HttpRequest(string url, string content = null, Connection connection = null) { if (connection != null) _con = connection; url = (url.IndexOf("http://") >= 0) ? url.Substring(url.IndexOf("http://") + 7) : url; Host = url.Split('/')[0].Trim(); Path = System.Web.HttpUtility.UrlEncode(url.Substring(Host.Length).Trim(), false); if (Path == string.Empty) Path = "/"; Protocol = "HTTP/1.1"; Content = content; RequestType = "GET"; Headers = new Hashtable(); if (content != null && content != string.Empty) { RequestType = "POST"; Headers.Add("Content-Length", content.Length.ToString()); } }
internal static void FireUdpPacketEvent(byte[] packet, Connection socket) { OnUdpReceivedPacketEvent.Invoke(new Packet(PacketType.UDP) { Content = packet, Socket = socket }); }
internal static void FireHttpPacketEvent(byte[] packet, Connection socket) { try { if (OnHttpReceivedPacketEvent != null) OnHttpReceivedPacketEvent.Invoke(new HttpRequest(packet, socket)); } catch { //TODO: throwing an exception is expensive and could waste precious resources. A simple Validation of the message could be very effective. // if an http message is malformed, or it just isn't http, it will get caught here Debug.WriteLine("A bad Request was received and ignored. "); } }
internal static void FireTcpPacketEvent(byte[] packet, uint seqNumber, Connection socket) { if (OnTcpReceivedPacketEvent != null) OnTcpReceivedPacketEvent.Invoke(new Packet(PacketType.TCP) { SequenceNumber = seqNumber, Content = packet, Socket = socket }); }