diff -Nru exabgp-4.0.6/CHANGELOG exabgp-4.0.8/CHANGELOG --- exabgp-4.0.6/CHANGELOG 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/CHANGELOG 2018-07-11 13:43:27.000000000 +0000 @@ -3,6 +3,42 @@ minor : increase on risk of code breakage during a major release bug : increase on bug or incremental changes +Version 4.0.8 + * Fix: PEP-0479 + +Version 4.0.7 + * Feature: added prefix for ip-reachability-tlv + patch: Tinus Flagstad + * Feature: forewardport #840 (RFC 7674 - Support redirect traffic to a VRF encoded as 4-octet AS) + original patch by: Omri Matitiau + * Fix: handling of processes when the configuration reload fails + patch by: Malcolm Dodds + * Fix: healthcheck, remove options.ip_setup in dynamic ip management + patch by: Ahmet Demir + * Fix: present unknown RD as hexadecimal string (previous representation could not be JSON parsed) + reported by: Jitoxxx + * Fix: fix permissions of systemd service files + patch by: Malcolm Dodds + * Fix: properly show connection as down in the cli + reported by: chantra + * Fix: JSON encoding for route-refresh + reported by: nfz1 + * Fix: Prevent busy spinning in multiple code path (including when a peer closed the connection on us) + reported by: chantra + * Fix: busy spinning when the peer went away + reported by: chantra + * Improvement: many fix to QA code + patch by: Vincent Bernart + * Improvement: exit with error code 0 on SIGTERM + reported by: Johan Guldmyr + * Fix: fix neighbor CLI to match against peer address + patch by: Malcolm Dodds + * Fix: fix parsing of labelled default IP route + patch by: Thomas Morin + * Fix: checks for consumption of data in bgp-ls + patch by: he32 + * Fix: support latest python3 (with async as keyword) + Version 4.0.6 * Fix: default network for IPv6 is 128 .. not 32 patch by: Donatas Abraitis diff -Nru exabgp-4.0.6/debian/changelog exabgp-4.0.8/debian/changelog --- exabgp-4.0.6/debian/changelog 2018-04-28 18:24:37.000000000 +0000 +++ exabgp-4.0.8/debian/changelog 2018-07-24 05:47:22.000000000 +0000 @@ -1,3 +1,11 @@ +exabgp (4.0.8-1) unstable; urgency=medium + + * New upstream release. + - Compatibility with Python 3.7. Closes: #904390. + * d/patches: remove merged patches. + + -- Vincent Bernat Tue, 24 Jul 2018 07:47:22 +0200 + exabgp (4.0.6-2) unstable; urgency=medium * d/postinst: add exabgpcli to the alternative system. diff -Nru exabgp-4.0.6/debian/patches/0001-Ensure-NoNextHop-stays-a-singleton.patch exabgp-4.0.8/debian/patches/0001-Ensure-NoNextHop-stays-a-singleton.patch --- exabgp-4.0.6/debian/patches/0001-Ensure-NoNextHop-stays-a-singleton.patch 2018-04-28 18:24:37.000000000 +0000 +++ exabgp-4.0.8/debian/patches/0001-Ensure-NoNextHop-stays-a-singleton.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,33 +0,0 @@ -From 186bcfe4c81f7bb48d0495fb4fef5cf855f12adc Mon Sep 17 00:00:00 2001 -From: Vincent Bernat -Date: Sat, 28 Apr 2018 17:31:42 +0200 -Subject: [PATCH] Ensure NoNextHop stays a singleton - -In 58e809a8549f9f0f1e5372d4c800c090275d9b9c, a deep copy is done. This -makes a second "NoNextHop" instance while we believe it should be a -singleton. Implement `__copy__` and `__deepcopy__` to ensure we keep a -singleton. ---- - lib/exabgp/protocol/ip/__init__.py | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/lib/exabgp/protocol/ip/__init__.py b/lib/exabgp/protocol/ip/__init__.py -index bd690332367c..2ce27efeeee3 100644 ---- a/lib/exabgp/protocol/ip/__init__.py -+++ b/lib/exabgp/protocol/ip/__init__.py -@@ -226,6 +226,12 @@ class _NoNextHop (object): - def __str__ (self): - return 'no-nexthop' - -+ def __deepcopy__(self, _): -+ return self -+ -+ def __copy__(self, _): -+ return self -+ - - NoNextHop = _NoNextHop() - --- -2.17.0 - diff -Nru exabgp-4.0.6/debian/patches/0001-qa-FakeNeighbor-should-have-an-extended_message-attr.patch exabgp-4.0.8/debian/patches/0001-qa-FakeNeighbor-should-have-an-extended_message-attr.patch --- exabgp-4.0.6/debian/patches/0001-qa-FakeNeighbor-should-have-an-extended_message-attr.patch 2018-04-28 18:24:37.000000000 +0000 +++ exabgp-4.0.8/debian/patches/0001-qa-FakeNeighbor-should-have-an-extended_message-attr.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,54 +0,0 @@ -From 7f40865b4528d0275e6aaa64b13191834ed08699 Mon Sep 17 00:00:00 2001 -From: Vincent Bernat -Date: Sat, 28 Apr 2018 17:38:48 +0200 -Subject: [PATCH] qa: FakeNeighbor should have an extended_message attribute - -Otherwise, we fail here: - - + env INTERPRETER=python2.7 ETC=/build/exabgp-4.0.6/etc/exabgp exabgp_log_enable=false python2.7 -m nose ./qa/tests/cache_test.py ./qa/tests/control_test.py ./qa/tests/decode_test.py ./qa/tests/flow_test.py ./qa/tests/l2vpn_test.py ./qa/tests/open_test.py ./qa/tests/parsing_test.py - ......EE......... - ====================================================================== - ERROR: test_decoding_udpate_asn (decode_test.TestUpdateDecoding) - ---------------------------------------------------------------------- - Traceback (most recent call last): - File "/build/exabgp-4.0.6/qa/tests/decode_test.py", line 356, in setUp - capa = Capabilities().new(neighbor,False) - File "/build/exabgp-4.0.6/lib/exabgp/bgp/message/open/capability/capabilities.py", line 145, in new - self._extended_message(neighbor) - File "/build/exabgp-4.0.6/lib/exabgp/bgp/message/open/capability/capabilities.py", line 119, in _extended_message - if not neighbor.extended_message: - AttributeError: 'FakeNeighbor' object has no attribute 'extended_message' - - ====================================================================== - ERROR: test_decoding_udpate_asn4 (decode_test.TestUpdateDecoding) - ---------------------------------------------------------------------- - Traceback (most recent call last): - File "/build/exabgp-4.0.6/qa/tests/decode_test.py", line 356, in setUp - capa = Capabilities().new(neighbor,False) - File "/build/exabgp-4.0.6/lib/exabgp/bgp/message/open/capability/capabilities.py", line 145, in new - self._extended_message(neighbor) - File "/build/exabgp-4.0.6/lib/exabgp/bgp/message/open/capability/capabilities.py", line 119, in _extended_message - if not neighbor.extended_message: - AttributeError: 'FakeNeighbor' object has no attribute 'extended_message' - - ---------------------------------------------------------------------- - Ran 17 tests in 1.986s ---- - qa/tests/decode_test.py | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/qa/tests/decode_test.py b/qa/tests/decode_test.py -index 8faf1f531732..7a4eb842d41f 100755 ---- a/qa/tests/decode_test.py -+++ b/qa/tests/decode_test.py -@@ -317,6 +317,7 @@ class FakeNeighbor (object): - hold_time = HoldTime(180) - asn4 = False - add_path = 0 -+ extended_message = False - - # capability - route_refresh = False --- -2.17.0 - diff -Nru exabgp-4.0.6/debian/patches/series exabgp-4.0.8/debian/patches/series --- exabgp-4.0.6/debian/patches/series 2018-04-28 18:24:37.000000000 +0000 +++ exabgp-4.0.8/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -0001-Ensure-NoNextHop-stays-a-singleton.patch -0001-qa-FakeNeighbor-should-have-an-extended_message-attr.patch diff -Nru exabgp-4.0.6/etc/exabgp/example-api-program.run exabgp-4.0.8/etc/exabgp/example-api-program.run --- exabgp-4.0.6/etc/exabgp/example-api-program.run 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/etc/exabgp/example-api-program.run 2018-07-11 13:43:27.000000000 +0000 @@ -29,7 +29,7 @@ )) -def async (fd): +def asynchronous(fd): try: fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK) return True @@ -45,7 +45,7 @@ return False -if not async(sys.stdin): +if not asynchronous(sys.stdin): print >> sys.stderr, "could not set stdin/stdout non blocking" sys.stderr.flush() sys.exit(1) diff -Nru exabgp-4.0.6/etc/exabgp/example-tcp-control.conf exabgp-4.0.8/etc/exabgp/example-tcp-control.conf --- exabgp-4.0.6/etc/exabgp/example-tcp-control.conf 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/etc/exabgp/example-tcp-control.conf 2018-07-11 13:43:27.000000000 +0000 @@ -1,6 +1,6 @@ # Use the forking watchdog system to run a tcp-server process tcp-control { - run etc/exabgp/example-tcp-control.run; + run ./example-tcp-control.run; encoder text; } diff -Nru exabgp-4.0.6/etc/exabgp/parse-multiple-process.conf exabgp-4.0.8/etc/exabgp/parse-multiple-process.conf --- exabgp-4.0.6/etc/exabgp/parse-multiple-process.conf 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/etc/exabgp/parse-multiple-process.conf 2018-07-11 13:43:27.000000000 +0000 @@ -1,5 +1,5 @@ process my-process { - run etc/exabgp/run/syslog-1.py; + run ./run/syslog-1.py; encoder json; } @@ -19,6 +19,7 @@ } } neighbor 10.0.0.3 { + inherit test; description "will pass received routes to the program"; router-id 10.0.0.2; local-address 10.0.0.2; diff -Nru exabgp-4.0.6/etc/exabgp/parse-multisession.conf exabgp-4.0.8/etc/exabgp/parse-multisession.conf --- exabgp-4.0.6/etc/exabgp/parse-multisession.conf 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/etc/exabgp/parse-multisession.conf 2018-07-11 13:43:27.000000000 +0000 @@ -1,5 +1,5 @@ process parsed-route-backend { - run etc/exabgp/run/syslog-1.py; + run ./run/syslog-1.py; encoder json; } diff -Nru exabgp-4.0.6/etc/exabgp/parse-process.conf exabgp-4.0.8/etc/exabgp/parse-process.conf --- exabgp-4.0.6/etc/exabgp/parse-process.conf 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/etc/exabgp/parse-process.conf 2018-07-11 13:43:27.000000000 +0000 @@ -1,5 +1,5 @@ process my-process { - run etc/exabgp/run/syslog-1.py; + run ./run/syslog-1.py; encoder json; } diff -Nru exabgp-4.0.6/lib/exabgp/application/bgp.py exabgp-4.0.8/lib/exabgp/application/bgp.py --- exabgp-4.0.6/lib/exabgp/application/bgp.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/application/bgp.py 2018-07-11 13:43:27.000000000 +0000 @@ -365,8 +365,8 @@ logger.info('to read responses %sexabgp.out' % pipe,'cli control') if not env.profile.enable: - was_ok = Reactor(configurations).run(validate,root) - __exit(env.debug.memory,0 if was_ok else 1) + exit_code = Reactor(configurations).run(validate,root) + __exit(env.debug.memory, exit_code) try: import cProfile as profile @@ -375,8 +375,8 @@ if env.profile.file == 'stdout': profiled = 'Reactor(%s).run(%s,"%s")' % (str(configurations),str(validate),str(root)) - was_ok = profile.run(profiled) - __exit(env.debug.memory,0 if was_ok else 1) + exit_code = profile.run(profiled) + __exit(env.debug.memory, exit_code) if pid: profile_name = "%s-pid-%d" % (env.profile.file,pid) @@ -395,9 +395,9 @@ profiler = profile.Profile() profiler.enable() try: - was_ok = Reactor(configurations).run(validate,root) + exit_code = Reactor(configurations).run(validate, root) except Exception: - was_ok = False + exit_code = Reactor.Exit.unknown raise finally: profiler.disable() @@ -411,7 +411,7 @@ logger.debug("-"*len(notice),'reactor') logger.debug(notice,'reactor') logger.debug("-"*len(notice),'reactor') - __exit(env.debug.memory,0 if was_ok else 1) + __exit(env.debug.memory,exit_code) else: logger.debug("-"*len(notice),'reactor') logger.debug(notice,'reactor') diff -Nru exabgp-4.0.6/lib/exabgp/application/cli.py exabgp-4.0.8/lib/exabgp/application/cli.py --- exabgp-4.0.6/lib/exabgp/application/cli.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/application/cli.py 2018-07-11 13:43:27.000000000 +0000 @@ -10,6 +10,7 @@ import os import sys +import time import select import signal import errno @@ -21,6 +22,7 @@ from exabgp.application.control import check_fifo from exabgp.reactor.network.error import error +from exabgp.reactor.api.response.answer import Answer from exabgp.vendoring import docopt @@ -42,6 +44,12 @@ """.replace('\t',' ') +class AnswerStream: + done = '\n%s\n' % Answer.done + error = '\n%s\n' % Answer.error + shutdown = '\n%s\n' % Answer.error + buffer_size = Answer.buffer_size + 2 + def main (): options = docopt.docopt(usage, help=False) options['--env'] = '' # exabgp compatibility @@ -91,6 +99,35 @@ sys.stderr.flush() sys.exit(1) + buffer = '' + start = time.time() + try: + reader = os.open(recv, os.O_RDONLY | os.O_EXCL | os.O_NONBLOCK) + while True: + while select.select([reader], [], [], 0) != ([], [], []): + buffer += os.read(reader,4096) + buffer = buffer[-AnswerStream.buffer_size:] + # we read nothing, nothing to do + if not buffer: + break + # we read some data but it is not ending by a new line (ie: not a command completion) + if buffer[-1] != '\n': + continue + if AnswerStream.done.endswith(buffer[-len(AnswerStream.done):]): + break + if AnswerStream.error.endswith(buffer[-len(AnswerStream.error):]): + break + if AnswerStream.shutdown.endswith(buffer[-len(AnswerStream.shutdown):]): + break + # we are not ack'ing the command and probably have read all there is + if time.time() > start + 1.5: + break + + except Exception as exc: + sys.stdout.write('could not clear named pipe from potential previous command data') + sys.stdout.write(exc) + sys.stdout.flush() + signal.signal(signal.SIGALRM, write_timeout) signal.alarm(2) @@ -136,21 +173,22 @@ buf += raw while b'\n' in buf: line,buf = buf.split(b'\n',1) - if line == b'done': + string = line.decode() + if string == Answer.done: done = True break - if line == b'shutdown': + if string == Answer.shutdown: sys.stderr.write('ExaBGP is shutting down, command aborted\n') sys.stderr.flush() done = True break - if line == b'error': + if string == Answer.error: done = True sys.stderr.write('ExaBGP returns an error (see ExaBGP\'s logs for more information)\n') sys.stderr.write('use help for a list of available commands\n') sys.stderr.flush() break - sys.stdout.write('%s\n' % line.decode()) + sys.stdout.write('%s\n' % string) sys.stdout.flush() select.select([reader],[],[],0.01) diff -Nru exabgp-4.0.6/lib/exabgp/application/healthcheck.py exabgp-4.0.8/lib/exabgp/application/healthcheck.py --- exabgp-4.0.6/lib/exabgp/application/healthcheck.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/application/healthcheck.py 2018-07-11 13:43:27.000000000 +0000 @@ -193,7 +193,7 @@ g.add_argument("--increase", metavar='M', type=int, default=1, help=("for each additional IP address, " - "increase metric value by W")) + "increase metric value by M")) g.add_argument("--community", metavar="COMMUNITY", type=str, default=None, help="announce IPs with the supplied community") @@ -309,7 +309,10 @@ mo = ipre.match(line) if not mo: continue - mask = int(mo.group("mask")) or bin(int(mo.group("netmask"), 16)).count("1") + if mo.group("mask"): + mask = int(mo.group("mask")) + else: + mask = bin(int(mo.group("netmask"), 16)).count("1") try: ip = ip_network("{0}/{1}".format(mo.group("ip"), mask)) @@ -458,7 +461,7 @@ logger.info("service down, deleting loopback ips") remove_ips(options.ips, options.label, options.sudo) # if ips was deleted with dyn ip, re-setup them - if target == states.UP and options.ip_dynamic and options.ip_setup: + if target == states.UP and options.ip_dynamic: logger.info("service up, restoring loopback ips") setup_ips(options.ips, options.label, options.sudo) diff -Nru exabgp-4.0.6/lib/exabgp/bgp/message/update/attribute/community/extended/__init__.py exabgp-4.0.8/lib/exabgp/bgp/message/update/attribute/community/extended/__init__.py --- exabgp-4.0.6/lib/exabgp/bgp/message/update/attribute/community/extended/__init__.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/bgp/message/update/attribute/community/extended/__init__.py 2018-07-11 13:43:27.000000000 +0000 @@ -25,6 +25,7 @@ from exabgp.bgp.message.update.attribute.community.extended.traffic import TrafficRate from exabgp.bgp.message.update.attribute.community.extended.traffic import TrafficAction from exabgp.bgp.message.update.attribute.community.extended.traffic import TrafficRedirect +from exabgp.bgp.message.update.attribute.community.extended.traffic import TrafficRedirectASN4 from exabgp.bgp.message.update.attribute.community.extended.traffic import TrafficMark from exabgp.bgp.message.update.attribute.community.extended.traffic import TrafficNextHop from exabgp.bgp.message.update.attribute.community.extended.encapsulation import Encapsulation diff -Nru exabgp-4.0.6/lib/exabgp/bgp/message/update/attribute/community/extended/traffic.py exabgp-4.0.8/lib/exabgp/bgp/message/update/attribute/community/extended/traffic.py --- exabgp-4.0.6/lib/exabgp/bgp/message/update/attribute/community/extended/traffic.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/bgp/message/update/attribute/community/extended/traffic.py 2018-07-11 13:43:27.000000000 +0000 @@ -11,6 +11,7 @@ from struct import unpack from exabgp.bgp.message.open.asn import ASN +from exabgp.bgp.message.open.capability.asn4 import ASN4 from exabgp.bgp.message.update.attribute.community.extended import ExtendedCommunity @@ -125,6 +126,33 @@ return TrafficRedirect(ASN(asn),target,data[:8]) +@ExtendedCommunity.register +class TrafficRedirectASN4 (ExtendedCommunity): + COMMUNITY_TYPE = 0x82 + COMMUNITY_SUBTYPE = 0x08 + + __slots__ = ['asn', 'target'] + + def __init__(self, asn, target, community=None): + self.asn = asn + self.target = target + ExtendedCommunity.__init__( + self, + community if community is not None else pack( + "!2sLH", + self._subtype(), + asn, target + ) + ) + + def __str__(self): + return "redirect:%s:%s" % (self.asn, self.target) + + @staticmethod + def unpack(data): + asn, target = unpack('!LH', data[2:8]) + return TrafficRedirectASN4(ASN4(asn), target, data[:8]) + # ================================================================== TrafficMark # RFC 5575 diff -Nru exabgp-4.0.6/lib/exabgp/bgp/message/update/nlri/bgpls/node.py exabgp-4.0.8/lib/exabgp/bgp/message/update/nlri/bgpls/node.py --- exabgp-4.0.6/lib/exabgp/bgp/message/update/nlri/bgpls/node.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/bgp/message/update/nlri/bgpls/node.py 2018-07-11 13:43:27.000000000 +0000 @@ -84,7 +84,7 @@ # Unpack Node Descriptor Sub-TLVs node_id, left = NodeDescriptor.unpack(values, proto_id) node_ids.append(node_id) - if left == data: + if left == values: raise RuntimeError("sub-calls should consume data") values = left diff -Nru exabgp-4.0.6/lib/exabgp/bgp/message/update/nlri/bgpls/prefixv4.py exabgp-4.0.8/lib/exabgp/bgp/message/update/nlri/bgpls/prefixv4.py --- exabgp-4.0.6/lib/exabgp/bgp/message/update/nlri/bgpls/prefixv4.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/bgp/message/update/nlri/bgpls/prefixv4.py 2018-07-11 13:43:27.000000000 +0000 @@ -71,7 +71,7 @@ # follows IGP type node, left = NodeDescriptor.unpack(values, proto_id) local_node.append(node) - if left == tlvs: + if left == values: raise RuntimeError("sub-calls should consume data") values = left tlvs = tlvs[4 + tlv_length:] diff -Nru exabgp-4.0.6/lib/exabgp/bgp/message/update/nlri/bgpls/tlvs/ipreach.py exabgp-4.0.8/lib/exabgp/bgp/message/update/nlri/bgpls/tlvs/ipreach.py --- exabgp-4.0.6/lib/exabgp/bgp/message/update/nlri/bgpls/tlvs/ipreach.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/bgp/message/update/nlri/bgpls/tlvs/ipreach.py 2018-07-11 13:43:27.000000000 +0000 @@ -34,9 +34,10 @@ class IpReach(object): - def __init__ (self, prefix, packed=None): + def __init__ (self, prefix, plength=None, packed=None): self.prefix = prefix self._packed = packed + self.plength = plength @classmethod def unpack (cls, data): @@ -51,8 +52,8 @@ # octets for prefix length 9 to 16, 3 octets for prefix length 17 up to # 24, 4 octets for prefix length 25 up to 32, etc. - # plenght = unpack('!B',data[0:1])[0] - # octet = int(math.ceil(plenght / 8)) + plength = unpack('!B',data[0:1])[0] + # octet = int(math.ceil(plength / 8)) octet = len(data[1:]) prefix_list = unpack("!%dB" % octet,data[1:octet + 1]) prefix_list = [str(x) for x in prefix_list] @@ -60,10 +61,14 @@ # a 4 octet IP prefix prefix_list = prefix_list + ["0"]*(4 - len(prefix_list)) prefix = '.'.join(prefix_list) - return cls(prefix=prefix) + return cls(prefix=prefix, plength=plength) def json (self, compact=None): - return '"ip-reachability-tlv": "%s"' % str(self.prefix) + return ', '.join([ + '"ip-reachability-tlv": "%s"' % str(self.prefix), + '"ip-reach-prefix": "%s/%s"' % + (str(self.prefix), str(self.plength)), + ]) def __eq__ (self, other): return self.prefix == other.prefix diff -Nru exabgp-4.0.6/lib/exabgp/bgp/message/update/nlri/inet.py exabgp-4.0.8/lib/exabgp/bgp/message/update/nlri/inet.py --- exabgp-4.0.6/lib/exabgp/bgp/message/update/nlri/inet.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/bgp/message/update/nlri/inet.py 2018-07-11 13:43:27.000000000 +0000 @@ -26,7 +26,6 @@ from exabgp.bgp.message.update.nlri.qualifier import RouteDistinguisher from exabgp.bgp.message.notification import Notify - @NLRI.register(AFI.ipv4,SAFI.unicast) @NLRI.register(AFI.ipv6,SAFI.unicast) @NLRI.register(AFI.ipv4,SAFI.multicast) @@ -110,7 +109,7 @@ if safi.has_label(): labels = [] - while mask - rd_mask > 24: + while mask - rd_mask >= 24: label = int(unpack('!L',character(0) + bgp[:3])[0]) bgp = bgp[3:] mask -= 24 # 3 bytes @@ -127,6 +126,7 @@ break nlri.labels = Labels(labels) + if rd_size: mask -= rd_mask # the route distinguisher rd = bgp[:rd_size] diff -Nru exabgp-4.0.6/lib/exabgp/bgp/message/update/nlri/qualifier/rd.py exabgp-4.0.8/lib/exabgp/bgp/message/update/nlri/qualifier/rd.py --- exabgp-4.0.6/lib/exabgp/bgp/message/update/nlri/qualifier/rd.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/bgp/message/update/nlri/qualifier/rd.py 2018-07-11 13:43:27.000000000 +0000 @@ -12,6 +12,7 @@ from exabgp.util import character from exabgp.util import concat_bytes_i +from exabgp.util import hexstring # =========================================================== RouteDistinguisher @@ -58,7 +59,7 @@ elif t == 2: rd = '%d:%d' % ((c1 << 16) + c2,c3) else: - rd = str(self.rd) + rd = hexstring(self.rd) return rd def json (self): diff -Nru exabgp-4.0.6/lib/exabgp/configuration/configuration.py exabgp-4.0.8/lib/exabgp/configuration/configuration.py --- exabgp-4.0.6/lib/exabgp/configuration/configuration.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/configuration/configuration.py 2018-07-11 13:43:27.000000000 +0000 @@ -376,6 +376,7 @@ def _rollback_reload (self): self.neighbors = self._previous_neighbors + self.processes = self.process.processes self._neighbors = {} self._previous_neighbors = {} @@ -431,6 +432,7 @@ if self.section('root') is not True: # XXX: Should it be in neighbor ? + self.process.add_api() self._rollback_reload() return self.error.set( diff -Nru exabgp-4.0.6/lib/exabgp/configuration/core/tokeniser.py exabgp-4.0.8/lib/exabgp/configuration/core/tokeniser.py --- exabgp-4.0.6/lib/exabgp/configuration/core/tokeniser.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/configuration/core/tokeniser.py 2018-07-11 13:43:27.000000000 +0000 @@ -51,7 +51,7 @@ @staticmethod def _off (): - raise StopIteration() + return iter([]) def __init__ (self, scope, error, logger): self.scope = scope @@ -123,13 +123,21 @@ def _source (fname): with open(fname,'r') as fileobject: def formated (): - while True: - line = six.next(fileobject).rstrip() + line = '' + for current in fileobject: self.index_line += 1 - while line.endswith('\\'): - line = line[:-1] + six.next(fileobject).rstrip() - self.index_line += 1 + current = current.rstrip() + if current.endswith('\\'): + line += current + continue + elif line: + yield line + current + line = '' + else: + yield current + if line: yield line + for _ in self._tokenise(formated()): yield _ self.type = 'file' diff -Nru exabgp-4.0.6/lib/exabgp/configuration/flow/parser.py exabgp-4.0.8/lib/exabgp/configuration/flow/parser.py --- exabgp-4.0.6/lib/exabgp/configuration/flow/parser.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/configuration/flow/parser.py 2018-07-11 13:43:27.000000000 +0000 @@ -35,6 +35,7 @@ from exabgp.bgp.message.update.attribute.community.extended import TrafficRate from exabgp.bgp.message.update.attribute.community.extended import TrafficAction from exabgp.bgp.message.update.attribute.community.extended import TrafficRedirect +from exabgp.bgp.message.update.attribute.community.extended import TrafficRedirectASN4 from exabgp.bgp.message.update.attribute.community.extended import TrafficMark from exabgp.bgp.message.update.attribute.community.extended import TrafficNextHop @@ -274,8 +275,13 @@ asn = int(prefix) route_target = int(suffix) + + if asn >= pow(2, 32): + raise ValueError('asn is a 32 bits number, value too large %s' % asn) if asn >= pow(2,16): - raise ValueError('asn is a 32 bits number, it can only be 16 bit %s' % route_target) + if route_target >= pow(2, 16): + raise ValueError('asn is a 32 bits number, route target can only be 16 bit %s' % route_target) + return NoNextHop, ExtendedCommunities().add(TrafficRedirectASN4(asn, route_target)) if route_target >= pow(2,32): raise ValueError('route target is a 32 bits number, value too large %s' % route_target) return NoNextHop,ExtendedCommunities().add(TrafficRedirect(asn,route_target)) diff -Nru exabgp-4.0.6/lib/exabgp/configuration/neighbor/__init__.py exabgp-4.0.8/lib/exabgp/configuration/neighbor/__init__.py --- exabgp-4.0.6/lib/exabgp/configuration/neighbor/__init__.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/configuration/neighbor/__init__.py 2018-07-11 13:43:27.000000000 +0000 @@ -221,8 +221,11 @@ neighbor.local_address = None neighbor.md5_ip = None - if not neighbor.router_id and neighbor.peer_address.afi == AFI.ipv4 and not neighbor.auto_discovery: - neighbor.router_id = neighbor.local_address + if not neighbor.router_id: + if neighbor.peer_address.afi == AFI.ipv4 and not neighbor.auto_discovery: + neighbor.router_id = neighbor.local_address + else: + return self.error.set('missing router-id for the peer, it can not be set using the local-ip') if neighbor.route_refresh: if neighbor.adj_rib_out: diff -Nru exabgp-4.0.6/lib/exabgp/protocol/ip/__init__.py exabgp-4.0.8/lib/exabgp/protocol/ip/__init__.py --- exabgp-4.0.6/lib/exabgp/protocol/ip/__init__.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/protocol/ip/__init__.py 2018-07-11 13:43:27.000000000 +0000 @@ -226,6 +226,12 @@ def __str__ (self): return 'no-nexthop' + def __deepcopy__(self, _): + return self + + def __copy__(self, _): + return self + NoNextHop = _NoNextHop() diff -Nru exabgp-4.0.6/lib/exabgp/reactor/api/command/announce.py exabgp-4.0.8/lib/exabgp/reactor/api/command/announce.py --- exabgp-4.0.6/lib/exabgp/reactor/api/command/announce.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/api/command/announce.py 2018-07-11 13:43:27.000000000 +0000 @@ -60,7 +60,7 @@ reactor.processes.answer(service,'error') yield True - reactor.async.schedule(service,line,callback()) + reactor.asynchronous.schedule(service,line,callback()) return True @@ -110,7 +110,7 @@ reactor.processes.answer(service,'error') yield True - reactor.async.schedule(service,line,callback()) + reactor.asynchronous.schedule(service,line,callback()) return True @@ -149,7 +149,7 @@ reactor.processes.answer(service,'error') yield True - reactor.async.schedule(service,line,callback()) + reactor.asynchronous.schedule(service,line,callback()) return True @@ -192,7 +192,7 @@ reactor.processes.answer(service,'error') yield True - reactor.async.schedule(service,line,callback()) + reactor.asynchronous.schedule(service,line,callback()) return True @@ -231,7 +231,7 @@ reactor.processes.answer(service,'error') yield True - reactor.async.schedule(service,line,callback()) + reactor.asynchronous.schedule(service,line,callback()) return True @@ -273,7 +273,7 @@ reactor.processes.answer(service,'error') yield True - reactor.async.schedule(service,line,callback()) + reactor.asynchronous.schedule(service,line,callback()) return True @@ -312,7 +312,7 @@ reactor.processes.answer(service,'error') yield True - reactor.async.schedule(service,line,callback()) + reactor.asynchronous.schedule(service,line,callback()) return True @@ -354,7 +354,7 @@ reactor.processes.answer(service,'error') yield True - reactor.async.schedule(service,line,callback()) + reactor.asynchronous.schedule(service,line,callback()) return True @@ -381,7 +381,7 @@ self.log_failure('no neighbor matching the command : %s' % command) reactor.processes.answer(service,'error') return False - reactor.async.schedule(service,command,callback(self,command,peers)) + reactor.asynchronous.schedule(service,command,callback(self,command,peers)) return True except ValueError: self.log_failure('issue parsing the command') @@ -416,7 +416,7 @@ self.log_failure('no neighbor matching the command : %s' % command) reactor.processes.answer(service,'error') return False - reactor.async.schedule(service,command,callback(self,command,peers)) + reactor.asynchronous.schedule(service,command,callback(self,command,peers)) return True except ValueError: self.log_failure('issue parsing the command') @@ -457,7 +457,7 @@ self.log_failure('no neighbor matching the command : %s' % command) reactor.processes.answer(service,'error') return False - reactor.async.schedule(service,command,callback(self,command,peers)) + reactor.asynchronous.schedule(service,command,callback(self,command,peers)) return True except ValueError: self.log_failure('issue parsing the command') diff -Nru exabgp-4.0.6/lib/exabgp/reactor/api/command/neighbor.py exabgp-4.0.8/lib/exabgp/reactor/api/command/neighbor.py --- exabgp-4.0.6/lib/exabgp/reactor/api/command/neighbor.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/api/command/neighbor.py 2018-07-11 13:43:27.000000000 +0000 @@ -13,6 +13,8 @@ from exabgp.reactor.api.command.limit import match_neighbor from exabgp.reactor.api.command.limit import extract_neighbors +from exabgp.reactor.api.response.answer import Answer + def register_neighbor (): pass @@ -60,11 +62,15 @@ @classmethod def extensive (cls,answer): + if answer['duration']: + duration = cls.extensive_kv % ('up for', timedelta(seconds=answer['duration']), '', '') + else: + duration = cls.extensive_kv % ('down for', timedelta(seconds=answer['down']), '', '') formated = { 'peer-address': answer['peer-address'], 'local-address': cls.extensive_kv % ('local',answer['local-address'],'',''), 'state': cls.extensive_kv % ('state',answer['state'],'',''), - 'duration': cls.extensive_kv % ('up for',timedelta(seconds=answer['duration']),'',''), + 'duration': duration, 'as': cls.extensive_kv % ('AS',answer['local-as'],_pr(answer['peer-as']),''), 'id': cls.extensive_kv % ('ID',answer['local-id'],_pr(answer['peer-id']),''), 'hold': cls.extensive_kv % ('hold-time',answer['local-hold'],_pr(answer['peer-hold']),''), @@ -100,10 +106,10 @@ reactor.processes.answer_done(service) return True except ValueError: - reactor.processes.answer(service,'error') + reactor.processes.answer(service,Answer.error) return False except IndexError: - reactor.processes.answer(service,'error') + reactor.processes.answer(service,Answer.error) return False @@ -154,7 +160,7 @@ peer = reactor.peers.get(peer_name,None) if not peer: continue - if limit and limit not in peer.neighbor.name(): + if limit and limit != str(peer.neighbor.peer_address): continue for line in Neighbor.summary(peer.cli_data()).split('\n'): reactor.processes.answer(service,line) @@ -162,17 +168,17 @@ reactor.processes.answer_done(service) if summary: - reactor.async.schedule(service,command,callback_summary()) + reactor.asynchronous.schedule(service,command,callback_summary()) return True if extensive: - reactor.async.schedule(service,command,callback_extensive()) + reactor.asynchronous.schedule(service, command, callback_extensive()) return True if configuration: - reactor.async.schedule(service,command,callback_configuration()) + reactor.asynchronous.schedule(service, command, callback_configuration()) return True reactor.processes.answer(service,'please specify summary, extensive or configuration') - reactor.processes.answer(service,'you can filter by per ip address adding it after the word neighbor') + reactor.processes.answer(service,'you can filter by peer ip address adding it after the word neighbor') reactor.processes.answer_done(service) diff -Nru exabgp-4.0.6/lib/exabgp/reactor/api/command/reactor.py exabgp-4.0.8/lib/exabgp/reactor/api/command/reactor.py --- exabgp-4.0.6/lib/exabgp/reactor/api/command/reactor.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/api/command/reactor.py 2018-07-11 13:43:27.000000000 +0000 @@ -81,7 +81,7 @@ @Command.register('text','reset',False) def reset (self, reactor, service, line): - reactor.async.clear(service) + reactor.asynchronous.clear(service) @Command.register('text','crash') @@ -89,5 +89,5 @@ def callback(): raise ValueError('crash test of the API') yield None - reactor.async.schedule(service,line,callback()) + reactor.asynchronous.schedule(service, line, callback()) return True diff -Nru exabgp-4.0.6/lib/exabgp/reactor/api/command/rib.py exabgp-4.0.8/lib/exabgp/reactor/api/command/rib.py --- exabgp-4.0.6/lib/exabgp/reactor/api/command/rib.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/api/command/rib.py 2018-07-11 13:43:27.000000000 +0000 @@ -17,6 +17,8 @@ from exabgp.bgp.message.update.nlri.vpls import VPLS from exabgp.bgp.message.update.nlri.evpn.nlri import EVPN +from exabgp.reactor.api.response.answer import Answer + from exabgp.configuration.environment import environment @@ -65,11 +67,11 @@ elif words[1] == 'adj-rib-out': rib = 'out' else: - reactor.processes.answer(service,"error") + reactor.processes.answer(service,Answer.error) return False if rib not in ('in','out'): - reactor.processes.answer(service,"error") + reactor.processes.answer(service,Answer.error) return False klass = NLRI @@ -86,7 +88,7 @@ words.remove(remove) last = '' if not words else words[0] callback = _show_adjrib_callback(reactor, service, last, klass, False, rib, extensive) - reactor.async.schedule(service,line,callback()) + reactor.asynchronous.schedule(service, line, callback()) return True @@ -107,17 +109,17 @@ peers = match_neighbors(reactor.peers,descriptions) if not peers: self.log_failure('no neighbor matching the command : %s' % command,'warning') - reactor.processes.answer(service,'error') + reactor.processes.answer(service,Answer.error) return False - reactor.async.schedule(service,command,callback(self,peers)) + reactor.asynchronous.schedule(service, command, callback(self, peers)) return True except ValueError: self.log_failure('issue parsing the command') - reactor.processes.answer(service,'error') + reactor.processes.answer(service, Answer.error) return False except IndexError: self.log_failure('issue parsing the command') - reactor.processes.answer(service,'error') + reactor.processes.answer(service, Answer.error) return False @@ -142,17 +144,17 @@ peers = match_neighbors(reactor.peers,descriptions) if not peers: self.log_failure('no neighbor matching the command : %s' % command,'warning') - reactor.processes.answer(service,'error') + reactor.processes.answer(service, Answer.error) return False words = line.split() direction = 'in' if 'adj-rib-in' in words or 'in' in words else 'out' - reactor.async.schedule(service,command,callback(self,peers,direction)) + reactor.asynchronous.schedule(service, command, callback(self, peers, direction)) return True except ValueError: self.log_failure('issue parsing the command') - reactor.processes.answer(service,'error') + reactor.processes.answer(service, Answer.error) return False except IndexError: self.log_failure('issue parsing the command') - reactor.processes.answer(service,'error') + reactor.processes.answer(service, Answer.error) return False diff -Nru exabgp-4.0.6/lib/exabgp/reactor/api/command/watchdog.py exabgp-4.0.8/lib/exabgp/reactor/api/command/watchdog.py --- exabgp-4.0.6/lib/exabgp/reactor/api/command/watchdog.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/api/command/watchdog.py 2018-07-11 13:43:27.000000000 +0000 @@ -31,7 +31,7 @@ name = line.split(' ')[2] except IndexError: name = service - reactor.async.schedule(service,line,callback(name)) + reactor.asynchronous.schedule(service, line, callback(name)) return True @@ -52,5 +52,5 @@ name = line.split(' ')[2] except IndexError: name = service - reactor.async.schedule(service,line,callback(name)) + reactor.asynchronous.schedule(service, line, callback(name)) return True diff -Nru exabgp-4.0.6/lib/exabgp/reactor/api/__init__.py exabgp-4.0.8/lib/exabgp/reactor/api/__init__.py --- exabgp-4.0.6/lib/exabgp/reactor/api/__init__.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/api/__init__.py 2018-07-11 13:43:27.000000000 +0000 @@ -20,6 +20,8 @@ from exabgp.reactor.api.command import Command from exabgp.configuration.configuration import Configuration +from exabgp.reactor.api.response.answer import Answer + # ======================================================================= Parser # @@ -42,7 +44,7 @@ for registered in self.functions: if registered == command or registered + ' ' in command: return self.callback['text'][registered](self,reactor,service,command) - reactor.processes.answer(service,'error') + reactor.processes.answer(service,Answer.error) self.logger.warning('command from process not understood : %s' % command,'api') return False @@ -104,7 +106,7 @@ safi = SAFI.value(tokens.pop(0)) if afi is None or safi is None: return False - return RouteRefresh(afi,safi) + return [RouteRefresh(afi,safi)] def api_eor (self, command): tokens = formated(command).split(' ')[2:] diff -Nru exabgp-4.0.6/lib/exabgp/reactor/api/processes.py exabgp-4.0.8/lib/exabgp/reactor/api/processes.py --- exabgp-4.0.6/lib/exabgp/reactor/api/processes.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/api/processes.py 2018-07-11 13:43:27.000000000 +0000 @@ -20,6 +20,8 @@ from exabgp.configuration.core.format import formated from exabgp.reactor.api.response import Response +from exabgp.reactor.api.response.answer import Answer + from exabgp.bgp.message import Message from exabgp.logger import Logger @@ -195,44 +197,45 @@ self._handle_problem(process) return r,_,_ = select.select([proc.stdout,],[],[],0) - if r: - try: - # Calling next() on Linux and OSX works perfectly well - # but not on OpenBSD where it always raise StopIteration - # and only readline() works - buf = str_ascii(proc.stdout.read(16384)) - if buf == '' and poll is not None: - # if proc.poll() is None then - # process is fine, we received an empty line because - # we're doing .readline() on a non-blocking pipe and - # the process maybe has nothing to send yet - self._handle_problem(process) - continue - - raw = self._buffer.get(process,'') + buf - - while '\n' in raw: - line,raw = raw.split('\n',1) - line = line.rstrip() - consumed_data = True - self.logger.debug('command from process %s : %s ' % (process,line),'process') - yield (process,formated(line)) - - self._buffer[process] = raw - - except IOError as exc: - if not exc.errno or exc.errno in error.fatal: - # if the program exits we can get an IOError with errno code zero ! - self._handle_problem(process) - elif exc.errno in error.block: - # we often see errno.EINTR: call interrupted and - # we most likely have data, we will try to read them a the next loop iteration - pass - else: - self.logger.debug('unexpected errno received from forked process (%s)' % errstr(exc),'process') - except StopIteration: - if not consumed_data: - self._handle_problem(process) + if not r: + continue + try: + # Calling next() on Linux and OSX works perfectly well + # but not on OpenBSD where it always raise StopIteration + # and only readline() works + buf = str_ascii(proc.stdout.read(16384)) + if buf == '' and poll is not None: + # if proc.poll() is None then + # process is fine, we received an empty line because + # we're doing .readline() on a non-blocking pipe and + # the process maybe has nothing to send yet + self._handle_problem(process) + continue + + raw = self._buffer.get(process,'') + buf + + while '\n' in raw: + line,raw = raw.split('\n',1) + line = line.rstrip() + consumed_data = True + self.logger.debug('command from process %s : %s ' % (process,line),'process') + yield (process,formated(line)) + + self._buffer[process] = raw + + except IOError as exc: + if not exc.errno or exc.errno in error.fatal: + # if the program exits we can get an IOError with errno code zero ! + self._handle_problem(process) + elif exc.errno in error.block: + # we often see errno.EINTR: call interrupted and + # we most likely have data, we will try to read them a the next loop iteration + pass + else: + self.logger.debug('unexpected errno received from forked process (%s)' % errstr(exc),'process') + except StopIteration: + if not consumed_data: + self._handle_problem(process) except (subprocess.CalledProcessError,OSError,ValueError): self._handle_problem(process) @@ -266,14 +269,14 @@ def answer (self, service, string, force=False): if force or self.ack: - self.logger.debug('responding to %s : %s' % (service,string.replace('\n','\\n')),'process') + self.logger.debug('responding to %s : %s' % (service,string.replace('\n', '\\n')), 'process') self._write(service,string) def answer_done (self, service): - self.answer(service,'done') + self.answer(service,Answer.done) def answer_error (self, service): - self.answer(service,'error') + self.answer(service,Answer.error) def _notify (self, neighbor, event): for process in neighbor.api[event]: diff -Nru exabgp-4.0.6/lib/exabgp/reactor/api/response/answer.py exabgp-4.0.8/lib/exabgp/reactor/api/response/answer.py --- exabgp-4.0.6/lib/exabgp/reactor/api/response/answer.py 1970-01-01 00:00:00.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/api/response/answer.py 2018-07-11 13:43:27.000000000 +0000 @@ -0,0 +1,6 @@ +class Answer: + error = 'error' + done = 'done' + shutdown = 'shutdown' + + buffer_size = max(len(error),len(done),len(shutdown)) \ No newline at end of file diff -Nru exabgp-4.0.6/lib/exabgp/reactor/api/response/json.py exabgp-4.0.8/lib/exabgp/reactor/api/response/json.py --- exabgp-4.0.6/lib/exabgp/reactor/api/response/json.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/api/response/json.py 2018-07-11 13:43:27.000000000 +0000 @@ -284,37 +284,37 @@ def refresh (self, neighbor, direction, refresh, negotiated, header, body): return self._header(self._neighbor(neighbor,direction,self._kv({ 'route-refresh': '{ %s }' % self._kv({ - 'afi': refresh.afi, - 'safi': refresh.safi, - 'subtype': refresh.reserved + 'afi': '"%s"' % refresh.afi, + 'safi': '"%s"' % refresh.safi, + 'subtype': '"%s"' % refresh.reserved }) })),header,body,neighbor,message_type='refresh') def _operational_query (self, neighbor, direction, operational, header, body): return self._header(self._neighbor(neighbor,direction,self._kv({ 'operational': '{ %s }' % self._kv({ - 'name': operational.name, - 'afi': operational.afi, - 'safi': operational.safi, + 'name': '"%s"' % operational.name, + 'afi': '"%s"' % operational.afi, + 'safi': '"%s"' % operational.safi, }) })),header,body,neighbor,message_type='operational') def _operational_advisory (self, neighbor, direction, operational, header, body): return self._header(self._neighbor(neighbor,direction,self._kv({ 'operational': '{ %s }' % self._kv({ - 'name': operational.name, - 'afi': operational.afi, - 'safi': operational.safi, - 'advisory': operational.data + 'name': '"%s"' % operational.name, + 'afi': '"%s"' % operational.afi, + 'safi': '"%s"' % operational.safi, + 'advisory': '"%s"' % operational.data }) })),header,body,neighbor,message_type='operational') def _operational_counter (self, neighbor, direction, operational, header, body): return self._header(self._neighbor(neighbor,direction,self._kv({ 'operational': '{ %s }' % self._kv({ - 'name': operational.name, - 'afi': operational.afi, - 'safi': operational.safi, + 'name': '"%s"' % operational.name, + 'afi': '"%s"' % operational.afi, + 'safi': '"%s"' % operational.safi, 'router-id': operational.routerid, 'sequence': operational.sequence, 'counter': operational.counter diff -Nru exabgp-4.0.6/lib/exabgp/reactor/asynchronous.py exabgp-4.0.8/lib/exabgp/reactor/asynchronous.py --- exabgp-4.0.6/lib/exabgp/reactor/asynchronous.py 1970-01-01 00:00:00.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/asynchronous.py 2018-07-11 13:43:27.000000000 +0000 @@ -0,0 +1,65 @@ +# encoding: utf-8 +""" +reactor/async.py + +Created by Thomas Mangin on 2017-07-01. +Copyright (c) 2009-2017 Exa Networks. All rights reserved. +License: 3-clause BSD. (See the COPYRIGHT file) +""" + +from collections import deque + +from exabgp.logger import Logger +from exabgp.vendoring import six + +class ASYNC (object): + LIMIT = 500 + + def __init__ (self): + self.logger = Logger() + self._async = deque() + + def ready (self): + return len(self._async) > 0 + + def schedule (self, uid, command, callback): + self.logger.debug('async | %s | %s' % (uid,command),'reactor') + self._async.append((uid,callback)) + + def clear (self, deluid=None): + if not self._async: + return + if deluid is None: + # We could delete all the generators just to be safe + self._async = deque() + return + running = deque() + for (uid,generator) in self._async: + if uid != deluid: + running.append((uid,generator)) + self._async = running + + def run (self): + if not self.ready(): + return False + + length = range(min(len(self._async),self.LIMIT)) + uid, generator = self._async.popleft() + + for _ in length: + try: + six.next(generator) + six.next(generator) + except StopIteration: + if not self._async: + return False + uid, generator = self._async.popleft() + except KeyboardInterrupt: + raise + except Exception as exc: + self.logger.error('async | %s | problem with function' % uid,'reactor') + for line in str(exc).split('\n'): + self.logger.error('async | %s | %s' % (uid,line),'reactor') + + self._async.appendleft((uid, generator)) + return True diff -Nru exabgp-4.0.6/lib/exabgp/reactor/async.py exabgp-4.0.8/lib/exabgp/reactor/async.py --- exabgp-4.0.6/lib/exabgp/reactor/async.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/async.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,65 +0,0 @@ -# encoding: utf-8 -""" -reactor/async.py - -Created by Thomas Mangin on 2017-07-01. -Copyright (c) 2009-2017 Exa Networks. All rights reserved. -License: 3-clause BSD. (See the COPYRIGHT file) -""" - -from collections import deque - -from exabgp.logger import Logger -from exabgp.vendoring import six - -class ASYNC (object): - LIMIT = 500 - - def __init__ (self): - self.logger = Logger() - self._async = deque() - - def ready (self): - return len(self._async) > 0 - - def schedule (self, uid, command, callback): - self.logger.debug('async | %s | %s' % (uid,command),'reactor') - self._async.append((uid,callback)) - - def clear (self, deluid=None): - if not self._async: - return - if deluid is None: - # We could delete all the generators just to be safe - self._async = deque() - return - running = deque() - for (uid,generator) in self._async: - if uid != deluid: - running.append((uid,generator)) - self._async = running - - def run (self): - if not self.ready(): - return False - - length = range(min(len(self._async),self.LIMIT)) - uid, generator = self._async.popleft() - - for _ in length: - try: - six.next(generator) - six.next(generator) - except StopIteration: - if not self._async: - return False - uid, generator = self._async.popleft() - except KeyboardInterrupt: - raise - except Exception as exc: - self.logger.error('async | %s | problem with function' % uid,'reactor') - for line in str(exc).split('\n'): - self.logger.error('async | %s | %s' % (uid,line),'reactor') - - self._async.appendleft((uid, generator)) - return True diff -Nru exabgp-4.0.6/lib/exabgp/reactor/listener.py exabgp-4.0.8/lib/exabgp/reactor/listener.py --- exabgp-4.0.6/lib/exabgp/reactor/listener.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/listener.py 2018-07-11 13:43:27.000000000 +0000 @@ -155,6 +155,7 @@ ranged_neighbor = [] for connection in self._connected(): + self.logger.debug('new connection received %s' % connection.name(),'network') for key in reactor.peers: peer = reactor.peers[key] neighbor = peer.neighbor @@ -192,11 +193,13 @@ matched = len(ranged_neighbor) if matched > 1: self.logger.debug('could not accept connection from %s (more than one neighbor match)' % connection.name(),'network') - reactor.async.schedule(str(uuid.uuid1()),'sending notification (6,5)',connection.notification(6,5,'could not accept the connection (more than one neighbor match)')) + reactor.asynchronous.schedule(str(uuid.uuid1()), 'sending notification (6,5)', connection.notification( + 6, 5, 'could not accept the connection (more than one neighbor match)')) return if not matched: self.logger.debug('no session configured for %s' % connection.name(),'network') - reactor.async.schedule(str(uuid.uuid1()),'sending notification (6,3)',connection.notification(6,3,'no session configured for the peer')) + reactor.asynchronous.schedule(str(uuid.uuid1()), 'sending notification (6,3)', connection.notification( + 6, 3, 'no session configured for the peer')) return new_neighbor = copy.copy(ranged_neighbor[0]) diff -Nru exabgp-4.0.6/lib/exabgp/reactor/loop.py exabgp-4.0.8/lib/exabgp/reactor/loop.py --- exabgp-4.0.6/lib/exabgp/reactor/loop.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/loop.py 2018-07-11 13:43:27.000000000 +0000 @@ -21,7 +21,7 @@ from exabgp.reactor.api.processes import ProcessError from exabgp.reactor.peer import Peer from exabgp.reactor.peer import ACTION -from exabgp.reactor.async import ASYNC +from exabgp.reactor.asynchronous import ASYNC from exabgp.reactor.interrupt import Signal from exabgp.reactor.network.error import error @@ -34,6 +34,20 @@ class Reactor (object): + class Exit (object): + normal = 0 + validate = 0 + listening = 1 + configuration = 1 + privileges = 1 + log = 1 + pid = 1 + socket = 1 + io_error = 1 + process = 1 + select = 1 + unknown = 1 + # [hex(ord(c)) for c in os.popen('clear').read()] clear = concat_bytes_i(character(int(c,16)) for c in ['0x1b', '0x5b', '0x48', '0x1b', '0x5b', '0x32', '0x4a']) @@ -41,15 +55,18 @@ self._ips = environment.settings().tcp.bind self._port = environment.settings().tcp.port self._stopping = environment.settings().tcp.once + self.exit_code = self.Exit.unknown self.max_loop_time = environment.settings().reactor.speed + self._sleep_time = self.max_loop_time / 100 + self._busyspin = {} self.early_drop = environment.settings().daemon.drop self.processes = None self.configuration = Configuration(configurations) self.logger = Logger() - self.async = ASYNC() + self.asynchronous = ASYNC() self.signal = Signal() self.daemon = Daemon(self) self.listener = Listener(self) @@ -60,14 +77,25 @@ self._reload_processes = False self._saved_pid = False - def _termination (self,reason): + def _termination (self,reason, exit_code): + self.exit_code = exit_code self.signal.received = Signal.SHUTDOWN self.logger.critical(reason,'reactor') - def _api_ready (self,sockets,peers): - sleeptime = 0 if peers or self.async.ready() else self.max_loop_time / 100 + def _prevent_spin(self): + second = int(time.time()) + if not second in self._busyspin: + self._busyspin = {second: 0} + self._busyspin[second] += 1 + if self._busyspin[second] > self.max_loop_time: + time.sleep(self._sleep_time) + return True + return False + + def _api_ready (self,sockets,sleeptime): fds = self.processes.fds() ios = fds + sockets + try: read,_,_ = select.select(ios,[],[],sleeptime) for fd in fds: @@ -78,6 +106,7 @@ err_no,message = exc.args # pylint: disable=W0633 if err_no not in error.block: raise exc + self._prevent_spin() return [] except socket.error as exc: # python 3 does not raise on closed FD, but python2 does @@ -86,12 +115,14 @@ # (EBADF from python2 must be ignored if when checkign error.fatal) # otherwise sending notification causes TCP to drop and cause # this code to kill ExaBGP + self._prevent_spin() return [] except ValueError as exc: # The peer closing the TCP connection lead to a negative file descritor + self._prevent_spin() return [] except KeyboardInterrupt: - self._termination('^C received') + self._termination('^C received',self.Exit.normal) return [] def _active_peers (self): @@ -126,10 +157,10 @@ # but I can not see any way to avoid it for ip in self._ips: if not self.listener.listen_on(ip, None, self._port, None, False, None): - return False + return self.Exit.listening if not self.load(): - return False + return self.Exit.configuration if validate: # only validate configuration self.logger.warning('','configuration') @@ -139,12 +170,12 @@ for key in self.peers: self.logger.warning(str(self.peers[key].neighbor),'configuration') self.logger.warning('','configuration') - return True + return self.Exit.validate for neighbor in self.configuration.neighbors.values(): if neighbor.listen: if not self.listener.listen_on(neighbor.md5_ip, neighbor.peer_address, neighbor.listen, neighbor.md5_password, neighbor.md5_base64, neighbor.ttl_in): - return False + return self.Exit.listening if not self.early_drop: self.processes.start(self.configuration.processes) @@ -152,7 +183,7 @@ if not self.daemon.drop_privileges(): self.logger.critical('could not drop privileges to \'%s\' refusing to run as root' % self.daemon.user,'reactor') self.logger.critical('set the environmemnt value exabgp.daemon.user to change the unprivileged user','reactor') - return + return self.Exit.privileges if self.early_drop: self.processes.start(self.configuration.processes) @@ -160,10 +191,10 @@ # This is required to make sure we can write in the log location as we now have dropped root privileges if not self.logger.restart(): self.logger.critical('could not setup the logger, aborting','reactor') - return + return self.Exit.log if not self.daemon.savepid(): - return + return self.Exit.pid # did we complete the run of updates caused by the last SIGUSR1/SIGUSR2 ? reload_completed = False @@ -209,12 +240,19 @@ if self.listener.incoming(): # check all incoming connection - self.async.schedule(str(uuid.uuid1()),'check new connection',self.listener.new_connections()) + self.asynchronous.schedule(str(uuid.uuid1()),'checking for new connection(s)',self.listener.new_connections()) peers = self._active_peers() if self._completed(peers): reload_completed = True + sleep = self._sleep_time + + # do not attempt to listen on closed sockets even if the peer is still here + for io in list(workers.keys()): + if io.fileno() == -1: + del workers[io] + # give a turn to all the peers for key in list(peers): peer = self.peers[key] @@ -234,6 +272,8 @@ workers[io] = key # no need to come back to it before a a full cycle peers.discard(key) + elif action == ACTION.NOW: + sleep = 0 if not peers: break @@ -241,29 +281,32 @@ # read at least on message per process if there is some and parse it for service,command in self.processes.received(): self.api.text(self,service,command) + sleep = 0 - self.async.run() + self.asynchronous.run() - for io in self._api_ready(list(workers),peers): + for io in self._api_ready(list(workers),sleep): peers.add(workers[io]) del workers[io] if self._stopping and not self.peers.keys(): - self._termination('exiting on peer termination') + self._termination('exiting on peer termination',self.Exit.normal) except KeyboardInterrupt: - self._termination('^C received') + self._termination('^C received',self.Exit.normal) + except SystemExit: + self._termination('exiting', self.Exit.normal) # socket.error is a subclass of IOError (so catch it first) except socket.error: - self._termination('socket error received') + self._termination('socket error received',self.Exit.socket) except IOError: - self._termination('I/O Error received, most likely ^C during IO') - except SystemExit: - self._termination('exiting') + self._termination('I/O Error received, most likely ^C during IO',self.Exit.io_error) except ProcessError: - self._termination('Problem when sending message(s) to helper program, stopping') + self._termination('Problem when sending message(s) to helper program, stopping',self.Exit.process) except select.error: - self._termination('problem using select, stopping') + self._termination('problem using select, stopping',self.Exit.select) + + return self.exit_code def shutdown (self): """Terminate all the current BGP connections""" @@ -273,7 +316,7 @@ self.listener = None for key in self.peers.keys(): self.peers[key].stop() - self.async.clear() + self.asynchronous.clear() self.processes.terminate() self.daemon.removepid() self._stopping = True diff -Nru exabgp-4.0.6/lib/exabgp/reactor/network/connection.py exabgp-4.0.8/lib/exabgp/reactor/network/connection.py --- exabgp-4.0.6/lib/exabgp/reactor/network/connection.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/network/connection.py 2018-07-11 13:43:27.000000000 +0000 @@ -69,8 +69,8 @@ # Just in case .. def __del__ (self): if self.io: - self.logger.warning('connection to %s closed' % self.peer,self.session()) self.close() + self.logger.warning('connection to %s closed' % self.peer, self.session()) def name (self): return "%s-%d %s-%s" % (self.direction,self.id,self.local,self.peer) @@ -78,6 +78,12 @@ def session (self): return "%s-%d" % (self.direction,self.id) + def fd (self): + if self.io and self.io.fileno() != -1: + return self.io + # the socket is closed (fileno() == -1) or not open yet (io is None) + return None + def close (self): try: self.logger.warning('%s, closing connection' % self.name(),source=self.session()) @@ -87,7 +93,7 @@ except KeyboardInterrupt as exc: raise exc except Exception: - pass + self.io = None def reading (self): while True: diff -Nru exabgp-4.0.6/lib/exabgp/reactor/network/incoming.py exabgp-4.0.8/lib/exabgp/reactor/network/incoming.py --- exabgp-4.0.6/lib/exabgp/reactor/network/incoming.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/network/incoming.py 2018-07-11 13:43:27.000000000 +0000 @@ -2,7 +2,7 @@ from .connection import Connection from .tcp import nagle -from .tcp import async +from .tcp import asynchronous from .error import NetworkError from .error import NotConnected @@ -19,7 +19,7 @@ try: self.io = io - async(self.io,self.peer) + asynchronous(self.io, self.peer) nagle(self.io,self.peer) self.success() except NetworkError as exc: diff -Nru exabgp-4.0.6/lib/exabgp/reactor/network/outgoing.py exabgp-4.0.8/lib/exabgp/reactor/network/outgoing.py --- exabgp-4.0.6/lib/exabgp/reactor/network/outgoing.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/network/outgoing.py 2018-07-11 13:43:27.000000000 +0000 @@ -8,7 +8,7 @@ from .tcp import nagle from .tcp import TTL from .tcp import TTLv6 -from .tcp import async +from .tcp import asynchronous from .tcp import ready from .error import NetworkError @@ -35,7 +35,7 @@ TTLv6(self.io, self.peer, self.ttl) if local: bind(self.io,self.local,afi) - async(self.io,self.peer) + asynchronous(self.io, self.peer) connect(self.io,self.peer,port,afi,md5) if not self.local: self.local = self.io.getsockname()[0] @@ -51,19 +51,16 @@ yield False return - try: - generator = ready(self.io) - while True: - connected = six.next(generator) - if not connected: - yield False - continue - yield True - return - except StopIteration: - # self.io MUST NOT be closed here, it is closed by the caller - yield False - return + generator = ready(self.io) + for connected in generator: + if not connected: + yield False + continue + yield True + yield False + # self.io MUST NOT be closed here, it is closed by the caller + return + nagle(self.io,self.peer) # Not working after connect() at least on FreeBSD TTL(self.io,self.peer,self.ttl) diff -Nru exabgp-4.0.6/lib/exabgp/reactor/network/tcp.py exabgp-4.0.8/lib/exabgp/reactor/network/tcp.py --- exabgp-4.0.6/lib/exabgp/reactor/network/tcp.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/network/tcp.py 2018-07-11 13:43:27.000000000 +0000 @@ -165,12 +165,12 @@ if md5: if md5_bytes is None: md5_bytes = bytes_ascii(md5) - key = pack('2xH4x%ds' % TCP_MD5SIG_MAXKEYLEN, len(md5_bytes), md5_bytes) - else: - key = pack('2xH4x%ds' % TCP_MD5SIG_MAXKEYLEN, 0, b'') + key = pack('2xH4x%ds' % TCP_MD5SIG_MAXKEYLEN, len(md5_bytes), md5_bytes) + else: + key = pack('2xH4x%ds' % TCP_MD5SIG_MAXKEYLEN, 0, b'') - TCP_MD5SIG = 14 - io.setsockopt(socket.IPPROTO_TCP, TCP_MD5SIG, sockaddr + key) + TCP_MD5SIG = 14 + io.setsockopt(socket.IPPROTO_TCP, TCP_MD5SIG, sockaddr + key) except socket.error as exc: if exc.errno != errno.ENOENT: raise MD5Error('This linux machine does not support TCP_MD5SIG, you can not use MD5 (%s)' % errstr(exc)) @@ -219,7 +219,7 @@ raise TTLError('This OS does not support IP_MINTTL or IP_TTL (ttl-security) for %s (%s)' % (ip,errstr(exc))) -def async (io, ip): +def asynchronous(io, ip): try: io.setblocking(0) except socket.error as exc: diff -Nru exabgp-4.0.6/lib/exabgp/reactor/peer.py exabgp-4.0.8/lib/exabgp/reactor/peer.py --- exabgp-4.0.6/lib/exabgp/reactor/peer.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/peer.py 2018-07-11 13:43:27.000000000 +0000 @@ -67,6 +67,9 @@ pass +class Stop (Exception): + pass + # ======================================================================== Peer # Present a File like interface to socket.socket @@ -94,7 +97,8 @@ self.stats = { 'fsm': self.fsm, 'creation': now, - 'complete': now, + 'reset': now, + 'complete': 0, } self.generator = None @@ -122,7 +126,8 @@ self.stats = { 'fsm': self.fsm, 'creation': self.stats['creation'], - 'complete': self.stats['creation'], + 'reset': time.time(), + 'complete': 0, } if self.proto: self.proto.close(u"peer reset, message [{0}] error[{1}]".format(message, error)) @@ -196,12 +201,15 @@ # sockets we must monitor def sockets (self): - ios = [] - if self.proto and self.proto.connection and self.proto.connection.io: - ios.append(self.proto.connection.io) - return ios + if self.proto: + fd = self.proto.fd() + if fd: + return [fd] + return [] def handle_connection (self, connection): + self.logger.debug("state machine for the peer is %s" % self.fsm.name(), self.id()) + # if the other side fails, we go back to idle if self.fsm == FSM.ESTABLISHED: self.logger.debug('we already have a peer in state established for %s' % connection.name(),self.id()) @@ -224,7 +232,9 @@ # accept the connection if self.proto: + self.logger.debug('closing outgoing connection as we have another incoming on with higher router-id for %s' % connection.name(),self.id()) self.proto.close('closing outgoing connection as we have another incoming on with higher router-id') + self.proto = Protocol(self).accept(connection) self.generator = None # Let's make sure we do some work with this connection @@ -253,14 +263,15 @@ connected = False try: - while not connected: + for connected in generator: + if connected: + break if self._teardown: - raise StopIteration() - connected = six.next(generator) + raise Stop() # we want to come back as soon as possible yield ACTION.LATER self.proto = proto - except StopIteration: + except Stop: # Connection failed if not connected and self.proto: self.proto.close('connection to %s:%d failed' % (self.neighbor.peer_address,self.neighbor.connect)) @@ -695,6 +706,7 @@ messages['total'] = (total_sent, total_rcvd) return { + 'down': int(self.stats['reset'] - self.stats['creation']), 'duration': int(time.time() - self.stats['complete']) if self.stats['complete'] else 0, 'local-address': str(self.neighbor.local_address), 'peer-address': str(self.neighbor.peer_address), diff -Nru exabgp-4.0.6/lib/exabgp/reactor/protocol.py exabgp-4.0.8/lib/exabgp/reactor/protocol.py --- exabgp-4.0.6/lib/exabgp/reactor/protocol.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/reactor/protocol.py 2018-07-11 13:43:27.000000000 +0000 @@ -77,6 +77,11 @@ from exabgp.configuration.environment import environment self.log_routes = peer.neighbor.adj_rib_in or environment.settings().log.routes + def fd (self): + if self.connection is None: + return None + return self.connection.fd() + # XXX: we use self.peer.neighbor.peer_address when we could use self.neighbor.peer_address def me (self, message): @@ -109,23 +114,16 @@ if self.neighbor.router_id is None and self.neighbor.local_address.afi == AFI.ipv4: self.neighbor.router_id = self.neighbor.local_address - try: - generator = self.connection.establish() - while True: - connected = six.next(generator) - if not connected: - yield False - continue - if self.peer.neighbor.api['neighbor-changes']: - self.peer.reactor.processes.connected(self.peer.neighbor) - yield True - return - except StopIteration: - # close called by the caller - # self.close('could not connect to remote end') - yield False + for connected in self.connection.establish(): + if not connected: + yield False + continue + if self.peer.neighbor.api['neighbor-changes']: + self.peer.reactor.processes.connected(self.peer.neighbor) + yield True return + def close (self, reason='protocol closed, reason unspecified'): if self.connection: self.logger.debug(reason,self.connection.session()) diff -Nru exabgp-4.0.6/lib/exabgp/version.py exabgp-4.0.8/lib/exabgp/version.py --- exabgp-4.0.6/lib/exabgp/version.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/lib/exabgp/version.py 2018-07-11 13:43:27.000000000 +0000 @@ -1,6 +1,6 @@ import os -release = "4.0.6-daa3d6ba" +release = "4.0.8-793a2931" json = "4.0.1" text = "4.0.1" version = os.environ.get('EXABGP_VERSION',release) diff -Nru exabgp-4.0.6/qa/bin/parsing exabgp-4.0.8/qa/bin/parsing --- exabgp-4.0.6/qa/bin/parsing 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/qa/bin/parsing 2018-07-11 13:43:27.000000000 +0000 @@ -42,7 +42,7 @@ else printf "failed\n" printf "\n" - printf "env exabgp.debug.configuration=true exabgp.tcp.bind='' exabgp.debug.selfcheck=true $path/sbin/exabgp -d -p $path/etc/exabgp/$conf 2>&1" + printf "env exabgp_debug_configuration=true exabgp_tcp_bind='' exabgp_debug_selfcheck=true $path/sbin/exabgp -d -p $path/etc/exabgp/$conf 2>&1" printf "\n\n" printf "$result" printf "\n\n" diff -Nru exabgp-4.0.6/qa/ci/api-flow.msg exabgp-4.0.8/qa/ci/api-flow.msg --- exabgp-4.0.6/qa/ci/api-flow.msg 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/qa/ci/api-flow.msg 2018-07-11 13:43:27.000000000 +0000 @@ -1,6 +1,7 @@ 1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:001E:02:00000007900F0003000185 +1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0025:02:0000000E4001010040020040050400000064 2:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:003F:02:000000284001010040020040050400000064800F170001851301200000000002200000000003810605910C38 -3:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:004C:02:000000354001010040020040050400000064C010088006000000000000800E1900018500001301200000000002200000000003810605910C38 -4:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0045:02:0000002E4001010040020040050400000064C0100880060000477FFF00800E1200018500000C0120FFFFFFFF0220FFFFFFFF -5:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:003F:02:000000284001010040020040050400000064800F170001851301200000000002200000000003810605910C38 -6:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:004C:02:000000354001010040020040050400000064C010088006000000000000800E1900018500001301200000000002200000000003810605910C38 +2:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:004C:02:000000354001010040020040050400000064C010088006000000000000800E1900018500001301200000000002200000000003810605910C38 +2:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0045:02:0000002E4001010040020040050400000064C0100880060000477FFF00800E1200018500000C0120FFFFFFFF0220FFFFFFFF +2:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:003F:02:000000284001010040020040050400000064800F170001851301200000000002200000000003810605910C38 +2:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:004C:02:000000354001010040020040050400000064C010088006000000000000800E1900018500001301200000000002200000000003810605910C38 diff -Nru exabgp-4.0.6/qa/ci/api-reload.msg exabgp-4.0.8/qa/ci/api-reload.msg --- exabgp-4.0.6/qa/ci/api-reload.msg 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/qa/ci/api-reload.msg 2018-07-11 13:43:27.000000000 +0000 @@ -6,5 +6,5 @@ A2:signal:SIGUSR1 A3:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0030:02:0000001540010100400200400304040000004005040000006418010000 -A3:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0030:02:0000001540010100400200400304040000004005040000006418020000 A3:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0030:02:0000001540010100400200400304040000004005040000006418030000 +A3:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:001B:02:0004180200000000 diff -Nru exabgp-4.0.6/qa/tests/decode_test.py exabgp-4.0.8/qa/tests/decode_test.py --- exabgp-4.0.6/qa/tests/decode_test.py 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/qa/tests/decode_test.py 2018-07-11 13:43:27.000000000 +0000 @@ -317,6 +317,7 @@ hold_time = HoldTime(180) asn4 = False add_path = 0 + extended_message = False # capability route_refresh = False diff -Nru exabgp-4.0.6/README.md exabgp-4.0.8/README.md --- exabgp-4.0.6/README.md 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/README.md 2018-07-11 13:43:27.000000000 +0000 @@ -1,6 +1,5 @@ [![License](https://img.shields.io/pypi/l/exabgp.svg)](https://github.com/Exa-Networks/exabgp/blob/master/COPYRIGHT) [![PyPI](https://img.shields.io/pypi/v/exabgp.svg)](https://pypi.python.org/pypi/exabgp) -[![PyPI Downloads](https://img.shields.io/pypi/dm/exabgp.svg)](https://pypi.python.org/pypi/exabgp) [![PyPI Status](https://img.shields.io/pypi/status/exabgp.svg)](https://pypi.python.org/pypi/exabgp) [![PyPI Wheel](https://img.shields.io/pypi/wheel/exabgp.svg)](https://pypi.python.org/pypi/exabgp) [![Gitter](https://badges.gitter.im/Exa-Networks/exabgp.svg)](https://gitter.im/Exa-Networks/exabgp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) @@ -25,7 +24,7 @@ [Cisco Systems](http://www.ciscoknowledgenetwork.com/files/452_06-11-14-20140610_v3_BGP_Optimizing_the_SDN-v1-0.pdf?), [CloudFlare](https://www.slideshare.net/TomPaseka/flowspec-apf-2013), [Dailymotion](https://github.com/pyke369/exabgp-helpers), -[Facebook](https://code.facebook.com/posts/1734309626831603/dhcplb-an-open-source-load-balancer/), +[Facebook](https://code.facebook.com/posts/1734.0.726831603/dhcplb-an-open-source-load-balancer/), [MaxCDN](https://blog.maxcdn.com/anycast-ip-routing-used-maxcdn/), [Microsoft](https://www.nanog.org/sites/default/files/wed.general.brainslug.lapukhov.20.pdf), [OpenDNS](https://blog.opendns.com/2013/01/10/high-availability-with-anycast-routing/), @@ -50,12 +49,12 @@ It is also possible to download the latest archive from github ```sh -> curl -L https://github.com/Exa-Networks/exabgp/archive/4.0.5.tar.gz | tar zx -> ./exabgp-4.0.5/sbin/exabgp --help +> curl -L https://github.com/Exa-Networks/exabgp/archive/4.0.7.tar.gz | tar zx +> ./exabgp-4.0.7/sbin/exabgp --help > ./bin/healthcheck --help ``` -If using `git`, for production deployment, please use the "3.4` branch. +If using `git`, for production deployment, use the "3.4` branch. ```sh > git clone https://github.com/Exa-Networks/exabgp.git @@ -71,8 +70,6 @@ ## Support -[![Testing Status](https://img.shields.io/codeship/d6c1ddd0-16a3-0132-5f85-2e35c05e22b1.svg)]() -[![Codacy Rating](https://www.codacy.com/project/badge/1f5fedb98bfd47bcb9ab868ea53ea210)](https://www.codacy.com/public/thomasmangin/exabgp_2) [![Landscape Code Quality](https://landscape.io/github/Exa-Networks/exabgp/master/landscape.svg)](https://landscape.io/github/Exa-Networks/exabgp/) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Exa-Networks/exabgp/badges/quality-score.png)](https://scrutinizer-ci.com/g/Exa-Networks/exabgp/) [![Coverage Status](https://img.shields.io/coveralls/Exa-Networks/exabgp.svg)](https://coveralls.io/r/Exa-Networks/exabgp) @@ -80,7 +77,8 @@ diff -Nru exabgp-4.0.6/redhat/python-exabgp.spec exabgp-4.0.8/redhat/python-exabgp.spec --- exabgp-4.0.6/redhat/python-exabgp.spec 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/redhat/python-exabgp.spec 2018-07-11 13:43:27.000000000 +0000 @@ -1,6 +1,6 @@ %{!?__python2: %global __python2 /usr/bin/python2} %{!?python2_sitelib: %global python2_sitelib %(%{__python2} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} -%define version %(echo "$(python setup.py current)") +%define version %(echo "$(python setup.py next)") Name: python-exabgp Version: %{version} @@ -92,6 +92,7 @@ %attr(744, root, root) %{_sysconfdir}/exabgp/examples/* %{_unitdir}/exabgp.service %{_unitdir}/exabgp@.service +%attr(644, root, root) %{_unitdir}/* %doc COPYRIGHT CHANGELOG README.md %{_mandir}/man1/* %{_mandir}/man5/* diff -Nru exabgp-4.0.6/redhat/python-exabgp.spec.git exabgp-4.0.8/redhat/python-exabgp.spec.git --- exabgp-4.0.6/redhat/python-exabgp.spec.git 2018-04-24 17:28:32.000000000 +0000 +++ exabgp-4.0.8/redhat/python-exabgp.spec.git 2018-07-11 13:43:27.000000000 +0000 @@ -1,6 +1,6 @@ %{!?__python2: %global __python2 /usr/bin/python2} %{!?python2_sitelib: %global python2_sitelib %(%{__python2} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} -%define version %(echo "$(python setup.py current).git$(git rev-parse --short $(python setup.py current))") +%define version %(echo "$(python setup.py next).git$(git rev-parse --short HEAD)") # Warning: This .spec is meant to be used with https://github.com/iovation/git-build-rpm # @@ -100,6 +100,7 @@ %attr(744, root, root) %{_sysconfdir}/exabgp/examples/* %{_unitdir}/exabgp.service %{_unitdir}/exabgp@.service +%attr(644, root, root) %{_unitdir}/* %doc COPYRIGHT CHANGELOG README.md %{_mandir}/man1/* %{_mandir}/man5/*