/// <summary> /// Invoked when IP data is being delivered through the specified interface. /// </summary> /// <param name="ifc">The interface through which the data was received.</param> /// <param name="data">A sequence of bytes received.</param> /// <exception cref="ArgumentNullException">Thrown if either argument /// is null.</exception> void OnIpInput(Interface ifc, byte[] data) { ifc.ThrowIfNull("ifc"); data.ThrowIfNull("data"); WriteLine(ifc.FullName + " has received new IP packet."); try { var packet = IpPacket.Deserialize(data); // This method is called in "interrupt context" and can execute // on behalf of different NIC's simultaneously. The IP stack // is usually single threaded however, so incoming packets are queued // globally and processed sequentially. if (inputQueue.Count == 0) { Simulation.Callback(nodalProcessingDelay, ProcessPackets); } try { // Enqueue the packet and the interface through which it was // received. inputQueue.Enqueue(new Tuple <IpPacket, Interface>(packet, ifc)); } catch (InvalidOperationException) { // If the host's input queue is full, we must drop the packet. WriteLine("IP input queue overflow, dropping packet."); // Send a "Source Quench" ICMP to the packet's originator. SendIcmp(ifc, packet.Source, IcmpPacket.SourceQuench(packet)); } } catch (SerializationException) { WriteLine(ifc.FullName + " has detected a bad checksum, " + "discarding IP packet."); } }
/// <summary> /// Fragments the specified packet into multiple packets taking into /// account the specified maximum transmission unit. /// </summary> /// <param name="packet">The packet to fragment.</param> /// <param name="Mtu">The maximum transmission unit; The maximum size, /// in bytes, each of the fragmented packets may have.</param> /// <returns>An enumerable collection of packet fragments.</returns> public IEnumerable <IpPacket> FragmentPacket(IpPacket packet, int Mtu) { // The maximum size of a segment is the MTU minus the IP header size. var maxSegmentSize = Mtu - 20; var numSegments = (int)Math.Ceiling(packet.Data.Length / (double)maxSegmentSize); var list = new List <IpPacket>(); ushort ident = (ushort)(Simulation.Time % 0xFFFF), offset = 0; for (var i = 0; i < numSegments; i++) { // Set MoreFragments flag for all but the last segment. var mf = i < (numSegments - 1); var flags = mf ? (packet.Flags | IpFlag.MoreFragments) : packet.Flags; var dataSize = Math.Min(maxSegmentSize, packet.Data.Length - offset * 8); var data = new byte[dataSize]; // Add offset of original packet, as the original packet might be // a fragment itself. var packetOffset = (ushort)(packet.FragmentOffset + offset); Array.Copy(packet.Data, offset * 8, data, 0, dataSize); var segment = new IpPacket(packet.Destination, packet.Source, packet.Protocol, packet.Ihl, packet.Dscp, packet.TimeToLive, ident, flags, packetOffset, data); offset = (ushort)(offset + (maxSegmentSize / 8)); list.Add(segment); } return(list); }
/// <summary> /// Resolves the specified IPv4 destination address to a physical /// address and hands the specified IPv4 packet down to the link /// layer. /// </summary> /// <param name="ifc">The interface through which to output the /// data.</param> /// <param name="destination">The logical address of the destination /// host, which can different from the final destination address /// contained in the IP packet.</param> /// <param name="packet">The IPv4 packet to transmit.</param> /// <exception cref="ArgumentNullException">Thrown if any of the arguments /// is null.</exception> void Output(Interface ifc, IpAddress destination, IpPacket packet) { ifc.ThrowIfNull("ifc"); destination.ThrowIfNull("destination"); packet.ThrowIfNull("packet"); // Translate IP address into MAC-Address. var macDestination = arp.Lookup(ifc, destination); // IP address is not in our ARP table. if (macDestination == null) { // Put packet on hold until the MAC-48 destination address has // been figured out. WaitingPacketsOf(ifc).Add( new Tuple <IpAddress, IpPacket>(destination, packet)); WriteLine(ifc.FullName + " is putting IP packet on-hold due to pending ARP request."); // Schedule a new ARP request. arp.Resolve(ifc, destination); } else { WriteLine(ifc.FullName + " is queueing IP packet for " + destination); Output(ifc, macDestination, packet); } }
/// <summary> /// Computes the 16-bit checksum of the specified IPv4 packet. /// </summary> /// <param name="packet">The packet to compute the checksum for.</param> /// <param name="withChecksumField">true to include the packet's /// checksum field in the calculation; otherwise false.</param> /// <returns>The checksum of the specified IPv4 packet.</returns> /// <exception cref="ArgumentNullException">Thrown if the packet /// argument is null.</exception> public static ushort ComputeChecksum(IpPacket packet, bool withChecksumField = false) { packet.ThrowIfNull("packet"); // The version and IHL fields are 4 bit wide each. var vi = (byte)(((packet.Ihl & 0x0F) << 4) | (((int)packet.Version) & 0x0F)); // The flags field is 3 bits and the fragment offset 13 bits wide. var ffo = (ushort)(((packet.FragmentOffset & 0x1FFF) << 3) | ((int)packet.Flags & 0x07)); var bytes = new ByteBuilder() .Append(vi) .Append(packet.Dscp) .Append(packet.TotalLength) .Append(packet.Identification) .Append(ffo) .Append(packet.TimeToLive) .Append((byte)packet.Protocol) .Append(withChecksumField ? packet.Checksum : (ushort)0) .Append(packet.Source.Bytes) .Append(packet.Destination.Bytes) .ToArray(); var sum = 0; // Treat the header bytes as a sequence of unsigned 16-bit values and // sum them up. for (var n = 0; n < bytes.Length; n += 2) { sum += BitConverter.ToUInt16(bytes, n); } // Use carries to compute the 1's complement sum. sum = (sum >> 16) + (sum & 0xFFFF); // Return the inverted 16-bit result. return((ushort)(~sum)); }
/// <summary> /// Deserializes an IpPacket instance from the specified sequence of /// bytes. /// </summary> /// <param name="data">The sequence of bytes to deserialize an IpPacket /// object from.</param> /// <returns>A deserialized IpPacket object.</returns> /// <exception cref="ArgumentNullException">Thrown if the data argument is /// null.</exception> /// <exception cref="SerializationException">Thrown if the IP packet could /// not be deserialized from the specified byte array.</exception> public static IpPacket Deserialize(byte[] data) { data.ThrowIfNull("data"); using (var ms = new MemoryStream(data)) { using (var reader = new BinaryReader(ms)) { var vi = reader.ReadByte(); var version = (IpVersion)(vi & 0x0F); byte ihl = (byte)(vi >> 4), dscp = reader.ReadByte(); ushort totalLength = reader.ReadUInt16(), identification = reader.ReadUInt16(), ffo = reader.ReadUInt16(); var flags = (IpFlag)(ffo & 0x07); var fragmentOffset = (ushort)(ffo >> 3); var ttl = reader.ReadByte(); var type = (IpProtocol)reader.ReadByte(); var checksum = reader.ReadUInt16(); IpAddress src = new IpAddress(reader.ReadBytes(4)), dst = new IpAddress(reader.ReadBytes(4)); var packet = new IpPacket(version, ihl, dscp, totalLength, identification, flags, fragmentOffset, ttl, type, checksum, src, dst); // Computing the checksum should yield a value of 0 unless errors are // detected. if (ComputeChecksum(packet, true) != 0) { throw new SerializationException("The IPv4 header is corrupted."); } // If no errors have been detected, read the data section. packet.Data = reader.ReadBytes(totalLength - 20); return(packet); } } }
/// <summary> /// Determines whether the specified IP packet is for us. /// </summary> /// <param name="packet">The IP packet to examine.</param> /// <returns>True if the IP packet's destination matches one of the /// host's interfaces' addresses; Otherwise false.</returns> bool IsPacketForUs(IpPacket packet) { foreach (var ifc in interfaces) { if (ifc.IpAddress == packet.Destination) { return(true); } } return(false); }
/// <summary> /// Reassembles fragmented IP packets and hands them up to the transport /// layer once they have been fully reassembled. /// </summary> /// <param name="packet">An IP packet representing a fragment of a /// fragmented packet.</param> public void ReassemblePacket(IpPacket packet) { // Fragments belong to the same datagram if they have the same source, // destination, protocol, and identifier fields (RFC 791, p. 28). var hash = Hash.Sha256(packet.Source + packet.Destination.ToString() + packet.Protocol + packet.Identification ); // Group related fragments in a set under the same dictionary key. if (!fragments.ContainsKey(hash)) { fragments.Add(hash, new HashSet <IpPacket>()); } fragments[hash].Add(packet); // Figure out if we already have all fragments so that we can reassemble // the original packet. var uf = new UnionFind(65536); var originalDataSize = 0; foreach (var p in fragments[hash]) { var from = p.FragmentOffset * 8; var to = from + p.Data.Length - 1; uf.Union(from, to); uf.Union(to, to + 1); // Derive original packet size from last fragment. if (!p.Flags.HasFlag(IpFlag.MoreFragments)) { originalDataSize = from + p.Data.Length; } } // If this is still 0, last segment has not arrived yet. if (originalDataSize == 0) { return; } // If the first and the last byte are not part of the same component, // not all fragments have arrived yet. if (!uf.Connected(0, originalDataSize)) { return; } var data = new byte[originalDataSize]; foreach (var p in fragments[hash]) { Array.Copy(p.Data, 0, data, p.FragmentOffset * 8, p.Data.Length); } // Hand up reassembled data to transport layer. HandUp(data, packet.Protocol); }
/// <summary> /// Determines whether the specified IP packet is a fragment. /// </summary> /// <param name="packet">The IP packet to examine.</param> /// <returns>true if the packet is a fragment. Otherwise /// false.</returns> bool IsFragment(IpPacket packet) { if (packet.Flags.HasFlag(IpFlag.MoreFragments)) { return(true); } // The last fragment does not have the MoreFragments flag set, but has // a non-zero Fragment Offset field, differentiating it from an // unfragmented packet. if (packet.FragmentOffset > 0) { return(true); } return(false); }
/// <summary> /// Traverses the routing table and returns the best route for the /// specified packet. /// </summary> /// <param name="packet">The packet to find a route for.</param> /// <returns>The best route found in the routing table or null if /// no route was found.</returns> Route FindRoute(IpPacket packet) { Route best = null; foreach (var r in routingTable) { if ((r.Destination & r.Netmask) == (packet.Destination & r.Netmask)) { if (best == null) { best = r; } else if (best.Metric > r.Metric) { best = r; } } } return(best); }
/// <summary> /// Attempts to route the specified packet. /// </summary> /// <param name="packet">The packet to route.</param> /// <param name="ifc">The interface through which the packet was /// received.</param> void RoutePacket(IpPacket packet, Interface ifc) { // See if we can find a machting route in our routing table. var route = FindRoute(packet); // Drop packet and send "Unreachable" ICMP back to packet originator. if (route == null) { SendIcmp(ifc, packet.Source, IcmpPacket.Unreachable(packet)); return; } // Do we have to fragment the packet? if (route.Interface.MaximumTransmissionUnit < packet.TotalLength) { // Drop packet and send "Fragmentation Required" ICMP back to // packet originator. if (packet.Flags.HasFlag(IpFlag.DontFragment)) { SendIcmp(ifc, packet.Source, IcmpPacket.FragmentationRequired(packet)); return; } var packets = FragmentPacket(packet, route.Interface.MaximumTransmissionUnit); // Forward fragmented packets. foreach (var p in packets) { Output(route.Interface, route.Gateway != null ? route.Gateway : p.Destination, p); } } else { // Forward packet. Output(route.Interface, route.Gateway != null ? route.Gateway : packet.Destination, packet); } }
/// <summary> /// Wraps the specified higher-level data into IP packets and /// transmits them to the specified destination. /// </summary> /// <param name="ifc">The interface through which to output the data.</param> /// <param name="destination">The logical address of the destination /// host.</param> /// <param name="data">The higher-level data to transmit.</param> /// <param name="type">The type of the higher-level data.</param> /// <exception cref="ArgumentNullException">Thrown if any of the arguments /// is null.</exception> /// <remarks>This API is exposed to the next higher-up layer.</remarks> public void Output(Interface ifc, IpAddress destination, byte[] data, IpProtocol type) { ifc.ThrowIfNull("ifc"); destination.ThrowIfNull("destination"); data.ThrowIfNull("data"); // Construct IP packets of the size of the MTU of the data-link. var maxDataSize = ifc.MaximumTransmissionUnit - 20; var numPackets = (int)Math.Ceiling(data.Length / (double)maxDataSize); var sameSubnet = (destination & ifc.Netmask) == (ifc.IpAddress & ifc.Netmask); for (int i = 0, index = 0; i < numPackets; i++) { var numBytes = Math.Min(maxDataSize, data.Length - index); var packetData = new byte[numBytes]; Array.Copy(data, index, packetData, 0, numBytes); index = index + numBytes; // Construct the packet. var packet = new IpPacket(destination, ifc.IpAddress, type, packetData); // If source and destination are in the same subnet, we can deliver the // packet directly. Otherwise send it to the configured default gateway. Output(ifc, sameSubnet ? destination : ifc.Gateway, packet); } }
/// <summary> /// Computes the 16-bit checksum of the specified IPv4 packet. /// </summary> /// <param name="packet">The packet to compute the checksum for.</param> /// <param name="withChecksumField">true to include the packet's /// checksum field in the calculation; otherwise false.</param> /// <returns>The checksum of the specified IPv4 packet.</returns> /// <exception cref="ArgumentNullException">Thrown if the packet /// argument is null.</exception> public static ushort ComputeChecksum(IpPacket packet, bool withChecksumField = false) { packet.ThrowIfNull("packet"); // The version and IHL fields are 4 bit wide each. var vi = (byte) (((packet.Ihl & 0x0F) << 4) | (((int) packet.Version) & 0x0F)); // The flags field is 3 bits and the fragment offset 13 bits wide. var ffo = (ushort) (((packet.FragmentOffset & 0x1FFF) << 3) | ((int) packet.Flags & 0x07)); var bytes = new ByteBuilder() .Append(vi) .Append(packet.Dscp) .Append(packet.TotalLength) .Append(packet.Identification) .Append(ffo) .Append(packet.TimeToLive) .Append((byte) packet.Protocol) .Append(withChecksumField ? packet.Checksum : (ushort)0) .Append(packet.Source.Bytes) .Append(packet.Destination.Bytes) .ToArray(); var sum = 0; // Treat the header bytes as a sequence of unsigned 16-bit values and // sum them up. for (var n = 0; n < bytes.Length; n += 2) sum += BitConverter.ToUInt16(bytes, n); // Use carries to compute the 1's complement sum. sum = (sum >> 16) + (sum & 0xFFFF); // Return the inverted 16-bit result. return (ushort)(~ sum); }
/// <summary> /// Deserializes an IpPacket instance from the specified sequence of /// bytes. /// </summary> /// <param name="data">The sequence of bytes to deserialize an IpPacket /// object from.</param> /// <returns>A deserialized IpPacket object.</returns> /// <exception cref="ArgumentNullException">Thrown if the data argument is /// null.</exception> /// <exception cref="SerializationException">Thrown if the IP packet could /// not be deserialized from the specified byte array.</exception> public static IpPacket Deserialize(byte[] data) { data.ThrowIfNull("data"); using (var ms = new MemoryStream(data)) { using (var reader = new BinaryReader(ms)) { var vi = reader.ReadByte(); var version = (IpVersion)(vi & 0x0F); byte ihl = (byte) (vi >> 4), dscp = reader.ReadByte(); ushort totalLength = reader.ReadUInt16(), identification = reader.ReadUInt16(), ffo = reader.ReadUInt16(); var flags = (IpFlag) (ffo & 0x07); var fragmentOffset = (ushort) (ffo >> 3); var ttl = reader.ReadByte(); var type = (IpProtocol) reader.ReadByte(); var checksum = reader.ReadUInt16(); IpAddress src = new IpAddress(reader.ReadBytes(4)), dst = new IpAddress(reader.ReadBytes(4)); var packet = new IpPacket(version, ihl, dscp, totalLength, identification, flags, fragmentOffset, ttl, type, checksum, src, dst); // Computing the checksum should yield a value of 0 unless errors are // detected. if (ComputeChecksum(packet, true) != 0) throw new SerializationException("The IPv4 header is corrupted."); // If no errors have been detected, read the data section. packet.Data = reader.ReadBytes(totalLength - 20); return packet; } } }