首页 > 代码库 > Neutron分析(5)—— neutron-l3-agent中的iptables

Neutron分析(5)—— neutron-l3-agent中的iptables

一.iptables简介

1.iptables数据包处理流程

tables_traverse

以本机为目的的包,由上至下,走左边的路
本机产生的包,从local process开始走左边的路
本机转发的包,由上至下走右边的路

简化流程如下:

 

2.iptables表结构

在neutron中主要用到filter表和nat表
filter表:
Chain INPUT
Chain FORWARD
Chain OUTPUT
filter表用于信息包过滤,它包含INPUT、OUTPUT和FORWARD 链。

nat表:
Chain PREROUTING
Chain OUTPUT
Chain POSTROUTING
nat表用于网络地址转换,PREROUTING链由指定信息包一到达防火墙就改变它们的规则所组成,而 POSTROUTING 链由指定正当信息包打算离开防火墙时改变它们的规则所组成。

More:
Traversing of tables and chains
Linux Firewalls Using iptables

二.l3 agent消息处理

_rpc_loop  ---  _process_router            ---  _router_added                                            ---  process_router                                                                            ---  _router_removed                      ---  _process_router_delete     ---  _router_removed
 

在上面几个方法中,会涉及到iptables的处理。

三.iptables_manager初始化

iptables_manager的初始化是在class IptablesManager中完成的,它对iptables的链进行了包装。

源码目录:neutron/neutron/agent/linux/iptables_manager.py

主要操作:

新建一个neutron-filter-top链,这个是没有包装的,加在原生的FORWARD和OUTPUT链上。
对filter表的INPUT,OUTPUT,FORWARD链进行包装,将到达原链的数据包转发到包装链,还增加一个包装的local链。
对于nat表,PREROUTING,OUTPUT,POSTROUTING链进行包装,另外在POSTROUTING链之后加了snat链。

代码分析:

对于l3 agent,binary_name是neturon-l3-agent。

filter表的操作:
增加一个链neutron-filter-top,增加规则:
-A FORWARD -j neutron-filter-top
-A OUTPUT -j neutron-filter-top

增加一个包装链neutron-l3-agent-local,增加规则:
-A neutron-filter-top -j neutron-l3-agent-local

        # Add a neutron-filter-top chain. It‘s intended to be shared        # among the various nova components. It sits at the very top        # of FORWARD and OUTPUT.        for tables in [self.ipv4, self.ipv6]:            tables[filter].add_chain(neutron-filter-top, wrap=False)            tables[filter].add_rule(FORWARD, -j neutron-filter-top,                                      wrap=False, top=True)            tables[filter].add_rule(OUTPUT, -j neutron-filter-top,                                      wrap=False, top=True)            tables[filter].add_chain(local)            tables[filter].add_rule(neutron-filter-top, -j $local,                                      wrap=False)
 

包装IPv4和IPv6 filter表的INPUT,OUTPUT,FORWARD链,以及IPv4 nat表的PREROUTING,OUTPUT,POSTROUTING链。

将到达原链的数据包转发到包装链:

        # Wrap the built-in chains        builtin_chains = {4: {filter: [INPUT, OUTPUT, FORWARD]},                          6: {filter: [INPUT, OUTPUT, FORWARD]}}        if not state_less:            self.ipv4.update(                {nat: IptablesTable(binary_name=self.wrap_name)})            builtin_chains[4].update({nat: [PREROUTING,                                      OUTPUT, POSTROUTING]})        for ip_version in builtin_chains:            if ip_version == 4:                tables = self.ipv4            elif ip_version == 6:                tables = self.ipv6            for table, chains in builtin_chains[ip_version].iteritems():                for chain in chains:                    tables[table].add_chain(chain)                    tables[table].add_rule(chain, -j $%s %                                           (chain), wrap=False)
 

包装链neutron-l3-agent-INPUT,neutron-l3-agent-OUTPUT,neutron-l3-agent-FORWARD,添加规则:
-A INPUT -j neutron-l3-agent-INPUT
-A OUTPUT -j neutron-l3-agent-OUTPUT
-A FORWARD -j neutron-l3-agent-FORWARD

nat表的操作:
(承上面的代码)
包装链neutron-l3-agent-PREROUTING,neutron-l3-agent-OUTPUT,neutron-l3-agent-POSTROUTING,添加规则:
-A PREROUTING -j neutron-l3-agent-PREROUTING
-A OUTPUT -j neutron-l3-agent-OUTPUT
-A POSTROUTING -j neutron-l3-agent-POSTROUTING

nat表中添加neutron-postrouting-bottom链,增加规则:
-A POSTROUTING -j neutron-postrouting-bottom

