Completed
Push — master ( 1f22e8...7707d9 )
by Thomas
10:32
created

exabgp.application.healthcheck.setup_ips()   C

Complexity

Conditions 9

Size

Total Lines 21
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 18
nop 3
dl 0
loc 21
rs 6.6666
c 0
b 0
f 0
1
#!/usr/bin/env python
2
3
"""Healthchecker for exabgp.
4
5
This program is to be used as a process for exabgp. It will announce
6
some VIP depending on the state of a check whose a third-party program
7
wrapped by this program.
8
9
To use, declare this program as a process in your
10
:file:`/etc/exabgp/exabgp.conf`::
11
12
    neighbor 192.0.2.1 {
13
       router-id 192.0.2.2;
14
       local-as 64496;
15
       peer-as 64497;
16
    }
17
    process watch-haproxy {
18
       run python -m exabgp healthcheck --cmd "curl -sf http://127.0.0.1/healthcheck" --label haproxy;
19
    }
20
    process watch-mysql {
21
       run python -m exabgp healthcheck --cmd "mysql -u check -e 'SELECT 1'" --label mysql;
22
    }
23
24
Use :option:`--help` to get options accepted by this program. A
25
configuration file is also possible. Such a configuration file looks
26
like this::
27
28
     debug
29
     name = haproxy
30
     interval = 10
31
     fast-interval = 1
32
     command = curl -sf http://127.0.0.1/healthcheck
33
34
The left-part of each line is the corresponding long option.
35
36
When using label for loopback selection, the provided value should
37
match the beginning of the label without the interface prefix. In the
38
example above, this means that you should have addresses on lo
39
labelled ``lo:haproxy1``, ``lo:haproxy2``, etc.
40
41
"""
42
43
from __future__ import print_function
44
from __future__ import unicode_literals
45
46
import sys
47
import os
48
import subprocess
49
import re
50
import logging
51
import logging.handlers
52
import argparse
53
import signal
54
import time
55
import collections
56
57
logger = logging.getLogger("healthcheck")
58
59
try:
60
    # Python 3.3+ or backport
61
    from ipaddress import ip_network as ip_network  # pylint: disable=F0401
62
    from ipaddress import ip_address as ip_address  # pylint: disable=F0401
63
64
    def fix(f):
65
        def fixed(x):
66
            try:
67
                x = x.decode('ascii')
68
            except AttributeError:
69
                pass
70
            return f(x)
71
        return fixed
72
    ip_network = fix(ip_network)
73
    ip_address = fix(ip_address)
74
75
except ImportError:
76
    try:
77
        # Python 2.6, 2.7, 3.2
78
        from ipaddr import IPNetwork as ip_network
79
        from ipaddr import IPAddress as ip_address
80
    except ImportError:
81
        sys.stderr.write(
82
            '\n'
83
            'This program requires the python module ip_address (for python 3.3+) or ipaddr (for python 2.6, 2.7, 3.2)\n'
84
            'Please pip install one of them with one of the following command.\n'
85
            '> pip install ip_address\n'
86
            '> pip install ipaddr\n'
87
            '\n'
88
        )
89
        sys.exit(1)
90
91
92
def enum(*sequential):
93
    """Create a simple enumeration."""
94
    return type(str("Enum"), (), dict(zip(sequential, sequential)))
95
96
97
def parse():
98
    """Parse arguments"""
99
    formatter = argparse.RawDescriptionHelpFormatter
100
    parser = argparse.ArgumentParser(description=sys.modules[__name__].__doc__,
101
                                     formatter_class=formatter)
102
103
    g = parser.add_mutually_exclusive_group()
104
    g.add_argument("--debug", "-d", action="store_true",
105
                   default=False,
106
                   help="enable debugging")
107
    g.add_argument("--no-ack", "-a", action="store_true",
108
                   default=False,
109
                   help="set for exabgp 3.4 or 4.x when exabgp.api.ack=false")
110
    g.add_argument("--silent", "-s", action="store_true",
111
                   default=False,
112
                   help="don't log to console")
113
    g.add_argument("--syslog-facility", "-sF", metavar="FACILITY",
114
                   nargs='?',
115
                   const="daemon",
116
                   default="daemon",
117
                   help=("log to syslog using FACILITY, "
118
                         "default FACILITY is daemon"))
119
    g.add_argument("--sudo", action="store_true", default=False,
120
                   help="use sudo to setup ip addresses")
121
    g.add_argument("--no-syslog", action="store_true",
122
                   help="disable syslog logging")
