Use of network sockets

This article introduces primitive sockets to implement classic network commands , namely the Ping command . Through the completion of a Ping command to initially understand and master the use of raw sockets.

When using TCP or UDP, you need to specify the corresponding type for its second parameter when calling the socket() function. For example, SOCK_STREAM means to use TCP, and SOCK_DGRAM means to use UDP protocol. In addition to specifying these two types, you can also specify the raw socket type, that is, SOCK_RAW. When the second parameter of the socket() function is specified as SOCK_STREAM or SOCK_DGRAM, the third parameter can be defaulted. When the second parameter of the socket() function is specified as SOCK_RAW, the third parameter must clearly specify the protocol to be used.

When the socket type is specified as SOCK_RAW, the common values ​​of the protocol type are IPPROTO_IP, IPPROTO_ICMP, IPPROTO_TCP, IPPROTO_UDP and IPPROTO_RAW. Using the first four types, when sending data, the system will automatically add the IP header to the data and set the upper layer protocol field in the IP header (if there is the IP_HDRINCL option, the system will not automatically add the IP header); when receiving data, The system will not remove the IP header, it needs to be processed by the program itself. If IPPROTO_RAW is used, the system sends the data packet directly to the network layer to send the data, and the program needs to construct the fields in the IP header by itself.

This article introduces primitive sockets to implement classic network commands, namely the Ping command. Through the completion of a Ping command to initially understand and master the use of raw sockets.

1. Use of Ping command

The purpose of the Ping command is to test whether another host is reachable. The Ping command sends an ICMP echo request message to the host and waits for an ICMP echo response to be returned. Generally speaking, if you cannot ping a certain host, then you cannot communicate with that host (the exception is that the firewall of the other host blocks the echo request packets that enter the host. In this case, although the ping fails, but Can still communicate normally).

The Ping command has many parameters. Open the command line and directly enter Ping and press the Enter key, so that you can see the parameter list of the Ping command, as shown in the figure below.

Use of network sockets Use of network sockets

Normally, users simply ping the address of a certain host. The parameters of the Ping command can be the host name, domain name, and IP address, the latter two are more commonly used. The following is a simple demonstration of a Ping example, as follows:

C:\>ping 8.8.4.4  
Pinging 8.8.4.4 with 32 bytes of data:  
Reply from 8.8.4.4: bytes=32 time=57ms TTL=47  
Reply from 8.8.4.4: bytes=32 time=54ms TTL=47  
Reply from 8.8.4.4: bytes=32 time=54ms TTL=47  
Reply from 8.8.4.4: bytes=32 time=51ms TTL=47  
Ping statistics for 8.8.4.4:  
 Packets: Sent = 4, Received = 4, Lost = 0 (0% loss), 
Approximate round trip times in milli-seconds:  
 Minimum = 51ms, Maximum = 57ms, Average = 54ms 

The above is the output information after using the Ping command to echo the 8.8.4.4 IP. Here to explain the meaning of the echo message after the request.

Pinging 8.8.4.4 with 32 bytes of data: 

32-byte data is being sent to the remote host 8.8.4.4. If Ping is a domain name or host name, the domain name (host name) will be converted to an IP address and displayed.

Reply from 8.8.4.4: bytes=32 time=57ms TTL=47 

The local host has received the echo response message, bytes=32 means there are 32 bytes, time=57ms means 57 milliseconds in common, TTL means the time to live value, this value can be set, the maximum value is 255. Every router that processes a packet needs to subtract 1 from the TTL value or minus the number of seconds the packet stays in the router. Since most routers forward data packets with a delay of less than 1 second, TTL eventually becomes a hop counter. Each router that passes through will reduce its value by 1. When the value is reduced to 0, the packet Will be discarded.

Ping statistics for 8.8.4.4:  
 Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),  
Approximate round trip times in milli-seconds:  
 Minimum = 51ms, Maximum = 57ms, Average = 54ms 

The statistical information of Ping 8.8.4.4 is: Sent=4 means 4 data packets are sent, Received=4 means 4 data packets are received, Lost=0 (0% loss) means 0 packets are lost, and packets are lost. The rate is 0%.

The approximate situation of the sending time: Mininum=51ms, the fastest is 51ms, the Maximum=57ms, the slowest is 57ms, Average=54ms, and the average is 54ms.

2. The structure of the Ping command

The Ping command does not depend on TCP or UDP, it depends on ICMP. ICMP is one of the protocols of the IP layer. It transmits error messages and other information that needs attention. ICMP messages are usually used by the IP layer or higher-level protocols. ICMP is encapsulated inside the IP datagram, as shown in the figure below.

Use of network sockets Use of network sockets

The format of the ICMP message is shown in the figure below.

Use of network sockets Use of network sockets

The type code and code of the ICMP protocol take different values ​​according to different situations. The Ping command type code uses two values, 0 and 8. The value of the code is 0. When the value of the type code is 0, the value of 0 of the code indicates an echo response; when the value of the type code is 8, the value of 0 of the code indicates that an echo is requested. When the Ping command sends an ICMP datagram, the type code is 8 and the code is 0, which means the request is echoed to the other party's host; when the other party's ICMP datagram is received, the type code is 0, and the code is 0, which means that the other party's host is received. Echo the response. Simply put, in the data sent by the Ping command, the type is 8 and the code is 0. If the other party responds, then in the data that the other party responds, the type is 0 and the code is 0.

When implementing the Ping command by yourself, it is to construct an ICMP datagram requesting echo, and then send it. The data structure of ICMP is defined as follows:

