Use raw socket programming in C # To implement network packet monitoring (zt)

Source: Internet
Author: User
Transferred from Author's blog:Http://blog.csdn.net/cilong521/

When talking about socket programming, you may think of QQ and IE. That's right. Many other network tools, such as P2P and Netmeeting, are implemented at the application layer using socket. Socket is a network programming interface that is implemented on the network application layer. Windows Socket includes a set of system components that fully utilize the features of Microsoft Windows message driver. The socket Specification Version 1.1 was released in January 1993 and is widely used in later Windows 9x operating systems. Socket Specification Version 2.2 (its version on Windows is winsock2.2, also known as Winsock2) was released in May 1996. Windows NT 5.0 and later versions support Winsock2. In Winsock2, supports original sockets of multiple transmission protocols, overlapping I/O models, and service quality control.

This article introduces some programming of the original socket (raw socket) implemented by C # in Windows Sockets and the network packet Monitoring Technology Based on this. Compared with winsock1, Winsock2 supports the raw socket type most obviously. Using raw socket, you can set the NIC to the hybrid mode. In this mode, we can receive the IP packet on the network, of course, for the purpose of not the local IP packet, through the original socket, we can also more freely control the various protocols in windows, it can also control the underlying transmission mechanism of the network.

In this example, the rawsocket class is implemented in the nbyte. basicclass namespace, which includes the core technology for implementing packet monitoring. Before implementing this class, you need to write an IP header structure to temporarily store some information about network packets:

[Structlayout (layoutkind. explicit)]
Public struct ipheader
{
[Fieldoffset (0)] public byte ip_verlen; // I4-bit Header Length + 4-bit IP version number
[Fieldoffset (1)] public byte ip_tos; // 8-bit service type TOS
[Fieldoffset (2)] public ushort ip_totallength; // The total length of a 16-bit data packet (in bytes)
[Fieldoffset (4)] public ushort ip_id; // 16-bit ID
[Fieldoffset (6)] public ushort ip_offset; // 3-Bit Flag
[Fieldoffset (8)] public byte ip_ttl; // 8-bit TTL
[Fieldoffset (9)] public byte ip_protocol; // 8-bit protocol (TCP, UDP, ICMP, etc .)
[Fieldoffset (10)] public ushort ip_checksum; // 16-bit IP header checksum
[Fieldoffset (12)] public uint ip_srcaddr; // 32-bit source IP address
[Fieldoffset (16)] public uint ip_destaddr; // 32-bit destination IP address
}

In this way, when each packet arrives, you can use forced type conversion to convert the data stream in the packet into ipheader objects.
The following describes the rawsocket class. At the beginning, several parameters are defined, including:
Private bool error_occurred; // whether an error occurs when the socket receives the packet
Public bool keeprunning; // whether to continue
Private Static int len_receive_buf; // The length of the data stream.
Byte [] receive_buf_bytes; // received bytes
Private Socket socket = NULL; // declare a socket
There is also a constant:
Const int sio_rcvall = unchecked (INT) 0x98000001); // listen to all data packets

The sio_rcvall indicates that rawsocket receives all data packets. It will be used in later iocontrl functions. In the following constructor, initialization of some variable parameters is implemented:

Public rawsocket () // Constructor
{
Error_occurred = false;
Len_receive_buf = 4096;
Receive_buf_bytes = new byte [len_receive_buf];
}

