neutron/agent/linux/ip_lib.py
实现了 linux 上 ip 命令的操作。
- 这个模块主要构造了两种类:
- 以
IpCommandBase
为基类的子类。这些类用来构造具体的想要执行的命令(比如ip netns
),但是不执行。 - 以
SubProcessBase
为基类的子类。这些类用来将上面构造的具体的命令以一个单独的子进程来启动。 IpCommandBase
的子类有三种:IPWrapper
IPDevice
IPRule
IPRoute
IPWrapper
负责IpNetnsCommand
的执行IPDevice
负责IpLinkCommand
IpAddrCommand
IpRouteCommand
IpNeighCommand
的执行IPRule
负责IpRuleCommand
的执行IPRoute
负责IpRouteCommand
的执行
def __init__(self, namespace=None,
log_fail_as_error=True):
self.namespace = namespace
self.log_fail_as_error = log_fail_as_error
try:
self.force_root = cfg.CONF.ip_lib_force_root
except cfg.NoSuchOptError:
# Only callers that need to force use of the root helper
# need to register the option.
self.force_root = False
我找了找配置文件,还真没有 ip_lib_force_root
这个选项
def _as_root(self, options, command, args, use_root_namespace=False):
namespace = self.namespace if not use_root_namespace else None
return self._execute(options, command, args, run_as_root=True,
namespace=namespace,
log_fail_as_error=self.log_fail_as_error)
确定要在那个命名空间下以 root 的身份执行子进程。(root 既是 None,无需命名空间)
def _execute(cls, options, command, args, run_as_root=False, namespace=None, log_fail_as_error=True)
类方法
@classmethod
def _execute(cls, options, command, args, run_as_root=False,
namespace=None, log_fail_as_error=True):
opt_list = ['-%s' % o for o in options]
ip_cmd = add_namespace_to_cmd(['ip'], namespace)
cmd = ip_cmd + opt_list + [command] + list(args)
return utils.execute(cmd, run_as_root=run_as_root,
log_fail_as_error=log_fail_as_error)
- 参数说明:
options
:命令执行的选项command
:要执行的命令args
:命令执行的参数(选项时可选的,参数是必须的)run_as_root
:是否以 root 权限执行namespace
:命令执行的命名空间log_fail_as_error
:命令执行出错时,是否记录到 Log 中
调用 utils.execute
完成命令的执行
def _run(self, options, command, args):
if self.namespace:
return self._as_root(options, command, args)
elif self.force_root:
# Force use of the root helper to ensure that commands
# will execute in dom0 when running under XenServer/XCP.
return self._execute(options, command, args, run_as_root=True,
log_fail_as_error=self.log_fail_as_error)
else:
return self._execute(options, command, args,
log_fail_as_error=self.log_fail_as_error)
根据不同的情况,设定 self._execute
的执行参数
def set_log_fail_as_error(self, fail_with_error):
self.log_fail_as_error = fail_with_error
def get_log_fail_as_error(self):
return self.log_fail_as_error
def __init__(self, namespace=None):
super(IPWrapper, self).__init__(namespace=namespace)
self.netns = IpNetnsCommand(self)
获取该 namespace 下的网络设备名称,并以 IPDevice
包装后返回。
exclude_loopback
:是否排除本地回环设备(lo
)exclude_gre_devices
:是否排除 gre 设备(gre0
gretap0
)
相当于执行如下命令:ip netns exec net0 find /sys/class/net -maxdepth 1 -type l -printf '%f '
将命令执行的结果以 IPDevice
包装后返回。
用 IPDevice
表示一个在该 namespace 下的一个设备
根据 ip 地址获取网络设备。
调用 IpAddrCommand.get_devices_with_ip
实现,但是只会取第一个设备(不同的网卡能拥有完全相同的 ip 地址)。
返回以 IPDevice
包装的网络设备
确保名称为 name 的 namespace 存在。若是不存在,则新建一个,并启动其中的回环设备。
增加一堆 veth 设备,名称为 name1 的在本 namespace 下,名称为 name2 的在 namespace2 下面
执行如下命令:ip netns exec net0 ip link add tap0 type veth peer name tap1 netns net1
返回以 IPDevice
描述的两格 veth 设备。
增加一个名称为 name 的 tun/tap 设备
执行如下命令:ip netns exec net0 ip tuntap add tap-simple mode tap
返回以 IPDevice
描述的 tun/tap 设备
- 参考
Linux下Tun/Tap设备通信原理 虚拟网卡 TUN/TAP 驱动程序设计原理 关于 TCP 并发连接的几个思考题与试验
增加一个名称为 name 的 macvtap 设备,默认的模式为 bridge
执行如下命令:ip link add link eth0 name macv1 type macvtap mode bridge
返回以 IPDevice
描述的 macvtap 设备
- 参考
图解几个与Linux网络虚拟化相关的虚拟网卡-VETH/MACVLAN/MACVTAP/IPVLAN Linux 上虚拟网络与真实网络的映射
删除名为 name 的 veth 设备
执行如下命令:ip link delete tap0
添加一个名称为 name 的 dummy 类型的网卡(哑网卡只会简单的直接丢弃所有发送给它的数据包)。
执行如下命令:ip link add dum type dummy
返回以 IPDevice
描述的 dummy 设备
判断是否当前的 namespace 除了回环设备和 gre 设备外,再无其它设备
如果当前的 namespace 是空的话,则删除此命名空间。
将一个以 IPDevice
描述的网络设备增加到当前的命名空间中。
增加一个 vlan 设备。
执行如下命令:ip link add link eth0 name v1 type vlan id 34
返回以 IPDevice
描述的 vlan 设备
def add_vxlan(self, name, vni, group=None, dev=None, ttl=None, tos=None, local=None, port=None, proxy=False)
增加一个 vxlan 设备。
- 参数说明:
name
:网卡名称vni
:vxlan idgroup
:多播地址dev
:物理网卡的名称ttl
:生存期限tos
:服务类型(请参考:IP首部中的服务类型(TOS))local
:设定发出包的 VETP(Vxlan Tunnel End Point) 源 ip 地址 Vxlan基础理解port
:用于声明本地监听 UDP 端口的范围。(在新版的 ip-link 的命令中,这个发生了变化:1. 用dstport
来声明对端的 UDP 端口;2. 使用srcport
来声明本端监听的 UDP 端口的范围)proxy
:arp proxy 功能(请参考:配置 L2 Population - 每天5分钟玩转 OpenStack(114))
执行如下命令:ip link add vxlan0 type vxlan id 42 group 239.1.1.1 dev eth0 port 4750 4790 proxy
获取所有的 namespace。执行命令:ip netns list
COMMAND = 'netns'
use_helper_for_ns_read
:指明读取 ip namespace 时是否使用 root 权限(默认为 True)。
执行 ip -o netns list
命令,然后查询名字为 name 的 namespace 是否存在
删除名字为 name 的 namespace。执行命令:ip netns delete net0
增加一个名称为 name 的 namespace。
执行命令:
ip netns add net0
ip netns exec net0 sysctl -w net.ipv4.conf.all.promote_secondaries=1
关于 sysctl 调整内核参数,请参考:内核参数说明
返回一个以 IPWrapper
包装的 namespace
def execute(self, cmds, addl_env=None, check_exit_code=True, log_fail_as_error=True, extra_ok_codes=None, run_as_root=False)
参数同 utils.execute
方法里面的参数。
在当前的命名空间下执行 cmd 命令。
def __init__(self, name, namespace=None):
super(IPDevice, self).__init__(namespace=namespace)
self._name = name
self.link = IpLinkCommand(self)
self.addr = IpAddrCommand(self)
self.route = IpRouteCommand(self)
self.neigh = IpNeighCommand(self)
def __eq__(self, other):
return (other is not None and self.name == other.name
and self.namespace == other.namespace)
def __str__(self):
return self.name
COMMAND = ''
ip 命令的格式如下:
ip [ OPTIONS ] OBJECT { COMMAND | help }
这里的 COMMAND 就相当不 OBJECT,代表着你要操作的对象。
def __init__(self, parent):
self._parent = parent
def _run(self, options, args):
return self._parent._run(options, self.COMMAND, args)
def _as_root(self, options, args, use_root_namespace=False):
return self._parent._as_root(options,
self.COMMAND,
args,
use_root_namespace=use_root_namespace)
class IpDeviceCommandBase(IpCommandBase):
@property
def name(self):
return self._parent.name
COMMAND = 'link
link 既是指网络设备的相关操作
def delete(self):
self._as_root([], ('delete', self.name))
执行删除网络设备的命令。例如:ip netns qdhcp-ea3928dc-b1fd-4a1a-940e-82b8c55214e6 ip link delete tap13685e28-b0
def set_address(self, mac_address):
self._as_root([], ('set', self.name, 'address', mac_address))
执行设置网络设备网络(mac)地址的命令。例如:ip netns qdhcp-ea3928dc-b1fd-4a1a-940e-82b8c55214e6 ip link set tap13685e28-b0 00:01:4f:00:15:f1
执行设置网络设备是否支持多播模式的命令。例如:ip netns qdhcp-ea3928dc-b1fd-4a1a-940e-82b8c55214e6 ip link set tap13685e28-b0 allmulticast on
设置此参数后,网卡将接收网络中所有的多播数据包
设置网卡 mtu 的大小。例如:ip link set tap13685e28-b0 mtu 1400
启动该网卡 ip link set tap13685e28-b0 up
关闭该网卡 ip link set tap13685e28-b0 down
设置该网卡所在的命名空间。 ip link set tap13685e28-b0 netns qdhcp-ea3928dc-b1fd-4a1a-940e-82b8c55214e6
设置网卡的名称。ip link set tap13685e28-b0 name tap-simple
该命令只有在网卡关闭的情况下才能执行成功
为网卡设置别名。ip link set tap13685e28-b0 alias tap-simple
属性方法。相当于执行:ip netns exec net0 ip -o link show tap13685e28-b0
-o 选项,意味着命令的输出仅为一行(换行符用 \ 替代)。
调用 _parse_line
方法解析 Ip 命令的输出,获取易读的网卡的状态:
- 解析前:
ip netns exec net0 ip -o link show veth0
3: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1400 qdisc noqueue state DOWN mode DEFAULT qlen 1000\ link/ether 00:01:4f:00:15:f2 brd ff:ff:ff:ff:ff:ff\ alias veth0
- 解析后:
{'qlen': 1000, 'qdisc': 'noqueue', 'link/ether': '00:01:4f:00:15:f2', 'mtu': 1400, 'alias': 'veth0', 'state': 'DOWN', 'mode': 'DEFAULT', 'brd': 'ff:ff:ff:ff:ff:ff'}
属性方法,获取网卡的网络(mac)地址
属性方法,获取网卡的状态
属性方法,获取网卡的 mtu
属性方法,获取网卡的流量的排队准则。
参考:
LIUNX下tc流量控制命令详解 Linux 网络堆栈的排队机制 从veth看虚拟网络设备的qdisc
属性方法,获取网卡 qlen(网络接口传输队列的默认长度)数据。
获取网卡别名
COMMAND = 'addr'
为网卡增加 ip 地址。
- 参数说明:
cidr
:ip 地址(192.168.100.10/24)scope
:ip 地址的有效范围(参考 ip-address 命令手册 man ip-address)add_broadcast
:是否增加广播地址
相当于执行如下命令:ip -4 addr add 192.168.100.10/24 scope global dev veth0 brd 192.168.100.255
删除网卡上的 ip 地址。ip -4 addr del 192.168.100.10/24 dev veth0
清除网卡上的 ip 地址。ip -4 addr flush dev veth0
返回网卡的参数(不同的网卡可能拥有完全相同的 ip 地址)。
- 参数说明:
name
:网卡名称scope
:地址范围to
:地址的匹配项filters
:其他过滤选项ip-version
:ip 版本
ip -4 addr show veth0 scope global to 192.168.100.10
结果为:
3: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1400 qdisc noqueue state DOWN qlen 1000
inet 192.168.100.11/24 brd 192.168.100.255 scope global veth0
valid_lft forever preferred_lft forever
则该方法的返回值为:{'name':'veth0','cidr':'192.168.100.11/24', 'scope':'global', 'dynamic':False, 'tentative':False, 'dadfailed':False}
调用 get_devices_with_ip
返回本网卡的参数
在 wait_time 的时间内,一直等待 ip 地址 address 在本网卡上准备好。若是没有准备好,则引发异常
COMMAND = 'neigh'
构建相邻表(ARP 表)
增加(替换)一个相邻表项。
执行命令:ip -4 neigh replace 10.0.0.3 lladdr fe:ee:ff:ff:ff:ff nud permanent dev eth0
其中,permanent
指邻接条目永远有效并且只能由管理员删除。
删除一个相邻表项
执行命令:ip -4 del 10.0.0.3 lladdr fe:ee:ff:ff:ff:ff dev eth0
显示一个网络设备上的相邻表。
执行命令:ip -4 show dev eth0
清除当前设备的相邻表项。
执行命令:ip -4 flush to 10.0.0.3
class IPRoute(SubProcessBase):
def __init__(self, namespace=None, table=None):
super(IPRoute, self).__init__(namespace=namespace)
self.name = None
self.route = IpRouteCommand(self, table=table)
系统中的路由记录是在一个一个的路由表中存在的(路由表在 /etc/iproute2/rt_tables 文件中记录)。最多可以定义 256 个路由表,启动 0、253、254、255 是由 kernel 维护的。
- linux 系统中,可以自定义从 1-252个路由表,其中,linux系统维护了4个路由表:
- 0#表: 系统保留表
- 253#表: defulte table 没特别指定的默认路由都放在该表
- 254#表: main table 没指明路由表的所有路由放在该表
- 255#表: locale table 保存本地接口地址,广播地址、NAT地址 由系统维护,用户不得更改
IpRouteCommand
也是以路由表为单位来进行维护的。在没有指明路由表的情况下,默认是对 main 表进行操作的。
COMMAND = 'route'
def __init__(self, parent, table=None):
super(IpRouteCommand, self).__init__(parent)
self._table = table
返回一个 IpRouteCommand
描述的路由表。
构造 ip route
命令的 table
选项。
def _table_args(self, override=None):
if override:
return ['table', override]
return ['table', self._table] if self._table else []
例如:ip route show table local
就是查询 local 表中记录的路由项。
构造 ip route
命令的 dev
选项。
def _dev_args(self):
return ['dev', self.name] if self.name else []
例如:ip route show dev eth0
就是查询经过 eth0 网卡的路由。
处理运行命令时,无法找到 device 的异常
filters
:之其他的过滤选项
获取本表中,经过本设备转发的网关地址及其 metric
属性。
执行命令如下:ip -4 route show table main dev eno1 scope globle
命令返回为: default via 192.168.40.254 proto static metric 100
方法返回为:{'gateway':'192.168.40.254','metric':100}
生成器方法,将 ip route 输出的结果转化为易读的格式。
查询路由。
执行命令:ip -4 router list table main dev eno1
调用 _parse_routes
对命令结果的数据进行转化。
列出 scope 为 link 的路由。
执行命令:ip -4 router list table main dev eno1 scope link
不过这里面的不带 src
是什么鬼? list_onlink_routes
与 add_onlink_routes
方法是对应的,因为 add_onlink_routes
添加路由表项时,也没有添加 src 属性。
在路由表中增加一个记录。
执行命令如下:ip -4 route replace 192.168.40.237 via 192.168.40.254 dev eno1 table main src 192.168.40.247 scope global
增加一个 scope 为 link 的路由表
执行命令如下:ip -4 route replace 192.168.40.237 dev eno1 table main scope link
删除一个路由表项
执行命令如下:ip -4 route del 192.168.40.237 dev eno1 table main
删除一个 scope 为 link 的路由表项
执行命令如下:ip -4 route del 192.168.40.237 dev eno1 table main scope link
增加一个网关记录。
执行命令如下:ip -4 route default via 192.168.40.254 dev eno1 table main matric 100
删除默认网关的记录。
执行命令如下:ip -4 route del default via 192.168.40.254 dev eno1 table main
清空某一个路由表的记录。
执行命令如下:ip -4 flush table main
class IPRule(SubProcessBase):
def __init__(self, namespace=None):
super(IPRule, self).__init__(namespace=namespace)
self.rule = IpRuleCommand(self)
COMMAND = 'rule'
例如当前策略路由的所有规则。
执行命令:ip -4 rule show
命令返回值为:
220: from 192.203.80.0/24 fwmark 0x1 lookup main
调用 _parse_line
来解析命令的返回值。
解析完成后:
{'fwmark': '0x1/0xffffffff', 'table': 'main', 'priority': '220', 'type': 'unicast', 'from': '192.203.80.0/24'}
kwargs:包含某个 rule 的属性。判断该 rule 是否存在。
增加一个 rule 记录。
执行命令如下:ip ru add from 193.233.7.83 nat 192.203.80.144 table 1 pref 320
删除一个 rule 记录
执行命令如下:ip ru del prio 32767
def add_namespace_to_cmd(cmd, namespace=None):
"""Add an optional namespace to the command."""
return ['ip', 'netns', 'exec', namespace] + cmd if namespace else cmd
当 namespace 不为空时,构造类似于下面的命令:ip netns exec namespce cmd
查询在名称为 namespace 的命名空间内是够已经存在了 vlan id 为 segmentation_id 的接口。
查询在名称为 namespace 的命名空间内是够已经存在了 vxlan id 为 segmentation_id 的接口。
查询在名称为 namespace 的命名空间内是够已经存在了名称为 device_name 的接口。
查询名称为 device_name
、ip 地址为 ip_cidrs
、网卡地址为 mac
的网络设备是否存在。
执行命令:ip netns exec qdhcp-5898859a-6a58-4aa1-baf8-743414f3c99c ip -4 route
获取命名空间 namespace 下的 main 路由表项
确认名为 device_name 的设备已经启动
执行命令 ip command help
命令,确认该 command 命令是否支持后面的参数。
根据 mac 地址获取该网卡的 ipv6 的 link-local address 。参考:华为和思科IPV6初探,IPV6中的link-local address
arping:Ping destination on device interface by ARP packets, using source address source.
告诉 address 发送 ARP 回复命令。
send_arp_for_ha
在 l3_agent.ini 配置:send_arp_for_ha = 3
发送 arp 数据包的个数
执行如下命令:arping -A -I eth0 -c 3 -w 4.5 172.16.100.168
-A 以 ARP 相应包的形式发送 -I 指定发送端口 -c 发送书包的个数 -w 超时时间
什么作用?
使用命令:sysctl -bn net.ipv4.ip_nonlocal_bind
读取 net.ipv4.ip_nonlocal_bind
内核参数,是否允许进程绑定到非本地地址
使用命令:sysctl -w net.ipv4.ip_nonlocal_bind value
试用命令:sysctl -w net.ipv4.ip_nonlocal_bind 0