123
    parser.add_argument("--name", "-n", metavar="NAME",
124
                        help="name for this healthchecker")
125
    parser.add_argument("--config", "-F", metavar="FILE", type=open,
126
                        help="read configuration from a file")
127
    parser.add_argument("--pid", "-p", metavar="FILE",
128
                        type=argparse.FileType('w'),
129
                        help="write PID to the provided file")
130
    parser.add_argument("--user", metavar="USER",
131
                        help="set user after setting loopback addresses")
132
    parser.add_argument("--group", metavar="GROUP",
133
                        help="set group after setting loopback addresses")
134
135
    g = parser.add_argument_group("checking healthiness")
136
    g.add_argument("--interval", "-i", metavar='N',
137
                   default=5,
138
                   type=float,
139
                   help="wait N seconds between each healthcheck")
140
    g.add_argument("--fast-interval", "-f", metavar='N',
141
                   default=1,
142
                   type=float, dest="fast",
143
                   help=("when a state change is about to occur, "
144
                         "wait N seconds between each healthcheck"))
145
    g.add_argument("--timeout", "-t", metavar='N',
146
                   default=5,
147
                   type=int,
148
                   help="wait N seconds for the check command to execute")
149
    g.add_argument("--rise", metavar='N',
150
                   default=3,
151
                   type=int,
152
                   help="check N times before considering the service up")
153
    g.add_argument("--fall", metavar='N',
154
                   default=3,
155
                   type=int,
156
                   help="check N times before considering the service down")
157
    g.add_argument("--disable", metavar='FILE',
158
                   type=str,
159
                   help="if FILE exists, the service is considered disabled")
160
    g.add_argument("--command", "--cmd", "-c", metavar='CMD',
161
                   type=str,
162
                   help="command to use for healthcheck")
163
164
    g = parser.add_argument_group("advertising options")
165
    g.add_argument("--next-hop", "-N", metavar='IP',
166
                   type=ip_address,
167
                   help="self IP address to use as next hop")
168
    g.add_argument("--ip", metavar='IP',
169
                   type=ip_network, dest="ips", action="append",
170
                   help="advertise this IP address or network (CIDR notation)")
171
    g.add_argument("--local-preference", metavar='P',
172
                   type=int, default=-1,
173
                   help="advertise with local preference P")
174
    g.add_argument("--deaggregate-networks",
175
                   dest="deaggregate_networks", action="store_true",
176
                   help="Deaggregate Networks specified in --ip")
177
    g.add_argument("--no-ip-setup",
178
                   action="store_false", dest="ip_setup",
179
                   help="don't setup missing IP addresses")
180
    g.add_argument("--dynamic-ip-setup", default=False,
181
                   action="store_true", dest="ip_dynamic",
182
                   help="delete existing loopback ips on state down and "
183
                        "disabled, then restore loopback when up")
184
    g.add_argument("--label", default=None,
185
                   help="use the provided label to match loopback addresses")
186
    g.add_argument("--start-ip", metavar='N',
187
                   type=int, default=0,
188
                   help="index of the first IP in the list of IP addresses")
189
    g.add_argument("--up-metric", metavar='M',
190
                   type=int, default=100,
191
                   help="first IP get the metric M when the service is up")
192
    g.add_argument("--down-metric", metavar='M',
193
                   type=int, default=1000,
194
                   help="first IP get the metric M when the service is down")
195
    g.add_argument("--disabled-metric", metavar='M',
196
                   type=int, default=500,
197
                   help=("first IP get the metric M "
198
                         "when the service is disabled"))
199
    g.add_argument("--increase", metavar='M',
200
                   type=int, default=1,
201
                   help=("for each additional IP address, "
202
                         "increase metric value by M"))
203
    g.add_argument("--community", metavar="COMMUNITY",
204
                   type=str, default=None,
205
                   help="announce IPs with the supplied community")
206
    g.add_argument("--extended-community", metavar="EXTENDEDCOMMUNITY",
207
                   type=str, default=None,
208
                   help="announce IPs with the supplied extended community")
209
    g.add_argument("--large-community", metavar="LARGECOMMUNITY",
210
                   type=str, default=None,
211
                   help="announce IPs with the supplied large community")
212
    g.add_argument("--disabled-community", metavar="DISABLEDCOMMUNITY",
213
                   type=str, default=None,
214
                   help="announce IPs with the supplied community when disabled")
