Network programming is very much concerned with moving data from client to server, but when you need to look at what is moving between the client and server, you encounter a problem.
In most cases, there is no need for a program to know what data is being received by other applications. Furthermore, it is a security risk to have one program that could scan third-party applications, such as FTP software, and retrieve the username and password for your Web site; however, if you are building a value-added package to a third-party application, such as a content filter for a proprietary or legacy application, tapping into what is being sent between client and server is a good start.
Packet capture isn’t something new. It has been around for many years. But very few applications actually leverage the technology to provide tools that can be used in conjunction with other software to provide virus or computer-misuse detection. What is available, though, are extensive tools that can tell you what each byte in every packet means, down to even the computer manufacturer that sent the packet. Figure 13.1 shows the demo version of TracePlus from www.sstinc.com.
In order to determine the manufacturer of a particular piece of equipment from its MAC address, access the listing at http://standards.ieee.org/regauth/oui/oui.txt, which contains most, if not all, network equipment manufacturers with their allocated MAC address space.
Software that can leverage packet-level data can be useful for businesses. We have all heard of the scenario where a few employees decide to download their favorite band’s latest album on Mp3 the day of a big presentation, causing a total misallocation of bandwidth within a company. This is where traffic-detection software comes into its own, providing an early warning system for bandwidth misuse.
Traffic-detection software can be used to detect packets on a network that could uncover viruses, use of unauthorized software, and email forgery. Let’s look briefly at how the applications mentioned above could be implemented using packet-level monitoring.
You can use traffic detection to discover the presence of viruses and attacks in progress, but unfortunately not to prevent them. It could, however, be used to provide companywide detection of infected computers and denial-of-service attacks. The telltale signs of virus propagation could be rapid sequential accesses to computers within the subnet on port 80 (scanning for servers to infect) or heartbeat signals coming from a nonstandard port to an external server (firewall tunneling).
Denial-of-service attacks could be detected from the presence of a large number of corrupted packets sent to a particular server. A fragmented ping request would indicate a ping-of-death attack. Large numbers of incomplete TCP connections would indicate a SYN
flood attack, in which the first packet of a TCP handshake is sent repetitively and rapidly. The victim attempts to establish TCP sessions for each of the packets by sending ACK
(acknowledge) packets to the attacker, which are not responded to. The victim eventually becomes overwhelmed with pending TCP sessions and denies all network traffic.
Detection of unauthorized software usage could be useful in a company where employees may be partial to spending time playing computer games during work hours. Multiplayer computer games generally operate on a high port over TCP/IP or IPX. Games produce a lot of network traffic and, thus, can be spotted easily in a TCP/IP trace. The IP addresses of the offending employee’s computers could be logged, and the employee could be suitably warned.
Email traffic could also be monitored remotely using these techniques. This could be used to detect company secrets being sent to a competitor. Furthermore, a system to prevent email spoofing and forgery could be implemented if SMTP traffic were monitored. An application could keep a record of each employee’s computer’s IP address and email address. In the event of a mismatch between the IP and email address, an alarm could be raised, possibly sending an email to the recipient warning of the possibility of email forgery.
This chapter begins with information about how to read and interpret IP-level traffic on your network. It then progresses to more complex examples about how to drill down further into the network stack and extract lower-level data at the frame level. The chapter concludes with information about how to use new classes introduced in .NET 2.0 Whidbey to gather systemwide network information.
Network tapping anything that runs at the IP level includes TCP/IP and UDP and everything above that, such as DNS, HTTP, FTP, and so forth. At this level, you don’t need to use any special software. Everything can be done natively in .NET.
To implement a layer 3 network tap in .NET, open a new project in Visual Studio .NET and add a list box named lbPackets
and two buttons, btnStart
and btnStop
. It may be worthwhile to set the font for the list box to Courier for easier reading.
After designing the user interface, you should add the following public variable, a reference to the main listener thread:
C#
public Thread Listener;
VB.NET
Public Listener as Thread
Click on the Start button and enter the following code:
C#
private void btnStart_Click(object sender, System.EventArgs e) { btnStart.Enabled = false; btnStop.Enabled = true; Listener = new Thread(new ThreadStart(Run)); Listener.Start(); }
VB.NET
Private Sub btnStart_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) btnStart.Enabled = False btnStop.Enabled = True Listener = New Thread(New ThreadStart(AddressOf Run)) Listener.Start() End Sub
The Run
method is where the network tap takes place. It is a processor-intensive task, so it is executed in its own thread, as can be seen from the code. Click on the Stop button and enter the following code:
C#
private void btnStop_Click(object sender, System.EventArgs e) { btnStart.Enabled = true; btnStop.Enabled = false; if(Listener != null) { Listener.Abort(); Listener.Join(); Listener = null; } }
Private Sub btnStop_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) btnStart.Enabled = True btnStop.Enabled = False If Not Listener Is Nothing Then Listener.Abort() Listener.Join() Listener = Nothing End If End Sub
This code simply kills the thread containing the network tap, which effectively stops reporting the arrival of new packets.
C#
public void Run() { int len_receive_buf = 4096; int len_send_buf = 4096; byte[] receive_buf = new byte[len_receive_buf]; byte[] send_buf = new byte[len_send_buf]; int cout_receive_bytes; Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP); socket.Blocking = false; IPHostEntry IPHost = Dns.GetHostByName(Dns.GetHostName()); socket.Bind(new IPEndPoint(IPAddress.Parse (IPHost.AddressList[0].ToString()), 0)); socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, 1); byte []IN = new byte[4]{1, 0, 0, 0}; byte []OUT = new byte[4]; int SIO_RCVALL = unchecked((int)0x98000001); int ret_code = socket.IOControl(SIO_RCVALL, IN, OUT); while(true) { IAsyncResult ar = socket.BeginReceive(receive_buf, 0, len_receive_buf, SocketFlags.None, null, this); cout_receive_bytes = socket.EndReceive(ar); Receive(receive_buf, cout_receive_bytes); } }
Public Sub Run() Dim len_receive_buf As Integer = 4096 Dim len_send_buf As Integer = 4096 Dim receive_buf() As Byte = New Byte(len_receive_buf) {} Dim send_buf() As Byte = New Byte(len_send_buf) {} Dim cout_receive_bytes As Integer Dim socket As Socket = New _ Socket(AddressFamily.InterNetwork, _ SocketType.Raw, ProtocolType.IP) socket.Blocking = False Dim IPHost As IPHostEnTry = _ Dns.GetHostByName(Dns.GetHostName()) socket.Bind(New _ IPEndPoint(IPAddress.Parse _ (IPHost.AddressList(0).ToString()), 0)) socket.SetSocketOption(SocketOptionLevel.IP, _ SocketOptionName.HeaderIncluded, 1) Dim bIN As Byte() = New Byte() {1, 0, 0, 0} Dim bOUT As Byte() = New Byte() {0, 0, 0, 0} Dim SIO_RCVALL As Integer = &H98000001 Dim ret_code As Integer = socket.IOControl _ (SIO_RCVALL, bIN, bOUT) Do Dim ar As IAsyncResult = socket.BeginReceive _ (receive_buf, 0, _ len_receive_buf, SocketFlags.None, Nothing, Me) cout_receive_bytes = socket.EndReceive(ar) Receive(receive_buf, cout_receive_bytes) Loop End Sub
The Run
method is the core thread of the application. It creates a raw socket bound to the local machine on the default adapter. The socket’s normal operating parameters are then modified using the IOControl
method, which accesses the underlying socket API function WSAIoctl
. This function is passed a parameter SIO_RCVALL
(98000001 Hex). Use of this parameter enables a socket to receive all IP packets on the network. The socket must be in RAW
mode, using the IP protocol, and bound to a specific local adapter. This feature requires administrator privilege on the local machine. The packet parsing and display has been separated from the tapping thread to make the program more legible. This method is called Receive
and should be implemented thus:
C#
public void Receive(byte []buf, int len) { if (buf[9]==6) { lbPackets.Items.Add (Encoding.ASCII.GetString(buf).Replace(" "," ")); } }
VB.NET
Public Sub Receive(ByVal buf as byte(), ByVal len As Integer) If buf(9)=6 then lbPackets.Items.Add(Encoding.ASCII.GetString _ (buf).Replace(chr(0)," ")) end if End Sub
In this example, traffic is filtered so that only TCP/IP packets are shown. This means that the screen is not cluttered with DNS queries, pings, and UDP data. TCP/IP packets will always have the ninth byte in the header set to 6. All null (ASCII code 0) characters are displayed as spaces so that the list box does not crop the string at the first null character.
Finally, you need to add some standard namespaces to the code:
C#
using System; using System.Windows.Forms; using System.Net.Sockets; using System.Net; using System.Threading; using System.Text;
Imports System Imports System.Windows.Forms Imports System.Net.Sockets Imports System.Net Imports System.Threading Imports System.Text
To test the application, run it from Visual Studio .NET, and visit a Web site using your browser. You should see the raw TCP data flowing between your browser and the Web server appear in the list box, as shown in Figure 13.2.
Capturing and interpreting raw network data are totally separate things. Being able to recognize anomalies in the network data is the key to providing a useful tool that could be of real benefit to network managers and administrators.
Raw network data can appear totally unordered, with HTTP packets mixed in with NETBIOS (Microsoft file sharing) and ARP, each performing different tasks, but working simultaneously. Some of these packets can be recognized immediately: familiar snippets of HTML generally indicate HTTP (although it could be a rich-text email or a Web page being uploaded). Most networks will primarily ferry IP traffic, but will also carry non-IP traffic such as NETBIOS and ARP.
Every packet carries a header that is in a strictly defined binary format. To define the standards involved most concisely, the tables in this chapter list the name and starting point of each field in the relevant header. Every field runs contiguously with the next; thus, the length of any field can be calculated by subtracting its starting point from the following field’s starting point.
Because fields do not need to start at a byte boundary, the bit number is also provided in the second column. Where a field is commonly known as being part of a collective field, it is separated in the description column from the parent field by a colon.
The frame header (Table 13.1) is the only part that the hardware within the network card will actually read or process. It is 14 bytes long, containing the hardware address of the source and destination computers. In the case where the hardware address of the destination computer is unknown, this address is set to FF-FF-FF-FF-FF-FF
(hex). Over PPP connections, the source and destination MAC address may be omitted and replaced by SRC
and DEST
.
The Ethernet type code is a two-digit hex number that specifies the packet protocol (Table 13.2). A fairly comprehensive list of Ethernet type codes can be seen at www.cavebear.com/CaveBear/Ethernet.
In IP packets, the IP header (Table 13.3) immediately follows the frame header at byte 14. IP is definitively described in RFC 791.
Table 13.3. IP header.
Byte Offset | Bit Offset | Description |
---|---|---|
1 | 1 | Version |
1 | 5 | Header length |
2 | 1 | Type of service: Precedence |
2 | 4 | Type of service: Delay |
2 | 5 | Type of service: Throughput |
2 | 6 | Type of service: Reliability |
2 | 7 | Type of service: (Reserved) |
3 | 1 | Data length |
5 | 1 | Identification |
7 | 1 | Flags |
7 | 4 | Fragment offset |
9 | 1 | TTL |
10 | 1 | Protocol |
11 | 1 | Header checksum |
13 | 1 | Source address |
17 | 1 | Destination address |
21 | 1 | Option: code: Flag |
21 | 2 | Option: code: Class |
21 | 4 | Option: code: Number |
22 | 1 | Option: Length |
23 | 1 | Option: Data (variable length) |
Data (variable length) |
Version: Set to 0100 for IPv4 and 0110 for IPv6.
Header length: The length of the header divided by 4. Max length is 60.
Type of service: May be used to increase quality of service on some networks.
Data length: The combined length of the header and data.
Identification: A random number used to identify duplicate packets.
Flags: Indicates the fragmentation status of the packet. Bit 2 is set to 1 when the datagram cannot be fragmented (0 when it can). Bit 3 is set to 1 when there are more fragments in the datagram, and 0 when this packet is the last fragment.
Fragment offset: Indicates the position a packet should occupy within a fragmented datagram.
TTL: Indicates the number of nodes through which the datagram can pass before being discarded. Used to avoid infinite routing loops.
Protocol: Set to 6 for TCP, 17 for UDP, 1 for ICMP, and 2 for IGMP.
Checksum: A checksum of the IP header calculated using a 16-bit ones complement sum.
Source: The IP address of the sending computer.
Option: An optional field that may contain security options, routing records, timestamps, and so forth.
In ICMP (ping) packets immediately following the IP header make up a 4-byte header (Table 13.4) followed by a body of variable length. A definitive description of ICMP can be found in RFC 792.
Type: Defines the purpose or function of the packet. See Table 13.5.
Code: A type code–specific identifier that more accurately describes the function of the packet. In a destination unreachable (3) packet, 0 indicates the subnet is down, whereas 1 indicates that only the host is down.
Checksum: The checksum is the 16-bit ones complement of the ones complement sum of the ICMP message starting with the ICMP type.
In TCP/IP packets, immediately following the IP header is a 24-byte TCP header (Table 13.6). A definitive description of TCP can be found in RFC 793.
Table 13.6. TCP header.
Byte Offset | Bit Offset | Description |
---|---|---|
1 | 1 | Source port |
3 | 1 | Destination port |
5 | 1 | Sequence number |
9 | 1 | Acknowledgment number |
13 | 1 | Data offset |
13 | 4 | Reserved |
14 | 3 | Urgent (URG) |
14 | 4 | Acknowledge (ACK) |
14 | 5 | Push (PSH) |
14 | 6 | Reset (RST) |
14 | 7 | Synchronize (SYN) |
14 | 8 | Finish (FIN) |
15 | 1 | Window |
17 | 1 | Checksum |
19 | 1 | Urgent pointer |
21 | 1 | Options |
24 | 1 | Padding |
Source port: The port the TCP connection is made from, usually a high port.
Destination port: The destination port, 80 for HTTP and 25 for SMTP, etc.
Sequence number: The sequence number of the first data octet in this segment (except when SYN
is present). If SYN
is present, the sequence number is the initial sequence number (ISN), and the first data octet is ISN + 1.
Acknowledgment number: If the ACK
control bit is set, this field contains the value of the next sequence number the segment sender is expecting to receive. Once a connection is established, this is always sent.
Data offset: The number of 32-bit words in the TCP header. This indicates where the data begins. The TCP header (even one including options) is an integral number 32 bits long.
Urgent (URG
): When set to 1, the urgent pointer field is significant.
Acknowledge (ACK
): When set to 1, the acknowledgment field is significant.
Push (PSH
): Implements a push function.
Reset (RST
): When set to 1, it will reset the connection.
Synchronize (SYN
): Synchronizes sequence numbers.
Finish (FIN
): Indicates that there is no more data from the sender.
Window: The number of data octets indicated in the acknowledgment field that the sender of this segment is willing to accept.
Checksum: The checksum field is the 16-bit ones complement of the ones complement sum of all 16-bit words in the header and text, if segments are zero-padded to form a multiple of 16-bit words for checksum purposes. The pad is not transmitted as part of the segment. While computing the checksum, the checksum field is replaced with zeros. The checksum also covers a pseudoheader that is prefixed to the TCP header while computing the checksum only. This pseudoheader contains the source address, the destination address, the protocol, and TCP length.
Urgent pointer: Indicates the current value of the urgent pointer as an offset from the sequence number in this segment. The urgent pointer points to the sequence number of the byte following the urgent data. This field is only to be interpreted in segments with the URG
control bit set.
Options: This contains vendor-specific IP options that may not be implemented on all systems. It is guaranteed to end on a 32-bit boundary with zero padding.
TCP is a connection-oriented protocol with built-in protection from duplicated, dropped, and out-of order packets. This is done by explicitly opening a connection between client and server and assigning each packet a sequence number. The client will reply to the server with an acknowledgment for every packet sent; if sequence numbers go missing, appear twice, or appear out of order to the client, the acknowledgment will not be sent, and the server can take appropriate action.
A TCP connection is established with a three-way handshake. Initially, the client sends a SYN
request to the server, and the server replies with an ACK
response, to which the client replies with an ACK
reply. Similarly, a TCP connection is closed with a two-way handshake, where one party sends a FIN
request to the other, which then replies with an ACK
response.
In UDP packets, immediately following the IP header is an 8-byte UDP header (Table 13.7). A definitive description of UDP can be found in RFC 768.
UDP is the most basic data-carrying protocol that is valid for the Internet. It contains no protection against lost or duplicated packets, and therefore is not applicable to media that require high integrity. It is acceptable for live streaming audio and video formats.
Source port: The port the UDP connection is made from, usually a high port.
Destination port: The destination port, 161 for SNMP and 53 for DNS, etc.
Length: The number of bytes following the header, plus the header itself.
Checksum: Computed as the 16-bit ones complement of the ones complement sum of a pseudoheader of information from the IP header, the UDP header, and the data, padded as needed with zero bytes at the end to make a multiple of 2 bytes. If the checksum is set to 0, then check summing is disabled. If the computed checksum is 0, then this field must be set to 0xFFFF.
In DNS packets, immediately following the UDP header (port 53) is a 12-byte DNS header (Table 13.8). A definitive description of DNS can be found in RFC 1035.
ID: Used to correlate queries and responses.
Query or response: Identifies the message as a query or response.
Query: Field that describes the type of message: 0 for standard query (name to address), 1 for inverse query (address to name), and 2 for server status request.
Authoritative answer: Identifies the response as one made by an authoritative name server when set to 1.
Truncation: Indicates the message has been truncated when set to 1.
Recursive: Set to 1 to request the name server to perform recursive searches.
Availability: Indicates if the name server can provide recursive service.
Result code (RCODE): Used to indicate errors in processing the DNS query.
Question count: Indicates the number of entries in the question section.
Answer count: Indicates the number of resource records in the answer section.
Authority count: Indicates the number of name server resource records in the authority section.
Additional count: Indicates the number of resource records in the additional records section.
DNS is used primarily to resolve IP addresses from domain names; however, it can also be used to locate mail exchanges and so forth.
When you tap into (sniff) network traffic at level 2, you receive not only data from other applications on your computer, but also from other applications on different computers that are on the same network. Furthermore, you get more than just IP traffic: you start to see ARP requests, NETBIOS packets, and many other weird and wonderful inhabitants of the network, and they all come complete with frame headers.
WinPCap
is, in essence, a driver that enables you to read packets directly from a network adapter. It was developed by the Politecnico di Torino (Italy) and can be distributed in binary format with the addition of a copyright notice and disclaimer to your application. WinPCap
can be downloaded from http://winpcap.mirror.ethereal.com.
The WinPCap
DLL is designed for use with Visual C++ and is difficult to use directly from C# or VB.NET. A wrapper of some description is required to import this library into your .NET application. One of the well-known wrappers is an ActiveX control named PacketX
, which is available from www.beesync.com. This software is shareware and, therefore, may not be applicable for inclusion in freeware packages.
Alternatively, I have prepared a wrapper (a C++ DLL) named rvpacket.dll
that is available for download at http://network.programming-in.net/downloads/rvPacket.zip. This DLL is open source and can be redistributed as required. The DLL is only a basic implementation of WinPCap
, but the source code is available for those who are savvy in C++ to extend the functionality.
Developers should be aware of the following known limitations to WinPCap
:
It will not operate correctly on Windows NT, 2000, or XP dial-up connections.
It may reset dial-up connections on Windows 95.
Wireless network cards are not fully supported.
Some implementations of VPN are not supported.
This first example uses WinPCap
with the rvPacket
wrapper to display all network traffic on a textbox. It is normal for network data to pass through the adapter faster than it can appear on-screen, so there may be some time lag between data passing through the network and what is displayed on-screen. The first step in developing this application is to download and install WinPCap
from http://winpcap.mirror.ethereal.com, then to download rvPacket
from http://network.programming-in.net/downloads/rvPacket.zip, and copy the DLL into your Windows system folder.
Create a new project in Visual Studio .NET, and draw a textbox named tbPackets
with multiline
set to true
. Two buttons named btnStart
and btnStop
are required. VB.NET developers will need to add a reference to Microsoft.VisualBasic.Compatibility
using Project→ Add References.
Click on the Start button and add the following code:
C#
private void btnStart_Click(object sender, System.EventArgs e) { short Qid; string packetBuffer; short openSuccess; short packetQueue; short packetLen; string rawAdapterDetails = ""; int posDefaultAdapter; getAdapterNames(rawAdapterDetails); Adapter="\"; // default adapter openSuccess = openAdapter("\"); if (openSuccess != ERR_SUCCESS) { MessageBox.Show( "Unable to start. Check WinPCap is installed"); return; } while(true) { packetQueue = checkPacketQueue(Adapter); for (Qid = 1; Qid<packetQueue;Qid++) { packetBuffer = new StringBuilder().Append (' ',MAX_PACKET_SIZE).ToString(); packetLen = getQueuedPacket(packetBuffer); packetBuffer = packetBuffer.Substring(0, packetLen); tbPackets.Text = tbPackets.Text + packetBuffer.Replace(" "," "); tbPackets.SelectionStart = tbPackets.Text.Length; Application.DoEvents(); } Application.DoEvents(); } }
Private Sub cmdStart_Click(ByVal eventSender As _ System.Object, ByVal eventArgs As _ System.EventArgs) Handles cmdStart.Click Dim Qid As Short Dim packetBuffer As String Dim adapters() As String Dim openSuccess As Short Dim packetQueue As Short Dim packetLen As Short Dim rawAdapterDetails As String Dim posDefaultAdapter As Short rawAdapterDetails = Space(MAX_ADAPTER_LEN) getAdapterNames(rawAdapterDetails) posDefaultAdapter = _ rawAdapterDetails.IndexOf(ADAPTER_DELIMITER) Adapter = rawAdapterDetails.Substring(0, posDefaultAdapter) openSuccess = openAdapter(Adapter) If openSuccess <> ERR_SUCCESS Then MsgBox("Unable to start. Check WinPCap is installed") Exit Sub End If Do packetQueue = checkPacketQueue(Adapter) For Qid = 1 To packetQueue packetBuffer = Space(MAX_PACKET_SIZE) packetLen = getQueuedPacket(packetBuffer) packetBuffer = packetBuffer.Substring(0, packetLen) tbPackets.Text = tbPackets.Text & Replace _ (packetBuffer, Chr(0), " ") tbPackets.SelectionStart = Len(tbPackets.Text) System.Windows.Forms.Application.DoEvents() Next System.Windows.Forms.Application.DoEvents() Loop End Sub
The code listed above performs two functions; first, detects all of the network adapters on the system, bearing in mind that computers can have more than one means of connecting to a network, either by modem, Ethernet, or some other system. The getAdapterNames
returns a list of adapter names separated by a pipe character (“|”). Here the first default adapter is used.
Network traffic regularly arrives faster than it can be read and handled by an application; thus, the data is buffered internally in a linked-list structure. The rvPacket
library has two functions for reading this buffer, checkPacketQueue
and getQueuedPacket
. As the names suggest, the former is a nonblocking function that retrieves the number of packets in the queue, and the latter will then read each packet in the queue sequentially. The queue is guaranteed not to grow in size between calls to checkPacketQueue
, and no data on this buffer will be altered.
The threading model is primitive, using nothing more than a DoEvents
call to maintain responsiveness for the user interface. This has the side effect of pushing CPU usage to 100%, which looks unprofessional in a consumer product. In a more polished version, proper threading should be used.
All bytes of value 0 are replaced with a space character in the code above to help display the text on-screen; this serves no other purpose.
Although not strictly necessary, the adapter should be closed after use. If it is not closed before the application is destroyed, then a memory leak will result. More importantly, if two separate processes open the adapter at once, Windows will crash.
Click on the Stop button, and enter the following code:
C#
private void btnStop_Click(object sender, System.EventArgs e) { closeAdapter(Adapter); }
Private Sub btnStop_Click(ByVal sender As _ System.Object, ByVal e As System.EventArgs) _ Handles btnStop.Click closeAdapter(Adapter) End Sub
Several API declarations have to be made to make the rvPacket
library accessible. Insert this code directly after the form constructor:
C#
[DllImport("rvPacket.dll")] public static extern short getAdapterNames (string s); [DllImport("rvPacket.dll")] public static extern short openAdapter (string Adapter); [DllImport("rvPacket.dll")] public static extern short checkPacketQueue(string Adapter); [DllImport("rvPacket.dll")] public static extern short getQueuedPacket(string s); [DllImport("rvPacket.dll")] public static extern void closeAdapter(string Adapter); const short SIMULTANEOUS_READS = 10; const short MAX_ADAPTER_LEN = 512; const string ADAPTER_DELIMITER = "|"; const short MAX_PACKET_SIZE = 10000; const short ERR_SUCCESS = 1; const short ERR_ADAPTER_ID= 2; const short ERR_INVALID_HANDLE= 3; const short ERR_INVALID_ADAPTER= 4; const short ERR_ALLOCATE_PACKET= 5; string Adapter = "";
VB.NET
Private Declare Function getAdapterNames Lib _ "rvPacket.dll" (ByVal s As String) As Short Private Declare Function openAdapter Lib _ "rvPacket.dll" (ByVal Adapter As String) As Short Private Declare Function checkPacketQueue Lib _ "rvPacket.dll" (ByVal Adapter As String) As Short Private Declare Function getQueuedPacket Lib _ "rvPacket.dll" (ByVal s As String) As Short Private Declare Sub closeAdapter Lib _ "rvPacket.dll" (ByVal Adapter As String) Private Const SIMULTANEOUS_READS As Short = 10 Private Const MAX_ADAPTER_LEN As Short = 512 Private Const ADAPTER_DELIMITER As String = "|" Private Const MAX_PACKET_SIZE As Short = 10000 Private Const ERR_SUCCESS As Short = 1 Private Const ERR_ADAPTER_ID As Short = 2 Private Const ERR_INVALID_HANDLE As Short = 3 Private Const ERR_INVALID_ADAPTER As Short = 4 Private Const ERR_ALLOCATE_PACKET As Short = 5 Public Adapter As String
The rvPacket
library is developed in unmanaged C++; therefore, these rather cryptic function declarations must be used, in the same way as they were required to access the Windows API. The calling conventions for each of the functions are identical: they all accept a string and return a number.
GetAdapterNames
is used to retrieve a list of network adapters present on the system. One of these adapter names would be passed to openAdapter
, which effectively begins the sniffing process on that network adapter.
CheckPacketQueue
returns the number of packets that are currently held in the network card buffer. The GetQueued
packet can then retrieve each of these packets one at a time.
CloseAdapter
stops the sniffing process and frees up the adapter for any other process to use it. Immediately following the declarations are several constants that can be used within the code to better explain errors to users, and so forth.
Finally, C# programmers require the following namespaces:
C#
using System.Text; using System.Runtime.InteropServices;
To test the application, make sure you are connected to an active network and that WinPCap
and rvPacket
have been installed. Run the program from Visual Studio .NET and press Start. If you open a browser and start using the Web, you will see the HTTP sent and received in the textbox (Figure 13.3).
The following example uses BeeSync’s packetX
control (www.beesync.com) to illustrate the concept. There would be no problem in using rvPacket
for this example, but packetX
is a useful alternative to know how to use. The object of the example is to log packets that match a certain criterion. In this case, only TCP/IP traffic will be logged.
TCP/IP traffic can be isolated from raw network traffic by checking two bytes in the packet. In an IP packet, the IP header follows the frame header and, thus, will appear at the 14th byte in the packet. The first byte in the IP header will always be 69 when IPv4 is used with standard priority. The second byte to check is the protocol byte, the 10th byte in the IP header. This byte will always be 6 when TCP/IP is used.
You will need to have downloaded and installed both WinPCap
and PacketX
before starting to code this program. Start a new project in Visual Studio .NET, right-click on the toolbox on the left, and click Customize Toolbox (or Add/Remove Items in Visual Studio .NET 2003), click the COM tab, check PacketXCtrl Class
, and press OK. Drag the new icon onto the form. Draw a List View control named lvPackets
onto the form as well as a Start button named btnStart
.
To start with, add a few column headers into the list view so that the results it displays will be evenly tabulated. Add the following lines of code to the load
event:
C#
private void Form1_Load(object sender, System.EventArgs e) { lvPackets.Columns.Add("From", lvPackets.Width / 3, HorizontalAlignment.Left); lvPackets.Columns.Add("To", lvPackets.Width / 3, HorizontalAlignment.Left); lvPackets.Columns.Add("Size", lvPackets.Width / 3, HorizontalAlignment.Left); lvPackets.View = View.Details; }
VB.NET
Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load With lvPackets .Columns.Add("From", .Width / 3, HorizontalAlignment.Left) .Columns.Add("To", .Width / 3, HorizontalAlignment.Left) .Columns.Add("Size", .Width / 3, HorizontalAlignment.Left) .View = View.Details End With End Sub
The PacketX
control will not start detecting packets until the Start method is called. This is to facilitate choosing nondefault adapters:
C#
private void btnStart_Click(object sender, System.EventArgs e) { axPacketXCtrl1.Start(); }
Private Sub btnStart_Click(ByVal eventSender As _ System.Object, ByVal eventArgs As System.EventArgs) _ Handles Command1.Click axPacketXCtrl1.Start() End Sub
Unlike the polling mechanism of the rvPacket
library, PacketX
uses an event to notify the host application of the arrival of packets. The event delivers an object that effectively derives from System.EventArgs
, yet contains a PacketClass
object that contains information on the packet contents and the exact time (with microsecond accuracy) the packet was received.
The packet contents are stored in a byte array named Data
. This byte array appears to .NET as a generic object; thus, to handle it without causing type cast errors, Option Strict Off
must be added as the first line of the VB.NET code:
C#
private void axPacketXCtrl1_OnPacket(object sender, AxPACKETXLib._IPktXPacketXCtrlEvents_OnPacketEvent e) { short I; string thisPacket; string SourceIP; string DestIP; ListViewItem item = new ListViewItem(); thisPacket = ""; byte[] packetData = (byte[])e.pPacket.Data; for (I = 0;I<e.pPacket.DataSize - 1;I++) { thisPacket = thisPacket + Convert.ToChar(packetData[I]); } if (packetData[14] == 69 && packetData[23] == 6) { SourceIP = packetData[26] + "." + packetData[27] + "." + packetData[28] + "." + packetData[29]; DestIP = packetData[30] + "." + packetData[31] + "." + packetData[32] + "." + packetData[33] + "."; item.SubItems[0].Text = SourceIP; item.SubItems.Add(DestIP); item.SubItems.Add(e.pPacket.DataSize.ToString()); lvPackets.Items.Add(item); } }
Private Sub axPacketXCtrl1_OnPacket(ByVal eventSender _ As System.Object, ByVal e As _ AxPACKETXLib.IPktXPacketXCtrlEvents_OnPacketEvent) _ Handles axPacketXCtrl1.OnPacket Dim I As Short Dim thisPacket As String Dim SourceIP As String Dim DestIP As String Dim item As New ListViewItem() thisPacket = "" For I = 0 To e.pPacket.DataSize - 1 thisPacket = thisPacket & Chr(eventArgs.pPacket.Data(I)) Next If e.pPacket.Data(14) = 69 And e.pPacket.Data(23) = 6 Then SourceIP = e.pPacket.Data(26) & "." & _ e.pPacket.Data(27) & "." & + _ e.pPacket.Data(28) & "." & + _ e.pPacket.Data(29) DestIP = e.pPacket.Data(30) & "." & _ e.pPacket.Data(31) & "." & + _ e.pPacket.Data(32) & "." & + _ e.pPacket.Data(33) item.SubItems(0).Text = SourceIP item.SubItems.Add(DestIP) item.SubItems.Add(e.pPacket.DataSize) lvPackets.Items.Add(item) End If End Sub
The actual network packet that is passed within the eventArgs
or e
parameter of the event takes the form of an object stored in e.pPacket.Data
, which can be cast to a byte array (implicitly so, in the case of VB.NET). This array is examined for key bytes, first to filter out non-TCP/IP data and then to extract the Local and Remote IP addresses from the header. The extracted data is displayed on-screen in a list box.
To test this application, run it from Visual Studio .NET and wait for a TCP/IP connection to take place on the network. Alternately, simply opening a browser should generate a TCP/IP connection to the server hosting your browser’s home page.
The example shown in Figure 13.4 is one single HTTP request between two computers on a LAN. Note that it does not involve simply one packet for the request and one for the response, but a fair amount of handshaking takes place between client and server, before a connection is made. This may be worth knowing if, for instance, you were using a TCP trace to build up statistics on user browsing habits. You cannot equate the number of packets to the amount of Web pages or emails sent. It might be better to count occurrences of the string HTTP/1.1
or HELLO
in outgoing packets on ports 80 and 25, respectively, in this instance.
A busy network may produce an overwhelming number of packets, and it is likely that .NET will not be able to process 10 Mb of packets per second, as is commonplace on LANs. In this case, we can use hardware filters that are built into network cards to cope with high-volume traffic.
To detect only packets destined for the local machine, we can apply the directed packet hardware filter to the WinPCap
driver by setting a parameter in the PacketX
object with:
PacketXCtrl1.Adapter.HWFilter = 1
The default hardware filter is promiscuous mode, which will pass up every packet seen by the network adapter. The rvPacket
library only operates in promiscuous mode. Wireless network cards cannot operate in promiscuous mode; therefore, a nonpromiscuous hardware filter (Table 13.9) must be applied for wireless devices.
Table 13.9. WinPCap
hardware filters.
Filter ID | Purpose |
---|---|
1 | Directed packets that contain a destination address equal to the station address of the NIC |
2 | Multicast address packets sent to addresses in the multicast address list |
4 | All multicast address packets, not just the ones enumerated in the multicast address list |
8 | Broadcast packets |
16 | All source routing packets |
32 | Default, promiscuous mode; specifies all packets |
64 | SMT packets that an FDDI NIC receives |
128 | All packets sent by installed protocols |
4096 | Packets sent to the current group address |
8192 | All functional address packets, not just the ones in the current functional address |
16384 | Functional address packets sent to addresses included in the current functional address |
32768 | NIC driver frames that a Token Ring NIC receives |
WinPCap
also has the capability to send and receive packets. This functionality can be accessed through Adapter.SendPacket
, which could be useful for generating non-IP-based packets, such as ARP requests or raw server message block (SMB) data. These packets would not be routable over the Internet, but they may have applications within company networks.
Although there would be no conceivable reason for software to read data at this low level, it might be important to know whether the phone line is connected to the computer or not.
A program might also want to determine the type of connection the computer has to the Internet. To cite an example, when developing a peer-to-peer network, clients that have a fast connection via a LAN should be given higher weighting in the index server(s) than 56K dial-up connections. This would ensure that new clients do not waste time attempting to connect to dial-up connections, which would be more than likely disconnected, but instead run queries against more reliable, faster connections.
The Adapter.LinkType
and Adapter.LinkSpeed
properties of PacketX
provide information on the network type (Table 13.10) and link speed in bits per second, respectively.
Using WinPCap
and PacketX
may seem like overkill to determine whether a computer is connected to the Internet, but you could, of course, always ping a well-known Web site address or use the getInternetConnectedState
API function call.
In .NET version 2 (Whidbey), the NetworkInformation
class provides a simple mechanism to determine whether a computer is connected to the network as follows:
NetworkInformation netInfo = new NetworkInformation(); If (netInfo.GetIsConnected() == true) { // connected to network }
VB.NET
Dim netInfo as new NetworkInformation() If (netInfo.GetIsConnected()= True) ' connected to network end if
The NetworkInformation
class (Table 13.11) inherits from System.Net.NetworkInformation
. It contains a host of useful properties, which describe low-level network activities. The last five methods listed in table 13.11 may be alternatively retrieved from the GetNetworkParams
Windows API function.
Table 13.11. Significant members of the NetworkInformation
class.
Method or Property | Purpose |
---|---|
| Sets |
| Lists all active UDP ports. Returns |
| Retrieves statistics of ping (ICMP) activity. Returns |
| Retrieves statistics of IP activity. Returns |
| Determines if the computer is connected to the network. Returns |
| Retrieves information about connected network hardware. Returns |
| Retrieves statistics of TCP/IP activity. Returns |
| Retrieves statistics of UDP/IP activity. Returns |
| Gets the DHCP scope name. Returns |
| Gets the locally registered domain name. Returns |
| Gets the host name for the local computer. Returns |
| Specifies if the computer is acting as a WINS proxy. Returns |
| Gets the NetBIOS node type of the computer. Returns |
The ActiveUdpListener
class, as returned by GetActiveUdpListeners
, is descried in Table 13.12. This is equivalent to calling the GetUdpTable
Windows API, or running NETSTAT -p udp -a
from the command line.
The IcmpV4Statistics
class, as returned by GetIcmpV4Statistics
, is described in Table 13.13 (all properties return int64
unless otherwise specified). This class is equivalent to the GetIcmpStatistics
Windows IP Helper API.
Table 13.13. Significant members of the IcmpV4Statistics
class.
Method or Property | Purpose |
---|---|
| Gets the number of address mask replies received |
| Gets the number of address mask replies sent |
| Gets the number of address mask requests received |
| Gets the number of address mask requests sent |
| Gets the number of destination unreachable messages received |
| Gets the number of destination unreachable messages sent |
| Gets the number of echo replies received |
| Gets the number of echo replies sent |
| Gets the number of echo requests received |
| Gets the number of echo requests sent |
| Gets the number of errors received |
| Gets the number of errors sent |
| Gets the number of messages received |
| Gets the number of messages sent |
| Gets the number of parameter problems received |
| Gets the number of parameter problems sent |
| Gets the number of redirects received |
| Gets the number of redirects sent |
| Gets the number of source quenches received |
| Gets the number of source quenches sent |
| Gets the number of time exceeded messages received |
| Gets the number of time exceeded messages sent |
| Gets the number of timestamp replies received |
| Gets the number of timestamp replies sent |
| Gets the number of timestamp requests received |
| Gets the number of timestamp requests sent |
The IPStatistics
class, as returned by GetIPStatistics
, is described in Table 13.14 (all properties return int64
unless otherwise specified). This is equivalent to calling the GetIpStatistics
Windows IP Helper API, or running NETSTAT -s
from the command line.
Table 13.14. Significant members of the IPStatistics
class.
Method or Property | Purpose |
---|---|
| Gets the default TTL |
| Determines if forwarding is enabled; returns |
| Gets the number of interfaces |
| Gets the number of IP addresses |
| Gets the number of output packet requests |
| Gets the number of output packet routing discards |
| Gets the number of output packets discarded |
| Gets the number of output packets with no route |
| Gets the number of packet fragment failures |
| Gets the number of packet reassemblies required |
| Gets the number of packet reassembly failures |
| Retrieves the packet reassembly timeout |
| Gets the number of packets fragmented |
| Gets the number of packets reassembled |
| Gets the number of received packets |
| Gets the number of received packets delivered |
| Gets the number of received packets discarded |
| Gets the number of received packets forwarded |
| Gets the number of received packets with address errors |
| Gets the number of received packets with headers errors |
| Gets the number of received packets with unknown protocol |
| Gets the number of routes used |
The NetworkInterface
class, as returned by GetNetworkInterfaces
, is described in Table 13.15.
Table 13.15. Significant members of the NetworkInterface
class.
Method or Property | Purpose |
---|---|
| Retrieves information on network activity on the interface. Returns |
| Returns information on the IP address assigned to the interface. Returns |
| Gets information concerning local IP routing, etc. Returns |
| Retrieves the interface’s MAC address. Returns |
| A friendly name for the interface. Returns |
| Determines if DNS is enabled on the interface. Returns |
| Determines if Dynamic DNS is enabled on the interface. Returns |
| Determines the IP version 4 index on the interface. Returns |
| Determines the IP version 6 index on the interface. Returns |
| Determines the IP version(s) supported by the interface. Returns |
| Determines if the interface is connected to an active network. Returns |
| Determines the maximum transmission unit of the interface. Returns |
| Gets a name for the interface. Returns string. |
| Gets the operational status of the interface. Returns |
| Determines the interface hardware. Returns |
The InterfaceStatistics
class, as returned by GetInterfaceStatistics
, is described in Table 13.16 (all properties return int64
unless otherwise specified).
Table 13.16. Significant members of the InterfaceStatistics
class.
Method or Property | Purpose |
---|---|
| Gets the number of bytes received |
| Gets the number of bytes sent |
| Gets the number of incoming packets discarded |
| Gets the number of incoming packets with errors |
| Gets the number of incoming unknown protocol packets |
| Gets the number of non-Unicast packets received |
| Gets the number of non-Unicast packets sent |
| Gets the number of outgoing packets discarded |
| Gets the number of outgoing packets with errors |
| Gets the number of output queue length |
| Gets the speed of the interface |
| Gets the number of Unicast packets received |
| Gets the number of Unicast packets sent |
The IPAddressInformation
class, as returned by GetIPAddressInformation
, is described in Table 13.17.
The IPv4Properties
class, as returned by GetIPv4Properties
, is described in Table 13.18. These properties may be alternatively ascertained on an adapter-by-adapter basis through the GetAdaptersInfo
Windows IP Helper API function.
Table 13.18. Significant members of the IPv4Properties
class.
Method or Property | Purpose |
---|---|
| Retrieves the local DHCP server addresses. Returns |
| Retrieves the local gateway addresses. Returns |
| Retrieves the local WINS servers addresses. Returns |
| Determines if automatic private addressing is active. Returns |
| Determines if automatic private addressing is enabled. Returns |
| Determines if DHCP is enabled. Returns |
| Determines if routing is enabled. Returns |
| Determines if the computer uses WINS. Returns |
The TcpStatistics
class, as returned by GetTcpStatistics
, is described in Table 13.19 (all properties return int64
unless otherwise stated). This is equivalent to calling the GetTcpTable
Windows IP Helper API, or running NETSTAT -p tcp -a
from the command line.
Table 13.19. Significant members of the TcpStatistics
class.
Method or Property | Purpose |
---|---|
| Determines the number of connections accepted |
| Determines the number of connections initiated |
| Determines the number of cumulative connections |
| Determines the number of current connections |
| Determines the number of errors received |
| Determines the number of failed connection attempts |
| Determines the maximum number of connections |
| Determines the maximum transmission time out |
| Determines the minimum transmission time out |
| Determines the number of reset connections |
| Determines the number of segments received |
| Determines the number of segments resent |
| Determines the number of segments sent |
| Determines the number of segments sent with reset |
The UdpStatistics
class, as returned by GetUdpStatistics
, is described in Table 13.20 (all properties return int64
unless otherwise stated). This is equivalent to the GetUdpStatistics
Windows IP Helper API function.
Table 13.20. Significant members of the UdpStatistics
class.
Method or Property | Purpose |
---|---|
| Determines the number of datagrams received |
| Determines the number of datagrams sent |
| Determines the number of incoming datagrams discarded |
| Determines the number of incoming datagrams with errors |
| Determines the number of active UDP listeners |
This chapter has shown three different means to tap nonintrusively into the data that flows between computers. When local system traffic monitoring is all that is required, then use of the pure .NET implementation is highly recommended, but for an enterprisewide implementation, then PacketX
combined with WinPCap
is possibly the best option. Where financial constraints prevent the use of a third-party commercial component, then rvPacket
will probably point you in the right direction.
It would be impossible to document the format of every protocol that could exist on a network, so only IP and TCP have been described in this chapter. Interested readers are advised to consult the relevant RFC for information on any specific protocol.
The next chapter deals with a form of telecommunication that has been with us since the 1880s (i.e., the ubiquitous phone call); however, the chapter is taken from a Computer Telephony Integration (CTI) developer’s perspective. Prepare to be introduced to the telephony API.
18.219.81.43