Linux tips -- analog ping of raw socket

Posted by Michael Lasky on Thu, 04 Nov 2021 23:27:34 +0100

Raw socket raw socket

In recent years, the main idea of scanning the surviving host is to simulate the ICMP message through socket, and then discover a new world of socket -- raw socket.

raw socket, that is, the original socket, can receive data frames or packets on the local network card. It is very useful for monitoring network traffic and analysis. There are four ways to create this socket.
1.socket(PF_INET, SOCK_RAW, IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP) sends and receives ip packets
2.socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)) sends and receives Ethernet data frames
3.socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)) sends and receives Ethernet data frames (excluding Ethernet header) [1]
4.socket(PF_INET, SOCK_PACKET, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)) is outdated. Don't use it

We can see that through the original socket, we can get all the information of layer 2 and layer 3, and can also simulate various protocols of layer 2 and layer 3. Is it very powerful

So if you want to simulate ping, you need to use the first method,
socket(PF_INET, SOCK_RAW, IPPROTO_ICMP).
We can simulate the creation of various three-tier protocols through the third parameter of the socket function. Other types will be used when simulating the TCP protocol in the next phase.

The following is the ICMP function created after encapsulation

/* create a socke to icmp */
int icmp_socket(char *ipv4)
{
    int sockfd;
    int size = ICMP_RECVBUF_SIZE;
    
    if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0)
    {
       	usleep(1000);
		if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0)
	    {
	        ICMP_LOG_ERR("establish socket fail");
	        return -1;
	    }
    }

	//
	//To filter the response source address, you need to bind the source IP here
    {
		struct sockaddr_in serv_addr;
		unsigned long ul = 1;
		
		ioctl(sockfd, FIONBIO, &ul); //Set to non blocking mode
		
		memset(&serv_addr, 0, sizeof(serv_addr));  //Each byte is filled with 0
		serv_addr.sin_family = AF_INET;  //Use IPv4 address
		serv_addr.sin_addr.s_addr = inet_addr(ipv4);  //Specific IP address
		serv_addr.sin_port = htons(1234);  //Fill in the port at will,

		//Bind socket to IP and port
		if(connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
        {
        	struct timeval tm;
			fd_set set;
            int error=-1, len;
			
            len = sizeof(int);
            tm.tv_sec = 1;
            tm.tv_usec = 0;
            FD_ZERO(&set);
            FD_SET(sockfd, &set);
            if( select(sockfd+1, NULL, &set, NULL, &tm) > 0)
            {
                getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
                if(error == 0)
                {
					//ret = true;
				}
                else
                {
					ICMP_LOG_ERR("connect[%s] socket Failed 1",ipv4);
					close(sockfd);
					return -1;
				}
            } 
			else
			{
				ICMP_LOG_ERR("connect[%s] socket Fail 2",ipv4);
				close(sockfd);
				return -1;
			}
        }
        else
        {
			//ret = true;
		}
        ul = 0;
        ioctl(sockfd, FIONBIO, &ul); //Set to blocking mode
	}
	//Set receive buf size
    if(setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size))!=0)
    {
        ICMP_LOG_ERR("setsockopt SO_RCVBUF error.\n\r");
        close(sockfd);
        return -1;
    }

	//Set the timeout to affect the subsequent revform function so that it will not be blocked all the time
    if(setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char*)&icmp_wait_timeout, sizeof(struct timeval))!=0)
    {
        ICMP_LOG_ERR("setsockopt SO_RCVTIMEO error.\n\r");
        close(sockfd);
        return -1;
    }
   
    return sockfd;
}

The more important point in this function is that to filter the response source address, the source IP needs to be bound here. The meaning of this sentence is that the Raw socket we created is relatively loose filtering. If the source IP is not bound, we will receive all ICMP messages. It doesn't matter if the network environment is clean, but if it is a complex environment, If there are other ICMP messages in it, it will interfere with our analysis of response results.
The following paragraph is very important for Raw Socket

