分类: BSD
2005-10-17 14:26:35
又发个为完成的.关于OPENBSD的CARP(通用地址冗余协议)
--CARP协议原理及结构
/*作者:xie_minix*/
CARP ---通用地址冗余协议
源代码:(OpenBSD系统) src/sys/netinet/ip_carp.h, Revision 1.8
在核心配置文件/sys/arch/i386/conf/GENERIC中.定义为:
pseudo-device carp [count]
其中count 为支持虚拟设备carp的数量
描述:
carp接口为一虚拟设备.(注:虚拟设备即在机器中不真实存在).此种设备一般是使用通用
接口的克隆技术来生成.比如在carp程序中的挂接设备函数(一般设备的挂接都是使用"设备名
+attach")carpattch只是简单调用通用接口文件(if.c)中的通用设备克隆挂接函数
if_clone_attach(源代码:753行).实际上是在所有的使用克隆产生的设备列表中插入该carp
设备到表头(全局变量if_cloners是所有克隆设备的链表头.源代码:if.c第135行).
CARP协议是在IP之上的一种协议.请注意carp和CARP的不同.即carp表示的是一种虚拟设备
.此设备提供对CARP协议的支持.关于CARP协议将在以下介绍.先来看看carp的作用.carp接口允
许本地网络的(注意是同一网段,不能跨越路由器)多个机器来共享一个(一组)IP地址.实际上的
结果就是当一台该IP地址的主机在出现意外事故的情况下不能工作.其它的机器能够立刻自动
的接替其工作.这一点对于防火墙系统来说提供冗余功能是非常不错的.目前的具有冗余功能的
防火墙系统也只有OpenBSD系统.当然我在从事ARP研究时曾经提出过类似问题,即如何在核心内
实现IP冒充技术.但只能是单向实现欺骗.对carp进行一些设置后它还能提供负载均衡功能.关于
这些文章详见Ryan McBride的文章.
使用方法:
要使用carp设备必须首先在编译内核时加入对carp的支持.
在/sys/arch/i386/conf/GENERIC (或你自己定义的核心配置文件)中加入:
pseudo-device carp 16 (注意:在Man手册中有参数,即在carp后可以接参数,但在最新的GENERIC中没有说明接参数)
编译核心后使用ifconfig carp0 create 来建立carp第0号设备.
使用:ifconfig carp0 vhid 1 pass mekmitasdigoat 192.168.1.10 255.255.255.0来设置本机的第一个carp设备.
意思是carp第0号设备(即第一个)的主机号为1,pass后接的是要用SHA1加密的通讯字符(当然可以自己随便取,不过只
是前20个字符有用,下面我会有说明).IP地址是设置的carp设备的IP地址.
说明:
实际上任何的ifconfig都将调用欲设置的设备的源代码部分的ioctl函数(即该设备名+"_ioctl",在此例中是carp_ioctl).
我们的参数1,mekmitasdigoat等都将放到一个叫carpreq结构中.在源代码的1594行有申明:struct carpreq carpr;
通过copyin(ifr->ifr_data, &carpr, sizeof carpr) (源代码1687行) 从用户区把参拷贝到核心区的carpreq结构的实例
carpr中.然后设置该设备的硬件地址.我们看看源代码的1718行:
sc->sc_vhid = carpr.carpr_vhid;
sc->sc_ac.ac_enaddr[0] = 0;
sc->sc_ac.ac_enaddr[1] = 0;
sc->sc_ac.ac_enaddr[2] = 0x5e;
sc->sc_ac.ac_enaddr[3] = 0;
sc->sc_ac.ac_enaddr[4] = 1;
sc->sc_ac.ac_enaddr[5] = sc->sc_vhid;
以上的ac_enaddr的前五位地址说明CARP协议使用的是多播地址.硬件地址使用的最后一位是参数中的主机号,也就是说.
ifconfig中的主机号是用于设置carp的地址用的.
参数mekmitasdigoat在源代码的第1740行进行了处理.bcopy(carpr.carpr_key, sc->sc_key, sizeof(sc->sc_key));
即把参数(结构carpreq的成员carpr_key)放到了结构carp_softc的成员sc_key中.结构carp_softc是接口设备的专用
数据结构,每个接口都有相应的该结构(详见xie_minix写的"以太网通用驱动源代码详解").其成员将在下一篇的"carp的
源代码实现"中会详细介绍.我们现在看看carp_softc的成员sc_key的长度.源代码127行:unsigned char sc_key[CARP_KEY_LEN];
其中CARP_KEY_LEN在本文(即carp.h头文件)有定义.长度为20.这就是为什么参数最大长度为20个字符的原因.以上的设置只是
对备用机的设置.对主用机的设置用以下语法:
ifconfig carp0 create
ifconfig carp0 vhid 1 advskew 100 pass mekmitasdigoat 192.168.1.10 255.255.255.0
当然这些语句是在另一台机器上执行的.唯一的区别是多了参数advskew 100,该参数是用来设定主用机发送广告包(CARP)的频率
CARP协议结构框图:
位长 |0------45------7|8------------1516------------2324------------31|
版本 | 类型 | 虚拟的主机ID | CARP广告时间微秒级 | COUNTER+HMAC的32位长度即第3-9行的行数量 |
保留 | CARP广告时间秒级 | 校验和 | ||
64位计数器的高32位 | ||||
64位计数器的低32位 | ||||
本机的MAC地址HASH值(第一部分) | ||||
本机的MAC地址HASH值(第二部分) | ||||
本机的MAC地址HASH值(第三部分) | ||||
本机的MAC地址HASH值(第四部分) | ||||
本机的MAC地址HASH值(第五部分) |
上图的具体解释在下面的结构说明中. 1.1 mcbride 56: struct carp_header { /*CARP协议头部,实际上该协议只有头部*/ 57: #if BYTE_ORDER == LITTLE_ENDIAN /*小头字节序*/ 58: u_int8_t carp_type:4, /*该成员只在函数carp_send_ad(定时发送CARP广告)中进行了
填充CARP_ADVERTISEMENT常量.其他就没用过了,实际上该成员是留着以后可以进行其他的扩充.*/ 59: carp_version:4;/*版本号,即CARP_VERSION常量在发送时被填充,在接收时
函数carp_input_c(即接收包处理函数)会进行判断.如果对方主机发送的CARP包的版本号不为CARP_VERSION
时会丢弃该包.我们看看原形:if (ch->carp_version != CARP_VERSION).在本机发送CARP时,该成员也必须
填充该值*/ 60: #endif 61: #if BYTE_ORDER == BIG_ENDIAN 62: u_int8_t carp_version:4, 63: carp_type:4; 64: #endif 65: u_int8_t carp_vhid; /* 虚拟主机的ID*/ 66: u_int8_t carp_advskew; /* CARP广告的毫秒级值,当主,僚两机在抢占模式时,根据
该值和下面的advbase秒级的值大小来确定哪台机器为主,哪台为僚机. */ 67: u_int8_t carp_authlen; /* counter+md的32位块长度,实际上从上图可以看出为7.
不直接填充7的原因大概是为了以后的扩充,在程序中除了发送时填充为7外,接收时根本就不判断.目前有点多余*/ 68: u_int8_t carp_pad1; /* 保留*/ 69: u_int8_t carp_advbase; /* 发出CARP广告的秒级间隔时间,和上面的advskew合用 */ 70: u_int16_t carp_cksum; /*校验和*/ 71: u_int32_t carp_counter[2]; /*实际上是一个64位长的计数器.有点类似IP的序号*/ 1.8 |mcbride 72: unsigned char carp_md[20]; /* 用SHA1进行HASH过的MAC地址 */ 1.4 avsm 73: } __packed; /*说明是紧凑型*/ 1.1 mcbride 74: 75: #define CARP_DFLTTL 255 /*发送CARP时的IP包的TTL的值,懂IP协议的人都知道
TTL值经过路由器时会被减1,在CARP接收程序中会判断该IP包的TTL值是否小于255,如果小于的话,说明该负载CARP
的IP包来自于外网,是个可疑的包,因为CARP本身是基于内网的.*/ 76: 77: /* carp_version */ 78: #define CARP_VERSION 2 /*CARP的版本*/ 79: 80: /* carp_type */ 81: #define CARP_ADVERTISEMENT 0x01 /*CARP包的类型,注意目前只有该一种类型,我们可以对其
进行扩展.如在CARP防火墙冗余系统中加入加密后的过滤规则的传送,好处是在两台有CARP冗余系统的OPENBSD中随便
哪台上设置后,在一台出现故障后,另一台能自动获取最新设置的过滤规则.*/ 82: 83: #define CARP_KEY_LEN 20 /* 用于本地的某一CARP虚拟设备的MAC地址
HASH后产生的值的长度.在carp_softc的成员表示为sc_key[CARP_KEY_LEN],该值的填充一般是在CARP设备初始化
时对MAC地址HASH后填充到这个20字节长的区域中.*/ 84: 85: /* carp_advbase */ 86: #define CARP_DFLTINTV 1 87: 88: /* 89: * 统计数据. 90: */ 91: struct carpstats { 1.6 mcbride 92: u_int64_t carps_ipackets; /* IPV4版本的进入的包数 */ 93: u_int64_t carps_ipackets6; /* IPV6版本的进入的包数 */ 94: u_int64_t carps_badif; /* 如果接收包的接口无CARP,则该成员加1 */ 95: u_int64_t carps_badttl; /* TTL不是CARP_DFLTTL,目前只是为255 */ 96: u_int64_t carps_hdrops; /* 如果mbuf的长度小于IP头+CARP结构长 */ 97: u_int64_t carps_badsum; /* 校验和错 */ 98: u_int64_t carps_badver; /* 错误的版本号 */ 99: u_int64_t carps_badlen; /* 得到的IP+CARP>分组的长度错误 */ 100: u_int64_t carps_badauth; /* 错误的HASH内容长度值,应该为7 */ 101: u_int64_t carps_badvhid; /* 错误的虚拟主机ID */ 102: u_int64_t carps_badaddrs; /* 居然没使用过 */ 103: 104: u_int64_t carps_opackets; /* IPV4版本的发出的包数 */ 105: u_int64_t carps_opackets6; /* IPV6版本的发出的包数 */ 106: u_int64_t carps_onomem; /* 在发送CARP包时申请内存失败 */ 107: u_int64_t carps_ostates; /* 也居然没用过 */ 1.1 mcbride 108: 1.6 mcbride 109: u_int64_t carps_preempt; /* 也没用过 */ 1.1 mcbride 110: }; 111: 112: /* 113: * 用于SIOCSVH SIOCGVH,即设置(获取)机器的carp设备的ID和广告间隔值等参数 114: */ 115: struct carpreq { /*该结构被用来放置从用户区传来的参数*/
/*在语句copyin(ifr->ifr_data, &carpr, sizeof carpr)中即是从用户区拷贝数据到核心区的该结构中(carpr就是*/
/*carpreq结构的一个实例),在随后的程序中就要使用其各个成员来设置carp设备的carp_softc属性*/ 116: int carpr_state;/*该值是用户将要设置(在进行SIOCSVH时)时欲改变成哪种状态*/
/*如果本机carp设备已经启用了并且要更改的状态和现有的状态一样,则不会改变什么.*/ 117: #define CARP_STATES "INIT", "BACKUP", "MASTER" 118: #define CARP_MAXSTATE 2 119: int carpr_vhid; 120: int carpr_advskew; 121: int carpr_advbase; 122: unsigned char carpr_key[CARP_KEY_LEN]; 123: }; 124: #define SIOCSVH _IOWR('i', 245, struct ifreq) 125: #define SIOCGVH _IOWR('i', 246, struct ifreq) 126: 127: /* 128: * Names for CARP sysctl objects 129: */ 130: #define CARPCTL_ALLOW 1 /* 在carp_input函数中,判断CARP接口是否允许接收CARP包 */ 131: #define CARPCTL_PREEMPT 2 /* 抢占模式 */ 132: #define CARPCTL_LOG 3 /* 记录错误的包 */ 133: #define CARPCTL_ARPBALANCE 4 /* 均衡ARP回应 */ 134: #define CARPCTL_MAXID 5 135: 136: #define CARPCTL_NAMES { 137: { 0, 0 }, 138: { "allow", CTLTYPE_INT }, 139: { "preempt", CTLTYPE_INT }, 140: { "log", CTLTYPE_INT }, 141: { "arpbalance", CTLTYPE_INT }, 142: } 143: 144: #ifdef _KERNEL /*说明下面的函数在核心中使用,属于全局函数*/ 145: void carp_ifdetach (struct ifnet *); /*卸载carp接口*/ 146: void carp_input (struct mbuf *, ...);/*由以太网通用例程ether_input函数调用*/ 1.5 mcbride 147: void carp_carpdev_state(void *);/*新加的函数,还没看过,估计是查看CARP虚拟设备状态,统计等*/ 1.2 mcbride 148: int carp6_input (struct mbuf **, int *, int);/*IPV6用*/ 1.1 mcbride 149: int carp_output (struct ifnet *, struct mbuf *, struct sockaddr *, 150: struct rtentry *); 151: int carp_iamatch (void *, struct in_ifaddr *, struct in_addr *, 152: u_int8_t **); 1.2 mcbride 153: struct ifaddr *carp_iamatch6(void *, struct in6_addr *); 154: void *carp_macmatch6(void *, struct mbuf *, struct in6_addr *); 1.1 mcbride 155: struct ifnet *carp_forus (void *, void *); 156: int carp_sysctl (int *, u_int, void *, size_t *, void *, size_t); 157: #endif