215
    g.add_argument("--as-path", metavar="ASPATH",
216
                   type=str, default=None,
217
                   help="announce IPs with the supplied as-path")
218
    g.add_argument("--withdraw-on-down", action="store_true",
219
                   help=("Instead of increasing the metric on health failure, "
220
                         "withdraw the route"))
221
222
    g = parser.add_argument_group("reporting")
223
    g.add_argument("--execute", metavar='CMD',
224
                   type=str, action="append",
225
                   help="execute CMD on state change")
226
    g.add_argument("--up-execute", metavar='CMD',
227
                   type=str, action="append",
228
                   help="execute CMD when the service becomes available")
229
    g.add_argument("--down-execute", metavar='CMD',
230
                   type=str, action="append",
231
                   help="execute CMD when the service becomes unavailable")
232
    g.add_argument("--disabled-execute", metavar='CMD',
233
                   type=str, action="append",
234
                   help="execute CMD when the service is disabled")
235
236
    options = parser.parse_args()
237
    if options.config is not None:
238
        # A configuration file has been provided. Read each line and
239
        # build an equivalent command line.
240
        args = sum(["--{0}".format(l.strip()).split("=", 1)
241
                    for l in options.config.readlines()
242
                    if not l.strip().startswith("#") and l.strip()], [])
243
        args = [x.strip() for x in args]
244
        args.extend(sys.argv[1:])
245
        options = parser.parse_args(args)
246
    return options
247
248
249
def setup_logging(debug, silent, name, syslog_facility, syslog):
250
    """Setup logger"""
251
252
    def syslog_address():
253
        """Return a sensible syslog address"""
254
        if sys.platform == "darwin":
255
            return "/var/run/syslog"
256
        if sys.platform.startswith("freebsd"):
257
            return "/var/run/log"
258
        if sys.platform.startswith("netbsd"):
259
            return "/var/run/log"
260
        if sys.platform.startswith("linux"):
261
            return "/dev/log"
262
        raise EnvironmentError("Unable to guess syslog address for your "
263
                               "platform, try to disable syslog")
264
265
    logger.setLevel(debug and logging.DEBUG or logging.INFO)
266
    enable_syslog = syslog and not debug
267
    # To syslog
268
    if enable_syslog:
269
        facility = getattr(logging.handlers.SysLogHandler,
270
                           "LOG_{0}".format(syslog_facility.upper()))
271
        sh = logging.handlers.SysLogHandler(address=str(syslog_address()),
272
                                            facility=facility)
273
        if name:
274
            healthcheck_name = "healthcheck-{0}".format(name)
275
        else:
276
            healthcheck_name = "healthcheck"
277
        sh.setFormatter(logging.Formatter(
278
            "{0}[{1}]: %(message)s".format(
279
                healthcheck_name,
280
                os.getpid())))
281
        logger.addHandler(sh)
282
    # To console
283
    toconsole = (hasattr(sys.stderr, "isatty") and
284
                 sys.stderr.isatty() and  # pylint: disable=E1101
285
                 not silent)
286
    if toconsole:
287
        ch = logging.StreamHandler()
288
        ch.setFormatter(logging.Formatter(
289
            "%(levelname)s[%(name)s] %(message)s"))
290
        logger.addHandler(ch)
291
292
293
def loopback_ips(label):
294
    """Retrieve loopback IP addresses"""
295
    logger.debug("Retrieve loopback IP addresses")
296
    addresses = []
297
298
    if sys.platform.startswith("linux"):
299
        # Use "ip" (ifconfig is not able to see all addresses)
300
        ipre = re.compile(r"^(?P<index>\d+):\s+(?P<name>\S+)\s+inet6?\s+"
301
                          r"(?P<ip>[\da-f.:]+)/(?P<mask>\d+)\s+.*")
302
        labelre = re.compile(r".*\s+lo:(?P<label>\S+).*")
303
        cmd = subprocess.Popen("/sbin/ip -o address show dev lo".split(),
304
                               shell=False, stdout=subprocess.PIPE)
305
    else:
306
        # Try with ifconfig
307
        ipre = re.compile(r"^inet6?\s+(alias\s+)?(?P<ip>[\da-f.:]+)\s+"
308
                          r"(?:netmask 0x(?P<netmask>[0-9a-f]+)|"
309
                          r"prefixlen (?P<mask>\d+)).*")
310
        cmd = subprocess.Popen("/sbin/ifconfig lo0".split(), shell=False,
311
                               stdout=subprocess.PIPE)
312
        labelre = re.compile(r"")