The following function creates rawsocket and binds it to the endpoint (ipendpoint: local IP address and port:
Public void createandbindsocket (string IP) // create and bind a socket
{
Socket = new socket (addressfamily. InterNetwork, sockettype. Raw, protocoltype. IP );
Socket. Blocking = false; // set the non-blocking status of the socket.
Socket. BIND (New ipendpoint (IPaddress. parse (IP), 0); // bind a socket

If (setsocketoption () = false) error_occurred = true;
}
There are three parameters in socket = new socket (addressfamily. InterNetwork, sockettype. Raw, protocoltype. IP) when creating a socket:

The first parameter is to set the address family. The description on msdn is "addressing scheme for specifying the socket instance to resolve the address". When you want to bind the socket to the IP endpoint, internetwork members need to be used, that is, the address format of IP version 4. This is also an addressing solution (addressfamily) adopted by most socket programming today ).

The socket type set by the second parameter is the raw type we use. sockettype is an enumeration data type. The raw socket type supports access to the basic transmission protocol. Use sockettype. raw. You can not only use the Transmission Control Protocol (TCP) to communicate with User Datagram Protocol (UDP), but also use the Internet message Control Protocol (ICMP) and Internet Group Management Protocol (IGMP). At the time of sending, your application must provide the complete IP header. The IP header and option of the received datagram remain unchanged when it is returned.

The third parameter sets the protocol type. The socket class uses protocoltype to enumerate the data type and notifies the Windows Socket API of the requested protocol. The IP protocol is used here, so the protocoltype. IP parameter must be used.

The createandbindsocket function has a custom setsocketoption function, which is different from the setsocketoption function in the socket class. Here we define the setsocketoption with IO control. Its definition is as follows:

Private bool setsocketoption () // sets raw socket
{
Bool ret_value = true;
Try
{
Socket. setsocketoption (socketoptionlevel. IP, socketoptionname. headerincluded, 1 );
Byte [] In = new byte [4] {1, 0, 0, 0 };
Byte [] out = new byte [4];
 
// In the low-level operation mode, it is critical to accept all data packets. You must set the socket to raw and IP level for the sio_rcvall operation.
Int ret_code = socket. iocontrol (sio_rcvall, in, out );
Ret_code = out [0] + out [1] + out [2] + out [3]; // synthesize four 8-bit bytes into a 32-bit integer
If (ret_code! = 0) ret_value = false;
}
Catch (socketexception)
{
Ret_value = false;
}
Return ret_value;
}

When setting the socket option, the socket must contain an IP header. Otherwise, the IP header structure cannot be filled or the packet information cannot be obtained.
Int ret_code = socket. iocontrol (sio_rcvall, in, out); is the most critical step in the function, because in windows, we cannot use the receive function to receive raw socket data. This is because, all IP packets are first submitted to the system core and then transmitted to the user program. When a raws socket packet (such as SYN) is sent, the core does not know, there is no record of this data being sent or established by the connection. Therefore, when the remote host responds, the system core will discard all these packages and thus cannot access the application. Therefore, you cannot simply use the receive function to receive these data packets. To receive data, you must use sniffing to receive all the passed data packets, and then filter them, leaving the data that meets our needs. You can set sio_rcvall to receive packets from all networks. Next we will introduce the iocontrol function. Msdn explains that it sets sockets to a low-level operation mode. How can I perform a low-level operation? In fact, this function is similar to the wsaioctl function in the API. The wsaioctl function is defined as follows:

Int wsaioctl (
Socket S, // a specified socket
DWORD dwiocontrolcode, // control the operation code
Lpvoid lpvinbuffer, // pointer to the input data stream
DWORD cbinbuffer, // size of the input data stream (bytes)
Lpvoid lpvoutbuffer, // pointer to the output data stream
DWORD cboutbuffer, // size of the output data stream (bytes)
Lpdword maid, // point to the number of output byte streams
Lpwsaoverlapped lpoverlapped, // point to a wsaoverlapped Structure
Lpwsaoverlapped_completion_routine lpcompletionroutine // point to the routine executed when the operation is complete
);

The iocontrol function of C # is not as complex as the wsaioctl function. It only includes three parameters: control operation code, input byte stream, and output byte stream. However, these three parameters are sufficient. The function defines a byte array: byte [] In = new byte [4] {1, 0, 0, 0} is actually a DWORD or int32 with a value of 1, also byte [] out = new byte [4]; also, it is integrated with an int, as the value pointed to by the lpcbbytesreturned parameter in the wsaioctl function.
Because errors may occur when setting SOCKET options, you need to use a value to pass the error mark:

Public bool erroroccurred
{
Get
{
Return error_occurred;
}
}

The following functions receive data packets:

// Parse the received data packet to form the packetarrivedeventargs event data Class Object and trigger the packetarrival event
Unsafe private void receive (byte [] Buf, int Len)
{
Byte temp_protocol = 0;
Uint temp_version = 0;
Uint temp_ip_srcaddr = 0;
Uint temp_ip_destaddr = 0;
Short temp_srcport = 0;
Short temp_dstport = 0;
IPaddress temp_ip;

Packetarrivedeventargs E = new packetarrivedeventargs (); // new network packet Information Event

Fixed (byte * fixed_buf = BUF)
{
Ipheader * head = (ipheader *) fixed_buf; // sets the data stream to an ipheader structure.
E. headerlength = (uint) (Head-> ip_verlen & 0x0f) <2;

Temp_protocol = head-> ip_protocol;
Switch (temp_protocol) // extract protocol type
{
Case 1: E. Protocol = "ICMP"; break;
Case 2: E. Protocol = "IGMP"; break;
Case 6: E. Protocol = "TCP"; break;
Case 17: E. Protocol = "UDP"; break;
Default: E. Protocol = "unknown"; break;
}

Temp_version = (uint) (Head-> ip_verlen & 0xf0)> 4; // extract the IP protocol version
E. ipversion = temp_version.tostring ();

// The following statement extracts other parameters in the packetarrivedeventargs object
Temp_ip_srcaddr = head-> ip_srcaddr;
Temp_ip_destaddr = head-> ip_destaddr;
Temp_ip = new IPaddress (temp_ip_srcaddr );
E. originationaddress = temp_ip.tostring ();
Temp_ip = new IPaddress (temp_ip_destaddr );
E. destinationaddress = temp_ip.tostring ();

Temp_srcport = * (short *) & fixed_buf [E. headerlength];
Temp_dstport = * (short *) & fixed_buf [E. headerlength + 2];
E. originationport = IPaddress. networktohostorder (temp_srcport). tostring ();
E. destinationport = IPaddress. networktohostorder (temp_dstport). tostring ();

E. packetlength = (uint) Len;
E. messagelength = (uint) len-E. headerlength;

E. receivebuffer = Buf;
// Assign the IP header in the Buf to ipheaderbuffer in packetarrivedeventargs
Array. Copy (BUF, 0, E. ipheaderbuffer, 0, (INT) E. headerlength );
// Assign the package content in the Buf to messagebuffer in packetarrivedeventargs
Array. Copy (BUF, (INT) E. headerlength, E. messagebuffer, 0, (INT) E. messagelength );
}
// Trigger the packetarrival event
Onpacketarrival (E );
}

We have noticed that in the above function, we use the so-called Insecure code like pointer, it can be seen that the original operations such as pointer and shift operations in C # can also bring programming convenience to the programmer. Declare the packetarrivedeventargs class object in the function, so that the data packet information can be transmitted through the onpacketarrival (e) function through events. The packetarrivedeventargs class is a nested class in the rawsocket class. It inherits the system event class and encapsulates information contained in other data headers, such as the IP address, port, and Protocol of the data packet. In the function that starts receiving data packets, we use the asynchronous operation method. The following function enables the asynchronous listening interface:

Public void run () // start listening
{
Iasyncresult AR = socket. beginreceive (receive_buf_bytes, 0, len_receive_buf, socketflags. None, new asynccallback (callreceive), this );
}

Socket. the beginreceive function returns an asynchronous operation interface, and declares the asynchronous callback function callreceive in the beginreceive function, and transmits the received network data stream to receive_buf_bytes, in this way, an asynchronous callback function with interface parameters for asynchronous operations can continuously receive data packets:

Private void callreceive (iasyncresult AR) // asynchronous callback
{
Int received_bytes;
Export ed_bytes = socket. endreceive (AR );
Receive (receive_buf_bytes, received_bytes );
If (keeprunning) Run ();
}

This function receives a new data packet when it suspends or ends asynchronous reading. This ensures that every data packet can be detected by the program.
The following describes how to declare a proxy event handle to communicate with the outside world:

Public Delegate void packetarrivedeventhandler (Object sender, packetarrivedeventargs ARGs );
// Event handle: the event is triggered when the package arrives.
Public event packetarrivedeventhandler packetarrival; // declare the time handle Function

In this way, the data packet information can be obtained. the asynchronous callback function can improve the efficiency of receiving data packets and transmit the packet information to the outside world through proxy events. Since we can pass all the packet information out, we can analyze the data packet. :) However, the rawsocket task is not complete yet. In the end, do not close the socket:

Public void Shutdown () // close raw socket
{
If (socket! = NULL)
{
Socket. Shutdown (socketshutdown. Both );
Socket. Close ();
}
}

The rawsocket class obtains information in the package by constructing an IP header, and receives data packets through an asynchronous callback function, the time proxy handle and custom data packet information events are used to send data packet information, so as to monitor network data packets, in this way, we can add some functions to analyze data packets.

Author's blog:Http://blog.csdn.net/cilong521/

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.