// ICMP protocol structure definition   
struct icmp_header   
{   
  unsigned char icmp_type; // message type   
  unsigned char icmp_code; // code   
  unsigned short icmp_checksum; // checksum   
  unsigned short icmp_id; // ID number used to uniquely identify this request, Usually set to process ID   
  unsigned short icmp_sequence; // serial number   
  unsigned long icmp_timestamp; // timestamp   
);

The data structure of ICMP is often used in network development and can be saved for later use.

Understand the data structure of the ICMP protocol, now use the packet capture tool (also known as the protocol analysis tool) Wireshark to analyze the real situation of the ICMP structure, as shown in the figure below.

Use of network sockets Use of network sockets

In the above figure, the part marked with 1 is to filter the protocol. Entering "ICMP" in this part allows Wireshark to display only ICMP data records. Correspondingly, you can enter "TCP", "UDP", "HTTP" and other protocols for filtering. The part of mark 2 is used to display the filtered ICMP record, from here it can be clearly seen the source IP address, destination IP address, and protocol type. The part marked 3 is used to display the value of the ICMP data structure and additional data content. The bottom part shows the original binary data of the data. After mastering the protocol, it is not impossible to view the original binary data.

3. Implementation of Ping command

With the previous foundation, you can construct your own ICMP datagram to construct your own Ping command. First, define two constants and a function to calculate the checksum, as follows:

struct icmp_header   
{   
  unsigned char icmp_type; // message type   
  unsigned char icmp_code; // code   
  unsigned short icmp_checksum; // checksum   
  unsigned short icmp_id; // ID number used to uniquely identify this request, usually set to the process ID   
  unsigned short icmp_sequence; // Serial number   
  unsigned long icmp_timestamp; // Timestamp 
);   
#define ICMP_HEADER_SIZE sizeof(icmp_header)   
#define ICMP_ECHO_REQUEST 0x08   
#define ICMP_ECHO_REPLY 0x00   
// Calculate checksum   
unsigned short chsum(struct mp_header *pic)   
{   
  long sum = 0;   
  unsigned short *pusicmp = (unsigned short *)picmp;   
  while (len> 1)   
  {    
    sum += *(pusicmp++);  
    if (sum & 0x80000000)  
    {  
      sum = (sum & 0xffff) + (sum >> 16);  
    }  
    len -= 2; 
  }  
  if ( len )  
  {  
    sum += (unsigned short)*(unsigned char *)pusicmp;  
  }  
  while ( sum >> 16 )  
  {  
    sum = (sum & 0xffff) + (sum >> 16);  
  }  
  return (unsigned short)~sum;  
} 

The check value of ICMP is a 16-bit unsigned integer, which accumulates the data in the header of the ICMP protocol. When the accumulation overflows, the overflowed part is also accumulated. The specific algorithm for calculating the checksum will not be introduced too much. If you do not understand the code for the checksum calculation, you can perform single-step debugging for analysis. Let's take a look at the filling of the ICMP structure. The specific code is as follows:

BOOL MyPing(char *szDestIp)   
{   
  BOOL bRet = TRUE;   
  WSADATA wsaData;   
  int nTimeOut = 1000;   
  char szBuff[ICMP_HEADER_SIZE + 32] = {0 };   
  icmp_header *pIcmp = (icmp_header *)szBuff;   
  char icmp_data[32 0 };   
  WSAStartup(MAKEWORD(2, 2), &wsaData); 
  // Create raw socket   
  SOCKET s = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)   
  // Set receiving timeout   
  setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char const* )&nTimeOut, sizeof(nTimeOut));   
  // Set the destination address   
  sockaddr_in dest_addr;   
  dest_addr.sin_family = AF_INET;   
  dest_addr.sin_addr.S_un.S_addr = inet_addr(szDestIp);    
  dest_addr.sin_port =  
  // Construct an ICMP packet  
  pIcmp-> = ICMP_ECHO_REQUEST An icmp_type;   
  pIcmp-> icmp_code = 0;   
  pIcmp-> icmp_id = (USHORT) :: GetCurrentProcessId ();   
  pIcmp-> icmp_sequence = 0;   
  pIcmp-> icmp_timestamp = 0;   
  pIcmp-> icmp_checksum = 0;   
  / / Copy data   
  // The data here can be arbitrary   
  // The abc used here is to look the same as the one provided by the system   
  memcpy((szBuff + ICMP_HEADER_SIZE), "abcdefghijklmnopqrstuvwabcdefghi", 32);   
  // Calculate the checksum   
  pIcmp-> icmp_checksum = chsum((struct icmp_header *)szBuff, sizeof(szBuff));   
  sockaddr_in from_addr;   
  char szRecvBuff[1024];   
  int nLen = sizeof(from_addr);    
  sendto(s, szBuff, size) 0, (SOCKADDR *)&dest_addr,  sizeof(SOCKADDR));  
  recvfrom(s, szRecvBuff, from MAXBYr, *) &nLen);  
  // Determine whether the received address is the address you requested  
  if ( lstrcmp(inet_ntoa(from_addr.sin_addr), szDestIp) )  
  {  
    bRet = FALSE;  
  }  
  else  
  {  
    struct icmp_header *pIcmp1 = (icmp_header *)(szRecvBuff + 20);  
    printf("%s\r\n", inet_ntoa(from_addr.sin_addr));  
  }  
  return bRet;  
} 

This is the entire code of the Ping command. Write a function yourself and call it for testing.

When running in an operating system above Windows XP, such as Windows 8, the program may not run normally due to the operating system permissions. Right-click on the compiled program and select "Run as Administrator" on the pop-up menu, so that the program can be executed normally.

Guess you like

Origin blog.csdn.net/yaxuan88521/article/details/113877395