红联Linux门户
Linux帮助

CARP协议详解

发布时间:2006-09-18 00:19:02来源:红联作者:译元
今天整理资料,关于OpenBSD的CARP,google了一下,找到了如下资料:

The Common Address Redundancy Protocol manages failover at the intersection of Layers 2 and 3 in the OSI Model (link layer and IP layer). Each CARP group has a virtual MAC (link layer) address, and one or more virtual host IP addresses (the common address). CARP hosts respond to ARP requests for the common address with the virtual MAC address, and the CARP advertisements themselves are sent out with this as the source address, which helps switches quickly determine which port the virtual MAC address is currently "at".

The master of the address sends out CARP advertisement messages via multicast using the CARP protocol (IP Protocol 112) on a regular basis, and the backup hosts listen for this advertisement. If the advertisements stop, the backup hosts will begin advertising. The advertisement frequency is configurable, and the host which advertises most frequently is the one most likely to become master in the event of a failure.

A reader who is familiar with VRRP will find this is somewhat familiar, however there are some significant differences:

? The CARP protocol is address family independent. The OpenBSD implementation supports both IPv4 and IPv6, as a transport for the CARP packets as well as common addresses to be shared.

? CARP has an "arpbalance" feature that allows multiple hosts to share a single IP address simultaneously; in this configuration, there is a virtual MAC address for each host, but only one IP address.

? CARP uses a cryptographically strong SHA-1 HMAC to protect each advertisement.

Besides these technical differences, there is another significant difference (perhaps the most important one, in fact): CARP is not patent encumbered. See this page for details on the history of CARP and our reasons for avoiding a VRRP implementation.


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
文章评论

共有 0 条评论