|
| 1 | +#!/usr/bin/python |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | + |
| 4 | +import sys, os, stat |
| 5 | +import yaml |
| 6 | +import time |
| 7 | +import socket |
| 8 | +import optparse |
| 9 | +import re |
| 10 | + |
| 11 | +configfile = '/etc/netgwm/netgwm.yml' |
| 12 | +gwstorefile = '/var/run/netgwm/gwstore.yml' |
| 13 | +modefile = '/var/lib/netgwm/mode' |
| 14 | + |
| 15 | +def main(): |
| 16 | + parser = optparse.OptionParser(add_help_option = False) |
| 17 | + parser.add_option('-h', '--help', action = 'help') |
| 18 | + parser.add_option('-c', '--config', default = configfile) |
| 19 | + options, args = parser.parse_args() |
| 20 | + |
| 21 | + config = yaml.load(open(options.config, 'r')) |
| 22 | + if not os.path.exists('/var/run/netgwm/'): os.mkdir('/var/run/netgwm/') |
| 23 | + |
| 24 | + try: gwstore = yaml.load(open(gwstorefile, 'r')) |
| 25 | + except: gwstore = {} |
| 26 | + |
| 27 | + gateways = [] |
| 28 | + if 'gateways' in config and not config['gateways'] is None: |
| 29 | + for gw_identifier, gw_data in config['gateways'].iteritems(): |
| 30 | + gateways.append(GatewayManager(gwstore, identifier=gw_identifier, **gw_data)) |
| 31 | + |
| 32 | + currentgw = GatewayManager.get_current_gateway(gateways) |
| 33 | + |
| 34 | + try: |
| 35 | + if 'mode' in config: mode = config['mode'] |
| 36 | + else: mode = open(modefile, 'r').read().strip() |
| 37 | + if mode not in config['gateways']: raise Exception() |
| 38 | + except: mode = 'auto' |
| 39 | + |
| 40 | + if mode == 'auto': |
| 41 | + if currentgw is not None and currentgw.check(config['check_sites']): |
| 42 | + # если доступен интернет |
| 43 | + # ищем доступный роутер с приоритетом выше, чем у текущего |
| 44 | + candidates = [x for x in gateways if x.priority < currentgw.priority] |
| 45 | + for gw in sorted(candidates, key = lambda x: x.priority): |
| 46 | + if gw.check(config['check_sites']) and gw.wakeuptime < (time.time() - config['min_uptime']): |
| 47 | + # роутер работает и работает без сбоев достаточно долго |
| 48 | + gw.setdefault() |
| 49 | + post_replace_trigger(newgw=gw, oldgw=currentgw) |
| 50 | + break |
| 51 | + else: continue |
| 52 | + else: |
| 53 | + # Срочно переключаемся на самый приоритетный доступный шлюз |
| 54 | + for gw in sorted(gateways, key = lambda x: x.priority): |
| 55 | + if gw == currentgw: continue # и так понятно, что текущий роутер не работает |
| 56 | + if gw.check(config['check_sites']): |
| 57 | + gw.setdefault() |
| 58 | + post_replace_trigger(newgw=gw, oldgw=currentgw) |
| 59 | + break |
| 60 | + else: continue |
| 61 | + # ни один роутер не работает |
| 62 | + else: |
| 63 | + fixedgw = [x for x in gateways if x.identifier == mode].pop() |
| 64 | + if currentgw is None or currentgw != fixedgw: |
| 65 | + fixedgw.setdefault() |
| 66 | + post_replace_trigger(newgw=fixedgw, oldgw=currentgw) |
| 67 | + |
| 68 | + if 'check_all_gateways' in config and config['check_all_gateways'] is True: |
| 69 | + for gw in [x for x in gateways if not x.is_checked]: gw.check(config['check_sites']) |
| 70 | + |
| 71 | + GatewayManager.store_gateways(gateways) |
| 72 | + |
| 73 | + |
| 74 | +def post_replace_trigger(newgw, oldgw): |
| 75 | + # post-replace.d |
| 76 | + args = [] |
| 77 | + args.append(newgw.identifier) |
| 78 | + args.append(newgw.ip if hasattr(newgw, 'ip') else 'NaN') |
| 79 | + args.append(newgw.dev if hasattr(newgw, 'dev') else 'NaN') |
| 80 | + args.append(oldgw.identifier if not oldgw is None else 'Nan') |
| 81 | + args.append(oldgw.ip if not oldgw is None and hasattr(oldgw, 'ip') else 'NaN') |
| 82 | + args.append(oldgw.dev if not oldgw is None and hasattr(oldgw, 'dev') else 'NaN') |
| 83 | + for filename in sorted(os.listdir('/etc/netgwm/post-replace.d/')): |
| 84 | + execpath = '/etc/netgwm/post-replace.d/'+filename |
| 85 | + if os.path.isfile(execpath) and (os.stat(execpath).st_mode & stat.S_IXUSR): |
| 86 | + os.system(execpath+' '+' '.join(args)) |
| 87 | + |
| 88 | + |
| 89 | +class GatewayManager: |
| 90 | + def __init__(self, gwstore, **kwargs): |
| 91 | + self.priority = kwargs['priority'] |
| 92 | + self.identifier = kwargs['identifier'] |
| 93 | + self.is_checked = False |
| 94 | + if 'ip' in kwargs and kwargs['ip'] is not None: self.ip = kwargs['ip'] |
| 95 | + if 'dev' in kwargs and kwargs['dev'] is not None: self.dev = kwargs['dev'] |
| 96 | + |
| 97 | + if self.identifier in gwstore: self.wakeuptime = gwstore[self.identifier]['wakeuptime'] |
| 98 | + else: self.wakeuptime = 0 # считаем, что при первом появлении роутера в системе, его аптайм -- много лет. |
| 99 | + |
| 100 | + def __eq__(self, other): |
| 101 | + if other is None: return False |
| 102 | + else: return self.identifier == other.identifier |
| 103 | + |
| 104 | + def check(self, check_sites): |
| 105 | + # check gw status |
| 106 | + print 'checking ' + self.identifier |
| 107 | + ipresult = not os.system('/sbin/ip route replace default %s table netgwm_check' % self.generate_route()) |
| 108 | + |
| 109 | + if ipresult is True: |
| 110 | + for site in check_sites: |
| 111 | + site_ip = socket.gethostbyname(site) |
| 112 | + |
| 113 | + os.system('/sbin/ip rule add iif lo to %s lookup netgwm_check' % site_ip) |
| 114 | + |
| 115 | + p = os.popen('ping -q -n -W 1 -c 2 %s 2> /dev/null' % site_ip) |
| 116 | + pingout = p.read() |
| 117 | + status = not p.close() |
| 118 | + |
| 119 | + os.system('/sbin/ip rule del iif lo to %s lookup netgwm_check' % site_ip) |
| 120 | + |
| 121 | + if status is True: |
| 122 | + # ping success |
| 123 | + rtt = re.search('\d+\.\d+/(\d+\.\d+)/\d+\.\d+/\d+\.\d+', pingout).group(1) |
| 124 | + info = 'up:'+site+':'+rtt |
| 125 | + break |
| 126 | + else: |
| 127 | + # ping fail |
| 128 | + info = 'down' |
| 129 | + os.system('/sbin/ip route del default %s table netgwm_check' % self.generate_route()) |
| 130 | + else: |
| 131 | + status = False |
| 132 | + info = 'down' |
| 133 | + |
| 134 | + try: |
| 135 | + with open('/var/run/netgwm/'+self.identifier, 'w') as f: f.write(info) |
| 136 | + except: pass |
| 137 | + |
| 138 | + if self.wakeuptime is None and status is True: self.wakeuptime = time.time() # Если не установлено время подъема и сервак пинганулся -- устанавливае |
| 139 | + elif status is False: self.wakeuptime = None # Если не пинганулся -- затираем |
| 140 | + |
| 141 | + self.is_checked = True |
| 142 | + |
| 143 | + return status |
| 144 | + |
| 145 | + def setdefault(self): |
| 146 | + # replace |
| 147 | + print '/sbin/ip route replace default ' + self.generate_route() |
| 148 | + os.system('/sbin/ip route replace default ' + self.generate_route()) |
| 149 | + |
| 150 | + def generate_route(self): |
| 151 | + res = [] |
| 152 | + if hasattr(self, 'ip'): res.append('via ' + self.ip) |
| 153 | + if hasattr(self, 'dev'): res.append('dev ' + self.dev) |
| 154 | + return ' '.join(res) |
| 155 | + |
| 156 | + @staticmethod |
| 157 | + def get_current_gateway(gateways): |
| 158 | + currentgw_ip = os.popen("/sbin/ip route | grep 'default via' | sed -r 's/default via (([0-9]+\.){3}[0-9]+) dev .+/\\1/g'").read().strip() |
| 159 | + currentgw_dev = os.popen("/sbin/ip route | grep 'default dev' | sed -r 's/default dev ([a-z0-9]+)(\s+.*)?/\\1/g'").read().strip() |
| 160 | + |
| 161 | + if currentgw_ip == '' and currentgw_dev == '': |
| 162 | + return None |
| 163 | + elif currentgw_ip != '': |
| 164 | + for g in [x for x in gateways if hasattr(x, 'ip')]: |
| 165 | + if g.ip == currentgw_ip: return g |
| 166 | + elif currentgw_dev != '': |
| 167 | + for g in [x for x in gateways if hasattr(x, 'dev')]: |
| 168 | + if g.dev == currentgw_dev: return g |
| 169 | + else: raise Exception('current gw is not listed in config.') |
| 170 | + |
| 171 | + @staticmethod |
| 172 | + def store_gateways(gateways): |
| 173 | + gwstore = {} |
| 174 | + for gw in gateways: gwstore[gw.identifier] = {'wakeuptime': gw.wakeuptime} |
| 175 | + open(gwstorefile, 'w').write(yaml.dump(gwstore)) |
| 176 | + |
| 177 | + |
| 178 | +if __name__ == '__main__': |
| 179 | + main() |
| 180 | + |
0 commit comments