9.IPv6协议分析与实践

IPv6 协议分析与实践

1. 概述

1.1 简介

  • 用于解决 IPv4 的地址枯竭、骨干路由器维护的路由表过大、安全问题、不易进行自动配置和重新编址
  • 简化了报文头格式、充足的地址空间、层次化的地址结构、灵活的扩展头和邻居发现协议(NDP)

1.2 IPv6 地址

  • IPv6 采用128 位地址,分为 8 组,由冒号分隔; IPv4 映射 IPv6 地址,格式为 X:X:X:X:X:X:d.d.d.d
  • IPv6 地址由网络前缀和接口标识两个逻辑部分组成,接口标识通常根据物理地址自动生成,叫做 EUI-64
  • IPv6 没有广播地址,所有广播功能由组播实现
地址分类说明
单播地址(Unicast)一个单播地址对应一个接口,发往单播地址的数据包会被对应的接口接收
1. 未指定地址 ::/128,用于系统启动时,请求分配 IP 时作为源地址使用
2. 环回地址 ::1/128,用于自己向自己发送数据包时使用
3. 组播地址 FF00::/8
4. 唯一本地地址 FC00::/7,相当于私有IP,仅能够在本地网络使用
5. 链路本地单播地址 FE80::/10,用于没有路由存在的网络中,主机通过
MAC 地址自动配置生成IPv6地址,仅能在本地网络中使用
任播地址(Anycast)一个任播地址对应一组接口,发往任播地址的数据包会被这组接口的其中一个
接收,被哪个接口接收由具体的路由协议确定
多播地址(Multicast)一个组播地址对应一组接口,发往组播地址的数据包会被这组的所有接口接收

1.3 IPv6 头报文格式

在这里插入图片描述

  • 版本 : 版本号,IPv6 固定为 6

  • 传输类别 : 用于区分数据包的不同类别或优先级

  • 流标签 : 用于源端标记数据包顺序,供路由器特别处理,用来满足某些特殊服务,如QoS和实时信息

  • 载荷长度 : 数据长度,包括扩展报头

  • 下一个头部 : 基本报头后面的扩展报头类型

  • 跳数限制 : 数据包能经过的最大跳数,每经过一个路由器就减一

  • 源地址 : 发送端 IP 地址

  • 目的地址 : 接收端 IP 地址

  • 扩展报头 : 用于取代 IPv4 中的选项,相对而言提高了处理效率和扩展性

1.4 IPv6 常见扩展报文头

  • 当存在多个扩展报头时,必须按照下表顺序排列
序号名称说明
1IPv6 基本报头-
2逐跳选项扩展报头0在 IPv6 基本报头中定义
3目的选项扩展报头60指那些将被分组报文的最终目的地处理的选项
4路由扩展报头43用于源路由选项和Mobile IPv6
5分片扩展报头44在源节点发送的报文超过Path MTU时对报文分片时使用
6授权扩展报头51用于IPSec,提供报文验证、完整性检查
7封装安全有效载荷扩展报头50用于IPSec,提供报文验证、完整性检查和 IP 报文加密
8上层扩展报头如 TCP/UDP/ICMP 等,值与 IPv4 报文中协议一致

1.5 IPv6 伪头部

  • IPv6 报文校验工作都由上层协议完成,IPv6 报文没有校验和字段
    在这里插入图片描述

2. IPv6 编程

2.1 ipv6.h

#ifndef __ipv6_h_
#define __ipv6_h_

#define IP_VERSION_6          6

#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

/*
 *  IPv6 fixed header
 *
 *  BEWARE, it is incorrect. The first 4 bits of flow_lbl
 *  are glued to priority now, forming "class".
 */
struct ipv6_hdr {
	uint8_t  priority:4;
	uint8_t  version:4;
    uint8_t  flow_lbl[3]; 
	uint16_t payload_len;
    uint8_t  nexthdr;
    uint8_t  hop_limit;
    uint8_t  saddr[16];
    uint8_t  daddr[16];
	uint8_t  data[0];
};

struct ipv6_hdr *ipv6_alloc_packet(uint32_t flow_lbl, uint16_t payload_len, uint8_t nexthdr, 
		uint8_t hop_limit, const char *saddr, const char *daddr, const void *data);

