I have only heard of TCP/IP protocol, but have not studied it carefully. Some knowledge systems are scattered, such as three-way handshake, four-way wave, sliding window, zero-copy technology and so on. I have such a thing in my knowledge, but I don’t know what it is. So put these days to learn the TCP/IP protocol knowledge sorted out, form a knowledge system of their own.
1.1 7-layer 5-layer model
Use this chart every time you talk about TCP/IP to make yourself look professional (which you’re not). There are a lot of examples of these five layers on the web, and I’ll just quote them hereOSI seven-layer model and TCP/IP five-layer model
Steal two pictures: The first one describes the different equipment working on that floor:The second is what the main agreements for each layer are:
As you can see from the last diagram, HTTP protocol is implemented in the application layer, and then we directly call the API, such as send and RECV. Where does the data sent by send and RECV go? Data is sent to the transport layer (i.e. TCP/UDP) after sending and recV to the application layer. The data is sent to the transport layer (i.e. TCP/UDP). So today we will implement a simple UDP protocol.
1.2 Implement UDP by hand
1.2.1 Packet header of UDP Data frames
This is Mr. King’s own drawing. Here is another stolen drawing. On the right is the TCP/IP model. When we call Sendto from the application, the user layer will send the user data to the transport layer. The transport layer will add the data header (actually the port) of the transport layer on the basis of the user data, and then send the data to the transport layer. Network layer will be added on the basis of the data transport layer on its own baotou (IP) is the network layer, network layer continue to go down, the data link layer encapsulation his own baotou (in fact, the MAC address), so that through layers of packaging, just to send the physical layer, the other end of the received data, is a layer of a layer of unpacking, just like collect Courier, I won’t go into details here.
1.2.2 Data Link Layer Packet header
The packet header of the data link layer contains 14 bytes, including the destination MAC address, source destination MAC address, and type
The encapsulated code is as follows:
#define ETH_LENGTH 6
struct ethhdr {
unsigned char dest_mac[ETH_LENGTH]; // Source MAC address
unsigned char src_mac[ETH_LENGTH]; // Destination MAC address
unsigned short proto; // Network layer protocol type, usually IP protocol, 0x0800
}
Copy the code
1.2.3 the IP header
Version: Indicates the version number of the IP datagram. The version number is 14 for IPv4 and 6 for IPv6.
Header length: Indicates the length of the IP packet header, in words (4 bytes). It is a 4-bit field, so IPv4 headers are 15*4=60 bytes of data. The normal value for this field is 5 (if there is no option). IPv6 does not have this field, and its header length is fixed at 40 bytes.
Service Type (Tos) : Identifies different services
Total length: Total length of IPv4 dataset packets. Because it is a 16-bit field, the maximum length of an IPv4 datagram, including the header, is 65,535 bytes.
Identifier: The value of this field is that when a large datagram is split, the value of this field is the same as that of the smaller datagram segments.
Mark and segment offset later
TTL: Indicates the TTL that sets the upper limit of the number of routers a datagram can pass through. The sender initializes it to a recommended value of 64, which each router reduces by 1 when forwarding the packet to indicate how many gateways it has passed through the network. When the value of this field is 0, the data is discarded to prevent unwanted routing loops that cause datagrams to loop forever across the network.
Protocol number: indicates the protocol used by the application layer, for example, UDP=17, TCP=6.
Header checksum: the checksum of the IP header. (The reason why the checksum is added to each layer is that the data of the twisted pair is prone to errors. After the checksum is added to each layer, you can know that the data of that layer is faulty, and the fault can be located easily.)
Source IP address: IP address of the host from which the packet is sent
Destination IP Address: IP address of the host to which the packet is sent
The encapsulated code is as follows:
struct iphdr {
unsigned char version : 4;
unsigned char hdrlen : 4; // Header length
unsigned char tos;
unsigned short totlen;
unsigned short id; // Fragment id
unsigned short flag : 3;
unsigned short offset : 13;
unsigned short ttl; // The switch is reduced by 1 each time it passes through a gateway. The default value is 64, and the switch is reduced by 1 across the network
unsigned char proto; // Application layer protocol
unsigned short check;
unsigned int sip; // Source IP address
unsigned int dip; // Destination IP address
};
Copy the code
1. The udp header
Udp header is relatively simple, there is no IP header so much. I won’t explain it, but go straight to the code:
struct udphdr
{
unsigned short sport;
unsigned short dport;
unsigned short length;
unsigned short crc;
};
Copy the code
1.2.5 udp frame
Udp data frame is actually the packet header in front of all superimposed, plus user data, here user data is defined by flexible array:
//udp packet = ethhdr + iphdr + udphdr + userData
struct udppkt {
struct ethhdr eh; / / 14
struct iphdr ip; / / 20
struct udphdr udp; / / 8
unsigned char body[0];
};
Copy the code
1.2.6 Implementation of protocol basis at the user layer
How to implement the user layer procedures to achieve the protocol, github has an open source code Netmap, is to map the network card data directly into memory, not through the kernel, a bit like DMA technology, so we need to use this technology to complete the network card received data directly into memory, code Github connection.
The driver was compiled for several days, using several systems, and finally had no choice but to switch back to Ubuntu16 system, because the driver seems to be based on Ubuntu16 written, so I can only obediently switch back, later free to analyze the steps of installing different systems.
1.2.7 Simple IMPLEMENTATION of UDP
This section describes the steps of udp
1,struct nm_desc *nmr = nm_open("netmap:eth0".NULL.0.NULL);
// Use netmap to open a virtual nic device.
2, PDF. Fd = NMR - > fd; pfd.events = POLLIN;int ret = poll(&pfd, 1.- 1);
// Use poll to manage NMR ->fd device files
3If the poll has data, it can process the data. First, it converts the poll into an ethhDR packet and checks whether the protocol is IP. The value of IP is0x0800.
if(ntohs(eh->h_proto) == PROTO_IP )
4If the network layer is using IP protocol, at this time you can obtain IP packet, and parse, the transport layer is using what protocol, if UDP is17Icmp is1.if(ntohs(eh->h_proto) == PROTO_IP )
5.If the transport layer is UDP, then we can fetch the data, and it's important to note that we're using flexible data to receive, so we need to get the data length, which is what udp packets already have,if(udp->ip.proto == PROTO_UDP) {
unsigned short udplen = ntohs(udp->udp.length);
udp->body[udp->udp.length- 8 -] = '\ 0';
}
Copy the code
In fact, relatively simple, do MCU, are not strange to this protocol, at the beginning of the tear I2c, but this is hierarchical processing, after using, more and more feel the benefits of stratification.
Test results:
1.2.8 implementation arp
The result of the above procedure is that it can only be found for a period of time, and after a while it can not be sent.
This is the lack of implementation of THE ARP protocol, (in fact, I did not know what ARP is), now to use, will be popularized.
Arp protocol details, I see this article on the good, ARP protocol details
It is very detailed, and I will summarize it here: ARP address resolution protocol, in Ethernet, a host and a host of communication, is through the MAC address of communication, but within a local area network (LAN), we only know the IP address, and don’t know the MAC address, this time would require the ARP protocol, IP address and MAC address of a mapping table, by IP address lookup to the corresponding MAC address.
ARP mapping is mainly dynamic. If other hosts want to know the ARP mapping table, they will poll and send ARP packets. Therefore, our UDP association also needs to reply an ARP packet.
1.2.8.1 arp packet
I borrowed the arp packet from that blog post,
The code package is as follows:
/ / IP layer protocol
struct arphdr {
unsigned short hw_type;
unsigned short proto_type;
unsigned char hw_addr_len;
unsigned char proto_addr_len;
unsigned short op;
unsigned char s_mac[ETH_LENGTH]; / / MAC address
unsigned int sip;
unsigned char d_mac[ETH_LENGTH];
unsigned int dip;
};
Copy the code
The ARP packet is a protocol at the network layer. It is the highest-level protocol, and there are no other layers above it.
Arp packets are also basic data link layer, so the entire ARP packet:
/ / IP layer protocol
struct arphdr {
unsigned short hw_type;
unsigned short proto_type;
unsigned char hw_addr_len;
unsigned char proto_addr_len;
unsigned short op;
unsigned char s_mac[ETH_LENGTH]; / / MAC address
unsigned int sip;
unsigned char d_mac[ETH_LENGTH];
unsigned int dip;
};
Copy the code
1.2.8.2 ARP Packet Reply
Program idea:
1Because ARP is a network layer protocol, determine the protocol sent from the data link layer:if(ntohs(eh->h_proto) == PROTO_ARP)
2.When receiving ARP packets, you need to check whether the packets are their own IP addressesif(arp->arp.dip == inet_addr("192.168.121.155")
3.If a match is found, an ARP packet is returnedvoid echo_arp_pkt(struct arppkt *arp, struct arppkt *arp_rt, char *hmac)
{
memcpy(arp_rt, arp, sizeof(struct arppkt));
memcpy(arp_rt->eh.h_dest, arp->eh.h_src, ETH_LENGTH);
str2mac(arp_rt->eh.h_src, hmac); / / source MAC FFFFFFFF
arp_rt->eh.h_proto = arp->eh.h_proto;
arp_rt->arp.hw_addr_len = 6;
arp_rt->arp.proto_addr_len = 4;
arp_rt->arp.op = htons(2);
str2mac(arp_rt->arp.s_mac, hmac);
arp_rt->arp.sip = arp->arp.dip;
memcpy(arp_rt->arp.d_mac, arp->arp.s_mac, ETH_LENGTH); arp_rt->arp.dip = arp->arp.sip; } Change the source MAC address and source IP address to the destination MAC address and destination IP address, and then add your MAC address and IP address to the source MAC address and destination IP addressCopy the code
After adding this ARP protocol, our udp protocol is more stable.
1.2.9 implementation ping
If we ping the IP address of the VIRTUAL machine on the computer, it is found that the ping fails, why? Because we didn’t implement the ping protocol.
Ping is actually in the ICMP protocol, icmp protocol is at the network layer, is part of the iP packet, the iP packet this big brother’s thigh, do something indescribable. Protocol details I am not good at long, so or quoted someone else’s protocol details, ICMP protocol full resolution, this is very clear, you can have a good look.
1.2.9.1 ICMP Packet
Are you happy to see this protocol package? It’s relatively simple.
struct icmppkt {
unsigned char type;
unsigned char code;
unsigned short sum; // This is an ICMP packet
unsigned short id; // This is the ping package id
unsigned short num; // Is the num of the ping package
};
Copy the code
Ping package is not easy to find, directly post the code:
struct pingpkt {
struct ethhdr eh; / / 14
struct iphdr ip; / / 20
struct icmppkt icmp; / / 8
unsigned char data[0]; // Flexible array stores data
};
Copy the code
1.2.9.2 Ping Request Package
After receiving the ping packet, we need to restore the ping packet and use the wireshark to capture the packet:ICMP is at the IP layer, with protocol 1 and destination IP, so the first thing we do is to determine the protocol type and IP address:
if(udp->ip.proto == PROTO_ICMP) {
if(udp->ip.dip == inet_addr("192.168.121.155"))
Copy the code
If these two conditions are met, ping packets can be resumed. Let’s look at the packet capture diagram of ICMP packets:Type: 8: a string of data: a string of data: a string of data: a string of data: a string of data: a string of data
1.2.9.2 Ping A Reply packet
Simple thinking in code:
1First, calculate the length of data data (that is, inexplicable data) by receiving the ping package, and apply for the memory of a ping package through this length. Here is flexible data, remember to apply for the length of data data.2, copy the entire ping packet to the packet to be sent3Prepare the ethhDR package to exchange the source and destination MAC addresses4Prepare IP packets, exchange source IP and destination IP, compare and calculate the total length, this is very important, remember to add icmp data length5To prepare the ping package, run type=0Is the reply8And calculate the checksum, which is copied from King's checksum6And call the sending interface to send data packetsnm_inject(nmr, ping_rt, len);
Copy the code
The complete code is as follows. I haven’t removed the printed information yet. You can remove the printed information
struct pingpkt* echo_ping_pkt(struct pingpkt *ping, unsigned short *len)
{
if(ping == NULL) {
printf("ping is null\n");
return NULL;
}
/ / the first step
unsigned short icmp_data_len = ntohs(ping->ip.totlen)-sizeof(struct iphdr)-sizeof(struct icmppkt); //strlen(ping->data)
printf("icm_data_len %d\n", icmp_data_len);
struct pingpkt* ping_rt = NULL;
ping_rt = malloc(sizeof(struct pingpkt)+icmp_data_len);
if(ping_rt == NULL) {
printf("echo_ping_pkt malloc error\n");
return NULL;
}
/ / the second step
memcpy(ping_rt, ping, sizeof(struct pingpkt)+icmp_data_len);
printf("ping->data %s %d %d\n", ping->data, icmp_data_len, sizeof(ping_rt->data));
//memcpy(ping_rt->data, ping->data, icmp_data_len);
printf("ping_rt->data %s\n", ping_rt->data);
/ / the third step
memcpy(ping_rt->eh.h_dest, ping->eh.h_src, 6);
memcpy(ping_rt->eh.h_src, ping->eh.h_dest, 6);
ping_rt->eh.h_proto = ping->eh.h_proto;
/ / step 4
ping_rt->ip.sip = ping->ip.dip;
ping_rt->ip.dip = ping->ip.sip;
printf("ip tolen = %d %d\n".ntohs(ping->ip.totlen), icmp_data_len);
unsigned short ippkt_len = sizeof(struct iphdr)+sizeof(struct icmppkt)+icmp_data_len;
ping_rt->ip.totlen = htons(ippkt_len);
/ / step 5
ping_rt->icmp.type = 0;
ping_rt->icmp.code = 0;
printf("ping->id %d ping->seq %d\n", ping->icmp.id, ping->icmp.num);
ping_rt->icmp.sum = 0;
//ping_rt->icmp.sum = cimp_pkt_sum(&ping_rt->icmp, sizeof(struct icmppkt)+icmp_data_len);
ping_rt->icmp.sum = in_cksum(&ping_rt->icmp, sizeof(struct icmppkt)+icmp_data_len);
printf("dd\n");
*len = sizeof(struct pingpkt)+icmp_data_len;
return ping_rt;
}
Copy the code
The checksum code is a bit tricky, so here’s a copy of King’s checksum code:
unsigned short in_cksum(unsigned short *addr, int len) {
register int nleft = len;
register unsigned short *w = addr;
register int sum = 0;
unsigned short answer = 0;
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1) {
*(u_char *)(&answer) = *(u_char *)w ;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return (answer);
}
Copy the code
1.2.9.3 Implementation Result
The left side is ping data, the right side is captured out of the data, a simple implementation of udp packets can be.