Overview
Based on the byte stream socket (SOCK_STREAM) and datagram Sockets (SOCK_DGRAM) can not access the Transport layer protocol, only the application layer of the message to operate, the datagram format of the transport layer is provided by the System protocol stack implementation, the user only need to fill the corresponding application beginning text, The system completes the filling and sending of the underlying message header. The original socket (SOCK_RAW) can access the Transport layer protocol at the base level, with no port number for the original socket.
The original socket (SOCK_RAW) is a different socket than Sock_stream, SOCK_DGRAM, which is implemented at the core of the system. The original socket enables the process to read and write network messages such as ICMP, IGMP, or special IPV4 messages; The process can also construct the IP header by the user by setting the IP_HDRINCL socket option. The original socket can be used to assemble the IP datagram itself and then send the datagram to another terminal. However, only Administrator privileges can use the original socket, which prevents ordinary users from writing their own constructed IP datagrams to the network.
Raw Socket Creation
When you call the socket function to create a socket, specify that the socket type is sock_raw to create an original socket.
int sockfd;/* Creates a IPv4 of the original socket */SOCKFD = socket (af_inet, SOCK_RAW, protocol);
After you create the original socket, you can choose whether to turn on the IP_HDRINCL socket option.
const int on = 1;IF (setsockopt (SOCKFD, Ipproto_ip, Ip_hdrincl, &on, sizeof (ON)) < 0)/* Next there are some error handlers */
Raw socket OUTPUT
The output of the original socket follows these rules:
- If the socket is already connected, the write, Writev, or send function output can be called, otherwise the normal output can only call the SendTo or sendmsg function and specify the destination IP address to complete the output;
- The starting address of the process for the data sent by the kernel:
- If the P_HDRINCL socket option is not turned on, the starting address is the first byte after the IP header , because at this point the IP header is constructed by the kernel and placed before the data from the process;
- If the P_HDRINCL socket option is turned on, the starting address is the first byte of the IP header , because at this point the IP header is constructed by the process, so the process data contains the IP header;
- The kernel shards the original packet that exceeds the MTU (maximum transmission unit) of the Egress interface;
Raw socket input
The original socket follows the following rules:
- Received UDP packets and TCP packets are never passed to any of the original sockets;
- Most ICMP packets are passed to the original socket after the kernel has finished processing the ICMP messages;
- All IGMP packets are passed to the original socket after the kernel has processed the IGMP message;
- All IP datagrams for the Kernel Unrecognized protocol field are passed to the original socket;
- No shards are passed to the original socket until all shards of the datagram arrive;
The kernel must match all the original sockets on all processes before passing the IP data to the original socket, and if the match succeeds, the copy of the IP datagram is passed to the matching original socket. The detection matching steps are as follows:
- The third parameter of the socket function must specify a value other than 0 when creating the original socket;
- If the original socket bind binds to a local IP address, the local IP address must match the destination IP address of the IP datagram;
- If the original socket has been assigned a foreign IP address by the connect call, the foreign IP address must match the source IP address of the IP datagram;
Ping Program
The operation of the ping program is simple, and when the source host sends an ICMP echo request datagram to the target host, it expects the target host to answer. After the target host receives an ICMP echo request datagram, it swaps the address of the source, destination host, and then wraps the data in the received ICMP echo request datagram in its own ICMP echo Reply datagram intact, and then sends back to the party that sent the ICMP echo request. If the checksum is correct, the sender will assume that the target host's echo service is normal, that is, the physical connection is unblocked.
The ICMP protocol is required for programming ping programs, and the knowledge of the ICMP protocol can refer to the previous article "ICMP protocol". The ping command uses only two of the many ICMP packets: "Request (Icmp_echo)" and "Response (icmp_echoreply)", both of which are shown in the following ICMP message formats:
First look at the output of the system's own ping program:
$ ping Www.github.comPING github.com (192.30.252.128) (+) bytes of data.64 bytes from github.com (192.30.252.128): ICMP _req=1 ttl=45 time=269 ms64 bytes from github.com (192.30.252.128): icmp_req=2 ttl=45 time=274 ms64 bytes from github.com (192.30.252.128): icmp_req=3 ttl=45 time=270 ms64 bytes from github.com (192.30.252.128): icmp_req=4 ttl=45 time=281 Ms64 Bytes from github.com (192.30.252.128): icmp_req=5 ttl=45 time=283 ms64 bytes from github.com (192.30.252.128): icmp_req=6 Ttl=45 time=249 ms64 bytes from github.com (192.30.252.128): icmp_req=7 ttl=45 time=253 ms^c---github.com ping statistic s---7 packets transmitted, 7 received, 0% packet loss, time 6006msrtt Min/avg/max/mdev = 249.472/269.010/283.945/12.186 m S
Programming steps for the PING program:
1) Create the original socket of type SOCK_RAW, and set the protocol as IPPROTO_ICMP;
2) Create and initialize the ICMP header;
3) Call the SendTo function to send the ICMP request to the remote host;
4) Call the Recvform function to receive any ICMP response;
The data structure for <netinet/ip_icmp.h> ICMP in Linux is defined as follows:
struct icmp{u_int8_t icmp_type;/* type of message, see below */u_int8_t icmp_code;/* Type Sub code */u_int16_t ICM p_cksum;/* ones complement checksum of struct */union {U_char ih_pptr;/* icmp_paramprob */struct IN_ADDR Ih_gwa ddr;/* Gateway address */struct ih_idseq/* echo Datagram */{u_int16_t icd_id; u_int16_t Icd_seq; } ih_idseq; u_int32_t ih_void; /* Icmp_unreach_needfrag-Path MTU Discovery (RFC1191) */struct IH_PMTU {u_int16_t ipm_void; u_int16_t Ipm_nextmtu; } IH_PMTU; struct IH_RTRADV {u_int8_t Irt_num_addrs; u_int8_t IRT_WPA; u_int16_t Irt_lifetime; } ih_rtradv; } Icmp_hun; #defineicmp_pptricmp_hun. ih_pptr#defineicmp_gwaddricmp_hun.ih_gwaddr#defineicmp_idicmp_hun.ih_ Idseq.icd_id#defineicmp_seqicmp_hun.ih_idseq.icd_seq#defineicmp_voidicmp_hun.ih_void#defineicmp_pmvoidicmp_ Hun.ih_pmtu.ipm_void#defineicmp_nextmtuicmp_hun.ih_pmtu.ipm_nextmtu#defineicmp_num_addrsicmp_hun.ih_rtradv.irt _num_Addrs#defineicmp_wpaicmp_hun.ih_rtradv.irt_wpa#defineicmp_lifetimeicmp_hun.ih_rtradv.irt_lifetime Union {struct { u_int32_t Its_otime; u_int32_t Its_rtime; u_int32_t Its_ttime; } id_ts; struct {struct IP idi_ip; /* options and then the bits of data */} ID_IP; struct ICMP_RA_ADDR id_radv; u_int32_t Id_mask; u_int8_t Id_data[1]; } Icmp_dun; #defineicmp_otimeicmp_dun. Id_ts.its_otime#defineicmp_rtimeicmp_dun.id_ts.its_rtime#defineicmp_ Ttimeicmp_dun.id_ts.its_ttime#defineicmp_ipicmp_dun.id_ip.idi_ip#defineicmp_radvicmp_dun.id_radv#defineicmp_ Maskicmp_dun.id_mask#defineicmp_dataicmp_dun.id_data};
The functions that make up the ping program and their relationships are as follows:
First define a header file:
#ifndef ping_h#define ping_h#include <netinet/in.h> #include <netinet/ip.h> #include <netinet/ip_ icmp.h> #include <sys/socket.h> #include <signal.h> #include <unistd.h> #include <arpa/inet.h > #include <netdb.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include < errno.h> #include <sys/time.h> #include <sys/types.h> #define BUFSIZE 4096char Sendbuf[bufsize];extern int datalen; /* # bytes of data following ICMP header */char *host;int nsent; /* Add 1 for each sendto () */int nrecv; /* Add 1 for each recvmsg () */pid_t pid; /* Our PID */int sockfd;int verbose;/* function prototypes */void init_v6 (void); void Proc_v4 (char *, ssize_t, struct MSGHD R *, struct timeval *), void proc_v6 (char *, ssize_t, struct msghdr *, struct timeval *); void send_v4 (void); void Send_v6 (VO ID), void readloop (void), void sig_alrm (int), void tv_sub (struct timeval *, struct timeval *);/*This structure is primarily designed to deal with the difference between IPv4 and IPv6 */struct proto{/* 3 function pointers */void (*FPROC) (char *, ssize_t, struct msghdr *, struct Timeva L *); void (*fsend) (void); void (*finit) (void); /* 2 socket address structure pointer */struct SOCKADDR *sasend; /* sockaddr{} for Send, from getaddrinfo */struct sockaddr *sarecv; /* SOCKADDR for Receiving */socklen_t Salen; /* Length of Sockaddr{}s */* ICMP protocol value */int icmpprot; /* IPPROTO_XXX value for ICMP */} *PR; #ifdef ipv6#include <netinet/ip6.h> #include <netinet/icmp6.h> #endif # endif
Then look at the main function:
#include "ping.h"/* Initialize IPV4 structure */struct proto proto_v4 = {proc_v4, send_v4, NULL, NULL, NULL, 0, ipproto_icmp}; #ifdef ipv6/ * If IPV6 is present, initialize IPV6 structure */struct proto proto_v6 = {proc_v6, send_v6, init_v6, NULL, NULL, 0, Ipproto_icmpv6}; #endiftypedef vo ID sigfunc (int); extern int datalen = 56; /* data that goes with ICMP Echo request */extern sigfunc *mysignal (int signo, Sigfunc *func); extern struct Addrinfo *host _serv (const char *host, const char *serv, int family, int socktype), extern char *sock_ntop_host (const struct SOCKADDR *sa, socklen_t salen), extern void *calloc (size_t n, size_t size), void statistics (int signo), int main (int argc, char **argv) { int n; struct Addrinfo *ai; Char *h; Opterr = 0; /* don ' t want Getopt () writing to stderr */* Only one parameter option of ping is implemented for query */*/////////////* For use of getopt functions can refer to the relevant data */while (n = Opt (argc, argv, "V"))! =-1) {switch (n) {case ' V ': verbose++; Break Case '? ': printf ("unrecognize option:%c\n", N); Exit (1); }} if (Optind! = argc-1) {perror ("usage:ping [-v]
Data processing functions
#include "ping.h" void Readloop () {int Size;char Recvbuf[bufsize];char controlbuf[bufsize];struct msghdr msg;struct Iovec iov;ssize_t n;struct timeval tval;/* Create an ICMP original socket, must be root permission */if ((SOCKFD = socket (pr->sasend->sa_family, Sock_raw, Pr->icmpprot)) < 0) {perror ("socket error"); exit (1);} /* Reclaim root permissions, set current user rights */setuid (Getuid ());/* Initialize IPV6 */if (pr->finit) (*pr->finit) (); size = 60 * 1024;/* The size of the receive buffer is set to 60k, mainly in order to reduce the receive buffer overflow */setsockopt (SOCKFD, Sol_socket, So_rcvbuf, &size, sizeof (size));/* Send first packet */sig_ ALRM (SIGALRM);/* Initialize receive buffer */iov.iov_base = Recvbuf;iov.iov_len = sizeof (RECVBUF); msg.msg_name = Pr->sarecv;msg.msg_ Iov = &iov;msg.msg_iovlen = 1;msg.msg_control = Controlbuf;for (;;) {/* receive ICMP packets */msg.msg_namelen = pr->salen;msg . Msg_controllen = sizeof (CONTROLBUF);/* receive data from socket */n = recvmsg (sockfd, &msg, 0); if (n < 0) {if (errno = eintr) continu E;else{perror ("recvmsg error"); exit (1);}} /* Record receive time */gettimeofday (&tval, NULL);/* Call handler function */(*PR->FPROC) (recvBUF, N, &msg, &tval);}}
Send Echo Request datagram:#include "ping.h"/* Send packet in IPv4 domain */extern uint16_t in_cksum (uint16_t *addr, int len); voidsend_v4 (void) {Intlen;struct icmp*icmp; /* Set ICMP header */icmp = (struct ICMP *) Sendbuf;icmp->icmp_type = icmp_echo;/* echo Request */icmp->icmp_code = 0;icmp->icmp _id = Pid;icmp->icmp_seq = Nsent++;memset (Icmp->icmp_data, 0xa5, datalen);/* Fill with pattern */gettimeofday (( struct Timeval *) icmp->icmp_data, NULL);/* Record send time */len = 8 + datalen;/* checksum ICMP header and data */icmp->icmp _cksum = 0; /* Test and algorithm */icmp->icmp_cksum = In_cksum ((u_short *) ICMP, len); /* Send packet */if (len! = sendto (SOCKFD, SendBuf, Len, 0, Pr->sasend, Pr->salen)) { perror ("SendTo error");
exit (1);} }
Receive data and display echo response datagrams:
#include "ping.h" extern char *sock_ntop_host (const struct SOCKADDR *sa, socklen_t salen); Voidproc_v4 (char *ptr, ssize_t Len, struct msghdr *msg, struct timeval *tvrecv) {inthlen1, icmplen;doublertt;struct ip*ip;struct icmp*icmp;struct Timeval*tvsend;ip = (struct IP *) ptr;/* start of IP header */* IP Message First Minister, that is, the length flag of the IP header is multiplied by 4 */hlen1 = Ip->ip_hl <& Lt 2;/* length of IP header */if (ip->ip_p! = ipproto_icmp) return;/* not ICMP/* * crosses IP header, points to ICMP header */icmp = (struct IC MP *) (PTR + hlen1);/* start of ICMP header */* ICMP header and ICMP datagram total length, if less than 8, unreasonable */if ((Icmplen = Len-hlen1) < 8) re turn;/* Malformed Packet */* Ensure that all incoming datagrams are ICMP echo reply */if (Icmp->icmp_type = = icmp_echoreply) {if (icmp->icmp_id! = PID) return;/* not a response to our echo_request */if (Icmplen <) return;/* Not enough data to use */tvsend = (struc T timeval *) icmp->icmp_data; /* Calculates the received and sent time difference */tv_sub (TVRECV, tvsend); /* Calculate RTT */rtt = tvrecv->tv_sec * 1000.0 + tvrecv->tv in milliseconds_usec/1000.0; /* Print related information */printf ("%d bytes from%s:icmp_seq=%u ttl=%d rtt=%.3f ms\n", Icmplen, Sock_ntop_host (PR->SARECV, Pr->sa Len), Icmp->icmp_seq, Ip->ip_ttl, RTT); nrecv++;} else if (verbose) {printf ("%d bytes from%s:icmp_type =%d, Icmp_code =%d\n", Icmplen, Sock_ntop_host (PR->SARECV, PR ->salen), Icmp->icmp_type, Icmp->icmp_code);}}
Calculate Tests and functions:#include <stdint.h>/* test and Algorithm */uint16_tin_cksum (uint16_t *addr, int len) {intnleft = Len;uint32_tsum = 0;uint16_t*w = Addr;uint16_tanswer = 0;/* * Our algorithm are simple, using a-bit accumulator (sum), we add * sequential-bit words To it, and at the end, fold the "carry bits from the top" to the lower bits. * /* Add the ICMP header binary data in 2-byte increments */while (Nleft > 1) {sum + = *w++;nleft-= 2;} /* 4mop up an odd byte, if necessary */if (Nleft = = 1) {/* If the ICMP header is an odd number of bytes, the last byte is treated as a high byte of 2 bytes of data, then the low byte is 0 and continues to accumulate */* (unsigned char *) (&answer) = * (unsigned char *) w; sum + = answer;} /* 4add back carry outs from top, bits to low, bits */sum = (sum >> +) + (sum & 0xffff);/* Add hi to Low */sum + = (sum >>);/* Add carry */answer = ~sum;/* truncate to + bits */return (answer);}
Signal Processing function:#include "ping.h"/* Send the packet and set the alarm to send SIGALRM signal */voidsig_alrm (int signo) {(*pr->fsend) () alarm (1) to the process in a second. return;}
Compile steps:
sudo makesudo chmod u+s Ping
Test:
$./ping Www.github.comPING github.com (192.30.252.129)-bytes of data.64 bytes from 192.30.252.129:icmp_seq=0 ttl= rtt=303.057 ms64 bytes from 192.30.252.129:icmp_seq=1 ttl=45 rtt=301.416 ms64 bytes from 192.30.252.129 : icmp_seq=2 ttl=45 rtt=301.614 ms64 bytes from 192.30.252.129:icmp_seq=3 ttl=45 rtt=301.727 ms64 Bytes from 192.30.252.129:icmp_seq=4 ttl=45 rtt=308.911 ms64 bytes from 192.30.252.129:icmp_seq=5 ttl= rtt=303.088 ms64 bytes from 192.30.252.129:icmp_seq=6 ttl=45 rtt=305.763 ms^c----------- 192.30.252.129 Ping Statistics-----------7 packets transmitted, 7 received, 0 packet lost
Note: Here is just a IPv4 implementation of the PING program, the complete program can be downloaded to my github, "ping full program."Resources:"Unix Network Programming"
"Network Programming" the original socket---ping program implementation