• 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处理流程如下:

  • 相关阅读:
    迟到感悟
    让自己记住吧。
    hadoop 数据抽取
    指标导入常用函数
    linux shell中单引号、双引号、反引号、反斜杠的区别
    简单解说Linux命令输出与命令替换
    生成表结构
    ASP.NET MVC3在Visual Studio 2010中的变化
    主键自增归0
    解决包含已存在的php文件,但提示就是找不到的问题
  • 原文地址:https://www.cnblogs.com/liuhongru/p/11072086.html
Copyright © 2020-2023  润新知