313
    for line in cmd.stdout:
314
        line = line.decode("ascii", "ignore").strip()
315
        mo = ipre.match(line)
316
        if not mo:
317
            continue
318
        if mo.group("mask"):
319
            mask = int(mo.group("mask"))
320
        else:
321
            mask = bin(int(mo.group("netmask"), 16)).count("1")
322
        try:
323
            ip = ip_network("{0}/{1}".format(mo.group("ip"),
324
                                             mask))
325
        except ValueError:
326
            continue
327
        if not ip.is_loopback:
328
            if label:
329
                lmo = labelre.match(line)
330
                if not lmo:
331
                    continue
332
                if lmo.groupdict().get("label","").startswith(label):
333
                    addresses.append(ip)
334
335
    logger.debug("Loopback addresses: %s", addresses)
336
    return addresses
337
338
def loopback():
339
    lo = "lo0"
340
    if sys.platform.startswith("linux"):
341
        lo = "lo"
342
    return lo
343
344
def setup_ips(ips, label, sudo=False):
345
    """Setup missing IP on loopback interface"""
346
347
    existing = set(loopback_ips(label))
348
    toadd = set([ip_network(ip) for net in ips for ip in net]) - existing
349
    for ip in toadd:
350
        logger.debug("Setup loopback IP address %s", ip)
351
        with open(os.devnull, "w") as fnull:
352
            cmd = ["ip", "address", "add", str(ip), "dev", loopback()]
353
            if sudo:
354
                cmd.insert(0, "sudo")
355
            if label:
356
                cmd += ["label", "{0}:{1}".format(loopback(),label)]
357
                try:
358
                    subprocess.check_call(
359
                        cmd, stdout=fnull, stderr=fnull)
360
                except subprocess.CalledProcessError as e:
361
                    # the IP address is already setup, ignoring
362
                    if cmd[0] == "ip" and cmd[2] == "add" and e.returncode == 2:
363
                        continue
364
                    raise e
365
366
def remove_ips(ips, label, sudo=False):
367
    """Remove added IP on loopback interface"""
368
    existing = set(loopback_ips(label))
369
370
    # Get intersection of IPs (ips setup, and IPs configured by ExaBGP)
371
    toremove = set([ip_network(ip) for net in ips for ip in net]) & existing
372
    for ip in toremove:
373
        logger.debug("Remove loopback IP address %s", ip)
374
        with open(os.devnull, "w") as fnull:
375
            cmd = ["ip", "address", "delete", str(ip), "dev", loopback()]
376
            if sudo:
377
                cmd.insert(0, "sudo")
378
            if label:
379
                cmd += ["label", "{0}:{1}".format(loopback(),label)]
380
            try:
381
                subprocess.check_call(
382
                    cmd, stdout=fnull, stderr=fnull)
383
            except subprocess.CalledProcessError:
