http://squarey.me/cloud-virtualization/neutron-l3-analyse.html
一.Layer-3 Networking Extension
neutron l3作为一种API扩展,向租户提供了路由和NAT功能。
l3扩展包含两种资源:
router:
在不同内部子网中转发数据包;通过指定内部网关做NAT。
每一个子网对应router上的一个端口,这个端口的ip就是子网的网关。
floating ip:
代表一个外部网络的IP,映射到内部网络的端口上。
当网络的router:external属性为True时,floating ip才能定义。
这两种资源都对应有不同的属性。支持CRUD操作。
二.代码分析
既然neutron中支持了l3扩展,那么怎样通过API来创建router或者floating ip,以提供路由以及NAT的功能的呢?
主要有以下几个步骤:
1.租户通过horizon,nova命令或者自定义的脚本,发送与router或floating ip相关的操作。
2.这些API请求发送到neutron server,通过neutron提供的API extension相对应。
3.实现这些API extension的操作,比如说create_router,则由具体的plugin和database来共同完成。
4.plugin会通过rpc机制与计算网络节点上运行的l3 agent来执行l3 转发和NAT的功能。
rpc处理
在上面的l3_db.py中,会将涉及router和floating ip的处理读取或者写入到数据中。但是还有一些操作不仅如此,还需要通过rpc(通过调用l3_rpc_agent_api中的函数,这些操作大部分会去调用routers_updated),通知l3 agent进行处理。
这些需要处理的地方包括:
update_router,delete_router,add_router_interface,remove_router_interface,create_floatingip,update_floatingip,delete_floatingip,disassociate_floatingips等操作。
l3_agent.py
源码目录:/neutron/agent/l3_agent.py
l3 agent使用Linux ip协议栈和iptables来实现router和NAT的功能。
这时候,如果在horizon的界面创建一个路由,不进行任何操作的话,plugin只会操作数据库,l3 agent不会作处理。
而当update router,如设置外部网关时,l3才会去处理请求。
在l3 agent中,会有一个rpc loop,去循环检测从plugin发送过来的rpc请求
在l3 agent中,会有一个rpc loop,去循环检测从plugin发送过来的rpc请求。
|
|
如果有rpc请求过来,即需要更新路由信息,或者添加路由子接口,创建floating ip等操作,都会在这里执行。
这个函数里会去调用_process_routers函数,在_process_routers函数中会去创建绿色线程,执行process_router函数。
可以说,l3 agent调用网络设备的工作都会在process_router中进行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
def
process_router(
self
, ri):
ri.iptables_manager.defer_apply_on()
ex_gw_port
=
self
._get_ex_gw_port(ri)
internal_ports
=
ri.router.get(l3_constants.INTERFACE_KEY, [])
existing_port_ids
=
set
([p[
'id'
]
for
p
in
ri.internal_ports])
current_port_ids
=
set
([p[
'id'
]
for
p
in
internal_ports
if
p[
'admin_state_up'
]])
new_ports
=
[p
for
p
in
internal_ports
if
p[
'id'
]
in
current_port_ids
and
p[
'id'
]
not
in
existing_port_ids]
old_ports
=
[p
for
p
in
ri.internal_ports
if
p[
'id'
]
not
in
current_port_ids]
for
p
in
new_ports:
self
._set_subnet_info(p)
self
.internal_network_added(ri, p[
'network_id'
], p[
'id'
],
p[
'ip_cidr'
], p[
'mac_address'
])
ri.internal_ports.append(p)
for
p
in
old_ports:
self
.internal_network_removed(ri, p[
'id'
], p[
'ip_cidr'
])
ri.internal_ports.remove(p)
internal_cidrs
=
[p[
'ip_cidr'
]
for
p
in
ri.internal_ports]
# TODO(salv-orlando): RouterInfo would be a better place for
# this logic too
ex_gw_port_id
=
(ex_gw_port
and
ex_gw_port[
'id'
]
or
ri.ex_gw_port
and
ri.ex_gw_port[
'id'
])
interface_name
=
None
if
ex_gw_port_id:
interface_name
=
self
.get_external_device_name(ex_gw_port_id)
if
ex_gw_port
and
not
ri.ex_gw_port:
self
._set_subnet_info(ex_gw_port)
self
.external_gateway_added(ri, ex_gw_port,
interface_name, internal_cidrs)
elif
not
ex_gw_port
and
ri.ex_gw_port:
self
.external_gateway_removed(ri, ri.ex_gw_port,
interface_name, internal_cidrs)
# Process static routes for router
self
.routes_updated(ri)
# Process SNAT rules for external gateway
ri.perform_snat_action(
self
._handle_router_snat_rules,
internal_cidrs, interface_name)
# Process SNAT/DNAT rules for floating IPs
fip_statuses
=
{}
try
:
if
ex_gw_port:
existing_floating_ips
=
ri.floating_ips
self
.process_router_floating_ip_nat_rules(ri)
ri.iptables_manager.defer_apply_off()
# Once NAT rules for floating IPs are safely in place
# configure their addresses on the external gateway port
fip_statuses
=
self
.process_router_floating_ip_addresses(
ri, ex_gw_port)
except
Exception:
# TODO(salv-orlando): Less broad catching
# All floating IPs must be put in error state
for
fip
in
ri.router.get(l3_constants.FLOATINGIP_KEY, []):
fip_statuses[fip]
=
l3_constants.FLOATINGIP_STATUS_ERROR
if
ex_gw_port:
# Identify floating IPs which were disabled
ri.floating_ips
=
set
(fip_statuses.keys())
for
fip_id
in
existing_floating_ips
-
ri.floating_ips:
fip_statuses[fip_id]
=
l3_constants.FLOATINGIP_STATUS_DOWN
# Update floating IP status on the neutron server
self
.plugin_rpc.update_floatingip_statuses(
self
.context, ri.router_id, fip_statuses)
# Update ex_gw_port and enable_snat on the router info cache
ri.ex_gw_port
=
ex_gw_port
ri.enable_snat
=
ri.router.get(
'enable_snat'
)
|
process_router函数所做的工作有:
1.处理内部接口
这个是在router添加和删除子接口时工作。它会调用internal_network_added和internal_network_removed这个两个函数。
在internal_network_added和internal_network_removed这个两个函数会去调用BridgeInterfaceDriver(源码目录/neutron/agent/linux/interface.py)的plug和unplug函数,这两个函数最终会用ip link 和ip addr的命令去处理接口和ip地址。
2.处理外部网关
router添加和删除外部网关。调用external_gateway_added和external_gateway_removed函数,同样也会调用plug和unplug函数,用ip link 和ip addr的命令进行最终处理
3.为外部网关做SNAT
调用_handle_router_snat_rules函数,使用iptables来加链和删除链。
在我的测试网络中,router上有3个接口,外部网关地址为192.168.39.2,内部两个子网的网关为10.1.0.1,10.2.0.1。iptables规则如下:
1
2
3
|
iptables -t nat -A POSTROUTING ! -i qg-fcb1a762-1f ! -o qg-fcb1a762-1f -m conntrack ! --ctstate DNAT -j ACCEPT
iptables -t nat -A snat -s 10.2.0.1
/24
-j SNAT --to-
source
192.168.39.2
iptables -t nat -A snat -s 10.1.0.1
/24
-j SNAT --to-
source
192.168.39.2
|
qg-fcb1a762-1f为外部网关接口的索引,使用ip netns exec $namespace ip link list可查看。
4.为floating ip做SNAT/DNAT
和浮动IP相关,如创建,更新,删除,绑定到一个云主机的接口,解绑定等。
不同neutron版本这部分的处理不同,这里是基于Icehouse rc1版本的,在havava stable版本,只有一个函数来处理iptables规则和floating ip。
process_router_floating_ip_nat_rules :
当floating ip与云主机绑定时,会先清除已有的floating_ip规则,再加上要添加的iptables规则,同时重新加载清除的iptables规则。
比如,一个云主机10.1.0.2上绑定了一个floating ip(192.168.39.5)。那么最终会在iptable不同的链中添加iptables规则,float-snat为neutron自定义链。
1
2
3
|
iptables -t nat -A PREROUTING -d 192.168.39.5 -j DNAT --to 10.1.0.2
iptables -t nat -A OUTPUT -d 192.168.39.5 -j DNAT --to 10.1.0.2
iptables -t nat -A float-snat -s 10.1.0.2 -j SNAT --to 192.168.39.5
|
process_router_floating_ip_addresses:
将floating ip和云主机绑定时,使用ip addr add命令添加ip地址。
解除floating ip和云主机绑定时,使用ip addr del命令将floating ip删除。