When the kernel has an IP datagram that needs to be passed to the original socket, it checks the original socket on all processes to find all matching sockets. Each matching socket will be delivered with a copy of the IP datagram. (it turns out that if there are too many processes and too many matching sockets, the kernel will be busy with soft filtering and distribution of datagrams, while the actual sockets are idle, resulting in performance degradation)
The kernel performs the following three tests on each original socket. Only when the three tests are true, the kernel will deliver the received datagram to the socket.
[1] If a non-zero protocol parameter (the third parameter of the socket) is specified when creating the socket, the protocol field of the received datagram must match the value, otherwise the datagram will not be delivered to the socket
[2] If the original socket has been bound to a local IP address by the bind call, the destination IP address of the received datagram must match the bound address, otherwise the datagram will not be delivered to the socket.
[3] If the original socket has a foreign IP address specified by the connect call, the source IP address of the received datagram must match the connected address, otherwise the datagram will not be delivered to the socket.

Improper use of bind and connect will directly affect your data reception. If it is not handled well, you should touch and receive more, or you can't receive it

The following is the function of each module.
Calculate check code function

unsigned short icmp_gen_chksum(unsigned short * data, int len)
{
    int             nleft   = len;
    int             sum     = 0;
    unsigned short  *w      = data;
    unsigned short  answer  = 0;
 
    while (nleft > 1)
    {
        sum += *w++;
        nleft -= 2;
    }
 
    if (nleft == 1)
    {
        * (unsigned char *) (&answer) = *(unsigned char *)w;
        sum += answer;
    }
 
    sum     = (sum >> 16) + (sum & 0xffff);
    sum     += (sum >> 16);
    answer  = ~sum;
    
    return answer;
}

Generate ICMP message function. Note that if there is no IP address bound in front, you can also configure ICMP here_ ID, which is passed back and forth between the two places in each operation, so it can be used for authentication.

int icmp_pkg_pack(void *buffer,const void *data, int data_size)
{
    int  packsize = 0;
    struct icmp * icmp  = malloc(sizeof(struct icmp));
    icmp->icmp_type     = ICMP_ECHO;
    icmp->icmp_code     = 0;
    icmp->icmp_cksum    = 0;
    icmp->icmp_seq      = htons(0);
    icmp->icmp_id       = 0xff00;

    gettimeofday((struct timeval *) &icmp->icmp_data, NULL);
 
    memcpy(buffer, icmp, sizeof(struct icmp));
    packsize += sizeof(struct icmp);
 
    if(data && data_size)
    {
        memcpy(buffer+packsize, data, data_size);
        packsize += data_size;
    }
 
    return packsize;
}
 

Send ICMP message function

 
/* send icmp package */
int icmp_send_pkg(char* ipv4,int socket, const void *data, int size)
{
    int             packetsize;
    unsigned short  checksum = 0;
    int             n = 0;
  	struct sockaddr_in dst_addr;
     
    char pkg_buffer[ICMP_BUF_SIZE];
    
    packetsize  = icmp_pkg_pack(pkg_buffer, data, size);
    checksum    = icmp_gen_chksum((unsigned short *)pkg_buffer, packetsize);
    
     icmp_dst_addr(ipv4, &dst_addr);

    memcpy(pkg_buffer + ICMP_PKG_CHKSUM_OFFSET, &checksum, ICMP_PKG_CHKSUM_SIZE);
 
    if ((n = sendto(socket, pkg_buffer, packetsize, 0, (struct sockaddr *) &dst_addr, sizeof(struct sockaddr_in)))< 0)
    {
	        ICMP_LOG_ERR("send out ICMP fail n = %d", n);
	        return 0;
    }
                       
    return n;
}

Receive ICMP message function

