需求
将多个网卡配置为 bond(模式mode 4 802.3ad,对接 lacp),并配置 bond子接口,为每个子接口都开启策略路由,实现源进源出的效果(ip rule + rp_filter = 0)。
适用于 CentOS 7 系列,系统自带 python2.7
多网卡都配置网关可能会造成断网,根据自己实际情况修改
使用方法:
python2 test.py -h
usage: 1.py [-h] [-b bond] [-d [eth0 [eth0 ...]]] [-s [SUBIF [SUBIF ...]]]
optional arguments:
-h, --help show this help message and exit
-b bond, --bond bond Input bond interface.
-d [eth0 [eth0 ...]], --device [eth0 [eth0 ...]]
EXAMPLE: eth0 eth1.
-s [SUBIF [SUBIF ...]], --subif [SUBIF [SUBIF ...]]
EXAMPLE: bond0.100 192.168.1.100 24 192.168.1.1
创建一个bond
python2 test.py -b <bond名称> -d <物理接口>
必须指定物理接口,且接口存在(如果出错脚本会检测列出当前网卡)
例如,将 em1 和 em2 配置为 bond2
python2 test.py -b bond2 -d em1 em2
# 若接口不存在,报以下错误
python2 test.py -b bond2 -d eth00
usage: test.py [-h] [-b bond] [-d [eth0 [eth0 ...]]] [-s [SUBIF [SUBIF ...]]]
test.py: error: argument -d/--device: invalid choice: 'eth000' (choose from 'bond0.1401@bond0', 'bond0', 'bond2', 'bond0.1401', 'bond0.1403', 'p6p2', 'em4', 'bond2.100', 'p6p1', 'em1', 'em3', 'em2', 'bond0.1403@bond0')
创建一个子接口并配置IP
默认掩码为 24 ,默认网关为 IP 地址 .1 ,bond 接口必须存在
例如,为 bond2 配置 bond2.100 子接口和 bond2.300 子接口
# 默认掩码是 24,网关是 .1
python2 test.py -s bond2.100 2.2.2.2
# 可以指定掩码和网关
python2 test.py -s bond2.200 3.3.3.3 23 3.3.3.25
脚本没有对掩码和网关进行检测,例如你配置 1.1.1.100/24 的 IP,指定 2.2.2.1 的网关,也是可以配置成功的。
配置完成后,需要重启网络,并使策略路由立即生效,可以
python2 test.py restart
代码,可自行修改:
#!/usr/bin/env python
import os
import subprocess
import argparse
import re
INTERFACE = r'/etc/sysconfig/network-scripts/ifcfg-'
ip_pattern = re.compile(r'^((25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$')
DEV_CONF = '''
TYPE=Ethernet
BOOTPROTO=none
USERCTL=no
DEVICE={dev}
ONBOOT=yes
MASTER={bond_name}
SLAVE=yes
'''
BOND_CONF = '''
DEVICE={bond_name}
TYPE=Bond
BONDING_MASTER=yes
BOOTPROTO=none
ONBOOT=yes
USERCTL=no
NM_CONTROLLED=no
'''
SUB_IF_CONF = '''
DEVICE={dev}
USERCTL=no
BOOTPROTO=static
IPADDR={ip}
PREFIX={mask}
GATEWAY={gw}
BONDING_MASTER=yes
NM_CONTROLLED=no
ONBOOT=yes
VLAN=yes
'''
BONDING_CONF = '''
alias {bond_name} bonding
options {bond_name} miimon=100 mode={mode}
'''
IP_RULE_CONF = '''
ip route flush table {table_name}
ip route add default via {gw} dev {dev} src {ip} table {table_name}
ip rule add from {ip} table {table_name}
'''
def _separator(s=''):
return s.center(60, '=')
def _cmd(cmd):
return os.popen(cmd).read()
def _popen(cmd):
popen = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
popen.wait()
return popen.returncode, popen.stdout.read(), popen.stderr.read()
def _init_bond(bond_name, mode='4'):
with open(r'/etc/modprobe.d/bonding.conf', 'a+') as f:
f.write(BONDING_CONF.format(bond_name=bond_name, mode=mode))
_popen(r'modprobe --first-time bonding')
_popen(r'modprobe --first-time 8021q')
def _get_interface_list():
cmd1 = 'ip link | grep ^[0-9] | awk -F": " \'{print $2}\' | grep -v "lo"'
cmd2 = 'ls /etc/sysconfig/network-scripts/ | grep -E "ifcfg" | awk -F"-" \'{print $2}\' | grep -v "lo"'
dev1 = _cmd(cmd1).split()
dev2 = _cmd(cmd2).split()
return list(set(dev1 + dev2))
def _config_phy_if(bond_name, dev_names):
for dev in dev_names:
with open(INTERFACE + dev, 'w') as f:
f.write(DEV_CONF.format(dev=dev, bond_name=bond_name))
with open(INTERFACE + bond_name, 'w') as f:
f.write(BOND_CONF.format(bond_name=bond_name))
def _config_bond_subif(dev, ip, mask, gw):
with open(INTERFACE + dev, 'w') as f:
f.write(SUB_IF_CONF.format(dev=dev, ip=ip, mask=mask, gw=gw))
def _disable_rp_filter(dev):
cmd = r'echo net.ipv4.conf.{dev}.rp_filter=0 >> /etc/sysctl.conf'.format(dev=dev)
code, _, err = _popen(cmd)
if code != 0:
print 'Disable {dev} rp_filter Failed.'.format(dev=dev), '\n', err
def _config_ip_rule(dev, ip, gw):
table_name = 'cloud-' + dev.split('.')[-1]
num_list = []
with open('/etc/iproute2/rt_tables', 'a+') as f:
for line in f.readlines():
if not (line.startswith("#") or line.startswith('0')):
num_list.append(line.strip().split()[0])
num = int(min(num_list)) - 1
f.write('{num} {table_name}\n'.format(num=num, table_name=table_name, ))
with open(r'/etc/rc.local', 'a+') as f:
f.write(IP_RULE_CONF.format(
ip=ip, gw=gw, dev=dev, table_name=table_name))
def bond(bond_name, dev_names):
_config_phy_if(bond_name, dev_names)
_init_bond(bond_name)
for dev in dev_names:
_disable_rp_filter(dev)
_disable_rp_filter(bond_name)
print bond_name + ' config done'
def sub_if(dev, ip, mask='24', gw=''):
if not gw:
gw = '.'.join(ip.split('.')[0:3]) + '.1'
if not re.findall(ip_pattern, ip) or not re.findall(ip_pattern, gw):
print 'ERROR: IP or GW wrong'
if not 2 < int(mask) < 30:
print 'ERROR: MASK wrong'
_config_bond_subif(dev=dev, ip=ip, mask=mask, gw=gw)
_disable_rp_filter(dev=dev)
_config_ip_rule(dev=dev, ip=ip, gw=gw)
print dev + ' config done.'
parser = argparse.ArgumentParser()
parser.add_argument('-b', '--bond', metavar='bond', help='Input bond interface.')
parser.add_argument('-d', '--device', nargs='*', metavar='eth0', choices=_get_interface_list(),
help='EXAMPLE: eth0 eth1.')
parser.add_argument('-s', '--subif', nargs='*', help='EXAMPLE: bond0.100 192.168.1.100 24 192.168.1.1')
parser.add_argument('-r', '--restart', action='store_true', help="Restart Network")
parsed_args = parser.parse_args()
if __name__ == '__main__':
if parsed_args.bond or parsed_args.device:
if parsed_args.bond and parsed_args.device:
bond(parsed_args.bond, parsed_args.device)
# print parsed_args.bond, parsed_args.device
else:
print 'ERROR: Argument Error, both "-b bond" and "-d phy-if" is required.'
if parsed_args.subif:
subif_lis = parsed_args.subif[0].split('.')
if subif_lis[0] not in _get_interface_list():
print 'ERROR: No bond interface. Use "-b bond" and "-d phy-if" to create'
elif len(subif_lis) != 2:
print 'ERROR: Sub-if name wrong,it must be <bond if>.<vlan id> Example: bond1.1024'
else:
sub_if(*parsed_args.subif)
# print parsed_args.subif
if parsed_args.restart:
_popen(r'echo net.ipv4.conf.all.rp_filter=0 >> /etc/sysctl.conf')
_popen(r'echo net.ipv4.conf.default.rp_filter=0 >> /etc/sysctl.conf')
_popen(r'sysctl -p')
_popen(r'systemctl restart network')
_popen(r'chmod +x /etc/rc.local && source /etc/rc.local')