nat表中添加包装链neutron-l3-agent-snat,增加规则:
-A neutron-postrouting-bottom -j neutron-l3-agent-snat

nat表中添加包装链neutron-l3-agent-float-snat,增加规则:
-A neutron-l3-agent-snat -j neutron-l3-agent-float-snat

代码如下:

        if not state_less:            # Add a neutron-postrouting-bottom chain. It‘s intended to be            # shared among the various nova components. We set it as the last            # chain of POSTROUTING chain.            self.ipv4[nat].add_chain(neutron-postrouting-bottom,                                       wrap=False)            self.ipv4[nat].add_rule(POSTROUTING,                                      -j neutron-postrouting-bottom,                                      wrap=False)            # We add a snat chain to the shared neutron-postrouting-bottom            # chain so that it‘s applied last.            self.ipv4[nat].add_chain(snat)            self.ipv4[nat].add_rule(neutron-postrouting-bottom,                                      -j $snat, wrap=False)            # And then we add a float-snat chain and jump to first thing in            # the snat chain.            self.ipv4[nat].add_chain(float-snat)            self.ipv4[nat].add_rule(snat, -j $float-snat)
 

四.l3 agent代码中关于iptables的处理

1._router_added

_router_added方法,创建和metadata相关的iptables规则:

    def _router_added(self, router_id, router):        ri = RouterInfo(router_id, self.root_helper,                        self.conf.use_namespaces, router)        self.router_info[router_id] = ri        if self.conf.use_namespaces:            self._create_router_namespace(ri)        for c, r in self.metadata_filter_rules():            ri.iptables_manager.ipv4[filter].add_rule(c, r)        for c, r in self.metadata_nat_rules():            ri.iptables_manager.ipv4[nat].add_rule(c, r)        ri.iptables_manager.apply()        super(L3NATAgent, self).process_router_add(ri)        if self.conf.enable_metadata_proxy:            self._spawn_metadata_proxy(ri.router_id, ri.ns_name)
 

1.metadata_filter_rules方法中,如果enable_metadata_proxy为True,增加规则

    def metadata_filter_rules(self):        rules = []        if self.conf.enable_metadata_proxy:            rules.append((INPUT, -s 0.0.0.0/0 -d 127.0.0.1                           -p tcp -m tcp --dport %s                           -j ACCEPT % self.conf.metadata_port))        return rules
 

然后在filter表中增加这条规则,接受所有从外面进来到达metadata_port端口的数据包:
-A neutron-l3-agent-INPUT -s 0.0.0.0/0 -d 127.0.0.1 -p tcp -m tcp –dport 9697 -j ACCEPT

2.metadata_nat_rules方法,如果enable_metadata_proxy为True,增加规则

    def metadata_nat_rules(self):        rules = []        if self.conf.enable_metadata_proxy:            rules.append((PREROUTING, -s 0.0.0.0/0 -d 169.254.169.254/32                           -p tcp -m tcp --dport 80 -j REDIRECT                           --to-port %s % self.conf.metadata_port))        return rules
 

然后在nat表中增加这条规则做DNAT转换,在route之前,将虚拟机访问169.254.169.254端口80的数据包重定向到metadat_port端口:
-A neutron-l3-agent-PREROUTING -s 0.0.0.0/0 -d 169.254.169.254/32 -p tcp -m tcp –dport 80 -j REDIRECT –to-port 9697

再调用iptables_manager.apply()方法,应用规则:
iptables-save -c ,获取当前所有iptables信息;
iptables-restore -c ,应用最新的iptables配置;

2.process_router

process_router方法:

1.perform_snat_action,为external gateway处理SNAT规则

    def _handle_router_snat_rules(self, ri, ex_gw_port, internal_cidrs,                                  interface_name, action):        # Remove all the rules        # This is safe because if use_namespaces is set as False        # then the agent can only configure one router, otherwise        # each router‘s SNAT rules will be in their own namespace        ri.iptables_manager.ipv4[nat].empty_chain(POSTROUTING)        ri.iptables_manager.ipv4[nat].empty_chain(snat)        # Add back the jump to float-snat        ri.iptables_manager.ipv4[nat].add_rule(snat, -j $float-snat)        # And add them back if the action if add_rules        if action == add_rules and ex_gw_port:            # ex_gw_port should not be None in this case            # NAT rules are added only if ex_gw_port has an IPv4 address            for ip_addr in ex_gw_port[fixed_ips]:                ex_gw_ip = ip_addr[ip_address]                if netaddr.IPAddress(ex_gw_ip).version == 4:                    rules = self.external_gateway_nat_rules(ex_gw_ip,                                                            internal_cidrs,                                                            interface_name)                    for rule in rules:                        ri.iptables_manager.ipv4[nat].add_rule(*rule)                    break        ri.iptables_manager.apply()
 