void ipv6_free_packet(struct ipv6_hdr **ipv6);

void ipv6_print(struct ipv6_hdr *ipv6);


int ipv6_socket();

ssize_t ipv6_send(int sockfd, const void *data, 
		size_t size, const char *daddr, int flags);

ssize_t ipv6_recv(int sockfd, void *data, 
		size_t size, const char *daddr, int flags);

void ipv6_close(int sockfd);


/**
 * IPv6 伪头部, 用于上层协议计算其 checksum
 *
 * 注 : IPv6 没在校验和字段
 */
struct ipv6_pseudo_header {
	uint8_t  src_addr[16];
	uint8_t  dest_addr[16];
	uint32_t length;
	uint8_t  zero[3];
	uint8_t  nextptr;
	uint8_t  data[0];
};

uint16_t ipv6_cksum(const char *saddr, const char *daddr, 
		uint8_t nextptr, const void *data, size_t size);

#endif /* __ipv6_h_ */

2.2 ipv6.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>

#include "ipv6.h"
#include "cksum.h"
#include "common.h"

struct ipv6_hdr *ipv6_alloc_packet(uint32_t flow_lbl, uint16_t payload_len, uint8_t nexthdr, 
		uint8_t hop_limit, const char *saddr, const char *daddr, const void *data) 
{
	struct ipv6_hdr *ipv6;
	struct sockaddr_in6 addr;

	ipv6 = (struct ipv6_hdr *) calloc(1, sizeof(struct ipv6_hdr) + payload_len);
	ipv6->version     = IP_VERSION_6;
	ipv6->flow_lbl[0] = flow_lbl >> 16 & 0x0000000F;
	ipv6->flow_lbl[1] = flow_lbl >> 8  & 0x000000FF;
	ipv6->flow_lbl[2] = flow_lbl >> 0  & 0x000000FF;
	ipv6->payload_len = htons(payload_len);
	ipv6->nexthdr     = nexthdr;
	ipv6->hop_limit   = hop_limit;

	if (inet_pton(AF_INET6, saddr, &addr.sin6_addr) == 0) 
		handle_error_en(EINVAL, "saddr");
	memcpy(ipv6->saddr, &addr.sin6_addr, sizeof(addr.sin6_addr));

	if (inet_pton(AF_INET6, daddr, &addr.sin6_addr) == 0) 
		handle_error_en(EINVAL, "saddr");
	memcpy(ipv6->daddr, &addr.sin6_addr, sizeof(addr.sin6_addr));
	
	memcpy(ipv6->data, data, payload_len);
	return ipv6;	
}

void ipv6_free_packet(struct ipv6_hdr **ipv6)
{
	if (NULL != ipv6 && NULL != *ipv6) {
		free(*ipv6);
		*ipv6 = NULL;
	}
}

void ipv6_print(struct ipv6_hdr *ipv6)
{
	char str[INET6_ADDRSTRLEN];
	printf("%u\n", ipv6->version);
	printf("%s\n", ipv6->flow_lbl);
	printf("%u\n", ntohs(ipv6->payload_len));
	printf("%u\n", ipv6->nexthdr);
	printf("%u\n", ipv6->hop_limit);

	if (inet_ntop(AF_INET6, ipv6->saddr, str, INET6_ADDRSTRLEN) == NULL)
		handle_error("inet_ntop");
	printf("%s\n", str);

	if (inet_ntop(AF_INET6, ipv6->daddr, str, INET6_ADDRSTRLEN) == NULL)
		handle_error("inet_ntop");
	printf("%s\n", str);
}


int ipv6_socket() 
{
	int sockfd;
	int flag = 1;

	if ((sockfd = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW)) == -1) 
		handle_error("socket");

	if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_HDRINCL, &flag, sizeof(flag)) == -1)
		handle_error("setsockopt : IPV6_HDRINCL");
	return sockfd;

}

