以下iptables命令查看ipvs匹配扩展的帮助信息。可匹配的字段分别为:虚拟服务的协议号、地址、端口、数据流的方向、转发模式以及控制连接的端口号。对于类似FTP的服务,其控制连接的端口为21,数据端口为20。
$ iptables -m ipvs --help
iptables v1.6.0
IPVS match options:
[!] --ipvs packet belongs to an IPVS connection
Any of the following options implies --ipvs (even negated)
[!] --vproto protocol VIP protocol to match; by number or name, e.g. "tcp"
[!] --vaddr address[/mask] VIP address to match
[!] --vport port VIP port to match; by number or name, e.g. "http"
--vdir {ORIGINAL|REPLY} flow direction of packet
[!] --vmethod {GATE|IPIP|MASQ} IPVS forwarding method used
[!] --vportctl port VIP port of the controlling connection to match, e.g. 21 for FTP
函数ipvs_mt_init先系统注册IPVS匹配扩展定义xt_ipvs_mt_reg。匹配名称为ipvs,这里定义了匹配函数ipvs_mt,以及检查函数ipvs_mt_check。
static struct xt_match xt_ipvs_mt_reg __read_mostly = {
.name = "ipvs",
.revision = 0,
.family = NFPROTO_UNSPEC,
.match = ipvs_mt,
.checkentry = ipvs_mt_check,
.matchsize = XT_ALIGN(sizeof(struct xt_ipvs_mtinfo)),
};
static int __init ipvs_mt_init(void)
{
return xt_register_match(&xt_ipvs_mt_reg);
}
匹配结构中的matchsize字段,给定了数据结构xt_ipvs_mtinfo的长度,其定义中包含所有要匹配的字段变量。其最后一个成员变量bitmask掩码,取值为XT_IPVS_XX中的一个或多个,置位表明指定了相应的字段;否则,不处理相应的字段。如bitmask中置位XT_IPVS_METHOD,表明指定了转发模式字段fwd_method。
字段invert的取值与bitmask相同,但是invert中的相应位指明进行取反操作。例如,invert中设置了XT_IPVS_METHOD位,表明匹配除fwd_method变量中所指定了转发模式之外的模式。
enum {
XT_IPVS_IPVS_PROPERTY = 1 << 0, /* all other options imply this one */
XT_IPVS_PROTO = 1 << 1,
XT_IPVS_VADDR = 1 << 2,
XT_IPVS_VPORT = 1 << 3,
XT_IPVS_DIR = 1 << 4,
XT_IPVS_METHOD = 1 << 5,
XT_IPVS_VPORTCTL = 1 << 6,
}
struct xt_ipvs_mtinfo {
union nf_inet_addr vaddr, vmask;
__be16 vport;
__u8 l4proto;
__u8 fwd_method;
__be16 vportctl;
__u8 invert;
__u8 bitmask;
};
由注册的函数ipvs_mt_check可知,IPVS匹配模块仅支持IPv4和IPv6两种协议。此函数在匹配函数ipvs_mt之前执行。
static int ipvs_mt_check(const struct xt_mtchk_param *par)
{
if (par->family != NFPROTO_IPV4
#ifdef CONFIG_IP_VS_IPV6
&& par->family != NFPROTO_IPV6
#endif
) {
pr_info("protocol family %u not supported\n", par->family);
return -EINVAL;
}
return 0;
因为XT_IPVS_IPVS_PROPERTY属性是一个基础属性,并且之后的其它字段都隐含了此字段,如果bitmask掩码完全等于XT_IPVS_IPVS_PROPERTY,表明仅仅设置了此字段,即iptables配置命令行的命令字:(–ipvs)。根据invert的值决定是否匹配,如果此报文仅有IPVS系统处理,skb的成员ipvs_property为1,如果invert为0,不取反,则匹配发生。
其次,如果报文未经过ipvs系统处理,ipvs_property为0,就没有继续处理的必要了。
static bool ipvs_mt(const struct sk_buff *skb, struct xt_action_param *par)
{
const struct xt_ipvs_mtinfo *data = par->matchinfo;
struct netns_ipvs *ipvs = net_ipvs(xt_net(par));
const u_int8_t family = xt_family(par); /* ipvs_mt_check ensures that family is only NFPROTO_IPV[46]. */
struct ip_vs_iphdr iph;
if (data->bitmask == XT_IPVS_IPVS_PROPERTY) {
match = skb->ipvs_property ^ !!(data->invert & XT_IPVS_IPVS_PROPERTY);
goto out;
}
/* other flags than XT_IPVS_IPVS_PROPERTY are set */
if (!skb->ipvs_property) {
match = false;
goto out;
}
函数ip_vs_fill_iph_skb获取数据包中的IP头部信息,保存于ip_vs_iphdr结构类型的变量iph中。匹配字段XT_IPVS_PROTO对应于iptables配置命令的命令选项(–vproto)。注意这里的字段对比与以上XT_IPVS_IPVS_PROPERTY字段的操作不同,如果协议字段相等,并且invert字段为1,表明匹配失败。反之,匹配成功,match的初始值就位true。
其后,根据协议值,获取IPVS的协议处理结构和IPVS连接结构。
ip_vs_fill_iph_skb(family, skb, true, &iph);
if (data->bitmask & XT_IPVS_PROTO)
if ((iph.protocol == data->l4proto) ^ !(data->invert & XT_IPVS_PROTO)) {
match = false;
goto out;
}
pp = ip_vs_proto_get(iph.protocol);
if (unlikely(!pp)) {
match = false;
goto out;
}
/* Check if the packet belongs to an existing entry */
cp = pp->conn_out_get(ipvs, family, skb, &iph);
if (unlikely(cp == NULL)) {
match = false;
goto out;
}
以下代码根据IPVS连接结构cp中的信息进行匹配比较,其逻辑与以上的XT_IPVS_PROTO字段一致。包括:XT_IPVS_VPORT、XT_IPVS_VPORTCTL、XT_IPVS_DIR、XT_IPVS_METHOD和XT_IPVS_VADDR等字段。
对于XT_IPVS_VPORTCTL字段,需要找到其IPVS控制连接结构,对比其中的虚拟服务端口,因为此为控制端口。
对于方向XT_IPVS_DIR字段的比较,使用conntrack系统的信息值ctinfo进行匹配。
if (data->bitmask & XT_IPVS_VPORT)
if ((cp->vport == data->vport) ^ !(data->invert & XT_IPVS_VPORT)) {
match = false; goto out_put_cp;
}
if (data->bitmask & XT_IPVS_VPORTCTL)
if ((cp->control != NULL && cp->control->vport == data->vportctl) ^ !(data->invert & XT_IPVS_VPORTCTL)) {
match = false; goto out_put_cp;
}
if (data->bitmask & XT_IPVS_DIR) {
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
if (ct == NULL) {
match = false; goto out_put_cp;
}
if ((ctinfo >= IP_CT_IS_REPLY) ^ !!(data->invert & XT_IPVS_DIR)) {
match = false; goto out_put_cp;
}
}
if (data->bitmask & XT_IPVS_METHOD)
if (((cp->flags & IP_VS_CONN_F_FWD_MASK) == data->fwd_method) ^ !(data->invert & XT_IPVS_METHOD)) {
match = false; goto out_put_cp;
}
if (data->bitmask & XT_IPVS_VADDR) {
if (ipvs_mt_addrcmp(&cp->vaddr, &data->vaddr, &data->vmask, family) ^ !(data->invert & XT_IPVS_VADDR)) {
match = false; goto out_put_cp;
}
在内核邮件列表中,作者提供了一种利用ipvs匹配实现全NAT(SNAT+DNAT)的转发模式。IPVS的NAT/Masq转发模式实现的是DNAT转发,以下iptables命令开启SNAT功能。
# ipvsadm -A -t 192.168.100.30:80 -s rr
# ipvsadm -a -t 192.168.100.30:80 -r 192.168.10.20:80 -m
# ...
# Source NAT for VIP 192.168.100.30:80
# iptables -t nat -A POSTROUTING -m ipvs --vaddr 192.168.100.30/32 --vport 80 -j SNAT --to-source 192.168.10.10
or SNAT-ing only a specific real server:
# iptables -t nat -A POSTROUTING --dst 192.168.10.20 -m ipvs --vaddr 192.168.100.30/32 -j SNAT --to-source 192.168.10.10
以上命令将到达虚拟服务:192.168.100.30:80的流量,进行SNAT转换,源地址变换为:192.168.10.10。或者仅仅对调度到真实服务器192.168.10.20的流量,进行SNAT转换。
这里有一个问题,如果指定的虚拟服务的端口为FTP端口21,对其进行SNAT操作,那么对于FTP的数据通道,其端口并不是21,就需要以下的命令为其数据连接指定SNAT,这里使用命令选项(–vportctl)。
# SNAT FTP control connection
# iptables -t nat -A POSTROUTING -m ipvs --vaddr 192.168.100.30/32 --vport 21 -j SNAT --to-source 192.168.10.10
# SNAT FTP passive data connection
# iptables -t nat -A POSTROUTING -m ipvs --vaddr 192.168.100.30/32 --vportctl 21 -j SNAT --to-source 192.168.10.10
以上的配置SNAT方式,在开启IPVS同步功能时,在主和被机上将产生不同的行为,
内核版本 5.0