384
                logger.warn("Unable to remove loopback IP address %s - is \
385
                    healthcheck running as root?", str(ip))
386
387
388
def drop_privileges(user, group):
389
    """Drop privileges to specified user and group"""
390
    if group is not None:
391
        import grp
392
        gid = grp.getgrnam(group).gr_gid
393
        logger.debug("Dropping privileges to group {0}/{1}".format(group, gid))
394
        try:
395
            os.setresgid(gid, gid, gid)
396
        except AttributeError:
397
            os.setregid(gid, gid)
398
    if user is not None:
399
        import pwd
400
        uid = pwd.getpwnam(user).pw_uid
401
        logger.debug("Dropping privileges to user {0}/{1}".format(user, uid))
402
        try:
403
            os.setresuid(uid, uid, uid)
404
        except AttributeError:
405
            os.setreuid(uid, uid)
406
407
408
def check(cmd, timeout):
409
    """Check the return code of the given command.
410
411
    :param cmd: command to execute. If :keyword:`None`, no command is executed.
412
    :param timeout: how much time we should wait for command completion.
413
    :return: :keyword:`True` if the command was successful or
414
             :keyword:`False` if not or if the timeout was triggered.
415
    """
416
    if cmd is None:
417
        return True
418
419
    class Alarm(Exception):
420
        """Exception to signal an alarm condition."""
421
        pass
422
423
    def alarm_handler(number, frame):  # pylint: disable=W0613
424
        """Handle SIGALRM signal."""
425
        raise Alarm()
0 ignored issues
show
introduced by
The variable Alarm does not seem to be defined for all execution paths.
Loading history...
426
427
    logger.debug("Checking command %s", repr(cmd))
428
    p = subprocess.Popen(cmd, shell=True,
429
                         stdout=subprocess.PIPE,
430
                         stderr=subprocess.STDOUT,
431
                         preexec_fn=os.setpgrp)
432
    if timeout:
433
        signal.signal(signal.SIGALRM, alarm_handler)
434
        signal.alarm(timeout)
435
    try:
436
        stdout = None
437
        stdout, _ = p.communicate()
438
        if timeout:
439
            signal.alarm(0)
440
        if p.returncode != 0:
441
            logger.warn("Check command was unsuccessful: %s",
442
                        p.returncode)
443
            if stdout.strip():
444
                logger.info("Output of check command: %s", stdout)
445
            return False
446
        logger.debug(
447
            "Command was executed successfully %s %s", p.returncode, stdout)
448
        return True
449
    except Alarm:
450
        logger.warn("Timeout (%s) while running check command %s",
451
                    timeout, cmd)
452
        os.killpg(p.pid, signal.SIGKILL)
453
        return False
454
455
456
def loop(options):
457
    """Main loop."""
458
    states = enum(
459
        "INIT",                 # Initial state
460
        "DISABLED",             # Disabled state
461
        "RISING",               # Checks are currently succeeding.
462
        "FALLING",              # Checks are currently failing.
463
        "UP",                   # Service is considered as up.
464
        "DOWN",                 # Service is considered as down.
465
        "EXIT",                 # Exit state
466
    )
467
468
    def exabgp(target):
469
        """Communicate new state to ExaBGP"""
470
        if target not in (states.UP, states.DOWN, states.DISABLED, states.EXIT):
471
            return
472
        # dynamic ip management. When the service fail, remove the loopback
473
        if target in (states.EXIT,) and (options.ip_dynamic or options.ip_setup):
474
            logger.info("exiting, deleting loopback ips")
475
            remove_ips(options.ips, options.label, options.sudo)
476
        # dynamic ip management. When the service fail, remove the loopback
477
        if target in (states.DOWN, states.DISABLED) and options.ip_dynamic:
478
            logger.info("service down, deleting loopback ips")
479
            remove_ips(options.ips, options.label, options.sudo)
480
        # if ips was deleted with dyn ip, re-setup them
481
        if target == states.UP and options.ip_dynamic:
482
            logger.info("service up, restoring loopback ips")
483
            setup_ips(options.ips, options.label, options.sudo)
484
485
        logger.info("send announces for %s state to ExaBGP", target)
486
        metric = vars(options).get("{0}_metric".format(str(target).lower()), 0)
487
        for ip in options.ips:
488
            if options.withdraw_on_down or target is states.EXIT:
489
                command = "announce" if target is states.UP else "withdraw"
490
            else:
491
                command = "announce"
492
            announce = "route {0} next-hop {1}".format(
493
                str(ip),
494
                options.next_hop or "self")
495
496
            if command == "announce":
497
                announce = "{0} med {1}".format(announce, metric)
498
                if options.local_preference >= 0:
499
                    announce = "{0} local-preference {1}".format(announce, options.local_preference)
500
                if options.community or options.disabled_community:
501
                    community = options.community
502
                    if target in (states.DOWN, states.DISABLED):
503
                        if options.disabled_community:
504
                            community = options.disabled_community
505
                    if community:
506
                        announce = "{0} community [ {1} ]".format(
507
                            announce, community)
508
                if options.extended_community:
509
                    announce = "{0} extended-community [ {1} ]".format(
510
                        announce,
511
                        options.extended_community)
512
                if options.large_community:
513
                    announce = "{0} large-community [ {1} ]".format(
514
                        announce,
515
                        options.large_community)
516
                if options.as_path:
517
                    announce = "{0} as-path [ {1} ]".format(
518
                        announce,
519
                        options.as_path)
520
521
            metric += options.increase
522
523
            # Send and flush command
524
            logger.debug("exabgp: {0} {1}".format(command, announce))
525
            print("{0} {1}".format(command, announce))
526
            sys.stdout.flush()
527
            
528
            # Wait for confirmation from ExaBGP if expected
529
            if options.no_ack:
530
                continue
531
            # if the program is not ran manually, do not read the input
532
            if hasattr(sys.stdout, "isatty") and sys.stdout.isatty():
533
                continue
534
            sys.stdin.readline()
535
536
    def trigger(target):
537
        """Trigger a state change and execute the appropriate commands"""
538
        # Shortcut for RISING->UP and FALLING->UP
539
        if target == states.RISING and options.rise <= 1:
540
            target = states.UP
541
        elif target == states.FALLING and options.fall <= 1:
542
            target = states.DOWN
543
544
        # Log and execute commands
545
        logger.debug("Transition to %s", str(target))
546
        cmds = []
547
        cmds.extend(vars(options).get("{0}_execute".format(
548
            str(target).lower()), []) or [])
549
        cmds.extend(vars(options).get("execute", []) or [])
550
        for cmd in cmds:
551
            logger.debug("Transition to %s, execute `%s`",
552
                         str(target), cmd)
553
            env = os.environ.copy()
554
            env.update({"STATE": str(target)})
555
            with open(os.devnull, "w") as fnull:
556
                subprocess.call(
557
                    cmd, shell=True, stdout=fnull, stderr=fnull, env=env)
558
559
        return target
560
561
    def one(checks, state):
562
        """Execute one loop iteration."""
563
        disabled = (options.disable is not None and
564
                    os.path.exists(options.disable))
565
        successful = disabled or check(options.command, options.timeout)
566
        # FSM
567
        if state != states.DISABLED and disabled:
568
            state = trigger(states.DISABLED)
569
        elif state == states.INIT:
570
            if successful and options.rise <= 1:
571
                state = trigger(states.UP)
572
            elif successful:
573
                state = trigger(states.RISING)
574
                checks = 1
575
            else:
576
                state = trigger(states.FALLING)
577
                checks = 1
578
        elif state == states.DISABLED:
579
            if not disabled:
580
                state = trigger(states.INIT)
581
        elif state == states.RISING:
582
            if successful:
583
                checks += 1
584
                if checks >= options.rise:
585
                    state = trigger(states.UP)
586
            else:
587
                state = trigger(states.FALLING)
588
                checks = 1
589
        elif state == states.FALLING:
590
            if not successful:
591
                checks += 1
592
                if checks >= options.fall:
593
                    state = trigger(states.DOWN)
594
            else:
595
                state = trigger(states.RISING)
596
                checks = 1
597
        elif state == states.UP:
598
            if not successful:
599
                state = trigger(states.FALLING)
600
                checks = 1
601
        elif state == states.DOWN:
602
            if successful:
603
                state = trigger(states.RISING)
604
                checks = 1
605
        else:
606
            raise ValueError("Unhandled state: {0}".format(str(state)))
607
608
        # Send announces. We announce them on a regular basis in case
609
        # we lose connection with a peer.
610
        exabgp(state)
611
        return checks, state
612
613
    checks = 0
614
    state = states.INIT
615
    # Do cleanups on SIGTERM
616
    def sigterm_handler(signum, frame):  # pylint: disable=W0612,W0613
617
        exabgp(states.EXIT)
618
        sys.exit(0)
619
620
    signal.signal(signal.SIGTERM, sigterm_handler)
621
622
    while True:
623
        checks, state = one(checks, state)
624
625
        try:
626
            # How much we should sleep?
627
            if state in (states.FALLING, states.RISING):
628
                time.sleep(options.fast)
629
            else:
630
                time.sleep(options.interval)
631
        except KeyboardInterrupt:
632
            exabgp(states.EXIT)
633
            break
634
635
636
def main():
637
    """Entry point."""
638
    options = parse()
639
    setup_logging(options.debug, options.silent, options.name,
640
                  options.syslog_facility, not options.no_syslog)
641
    if options.pid:
642
        options.pid.write("{0}\n".format(os.getpid()))
643
        options.pid.close()
644
    try:
645
        # Setup IP to use
646
        options.ips = options.ips or loopback_ips(options.label)
647
        if not options.ips:
648
            logger.error("No IP found")
649
            sys.exit(1)
650
        if options.ip_setup:
651
            setup_ips(options.ips, options.label, options.sudo)
652
        drop_privileges(options.user, options.group)
653
654
        # Parse defined networks into a list of IPs for advertisement
655
        if options.deaggregate_networks:
656
            options.ips = [ip_network(ip) for net in options.ips for ip in net]
657
658
        options.ips = collections.deque(options.ips)
659
        options.ips.rotate(-options.start_ip)
660
        options.ips = list(options.ips)
661
        # Main loop
662
        loop(options)
663
    except Exception as e:  # pylint: disable=W0703
664
        logger.exception("Uncaught exception: %s", e)
665
        sys.exit(1)
666
667
668
if __name__ == "__main__":
669
    main()
670