ssize_t ipv6_send(int sockfd, const void *data, 
		size_t size, const char *daddr, int flags) 
{
	ssize_t count;
	struct sockaddr_in6 addr;

	memset(&addr, 0, sizeof(addr));
	addr.sin6_family = AF_INET6;
	inet_pton(addr.sin6_family, daddr, &addr.sin6_addr);

	if ((count = sendto(sockfd, data, size, flags, (struct sockaddr *)&addr, sizeof(addr))) == -1)
		handle_error("sendto");
	return count;
}

ssize_t ipv6_recv(int sockfd, void *data, 
		size_t size, const char *daddr, int flags) 
{
	ssize_t count;
	struct sockaddr_in6 addr;
	socklen_t socklen = sizeof(addr);

	memset(&addr, 0, sizeof(addr));
	addr.sin6_family = AF_INET6;
	inet_pton(addr.sin6_family, daddr, &addr.sin6_addr);

	if ((count = recvfrom(sockfd, data, size, flags, (struct sockaddr *)&addr, &socklen)) == -1)
		handle_error("recvfrom");
	return count;
}

void ipv6_close(int sockfd)
{
	if (close(sockfd) == -1)
		handle_error("close");
}


uint16_t ipv6_cksum(const char *saddr, const char *daddr, 
        uint8_t nextptr, const void *data, size_t size)
{
    struct sockaddr_in6 addr;
    unsigned int hdr_len, checksum;
    struct ipv6_pseudo_header *header; 

    hdr_len = sizeof(struct ipv6_pseudo_header) + size;
    header  = (struct ipv6_pseudo_header *) calloc(1, hdr_len);

    if (inet_pton(AF_INET6, saddr, &addr.sin6_addr) == 0)  
        handle_error_en(EINVAL, "saddr");
    memcpy(header->src_addr, &addr.sin6_addr, sizeof(addr.sin6_addr));

    if (inet_pton(AF_INET6, daddr, &addr.sin6_addr) == 0)  
        handle_error_en(EINVAL, "daddr");
    memcpy(header->dest_addr, &addr.sin6_addr, sizeof(addr.sin6_addr));

    header->nextptr = nextptr;
    header->length  = htons(size);
    memcpy(header->data, data, size);   
    checksum = cksum((uint16_t *)header, hdr_len);

    free(header);
    return htons(checksum);
}

2.3 main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <arpa/inet.h>

#include "ipv6.h"

static void ipv6_test() 
{
	int sockfd;
	struct ipv6_hdr *ipv6;
	unsigned short tot_len;
	unsigned short data_len;
	
	const char *saddr = "fe80::20c:cff:fe0c:c0c";
	const char *daddr = "fe80::20d:dff:fe0d:d0d";
	const char data[] = { 0x1f, 0x40, 0x23, 0x28, 0x00, 0x0c, 0x31, 0x6a, 0x61, 0x62, 0x63, 0x0a };

	sockfd   = ipv6_socket(daddr);
	data_len = sizeof(data);
	tot_len  = sizeof(struct ipv6_hdr) + data_len;

	ipv6 = ipv6_alloc_packet(0xd3f3e, data_len, IPPROTO_UDP, 64, saddr, daddr, data);
	ipv6_send(sockfd, ipv6, tot_len, daddr, 0);

	ipv6_free_packet(&ipv6);
	ipv6_close(sockfd);
}

int main(int argc, char *argv[])
{
	ipv6_test();
	return 0;
}
192.168.2.200> sudo tcpdump -nt -XX ip6
IP6 fe80::20c:cff:fe0c:c0c.8000 > fe80::20d:dff:fe0d:d0d.9000: UDP, length 4
	0x0000:  000d 0d0d 0d0d 000c 0c0c 0c0c 86dd 600d  ..............`.
	0x0010:  3f3e 000c 1140 fe80 0000 0000 0000 020c  ?>...@..........
	0x0020:  0cff fe0c 0c0c fe80 0000 0000 0000 020d  ................
	0x0030:  0dff fe0d 0d0d 1f40 2328 000c 316a 6162  .......@#(..1jab
	0x0040:  630a
	
192.168.2.100> make run
参考链接
  • https://tools.ietf.org/html/rfc2460
  • https://zh.wikipedia.org/wiki/IPv6
  • https://support.huawei.com/hedex/hdx.do?docid=EDOC1000105967&lang=zh&idPath=24030814|9856750|22715517|9858933|15837
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值