int icmp_recv_pkg(int socket, void *recvbuf, int size)
{
    int n;
	socklen_t fromlen;
	struct sockaddr_in from_addr; 
	 
    fromlen = sizeof(struct sockaddr_in);
 
    if((n = recvfrom(socket, recvbuf, size, 0, (struct sockaddr *) &from_addr, &fromlen)) < 0)
    {
        ICMP_LOG_ERR("recvfrom error.n = %d\n\r", n);
        return 0;
    }
    return n;
}

Parsing ICMP message function

int icmp_pkg_unpack(char * buf, int len)
{
    int		iphdrlen;
    struct  ip * ip = NULL;
    struct  icmp * icmp = NULL;
 
    ip          = (struct ip *)buf;
    iphdrlen    = ip->ip_hl << 2;
    icmp        = (struct icmp *) (buf + iphdrlen);
    len        -= iphdrlen;
 
    if (len < 8)
    {
        ICMP_LOG_ERR("ICMP packet\'s length is less than 8\n\r");
        return - 1;
    }
    if (icmp->icmp_type != ICMP_ECHOREPLY) 
    {
        return - 1;
    }
    return 0;
}

The main function tests the ping function and some definitions

#ifndef offsetof
#define offsetof(type, member)    ((int) & ((type*)0) -> member )
#endif
 
#define ICMP_BUF_SIZE           256
#define ICMP_RECVBUF_SIZE       (50 * 1024)

#define ICMP_PROTO_NAME         "icmp"
#define ICMP_DATA               "my ICMP Ping"
 
#define ICMP_PING_SUCC          __ICMP_PING_SUCC
#define ICMP_PING_FAIL          __ICMP_PING_FAIL
 
#define ICMP_LOG(fmt...)        printf(fmt);
 
#define ICMP_LOG_ERR(fmt...)   	printf(fmt);

#define ICMP_PKG_CHKSUM_OFFSET  offsetof(struct icmp, icmp_cksum)
#define ICMP_PKG_CHKSUM_SIZE    2

typedef enum 
{
    __ICMP_PING_FAIL = 0,
    __ICMP_PING_SUCC,
}icmp_ping_rlst_t;

static struct timeval icmp_wait_timeout = {0,100000}; //sec

int icmp_ping_fun(char *ipv4)
{
	int res=__ICMP_PING_FAIL;
    char pkg_buffer[ICMP_BUF_SIZE];
    int ping_socket = 0;
	
	ping_socket =icmp_socket(ipv4);

	if(ping_socket ==NULL)
	{
		return __ICMP_PING_FAIL;
	}
	else
	{
		int n=0;
		n = icmp_send_pkg(ipv4,ping_socket, ICMP_DATA, sizeof(ICMP_DATA));
		if(n < 8) 
		{
			close(ping_socket );
			return __ICMP_PING_FAIL;
		}
		else
		{
			n = icmp_recv_pkg(ping_socket, pkg_buffer, ICMP_BUF_SIZE);
			if(n<8) 
			{
				close(ping_socket );
				return __ICMP_PING_FAIL;
			}
			else
			{
				if(icmp_pkg_unpack(pkg_buffer, n)==0)
				{
    				close(ping_socket );
   					return __ICMP_PING_SUCC;
    				}
				else
				{
					close(ping_socket );
					return __ICMP_PING_FAIL;
				}
			}
		}
	}
}

This operation, combined with multithreading, ping65535 IP S at the same time, can get all the results in 3 seconds.

Finally, I thank these people for their dedication, refer to other people's code and make some changes.

The ICMP Protocol is implemented in C language and tested by PING
raw_ Ultimate summary of socket (original socket) and ordinary socket use

The next chapter will introduce the use of raw sockets to simulate tcp communication for remote port scanning again.
A while ago, in order to provide children with a place to play with the soil and a shed shared with their neighbors, children's idea of seeing the soil was

It turns out that we adults from rural areas prefer this feeling of farming.

I picked up several strawberry plants from the waste soil of the nearby greenhouse, and they actually blossomed

Topics: Linux Network Protocol Ping