先清空nat表的neutron-l3-agent-POSTROUTING链和neutron-l3-agent-snat链;

再在nat表的neutron-l3-agent-snat链添加规则:

-A neutron-l3-agent-snat -j neutron-l3-agent-float-snat

然后对应add_rules操作,则处理external_gateway_nat_rules,处理完后在nat表中添加规则:

    def external_gateway_nat_rules(self, ex_gw_ip, internal_cidrs,                                   interface_name):        rules = [(POSTROUTING, ! -i %(interface_name)s                   ! -o %(interface_name)s -m conntrack !                   --ctstate DNAT -j ACCEPT %                  {interface_name: interface_name})]        for cidr in internal_cidrs:            rules.extend(self.internal_network_nat_rules(ex_gw_ip, cidr))        return rules
 

规则命令如下:

-A neutron-l3-agent-POSTROUTING ! -i qg-XXX ! -o qg-XXX -m conntrack ! –ctstate DNAT -j ACCEPT

这条命令的意思是除了出口和入口都为qg-XXX,(qg即是router上的外部网关接口)匹配除了DNAT之外的其他状态。

然后处理internal_network_nat_rules:

    def internal_network_nat_rules(self, ex_gw_ip, internal_cidr):        rules = [(snat, -s %s -j SNAT --to-source %s %                 (internal_cidr, ex_gw_ip))]        return rules
 

规则命令如下:

-A neutron-l3-agent-snat -s internal_cidr -j SNAT –to-source ex_gw_ip

2.process_router_floating_ip_nat_rules方法,处理floating ip,作SNAT/DNAT转换。

    def process_router_floating_ip_nat_rules(self, ri):        """Configure NAT rules for the router‘s floating IPs.        Configures iptables rules for the floating ips of the given router        """        # Clear out all iptables rules for floating ips        ri.iptables_manager.ipv4[nat].clear_rules_by_tag(floating_ip)        # Loop once to ensure that floating ips are configured.        for fip in ri.router.get(l3_constants.FLOATINGIP_KEY, []):            # Rebuild iptables rules for the floating ip.            fixed = fip[fixed_ip_address]            fip_ip = fip[floating_ip_address]            for chain, rule in self.floating_forward_rules(fip_ip, fixed):                ri.iptables_manager.ipv4[nat].add_rule(chain, rule,                                                         tag=floating_ip)        ri.iptables_manager.apply()   def floating_forward_rules(self, floating_ip, fixed_ip):        return [(PREROUTING, -d %s -j DNAT --to %s %                 (floating_ip, fixed_ip)),                (OUTPUT, -d %s -j DNAT --to %s %                 (floating_ip, fixed_ip)),                (float-snat, -s %s -j SNAT --to %s %                 (fixed_ip, floating_ip))]
 

先清理nat表所有的floationg ip规则;然后floating_forward_rules方法,在nat表中处理floating ip和fixed ip的NAT转换:

 

具体规则如下:
-A neutron-l3-agent-PREROUTING -d floating_ip -j DNAT –to fixed_ip
-A neutron-l3-agent-OUTPUT -d floating_ip -j DNAT –to fixed_ip
-A neutron-l3-agent-float-snat -s fixed_ip -j SNAT –to floating_ip

3._router_removed

_router_removed方法,删除和metadata相关的规则:

    def _router_removed(self, router_id):        ri = self.router_info.get(router_id)        if ri is None:            LOG.warn(_("Info for router %s were not found. "                       "Skipping router removal"), router_id)            return        ri.router[gw_port] = None        ri.router[l3_constants.INTERFACE_KEY] = []        ri.router[l3_constants.FLOATINGIP_KEY] = []        self.process_router(ri)        for c, r in self.metadata_filter_rules():            ri.iptables_manager.ipv4[filter].remove_rule(c, r)        for c, r in self.metadata_nat_rules():            ri.iptables_manager.ipv4[nat].remove_rule(c, r)        ri.iptables_manager.apply()        if self.conf.enable_metadata_proxy:            self._destroy_metadata_proxy(ri.router_id, ri.ns_name)        del self.router_info[router_id]        self._destroy_router_namespace(ri.ns_name)

 

五.总结

l3 agent初始化完成后,iptables处理流程如下:

感谢春祥提供图片!

Reference:
Neutron中的iptables

本文转自http://squarey.me/cloud-virtualization/iptables_usage_in_l3_agent.html