Completed
Push — master ( ad3d88...1f22e8 )
by Thomas
11:30
created

exabgp.application.healthcheck.loopback()   A

Complexity

Conditions 2

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nop 0
dl 0
loc 5
rs 10
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
            subprocess.check_call(
358
                cmd, stdout=fnull, stderr=fnull)
359
360
def remove_ips(ips, label, sudo=False):
361
    """Remove added IP on loopback interface"""
362
    existing = set(loopback_ips(label))
363
364
    # Get intersection of IPs (ips setup, and IPs configured by ExaBGP)
365
    toremove = set([ip_network(ip) for net in ips for ip in net]) & existing
366
    for ip in toremove:
367
        logger.debug("Remove loopback IP address %s", ip)
368
        with open(os.devnull, "w") as fnull:
369
            cmd = ["ip", "address", "delete", str(ip), "dev", loopback()]
370
            if sudo:
371
                cmd.insert(0, "sudo")
372
            if label:
373
                cmd += ["label", "{0}:{1}".format(loopback(),label)]
374
            try:
375
                subprocess.check_call(
376
                    cmd, stdout=fnull, stderr=fnull)
377
            except subprocess.CalledProcessError:
378
                logger.warn("Unable to remove loopback IP address %s - is \
379
                    healthcheck running as root?", str(ip))
380
381
382
def drop_privileges(user, group):
383
    """Drop privileges to specified user and group"""
384
    if group is not None:
385
        import grp
386
        gid = grp.getgrnam(group).gr_gid
387
        logger.debug("Dropping privileges to group {0}/{1}".format(group, gid))
388
        try:
389
            os.setresgid(gid, gid, gid)
390
        except AttributeError:
391
            os.setregid(gid, gid)
392
    if user is not None:
393
        import pwd
394
        uid = pwd.getpwnam(user).pw_uid
395
        logger.debug("Dropping privileges to user {0}/{1}".format(user, uid))
396
        try:
397
            os.setresuid(uid, uid, uid)
398
        except AttributeError:
399
            os.setreuid(uid, uid)
400
401
402
def check(cmd, timeout):
403
    """Check the return code of the given command.
404
405
    :param cmd: command to execute. If :keyword:`None`, no command is executed.
406
    :param timeout: how much time we should wait for command completion.
407
    :return: :keyword:`True` if the command was successful or
408
             :keyword:`False` if not or if the timeout was triggered.
409
    """
410
    if cmd is None:
411
        return True
412
413
    class Alarm(Exception):
414
        """Exception to signal an alarm condition."""
415
        pass
416
417
    def alarm_handler(number, frame):  # pylint: disable=W0613
418
        """Handle SIGALRM signal."""
419
        raise Alarm()
0 ignored issues
show
introduced by
The variable Alarm does not seem to be defined for all execution paths.
Loading history...
420
421
    logger.debug("Checking command %s", repr(cmd))
422
    p = subprocess.Popen(cmd, shell=True,
423
                         stdout=subprocess.PIPE,
424
                         stderr=subprocess.STDOUT,
425
                         preexec_fn=os.setpgrp)
426
    if timeout:
427
        signal.signal(signal.SIGALRM, alarm_handler)
428
        signal.alarm(timeout)
429
    try:
430
        stdout = None
431
        stdout, _ = p.communicate()
432
        if timeout:
433
            signal.alarm(0)
434
        if p.returncode != 0:
435
            logger.warn("Check command was unsuccessful: %s",
436
                        p.returncode)
437
            if stdout.strip():
438
                logger.info("Output of check command: %s", stdout)
439
            return False
440
        logger.debug(
441
            "Command was executed successfully %s %s", p.returncode, stdout)
442
        return True
443
    except Alarm:
444
        logger.warn("Timeout (%s) while running check command %s",
445
                    timeout, cmd)
446
        os.killpg(p.pid, signal.SIGKILL)
447
        return False
448
449
450
def loop(options):
451
    """Main loop."""
452
    states = enum(
453
        "INIT",                 # Initial state
454
        "DISABLED",             # Disabled state
455
        "RISING",               # Checks are currently succeeding.
456
        "FALLING",              # Checks are currently failing.
457
        "UP",                   # Service is considered as up.
458
        "DOWN",                 # Service is considered as down.
459
        "EXIT",                 # Exit state
460
    )
461
462
    def exabgp(target):
463
        """Communicate new state to ExaBGP"""
464
        if target not in (states.UP, states.DOWN, states.DISABLED, states.EXIT):
465
            return
466
        # dynamic ip management. When the service fail, remove the loopback
467
        if target in (states.EXIT,) and (options.ip_dynamic or options.ip_setup):
468
            logger.info("exiting, deleting loopback ips")
469
            remove_ips(options.ips, options.label, options.sudo)
470
        # dynamic ip management. When the service fail, remove the loopback
471
        if target in (states.DOWN, states.DISABLED) and options.ip_dynamic:
472
            logger.info("service down, deleting loopback ips")
473
            remove_ips(options.ips, options.label, options.sudo)
474
        # if ips was deleted with dyn ip, re-setup them
475
        if target == states.UP and options.ip_dynamic:
476
            logger.info("service up, restoring loopback ips")
477
            setup_ips(options.ips, options.label, options.sudo)
478
479
        logger.info("send announces for %s state to ExaBGP", target)
480
        metric = vars(options).get("{0}_metric".format(str(target).lower()), 0)
481
        for ip in options.ips:
482
            if options.withdraw_on_down or target is states.EXIT:
483
                command = "announce" if target is states.UP else "withdraw"
484
            else:
485
                command = "announce"
486
            announce = "route {0} next-hop {1}".format(
487
                str(ip),
488
                options.next_hop or "self")
489
490
            if command == "announce":
491
                announce = "{0} med {1}".format(announce, metric)
492
                if options.local_preference >= 0:
493
                    announce = "{0} local-preference {1}".format(announce, options.local_preference)
494
                if options.community or options.disabled_community:
495
                    community = options.community
496
                    if target in (states.DOWN, states.DISABLED):
497
                        if options.disabled_community:
498
                            community = options.disabled_community
499
                    if community:
500
                        announce = "{0} community [ {1} ]".format(
501
                            announce, community)
502
                if options.extended_community:
503
                    announce = "{0} extended-community [ {1} ]".format(
504
                        announce,
505
                        options.extended_community)
506
                if options.large_community:
507
                    announce = "{0} large-community [ {1} ]".format(
508
                        announce,
509
                        options.large_community)
510
                if options.as_path:
511
                    announce = "{0} as-path [ {1} ]".format(
512
                        announce,
513
                        options.as_path)
514
515
            metric += options.increase
516
517
            # Send and flush command
518
            logger.debug("exabgp: {0} {1}".format(command, announce))
519
            print("{0} {1}".format(command, announce))
520
            sys.stdout.flush()
521
            
522
            # Wait for confirmation from ExaBGP if expected
523
            if options.no_ack:
524
                continue
525
            # if the program is not ran manually, do not read the input
526
            if hasattr(sys.stdout, "isatty") and sys.stdout.isatty():
527
                continue
528
            sys.stdin.readline()
529
530
    def trigger(target):
531
        """Trigger a state change and execute the appropriate commands"""
532
        # Shortcut for RISING->UP and FALLING->UP
533
        if target == states.RISING and options.rise <= 1:
534
            target = states.UP
535
        elif target == states.FALLING and options.fall <= 1:
536
            target = states.DOWN
537
538
        # Log and execute commands
539
        logger.debug("Transition to %s", str(target))
540
        cmds = []
541
        cmds.extend(vars(options).get("{0}_execute".format(
542
            str(target).lower()), []) or [])
543
        cmds.extend(vars(options).get("execute", []) or [])
544
        for cmd in cmds:
545
            logger.debug("Transition to %s, execute `%s`",
546
                         str(target), cmd)
547
            env = os.environ.copy()
548
            env.update({"STATE": str(target)})
549
            with open(os.devnull, "w") as fnull:
550
                try:
551
                    subprocess.call(
552
                        cmd, shell=True, stdout=fnull, stderr=fnull, env=env)
553
                except subprocess.CalledProcessError as e:
554
                    # the IP address is already setup, ignoring
555
                    if cmd[0] == "ip" and cmd[2] == "add" and e.returncode == 2:
556
                        continue
557
                    raise e
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