summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml25
-rw-r--r--INSTALL4
-rw-r--r--aclocal.m414
-rw-r--r--bird-gdb.py320
-rw-r--r--conf/conf.c4
-rw-r--r--conf/conf.h2
-rw-r--r--configure.ac46
-rw-r--r--doc/bird.sgml17
-rw-r--r--doc/threads/.gitignore2
-rw-r--r--doc/threads/00_filter_structure.pngbin0 -> 307458 bytes
-rw-r--r--doc/threads/00_the_name_of_the_game.md114
-rw-r--r--doc/threads/01_the_route_and_its_attributes.md159
-rw-r--r--doc/threads/02_asynchronous_export.md463
-rw-r--r--doc/threads/03_coroutines.md235
-rw-r--r--doc/threads/04_memory_management.md223
-rw-r--r--doc/threads/Makefile29
-rw-r--r--doc/threads/stats-draw.gnuplot41
-rw-r--r--doc/threads/stats-filter-2d.pl137
-rw-r--r--doc/threads/stats-filter.pl84
-rw-r--r--doc/threads/stats-longfilters.csv1964
-rw-r--r--filter/config.Y19
-rw-r--r--filter/data.h1
-rw-r--r--filter/decl.m424
-rw-r--r--filter/f-inst.c37
-rw-r--r--filter/filter.c176
-rw-r--r--filter/filter.h4
-rw-r--r--filter/filter_test.c1
-rw-r--r--filter/test.conf1
-rw-r--r--filter/tree_test.c2
-rw-r--r--lib/Makefile2
-rw-r--r--lib/birdlib.h8
-rw-r--r--lib/coro.h31
-rw-r--r--lib/event.c200
-rw-r--r--lib/event.h74
-rw-r--r--lib/event_test.c7
-rw-r--r--lib/flowspec_test.c11
-rw-r--r--lib/hash.h6
-rw-r--r--lib/hash_test.c6
-rw-r--r--lib/io-loop.h58
-rw-r--r--lib/lists.c27
-rw-r--r--lib/lists.h14
-rw-r--r--lib/locking.h66
-rw-r--r--lib/mempool.c21
-rw-r--r--lib/rcu.c79
-rw-r--r--lib/rcu.h55
-rw-r--r--lib/resource.c133
-rw-r--r--lib/resource.h21
-rw-r--r--lib/slab.c19
-rw-r--r--lib/socket.h6
-rw-r--r--lib/timer.c115
-rw-r--r--lib/timer.h42
-rw-r--r--nest/a-path_test.c17
-rw-r--r--nest/a-set_test.c17
-rw-r--r--nest/bfd.h12
-rw-r--r--nest/cli.c8
-rw-r--r--nest/cmds.c13
-rw-r--r--nest/config.Y20
-rw-r--r--nest/iface.c119
-rw-r--r--nest/iface.h18
-rw-r--r--nest/limit.h49
-rw-r--r--nest/neighbor.c143
-rw-r--r--nest/proto-hooks.c40
-rw-r--r--nest/proto.c1368
-rw-r--r--nest/protocol.h220
-rw-r--r--nest/route.h531
-rw-r--r--nest/rt-attr.c433
-rw-r--r--nest/rt-dev.c19
-rw-r--r--nest/rt-show.c103
-rw-r--r--nest/rt-table.c3228
-rw-r--r--proto/babel/babel.c155
-rw-r--r--proto/babel/babel.h1
-rw-r--r--proto/bfd/Makefile4
-rw-r--r--proto/bfd/bfd.c469
-rw-r--r--proto/bfd/bfd.h27
-rw-r--r--proto/bfd/config.Y1
-rw-r--r--proto/bfd/io.c535
-rw-r--r--proto/bfd/io.h34
-rw-r--r--proto/bfd/packets.c21
-rw-r--r--proto/bgp/attrs.c207
-rw-r--r--proto/bgp/bgp.c252
-rw-r--r--proto/bgp/bgp.h47
-rw-r--r--proto/bgp/config.Y1
-rw-r--r--proto/bgp/packets.c43
-rw-r--r--proto/mrt/mrt.c93
-rw-r--r--proto/mrt/mrt.h7
-rw-r--r--proto/ospf/iface.c15
-rw-r--r--proto/ospf/neighbor.c6
-rw-r--r--proto/ospf/ospf.c93
-rw-r--r--proto/ospf/ospf.h2
-rw-r--r--proto/ospf/rt.c62
-rw-r--r--proto/ospf/topology.c12
-rw-r--r--proto/ospf/topology.h2
-rw-r--r--proto/perf/perf.c19
-rw-r--r--proto/pipe/config.Y8
-rw-r--r--proto/pipe/pipe.c136
-rw-r--r--proto/pipe/pipe.h2
-rw-r--r--proto/radv/radv.c30
-rw-r--r--proto/rip/rip.c115
-rw-r--r--proto/rip/rip.h1
-rw-r--r--proto/rpki/config.Y1
-rw-r--r--proto/rpki/packets.c46
-rw-r--r--proto/rpki/rpki.c66
-rw-r--r--proto/rpki/rpki.h2
-rw-r--r--proto/rpki/ssh_transport.c2
-rw-r--r--proto/rpki/tcp_transport.c2
-rw-r--r--proto/rpki/transport.c3
-rw-r--r--proto/static/static.c78
-rw-r--r--sysdep/bsd/krt-sock.c59
-rw-r--r--sysdep/linux/netlink.c80
-rw-r--r--sysdep/unix/Makefile2
-rw-r--r--sysdep/unix/alloc.c239
-rw-r--r--sysdep/unix/coroutine.c206
-rw-r--r--sysdep/unix/io-loop.c620
-rw-r--r--sysdep/unix/io-loop.h58
-rw-r--r--sysdep/unix/io.c235
-rw-r--r--sysdep/unix/krt.c447
-rw-r--r--sysdep/unix/krt.h14
-rw-r--r--sysdep/unix/log.c76
-rw-r--r--sysdep/unix/main.c28
-rw-r--r--sysdep/unix/unix.h1
-rw-r--r--test/birdtest.c5
-rw-r--r--test/bt-utils.c7
122 files changed, 11317 insertions, 4867 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e7eaa66b..2c1f7175 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -134,21 +134,11 @@ docker_fedora-34-amd64:
IMG_NAME: "fedora-34-amd64"
<<: *docker_build
-docker_centos-7-amd64:
- variables:
- IMG_NAME: "centos-7-amd64"
- <<: *docker_build
-
docker_centos-8-amd64:
variables:
IMG_NAME: "centos-8-amd64"
<<: *docker_build
-docker_ubuntu-14_04-amd64:
- variables:
- IMG_NAME: "ubuntu-14.04-amd64"
- <<: *docker_build
-
docker_ubuntu-16_04-amd64:
variables:
IMG_NAME: "ubuntu-16.04-amd64"
@@ -312,18 +302,10 @@ build-fedora-34-amd64:
<<: *build-linux
image: registry.nic.cz/labs/bird:fedora-33-amd64
-build-centos-7-amd64:
- <<: *build-linux
- image: registry.nic.cz/labs/bird:centos-7-amd64
-
build-centos-8-amd64:
<<: *build-linux
image: registry.nic.cz/labs/bird:centos-8-amd64
-build-ubuntu-14_04-amd64:
- <<: *build-linux
- image: registry.nic.cz/labs/bird:ubuntu-14.04-amd64
-
build-ubuntu-16_04-amd64:
<<: *build-linux
image: registry.nic.cz/labs/bird:ubuntu-16.04-amd64
@@ -468,13 +450,6 @@ pkg-fedora-34-amd64:
needs: [build-fedora-34-amd64]
image: registry.nic.cz/labs/bird:fedora-34-amd64
-pkg-centos-7-amd64:
- <<: *pkg-rpm-wa
- variables:
- LC_ALL: en_US.UTF-8
- needs: [build-centos-7-amd64]
- image: registry.nic.cz/labs/bird:centos-7-amd64
-
pkg-centos-8-amd64:
<<: *pkg-rpm-wa
needs: [build-centos-8-amd64]
diff --git a/INSTALL b/INSTALL
index 7a179284..c1094ee5 100644
--- a/INSTALL
+++ b/INSTALL
@@ -27,9 +27,9 @@ Requirements
For compiling BIRD you need these programs and libraries:
- - GNU C Compiler (or LLVM Clang)
+ - GNU C Compiler (or LLVM Clang) capable of compiling C11 code
- GNU Make
- - GNU Bison
+ - GNU Bison (at least 3.0)
- GNU M4
- Flex
diff --git a/aclocal.m4 b/aclocal.m4
index 1613d680..3405b85b 100644
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -1,5 +1,6 @@
dnl ** Additional Autoconf tests for BIRD configure script
dnl ** (c) 1999 Martin Mares <mj@ucw.cz>
+dnl ** (c) 2021 Maria Matejka <mq@jmq.cz>
AC_DEFUN([BIRD_CHECK_THREAD_LOCAL],
[
@@ -9,14 +10,23 @@ AC_DEFUN([BIRD_CHECK_THREAD_LOCAL],
AC_COMPILE_IFELSE([
AC_LANG_PROGRAM(
[
- _Thread_local static int x = 42;
+ static _Thread_local int x = 42;
],
[]
)
],
[bird_cv_thread_local=yes],
+ [AC_COMPILE_IFELSE([
+ AC_LANG_PROGRAM(
+ [
+ static __thread int x = 42;
+ ],
+ []
+ )
+ ],
+ [bird_cv_thread_local=__thread],
[bird_cv_thread_local=no]
- )
+ )])
)
])
diff --git a/bird-gdb.py b/bird-gdb.py
index 3cf65a9c..e11f9757 100644
--- a/bird-gdb.py
+++ b/bird-gdb.py
@@ -1,12 +1,16 @@
+import itertools
+import functools
+
class BIRDPrinter:
def __init__(self, val):
self.val = val
@classmethod
def lookup(cls, val):
- if val.type.code != cls.typeCode:
+ t = val.type.strip_typedefs()
+ if t.code != cls.typeCode:
return None
- if val.type.tag != cls.typeTag:
+ if t.tag != cls.typeTag:
return None
return cls(val)
@@ -123,7 +127,7 @@ class BIRDFLinePrinter(BIRDPrinter):
"n": n,
"code": str(self.val['items'][n]['fi_code']),
} if n % 8 == 0 else str(self.val['items'][n]['fi_code']) for n in range(cnt)]))
-
+
class BIRDFExecStackPrinter(BIRDPrinter):
"Print BIRD's struct f_exec_stack"
@@ -141,6 +145,315 @@ class BIRDFExecStackPrinter(BIRDPrinter):
"n": n
} for n in range(cnt-1, -1, -1) ])
+
+class BIRD:
+ def skip_back(t, i, v):
+ if isinstance(t, str):
+ t = gdb.lookup_type(t)
+ elif isinstance(t, gdb.Value):
+ t = gdb.lookup_type(t.string())
+ elif not isinstance(t, gdb.Type):
+ raise Exception(f"First argument of skip_back(t, i, v) must be a type, got {type(t)}")
+
+ t = t.strip_typedefs()
+ nullptr = gdb.Value(0).cast(t.pointer())
+
+ if isinstance(i, gdb.Value):
+ i = i.string()
+ elif not isinstance(i, str):
+ raise Exception(f"Second argument of skip_back(t, i, v) must be a item name, got {type(i)}")
+
+ if not isinstance(v, gdb.Value):
+ raise Exception(f"Third argument of skip_back(t, i, v) must be a value, got {type(v)}")
+ if v.type.code != gdb.TYPE_CODE_PTR and v.type.code != gdb.TYPE_CODE_REF:
+ raise Exception(f"Third argument of skip_back(t, i, v) must be a pointer, is {v.type} ({v.type.code})")
+ if v.type.target().strip_typedefs() != nullptr[i].type:
+ raise Exception(f"Third argument of skip_back(t, i, v) points to type {v.type.target().strip_typedefs()}, should be {nullptr[i].type}")
+
+ uintptr_t = gdb.lookup_type("uintptr_t")
+ taddr = v.dereference().address.cast(uintptr_t) - nullptr[i].address.cast(uintptr_t)
+ return gdb.Value(taddr).cast(t.pointer())
+
+ class skip_back_gdb(gdb.Function):
+ "Given address of a structure item, returns address of the structure, as the SKIP_BACK macro does"
+ def __init__(self):
+ gdb.Function.__init__(self, "SKIP_BACK")
+
+ def invoke(self, t, i, v):
+ return BIRD.skip_back(t, i, v)
+
+
+BIRD.skip_back_gdb()
+
+
+class BIRDList:
+ def __init__(self, val):
+ ltype = val.type.strip_typedefs()
+ if ltype.code != gdb.TYPE_CODE_UNION or ltype.tag != "list":
+ raise Exception(f"Not a list, is type {ltype}")
+
+ self.head = val["head"]
+ self.tail_node = val["tail_node"]
+
+ if str(self.head.address) == '0x0':
+ raise Exception("List head is NULL")
+
+ if str(self.tail_node["prev"].address) == '0x0':
+ raise Exception("List tail is NULL")
+
+ def __iter__(self):
+ cur = self.head
+ while cur.dereference() != self.tail_node:
+ yield cur
+ cur = cur.dereference()["next"]
+
+ def __len__(self):
+ return sum([1 for _ in self])
+
+ def __getitem__(self, key):
+ for item in itertools.islice(self, key):
+ return item
+
+ raise KeyError("Not enough elements in list")
+
+class BIRDListLength(gdb.Function):
+ """Returns length of the list, as in
+ print $list_length(routing_tables)"""
+ def __init__(self):
+ super(BIRDListLength, self).__init__("list_length")
+
+ def invoke(self, l):
+ return len(BIRDList(l))
+
+BIRDListLength()
+
+class BIRDListItem(gdb.Function):
+ """Returns n-th item of the list."""
+ def __init__(self):
+ super(BIRDListItem, self).__init__("list_item")
+
+ def invoke(self, l, n, t=None, item="n"):
+ if t is None:
+ return BIRDList(l)[n]
+ else:
+ return BIRD.skip_back(t, item, BIRDList(l)[n])
+
+BIRDListItem()
+
+class BIRDResourceSize():
+ def __init__(self, netto, overhead, free):
+ self.netto = netto
+ self.overhead = overhead
+ self.free = free
+
+ def __str__(self):
+ ns = str(self.netto)
+ os = str(self.overhead)
+ fs = str(self.free)
+
+ return "{: >12s} | {: >12s} | {: >12s}".format(ns, os, fs)
+
+ def __add__(self, val):
+ return BIRDResourceSize(self.netto + val.netto, self.overhead + val.overhead, self.free + val.free)
+
+class BIRDResource():
+ def __init__(self, val):
+ self.val = val
+
+ def __str__(self):
+ return f"Item {self.val.address} of class \"{self.val['class']['name'].string()}\""
+
+ def memsize(self):
+ if str(self.val["class"]["memsize"]) == '0x0':
+ size = self.val["class"]["size"]
+ ressize = gdb.lookup_type("struct resource").sizeof
+ return BIRDResourceSize(size - ressize, ressize, 0)
+ else:
+ raise Exception(f"Resource class {self.val['class']['name']} with defined memsize() not known by Python")
+
+ def parse(self):
+ pass
+
+class BIRDMBResource(BIRDResource):
+ def __init__(self, val):
+ self.mbtype = gdb.lookup_type("struct mblock")
+ self.val = val.cast(self.mbtype)
+
+ def memsize(self):
+ return BIRDResourceSize(self.val["size"], 8 + self.mbtype.sizeof, 0)
+
+ def __str__(self):
+ return f"Standalone memory block {self.val.address} of size {self.val['size']}, data at {self.val['data'].address}"
+
+class BIRDLinPoolResource(BIRDResource):
+ def __init__(self, val):
+ self.lptype = gdb.lookup_type("struct linpool")
+ self.val = val.cast(self.lptype)
+ self.info = None
+ self.std = self.ChunkList(self.val["first"])
+ self.large = self.ChunkList(self.val["first_large"])
+
+ class ChunkList:
+ def __init__(self, val):
+ self.val = val
+
+ def __iter__(self):
+ chunk = self.val
+ while str(chunk) != 0x0:
+ yield chunk
+ chunk = chunk.dereference()["next"]
+
+ def __len__(self):
+ return sum([1 for _ in self])
+
+ def parse(self):
+ self.info = {
+ "std_chunks": len(self.std),
+ "large_chunks": len(self.large),
+ }
+
+ def memsize(self):
+ if self.info is None:
+ self.parse()
+
+ overhead = (8 - 8*self.val["use_pages"]) + gdb.lookup_type("struct lp_chunk").sizeof
+ return BIRDResourceSize(
+ self.val["total"] + self.val["total_large"],
+ (self.info["std_chunks"] + self.info["large_chunks"]) * overhead,
+ 0)
+
+ def __str__(self):
+ if self.info is None:
+ self.parse()
+
+ return f"Linpool {self.val.address} with {self.info['std_chunks']} standard chunks of size {self.val['chunk_size']} and {self.info['large_chunks']} large chunks"
+
+class BIRDSlabResource(BIRDResource):
+ def __init__(self, val):
+ self.slabtype = gdb.lookup_type("struct slab")
+ self.val = val.cast(self.slabtype)
+ self.info = None
+
+ def count_heads(self, which):
+ self.hcnt = 0
+ self.used = 0
+ for item in BIRDList(self.val[which + "_heads"]):
+ self.hcnt += 1
+ self.used += item.dereference().cast(self.slheadtype)["num_full"]
+
+ self.info[which + "_heads"] = self.hcnt
+ self.info[which + "_used"] = self.used
+ return (self.hcnt, self.used)
+
+ def parse(self):
+ self.page_size = gdb.lookup_symbol("page_size")[0].value()
+ self.slheadtype = gdb.lookup_type("struct sl_head")
+ self.info = {}
+ self.count_heads("empty")
+ self.count_heads("partial")
+ self.count_heads("full")
+
+ def memsize(self):
+ if self.info is None:
+ self.parse()
+
+ total_used = self.info["empty_used"] + self.info["partial_used"] + self.info["full_used"]
+ total_heads = self.info["empty_heads"] + self.info["partial_heads"] + self.info["full_heads"]
+
+ eff_size = total_used * self.val["obj_size"]
+ free_size = self.info["empty_heads"] * self.page_size
+ total_size = total_heads * self.page_size + self.slabtype.sizeof
+
+ return BIRDResourceSize( eff_size, total_size - free_size - eff_size, free_size)
+
+ def __str__(self):
+ if self.info is None:
+ self.parse()
+
+ return f"Slab {self.val.address} " + ", ".join([
+ f"{self.info[x + '_heads']} {x} heads" for x in [ "empty", "partial", "full" ]]) + \
+ f", {self.val['objs_per_slab']} objects of size {self.val['obj_size']} per head"
+
+
+class BIRDIOLoopResource(BIRDResource):
+ def __init__(self, val):
+ self.iolooptype = gdb.lookup_type("struct birdloop")
+ self.val = val.cast(self.iolooptype)
+ self.pages = self.val["pages"]
+ self.page_size = gdb.lookup_symbol("page_size")[0].value()
+
+ def memsize(self):
+ return BIRDResourceSize(0, self.iolooptype.sizeof, self.pages['cnt'] * self.page_size)
+
+ def __str__(self):
+ return f"IO Loop {self.val.address} containing {self.pages['cnt']} free pages (min {self.pages['min']} max {self.pages['max']}), cleanup event {self.pages['cleanup'].dereference().address}: " + \
+ ", ".join([ str(p) for p in BIRDList(self.pages["list"])])
+
+
+class BIRDPoolResource(BIRDResource):
+ def __init__(self, val):
+ self.pooltype = gdb.lookup_type("struct pool")
+ self.resptrtype = gdb.lookup_type("struct resource").pointer()
+ self.val = val.cast(self.pooltype)
+ self.inside = BIRDList(self.val["inside"])
+
+ def __iter__(self):
+ for val in self.inside:
+ yield BIRDNewResource(val.cast(self.resptrtype).dereference())
+
+ def __len__(self):
+ return len(self.inside)
+
+ def memsize(self):
+ sum = BIRDResourceSize(0, self.pooltype.sizeof, 0)
+# for i in self.items:
+# sum += i.memsize()
+
+ return sum
+
+ def __str__(self):
+# for i in self.items:
+# print(i)
+
+ return f"Resource pool {self.val.address} \"{self.val['name'].string()}\" containing {len(self)} items"
+
+BIRDResourceMap = {
+ "mbl_memsize": BIRDMBResource,
+ "pool_memsize": BIRDPoolResource,
+ "lp_memsize": BIRDLinPoolResource,
+ "slab_memsize": BIRDSlabResource,
+ "birdloop_memsize": BIRDIOLoopResource,
+ }
+
+def BIRDNewResource(res):
+ cms = res["class"].dereference()["memsize"]
+ for cx in BIRDResourceMap:
+ if cms == gdb.lookup_symbol(cx)[0].value():
+ return BIRDResourceMap[cx](res)
+
+ return BIRDResource(res)
+
+
+class BIRDResourcePrinter(BIRDPrinter):
+ "Print BIRD's resource"
+ typeCode = gdb.TYPE_CODE_STRUCT
+ typeTag = "resource"
+
+ def __init__(self, val):
+ super(BIRDResourcePrinter, self).__init__(val)
+ self.resource = BIRDNewResource(val)
+ self.resourcetype = gdb.lookup_type("struct resource")
+
+ if type(self.resource) == BIRDPoolResource:
+ self.children = self.pool_children
+
+ def pool_children(self):
+ return iter([ ("\n", i.val.cast(self.resourcetype)) for i in self.resource ])
+
+ def to_string(self):
+ return f"[ {str(self.resource.memsize())} ] {str(self.resource)}"
+
+
def register_printers(objfile):
objfile.pretty_printers.append(BIRDFInstPrinter.lookup)
objfile.pretty_printers.append(BIRDFValPrinter.lookup)
@@ -148,6 +461,7 @@ def register_printers(objfile):
objfile.pretty_printers.append(BIRDFLineItemPrinter.lookup)
objfile.pretty_printers.append(BIRDFLinePrinter.lookup)
objfile.pretty_printers.append(BIRDFExecStackPrinter.lookup)
+ objfile.pretty_printers.append(BIRDResourcePrinter.lookup)
register_printers(gdb.current_objfile())
diff --git a/conf/conf.c b/conf/conf.c
index a2b01667..e8f1559a 100644
--- a/conf/conf.c
+++ b/conf/conf.c
@@ -89,7 +89,7 @@ int undo_available; /* Undo was not requested from last reconfiguration */
struct config *
config_alloc(const char *name)
{
- pool *p = rp_new(&root_pool, "Config");
+ pool *p = rp_new(&root_pool, &main_birdloop, "Config");
linpool *l = lp_new_default(p);
struct config *c = lp_allocz(l, sizeof(struct config));
@@ -196,7 +196,7 @@ void
config_free(struct config *c)
{
if (c)
- rfree(c->pool);
+ rp_free(c->pool, &root_pool);
}
void
diff --git a/conf/conf.h b/conf/conf.h
index 3bc37959..4f6aa6eb 100644
--- a/conf/conf.h
+++ b/conf/conf.h
@@ -35,6 +35,7 @@ struct config {
u32 proto_default_debug; /* Default protocol debug mask */
u32 proto_default_mrtdump; /* Default protocol mrtdump mask */
u32 channel_default_debug; /* Default channel debug mask */
+ u16 filter_vstk, filter_estk; /* Filter stack depth */
struct timeformat tf_route; /* Time format for 'show route' */
struct timeformat tf_proto; /* Time format for 'show protocol' */
struct timeformat tf_log; /* Time format for the logfile */
@@ -44,6 +45,7 @@ struct config {
int cli_debug; /* Tracing of CLI connections and commands */
int latency_debug; /* I/O loop tracks duration of each event */
+ int table_debug; /* Track route propagation through tables */
u32 latency_limit; /* Events with longer duration are logged (us) */
u32 watchdog_warning; /* I/O loop watchdog limit for warning (us) */
u32 watchdog_timeout; /* Watchdog timeout (in seconds, 0 = disabled) */
diff --git a/configure.ac b/configure.ac
index 64181d29..5c0cf002 100644
--- a/configure.ac
+++ b/configure.ac
@@ -36,12 +36,6 @@ AC_ARG_ENABLE([memcheck],
[enable_memcheck=yes]
)
-AC_ARG_ENABLE([pthreads],
- [AS_HELP_STRING([--enable-pthreads], [enable POSIX threads support @<:@try@:>@])],
- [],
- [enable_pthreads=try]
-)
-
AC_ARG_ENABLE([libssh],
[AS_HELP_STRING([--enable-libssh], [enable LibSSH support in RPKI @<:@try@:>@])],
[],
@@ -125,25 +119,19 @@ if test -z "$GCC" ; then
fi
BIRD_CHECK_THREAD_LOCAL
-if test "$bird_cv_thread_local" = yes ; then
- AC_DEFINE([HAVE_THREAD_LOCAL], [1], [Define to 1 if _Thread_local is available])
+if test "$bird_cv_thread_local" = no ; then
+ AC_MSG_ERROR([This program requires thread local storage.])
+elif test "$bird_cv_thread_local" != yes ; then
+ AC_DEFINE_UNQUOTED([_Thread_local], [$bird_cv_thread_local], [Legacy _Thread_local])
fi
-if test "$enable_pthreads" != no ; then
- BIRD_CHECK_PTHREADS
+BIRD_CHECK_PTHREADS
- if test "$bird_cv_lib_pthreads" = yes ; then
- AC_DEFINE([USE_PTHREADS], [1], [Define to 1 if pthreads are enabled])
- CFLAGS="$CFLAGS -pthread"
- LDFLAGS="$LDFLAGS -pthread"
- proto_bfd=bfd
- elif test "$enable_pthreads" = yes ; then
- AC_MSG_ERROR([POSIX threads not available.])
- fi
-
- if test "$enable_pthreads" = try ; then
- enable_pthreads="$bird_cv_lib_pthreads"
- fi
+if test "$bird_cv_lib_pthreads" = yes ; then
+ CFLAGS="$CFLAGS -pthread"
+ LDFLAGS="$LDFLAGS -pthread"
+else
+ AC_MSG_ERROR([POSIX threads not available.])
fi
# This is assumed to be necessary for proper BIRD build
@@ -304,8 +292,7 @@ if test "$enable_mpls_kernel" != no ; then
fi
fi
-all_protocols="$proto_bfd babel bgp mrt ospf perf pipe radv rip rpki static"
-
+all_protocols="bfd babel bgp mrt ospf perf pipe radv rip rpki static"
all_protocols=`echo $all_protocols | sed 's/ /,/g'`
if test "$with_protocols" = all ; then
@@ -351,9 +338,15 @@ case $sysdesc in
esac
AC_CHECK_HEADERS_ONCE([alloca.h syslog.h])
-AC_CHECK_HEADER([sys/mman.h], [AC_DEFINE([HAVE_MMAP], [1], [Define to 1 if mmap() is available.])])
+AC_CHECK_HEADER([sys/mman.h], [AC_DEFINE([HAVE_MMAP], [1], [Define to 1 if mmap() is available.])], have_mman=no)
+AC_CHECK_FUNC([aligned_alloc], [AC_DEFINE([HAVE_ALIGNED_ALLOC], [1], [Define to 1 if aligned_alloc() is available.])], have_aligned_alloc=no)
AC_CHECK_MEMBERS([struct sockaddr.sa_len], [], [], [#include <sys/socket.h>])
+if test "$have_aligned_alloc" = "no" && test "$have_mman" = "no" ; then
+ AC_MSG_ERROR([No means of aligned alloc found. Need mmap() or aligned_alloc().])
+fi
+
+
AC_C_BIGENDIAN(
[AC_DEFINE([CPU_BIG_ENDIAN], [1], [Define to 1 if cpu is big endian])],
[AC_DEFINE([CPU_LITTLE_ENDIAN], [1], [Define to 1 if cpu is little endian])],
@@ -402,7 +395,7 @@ if test "$enable_debug" = yes ; then
fi
fi
- if test "enable_debug_expensive" = yes ; then
+ if test "$enable_debug_expensive" = yes ; then
AC_DEFINE([ENABLE_EXPENSIVE_CHECKS], [1], [Define to 1 if you want to run expensive consistency checks.])
fi
fi
@@ -467,7 +460,6 @@ AC_MSG_RESULT([ Object directory: $objdir])
AC_MSG_RESULT([ Iproute2 directory: $iproutedir])
AC_MSG_RESULT([ System configuration: $sysdesc])
AC_MSG_RESULT([ Debugging: $enable_debug])
-AC_MSG_RESULT([ POSIX threads: $enable_pthreads])
AC_MSG_RESULT([ Routing protocols: $protocols])
AC_MSG_RESULT([ LibSSH support in RPKI: $enable_libssh])
AC_MSG_RESULT([ Kernel MPLS support: $enable_mpls_kernel])
diff --git a/doc/bird.sgml b/doc/bird.sgml
index a63493da..99aa140a 100644
--- a/doc/bird.sgml
+++ b/doc/bird.sgml
@@ -145,6 +145,13 @@ BIRD executable by configuring out routing protocols you don't use, and
<p>You can pass several command-line options to bird:
<descrip>
+ <tag><label id="argv-block">-B <m/exp/</tag>
+ allocate memory using 2^<cf/exp/ byte sized blocks;
+ if you're expecting high memory load, raise this to
+ reduce number of allocated memory pages. For a million routes
+ in one table, the recommended setting is 18.
+ Default is your system page size, typically 12 for 4096 bytes.
+
<tag><label id="argv-config">-c <m/config name/</tag>
use given configuration file instead of <it/prefix/<file>/etc/bird.conf</file>.
@@ -1700,7 +1707,7 @@ Common route attributes are:
<tag><label id="rta-source"><m/enum/ source</tag>
what protocol has told me about this route. Possible values:
- <cf/RTS_DUMMY/, <cf/RTS_STATIC/, <cf/RTS_INHERIT/, <cf/RTS_DEVICE/,
+ <cf/RTS_STATIC/, <cf/RTS_INHERIT/, <cf/RTS_DEVICE/,
<cf/RTS_RIP/, <cf/RTS_OSPF/, <cf/RTS_OSPF_IA/, <cf/RTS_OSPF_EXT1/,
<cf/RTS_OSPF_EXT2/, <cf/RTS_BGP/, <cf/RTS_PIPE/, <cf/RTS_BABEL/.
@@ -4126,6 +4133,14 @@ include standard channel config options; see the example below.
<tag><label id="pipe-peer-table">peer table <m/table/</tag>
Defines secondary routing table to connect to. The primary one is
selected by the <cf/table/ keyword.
+
+ <tag><label id="pipe-max-generation">max generation <m/expr/</tag>
+ Sets maximal generation of route that may pass through this pipe.
+ The generation value is increased by one by each pipe on its path.
+ Not meeting this requirement causes an error message complaining about
+ an overpiped route. If you have long chains of pipes, you probably want
+ to raise this value; anyway the default of 16 should be enough for even
+ most strange uses. Maximum is 254.
</descrip>
<sect1>Attributes
diff --git a/doc/threads/.gitignore b/doc/threads/.gitignore
new file mode 100644
index 00000000..23f832b7
--- /dev/null
+++ b/doc/threads/.gitignore
@@ -0,0 +1,2 @@
+*.html
+*.pdf
diff --git a/doc/threads/00_filter_structure.png b/doc/threads/00_filter_structure.png
new file mode 100644
index 00000000..a61603cb
--- /dev/null
+++ b/doc/threads/00_filter_structure.png
Binary files differ
diff --git a/doc/threads/00_the_name_of_the_game.md b/doc/threads/00_the_name_of_the_game.md
new file mode 100644
index 00000000..ddc4f638
--- /dev/null
+++ b/doc/threads/00_the_name_of_the_game.md
@@ -0,0 +1,114 @@
+# BIRD Journey to Threads. Chapter 0: The Reason Why.
+
+BIRD is a fast, robust and memory-efficient routing daemon designed and
+implemented at the end of 20th century. Its concept of multiple routing
+tables with pipes between them, as well as a procedural filtering language,
+has been unique for a long time and is still one of main reasons why people use
+BIRD for big loads of routing data.
+
+## IPv4 / IPv6 duality: Solved
+
+The original design of BIRD has also some drawbacks. One of these was an idea
+of two separate daemons – one BIRD for IPv4 and another BIRD for IPv6, built from the same
+codebase, cleverly using `#ifdef IPV6` constructions to implement the
+common parts of BIRD algorithms and data structures only once.
+If IPv6 adoption went forward as people thought in that time,
+it would work; after finishing the worldwide transition to IPv6, people could
+just stop building BIRD for IPv4 and drop the `#ifdef`-ed code.
+
+The history went other way, however. BIRD developers therefore decided to *integrate*
+these two versions into one daemon capable of any address family, allowing for
+not only IPv6 but for virtually anything. This rework brought quite a lot of
+backward-incompatible changes, therefore we decided to release it as a version 2.0.
+This work was mostly finished in 2018 and as for March 2021, we have already
+switched the 1.6.x branch to a bugfix-only mode.
+
+## BIRD is single-threaded now
+
+The second drawback is a single-threaded design. Looking back to 1998, this was
+a good idea. A common PC had one single core and BIRD was targeting exactly
+this segment. As the years went by, the manufacturers launched multicore x86 chips
+(AMD Opteron in 2004, Intel Pentium D in 2005). This ultimately led to a world
+where as of March 2021, there is virtually no new PC sold with a single-core CPU.
+
+Together with these changes, the speed of one single core has not been growing as fast
+as the Internet is growing. BIRD is still capable to handle the full BGP table
+(868k IPv4 routes in March 2021) with one core, anyway when BIRD starts, it may take
+long minutes to converge.
+
+## Intermezzo: Filters
+
+In 2018, we took some data we had from large internet exchanges and simulated
+a cold start of BIRD as a route server. We used `linux-perf` to find most time-critical
+parts of BIRD and it pointed very clearly to the filtering code. It also showed that the
+IPv4 version of BIRD v1.6.x is substantially faster than the *integrated* version, while
+the IPv6 version was quite as fast as the *integrated* one.
+
+Here we should show a little bit more about how the filters really work. Let's use
+an example of a simple filter:
+
+```
+filter foo {
+ if net ~ [10.0.0.0/8+] then reject;
+ preference = 2 * preference - 41;
+ accept;
+}
+```
+
+This filter gets translated to an infix internal structure.
+
+![Example of filter internal representation](00_filter_structure.png)
+
+When executing, the filter interpreter just walks the filter internal structure recursively in the
+right order, executes the instructions, collects their results and finishes by
+either rejection or acceptation of the route
+
+## Filter rework
+
+Further analysis of the filter code revealed an absurdly-looking result. The
+most executed parts of the interpreter function were the `push` CPU
+instructions on its very beginning and the `pop` CPU instructions on its very
+end. This came from the fact that the interpreter function was quite long, yet
+most of the filter instructions used an extremely short path, doing all the
+stack manipulation at the beginning, branching by the filter instruction type,
+then it executed just several CPU instructions, popped everything from the
+stack back and returned.
+
+After some thoughts how to minimize stack manipulation when everything you need
+is to take two numbers and multiply them, we decided to preprocess the filter
+internal structure to another structure which is much easier to execute. The
+interpreter is now using a data stack and behaves generally as a
+postfix-ordered language. We also thought about Lua which showed up to be quite
+a lot of work implementing all the glue achieving about the same performance.
+
+After these changes, we managed to reduce the filter execution time by 10–40%,
+depending on how complex the filter is.
+Anyway, even this reduction is quite too little when there is one CPU core
+running for several minutes while others are sleeping.
+
+## We need more threads
+
+As a side effect of the rework, the new filter interpreter is also completely
+thread-safe. It seemed to be the way to go – running the filters in parallel
+while keeping everything else single-threaded. The main problem of this
+solution is a too fine granularity of parallel jobs. We would spend lots of
+time on synchronization overhead.
+
+The only filter parallel execution was also too one-sided, useful only for
+configurations with complex filters. In other cases, the major problem is best
+route recalculation, OSPF recalculation or also kernel synchronization.
+It also turned out to be dirty a lot from the code cleanliness' point of view.
+
+Therefore we chose to make BIRD multithreaded completely. We designed a way how
+to gradually enable parallel computation and best usage of all available CPU
+cores. Our goals are three:
+
+* We want to keep current functionality. Parallel computation should never drop
+ a useful feature.
+* We want to do little steps. No big reworks, even though even the smallest
+ possible step will need quite a lot of refactoring before.
+* We want to be backwards compatible as much as possible.
+
+*It's still a long road to the version 2.1. This series of texts should document
+what is needed to be changed, why we do it and how. In the next chapter, we're
+going to describe the structures for routes and their attributes. Stay tuned!*
diff --git a/doc/threads/01_the_route_and_its_attributes.md b/doc/threads/01_the_route_and_its_attributes.md
new file mode 100644
index 00000000..079eb365
--- /dev/null
+++ b/doc/threads/01_the_route_and_its_attributes.md
@@ -0,0 +1,159 @@
+# BIRD Journey to Threads. Chapter 1: The Route and its Attributes
+
+BIRD is a fast, robust and memory-efficient routing daemon designed and
+implemented at the end of 20th century. We're doing a significant amount of
+BIRD's internal structure changes to make it possible to run in multiple
+threads in parallel. This chapter covers necessary changes of data structures
+which store every single routing data.
+
+*If you want to see the changes in code, look (basically) into the
+`route-storage-updates` branch. Not all of them are already implemented, anyway
+most of them are pretty finished as of end of March, 2021.*
+
+## How routes are stored
+
+BIRD routing table is just a hierarchical noSQL database. On top level, the
+routes are keyed by their destination, called *net*. Due to historic reasons,
+the *net* is not only *IPv4 prefix*, *IPv6 prefix*, *IPv4 VPN prefix* etc.,
+but also *MPLS label*, *ROA information* or *BGP Flowspec record*. As there may
+be several routes for each *net*, an obligatory part of the key is *src* aka.
+*route source*. The route source is a tuple of the originating protocol
+instance and a 32-bit unsigned integer. If a protocol wants to withdraw a route,
+it is enough and necessary to have the *net* and *src* to identify what route
+is to be withdrawn.
+
+The route itself consists of (basically) a list of key-value records, with
+value types ranging from a 16-bit unsigned integer for preference to a complex
+BGP path structure. The keys are pre-defined by protocols (e.g. BGP path or
+OSPF metrics), or by BIRD core itself (preference, route gateway).
+Finally, the user can declare their own attribute keys using the keyword
+`attribute` in config.
+
+## Attribute list implementation
+
+Currently, there are three layers of route attributes. We call them *route*
+(*rte*), *attributes* (*rta*) and *extended attributes* (*ea*, *eattr*).
+
+The first layer, *rte*, contains the *net* pointer, several fixed-size route
+attributes (mostly preference and protocol-specific metrics), flags, lastmod
+time and a pointer to *rta*.
+
+The second layer, *rta*, contains the *src* (a pointer to a singleton instance),
+a route gateway, several other fixed-size route attributes and a pointer to
+*ea* list.
+
+The third layer, *ea* list, is a variable-length list of key-value attributes,
+containing all the remaining route attributes.
+
+Distribution of the route attributes between the attribute layers is somehow
+arbitrary. Mostly, in the first and second layer, there are attributes that
+were thought to be accessed frequently (e.g. in best route selection) and
+filled in in most routes, while the third layer is for infrequently used
+and/or infrequently accessed route attributes.
+
+## Attribute list deduplication
+
+When protocols originate routes, there are commonly more routes with the
+same attribute list. BIRD could ignore this fact, anyway if you have several
+tables connected with pipes, it is more memory-efficient to store the same
+attribute lists only once.
+
+Therefore, the two lower layers (*rta* and *ea*) are hashed and stored in a
+BIRD-global database. Routes (*rte*) contain a pointer to *rta* in this
+database, maintaining a use-count of each *rta*. Attributes (*rta*) contain
+a pointer to normalized (sorted by numerical key ID) *ea*.
+
+## Attribute list rework
+
+The first thing to change is the distribution of route attributes between
+attribute list layers. We decided to make the first layer (*rte*) only the key
+and other per-record internal technical information. Therefore we move *src* to
+*rte* and preference to *rta* (beside other things). *This is already done.*
+
+We also found out that the nexthop (gateway), originally one single IP address
+and an interface, has evolved to a complex attribute with several sub-attributes;
+not only considering multipath routing but also MPLS stacks and other per-route
+attributes. This has led to a too complex data structure holding the nexthop set.
+
+We decided finally to squash *rta* and *ea* to one type of data structure,
+allowing for completely dynamic route attribute lists. This is also supported
+by adding other *net* types (BGP FlowSpec or ROA) where lots of the fields make
+no sense at all, yet we still want to use the same data structures and implementation
+as we don't like duplicating code. *Multithreading doesn't depend on this change,
+anyway this change is going to happen soon anyway.*
+
+## Route storage
+
+The process of route import from protocol into a table can be divided into several phases:
+
+1. (In protocol code.) Create the route itself (typically from
+ protocol-internal data) and choose the right channel to use.
+2. (In protocol code.) Create the *rta* and *ea* and obtain an appropriate
+ hashed pointer. Allocate the *rte* structure and fill it in.
+3. (Optionally.) Store the route to the *import table*.
+4. Run filters. If reject, free everything.
+5. Check whether this is a real change (it may be idempotent). If not, free everything and do nothing more.
+6. Run the best route selection algorithm.
+7. Execute exports if needed.
+
+We found out that the *rte* structure allocation is done too early. BIRD uses
+global optimized allocators for fixed-size blocks (which *rte* is) to reduce
+its memory footprint, therefore the allocation of *rte* structure would be a
+synchronization point in multithreaded environment.
+
+The common code is also much more complicated when we have to track whether the
+current *rte* has to be freed or not. This is more a problem in export than in
+import as the export filter can also change the route (and therefore allocate
+another *rte*). The changed route must be therefore freed after use. All the
+route changing code must also track whether this route is writable or
+read-only.
+
+We therefore introduce a variant of *rte* called *rte_storage*. Both of these
+hold the same, the layer-1 route information (destination, author, cached
+attribute pointer, flags etc.), anyway *rte* is always local and *rte_storage*
+is intended to be put in global data structures.
+
+This change allows us to remove lots of the code which only tracks whether any
+*rte* is to be freed as *rte*'s are almost always allocated on-stack, naturally
+limiting their lifetime. If not on-stack, it's the responsibility of the owner
+to free the *rte* after import is done.
+
+This change also removes the need for *rte* allocation in protocol code and
+also *rta* can be safely allocated on-stack. As a result, protocols can simply
+allocate all the data on stack, call the update routine and the common code in
+BIRD's *nest* does all the storage for them.
+
+Allocating *rta* on-stack is however not required. BGP and OSPF use this to
+import several routes with the same attribute list. In BGP, this is due to the
+format of BGP update messages containing first the attributes and then the
+destinations (BGP NLRI's). In OSPF, in addition to *rta* deduplication, it is
+also presumed that no import filter (or at most some trivial changes) is applied
+as OSPF would typically not work well when filtered.
+
+*This change is already done.*
+
+## Route cleanup and table maintenance
+
+In some cases, the route update is not originated by a protocol/channel code.
+When the channel shuts down, all routes originated by that channel are simply
+cleaned up. Also routes with recursive routes may get changed without import,
+simply by changing the IGP route.
+
+This is currently done by a `rt_event` (see `nest/rt-table.c` for source code)
+which is to be converted to a parallel thread, running when nobody imports any
+route. *This change is freshly done in branch `guernsey`.*
+
+## Parallel protocol execution
+
+The long-term goal of these reworks is to allow for completely independent
+execution of all the protocols. Typically, there is no direct interaction
+between protocols; everything is done thought BIRD's *nest*. Protocols should
+therefore run in parallel in future and wait/lock only when something is needed
+to do externally.
+
+We also aim for a clean and documented protocol API.
+
+*It's still a long road to the version 2.1. This series of texts should document
+what is needed to be changed, why we do it and how. In the next chapter, we're
+going to describe how the route is exported from table to protocols and how this
+process is changing. Stay tuned!*
diff --git a/doc/threads/02_asynchronous_export.md b/doc/threads/02_asynchronous_export.md
new file mode 100644
index 00000000..62e4ea9f
--- /dev/null
+++ b/doc/threads/02_asynchronous_export.md
@@ -0,0 +1,463 @@
+# BIRD Journey to Threads. Chapter 2: Asynchronous route export
+
+Route export is a core algorithm of BIRD. This chapter covers how we are making
+this procedure multithreaded. Desired outcomes are mostly lower latency of
+route import, flap dampening and also faster route processing in large
+configurations with lots of export from one table.
+
+BIRD is a fast, robust and memory-efficient routing daemon designed and
+implemented at the end of 20th century. We're doing a significant amount of
+BIRD's internal structure changes to make it possible to run in multiple
+threads in parallel.
+
+## How routes are propagated through BIRD
+
+In the [previous chapter](https://en.blog.nic.cz/2021/03/23/bird-journey-to-threads-chapter-1-the-route-and-its-attributes/), you could learn how the route import works. We should
+now extend that process by the route export.
+
+1. (In protocol code.) Create the route itself and propagate it through the
+ right channel by calling `rte_update`.
+2. The channel runs its import filter.
+3. New best route is selected.
+4. For each channel:
+ 1. The channel runs its preexport hook and export filter.
+ 2. (Optionally.) The channel merges the nexthops to create an ECMP route.
+ 3. The channel calls the protocol's `rt_notify` hook.
+5. After all exports are finished, the `rte_update` call finally returns and
+ the source protocol may do anything else.
+
+Let's imagine that all the protocols are running in parallel. There are two
+protocols with a route prepared to import. One of those wins the table lock,
+does the import and then the export touches the other protocol which must
+either:
+
+* store the route export until it finishes its own imports, or
+* have independent import and export parts.
+
+Both of these conditions are infeasible for common use. Implementing them would
+make protocols much more complicated with lots of new code to test and release
+at once and also quite a lot of corner cases. Risk of deadlocks is also worth
+mentioning.
+
+## Asynchronous route export
+
+We decided to make it easier for protocols and decouple the import and export
+this way:
+
+1. The import is done.
+2. Best route is selected.
+3. Resulting changes are stored.
+
+Then, after the importing protocol returns, the exports are processed for each
+exporting channel in parallel: Some protocols
+may process the export directly after it is stored, other protocols wait
+until they finish another job.
+
+This eliminates the risk of deadlocks and all protocols' `rt_notify` hooks can
+rely on their independence. There is only one question. How to store the changes?
+
+## Route export modes
+
+To find a good data structure for route export storage, we shall first know the
+readers. The exporters may request different modes of route export.
+
+### Export everything
+
+This is the most simple route export mode. The exporter wants to know about all
+the routes as they're changing. We therefore simply store the old route until
+the change is fully exported and then we free the old stored route.
+
+To manage this, we can simply queue the changes one after another and postpone
+old route cleanup after all channels have exported the change. The queue member
+would look like this:
+
+```
+struct {
+ struct rte_storage *new;
+ struct rte_storage *old;
+};
+```
+
+### Export best
+
+This is another simple route export mode. We check whether the best route has
+changed; if not, no export happens. Otherwise, the export is propagated as the
+old best route changing to the new best route.
+
+To manage this, we could use the queue from the previous point by adding new
+best and old best pointers. It is guaranteed that both the old best and new
+best pointers are always valid in time of export as all the changes in them
+must be stored in future changes which have not been exported yet by this
+channel and therefore not freed yet.
+
+```
+struct {
+ struct rte_storage *new;
+ struct rte_storage *new_best;
+ struct rte_storage *old;
+ struct rte_storage *old_best;
+};
+```
+
+Anyway, we're getting to the complicated export modes where this simple
+structure is simply not enough.
+
+### Export merged
+
+Here we're getting to some kind of problems. The exporting channel requests not
+only the best route but also all routes that are good enough to be considered
+ECMP-eligible (we call these routes *mergable*). The export is then just one
+route with just the nexthops merged. Export filters are executed before
+merging and if the best route is rejected, nothing is exported at all.
+
+To achieve this, we have to re-evaluate export filters any time the best route
+or any mergable route changes. Until now, the export could just do what it wanted
+as there was only one thread working. To change this, we need to access the
+whole route list and process it.
+
+### Export first accepted
+
+In this mode, the channel runs export filters on a sorted list of routes, best first.
+If the best route gets rejected, it asks for the next one until it finds an
+acceptable route or exhausts the list. This export mode requires a sorted table.
+BIRD users may know this export mode as `secondary` in BGP.
+
+For now, BIRD stores two bits per route for each channel. The *export bit* is set
+if the route has been really exported to that channel. The *reject bit* is set
+if the route was rejected by the export filter.
+
+When processing a route change for accepted, the algorithm first checks the
+export bit for the old route. If this bit is set, the old route is that one
+exported so we have to find the right one to export. Therefore the sorted route
+list is walked best to worst to find a new route to export, using the reject
+bit to evaluate only routes which weren't rejected in previous runs of this
+algorithm.
+
+If the old route bit is not set, the algorithm walks the sorted route list best
+to worst, checking the position of new route with respect to the exported route.
+If the new route is worse, nothing happens, otherwise the new route is sent to
+filters and finally exported if passes.
+
+### Export by feed
+
+To resolve problems arising from previous two export modes (merged and first accepted),
+we introduce a way to process a whole route list without locking the table
+while export filters are running. To achieve this, we follow this algorithm:
+
+1. The exporting channel sees a pending export.
+2. *The table is locked.*
+3. All routes (pointers) for the given destination are dumped to a local array.
+4. Also first and last pending exports for the given destination are stored.
+5. *The table is unlocked.*
+6. The channel processes the local array of route pointers.
+7. All pending exports between the first and last stored (incl.) are marked as processed to allow for cleanup.
+
+After unlocking the table, the pointed-to routes are implicitly guarded by the
+sole fact that no pending export has not yet been processed by all channels
+and the cleanup routine frees only resources after being processed.
+
+The pending export range must be stored together with the feed. While
+processing export filters for the feed, another export may come in. We
+must process the export once again as the feed is now outdated, therefore we
+must mark only these exports that were pending for this destination when the
+feed was being stored. We also can't mark them before actually processing them
+as they would get freed inbetween.
+
+## Pending export data structure
+
+As the two complicated export modes use the export-by-feed algorithm, the
+pending export data structure may be quite minimalistic.
+
+```
+struct rt_pending_export {
+ struct rt_pending_export * _Atomic next; /* Next export for the same destination */
+ struct rte_storage *new; /* New route */
+ struct rte_storage *new_best; /* New best route in unsorted table */
+ struct rte_storage *old; /* Old route */
+ struct rte_storage *old_best; /* Old best route in unsorted table */
+ _Atomic u64 seq; /* Sequential ID (table-local) of the pending export */
+};
+```
+
+To allow for squashing outdated pending exports (e.g. for flap dampening
+purposes), there is a `next` pointer to the next export for the same
+destination. This is also needed for the export-by-feed algorithm to traverse
+the list of pending exports.
+
+We should also add several items into `struct channel`.
+
+```
+ struct coroutine *export_coro; /* Exporter and feeder coroutine */
+ struct bsem *export_sem; /* Exporter and feeder semaphore */
+ struct rt_pending_export * _Atomic last_export; /* Last export processed */
+ struct bmap export_seen_map; /* Keeps track which exports were already processed */
+ u64 flush_seq; /* Table export seq when the channel announced flushing */
+```
+
+To run the exports in parallel, `export_coro` is run and `export_sem` is
+used for signalling new exports to it. The exporter coroutine also marks all
+seen sequential IDs in its `export_seen_map` to make it possible to skip over
+them if seen again. The exporter coroutine is started when export is requested
+and stopped when export is stopped.
+
+There is also a table cleaner routine
+(see [previous chapter](https://en.blog.nic.cz/2021/03/23/bird-journey-to-threads-chapter-1-the-route-and-its-attributes/))
+which must cleanup also the pending exports after all the channels are finished with them.
+To signal that, there is `last_export` working as a release point: the channel
+guarantees that it doesn't touch the pointed-to pending export (or any older), nor any data
+from it.
+
+The last tricky point here is channel flushing. When any channel stops, all its
+routes are automatically freed and withdrawals are exported if appropriate.
+Until now, the routes could be flushed synchronously, anyway now flush has
+several phases, stored in `flush_active` channel variable:
+
+1. Flush started.
+2. Withdrawals for all the channel's routes are issued.
+ Here the channel stores the `seq` of last current pending export to `flush_seq`)
+3. When the table's cleanup routine cleans up the withdrawal with `flush_seq`,
+ the channel may safely stop and free its structures as all `sender` pointers in routes are now gone.
+
+Finally, some additional information has to be stored in tables:
+
+```
+ _Atomic byte export_used; /* Export journal cleanup scheduled */ \
+ struct rt_pending_export * _Atomic first_export; /* First export to announce */ \
+ byte export_scheduled; /* Export is scheduled */
+ list pending_exports; /* List of packed struct rt_pending_export */
+ struct fib export_fib; /* Auxiliary fib for storing pending exports */
+ u64 next_export_seq; /* The next export will have this ID */
+```
+
+The exports are:
+1. Assigned the `next_export_seq` sequential ID, incrementing this item by one.
+2. Put into `pending_exports` and `export_fib` for both sequential and by-destination access.
+3. Signalled by setting `export_scheduled` and `first_export`.
+
+After processing several exports, `export_used` is set and route table maintenance
+coroutine is woken up to possibly do cleanup.
+
+The `struct rt_pending_export` seems to be best allocated by requesting a whole
+memory page, containing a common list node, a simple header and packed all the
+structures in the rest of the page. This may save a significant amount of memory.
+In case of congestion, there will be lots of exports and every spare kilobyte
+counts. If BIRD is almost idle, the optimization does nothing on the overall performance.
+
+## Export algorithm
+
+As we have explained at the beginning, the current export algorithm is
+synchronous and table-driven. The table walks the channel list and propagates the update.
+The new export algorithm is channel-driven. The table just indicates that it
+has something new in export queue and the channel decides what to do with that and when.
+
+### Pushing an export
+
+When a table has something to export, it enqueues an instance of
+`struct rt_pending_export` together with updating the `last` pointer (and
+possibly also `first`) for this destination's pending exports.
+
+Then it pings its maintenance coroutine (`rt_event`) to notify the exporting
+channels about a new route. Before the maintenance coroutine acquires the table
+lock, the importing protocol may e.g. prepare the next route inbetween.
+The maintenance coroutine, when it wakes up, walks the list of channels and
+wakes their export coroutines.
+
+These two levels of asynchronicity are here for an efficiency reason.
+
+1. In case of low table load, the export is announced just after the import happens.
+2. In case of table congestion, the export notification locks the table as well
+ as all route importers, effectively reducing the number of channel list traversals.
+
+### Processing an export
+
+After these two pings, the channel finally knows that there is an export pending.
+
+1. The channel waits for a semaphore. This semaphore is posted by the table
+ maintenance coroutine.
+2. The channel checks whether there is a `last_export` stored.
+ 1. If yes, it proceeds with the next one.
+ 2. Otherwise it takes `first_export` from the table. This special
+ pointer is atomic and can be accessed without locking and also without clashing
+ with the export cleanup routine.
+3. The channel checks its `export_seen_map` whether this export has been
+ already processed. If so, it goes back to 1. to get the next export. No
+ action is needed with this one.
+4. As now the export is clearly new, the export chain (single-linked list) is
+ scanned for the current first and last export. This is done by following the
+ `next` pointer in the exports.
+5. If all-routes mode is used, the exports are processed one-by-one. In future
+ versions, we may employ some simple flap-dampening by checking the pending
+ export list for the same route src. *No table locking happens.*
+6. If best-only mode is employed, just the first and last exports are
+ considered to find the old and new best routes. The inbetween exports do nothing. *No table locking happens.*
+7. If export-by-feed is used, the current state of routes in table are fetched and processed
+ as described above in the "Export by feed" section.
+8. All processed exports are marked as seen.
+9. The channel stores the first processed export to `last_export` and returns
+ to beginning.to wait for next exports. The latter exports are then skipped by
+ step 3 when the export coroutine gets to them.
+
+## The full life-cycle of routes
+
+Until now, we're always assuming that the channels *just exist*. In real life,
+any channel may go up or down and we must handle it, flushing the routes
+appropriately and freeing all the memory just in time to avoid both
+use-after-free and memory leaks. BIRD is written in C which has no garbage
+collector or other modern features alike so memory management is a thing.
+
+### Protocols and channels as viewed from a route
+
+BIRD consists effectively of protocols and tables. **Protocols** are active parts,
+kind-of subprocesses manipulating routes and other data. **Tables** are passive,
+serving as a database of routes. To connect a protocol to a table, a
+**channel** is created.
+
+Every route has its `sender` storing the channel which has put the route into
+the current table. Therefore we know which routes to flush when a channel goes down.
+
+Every route also has its `src`, a route source allocated by the protocol which
+originated it first. This is kept when a route is passed through a *pipe*. The
+route source is always bound to protocol; it is possible that a protocol
+announces routes via several channels using the same src.
+
+Both `src` and `sender` must point to active protocols and channels as inactive
+protocols and channels may be deleted any time.
+
+### Protocol and channel lifecycle
+
+In the beginning, all channels and protocols are down. Until they fully start,
+no route from them is allowed to any table. When the protocol and channel is up,
+they may originate and receive routes freely. However, the transitions are worth mentioning.
+
+### Channel startup and feed
+
+When protocols and channels start, they need to get the current state of the
+appropriate table. Therefore, after a protocol and channel start, also the
+export-feed coroutine is initiated.
+
+Tables can contain millions of routes. It may lead to long import latency if a channel
+was feeding itself in one step. The table structure is (at least for now) too
+complicated to be implemented as lockless, thus even read access needs locking.
+To mitigate this, the feeds are split to allow for regular route propagation
+with a reasonable latency.
+
+When the exports were synchronous, we simply didn't care and just announced the
+exports to the channels from the time they started feeding. When making exports
+asynchronous, it is crucial to avoid (hopefully) all the possible race conditions
+which could arise from simultaneous feed and export. As the feeder routines had
+to be rewritten, it is a good opportunity to make this precise.
+
+Therefore, when a channel goes up, it also starts exports:
+
+1. Start the feed-export coroutine.
+2. *Lock the table.*
+3. Store the last export in queue.
+4. Read a limited number of routes to local memory together with their pending exports.
+5. If there are some routes to process:
+ 1. *Unlock the table.*
+ 2. Process the loaded routes.
+ 3. Set the appropriate pending exports as seen.
+ 4. *Lock the table*
+ 5. Go to 4. to continue feeding.
+6. If there was a last export stored, load the next one to be processed. Otherwise take the table's `first_export`.
+7. *Unlock the table.*
+8. Run the exporter loop.
+
+*Note: There are some nuances not mentioned here how to do things in right
+order to avoid missing some events while changing state. For specifics, look
+into the code in `nest/rt-table.c` in branch `alderney`.*
+
+When the feeder loop finishes, it continues smoothly to process all the exports
+that have been queued while the feed was running. Step 5.3 ensures that already
+seen exports are skipped, steps 3 and 6 ensure that no export is missed.
+
+### Channel flush
+
+Protocols and channels need to stop for a handful of reasons, All of these
+cases follow the same routine.
+
+1. (Maybe.) The protocol requests to go down or restart.
+2. The channel requests to go down or restart.
+3. The channel requests to stop export.
+4. In the feed-export coroutine:
+ 1. At a designated cancellation point, check cancellation.
+ 2. Clean up local data.
+ 3. *Lock main BIRD context*
+ 4. If shutdown requested, switch the channel to *flushing* state and request table maintenance.
+ 5. *Stop the coroutine and unlock main BIRD context.*
+5. In the table maintenance coroutine:
+ 1. Walk across all channels and check them for *flushing* state, setting `flush_active` to 1.
+ 2. Walk across the table (split to allow for low latency updates) and
+ generate a withdrawal for each route sent by the flushing channels.
+ 3. When all the table is traversed, the flushing channels' `flush_active` is set to 2 and
+ `flush_seq` is set to the current last export seq.
+ 3. Wait until all the withdrawals are processed by checking the `flush_seq`.
+ 4. Mark the flushing channels as *down* and eventually proceed to the protocol shutdown or restart.
+
+There is also a separate routine that handles bulk cleanup of `src`'s which
+contain a pointer to the originating protocol. This routine may get reworked in
+future; for now it is good enough.
+
+### Route export cleanup
+
+Last but not least is the export cleanup routine. Until now, the withdrawn
+routes were exported synchronously and freed directly after the import was
+done. This is not possible anymore. The export is stored and the import returns
+to let the importing protocol continue its work. We therefore need a routine to
+cleanup the withdrawn routes and also the processed exports.
+
+First of all, this routine refuses to cleanup when any export is feeding or
+shutting down. In future, cleanup while feeding should be possible, anyway for
+now we aren't sure about possible race conditions.
+
+Anyway, when all the exports are in a steady state, the routine works as follows:
+
+1. Walk the active exports and find a minimum (oldest export) between their `last_export` values.
+2. If there is nothing to clear between the actual oldest export and channels' oldest export, do nothing.
+3. Find the table's new `first_export` and set it. Now there is nobody pointing to the old exports.
+4. Free the withdrawn routes.
+5. Free the old exports, removing them also from the first-last list of exports for the same destination.
+
+## Results of these changes
+
+This step is a first major step to move forward. Using just this version may be
+still as slow as the single-threaded version, at least if your export filters are trivial.
+Anyway, the main purpose of this step is not an immediate speedup. It is more
+of a base for the next steps:
+
+* Unlocking of pipes should enable parallel execution of all the filters on
+ pipes, limited solely by the principle *one thread for every direction of
+ pipe*.
+* Conversion of CLI's `show route` to the new feed-export coroutines should
+ enable faster table queries. Moreover, this approach will allow for
+ better splitting of model and view in CLI with a good opportunity to
+ implement more output formats, e.g. JSON.
+* Unlocking of kernel route synchronization should fix latency issues induced
+ by long-lasting kernel queries.
+* Partial unlocking of BGP packet processing should allow for parallel
+ execution in almost all phases of BGP route propagation.
+* Partial unlocking of OSPF route recalculation should raise the useful
+ maximums of topology size.
+
+The development is now being done mostly in the branch `alderney`. If you asked
+why such strange branch names like `jersey`, `guernsey` and `alderney`, here is
+a kind-of reason. Yes, these branches could be named `mq-async-export`,
+`mq-async-export-new`, `mq-async-export-new-new`, `mq-another-async-export` and
+so on. That's so ugly, isn't it? Let's be creative. *Jersey* is an island where a
+same-named knit was first produced – and knits are made of *threads*. Then, you
+just look into a map and find nearby islands.
+
+Also why so many branches? The development process is quite messy. BIRD's code
+heavily depends on single-threaded approach. This is (in this case)
+exceptionally good for performance, as long as you have one thread only. On the
+other hand, lots of these assumptions are not documented so in many cases one
+desired change yields a chain of other unforeseen changes which must precede.
+This brings lots of backtracking, branch rebasing and other Git magic. There is
+always a can of worms somewhere in the code.
+
+*It's still a long road to the version 2.1. This series of texts should document
+what is needed to be changed, why we do it and how. The
+[previous chapter](https://en.blog.nic.cz/2021/03/23/bird-journey-to-threads-chapter-1-the-route-and-its-attributes/)
+showed the necessary changes in route storage. In the next chapter, we're going
+to describe how the coroutines are implemented and what kind of locking system
+are we employing to prevent deadlocks. Stay tuned!*
diff --git a/doc/threads/03_coroutines.md b/doc/threads/03_coroutines.md
new file mode 100644
index 00000000..8d8e6c96
--- /dev/null
+++ b/doc/threads/03_coroutines.md
@@ -0,0 +1,235 @@
+# BIRD Journey to Threads. Chapter 3: Parallel execution and message passing.
+
+Parallel execution in BIRD uses an underlying mechanism of dedicated IO loops
+and hierarchical locks. The original event scheduling module has been converted
+to do message passing in multithreaded environment. These mechanisms are
+crucial for understanding what happens inside BIRD and how its internal API changes.
+
+BIRD is a fast, robust and memory-efficient routing daemon designed and
+implemented at the end of 20th century. We're doing a significant amount of
+BIRD's internal structure changes to make it run in multiple threads in parallel.
+
+## Locking and deadlock prevention
+
+Most of BIRD data structures and algorithms are thread-unsafe and not even
+reentrant. Checking and possibly updating all of these would take an
+unreasonable amount of time, thus the multithreaded version uses standard mutexes
+to lock all the parts which have not been checked and updated yet.
+
+The authors of original BIRD concepts wisely chose a highly modular structure
+which allows to create a hierarchy for locks. The main chokepoint was between
+protocols and tables and it has been removed by implementing asynchronous exports
+as described in the [previous chapter](https://en.blog.nic.cz/2021/06/14/bird-journey-to-threads-chapter-2-asynchronous-route-export/).
+
+Locks in BIRD (called domains, as they always lock some defined part of BIRD)
+are partially ordered. Every *domain* has its *type* and all threads are
+strictly required to lock the domains in the order of their respective types.
+The full order is defined in `lib/locking.h`. It's forbidden to lock more than
+one domain of a type (these domains are uncomparable) and recursive locking is
+forbidden as well.
+
+The locking hiearchy is (roughly; as of February 2022) like this:
+
+1. The BIRD Lock (for everything not yet checked and/or updated)
+2. Protocols (as of February 2022, it is BFD, RPKI, Pipe and BGP)
+3. Routing tables
+4. Global route attribute cache
+5. Message passing
+6. Internals and memory management
+
+There are heavy checks to ensure proper locking and to help debugging any
+problem when any code violates the hierarchy rules. This impedes performance
+depending on how much that domain is contended and in some cases I have already
+implemented lockless (or partially lockless) data structures to overcome this.
+
+You may ask, why are these heavy checks then employed in production builds?
+Risks arising from dropping some locking checks include:
+
+* deadlocks; these are deadly in BIRD anyway so it should just fail with a meaningful message, or
+* data corruption; it either kills BIRD anyway, or it results into a slow and vicious death,
+ leaving undebuggable corefiles behind.
+
+To be honest, I believe in principles like *"every nontrivial software has at least one bug"*
+and I also don't trust my future self or anybody else to always write bugless code when
+it comes to proper locking. I also believe that if a lock becomes a bottle-neck,
+then we should think about what is locked inside and how to optimize that,
+possibly implementing a lockless or waitless data structure instead of dropping
+thorough consistency checks, especially in a multithreaded environment.
+
+### Choosing the right locking order
+
+When considering the locking order of protocols and route tables, the answer
+was quite easy. We had to make either import or export asynchronous (or both).
+Major reasons for asynchronous export have been stated in the previous chapter,
+therefore it makes little sense to allow entering protocol context from table code.
+
+As I write further in this text, even accessing table context from protocol
+code leads to contention on table locks, yet for now, it is good enough and the
+lock order features routing tables after protocols to make the multithreading
+goal easier to achieve.
+
+The major lock level is still The BIRD Lock, containing not only the
+not-yet-converted protocols (like Babel, OSPF or RIP) but also processing CLI
+commands and reconfiguration. This involves an awful lot of direct access into
+other contexts which would be unnecessarily complicated to implement by message
+passing. Therefore, this lock is simply *"the director"*, sitting on the top
+with its own category.
+
+The lower lock levels under routing tables are mostly for shared global data
+structures accessed from everywhere. We'll address some of these later.
+
+## IO Loop
+
+There has been a protocol, BFD, running in its own thread since 2013. This
+separation has a good reason; it needs low latency and the main BIRD loop just
+walks round-robin around all the available sockets and one round-trip may take
+a long time (even more than a minute with large configurations). BFD had its
+own IO loop implementation and simple message passing routines. This code could
+be easily updated for general use so I did it.
+
+To understand the internal principles, we should say that in the `master`
+branch, there is a big loop centered around a `poll()` call, dispatching and
+executing everything as needed. In the `sark` branch, there are multiple loops
+of this kind. BIRD has several means how to get something dispatched from a
+loop.
+
+1. Requesting to read from a **socket** makes the main loop call your hook when there is some data received.
+ The same happens when a socket refuses to write data. Then the data is buffered and you are called when
+ the buffer is free to continue writing. There is also a third callback, an error hook, for obvious reasons.
+
+2. Requesting to be called back after a given amount of time. This is called **timer**.
+ As is common with all timers, they aren't precise and the callback may be
+ delayed significantly. This was also the reason to have BFD loop separate
+ since the very beginning, yet now the abundance of threads may lead to
+ problems with BFD latency in large-scale configurations. We haven't tested
+ this yet.
+
+3. Requesting to be called back from a clean context when possible. This is
+ useful to run anything not reentrant which might mess with the caller's
+ data, e.g. when a protocol decides to shutdown due to some inconsistency
+ in received data. This is called **event**.
+
+4. Requesting to do some work when possible. These are also events, there is only
+ a difference where this event is enqueued; in the main loop, there is a
+ special *work queue* with an execution limit, allowing sockets and timers to be
+ handled with a reasonable latency while still doing all the work needed.
+ Other loops don't have designated work queues (we may add them later).
+
+All these, sockets, timers and events, are tightly bound to some domain.
+Sockets typically belong to a protocol, timers and events to a protocol or table.
+With the modular structure of BIRD, the easy and convenient approach to multithreading
+is to get more IO loops, each bound to a specific domain, running their events, timers and
+socket hooks in their threads.
+
+## Message passing and loop entering
+
+To request some work in another module, the standard way is to pass a message.
+For this purpose, events have been modified to be sent to a given loop without
+locking that loop's domain. In fact, every event queue has its own lock with a
+low priority, allowing to pass messages from almost any part of BIRD, and also
+an assigned loop which executes the events enqueued. When a message is passed
+to a queue executed by another loop, that target loop must be woken up so we
+must know what loop to wake up to avoid unnecessary delays. Then the target
+loop opens its mailbox and processes the task in its context.
+
+The other way is a direct access of another domain. This approach blocks the
+appropriate loop from doing anything and we call it *entering a birdloop* to
+remember that the task must be fast and *leave the birdloop* as soon as possible.
+Route import is done via direct access from protocols to tables; in large
+setups with fast filters, this is a major point of contention (after filters
+have been parallelized) and will be addressed in future optimization efforts.
+Reconfiguration and interface updates also use direct access; more on that later.
+In general, this approach should be avoided unless there are good reasons to use it.
+
+Even though direct access is bad, sending lots of messages may be even worse.
+Imagine one thousand post(wo)men, coming one by one every minute, ringing your
+doorbell and delivering one letter each to you. Horrible! Asynchronous message
+passing works exactly this way. After queuing the message, the source sends a
+byte to a pipe to wakeup the target loop to process the task. We could also
+periodically poll for messages instead of waking up the targets, yet it would
+add quite a lot of latency which we also don't like.
+
+Messages in BIRD don't typically suffer from the problem of amount and the
+overhead is negligible compared to the overall CPU consumption. With one notable
+exception: route import/export.
+
+### Route export message passing
+
+If we had to send a ping for every route we import to every exporting channel,
+we'd spend more time pinging than doing anything else. Been there, seen
+those unbelievable 80%-like figures in Perf output. Never more.
+
+Route update is quite a complicated process. BIRD must handle large-scale
+configurations with lots of importers and exporters. Therefore, a
+triple-indirect delayed route announcement is employed:
+
+1. First, when a channel imports a route by entering a loop, it sends an event
+ to its own loop (no ping needed in such case). This operation is idempotent,
+ thus for several routes in a row, only one event is enqueued. This reduces
+ several route import announcements (even hundreds in case of massive BGP
+ withdrawals) to one single event.
+2. When the channel is done importing (or at least takes a coffee break and
+ checks its mailbox), the scheduled event in its own loop is run, sending
+ another event to the table's loop, saying basically *"Hey, table, I've just
+ imported something."*. This event is also idempotent and further reduces
+ route import announcements from multiple sources to one single event.
+3. The table's announcement event is then executed from its loop, enqueuing export
+ events for all connected channels, finally initiating route exports. As we
+ already know, imports are done by direct access, therefore if protocols keep
+ importing, export announcements are slowed down.
+4. The actual data on what has been updated is stored in a table journal. This
+ peculiar technique is used only for informing the exporting channels that
+ *"there is something to do"*.
+
+This may seem overly complicated, yet it should work and it seems to work. In
+case of low load, all these notifications just come through smoothly. In case
+of high load, it's common that multiple updates come for the same destination.
+Delaying the exports allows for the updates to settle down and export just the
+final result, reducing CPU load and export traffic.
+
+## Cork
+
+Route propagation is involved in yet another problem which has to be addressed.
+In the old versions with synchronous route propagation, all the buffering
+happened after exporting routes to BGP. When a packet arrived, all the work was
+done in BGP receive hook – parsing, importing into a table, running all the
+filters and possibly sending to the peers. No more routes until the previous
+was done. This self-regulating mechanism doesn't work any more.
+
+Route table import now returns immediately after inserting the route into a
+table, creating a buffer there. These buffers have to be processed by other protocols'
+export events. In large-scale configurations, one route import has to be
+processed by hundreds, even thousands of exports. Unlimited imports are a major
+cause of buffer bloating. This is even worse in configurations with pipes,
+as these multiply the exports by propagating them all the way down to other
+tables, eventually eating about twice the amount of memory than the single-threaded version.
+
+There is therefore a cork to make this stop. Every table is checking how many
+exports it has pending, and when adding a new export to the queue, it may request
+a cork, saying simply "please stop the flow for a while". When the export buffer
+size is reduced low enough, the table uncorks.
+
+On the other side, there are events and sockets with a cork assigned. When
+trying to enqueue an event and the cork is applied, the event is instead put
+into the cork's queue and released only when the cork is released. In case of
+sockets, when read is indicated or when `poll` arguments are recalculated,
+the corked socket is simply not checked for received packets, effectively
+keeping them in the TCP queue and slowing down the flow until cork is released.
+
+The cork implementation is quite crude and rough and fragile. It may get some
+rework while stabilizing the multi-threaded version of BIRD or we may even
+completely drop it for some better mechanism. One of these candidates is this
+kind of API:
+
+* (table to protocol) please do not import
+* (table to protocol) you may resume imports
+* (protocol to table) not processing any exports
+* (protocol to table) resuming export processing
+
+Anyway, cork works as intended in most cases at least for now.
+
+*It's a long road to the version 2.1. This series of texts should document what
+is changing, why we do it and how. The
+[previous chapter](https://en.blog.nic.cz/2021/06/14/bird-journey-to-threads-chapter-2-asynchronous-route-export/)
+shows how the route export had to change to allow parallel execution. In the next chapter, some memory management
+details are to be explained together with the reasons why memory management matters. Stay tuned!*
diff --git a/doc/threads/04_memory_management.md b/doc/threads/04_memory_management.md
new file mode 100644
index 00000000..24ef89d3
--- /dev/null
+++ b/doc/threads/04_memory_management.md
@@ -0,0 +1,223 @@
+# BIRD Journey to Threads. Chapter 4: Memory and other resource management.
+
+BIRD is mostly a large specialized database engine, storing mega/gigabytes of
+Internet routing data in memory. To keep accounts of every byte of allocated data,
+BIRD has its own resource management system which must be adapted to the
+multithreaded environment. The resource system has not changed much, yet it
+deserves a short chapter.
+
+BIRD is a fast, robust and memory-efficient routing daemon designed and
+implemented at the end of 20th century. We're doing a significant amount of
+BIRD's internal structure changes to make it run in multiple threads in parallel.
+
+## Resources
+
+Inside BIRD, (almost) every piece of allocated memory is a resource. To achieve this,
+every such memory block includes a generic `struct resource` header. The node
+is enlisted inside a linked list of a *resource pool* (see below), the class
+pointer defines basic operations done on resources.
+
+```
+typedef struct resource {
+ node n; /* Inside resource pool */
+ struct resclass *class; /* Resource class */
+} resource;
+
+struct resclass {
+ char *name; /* Resource class name */
+ unsigned size; /* Standard size of single resource */
+ void (*free)(resource *); /* Freeing function */
+ void (*dump)(resource *); /* Dump to debug output */
+ resource *(*lookup)(resource *, unsigned long); /* Look up address (only for debugging) */
+ struct resmem (*memsize)(resource *); /* Return size of memory used by the resource, may be NULL */
+};
+
+void *ralloc(pool *, struct resclass *);
+```
+
+Resource cycle begins with an allocation of a resource. To do that, you should call `ralloc()`,
+passing the parent pool and the appropriate resource class as arguments. BIRD
+allocates a memory block of size given by the given class member `size`.
+Beginning of the block is reserved for `struct resource` itself and initialized
+by the given arguments. Therefore, you may sometimes see an idiom where a structure
+has a first member `struct resource r;`, indicating that this item should be
+allocated as a resource.
+
+The counterpart is resource freeing. This may be implicit (by resource pool
+freeing) or explicit (by `rfree()`). In both cases, the `free()` function of
+the appropriate class is called to cleanup the resource before final freeing.
+
+To account for `dump` and `memsize` calls, there are CLI commands `dump
+resources` and `show memory`, using these to dump resources or show memory
+usage as perceived by BIRD.
+
+The last, `lookup`, is quite an obsolete way to identify a specific pointer
+from a debug interface. You may call `rlookup(pointer)` and BIRD should dump
+that resource to the debug output. This mechanism is probably incomplete as no
+developer uses it actively for debugging.
+
+Resources can be also moved between pools by `rmove` when needed.
+
+## Resource pools
+
+The first internal resource class is a recursive resource – a resource pool. In
+the singlethreaded version, this is just a simple structure:
+
+```
+struct pool {
+ resource r;
+ list inside;
+ struct birdloop *loop; /* In multithreaded version only */
+ const char *name;
+};
+```
+
+Resource pools are used for grouping resources together. There are pools everywhere
+and it is a common idiom inside BIRD to just `rfree` the appropriate pool when
+e.g. a protocol or table is going down. Everything left there is cleaned up.
+
+There are anyway several classes which must be freed with care. In the
+singlethreaded version, the *slab* allocator (see below) must be empty before
+it may be freed and this is kept to the multithreaded version while other
+restrictions have been added.
+
+There is also a global pool, `root_pool`, containing every single resource BIRD
+knows about, either directly or via another resource pool.
+
+### Thread safety in resource pools
+
+In the multithreaded version, every resource pool is bound to a specific IO
+loop and therefore includes an IO loop pointer. This is important for allocations
+as the resource list inside the pool is thread-unsafe. All pool operations
+therefore require the IO loop to be entered to do anything with them, if possible.
+(In case of `rfree`, the pool data structure is not accessed at all so no
+assert is possible. We're currently relying on the caller to ensure proper locking.
+In future, this may change.)
+
+Each IO loop also has its base resource pool for its allocations. All pools
+inside the IO loop pool must belong to the same loop or to a loop with a
+subordinate lock (see the previous chapter for lock ordering). If there is a
+need for multiple IO loops to access one shared data structure, it must be
+locked by another lock and allocated in such a way that is independent on these
+accessor loops.
+
+The pool structure should follow the locking order. Any pool should belong to
+either the same loop as its parent or its loop lock should be after its parent
+loop lock in the locking order. This is not enforced explicitly, yet it is
+virtually impossible to write some working code violating this recommendation.
+
+### Resource pools in the wilderness
+
+Root pool contains (among others):
+
+* route attributes and sources
+* routing tables
+* protocols
+* interfaces
+* configuration data
+
+Each table has its IO loop and uses the loop base pool for allocations.
+The same holds for protocols. Each protocol has its pool; it is either its IO
+loop base pool or an ordinary pool bound to main loop.
+
+## Memory allocators
+
+BIRD stores data in memory blocks allocated by several allocators. There are 3
+of them: simple memory blocks, linear pools and slabs.
+
+### Simple memory block
+
+When just a chunk of memory is needed, `mb_alloc()` or `mb_allocz()` is used
+to get it. The first with `malloc()` semantics, the other is also zeroed.
+There is also `mb_realloc()` available, `mb_free()` to explicitly free such a
+memory and `mb_move()` to move that memory to another pool.
+
+Simple memory blocks consume a fixed amount of overhead memory (32 bytes on
+systems with 64-bit pointers) so they are suitable mostly for big chunks,
+taking advantage of the default *stdlib* allocator which is used by this
+allocation strategy. There are anyway some parts of BIRD (in all versions)
+where this allocator is used for little blocks. This will be fixed some day.
+
+### Linear pools
+
+Sometimes, memory is allocated temporarily. When the data may just sit on
+stack, we put it there. Anyway, many tasks need more structured execution where
+stack allocation is incovenient or even impossible (e.g. when callbacks from
+parsers are involved). For such a case, a *linpool* is the best choice.
+
+This data structure allocates memory blocks of requested size with negligible
+overhead in functions `lp_alloc()` (uninitialized) or `lp_allocz()` (zeroed).
+There is anyway no `realloc` and no `free` call; to have a larger chunk, you
+need to allocate another block. All this memory is freed at once by `lp_flush()`
+when it is no longer needed.
+
+You may see linpools in parsers (BGP, Linux netlink, config) or in filters.
+
+In the multithreaded version, linpools have received an update, allocating
+memory pages directly by `mmap()` instead of calling `malloc()`. More on memory
+pages below.
+
+### Slabs
+
+To allocate lots of same-sized objects, a [slab allocator](https://en.wikipedia.org/wiki/Slab_allocation)
+is an ideal choice. In versions until 2.0.8, our slab allocator used blocks
+allocated by `malloc()`, every object included a *slab head* pointer and free objects
+were linked into a single-linked list. This led to memory inefficiency and to
+contra-intuitive behavior where a use-after-free bug could do lots of damage
+before finally crashing.
+
+Versions from 2.0.9, and also all the multithreaded versions, are coming with
+slabs using directly allocated memory pages and usage bitmaps instead of
+single-linking the free objects. This approach however relies on the fact that
+pointers returned by `mmap()` are always divisible by page size. Freeing of a
+slab object involves zeroing (mostly) 13 least significant bits of its pointer
+to get the page pointer where the slab head resides.
+
+This update helps with memory consumption by about 5% compared to previous
+versions; exact numbers depend on the usage pattern.
+
+## Raw memory pages
+
+Until 2.0.8 (incl.), BIRD allocated all memory by `malloc()`. This method is
+suitable for lots of use cases, yet when gigabytes of memory should be
+allocated by little pieces, BIRD uses its internal allocators to keep track
+about everything. This brings some ineffectivity as stdlib allocator has its
+own overhead and doesn't allocate aligned memory unless asked for.
+
+Slabs and linear pools are backed by blocks of memory of kilobyte sizes. As a
+typical memory page size is 4 kB, it is a logical step to drop stdlib
+allocation from these allocators and to use `mmap()` directly. This however has
+some drawbacks, most notably the need of a syscall for every memory mapping and
+unmapping. For allocations, this is not much a case and the syscall time is typically
+negligible compared to computation time. When freeing memory, this is much
+worse as BIRD sometimes frees gigabytes of data in a blink of eye.
+
+To minimize the needed number of syscalls, there is a per-thread page cache,
+keeping pages for future use:
+
+* When a new page is requested, first the page cache is tried.
+* When a page is freed, the per-thread page cache keeps it without telling the kernel.
+* When the number of pages in any per-thread page cache leaves a pre-defined range,
+ a cleanup routine is scheduled to free excessive pages or request more in advance.
+
+This method gives the multithreaded BIRD not only faster memory management than
+ever before but also almost immediate shutdown times as the cleanup routine is
+not scheduled on shutdown at all.
+
+## Other resources
+
+Some objects are not only a piece of memory; notable items are sockets, owning
+the underlying mechanism of I/O, and *object locks*, owning *the right to use a
+specific I/O*. This ensures that collisions on e.g. TCP port numbers and
+addresses are resolved in a predictable way.
+
+All these resources should be used with the same locking principles as the
+memory blocks. There aren't many checks inside BIRD code to ensure that yet,
+nevertheless violating this recommendation may lead to multiple-access issues.
+
+*It's still a long road to the version 2.1. This series of texts should document
+what is needed to be changed, why we do it and how. The
+[previous chapter](TODO)
+showed the locking system and how the parallel execution is done.
+The next chapter will cover a bit more detailed explanation about route sources
+and route attributes and how lockless data structures are employed there. Stay tuned!*
diff --git a/doc/threads/Makefile b/doc/threads/Makefile
new file mode 100644
index 00000000..fafb8d47
--- /dev/null
+++ b/doc/threads/Makefile
@@ -0,0 +1,29 @@
+SUFFICES := .pdf -wordpress.html
+CHAPTERS := 00_the_name_of_the_game 01_the_route_and_its_attributes 02_asynchronous_export 03_coroutines
+
+all: $(foreach ch,$(CHAPTERS),$(addprefix $(ch),$(SUFFICES)))
+
+00_the_name_of_the_game.pdf: 00_filter_structure.png
+
+%.pdf: %.md
+ pandoc -f markdown -t latex -o $@ $<
+
+%.html: %.md
+ pandoc -f markdown -t html5 -o $@ $<
+
+%-wordpress.html: %.html Makefile
+ sed -r 's#</p>#\n#g; s#<p>##g; s#<(/?)code>#<\1tt>#g; s#<pre><tt>#<code>#g; s#</tt></pre>#</code>#g; s#</?figure>##g; s#<figcaption>#<p style="text-align: center">#; s#</figcaption>#</p>#; ' $< > $@
+
+stats-%.csv: stats.csv stats-filter.pl
+ perl stats-filter.pl $< $* > $@
+
+STATS_VARIANTS := multi imex mulimex single
+stats-all: $(patsubst %,stats-%.csv,$(STATS_VARIANTS))
+
+stats-2d-%.pdf: stats.csv stats-filter-2d.pl
+ perl stats-filter-2d.pl $< $* $@
+
+stats-2d-%.png: stats-2d-%.pdf
+ gs -dBATCH -dNOPAUSE -sDEVICE=pngalpha -sOutputFile=$@ -r300 $<
+
+stats-all-2d: $(foreach suf,pdf png,$(patsubst %,stats-2d-%.$(suf),$(STATS_VARIANTS)))
diff --git a/doc/threads/stats-draw.gnuplot b/doc/threads/stats-draw.gnuplot
new file mode 100644
index 00000000..734f14a6
--- /dev/null
+++ b/doc/threads/stats-draw.gnuplot
@@ -0,0 +1,41 @@
+set datafile columnheaders
+set datafile separator ";"
+#set isosample 15
+set dgrid3d 8,8
+set logscale
+set view 80,15,1,1
+set autoscale xy
+#set pm3d
+
+set term pdfcairo size 20cm,15cm
+
+set xlabel "TOTAL ROUTES" offset 0,-1.5
+set xrange [10000:320000]
+set xtics offset 0,-0.5
+set xtics (10000,15000,30000,50000,100000,150000,300000)
+
+set ylabel "PEERS"
+#set yrange [10:320]
+#set ytics (10,15,30,50,100,150,300)
+set yrange [10:320]
+set ytics (10,15,30,50,100,150,300)
+
+set zrange [1:2000]
+set xyplane at 1
+
+set border 895
+
+#set grid ztics lt 20
+
+set output ARG1 . "-" . ARG4 . ".pdf"
+
+splot \
+ ARG1 . ".csv" \
+ using "TOTAL_ROUTES":"PEERS":ARG2."/".ARG4 \
+ with lines \
+ title ARG2."/".ARG4, \
+ "" \
+ using "TOTAL_ROUTES":"PEERS":ARG3."/".ARG4 \
+ with lines \
+ title ARG3."/".ARG4
+
diff --git a/doc/threads/stats-filter-2d.pl b/doc/threads/stats-filter-2d.pl
new file mode 100644
index 00000000..59eda26b
--- /dev/null
+++ b/doc/threads/stats-filter-2d.pl
@@ -0,0 +1,137 @@
+#!/usr/bin/perl
+
+use common::sense;
+use Data::Dump;
+use List::Util;
+
+my @GROUP_BY = qw/VERSION PEERS TOTAL_ROUTES/;
+my @VALUES = qw/TIMEDIF/;
+
+my ($FILE, $TYPE, $OUTPUT) = @ARGV;
+
+### Load data ###
+my %data;
+open F, "<", $FILE or die $!;
+my @header = split /;/, <F>;
+chomp @header;
+
+my $line = undef;
+while ($line = <F>)
+{
+ chomp $line;
+ $line =~ s/;;(.*);;/;;\1;/;
+ $line =~ s/v2\.0\.8-1[89][^;]+/bgp/;
+ $line =~ s/v2\.0\.8-[^;]+/sark/;
+ $line =~ s/master;/v2.0.8;/;
+ my %row;
+ @row{@header} = split /;/, $line;
+ push @{$data{join ";", @row{@GROUP_BY}}}, { %row } if $row{TYPE} eq $TYPE;
+}
+
+### Do statistics ###
+sub avg {
+ return List::Util::sum(@_) / @_;
+}
+
+sub stdev {
+ my $avg = shift;
+ return 0 if @_ <= 1;
+ return sqrt(List::Util::sum(map { ($avg - $_)**2 } @_) / (@_-1));
+}
+
+my %output;
+my %vers;
+my %peers;
+
+STATS:
+foreach my $k (keys %data)
+{
+ my %cols = map { my $vk = $_; $vk => [ map { $_->{$vk} } @{$data{$k}} ]; } @VALUES;
+
+ my %avg = map { $_ => avg(@{$cols{$_}})} @VALUES;
+ my %stdev = map { $_ => stdev($avg{$_}, @{$cols{$_}})} @VALUES;
+
+ foreach my $v (@VALUES) {
+ next if $stdev{$v} / $avg{$v} < 0.035;
+
+ for (my $i=0; $i<@{$cols{$v}}; $i++)
+ {
+ my $dif = $cols{$v}[$i] - $avg{$v};
+ next if $dif < $stdev{$v} * 2 and $dif > $stdev{$v} * (-2);
+=cut
+ printf "Removing an outlier for %s/%s: avg=%f, stdev=%f, variance=%.1f%%, val=%f, valratio=%.1f%%\n",
+ $k, $v, $avg{$v}, $stdev{$v}, (100 * $stdev{$v} / $avg{$v}), $cols{$v}[$i], (100 * $dif / $stdev{$v});
+=cut
+ splice @{$data{$k}}, $i, 1, ();
+ redo STATS;
+ }
+ }
+
+ $vers{$data{$k}[0]{VERSION}}++;
+ $peers{$data{$k}[0]{PEERS}}++;
+ $output{$data{$k}[0]{VERSION}}{$data{$k}[0]{PEERS}}{$data{$k}[0]{TOTAL_ROUTES}} = { %avg };
+}
+
+(3 == scalar %vers) and $vers{sark} and $vers{bgp} and $vers{"v2.0.8"} or die "vers size is " . (scalar %vers) . ", items ", join ", ", keys %vers;
+
+### Export the data ###
+
+open PLOT, "|-", "gnuplot" or die $!;
+
+say PLOT <<EOF;
+set logscale
+
+set term pdfcairo size 20cm,15cm
+
+set xlabel "Total number of routes" offset 0,-1.5
+set xrange [10000:1500000]
+set xtics offset 0,-0.5
+set xtics (10000,15000,30000,50000,100000,150000,300000,500000,1000000)
+
+set ylabel "Time to converge (s)"
+set yrange [0.5:10800]
+
+set grid
+
+set key left top
+
+set output "$OUTPUT"
+EOF
+
+my @colors = (
+ [ 1, 0.3, 0.3 ],
+ [ 1, 0.7, 0 ],
+ [ 0.3, 1, 0 ],
+ [ 0, 1, 0.3 ],
+ [ 0, 0.7, 1 ],
+ [ 0.3, 0.3, 1 ],
+);
+
+my $steps = (scalar %peers) - 1;
+
+my @plot_data;
+foreach my $v (sort keys %vers) {
+ my $color = shift @colors;
+ my $endcolor = shift @colors;
+ my $stepcolor = [ map +( ($endcolor->[$_] - $color->[$_]) / $steps ), (0, 1, 2) ];
+
+ foreach my $p (sort { int $a <=> int $b } keys %peers) {
+ my $vnodot = $v; $vnodot =~ s/\.//g;
+ say PLOT "\$data_${vnodot}_${p} << EOD";
+ foreach my $tr (sort { int $a <=> int $b } keys %{$output{$v}{$p}}) {
+ say PLOT "$tr $output{$v}{$p}{$tr}{TIMEDIF}";
+ }
+ say PLOT "EOD";
+
+ my $colorstr = sprintf "linecolor rgbcolor \"#%02x%02x%02x\"", map +( int($color->[$_] * 255 + 0.5)), (0, 1, 2);
+ push @plot_data, "\$data_${vnodot}_${p} using 1:2 with lines $colorstr linewidth 2 title \"$v, $p peers\"";
+ $color = [ map +( $color->[$_] + $stepcolor->[$_] ), (0, 1, 2) ];
+ }
+}
+
+push @plot_data, "2 with lines lt 1 dashtype 2 title \"Measurement instability\"";
+
+say PLOT "plot ", join ", ", @plot_data;
+close PLOT;
+
+
diff --git a/doc/threads/stats-filter.pl b/doc/threads/stats-filter.pl
new file mode 100644
index 00000000..51690651
--- /dev/null
+++ b/doc/threads/stats-filter.pl
@@ -0,0 +1,84 @@
+#!/usr/bin/perl
+
+use common::sense;
+use Data::Dump;
+use List::Util;
+
+my @GROUP_BY = qw/VERSION PEERS TOTAL_ROUTES/;
+my @VALUES = qw/RSS SZ VSZ TIMEDIF/;
+
+my ($FILE, $TYPE) = @ARGV;
+
+### Load data ###
+my %data;
+open F, "<", $FILE or die $!;
+my @header = split /;/, <F>;
+chomp @header;
+
+my $line = undef;
+while ($line = <F>)
+{
+ chomp $line;
+ my %row;
+ @row{@header} = split /;/, $line;
+ push @{$data{join ";", @row{@GROUP_BY}}}, { %row } if $row{TYPE} eq $TYPE;
+}
+
+### Do statistics ###
+sub avg {
+ return List::Util::sum(@_) / @_;
+}
+
+sub stdev {
+ my $avg = shift;
+ return 0 if @_ <= 1;
+ return sqrt(List::Util::sum(map { ($avg - $_)**2 } @_) / (@_-1));
+}
+
+my %output;
+my %vers;
+
+STATS:
+foreach my $k (keys %data)
+{
+ my %cols = map { my $vk = $_; $vk => [ map { $_->{$vk} } @{$data{$k}} ]; } @VALUES;
+
+ my %avg = map { $_ => avg(@{$cols{$_}})} @VALUES;
+ my %stdev = map { $_ => stdev($avg{$_}, @{$cols{$_}})} @VALUES;
+
+ foreach my $v (@VALUES) {
+ next if $stdev{$v} / $avg{$v} < 0.035;
+
+ for (my $i=0; $i<@{$cols{$v}}; $i++)
+ {
+ my $dif = $cols{$v}[$i] - $avg{$v};
+ next if $dif < $stdev{$v} * 2 and $dif > $stdev{$v} * (-2);
+=cut
+ printf "Removing an outlier for %s/%s: avg=%f, stdev=%f, variance=%.1f%%, val=%f, valratio=%.1f%%\n",
+ $k, $v, $avg{$v}, $stdev{$v}, (100 * $stdev{$v} / $avg{$v}), $cols{$v}[$i], (100 * $dif / $stdev{$v});
+=cut
+ splice @{$data{$k}}, $i, 1, ();
+ redo STATS;
+ }
+ }
+
+ $vers{$data{$k}[0]{VERSION}}++;
+ $output{"$data{$k}[0]{PEERS};$data{$k}[0]{TOTAL_ROUTES}"}{$data{$k}[0]{VERSION}} = { %avg };
+}
+
+### Export the data ###
+
+say "PEERS;TOTAL_ROUTES;" . join ";", ( map { my $vk = $_; map { "$_/$vk" } keys %vers; } @VALUES );
+
+sub keysort {
+ my ($pa, $ta) = split /;/, $_[0];
+ my ($pb, $tb) = split /;/, $_[1];
+
+ return (int $ta) <=> (int $tb) if $pa eq $pb;
+ return (int $pa) <=> (int $pb);
+}
+
+foreach my $k (sort { keysort($a, $b); } keys %output)
+{
+ say "$k;" . join ";", ( map { my $vk = $_; map { $output{$k}{$_}{$vk}; } keys %vers; } @VALUES );
+}
diff --git a/doc/threads/stats-longfilters.csv b/doc/threads/stats-longfilters.csv
new file mode 100644
index 00000000..3b794e53
--- /dev/null
+++ b/doc/threads/stats-longfilters.csv
@@ -0,0 +1,1964 @@
+VERSION;TYPE;PEERS;ROUTES;BLOCKSIZE;TOTAL_ROUTES;TOTAL_PREFICES;RSS;SZ;VSZ;i;TIMEDIF;DATETIME
+v2.0.8-161-ga788372e;multi;10;10000;;12925;6245;21320;187203;748812;20;4.301861676
+v2.0.8-161-ga788372e;multi;10;10000;;12925;6245;21140;187190;748760;22;4.735876270
+v2.0.8-161-ga788372e;multi;10;10000;;12925;6245;21044;187207;748828;20;4.304013846
+v2.0.8-161-ga788372e;multi;10;10000;;12925;6245;20772;187196;748784;19;4.084681546
+v2.0.8-161-ga788372e;multi;10;10000;;12925;6245;20772;187188;748752;21;4.524855352
+v2.0.8-161-ga788372e;multi;10;10000;;12925;6245;20800;187197;748788;19;4.089552928
+v2.0.8-161-ga788372e;multi;10;10000;;12925;6245;21072;187192;748768;20;4.301069856
+v2.0.8-161-ga788372e;multi;10;10000;;12925;6245;21148;187192;748768;18;3.870419206
+v2.0.8-161-ga788372e;imex;10;10000;;12925;6245;19392;6769;27076;24;5.186131393
+v2.0.8-161-ga788372e;imex;10;10000;;12925;6245;19456;6749;26996;24;5.171237831
+v2.0.8-161-ga788372e;imex;10;10000;;12925;6245;19528;6754;27016;25;5.404748204
+v2.0.8-161-ga788372e;imex;10;10000;;12925;6245;19292;6768;27072;24;5.170694801
+v2.0.8-161-ga788372e;imex;10;10000;;12925;6245;19396;6760;27040;27;5.838374799
+v2.0.8-161-ga788372e;imex;10;10000;;12925;6245;19508;6777;27108;25;5.402606133
+v2.0.8-161-ga788372e;imex;10;10000;;12925;6245;19284;6752;27008;24;5.182178320
+v2.0.8-161-ga788372e;imex;10;10000;;12925;6245;19548;6763;27052;24;5.174747059
+v2.0.8-161-ga788372e;single;10;10000;;12925;6245;;6968;3344;13376;26;5.608568489
+v2.0.8-161-ga788372e;single;10;10000;;12925;6245;;7224;3377;13508;26;5.615165423;2021-12-06@14:05:32
+v2.0.8-161-ga788372e;single;10;10000;;12925;6245;;7316;3375;13500;26;5.618026061;2021-12-06@14:06:00
+v2.0.8-161-ga788372e;single;10;10000;;12925;6245;;7260;3352;13408;23;4.961829208;2021-12-06@14:06:27
+v2.0.8-161-ga788372e;single;10;10000;;12925;6245;;7252;3369;13476;25;5.388673471;2021-12-06@14:06:55
+v2.0.8-161-ga788372e;single;10;10000;;12925;6245;;7288;3350;13400;26;5.621786939;2021-12-06@14:07:23
+v2.0.8-161-ga788372e;single;10;10000;;12925;6245;;7188;3369;13476;26;5.609636187;2021-12-06@14:07:51
+v2.0.8-161-ga788372e;single;10;10000;;12925;6245;;7316;3364;13456;24;5.172085664;2021-12-06@14:08:19
+v2.0.8-161-ga788372e;mulimex;10;10000;;12925;6245;31332;189917;759668;17;3.659687724;2021-12-06@14:08:45
+v2.0.8-161-ga788372e;mulimex;10;10000;;12925;6245;30948;189945;759780;22;4.730535289;2021-12-06@14:09:11
+v2.0.8-161-ga788372e;mulimex;10;10000;;12925;6245;30540;189905;759620;20;4.306343387;2021-12-06@14:09:38
+v2.0.8-161-ga788372e;mulimex;10;10000;;12925;6245;30444;189942;759768;20;4.302684747;2021-12-06@14:10:06
+v2.0.8-161-ga788372e;mulimex;10;10000;;12925;6245;30808;189919;759676;18;3.875768252;2021-12-06@14:10:32
+v2.0.8-161-ga788372e;mulimex;10;10000;;12925;6245;30272;189907;759628;19;4.086659602;2021-12-06@14:10:58
+v2.0.8-161-ga788372e;mulimex;10;10000;;12925;6245;30672;189903;759612;20;4.302416044;2021-12-06@14:11:25
+v2.0.8-161-ga788372e;mulimex;10;10000;;12925;6245;30368;189886;759544;21;4.518095173;2021-12-06@14:11:52
+v2.0.8-161-ga788372e;multi;18;10000;;15314;6305;33640;321631;1286524;19;4.085633944;2021-12-06@14:12:21
+v2.0.8-161-ga788372e;multi;18;10000;;15314;6305;33060;321630;1286520;21;4.516950872;2021-12-06@14:12:53
+v2.0.8-161-ga788372e;multi;18;10000;;15314;6305;32888;321622;1286488;20;4.307035392;2021-12-06@14:13:24
+v2.0.8-161-ga788372e;multi;18;10000;;15314;6305;32536;321610;1286440;18;3.875851167;2021-12-06@14:13:54
+v2.0.8-161-ga788372e;multi;18;10000;;15314;6305;33352;321615;1286460;22;4.739828577;2021-12-06@14:14:24
+v2.0.8-161-ga788372e;multi;18;10000;;15314;6305;33432;321630;1286520;18;3.879518698;2021-12-06@14:14:56
+v2.0.8-161-ga788372e;multi;18;10000;;15314;6305;33368;321637;1286548;21;4.519740200;2021-12-06@14:15:25
+v2.0.8-161-ga788372e;multi;18;10000;;15314;6305;33268;321610;1286440;20;4.305856029;2021-12-06@14:15:56
+v2.0.8-161-ga788372e;imex;18;10000;;15314;6305;27564;9030;36120;40;8.684061410;2021-12-06@14:16:30
+v2.0.8-161-ga788372e;imex;18;10000;;15314;6305;27752;9060;36240;38;8.233427802;2021-12-06@14:17:06
+v2.0.8-161-ga788372e;imex;18;10000;;15314;6305;27436;9051;36204;41;8.896879840;2021-12-06@14:17:40
+v2.0.8-161-ga788372e;imex;18;10000;;15314;6305;27672;9067;36268;40;8.668315617;2021-12-06@14:18:14
+v2.0.8-161-ga788372e;imex;18;10000;;15314;6305;27552;9055;36220;40;8.673992917;2021-12-06@14:18:49
+v2.0.8-161-ga788372e;imex;18;10000;;15314;6305;27768;9070;36280;40;8.675525863;2021-12-06@14:19:23
+v2.0.8-161-ga788372e;imex;18;10000;;15314;6305;27604;9063;36252;41;8.896695554;2021-12-06@14:19:58
+v2.0.8-161-ga788372e;imex;18;10000;;15314;6305;27620;9068;36272;37;8.026595858;2021-12-06@14:20:35
+v2.0.8-161-ga788372e;single;18;10000;;15314;6305;;8164;3609;14436;38;8.246006718;2021-12-06@14:21:08
+v2.0.8-161-ga788372e;single;18;10000;;15314;6305;;8240;3616;14464;39;8.451448843;2021-12-06@14:21:42
+v2.0.8-161-ga788372e;single;18;10000;;15314;6305;;8312;3630;14520;41;8.885674030;2021-12-06@14:22:17
+v2.0.8-161-ga788372e;single;18;10000;;15314;6305;;8256;3608;14432;39;8.452951862;2021-12-06@14:22:51
+v2.0.8-161-ga788372e;single;18;10000;;15314;6305;;8296;3612;14448;39;8.447394502;2021-12-06@14:23:26
+v2.0.8-161-ga788372e;single;18;10000;;15314;6305;;8224;3611;14444;39;8.461078185;2021-12-06@14:24:01
+v2.0.8-161-ga788372e;single;18;10000;;15314;6305;;8172;3599;14396;40;8.671040923;2021-12-06@14:24:35
+v2.0.8-161-ga788372e;single;18;10000;;15314;6305;;8272;3606;14424;41;8.892147820;2021-12-06@14:25:09
+v2.0.8-161-ga788372e;mulimex;18;10000;;15314;6305;49120;326319;1305276;19;4.089241311;2021-12-06@14:25:40
+v2.0.8-161-ga788372e;mulimex;18;10000;;15314;6305;49224;326251;1305004;22;4.737475091;2021-12-06@14:26:10
+v2.0.8-161-ga788372e;mulimex;18;10000;;15314;6305;49192;326268;1305072;17;3.657795376;2021-12-06@14:26:40
+v2.0.8-161-ga788372e;mulimex;18;10000;;15314;6305;49432;326269;1305076;21;4.520732609;2021-12-06@14:27:10
+v2.0.8-161-ga788372e;mulimex;18;10000;;15314;6305;48564;326226;1304904;20;4.310730874;2021-12-06@14:27:40
+v2.0.8-161-ga788372e;mulimex;18;10000;;15314;6305;49160;326273;1305092;19;4.090173008;2021-12-06@14:28:11
+v2.0.8-161-ga788372e;mulimex;18;10000;;15314;6305;49368;326279;1305116;20;4.307600202;2021-12-06@14:28:41
+v2.0.8-161-ga788372e;mulimex;18;10000;;15314;6305;49696;326329;1305316;22;4.746332733;2021-12-06@14:29:11
+v2.0.8-161-ga788372e;multi;32;10000;;16892;6334;53904;556871;2227484;22;4.744210933;2021-12-06@14:29:47
+v2.0.8-161-ga788372e;multi;32;10000;;16892;6334;53844;556877;2227508;19;4.092227972;2021-12-06@14:30:25
+v2.0.8-161-ga788372e;multi;32;10000;;16892;6334;54532;556860;2227440;22;4.737008659;2021-12-06@14:31:04
+v2.0.8-161-ga788372e;multi;32;10000;;16892;6334;55396;556884;2227536;19;4.102128776;2021-12-06@14:31:43
+v2.0.8-161-ga788372e;multi;32;10000;;16892;6334;55708;556931;2227724;21;4.530517178;2021-12-06@14:32:23
+v2.0.8-161-ga788372e;multi;32;10000;;16892;6334;54508;556848;2227392;18;3.875664935;2021-12-06@14:33:02
+v2.0.8-161-ga788372e;multi;32;10000;;16892;6334;54032;556799;2227196;18;3.878299866;2021-12-06@14:33:40
+v2.0.8-161-ga788372e;multi;32;10000;;16892;6334;54284;556884;2227536;19;4.092237866;2021-12-06@14:34:18
+v2.0.8-161-ga788372e;imex;32;10000;;16892;6334;40140;12621;50484;65;14.149852026;2021-12-06@14:35:06
+v2.0.8-161-ga788372e;imex;32;10000;;16892;6334;40324;12634;50536;64;13.926114889;2021-12-06@14:35:54
+v2.0.8-161-ga788372e;imex;32;10000;;16892;6334;40184;12618;50472;65;14.145470802;2021-12-06@14:36:44
+v2.0.8-161-ga788372e;imex;32;10000;;16892;6334;40572;12700;50800;64;13.919928624;2021-12-06@14:37:31
+v2.0.8-161-ga788372e;imex;32;10000;;16892;6334;40176;12619;50476;66;14.370538072;2021-12-06@14:38:19
+v2.0.8-161-ga788372e;imex;32;10000;;16892;6334;40308;12612;50448;63;13.718296061;2021-12-06@14:39:07
+v2.0.8-161-ga788372e;imex;32;10000;;16892;6334;40296;12626;50504;65;14.138538412;2021-12-06@14:39:55
+v2.0.8-161-ga788372e;imex;32;10000;;16892;6334;40156;12614;50456;63;13.717587900;2021-12-06@14:40:43
+v2.0.8-161-ga788372e;single;32;10000;;16892;6334;;9324;3886;15544;69;15.006621142;2021-12-06@14:41:34
+v2.0.8-161-ga788372e;single;32;10000;;16892;6334;;9044;3874;15496;67;14.576172686;2021-12-06@14:42:22
+v2.0.8-161-ga788372e;single;32;10000;;16892;6334;;9168;3877;15508;67;14.580721314;2021-12-06@14:43:09
+v2.0.8-161-ga788372e;single;32;10000;;16892;6334;;9360;3874;15496;70;15.215318150;2021-12-06@14:43:57
+v2.0.8-161-ga788372e;single;32;10000;;16892;6334;;9092;3861;15444;67;14.565809392;2021-12-06@14:44:45
+v2.0.8-161-ga788372e;single;32;10000;;16892;6334;;8948;3852;15408;67;14.565712451;2021-12-06@14:45:32
+v2.0.8-161-ga788372e;single;32;10000;;16892;6334;;9308;3908;15632;68;14.795375802;2021-12-06@14:46:22
+v2.0.8-161-ga788372e;single;32;10000;;16892;6334;;9080;3881;15524;65;14.137421494;2021-12-06@14:47:10
+v2.0.8-161-ga788372e;mulimex;32;10000;;16892;6334;82076;564613;2258452;21;4.522385738;2021-12-06@14:47:48
+v2.0.8-161-ga788372e;mulimex;32;10000;;16892;6334;83560;564925;2259700;20;4.309231785;2021-12-06@14:48:28
+v2.0.8-161-ga788372e;mulimex;32;10000;;16892;6334;82556;564756;2259024;22;4.740118038;2021-12-06@14:49:08
+v2.0.8-161-ga788372e;mulimex;32;10000;;16892;6334;81832;564848;2259392;20;4.307002878;2021-12-06@14:49:48
+v2.0.8-161-ga788372e;mulimex;32;10000;;16892;6334;81964;564749;2258996;20;4.308891257;2021-12-06@14:50:28
+v2.0.8-161-ga788372e;mulimex;32;10000;;16892;6334;82512;564788;2259152;19;4.095858857;2021-12-06@14:51:07
+v2.0.8-161-ga788372e;mulimex;32;10000;;16892;6334;81600;564683;2258732;18;3.878144182;2021-12-06@14:51:46
+v2.0.8-161-ga788372e;mulimex;32;10000;;16892;6334;81688;564605;2258420;18;3.880192724;2021-12-06@14:52:23
+v2.0.8-161-ga788372e;multi;56;10000;;17919;6345;90636;960532;3842128;25;5.413937245;2021-12-06@14:53:11
+v2.0.8-161-ga788372e;multi;56;10000;;17919;6345;90560;960331;3841324;26;5.625838555;2021-12-06@14:54:04
+v2.0.8-161-ga788372e;multi;56;10000;;17919;6345;90656;960231;3840924;26;5.622985139;2021-12-06@14:54:54
+v2.0.8-161-ga788372e;multi;56;10000;;17919;6345;88600;959985;3839940;25;5.421197146;2021-12-06@14:55:44
+v2.0.8-161-ga788372e;multi;56;10000;;17919;6345;92224;960576;3842304;25;5.412104972;2021-12-06@14:56:36
+v2.0.8-161-ga788372e;multi;56;10000;;17919;6345;90032;960088;3840352;25;5.429419527;2021-12-06@14:57:28
+v2.0.8-161-ga788372e;multi;56;10000;;17919;6345;88716;960064;3840256;25;5.409571988;2021-12-06@14:58:19
+v2.0.8-161-ga788372e;multi;56;10000;;17919;6345;89904;959874;3839496;25;5.402138865;2021-12-06@14:59:09
+v2.0.8-161-ga788372e;imex;56;10000;;17919;6345;60804;18448;73792;113;24.614735142;2021-12-06@15:00:42
+v2.0.8-161-ga788372e;imex;56;10000;;17919;6345;61532;18608;74432;109;23.732816525;2021-12-06@15:01:53
+v2.0.8-161-ga788372e;imex;56;10000;;17919;6345;60684;18462;73848;109;23.745012029;2021-12-06@15:03:09
+v2.0.8-161-ga788372e;imex;56;10000;;17919;6345;60824;18483;73932;111;24.182505333;2021-12-06@15:04:22
+v2.0.8-161-ga788372e;imex;56;10000;;17919;6345;60740;18485;73940;109;23.743722383;2021-12-06@15:05:37
+v2.0.8-161-ga788372e;imex;56;10000;;17919;6345;60708;18482;73928;109;23.737597586;2021-12-06@15:06:54
+v2.0.8-161-ga788372e;imex;56;10000;;17919;6345;60812;18493;73972;111;24.171849671;2021-12-06@15:08:12
+v2.0.8-161-ga788372e;imex;56;10000;;17919;6345;60624;18453;73812;109;23.750090485;2021-12-06@15:09:30
+v2.0.8-161-ga788372e;single;56;10000;;17919;6345;10500;4193;16772;115;25.090458989;2021-12-06@15:10:45
+v2.0.8-161-ga788372e;single;56;10000;;17919;6345;10576;4198;16792;115;25.053933566;2021-12-06@15:12:01
+v2.0.8-161-ga788372e;single;56;10000;;17919;6345;10440;4175;16700;114;24.832875853;2021-12-06@15:13:17
+v2.0.8-161-ga788372e;single;56;10000;;17919;6345;10672;4178;16712;114;24.838940159;2021-12-06@15:14:34
+v2.0.8-161-ga788372e;single;56;10000;;17919;6345;10424;4191;16764;114;24.845332591;2021-12-06@15:15:51
+v2.0.8-161-ga788372e;single;56;10000;;17919;6345;10536;4185;16740;114;24.842495171;2021-12-06@15:17:09
+v2.0.8-161-ga788372e;single;56;10000;;17919;6345;10660;4192;16768;114;24.835230118;2021-12-06@15:18:21
+v2.0.8-161-ga788372e;single;56;10000;;17919;6345;10452;4176;16704;114;24.824440064;2021-12-06@15:19:36
+v2.0.8-161-ga788372e;mulimex;56;10000;;17919;6345;133784;973094;3892376;26;5.639381304;2021-12-06@15:20:34
+v2.0.8-161-ga788372e;mulimex;56;10000;;17919;6345;134832;973308;3893232;27;5.856331757;2021-12-06@15:21:24
+v2.0.8-161-ga788372e;mulimex;56;10000;;17919;6345;136404;973067;3892268;26;5.637067908;2021-12-06@15:22:18
+v2.0.8-161-ga788372e;mulimex;56;10000;;17919;6345;133332;973120;3892480;27;5.855872096;2021-12-06@15:23:10
+v2.0.8-161-ga788372e;mulimex;56;10000;;17919;6345;134404;973226;3892904;27;5.855301093;2021-12-06@15:24:02
+v2.0.8-161-ga788372e;mulimex;56;10000;;17919;6345;137088;973290;3893160;27;5.860414051;2021-12-06@15:24:53
+v2.0.8-161-ga788372e;mulimex;56;10000;;17919;6345;136044;973191;3892764;29;6.296841111;2021-12-06@15:25:48
+v2.0.8-161-ga788372e;mulimex;56;10000;;17919;6345;134192;973279;3893116;27;5.865426200;2021-12-06@15:26:41
+v2.0.8-161-ga788372e;multi;100;10000;;18563;6381;157228;1699840;6799360;47;10.243395607;2021-12-06@15:27:58
+v2.0.8-161-ga788372e;multi;100;10000;;18563;6381;153884;1699446;6797784;47;10.260627518;2021-12-06@15:29:21
+v2.0.8-161-ga788372e;multi;100;10000;;18563;6381;154424;1699521;6798084;47;10.232038900;2021-12-06@15:30:40
+v2.0.8-161-ga788372e;multi;100;10000;;18563;6381;156488;1699984;6799936;46;10.006832801;2021-12-06@15:31:59
+v2.0.8-161-ga788372e;multi;100;10000;;18563;6381;155764;1700064;6800256;47;10.238049395;2021-12-06@15:33:18
+v2.0.8-161-ga788372e;multi;100;10000;;18563;6381;158156;1700036;6800144;48;10.447295937;2021-12-06@15:34:39
+v2.0.8-161-ga788372e;multi;100;10000;;18563;6381;153812;1699510;6798040;47;10.240523586;2021-12-06@15:36:01
+v2.0.8-161-ga788372e;multi;100;10000;;18563;6381;155628;1699815;6799260;46;10.007345700;2021-12-06@15:37:21
+v2.0.8-161-ga788372e;imex;100;10000;;18563;6381;97528;28982;115928;192;41.865100099;2021-12-06@15:39:15
+v2.0.8-161-ga788372e;imex;100;10000;;18563;6381;97660;28982;115928;196;42.765122043;2021-12-06@15:41:08
+v2.0.8-161-ga788372e;imex;100;10000;;18563;6381;98328;29160;116640;196;42.776826745;2021-12-06@15:43:00
+v2.0.8-161-ga788372e;imex;100;10000;;18563;6381;98092;29098;116392;193;42.113227186;2021-12-06@15:44:55
+v2.0.8-161-ga788372e;imex;100;10000;;18563;6381;98024;29079;116316;191;41.670385310;2021-12-06@15:46:51
+v2.0.8-161-ga788372e;imex;100;10000;;18563;6381;97596;28975;115900;194;42.347403585;2021-12-06@15:48:41
+v2.0.8-161-ga788372e;imex;100;10000;;18563;6381;97940;29089;116356;193;42.124749758;2021-12-06@15:50:32
+v2.0.8-161-ga788372e;imex;100;10000;;18563;6381;98340;29184;116736;194;42.340088089;2021-12-06@15:52:25
+v2.0.8-161-ga788372e;single;100;10000;;18563;6381;12396;4685;18740;201;43.886697347;2021-12-06@15:54:18
+v2.0.8-161-ga788372e;single;100;10000;;18563;6381;12416;4650;18600;204;44.557125113;2021-12-06@15:56:12
+v2.0.8-161-ga788372e;single;100;10000;;18563;6381;12360;4669;18676;203;44.345104651;2021-12-06@15:58:07
+v2.0.8-161-ga788372e;single;100;10000;;18563;6381;12288;4668;18672;202;44.111571144;2021-12-06@16:00:02
+v2.0.8-161-ga788372e;single;100;10000;;18563;6381;12372;4679;18716;217;47.417960550;2021-12-06@16:02:00
+v2.0.8-161-ga788372e;single;100;10000;;18563;6381;12424;4684;18736;204;44.541854720;2021-12-06@16:03:52
+v2.0.8-161-ga788372e;single;100;10000;;18563;6381;12428;4681;18724;205;44.783031958;2021-12-06@16:05:47
+v2.0.8-161-ga788372e;single;100;10000;;18563;6381;12372;4665;18660;204;44.552089966;2021-12-06@16:07:42
+v2.0.8-161-ga788372e;mulimex;100;10000;;18563;6381;238176;1723216;6892864;49;10.710845221;2021-12-06@16:09:01
+v2.0.8-161-ga788372e;mulimex;100;10000;;18563;6381;235456;1722775;6891100;50;10.916215590;2021-12-06@16:10:30
+v2.0.8-161-ga788372e;mulimex;100;10000;;18563;6381;238756;1723260;6893040;50;10.921517102;2021-12-06@16:11:50
+v2.0.8-161-ga788372e;mulimex;100;10000;;18563;6381;235632;1722288;6889152;51;11.104239267;2021-12-06@16:13:14
+v2.0.8-161-ga788372e;mulimex;100;10000;;18563;6381;235808;1723028;6892112;50;10.924535204;2021-12-06@16:14:36
+v2.0.8-161-ga788372e;mulimex;100;10000;;18563;6381;234916;1722808;6891232;50;10.932362482;2021-12-06@16:15:58
+v2.0.8-161-ga788372e;mulimex;100;10000;;18563;6381;234384;1722756;6891024;49;10.673398292;2021-12-06@16:17:19
+v2.0.8-161-ga788372e;mulimex;100;10000;;18563;6381;232416;1722182;6888728;49;10.756571900;2021-12-06@16:18:41
+v2.0.8-161-ga788372e;multi;178;10000;;19033;6531;274016;2159350;8637400;86;18.810143685;2021-12-06@16:20:41
+v2.0.8-161-ga788372e;multi;178;10000;;19033;6531;275624;2159202;8636808;85;18.604219427;2021-12-06@16:22:51
+v2.0.8-161-ga788372e;multi;178;10000;;19033;6531;275532;2159255;8637020;85;18.592356953;2021-12-06@16:25:02
+v2.0.8-161-ga788372e;multi;178;10000;;19033;6531;270780;2158573;8634292;86;18.833227881;2021-12-06@16:27:11
+v2.0.8-161-ga788372e;multi;178;10000;;19033;6531;267068;2157881;8631524;84;18.480694418;2021-12-06@16:29:22
+v2.0.8-161-ga788372e;multi;178;10000;;19033;6531;269316;2159098;8636392;86;18.836943286;2021-12-06@16:31:34
+v2.0.8-161-ga788372e;multi;178;10000;;19033;6531;270220;2159439;8637756;85;18.572625658;2021-12-06@16:33:45
+v2.0.8-161-ga788372e;multi;178;10000;;19033;6531;274572;2159315;8637260;86;18.796062554;2021-12-06@16:35:55
+v2.0.8-161-ga788372e;imex;178;10000;;19033;6531;168512;49056;196224;372;81.435064869;2021-12-06@16:39:09
+v2.0.8-161-ga788372e;imex;178;10000;;19033;6531;167024;48697;194788;368;80.558510744;2021-12-06@16:42:38
+v2.0.8-161-ga788372e;imex;178;10000;;19033;6531;166748;48570;194280;368;80.585125026;2021-12-06@16:45:55
+v2.0.8-161-ga788372e;imex;178;10000;;19033;6531;165476;48336;193344;374;81.875080009;2021-12-06@16:49:14
+v2.0.8-161-ga788372e;imex;178;10000;;19033;6531;167888;48908;195632;383;83.810889861;2021-12-06@16:52:25
+v2.0.8-161-ga788372e;imex;178;10000;;19033;6531;165844;48383;193532;372;81.602119381;2021-12-06@16:55:38
+v2.0.8-161-ga788372e;imex;178;10000;;19033;6531;165848;48333;193332;362;79.242521109;2021-12-06@16:58:57
+v2.0.8-161-ga788372e;imex;178;10000;;19033;6531;166668;48572;194288;374;81.881825688;2021-12-06@17:02:05
+v2.0.8-161-ga788372e;single;178;10000;;19033;6531;15676;5487;21948;381;83.730208659;2021-12-06@17:05:13
+v2.0.8-161-ga788372e;single;178;10000;;19033;6531;15500;5482;21928;378;82.901160368;2021-12-06@17:08:21
+v2.0.8-161-ga788372e;single;178;10000;;19033;6531;15760;5464;21856;398;87.240639421;2021-12-06@17:11:32
+v2.0.8-161-ga788372e;single;178;10000;;19033;6531;15612;5481;21924;379;83.040451555;2021-12-06@17:14:42
+v2.0.8-161-ga788372e;single;178;10000;;19033;6531;15696;5476;21904;385;84.677077832;2021-12-06@17:17:49
+v2.0.8-161-ga788372e;single;178;10000;;19033;6531;15576;5473;21892;387;84.804172913;2021-12-06@17:21:00
+v2.0.8-161-ga788372e;single;178;10000;;19033;6531;15804;5479;21916;388;85.163903295;2021-12-06@17:24:09
+v2.0.8-161-ga788372e;single;178;10000;;19033;6531;15612;5484;21936;381;83.447983331;2021-12-06@17:27:20
+v2.0.8-161-ga788372e;mulimex;178;10000;;19033;6531;420112;2201113;8804452;90;19.734871982;2021-12-06@17:29:24
+v2.0.8-161-ga788372e;mulimex;178;10000;;19033;6531;416132;2199642;8798568;91;19.886417571;2021-12-06@17:31:28
+v2.0.8-161-ga788372e;mulimex;178;10000;;19033;6531;419452;2200580;8802320;91;19.890319401;2021-12-06@17:33:33
+v2.0.8-161-ga788372e;mulimex;178;10000;;19033;6531;414268;2199304;8797216;91;19.893538266;2021-12-06@17:46:46
+v2.0.8-161-ga788372e;mulimex;178;10000;;19033;6531;414816;2199111;8796444;91;19.908762754;2021-12-06@17:48:53
+v2.0.8-161-ga788372e;mulimex;178;10000;;19033;6531;413064;2199621;8798484;92;20.170746382;2021-12-06@17:51:06
+v2.0.8-161-ga788372e;mulimex;178;10000;;19033;6531;416784;2199609;8798436;91;19.914442486;2021-12-06@17:53:12
+v2.0.8-161-ga788372e;mulimex;178;10000;;19033;6531;414452;2200847;8803388;91;19.897780806;2021-12-06@17:55:19
+v2.0.8-161-ga788372e;multi;316;10000;;19553;6638;473272;2217524;8870096;160;35.109724230;2021-12-06@17:58:43
+v2.0.8-161-ga788372e;multi;316;10000;;19553;6638;489336;2216918;8867672;160;35.036585681;2021-12-06@18:02:37
+v2.0.8-161-ga788372e;multi;316;10000;;19553;6638;499536;2219768;8879072;166;36.453827085;2021-12-06@18:06:33
+v2.0.8-163-ge813cd67;multi;316;10000;;19553;6638;486876;2217561;8870244;165;36.219933598;2021-12-06@18:10:27
+v2.0.8-163-ge813cd67;multi;316;10000;;19553;6638;492756;2219329;8877316;168;36.803414011;2021-12-06@18:14:27
+v2.0.8-163-ge813cd67;multi;316;10000;;19553;6638;484292;2217366;8869464;167;36.641744843;2021-12-06@18:18:42
+v2.0.8-163-ge813cd67;multi;316;10000;;19553;6638;499180;2219884;8879536;166;36.367060291;2021-12-06@18:22:38
+v2.0.8-163-ge813cd67;multi;316;10000;;19553;6638;491808;2220103;8880412;166;36.263668177;2021-12-06@18:26:35
+v2.0.8-163-ge813cd67;imex;316;10000;;19553;6638;284668;82254;329016;739;162.528245515;2021-12-06@18:33:29
+v2.0.8-163-ge813cd67;imex;316;10000;;19553;6638;294544;84701;338804;729;160.674302810;2021-12-06@18:39:20
+v2.0.8-163-ge813cd67;imex;316;10000;;19553;6638;285424;82451;329804;785;172.657519651;2021-12-06@18:45:21
+v2.0.8-163-ge813cd67;imex;316;10000;;19553;6638;294592;84674;338696;734;161.864779166;2021-12-06@18:51:13
+v2.0.8-163-ge813cd67;imex;316;10000;;19553;6638;283944;82114;328456;731;161.452744341;2021-12-06@18:57:03
+v2.0.8-163-ge813cd67;imex;316;10000;;19553;6638;284620;82202;328808;726;159.871484038;2021-12-06@19:02:50
+v2.0.8-163-ge813cd67;imex;316;10000;;19553;6638;288772;83257;333028;747;164.672569104;2021-12-06@19:08:43
+v2.0.8-163-ge813cd67;imex;316;10000;;19553;6638;289132;83339;333356;723;159.327889660;2021-12-06@19:14:28
+v2.0.8-163-ge813cd67;single;316;10000;;19553;6638;21032;6826;27304;799;176.389544066;2021-12-06@19:20:35
+v2.0.8-163-ge813cd67;single;316;10000;;19553;6638;21096;6802;27208;771;170.469001660;2021-12-06@19:26:40
+v2.0.8-163-ge813cd67;single;316;10000;;19553;6638;21228;6838;27352;771;170.445679124;2021-12-06@19:32:48
+v2.0.8-163-ge813cd67;single;316;10000;;19553;6638;20984;6789;27156;782;173.457854715;2021-12-06@19:38:50
+v2.0.8-163-ge813cd67;single;316;10000;;19553;6638;21096;6831;27324;781;172.483058648;2021-12-06@19:44:54
+v2.0.8-163-ge813cd67;single;316;10000;;19553;6638;20852;6799;27196;775;170.973709728;2021-12-06@19:50:55
+v2.0.8-163-ge813cd67;single;316;10000;;19553;6638;21016;6810;27240;767;169.147439080;2021-12-06@19:56:53
+v2.0.8-163-ge813cd67;single;316;10000;;19553;6638;21044;6811;27244;782;172.726994658;2021-12-06@20:02:55
+v2.0.8-163-ge813cd67;mulimex;316;10000;;19553;6638;747296;2293442;9173768;177;38.934544160;2021-12-06@20:06:41
+v2.0.8-163-ge813cd67;mulimex;316;10000;;19553;6638;748264;2294413;9177652;178;39.279433773;2021-12-06@20:07:22
+v2.0.8-163-ge813cd67;mulimex;316;10000;;19553;6638;740736;2292451;9169804;181;39.838252438;2021-12-06@20:08:03
+v2.0.8-163-ge813cd67;mulimex;316;10000;;19553;6638;742224;2292343;9169372;182;40.215076390;2021-12-06@20:08:45
+v2.0.8-163-ge813cd67;mulimex;316;10000;;19553;6638;740520;2291831;9167324;181;39.731890394;2021-12-06@20:09:26
+v2.0.8-163-ge813cd67;mulimex;316;10000;;19553;6638;734676;2290626;9162504;179;39.293273175;2021-12-06@20:10:07
+v2.0.8-163-ge813cd67;mulimex;316;10000;;19553;6638;753600;2294869;9179476;181;39.738133835;2021-12-06@20:10:49
+v2.0.8-163-ge813cd67;mulimex;316;10000;;19553;6638;741224;2292737;9170948;183;40.225612561;2021-12-06@20:11:30
+v2.0.8-163-ge813cd67;multi;10;17783;;29952;12293;43220;192073;768292;16;3.501894537;2021-12-06@20:13:05
+v2.0.8-163-ge813cd67;multi;10;17783;;29952;12293;43196;192024;768096;18;3.930211244;2021-12-06@20:13:11
+v2.0.8-163-ge813cd67;multi;10;17783;;29952;12293;43300;192041;768164;19;4.153959610;2021-12-06@20:13:16
+v2.0.8-163-ge813cd67;multi;10;17783;;29952;12293;43332;192050;768200;19;4.154886280;2021-12-06@20:13:22
+v2.0.8-163-ge813cd67;multi;10;17783;;29952;12293;43324;192054;768216;19;4.156487913;2021-12-06@20:13:28
+v2.0.8-163-ge813cd67;multi;10;17783;;29952;12293;43536;192058;768232;19;4.155744364;2021-12-06@20:13:33
+v2.0.8-163-ge813cd67;multi;10;17783;;29952;12293;43232;192053;768212;19;4.158111188;2021-12-06@20:13:39
+v2.0.8-163-ge813cd67;multi;10;17783;;29952;12293;43076;192027;768108;19;4.151844965;2021-12-06@20:13:45
+v2.0.8-163-ge813cd67;imex;10;17783;;29952;12293;36312;11009;44036;45;9.845653838;2021-12-06@20:14:05
+v2.0.8-163-ge813cd67;imex;10;17783;;29952;12293;36424;11000;44000;46;10.105034944;2021-12-06@20:14:17
+v2.0.8-163-ge813cd67;imex;10;17783;;29952;12293;36384;10999;43996;45;9.875427425;2021-12-06@20:14:28
+v2.0.8-163-ge813cd67;imex;10;17783;;29952;12293;36440;10994;43976;47;10.319987275;2021-12-06@20:14:40
+v2.0.8-163-ge813cd67;imex;10;17783;;29952;12293;36260;11007;44028;46;10.097267729;2021-12-06@20:14:52
+v2.0.8-163-ge813cd67;imex;10;17783;;29952;12293;36300;11006;44024;47;10.315758143;2021-12-06@20:15:04
+v2.0.8-163-ge813cd67;imex;10;17783;;29952;12293;36388;10993;43972;47;10.325056653;2021-12-06@20:15:15
+v2.0.8-163-ge813cd67;imex;10;17783;;29952;12293;36600;11025;44100;46;10.097867454;2021-12-06@20:15:27
+v2.0.8-163-ge813cd67;single;10;17783;;29952;12293;11824;4515;18060;46;10.075913204;2021-12-06@20:15:46
+v2.0.8-163-ge813cd67;single;10;17783;;29952;12293;11460;4504;18016;50;10.976056302;2021-12-06@20:15:58
+v2.0.8-163-ge813cd67;single;10;17783;;29952;12293;11708;4512;18048;48;10.537367488;2021-12-06@20:16:10
+v2.0.8-163-ge813cd67;single;10;17783;;29952;12293;11492;4504;18016;46;10.095178399;2021-12-06@20:16:22
+v2.0.8-163-ge813cd67;single;10;17783;;29952;12293;11744;4502;18008;46;10.090156325;2021-12-06@20:16:33
+v2.0.8-163-ge813cd67;single;10;17783;;29952;12293;11840;4532;18128;47;10.326571395;2021-12-06@20:16:45
+v2.0.8-163-ge813cd67;single;10;17783;;29952;12293;11748;4522;18088;47;10.317123405;2021-12-06@20:16:57
+v2.0.8-163-ge813cd67;single;10;17783;;29952;12293;11860;4533;18132;47;10.317285413;2021-12-06@20:17:09
+v2.0.8-163-ge813cd67;mulimex;10;17783;;29952;12293;62000;197033;788132;19;4.096507190;2021-12-06@20:17:22
+v2.0.8-163-ge813cd67;mulimex;10;17783;;29952;12293;61496;197049;788196;19;4.159589447;2021-12-06@20:17:28
+v2.0.8-163-ge813cd67;mulimex;10;17783;;29952;12293;61528;197013;788052;19;4.157080121;2021-12-06@20:17:33
+v2.0.8-163-ge813cd67;mulimex;10;17783;;29952;12293;61336;197054;788216;20;4.375605851;2021-12-06@20:17:39
+v2.0.8-163-ge813cd67;mulimex;10;17783;;29952;12293;61960;197035;788140;19;4.158493927;2021-12-06@20:17:45
+v2.0.8-163-ge813cd67;mulimex;10;17783;;29952;12293;62100;197061;788244;19;4.153872220;2021-12-06@20:17:50
+v2.0.8-163-ge813cd67;mulimex;10;17783;;29952;12293;62156;197028;788112;19;4.158705283;2021-12-06@20:17:56
+v2.0.8-163-ge813cd67;mulimex;10;17783;;29952;12293;61480;197031;788124;19;4.148763160;2021-12-06@20:18:02
+v2.0.8-163-ge813cd67;multi;18;17783;;31744;12312;64672;328806;1315224;20;4.318850415;2021-12-06@20:18:17
+v2.0.8-163-ge813cd67;multi;18;17783;;31744;12312;64820;329049;1316196;22;4.815345088;2021-12-06@20:18:23
+v2.0.8-163-ge813cd67;multi;18;17783;;31744;12312;65324;328908;1315632;22;4.818446147;2021-12-06@20:18:30
+v2.0.8-163-ge813cd67;multi;18;17783;;31744;12312;65360;328856;1315424;22;4.817503278;2021-12-06@20:18:36
+v2.0.8-163-ge813cd67;multi;18;17783;;31744;12312;64740;328777;1315108;23;5.041512642;2021-12-06@20:18:43
+v2.0.8-163-ge813cd67;multi;18;17783;;31744;12312;64620;328807;1315228;22;4.812746187;2021-12-06@20:18:49
+v2.0.8-163-ge813cd67;multi;18;17783;;31744;12312;65080;328962;1315848;23;5.036722026;2021-12-06@20:18:55
+v2.0.8-163-ge813cd67;multi;18;17783;;31744;12312;65008;328785;1315140;22;4.814744526;2021-12-06@20:19:02
+v2.0.8-163-ge813cd67;imex;18;17783;;31744;12312;49696;14547;58188;76;16.710933951;2021-12-06@20:19:31
+v2.0.8-163-ge813cd67;imex;18;17783;;31744;12312;49616;14539;58156;76;16.719335322;2021-12-06@20:19:49
+v2.0.8-163-ge813cd67;imex;18;17783;;31744;12312;49788;14561;58244;76;16.720804601;2021-12-06@20:20:08
+v2.0.8-163-ge813cd67;imex;18;17783;;31744;12312;49420;14537;58148;74;16.291328611;2021-12-06@20:20:25
+v2.0.8-163-ge813cd67;imex;18;17783;;31744;12312;49760;14542;58168;75;16.506797991;2021-12-06@20:20:43
+v2.0.8-163-ge813cd67;imex;18;17783;;31744;12312;49460;14543;58172;75;16.510428069;2021-12-06@20:21:01
+v2.0.8-163-ge813cd67;imex;18;17783;;31744;12312;49488;14565;58260;78;17.159815365;2021-12-06@20:21:20
+v2.0.8-163-ge813cd67;imex;18;17783;;31744;12312;49628;14539;58156;77;16.937557715;2021-12-06@20:21:38
+v2.0.8-163-ge813cd67;single;18;17783;;31744;12312;12664;4752;19008;78;17.150778775;2021-12-06@20:22:09
+v2.0.8-163-ge813cd67;single;18;17783;;31744;12312;12852;4752;19008;75;16.572674042;2021-12-06@20:22:27
+v2.0.8-163-ge813cd67;single;18;17783;;31744;12312;12696;4735;18940;74;16.284344401;2021-12-06@20:22:45
+v2.0.8-163-ge813cd67;single;18;17783;;31744;12312;12684;4743;18972;77;16.958014917;2021-12-06@20:23:03
+v2.0.8-163-ge813cd67;single;18;17783;;31744;12312;12856;4766;19064;76;16.736716239;2021-12-06@20:23:21
+v2.0.8-163-ge813cd67;single;18;17783;;31744;12312;12708;4750;19000;76;16.724097892;2021-12-06@20:23:40
+v2.0.8-163-ge813cd67;single;18;17783;;31744;12312;12792;4758;19032;80;17.605004747;2021-12-06@20:23:59
+v2.0.8-163-ge813cd67;single;18;17783;;31744;12312;12812;4759;19036;79;17.394453901;2021-12-06@20:24:18
+v2.0.8-163-ge813cd67;mulimex;18;17783;;31744;12312;95888;336939;1347756;21;4.545320025;2021-12-06@20:24:36
+v2.0.8-163-ge813cd67;mulimex;18;17783;;31744;12312;95048;336946;1347784;23;5.043875112;2021-12-06@20:24:42
+v2.0.8-163-ge813cd67;mulimex;18;17783;;31744;12312;94508;336884;1347536;23;5.047837888;2021-12-06@20:24:49
+v2.0.8-163-ge813cd67;mulimex;18;17783;;31744;12312;94472;336843;1347372;23;5.044710659;2021-12-06@20:24:55
+v2.0.8-163-ge813cd67;mulimex;18;17783;;31744;12312;94740;336885;1347540;23;5.043086984;2021-12-06@20:25:02
+v2.0.8-163-ge813cd67;mulimex;18;17783;;31744;12312;94692;336878;1347512;24;5.263748739;2021-12-06@20:25:09
+v2.0.8-163-ge813cd67;mulimex;18;17783;;31744;12312;94916;336884;1347536;23;5.038846352;2021-12-06@20:25:15
+v2.0.8-163-ge813cd67;mulimex;18;17783;;31744;12312;95928;336986;1347944;23;5.045801500;2021-12-06@20:25:22
+v2.0.8-163-ge813cd67;multi;32;17783;;32894;12393;102200;568266;2273064;27;5.889785287;2021-12-06@20:25:48
+v2.0.8-163-ge813cd67;multi;32;17783;;32894;12393;103496;568251;2273004;33;7.254135647;2021-12-06@20:25:57
+v2.0.8-163-ge813cd67;multi;32;17783;;32894;12393;103060;568155;2272620;33;7.258544791;2021-12-06@20:26:05
+v2.0.8-163-ge813cd67;multi;32;17783;;32894;12393;103120;568281;2273124;33;7.259433077;2021-12-06@20:26:14
+v2.0.8-163-ge813cd67;multi;32;17783;;32894;12393;102232;568193;2272772;33;7.260431843;2021-12-06@20:26:23
+v2.0.8-163-ge813cd67;multi;32;17783;;32894;12393;103408;568131;2272524;33;7.257202648;2021-12-06@20:26:32
+v2.0.8-163-ge813cd67;multi;32;17783;;32894;12393;102248;568195;2272780;32;7.033852291;2021-12-06@20:26:40
+v2.0.8-163-ge813cd67;multi;32;17783;;32894;12393;103584;568271;2273084;33;7.250038875;2021-12-06@20:26:49
+v2.0.8-163-ge813cd67;imex;32;17783;;32894;12393;71536;20480;81920;130;28.634837924;2021-12-06@20:27:41
+v2.0.8-163-ge813cd67;imex;32;17783;;32894;12393;71828;20484;81936;127;27.989196410;2021-12-06@20:28:11
+v2.0.8-163-ge813cd67;imex;32;17783;;32894;12393;71440;20475;81900;128;28.197493992;2021-12-06@20:28:41
+v2.0.8-163-ge813cd67;imex;32;17783;;32894;12393;71676;20476;81904;129;28.434298098;2021-12-06@20:29:11
+v2.0.8-163-ge813cd67;imex;32;17783;;32894;12393;71684;20463;81852;128;28.214101359;2021-12-06@20:29:41
+v2.0.8-163-ge813cd67;imex;32;17783;;32894;12393;71496;20481;81924;127;28.004319718;2021-12-06@20:30:10
+v2.0.8-163-ge813cd67;imex;32;17783;;32894;12393;71432;20462;81848;128;28.211302327;2021-12-06@20:30:40
+v2.0.8-163-ge813cd67;imex;32;17783;;32894;12393;71788;20486;81944;129;28.440937824;2021-12-06@20:31:10
+v2.0.8-163-ge813cd67;single;32;17783;;32894;12393;13788;5053;20212;138;30.418650112;2021-12-06@20:32:03
+v2.0.8-163-ge813cd67;single;32;17783;;32894;12393;14100;5056;20224;130;28.650281365;2021-12-06@20:32:33
+v2.0.8-163-ge813cd67;single;32;17783;;32894;12393;14112;5053;20212;132;29.096653304;2021-12-06@20:33:04
+v2.0.8-163-ge813cd67;single;32;17783;;32894;12393;13752;5054;20216;133;29.323401255;2021-12-06@20:33:34
+v2.0.8-163-ge813cd67;single;32;17783;;32894;12393;14000;5051;20204;130;28.640473480;2021-12-06@20:34:06
+v2.0.8-163-ge813cd67;single;32;17783;;32894;12393;13808;5053;20212;128;28.205272779;2021-12-06@20:34:36
+v2.0.8-163-ge813cd67;single;32;17783;;32894;12393;13872;5066;20264;129;28.431496100;2021-12-06@20:35:06
+v2.0.8-163-ge813cd67;single;32;17783;;32894;12393;13728;5058;20232;134;29.531467650;2021-12-06@20:35:37
+v2.0.8-163-ge813cd67;mulimex;32;17783;;32894;12393;152396;581787;2327148;29;6.324602926;2021-12-06@20:36:04
+v2.0.8-163-ge813cd67;mulimex;32;17783;;32894;12393;153572;581745;2326980;34;7.484398688;2021-12-06@20:36:13
+v2.0.8-163-ge813cd67;mulimex;32;17783;;32894;12393;153808;581826;2327304;34;7.483412616;2021-12-06@20:36:22
+v2.0.8-163-ge813cd67;mulimex;32;17783;;32894;12393;153264;581724;2326896;34;7.485421176;2021-12-06@20:36:30
+v2.0.8-163-ge813cd67;mulimex;32;17783;;32894;12393;152912;581772;2327088;34;7.483382201;2021-12-06@20:36:39
+v2.0.8-163-ge813cd67;mulimex;32;17783;;32894;12393;153932;581737;2326948;34;7.481120459;2021-12-06@20:36:48
+v2.0.8-163-ge813cd67;mulimex;32;17783;;32894;12393;152668;581760;2327040;35;7.705629881;2021-12-06@20:36:58
+v2.0.8-163-ge813cd67;mulimex;32;17783;;32894;12393;153600;581890;2327560;33;7.260312320;2021-12-06@20:37:06
+v2.0.8-163-ge813cd67;multi;56;17783;;33578;12411;167480;978389;3913556;46;10.121600957;2021-12-06@20:37:47
+v2.0.8-163-ge813cd67;multi;56;17783;;33578;12411;167404;978474;3913896;51;11.233279854;2021-12-06@20:38:00
+v2.0.8-163-ge813cd67;multi;56;17783;;33578;12411;168088;978336;3913344;51;11.248628932;2021-12-06@20:38:13
+v2.0.8-163-ge813cd67;multi;56;17783;;33578;12411;168432;978410;3913640;52;11.469414891;2021-12-06@20:38:26
+v2.0.8-163-ge813cd67;multi;56;17783;;33578;12411;169440;978712;3914848;52;11.480132192;2021-12-06@20:38:39
+v2.0.8-163-ge813cd67;multi;56;17783;;33578;12411;168784;978638;3914552;53;11.694225140;2021-12-06@20:38:52
+v2.0.8-163-ge813cd67;multi;56;17783;;33578;12411;165712;978262;3913048;52;11.480888442;2021-12-06@20:39:05
+v2.0.8-163-ge813cd67;multi;56;17783;;33578;12411;166332;978182;3912728;51;11.254613500;2021-12-06@20:39:18
+v2.0.8-163-ge813cd67;imex;56;17783;;33578;12411;108252;30352;121408;217;47.854685718;2021-12-06@20:40:46
+v2.0.8-163-ge813cd67;imex;56;17783;;33578;12411;108100;30371;121484;222;48.990026257;2021-12-06@20:41:36
+v2.0.8-163-ge813cd67;imex;56;17783;;33578;12411;108020;30302;121208;214;47.192668348;2021-12-06@20:42:25
+v2.0.8-163-ge813cd67;imex;56;17783;;33578;12411;108024;30293;121172;211;46.537644076;2021-12-06@20:43:13
+v2.0.8-163-ge813cd67;imex;56;17783;;33578;12411;108008;30312;121248;229;50.482256930;2021-12-06@20:44:05
+v2.0.8-163-ge813cd67;imex;56;17783;;33578;12411;108220;30354;121416;220;48.511016057;2021-12-06@20:44:55
+v2.0.8-163-ge813cd67;imex;56;17783;;33578;12411;108432;30316;121264;219;48.295091310;2021-12-06@20:45:45
+v2.0.8-163-ge813cd67;imex;56;17783;;33578;12411;108204;30357;121428;222;48.979834405;2021-12-06@20:46:35
+v2.0.8-163-ge813cd67;single;56;17783;;33578;12411;15228;5432;21728;240;52.940262022;2021-12-06@20:48:04
+v2.0.8-163-ge813cd67;single;56;17783;;33578;12411;15304;5435;21740;228;50.303076962;2021-12-06@20:48:56
+v2.0.8-163-ge813cd67;single;56;17783;;33578;12411;15548;5459;21836;220;48.554455486;2021-12-06@20:49:47
+v2.0.8-163-ge813cd67;single;56;17783;;33578;12411;15308;5444;21776;222;49.008509564;2021-12-06@20:50:38
+v2.0.8-163-ge813cd67;single;56;17783;;33578;12411;15428;5452;21808;221;48.781160247;2021-12-06@20:51:31
+v2.0.8-163-ge813cd67;single;56;17783;;33578;12411;15228;5455;21820;217;47.883786884;2021-12-06@20:52:21
+v2.0.8-163-ge813cd67;single;56;17783;;33578;12411;15288;5426;21704;258;56.958597239;2021-12-06@20:53:19
+v2.0.8-163-ge813cd67;single;56;17783;;33578;12411;15500;5451;21804;222;48.989794059;2021-12-06@20:54:09
+v2.0.8-163-ge813cd67;mulimex;56;17783;;33578;12411;250968;1001355;4005420;50;11.060636581;2021-12-06@20:54:56
+v2.0.8-163-ge813cd67;mulimex;56;17783;;33578;12411;252784;1001514;4006056;55;12.152078628;2021-12-06@20:55:10
+v2.0.8-163-ge813cd67;mulimex;56;17783;;33578;12411;252052;1001277;4005108;56;12.374197611;2021-12-06@20:55:26
+v2.0.8-163-ge813cd67;mulimex;56;17783;;33578;12411;250648;1001277;4005108;54;11.926556592;2021-12-06@20:55:41
+v2.0.8-163-ge813cd67;mulimex;56;17783;;33578;12411;250812;1001283;4005132;55;12.158614198;2021-12-06@20:55:57
+v2.0.8-163-ge813cd67;mulimex;56;17783;;33578;12411;252516;1001220;4004880;56;12.367040358;2021-12-06@20:56:13
+v2.0.8-163-ge813cd67;mulimex;56;17783;;33578;12411;252056;1001418;4005672;57;12.576237327;2021-12-06@20:56:29
+v2.0.8-163-ge813cd67;mulimex;56;17783;;33578;12411;252780;1001270;4005080;57;12.599500941;2021-12-06@20:56:45
+v2.0.8-163-ge813cd67;multi;100;17783;;33951;12499;286552;1730237;6920948;87;19.241520785;2021-12-06@20:58:01
+v2.0.8-163-ge813cd67;multi;100;17783;;33951;12499;288848;1731029;6924116;92;20.325654482;2021-12-06@20:58:23
+v2.0.8-163-ge813cd67;multi;100;17783;;33951;12499;288908;1730865;6923460;93;20.556020913;2021-12-06@20:58:45
+v2.0.8-163-ge813cd67;multi;100;17783;;33951;12499;284360;1730148;6920592;92;20.374314073;2021-12-06@20:59:08
+v2.0.8-163-ge813cd67;multi;100;17783;;33951;12499;289104;1730877;6923508;95;21.010108917;2021-12-06@20:59:31
+v2.0.8-163-ge813cd67;multi;100;17783;;33951;12499;288204;1731890;6927560;94;20.817362860;2021-12-06@20:59:54
+v2.0.8-163-ge813cd67;multi;100;17783;;33951;12499;289448;1731094;6924376;88;19.514472725;2021-12-06@21:00:16
+v2.0.8-163-ge813cd67;multi;100;17783;;33951;12499;284968;1730647;6922588;92;20.373575097;2021-12-06@21:00:39
+v2.0.8-163-ge813cd67;imex;100;17783;;33951;12499;175420;48437;193748;393;86.931550083;2021-12-06@21:03:13
+v2.0.8-163-ge813cd67;imex;100;17783;;33951;12499;175680;48530;194120;389;86.047319063;2021-12-06@21:04:40
+v2.0.8-163-ge813cd67;imex;100;17783;;33951;12499;174932;48333;193332;389;86.042806907;2021-12-06@21:06:08
+v2.0.8-163-ge813cd67;imex;100;17783;;33951;12499;175256;48409;193636;374;82.686947744;2021-12-06@21:07:32
+v2.0.8-163-ge813cd67;imex;100;17783;;33951;12499;174264;48227;192908;391;86.534293120;2021-12-06@21:09:00
+v2.0.8-163-ge813cd67;imex;100;17783;;33951;12499;175164;48321;193284;395;87.429189880;2021-12-06@21:10:29
+v2.0.8-163-ge813cd67;imex;100;17783;;33951;12499;174788;48355;193420;386;85.434448517;2021-12-06@21:11:56
+v2.0.8-163-ge813cd67;imex;100;17783;;33951;12499;174720;48348;193392;377;83.379206264;2021-12-06@21:13:21
+v2.0.8-163-ge813cd67;single;100;17783;;33951;12499;17808;6029;24116;409;90.665509842;2021-12-06@21:15:57
+v2.0.8-163-ge813cd67;single;100;17783;;33951;12499;17564;6010;24040;406;90.077951916;2021-12-06@21:17:29
+v2.0.8-163-ge813cd67;single;100;17783;;33951;12499;17840;6006;24024;409;90.699169533;2021-12-06@21:19:01
+v2.0.8-163-ge813cd67;single;100;17783;;33951;12499;17744;6054;24216;400;88.774242984;2021-12-06@21:20:32
+v2.0.8-163-ge813cd67;single;100;17783;;33951;12499;17516;5977;23908;420;93.218086288;2021-12-06@21:22:06
+v2.0.8-163-ge813cd67;single;100;17783;;33951;12499;17516;5966;23864;403;89.497769150;2021-12-06@21:23:37
+v2.0.8-163-ge813cd67;single;100;17783;;33951;12499;17776;6063;24252;409;90.766893951;2021-12-06@21:25:09
+v2.0.8-163-ge813cd67;single;100;17783;;33951;12499;17440;5989;23956;411;91.293720343;2021-12-06@21:26:42
+v2.0.8-163-ge813cd67;mulimex;100;17783;;33951;12499;434020;1770411;7081644;92;20.374003266;2021-12-06@21:28:07
+v2.0.8-163-ge813cd67;mulimex;100;17783;;33951;12499;433596;1770884;7083536;97;21.449047419;2021-12-06@21:28:29
+v2.0.8-163-ge813cd67;mulimex;100;17783;;33951;12499;432756;1770333;7081332;98;21.788378714;2021-12-06@21:28:53
+v2.0.8-163-ge813cd67;mulimex;100;17783;;33951;12499;432368;1770376;7081504;98;21.669638297;2021-12-06@21:29:16
+v2.0.8-163-ge813cd67;mulimex;100;17783;;33951;12499;432388;1770464;7081856;98;21.721247625;2021-12-06@21:29:39
+v2.0.8-163-ge813cd67;mulimex;100;17783;;33951;12499;433992;1770388;7081552;98;21.706651846;2021-12-06@21:30:02
+v2.0.8-163-ge813cd67;mulimex;100;17783;;33951;12499;432252;1770500;7082000;97;21.460912763;2021-12-06@21:30:25
+v2.0.8-163-ge813cd67;mulimex;100;17783;;33951;12499;431716;1770274;7081096;98;21.706835494;2021-12-06@21:30:48
+v2.0.8-163-ge813cd67;multi;178;17783;;34156;12582;493224;2211142;8844568;156;34.631798281;2021-12-06@21:33:00
+v2.0.8-163-ge813cd67;multi;178;17783;;34156;12582;495676;2211991;8847964;162;35.912034559;2021-12-06@21:33:37
+v2.0.8-163-ge813cd67;multi;178;17783;;34156;12582;491824;2211005;8844020;162;35.949107695;2021-12-06@21:34:15
+v2.0.8-163-ge813cd67;multi;178;17783;;34156;12582;493592;2211067;8844268;164;36.282322167;2021-12-06@21:34:52
+v2.0.8-163-ge813cd67;multi;178;17783;;34156;12582;499688;2212468;8849872;163;36.084842565;2021-12-06@21:35:30
+v2.0.8-163-ge813cd67;multi;178;17783;;34156;12582;499512;2212139;8848556;163;36.161966909;2021-12-06@21:36:08
+v2.0.8-163-ge813cd67;multi;178;17783;;34156;12582;498736;2212306;8849224;161;35.698107826;2021-12-06@21:36:45
+v2.0.8-163-ge813cd67;multi;178;17783;;34156;12582;497008;2212270;8849080;164;36.357921824;2021-12-06@21:37:23
+v2.0.8-163-ge813cd67;imex;178;17783;;34156;12582;294084;80485;321940;713;158.285876408;2021-12-06@21:42:00
+v2.0.8-163-ge813cd67;imex;178;17783;;34156;12582;292964;80178;320712;696;154.492668580;2021-12-06@21:44:36
+v2.0.8-163-ge813cd67;imex;178;17783;;34156;12582;294524;80562;322248;701;155.776462026;2021-12-06@21:47:14
+v2.0.8-163-ge813cd67;imex;178;17783;;34156;12582;293552;80305;321220;733;163.349949427;2021-12-06@21:49:58
+v2.0.8-163-ge813cd67;imex;178;17783;;34156;12582;295016;80685;322740;681;151.631866600;2021-12-06@21:52:32
+v2.0.8-163-ge813cd67;imex;178;17783;;34156;12582;292956;80171;320684;680;150.936769125;2021-12-06@21:55:04
+v2.0.8-163-ge813cd67;imex;178;17783;;34156;12582;294468;80503;322012;672;149.162057416;2021-12-06@21:57:35
+v2.0.8-163-ge813cd67;imex;178;17783;;34156;12582;294588;80557;322228;703;156.113833841;2021-12-06@22:00:12
+v2.0.8-163-ge813cd67;single;178;17783;;34156;12582;21020;6899;27596;750;166.981207920;2021-12-06@22:04:52
+v2.0.8-163-ge813cd67;single;178;17783;;34156;12582;21268;6893;27572;765;170.676730082;2021-12-06@22:07:44
+v2.0.8-163-ge813cd67;single;178;17783;;34156;12582;21032;6867;27468;724;161.047044019;2021-12-06@22:10:27
+v2.0.8-163-ge813cd67;single;178;17783;;34156;12582;21076;6867;27468;746;166.213513958;2021-12-06@22:13:14
+v2.0.8-163-ge813cd67;single;178;17783;;34156;12582;21252;6880;27520;702;156.176459554;2021-12-06@22:15:53
+v2.0.8-163-ge813cd67;single;178;17783;;34156;12582;21252;6906;27624;745;165.893160710;2021-12-06@22:18:41
+v2.0.8-163-ge813cd67;single;178;17783;;34156;12582;21328;6885;27540;716;159.211054427;2021-12-06@22:21:22
+v2.0.8-163-ge813cd67;single;178;17783;;34156;12582;21372;6912;27648;753;167.677584181;2021-12-06@22:24:12
+v2.0.8-163-ge813cd67;mulimex;178;17783;;34156;12582;761996;2283589;9134356;158;35.077615140;2021-12-06@22:26:35
+v2.0.8-163-ge813cd67;mulimex;178;17783;;34156;12582;757388;2282014;9128056;173;38.366688669;2021-12-06@22:27:15
+v2.0.8-163-ge813cd67;mulimex;178;17783;;34156;12582;760388;2283387;9133548;172;38.185089177;2021-12-06@22:27:54
+v2.0.8-163-ge813cd67;mulimex;178;17783;;34156;12582;754976;2281953;9127812;169;37.510356497;2021-12-06@22:28:33
+v2.0.8-163-ge813cd67;mulimex;178;17783;;34156;12582;757304;2282234;9128936;170;37.729832892;2021-12-06@22:29:13
+v2.0.8-163-ge813cd67;mulimex;178;17783;;34156;12582;756040;2281805;9127220;174;38.618450690;2021-12-06@22:29:53
+v2.0.8-163-ge813cd67;mulimex;178;17783;;34156;12582;760120;2283030;9132120;169;37.503847768;2021-12-06@22:30:32
+v2.0.8-163-ge813cd67;mulimex;178;17783;;34156;12582;761100;2283870;9135480;169;37.636358569;2021-12-06@22:31:11
+v2.0.8-163-ge813cd67;multi;316;17783;;34574;12710;876356;2311836;9247344;293;65.392363755;2021-12-06@22:35:12
+v2.0.8-163-ge813cd67;multi;316;17783;;34574;12710;867084;2310575;9242300;291;65.238325685;2021-12-06@22:36:20
+v2.0.8-163-ge813cd67;multi;316;17783;;34574;12710;876580;2311633;9246532;298;66.139101648;2021-12-06@22:37:27
+v2.0.8-163-ge813cd67;multi;316;17783;;34574;12710;864840;2309843;9239372;285;63.312026602;2021-12-06@22:38:32
+v2.0.8-163-ge813cd67;multi;316;17783;;34574;12710;875652;2311655;9246620;293;65.272628546;2021-12-06@22:39:39
+v2.0.8-163-ge813cd67;multi;316;17783;;34574;12710;864216;2309545;9238180;291;64.769997895;2021-12-06@22:40:45
+v2.0.8-163-ge813cd67;multi;316;17783;;34574;12710;858300;2308839;9235356;293;65.119980084;2021-12-06@22:41:52
+v2.0.8-163-ge813cd67;multi;316;17783;;34574;12710;873928;2311692;9246768;299;66.837449563;2021-12-06@22:43:01
+v2.0.8-163-ge813cd67;imex;316;17783;;34574;12710;508512;138198;552792;1319;294.454130761;2021-12-06@22:51:14
+v2.0.8-163-ge813cd67;imex;316;17783;;34574;12710;506972;137849;551396;1320;295.056020887;2021-12-06@22:56:11
+v2.0.8-163-ge813cd67;imex;316;17783;;34574;12710;515604;140023;560092;1291;288.661181754;2021-12-06@23:01:01
+v2.0.8-163-ge813cd67;imex;316;17783;;34574;12710;507152;137844;551376;1278;285.510122239;2021-12-06@23:06:03
+v2.0.8-163-ge813cd67;imex;316;17783;;34574;12710;506552;137709;550836;1321;294.923234433;2021-12-06@23:11:00
+v2.0.8-163-ge813cd67;imex;316;17783;;34574;12710;511720;138992;555968;1282;287.720350930;2021-12-06@23:15:49
+v2.0.8-163-ge813cd67;imex;316;17783;;34574;12710;506536;137741;550964;1340;300.026245249;2021-12-06@23:20:51
+v2.0.8-163-ge813cd67;imex;316;17783;;34574;12710;515792;139964;559856;1269;284.149055276;2021-12-06@23:25:36
+v2.0.8-163-ge813cd67;single;316;17783;;34574;12710;27536;8477;33908;1375;307.628461075;2021-12-06@23:34:13
+v2.0.8-163-ge813cd67;single;316;17783;;34574;12710;27484;8425;33700;1372;307.259035026;2021-12-06@23:39:22
+v2.0.8-163-ge813cd67;single;316;17783;;34574;12710;27340;8435;33740;1395;312.356857004;2021-12-06@23:44:36
+v2.0.8-163-ge813cd67;single;316;17783;;34574;12710;27532;8441;33764;1377;308.193987236;2021-12-06@23:49:46
+v2.0.8-163-ge813cd67;single;316;17783;;34574;12710;27304;8441;33764;1398;312.486161392;2021-12-06@23:55:00
+v2.0.8-163-ge813cd67;single;316;17783;;34574;12710;27448;8468;33872;1391;311.376580435;2021-12-07@00:00:12
+v2.0.8-163-ge813cd67;single;316;17783;;34574;12710;27408;8488;33952;1415;316.358885819;2021-12-07@00:05:30
+v2.0.8-163-ge813cd67;single;316;17783;;34574;12710;27208;8411;33644;1395;312.665274985;2021-12-07@00:10:44
+v2.0.8-163-ge813cd67;mulimex;316;17783;;34574;12710;1345012;2437790;9751160;303;67.809636965;2021-12-07@00:15:15
+v2.0.8-163-ge813cd67;mulimex;316;17783;;34574;12710;1343316;2438145;9752580;310;68.863045109;2021-12-07@00:16:26
+v2.0.8-163-ge813cd67;mulimex;316;17783;;34574;12710;1333476;2436939;9747756;313;69.917316184;2021-12-07@00:17:37
+v2.0.8-163-ge813cd67;mulimex;316;17783;;34574;12710;1339004;2437407;9749628;311;69.161857163;2021-12-07@00:18:49
+v2.0.8-163-ge813cd67;mulimex;316;17783;;34574;12710;1339604;2436905;9747620;305;67.833680449;2021-12-07@00:19:58
+v2.0.8-163-ge813cd67;mulimex;316;17783;;34574;12710;1340520;2438025;9752100;312;69.367867416;2021-12-07@00:21:09
+v2.0.8-163-ge813cd67;mulimex;316;17783;;34574;12710;1350528;2439559;9758236;309;68.616446795;2021-12-07@00:22:20
+v2.0.8-163-ge813cd67;mulimex;316;17783;;34574;12710;1342512;2438486;9753944;311;69.337702741;2021-12-07@00:23:31
+v2.0.8-163-ge813cd67;multi;10;31623;;54425;24231;76764;199484;797936;25;5.577802708;2021-12-07@00:25:14
+v2.0.8-163-ge813cd67;multi;10;31623;;54425;24231;76900;199458;797832;26;5.788412392;2021-12-07@00:25:21
+v2.0.8-163-ge813cd67;multi;10;31623;;54425;24231;76992;199475;797900;27;6.017407981;2021-12-07@00:25:29
+v2.0.8-163-ge813cd67;multi;10;31623;;54425;24231;76676;199483;797932;27;6.020662961;2021-12-07@00:25:36
+v2.0.8-163-ge813cd67;multi;10;31623;;54425;24231;76620;199481;797924;27;6.020711463;2021-12-07@00:25:44
+v2.0.8-163-ge813cd67;multi;10;31623;;54425;24231;76740;199655;798620;26;5.783806234;2021-12-07@00:25:51
+v2.0.8-163-ge813cd67;multi;10;31623;;54425;24231;76904;199527;798108;27;6.026277832;2021-12-07@00:25:58
+v2.0.8-163-ge813cd67;multi;10;31623;;54425;24231;76908;199483;797932;27;6.022514340;2021-12-07@00:26:06
+v2.0.8-163-ge813cd67;imex;10;31623;;54425;24231;64644;18025;72100;77;17.226446663;2021-12-07@00:26:32
+v2.0.8-163-ge813cd67;imex;10;31623;;54425;24231;64444;18016;72064;80;17.907633542;2021-12-07@00:26:51
+v2.0.8-163-ge813cd67;imex;10;31623;;54425;24231;64408;18007;72028;80;17.926030627;2021-12-07@00:27:11
+v2.0.8-163-ge813cd67;imex;10;31623;;54425;24231;64464;18013;72052;77;17.233416684;2021-12-07@00:27:29
+v2.0.8-163-ge813cd67;imex;10;31623;;54425;24231;64412;18030;72120;79;17.687326143;2021-12-07@00:27:49
+v2.0.8-163-ge813cd67;imex;10;31623;;54425;24231;64364;18005;72020;81;18.123377453;2021-12-07@00:28:08
+v2.0.8-163-ge813cd67;imex;10;31623;;54425;24231;64440;18016;72064;82;18.364293282;2021-12-07@00:28:28
+v2.0.8-163-ge813cd67;imex;10;31623;;54425;24231;64492;18000;72000;76;17.009441753;2021-12-07@00:28:47
+v2.0.8-163-ge813cd67;single;10;31623;;54425;24231;18460;6167;24668;81;18.101138551;2021-12-07@00:29:13
+v2.0.8-163-ge813cd67;single;10;31623;;54425;24231;18504;6168;24672;78;17.458490078;2021-12-07@00:29:32
+v2.0.8-163-ge813cd67;single;10;31623;;54425;24231;18448;6194;24776;80;17.911326333;2021-12-07@00:29:51
+v2.0.8-163-ge813cd67;single;10;31623;;54425;24231;18436;6177;24708;81;18.146125845;2021-12-07@00:30:11
+v2.0.8-163-ge813cd67;single;10;31623;;54425;24231;18556;6169;24676;78;17.452083239;2021-12-07@00:30:30
+v2.0.8-163-ge813cd67;single;10;31623;;54425;24231;18480;6176;24704;79;17.677930319;2021-12-07@00:30:49
+v2.0.8-163-ge813cd67;single;10;31623;;54425;24231;18460;6170;24680;81;18.121845008;2021-12-07@00:31:09
+v2.0.8-163-ge813cd67;single;10;31623;;54425;24231;18420;6170;24680;79;17.678617968;2021-12-07@00:31:28
+v2.0.8-163-ge813cd67;mulimex;10;31623;;54425;24231;112172;208924;835696;25;5.495750793;2021-12-07@00:31:42
+v2.0.8-163-ge813cd67;mulimex;10;31623;;54425;24231;111604;208478;833912;27;6.021183973;2021-12-07@00:31:50
+v2.0.8-163-ge813cd67;mulimex;10;31623;;54425;24231;111644;208531;834124;27;6.019107549;2021-12-07@00:31:58
+v2.0.8-163-ge813cd67;mulimex;10;31623;;54425;24231;112108;208522;834088;27;6.025052294;2021-12-07@00:32:05
+v2.0.8-163-ge813cd67;mulimex;10;31623;;54425;24231;111708;208578;834312;27;6.023775603;2021-12-07@00:32:13
+v2.0.8-163-ge813cd67;mulimex;10;31623;;54425;24231;112200;208933;835732;28;6.250478218;2021-12-07@00:32:20
+v2.0.8-163-ge813cd67;mulimex;10;31623;;54425;24231;111696;208506;834024;27;6.022541521;2021-12-07@00:32:28
+v2.0.8-163-ge813cd67;mulimex;10;31623;;54425;24231;111580;208581;834324;27;6.020572487;2021-12-07@00:32:36
+v2.0.8-163-ge813cd67;multi;18;31623;;57202;24252;116424;340481;1361924;29;6.400766487;2021-12-07@00:32:53
+v2.0.8-163-ge813cd67;multi;18;31623;;57202;24252;116692;340571;1362284;33;7.379586660;2021-12-07@00:33:02
+v2.0.8-163-ge813cd67;multi;18;31623;;57202;24252;116604;340586;1362344;33;7.381501958;2021-12-07@00:33:11
+v2.0.8-163-ge813cd67;multi;18;31623;;57202;24252;116620;340566;1362264;33;7.381369677;2021-12-07@00:33:20
+v2.0.8-163-ge813cd67;multi;18;31623;;57202;24252;116664;340622;1362488;33;7.384938356;2021-12-07@00:33:28
+v2.0.8-163-ge813cd67;multi;18;31623;;57202;24252;117200;340835;1363340;33;7.373041717;2021-12-07@00:33:37
+v2.0.8-163-ge813cd67;multi;18;31623;;57202;24252;116348;340492;1361968;33;7.389839125;2021-12-07@00:33:46
+v2.0.8-163-ge813cd67;multi;18;31623;;57202;24252;116464;340628;1362512;33;7.379063027;2021-12-07@00:33:55
+v2.0.8-163-ge813cd67;imex;18;31623;;57202;24252;88760;24347;97388;134;30.083540487;2021-12-07@00:34:38
+v2.0.8-163-ge813cd67;imex;18;31623;;57202;24252;88600;24339;97356;135;30.306881467;2021-12-07@00:35:10
+v2.0.8-163-ge813cd67;imex;18;31623;;57202;24252;88580;24323;97292;136;30.533026576;2021-12-07@00:35:42
+v2.0.8-163-ge813cd67;imex;18;31623;;57202;24252;88728;24308;97232;145;32.572913302;2021-12-07@00:36:16
+v2.0.8-163-ge813cd67;imex;18;31623;;57202;24252;88696;24332;97328;136;30.525377490;2021-12-07@00:36:48
+v2.0.8-163-ge813cd67;imex;18;31623;;57202;24252;88808;24349;97396;131;29.401545980;2021-12-07@00:37:19
+v2.0.8-163-ge813cd67;imex;18;31623;;57202;24252;88932;24318;97272;136;30.529224325;2021-12-07@00:37:51
+v2.0.8-163-ge813cd67;imex;18;31623;;57202;24252;88752;24336;97344;134;30.071959037;2021-12-07@00:38:22
+v2.0.8-163-ge813cd67;single;18;31623;;57202;24252;19868;6518;26072;136;30.477845740;2021-12-07@00:39:08
+v2.0.8-163-ge813cd67;single;18;31623;;57202;24252;19816;6510;26040;135;30.291957133;2021-12-07@00:39:40
+v2.0.8-163-ge813cd67;single;18;31623;;57202;24252;19812;6532;26128;134;30.058306208;2021-12-07@00:40:12
+v2.0.8-163-ge813cd67;single;18;31623;;57202;24252;19824;6503;26012;134;30.088943675;2021-12-07@00:40:43
+v2.0.8-163-ge813cd67;single;18;31623;;57202;24252;19744;6516;26064;137;30.729689582;2021-12-07@00:41:15
+v2.0.8-163-ge813cd67;single;18;31623;;57202;24252;19864;6523;26092;135;30.302545625;2021-12-07@00:41:47
+v2.0.8-163-ge813cd67;single;18;31623;;57202;24252;19836;6506;26024;135;30.299142632;2021-12-07@00:42:19
+v2.0.8-163-ge813cd67;single;18;31623;;57202;24252;19816;6503;26012;133;29.843170551;2021-12-07@00:42:50
+v2.0.8-163-ge813cd67;mulimex;18;31623;;57202;24252;173404;355989;1423956;31;6.885606766;2021-12-07@00:43:10
+v2.0.8-163-ge813cd67;mulimex;18;31623;;57202;24252;172724;355300;1421200;35;7.833118953;2021-12-07@00:43:19
+v2.0.8-163-ge813cd67;mulimex;18;31623;;57202;24252;173084;355306;1421224;35;7.847829123;2021-12-07@00:43:29
+v2.0.8-163-ge813cd67;mulimex;18;31623;;57202;24252;172604;355301;1421204;35;7.830660423;2021-12-07@00:43:38
+v2.0.8-163-ge813cd67;mulimex;18;31623;;57202;24252;173664;355321;1421284;34;7.621537193;2021-12-07@00:43:47
+v2.0.8-163-ge813cd67;mulimex;18;31623;;57202;24252;173276;355327;1421308;35;7.841106487;2021-12-07@00:43:56
+v2.0.8-163-ge813cd67;mulimex;18;31623;;57202;24252;173584;355401;1421604;35;7.844758686;2021-12-07@00:44:06
+v2.0.8-163-ge813cd67;mulimex;18;31623;;57202;24252;174300;355426;1421704;35;7.845561359;2021-12-07@00:44:15
+v2.0.8-163-ge813cd67;multi;32;31623;;58800;24186;182716;587123;2348492;47;10.579153166;2021-12-07@00:44:43
+v2.0.8-163-ge813cd67;multi;32;31623;;58800;24186;184024;587074;2348296;51;11.463146307;2021-12-07@00:44:56
+v2.0.8-163-ge813cd67;multi;32;31623;;58800;24186;184800;586959;2347836;53;11.911091684;2021-12-07@00:45:10
+v2.0.8-163-ge813cd67;multi;32;31623;;58800;24186;184032;587062;2348248;52;11.692711266;2021-12-07@00:45:23
+v2.0.8-163-ge813cd67;multi;32;31623;;58800;24186;186044;587247;2348988;52;11.695563953;2021-12-07@00:45:36
+v2.0.8-163-ge813cd67;multi;32;31623;;58800;24186;184560;588183;2352732;52;11.701077247;2021-12-07@00:45:49
+v2.0.8-163-ge813cd67;multi;32;31623;;58800;24186;185772;588521;2354084;52;11.689344915;2021-12-07@00:46:02
+v2.0.8-163-ge813cd67;multi;32;31623;;58800;24186;185504;587566;2350264;52;11.690960133;2021-12-07@00:46:16
+v2.0.8-163-ge813cd67;imex;32;31623;;58800;24186;128752;34758;139032;237;53.312138122;2021-12-07@00:47:30
+v2.0.8-163-ge813cd67;imex;32;31623;;58800;24186;128692;34777;139108;228;51.235768652;2021-12-07@00:48:23
+v2.0.8-163-ge813cd67;imex;32;31623;;58800;24186;129096;34807;139228;234;52.648362874;2021-12-07@00:49:17
+v2.0.8-163-ge813cd67;imex;32;31623;;58800;24186;128696;34741;138964;232;52.133848451;2021-12-07@00:50:10
+v2.0.8-163-ge813cd67;imex;32;31623;;58800;24186;128676;34775;139100;229;51.523463627;2021-12-07@00:51:03
+v2.0.8-163-ge813cd67;imex;32;31623;;58800;24186;128924;34766;139064;234;52.616067758;2021-12-07@00:51:58
+v2.0.8-163-ge813cd67;imex;32;31623;;58800;24186;128692;34772;139088;236;53.036605963;2021-12-07@00:52:52
+v2.0.8-163-ge813cd67;imex;32;31623;;58800;24186;129244;34831;139324;230;51.705653714;2021-12-07@00:53:45
+v2.0.8-163-ge813cd67;single;32;31623;;58800;24186;21324;6853;27412;245;55.167119391;2021-12-07@00:55:01
+v2.0.8-163-ge813cd67;single;32;31623;;58800;24186;21180;6852;27408;236;53.172595220;2021-12-07@00:55:56
+v2.0.8-163-ge813cd67;single;32;31623;;58800;24186;21072;6862;27448;242;54.524588789;2021-12-07@00:56:52
+v2.0.8-163-ge813cd67;single;32;31623;;58800;24186;21176;6855;27420;236;53.161102168;2021-12-07@00:57:47
+v2.0.8-163-ge813cd67;single;32;31623;;58800;24186;21320;6848;27392;232;52.259276307;2021-12-07@00:58:40
+v2.0.8-163-ge813cd67;single;32;31623;;58800;24186;21052;6873;27492;232;52.266579352;2021-12-07@00:59:34
+v2.0.8-163-ge813cd67;single;32;31623;;58800;24186;21088;6856;27424;233;52.467697369;2021-12-07@01:00:28
+v2.0.8-163-ge813cd67;single;32;31623;;58800;24186;21468;6856;27424;237;53.383505884;2021-12-07@01:01:23
+v2.0.8-163-ge813cd67;mulimex;32;31623;;58800;24186;278572;611752;2447008;52;11.653541875;2021-12-07@01:01:58
+v2.0.8-163-ge813cd67;mulimex;32;31623;;58800;24186;278404;612760;2451040;55;12.368262406;2021-12-07@01:02:12
+v2.0.8-163-ge813cd67;mulimex;32;31623;;58800;24186;279616;612778;2451112;53;11.911194767;2021-12-07@01:02:25
+v2.0.8-163-ge813cd67;mulimex;32;31623;;58800;24186;278372;611777;2447108;55;12.368401746;2021-12-07@01:02:39
+v2.0.8-163-ge813cd67;mulimex;32;31623;;58800;24186;278708;611731;2446924;55;12.381498100;2021-12-07@01:02:53
+v2.0.8-163-ge813cd67;mulimex;32;31623;;58800;24186;278272;611671;2446684;54;12.134275556;2021-12-07@01:03:07
+v2.0.8-163-ge813cd67;mulimex;32;31623;;58800;24186;278436;611804;2447216;55;12.363755621;2021-12-07@01:03:21
+v2.0.8-163-ge813cd67;mulimex;32;31623;;58800;24186;278860;611664;2446656;55;12.360190208;2021-12-07@01:03:35
+v2.0.8-163-ge813cd67;multi;56;31623;;59758;24285;300868;1011829;4047316;79;17.841550637;2021-12-07@01:04:22
+v2.0.8-163-ge813cd67;multi;56;31623;;59758;24285;301204;1009790;4039160;85;19.150460681;2021-12-07@01:04:42
+v2.0.8-163-ge813cd67;multi;56;31623;;59758;24285;301884;1009931;4039724;85;19.156987935;2021-12-07@01:05:03
+v2.0.8-163-ge813cd67;multi;56;31623;;59758;24285;303212;1011935;4047740;85;19.168569429;2021-12-07@01:05:24
+v2.0.8-163-ge813cd67;multi;56;31623;;59758;24285;302832;1009878;4039512;85;19.152589510;2021-12-07@01:05:44
+v2.0.8-163-ge813cd67;multi;56;31623;;59758;24285;301332;1009520;4038080;85;19.156489738;2021-12-07@01:06:05
+v2.0.8-163-ge813cd67;multi;56;31623;;59758;24285;304196;1010217;4040868;86;19.419680958;2021-12-07@01:06:26
+v2.0.8-163-ge813cd67;multi;56;31623;;59758;24285;302612;1010067;4040268;85;19.179914363;2021-12-07@01:06:47
+v2.0.8-163-ge813cd67;imex;56;31623;;59758;24285;197272;52561;210244;411;92.667699750;2021-12-07@01:08:57
+v2.0.8-163-ge813cd67;imex;56;31623;;59758;24285;197560;52712;210848;392;88.400097042;2021-12-07@01:10:27
+v2.0.8-163-ge813cd67;imex;56;31623;;59758;24285;197332;52624;210496;393;88.657107917;2021-12-07@01:11:57
+v2.0.8-163-ge813cd67;imex;56;31623;;59758;24285;197476;52616;210464;395;89.129672065;2021-12-07@01:13:28
+v2.0.8-163-ge813cd67;imex;56;31623;;59758;24285;197100;52596;210384;410;92.483124898;2021-12-07@01:15:02
+v2.0.8-163-ge813cd67;imex;56;31623;;59758;24285;197380;52585;210340;390;87.999132200;2021-12-07@01:16:32
+v2.0.8-163-ge813cd67;imex;56;31623;;59758;24285;197308;52606;210424;388;87.613169021;2021-12-07@01:18:01
+v2.0.8-163-ge813cd67;imex;56;31623;;59758;24285;197384;52611;210444;389;87.708007347;2021-12-07@01:19:30
+v2.0.8-163-ge813cd67;single;56;31623;;59758;24285;22788;7301;29204;418;94.321222264;2021-12-07@01:21:40
+v2.0.8-163-ge813cd67;single;56;31623;;59758;24285;22700;7295;29180;410;92.585081943;2021-12-07@01:23:14
+v2.0.8-163-ge813cd67;single;56;31623;;59758;24285;23016;7326;29304;411;92.781088585;2021-12-07@01:24:48
+v2.0.8-163-ge813cd67;single;56;31623;;59758;24285;23032;7295;29180;403;90.937158773;2021-12-07@01:26:21
+v2.0.8-163-ge813cd67;single;56;31623;;59758;24285;23032;7290;29160;411;92.785834058;2021-12-07@01:27:55
+v2.0.8-163-ge813cd67;single;56;31623;;59758;24285;22900;7280;29120;402;90.704430019;2021-12-07@01:29:27
+v2.0.8-163-ge813cd67;single;56;31623;;59758;24285;22868;7305;29220;407;91.854492069;2021-12-07@01:31:01
+v2.0.8-163-ge813cd67;single;56;31623;;59758;24285;22860;7289;29156;409;92.300350374;2021-12-07@01:32:34
+v2.0.8-163-ge813cd67;mulimex;56;31623;;59758;24285;461260;1053358;4213432;87;19.699291229;2021-12-07@01:33:30
+v2.0.8-163-ge813cd67;mulimex;56;31623;;59758;24285;460912;1051415;4205660;92;20.785507756;2021-12-07@01:33:53
+v2.0.8-163-ge813cd67;mulimex;56;31623;;59758;24285;461152;1053291;4213164;87;19.726254969;2021-12-07@01:34:29
+v2.0.8-163-ge813cd67;mulimex;56;31623;;59758;24285;459876;1051307;4205228;92;20.775752567;2021-12-07@01:34:51
+v2.0.8-163-ge813cd67;mulimex;56;31623;;59758;24285;460072;1051324;4205296;91;20.555773537;2021-12-07@01:35:13
+v2.0.8-163-ge813cd67;mulimex;56;31623;;59758;24285;460996;1053211;4212844;91;20.528760828;2021-12-07@01:35:35
+v2.0.8-163-ge813cd67;mulimex;56;31623;;59758;24285;459916;1051392;4205568;92;20.781245594;2021-12-07@01:35:58
+v2.0.8-163-ge813cd67;mulimex;56;31623;;59758;24285;459308;1053332;4213328;92;20.789469705;2021-12-07@01:36:20
+v2.0.8-163-ge813cd67;multi;100;31623;;60390;24233;517380;1784173;7136692;153;34.416894304;2021-12-07@01:37:47
+v2.0.8-163-ge813cd67;multi;100;31623;;60390;24233;515612;1787609;7150436;157;35.405846288;2021-12-07@01:38:24
+v2.0.8-163-ge813cd67;multi;100;31623;;60390;24233;515420;1787527;7150108;154;34.881555762;2021-12-07@01:39:01
+v2.0.8-163-ge813cd67;multi;100;31623;;60390;24233;516432;1784413;7137652;155;35.032807443;2021-12-07@01:39:37
+v2.0.8-163-ge813cd67;multi;100;31623;;60390;24233;513816;1783791;7135164;155;35.070035286;2021-12-07@01:40:14
+v2.0.8-163-ge813cd67;multi;100;31623;;60390;24233;515732;1788102;7152408;157;35.566989248;2021-12-07@01:40:51
+v2.0.8-163-ge813cd67;multi;100;31623;;60390;24233;515368;1787864;7151456;152;34.373459724;2021-12-07@01:41:27
+v2.0.8-163-ge813cd67;multi;100;31623;;60390;24233;514820;1783941;7135764;158;35.657870096;2021-12-07@01:42:04
+v2.0.8-163-ge813cd67;imex;100;31623;;60390;24233;320068;84718;338872;709;160.467395019;2021-12-07@01:45:52
+v2.0.8-163-ge813cd67;imex;100;31623;;60390;24233;320124;84653;338612;719;162.870144403;2021-12-07@01:48:36
+v2.0.8-163-ge813cd67;imex;100;31623;;60390;24233;320528;84778;339112;695;157.321678340;2021-12-07@01:51:15
+v2.0.8-163-ge813cd67;imex;100;31623;;60390;24233;321228;84926;339704;710;160.900969657;2021-12-07@01:53:57
+v2.0.8-163-ge813cd67;imex;100;31623;;60390;24233;320524;84759;339036;714;161.734540286;2021-12-07@01:56:41
+v2.0.8-163-ge813cd67;imex;100;31623;;60390;24233;321320;84981;339924;734;166.062190793;2021-12-07@01:59:28
+v2.0.8-163-ge813cd67;imex;100;31623;;60390;24233;319704;84544;338176;783;177.636056130;2021-12-07@02:02:27
+v2.0.8-163-ge813cd67;imex;100;31623;;60390;24233;320308;84771;339084;725;164.311025332;2021-12-07@02:05:13
+v2.0.8-163-ge813cd67;single;100;31623;;60390;24233;25224;7915;31660;737;166.981266478;2021-12-07@02:09:01
+v2.0.8-163-ge813cd67;single;100;31623;;60390;24233;25580;7940;31760;720;163.318866170;2021-12-07@02:11:46
+v2.0.8-163-ge813cd67;single;100;31623;;60390;24233;25420;7917;31668;723;164.033187130;2021-12-07@02:14:32
+v2.0.8-163-ge813cd67;single;100;31623;;60390;24233;25660;7925;31700;752;170.489334259;2021-12-07@02:17:24
+v2.0.8-163-ge813cd67;single;100;31623;;60390;24233;25400;7944;31776;725;164.360053176;2021-12-07@02:20:10
+v2.0.8-163-ge813cd67;single;100;31623;;60390;24233;25464;7929;31716;752;170.679482590;2021-12-07@02:23:02
+v2.0.8-163-ge813cd67;single;100;31623;;60390;24233;25596;7948;31792;743;168.575556044;2021-12-07@02:25:52
+v2.0.8-163-ge813cd67;single;100;31623;;60390;24233;25720;7942;31768;725;164.418423815;2021-12-07@02:28:38
+v2.0.8-163-ge813cd67;mulimex;100;31623;;60390;24233;789592;1856704;7426816;159;36.027172624;2021-12-07@02:30:20
+v2.0.8-163-ge813cd67;mulimex;100;31623;;60390;24233;793800;1857106;7428424;166;37.648918435;2021-12-07@02:30:59
+v2.0.8-163-ge813cd67;mulimex;100;31623;;60390;24233;794172;1857583;7430332;168;38.092730405;2021-12-07@02:31:39
+v2.0.8-163-ge813cd67;mulimex;100;31623;;60390;24233;793608;1857138;7428552;166;37.684220950;2021-12-07@02:32:18
+v2.0.8-163-ge813cd67;mulimex;100;31623;;60390;24233;791064;1856783;7427132;169;38.270562574;2021-12-07@02:32:58
+v2.0.8-163-ge813cd67;mulimex;100;31623;;60390;24233;792100;1856917;7427668;164;37.153277285;2021-12-07@02:33:37
+v2.0.8-163-ge813cd67;mulimex;100;31623;;60390;24233;794384;1857127;7428508;165;37.381511046;2021-12-07@02:34:15
+v2.0.8-163-ge813cd67;mulimex;100;31623;;60390;24233;792788;1860509;7442036;164;37.170801259;2021-12-07@02:34:54
+v2.0.8-163-ge813cd67;multi;178;31623;;60806;24427;898856;2306688;9226752;282;63.485166959;2021-12-07@02:37:34
+v2.0.8-163-ge813cd67;multi;178;31623;;60806;24427;896396;2306813;9227252;285;64.147590879;2021-12-07@02:38:40
+v2.0.8-163-ge813cd67;multi;178;31623;;60806;24427;894276;2305744;9222976;289;64.815940230;2021-12-07@02:39:46
+v2.0.8-163-ge813cd67;multi;178;31623;;60806;24427;892804;2305880;9223520;288;65.236804114;2021-12-07@02:40:53
+v2.0.8-163-ge813cd67;multi;178;31623;;60806;24427;891536;2305180;9220720;284;64.155186868;2021-12-07@02:41:58
+v2.0.8-163-ge813cd67;multi;178;31623;;60806;24427;896172;2306350;9225400;288;64.981371702;2021-12-07@02:43:05
+v2.0.8-163-ge813cd67;multi;178;31623;;60806;24427;899116;2306601;9226404;286;64.785877279;2021-12-07@02:44:11
+v2.0.8-163-ge813cd67;multi;178;31623;;60806;24427;894444;2305725;9222900;286;64.495302153;2021-12-07@02:45:17
+v2.0.8-163-ge813cd67;imex;178;31623;;60806;24427;545384;143317;573268;1322;301.218931787;2021-12-07@02:52:17
+v2.0.8-163-ge813cd67;imex;178;31623;;60806;24427;542120;142445;569780;1271;289.021362138;2021-12-07@02:57:07
+v2.0.8-163-ge813cd67;imex;178;31623;;60806;24427;540200;142046;568184;1246;283.686969006;2021-12-07@03:01:53
+v2.0.8-163-ge813cd67;imex;178;31623;;60806;24427;545040;143179;572716;1275;290.751202239;2021-12-07@03:06:45
+v2.0.8-163-ge813cd67;imex;178;31623;;60806;24427;541324;142212;568848;1296;294.315330410;2021-12-07@03:11:41
+v2.0.8-163-ge813cd67;imex;178;31623;;60806;24427;545964;143497;573988;1328;302.179796484;2021-12-07@03:16:44
+v2.0.8-163-ge813cd67;imex;178;31623;;60806;24427;541064;142169;568676;1219;277.303647172;2021-12-07@03:21:23
+v2.0.8-163-ge813cd67;imex;178;31623;;60806;24427;543708;142865;571460;1219;277.251790644;2021-12-07@03:26:02
+v2.0.8-163-ge813cd67;single;178;31623;;60806;24427;29568;8953;35812;1381;314.933741822;2021-12-07@03:33:15
+v2.0.8-163-ge813cd67;single;178;31623;;60806;24427;29852;8987;35948;1381;314.884152091;2021-12-07@03:38:31
+v2.0.8-163-ge813cd67;single;178;31623;;60806;24427;29464;8955;35820;1315;299.610186275;2021-12-07@03:43:32
+v2.0.8-163-ge813cd67;single;178;31623;;60806;24427;29584;9000;36000;1341;305.879601392;2021-12-07@03:48:40
+v2.0.8-163-ge813cd67;single;178;31623;;60806;24427;29728;8974;35896;1327;302.430958846;2021-12-07@03:53:44
+v2.0.8-163-ge813cd67;single;178;31623;;60806;24427;29684;8987;35948;1340;305.636436267;2021-12-07@03:58:51
+v2.0.8-163-ge813cd67;single;178;31623;;60806;24427;29636;8994;35976;1343;306.805716527;2021-12-07@04:03:59
+v2.0.8-163-ge813cd67;single;178;31623;;60806;24427;29812;8938;35752;1358;309.108867395;2021-12-07@04:09:10
+v2.0.8-163-ge813cd67;mulimex;178;31623;;60806;24427;1383884;2433999;9735996;288;65.782263965;2021-12-07@04:12:09
+v2.0.8-163-ge813cd67;mulimex;178;31623;;60806;24427;1387548;2440387;9761548;296;67.251556543;2021-12-07@04:13:18
+v2.0.8-163-ge813cd67;mulimex;178;31623;;60806;24427;1380996;2439256;9757024;289;65.705329339;2021-12-07@04:14:25
+v2.0.8-163-ge813cd67;mulimex;178;31623;;60806;24427;1386556;2434621;9738484;303;68.836958194;2021-12-07@04:15:36
+v2.0.8-163-ge813cd67;mulimex;178;31623;;60806;24427;1394092;2436329;9745316;302;67.997739998;2021-12-07@04:16:46
+v2.0.8-163-ge813cd67;mulimex;178;31623;;60806;24427;1390056;2435491;9741964;304;68.100654920;2021-12-07@04:17:56
+v2.0.8-163-ge813cd67;mulimex;178;31623;;60806;24427;1386112;2434454;9737816;298;67.415780839;2021-12-07@04:19:05
+v2.0.8-163-ge813cd67;mulimex;178;31623;;60806;24427;1387456;2435007;9740028;298;67.383057018;2021-12-07@04:20:14
+v2.0.8-163-ge813cd67;multi;316;31623;;61364;24592;1564356;2475939;9903756;494;113.081628459;2021-12-07@04:25:05
+v2.0.8-163-ge813cd67;multi;316;31623;;61364;24592;1561796;2484820;9939280;499;114.135198074;2021-12-07@04:27:01
+v2.0.8-163-ge813cd67;multi;316;31623;;61364;24592;1568952;2476967;9907868;495;113.669964646;2021-12-07@04:28:57
+v2.0.8-163-ge813cd67;multi;316;31623;;61364;24592;1569832;2477406;9909624;497;113.601881144;2021-12-07@04:30:52
+v2.0.8-163-ge813cd67;multi;316;31623;;61364;24592;1558324;2474234;9896936;496;113.867045197;2021-12-07@04:32:48
+v2.0.8-163-ge813cd67;multi;316;31623;;61364;24592;1568836;2477533;9910132;497;113.782203885;2021-12-07@04:34:43
+v2.0.8-163-ge813cd67;multi;316;31623;;61364;24592;1565824;2485586;9942344;501;114.821111493;2021-12-07@04:36:40
+v2.0.8-163-ge813cd67;multi;316;31623;;61364;24592;1565860;2485028;9940112;498;114.275231717;2021-12-07@04:38:36
+v2.0.8-163-ge813cd67;imex;316;31623;;61364;24592;936584;245198;980792;2427;554.549783224;2021-12-07@04:51:18
+v2.0.8-163-ge813cd67;imex;316;31623;;61364;24592;933528;244480;977920;2295;524.808953460;2021-12-07@05:00:04
+v2.0.8-163-ge813cd67;imex;316;31623;;61364;24592;932280;244185;976740;2408;550.042429410;2021-12-07@05:09:16
+v2.0.8-163-ge813cd67;imex;316;31623;;61364;24592;932532;244198;976792;2362;540.242527510;2021-12-07@05:18:17
+v2.0.8-163-ge813cd67;imex;316;31623;;61364;24592;934304;244660;978640;2331;532.427188477;2021-12-07@05:27:11
+v2.0.8-163-ge813cd67;imex;316;31623;;61364;24592;931900;244106;976424;2330;532.685504183;2021-12-07@05:36:06
+v2.0.8-163-ge813cd67;imex;316;31623;;61364;24592;933356;244429;977716;2394;547.319692755;2021-12-07@05:45:15
+v2.0.8-163-ge813cd67;imex;316;31623;;61364;24592;936964;245256;981024;2406;550.548794255;2021-12-07@05:54:27
+v2.0.8-163-ge813cd67;single;316;31623;;61364;24592;36176;10588;42352;2521;579.773663369;2021-12-07@06:07:43
+v2.0.8-163-ge813cd67;single;316;31623;;61364;24592;36132;10623;42492;2531;581.015621061;2021-12-07@06:17:26
+v2.0.8-163-ge813cd67;single;316;31623;;61364;24592;36304;10607;42428;2965;681.686200617;2021-12-07@06:28:49
+v2.0.8-163-ge813cd67;single;316;31623;;61364;24592;36136;10581;42324;2474;567.921765651;2021-12-07@06:38:19
+v2.0.8-163-ge813cd67;single;316;31623;;61364;24592;36072;10599;42396;2499;574.871336843;2021-12-07@06:47:56
+v2.0.8-163-ge813cd67;single;316;31623;;61364;24592;36136;10570;42280;2496;572.756761287;2021-12-07@06:57:31
+v2.0.8-163-ge813cd67;single;316;31623;;61364;24592;36184;10618;42472;2534;582.963105866;2021-12-07@07:07:15
+v2.0.8-163-ge813cd67;single;316;31623;;61364;24592;36368;10586;42344;2635;605.514667353;2021-12-07@07:17:23
+v2.0.8-163-ge813cd67;mulimex;316;31623;;61364;24592;2440152;2704646;10818584;514;118.361277565;2021-12-07@07:22:53
+v2.0.8-163-ge813cd67;mulimex;316;31623;;61364;24592;2440672;2704599;10818396;525;121.075359678;2021-12-07@07:24:56
+v2.0.8-163-ge813cd67;mulimex;316;31623;;61364;24592;2438684;2703467;10813868;507;116.389648303;2021-12-07@07:26:55
+v2.0.8-163-ge813cd67;mulimex;316;31623;;61364;24592;2438696;2703819;10815276;526;120.279403573;2021-12-07@07:28:58
+v2.0.8-163-ge813cd67;mulimex;316;31623;;61364;24592;2436868;2704424;10817696;529;121.009792457;2021-12-07@07:31:02
+v2.0.8-163-ge813cd67;mulimex;316;31623;;61364;24592;2434668;2703865;10815460;538;123.069886967;2021-12-07@07:33:08
+v2.0.8-163-ge813cd67;mulimex;316;31623;;61364;24592;2442272;2704834;10819336;526;120.496355730;2021-12-07@07:35:11
+v2.0.8-163-ge813cd67;mulimex;316;31623;;61364;24592;2453404;2707315;10829260;530;121.430644180;2021-12-07@07:37:15
+v2.0.8-163-ge813cd67;multi;10;56234;;89496;27046;115296;208015;832060;35;7.964320633;2021-12-07@07:39:04
+v2.0.8-163-ge813cd67;multi;10;56234;;89496;27046;115392;208046;832184;40;9.061927972;2021-12-07@07:39:15
+v2.0.8-163-ge813cd67;multi;10;56234;;89496;27046;115584;208101;832404;39;8.861481273;2021-12-07@07:39:25
+v2.0.8-163-ge813cd67;multi;10;56234;;89496;27046;115808;208032;832128;40;9.087204592;2021-12-07@07:39:37
+v2.0.8-163-ge813cd67;multi;10;56234;;89496;27046;114916;207983;831932;39;8.854858360;2021-12-07@07:39:47
+v2.0.8-163-ge813cd67;multi;10;56234;;89496;27046;115424;208018;832072;37;8.382687618;2021-12-07@07:39:58
+v2.0.8-163-ge813cd67;multi;10;56234;;89496;27046;115200;208007;832028;39;8.853871091;2021-12-07@07:40:09
+v2.0.8-163-ge813cd67;multi;10;56234;;89496;27046;115720;208002;832008;39;8.843495349;2021-12-07@07:40:20
+v2.0.8-163-ge813cd67;imex;10;56234;;89496;27046;88528;24052;96208;105;23.752201886;2021-12-07@07:40:54
+v2.0.8-163-ge813cd67;imex;10;56234;;89496;27046;88760;24119;96476;103;23.401243122;2021-12-07@07:41:19
+v2.0.8-163-ge813cd67;imex;10;56234;;89496;27046;88500;24097;96388;107;24.256920240;2021-12-07@07:41:46
+v2.0.8-163-ge813cd67;imex;10;56234;;89496;27046;88764;24076;96304;107;24.270016785;2021-12-07@07:42:12
+v2.0.8-163-ge813cd67;imex;10;56234;;89496;27046;88716;24084;96336;110;24.958964455;2021-12-07@07:42:39
+v2.0.8-163-ge813cd67;imex;10;56234;;89496;27046;88480;24077;96308;107;24.241826574;2021-12-07@07:43:05
+v2.0.8-163-ge813cd67;imex;10;56234;;89496;27046;88784;24091;96364;108;24.480520406;2021-12-07@07:43:31
+v2.0.8-163-ge813cd67;imex;10;56234;;89496;27046;88632;24079;96316;101;22.900378385;2021-12-07@07:43:56
+v2.0.8-163-ge813cd67;single;10;56234;;89496;27046;26988;8345;33380;100;22.622742114;2021-12-07@07:44:31
+v2.0.8-163-ge813cd67;single;10;56234;;89496;27046;26904;8347;33388;106;23.991849353;2021-12-07@07:44:56
+v2.0.8-163-ge813cd67;single;10;56234;;89496;27046;27128;8372;33488;104;23.537595240;2021-12-07@07:45:22
+v2.0.8-163-ge813cd67;single;10;56234;;89496;27046;27180;8370;33480;102;23.085544094;2021-12-07@07:45:47
+v2.0.8-163-ge813cd67;single;10;56234;;89496;27046;27188;8338;33352;105;23.766262802;2021-12-07@07:46:13
+v2.0.8-163-ge813cd67;single;10;56234;;89496;27046;26888;8316;33264;99;22.415114825;2021-12-07@07:46:37
+v2.0.8-163-ge813cd67;single;10;56234;;89496;27046;27184;8353;33412;104;23.534318273;2021-12-07@07:47:03
+v2.0.8-163-ge813cd67;single;10;56234;;89496;27046;27276;8349;33396;101;22.862244797;2021-12-07@07:47:28
+v2.0.8-163-ge813cd67;mulimex;10;56234;;89496;27046;159200;219579;878316;35;7.880039793;2021-12-07@07:47:47
+v2.0.8-163-ge813cd67;mulimex;10;56234;;89496;27046;158340;219577;878308;39;8.847610285;2021-12-07@07:47:58
+v2.0.8-163-ge813cd67;mulimex;10;56234;;89496;27046;159336;219258;877032;39;8.847539392;2021-12-07@07:48:09
+v2.0.8-163-ge813cd67;mulimex;10;56234;;89496;27046;158620;219218;876872;39;8.842679114;2021-12-07@07:48:19
+v2.0.8-163-ge813cd67;mulimex;10;56234;;89496;27046;158760;219148;876592;39;8.850127546;2021-12-07@07:48:30
+v2.0.8-163-ge813cd67;mulimex;10;56234;;89496;27046;159036;219249;876996;39;8.859018830;2021-12-07@07:48:41
+v2.0.8-163-ge813cd67;mulimex;10;56234;;89496;27046;158632;219554;878216;40;9.080298570;2021-12-07@07:48:52
+v2.0.8-163-ge813cd67;mulimex;10;56234;;89496;27046;159272;219188;876752;40;9.066372626;2021-12-07@07:49:03
+v2.0.8-163-ge813cd67;multi;18;56234;;97395;27048;180020;355792;1423168;45;10.252088614;2021-12-07@07:49:27
+v2.0.8-163-ge813cd67;multi;18;56234;;97395;27048;179936;355063;1420252;50;11.404137946;2021-12-07@07:49:41
+v2.0.8-163-ge813cd67;multi;18;56234;;97395;27048;179824;355089;1420356;50;11.401650838;2021-12-07@07:49:55
+v2.0.8-163-ge813cd67;multi;18;56234;;97395;27048;179456;355171;1420684;50;11.398196284;2021-12-07@07:50:08
+v2.0.8-163-ge813cd67;multi;18;56234;;97395;27048;180000;355172;1420688;50;11.412623440;2021-12-07@07:50:22
+v2.0.8-163-ge813cd67;multi;18;56234;;97395;27048;180060;355055;1420220;52;11.851081639;2021-12-07@07:50:36
+v2.0.8-163-ge813cd67;multi;18;56234;;97395;27048;179836;355133;1420532;50;11.389664351;2021-12-07@07:50:49
+v2.0.8-163-ge813cd67;multi;18;56234;;97395;27048;179984;355132;1420528;50;11.394968365;2021-12-07@07:51:02
+v2.0.8-163-ge813cd67;imex;18;56234;;97395;27048;118140;31722;126888;171;38.854328830;2021-12-07@07:51:55
+v2.0.8-163-ge813cd67;imex;18;56234;;97395;27048;118220;31706;126824;179;40.659294292;2021-12-07@07:52:38
+v2.0.8-163-ge813cd67;imex;18;56234;;97395;27048;118380;31713;126852;174;39.493578984;2021-12-07@07:53:19
+v2.0.8-163-ge813cd67;imex;18;56234;;97395;27048;118156;31700;126800;160;36.332082074;2021-12-07@07:53:58
+v2.0.8-163-ge813cd67;imex;18;56234;;97395;27048;118272;31718;126872;169;38.345543683;2021-12-07@07:54:39
+v2.0.8-163-ge813cd67;imex;18;56234;;97395;27048;118144;31699;126796;199;45.193881902;2021-12-07@07:55:26
+v2.0.8-163-ge813cd67;imex;18;56234;;97395;27048;118020;31693;126772;174;39.485973693;2021-12-07@07:56:07
+v2.0.8-163-ge813cd67;imex;18;56234;;97395;27048;118172;31714;126856;168;38.135301926;2021-12-07@07:56:47
+v2.0.8-163-ge813cd67;single;18;56234;;97395;27048;29640;9005;36020;170;38.655581121;2021-12-07@07:57:39
+v2.0.8-163-ge813cd67;single;18;56234;;97395;27048;29516;9008;36032;167;37.870671823;2021-12-07@07:58:19
+v2.0.8-163-ge813cd67;single;18;56234;;97395;27048;29532;9039;36156;171;38.780162600;2021-12-07@07:59:00
+v2.0.8-163-ge813cd67;single;18;56234;;97395;27048;29860;9012;36048;164;37.199583477;2021-12-07@07:59:39
+v2.0.8-163-ge813cd67;single;18;56234;;97395;27048;29868;8998;35992;166;37.707089883;2021-12-07@08:00:19
+v2.0.8-163-ge813cd67;single;18;56234;;97395;27048;29808;9031;36124;175;39.737176961;2021-12-07@08:01:01
+v2.0.8-163-ge813cd67;single;18;56234;;97395;27048;29848;9013;36052;169;38.326010963;2021-12-07@08:01:42
+v2.0.8-163-ge813cd67;single;18;56234;;97395;27048;29672;9030;36120;172;39.010369791;2021-12-07@08:02:23
+v2.0.8-163-ge813cd67;mulimex;18;56234;;97395;27048;248892;372880;1491520;51;11.584644703;2021-12-07@08:02:49
+v2.0.8-163-ge813cd67;mulimex;18;56234;;97395;27048;248688;372802;1491208;53;12.101671580;2021-12-07@08:03:03
+v2.0.8-163-ge813cd67;mulimex;18;56234;;97395;27048;248012;372784;1491136;53;12.091223498;2021-12-07@08:03:17
+v2.0.8-163-ge813cd67;mulimex;18;56234;;97395;27048;248136;372837;1491348;53;12.074483306;2021-12-07@08:03:31
+v2.0.8-163-ge813cd67;mulimex;18;56234;;97395;27048;248840;372806;1491224;54;12.316975303;2021-12-07@08:03:45
+v2.0.8-163-ge813cd67;mulimex;18;56234;;97395;27048;248908;372818;1491272;53;12.092308627;2021-12-07@08:03:59
+v2.0.8-163-ge813cd67;mulimex;18;56234;;97395;27048;247540;372767;1491068;54;12.316622179;2021-12-07@08:04:14
+v2.0.8-163-ge813cd67;mulimex;18;56234;;97395;27048;248624;372819;1491276;53;12.088563447;2021-12-07@08:04:28
+v2.0.8-163-ge813cd67;multi;32;56234;;102105;27070;288504;611952;2447808;76;17.420087629;2021-12-07@08:05:04
+v2.0.8-163-ge813cd67;multi;32;56234;;102105;27070;289068;611974;2447896;82;18.784716667;2021-12-07@08:05:26
+v2.0.8-163-ge813cd67;multi;32;56234;;102105;27070;288008;611915;2447660;82;18.757279283;2021-12-07@08:05:47
+v2.0.8-163-ge813cd67;multi;32;56234;;102105;27070;290032;612135;2448540;82;18.765192718;2021-12-07@08:06:07
+v2.0.8-163-ge813cd67;multi;32;56234;;102105;27070;289108;611925;2447700;83;19.006998407;2021-12-07@08:06:28
+v2.0.8-163-ge813cd67;multi;32;56234;;102105;27070;289168;612061;2448244;82;18.743200002;2021-12-07@08:06:49
+v2.0.8-163-ge813cd67;multi;32;56234;;102105;27070;289596;613163;2452652;82;18.786777734;2021-12-07@08:07:10
+v2.0.8-163-ge813cd67;multi;32;56234;;102105;27070;289352;611957;2447828;82;18.748140821;2021-12-07@08:07:31
+v2.0.8-163-ge813cd67;imex;32;56234;;102105;27070;165428;60299;241196;298;67.814870127;2021-12-07@08:09:03
+v2.0.8-163-ge813cd67;imex;32;56234;;102105;27070;165268;43900;175600;288;65.519232477;2021-12-07@08:10:10
+v2.0.8-163-ge813cd67;imex;32;56234;;102105;27070;165328;43940;175760;285;64.825333771;2021-12-07@08:11:17
+v2.0.8-163-ge813cd67;imex;32;56234;;102105;27070;165452;43944;175776;288;65.533378214;2021-12-07@08:12:24
+v2.0.8-163-ge813cd67;imex;32;56234;;102105;27070;165556;43928;175712;281;63.928891601;2021-12-07@08:13:30
+v2.0.8-163-ge813cd67;imex;32;56234;;102105;27070;165512;43906;175624;293;66.626425942;2021-12-07@08:14:39
+v2.0.8-163-ge813cd67;imex;32;56234;;102105;27070;165300;43910;175640;292;66.444412953;2021-12-07@08:15:47
+v2.0.8-163-ge813cd67;imex;32;56234;;102105;27070;165232;43895;175580;279;63.615950535;2021-12-07@08:16:53
+v2.0.8-162-ge38c4f44;single;32;56234;;102105;27070;32284;9704;38816;309;70.313180065;2021-12-07@08:18:25
+v2.0.8-162-ge38c4f44;single;32;56234;;102105;27070;32480;9695;38780;286;65.090268007;2021-12-07@08:19:31
+v2.0.8-162-ge38c4f44;single;32;56234;;102105;27070;32448;9709;38836;289;65.764155421;2021-12-07@08:20:39
+v2.0.8-162-ge38c4f44;single;32;56234;;102105;27070;32572;9717;38868;298;67.857454848;2021-12-07@08:21:49
+v2.0.8-162-ge38c4f44;single;32;56234;;102105;27070;32424;9707;38828;291;66.291291684;2021-12-07@08:22:57
+v2.0.8-162-ge38c4f44;single;32;56234;;102105;27070;32332;9710;38840;290;65.987048128;2021-12-07@08:24:05
+v2.0.8-162-ge38c4f44;single;32;56234;;102105;27070;32376;9711;38844;296;67.354610963;2021-12-07@08:25:14
+v2.0.8-162-ge38c4f44;single;32;56234;;102105;27070;32468;9705;38820;292;66.471219031;2021-12-07@08:26:23
+v2.0.8-162-ge38c4f44;mulimex;32;56234;;102105;27070;399988;640722;2562888;80;18.315275334;2021-12-07@08:27:05
+v2.0.8-162-ge38c4f44;mulimex;32;56234;;102105;27070;401032;640741;2562964;86;19.745304123;2021-12-07@08:27:27
+v2.0.8-162-ge38c4f44;mulimex;32;56234;;102105;27070;400200;640797;2563188;86;19.709803601;2021-12-07@08:27:49
+v2.0.8-162-ge38c4f44;mulimex;32;56234;;102105;27070;400900;640769;2563076;86;19.712992622;2021-12-07@08:28:10
+v2.0.8-162-ge38c4f44;mulimex;32;56234;;102105;27070;401784;641902;2567608;86;19.730745754;2021-12-07@08:28:32
+v2.0.8-162-ge38c4f44;mulimex;32;56234;;102105;27070;400576;640761;2563044;86;19.710070855;2021-12-07@08:28:53
+v2.0.8-162-ge38c4f44;mulimex;32;56234;;102105;27070;400468;641860;2567440;86;19.725508931;2021-12-07@08:29:15
+v2.0.8-162-ge38c4f44;mulimex;32;56234;;102105;27070;400156;640855;2563420;86;19.685491649;2021-12-07@08:29:36
+v2.0.8-162-ge38c4f44;multi;56;56234;;104619;27074;472304;1051438;4205752;128;29.336743114;2021-12-07@08:30:38
+v2.0.8-162-ge38c4f44;multi;56;56234;;104619;27074;473268;1051443;4205772;138;31.614622262;2021-12-07@08:31:11
+v2.0.8-162-ge38c4f44;multi;56;56234;;104619;27074;472792;1051883;4207532;138;31.621249639;2021-12-07@08:31:45
+v2.0.8-162-ge38c4f44;multi;56;56234;;104619;27074;473360;1051686;4206744;138;31.650159319;2021-12-07@08:32:18
+v2.0.8-162-ge38c4f44;multi;56;56234;;104619;27074;473688;1051629;4206516;140;32.054780508;2021-12-07@08:32:52
+v2.0.8-162-ge38c4f44;multi;56;56234;;104619;27074;472152;1051817;4207268;138;31.622437078;2021-12-07@08:33:26
+v2.0.8-162-ge38c4f44;multi;56;56234;;104619;27074;475672;1052130;4208520;138;31.608018473;2021-12-07@08:34:00
+v2.0.8-162-ge38c4f44;multi;56;56234;;104619;27074;472132;1053356;4213424;139;31.900961027;2021-12-07@08:34:34
+v2.0.8-162-ge38c4f44;imex;56;56234;;104619;27074;241460;63734;254936;517;118.660453291;2021-12-07@08:37:12
+v2.0.8-162-ge38c4f44;imex;56;56234;;104619;27074;241788;63721;254884;506;116.235448544;2021-12-07@08:39:10
+v2.0.8-162-ge38c4f44;imex;56;56234;;104619;27074;241892;63776;255104;506;116.242797190;2021-12-07@08:41:08
+v2.0.8-162-ge38c4f44;imex;56;56234;;104619;27074;241796;63694;254776;533;122.374036110;2021-12-07@08:43:13
+v2.0.8-162-ge38c4f44;imex;56;56234;;104619;27074;241788;63730;254920;514;118.073074039;2021-12-07@08:45:13
+v2.0.8-162-ge38c4f44;imex;56;56234;;104619;27074;241920;63719;254876;562;129.107215328;2021-12-07@08:47:24
+v2.0.8-162-ge38c4f44;imex;56;56234;;104619;27074;241844;63742;254968;511;117.165818186;2021-12-07@08:49:23
+v2.0.8-162-ge38c4f44;imex;56;56234;;104619;27074;241660;63742;254968;520;119.408303814;2021-12-07@08:51:25
+v2.0.8-162-ge38c4f44;single;56;56234;;104619;27074;35340;10420;41680;536;123.008833467;2021-12-07@08:54:05
+v2.0.8-162-ge38c4f44;single;56;56234;;104619;27074;35388;10398;41592;506;116.171004341;2021-12-07@08:56:03
+v2.0.8-162-ge38c4f44;single;56;56234;;104619;27074;35412;10415;41660;513;117.736765623;2021-12-07@08:58:03
+v2.0.8-162-ge38c4f44;single;56;56234;;104619;27074;35020;10396;41584;510;117.098314999;2021-12-07@09:00:02
+v2.0.8-162-ge38c4f44;single;56;56234;;104619;27074;35344;10414;41656;508;116.602622562;2021-12-07@09:02:01
+v2.0.8-162-ge38c4f44;single;56;56234;;104619;27074;35256;10407;41628;504;115.710585524;2021-12-07@09:03:59
+v2.0.8-162-ge38c4f44;single;56;56234;;104619;27074;35440;10387;41548;515;118.263812415;2021-12-07@09:05:58
+v2.0.8-162-ge38c4f44;single;56;56234;;104619;27074;35212;10398;41592;510;117.028292461;2021-12-07@09:07:57
+v2.0.8-162-ge38c4f44;mulimex;56;56234;;104619;27074;655972;1098948;4395792;142;32.684225987;2021-12-07@09:09:09
+v2.0.8-162-ge38c4f44;mulimex;56;56234;;104619;27074;655612;1098977;4395908;146;33.577192836;2021-12-07@09:09:44
+v2.0.8-162-ge38c4f44;mulimex;56;56234;;104619;27074;653968;1099129;4396516;147;33.828639319;2021-12-07@09:10:20
+v2.0.8-162-ge38c4f44;mulimex;56;56234;;104619;27074;655868;1099055;4396220;146;33.566925798;2021-12-07@09:10:55
+v2.0.8-162-ge38c4f44;mulimex;56;56234;;104619;27074;656528;1099089;4396356;149;34.258436479;2021-12-07@09:11:31
+v2.0.8-162-ge38c4f44;mulimex;56;56234;;104619;27074;656372;1098978;4395912;148;34.008267792;2021-12-07@09:12:07
+v2.0.8-162-ge38c4f44;mulimex;56;56234;;104619;27074;656588;1099016;4396064;147;33.823526772;2021-12-07@09:12:42
+v2.0.8-162-ge38c4f44;mulimex;56;56234;;104619;27074;656684;1098952;4395808;149;34.287582292;2021-12-07@09:13:18
+v2.0.8-162-ge38c4f44;multi;100;56234;;106396;27105;811812;1857150;7428600;239;54.898294672;2021-12-07@09:15:05
+v2.0.8-162-ge38c4f44;multi;100;56234;;106396;27105;811388;1857159;7428636;254;58.536591543;2021-12-07@09:16:06
+v2.0.8-162-ge38c4f44;multi;100;56234;;106396;27105;815456;1858215;7432860;260;59.849598349;2021-12-07@09:17:07
+v2.0.8-162-ge38c4f44;multi;100;56234;;106396;27105;815236;1858048;7432192;252;57.905388552;2021-12-07@09:18:06
+v2.0.8-162-ge38c4f44;multi;100;56234;;106396;27105;813824;1858011;7432044;256;58.755406119;2021-12-07@09:19:07
+v2.0.8-162-ge38c4f44;multi;100;56234;;106396;27105;816012;1858205;7432820;255;58.591220082;2021-12-07@09:20:07
+v2.0.8-162-ge38c4f44;multi;100;56234;;106396;27105;815116;1858126;7432504;247;56.701459625;2021-12-07@09:21:05
+v2.0.8-162-ge38c4f44;multi;100;56234;;106396;27105;815352;1858086;7432344;255;58.561547021;2021-12-07@09:22:06
+v2.0.8-162-ge38c4f44;imex;100;56234;;106396;27105;380556;99721;398884;961;220.884274678;2021-12-07@09:26:53
+v2.0.8-162-ge38c4f44;imex;100;56234;;106396;27105;380972;99845;399380;945;217.191170823;2021-12-07@09:30:33
+v2.0.8-162-ge38c4f44;imex;100;56234;;106396;27105;380196;99712;398848;959;220.711569329;2021-12-07@09:34:16
+v2.0.8-162-ge38c4f44;imex;100;56234;;106396;27105;380916;99833;399332;926;213.098811443;2021-12-07@09:37:50
+v2.0.8-162-ge38c4f44;imex;100;56234;;106396;27105;380296;99674;398696;878;202.018146974;2021-12-07@09:41:15
+v2.0.8-162-ge38c4f44;imex;100;56234;;106396;27105;380496;99755;399020;883;203.084252840;2021-12-07@09:44:40
+v2.0.8-162-ge38c4f44;imex;100;56234;;106396;27105;380480;99710;398840;885;203.519919601;2021-12-07@09:48:05
+v2.0.8-162-ge38c4f44;imex;100;56234;;106396;27105;380476;99770;399080;884;203.587421629;2021-12-07@09:51:31
+v2.0.8-162-ge38c4f44;single;100;56234;;106396;27105;39076;11373;45492;953;220.067987661;2021-12-07@09:56:16
+v2.0.8-162-ge38c4f44;single;100;56234;;106396;27105;39044;11381;45524;934;216.386786625;2021-12-07@09:59:55
+v2.0.8-162-ge38c4f44;single;100;56234;;106396;27105;39272;11404;45616;988;228.136814978;2021-12-07@10:03:45
+v2.0.8-162-ge38c4f44;single;100;56234;;106396;27105;39180;11403;45612;936;216.213162155;2021-12-07@10:07:24
+v2.0.8-162-ge38c4f44;single;100;56234;;106396;27105;39288;11402;45608;973;225.237349413;2021-12-07@10:11:11
+v2.0.8-162-ge38c4f44;single;100;56234;;106396;27105;39356;11382;45528;1099;254.545626069;2021-12-07@10:15:28
+v2.0.8-162-ge38c4f44;single;100;56234;;106396;27105;39172;11398;45592;987;228.431737878;2021-12-07@10:19:18
+v2.0.8-162-ge38c4f44;single;100;56234;;106396;27105;39092;11382;45528;1012;234.441023614;2021-12-07@10:23:15
+v2.0.8-162-ge38c4f44;mulimex;100;56234;;106396;27105;1126108;1939306;7757224;263;60.607039104;2021-12-07@10:25:20
+v2.0.8-162-ge38c4f44;mulimex;100;56234;;106396;27105;1125028;1939386;7757544;271;62.383007883;2021-12-07@10:26:24
+v2.0.8-162-ge38c4f44;mulimex;100;56234;;106396;27105;1127300;1939684;7758736;270;62.210629062;2021-12-07@10:27:29
+v2.0.8-162-ge38c4f44;mulimex;100;56234;;106396;27105;1126772;1939524;7758096;269;61.916333402;2021-12-07@10:28:33
+v2.0.8-162-ge38c4f44;mulimex;100;56234;;106396;27105;1126724;1939454;7757816;273;62.782615139;2021-12-07@10:29:38
+v2.0.8-162-ge38c4f44;mulimex;100;56234;;106396;27105;1126352;1939422;7757688;269;62.016610175;2021-12-07@10:30:43
+v2.0.8-162-ge38c4f44;mulimex;100;56234;;106396;27105;1127524;1939638;7758552;270;62.140153398;2021-12-07@10:31:47
+v2.0.8-162-ge38c4f44;mulimex;100;56234;;106396;27105;1125708;1939467;7757868;271;62.329646487;2021-12-07@10:32:52
+v2.0.8-162-ge38c4f44;multi;178;56234;;107469;27173;1412368;2434437;9737748;452;105.074120590;2021-12-07@10:36:13
+v2.0.8-162-ge38c4f44;multi;178;56234;;107469;27173;1415832;2435204;9740816;467;108.303104105;2021-12-07@10:38:03
+v2.0.8-162-ge38c4f44;multi;178;56234;;107469;27173;1408988;2433879;9735516;463;107.237797066;2021-12-07@10:39:53
+v2.0.8-162-ge38c4f44;multi;178;56234;;107469;27173;1407728;2433286;9733144;464;107.756021722;2021-12-07@10:41:43
+v2.0.8-162-ge38c4f44;multi;178;56234;;107469;27173;1415320;2435237;9740948;462;106.905256557;2021-12-07@10:43:32
+v2.0.8-162-ge38c4f44;multi;178;56234;;107469;27173;1415976;2435191;9740764;451;104.395056517;2021-12-07@10:45:18
+v2.0.8-162-ge38c4f44;multi;178;56234;;107469;27173;1411576;2434361;9737444;469;108.176752682;2021-12-07@10:47:09
+v2.0.8-162-ge38c4f44;multi;178;56234;;107469;27173;1410640;2433821;9735284;448;103.802910023;2021-12-07@10:48:55
+v2.0.8-162-ge38c4f44;imex;178;56234;;107469;27173;626100;163488;653952;1741;401.900251645;2021-12-07@10:57:30
+v2.0.8-162-ge38c4f44;imex;178;56234;;107469;27173;627192;163714;654856;1668;386.070966366;2021-12-07@11:03:59
+v2.0.8-162-ge38c4f44;imex;178;56234;;107469;27173;626188;163543;654172;1717;396.937292355;2021-12-07@11:10:37
+v2.0.8-162-ge38c4f44;imex;178;56234;;107469;27173;627636;163849;655396;1680;388.250700679;2021-12-07@11:17:07
+v2.0.8-162-ge38c4f44;imex;178;56234;;107469;27173;626516;163584;654336;1732;400.114119250;2021-12-07@11:23:50
+v2.0.8-162-ge38c4f44;imex;178;56234;;107469;27173;624596;163102;652408;1657;382.726705351;2021-12-07@11:30:14
+v2.0.8-162-ge38c4f44;imex;178;56234;;107469;27173;625544;163339;653356;1691;390.508581231;2021-12-07@11:36:46
+v2.0.8-162-ge38c4f44;imex;178;56234;;107469;27173;625824;163424;653696;1764;406.973139329;2021-12-07@11:43:35
+v2.0.8-162-ge38c4f44;single;178;56234;;107469;27173;44764;12815;51260;1841;425.529791376;2021-12-07@11:52:32
+v2.0.8-162-ge38c4f44;single;178;56234;;107469;27173;45216;12848;51392;1623;375.002273344;2021-12-07@11:58:48
+v2.0.8-162-ge38c4f44;single;178;56234;;107469;27173;45104;12831;51324;1764;408.716158792;2021-12-07@12:05:39
+v2.0.8-162-ge38c4f44;single;178;56234;;107469;27173;45132;12832;51328;1731;400.106162572;2021-12-07@12:12:20
+v2.0.8-162-ge38c4f44;single;178;56234;;107469;27173;44740;12809;51236;1703;393.154512921;2021-12-07@12:18:55
+v2.0.8-162-ge38c4f44;single;178;56234;;107469;27173;44968;12843;51372;1622;374.993661126;2021-12-07@12:25:12
+v2.0.8-162-ge38c4f44;single;178;56234;;107469;27173;44808;12812;51248;1679;387.739081506;2021-12-07@12:31:41
+v2.0.8-162-ge38c4f44;single;178;56234;;107469;27173;45140;12834;51336;1608;372.600897973;2021-12-07@12:37:55
+v2.0.8-163-g542f2455;mulimex;178;56234;;107469;27173;1958068;2577134;10308536;464;107.705177588;2021-12-07@12:41:37
+v2.0.8-163-g542f2455;mulimex;178;56234;;107469;27173;1966572;2578759;10315036;468;107.885290589;2021-12-07@12:43:41
+v2.0.8-163-g542f2455;mulimex;178;56234;;107469;27173;1962524;2578133;10312532;462;107.137076114;2021-12-07@12:45:45
+v2.0.8-163-g542f2455;mulimex;178;56234;;107469;27173;1962600;2578118;10312472;462;107.717835010;2021-12-07@12:47:49
+v2.0.8-163-g542f2455;mulimex;178;56234;;107469;27173;1964796;2578613;10314452;471;108.260140050;2021-12-07@12:49:54
+v2.0.8-163-g542f2455;mulimex;178;56234;;107469;27173;1963108;2578321;10313284;468;108.541680736;2021-12-07@12:51:59
+v2.0.8-163-g542f2455;mulimex;178;56234;;107469;27173;1967808;2578469;10313876;468;108.124198194;2021-12-07@12:54:04
+v2.0.8-163-g542f2455;mulimex;178;56234;;107469;27173;1960704;2577558;10310232;461;107.053603216;2021-12-07@12:56:08
+v2.0.8-163-g542f2455;multi;316;56234;;108134;27288;2467048;2700998;10803992;797;186.242743507;2021-12-07@13:02:31
+v2.0.8-163-g542f2455;multi;316;56234;;108134;27288;2470612;2702008;10808032;807;188.098883875;2021-12-07@13:05:41
+v2.0.8-163-g542f2455;multi;316;56234;;108134;27288;2472368;2702141;10808564;805;187.476945648;2021-12-07@13:08:53
+v2.0.8-163-g542f2455;multi;316;56234;;108134;27288;2470252;2701710;10806840;870;199.900663013;2021-12-07@13:12:25
+v2.0.8-163-g542f2455;multi;316;56234;;108134;27288;2460868;2700067;10800268;1481;352.265841873;2021-12-07@13:22:30
+v2.0.8-163-g542f2455;multi;316;56234;;108134;27288;2477616;2703931;10815724;865;197.814149617;2021-12-07@13:25:50
+v2.0.8-163-g542f2455;multi;316;56234;;108134;27288;2474776;2703214;10812856;854;196.335411369;2021-12-07@13:29:10
+v2.0.8-163-g542f2455;multi;316;56234;;108134;27288;2477244;2703571;10814284;854;196.148596118;2021-12-07@13:32:30
+v2.0.8-163-g542f2455;imex;316;56234;;108134;27288;1061240;276415;1105660;2920;675.382400523;2021-12-07@13:46:56
+v2.0.8-163-g542f2455;imex;316;56234;;108134;27288;1062464;276715;1106860;2961;687.321316322;2021-12-07@13:58:40
+v2.0.8-163-g542f2455;imex;316;56234;;108134;27288;1059872;276093;1104372;2953;683.802078276;2021-12-07@14:10:21
+v2.0.8-163-g542f2455;imex;316;56234;;108134;27288;1060332;276189;1104756;2977;692.262919920;2021-12-07@14:21:55
+v2.0.8-163-g542f2455;imex;316;56234;;108134;27288;1059680;275992;1103968;2775;644.806413846;2021-12-07@14:32:41
+v2.0.8-163-g542f2455;imex;316;56234;;108134;27288;1063264;276848;1107392;2850;663.890224686;2021-12-07@14:43:47
+v2.0.8-163-g542f2455;imex;316;56234;;108134;27288;1062584;276719;1106876;2987;700.474717231;2021-12-07@14:55:29
+v2.0.8-163-g542f2455;imex;316;56234;;108134;27288;1059304;275920;1103680;3107;721.580970601;2021-12-07@15:07:32
+v2.0.8-164-gf9e098c9;single;316;56234;;108134;27288;53612;14987;59948;3183;738.604693202;2021-12-07@15:22:59
+v2.0.8-164-gf9e098c9;single;316;56234;;108134;27288;53816;15048;60192;3110;722.117562203;2021-12-07@15:35:03
+v2.0.8-164-gf9e098c9;single;316;56234;;108134;27288;53888;15090;60360;3088;715.782867904;2021-12-07@15:47:01
+v2.0.8-164-gf9e098c9;single;316;56234;;108134;27288;53684;14999;59996;3182;739.619192441;2021-12-07@15:59:22
+v2.0.8-164-gf9e098c9;single;316;56234;;108134;27288;52940;14884;59536;3155;732.560875442;2021-12-07@16:11:36
+v2.0.8-164-gf9e098c9;single;316;56234;;108134;27288;53484;14970;59880;3177;734.713577511;2021-12-07@16:23:53
+v2.0.8-164-gf9e098c9;single;316;56234;;108134;27288;53624;14993;59972;3197;742.265836951;2021-12-07@16:36:17
+v2.0.8-164-gf9e098c9;single;316;56234;;108134;27288;53708;14985;59940;3121;724.805608268;2021-12-07@16:48:24
+v2.0.8-164-gf9e098c9;mulimex;316;56234;;108134;27288;3441604;2954812;11819248;842;196.066017728;2021-12-07@16:55:02
+v2.0.8-164-gf9e098c9;mulimex;316;56234;;108134;27288;3455192;2958203;11832812;858;198.019355689;2021-12-07@16:58:23
+v2.0.8-164-gf9e098c9;mulimex;316;56234;;108134;27288;3449340;2955449;11821796;848;198.250545793;2021-12-07@17:01:44
+v2.0.8-164-gf9e098c9;mulimex;316;56234;;108134;27288;3441232;2954377;11817508;851;198.632521435;2021-12-07@17:05:05
+v2.0.8-164-gf9e098c9;mulimex;316;56234;;108134;27288;3436228;2953826;11815304;843;196.740888733;2021-12-07@17:08:24
+v2.0.8-164-gf9e098c9;mulimex;316;56234;;108134;27288;3441636;2954579;11818316;858;198.089961540;2021-12-07@17:11:45
+v2.0.8-164-gf9e098c9;mulimex;316;56234;;108134;27288;3455548;2957054;11828216;882;202.311464004;2021-12-07@17:15:10
+v2.0.8-164-gf9e098c9;mulimex;316;56234;;108134;27288;3441240;2954245;11816980;854;197.640247363;2021-12-07@17:18:31
+v2.0.8-164-gf9e098c9;multi;10;100000;;126751;52385;170308;220940;883760;43;10.211327539;2021-12-07@17:20:05
+v2.0.8-164-gf9e098c9;multi;10;100000;;126751;52385;170292;220949;883796;48;11.308742392;2021-12-07@17:20:18
+v2.0.8-164-gf9e098c9;multi;10;100000;;126751;52385;170792;220967;883868;49;11.507060210;2021-12-07@17:20:32
+v2.0.8-164-gf9e098c9;multi;10;100000;;126751;52385;170224;220977;883908;48;11.295528774;2021-12-07@17:20:45
+v2.0.8-164-gf9e098c9;multi;10;100000;;126751;52385;170200;220955;883820;48;11.298329429;2021-12-07@17:20:59
+v2.0.8-164-gf9e098c9;multi;10;100000;;126751;52385;170944;220957;883828;49;11.578842286;2021-12-07@17:21:12
+v2.0.8-164-gf9e098c9;multi;10;100000;;126751;52385;170532;220930;883720;49;11.571323611;2021-12-07@17:21:26
+v2.0.8-164-gf9e098c9;multi;10;100000;;126751;52385;170440;221038;884152;48;11.306314950;2021-12-07@17:21:40
+v2.0.8;multi;10;10000;;12925;6245;25444;7867;31468;34;7.380314168;2021-12-07@17:23:59
+v2.0.8;multi;10;10000;;12925;6245;25580;7913;31652;36;7.824798988;2021-12-07@17:24:09
+v2.0.8;multi;10;10000;;12925;6245;25632;7929;31716;37;8.046475550;2021-12-07@17:24:19
+v2.0.8;multi;10;10000;;12925;6245;25648;7940;31760;36;7.832148685;2021-12-07@17:24:30
+v2.0.8;multi;10;10000;;12925;6245;25468;7925;31700;36;7.822049309;2021-12-07@17:24:39
+v2.0.8;multi;10;10000;;12925;6245;25604;7916;31664;36;7.827260141;2021-12-07@17:24:49
+v2.0.8;multi;10;10000;;12925;6245;25480;7923;31692;37;8.038343117;2021-12-07@17:24:59
+v2.0.8;multi;10;10000;;12925;6245;25524;7921;31684;36;7.826981949;2021-12-07@17:25:09
+v2.0.8;imex;10;10000;;12925;6245;21536;6929;27716;26;5.636593475;2021-12-07@17:25:24
+v2.0.8;imex;10;10000;;12925;6245;21332;6929;27716;28;6.082880960;2021-12-07@17:25:32
+v2.0.8;imex;10;10000;;12925;6245;21516;6949;27796;28;6.087306502;2021-12-07@17:25:40
+v2.0.8;imex;10;10000;;12925;6245;21476;6948;27792;27;5.862203072;2021-12-07@17:25:48
+v2.0.8;imex;10;10000;;12925;6245;21844;6948;27792;28;6.077918495;2021-12-07@17:25:56
+v2.0.8;imex;10;10000;;12925;6245;21688;6929;27716;28;6.075490031;2021-12-07@17:26:04
+v2.0.8;imex;10;10000;;12925;6245;21416;6948;27792;28;6.079226967;2021-12-07@17:26:12
+v2.0.8;imex;10;10000;;12925;6245;21528;6942;27768;27;5.861203295;2021-12-07@17:26:20
+v2.0.8;single;10;10000;;12925;6245;;8332;3571;14284;27;5.834713449;2021-12-07@17:26:35
+v2.0.8;single;10;10000;;12925;6245;;8268;3602;14408;28;6.076678459;2021-12-07@17:26:43
+v2.0.8;single;10;10000;;12925;6245;;8280;3606;14424;29;6.295831071;2021-12-07@17:26:51
+v2.0.8;single;10;10000;;12925;6245;;8248;3610;14440;28;6.078340521;2021-12-07@17:26:59
+v2.0.8;single;10;10000;;12925;6245;;8488;3603;14412;29;6.298977355;2021-12-07@17:27:07
+v2.0.8;single;10;10000;;12925;6245;;8608;3627;14508;28;6.077737605;2021-12-07@17:27:15
+v2.0.8;single;10;10000;;12925;6245;;8172;3616;14464;28;6.082737448;2021-12-07@17:27:24
+v2.0.8;single;10;10000;;12925;6245;;8080;3611;14444;28;6.076552437;2021-12-07@17:27:32
+v2.0.8;mulimex;10;10000;;12925;6245;34896;10274;41096;34;7.375614892;2021-12-07@17:27:48
+v2.0.8;mulimex;10;10000;;12925;6245;35116;10319;41276;36;7.823989090;2021-12-07@17:27:58
+v2.0.8;mulimex;10;10000;;12925;6245;34828;10280;41120;36;7.824985758;2021-12-07@17:28:07
+v2.0.8;mulimex;10;10000;;12925;6245;35012;10264;41056;36;7.826464961;2021-12-07@17:28:17
+v2.0.8;mulimex;10;10000;;12925;6245;35172;10318;41272;37;8.047223876;2021-12-07@17:28:27
+v2.0.8;mulimex;10;10000;;12925;6245;34900;10281;41124;36;7.827521230;2021-12-07@17:28:38
+v2.0.8;mulimex;10;10000;;12925;6245;35060;10311;41244;36;7.829890366;2021-12-07@17:28:48
+v2.0.8;mulimex;10;10000;;12925;6245;34824;10284;41136;37;8.047637860;2021-12-07@17:28:58
+v2.0.8;multi;18;10000;;15314;6305;40776;11686;46744;62;13.504565080;2021-12-07@17:29:23
+v2.0.8;multi;18;10000;;15314;6305;40652;11669;46676;68;14.824318024;2021-12-07@17:29:40
+v2.0.8;multi;18;10000;;15314;6305;40536;11675;46700;69;15.039503816;2021-12-07@17:29:57
+v2.0.8;multi;18;10000;;15314;6305;40684;11651;46604;69;15.031619656;2021-12-07@17:30:14
+v2.0.8;multi;18;10000;;15314;6305;40720;11666;46664;68;14.813683180;2021-12-07@17:30:31
+v2.0.8;multi;18;10000;;15314;6305;40604;11669;46676;68;14.819970770;2021-12-07@17:30:48
+v2.0.8;multi;18;10000;;15314;6305;40572;11705;46820;68;14.827298019;2021-12-07@17:31:05
+v2.0.8;multi;18;10000;;15314;6305;40792;11671;46684;68;14.818596799;2021-12-07@17:31:21
+v2.0.8;imex;18;10000;;15314;6305;30360;9066;36264;41;8.899544407;2021-12-07@17:31:44
+v2.0.8;imex;18;10000;;15314;6305;29852;9072;36288;46;10.014288271;2021-12-07@17:31:56
+v2.0.8;imex;18;10000;;15314;6305;29916;9050;36200;45;9.795407102;2021-12-07@17:32:08
+v2.0.8;imex;18;10000;;15314;6305;29844;9045;36180;53;11.542368727;2021-12-07@17:32:22
+v2.0.8;imex;18;10000;;15314;6305;29852;9064;36256;44;9.577087526;2021-12-07@17:32:33
+v2.0.8;imex;18;10000;;15314;6305;29820;9076;36304;45;9.798265252;2021-12-07@17:32:45
+v2.0.8;imex;18;10000;;15314;6305;30148;9060;36240;44;9.575457314;2021-12-07@17:32:57
+v2.0.8;imex;18;10000;;15314;6305;30036;9066;36264;44;9.586265262;2021-12-07@17:33:08
+v2.0.8;single;18;10000;;15314;6305;;9212;3896;15584;45;9.787583814;2021-12-07@17:33:31
+v2.0.8;single;18;10000;;15314;6305;;9520;3946;15784;45;9.805247929;2021-12-07@17:33:43
+v2.0.8;single;18;10000;;15314;6305;;9568;3949;15796;45;9.798459050;2021-12-07@17:33:55
+v2.0.8;single;18;10000;;15314;6305;;9532;3955;15820;45;9.796275112;2021-12-07@17:34:06
+v2.0.8;single;18;10000;;15314;6305;;9588;3952;15808;45;9.796876411;2021-12-07@17:34:18
+v2.0.8;single;18;10000;;15314;6305;;9580;3929;15716;47;10.237484116;2021-12-07@17:34:30
+v2.0.8;single;18;10000;;15314;6305;;9796;3949;15796;46;10.021432268;2021-12-07@17:34:42
+v2.0.8;single;18;10000;;15314;6305;;9576;3965;15860;47;10.236391708;2021-12-07@17:34:55
+v2.0.8;mulimex;18;10000;;15314;6305;56628;15661;62644;62;13.504603988;2021-12-07@17:35:22
+v2.0.8;mulimex;18;10000;;15314;6305;56464;15687;62748;69;15.044574303;2021-12-07@17:35:38
+v2.0.8;mulimex;18;10000;;15314;6305;56692;15704;62816;69;15.045876645;2021-12-07@17:35:55
+v2.0.8;mulimex;18;10000;;15314;6305;56540;15693;62772;68;14.828177143;2021-12-07@17:36:12
+v2.0.8;mulimex;18;10000;;15314;6305;56720;15711;62844;69;15.049092830;2021-12-07@17:36:29
+v2.0.8;mulimex;18;10000;;15314;6305;56704;15682;62728;68;14.829925213;2021-12-07@17:36:46
+v2.0.8;mulimex;18;10000;;15314;6305;56632;15685;62740;69;15.041653552;2021-12-07@17:37:03
+v2.0.8;mulimex;18;10000;;15314;6305;56640;15690;62760;69;15.043299739;2021-12-07@17:37:20
+v2.0.8;multi;32;10000;;16892;6334;65688;18025;72100;120;26.180747823;2021-12-07@17:38:04
+v2.0.8;multi;32;10000;;16892;6334;65708;18023;72092;126;27.504178252;2021-12-07@17:38:33
+v2.0.8;multi;32;10000;;16892;6334;66120;18032;72128;127;27.724581477;2021-12-07@17:39:03
+v2.0.8;multi;32;10000;;16892;6334;65772;18016;72064;128;27.945166100;2021-12-07@17:39:33
+v2.0.8;multi;32;10000;;16892;6334;65960;18025;72100;127;27.691722123;2021-12-07@17:40:02
+v2.0.8;multi;32;10000;;16892;6334;65896;18022;72088;127;27.724702119;2021-12-07@17:40:32
+v2.0.8;multi;32;10000;;16892;6334;65876;18020;72080;127;27.721962977;2021-12-07@17:41:02
+v2.0.8;multi;32;10000;;16892;6334;65960;17997;71988;132;28.798228462;2021-12-07@17:41:32
+v2.0.8;imex;32;10000;;16892;6334;42900;12250;49000;71;15.460228949;2021-12-07@17:42:10
+v2.0.8;imex;32;10000;;16892;6334;42992;12277;49108;75;16.343240470;2021-12-07@17:42:28
+v2.0.8;imex;32;10000;;16892;6334;42632;12259;49036;73;15.915448156;2021-12-07@17:42:45
+v2.0.8;imex;32;10000;;16892;6334;42736;12264;49056;79;17.229714688;2021-12-07@17:43:05
+v2.0.8;imex;32;10000;;16892;6334;42892;12289;49156;74;16.132911122;2021-12-07@17:43:23
+v2.0.8;imex;32;10000;;16892;6334;42924;12270;49080;74;16.146408568;2021-12-07@17:43:41
+v2.0.8;imex;32;10000;;16892;6334;42740;12269;49076;73;15.900375136;2021-12-07@17:43:59
+v2.0.8;imex;32;10000;;16892;6334;42796;12260;49040;77;16.775149691;2021-12-07@17:44:18
+v2.0.8;single;32;10000;;16892;6334;10964;4276;17104;74;16.109957913;2021-12-07@17:44:56
+v2.0.8;single;32;10000;;16892;6334;11056;4247;16988;80;17.443506335;2021-12-07@17:45:15
+v2.0.8;single;32;10000;;16892;6334;10828;4272;17088;78;17.011778950;2021-12-07@17:45:35
+v2.0.8;single;32;10000;;16892;6334;10776;4262;17048;75;16.352763608;2021-12-07@17:45:53
+v2.0.8;single;32;10000;;16892;6334;10912;4264;17056;75;16.360266090;2021-12-07@17:46:11
+v2.0.8;single;32;10000;;16892;6334;11036;4268;17072;76;16.572461117;2021-12-07@17:46:30
+v2.0.8;single;32;10000;;16892;6334;10916;4253;17012;76;16.576309170;2021-12-07@17:46:49
+v2.0.8;single;32;10000;;16892;6334;10956;4292;17168;76;16.567794493;2021-12-07@17:47:08
+v2.0.8;mulimex;32;10000;;16892;6334;92792;24750;99000;122;26.624865322;2021-12-07@17:47:56
+v2.0.8;mulimex;32;10000;;16892;6334;92832;24742;98968;127;27.752408830;2021-12-07@17:48:26
+v2.0.8;mulimex;32;10000;;16892;6334;92836;24742;98968;127;27.669849538;2021-12-07@17:48:56
+v2.0.8;mulimex;32;10000;;16892;6334;92840;24769;99076;127;27.736230391;2021-12-07@17:49:26
+v2.0.8;mulimex;32;10000;;16892;6334;93364;24845;99380;127;27.730842065;2021-12-07@17:49:55
+v2.0.8;mulimex;32;10000;;16892;6334;92756;24745;98980;126;27.500146233;2021-12-07@17:50:25
+v2.0.8;mulimex;32;10000;;16892;6334;92752;24725;98900;127;27.748070578;2021-12-07@17:50:55
+v2.0.8;mulimex;32;10000;;16892;6334;92896;24759;99036;127;27.751678200;2021-12-07@17:51:24
+v2.0.8;multi;56;10000;;17919;6345;108424;28620;114480;222;48.428642973;2021-12-07@17:52:43
+v2.0.8;multi;56;10000;;17919;6345;115212;30384;121536;230;50.093778564;2021-12-07@17:53:35
+v2.0.8;multi;56;10000;;17919;6345;116512;30660;122640;229;49.944954914;2021-12-07@17:54:26
+v2.0.8;multi;56;10000;;17919;6345;118292;31090;124360;230;50.010190612;2021-12-07@17:55:20
+v2.0.8;multi;56;10000;;17919;6345;115404;30413;121652;229;50.077709971;2021-12-07@17:56:13
+v2.0.8;multi;56;10000;;17919;6345;122112;32020;128080;231;50.186404896;2021-12-07@17:57:05
+v2.0.8;multi;56;10000;;17919;6345;111028;29302;117208;229;49.958732992;2021-12-07@17:57:57
+v2.0.8;multi;56;10000;;17919;6345;116172;30616;122464;230;50.011137354;2021-12-07@17:58:49
+v2.0.8;imex;56;10000;;17919;6345;63352;17388;69552;123;26.857051411;2021-12-07@17:59:53
+v2.0.8;imex;56;10000;;17919;6345;63264;17387;69548;125;27.310443534;2021-12-07@18:00:22
+v2.0.8;imex;56;10000;;17919;6345;63308;17368;69472;124;27.085014487;2021-12-07@18:00:52
+v2.0.8;imex;56;10000;;17919;6345;63232;17376;69504;124;27.078020642;2021-12-07@18:01:20
+v2.0.8;imex;56;10000;;17919;6345;63080;17351;69404;124;27.093878431;2021-12-07@18:01:50
+v2.0.8;imex;56;10000;;17919;6345;63208;17360;69440;123;26.864556296;2021-12-07@18:02:19
+v2.0.8;imex;56;10000;;17919;6345;63272;17378;69512;124;27.080865302;2021-12-07@18:02:48
+v2.0.8;imex;56;10000;;17919;6345;63292;17401;69604;127;27.746670444;2021-12-07@18:03:17
+v2.0.8;single;56;10000;;17919;6345;12728;4720;18880;127;27.688249077;2021-12-07@18:04:23
+v2.0.8;single;56;10000;;17919;6345;12528;4671;18684;129;28.168592390;2021-12-07@18:04:53
+v2.0.8;single;56;10000;;17919;6345;12460;4667;18668;132;28.816726827;2021-12-07@18:05:24
+v2.0.8;single;56;10000;;17919;6345;13100;4730;18920;131;28.612050555;2021-12-07@18:05:55
+v2.0.8;single;56;10000;;17919;6345;12676;4734;18936;129;28.179133677;2021-12-07@18:06:25
+v2.0.8;single;56;10000;;17919;6345;12628;4677;18708;131;28.600839303;2021-12-07@18:06:56
+v2.0.8;single;56;10000;;17919;6345;12860;4724;18896;130;28.390482520;2021-12-07@18:07:27
+v2.0.8;single;56;10000;;17919;6345;12608;4682;18728;130;28.383924062;2021-12-07@18:07:58
+v2.0.8;mulimex;56;10000;;17919;6345;153720;39990;159960;222;48.558125010;2021-12-07@18:09:24
+v2.0.8;mulimex;56;10000;;17919;6345;173452;44911;179644;229;49.819159265;2021-12-07@18:10:17
+v2.0.8;mulimex;56;10000;;17919;6345;161572;41947;167788;229;50.037428440;2021-12-07@18:11:09
+v2.0.8;mulimex;56;10000;;17919;6345;162396;42118;168472;228;50.001794469;2021-12-07@18:12:01
+v2.0.8;mulimex;56;10000;;17919;6345;164284;42560;170240;229;49.904116030;2021-12-07@18:12:53
+v2.0.8;mulimex;56;10000;;17919;6345;165128;42803;171212;230;50.055860076;2021-12-07@18:13:45
+v2.0.8;mulimex;56;10000;;17919;6345;162508;42198;168792;229;49.858875896;2021-12-07@18:14:37
+v2.0.8;mulimex;56;10000;;17919;6345;163648;42476;169904;229;49.865987762;2021-12-07@18:15:29
+v2.0.8;multi;100;10000;;18563;6381;185588;47943;191772;407;89.336977668;2021-12-07@18:17:51
+v2.0.8;multi;100;10000;;18563;6381;185588;47911;191644;413;90.664938942;2021-12-07@18:19:24
+v2.0.8;multi;100;10000;;18563;6381;185424;47920;191680;408;89.688455243;2021-12-07@18:21:10
+v2.0.8;multi;100;10000;;18563;6381;185604;47932;191728;412;90.489482553;2021-12-07@18:22:44
+v2.0.8;multi;100;10000;;18563;6381;185740;47923;191692;412;90.682941706;2021-12-07@18:24:17
+v2.0.8;multi;100;10000;;18563;6381;185560;47922;191688;412;90.483333839;2021-12-07@18:25:50
+v2.0.8;multi;100;10000;;18563;6381;185712;47937;191748;413;90.720244470;2021-12-07@18:27:23
+v2.0.8;multi;100;10000;;18563;6381;185464;47917;191668;413;90.673136813;2021-12-07@18:28:56
+v2.0.8;imex;100;10000;;18563;6381;98996;26295;105180;228;49.772746849;2021-12-07@18:30:50
+v2.0.8;imex;100;10000;;18563;6381;99248;26325;105300;220;48.086806626;2021-12-07@18:31:41
+v2.0.8;imex;100;10000;;18563;6381;99472;26428;105712;225;49.166994106;2021-12-07@18:32:32
+v2.0.8;imex;100;10000;;18563;6381;99112;26308;105232;225;49.193044838;2021-12-07@18:33:24
+v2.0.8;imex;100;10000;;18563;6381;99320;26431;105724;218;48.375874157;2021-12-07@18:34:14
+v2.0.8;imex;100;10000;;18563;6381;99048;26358;105432;208;48.717563830;2021-12-07@18:35:06
+v2.0.8;imex;100;10000;;18563;6381;99212;26333;105332;220;48.239671818;2021-12-07@18:35:56
+v2.0.8;imex;100;10000;;18563;6381;98996;26308;105232;224;49.011486298;2021-12-07@18:36:47
+v2.0.8;single;100;10000;;18563;6381;14668;5186;20744;236;51.517662396;2021-12-07@18:38:41
+v2.0.8;single;100;10000;;18563;6381;14860;5231;20924;230;50.230503198;2021-12-07@18:39:33
+v2.0.8;single;100;10000;;18563;6381;14720;5196;20784;232;50.693102460;2021-12-07@18:40:26
+v2.0.8;single;100;10000;;18563;6381;14436;5152;20608;232;50.672487282;2021-12-07@18:41:18
+v2.0.8;single;100;10000;;18563;6381;14724;5223;20892;230;50.228859148;2021-12-07@18:42:11
+v2.0.8;single;100;10000;;18563;6381;14828;5214;20856;227;49.603268658;2021-12-07@18:43:02
+v2.0.8;single;100;10000;;18563;6381;14240;5155;20620;231;50.441219511;2021-12-07@18:43:55
+v2.0.8;single;100;10000;;18563;6381;14660;5165;20660;231;50.458875466;2021-12-07@18:44:47
+v2.0.8;mulimex;100;10000;;18563;6381;264512;67636;270544;408;89.573877427;2021-12-07@18:47:19
+v2.0.8;mulimex;100;10000;;18563;6381;264788;67650;270600;413;90.670580332;2021-12-07@18:48:52
+v2.0.8;mulimex;100;10000;;18563;6381;264352;67649;270596;415;91.098431472;2021-12-07@18:50:25
+v2.0.8;mulimex;100;10000;;18563;6381;264312;67631;270524;414;90.901171045;2021-12-07@18:51:58
+v2.0.8;mulimex;100;10000;;18563;6381;264372;67615;270460;415;91.116916145;2021-12-07@18:53:33
+v2.0.8;mulimex;100;10000;;18563;6381;264688;67689;270756;415;91.105156060;2021-12-07@18:55:07
+v2.0.8;mulimex;100;10000;;18563;6381;264284;67647;270588;415;91.104647172;2021-12-07@18:56:42
+v2.0.8;mulimex;100;10000;;18563;6381;264340;67658;270632;415;91.050613393;2021-12-07@18:58:15
+v2.0.8;multi;178;10000;;19033;6531;323688;82470;329880;737;162.869679370;2021-12-07@19:02:32
+v2.0.8;multi;178;10000;;19033;6531;323716;82499;329996;749;165.194929662;2021-12-07@19:05:19
+v2.0.8;multi;178;10000;;19033;6531;323924;82481;329924;747;164.678245748;2021-12-07@19:08:07
+v2.0.8;multi;178;10000;;19033;6531;323808;82478;329912;746;164.419681992;2021-12-07@19:10:54
+v2.0.8;multi;178;10000;;19033;6531;323624;82452;329808;747;164.624263970;2021-12-07@19:13:41
+v2.0.8;multi;178;10000;;19033;6531;323784;82445;329780;759;167.370843808;2021-12-07@19:16:31
+v2.0.8;multi;178;10000;;19033;6531;323920;82453;329812;747;164.679852071;2021-12-07@19:19:19
+v2.0.8;multi;178;10000;;19033;6531;323780;82453;329812;747;164.670572918;2021-12-07@19:22:06
+v2.0.8;imex;178;10000;;19033;6531;165764;43006;172024;440;96.468907431;2021-12-07@19:25:39
+v2.0.8;imex;178;10000;;19033;6531;166300;43086;172344;428;93.906688190;2021-12-07@19:27:15
+v2.0.8;imex;178;10000;;19033;6531;166080;43045;172180;422;92.639551274;2021-12-07@19:28:51
+v2.0.8;imex;178;10000;;19033;6531;165964;43079;172316;420;92.106320314;2021-12-07@19:30:25
+v2.0.8;imex;178;10000;;19033;6531;165980;43025;172100;419;92.364277190;2021-12-07@19:31:59
+v2.0.8;imex;178;10000;;19033;6531;165724;43023;172092;431;94.616677919;2021-12-07@19:33:36
+v2.0.8;imex;178;10000;;19033;6531;165772;43017;172068;426;93.487271096;2021-12-07@19:35:12
+v2.0.8;imex;178;10000;;19033;6531;166000;43072;172288;424;93.016330089;2021-12-07@19:36:47
+v2.0.8;single;178;10000;;19033;6531;18628;6166;24664;457;100.596303996;2021-12-07@19:40:12
+v2.0.8;single;178;10000;;19033;6531;19388;6353;25412;442;97.137298157;2021-12-07@19:41:52
+v2.0.8;single;178;10000;;19033;6531;19456;6384;25536;441;96.715050045;2021-12-07@19:43:30
+v2.0.8;single;178;10000;;19033;6531;19164;6313;25252;439;96.296256213;2021-12-07@19:45:09
+v2.0.8;single;178;10000;;19033;6531;19184;6364;25456;446;97.855675289;2021-12-07@19:46:49
+v2.0.8;single;178;10000;;19033;6531;19396;6347;25388;450;98.684735012;2021-12-07@19:48:29
+v2.0.8;single;178;10000;;19033;6531;19348;6328;25312;450;98.714682791;2021-12-07@19:50:10
+v2.0.8;single;178;10000;;19033;6531;19448;6444;25776;447;98.041881310;2021-12-07@19:51:50
+v2.0.8;mulimex;178;10000;;19033;6531;465480;117905;471620;741;163.492284571;2021-12-07@19:56:20
+v2.0.8;mulimex;178;10000;;19033;6531;465412;117893;471572;752;165.821125164;2021-12-07@19:59:08
+v2.0.8;mulimex;178;10000;;19033;6531;465540;117933;471732;749;165.118394046;2021-12-07@20:01:56
+v2.0.8;mulimex;178;10000;;19033;6531;485248;122800;491200;753;165.268371616;2021-12-07@20:04:43
+v2.0.8;mulimex;178;10000;;19033;6531;465580;117927;471708;749;165.185756859;2021-12-07@20:07:31
+v2.0.8;mulimex;178;10000;;19033;6531;465668;117924;471696;748;164.826062978;2021-12-07@20:10:18
+v2.0.8;mulimex;178;10000;;19033;6531;465340;117874;471496;749;165.087836049;2021-12-07@20:13:06
+v2.0.8;mulimex;178;10000;;19033;6531;465368;117911;471644;748;164.828776379;2021-12-07@20:15:53
+v2.0.8;multi;316;10000;;19553;6638;576412;145619;582476;1364;301.437332047;2021-12-07@20:23:50
+v2.0.8;multi;316;10000;;19553;6638;576120;145565;582260;1391;308.026027526;2021-12-07@20:29:01
+v2.0.8;multi;316;10000;;19553;6638;576256;145604;582416;1381;305.780135037;2021-12-07@20:34:10
+v2.0.8;multi;316;10000;;19553;6638;576476;145702;582808;1392;308.944966159;2021-12-07@20:39:22
+v2.0.8;multi;316;10000;;19553;6638;576724;145735;582940;1391;307.443356777;2021-12-07@20:44:32
+v2.0.8;multi;316;10000;;19553;6638;575996;145566;582264;1390;307.199272424;2021-12-07@20:49:43
+v2.0.8;multi;316;10000;;19553;6638;576796;145727;582908;1389;308.110883414;2021-12-07@20:54:54
+v2.0.8;multi;316;10000;;19553;6638;576512;145707;582828;1381;306.679623567;2021-12-07@21:00:04
+v2.0.8;imex;316;10000;;19553;6638;284180;72597;290388;1162;256.999570973;2021-12-07@21:07:58
+v2.0.8;imex;316;10000;;19553;6638;284108;72602;290408;837;185.575683561;2021-12-07@21:11:05
+v2.0.8;imex;316;10000;;19553;6638;284504;72667;290668;853;188.174848726;2021-12-07@21:14:16
+v2.0.8;imex;316;10000;;19553;6638;284452;72603;290412;858;189.552162419;2021-12-07@21:17:28
+v2.0.8;imex;316;10000;;19553;6638;283988;72571;290284;855;188.332651908;2021-12-07@21:20:38
+v2.0.8;imex;316;10000;;19553;6638;284152;72613;290452;865;190.454185115;2021-12-07@21:23:51
+v2.0.8;imex;316;10000;;19553;6638;284128;72581;290324;859;190.103712198;2021-12-07@21:27:04
+v2.0.8;imex;316;10000;;19553;6638;284100;72595;290380;843;186.871446146;2021-12-07@21:30:14
+v2.0.8;single;316;10000;;19553;6638;29668;8937;35748;1209;268.291747662;2021-12-07@21:38:13
+v2.0.8;single;316;10000;;19553;6638;29456;8870;35480;1157;256.729359927;2021-12-07@21:43:14
+v2.0.8;single;316;10000;;19553;6638;29436;8865;35460;897;198.441831717;2021-12-07@21:46:50
+v2.0.8;single;316;10000;;19553;6638;29372;8809;35236;1186;263.235567951;2021-12-07@21:51:55
+v2.0.8;single;316;10000;;19553;6638;29676;8923;35692;1138;251.997587950;2021-12-07@21:57:04
+v2.0.8;single;316;10000;;19553;6638;29676;8910;35640;1196;264.643353842;2021-12-07@22:03:48
+v2.0.8;single;316;10000;;19553;6638;28636;8658;34632;871;192.101982736;2021-12-07@22:07:02
+v2.0.8;single;316;10000;;19553;6638;28784;8702;34808;862;190.054995406;2021-12-07@22:10:14
+v2.0.8;mulimex;316;10000;;19553;6638;829616;208913;835652;1379;305.101826067;2021-12-07@22:18:30
+v2.0.8;mulimex;316;10000;;19553;6638;829692;208922;835688;1391;308.145536069;2021-12-07@22:23:43
+v2.0.8;mulimex;316;10000;;19553;6638;829244;208802;835208;1388;307.228636267;2021-12-07@22:28:53
+v2.0.8;mulimex;316;10000;;19553;6638;829120;208850;835400;1387;306.742807045;2021-12-07@22:34:03
+v2.0.8;mulimex;316;10000;;19553;6638;829216;208817;835268;1392;308.302072501;2021-12-07@22:39:15
+v2.0.8;mulimex;316;10000;;19553;6638;829748;208921;835684;1388;307.362917314;2021-12-07@22:44:25
+v2.0.8;mulimex;316;10000;;19553;6638;829208;208807;835228;1395;308.999325562;2021-12-07@22:49:38
+v2.0.8;mulimex;316;10000;;19553;6638;828944;208780;835120;1388;307.283899217;2021-12-07@22:54:49
+v2.0.8;multi;10;17783;;29952;12293;52600;14689;58756;68;14.981767531;2021-12-07@22:56:42
+v2.0.8;multi;10;17783;;29952;12293;52684;14698;58792;73;16.061628388;2021-12-07@22:57:00
+v2.0.8;multi;10;17783;;29952;12293;52960;14697;58788;73;16.066075809;2021-12-07@22:57:18
+v2.0.8;multi;10;17783;;29952;12293;52668;14714;58856;73;16.062526647;2021-12-07@22:57:36
+v2.0.8;multi;10;17783;;29952;12293;52928;14714;58856;73;16.069427309;2021-12-07@22:57:54
+v2.0.8;multi;10;17783;;29952;12293;52596;14700;58800;73;16.059685774;2021-12-07@22:58:12
+v2.0.8;multi;10;17783;;29952;12293;52448;14710;58840;73;16.065174594;2021-12-07@22:58:30
+v2.0.8;multi;10;17783;;29952;12293;52784;14706;58824;73;16.059399392;2021-12-07@22:58:48
+v2.0.8;imex;10;17783;;29952;12293;42096;12074;48296;47;10.279322481;2021-12-07@22:59:07
+v2.0.8;imex;10;17783;;29952;12293;42148;12090;48360;47;10.316036647;2021-12-07@22:59:19
+v2.0.8;imex;10;17783;;29952;12293;41948;12076;48304;48;10.541208636;2021-12-07@22:59:32
+v2.0.8;imex;10;17783;;29952;12293;42196;12091;48364;48;10.535102474;2021-12-07@22:59:44
+v2.0.8;imex;10;17783;;29952;12293;41976;12086;48344;48;10.536781737;2021-12-07@22:59:57
+v2.0.8;imex;10;17783;;29952;12293;42168;12098;48392;48;10.547518191;2021-12-07@23:00:09
+v2.0.8;imex;10;17783;;29952;12293;41840;12076;48304;48;10.536963398;2021-12-07@23:00:22
+v2.0.8;imex;10;17783;;29952;12293;42264;12092;48368;48;10.539528854;2021-12-07@23:00:34
+v2.0.8;single;10;17783;;29952;12293;14076;5068;20272;47;10.281822482;2021-12-07@23:00:54
+v2.0.8;single;10;17783;;29952;12293;14220;5098;20392;49;10.756095546;2021-12-07@23:01:07
+v2.0.8;single;10;17783;;29952;12293;14360;5123;20492;58;12.731911771;2021-12-07@23:01:22
+v2.0.8;single;10;17783;;29952;12293;14244;5121;20484;49;10.759502433;2021-12-07@23:01:34
+v2.0.8;single;10;17783;;29952;12293;14120;5090;20360;49;10.757031388;2021-12-07@23:01:47
+v2.0.8;single;10;17783;;29952;12293;14384;5101;20404;50;10.986469990;2021-12-07@23:02:00
+v2.0.8;single;10;17783;;29952;12293;14400;5092;20368;49;10.756130568;2021-12-07@23:02:13
+v2.0.8;single;10;17783;;29952;12293;14204;5121;20484;50;10.981860948;2021-12-07@23:02:26
+v2.0.8;mulimex;10;17783;;29952;12293;72284;19560;78240;68;14.956576090;2021-12-07@23:02:49
+v2.0.8;mulimex;10;17783;;29952;12293;72000;19545;78180;73;16.057923166;2021-12-07@23:03:08
+v2.0.8;mulimex;10;17783;;29952;12293;71980;19563;78252;73;16.062892732;2021-12-07@23:03:27
+v2.0.8;mulimex;10;17783;;29952;12293;72200;19545;78180;73;16.065900423;2021-12-07@23:03:45
+v2.0.8;mulimex;10;17783;;29952;12293;72108;19569;78276;73;16.065647037;2021-12-07@23:04:02
+v2.0.8;mulimex;10;17783;;29952;12293;72220;19553;78212;73;16.060401545;2021-12-07@23:04:20
+v2.0.8;mulimex;10;17783;;29952;12293;72092;19564;78256;73;16.065845448;2021-12-07@23:04:39
+v2.0.8;mulimex;10;17783;;29952;12293;71912;19576;78304;73;16.081175264;2021-12-07@23:04:57
+v2.0.8;multi;18;17783;;31744;12312;78796;21239;84956;128;28.210543448;2021-12-07@23:05:37
+v2.0.8;multi;18;17783;;31744;12312;78892;21236;84944;133;29.313785115;2021-12-07@23:06:08
+v2.0.8;multi;18;17783;;31744;12312;78860;21259;85036;132;29.089304016;2021-12-07@23:06:39
+v2.0.8;multi;18;17783;;31744;12312;78896;21234;84936;132;29.110696357;2021-12-07@23:07:10
+v2.0.8;multi;18;17783;;31744;12312;78660;21242;84968;132;29.113043019;2021-12-07@23:07:41
+v2.0.8;multi;18;17783;;31744;12312;78820;21249;84996;132;29.098402324;2021-12-07@23:08:12
+v2.0.8;multi;18;17783;;31744;12312;79132;21254;85016;132;29.090970554;2021-12-07@23:08:43
+v2.0.8;multi;18;17783;;31744;12312;78908;21241;84964;132;29.089269354;2021-12-07@23:09:14
+v2.0.8;imex;18;17783;;31744;12312;56100;15548;62192;77;16.934970885;2021-12-07@23:09:45
+v2.0.8;imex;18;17783;;31744;12312;55912;15563;62252;78;17.183357570;2021-12-07@23:10:04
+v2.0.8;imex;18;17783;;31744;12312;55996;15558;62232;78;17.176566163;2021-12-07@23:10:23
+v2.0.8;imex;18;17783;;31744;12312;56012;15556;62224;79;17.397583590;2021-12-07@23:10:43
+v2.0.8;imex;18;17783;;31744;12312;56136;15556;62224;79;17.389481949;2021-12-07@23:11:02
+v2.0.8;imex;18;17783;;31744;12312;56132;15552;62208;78;17.179822082;2021-12-07@23:11:21
+v2.0.8;imex;18;17783;;31744;12312;56024;15544;62176;79;17.384813456;2021-12-07@23:11:41
+v2.0.8;imex;18;17783;;31744;12312;56252;15565;62260;79;17.396236983;2021-12-07@23:12:00
+v2.0.8;single;18;17783;;31744;12312;15444;5423;21692;82;18.007927575;2021-12-07@23:12:32
+v2.0.8;single;18;17783;;31744;12312;15464;5421;21684;82;18.045997939;2021-12-07@23:12:52
+v2.0.8;single;18;17783;;31744;12312;15400;5422;21688;82;18.033770437;2021-12-07@23:13:12
+v2.0.8;single;18;17783;;31744;12312;15596;5421;21684;81;17.816019585;2021-12-07@23:13:31
+v2.0.8;single;18;17783;;31744;12312;15820;5431;21724;82;18.047372275;2021-12-07@23:13:52
+v2.0.8;single;18;17783;;31744;12312;15744;5429;21716;82;18.047277703;2021-12-07@23:14:12
+v2.0.8;single;18;17783;;31744;12312;15704;5434;21736;81;17.802801021;2021-12-07@23:14:32
+v2.0.8;single;18;17783;;31744;12312;15572;5426;21704;84;18.500995532;2021-12-07@23:14:52
+v2.0.8;mulimex;18;17783;;31744;12312;110216;29085;116340;126;27.863846148;2021-12-07@23:15:34
+v2.0.8;mulimex;18;17783;;31744;12312;110224;29085;116340;132;29.110628931;2021-12-07@23:16:06
+v2.0.8;mulimex;18;17783;;31744;12312;110368;29059;116236;139;30.647025097;2021-12-07@23:16:38
+v2.0.8;mulimex;18;17783;;31744;12312;110052;29075;116300;132;29.103343787;2021-12-07@23:17:09
+v2.0.8;mulimex;18;17783;;31744;12312;110556;29070;116280;132;29.119485992;2021-12-07@23:17:40
+v2.0.8;mulimex;18;17783;;31744;12312;110232;29079;116316;132;29.107660152;2021-12-07@23:18:11
+v2.0.8;mulimex;18;17783;;31744;12312;110112;29070;116280;132;29.122564770;2021-12-07@23:18:43
+v2.0.8;mulimex;18;17783;;31744;12312;110428;29097;116388;133;29.338075528;2021-12-07@23:19:15
+v2.0.8;multi;32;17783;;32894;12393;123888;32500;130000;231;51.025597980;2021-12-07@23:20:26
+v2.0.8;multi;32;17783;;32894;12393;123880;32488;129952;236;52.143516022;2021-12-07@23:21:20
+v2.0.8;multi;32;17783;;32894;12393;124060;32473;129892;236;52.153346593;2021-12-07@23:22:14
+v2.0.8;multi;32;17783;;32894;12393;123876;32480;129920;236;52.130243958;2021-12-07@23:23:08
+v2.0.8;multi;32;17783;;32894;12393;123792;32499;129996;236;52.117796364;2021-12-07@23:24:02
+v2.0.8;multi;32;17783;;32894;12393;123876;32501;130004;236;52.127692385;2021-12-07@23:24:55
+v2.0.8;multi;32;17783;;32894;12393;123756;32485;129940;236;52.132111841;2021-12-07@23:25:49
+v2.0.8;multi;32;17783;;32894;12393;123744;32491;129964;236;52.154881655;2021-12-07@23:26:43
+v2.0.8;imex;32;17783;;32894;12393;78876;21268;85072;133;29.291456317;2021-12-07@23:27:35
+v2.0.8;imex;32;17783;;32894;12393;78948;21281;85124;133;29.312148251;2021-12-07@23:28:06
+v2.0.8;imex;32;17783;;32894;12393;79072;21251;85004;134;29.546412078;2021-12-07@23:28:37
+v2.0.8;imex;32;17783;;32894;12393;78724;21291;85164;133;29.311076537;2021-12-07@23:29:09
+v2.0.8;imex;32;17783;;32894;12393;79204;21285;85140;133;29.322697128;2021-12-07@23:29:40
+v2.0.8;imex;32;17783;;32894;12393;78736;21266;85064;133;29.330312741;2021-12-07@23:30:11
+v2.0.8;imex;32;17783;;32894;12393;78648;21252;85008;134;29.545044913;2021-12-07@23:30:43
+v2.0.8;imex;32;17783;;32894;12393;78892;21269;85076;132;29.095177339;2021-12-07@23:31:14
+v2.0.8;single;32;17783;;32894;12393;16984;5785;23140;139;30.613300128;2021-12-07@23:32:07
+v2.0.8;single;32;17783;;32894;12393;17048;5794;23176;138;30.414009306;2021-12-07@23:32:39
+v2.0.8;single;32;17783;;32894;12393;16940;5800;23200;140;30.867701920;2021-12-07@23:33:12
+v2.0.8;single;32;17783;;32894;12393;16812;5793;23172;138;30.428487508;2021-12-07@23:33:45
+v2.0.8;single;32;17783;;32894;12393;16980;5783;23132;137;30.204243693;2021-12-07@23:34:17
+v2.0.8;single;32;17783;;32894;12393;16856;5789;23156;140;30.853967292;2021-12-07@23:34:50
+v2.0.8;single;32;17783;;32894;12393;17232;5817;23268;140;30.865001918;2021-12-07@23:35:23
+v2.0.8;single;32;17783;;32894;12393;16964;5823;23292;138;30.411766172;2021-12-07@23:35:55
+v2.0.8;mulimex;32;17783;;32894;12393;176232;45555;182220;233;51.453441508;2021-12-07@23:37:08
+v2.0.8;mulimex;32;17783;;32894;12393;176196;45575;182300;237;52.306092287;2021-12-07@23:38:03
+v2.0.8;mulimex;32;17783;;32894;12393;176072;45557;182228;237;52.360627637;2021-12-07@23:38:57
+v2.0.8;mulimex;32;17783;;32894;12393;176280;45563;182252;237;52.329645269;2021-12-07@23:39:52
+v2.0.8;mulimex;32;17783;;32894;12393;176180;45571;182284;237;52.334679469;2021-12-07@23:40:46
+v2.0.8;mulimex;32;17783;;32894;12393;175936;45551;182204;237;52.266928195;2021-12-07@23:41:41
+v2.0.8;mulimex;32;17783;;32894;12393;176080;45553;182212;237;52.361941135;2021-12-07@23:42:35
+v2.0.8;mulimex;32;17783;;32894;12393;176028;45536;182144;237;52.321463733;2021-12-07@23:43:29
+v2.0.8;multi;56;17783;;33578;12411;199572;51429;205716;410;90.709436502;2021-12-07@23:45:30
+v2.0.8;multi;56;17783;;33578;12411;202084;52088;208352;416;92.071376431;2021-12-07@23:47:05
+v2.0.8;multi;56;17783;;33578;12411;201836;52041;208164;427;94.509872876;2021-12-07@23:48:42
+v2.0.8;multi;56;17783;;33578;12411;202932;52236;208944;418;92.201157262;2021-12-07@23:50:16
+v2.0.8;multi;56;17783;;33578;12411;203200;52327;209308;517;114.540884193;2021-12-07@23:52:13
+v2.0.8;multi;56;17783;;33578;12411;201932;51992;207968;417;92.309036585;2021-12-07@23:53:47
+v2.0.8;multi;56;17783;;33578;12411;199492;51395;205580;416;92.126528983;2021-12-07@23:55:22
+v2.0.8;multi;56;17783;;33578;12411;205612;52988;211952;518;114.650827855;2021-12-07@23:57:19
+v2.0.8;imex;56;17783;;33578;12411;116292;30634;122536;227;50.028597511;2021-12-07@23:58:47
+v2.0.8;imex;56;17783;;33578;12411;116492;30623;122492;224;49.454963126;2021-12-07@23:59:39
+v2.0.8;imex;56;17783;;33578;12411;116344;30604;122416;224;49.430264565;2021-12-08@00:00:30
+v2.0.8;imex;56;17783;;33578;12411;116284;30628;122512;232;51.179389336;2021-12-08@00:01:24
+v2.0.8;imex;56;17783;;33578;12411;116292;30635;122540;232;51.184033626;2021-12-08@00:02:17
+v2.0.8;imex;56;17783;;33578;12411;116428;30608;122432;226;49.924571322;2021-12-08@00:03:09
+v2.0.8;imex;56;17783;;33578;12411;116116;30601;122404;225;49.642720352;2021-12-08@00:04:00
+v2.0.8;imex;56;17783;;33578;12411;116044;30611;122444;231;50.976736588;2021-12-08@00:04:54
+v2.0.8;single;56;17783;;33578;12411;18916;6267;25068;238;52.491792706;2021-12-08@00:06:23
+v2.0.8;single;56;17783;;33578;12411;18848;6246;24984;234;51.655042864;2021-12-08@00:07:17
+v2.0.8;single;56;17783;;33578;12411;18964;6281;25124;236;52.079049228;2021-12-08@00:08:11
+v2.0.8;single;56;17783;;33578;12411;18848;6248;24992;233;51.420948875;2021-12-08@00:09:04
+v2.0.8;single;56;17783;;33578;12411;18876;6311;25244;234;51.679661905;2021-12-08@00:09:58
+v2.0.8;single;56;17783;;33578;12411;18568;6235;24940;234;51.654971494;2021-12-08@00:10:51
+v2.0.8;single;56;17783;;33578;12411;19012;6241;24964;244;53.827829759;2021-12-08@00:11:47
+v2.0.8;single;56;17783;;33578;12411;18868;6268;25072;234;51.656667337;2021-12-08@00:12:41
+v2.0.8;mulimex;56;17783;;33578;12411;291268;74389;297556;411;90.828744403;2021-12-08@00:14:49
+v2.0.8;mulimex;56;17783;;33578;12411;288980;73784;295136;416;92.179599742;2021-12-08@00:16:23
+v2.0.8;mulimex;56;17783;;33578;12411;288208;73555;294220;417;92.196572792;2021-12-08@00:17:58
+v2.0.8;mulimex;56;17783;;33578;12411;290044;74056;296224;416;92.284506559;2021-12-08@00:19:33
+v2.0.8;mulimex;56;17783;;33578;12411;291748;74514;298056;417;92.166442512;2021-12-08@00:21:07
+v2.0.8;mulimex;56;17783;;33578;12411;290960;74215;296860;417;92.124233886;2021-12-08@00:22:42
+v2.0.8;mulimex;56;17783;;33578;12411;288884;73789;295156;417;92.200807914;2021-12-08@00:24:16
+v2.0.8;mulimex;56;17783;;33578;12411;290012;74004;296016;418;92.364635929;2021-12-08@00:25:51
+v2.0.8;multi;100;17783;;33951;12499;337100;85821;343284;737;163.537420557;2021-12-08@00:29:28
+v2.0.8;multi;100;17783;;33951;12499;380704;96687;386748;754;166.096816473;2021-12-08@00:32:16
+v2.0.8;multi;100;17783;;33951;12499;374888;95225;380900;751;166.000934873;2021-12-08@00:35:05
+v2.0.8;multi;100;17783;;33951;12499;375784;95511;382044;751;165.967554851;2021-12-08@00:37:57
+v2.0.8;multi;100;17783;;33951;12499;338612;86179;344716;739;163.883216607;2021-12-08@00:40:57
+v2.0.8;multi;100;17783;;33951;12499;375588;95469;381876;454;100.667637280;2021-12-08@00:43:46
+v2.0.8;multi;100;17783;;33951;12499;375468;95448;381792;752;166.204631750;2021-12-08@00:46:35
+v2.0.8;multi;100;17783;;33951;12499;368620;93677;374708;452;100.455355480;2021-12-08@00:49:23
+v2.0.8;imex;100;17783;;33951;12499;184684;47733;190932;417;92.236102418;2021-12-08@00:52:00
+v2.0.8;imex;100;17783;;33951;12499;184704;47737;190948;411;91.056504584;2021-12-08@00:53:34
+v2.0.8;imex;100;17783;;33951;12499;184752;47729;190916;419;92.899060561;2021-12-08@00:55:08
+v2.0.8;imex;100;17783;;33951;12499;184808;47731;190924;417;92.277196693;2021-12-08@00:56:42
+v2.0.8;imex;100;17783;;33951;12499;184832;47742;190968;414;91.628513044;2021-12-08@00:58:16
+v2.0.8;imex;100;17783;;33951;12499;184684;47746;190984;412;91.207421323;2021-12-08@00:59:49
+v2.0.8;imex;100;17783;;33951;12499;184824;47744;190976;406;89.856505062;2021-12-08@01:01:21
+v2.0.8;imex;100;17783;;33951;12499;184500;47701;190804;408;90.295013261;2021-12-08@01:02:53
+v2.0.8;single;100;17783;;33951;12499;21448;6903;27612;432;95.548503721;2021-12-08@01:05:30
+v2.0.8;single;100;17783;;33951;12499;21712;6932;27728;438;96.989551953;2021-12-08@01:07:09
+v2.0.8;single;100;17783;;33951;12499;21444;6930;27720;422;93.446610267;2021-12-08@01:08:44
+v2.0.8;single;100;17783;;33951;12499;21764;6951;27804;420;92.991451896;2021-12-08@01:10:19
+v2.0.8;single;100;17783;;33951;12499;21408;6932;27728;424;93.896027936;2021-12-08@01:11:55
+v2.0.8;single;100;17783;;33951;12499;21408;6879;27516;428;94.736583603;2021-12-08@01:13:32
+v2.0.8;single;100;17783;;33951;12499;21588;6928;27712;422;93.414625593;2021-12-08@01:15:07
+v2.0.8;single;100;17783;;33951;12499;21120;6867;27468;428;94.866818099;2021-12-08@01:16:44
+v2.0.8;mulimex;100;17783;;33951;12499;490120;124106;496424;738;164.269970695;2021-12-08@01:20:29
+v2.0.8;mulimex;100;17783;;33951;12499;549020;138821;555284;455;99.813440827;2021-12-08@01:23:18
+v2.0.8;mulimex;100;17783;;33951;12499;545632;137969;551876;455;100.004036697;2021-12-08@01:26:06
+v2.0.8;mulimex;100;17783;;33951;12499;541404;136885;547540;451;100.153725312;2021-12-08@01:28:55
+v2.0.8;mulimex;100;17783;;33951;12499;540380;136614;546456;452;100.417072126;2021-12-08@01:31:43
+v2.0.8;mulimex;100;17783;;33951;12499;541076;136810;547240;455;100.331728609;2021-12-08@01:34:32
+v2.0.8;mulimex;100;17783;;33951;12499;544456;137644;550576;453;100.029168896;2021-12-08@01:37:20
+v2.0.8;mulimex;100;17783;;33951;12499;540284;136511;546044;449;100.207336557;2021-12-08@01:40:08
+v2.0.8;multi;178;17783;;34156;12582;580792;146746;586984;1370;306.165294100;2021-12-08@01:46:51
+v2.0.8;multi;178;17783;;34156;12582;580356;146654;586616;1551;347.958025333;2021-12-08@01:52:42
+v2.0.8;multi;178;17783;;34156;12582;580288;146613;586452;1556;348.992405464;2021-12-08@01:58:33
+v2.0.8;multi;178;17783;;34156;12582;583052;147269;589076;1360;303.963340554;2021-12-08@02:03:40
+v2.0.8;multi;178;17783;;34156;12582;580092;146595;586380;1358;303.804616588;2021-12-08@02:08:47
+v2.0.8;multi;178;17783;;34156;12582;580392;146626;586504;1360;303.917704343;2021-12-08@02:13:54
+v2.0.8;multi;178;17783;;34156;12582;580660;146639;586556;1360;304.079597270;2021-12-08@02:19:01
+v2.0.8;multi;178;17783;;34156;12582;580336;146615;586460;1355;302.795462304;2021-12-08@02:24:06
+v2.0.8;imex;178;17783;;34156;12582;305088;77832;311328;777;172.466694965;2021-12-08@02:28:57
+v2.0.8;imex;178;17783;;34156;12582;305332;77899;311596;733;162.621100016;2021-12-08@02:31:42
+v2.0.8;imex;178;17783;;34156;12582;305312;77923;311692;757;168.184861654;2021-12-08@02:34:33
+v2.0.8;imex;178;17783;;34156;12582;305420;77906;311624;749;166.092477614;2021-12-08@02:37:21
+v2.0.8;imex;178;17783;;34156;12582;305408;77912;311648;720;159.758593918;2021-12-08@02:40:03
+v2.0.8;imex;178;17783;;34156;12582;305424;77912;311648;759;168.331348384;2021-12-08@02:42:53
+v2.0.8;imex;178;17783;;34156;12582;305576;77921;311684;769;170.463281707;2021-12-08@02:45:46
+v2.0.8;imex;178;17783;;34156;12582;305560;77939;311756;742;164.691179161;2021-12-08@02:48:33
+v2.0.8;single;178;17783;;34156;12582;25572;7941;31764;801;178.038615746;2021-12-08@02:53:15
+v2.0.8;single;178;17783;;34156;12582;25516;7942;31768;746;166.299921978;2021-12-08@02:56:03
+v2.0.8;single;178;17783;;34156;12582;25436;7898;31592;762;169.129662317;2021-12-08@02:58:54
+v2.0.8;single;178;17783;;34156;12582;25392;7848;31392;774;171.813185791;2021-12-08@03:01:48
+v2.0.8;single;178;17783;;34156;12582;25468;7888;31552;763;169.405391931;2021-12-08@03:04:40
+v2.0.8;single;178;17783;;34156;12582;25984;7995;31980;762;169.368580681;2021-12-08@03:07:31
+v2.0.8;single;178;17783;;34156;12582;25324;7862;31448;767;170.154276387;2021-12-08@03:10:23
+v2.0.8;single;178;17783;;34156;12582;26012;7989;31956;771;171.319508879;2021-12-08@03:13:16
+v2.0.8;mulimex;178;17783;;34156;12582;850224;214038;856152;1352;302.580194981;2021-12-08@03:20:04
+v2.0.8;mulimex;178;17783;;34156;12582;849928;213999;855996;1368;305.720676944;2021-12-08@03:25:13
+v2.0.8;mulimex;178;17783;;34156;12582;849720;214004;856016;1359;304.264812969;2021-12-08@03:30:21
+v2.0.8;mulimex;178;17783;;34156;12582;849892;214041;856164;1364;304.962652047;2021-12-08@03:35:30
+v2.0.8;mulimex;178;17783;;34156;12582;850140;214062;856248;1359;303.756259177;2021-12-08@03:40:36
+v2.0.8;mulimex;178;17783;;34156;12582;850068;214111;856444;1356;303.201357056;2021-12-08@03:45:43
+v2.0.8;mulimex;178;17783;;34156;12582;849968;214056;856224;461;101.320614191;2021-12-08@03:50:50
+v2.0.8;mulimex;178;17783;;34156;12582;850000;214075;856300;1360;302.886359514;2021-12-08@03:55:56
+v2.0.8;multi;316;17783;;34574;12710;1017108;255823;1023292;2936;661.001866960;2021-12-08@04:09:53
+v2.0.8;multi;316;17783;;34574;12710;1017140;255807;1023228;2376;533.394451529;2021-12-08@04:18:51
+v2.0.8;multi;316;17783;;34574;12710;1017404;255856;1023424;2381;535.857541385;2021-12-08@04:27:51
+v2.0.8;multi;316;17783;;34574;12710;1017200;255855;1023420;2361;529.716934273;2021-12-08@04:36:45
+v2.0.8;multi;316;17783;;34574;12710;1017236;255840;1023360;2375;533.717903276;2021-12-08@04:45:42
+v2.0.8;multi;316;17783;;34574;12710;1017072;255794;1023176;2385;536.516402253;2021-12-08@04:54:43
+v2.0.8;multi;316;17783;;34574;12710;1017548;255881;1023524;2372;534.219961926;2021-12-08@05:03:41
+v2.0.8;multi;316;17783;;34574;12710;1017056;255853;1023412;2378;535.330217231;2021-12-08@05:12:40
+v2.0.8;imex;316;17783;;34574;12710;524428;132600;530400;1574;353.785847633;2021-12-08@05:22:13
+v2.0.8;imex;316;17783;;34574;12710;522096;132027;528108;1452;324.136401718;2021-12-08@05:27:39
+v2.0.8;imex;316;17783;;34574;12710;521836;131995;527980;1675;376.266049845;2021-12-08@05:33:58
+v2.0.8;imex;316;17783;;34574;12710;521712;131998;527992;1735;388.377112181;2021-12-08@05:40:29
+v2.0.8;imex;316;17783;;34574;12710;522420;132138;528552;1441;321.706880621;2021-12-08@05:45:53
+v2.0.8;imex;316;17783;;34574;12710;521548;131975;527900;1448;323.891415141;2021-12-08@05:51:19
+v2.0.8;imex;316;17783;;34574;12710;522536;132211;528844;1439;321.977521834;2021-12-08@05:56:44
+v2.0.8;imex;316;17783;;34574;12710;521972;132022;528088;1645;368.637133013;2021-12-08@06:02:55
+v2.0.8;single;316;17783;;34574;12710;32900;9744;38976;1648;369.471450326;2021-12-08@06:12:16
+v2.0.8;single;316;17783;;34574;12710;35192;10379;41516;1522;341.706180958;2021-12-08@06:18:00
+v2.0.8;single;316;17783;;34574;12710;31904;9478;37912;1738;389.427384637;2021-12-08@06:24:31
+v2.0.8;single;316;17783;;34574;12710;33784;9933;39732;1523;339.824139183;2021-12-08@06:30:13
+v2.0.8;single;316;17783;;34574;12710;32060;9574;38296;1538;343.246728738;2021-12-08@06:35:58
+v2.0.8;single;316;17783;;34574;12710;32748;9677;38708;1734;387.685683063;2021-12-08@06:42:27
+v2.0.8;single;316;17783;;34574;12710;34916;10277;41108;1759;394.525265765;2021-12-08@06:49:04
+v2.0.8;single;316;17783;;34574;12710;34888;10247;40988;1512;336.965964558;2021-12-08@06:54:43
+v2.0.8;mulimex;316;17783;;34574;12710;1496456;375646;1502584;2606;586.344607203;2021-12-08@07:07:40
+v2.0.8;mulimex;316;17783;;34574;12710;1496384;375639;1502556;1368;305.659665680;2021-12-08@07:16:39
+v2.0.8;mulimex;316;17783;;34574;12710;1496368;375586;1502344;2393;535.508388759;2021-12-08@07:25:40
+v2.0.8;mulimex;316;17783;;34574;12710;1496864;375710;1502840;2364;531.096878086;2021-12-08@07:34:36
+v2.0.8;mulimex;316;17783;;34574;12710;1496264;375640;1502560;2402;537.854836184;2021-12-08@07:43:39
+v2.0.8;mulimex;316;17783;;34574;12710;1496384;375654;1502616;2386;535.118076245;2021-12-08@07:52:39
+v2.0.8;mulimex;316;17783;;34574;12710;1496504;375617;1502468;2396;536.890438660;2021-12-08@08:01:41
+v2.0.8;mulimex;316;17783;;34574;12710;1496752;375656;1502624;2390;535.641925649;2021-12-08@08:10:41
+v2.0.8;multi;10;31623;;54425;24231;92936;24807;99228;117;26.368815866;2021-12-08@08:12:47
+v2.0.8;multi;10;31623;;54425;24231;93212;24810;99240;124;27.874472220;2021-12-08@08:13:18
+v2.0.8;multi;10;31623;;54425;24231;93140;24805;99220;124;27.872004940;2021-12-08@08:13:49
+v2.0.8;multi;10;31623;;54425;24231;92888;24813;99252;124;27.874111579;2021-12-08@08:14:21
+v2.0.8;multi;10;31623;;54425;24231;93128;24817;99268;124;27.884302590;2021-12-08@08:14:50
+v2.0.8;multi;10;31623;;54425;24231;93116;24796;99184;124;27.880850887;2021-12-08@08:15:20
+v2.0.8;multi;10;31623;;54425;24231;93092;24815;99260;124;27.897365927;2021-12-08@08:15:50
+v2.0.8;multi;10;31623;;54425;24231;92968;24810;99240;124;27.894408342;2021-12-08@08:16:20
+v2.0.8;imex;10;31623;;54425;24231;74980;20308;81232;78;17.411649311;2021-12-08@08:16:47
+v2.0.8;imex;10;31623;;54425;24231;75116;20318;81272;79;17.675904803;2021-12-08@08:17:06
+v2.0.8;imex;10;31623;;54425;24231;75088;20335;81340;80;17.889252699;2021-12-08@08:17:26
+v2.0.8;imex;10;31623;;54425;24231;74984;20340;81360;80;17.906519849;2021-12-08@08:17:46
+v2.0.8;imex;10;31623;;54425;24231;75232;20319;81276;81;18.117705616;2021-12-08@08:18:06
+v2.0.8;imex;10;31623;;54425;24231;75168;20343;81372;81;18.113592541;2021-12-08@08:18:26
+v2.0.8;imex;10;31623;;54425;24231;75096;20326;81304;80;17.896420267;2021-12-08@08:18:46
+v2.0.8;imex;10;31623;;54425;24231;74964;20316;81264;80;17.891189684;2021-12-08@08:19:05
+v2.0.8;single;10;31623;;54425;24231;23080;7273;29092;81;18.109329035;2021-12-08@08:19:34
+v2.0.8;single;10;31623;;54425;24231;22916;7291;29164;85;19.012704115;2021-12-08@08:19:55
+v2.0.8;single;10;31623;;54425;24231;23080;7303;29212;83;18.569219327;2021-12-08@08:20:16
+v2.0.8;single;10;31623;;54425;24231;22892;7305;29220;84;18.806752030;2021-12-08@08:20:37
+v2.0.8;single;10;31623;;54425;24231;22984;7316;29264;84;18.790478733;2021-12-08@08:20:57
+v2.0.8;single;10;31623;;54425;24231;22956;7287;29148;84;18.773703064;2021-12-08@08:21:18
+v2.0.8;single;10;31623;;54425;24231;23016;7298;29192;83;18.562210551;2021-12-08@08:21:38
+v2.0.8;single;10;31623;;54425;24231;22972;7321;29284;84;18.787685160;2021-12-08@08:21:59
+v2.0.8;mulimex;10;31623;;54425;24231;130520;34147;136588;119;26.772858515;2021-12-08@08:22:35
+v2.0.8;mulimex;10;31623;;54425;24231;130304;34167;136668;124;27.888189765;2021-12-08@08:23:05
+v2.0.8;mulimex;10;31623;;54425;24231;130364;34166;136664;124;27.899722939;2021-12-08@08:23:35
+v2.0.8;mulimex;10;31623;;54425;24231;130504;34162;136648;124;27.890003623;2021-12-08@08:24:05
+v2.0.8;mulimex;10;31623;;54425;24231;130420;34140;136560;124;27.884966947;2021-12-08@08:24:34
+v2.0.8;mulimex;10;31623;;54425;24231;130460;34144;136576;124;27.913979295;2021-12-08@08:25:04
+v2.0.8;mulimex;10;31623;;54425;24231;130512;34131;136524;124;27.880353152;2021-12-08@08:25:34
+v2.0.8;mulimex;10;31623;;54425;24231;130412;34144;136576;124;27.910615175;2021-12-08@08:26:03
+v2.0.8;multi;18;31623;;57202;24252;140280;36577;146308;221;49.758809558;2021-12-08@08:27:06
+v2.0.8;multi;18;31623;;57202;24252;140092;36550;146200;227;51.115461302;2021-12-08@08:27:59
+v2.0.8;multi;18;31623;;57202;24252;139988;36551;146204;227;51.180680346;2021-12-08@08:28:52
+v2.0.8;multi;18;31623;;57202;24252;140024;36539;146156;227;51.120056180;2021-12-08@08:29:45
+v2.0.8;multi;18;31623;;57202;24252;140192;36536;146144;227;51.170873079;2021-12-08@08:30:38
+v2.0.8;multi;18;31623;;57202;24252;140168;36560;146240;227;51.129130081;2021-12-08@08:31:31
+v2.0.8;multi;18;31623;;57202;24252;139892;36541;146164;227;51.134665995;2021-12-08@08:32:24
+v2.0.8;multi;18;31623;;57202;24252;140144;36546;146184;227;51.143209579;2021-12-08@08:33:17
+v2.0.8;imex;18;31623;;57202;24252;101252;26842;107368;137;30.709332501;2021-12-08@08:34:02
+v2.0.8;imex;18;31623;;57202;24252;101180;26880;107520;136;30.519154328;2021-12-08@08:34:34
+v2.0.8;imex;18;31623;;57202;24252;101280;26867;107468;139;31.184962472;2021-12-08@08:35:07
+v2.0.8;imex;18;31623;;57202;24252;101304;26871;107484;137;30.719885872;2021-12-08@08:35:40
+v2.0.8;imex;18;31623;;57202;24252;101452;26898;107592;138;30.967063958;2021-12-08@08:36:13
+v2.0.8;imex;18;31623;;57202;24252;101128;26872;107488;135;30.354842019;2021-12-08@08:36:45
+v2.0.8;imex;18;31623;;57202;24252;101180;26883;107532;139;31.176447833;2021-12-08@08:37:19
+v2.0.8;imex;18;31623;;57202;24252;101452;26865;107460;138;30.961640338;2021-12-08@08:37:53
+v2.0.8;single;18;31623;;57202;24252;24944;7793;31172;142;31.858615482;2021-12-08@08:38:40
+v2.0.8;single;18;31623;;57202;24252;24756;7760;31040;141;31.651175859;2021-12-08@08:39:14
+v2.0.8;single;18;31623;;57202;24252;24872;7778;31112;140;31.411432215;2021-12-08@08:39:47
+v2.0.8;single;18;31623;;57202;24252;24924;7775;31100;141;31.683337224;2021-12-08@08:40:21
+v2.0.8;single;18;31623;;57202;24252;24840;7774;31096;144;32.310743085;2021-12-08@08:40:55
+v2.0.8;single;18;31623;;57202;24252;24672;7774;31096;145;32.517663232;2021-12-08@08:41:29
+v2.0.8;single;18;31623;;57202;24252;24928;7769;31076;143;32.093836004;2021-12-08@08:42:03
+v2.0.8;single;18;31623;;57202;24252;25088;7802;31208;143;32.075427826;2021-12-08@08:42:37
+v2.0.8;mulimex;18;31623;;57202;24252;200848;51737;206948;223;50.241548010;2021-12-08@08:43:41
+v2.0.8;mulimex;18;31623;;57202;24252;200536;51708;206832;228;51.381461937;2021-12-08@08:44:34
+v2.0.8;mulimex;18;31623;;57202;24252;200512;51700;206800;228;51.383348517;2021-12-08@08:45:28
+v2.0.8;mulimex;18;31623;;57202;24252;200948;51709;206836;228;51.373186780;2021-12-08@08:46:21
+v2.0.8;mulimex;18;31623;;57202;24252;200620;51731;206924;228;51.385071771;2021-12-08@08:47:15
+v2.0.8;mulimex;18;31623;;57202;24252;200988;51716;206864;228;51.366925804;2021-12-08@08:48:08
+v2.0.8;mulimex;18;31623;;57202;24252;200900;51729;206916;282;63.554246095;2021-12-08@08:49:14
+v2.0.8;mulimex;18;31623;;57202;24252;200568;51726;206904;228;51.413892532;2021-12-08@08:50:07
+v2.0.8;multi;32;31623;;58800;24186;219516;56418;225672;401;90.740418873;2021-12-08@08:51:57
+v2.0.8;multi;32;31623;;58800;24186;219696;56439;225756;407;92.058291025;2021-12-08@08:53:32
+v2.0.8;multi;32;31623;;58800;24186;219404;56407;225628;407;92.057910792;2021-12-08@08:55:06
+v2.0.8;multi;32;31623;;58800;24186;219400;56454;225816;408;92.147867635;2021-12-08@08:56:40
+v2.0.8;multi;32;31623;;58800;24186;219516;56422;225688;407;92.059075489;2021-12-08@08:58:14
+v2.0.8;multi;32;31623;;58800;24186;219464;56448;225792;408;92.235867675;2021-12-08@08:59:49
+v2.0.8;multi;32;31623;;58800;24186;219820;56422;225688;407;92.062971760;2021-12-08@09:01:23
+v2.0.8;multi;32;31623;;58800;24186;219684;56458;225832;408;92.163197655;2021-12-08@09:02:57
+v2.0.8;imex;32;31623;;58800;24186;143972;37508;150032;239;53.774679488;2021-12-08@09:04:14
+v2.0.8;imex;32;31623;;58800;24186;143696;37491;149964;237;53.290428657;2021-12-08@09:05:09
+v2.0.8;imex;32;31623;;58800;24186;143900;37493;149972;236;53.082550593;2021-12-08@09:06:04
+v2.0.8;imex;32;31623;;58800;24186;143824;37539;150156;240;53.956441175;2021-12-08@09:07:00
+v2.0.8;imex;32;31623;;58800;24186;143620;37503;150012;236;53.063892848;2021-12-08@09:07:55
+v2.0.8;imex;32;31623;;58800;24186;143804;37497;149988;237;53.310853415;2021-12-08@09:08:50
+v2.0.8;imex;32;31623;;58800;24186;143736;37487;149948;239;53.768769316;2021-12-08@09:09:46
+v2.0.8;imex;32;31623;;58800;24186;143488;37487;149948;236;53.037211619;2021-12-08@09:10:41
+v2.0.8;single;32;31623;;58800;24186;26920;8188;32752;248;55.736633070;2021-12-08@09:11:59
+v2.0.8;single;32;31623;;58800;24186;26496;8218;32872;246;55.345376662;2021-12-08@09:12:56
+v2.0.8;single;32;31623;;58800;24186;26452;8203;32812;245;55.150783123;2021-12-08@09:13:53
+v2.0.8;single;32;31623;;58800;24186;26528;8201;32804;247;55.586255114;2021-12-08@09:14:51
+v2.0.8;single;32;31623;;58800;24186;26828;8224;32896;246;55.392899527;2021-12-08@09:15:48
+v2.0.8;single;32;31623;;58800;24186;26568;8213;32852;246;55.352709793;2021-12-08@09:16:46
+v2.0.8;single;32;31623;;58800;24186;26512;8212;32848;245;55.098677291;2021-12-08@09:17:42
+v2.0.8;single;32;31623;;58800;24186;26876;8250;33000;251;56.466797543;2021-12-08@09:18:41
+v2.0.8;mulimex;32;31623;;58800;24186;320124;81618;326472;402;90.828775110;2021-12-08@09:20:35
+v2.0.8;mulimex;32;31623;;58800;24186;320184;81579;326316;409;92.431039536;2021-12-08@09:22:10
+v2.0.8;mulimex;32;31623;;58800;24186;320260;81559;326236;414;93.547639538;2021-12-08@09:23:45
+v2.0.8;mulimex;32;31623;;58800;24186;320064;81571;326284;409;92.398306196;2021-12-08@09:25:20
+v2.0.8;mulimex;32;31623;;58800;24186;320328;81576;326304;409;92.358358164;2021-12-08@09:26:55
+v2.0.8;mulimex;32;31623;;58800;24186;319840;81548;326192;409;92.453979741;2021-12-08@09:28:29
+v2.0.8;mulimex;32;31623;;58800;24186;319872;81540;326160;409;92.411970574;2021-12-08@09:30:04
+v2.0.8;mulimex;32;31623;;58800;24186;319924;81569;326276;409;92.389010192;2021-12-08@09:31:39
+v2.0.8;multi;56;31623;;59758;24285;354688;90225;360900;715;161.882937029;2021-12-08@09:34:52
+v2.0.8;multi;56;31623;;59758;24285;354564;90209;360836;717;162.852905444;2021-12-08@09:37:37
+v2.0.8;multi;56;31623;;59758;24285;354656;90227;360908;719;162.936763656;2021-12-08@09:40:22
+v2.0.8;multi;56;31623;;59758;24285;354896;90267;361068;718;162.651194717;2021-12-08@09:43:06
+v2.0.8;multi;56;31623;;59758;24285;354808;90262;361048;717;162.643113485;2021-12-08@09:45:51
+v2.0.8;multi;56;31623;;59758;24285;354728;90229;360916;717;162.501436861;2021-12-08@09:48:35
+v2.0.8;multi;56;31623;;59758;24285;354612;90231;360924;715;162.355517930;2021-12-08@09:51:19
+v2.0.8;multi;56;31623;;59758;24285;354792;90236;360944;719;163.046735284;2021-12-08@09:54:04
+v2.0.8;imex;56;31623;;59758;24285;215568;55455;221820;414;93.411149256;2021-12-08@09:56:14
+v2.0.8;imex;56;31623;;59758;24285;215940;55529;222116;407;91.881835857;2021-12-08@09:57:48
+v2.0.8;imex;56;31623;;59758;24285;215916;55494;221976;408;92.077494877;2021-12-08@09:59:23
+v2.0.8;imex;56;31623;;59758;24285;215576;55475;221900;409;92.359334313;2021-12-08@10:00:57
+v2.0.8;imex;56;31623;;59758;24285;215524;55487;221948;409;92.385897340;2021-12-08@10:02:32
+v2.0.8;imex;56;31623;;59758;24285;215732;55497;221988;441;99.625898648;2021-12-08@10:04:14
+v2.0.8;imex;56;31623;;59758;24285;215648;55511;222044;406;91.681708219;2021-12-08@10:05:48
+v2.0.8;imex;56;31623;;59758;24285;215984;55486;221944;408;92.104395465;2021-12-08@10:07:23
+v2.0.8;single;56;31623;;59758;24285;28992;8831;35324;433;97.751392193;2021-12-08@10:09:37
+v2.0.8;single;56;31623;;59758;24285;29156;8814;35256;427;96.402729970;2021-12-08@10:11:16
+v2.0.8;single;56;31623;;59758;24285;29028;8788;35152;427;96.397286775;2021-12-08@10:12:54
+v2.0.8;single;56;31623;;59758;24285;29072;8852;35408;425;95.973269697;2021-12-08@10:14:33
+v2.0.8;single;56;31623;;59758;24285;28788;8754;35016;420;94.841135407;2021-12-08@10:16:10
+v2.0.8;single;56;31623;;59758;24285;28968;8787;35148;429;96.885858180;2021-12-08@10:17:49
+v2.0.8;single;56;31623;;59758;24285;29084;8815;35260;430;97.200190073;2021-12-08@10:19:28
+v2.0.8;single;56;31623;;59758;24285;29232;8816;35264;422;95.318085951;2021-12-08@10:21:05
+v2.0.8;mulimex;56;31623;;59758;24285;524208;132570;530280;716;161.948015906;2021-12-08@10:24:24
+v2.0.8;mulimex;56;31623;;59758;24285;524228;132589;530356;718;163.061667192;2021-12-08@10:27:10
+v2.0.8;mulimex;56;31623;;59758;24285;524176;132580;530320;720;162.980213869;2021-12-08@10:29:55
+v2.0.8;mulimex;56;31623;;59758;24285;524280;132610;530440;720;163.224117724;2021-12-08@10:32:42
+v2.0.8;mulimex;56;31623;;59758;24285;525116;132875;531500;720;163.238807927;2021-12-08@10:35:28
+v2.0.8;mulimex;56;31623;;59758;24285;524284;132589;530356;719;163.292320790;2021-12-08@10:38:14
+v2.0.8;mulimex;56;31623;;59758;24285;524116;132572;530288;720;163.067451590;2021-12-08@10:41:00
+v2.0.8;mulimex;56;31623;;59758;24285;524388;132639;530556;720;163.206057859;2021-12-08@10:43:47
+v2.0.8;multi;100;31623;;60390;24233;601344;151833;607332;1513;344.883511822;2021-12-08@10:50:26
+v2.0.8;multi;100;31623;;60390;24233;624400;157645;630580;1165;268.780763274;2021-12-08@10:56:03
+v2.0.8;multi;100;31623;;60390;24233;623584;157430;629720;1377;313.470390920;2021-12-08@11:01:21
+v2.0.8;multi;100;31623;;60390;24233;621912;156976;627904;1245;288.208275459;2021-12-08@11:07:17
+v2.0.8;multi;100;31623;;60390;24233;617444;155895;623580;1230;283.293392755;2021-12-08@11:13:09
+v2.0.8;multi;100;31623;;60390;24233;612280;154578;618312;1496;341.697738777;2021-12-08@11:18:54
+v2.0.8;multi;100;31623;;60390;24233;616216;155535;622140;1219;282.210077287;2021-12-08@11:24:44
+v2.0.8;multi;100;31623;;60390;24233;610368;154137;616548;1346;307.148932535;2021-12-08@11:29:55
+v2.0.8;imex;100;31623;;60390;24233;344400;87650;350600;756;171.078737269;2021-12-08@11:33:53
+v2.0.8;imex;100;31623;;60390;24233;344428;87661;350644;734;166.334170376;2021-12-08@11:36:42
+v2.0.8;imex;100;31623;;60390;24233;344884;87749;350996;734;166.429060944;2021-12-08@11:39:30
+v2.0.8;imex;100;31623;;60390;24233;343984;87595;350380;729;165.233216802;2021-12-08@11:42:18
+v2.0.8;imex;100;31623;;60390;24233;344632;87697;350788;733;166.079004371;2021-12-08@11:45:06
+v2.0.8;imex;100;31623;;60390;24233;344520;87649;350596;728;165.027959638;2021-12-08@11:47:53
+v2.0.8;imex;100;31623;;60390;24233;344444;87679;350716;734;166.313666528;2021-12-08@11:50:42
+v2.0.8;imex;100;31623;;60390;24233;344544;87714;350856;742;168.042208415;2021-12-08@11:53:33
+v2.0.8;single;100;31623;;60390;24233;32512;9612;38448;775;175.458575666;2021-12-08@11:57:31
+v2.0.8;single;100;31623;;60390;24233;31908;9567;38268;761;172.604947657;2021-12-08@12:00:25
+v2.0.8;single;100;31623;;60390;24233;32140;9563;38252;752;170.498530623;2021-12-08@12:03:17
+v2.0.8;single;100;31623;;60390;24233;31796;9534;38136;756;171.451913360;2021-12-08@12:06:10
+v2.0.8;single;100;31623;;60390;24233;32340;9575;38300;761;172.493975304;2021-12-08@12:09:04
+v2.0.8;single;100;31623;;60390;24233;32116;9537;38148;760;172.280034669;2021-12-08@12:11:58
+v2.0.8;single;100;31623;;60390;24233;32516;9591;38364;763;172.952830973;2021-12-08@12:14:53
+v2.0.8;single;100;31623;;60390;24233;32552;9629;38516;788;178.451672749;2021-12-08@12:17:53
+v2.0.8;mulimex;100;31623;;60390;24233;916628;230687;922748;1425;325.732562101;2021-12-08@12:24:19
+v2.0.8;mulimex;100;31623;;60390;24233;932556;234656;938624;1161;267.619864333;2021-12-08@12:29:55
+v2.0.8;mulimex;100;31623;;60390;24233;926924;233216;932864;1461;333.811069823;2021-12-08@12:35:32
+v2.0.8;mulimex;100;31623;;60390;24233;929632;233976;935904;1100;253.630645455;2021-12-08@12:40:54
+v2.0.8;mulimex;100;31623;;60390;24233;928928;233787;935148;1260;290.833195027;2021-12-08@12:46:53
+v2.0.8;mulimex;100;31623;;60390;24233;913212;229879;919516;1492;341.626259673;2021-12-08@12:52:38
+v2.0.8;mulimex;100;31623;;60390;24233;917728;230936;923744;1384;315.460346912;2021-12-08@12:57:56
+v2.0.8;mulimex;100;31623;;60390;24233;911564;229425;917700;1529;349.187945591;2021-12-08@13:03:49
+v2.0.8;multi;178;31623;;60806;24427;1036860;260700;1042800;2811;646.972937321;2021-12-08@13:16:10
+v2.0.8;multi;178;31623;;60806;24427;1115996;280495;1121980;2035;468.615300090;2021-12-08@13:30:04
+v2.0.8;multi;178;31623;;60806;24427;1057076;265842;1063368;2272;525.077433498;2021-12-08@13:41:04
+v2.0.8;multi;178;31623;;60806;24427;1054104;265004;1060016;1940;445.469762335;2021-12-08@13:50:44
+v2.0.8;multi;178;31623;;60806;24427;1086820;273284;1093136;2527;576.678661107;2021-12-08@14:00:24
+v2.0.8;multi;178;31623;;60806;24427;1059064;266319;1065276;2347;539.411727796;2021-12-08@14:11:38
+v2.0.8;multi;178;31623;;60806;24427;1081752;271998;1087992;2553;581.829601419;2021-12-08@14:21:23
+v2.0.8;multi;178;31623;;60806;24427;1036788;260791;1043164;2642;609.570901367;2021-12-08@14:32:43
+v2.0.8;imex;178;31623;;60806;24427;575180;145340;581360;1481;336.651069271;2021-12-08@14:40:17
+v2.0.8;imex;178;31623;;60806;24427;575636;145445;581780;1338;304.413725084;2021-12-08@14:45:24
+v2.0.8;imex;178;31623;;60806;24427;575072;145286;581144;1542;354.070235998;2021-12-08@14:51:21
+v2.0.8;imex;178;31623;;60806;24427;575116;145294;581176;1355;308.168328292;2021-12-08@14:56:31
+v2.0.8;imex;178;31623;;60806;24427;575432;145462;581848;1326;301.578053363;2021-12-08@15:01:35
+v2.0.8;imex;178;31623;;60806;24427;575928;145487;581948;1500;341.408838533;2021-12-08@15:07:19
+v2.0.8;imex;178;31623;;60806;24427;574992;145311;581244;1420;322.526401057;2021-12-08@15:12:44
+v2.0.8;imex;178;31623;;60806;24427;575280;145320;581280;1414;321.301434860;2021-12-08@15:18:08
+v2.0.8;single;178;31623;;60806;24427;38084;11030;44120;1515;345.289206830;2021-12-08@15:25:40
+v2.0.8;single;178;31623;;60806;24427;37424;10899;43596;1477;336.220475529;2021-12-08@15:31:18
+v2.0.8;single;178;31623;;60806;24427;37612;10900;43600;1598;366.610944496;2021-12-08@15:37:28
+v2.0.8;single;178;31623;;60806;24427;37036;10827;43308;1398;318.190836344;2021-12-08@15:42:48
+v2.0.8;single;178;31623;;60806;24427;37712;10921;43684;1415;321.954921245;2021-12-08@15:48:12
+v2.0.8;single;178;31623;;60806;24427;37636;10960;43840;1400;318.999584610;2021-12-08@15:53:33
+v2.0.8;single;178;31623;;60806;24427;37396;10804;43216;1373;312.399063378;2021-12-08@15:58:47
+v2.0.8;single;178;31623;;60806;24427;37112;10810;43240;1430;325.478678293;2021-12-08@16:04:15
+v2.0.8;mulimex;178;31623;;60806;24427;1653752;414960;1659840;2441;555.587873145;2021-12-08@16:15:18
+v2.0.8;mulimex;178;31623;;60806;24427;1593268;399833;1599332;2624;604.267722703;2021-12-08@16:25:27
+v2.0.8;mulimex;178;31623;;60806;24427;1587784;398538;1594152;2413;554.220212078;2021-12-08@16:34:46
+v2.0.8;mulimex;178;31623;;60806;24427;1557124;390857;1563428;2570;590.778817550;2021-12-08@16:44:42
+v2.0.8;mulimex;178;31623;;60806;24427;1597168;400850;1603400;2602;598.768923543;2021-12-08@16:54:46
+v2.0.8;mulimex;178;31623;;60806;24427;1599004;401244;1604976;1649;379.945126131;2021-12-08@17:04:46
+v2.0.8;mulimex;178;31623;;60806;24427;1584080;397580;1590320;2595;594.474870469;2021-12-08@17:14:45
+v2.0.8;mulimex;178;31623;;60806;24427;1591412;399453;1597812;2635;602.206602542;2021-12-08@17:24:53
+v2.0.8;multi;316;31623;;61364;24592;1817408;455885;1823540;4079;940.025651339;2021-12-08@17:43:31
+v2.0.8;multi;316;31623;;61364;24592;1817380;455894;1823576;4088;940.532223102;2021-12-08@17:59:17
+v2.0.8;multi;316;31623;;61364;24592;1817440;455922;1823688;4108;944.262149929;2021-12-08@18:15:07
+v2.0.8;multi;316;31623;;61364;24592;1817604;455939;1823756;4096;940.907790885;2021-12-08@18:30:54
+v2.0.8;multi;316;31623;;61364;24592;1817448;455894;1823576;4060;938.543177206;2021-12-08@18:46:39
+v2.0.8;multi;316;31623;;61364;24592;1817976;456045;1824180;4107;945.413934584;2021-12-08@19:02:31
+v2.0.8;multi;316;31623;;61364;24592;1817576;455937;1823748;4079;940.798451355;2021-12-08@19:18:17
+v2.0.8;multi;316;31623;;61364;24592;1847160;463294;1853176;3885;890.428946122;2021-12-08@19:44:11
+v2.0.8;imex;316;31623;;61364;24592;984560;247723;990892;2788;637.817606902;2021-12-08@19:58:29
+v2.0.8;imex;316;31623;;61364;24592;983444;247418;989672;2572;588.050905900;2021-12-08@20:08:21
+v2.0.8;imex;316;31623;;61364;24592;984100;247575;990300;2601;592.756215016;2021-12-08@20:18:16
+v2.0.8;imex;316;31623;;61364;24592;983544;247402;989608;2595;592.259221360;2021-12-08@20:28:11
+v2.0.8;imex;316;31623;;61364;24592;983388;247426;989704;2538;582.954021881;2021-12-08@20:37:56
+v2.0.8;imex;316;31623;;61364;24592;983320;247400;989600;2879;660.381515865;2021-12-08@20:48:59
+v2.0.8;imex;316;31623;;61364;24592;984616;247705;990820;2628;600.677377561;2021-12-08@20:59:02
+v2.0.8;imex;316;31623;;61364;24592;984460;247649;990596;2436;556.008854656;2021-12-08@21:08:21
+v2.0.8;single;316;31623;;61364;24592;43508;12472;49888;2884;657.314033765;2021-12-08@21:22:35
+v2.0.8;single;316;31623;;61364;24592;44604;12646;50584;2721;621.343520425;2021-12-08@21:32:58
+v2.0.8;single;316;31623;;61364;24592;44628;12665;50660;2978;684.008287181;2021-12-08@21:44:24
+v2.0.8;single;316;31623;;61364;24592;44908;12694;50776;2821;646.099908630;2021-12-08@21:55:13
+v2.0.8;single;316;31623;;61364;24592;44468;12599;50396;2640;601.153770946;2021-12-08@22:05:16
+v2.0.8;single;316;31623;;61364;24592;44252;12564;50256;2851;650.704807431;2021-12-08@23:00:27
+v2.0.8;single;316;31623;;61364;24592;43760;12441;49764;2778;633.012615648;2021-12-08@23:11:03
+v2.0.8;single;316;31623;;61364;24592;43780;12507;50028;2715;622.376185441;2021-12-08@23:21:30
+v2.0.8;mulimex;316;31623;;61364;24592;2738700;686198;2744792;4099;943.279600625;2021-12-08@23:40:30
+v2.0.8;mulimex;316;31623;;61364;24592;2738684;686236;2744944;4114;946.200502053;2021-12-08@23:56:25
+v2.0.8;mulimex;316;31623;;61364;24592;2740232;686610;2746440;4118;947.239619301;2021-12-09@00:12:22
+v2.0.8;mulimex;316;31623;;61364;24592;2740516;686654;2746616;4092;941.296457258;2021-12-09@00:28:13
+v2.0.8;mulimex;316;31623;;61364;24592;2739040;686269;2745076;4110;946.010538983;2021-12-09@00:44:08
+v2.0.8;mulimex;316;31623;;61364;24592;2738760;686210;2744840;4095;947.546542409;2021-12-09@01:00:05
+v2.0.8;mulimex;316;31623;;61364;24592;2739056;686287;2745148;4107;945.049324731;2021-12-09@01:16:00
+v2.0.8;mulimex;316;31623;;61364;24592;2739096;686292;2745168;3286;759.732133419;2021-12-09@01:31:57
+v2.0.8;multi;10;56234;;89496;27046;142696;37222;148888;187;43.076460176;2021-12-09@01:34:17
+v2.0.8;multi;10;56234;;89496;27046;142692;37217;148868;194;44.610942153;2021-12-09@01:35:05
+v2.0.8;multi;10;56234;;89496;27046;142788;37191;148764;193;44.413673811;2021-12-09@01:35:53
+v2.0.8;multi;10;56234;;89496;27046;142752;37212;148848;193;44.373585357;2021-12-09@01:36:41
+v2.0.8;multi;10;56234;;89496;27046;142580;37212;148848;193;44.385711186;2021-12-09@01:37:29
+v2.0.8;multi;10;56234;;89496;27046;142560;37221;148884;193;44.464580964;2021-12-09@01:38:17
+v2.0.8;multi;10;56234;;89496;27046;142652;37204;148816;193;44.421442437;2021-12-09@01:39:05
+v2.0.8;multi;10;56234;;89496;27046;142916;37200;148800;193;44.419207089;2021-12-09@01:39:53
+v2.0.8;imex;10;56234;;89496;27046;105644;27935;111740;100;22.747002538;2021-12-09@01:40:30
+v2.0.8;imex;10;56234;;89496;27046;105388;27918;111672;105;23.876284289;2021-12-09@01:40:57
+v2.0.8;imex;10;56234;;89496;27046;105468;27907;111628;102;23.225126913;2021-12-09@01:41:24
+v2.0.8;imex;10;56234;;89496;27046;105416;27891;111564;123;27.996070618;2021-12-09@01:41:56
+v2.0.8;imex;10;56234;;89496;27046;105440;27907;111628;106;24.117040667;2021-12-09@01:42:24
+v2.0.8;imex;10;56234;;89496;27046;105388;27937;111748;106;24.162428072;2021-12-09@01:42:51
+v2.0.8;imex;10;56234;;89496;27046;105340;27892;111568;101;22.983686177;2021-12-09@01:43:18
+v2.0.8;imex;10;56234;;89496;27046;105516;27945;111780;104;23.691121022;2021-12-09@01:43:46
+v2.0.8;single;10;56234;;89496;27046;34692;10169;40676;102;23.223283493;2021-12-09@01:44:23
+v2.0.8;single;10;56234;;89496;27046;34636;10185;40740;109;24.834617029;2021-12-09@01:44:51
+v2.0.8;single;10;56234;;89496;27046;34708;10197;40788;108;24.619898505;2021-12-09@01:45:19
+v2.0.8;single;10;56234;;89496;27046;34840;10184;40736;109;24.840966122;2021-12-09@01:45:48
+v2.0.8;single;10;56234;;89496;27046;34500;10189;40756;110;25.044417012;2021-12-09@01:46:17
+v2.0.8;single;10;56234;;89496;27046;34560;10245;40980;103;23.456869516;2021-12-09@01:46:44
+v2.0.8;single;10;56234;;89496;27046;34680;10208;40832;105;23.928176334;2021-12-09@01:47:11
+v2.0.8;single;10;56234;;89496;27046;34760;10202;40808;106;24.150051670;2021-12-09@01:47:39
+v2.0.8;mulimex;10;56234;;89496;27046;189712;49024;196096;188;43.298753541;2021-12-09@01:48:36
+v2.0.8;mulimex;10;56234;;89496;27046;189896;48995;195980;194;44.652787154;2021-12-09@01:49:24
+v2.0.8;mulimex;10;56234;;89496;27046;189904;49007;196028;194;44.645481839;2021-12-09@01:50:13
+v2.0.8;mulimex;10;56234;;89496;27046;189740;49000;196000;195;44.824372111;2021-12-09@01:51:01
+v2.0.8;mulimex;10;56234;;89496;27046;189940;49022;196088;194;44.643335669;2021-12-09@01:51:50
+v2.0.8;mulimex;10;56234;;89496;27046;190076;49002;196008;194;44.636194098;2021-12-09@01:52:38
+v2.0.8;mulimex;10;56234;;89496;27046;189788;49011;196044;195;44.867962880;2021-12-09@01:53:27
+v2.0.8;mulimex;10;56234;;89496;27046;189940;49000;196000;194;44.655484407;2021-12-09@01:54:15
+v2.0.8;multi;18;56234;;97395;27048;220856;56793;227172;365;84.242500491;2021-12-09@01:55:56
+v2.0.8;multi;18;56234;;97395;27048;220944;56797;227188;370;85.299469540;2021-12-09@01:57:25
+v2.0.8;multi;18;56234;;97395;27048;221188;56814;227256;371;85.548376086;2021-12-09@01:58:55
+v2.0.8;multi;18;56234;;97395;27048;221100;56785;227140;370;85.294809720;2021-12-09@02:00:24
+v2.0.8;multi;18;56234;;97395;27048;221248;56814;227256;370;85.313751432;2021-12-09@02:01:53
+v2.0.8;multi;18;56234;;97395;27048;220880;56800;227200;370;85.418382552;2021-12-09@02:03:23
+v2.0.8;multi;18;56234;;97395;27048;220896;56802;227208;371;85.577290839;2021-12-09@02:04:52
+v2.0.8;multi;18;56234;;97395;27048;220920;56825;227300;371;85.487229091;2021-12-09@02:06:22
+v2.0.8;imex;18;56234;;97395;27048;138196;36107;144428;174;39.863860580;2021-12-09@02:07:19
+v2.0.8;imex;18;56234;;97395;27048;138404;36103;144412;177;40.446869642;2021-12-09@02:08:03
+v2.0.8;imex;18;56234;;97395;27048;138384;36080;144320;170;38.827586332;2021-12-09@02:08:46
+v2.0.8;imex;18;56234;;97395;27048;138184;36077;144308;173;39.501912238;2021-12-09@02:09:29
+v2.0.8;imex;18;56234;;97395;27048;138212;36113;144452;167;38.114783504;2021-12-09@02:10:11
+v2.0.8;imex;18;56234;;97395;27048;138320;36096;144384;172;39.300459014;2021-12-09@02:10:54
+v2.0.8;imex;18;56234;;97395;27048;138060;36080;144320;172;39.272846939;2021-12-09@02:11:37
+v2.0.8;imex;18;56234;;97395;27048;138360;36110;144440;172;39.298479669;2021-12-09@02:12:20
+v2.0.8;single;18;56234;;97395;27048;38092;11076;44304;177;40.405539648;2021-12-09@02:13:17
+v2.0.8;single;18;56234;;97395;27048;38104;11088;44352;179;40.849114995;2021-12-09@02:14:02
+v2.0.8;single;18;56234;;97395;27048;38236;11085;44340;179;40.876602451;2021-12-09@02:14:46
+v2.0.8;single;18;56234;;97395;27048;38228;11092;44368;171;39.087449223;2021-12-09@02:15:29
+v2.0.8;single;18;56234;;97395;27048;38348;11091;44364;177;40.396962129;2021-12-09@02:16:13
+v2.0.8;single;18;56234;;97395;27048;38184;11086;44344;219;49.987324884;2021-12-09@02:17:07
+v2.0.8;single;18;56234;;97395;27048;38188;11098;44392;177;40.386130004;2021-12-09@02:17:51
+v2.0.8;single;18;56234;;97395;27048;38240;11095;44380;179;40.872568173;2021-12-09@02:18:35
+v2.0.8;mulimex;18;56234;;97395;27048;294980;75268;301072;368;84.825866604;2021-12-09@02:20:17
+v2.0.8;mulimex;18;56234;;97395;27048;294844;75261;301044;378;87.147814315;2021-12-09@02:21:48
+v2.0.8;mulimex;18;56234;;97395;27048;295148;75264;301056;372;85.749551801;2021-12-09@02:23:18
+v2.0.8;mulimex;18;56234;;97395;27048;294764;75250;301000;373;86.102422714;2021-12-09@02:24:48
+v2.0.8;mulimex;18;56234;;97395;27048;294928;75268;301072;372;85.750423733;2021-12-09@02:26:18
+v2.0.8;mulimex;18;56234;;97395;27048;294912;75279;301116;376;86.669508184;2021-12-09@02:27:48
+v2.0.8;mulimex;18;56234;;97395;27048;294756;75265;301060;372;85.807379445;2021-12-09@02:29:18
+v2.0.8;mulimex;18;56234;;97395;27048;294792;75240;300960;372;85.856400355;2021-12-09@02:30:48
+v2.0.8;multi;32;56234;;102105;27070;352820;89735;358940;679;156.724959930;2021-12-09@02:33:47
+v2.0.8;multi;32;56234;;102105;27070;352900;89768;359072;685;158.022130913;2021-12-09@02:36:29
+v2.0.8;multi;32;56234;;102105;27070;352724;89723;358892;682;157.578659868;2021-12-09@02:39:11
+v2.0.8;multi;32;56234;;102105;27070;352744;89750;359000;684;157.865235335;2021-12-09@02:41:53
+v2.0.8;multi;32;56234;;102105;27070;352952;89751;359004;683;157.586056618;2021-12-09@02:44:35
+v2.0.8;multi;32;56234;;102105;27070;352916;89739;358956;684;157.860741247;2021-12-09@02:47:17
+v2.0.8;multi;32;56234;;102105;27070;352768;89712;358848;684;157.830419550;2021-12-09@02:49:59
+v2.0.8;multi;32;56234;;102105;27070;353076;89768;359072;685;157.965420749;2021-12-09@02:52:41
+v2.0.8;imex;32;56234;;102105;27070;188048;48630;194520;301;68.873587314;2021-12-09@02:54:14
+v2.0.8;imex;32;56234;;102105;27070;188232;48612;194448;296;67.775865835;2021-12-09@02:55:25
+v2.0.8;imex;32;56234;;102105;27070;188232;48635;194540;296;67.713965671;2021-12-09@02:56:37
+v2.0.8;imex;32;56234;;102105;27070;188308;48595;194380;294;67.300473996;2021-12-09@02:57:48
+v2.0.8;imex;32;56234;;102105;27070;188196;48604;194416;297;67.966663623;2021-12-09@02:59:00
+v2.0.8;imex;32;56234;;102105;27070;188212;48593;194372;299;68.506095881;2021-12-09@03:00:12
+v2.0.8;imex;32;56234;;102105;27070;188260;48609;194436;296;67.765148938;2021-12-09@03:01:23
+v2.0.8;imex;32;56234;;102105;27070;188300;48624;194496;295;67.689075853;2021-12-09@03:02:35
+v2.0.8;single;32;56234;;102105;27070;41008;11845;47380;314;71.812278825;2021-12-09@03:04:09
+v2.0.8;single;32;56234;;102105;27070;41468;11856;47424;307;70.206675400;2021-12-09@03:05:23
+v2.0.8;single;32;56234;;102105;27070;41492;11852;47408;308;70.420815006;2021-12-09@03:06:37
+v2.0.8;single;32;56234;;102105;27070;41152;11852;47408;307;70.215297931;2021-12-09@03:07:51
+v2.0.8;single;32;56234;;102105;27070;41376;11871;47484;303;69.291348679;2021-12-09@03:09:04
+v2.0.8;single;32;56234;;102105;27070;41360;11840;47360;305;69.714203976;2021-12-09@03:10:18
+v2.0.8;single;32;56234;;102105;27070;41244;11849;47396;311;71.085526216;2021-12-09@03:11:32
+v2.0.8;single;32;56234;;102105;27070;41204;11869;47476;318;72.743029581;2021-12-09@03:12:49
+v2.0.8;mulimex;32;56234;;102105;27070;471504;119440;477760;682;157.544319694;2021-12-09@03:15:49
+v2.0.8;mulimex;32;56234;;102105;27070;471784;119493;477972;687;158.368983008;2021-12-09@03:18:32
+v2.0.8;mulimex;32;56234;;102105;27070;471812;119469;477876;687;158.386644390;2021-12-09@03:21:14
+v2.0.8;mulimex;32;56234;;102105;27070;471448;119446;477784;686;158.194186036;2021-12-09@03:23:57
+v2.0.8;mulimex;32;56234;;102105;27070;471520;119452;477808;686;158.401780461;2021-12-09@03:26:40
+v2.0.8;mulimex;32;56234;;102105;27070;471372;119407;477628;685;158.096997037;2021-12-09@03:29:22
+v2.0.8;mulimex;32;56234;;102105;27070;471640;119440;477760;687;158.530682276;2021-12-09@03:32:05
+v2.0.8;mulimex;32;56234;;102105;27070;471584;119483;477932;687;158.541124463;2021-12-09@03:34:48
+v2.0.8;multi;56;56234;;104619;27074;573900;145002;580008;1215;280.398077334;2021-12-09@03:40:00
+v2.0.8;multi;56;56234;;104619;27074;574072;145022;580088;1221;281.968479587;2021-12-09@03:44:47
+v2.0.8;multi;56;56234;;104619;27074;574052;145064;580256;1219;281.554420970;2021-12-09@03:49:33
+v2.0.8;multi;56;56234;;104619;27074;573896;145016;580064;1222;282.031923643;2021-12-09@03:54:20
+v2.0.8;multi;56;56234;;104619;27074;573760;145016;580064;1224;281.715512892;2021-12-09@03:59:06
+v2.0.8;multi;56;56234;;104619;27074;573972;145030;580120;1221;281.836097055;2021-12-09@04:03:53
+v2.0.8;multi;56;56234;;104619;27074;573812;144986;579944;1223;281.716253380;2021-12-09@04:08:39
+v2.0.8;multi;56;56234;;104619;27074;573896;144994;579976;1224;281.764882679;2021-12-09@04:13:26
+v2.0.8;imex;56;56234;;104619;27074;269332;68886;275544;535;122.765102225;2021-12-09@04:16:06
+v2.0.8;imex;56;56234;;104619;27074;269336;68875;275500;517;118.646660092;2021-12-09@04:18:08
+v2.0.8;imex;56;56234;;104619;27074;269236;68805;275220;512;117.515803180;2021-12-09@04:20:10
+v2.0.8;imex;56;56234;;104619;27074;269028;68830;275320;528;121.109350145;2021-12-09@04:22:15
+v2.0.8;imex;56;56234;;104619;27074;269552;68896;275584;514;117.963522291;2021-12-09@04:24:16
+v2.0.8;imex;56;56234;;104619;27074;268996;68817;275268;512;117.517070977;2021-12-09@04:26:18
+v2.0.8;imex;56;56234;;104619;27074;269356;68844;275376;511;117.291517129;2021-12-09@04:28:19
+v2.0.8;imex;56;56234;;104619;27074;269040;68836;275344;506;116.166008720;2021-12-09@04:30:19
+v2.0.8;single;56;56234;;104619;27074;44348;12626;50504;547;125.502251256;2021-12-09@04:33:01
+v2.0.8;single;56;56234;;104619;27074;44324;12620;50480;531;121.833247022;2021-12-09@04:35:07
+v2.0.8;single;56;56234;;104619;27074;44236;12633;50532;533;122.328728488;2021-12-09@04:37:13
+v2.0.8;single;56;56234;;104619;27074;44172;12622;50488;531;121.834201032;2021-12-09@04:39:18
+v2.0.8;single;56;56234;;104619;27074;44068;12602;50408;533;122.345329114;2021-12-09@04:41:24
+v2.0.8;single;56;56234;;104619;27074;44580;12638;50552;539;123.687200017;2021-12-09@04:43:31
+v2.0.8;single;56;56234;;104619;27074;44320;12611;50444;536;123.013284308;2021-12-09@04:45:38
+v2.0.8;single;56;56234;;104619;27074;44476;12642;50568;537;123.200488144;2021-12-09@04:47:45
+v2.0.8;mulimex;56;56234;;104619;27074;769032;193772;775088;1220;281.545428608;2021-12-09@04:53:03
+v2.0.8;mulimex;56;56234;;104619;27074;768960;193794;775176;1224;282.635176476;2021-12-09@04:57:51
+v2.0.8;mulimex;56;56234;;104619;27074;768900;193760;775040;1223;282.262223337;2021-12-09@05:02:38
+v2.0.8;mulimex;56;56234;;104619;27074;768984;193788;775152;1224;282.584243331;2021-12-09@05:07:26
+v2.0.8;mulimex;56;56234;;104619;27074;769200;193764;775056;1224;282.551595087;2021-12-09@05:12:14
+v2.0.8;mulimex;56;56234;;104619;27074;769172;193781;775124;1223;282.503140269;2021-12-09@05:17:01
+v2.0.8;mulimex;56;56234;;104619;27074;769276;193781;775124;1223;282.592751664;2021-12-09@05:21:49
+v2.0.8;mulimex;56;56234;;104619;27074;769024;193813;775252;1225;282.793893968;2021-12-09@05:26:37
+v2.0.8;multi;100;56234;;106396;27105;977892;246003;984012;2267;523.926696862;2021-12-09@05:36:14
+v2.0.8;multi;100;56234;;106396;27105;978152;246036;984144;2246;519.895864941;2021-12-09@05:45:00
+v2.0.8;multi;100;56234;;106396;27105;978028;245997;983988;2253;520.939267948;2021-12-09@05:53:46
+v2.0.8;multi;100;56234;;106396;27105;977692;245961;983844;2884;667.416313194;2021-12-09@06:05:00
+v2.0.8;multi;100;56234;;106396;27105;978300;246075;984300;2235;517.771739337;2021-12-09@06:13:43
+v2.0.8;multi;100;56234;;106396;27105;978076;246075;984300;2506;584.021092673;2021-12-09@06:23:33
+v2.0.8;multi;100;56234;;106396;27105;977724;246026;984104;2244;520.745145256;2021-12-09@06:32:19
+v2.0.8;multi;100;56234;;106396;27105;977984;246053;984212;2254;522.625676561;2021-12-09@06:41:08
+v2.0.8;imex;100;56234;;106396;27105;415120;105351;421404;954;219.437474200;2021-12-09@06:45:52
+v2.0.8;imex;100;56234;;106396;27105;414796;105291;421164;911;209.829620794;2021-12-09@06:49:26
+v2.0.8;imex;100;56234;;106396;27105;414812;105273;421092;904;208.213314639;2021-12-09@06:52:58
+v2.0.8;imex;100;56234;;106396;27105;414948;105270;421080;914;210.435671631;2021-12-09@06:56:33
+v2.0.8;imex;100;56234;;106396;27105;414520;105205;420820;907;208.992456957;2021-12-09@07:00:06
+v2.0.8;imex;100;56234;;106396;27105;415104;105332;421328;914;210.359145899;2021-12-09@07:03:40
+v2.0.8;imex;100;56234;;106396;27105;414936;105244;420976;916;210.990511293;2021-12-09@07:07:15
+v2.0.8;imex;100;56234;;106396;27105;415236;105322;421288;924;212.864787628;2021-12-09@07:10:52
+v2.0.8;single;100;56234;;106396;27105;48900;13689;54756;988;227.390902194;2021-12-09@07:15:39
+v2.0.8;single;100;56234;;106396;27105;48656;13705;54820;947;218.178643096;2021-12-09@07:19:21
+v2.0.8;single;100;56234;;106396;27105;48380;13698;54792;953;219.684324595;2021-12-09@07:23:04
+v2.0.8;single;100;56234;;106396;27105;48636;13725;54900;964;221.989491097;2021-12-09@07:26:50
+v2.0.8;single;100;56234;;106396;27105;48760;13733;54932;947;218.187075110;2021-12-09@07:30:32
+v2.0.8;single;100;56234;;106396;27105;48632;13708;54832;942;216.990504838;2021-12-09@07:34:13
+v2.0.8;single;100;56234;;106396;27105;48592;13712;54848;950;219.023368140;2021-12-09@07:37:55
+v2.0.8;single;100;56234;;106396;27105;48516;13691;54764;950;218.824297595;2021-12-09@07:41:38
+v2.0.8;mulimex;100;56234;;106396;27105;1312932;329766;1319064;2241;519.681333288;2021-12-09@07:51:17
+v2.0.8;mulimex;100;56234;;106396;27105;1312936;329759;1319036;2261;524.523725959;2021-12-09@08:00:08
+v2.0.8;mulimex;100;56234;;106396;27105;1316704;330724;1322896;2271;526.067791525;2021-12-09@08:09:00
+v2.0.8;mulimex;100;56234;;106396;27105;1313008;329725;1318900;2287;529.322695333;2021-12-09@08:17:56
+v2.0.8;mulimex;100;56234;;106396;27105;1312720;329748;1318992;2261;523.545375331;2021-12-09@08:26:46
+v2.0.8;mulimex;100;56234;;106396;27105;1312852;329770;1319080;2253;522.250997383;2021-12-09@08:35:34
+v2.0.8;mulimex;100;56234;;106396;27105;1312464;329681;1318724;2317;536.325125812;2021-12-09@08:44:37
+v2.0.8;mulimex;100;56234;;106396;27105;1313072;329739;1318956;1224;281.675648409;2021-12-09@08:53:27
+v2.0.8;multi;178;56234;;107469;27173;1699680;426462;1705848;4006;933.640679377;2021-12-09@09:10:35
+v2.0.8;multi;178;56234;;107469;27173;1691980;424509;1698036;4202;975.480960960;2021-12-09@09:30:38
+v2.0.8;multi;178;56234;;107469;27173;1692056;424544;1698176;4393;1022.885671784;2021-12-09@09:47:48
+v2.0.8;multi;178;56234;;107469;27173;1692112;424567;1698268;4216;980.464629212;2021-12-09@10:04:17
+v2.0.8;multi;178;56234;;107469;27173;1726688;433203;1732812;4271;990.155722730;2021-12-09@10:20:55
+v2.0.8;multi;178;56234;;107469;27173;1704360;427584;1710336;4171;967.545148543;2021-12-09@10:37:10
+v2.0.8;multi;178;56234;;107469;27173;1692696;424629;1698516;4215;979.130552574;2021-12-09@10:53:37
+v2.0.8;multi;178;56234;;107469;27173;1722412;432171;1728684;4198;972.045807733;2021-12-09@11:09:57
+v2.0.8;imex;178;56234;;107469;27173;670004;169039;676156;1776;410.361222060;2021-12-09@11:18:44
+v2.0.8;imex;178;56234;;107469;27173;671636;169440;677760;1800;415.415158479;2021-12-09@11:25:44
+v2.0.8;imex;178;56234;;107469;27173;669736;169005;676020;1682;388.352625214;2021-12-09@11:32:17
+v2.0.8;imex;178;56234;;107469;27173;669652;168949;675796;1636;378.256284325;2021-12-09@11:38:39
+v2.0.8;imex;178;56234;;107469;27173;670184;169133;676532;1638;378.576819179;2021-12-09@11:45:02
+v2.0.8;imex;178;56234;;107469;27173;670484;169175;676700;1685;389.622863147;2021-12-09@11:51:36
+v2.0.8;imex;178;56234;;107469;27173;670116;169067;676268;1645;380.161810258;2021-12-09@11:58:00
+v2.0.8;imex;178;56234;;107469;27173;670572;169206;676824;1670;386.420856463;2021-12-09@12:04:31
+v2.0.8;single;178;56234;;107469;27173;54052;14997;59988;1852;426.861549282;2021-12-09@12:13:23
+v2.0.8;single;178;56234;;107469;27173;53752;15012;60048;1713;395.364411427;2021-12-09@12:20:02
+v2.0.8;single;178;56234;;107469;27173;53760;14989;59956;1733;400.594933515;2021-12-09@12:26:46
+v2.0.8;single;178;56234;;107469;27173;53684;14977;59908;1694;391.627934647;2021-12-09@12:33:22
+v2.0.8;single;178;56234;;107469;27173;53544;14904;59616;1709;394.959460176;2021-12-09@12:40:00
+v2.0.8;single;178;56234;;107469;27173;53800;15024;60096;1722;397.759941738;2021-12-09@12:46:42
+v2.0.8;single;178;56234;;107469;27173;53860;15046;60184;1692;391.594794102;2021-12-09@12:53:17
+v2.0.8;single;178;56234;;107469;27173;53880;14950;59800;1692;390.680971967;2021-12-09@12:59:51
+v2.0.8;mulimex;178;56234;;107469;27173;2338648;586215;2344860;4023;936.017445147;2021-12-09@13:17:13
+v2.0.8;mulimex;178;56234;;107469;27173;2337864;585988;2343952;4041;940.990618366;2021-12-09@13:33:02
+v2.0.8;mulimex;178;56234;;107469;27173;2277424;570870;2283480;4039;936.371529079;2021-12-09@13:48:47
+v2.0.8;mulimex;178;56234;;107469;27173;2290092;574028;2296112;3700;865.724553463;2021-12-09@14:07:01
+v2.0.8;mulimex;178;56234;;107469;27173;2280928;571721;2286884;4165;975.368066359;2021-12-09@14:23:25
+v2.0.8;mulimex;178;56234;;107469;27173;2275232;570339;2281356;4037;942.583291360;2021-12-09@14:39:16
+v2.0.8;mulimex;178;56234;;107469;27173;2275076;570335;2281340;4059;948.040280054;2021-12-09@14:55:13
+v2.0.8;mulimex;178;56234;;107469;27173;2317016;580747;2322988;4008;934.728786925;2021-12-09@15:10:57
+v2.0.8;multi;316;56234;;108134;27288;2982428;747178;2988712;7069;1645.763423739;2021-12-09@15:41:16
+v2.0.8;multi;316;56234;;108134;27288;3021724;756982;3027928;7376;1723.483024098;2021-12-09@16:10:11
+v2.0.8;multi;316;56234;;108134;27288;2975452;745339;2981356;7056;1651.955468093;2021-12-09@16:37:54
+v2.0.8;multi;316;56234;;108134;27288;2954964;740284;2961136;7524;1755.771361504;2021-12-09@17:15:26
+v2.0.8;multi;316;56234;;108134;27288;3039412;761385;3045540;6925;1620.905531083;2021-12-09@17:52:38
+v2.0.8;multi;316;56234;;108134;27288;2954708;740192;2960768;7259;1695.924555217;2021-12-09@18:37:24
+v2.0.8;multi;316;56234;;108134;27288;2954412;740119;2960476;7262;1695.996853746;2021-12-09@19:05:52
+v2.0.8;multi;316;56234;;108134;27288;3008228;753575;3014300;6266;1465.627322403;2021-12-09@19:34:09
+v2.0.8;imex;316;56234;;108134;27288;1120464;281684;1126736;3427;796.432706981;2021-12-09@19:51:03
+v2.0.8;imex;316;56234;;108134;27288;1121164;281804;1127216;3342;772.079363080;2021-12-09@20:04:00
+v2.0.8;imex;316;56234;;108134;27288;1120360;281661;1126644;3204;742.032109509;2021-12-09@20:16:27
+v2.0.8;imex;316;56234;;108134;27288;1121228;281838;1127352;3501;810.484132004;2021-12-09@20:30:02
+v2.0.8;imex;316;56234;;108134;27288;1120212;281561;1126244;3423;790.724751814;2021-12-09@20:43:18
+v2.0.8;imex;316;56234;;108134;27288;1121304;281817;1127268;3132;724.962736472;2021-12-09@20:55:28
+v2.0.8;imex;316;56234;;108134;27288;1120072;281579;1126316;3276;756.808201315;2021-12-09@21:08:10
+v2.0.8;imex;316;56234;;108134;27288;1121312;281887;1127548;3423;791.096044598;2021-12-09@21:21:26
+v2.0.8;single;316;56234;;108134;27288;63192;17328;69312;3526;820.375490660;2021-12-09@21:38:21
+v2.0.8;single;316;56234;;108134;27288;62120;17071;68284;3767;872.360939351;2021-12-09@21:52:58
+v2.0.8;single;316;56234;;108134;27288;64692;17647;70588;3222;746.195466983;2021-12-09@22:05:28
+v2.0.8;single;316;56234;;108134;27288;64096;17519;70076;3208;743.262772134;2021-12-09@22:17:55
+v2.0.8;single;316;56234;;108134;27288;63044;17309;69236;3366;778.216515068;2021-12-09@22:30:57
+v2.0.8;single;316;56234;;108134;27288;63388;17308;69232;3357;777.614062224;2021-12-09@22:43:59
+v2.0.8;single;316;56234;;108134;27288;63032;17284;69136;3472;803.113882265;2021-12-09@22:57:26
+v2.0.8;single;316;56234;;108134;27288;63600;17364;69456;3264;756.433835243;2021-12-09@23:10:06
+v2.0.8;mulimex;316;56234;;108134;27288;4015060;1005320;4021280;6419;1498.673693895;2021-12-09@23:45:35
+v2.0.8;mulimex;316;56234;;108134;27288;4023336;1007386;4029544;7218;1693.209656559;2021-12-10@00:14:02
+v2.0.8;mulimex;316;56234;;108134;27288;4145892;1038037;4152148;7027;1647.495303583;2021-12-10@00:52:48
+v2.0.8;mulimex;316;56234;;108134;27288;4067076;1018274;4073096;6475;1515.253848630;2021-12-10@01:52:01
+v2.0.8;mulimex;316;56234;;108134;27288;4013040;1004824;4019296;7229;1691.493952292;2021-12-10@02:20:26
+v2.0.8;mulimex;316;56234;;108134;27288;4077188;1020810;4083240;7200;1691.526918780;2021-12-10@02:48:50
+v2.0.8;mulimex;316;56234;;108134;27288;3979960;996552;3986208;7216;1689.235991590;2021-12-10@03:17:14
+v2.0.8;mulimex;316;56234;;108134;27288;4064952;1017712;4070848;7188;1684.878327657;2021-12-10@03:45:32
+v2.0.8;multi;10;100000;;126751;52385;207180;53289;213156;256;61.422379061;2021-12-10@08:03:21
+v2.0.8;multi;10;100000;;126751;52385;207144;53317;213268;261;62.598339789;2021-12-10@08:04:27
+v2.0.8;multi;10;100000;;126751;52385;207032;53330;213320;263;63.008616201;2021-12-10@08:05:34
+v2.0.8;multi;10;100000;;126751;52385;206892;53313;213252;261;62.584602089;2021-12-10@08:06:40
+v2.0.8;multi;10;100000;;126751;52385;207060;53318;213272;262;62.757940658;2021-12-10@08:07:46
+v2.0.8;multi;10;100000;;126751;52385;207268;53372;213488;261;62.566563125;2021-12-10@08:08:53
+v2.0.8;multi;10;100000;;126751;52385;206964;53300;213200;261;62.595160233;2021-12-10@08:09:59
+v2.0.8;multi;10;100000;;126751;52385;207200;53363;213452;262;62.802329236;2021-12-10@08:11:06
+v2.0.8;imex;10;100000;;126751;52385;163032;42279;169116;163;38.670607040;2021-12-10@08:11:59
+v2.0.8;imex;10;100000;;126751;52385;162836;42296;169184;168;39.786845618;2021-12-10@08:12:43
+v2.0.8;imex;10;100000;;126751;52385;163364;42331;169324;166;39.358378706;2021-12-10@08:13:26
+v2.0.8;imex;10;100000;;126751;52385;163460;42382;169528;161;38.226190489;2021-12-10@08:14:08
+v2.0.8;imex;10;100000;;126751;52385;162920;42306;169224;166;39.361520596;2021-12-10@08:14:51
+v2.0.8;imex;10;100000;;126751;52385;163020;42303;169212;163;38.668722105;2021-12-10@08:15:34
+v2.0.8;imex;10;100000;;126751;52385;163000;42309;169236;163;38.630222251;2021-12-10@08:16:16
+v2.0.8;imex;10;100000;;126751;52385;163076;42274;169096;164;38.886226544;2021-12-10@08:16:59
+v2.0.8;single;10;100000;;126751;52385;47720;13496;53984;168;39.846843594;2021-12-10@08:17:54
+v2.0.8;single;10;100000;;126751;52385;48836;13737;54948;175;41.502038251;2021-12-10@08:18:39
+v2.0.8;single;10;100000;;126751;52385;48248;13613;54452;170;40.374474078;2021-12-10@08:19:23
+v2.0.8;single;10;100000;;126751;52385;48360;13602;54408;169;40.093415604;2021-12-10@08:20:07
+v2.0.8;single;10;100000;;126751;52385;48164;13594;54376;172;40.808377893;2021-12-10@08:20:51
+v2.0.8;single;10;100000;;126751;52385;48628;13649;54596;171;40.625741570;2021-12-10@08:21:36
+v2.0.8;single;10;100000;;126751;52385;48124;13611;54444;172;40.809039751;2021-12-10@08:22:20
+v2.0.8;single;10;100000;;126751;52385;48320;13623;54492;172;40.768748838;2021-12-10@08:23:04
+v2.0.8;mulimex;10;100000;;126751;52385;289288;73837;295348;257;61.657009484;2021-12-10@08:24:21
+v2.0.8;mulimex;10;100000;;126751;52385;289728;73964;295856;263;63.020400014;2021-12-10@08:25:28
+v2.0.8;mulimex;10;100000;;126751;52385;289948;74059;296236;262;62.793233488;2021-12-10@08:26:35
+v2.0.8;mulimex;10;100000;;126751;52385;289432;73931;295724;262;62.793702903;2021-12-10@08:27:42
+v2.0.8;mulimex;10;100000;;126751;52385;289612;73934;295736;262;62.771316175;2021-12-10@08:28:49
+v2.0.8;mulimex;10;100000;;126751;52385;289408;73867;295468;262;62.766908632;2021-12-10@08:29:55
+v2.0.8;mulimex;10;100000;;126751;52385;289184;73836;295344;262;62.795311186;2021-12-10@08:31:02
+v2.0.8;mulimex;10;100000;;126751;52385;289220;73862;295448;262;62.817272413;2021-12-10@08:32:09
+v2.0.8;multi;18;100000;;150528;52561;348400;88666;354664;540;130.489324272;2021-12-10@08:34:38
+v2.0.8;multi;18;100000;;150528;52561;348608;88660;354640;544;131.511207725;2021-12-10@08:36:54
+v2.0.8;multi;18;100000;;150528;52561;348680;88699;354796;545;131.566437230;2021-12-10@08:39:09
+v2.0.8;multi;18;100000;;150528;52561;348608;88663;354652;543;131.309395814;2021-12-10@08:41:25
+v2.0.8;multi;18;100000;;150528;52561;348844;88705;354820;543;131.294875974;2021-12-10@08:43:40
+v2.0.8;multi;18;100000;;150528;52561;348508;88681;354724;545;131.490671970;2021-12-10@08:45:55
+v2.0.8;multi;18;100000;;150528;52561;348424;88666;354664;545;131.502302428;2021-12-10@08:48:11
+v2.0.8;multi;18;100000;;150528;52561;348624;88687;354748;544;131.281956301;2021-12-10@08:50:26
+v2.0.8;imex;18;100000;;150528;52561;233052;59800;239200;287;68.101859285;2021-12-10@08:51:53
+v2.0.8;imex;18;100000;;150528;52561;232932;59756;239024;286;67.936193661;2021-12-10@08:53:05
+v2.0.8;imex;18;100000;;150528;52561;232692;59766;239064;286;67.894212419;2021-12-10@08:54:17
+v2.0.8;imex;18;100000;;150528;52561;232976;59748;238992;280;66.430536606;2021-12-10@08:55:27
+v2.0.8;imex;18;100000;;150528;52561;232712;59779;239116;292;69.312523916;2021-12-10@08:56:40
+v2.0.8;imex;18;100000;;150528;52561;233000;59786;239144;278;65.995881891;2021-12-10@08:57:50
+v2.0.8;imex;18;100000;;150528;52561;232784;59760;239040;282;66.905117325;2021-12-10@08:59:01
+v2.0.8;imex;18;100000;;150528;52561;232896;59776;239104;284;67.408694626;2021-12-10@09:00:12
+v2.0.8;single;18;100000;;150528;52561;57280;15824;63296;303;71.852114708;2021-12-10@09:01:42
+v2.0.8;single;18;100000;;150528;52561;56848;15818;63272;290;68.923846922;2021-12-10@09:02:55
+v2.0.8;single;18;100000;;150528;52561;57160;15822;63288;297;70.530116768;2021-12-10@09:04:09
+v2.0.8;single;18;100000;;150528;52561;57368;15846;63384;299;71.041915062;2021-12-10@09:05:24
+v2.0.8;single;18;100000;;150528;52561;57188;15830;63320;294;69.797519612;2021-12-10@09:06:37
+v2.0.8;single;18;100000;;150528;52561;57468;15876;63504;291;69.110833238;2021-12-10@09:07:50
+v2.0.8;single;18;100000;;150528;52561;57140;15847;63388;289;68.639002772;2021-12-10@09:09:02
+v2.0.8;single;18;100000;;150528;52561;57388;15846;63384;290;68.913569080;2021-12-10@09:10:15
+v2.0.8;mulimex;18;100000;;150528;52561;484416;122686;490744;540;130.553837694;2021-12-10@09:12:44
+v2.0.8;mulimex;18;100000;;150528;52561;484712;122676;490704;546;131.948042431;2021-12-10@09:15:00
+v2.0.8;mulimex;18;100000;;150528;52561;484424;122663;490652;546;132.018527845;2021-12-10@09:17:17
+v2.0.8;mulimex;18;100000;;150528;52561;484424;122686;490744;547;132.011198043;2021-12-10@09:19:33
+v2.0.8;mulimex;18;100000;;150528;52561;484524;122668;490672;547;132.034137333;2021-12-10@09:21:50
+v2.0.8;mulimex;18;100000;;150528;52561;484512;122680;490720;546;132.034952063;2021-12-10@09:24:06
+v2.0.8;mulimex;18;100000;;150528;52561;484608;122668;490672;545;131.694100866;2021-12-10@09:26:22
+v2.0.8;mulimex;18;100000;;150528;52561;484616;122655;490620;546;131.893197937;2021-12-10@09:28:38
+v2.0.8;multi;32;100000;;166748;52697;585500;147903;591612;1051;255.532629359;2021-12-10@09:33:18
+v2.0.8;multi;32;100000;;166748;52697;585464;147907;591628;1058;257.244346435;2021-12-10@09:37:40
+v2.0.8;multi;32;100000;;166748;52697;585556;147929;591716;1059;256.961497659;2021-12-10@09:42:01
+v2.0.8;multi;32;100000;;166748;52697;585472;147906;591624;1058;257.112199514;2021-12-10@09:46:23
+v2.0.8;multi;32;100000;;166748;52697;585676;147898;591592;1058;257.040912680;2021-12-10@09:50:45
+v2.0.8;multi;32;100000;;166748;52697;585416;147907;591628;1061;257.313266247;2021-12-10@09:55:07
+v2.0.8;multi;32;100000;;166748;52697;585332;147915;591660;1058;256.777926722;2021-12-10@09:59:28
+v2.0.8;multi;32;100000;;166748;52697;585756;147908;591632;1057;256.952081928;2021-12-10@10:03:50
+v2.0.8;imex;32;100000;;166748;52697;335240;85338;341352;506;120.751991642;2021-12-10@10:06:15
+v2.0.8;imex;32;100000;;166748;52697;334992;85336;341344;490;117.016551039;2021-12-10@10:08:16
+v2.0.8;imex;32;100000;;166748;52697;334944;85283;341132;486;115.992463342;2021-12-10@10:10:16
+v2.0.8;imex;32;100000;;166748;52697;335032;85300;341200;489;116.814684184;2021-12-10@10:12:17
+v2.0.8;imex;32;100000;;166748;52697;335028;85344;341376;510;121.783557333;2021-12-10@10:14:23
+v2.0.8;imex;32;100000;;166748;52697;334816;85284;341136;501;119.658040761;2021-12-10@10:16:26
+v2.0.8;imex;32;100000;;166748;52697;335148;85322;341288;497;118.590825695;2021-12-10@10:18:29
+v2.0.8;imex;32;100000;;166748;52697;334820;85317;341268;494;117.897587677;2021-12-10@10:20:31
+v2.0.8;single;32;100000;;166748;52697;64448;17675;70700;516;123.079794369;2021-12-10@10:22:58
+v2.0.8;single;32;100000;;166748;52697;64328;17686;70744;515;122.978158421;2021-12-10@10:25:05
+v2.0.8;single;32;100000;;166748;52697;64728;17699;70796;511;122.010874127;2021-12-10@10:27:11
+v2.0.8;single;32;100000;;166748;52697;64688;17656;70624;518;123.722731594;2021-12-10@10:29:18
+v2.0.8;single;32;100000;;166748;52697;64608;17704;70816;516;123.277272432;2021-12-10@10:31:25
+v2.0.8;single;32;100000;;166748;52697;64692;17693;70772;538;128.386620522;2021-12-10@10:33:37
+v2.0.8;single;32;100000;;166748;52697;64284;17685;70740;523;125.010457036;2021-12-10@10:35:46
+v2.0.8;single;32;100000;;166748;52697;64428;17712;70848;521;124.345744319;2021-12-10@10:37:54
+v2.0.8;mulimex;32;100000;;166748;52697;810824;204281;817124;1055;256.240882417;2021-12-10@10:42:34
+v2.0.8;mulimex;32;100000;;166748;52697;811256;204335;817340;1064;258.026130780;2021-12-10@10:46:57
+v2.0.8;mulimex;32;100000;;166748;52697;810872;204263;817052;1081;262.522433325;2021-12-10@10:51:24
+v2.0.8;mulimex;32;100000;;166748;52697;811256;204320;817280;1065;258.100905345;2021-12-10@10:55:47
+v2.0.8;mulimex;32;100000;;166748;52697;810972;204291;817164;1064;258.029904559;2021-12-10@11:00:10
+v2.0.8;mulimex;32;100000;;166748;52697;811280;204341;817364;1064;257.757754508;2021-12-10@11:04:33
+v2.0.8;mulimex;32;100000;;166748;52697;810812;204298;817192;1060;257.598729297;2021-12-10@11:08:55
+v2.0.8;mulimex;32;100000;;166748;52697;810768;204268;817072;1060;257.926354176;2021-12-10@11:13:18
+v2.0.8;multi;56;100000;;177146;52712;984564;247728;990912;1951;474.865680018;2021-12-10@11:21:47
+v2.0.8;multi;56;100000;;177146;52712;984792;247753;991012;1957;476.222394144;2021-12-10@11:29:49
+v2.0.8;multi;56;100000;;177146;52712;984952;247740;990960;1956;475.570635848;2021-12-10@11:37:50
+v2.0.8;multi;56;100000;;177146;52712;984900;247746;990984;1955;475.899522714;2021-12-10@11:45:51
+v2.0.8;multi;56;100000;;177146;52712;984628;247704;990816;1951;475.012637040;2021-12-10@11:53:52
+v2.0.8;multi;56;100000;;177146;52712;985000;247761;991044;1957;475.580521729;2021-12-10@12:01:53
+v2.0.8;multi;56;100000;;177146;52712;984672;247748;990992;1956;475.372066988;2021-12-10@12:09:53
+v2.0.8;multi;56;100000;;177146;52712;984836;247726;990904;1953;475.463190947;2021-12-10@12:17:54
+v2.0.8;imex;56;100000;;177146;52712;495208;125394;501576;898;215.066937792;2021-12-10@12:22:08
+v2.0.8;imex;56;100000;;177146;52712;495296;125334;501336;875;209.791672186;2021-12-10@12:25:42
+v2.0.8;imex;56;100000;;177146;52712;495128;125329;501316;873;209.367687161;2021-12-10@12:29:15
+v2.0.8;imex;56;100000;;177146;52712;495064;125323;501292;879;210.591852481;2021-12-10@12:32:50
+v2.0.8;imex;56;100000;;177146;52712;494948;125370;501480;876;209.979222487;2021-12-10@12:36:24
+v2.0.8;imex;56;100000;;177146;52712;495420;125388;501552;894;214.054642518;2021-12-10@12:40:02
+v2.0.8;imex;56;100000;;177146;52712;495308;125350;501400;878;210.553936800;2021-12-10@12:43:37
+v2.0.8;imex;56;100000;;177146;52712;495248;125352;501408;877;210.188968617;2021-12-10@12:47:11
+v2.0.8;single;56;100000;;177146;52712;71520;19403;77612;928;222.447862729;2021-12-10@12:51:30
+v2.0.8;single;56;100000;;177146;52712;71228;19345;77380;914;219.315697798;2021-12-10@12:55:13
+v2.0.8;single;56;100000;;177146;52712;71064;19374;77496;912;218.750667689;2021-12-10@12:58:56
+v2.0.8;single;56;100000;;177146;52712;71480;19388;77552;907;217.554823544;2021-12-10@13:02:37
+v2.0.8;single;56;100000;;177146;52712;71136;19358;77432;911;218.584815827;2021-12-10@13:06:19
+v2.0.8;single;56;100000;;177146;52712;71192;19369;77476;904;216.944834447;2021-12-10@13:10:00
+v2.0.8;single;56;100000;;177146;52712;71256;19381;77524;903;216.670268354;2021-12-10@13:13:40
+v2.0.8;single;56;100000;;177146;52712;71444;19349;77396;912;218.676539322;2021-12-10@13:17:23
+v2.0.8;mulimex;56;100000;;177146;52712;1359512;341403;1365612;1965;477.751909531;2021-12-10@13:25:57
+v2.0.8;mulimex;56;100000;;177146;52712;1359596;341468;1365872;1962;476.908165405;2021-12-10@13:34:00
+v2.0.8;mulimex;56;100000;;177146;52712;1359608;341465;1365860;1963;477.112198559;2021-12-10@13:42:04
+v2.0.8;mulimex;56;100000;;177146;52712;1359636;341419;1365676;1963;476.525478906;2021-12-10@13:50:07
+v2.0.8;mulimex;56;100000;;177146;52712;1359428;341396;1365584;1966;476.638603907;2021-12-10@13:58:09
+v2.0.8;mulimex;56;100000;;177146;52712;1359680;341480;1365920;1963;476.941676967;2021-12-10@14:06:13
+v2.0.8;mulimex;56;100000;;177146;52712;1359436;341431;1365724;1972;478.641511070;2021-12-10@14:14:17
+v2.0.8;mulimex;56;100000;;177146;52712;1359416;341414;1365656;1966;476.988056518;2021-12-10@14:22:21
+v2.0.8;multi;100;100000;;184114;52738;1713960;430068;1720272;3688;895.861507387;2021-12-10@14:38:11
+v2.0.8;multi;100;100000;;184114;52738;1713576;429937;1719748;3701;897.690082290;2021-12-10@14:53:16
+v2.0.8;multi;100;100000;;184114;52738;1713444;429927;1719708;3727;902.033207677;2021-12-10@15:08:26
+v2.0.8;multi;100;100000;;184114;52738;1713916;429976;1719904;3738;908.741144363;2021-12-10@15:23:42
+v2.0.8;multi;100;100000;;184114;52738;1714008;430015;1720060;3671;892.649549515;2021-12-10@15:38:42
+v2.0.8;multi;100;100000;;184114;52738;1713960;430016;1720064;3680;894.382012011;2021-12-10@15:53:44
+v2.0.8;multi;100;100000;;184114;52738;1713612;429948;1719792;3681;892.489340561;2021-12-10@16:08:44
+v2.0.8;multi;100;100000;;184114;52738;1713832;430002;1720008;3680;894.560041411;2021-12-10@16:23:46
+v2.0.8;imex;100;100000;;184114;52738;778156;196073;784292;1642;395.720583362;2021-12-10@16:31:28
+v2.0.8;imex;100;100000;;184114;52738;778344;196093;784372;1576;380.524516560;2021-12-10@16:37:53
+v2.0.8;imex;100;100000;;184114;52738;778224;196098;784392;1597;385.367538117;2021-12-10@16:44:23
+v2.0.8;imex;100;100000;;184114;52738;778408;196116;784464;1591;384.051018325;2021-12-10@16:50:51
+v2.0.8;imex;100;100000;;184114;52738;778596;196153;784612;1580;381.401205690;2021-12-10@16:57:17
+v2.0.8;imex;100;100000;;184114;52738;778212;196060;784240;1586;382.982382295;2021-12-10@17:03:45
+v2.0.8;imex;100;100000;;184114;52738;778436;196138;784552;1598;385.675210805;2021-12-10@17:10:14
+v2.0.8;imex;100;100000;;184114;52738;778044;196084;784336;1589;383.708026852;2021-12-10@17:16:42
+v2.0.8;single;100;100000;;184114;52738;78280;21122;84488;1719;414.137664009;2021-12-10@17:24:39
+v2.0.8;single;100;100000;;184114;52738;78364;21131;84524;1660;400.072047378;2021-12-10@17:31:23
+v2.0.8;single;100;100000;;184114;52738;78500;21173;84692;1652;398.091613145;2021-12-10@17:38:04
+v2.0.8;single;100;100000;;184114;52738;78536;21153;84612;1665;401.142907638;2021-12-10@17:44:49
+v2.0.8;single;100;100000;;184114;52738;78692;21221;84884;1655;398.961981207;2021-12-10@17:51:32
+v2.0.8;single;100;100000;;184114;52738;78540;21170;84680;1667;401.595647385;2021-12-10@17:58:17
+v2.0.8;single;100;100000;;184114;52738;78584;21170;84680;1733;417.163083625;2021-12-10@18:05:18
+v2.0.8;single;100;100000;;184114;52738;78460;21182;84728;1721;414.237359917;2021-12-10@18:12:16
+v2.0.8;mulimex;100;100000;;184114;52738;2360520;591716;2366864;3696;899.183164293;2021-12-10@18:28:17
+v2.0.8;mulimex;100;100000;;184114;52738;2360316;591657;2366628;3702;900.338327312;2021-12-10@18:43:26
+v2.0.8;mulimex;100;100000;;184114;52738;2360372;591623;2366492;3705;899.887592162;2021-12-10@18:58:35
+v2.0.8;mulimex;100;100000;;184114;52738;2359972;591564;2366256;3720;902.877746033;2021-12-10@19:13:46
+v2.0.8;mulimex;100;100000;;184114;52738;2360200;591648;2366592;3668;892.048996541;2021-12-10@19:28:47
+v2.0.8;mulimex;100;100000;;184114;52738;2360332;591607;2366428;2819;691.300006844;2021-12-10@19:43:51
+v2.0.8;mulimex;100;100000;;184114;52738;2360680;591670;2366680;3704;899.521566115;2021-12-10@19:59:00
+v2.0.8;mulimex;100;100000;;184114;52738;2360496;591655;2366620;3686;893.689240674;2021-12-10@20:14:02
+v2.0.8;multi;178;100000;;188000;52864;3018772;756202;3024808;6621;1622.367983252;2021-12-10@20:42:40
+v2.0.8;multi;178;100000;;188000;52864;2996336;750655;3002620;6889;1676.610774038;2021-12-10@21:10:48
+v2.0.8;multi;178;100000;;188000;52864;3005988;753029;3012116;5803;1423.152525213;2021-12-10@21:38:02
diff --git a/filter/config.Y b/filter/config.Y
index 8916ea97..15b77761 100644
--- a/filter/config.Y
+++ b/filter/config.Y
@@ -248,10 +248,6 @@ assert_assign(struct f_lval *lval, struct f_inst *expr, const char *start, const
setter = f_new_inst(FI_VAR_SET, expr, lval->sym);
getter = f_new_inst(FI_VAR_GET, lval->sym);
break;
- case F_LVAL_PREFERENCE:
- setter = f_new_inst(FI_PREF_SET, expr);
- getter = f_new_inst(FI_PREF_GET);
- break;
case F_LVAL_SA:
setter = f_new_inst(FI_RTA_SET, expr, lval->sa);
getter = f_new_inst(FI_RTA_GET, lval->sa);
@@ -290,7 +286,7 @@ CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN,
MIN, MAX,
EMPTY,
FILTER, WHERE, EVAL, ATTRIBUTE,
- BT_ASSERT, BT_TEST_SUITE, BT_CHECK_ASSIGN, BT_TEST_SAME, FORMAT)
+ BT_ASSERT, BT_TEST_SUITE, BT_CHECK_ASSIGN, BT_TEST_SAME, FORMAT, STACKS)
%nonassoc THEN
%nonassoc ELSE
@@ -314,6 +310,12 @@ CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN,
CF_GRAMMAR
+conf: FILTER STACKS expr expr ';' {
+ new_config->filter_vstk = $3;
+ new_config->filter_estk = $4;
+ }
+ ;
+
conf: filter_def ;
filter_def:
FILTER symbol { $2 = cf_define_symbol($2, SYM_FILTER, filter, NULL); cf_push_scope( $2 ); }
@@ -753,6 +755,7 @@ static_attr:
| IFNAME { $$ = f_new_static_attr(T_STRING, SA_IFNAME, 0); }
| IFINDEX { $$ = f_new_static_attr(T_INT, SA_IFINDEX, 1); }
| WEIGHT { $$ = f_new_static_attr(T_INT, SA_WEIGHT, 0); }
+ | PREFERENCE { $$ = f_new_static_attr(T_INT, SA_PREF, 0); }
| GW_MPLS { $$ = f_new_static_attr(T_INT, SA_GW_MPLS, 0); }
;
@@ -779,8 +782,6 @@ term:
| constant { $$ = $1; }
| constructor { $$ = $1; }
- | PREFERENCE { $$ = f_new_inst(FI_PREF_GET); }
-
| static_attr { $$ = f_new_inst(FI_RTA_GET, $1); }
| dynamic_attr { $$ = f_new_inst(FI_EA_GET, $1); }
@@ -878,9 +879,6 @@ cmd:
cf_error( "This static attribute is read-only.");
$$ = f_new_inst(FI_RTA_SET, $3, $1);
}
- | PREFERENCE '=' term ';' {
- $$ = f_new_inst(FI_PREF_SET, $3);
- }
| UNSET '(' dynamic_attr ')' ';' {
$$ = f_new_inst(FI_EA_UNSET, $3);
}
@@ -923,7 +921,6 @@ get_cf_position:
lvalue:
CF_SYM_KNOWN { cf_assert_symbol($1, SYM_VARIABLE); $$ = (struct f_lval) { .type = F_LVAL_VARIABLE, .sym = $1 }; }
- | PREFERENCE { $$ = (struct f_lval) { .type = F_LVAL_PREFERENCE }; }
| static_attr { $$ = (struct f_lval) { .type = F_LVAL_SA, .sa = $1 }; }
| dynamic_attr { $$ = (struct f_lval) { .type = F_LVAL_EA, .da = $1 }; };
diff --git a/filter/data.h b/filter/data.h
index bb815c34..6d4ca333 100644
--- a/filter/data.h
+++ b/filter/data.h
@@ -100,6 +100,7 @@ enum f_sa_code {
SA_IFNAME,
SA_IFINDEX,
SA_WEIGHT,
+ SA_PREF,
SA_GW_MPLS,
} PACKED;
diff --git a/filter/decl.m4 b/filter/decl.m4
index 5242c04c..44537aaa 100644
--- a/filter/decl.m4
+++ b/filter/decl.m4
@@ -32,6 +32,7 @@ m4_divert(-1)m4_dnl
#
# 101 content of per-inst struct
# 102 constructor arguments
+# 110 constructor attributes
# 103 constructor body
# 104 dump line item content
# (there may be nothing in dump-line content and
@@ -45,6 +46,7 @@ m4_divert(-1)m4_dnl
# Here are macros to allow you to _divert to the right directions.
m4_define(FID_STRUCT_IN, `m4_divert(101)')
m4_define(FID_NEW_ARGS, `m4_divert(102)')
+m4_define(FID_NEW_ATTRIBUTES, `m4_divert(110)')
m4_define(FID_NEW_BODY, `m4_divert(103)')
m4_define(FID_DUMP_BODY, `m4_divert(104)m4_define([[FID_DUMP_BODY_EXISTS]])')
m4_define(FID_LINEARIZE_BODY, `m4_divert(105)')
@@ -106,15 +108,18 @@ FID_STRUCT_IN()m4_dnl
struct f_inst * f$1;
FID_NEW_ARGS()m4_dnl
, struct f_inst * f$1
+FID_NEW_ATTRIBUTES()m4_dnl
+NONNULL(m4_eval($1+1))
FID_NEW_BODY()m4_dnl
whati->f$1 = f$1;
-for (const struct f_inst *child = f$1; child; child = child->next) {
- what->size += child->size;
+const struct f_inst *child$1 = f$1;
+do {
+ what->size += child$1->size;
FID_IFCONST([[
- if (child->fi_code != FI_CONSTANT)
+ if (child$1->fi_code != FI_CONSTANT)
constargs = 0;
]])
-}
+} while (child$1 = child$1->next);
FID_LINEARIZE_BODY
pos = linearize(dest, whati->f$1, pos);
FID_INTERPRET_BODY()')
@@ -190,6 +195,7 @@ FID_INTERPRET_BODY()')
# that was needed in the former implementation.
m4_define(LINEX, `FID_INTERPRET_EXEC()LINEX_($1)FID_INTERPRET_NEW()return $1 FID_INTERPRET_BODY()')
m4_define(LINEX_, `do {
+ if (fstk->ecnt + 1 >= fstk->elen) runtime("Filter execution stack overflow");
fstk->estk[fstk->ecnt].pos = 0;
fstk->estk[fstk->ecnt].line = $1;
fstk->estk[fstk->ecnt].ventry = fstk->vcnt;
@@ -227,7 +233,7 @@ FID_INTERPRET_BODY()')
# state the result and put it to the right place.
m4_define(RESULT, `RESULT_TYPE([[$1]]) RESULT_([[$1]],[[$2]],[[$3]])')
m4_define(RESULT_, `RESULT_VAL([[ (struct f_val) { .type = $1, .val.$2 = $3 } ]])')
-m4_define(RESULT_VAL, `FID_HIC(, [[do { res = $1; fstk->vcnt++; } while (0)]],
+m4_define(RESULT_VAL, `FID_HIC(, [[do { res = $1; f_vcnt_check_overflow(1); fstk->vcnt++; } while (0)]],
[[return fi_constant(what, $1)]])')
m4_define(RESULT_VOID, `RESULT_VAL([[ (struct f_val) { .type = T_VOID } ]])')
@@ -309,7 +315,9 @@ m4_undivert(107)m4_dnl
FID_NEW()m4_dnl Constructor and interpreter code together
FID_HIC(
[[m4_dnl Public declaration of constructor in H file
-struct f_inst *f_new_inst_]]INST_NAME()[[(enum f_instruction_code fi_code
+struct f_inst *
+m4_undivert(110)m4_dnl
+f_new_inst_]]INST_NAME()[[(enum f_instruction_code fi_code
m4_undivert(102)m4_dnl
);]],
[[m4_dnl The one case in The Big Switch inside interpreter
@@ -321,7 +329,9 @@ m4_undivert(102)m4_dnl
break;
]],
[[m4_dnl Constructor itself
-struct f_inst *f_new_inst_]]INST_NAME()[[(enum f_instruction_code fi_code
+struct f_inst *
+m4_undivert(110)m4_dnl
+f_new_inst_]]INST_NAME()[[(enum f_instruction_code fi_code
m4_undivert(102)m4_dnl
)
{
diff --git a/filter/f-inst.c b/filter/f-inst.c
index 901d2939..b4795357 100644
--- a/filter/f-inst.c
+++ b/filter/f-inst.c
@@ -519,20 +519,21 @@
{
STATIC_ATTR;
ACCESS_RTE;
- struct rta *rta = (*fs->rte)->attrs;
+ struct rta *rta = fs->rte->attrs;
switch (sa.sa_code)
{
case SA_FROM: RESULT(sa.f_type, ip, rta->from); break;
case SA_GW: RESULT(sa.f_type, ip, rta->nh.gw); break;
- case SA_NET: RESULT(sa.f_type, net, (*fs->rte)->net->n.addr); break;
- case SA_PROTO: RESULT(sa.f_type, s, rta->src->proto->name); break;
+ case SA_NET: RESULT(sa.f_type, net, fs->rte->net); break;
+ case SA_PROTO: RESULT(sa.f_type, s, fs->rte->src->owner->name); break;
case SA_SOURCE: RESULT(sa.f_type, i, rta->source); break;
case SA_SCOPE: RESULT(sa.f_type, i, rta->scope); break;
case SA_DEST: RESULT(sa.f_type, i, rta->dest); break;
case SA_IFNAME: RESULT(sa.f_type, s, rta->nh.iface ? rta->nh.iface->name : ""); break;
case SA_IFINDEX: RESULT(sa.f_type, i, rta->nh.iface ? rta->nh.iface->index : 0); break;
case SA_WEIGHT: RESULT(sa.f_type, i, rta->nh.weight + 1); break;
+ case SA_PREF: RESULT(sa.f_type, i, rta->pref); break;
case SA_GW_MPLS: RESULT(sa.f_type, i, rta->nh.labels ? rta->nh.label[0] : MPLS_NULL); break;
default:
@@ -549,7 +550,7 @@
f_rta_cow(fs);
{
- struct rta *rta = (*fs->rte)->attrs;
+ struct rta *rta = fs->rte->attrs;
switch (sa.sa_code)
{
@@ -561,7 +562,8 @@
{
ip_addr ip = v1.val.ip;
struct iface *ifa = ipa_is_link_local(ip) ? rta->nh.iface : NULL;
- neighbor *n = neigh_find(rta->src->proto, ip, ifa, 0);
+ /* XXX this code supposes that every owner is a protocol XXX */
+ neighbor *n = neigh_find(SKIP_BACK(struct proto, sources, fs->rte->src->owner), ip, ifa, 0);
if (!n || (n->scope == SCOPE_HOST))
runtime( "Invalid gw address" );
@@ -637,6 +639,10 @@
}
break;
+ case SA_PREF:
+ rta->pref = v1.val.i;
+ break;
+
default:
bug("Invalid static attribute access (%u/%u)", sa.f_type, sa.sa_code);
}
@@ -804,20 +810,6 @@
}
}
- INST(FI_PREF_GET, 0, 1) {
- ACCESS_RTE;
- RESULT(T_INT, i, (*fs->rte)->pref);
- }
-
- INST(FI_PREF_SET, 1, 0) {
- ACCESS_RTE;
- ARG(1,T_INT);
- if (v1.val.i > 0xFFFF)
- runtime( "Setting preference value out of bounds" );
- f_rte_cow(fs);
- (*fs->rte)->pref = v1.val.i;
- }
-
INST(FI_LENGTH, 1, 1) { /* Get length of */
ARG_ANY(1);
switch(v1.type) {
@@ -1103,6 +1095,7 @@
curline.vbase = curline.ventry;
/* Storage for local variables */
+ f_vcnt_check_overflow(sym->function->vars);
memset(&(fstk->vstk[fstk->vcnt]), 0, sizeof(struct f_val) * sym->function->vars);
fstk->vcnt += sym->function->vars;
}
@@ -1319,10 +1312,10 @@
INST(FI_ROA_CHECK_IMPLICIT, 0, 1) { /* ROA Check */
NEVER_CONSTANT;
RTC(1);
- struct rtable *table = rtc->table;
+ rtable *table = rtc->table;
ACCESS_RTE;
ACCESS_EATTRS;
- const net_addr *net = (*fs->rte)->net->n.addr;
+ const net_addr *net = fs->rte->net;
/* We ignore temporary attributes, probably not a problem here */
/* 0x02 is a value of BA_AS_PATH, we don't want to include BGP headers */
@@ -1351,7 +1344,7 @@
ARG(1, T_NET);
ARG(2, T_INT);
RTC(3);
- struct rtable *table = rtc->table;
+ rtable *table = rtc->table;
u32 as = v2.val.i;
diff --git a/filter/filter.c b/filter/filter.c
index e505d570..625b3ade 100644
--- a/filter/filter.c
+++ b/filter/filter.c
@@ -50,36 +50,31 @@ enum f_exception {
FE_RETURN = 0x1,
};
-
-struct filter_stack {
- /* Value stack for execution */
-#define F_VAL_STACK_MAX 4096
- uint vcnt; /* Current value stack size; 0 for empty */
- uint ecnt; /* Current execute stack size; 0 for empty */
-
- struct f_val vstk[F_VAL_STACK_MAX]; /* The stack itself */
-
- /* Instruction stack for execution */
-#define F_EXEC_STACK_MAX 4096
- struct {
- const struct f_line *line; /* The line that is being executed */
- uint pos; /* Instruction index in the line */
- uint ventry; /* Value stack depth on entry */
- uint vbase; /* Where to index variable positions from */
- enum f_exception emask; /* Exception mask */
- } estk[F_EXEC_STACK_MAX];
+struct filter_exec_stack {
+ const struct f_line *line; /* The line that is being executed */
+ uint pos; /* Instruction index in the line */
+ uint ventry; /* Value stack depth on entry */
+ uint vbase; /* Where to index variable positions from */
+ enum f_exception emask; /* Exception mask */
};
/* Internal filter state, to be allocated on stack when executing filters */
struct filter_state {
/* Stacks needed for execution */
- struct filter_stack *stack;
+ struct filter_stack {
+ /* Current filter stack depth */
- /* The route we are processing. This may be NULL to indicate no route available. */
- struct rte **rte;
+ /* Value stack */
+ uint vcnt, vlen;
+ struct f_val *vstk;
- /* The old rta to be freed after filters are done. */
- struct rta *old_rta;
+ /* Instruction stack for execution */
+ uint ecnt, elen;
+ struct filter_exec_stack *estk;
+ } stack;
+
+ /* The route we are processing. This may be NULL to indicate no route available. */
+ struct rte *rte;
/* Cached pointer to ea_list */
struct ea_list **eattrs;
@@ -95,21 +90,16 @@ struct filter_state {
};
_Thread_local static struct filter_state filter_state;
-_Thread_local static struct filter_stack filter_stack;
void (*bt_assert_hook)(int result, const struct f_line_item *assert);
-static inline void f_cache_eattrs(struct filter_state *fs)
-{
- fs->eattrs = &((*fs->rte)->attrs->eattrs);
-}
+#define _f_stack_init(fs, px, def) ((fs).stack.px##stk = alloca(sizeof(*(fs).stack.px##stk) * ((fs).stack.px##len = (config && config->filter_##px##stk) ? config->filter_##px##stk : (def))))
-static inline void f_rte_cow(struct filter_state *fs)
-{
- if (!((*fs->rte)->flags & REF_COW))
- return;
+#define f_stack_init(fs) ( _f_stack_init(fs, v, 128), _f_stack_init(fs, e, 128) )
- *fs->rte = rte_cow(*fs->rte);
+static inline void f_cache_eattrs(struct filter_state *fs)
+{
+ fs->eattrs = &(fs->rte->attrs->eattrs);
}
/*
@@ -118,22 +108,16 @@ static inline void f_rte_cow(struct filter_state *fs)
static void
f_rta_cow(struct filter_state *fs)
{
- if (!rta_is_cached((*fs->rte)->attrs))
+ if (!rta_is_cached(fs->rte->attrs))
return;
- /* Prepare to modify rte */
- f_rte_cow(fs);
-
- /* Store old rta to free it later, it stores reference from rte_cow() */
- fs->old_rta = (*fs->rte)->attrs;
-
/*
* Get shallow copy of rta. Fields eattrs and nexthops of rta are shared
* with fs->old_rta (they will be copied when the cached rta will be obtained
* at the end of f_run()), also the lock of hostentry is inherited (we
* suppose hostentry is not changed by filters).
*/
- (*fs->rte)->attrs = rta_do_cow((*fs->rte)->attrs, fs->pool);
+ fs->rte->attrs = rta_do_cow(fs->rte->attrs, fs->pool);
/* Re-cache the ea_list */
f_cache_eattrs(fs);
@@ -163,15 +147,17 @@ interpret(struct filter_state *fs, const struct f_line *line, struct f_val *val)
ASSERT(line->args == 0);
/* Initialize the filter stack */
- struct filter_stack *fstk = fs->stack;
+ struct filter_stack *fstk = &fs->stack;
fstk->vcnt = line->vars;
memset(fstk->vstk, 0, sizeof(struct f_val) * line->vars);
/* The same as with the value stack. Not resetting the stack for performance reasons. */
fstk->ecnt = 1;
- fstk->estk[0].line = line;
- fstk->estk[0].pos = 0;
+ fstk->estk[0] = (struct filter_exec_stack) {
+ .line = line,
+ .pos = 0,
+ };
#define curline fstk->estk[fstk->ecnt-1]
@@ -191,6 +177,8 @@ interpret(struct filter_state *fs, const struct f_line *line, struct f_val *val)
#define v2 vv(1)
#define v3 vv(2)
+#define f_vcnt_check_overflow(n) do { if (fstk->vcnt + n >= fstk->vlen) runtime("Filter execution stack overflow"); } while (0)
+
#define runtime(fmt, ...) do { \
if (!(fs->flags & FF_SILENT)) \
log_rl(&rl_runtime_err, L_ERR "filters, line %d: " fmt, what->lineno, ##__VA_ARGS__); \
@@ -241,29 +229,15 @@ interpret(struct filter_state *fs, const struct f_line *line, struct f_val *val)
/**
* f_run - run a filter for a route
* @filter: filter to run
- * @rte: route being filtered, may be modified
+ * @rte: route being filtered, must be write-able
* @tmp_pool: all filter allocations go from this pool
* @flags: flags
*
- * If filter needs to modify the route, there are several
- * posibilities. @rte might be read-only (with REF_COW flag), in that
- * case rw copy is obtained by rte_cow() and @rte is replaced. If
- * @rte is originally rw, it may be directly modified (and it is never
- * copied).
- *
- * The returned rte may reuse the (possibly cached, cloned) rta, or
- * (if rta was modified) contains a modified uncached rta, which
- * uses parts allocated from @tmp_pool and parts shared from original
- * rta. There is one exception - if @rte is rw but contains a cached
- * rta and that is modified, rta in returned rte is also cached.
- *
- * Ownership of cached rtas is consistent with rte, i.e.
- * if a new rte is returned, it has its own clone of cached rta
- * (and cached rta of read-only source rte is intact), if rte is
- * modified in place, old cached rta is possibly freed.
+ * If @rte->attrs is cached, the returned rte allocates a new rta on
+ * tmp_pool, otherwise the filters may modify it.
*/
enum filter_return
-f_run(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, int flags)
+f_run(const struct filter *filter, struct rte *rte, struct linpool *tmp_pool, int flags)
{
if (filter == FILTER_ACCEPT)
return F_ACCEPT;
@@ -271,48 +245,22 @@ f_run(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, i
if (filter == FILTER_REJECT)
return F_REJECT;
- int rte_cow = ((*rte)->flags & REF_COW);
DBG( "Running filter `%s'...", filter->name );
/* Initialize the filter state */
filter_state = (struct filter_state) {
- .stack = &filter_stack,
.rte = rte,
.pool = tmp_pool,
.flags = flags,
};
+ f_stack_init(filter_state);
+
LOG_BUFFER_INIT(filter_state.buf);
/* Run the interpreter itself */
enum filter_return fret = interpret(&filter_state, filter->root, NULL);
- if (filter_state.old_rta) {
- /*
- * Cached rta was modified and filter_state->rte contains now an uncached one,
- * sharing some part with the cached one. The cached rta should
- * be freed (if rte was originally COW, filter_state->old_rta is a clone
- * obtained during rte_cow()).
- *
- * This also implements the exception mentioned in f_run()
- * description. The reason for this is that rta reuses parts of
- * filter_state->old_rta, and these may be freed during rta_free(filter_state->old_rta).
- * This is not the problem if rte was COW, because original rte
- * also holds the same rta.
- */
- if (!rte_cow) {
- /* Cache the new attrs */
- (*filter_state.rte)->attrs = rta_lookup((*filter_state.rte)->attrs);
-
- /* Drop cached ea_list pointer */
- filter_state.eattrs = NULL;
- }
-
- /* Uncache the old attrs and drop the pointer as it is invalid now. */
- rta_free(filter_state.old_rta);
- filter_state.old_rta = NULL;
- }
-
/* Process the filter output, log it and return */
if (fret < F_ACCEPT) {
if (!(filter_state.flags & FF_SILENT))
@@ -337,18 +285,18 @@ f_run(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, i
*/
enum filter_return
-f_eval_rte(const struct f_line *expr, struct rte **rte, struct linpool *tmp_pool)
+f_eval_rte(const struct f_line *expr, struct rte *rte, struct linpool *tmp_pool)
{
filter_state = (struct filter_state) {
- .stack = &filter_stack,
.rte = rte,
.pool = tmp_pool,
};
+ f_stack_init(filter_state);
+
LOG_BUFFER_INIT(filter_state.buf);
- ASSERT(!((*rte)->flags & REF_COW));
- ASSERT(!rta_is_cached((*rte)->attrs));
+ ASSERT(!rta_is_cached(rte->attrs));
return interpret(&filter_state, expr, NULL);
}
@@ -363,10 +311,11 @@ enum filter_return
f_eval(const struct f_line *expr, struct linpool *tmp_pool, struct f_val *pres)
{
filter_state = (struct filter_state) {
- .stack = &filter_stack,
.pool = tmp_pool,
};
+ f_stack_init(filter_state);
+
LOG_BUFFER_INIT(filter_state.buf);
enum filter_return fret = interpret(&filter_state, expr, pres);
@@ -383,10 +332,11 @@ f_eval_int(const struct f_line *expr)
{
/* Called independently in parse-time to eval expressions */
filter_state = (struct filter_state) {
- .stack = &filter_stack,
.pool = cfg_mem,
};
+ f_stack_init(filter_state);
+
struct f_val val;
LOG_BUFFER_INIT(filter_state.buf);
@@ -475,6 +425,23 @@ filter_commit(struct config *new, struct config *old)
}
}
+void channel_filter_dump(const struct filter *f)
+{
+ if (f == FILTER_ACCEPT)
+ debug(" ALL");
+ else if (f == FILTER_REJECT)
+ debug(" NONE");
+ else if (f == FILTER_UNDEF)
+ debug(" UNDEF");
+ else if (f->sym) {
+ ASSERT(f->sym->filter == f);
+ debug(" named filter %s", f->sym->name);
+ } else {
+ debug("\n");
+ f_dump_line(f->root, 2);
+ }
+}
+
void filters_dump_all(void)
{
struct symbol *sym;
@@ -494,19 +461,10 @@ void filters_dump_all(void)
struct channel *c;
WALK_LIST(c, sym->proto->proto->channels) {
debug(" Channel %s (%s) IMPORT", c->name, net_label[c->net_type]);
- if (c->in_filter == FILTER_ACCEPT)
- debug(" ALL\n");
- else if (c->in_filter == FILTER_REJECT)
- debug(" NONE\n");
- else if (c->in_filter == FILTER_UNDEF)
- debug(" UNDEF\n");
- else if (c->in_filter->sym) {
- ASSERT(c->in_filter->sym->filter == c->in_filter);
- debug(" named filter %s\n", c->in_filter->sym->name);
- } else {
- debug("\n");
- f_dump_line(c->in_filter->root, 2);
- }
+ channel_filter_dump(c->in_filter);
+ debug(" EXPORT", c->name, net_label[c->net_type]);
+ channel_filter_dump(c->out_filter);
+ debug("\n");
}
}
}
diff --git a/filter/filter.h b/filter/filter.h
index 26c1037b..9964831c 100644
--- a/filter/filter.h
+++ b/filter/filter.h
@@ -51,8 +51,8 @@ struct filter {
struct rte;
-enum filter_return f_run(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, int flags);
-enum filter_return f_eval_rte(const struct f_line *expr, struct rte **rte, struct linpool *tmp_pool);
+enum filter_return f_run(const struct filter *filter, struct rte *rte, struct linpool *tmp_pool, int flags);
+enum filter_return f_eval_rte(const struct f_line *expr, struct rte *rte, struct linpool *tmp_pool);
uint f_eval_int(const struct f_line *expr);
enum filter_return f_eval_buf(const struct f_line *expr, struct linpool *tmp_pool, buffer *buf);
diff --git a/filter/filter_test.c b/filter/filter_test.c
index 7e4af092..2a0b5431 100644
--- a/filter/filter_test.c
+++ b/filter/filter_test.c
@@ -72,6 +72,7 @@ int
main(int argc, char *argv[])
{
bt_init(argc, argv);
+
bt_bird_init();
bt_assert_hook = bt_assert_filter;
diff --git a/filter/test.conf b/filter/test.conf
index 6a28e4b3..f373bac5 100644
--- a/filter/test.conf
+++ b/filter/test.conf
@@ -378,7 +378,6 @@ bt_test_suite(t_ip_set, "Testing sets of ip address");
function t_enum()
{
- bt_assert(format(RTS_DUMMY) = "(enum 30)0");
bt_assert(format(RTS_STATIC) = "(enum 30)1");
bt_assert(format(NET_IP4) = "(enum 36)1");
bt_assert(format(NET_VPN6) = "(enum 36)4");
diff --git a/filter/tree_test.c b/filter/tree_test.c
index 6472d17e..883d13bf 100644
--- a/filter/tree_test.c
+++ b/filter/tree_test.c
@@ -20,7 +20,7 @@ start_conf_env(void)
{
bt_bird_init();
- pool *p = rp_new(&root_pool, "helper_pool");
+ pool *p = rp_new(&root_pool, &main_birdloop, "helper_pool");
linpool *l = lp_new_default(p);
cfg_mem = l;
}
diff --git a/lib/Makefile b/lib/Makefile
index 4378a7bd..98c5db3c 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -1,4 +1,4 @@
-src := bitmap.c bitops.c blake2s.c blake2b.c checksum.c event.c flowspec.c idm.c ip.c lists.c mac.c md5.c mempool.c net.c patmatch.c printf.c resource.c sha1.c sha256.c sha512.c slab.c slists.c strtoul.c tbf.c timer.c xmalloc.c
+src := bitmap.c bitops.c blake2s.c blake2b.c checksum.c event.c flowspec.c idm.c ip.c lists.c mac.c md5.c mempool.c net.c patmatch.c printf.c resource.c rcu.c sha1.c sha256.c sha512.c slab.c slists.c strtoul.c tbf.c timer.c xmalloc.c
obj := $(src-o-files)
$(all-daemon)
diff --git a/lib/birdlib.h b/lib/birdlib.h
index 431b7c0d..385bf75c 100644
--- a/lib/birdlib.h
+++ b/lib/birdlib.h
@@ -9,6 +9,7 @@
#ifndef _BIRD_BIRDLIB_H_
#define _BIRD_BIRDLIB_H_
+#include "sysdep/config.h"
#include "lib/alloca.h"
/* Ugly structure offset handling macros */
@@ -16,7 +17,7 @@
struct align_probe { char x; long int y; };
#define OFFSETOF(s, i) ((size_t) &((s *)0)->i)
-#define SKIP_BACK(s, i, p) ((s *)((char *)p - OFFSETOF(s, i)))
+#define SKIP_BACK(s, i, p) ({ s *_ptr = ((s *)((char *)p - OFFSETOF(s, i))); ASSERT_DIE(&_ptr->i == p); _ptr; })
#define BIRD_ALIGN(s, a) (((s)+a-1)&~(a-1))
#define CPU_STRUCT_ALIGN (sizeof(struct align_probe))
@@ -70,6 +71,7 @@ static inline int u64_cmp(u64 i1, u64 i2)
/* Macros for gcc attributes */
#define NORET __attribute__((noreturn))
+#define USE_RESULT __atribute__((warn_unused_result))
#define UNUSED __attribute__((unused))
#define PACKED __attribute__((packed))
#define NONNULL(...) __attribute__((nonnull((__VA_ARGS__))))
@@ -77,10 +79,6 @@ static inline int u64_cmp(u64 i1, u64 i2)
#define STATIC_ASSERT(EXP) _Static_assert(EXP, #EXP)
#define STATIC_ASSERT_MSG(EXP,MSG) _Static_assert(EXP, MSG)
-#ifndef HAVE_THREAD_LOCAL
-#define _Thread_local
-#endif
-
/* Microsecond time */
typedef s64 btime;
diff --git a/lib/coro.h b/lib/coro.h
new file mode 100644
index 00000000..b36f1d2c
--- /dev/null
+++ b/lib/coro.h
@@ -0,0 +1,31 @@
+/*
+ * BIRD Coroutines
+ *
+ * (c) 2017 Martin Mares <mj@ucw.cz>
+ * (c) 2020-2021 Maria Matejka <mq@jmq.cz>
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_CORO_H_
+#define _BIRD_CORO_H_
+
+#include "lib/resource.h"
+
+/* A completely opaque coroutine handle. */
+struct coroutine;
+
+/* Coroutines are independent threads bound to pools.
+ * You request a coroutine by calling coro_run().
+ * It is forbidden to free a running coroutine from outside.
+ * The running coroutine must free itself by rfree() before returning.
+ */
+struct coroutine *coro_run(pool *, void (*entry)(void *), void *data);
+
+/* Get self. */
+extern _Thread_local struct coroutine *this_coro;
+
+/* Just wait for a little while. Not intended for general use; use events if possible. */
+void coro_yield(void);
+
+#endif
diff --git a/lib/event.c b/lib/event.c
index 273447e0..10f83c28 100644
--- a/lib/event.c
+++ b/lib/event.c
@@ -19,8 +19,14 @@
* events in them and explicitly ask to run them.
*/
+#undef LOCAL_DEBUG
+
#include "nest/bird.h"
#include "lib/event.h"
+#include "lib/locking.h"
+#include "lib/io-loop.h"
+
+extern _Thread_local struct coroutine *this_coro;
event_list global_event_list;
event_list global_work_list;
@@ -28,11 +34,16 @@ event_list global_work_list;
inline void
ev_postpone(event *e)
{
+ event_list *el = e->list;
+ if (!el)
+ return;
+
+ ASSERT_DIE(birdloop_inside(el->loop));
+
+ LOCK_DOMAIN(event, el->lock);
if (ev_active(e))
- {
- rem_node(&e->n);
- e->n.next = NULL;
- }
+ rem_node(&e->n);
+ UNLOCK_DOMAIN(event, el->lock);
}
static void
@@ -95,40 +106,50 @@ ev_run(event *e)
* list @l which can be run by calling ev_run_list().
*/
inline void
-ev_enqueue(event_list *l, event *e)
+ev_send(event_list *l, event *e)
{
- ev_postpone(e);
- add_tail(l, &e->n);
-}
+ DBG("ev_send(%p, %p)\n", l, e);
+ ASSERT_DIE(e->hook);
+ ASSERT_DIE(!e->list || (e->list == l) || (e->list->loop == l->loop));
-/**
- * ev_schedule - schedule an event
- * @e: an event
- *
- * This function schedules an event by enqueueing it to a system-wide
- * event list which is run by the platform dependent code whenever
- * appropriate.
- */
-void
-ev_schedule(event *e)
-{
- ev_enqueue(&global_event_list, e);
-}
+ e->list = l;
-/**
- * ev_schedule_work - schedule a work-event.
- * @e: an event
- *
- * This function schedules an event by enqueueing it to a system-wide work-event
- * list which is run by the platform dependent code whenever appropriate. This
- * is designated for work-events instead of regular events. They are executed
- * less often in order to not clog I/O loop.
- */
-void
-ev_schedule_work(event *e)
-{
- if (!ev_active(e))
- add_tail(&global_work_list, &e->n);
+ struct event_cork *ec = e->cork;
+
+ uint ping = 0;
+
+ if (ec)
+ {
+ LOCK_DOMAIN(cork, ec->lock);
+ LOCK_DOMAIN(event, l->lock);
+
+ if (!enlisted(&e->n))
+ if (ec->count)
+ add_tail(&ec->events, &e->n);
+ else
+ {
+ add_tail(&l->events, &e->n);
+ ping = 1;
+ }
+
+ UNLOCK_DOMAIN(event, l->lock);
+ UNLOCK_DOMAIN(cork, ec->lock);
+ }
+ else
+ {
+ LOCK_DOMAIN(event, l->lock);
+
+ if (!enlisted(&e->n))
+ {
+ add_tail(&l->events, &e->n);
+ ping = 1;
+ }
+
+ UNLOCK_DOMAIN(event, l->lock);
+ }
+
+ if (ping)
+ birdloop_ping(l->loop);
}
void io_log_event(void *hook, void *data);
@@ -142,35 +163,65 @@ void io_log_event(void *hook, void *data);
int
ev_run_list(event_list *l)
{
+ const _Bool legacy = LEGACY_EVENT_LIST(l);
+
+ if (legacy)
+ ASSERT_THE_BIRD_LOCKED;
+
node *n;
- list tmp_list;
+ list tmp_list;
init_list(&tmp_list);
- add_tail_list(&tmp_list, l);
- init_list(l);
+
+ /* Move the event list contents to a local list to avoid executing repeatedly added events */
+ LOCK_DOMAIN(event, l->lock);
+ add_tail_list(&tmp_list, &l->events);
+ init_list(&l->events);
+ UNLOCK_DOMAIN(event, l->lock);
+
WALK_LIST_FIRST(n, tmp_list)
{
event *e = SKIP_BACK(event, n, n);
+ ASSERT_DIE(n->next->prev == n);
- /* This is ugly hack, we want to log just events executed from the main I/O loop */
- if ((l == &global_event_list) || (l == &global_work_list))
+ if (legacy)
+ {
+ /* The legacy way of event execution */
io_log_event(e->hook, e->data);
-
- ev_run(e);
+ ev_postpone(e);
+ e->hook(e->data);
+ }
+ else
+ {
+ // io_log_event(e->hook, e->data); /* TODO: add support for event logging in other io loops */
+ ASSERT_DIE(e->list == l);
+ LOCK_DOMAIN(event, l->lock);
+ rem_node(&e->n);
+ UNLOCK_DOMAIN(event, l->lock);
+ e->hook(e->data);
+ }
}
- return !EMPTY_LIST(*l);
+ LOCK_DOMAIN(event, l->lock);
+ int repeat = ! EMPTY_LIST(l->events);
+ UNLOCK_DOMAIN(event, l->lock);
+ return repeat;
}
int
ev_run_list_limited(event_list *l, uint limit)
{
+ ASSERT_DIE(LEGACY_EVENT_LIST(l));
+ ASSERT_THE_BIRD_LOCKED;
+
node *n;
list tmp_list;
+ LOCK_DOMAIN(event, l->lock);
init_list(&tmp_list);
- add_tail_list(&tmp_list, l);
- init_list(l);
+ add_tail_list(&tmp_list, &l->events);
+ init_list(&l->events);
+ UNLOCK_DOMAIN(event, l->lock);
WALK_LIST_FIRST(n, tmp_list)
{
@@ -179,21 +230,66 @@ ev_run_list_limited(event_list *l, uint limit)
if (!limit)
break;
- /* This is ugly hack, we want to log just events executed from the main I/O loop */
- if ((l == &global_event_list) || (l == &global_work_list))
- io_log_event(e->hook, e->data);
+ io_log_event(e->hook, e->data);
ev_run(e);
limit--;
}
+ LOCK_DOMAIN(event, l->lock);
if (!EMPTY_LIST(tmp_list))
{
/* Attach new items after the unprocessed old items */
- add_tail_list(&tmp_list, l);
- init_list(l);
- add_tail_list(l, &tmp_list);
+ add_tail_list(&tmp_list, &l->events);
+ init_list(&l->events);
+ add_tail_list(&l->events, &tmp_list);
}
- return !EMPTY_LIST(*l);
+ int repeat = ! EMPTY_LIST(l->events);
+ UNLOCK_DOMAIN(event, l->lock);
+
+ return repeat;
+}
+
+void ev_cork(struct event_cork *ec)
+{
+ LOCK_DOMAIN(cork, ec->lock);
+ ec->count++;
+ UNLOCK_DOMAIN(cork, ec->lock);
+}
+
+void ev_uncork(struct event_cork *ec)
+{
+ LOCK_DOMAIN(cork, ec->lock);
+
+ if (--ec->count)
+ {
+ UNLOCK_DOMAIN(cork, ec->lock);
+ return;
+ }
+
+ node *n;
+ WALK_LIST_FIRST(n, ec->events)
+ {
+ event *e = SKIP_BACK(event, n, n);
+ event_list *el = e->list;
+
+ rem_node(&e->n);
+
+ LOCK_DOMAIN(event, el->lock);
+ add_tail(&el->events, &e->n);
+ UNLOCK_DOMAIN(event, el->lock);
+
+ birdloop_ping(el->loop);
+ }
+
+ struct birdsock *sk;
+ WALK_LIST_FIRST2(sk, cork_node, ec->sockets)
+ {
+// log(L_TRACE "Socket %p uncorked", sk);
+ rem_node(&sk->cork_node);
+ sk_ping(sk);
+ }
+
+ UNLOCK_DOMAIN(cork, ec->lock);
}
diff --git a/lib/event.h b/lib/event.h
index 5f3b78d8..cbabfc66 100644
--- a/lib/event.h
+++ b/lib/event.h
@@ -10,33 +10,95 @@
#define _BIRD_EVENT_H_
#include "lib/resource.h"
+#include "lib/locking.h"
+
+#include <stdatomic.h>
+
+DEFINE_DOMAIN(event);
+DEFINE_DOMAIN(cork);
typedef struct event {
resource r;
void (*hook)(void *);
void *data;
node n; /* Internal link */
+ struct event_list *list; /* List where this event is put in */
+ struct event_cork *cork; /* Event execution limiter */
+ node cork_node;
} event;
-typedef list event_list;
+typedef struct event_list {
+ list events;
+ pool *pool;
+ struct birdloop *loop;
+ DOMAIN(event) lock;
+} event_list;
+
+struct event_cork {
+ DOMAIN(cork) lock;
+ u32 count;
+ list events;
+ list sockets;
+};
extern event_list global_event_list;
extern event_list global_work_list;
event *ev_new(pool *);
void ev_run(event *);
-#define ev_init_list(el) init_list(el)
-void ev_enqueue(event_list *, event *);
-void ev_schedule(event *);
-void ev_schedule_work(event *);
+
+static inline void ev_init_list(event_list *el, struct birdloop *loop, const char *name)
+{
+ init_list(&el->events);
+ el->loop = loop;
+ el->lock = DOMAIN_NEW(event, name);
+}
+
+static inline void ev_init_cork(struct event_cork *ec, const char *name)
+{
+ init_list(&ec->events);
+ init_list(&ec->sockets);
+ ec->lock = DOMAIN_NEW(cork, name);
+ ec->count = 0;
+};
+
+void ev_send(event_list *, event *);
+#define ev_send_self(e) ({ ASSERT_DIE((e)->list); ev_send((e)->list, (e)); })
+#define ev_send_loop(l, e) ev_send(birdloop_event_list((l)), (e))
+
+#define ev_schedule(e) ({ ASSERT_THE_BIRD_LOCKED; if (!ev_active((e))) ev_send(&global_event_list, (e)); })
+#define ev_schedule_work(e) ({ ASSERT_THE_BIRD_LOCKED; if (!ev_active((e))) ev_send(&global_work_list, (e)); })
+
void ev_postpone(event *);
int ev_run_list(event_list *);
int ev_run_list_limited(event_list *, uint);
+#define LEGACY_EVENT_LIST(l) (((l) == &global_event_list) || ((l) == &global_work_list))
+
+void ev_cork(struct event_cork *);
+void ev_uncork(struct event_cork *);
+
+static inline u32 ev_corked(struct event_cork *ec)
+{
+ if (!ec)
+ return 0;
+
+ LOCK_DOMAIN(cork, ec->lock);
+ u32 out = ec->count;
+ UNLOCK_DOMAIN(cork, ec->lock);
+ return out;
+}
+
+_Bool birdloop_inside(struct birdloop *loop);
+
static inline int
ev_active(event *e)
{
- return e->n.next != NULL;
+ if (e->list == NULL)
+ return 0;
+
+ ASSERT_DIE(birdloop_inside(e->list->loop));
+ return enlisted(&e->n);
}
static inline event*
diff --git a/lib/event_test.c b/lib/event_test.c
index e1215bba..f8bb303b 100644
--- a/lib/event_test.c
+++ b/lib/event_test.c
@@ -48,14 +48,17 @@ init_event_check_points(void)
event_check_points[i] = 0;
}
+void resource_sys_init(void);
+
static int
t_ev_run_list(void)
{
int i;
+ resource_sys_init();
resource_init();
+ birdloop_init();
olock_init();
- timer_init();
io_init();
rt_init();
if_init();
@@ -82,7 +85,9 @@ main(int argc, char *argv[])
{
bt_init(argc, argv);
+ the_bird_lock();
bt_test_suite(t_ev_run_list, "Schedule and run 3 events in right order.");
+ the_bird_unlock();
return bt_exit_value();
}
diff --git a/lib/flowspec_test.c b/lib/flowspec_test.c
index ed4afe51..518b8fc7 100644
--- a/lib/flowspec_test.c
+++ b/lib/flowspec_test.c
@@ -8,6 +8,7 @@
#include "test/birdtest.h"
#include "lib/flowspec.h"
+#include "lib/io-loop.h"
#define NET_ADDR_FLOW4_(what,prefix,pxlen,data_) \
do \
@@ -446,8 +447,6 @@ t_validation6(void)
static int
t_builder4(void)
{
- resource_init();
-
struct flow_builder *fb = flow_builder_init(&root_pool);
linpool *lp = lp_new_default(&root_pool);
@@ -529,7 +528,6 @@ t_builder6(void)
{
net_addr_ip6 ip;
- resource_init();
linpool *lp = lp_new_default(&root_pool);
struct flow_builder *fb = flow_builder_init(&root_pool);
fb->ipv6 = 1;
@@ -666,10 +664,16 @@ t_formatting6(void)
return 1;
}
+void resource_sys_init(void);
+
int
main(int argc, char *argv[])
{
bt_init(argc, argv);
+ resource_sys_init();
+ resource_init();
+ the_bird_lock();
+ birdloop_init();
bt_test_suite(t_read_length, "Testing get NLRI length");
bt_test_suite(t_write_length, "Testing set NLRI length");
@@ -685,5 +689,6 @@ main(int argc, char *argv[])
bt_test_suite(t_formatting4, "Formatting Flow Specification (IPv4) into text representation");
bt_test_suite(t_formatting6, "Formatting Flow Specification (IPv6) into text representation");
+ the_bird_unlock();
return bt_exit_value();
}
diff --git a/lib/hash.h b/lib/hash.h
index ea4ca6dd..8febb33f 100644
--- a/lib/hash.h
+++ b/lib/hash.h
@@ -215,6 +215,12 @@ mem_hash_mix(u64 *h, const void *p, uint s)
*h = *h * multiplier + pp[i];
}
+static inline void
+mem_hash_mix_num(u64 *h, u64 val)
+{
+ mem_hash_mix(h, &val, sizeof(val));
+}
+
static inline uint
mem_hash_value(u64 *h)
{
diff --git a/lib/hash_test.c b/lib/hash_test.c
index 59beb7c0..7ef54662 100644
--- a/lib/hash_test.c
+++ b/lib/hash_test.c
@@ -9,7 +9,9 @@
#undef LOCAL_DEBUG
#include "test/birdtest.h"
+#include "test/bt-utils.h"
+#include "lib/io-loop.h"
#include "lib/hash.h"
struct test_node {
@@ -61,8 +63,7 @@ dump_nodes(void)
static void
init_hash_(uint order)
{
- resource_init();
- my_pool = rp_new(&root_pool, "Test pool");
+ my_pool = rp_new(&root_pool, &main_birdloop, "Test pool");
HASH_INIT(hash, my_pool, order);
@@ -290,6 +291,7 @@ int
main(int argc, char *argv[])
{
bt_init(argc, argv);
+ bt_bird_init();
bt_test_suite(t_insert_find, "HASH_INSERT and HASH_FIND");
bt_test_suite(t_insert_find_random, "HASH_INSERT pseudo-random keys and HASH_FIND");
diff --git a/lib/io-loop.h b/lib/io-loop.h
new file mode 100644
index 00000000..d60fb1ae
--- /dev/null
+++ b/lib/io-loop.h
@@ -0,0 +1,58 @@
+/*
+ * BIRD -- I/O and event loop
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_IO_LOOP_H_
+#define _BIRD_IO_LOOP_H_
+
+#include "nest/bird.h"
+#include "lib/lists.h"
+#include "lib/locking.h"
+#include "lib/resource.h"
+#include "lib/event.h"
+#include "lib/socket.h"
+
+void sk_start(sock *s);
+void sk_stop(sock *s);
+void sk_reloop(sock *s, struct birdloop *loop);
+void sk_ping(sock *s);
+
+extern struct birdloop main_birdloop;
+
+/* Start a new birdloop owned by given pool and domain.
+ * The loop allocates its internal pool for local allocations
+ * which is freed when the loop itself is stopped. */
+struct birdloop *birdloop_new(pool *p, uint order, const char *name);
+
+/* Stop the loop. At the end, the @stopped callback is called with locked
+ * parent to finish cleanup. The loop then frees itself together with its pool. */
+void birdloop_stop(struct birdloop *loop, void (*stopped)(void *data), void *data);
+void birdloop_stop_self(struct birdloop *loop, void (*stopped)(void *data), void *data);
+
+/* Get birdloop's event list */
+event_list *birdloop_event_list(struct birdloop *loop);
+
+/* Get birdloop's time heap */
+struct timeloop *birdloop_time_loop(struct birdloop *loop);
+
+/* Get birdloop's resource pool */
+pool *birdloop_pool(struct birdloop *loop);
+
+/* Enter and exit the birdloop */
+void birdloop_enter(struct birdloop *loop);
+void birdloop_leave(struct birdloop *loop);
+
+_Bool birdloop_inside(struct birdloop *loop);
+
+void birdloop_mask_wakeups(struct birdloop *loop);
+void birdloop_unmask_wakeups(struct birdloop *loop);
+
+void birdloop_link(struct birdloop *loop);
+void birdloop_unlink(struct birdloop *loop);
+
+void birdloop_ping(struct birdloop *loop);
+
+void birdloop_init(void);
+#endif /* _BIRD_IO_LOOP_H_ */
diff --git a/lib/lists.c b/lib/lists.c
index 200576cf..2f92defe 100644
--- a/lib/lists.c
+++ b/lib/lists.c
@@ -26,7 +26,7 @@
#define _BIRD_LISTS_C_
-#include "nest/bird.h"
+#include "lib/birdlib.h"
#include "lib/lists.h"
LIST_INLINE int
@@ -35,11 +35,12 @@ check_list(list *l, node *n)
if (!l)
{
ASSERT_DIE(n);
- ASSERT_DIE(n->prev);
- do { n = n->prev; } while (n->prev);
+ node *nn = n;
+ while (nn->prev)
+ nn = nn->prev;
- l = SKIP_BACK(list, head_node, n);
+ l = SKIP_BACK(list, head_node, nn);
}
int seen = 0;
@@ -60,7 +61,7 @@ check_list(list *l, node *n)
}
ASSERT_DIE(cur == &(l->tail_node));
- ASSERT_DIE(!n || (seen == 1));
+ ASSERT_DIE(!n || (seen == 1) || (n == &l->head_node) || (n == &l->tail_node));
return 1;
}
@@ -109,6 +110,15 @@ add_head(list *l, node *n)
l->head = n;
}
+LIST_INLINE void
+self_link(node *n)
+{
+ ASSUME(n->prev == NULL);
+ ASSUME(n->next == NULL);
+
+ n->prev = n->next = n;
+}
+
/**
* insert_node - insert a node to a list
* @n: a new list node
@@ -120,7 +130,7 @@ add_head(list *l, node *n)
LIST_INLINE void
insert_node(node *n, node *after)
{
- EXPENSIVE_CHECK(check_list(l, after));
+ EXPENSIVE_CHECK(check_list(NULL, after));
ASSUME(n->prev == NULL);
ASSUME(n->next == NULL);
@@ -141,7 +151,7 @@ insert_node(node *n, node *after)
LIST_INLINE void
rem_node(node *n)
{
- EXPENSIVE_CHECK(check_list(NULL, n));
+ EXPENSIVE_CHECK((n == n->prev) && (n == n->next) || check_list(NULL, n));
node *z = n->prev;
node *x = n->next;
@@ -198,6 +208,9 @@ add_tail_list(list *to, list *l)
EXPENSIVE_CHECK(check_list(to, NULL));
EXPENSIVE_CHECK(check_list(l, NULL));
+ ASSERT_DIE(l->head->prev == &l->head_node);
+ ASSERT_DIE(l->tail->next == &l->tail_node);
+
node *p = to->tail;
node *q = l->head;
diff --git a/lib/lists.h b/lib/lists.h
index 479f4ed1..f36c051c 100644
--- a/lib/lists.h
+++ b/lib/lists.h
@@ -42,6 +42,7 @@ typedef union list { /* In fact two overlayed nodes */
};
} list;
+#define STATIC_LIST_INIT(name) name = { .head = &name.tail_node, .tail = &name.head_node, .null = NULL }
#define NODE (node *)
#define HEAD(list) ((void *)((list).head))
@@ -68,6 +69,18 @@ typedef union list { /* In fact two overlayed nodes */
#define EMPTY_LIST(list) (!(list).head->next)
+static inline _Bool
+enlisted(node *n)
+{
+ switch ((!!n->next) + (!!n->prev))
+ {
+ case 0: return 0;
+ case 2: return 1;
+ case 1: bug("Garbled event list node");
+ }
+
+ bug("Maths is broken. And you should see a new heaven and a new earth: for the first heaven and the first earth had been passed away.");
+}
#ifndef _BIRD_LISTS_C_
#define LIST_INLINE static inline
@@ -78,6 +91,7 @@ typedef union list { /* In fact two overlayed nodes */
#define LIST_INLINE
void add_tail(list *, node *);
void add_head(list *, node *);
+void self_link(node *);
void rem_node(node *);
void add_tail_list(list *, list *);
void init_list(list *);
diff --git a/lib/locking.h b/lib/locking.h
new file mode 100644
index 00000000..1a8bdcd4
--- /dev/null
+++ b/lib/locking.h
@@ -0,0 +1,66 @@
+/*
+ * BIRD Library -- Locking
+ *
+ * (c) 2020--2021 Maria Matejka <mq@jmq.cz>
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_LOCKING_H_
+#define _BIRD_LOCKING_H_
+
+struct domain_generic;
+
+/* Here define the global lock order; first to last. */
+struct lock_order {
+ struct domain_generic *the_bird;
+ struct domain_generic *proto;
+ struct domain_generic *rtable;
+ struct domain_generic *attrs;
+ struct domain_generic *cork;
+ struct domain_generic *event;
+ struct domain_generic *resource;
+};
+
+extern _Thread_local struct lock_order locking_stack;
+extern _Thread_local struct domain_generic **last_locked;
+
+#define DOMAIN(type) struct domain__##type
+#define DEFINE_DOMAIN(type) DOMAIN(type) { struct domain_generic *type; }
+#define DOMAIN_ORDER(type) OFFSETOF(struct lock_order, type)
+
+#define DOMAIN_NEW(type, name) (DOMAIN(type)) { .type = domain_new(name, DOMAIN_ORDER(type)) }
+struct domain_generic *domain_new(const char *name, uint order);
+
+#define DOMAIN_FREE(type, d) domain_free((d).type)
+void domain_free(struct domain_generic *);
+
+#define DOMAIN_NULL(type) (DOMAIN(type)) {}
+
+#define LOCK_DOMAIN(type, d) do_lock(((d).type), &(locking_stack.type))
+#define UNLOCK_DOMAIN(type, d) do_unlock(((d).type), &(locking_stack.type))
+
+#define DOMAIN_IS_LOCKED(type, d) (((d).type) == (locking_stack.type))
+#define DG_IS_LOCKED(d) ((d) == *(DG_LSP(d)))
+
+/* Internal for locking */
+void do_lock(struct domain_generic *dg, struct domain_generic **lsp);
+void do_unlock(struct domain_generic *dg, struct domain_generic **lsp);
+
+uint dg_order(struct domain_generic *dg);
+
+#define DG_LSP(d) ((struct domain_generic **) (((void *) &locking_stack) + dg_order(d)))
+#define DG_LOCK(d) do_lock(d, DG_LSP(d))
+#define DG_UNLOCK(d) do_unlock(d, DG_LSP(d))
+
+/* Use with care. To be removed in near future. */
+DEFINE_DOMAIN(the_bird);
+extern DOMAIN(the_bird) the_bird_domain;
+
+#define the_bird_lock() LOCK_DOMAIN(the_bird, the_bird_domain)
+#define the_bird_unlock() UNLOCK_DOMAIN(the_bird, the_bird_domain)
+#define the_bird_locked() DOMAIN_IS_LOCKED(the_bird, the_bird_domain)
+
+#define ASSERT_THE_BIRD_LOCKED ({ if (!the_bird_locked()) bug("The BIRD lock must be locked here: %s:%d", __FILE__, __LINE__); })
+
+#endif
diff --git a/lib/mempool.c b/lib/mempool.c
index 90d7c774..6d157e5c 100644
--- a/lib/mempool.c
+++ b/lib/mempool.c
@@ -37,9 +37,10 @@ const int lp_chunk_size = sizeof(struct lp_chunk);
struct linpool {
resource r;
byte *ptr, *end;
+ pool *p;
struct lp_chunk *first, *current; /* Normal (reusable) chunks */
struct lp_chunk *first_large; /* Large chunks */
- uint chunk_size, threshold, total, total_large;
+ uint chunk_size, threshold, total:31, use_pages:1, total_large;
};
static void lp_free(resource *);
@@ -69,6 +70,13 @@ linpool
*lp_new(pool *p, uint blk)
{
linpool *m = ralloc(p, &lp_class);
+ m->p = p;
+ if (!blk)
+ {
+ m->use_pages = 1;
+ blk = page_size - lp_chunk_size;
+ }
+
m->chunk_size = blk;
m->threshold = 3*blk/4;
return m;
@@ -121,7 +129,11 @@ lp_alloc(linpool *m, uint size)
else
{
/* Need to allocate a new chunk */
- c = xmalloc(sizeof(struct lp_chunk) + m->chunk_size);
+ if (m->use_pages)
+ c = alloc_page();
+ else
+ c = xmalloc(sizeof(struct lp_chunk) + m->chunk_size);
+
m->total += m->chunk_size;
c->next = NULL;
c->size = m->chunk_size;
@@ -258,7 +270,10 @@ lp_free(resource *r)
for(d=m->first; d; d = c)
{
c = d->next;
- xfree(d);
+ if (m->use_pages)
+ free_page(d);
+ else
+ xfree(d);
}
for(d=m->first_large; d; d = c)
{
diff --git a/lib/rcu.c b/lib/rcu.c
new file mode 100644
index 00000000..69f3442f
--- /dev/null
+++ b/lib/rcu.c
@@ -0,0 +1,79 @@
+/*
+ * BIRD Library -- Read-Copy-Update Basic Operations
+ *
+ * (c) 2021 Maria Matejka <mq@jmq.cz>
+ * (c) 2021 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ * Note: all the relevant patents shall be expired.
+ *
+ * Using the Supplementary Material for User-Level Implementations of Read-Copy-Update
+ * by Matthieu Desnoyers, Paul E. McKenney, Alan S. Stern, Michel R. Dagenais and Jonathan Walpole
+ * obtained from https://www.efficios.com/pub/rcu/urcu-supp-accepted.pdf
+ */
+
+#include "lib/rcu.h"
+#include "lib/coro.h"
+#include "lib/locking.h"
+
+_Atomic uint rcu_gp_ctl = RCU_NEST_CNT;
+_Thread_local struct rcu_coro *this_rcu_coro = NULL;
+
+static list rcu_coro_list;
+
+static struct rcu_coro main_rcu_coro;
+
+DEFINE_DOMAIN(resource);
+static DOMAIN(resource) rcu_domain;
+
+static int
+rcu_gp_ongoing(_Atomic uint *ctl)
+{
+ uint val = atomic_load(ctl);
+ return (val & RCU_NEST_CNT) && ((val ^ rcu_gp_ctl) & RCU_GP_PHASE);
+}
+
+static void
+update_counter_and_wait(void)
+{
+ atomic_fetch_xor(&rcu_gp_ctl, RCU_GP_PHASE);
+ struct rcu_coro *rc;
+ WALK_LIST(rc, rcu_coro_list)
+ while (rcu_gp_ongoing(&rc->ctl))
+ coro_yield();
+}
+
+void
+synchronize_rcu(void)
+{
+ LOCK_DOMAIN(resource, rcu_domain);
+ update_counter_and_wait();
+ update_counter_and_wait();
+ UNLOCK_DOMAIN(resource, rcu_domain);
+}
+
+void
+rcu_coro_start(struct rcu_coro *rc)
+{
+ LOCK_DOMAIN(resource, rcu_domain);
+ add_tail(&rcu_coro_list, &rc->n);
+ this_rcu_coro = rc;
+ UNLOCK_DOMAIN(resource, rcu_domain);
+}
+
+void
+rcu_coro_stop(struct rcu_coro *rc)
+{
+ LOCK_DOMAIN(resource, rcu_domain);
+ this_rcu_coro = NULL;
+ rem_node(&rc->n);
+ UNLOCK_DOMAIN(resource, rcu_domain);
+}
+
+void
+rcu_init(void)
+{
+ rcu_domain = DOMAIN_NEW(resource, "Read-Copy-Update");
+ init_list(&rcu_coro_list);
+ rcu_coro_start(&main_rcu_coro);
+}
diff --git a/lib/rcu.h b/lib/rcu.h
new file mode 100644
index 00000000..ac8fc9ce
--- /dev/null
+++ b/lib/rcu.h
@@ -0,0 +1,55 @@
+/*
+ * BIRD Library -- Read-Copy-Update Basic Operations
+ *
+ * (c) 2021 Maria Matejka <mq@jmq.cz>
+ * (c) 2021 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ * Note: all the relevant patents shall be expired.
+ */
+
+#ifndef _BIRD_RCU_H_
+#define _BIRD_RCU_H_
+
+#include "lib/birdlib.h"
+#include "lib/lists.h"
+#include <stdatomic.h>
+
+#define RCU_GP_PHASE 0x100000
+#define RCU_NEST_MASK 0x0fffff
+#define RCU_NEST_CNT 0x000001
+
+extern _Atomic uint rcu_gp_ctl;
+
+struct rcu_coro {
+ node n;
+ _Atomic uint ctl;
+};
+
+extern _Thread_local struct rcu_coro *this_rcu_coro;
+
+static inline void rcu_read_lock(void)
+{
+ uint cmp = atomic_load_explicit(&this_rcu_coro->ctl, memory_order_acquire);
+
+ if (cmp & RCU_NEST_MASK)
+ atomic_store_explicit(&this_rcu_coro->ctl, cmp + RCU_NEST_CNT, memory_order_relaxed);
+ else
+ atomic_store(&this_rcu_coro->ctl, atomic_load_explicit(&rcu_gp_ctl, memory_order_acquire));
+}
+
+static inline void rcu_read_unlock(void)
+{
+ atomic_fetch_sub(&this_rcu_coro->ctl, RCU_NEST_CNT);
+}
+
+void synchronize_rcu(void);
+
+/* Registering and unregistering a coroutine. To be called from coroutine implementation */
+void rcu_coro_start(struct rcu_coro *);
+void rcu_coro_stop(struct rcu_coro *);
+
+/* Run this from resource init */
+void rcu_init(void);
+
+#endif
diff --git a/lib/resource.c b/lib/resource.c
index 5d4c7780..9a4c7717 100644
--- a/lib/resource.c
+++ b/lib/resource.c
@@ -14,6 +14,8 @@
#include "nest/bird.h"
#include "lib/resource.h"
#include "lib/string.h"
+#include "lib/rcu.h"
+#include "lib/io-loop.h"
/**
* DOC: Resource pools
@@ -29,12 +31,6 @@
* is freed upon shutdown of the module.
*/
-struct pool {
- resource r;
- list inside;
- const char *name;
-};
-
static void pool_dump(resource *);
static void pool_free(resource *);
static resource *pool_lookup(resource *, unsigned long);
@@ -56,26 +52,39 @@ static int indent;
/**
* rp_new - create a resource pool
* @p: parent pool
+ * @l: loop to assign
* @name: pool name (to be included in debugging dumps)
*
* rp_new() creates a new resource pool inside the specified
* parent pool.
*/
pool *
-rp_new(pool *p, const char *name)
+rp_new(pool *p, struct birdloop *loop, const char *name)
{
+ ASSERT_DIE(birdloop_inside(p->loop));
+ ASSERT_DIE(birdloop_inside(loop));
+
pool *z = ralloc(p, &pool_class);
+ z->loop = loop;
z->name = name;
init_list(&z->inside);
return z;
}
+_Thread_local static pool *pool_parent = NULL;
+
static void
pool_free(resource *P)
{
+ ASSERT_DIE(pool_parent);
+
pool *p = (pool *) P;
- resource *r, *rr;
+ ASSERT_DIE(birdloop_inside(p->loop));
+
+ pool *parent = pool_parent;
+ pool_parent = p;
+ resource *r, *rr;
r = HEAD(p->inside);
while (rr = (resource *) r->n.next)
{
@@ -83,14 +92,25 @@ pool_free(resource *P)
xfree(r);
r = rr;
}
+
+ pool_parent = parent;
+}
+
+void
+rp_free(pool *p, pool *parent)
+{
+ ASSERT_DIE(pool_parent == NULL);
+ pool_parent = parent;
+ rfree(p);
+ ASSERT_DIE(pool_parent == parent);
+ pool_parent = NULL;
}
static void
-pool_dump(resource *P)
+pool_dump_locked(pool *p)
{
- pool *p = (pool *) P;
resource *r;
-
+
debug("%s\n", p->name);
indent += 3;
WALK_LIST(r, p->inside)
@@ -98,10 +118,47 @@ pool_dump(resource *P)
indent -= 3;
}
-static struct resmem
-pool_memsize(resource *P)
+static void
+pool_dump(resource *P)
{
pool *p = (pool *) P;
+
+ if (p->loop != pool_parent->loop)
+ birdloop_enter(p->loop);
+
+ pool *parent = pool_parent;
+ pool_parent = p;
+
+ pool_dump_locked(p);
+
+ pool_parent = parent;
+
+ if (p->loop != pool_parent->loop)
+ birdloop_leave(p->loop);
+}
+
+void
+rp_dump(pool *p)
+{
+ int inside = birdloop_inside(p->loop);
+ if (!inside)
+ birdloop_enter(p->loop);
+
+ ASSERT_DIE(pool_parent == NULL);
+ pool_parent = p;
+
+ pool_dump_locked(p);
+
+ ASSERT_DIE(pool_parent == p);
+ pool_parent = NULL;
+
+ if (!inside)
+ birdloop_leave(p->loop);
+}
+
+static struct resmem
+pool_memsize_locked(pool *p)
+{
resource *r;
struct resmem sum = {
.effective = 0,
@@ -118,6 +175,46 @@ pool_memsize(resource *P)
return sum;
}
+static struct resmem
+pool_memsize(resource *P)
+{
+ pool *p = (pool *) P;
+
+ pool *parent = pool_parent;
+ pool_parent = p;
+
+ if (p->loop != parent->loop)
+ birdloop_enter(p->loop);
+
+ struct resmem sum = pool_memsize_locked(p);
+
+ if (p->loop != parent->loop)
+ birdloop_leave(p->loop);
+
+ pool_parent = parent;
+
+ return sum;
+}
+
+struct resmem
+rp_memsize(pool *p)
+{
+ int inside = birdloop_inside(p->loop);
+ if (!inside)
+ birdloop_enter(p->loop);
+
+ ASSERT_DIE(pool_parent == NULL);
+ pool_parent = p;
+ struct resmem sum = pool_memsize_locked(p);
+ ASSERT_DIE(pool_parent == p);
+ pool_parent = NULL;
+
+ if (!inside)
+ birdloop_leave(p->loop);
+
+ return sum;
+}
+
static resource *
pool_lookup(resource *P, unsigned long a)
{
@@ -228,12 +325,15 @@ rmemsize(void *res)
void *
ralloc(pool *p, struct resclass *c)
{
+ ASSERT_DIE(p);
+ ASSERT_DIE(birdloop_inside(p->loop));
+
resource *r = xmalloc(c->size);
bzero(r, c->size);
r->class = c;
- if (p)
- add_tail(&p->inside, &r->n);
+ add_tail(&p->inside, &r->n);
+
return r;
}
@@ -270,6 +370,8 @@ rlookup(unsigned long a)
void
resource_init(void)
{
+ rcu_init();
+
root_pool.r.class = &pool_class;
root_pool.name = "Root";
init_list(&root_pool.inside);
@@ -440,7 +542,6 @@ mb_free(void *m)
}
-
#define STEP_UP(x) ((x) + (x)/2 + 4)
void
diff --git a/lib/resource.h b/lib/resource.h
index 9ec41ed8..3e589e32 100644
--- a/lib/resource.h
+++ b/lib/resource.h
@@ -40,10 +40,15 @@ struct resclass {
/* Generic resource manipulation */
-typedef struct pool pool;
+typedef struct pool {
+ resource r;
+ list inside;
+ struct birdloop *loop;
+ const char *name;
+} pool;
void resource_init(void);
-pool *rp_new(pool *, const char *); /* Create new pool */
+
void rfree(void *); /* Free single resource */
void rdump(void *); /* Dump to debug output */
struct resmem rmemsize(void *res); /* Return size of memory used by the resource */
@@ -52,6 +57,11 @@ void rmove(void *, pool *); /* Move to a different pool */
void *ralloc(pool *, struct resclass *);
+pool *rp_new(pool *, struct birdloop *loop, const char *); /* Create new pool */
+void rp_free(pool *p, pool *parent); /* Free parent pool */
+struct resmem rp_memsize(pool *p); /* Return size of memory used by the pool */
+void rp_dump(pool *p); /* Dump pool to debug output */
+
extern pool root_pool;
/* Normal memory blocks */
@@ -82,7 +92,7 @@ void lp_restore(linpool *m, lp_state *p); /* Restore state */
extern const int lp_chunk_size;
#define LP_GAS 1024
#define LP_GOOD_SIZE(x) (((x + LP_GAS - 1) & (~(LP_GAS - 1))) - lp_chunk_size)
-#define lp_new_default(p) lp_new(p, LP_GOOD_SIZE(LP_GAS*4))
+#define lp_new_default(p) lp_new(p, 0)
/* Slabs */
@@ -100,11 +110,12 @@ void sl_free(slab *, void *);
void buffer_realloc(void **buf, unsigned *size, unsigned need, unsigned item_size);
+extern long page_size;
+
/* Allocator of whole pages; for use in slabs and other high-level allocators. */
-u64 get_page_size(void);
void *alloc_page(void);
void free_page(void *);
-extern uint pages_kept;
+#define PAGE_HEAD(x) ((void *) (((intptr_t) (x)) & ~(page_size-1)))
#ifdef HAVE_LIBDMALLOC
/*
diff --git a/lib/slab.c b/lib/slab.c
index 6cab6b7b..c3947162 100644
--- a/lib/slab.c
+++ b/lib/slab.c
@@ -155,6 +155,7 @@ slab_memsize(resource *r)
struct slab {
resource r;
+ pool *p;
uint obj_size, head_size, head_bitfield_len;
uint objs_per_slab, num_empty_heads, data_size;
list empty_heads, partial_heads, full_heads;
@@ -180,7 +181,7 @@ struct sl_alignment { /* Magic structure for testing of alignment */
int x[0];
};
-#define SL_GET_HEAD(x) ((struct sl_head *) (((uintptr_t) (x)) & ~(get_page_size()-1)))
+#define SL_GET_HEAD(x) ((struct sl_head *) PAGE_HEAD(x))
/**
* sl_new - create a new Slab
@@ -194,6 +195,7 @@ slab *
sl_new(pool *p, uint size)
{
slab *s = ralloc(p, &sl_class);
+ s->p = p;
uint align = sizeof(struct sl_alignment);
if (align < sizeof(int))
align = sizeof(int);
@@ -202,7 +204,6 @@ sl_new(pool *p, uint size)
s->obj_size = size;
s->head_size = sizeof(struct sl_head);
- u64 page_size = get_page_size();
do {
s->objs_per_slab = (page_size - s->head_size) / size;
@@ -272,6 +273,9 @@ no_partial:
goto okay;
}
h = alloc_page();
+#ifdef POISON
+ memset(h, 0xba, page_size);
+#endif
ASSERT_DIE(SL_GET_HEAD(h) == h);
memset(h, 0, s->head_size);
add_head(&s->partial_heads, &h->n);
@@ -327,7 +331,12 @@ sl_free(slab *s, void *oo)
{
rem_node(&h->n);
if (s->num_empty_heads >= MAX_EMPTY_HEADS)
+ {
+#ifdef POISON
+ memset(h, 0xde, page_size);
+#endif
free_page(h);
+ }
else
{
add_head(&s->empty_heads, &h->n);
@@ -391,7 +400,7 @@ slab_memsize(resource *r)
return (struct resmem) {
.effective = eff,
- .overhead = ALLOC_OVERHEAD + sizeof(struct slab) + heads * get_page_size() - eff,
+ .overhead = ALLOC_OVERHEAD + sizeof(struct slab) + heads * page_size - eff,
};
}
@@ -402,10 +411,10 @@ slab_lookup(resource *r, unsigned long a)
struct sl_head *h;
WALK_LIST(h, s->partial_heads)
- if ((unsigned long) h < a && (unsigned long) h + get_page_size() < a)
+ if ((unsigned long) h < a && (unsigned long) h + page_size < a)
return r;
WALK_LIST(h, s->full_heads)
- if ((unsigned long) h < a && (unsigned long) h + get_page_size() < a)
+ if ((unsigned long) h < a && (unsigned long) h + page_size < a)
return r;
return NULL;
}
diff --git a/lib/socket.h b/lib/socket.h
index 96fedeeb..89398edf 100644
--- a/lib/socket.h
+++ b/lib/socket.h
@@ -12,6 +12,7 @@
#include <errno.h>
#include "lib/resource.h"
+#include "lib/event.h"
#ifdef HAVE_LIBSSH
#define LIBSSH_LEGACY_0_4
#include <libssh/libssh.h>
@@ -39,6 +40,7 @@ struct ssh_sock {
typedef struct birdsock {
resource r;
pool *pool; /* Pool where incoming connections should be allocated (for SK_xxx_PASSIVE) */
+ struct birdloop *loop; /* The birdloop where this socket belongs to */
int type; /* Socket type */
int subtype; /* Socket subtype */
void *data; /* User data */
@@ -56,6 +58,8 @@ typedef struct birdsock {
uint fast_rx; /* RX has higher priority in event loop */
uint rbsize;
int (*rx_hook)(struct birdsock *, uint size); /* NULL=receiving turned off, returns 1 to clear rx buffer */
+ struct event_cork *cork; /* Cork to temporarily stop receiving data */
+ node cork_node; /* Node in cork list */
byte *tbuf, *tpos; /* NULL=allocate automatically */
byte *ttx; /* Internal */
@@ -79,6 +83,7 @@ typedef struct birdsock {
const char *password; /* Password for MD5 authentication */
const char *err; /* Error message */
struct ssh_sock *ssh; /* Used in SK_SSH */
+ struct event reloop; /* Reloop event */
} sock;
sock *sock_new(pool *); /* Allocate new socket */
@@ -128,6 +133,7 @@ extern int sk_priority_control; /* Suggested priority for control traffic, shou
#define SKF_TRUNCATED 0x200 /* Received packet was truncated, set by IO layer */
#define SKF_HDRINCL 0x400 /* Used internally */
#define SKF_PKTINFO 0x800 /* Used internally */
+#define SKF_PASSIVE_THREAD 0x1000 /* Child sockets used in thread, do not add to main loop */
/*
* Socket types SA SP DA DP IF TTL SendTo (?=may, -=must not, *=must)
diff --git a/lib/timer.c b/lib/timer.c
index 381163d0..eb7ea690 100644
--- a/lib/timer.c
+++ b/lib/timer.c
@@ -32,61 +32,18 @@
#include "nest/bird.h"
+#include "lib/coro.h"
#include "lib/heap.h"
#include "lib/resource.h"
#include "lib/timer.h"
-
-struct timeloop main_timeloop;
-
-
-#ifdef USE_PTHREADS
-
#include <pthread.h>
-/* Data accessed and modified from proto/bfd/io.c */
-pthread_key_t current_time_key;
-
-static inline struct timeloop *
-timeloop_current(void)
-{
- return pthread_getspecific(current_time_key);
-}
-
-static inline void
-timeloop_init_current(void)
-{
- pthread_key_create(&current_time_key, NULL);
- pthread_setspecific(current_time_key, &main_timeloop);
-}
+_Atomic btime last_time;
+_Atomic btime real_time;
void wakeup_kick_current(void);
-#else
-
-/* Just use main timelooop */
-static inline struct timeloop * timeloop_current(void) { return &main_timeloop; }
-static inline void timeloop_init_current(void) { }
-
-#endif
-
-btime
-current_time(void)
-{
- return timeloop_current()->last_time;
-}
-
-btime
-current_real_time(void)
-{
- struct timeloop *loop = timeloop_current();
-
- if (!loop->real_time)
- times_update_real_time(loop);
-
- return loop->real_time;
-}
-
#define TIMER_LESS(a,b) ((a)->expires < (b)->expires)
#define TIMER_SWAP(heap,a,b,t) (t = heap[a], heap[a] = heap[b], heap[b] = t, \
@@ -112,7 +69,7 @@ tm_dump(resource *r)
if (t->recurrent)
debug("recur %d, ", t->recurrent);
if (t->expires)
- debug("expires in %d ms)\n", (t->expires - current_time()) TO_MS);
+ debug("in loop %p expires in %d ms)\n", t->loop, (t->expires - current_time()) TO_MS);
else
debug("inactive)\n");
}
@@ -135,41 +92,40 @@ tm_new(pool *p)
return t;
}
-void
-tm_set(timer *t, btime when)
+static void
+tm_set_in_tl(timer *t, btime when, struct timeloop *local_timeloop)
{
- struct timeloop *loop = timeloop_current();
- uint tc = timers_count(loop);
+ uint tc = timers_count(local_timeloop);
if (!t->expires)
{
t->index = ++tc;
t->expires = when;
- BUFFER_PUSH(loop->timers) = t;
- HEAP_INSERT(loop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP);
+ BUFFER_PUSH(local_timeloop->timers) = t;
+ HEAP_INSERT(local_timeloop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP);
}
else if (t->expires < when)
{
t->expires = when;
- HEAP_INCREASE(loop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP, t->index);
+ HEAP_INCREASE(local_timeloop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP, t->index);
}
else if (t->expires > when)
{
t->expires = when;
- HEAP_DECREASE(loop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP, t->index);
+ HEAP_DECREASE(local_timeloop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP, t->index);
}
-#ifdef CONFIG_BFD
- /* Hack to notify BFD loops */
- if ((loop != &main_timeloop) && (t->index == 1))
- wakeup_kick_current();
-#endif
+ t->loop = local_timeloop;
+
+ if ((t->index == 1) && (local_timeloop->coro != this_coro))
+ birdloop_ping(local_timeloop->loop);
}
void
-tm_start(timer *t, btime after)
+tm_set_in(timer *t, btime when, struct birdloop *loop)
{
- tm_set(t, current_time() + MAX(after, 0));
+ ASSERT_DIE(birdloop_inside(loop));
+ tm_set_in_tl(t, when, birdloop_time_loop(loop));
}
void
@@ -178,20 +134,22 @@ tm_stop(timer *t)
if (!t->expires)
return;
- struct timeloop *loop = timeloop_current();
- uint tc = timers_count(loop);
+ TLOCK_TIMER_ASSERT(t->loop);
- HEAP_DELETE(loop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP, t->index);
- BUFFER_POP(loop->timers);
+ uint tc = timers_count(t->loop);
+
+ HEAP_DELETE(t->loop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP, t->index);
+ BUFFER_POP(t->loop->timers);
t->index = -1;
t->expires = 0;
+ t->loop = NULL;
}
void
timers_init(struct timeloop *loop, pool *p)
{
- times_init(loop);
+ TLOCK_TIMER_ASSERT(loop);
BUFFER_INIT(loop->timers, p, 4);
BUFFER_PUSH(loop->timers) = NULL;
@@ -200,13 +158,15 @@ timers_init(struct timeloop *loop, pool *p)
void io_log_event(void *hook, void *data);
void
-timers_fire(struct timeloop *loop)
+timers_fire(struct timeloop *loop, int io_log)
{
+ TLOCK_TIMER_ASSERT(loop);
+
btime base_time;
timer *t;
- times_update(loop);
- base_time = loop->last_time;
+ times_update();
+ base_time = current_time();
while (t = timers_first(loop))
{
@@ -217,32 +177,25 @@ timers_fire(struct timeloop *loop)
{
btime when = t->expires + t->recurrent;
- if (when <= loop->last_time)
- when = loop->last_time + t->recurrent;
+ if (when <= base_time)
+ when = base_time + t->recurrent;
if (t->randomize)
when += random() % (t->randomize + 1);
- tm_set(t, when);
+ tm_set_in_tl(t, when, loop);
}
else
tm_stop(t);
/* This is ugly hack, we want to log just timers executed from the main I/O loop */
- if (loop == &main_timeloop)
+ if (io_log)
io_log_event(t->hook, t->data);
t->hook(t);
}
}
-void
-timer_init(void)
-{
- timers_init(&main_timeloop, &root_pool);
- timeloop_init_current();
-}
-
/**
* tm_parse_time - parse a date and time
diff --git a/lib/timer.h b/lib/timer.h
index c5ea430c..161e39d3 100644
--- a/lib/timer.h
+++ b/lib/timer.h
@@ -12,8 +12,14 @@
#include "nest/bird.h"
#include "lib/buffer.h"
+#include "lib/io-loop.h"
+#include "lib/locking.h"
#include "lib/resource.h"
+#include <stdatomic.h>
+
+extern _Atomic btime last_time;
+extern _Atomic btime real_time;
typedef struct timer
{
@@ -25,36 +31,44 @@ typedef struct timer
uint randomize; /* Amount of randomization */
uint recurrent; /* Timer recurrence */
+ struct timeloop *loop; /* Loop where the timer is active */
+
int index;
} timer;
struct timeloop
{
BUFFER_(timer *) timers;
- btime last_time;
- btime real_time;
+ struct domain_generic *domain;
+ struct birdloop *loop;
+ struct coroutine *coro;
};
+#define TLOCK_TIMER_ASSERT(loop) ASSERT_DIE((loop)->domain && DG_IS_LOCKED((loop)->domain))
+#define TLOCK_LOCAL_ASSERT(loop) ASSERT_DIE(!(loop)->domain || DG_IS_LOCKED((loop)->domain))
+
static inline uint timers_count(struct timeloop *loop)
-{ return loop->timers.used - 1; }
+{ TLOCK_TIMER_ASSERT(loop); return loop->timers.used - 1; }
static inline timer *timers_first(struct timeloop *loop)
-{ return (loop->timers.used > 1) ? loop->timers.data[1] : NULL; }
+{ TLOCK_TIMER_ASSERT(loop); return (loop->timers.used > 1) ? loop->timers.data[1] : NULL; }
-extern struct timeloop main_timeloop;
-
-btime current_time(void);
-btime current_real_time(void);
+#define current_time() atomic_load_explicit(&last_time, memory_order_acquire)
+#define current_real_time() atomic_load_explicit(&real_time, memory_order_acquire)
+#define current_time_update() ({ times_update(); current_time(); })
//#define now (current_time() TO_S)
//#define now_real (current_real_time() TO_S)
extern btime boot_time;
timer *tm_new(pool *p);
-void tm_set(timer *t, btime when);
-void tm_start(timer *t, btime after);
+#define tm_set(t, when) tm_set_in((t), (when), &main_birdloop)
+#define tm_start(t, after) tm_start_in((t), (after), &main_birdloop)
void tm_stop(timer *t);
+void tm_set_in(timer *t, btime when, struct birdloop *loop);
+#define tm_start_in(t, after, loop) tm_set_in((t), (current_time() + MAX_((after), 0)), loop)
+
static inline int
tm_active(timer *t)
{
@@ -94,15 +108,11 @@ tm_start_max(timer *t, btime after)
}
/* In sysdep code */
-void times_init(struct timeloop *loop);
-void times_update(struct timeloop *loop);
-void times_update_real_time(struct timeloop *loop);
+void times_update(void);
/* For I/O loop */
void timers_init(struct timeloop *loop, pool *p);
-void timers_fire(struct timeloop *loop);
-
-void timer_init(void);
+void timers_fire(struct timeloop *loop, int io_log);
struct timeformat {
diff --git a/nest/a-path_test.c b/nest/a-path_test.c
index 9ed0a786..2533dbae 100644
--- a/nest/a-path_test.c
+++ b/nest/a-path_test.c
@@ -12,6 +12,7 @@
#include "nest/route.h"
#include "nest/attrs.h"
#include "lib/resource.h"
+#include "lib/io-loop.h"
#define TESTS_NUM 30
#define AS_PATH_LENGTH 1000
@@ -23,8 +24,6 @@
static int
t_as_path_match(void)
{
- resource_init();
-
int round;
for (round = 0; round < TESTS_NUM; round++)
{
@@ -70,8 +69,6 @@ t_as_path_match(void)
static int
t_path_format(void)
{
- resource_init();
-
struct adata empty_as_path = {};
struct adata *as_path = &empty_as_path;
struct linpool *lp = lp_new_default(&root_pool);
@@ -116,8 +113,6 @@ count_asn_in_array(const u32 *array, u32 asn)
static int
t_path_include(void)
{
- resource_init();
-
struct adata empty_as_path = {};
struct adata *as_path = &empty_as_path;
struct linpool *lp = lp_new_default(&root_pool);
@@ -161,8 +156,6 @@ t_path_include(void)
static int
t_as_path_converting(void)
{
- resource_init();
-
struct adata empty_as_path = {};
struct adata *as_path = &empty_as_path;
struct linpool *lp = lp_new_default(&root_pool);
@@ -204,10 +197,18 @@ t_as_path_converting(void)
}
#endif
+void resource_sys_init(void);
+void io_init(void);
+
int
main(int argc, char *argv[])
{
bt_init(argc, argv);
+ resource_sys_init();
+ resource_init();
+ the_bird_lock();
+ birdloop_init();
+ io_init();
bt_test_suite(t_as_path_match, "Testing AS path matching and some a-path utilities.");
bt_test_suite(t_path_format, "Testing formating as path into byte buffer");
diff --git a/nest/a-set_test.c b/nest/a-set_test.c
index 96b6a727..f8f6e781 100644
--- a/nest/a-set_test.c
+++ b/nest/a-set_test.c
@@ -13,6 +13,7 @@
#include "nest/route.h"
#include "nest/attrs.h"
#include "lib/resource.h"
+#include "lib/io-loop.h"
#define SET_SIZE 10
static const struct adata *set_sequence; /* <0; SET_SIZE) */
@@ -71,7 +72,6 @@ t_set_int_contains(void)
{
int i;
- resource_init();
generate_set_sequence(SET_TYPE_INT, SET_SIZE);
bt_assert(int_set_get_size(set_sequence) == SET_SIZE);
@@ -92,7 +92,6 @@ t_set_int_contains(void)
static int
t_set_int_union(void)
{
- resource_init();
generate_set_sequence(SET_TYPE_INT, SET_SIZE);
const struct adata *set_union;
@@ -111,7 +110,6 @@ t_set_int_union(void)
static int
t_set_int_format(void)
{
- resource_init();
generate_set_sequence(SET_TYPE_INT, SET_SIZE_FOR_FORMAT_OUTPUT);
bt_assert(int_set_format(set_sequence, 0, 0, buf, BUFFER_SIZE) == 0);
@@ -132,7 +130,6 @@ t_set_int_format(void)
static int
t_set_int_delete(void)
{
- resource_init();
generate_set_sequence(SET_TYPE_INT, SET_SIZE);
const struct adata *deleting_sequence = set_sequence;
@@ -160,7 +157,6 @@ t_set_ec_contains(void)
{
u32 i;
- resource_init();
generate_set_sequence(SET_TYPE_EC, SET_SIZE);
bt_assert(ec_set_get_size(set_sequence) == SET_SIZE);
@@ -181,7 +177,6 @@ t_set_ec_contains(void)
static int
t_set_ec_union(void)
{
- resource_init();
generate_set_sequence(SET_TYPE_EC, SET_SIZE);
const struct adata *set_union;
@@ -200,8 +195,6 @@ t_set_ec_union(void)
static int
t_set_ec_format(void)
{
- resource_init();
-
const struct adata empty_as_path = {};
set_sequence = set_sequence_same = set_sequence_higher = set_random = &empty_as_path;
lp = lp_new_default(&root_pool);
@@ -222,7 +215,6 @@ t_set_ec_format(void)
static int
t_set_ec_delete(void)
{
- resource_init();
generate_set_sequence(SET_TYPE_EC, SET_SIZE);
const struct adata *deleting_sequence = set_sequence;
@@ -240,10 +232,17 @@ t_set_ec_delete(void)
return 1;
}
+
+void resource_sys_init(void);
+
int
main(int argc, char *argv[])
{
bt_init(argc, argv);
+ resource_sys_init();
+ resource_init();
+ the_bird_lock();
+ birdloop_init();
bt_test_suite(t_set_int_contains, "Testing sets of integers: contains, get_data");
bt_test_suite(t_set_int_format, "Testing sets of integers: format");
diff --git a/nest/bfd.h b/nest/bfd.h
index 37561266..c91d6648 100644
--- a/nest/bfd.h
+++ b/nest/bfd.h
@@ -23,6 +23,11 @@ struct bfd_options {
u8 mode;
};
+struct bfd_session_state {
+ u8 state;
+ u8 diag;
+};
+
struct bfd_request {
resource r;
node n;
@@ -35,12 +40,12 @@ struct bfd_request {
void (*hook)(struct bfd_request *);
void *data;
+ event event;
struct bfd_session *session;
-
+ struct bfd_session_state old_state;
u8 state;
u8 diag;
- u8 old_state;
u8 down;
};
@@ -51,13 +56,12 @@ struct bfd_request {
#define BFD_STATE_INIT 2
#define BFD_STATE_UP 3
-
static inline struct bfd_options * bfd_new_options(void)
{ return cfg_allocz(sizeof(struct bfd_options)); }
#ifdef CONFIG_BFD
-struct bfd_request * bfd_request_session(pool *p, ip_addr addr, ip_addr local, struct iface *iface, struct iface *vrf, void (*hook)(struct bfd_request *), void *data, const struct bfd_options *opts);
+struct bfd_request * bfd_request_session(pool *p, ip_addr addr, ip_addr local, struct iface *iface, struct iface *vrf, void (*hook)(struct bfd_request *), void *data, struct event_list *list, const struct bfd_options *opts);
void bfd_update_request(struct bfd_request *req, const struct bfd_options *opts);
static inline void cf_check_bfd(int use UNUSED) { }
diff --git a/nest/cli.c b/nest/cli.c
index b54a0d76..7e5d2151 100644
--- a/nest/cli.c
+++ b/nest/cli.c
@@ -262,7 +262,7 @@ cli_command(struct cli *c)
log(L_TRACE "CLI: %s", c->rx_buf);
bzero(&f, sizeof(f));
f.mem = c->parser_pool;
- f.pool = rp_new(c->pool, "Config");
+ f.pool = rp_new(c->pool, &main_birdloop, "Config");
init_list(&f.symbols);
cf_read_hook = cli_cmd_read_hook;
cli_rh_pos = c->rx_buf;
@@ -308,7 +308,7 @@ cli_event(void *data)
cli *
cli_new(void *priv)
{
- pool *p = rp_new(cli_pool, "CLI");
+ pool *p = rp_new(cli_pool, &main_birdloop, "CLI");
cli *c = mb_alloc(p, sizeof(cli));
bzero(c, sizeof(cli));
@@ -413,7 +413,7 @@ cli_free(cli *c)
c->cleanup(c);
if (c == cmd_reconfig_stored_cli)
cmd_reconfig_stored_cli = NULL;
- rfree(c->pool);
+ rp_free(c->pool, &root_pool);
}
/**
@@ -425,7 +425,7 @@ cli_free(cli *c)
void
cli_init(void)
{
- cli_pool = rp_new(&root_pool, "CLI");
+ cli_pool = rp_new(&root_pool, &main_birdloop, "CLI");
init_list(&cli_log_hooks);
cli_log_inited = 1;
}
diff --git a/nest/cmds.c b/nest/cmds.c
index 1a16f9c7..77c92077 100644
--- a/nest/cmds.c
+++ b/nest/cmds.c
@@ -114,15 +114,10 @@ cmd_show_memory(void)
{
cli_msg(-1018, "BIRD memory usage");
cli_msg(-1018, "%-17s Effective Overhead", "");
- print_size("Routing tables:", rmemsize(rt_table_pool));
- print_size("Route attributes:", rmemsize(rta_pool));
- print_size("Protocols:", rmemsize(proto_pool));
- struct resmem total = rmemsize(&root_pool);
-#ifdef HAVE_MMAP
- print_size("Standby memory:", (struct resmem) { .overhead = get_page_size() * pages_kept });
- total.overhead += get_page_size() * pages_kept;
-#endif
- print_size("Total:", total);
+ print_size("Routing tables:", rp_memsize(rt_table_pool));
+ print_size("Route attributes:", rp_memsize(rta_pool));
+ print_size("Protocols:", rp_memsize(proto_pool));
+ print_size("Total:", rp_memsize(&root_pool));
cli_msg(0, "");
}
diff --git a/nest/config.Y b/nest/config.Y
index 7ead8589..f9ed0e69 100644
--- a/nest/config.Y
+++ b/nest/config.Y
@@ -111,7 +111,7 @@ proto_postconfig(void)
CF_DECLS
-CF_KEYWORDS(ROUTER, ID, HOSTNAME, PROTOCOL, TEMPLATE, PREFERENCE, DISABLED, DEBUG, ALL, OFF, DIRECT)
+CF_KEYWORDS(ROUTER, ID, HOSTNAME, PROTOCOL, TEMPLATE, PREFERENCE, DISABLED, DEBUG, ALL, OFF, DIRECT, PIPE)
CF_KEYWORDS(INTERFACE, IMPORT, EXPORT, FILTER, NONE, VRF, DEFAULT, TABLE, STATES, ROUTES, FILTERS)
CF_KEYWORDS(IPV4, IPV6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6, SADR, MPLS)
CF_KEYWORDS(RECEIVE, LIMIT, ACTION, WARN, BLOCK, RESTART, DISABLE, KEEP, FILTERED, RPKI)
@@ -128,7 +128,7 @@ CF_KEYWORDS(CHECK, LINK)
/* For r_args_channel */
CF_KEYWORDS(IPV4, IPV4_MC, IPV4_MPLS, IPV6, IPV6_MC, IPV6_MPLS, IPV6_SADR, VPN4, VPN4_MC, VPN4_MPLS, VPN6, VPN6_MC, VPN6_MPLS, ROA4, ROA6, FLOW4, FLOW6, MPLS, PRI, SEC)
-CF_ENUM(T_ENUM_RTS, RTS_, DUMMY, STATIC, INHERIT, DEVICE, STATIC_DEVICE, REDIRECT,
+CF_ENUM(T_ENUM_RTS, RTS_, STATIC, INHERIT, DEVICE, STATIC_DEVICE, REDIRECT,
RIP, OSPF, OSPF_IA, OSPF_EXT1, OSPF_EXT2, BGP, PIPE, BABEL)
CF_ENUM(T_ENUM_SCOPE, SCOPE_, HOST, LINK, SITE, ORGANIZATION, UNIVERSE, UNDEFINED)
CF_ENUM(T_ENUM_RTD, RTD_, UNICAST, BLACKHOLE, UNREACHABLE, PROHIBIT)
@@ -266,8 +266,8 @@ proto_item:
| MRTDUMP mrtdump_mask { this_proto->mrtdump = $2; }
| ROUTER ID idval { this_proto->router_id = $3; }
| DESCRIPTION text { this_proto->dsc = $2; }
- | VRF text { this_proto->vrf = if_get_by_name($2); this_proto->vrf_set = 1; }
- | VRF DEFAULT { this_proto->vrf = NULL; this_proto->vrf_set = 1; }
+ | VRF text { this_proto->vrf = if_get_by_name($2); }
+ | VRF DEFAULT { this_proto->vrf = &default_vrf; }
;
@@ -348,6 +348,7 @@ debug_default:
DEBUG PROTOCOLS debug_mask { new_config->proto_default_debug = $3; }
| DEBUG CHANNELS debug_mask { new_config->channel_default_debug = $3; }
| DEBUG COMMANDS expr { new_config->cli_debug = $3; }
+ | DEBUG TABLES bool { new_config->table_debug = $3; }
;
/* MRTDUMP PROTOCOLS is in systep/unix/config.Y */
@@ -433,6 +434,7 @@ proto: dev_proto '}' ;
dev_proto_start: proto_start DIRECT {
this_proto = proto_config_new(&proto_device, $1);
init_list(&DIRECT_CFG->iface_list);
+ this_proto->late_if_feed = 1;
}
;
@@ -643,12 +645,12 @@ r_args:
}
| r_args IMPORT TABLE channel_arg {
if (!$4->in_table) cf_error("No import table in channel %s.%s", $4->proto->name, $4->name);
- rt_show_add_table($$, $4->in_table);
+ rt_show_add_table($$, $4->in_table->tab);
$$->tables_defined_by = RSD_TDB_DIRECT;
}
| r_args EXPORT TABLE channel_arg {
if (!$4->out_table) cf_error("No export table in channel %s.%s", $4->proto->name, $4->name);
- rt_show_add_table($$, $4->out_table);
+ rt_show_add_table($$, $4->out_table->tab);
$$->tables_defined_by = RSD_TDB_DIRECT;
}
| r_args FILTER filter {
@@ -808,7 +810,7 @@ sym_args:
CF_CLI_HELP(DUMP, ..., [[Dump debugging information]])
CF_CLI(DUMP RESOURCES,,, [[Dump all allocated resource]])
-{ rdump(&root_pool); cli_msg(0, ""); } ;
+{ rp_dump(&root_pool); cli_msg(0, ""); } ;
CF_CLI(DUMP SOCKETS,,, [[Dump open sockets]])
{ sk_dump_all(); cli_msg(0, ""); } ;
CF_CLI(DUMP EVENTS,,, [[Dump event log]])
@@ -819,8 +821,10 @@ CF_CLI(DUMP NEIGHBORS,,, [[Dump neighbor cache]])
{ neigh_dump_all(); cli_msg(0, ""); } ;
CF_CLI(DUMP ATTRIBUTES,,, [[Dump attribute cache]])
{ rta_dump_all(); cli_msg(0, ""); } ;
-CF_CLI(DUMP ROUTES,,, [[Dump routing table]])
+CF_CLI(DUMP ROUTES,,, [[Dump routes]])
{ rt_dump_all(); cli_msg(0, ""); } ;
+CF_CLI(DUMP TABLES,,, [[Dump table connections]])
+{ rt_dump_hooks_all(); cli_msg(0, ""); } ;
CF_CLI(DUMP PROTOCOLS,,, [[Dump protocol information]])
{ protos_dump_all(); cli_msg(0, ""); } ;
CF_CLI(DUMP FILTER ALL,,, [[Dump all filters in linearized form]])
diff --git a/nest/iface.c b/nest/iface.c
index 682340c5..78f4eb0f 100644
--- a/nest/iface.c
+++ b/nest/iface.c
@@ -36,9 +36,12 @@
static pool *if_pool;
-list iface_list;
+DOMAIN(attrs) iface_domain;
+list global_iface_list;
+struct iface default_vrf;
static void if_recalc_preferred(struct iface *i);
+static void ifa_delete_locked(struct ifa *a);
/**
* ifa_dump - dump interface address
@@ -49,6 +52,7 @@ static void if_recalc_preferred(struct iface *i);
void
ifa_dump(struct ifa *a)
{
+ IFACE_LEGACY_ACCESS;
debug("\t%I, net %N bc %I -> %I%s%s%s%s\n", a->ip, &a->prefix, a->brd, a->opposite,
(a->flags & IA_PRIMARY) ? " PRIMARY" : "",
(a->flags & IA_SECONDARY) ? " SEC" : "",
@@ -68,6 +72,7 @@ if_dump(struct iface *i)
{
struct ifa *a;
+ IFACE_LEGACY_ACCESS;
debug("IF%d: %s", i->index, i->name);
if (i->flags & IF_SHUTDOWN)
debug(" SHUTDOWN");
@@ -109,8 +114,9 @@ if_dump_all(void)
{
struct iface *i;
+ IFACE_LEGACY_ACCESS;
debug("Known network interfaces:\n");
- WALK_LIST(i, iface_list)
+ WALK_LIST(i, global_iface_list)
if_dump(i);
debug("Router ID: %08x\n", config->router_id);
}
@@ -147,7 +153,7 @@ ifa_send_notify(struct proto *p, unsigned c, struct ifa *a)
{
if (p->ifa_notify &&
(p->proto_state != PS_DOWN) &&
- (!p->vrf_set || p->vrf == a->iface->master))
+ (!p->vrf || p->vrf == a->iface->master))
{
if (p->debug & D_IFACES)
log(L_TRACE "%s < address %N on interface %s %s",
@@ -165,7 +171,8 @@ ifa_notify_change_(unsigned c, struct ifa *a)
DBG("IFA change notification (%x) for %s:%I\n", c, a->iface->name, a->ip);
WALK_LIST(p, proto_list)
- ifa_send_notify(p, c, a);
+ PROTO_LOCKED_FROM_MAIN(p)
+ ifa_send_notify(p, c, a);
}
static inline void
@@ -185,7 +192,7 @@ if_send_notify(struct proto *p, unsigned c, struct iface *i)
{
if (p->if_notify &&
(p->proto_state != PS_DOWN) &&
- (!p->vrf_set || p->vrf == i->master))
+ (!p->vrf || p->vrf == i->master))
{
if (p->debug & D_IFACES)
log(L_TRACE "%s < interface %s %s", p->name, i->name,
@@ -225,7 +232,8 @@ if_notify_change(unsigned c, struct iface *i)
ifa_notify_change_(IF_CHANGE_DOWN, a);
WALK_LIST(p, proto_list)
- if_send_notify(p, c, i);
+ PROTO_LOCKED_FROM_MAIN(p)
+ if_send_notify(p, c, i);
if (c & IF_CHANGE_UP)
WALK_LIST(a, i->addrs)
@@ -243,7 +251,7 @@ if_recalc_flags(struct iface *i UNUSED, uint flags)
{
if ((flags & IF_ADMIN_UP) &&
!(flags & (IF_SHUTDOWN | IF_TMP_DOWN)) &&
- !(i->master_index && !i->master))
+ !(i->master_index && i->master == &default_vrf))
flags |= IF_UP;
else
flags &= ~IF_UP;
@@ -301,7 +309,13 @@ if_update(struct iface *new)
struct iface *i;
unsigned c;
- WALK_LIST(i, iface_list)
+ if (!new->master)
+ new->master = &default_vrf;
+
+ IFACE_LEGACY_ACCESS;
+ IFACE_LOCK;
+
+ WALK_LIST(i, global_iface_list)
if (!strcmp(new->name, i->name))
{
new->flags = if_recalc_flags(new, new->flags);
@@ -322,6 +336,8 @@ if_update(struct iface *new)
}
if_copy(i, new);
+ IFACE_UNLOCK;
+
if (c)
if_notify_change(c, i);
@@ -334,7 +350,9 @@ if_update(struct iface *new)
newif:
init_list(&i->neighbors);
i->flags |= IF_UPDATED | IF_TMP_DOWN; /* Tmp down as we don't have addresses yet */
- add_tail(&iface_list, &i->n);
+ add_tail(&global_iface_list, &i->n);
+ IFACE_UNLOCK;
+
return i;
}
@@ -344,7 +362,8 @@ if_start_update(void)
struct iface *i;
struct ifa *a;
- WALK_LIST(i, iface_list)
+ IFACE_LEGACY_ACCESS;
+ WALK_LIST(i, global_iface_list)
{
i->flags &= ~IF_UPDATED;
WALK_LIST(a, i->addrs)
@@ -355,6 +374,8 @@ if_start_update(void)
void
if_end_partial_update(struct iface *i)
{
+ IFACE_LEGACY_ACCESS;
+
if (i->flags & IF_NEEDS_RECALC)
if_recalc_preferred(i);
@@ -368,7 +389,8 @@ if_end_update(void)
struct iface *i;
struct ifa *a, *b;
- WALK_LIST(i, iface_list)
+ IFACE_LEGACY_ACCESS;
+ WALK_LIST(i, global_iface_list)
{
if (!(i->flags & IF_UPDATED))
if_change_flags(i, (i->flags & ~IF_ADMIN_UP) | IF_SHUTDOWN);
@@ -376,7 +398,11 @@ if_end_update(void)
{
WALK_LIST_DELSAFE(a, b, i->addrs)
if (!(a->flags & IA_UPDATED))
- ifa_delete(a);
+ {
+ IFACE_LOCK;
+ ifa_delete_locked(a);
+ IFACE_UNLOCK;
+ }
if_end_partial_update(i);
}
}
@@ -385,6 +411,7 @@ if_end_update(void)
void
if_flush_ifaces(struct proto *p)
{
+ IFACE_LEGACY_ACCESS;
if (p->debug & D_EVENTS)
log(L_TRACE "%s: Flushing interfaces", p->name);
if_start_update();
@@ -404,10 +431,12 @@ if_feed_baby(struct proto *p)
struct iface *i;
struct ifa *a;
+ IFACE_LEGACY_ACCESS;
+
if (!p->if_notify && !p->ifa_notify) /* shortcut */
return;
DBG("Announcing interfaces to new protocol %s\n", p->name);
- WALK_LIST(i, iface_list)
+ WALK_LIST(i, global_iface_list)
{
if_send_notify(p, IF_CHANGE_CREATE | ((i->flags & IF_UP) ? IF_CHANGE_UP : 0), i);
if (i->flags & IF_UP)
@@ -429,9 +458,15 @@ if_find_by_index(unsigned idx)
{
struct iface *i;
- WALK_LIST(i, iface_list)
+ IFACE_LOCK;
+ WALK_LIST(i, global_iface_list)
if (i->index == idx && !(i->flags & IF_SHUTDOWN))
+ {
+ IFACE_UNLOCK;
return i;
+ }
+
+ IFACE_UNLOCK;
return NULL;
}
@@ -448,9 +483,15 @@ if_find_by_name(const char *name)
{
struct iface *i;
- WALK_LIST(i, iface_list)
+ IFACE_LOCK;
+ WALK_LIST(i, global_iface_list)
if (!strcmp(i->name, name) && !(i->flags & IF_SHUTDOWN))
+ {
+ IFACE_UNLOCK;
return i;
+ }
+
+ IFACE_UNLOCK;
return NULL;
}
@@ -459,17 +500,21 @@ if_get_by_name(const char *name)
{
struct iface *i;
- WALK_LIST(i, iface_list)
+ IFACE_LEGACY_ACCESS;
+
+ WALK_LIST(i, global_iface_list)
if (!strcmp(i->name, name))
return i;
/* No active iface, create a dummy */
+ IFACE_LOCK;
i = mb_allocz(if_pool, sizeof(struct iface));
strncpy(i->name, name, sizeof(i->name)-1);
i->flags = IF_SHUTDOWN;
init_list(&i->addrs);
init_list(&i->neighbors);
- add_tail(&iface_list, &i->n);
+ add_tail(&global_iface_list, &i->n);
+ IFACE_UNLOCK;
return i;
}
@@ -555,7 +600,9 @@ if_recalc_all_preferred_addresses(void)
{
struct iface *i;
- WALK_LIST(i, iface_list)
+ IFACE_LEGACY_ACCESS;
+
+ WALK_LIST(i, global_iface_list)
{
if_recalc_preferred(i);
@@ -585,6 +632,8 @@ ifa_update(struct ifa *a)
struct iface *i = a->iface;
struct ifa *b;
+ IFACE_LEGACY_ACCESS;
+
WALK_LIST(b, i->addrs)
if (ifa_same(b, a))
{
@@ -603,10 +652,12 @@ ifa_update(struct ifa *a)
if ((a->prefix.type == NET_IP4) && (i->flags & IF_BROADCAST) && ipa_zero(a->brd))
log(L_WARN "Missing broadcast address for interface %s", i->name);
+ IFACE_LOCK;
b = mb_alloc(if_pool, sizeof(struct ifa));
memcpy(b, a, sizeof(struct ifa));
add_tail(&i->addrs, &b->n);
b->flags |= IA_UPDATED;
+ IFACE_UNLOCK;
i->flags |= IF_NEEDS_RECALC;
if (i->flags & IF_UP)
@@ -631,6 +682,17 @@ ifa_delete(struct ifa *a)
WALK_LIST(b, i->addrs)
if (ifa_same(b, a))
{
+ IFACE_LOCK;
+ ifa_delete_locked(b);
+ IFACE_UNLOCK;
+ return;
+ }
+}
+
+static void
+ifa_delete_locked(struct ifa *b)
+{
+ struct iface *i = b->iface;
rem_node(&b->n);
if (b->flags & IA_PRIMARY)
@@ -653,7 +715,6 @@ ifa_delete(struct ifa *a)
mb_free(b);
return;
- }
}
u32
@@ -662,8 +723,10 @@ if_choose_router_id(struct iface_patt *mask, u32 old_id)
struct iface *i;
struct ifa *a, *b;
+ IFACE_LEGACY_ACCESS;
+
b = NULL;
- WALK_LIST(i, iface_list)
+ WALK_LIST(i, global_iface_list)
{
if (!(i->flags & IF_ADMIN_UP) ||
(i->flags & IF_SHUTDOWN))
@@ -709,8 +772,10 @@ if_choose_router_id(struct iface_patt *mask, u32 old_id)
void
if_init(void)
{
- if_pool = rp_new(&root_pool, "Interfaces");
- init_list(&iface_list);
+ iface_domain = DOMAIN_NEW(attrs, "Interfaces");
+ if_pool = rp_new(&root_pool, &main_birdloop, "Interfaces");
+ init_list(&global_iface_list);
+ strcpy(default_vrf.name, "default");
neigh_init(if_pool);
}
@@ -837,13 +902,15 @@ if_show(void)
struct ifa *a;
char *type;
- WALK_LIST(i, iface_list)
+ IFACE_LEGACY_ACCESS;
+
+ WALK_LIST(i, global_iface_list)
{
if (i->flags & IF_SHUTDOWN)
continue;
char mbuf[16 + sizeof(i->name)] = {};
- if (i->master)
+ if (i->master != &default_vrf)
bsprintf(mbuf, " master=%s", i->master->name);
else if (i->master_index)
bsprintf(mbuf, " master=#%u", i->master_index);
@@ -879,8 +946,10 @@ if_show_summary(void)
{
struct iface *i;
+ IFACE_LEGACY_ACCESS;
+
cli_msg(-2005, "%-10s %-6s %-18s %s", "Interface", "State", "IPv4 address", "IPv6 address");
- WALK_LIST(i, iface_list)
+ WALK_LIST(i, global_iface_list)
{
byte a4[IPA_MAX_TEXT_LENGTH + 17];
byte a6[IPA_MAX_TEXT_LENGTH + 17];
diff --git a/nest/iface.h b/nest/iface.h
index 1189cdd4..87ad86cf 100644
--- a/nest/iface.h
+++ b/nest/iface.h
@@ -9,10 +9,20 @@
#ifndef _BIRD_IFACE_H_
#define _BIRD_IFACE_H_
+#include "lib/event.h"
#include "lib/lists.h"
#include "lib/ip.h"
+#include "lib/locking.h"
-extern list iface_list;
+DEFINE_DOMAIN(attrs);
+extern list global_iface_list;
+extern DOMAIN(attrs) iface_domain;
+
+#define IFACE_LEGACY_ACCESS ASSERT_DIE(birdloop_inside(&main_birdloop))
+
+#define IFACE_LOCK LOCK_DOMAIN(attrs, iface_domain)
+#define IFACE_UNLOCK UNLOCK_DOMAIN(attrs, iface_domain)
+#define ASSERT_IFACE_LOCKED ASSERT_DIE(DOMAIN_IS_LOCKED(attrs, iface_domain))
struct proto;
struct pool;
@@ -28,6 +38,8 @@ struct ifa { /* Interface address */
unsigned flags; /* Analogous to iface->flags */
};
+extern struct iface default_vrf;
+
struct iface {
node n;
char name[16];
@@ -129,6 +141,7 @@ typedef struct neighbor {
struct ifa *ifa; /* Ifa on related iface */
struct iface *iface; /* Interface it's connected to */
struct iface *ifreq; /* Requested iface, NULL for any */
+ struct event event; /* Notification event */
struct proto *proto; /* Protocol this belongs to */
void *data; /* Protocol-specific data */
uint aux; /* Protocol-specific data */
@@ -140,13 +153,14 @@ typedef struct neighbor {
#define NEF_STICKY 1
#define NEF_ONLINK 2
#define NEF_IFACE 4 /* Entry for whole iface */
+#define NEF_NOTIFY_MAIN 0x100 /* Notify from main_birdloop context */
neighbor *neigh_find(struct proto *p, ip_addr a, struct iface *ifa, uint flags);
void neigh_dump(neighbor *);
void neigh_dump_all(void);
-void neigh_prune(void);
+void neigh_prune(struct proto *p);
void neigh_if_up(struct iface *);
void neigh_if_down(struct iface *);
void neigh_if_link(struct iface *);
diff --git a/nest/limit.h b/nest/limit.h
new file mode 100644
index 00000000..5838ad3b
--- /dev/null
+++ b/nest/limit.h
@@ -0,0 +1,49 @@
+/*
+ * BIRD Internet Routing Daemon -- Limits
+ *
+ * (c) 1998--2000 Martin Mares <mj@ucw.cz>
+ * (c) 2021 Maria Matejka <mq@jmq.cz>
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_LIMIT_H_
+#define _BIRD_LIMIT_H_
+
+struct limit {
+ u32 max;
+ u32 count;
+ int (*action)(struct limit *, void *data);
+};
+
+static inline int limit_do_action(struct limit *l, void *data)
+{
+ return l->action ? l->action(l, data) : 1;
+}
+
+static inline int limit_push(struct limit *l, void *data)
+{
+ if ((l->count >= l->max) && limit_do_action(l, data))
+ return 1;
+
+ l->count++;
+ return 0;
+}
+
+static inline void limit_pop(struct limit *l)
+{
+ --l->count;
+}
+
+static inline void limit_reset(struct limit *l)
+{
+ l->count = 0;
+}
+
+static inline void limit_update(struct limit *l, void *data, u32 max)
+{
+ if (l->count > (l->max = max))
+ limit_do_action(l, data);
+}
+
+#endif
diff --git a/nest/neighbor.c b/nest/neighbor.c
index 1a31fb79..7b951366 100644
--- a/nest/neighbor.c
+++ b/nest/neighbor.c
@@ -59,6 +59,9 @@
static slab *neigh_slab;
static list neigh_hash_table[NEIGH_HASH_SIZE], sticky_neigh_list;
+static void neigh_do_notify(void *);
+static void neigh_do_notify_main(void *);
+static void neigh_free(neighbor *n);
static inline uint
neigh_hash(struct proto *p, ip_addr a, struct iface *i)
@@ -142,7 +145,7 @@ if_connected(ip_addr a, struct iface *i, struct ifa **ap, uint flags)
}
static inline int
-if_connected_any(ip_addr a, struct iface *vrf, uint vrf_set, struct iface **iface, struct ifa **addr, uint flags)
+if_connected_any(ip_addr a, struct iface *vrf, struct iface **iface, struct ifa **addr, uint flags)
{
struct iface *i;
struct ifa *b;
@@ -152,8 +155,8 @@ if_connected_any(ip_addr a, struct iface *vrf, uint vrf_set, struct iface **ifac
*addr = NULL;
/* Prefer SCOPE_HOST or longer prefix */
- WALK_LIST(i, iface_list)
- if ((!vrf_set || vrf == i->master) && ((s = if_connected(a, i, &b, flags)) >= 0))
+ WALK_LIST(i, global_iface_list)
+ if ((!vrf || vrf == i->master) && ((s = if_connected(a, i, &b, flags)) >= 0))
if (scope_better(s, scope) || (scope_remote(s, scope) && ifa_better(b, *addr)))
{
*iface = i;
@@ -216,28 +219,34 @@ neigh_find(struct proto *p, ip_addr a, struct iface *iface, uint flags)
struct iface *ifreq = iface;
struct ifa *addr = NULL;
+ IFACE_LOCK;
WALK_LIST(n, neigh_hash_table[h]) /* Search the cache */
if ((n->proto == p) && ipa_equal(n->addr, a) && (n->ifreq == iface))
+ {
+ IFACE_UNLOCK;
return n;
+ }
+
+#define NOT_FOUND goto not_found
if (flags & NEF_IFACE)
{
if (ipa_nonzero(a) || !iface)
- return NULL;
+ NOT_FOUND;
}
else
{
class = ipa_classify(a);
if (class < 0) /* Invalid address */
- return NULL;
+ NOT_FOUND;
if (((class & IADDR_SCOPE_MASK) == SCOPE_HOST) ||
(((class & IADDR_SCOPE_MASK) == SCOPE_LINK) && !iface) ||
!(class & IADDR_HOST))
- return NULL; /* Bad scope or a somecast */
+ NOT_FOUND; /* Bad scope or a somecast */
}
if ((flags & NEF_ONLINK) && !iface)
- return NULL;
+ NOT_FOUND;
if (iface)
{
@@ -245,13 +254,13 @@ neigh_find(struct proto *p, ip_addr a, struct iface *iface, uint flags)
iface = (scope < 0) ? NULL : iface;
}
else
- scope = if_connected_any(a, p->vrf, p->vrf_set, &iface, &addr, flags);
+ scope = if_connected_any(a, p->vrf, &iface, &addr, flags);
/* scope < 0 means i don't know neighbor */
/* scope >= 0 <=> iface != NULL */
if ((scope < 0) && !(flags & NEF_STICKY))
- return NULL;
+ NOT_FOUND;
n = sl_allocz(neigh_slab);
add_tail(&neigh_hash_table[h], &n->n);
@@ -264,7 +273,36 @@ neigh_find(struct proto *p, ip_addr a, struct iface *iface, uint flags)
n->flags = flags;
n->scope = scope;
+ ASSERT_DIE(birdloop_inside(p->loop));
+
+ if (flags & NEF_NOTIFY_MAIN)
+ n->event = (event) {
+ .hook = neigh_do_notify_main,
+ .data = n,
+ .list = &global_event_list,
+ };
+ else if (p->loop == &main_birdloop)
+ n->event = (event) {
+ .hook = neigh_do_notify,
+ .data = n,
+ .list = &global_event_list,
+ };
+ else
+ {
+ birdloop_link(p->loop);
+ n->event = (event) {
+ .hook = neigh_do_notify,
+ .data = n,
+ .list = birdloop_event_list(p->loop),
+ };
+ }
+
+ IFACE_UNLOCK;
return n;
+
+not_found:
+ IFACE_UNLOCK;
+ return NULL;
}
/**
@@ -298,18 +336,46 @@ neigh_dump_all(void)
neighbor *n;
int i;
+ IFACE_LOCK;
+
debug("Known neighbors:\n");
for(i=0; i<NEIGH_HASH_SIZE; i++)
WALK_LIST(n, neigh_hash_table[i])
neigh_dump(n);
debug("\n");
+
+ IFACE_UNLOCK;
}
static inline void
neigh_notify(neighbor *n)
{
- if (n->proto->neigh_notify && (n->proto->proto_state != PS_STOP))
+ if (!n->proto->neigh_notify)
+ return;
+
+ ev_send(n->event.list, &n->event);
+}
+
+static void
+neigh_do_notify_main(void *data)
+{
+ neighbor *n = data;
+ PROTO_LOCKED_FROM_MAIN(n->proto)
+ neigh_do_notify(data);
+}
+
+static void
+neigh_do_notify(void *data)
+{
+ neighbor *n = data;
+
+ ASSERT_DIE(birdloop_inside(n->proto->loop));
+
+ if (n->proto->proto_state != PS_STOP)
n->proto->neigh_notify(n);
+
+ if ((n->scope < 0) && !(n->flags & NEF_STICKY))
+ neigh_free(n);
}
static void
@@ -340,11 +406,21 @@ neigh_down(neighbor *n)
neigh_notify(n);
}
-static inline void
+static void
neigh_free(neighbor *n)
{
+ ASSERT_DIE(birdloop_inside(n->proto->loop));
+
+ if (n->flags & NEF_NOTIFY_MAIN)
+ ASSERT_DIE(birdloop_inside(&main_birdloop));
+
rem_node(&n->n);
rem_node(&n->if_n);
+
+ if (n->event.list != &global_event_list)
+ birdloop_unlink(n->proto->loop);
+
+ ev_postpone(&n->event);
sl_free(neigh_slab, n);
}
@@ -360,6 +436,8 @@ neigh_free(neighbor *n)
void
neigh_update(neighbor *n, struct iface *iface)
{
+ ASSERT_IFACE_LOCKED;
+
struct proto *p = n->proto;
struct ifa *ifa = NULL;
int scope = -1;
@@ -369,7 +447,7 @@ neigh_update(neighbor *n, struct iface *iface)
return;
/* VRF-bound neighbors ignore changes in other VRFs */
- if (p->vrf_set && (p->vrf != iface->master))
+ if (p->vrf && (p->vrf != iface->master))
return;
scope = if_connected(n->addr, iface, &ifa, n->flags);
@@ -379,7 +457,7 @@ neigh_update(neighbor *n, struct iface *iface)
{
/* When neighbor is going down, try to respawn it on other ifaces */
if ((scope < 0) && (n->scope >= 0) && !n->ifreq && (n->flags & NEF_STICKY))
- scope = if_connected_any(n->addr, p->vrf, p->vrf_set, &iface, &ifa, n->flags);
+ scope = if_connected_any(n->addr, p->vrf, &iface, &ifa, n->flags);
}
else
{
@@ -406,12 +484,6 @@ neigh_update(neighbor *n, struct iface *iface)
if (n->scope >= 0)
neigh_down(n);
- if ((n->scope < 0) && !(n->flags & NEF_STICKY))
- {
- neigh_free(n);
- return;
- }
-
if (scope >= 0)
neigh_up(n, iface, ifa, scope);
}
@@ -433,14 +505,18 @@ neigh_if_up(struct iface *i)
neighbor *n;
node *x, *y;
+ IFACE_LOCK;
+
/* Update neighbors that might be better off with the new iface */
- WALK_LIST(ii, iface_list)
+ WALK_LIST(ii, global_iface_list)
if (!EMPTY_LIST(ii->neighbors) && (ii != i) && if_intersect(i, ii))
WALK_LIST2_DELSAFE(n, x, y, ii->neighbors, if_n)
neigh_update(n, i);
WALK_LIST2_DELSAFE(n, x, y, sticky_neigh_list, if_n)
neigh_update(n, i);
+
+ IFACE_UNLOCK;
}
/**
@@ -457,8 +533,12 @@ neigh_if_down(struct iface *i)
neighbor *n;
node *x, *y;
+ IFACE_LOCK;
+
WALK_LIST2_DELSAFE(n, x, y, i->neighbors, if_n)
neigh_update(n, i);
+
+ IFACE_UNLOCK;
}
/**
@@ -474,8 +554,12 @@ neigh_if_link(struct iface *i)
neighbor *n;
node *x, *y;
+ IFACE_LOCK;
+
WALK_LIST2_DELSAFE(n, x, y, i->neighbors, if_n)
neigh_notify(n);
+
+ IFACE_UNLOCK;
}
/**
@@ -495,8 +579,10 @@ neigh_ifa_up(struct ifa *a)
neighbor *n;
node *x, *y;
+ IFACE_LOCK;
+
/* Update neighbors that might be better off with the new ifa */
- WALK_LIST(ii, iface_list)
+ WALK_LIST(ii, global_iface_list)
if (!EMPTY_LIST(ii->neighbors) && ifa_intersect(a, ii))
WALK_LIST2_DELSAFE(n, x, y, ii->neighbors, if_n)
neigh_update(n, i);
@@ -504,6 +590,8 @@ neigh_ifa_up(struct ifa *a)
/* Wake up all sticky neighbors that are reachable now */
WALK_LIST2_DELSAFE(n, x, y, sticky_neigh_list, if_n)
neigh_update(n, i);
+
+ IFACE_UNLOCK;
}
void
@@ -513,10 +601,14 @@ neigh_ifa_down(struct ifa *a)
neighbor *n;
node *x, *y;
+ IFACE_LOCK;
+
/* Update all neighbors whose scope has changed */
WALK_LIST2_DELSAFE(n, x, y, i->neighbors, if_n)
if (n->ifa == a)
neigh_update(n, i);
+
+ IFACE_UNLOCK;
}
static inline void
@@ -536,16 +628,21 @@ neigh_prune_one(neighbor *n)
* is shut down to get rid of all its heritage.
*/
void
-neigh_prune(void)
+neigh_prune(struct proto *p)
{
neighbor *n;
node *m;
int i;
+ IFACE_LOCK;
+
DBG("Pruning neighbors\n");
for(i=0; i<NEIGH_HASH_SIZE; i++)
WALK_LIST_DELSAFE(n, m, neigh_hash_table[i])
- neigh_prune_one(n);
+ if (n->proto == p)
+ neigh_prune_one(n);
+
+ IFACE_UNLOCK;
}
/**
diff --git a/nest/proto-hooks.c b/nest/proto-hooks.c
index bc88b4b4..716ce86c 100644
--- a/nest/proto-hooks.c
+++ b/nest/proto-hooks.c
@@ -76,16 +76,6 @@ void dump(struct proto *p)
{ DUMMY; }
/**
- * dump_attrs - dump protocol-dependent attributes
- * @e: a route entry
- *
- * This hook dumps all attributes in the &rte which belong to this
- * protocol to the debug output.
- */
-void dump_attrs(rte *e)
-{ DUMMY; }
-
-/**
* start - request instance startup
* @p: protocol instance
*
@@ -228,36 +218,6 @@ void neigh_notify(neighbor *neigh)
{ DUMMY; }
/**
- * make_tmp_attrs - convert embedded attributes to temporary ones
- * @e: route entry
- * @pool: linear pool to allocate attribute memory in
- *
- * This hook is called by the routing table functions if they need
- * to convert the protocol attributes embedded directly in the &rte
- * to temporary extended attributes in order to distribute them
- * to other protocols or to filters. make_tmp_attrs() creates
- * an &ea_list in the linear pool @pool, fills it with values of the
- * temporary attributes and returns a pointer to it.
- */
-ea_list *make_tmp_attrs(rte *e, struct linpool *pool)
-{ DUMMY; }
-
-/**
- * store_tmp_attrs - convert temporary attributes to embedded ones
- * @e: route entry
- * @attrs: temporary attributes to be converted
- *
- * This hook is an exact opposite of make_tmp_attrs() -- it takes
- * a list of extended attributes and converts them to attributes
- * embedded in the &rte corresponding to this protocol.
- *
- * You must be prepared for any of the attributes being missing
- * from the list and use default values instead.
- */
-void store_tmp_attrs(rte *e, ea_list *attrs)
-{ DUMMY; }
-
-/**
* preexport - pre-filtering decisions before route export
* @p: protocol instance the route is going to be exported to
* @e: the route in question
diff --git a/nest/proto.c b/nest/proto.c
index 31ee1fa1..2546e812 100644
--- a/nest/proto.c
+++ b/nest/proto.c
@@ -15,6 +15,7 @@
#include "lib/event.h"
#include "lib/timer.h"
#include "lib/string.h"
+#include "lib/coro.h"
#include "conf/conf.h"
#include "nest/route.h"
#include "nest/iface.h"
@@ -31,7 +32,6 @@ struct protocol *class_to_protocol[PROTOCOL__MAX];
#define CD(c, msg, args...) ({ if (c->debug & D_STATES) log(L_TRACE "%s.%s: " msg, c->proto->name, c->name ?: "?", ## args); })
#define PD(p, msg, args...) ({ if (p->debug & D_STATES) log(L_TRACE "%s: " msg, p->name, ## args); })
-static timer *proto_shutdown_timer;
static timer *gr_wait_timer;
#define GRS_NONE 0
@@ -43,24 +43,34 @@ static int graceful_restart_state;
static u32 graceful_restart_locks;
static char *p_states[] = { "DOWN", "START", "UP", "STOP" };
-static char *c_states[] = { "DOWN", "START", "UP", "FLUSHING" };
-static char *e_states[] = { "DOWN", "FEEDING", "READY" };
+static char *c_states[] = { "DOWN", "START", "UP", "STOP", "RESTART" };
extern struct protocol proto_unix_iface;
-static void channel_request_reload(struct channel *c);
-static void proto_shutdown_loop(timer *);
+static void channel_aux_request_refeed(struct channel_aux_table *cat);
static void proto_rethink_goal(struct proto *p);
static char *proto_state_name(struct proto *p);
-static void channel_verify_limits(struct channel *c);
-static inline void channel_reset_limit(struct channel_limit *l);
-
+static void channel_init_limit(struct channel *c, struct limit *l, int dir, struct channel_limit *cf);
+static void channel_update_limit(struct channel *c, struct limit *l, int dir, struct channel_limit *cf);
+static void channel_reset_limit(struct channel *c, struct limit *l, int dir);
+static void channel_feed_end(struct channel *c);
+static void channel_export_stopped(struct rt_export_request *req);
static inline int proto_is_done(struct proto *p)
-{ return (p->proto_state == PS_DOWN) && (p->active_channels == 0); }
+{ return (p->proto_state == PS_DOWN) && proto_is_inactive(p); }
+
+static inline event_list *proto_event_list(struct proto *p)
+{ return p->loop == &main_birdloop ? &global_event_list : birdloop_event_list(p->loop); }
+
+static inline event_list *proto_work_list(struct proto *p)
+{ return p->loop == &main_birdloop ? &global_work_list : birdloop_event_list(p->loop); }
+
+static inline void proto_send_event(struct proto *p)
+{ ev_send(proto_event_list(p), p->event); }
+
static inline int channel_is_active(struct channel *c)
-{ return (c->channel_state == CS_START) || (c->channel_state == CS_UP); }
+{ return (c->channel_state != CS_DOWN); }
static inline int channel_reloadable(struct channel *c)
{ return c->proto->reload_routes && c->reloadable; }
@@ -68,10 +78,48 @@ static inline int channel_reloadable(struct channel *c)
static inline void
channel_log_state_change(struct channel *c)
{
- if (c->export_state)
- CD(c, "State changed to %s/%s", c_states[c->channel_state], e_states[c->export_state]);
- else
- CD(c, "State changed to %s", c_states[c->channel_state]);
+ CD(c, "State changed to %s", c_states[c->channel_state]);
+}
+
+void
+channel_import_log_state_change(struct rt_import_request *req, u8 state)
+{
+ struct channel *c = SKIP_BACK(struct channel, in_req, req);
+ CD(c, "Channel import state changed to %s", rt_import_state_name(state));
+}
+
+void
+channel_export_log_state_change(struct rt_export_request *req, u8 state)
+{
+ struct channel *c = SKIP_BACK(struct channel, out_req, req);
+ CD(c, "Channel export state changed to %s", rt_export_state_name(state));
+
+ switch (state)
+ {
+ case TES_FEEDING:
+ if (c->out_table)
+ rt_refresh_begin(&c->out_table->push);
+ else if (c->proto->feed_begin)
+ c->proto->feed_begin(c, !c->refeeding);
+ break;
+ case TES_READY:
+ channel_feed_end(c);
+ break;
+ }
+}
+
+static void
+channel_dump_import_req(struct rt_import_request *req)
+{
+ struct channel *c = SKIP_BACK(struct channel, in_req, req);
+ debug(" Channel %s.%s import request %p\n", c->proto->name, c->name, req);
+}
+
+static void
+channel_dump_export_req(struct rt_export_request *req)
+{
+ struct channel *c = SKIP_BACK(struct channel, out_req, req);
+ debug(" Channel %s.%s export request %p\n", c->proto->name, c->name, req);
}
static void
@@ -111,7 +159,7 @@ proto_cf_find_channel(struct proto_config *pc, uint net_type)
* Returns pointer to channel or NULL
*/
struct channel *
-proto_find_channel_by_table(struct proto *p, struct rtable *t)
+proto_find_channel_by_table(struct proto *p, rtable *t)
{
struct channel *c;
@@ -141,6 +189,16 @@ proto_find_channel_by_name(struct proto *p, const char *n)
return NULL;
}
+rte * channel_preimport(struct rt_import_request *req, rte *new, rte *old);
+rte * channel_in_preimport(struct rt_import_request *req, rte *new, rte *old);
+
+void rt_notify_optimal(struct rt_export_request *req, const net_addr *net, struct rt_pending_export *rpe);
+void rt_notify_any(struct rt_export_request *req, const net_addr *net, struct rt_pending_export *rpe);
+void rt_feed_any(struct rt_export_request *req, const net_addr *net, struct rt_pending_export *rpe, rte **feed, uint count);
+void rt_notify_accepted(struct rt_export_request *req, const net_addr *net, struct rt_pending_export *rpe, rte **feed, uint count);
+void rt_notify_merged(struct rt_export_request *req, const net_addr *net, struct rt_pending_export *rpe, rte **feed, uint count);
+
+
/**
* proto_add_channel - connect protocol to a routing table
* @p: protocol instance
@@ -166,11 +224,17 @@ proto_add_channel(struct proto *p, struct channel_config *cf)
c->proto = p;
c->table = cf->table->table;
+ RT_LOCKED(c->table, t)
+ rt_lock_table(t);
+
c->in_filter = cf->in_filter;
c->out_filter = cf->out_filter;
- c->rx_limit = cf->rx_limit;
- c->in_limit = cf->in_limit;
- c->out_limit = cf->out_limit;
+
+ channel_init_limit(c, &c->rx_limit, PLD_RX, &cf->rx_limit);
+ channel_init_limit(c, &c->in_limit, PLD_IN, &cf->in_limit);
+ channel_init_limit(c, &c->out_limit, PLD_OUT, &cf->out_limit);
+
+ c->rte_update_pool = lp_new_default(proto_pool);
c->net_type = cf->net_type;
c->ra_mode = cf->ra_mode;
@@ -181,7 +245,6 @@ proto_add_channel(struct proto *p, struct channel_config *cf)
c->rpki_reload = cf->rpki_reload;
c->channel_state = CS_DOWN;
- c->export_state = ES_DOWN;
c->last_state_change = current_time();
c->reloadable = 1;
@@ -203,6 +266,9 @@ proto_remove_channel(struct proto *p UNUSED, struct channel *c)
CD(c, "Removed", c->name);
+ RT_LOCKED(c->table, t)
+ rt_unlock_table(t);
+
rem_node(&c->n);
mb_free(c);
}
@@ -223,7 +289,7 @@ proto_pause_channels(struct proto *p)
struct channel *c;
WALK_LIST(c, p->channels)
if (!c->disabled && channel_is_active(c))
- channel_set_state(c, CS_START);
+ channel_set_state(c, CS_PAUSE);
}
static void
@@ -232,7 +298,7 @@ proto_stop_channels(struct proto *p)
struct channel *c;
WALK_LIST(c, p->channels)
if (!c->disabled && channel_is_active(c))
- channel_set_state(c, CS_FLUSHING);
+ channel_set_state(c, CS_STOP);
}
static void
@@ -244,96 +310,25 @@ proto_remove_channels(struct proto *p)
}
static void
-channel_schedule_feed(struct channel *c, int initial)
-{
- // DBG("%s: Scheduling meal\n", p->name);
- ASSERT(c->channel_state == CS_UP);
-
- c->export_state = ES_FEEDING;
- c->refeeding = !initial;
-
- ev_schedule_work(c->feed_event);
-}
-
-static void
-channel_feed_loop(void *ptr)
-{
- struct channel *c = ptr;
-
- if (c->export_state != ES_FEEDING)
- return;
-
- /* Start feeding */
- if (!c->feed_active)
- {
- if (c->proto->feed_begin)
- c->proto->feed_begin(c, !c->refeeding);
-
- c->refeed_pending = 0;
- }
-
- // DBG("Feeding protocol %s continued\n", p->name);
- if (!rt_feed_channel(c))
- {
- ev_schedule_work(c->feed_event);
- return;
- }
-
- /* Reset export limit if the feed ended with acceptable number of exported routes */
- struct channel_limit *l = &c->out_limit;
- if (c->refeeding &&
- (l->state == PLS_BLOCKED) &&
- (c->refeed_count <= l->limit) &&
- (c->stats.exp_routes <= l->limit))
- {
- log(L_INFO "Protocol %s resets route export limit (%u)", c->proto->name, l->limit);
- channel_reset_limit(&c->out_limit);
-
- /* Continue in feed - it will process routing table again from beginning */
- c->refeed_count = 0;
- ev_schedule_work(c->feed_event);
- return;
- }
-
- // DBG("Feeding protocol %s finished\n", p->name);
- c->export_state = ES_READY;
- channel_log_state_change(c);
-
- if (c->proto->feed_end)
- c->proto->feed_end(c);
-
- /* Restart feeding */
- if (c->refeed_pending)
- channel_request_feeding(c);
-}
-
-
-static void
-channel_roa_in_changed(struct rt_subscription *s)
+channel_roa_in_changed(void *_data)
{
- struct channel *c = s->data;
- int active = c->reload_event && ev_active(c->reload_event);
+ struct channel *c = _data;
- CD(c, "Reload triggered by RPKI change%s", active ? " - already active" : "");
+ CD(c, "Reload triggered by RPKI change");
- if (!active)
- channel_request_reload(c);
- else
- c->reload_pending = 1;
+ channel_request_reload(c);
}
static void
-channel_roa_out_changed(struct rt_subscription *s)
+channel_roa_out_changed(void *_data)
{
- struct channel *c = s->data;
- int active = (c->export_state == ES_FEEDING);
+ struct channel *c = _data;
+ CD(c, "Feeding triggered by RPKI change");
- CD(c, "Feeding triggered by RPKI change%s", active ? " - already active" : "");
+ c->refeed_pending = 1;
- if (!active)
- channel_request_feeding(c);
- else
- c->refeed_pending = 1;
+ if (c->out_req.hook)
+ rt_stop_export(&c->out_req, channel_export_stopped);
}
/* Temporary code, subscriptions should be changed to resources */
@@ -345,14 +340,14 @@ struct roa_subscription {
static int
channel_roa_is_subscribed(struct channel *c, rtable *tab, int dir)
{
- void (*hook)(struct rt_subscription *) =
+ void (*hook)(void *) =
dir ? channel_roa_in_changed : channel_roa_out_changed;
struct roa_subscription *s;
node *n;
WALK_LIST2(s, n, c->roa_subscriptions, roa_node)
- if ((s->s.tab == tab) && (s->s.hook == hook))
+ if ((s->s.tab == tab) && (s->s.event->hook == hook))
return 1;
return 0;
@@ -366,9 +361,9 @@ channel_roa_subscribe(struct channel *c, rtable *tab, int dir)
return;
struct roa_subscription *s = mb_allocz(c->proto->pool, sizeof(struct roa_subscription));
+ s->s.event = ev_new_init(c->proto->pool, dir ? channel_roa_in_changed : channel_roa_out_changed, c);
+ s->s.event->list = proto_work_list(c->proto);
- s->s.hook = dir ? channel_roa_in_changed : channel_roa_out_changed;
- s->s.data = c;
rt_subscribe(tab, &s->s);
add_tail(&c->roa_subscriptions, &s->roa_node);
@@ -379,6 +374,7 @@ channel_roa_unsubscribe(struct roa_subscription *s)
{
rt_unsubscribe(&s->s);
rem_node(&s->roa_node);
+ rfree(s->s.event);
mb_free(s);
}
@@ -386,7 +382,7 @@ static void
channel_roa_subscribe_filter(struct channel *c, int dir)
{
const struct filter *f = dir ? c->in_filter : c->out_filter;
- struct rtable *tab;
+ rtable *tab;
int valid = 1, found = 0;
if ((f == FILTER_ACCEPT) || (f == FILTER_REJECT))
@@ -445,119 +441,554 @@ channel_roa_unsubscribe_all(struct channel *c)
}
static void
-channel_start_export(struct channel *c)
+channel_start_import(struct channel *c)
{
+ if (c->in_req.hook)
+ {
+ log(L_WARN "%s.%s: Attempted to start channel's already started import", c->proto->name, c->name);
+ return;
+ }
+
+ int nlen = strlen(c->name) + strlen(c->proto->name) + 2;
+ char *rn = mb_allocz(c->proto->pool, nlen);
+ bsprintf(rn, "%s.%s", c->proto->name, c->name);
+
+ c->in_req = (struct rt_import_request) {
+ .name = rn,
+ .list = proto_work_list(c->proto),
+ .trace_routes = c->debug | c->proto->debug,
+ .dump_req = channel_dump_import_req,
+ .log_state_change = channel_import_log_state_change,
+ .preimport = channel_preimport,
+ };
+
ASSERT(c->channel_state == CS_UP);
- ASSERT(c->export_state == ES_DOWN);
- channel_schedule_feed(c, 1); /* Sets ES_FEEDING */
+ channel_reset_limit(c, &c->rx_limit, PLD_RX);
+ channel_reset_limit(c, &c->in_limit, PLD_IN);
+
+ memset(&c->import_stats, 0, sizeof(struct channel_import_stats));
+
+ DBG("%s.%s: Channel start import req=%p\n", c->proto->name, c->name, &c->in_req);
+ rt_request_import(c->table, &c->in_req);
}
static void
-channel_stop_export(struct channel *c)
+channel_start_export(struct channel *c)
{
- /* Need to abort feeding */
- if (c->export_state == ES_FEEDING)
- rt_feed_channel_abort(c);
+ if (c->out_req.hook)
+ {
+ c->restart_export = 1;
+ log(L_WARN "%s.%s: Fast channel export restart", c->proto->name, c->name);
+ return;
+ }
+
+ ASSERT(c->channel_state == CS_UP);
+ int nlen = strlen(c->name) + strlen(c->proto->name) + 2;
+ char *rn = mb_allocz(c->proto->pool, nlen);
+ bsprintf(rn, "%s.%s", c->proto->name, c->name);
+
+ c->out_req = (struct rt_export_request) {
+ .name = rn,
+ .list = proto_work_list(c->proto),
+ .trace_routes = c->debug | c->proto->debug,
+ .dump_req = channel_dump_export_req,
+ .log_state_change = channel_export_log_state_change,
+ };
+
+ bmap_init(&c->export_map, c->proto->pool, 1024);
+ bmap_init(&c->export_reject_map, c->proto->pool, 1024);
+
+ channel_reset_limit(c, &c->out_limit, PLD_OUT);
- c->export_state = ES_DOWN;
- c->stats.exp_routes = 0;
- bmap_reset(&c->export_map, 1024);
+ memset(&c->export_stats, 0, sizeof(struct channel_export_stats));
+
+ switch (c->ra_mode) {
+ case RA_OPTIMAL:
+ c->out_req.export_one = rt_notify_optimal;
+ break;
+ case RA_ANY:
+ c->out_req.export_one = rt_notify_any;
+ c->out_req.export_bulk = rt_feed_any;
+ break;
+ case RA_ACCEPTED:
+ c->out_req.export_bulk = rt_notify_accepted;
+ break;
+ case RA_MERGED:
+ c->out_req.export_bulk = rt_notify_merged;
+ break;
+ default:
+ bug("Unknown route announcement mode");
+ }
+
+ DBG("%s.%s: Channel start export req=%p\n", c->proto->name, c->name, &c->out_req);
+ rt_request_export(c->table, &c->out_req);
}
+static void
+channel_check_stopped(struct channel *c)
+{
+ switch (c->channel_state)
+ {
+ case CS_STOP:
+ if (c->out_req.hook || c->in_req.hook || c->out_table || c->in_table)
+ return;
+
+ channel_set_state(c, CS_DOWN);
+ proto_send_event(c->proto);
+
+ break;
+ case CS_PAUSE:
+ if (c->out_req.hook)
+ return;
+
+ channel_set_state(c, CS_START);
+ break;
+ default:
+ bug("Stopped channel in a bad state: %d", c->channel_state);
+ }
+
+ DBG("%s.%s: Channel requests/hooks stopped (in state %s)\n", c->proto->name, c->name, c_states[c->channel_state]);
+}
-/* Called by protocol for reload from in_table */
void
-channel_schedule_reload(struct channel *c)
+channel_import_stopped(void *_c)
{
- ASSERT(c->channel_state == CS_UP);
+ struct channel *c = _c;
+
+ c->in_req.hook = NULL;
- rt_reload_channel_abort(c);
- ev_schedule_work(c->reload_event);
+ mb_free(c->in_req.name);
+ c->in_req.name = NULL;
+
+ channel_check_stopped(c);
}
static void
-channel_reload_loop(void *ptr)
+channel_export_stopped(struct rt_export_request *req)
{
- struct channel *c = ptr;
+ struct channel *c = SKIP_BACK(struct channel, out_req, req);
- /* Start reload */
- if (!c->reload_active)
- c->reload_pending = 0;
+ /* The hook has already stopped */
+ req->hook = NULL;
- if (!rt_reload_channel(c))
+ if (c->refeed_pending)
{
- ev_schedule_work(c->reload_event);
+ c->refeeding = 1;
+ c->refeed_pending = 0;
+
+ bmap_reset(&c->export_map, 1024);
+ bmap_reset(&c->export_reject_map, 1024);
+
+ rt_request_export(c->table, req);
return;
}
- /* Restart reload */
- if (c->reload_pending)
- channel_request_reload(c);
+ mb_free(c->out_req.name);
+ c->out_req.name = NULL;
+
+ bmap_free(&c->export_map);
+ bmap_free(&c->export_reject_map);
+
+ if (c->restart_export)
+ {
+ c->restart_export = 0;
+ channel_start_export(c);
+ }
+ else
+ channel_check_stopped(c);
+}
+
+static void
+channel_feed_end(struct channel *c)
+{
+ struct rt_export_request *req = &c->out_req;
+
+ /* Reset export limit if the feed ended with acceptable number of exported routes */
+ struct limit *l = &c->out_limit;
+ if (c->refeeding &&
+ (c->limit_active & (1 << PLD_OUT)) &&
+ (c->refeed_count <= l->max) &&
+ (l->count <= l->max))
+ {
+ log(L_INFO "Protocol %s resets route export limit (%u)", c->proto->name, l->max);
+ channel_reset_limit(c, &c->out_limit, PLD_OUT);
+
+ c->refeed_pending = 1;
+ rt_stop_export(req, channel_export_stopped);
+ return;
+ }
+
+ if (c->out_table)
+ rt_refresh_end(&c->out_table->push);
+ else if (c->proto->feed_end)
+ c->proto->feed_end(c);
+
+ if (c->refeed_pending)
+ rt_stop_export(req, channel_export_stopped);
+}
+
+#define CHANNEL_AUX_TABLE_DUMP_REQ(inout, imex, pgimex, pushget) static void \
+ channel_##inout##_##pushget##_dump_req(struct rt_##pgimex##_request *req) { \
+ struct channel_aux_table *cat = SKIP_BACK(struct channel_aux_table, pushget, req); \
+ debug(" Channel %s.%s " #imex " table " #pushget " request %p\n", cat->c->proto->name, cat->c->name, req); }
+
+CHANNEL_AUX_TABLE_DUMP_REQ(in, import, import, push)
+CHANNEL_AUX_TABLE_DUMP_REQ(in, import, export, get)
+CHANNEL_AUX_TABLE_DUMP_REQ(out, export, import, push)
+CHANNEL_AUX_TABLE_DUMP_REQ(out, export, export, get)
+
+#undef CHANNEL_AUX_TABLE_DUMP_REQ
+
+static uint channel_aux_imex(struct channel_aux_table *cat)
+{
+ if (cat->c->in_table == cat)
+ return 0;
+ else if (cat->c->out_table == cat)
+ return 1;
+ else
+ bug("Channel aux table must be in_table or out_table");
}
static void
-channel_reset_import(struct channel *c)
+channel_aux_stopped(void *data)
{
- /* Need to abort feeding */
- ev_postpone(c->reload_event);
- rt_reload_channel_abort(c);
+ struct channel_aux_table *cat;
+
+ RT_LOCKED((rtable *) data, t)
+ cat = t->config->owner;
- rt_prune_sync(c->in_table, 1);
+ ASSERT_DIE(cat->push.hook == NULL);
+ ASSERT_DIE(cat->get.hook == NULL);
+ ASSERT_DIE(cat->stop_pending);
+
+ struct channel *c = cat->c;
+
+ if (channel_aux_imex(cat))
+ c->out_table = NULL;
+ else
+ c->in_table = NULL;
+
+ mb_free(cat);
+ channel_check_stopped(c);
}
static void
-channel_reset_export(struct channel *c)
+channel_aux_import_stopped(void *_cat)
{
- /* Just free the routes */
- rt_prune_sync(c->out_table, 1);
+ struct channel_aux_table *cat = _cat;
+
+ cat->push.hook = NULL;
+
+ if (!cat->get.hook)
+ RT_LOCKED(cat->tab, t)
+ {
+ t->delete = channel_aux_stopped;
+ rt_unlock_table(t);
+ }
}
-/* Called by protocol to activate in_table */
-void
-channel_setup_in_table(struct channel *c)
+static void
+channel_aux_export_stopped(struct rt_export_request *req)
+{
+ struct channel_aux_table *cat = SKIP_BACK(struct channel_aux_table, get, req);
+ req->hook = NULL;
+
+ if (cat->refeed_pending && !cat->stop_pending)
+ {
+ cat->refeed_pending = 0;
+ rt_request_export(cat->tab, req);
+
+ return;
+ }
+
+ if (!cat->push.hook)
+ RT_LOCKED(cat->tab, t)
+ {
+ t->delete = channel_aux_stopped;
+ rt_unlock_table(t);
+ }
+}
+
+static void
+channel_aux_stop(struct channel_aux_table *cat)
+{
+ ASSERT_DIE(!cat->stop_pending);
+
+ cat->stop_pending = 1;
+
+ RT_LOCKED(cat->tab, t)
+ rt_lock_table(t);
+
+ cat->push_stopped = (event) {
+ .hook = channel_aux_import_stopped,
+ .data = cat,
+ .list = proto_event_list(cat->c->proto),
+ };
+
+ rt_stop_import(&cat->push, &cat->push_stopped);
+ rt_stop_export(&cat->get, channel_aux_export_stopped);
+}
+
+static void
+channel_push_log_state_change(struct rt_import_request *req, u8 state)
+{
+ struct channel_aux_table *cat = SKIP_BACK(struct channel_aux_table, push, req);
+ const char *imex = channel_aux_imex(cat) ? "export" : "import";
+ CD(cat->c, "Channel %s table import state changed to %s", imex, rt_import_state_name(state));
+}
+
+static void
+channel_get_log_state_change(struct rt_export_request *req, u8 state)
+{
+ struct channel_aux_table *cat = SKIP_BACK(struct channel_aux_table, get, req);
+ const char *imex = channel_aux_imex(cat) ? "export" : "import";
+ CD(cat->c, "Channel %s table export state changed to %s", imex, rt_export_state_name(state));
+
+ switch (state)
+ {
+ case TES_FEEDING:
+ if (imex && cat->c->proto->feed_begin)
+ cat->c->proto->feed_begin(cat->c, !cat->c->refeeding);
+ else if (!imex)
+ rt_refresh_begin(&cat->c->in_req);
+ break;
+
+ case TES_READY:
+ if (imex && cat->c->proto->feed_end)
+ cat->c->proto->feed_end(cat->c);
+ else if (!imex)
+ rt_refresh_end(&cat->c->in_req);
+
+ if (cat->refeed_pending)
+ rt_stop_export(&cat->get, channel_aux_export_stopped);
+
+ break;
+ }
+}
+
+void rte_update_direct(struct channel *c, const net_addr *n, rte *new, struct rte_src *src);
+
+static int
+channel_aux_export_one_any(struct rt_export_request *req, struct rt_pending_export *rpe, rte **new, rte **old)
+{
+ struct rte_src *src = rpe->new ? rpe->new->rte.src : rpe->old->rte.src;
+ *old = RTES_OR_NULL(rpe->old);
+ struct rte_storage *new_stored;
+
+ while (rpe)
+ {
+ new_stored = rpe->new;
+ rpe_mark_seen(req->hook, rpe);
+ rpe = rpe_next(rpe, src);
+ }
+
+ *new = RTES_CLONE(new_stored, *new);
+
+ return (*new || *old) && (&new_stored->rte != *old);
+}
+
+static int
+channel_aux_export_one_best(struct rt_export_request *req, struct rt_pending_export *rpe, rte **new, rte **old)
+{
+ *old = RTES_OR_NULL(rpe->old_best);
+ struct rte_storage *new_stored;
+
+ while (rpe)
+ {
+ new_stored = rpe->new_best;
+ rpe_mark_seen(req->hook, rpe);
+ rpe = rpe_next(rpe, NULL);
+ }
+
+ *new = RTES_CLONE(new_stored, *new);
+
+ return (*new || *old) && (&new_stored->rte != *old);
+}
+
+static void
+channel_in_export_one_any(struct rt_export_request *req, const net_addr *net, struct rt_pending_export *rpe)
+{
+ struct channel_aux_table *cat = SKIP_BACK(struct channel_aux_table, get, req);
+
+ rte n0, *new = &n0, *old;
+ if (channel_aux_export_one_any(req, rpe, &new, &old))
+ rte_update_direct(cat->c, net, new, old ? old->src : new->src);
+}
+
+static void
+channel_in_export_one_best(struct rt_export_request *req, const net_addr *net, struct rt_pending_export *rpe)
+{
+ struct channel_aux_table *cat = SKIP_BACK(struct channel_aux_table, get, req);
+
+ rte n0, *new = &n0, *old;
+ if (channel_aux_export_one_best(req, rpe, &new, &old))
+ rte_update_direct(cat->c, net, new, old ? old->src : new->src);
+}
+
+static void
+channel_in_export_bulk_any(struct rt_export_request *req, const net_addr *net, struct rt_pending_export *rpe UNUSED, rte **feed, uint count)
{
- struct rtable_config *cf = mb_allocz(c->proto->pool, sizeof(struct rtable_config));
+ struct channel_aux_table *cat = SKIP_BACK(struct channel_aux_table, get, req);
+ for (uint i=0; i<count; i++)
+ {
+ rte n0 = *feed[i];
+ rte_update_direct(cat->c, net, &n0, n0.src);
+ }
+}
- cf->name = "import";
- cf->addr_type = c->net_type;
- cf->internal = 1;
+static void
+channel_in_export_bulk_best(struct rt_export_request *req, const net_addr *net, struct rt_pending_export *rpe UNUSED, rte **feed, uint count)
+{
+ struct channel_aux_table *cat = SKIP_BACK(struct channel_aux_table, get, req);
+ if (!count)
+ return;
+
+ rte n0 = *feed[0];
+ rte_update_direct(cat->c, net, &n0, n0.src);
+}
+
+void do_rt_notify_direct(struct channel *c, const net_addr *net, rte *new, const rte *old);
+
+static void
+channel_out_export_one_any(struct rt_export_request *req, const net_addr *net, struct rt_pending_export *rpe)
+{
+ struct channel_aux_table *cat = SKIP_BACK(struct channel_aux_table, get, req);
+ rte n0, *new = &n0, *old;
+ if (channel_aux_export_one_any(req, rpe, &new, &old))
+ do_rt_notify_direct(cat->c, net, new, old);
+}
+
+static void
+channel_out_export_one_best(struct rt_export_request *req, const net_addr *net, struct rt_pending_export *rpe)
+{
+ struct channel_aux_table *cat = SKIP_BACK(struct channel_aux_table, get, req);
+ rte n0, *new = &n0, *old;
+ if (channel_aux_export_one_best(req, rpe, &new, &old))
+ do_rt_notify_direct(cat->c, net, new, old);
+}
+
+static void
+channel_out_export_bulk(struct rt_export_request *req, const net_addr *net, struct rt_pending_export *rpe UNUSED, rte **feed, uint count)
+{
+ struct channel_aux_table *cat = SKIP_BACK(struct channel_aux_table, get, req);
+ if (cat->c->ra_mode != RA_ANY)
+ ASSERT_DIE(count <= 1);
+
+ for (uint i=0; i<count; i++)
+ {
+ rte n0 = *feed[i];
+ do_rt_notify_direct(cat->c, net, &n0, NULL);
+ }
+}
+
+/* Called by protocol to activate in_table */
+void
+channel_setup_in_table(struct channel *c, int best)
+{
+ int nlen = sizeof("import") + strlen(c->name) + strlen(c->proto->name) + 3;
+
+ struct {
+ struct channel_aux_table cat;
+ struct rtable_config tab_cf;
+ char name[0];
+ } *cat = mb_allocz(c->proto->pool, sizeof(*cat) + nlen);
+
+ bsprintf(cat->name, "%s.%s.import", c->proto->name, c->name);
+
+ cat->tab_cf.owner = cat;
+ cat->tab_cf.name = cat->name;
+ cat->tab_cf.addr_type = c->net_type;
+ cat->tab_cf.cork_limit = 4 * page_size / sizeof(struct rt_pending_export);
+
+ c->in_table = &cat->cat;
+ c->in_table->push = (struct rt_import_request) {
+ .name = cat->name,
+ .list = proto_work_list(c->proto),
+ .trace_routes = c->debug | c->proto->debug,
+ .dump_req = channel_in_push_dump_req,
+ .log_state_change = channel_push_log_state_change,
+ .preimport = channel_in_preimport,
+ };
+ c->in_table->get = (struct rt_export_request) {
+ .name = cat->name,
+ .list = proto_work_list(c->proto),
+ .trace_routes = c->debug | c->proto->debug,
+ .dump_req = channel_in_get_dump_req,
+ .log_state_change = channel_get_log_state_change,
+ .export_one = best ? channel_in_export_one_best : channel_in_export_one_any,
+ .export_bulk = best ? channel_in_export_bulk_best : channel_in_export_bulk_any,
+ };
- c->in_table = rt_setup(c->proto->pool, cf);
+ c->in_table->c = c;
+ c->in_table->tab = rt_setup(c->proto->pool, &cat->tab_cf);
- c->reload_event = ev_new_init(c->proto->pool, channel_reload_loop, c);
+ rt_request_import(c->in_table->tab, &c->in_table->push);
+ rt_request_export(c->in_table->tab, &c->in_table->get);
}
/* Called by protocol to activate out_table */
void
channel_setup_out_table(struct channel *c)
{
- struct rtable_config *cf = mb_allocz(c->proto->pool, sizeof(struct rtable_config));
- cf->name = "export";
- cf->addr_type = c->net_type;
- cf->internal = 1;
+ int nlen = sizeof("export") + strlen(c->name) + strlen(c->proto->name) + 3;
+
+ struct {
+ struct channel_aux_table cat;
+ struct rtable_config tab_cf;
+ char name[0];
+ } *cat = mb_allocz(c->proto->pool, sizeof(*cat) + nlen);
- c->out_table = rt_setup(c->proto->pool, cf);
+ bsprintf(cat->name, "%s.%s.export", c->proto->name, c->name);
+
+ cat->tab_cf.owner = cat;
+ cat->tab_cf.name = cat->name;
+ cat->tab_cf.addr_type = c->net_type;
+ cat->tab_cf.cork_limit = 4 * page_size / sizeof(struct rt_pending_export);
+
+ c->out_table = &cat->cat;
+ c->out_table->push = (struct rt_import_request) {
+ .name = cat->name,
+ .list = proto_work_list(c->proto),
+ .trace_routes = c->debug | c->proto->debug,
+ .dump_req = channel_out_push_dump_req,
+ .log_state_change = channel_push_log_state_change,
+ };
+ c->out_table->get = (struct rt_export_request) {
+ .name = cat->name,
+ .list = proto_work_list(c->proto),
+ .trace_routes = c->debug | c->proto->debug,
+ .dump_req = channel_out_get_dump_req,
+ .log_state_change = channel_get_log_state_change,
+ .export_one = (c->ra_mode == RA_ANY) ? channel_out_export_one_any : channel_out_export_one_best,
+ .export_bulk = channel_out_export_bulk,
+ };
+
+ c->out_table->c = c;
+ c->out_table->tab = rt_setup(c->proto->pool, &cat->tab_cf);
+
+ rt_request_import(c->out_table->tab, &c->out_table->push);
+ rt_request_export(c->out_table->tab, &c->out_table->get);
}
+static void
+channel_aux_request_refeed(struct channel_aux_table *cat)
+{
+ if (cat->stop_pending)
+ return;
+
+ cat->refeed_pending = 1;
+ rt_stop_export(&cat->get, channel_aux_export_stopped);
+}
static void
channel_do_start(struct channel *c)
{
- rt_lock_table(c->table);
- add_tail(&c->table->channels, &c->table_node);
c->proto->active_channels++;
- c->feed_event = ev_new_init(c->proto->pool, channel_feed_loop, c);
-
- bmap_init(&c->export_map, c->proto->pool, 1024);
- memset(&c->stats, 0, sizeof(struct proto_stats));
-
- channel_reset_limit(&c->rx_limit);
- channel_reset_limit(&c->in_limit);
- channel_reset_limit(&c->out_limit);
-
CALL(c->channel->start, c);
+
+ channel_start_import(c);
}
static void
@@ -572,9 +1003,38 @@ channel_do_up(struct channel *c)
}
static void
-channel_do_flush(struct channel *c)
+channel_do_pause(struct channel *c)
+{
+ /* Stop export */
+ if (c->out_req.hook)
+ {
+ rt_stop_export(&c->out_req, channel_export_stopped);
+ c->refeeding = 0;
+ }
+
+ channel_roa_unsubscribe_all(c);
+}
+
+static void
+channel_do_stop(struct channel *c)
{
- rt_schedule_prune(c->table);
+ /* Drop auxiliary tables */
+ if (c->in_table)
+ channel_aux_stop(c->in_table);
+
+ if (c->out_table)
+ channel_aux_stop(c->out_table);
+
+ /* Stop import */
+ if (c->in_req.hook)
+ {
+ c->in_stopped = (event) {
+ .hook = channel_import_stopped,
+ .data = c,
+ .list = proto_event_list(c->proto),
+ };
+ rt_stop_import(&c->in_req, &c->in_stopped);
+ }
c->gr_wait = 0;
if (c->gr_lock)
@@ -582,48 +1042,30 @@ channel_do_flush(struct channel *c)
CALL(c->channel->shutdown, c);
- /* This have to be done in here, as channel pool is freed before channel_do_down() */
- bmap_free(&c->export_map);
- c->in_table = NULL;
- c->reload_event = NULL;
- c->out_table = NULL;
-
channel_roa_unsubscribe_all(c);
}
static void
channel_do_down(struct channel *c)
{
- ASSERT(!c->feed_active && !c->reload_active);
+ ASSERT(!c->out_req.hook && !c->in_req.hook && !c->out_table && !c->in_table);
- rem_node(&c->table_node);
- rt_unlock_table(c->table);
c->proto->active_channels--;
- if ((c->stats.imp_routes + c->stats.filt_routes) != 0)
- log(L_ERR "%s: Channel %s is down but still has some routes", c->proto->name, c->name);
-
- // bmap_free(&c->export_map);
- memset(&c->stats, 0, sizeof(struct proto_stats));
-
- c->in_table = NULL;
- c->reload_event = NULL;
- c->out_table = NULL;
-
- /* The in_table and out_table are going to be freed by freeing their resource pools. */
+ memset(&c->import_stats, 0, sizeof(struct channel_import_stats));
+ memset(&c->export_stats, 0, sizeof(struct channel_export_stats));
CALL(c->channel->cleanup, c);
/* Schedule protocol shutddown */
if (proto_is_done(c->proto))
- ev_schedule(c->proto->event);
+ proto_send_event(c->proto);
}
void
channel_set_state(struct channel *c, uint state)
{
uint cs = c->channel_state;
- uint es = c->export_state;
DBG("%s reporting channel %s state transition %s -> %s\n", c->proto->name, c->name, c_states[cs], c_states[state]);
if (state == cs)
@@ -635,24 +1077,15 @@ channel_set_state(struct channel *c, uint state)
switch (state)
{
case CS_START:
- ASSERT(cs == CS_DOWN || cs == CS_UP);
+ ASSERT(cs == CS_DOWN || cs == CS_PAUSE);
if (cs == CS_DOWN)
channel_do_start(c);
- if (es != ES_DOWN)
- channel_stop_export(c);
-
- if (c->in_table && (cs == CS_UP))
- channel_reset_import(c);
-
- if (c->out_table && (cs == CS_UP))
- channel_reset_export(c);
-
break;
case CS_UP:
- ASSERT(cs == CS_DOWN || cs == CS_START);
+ ASSERT(cs == CS_DOWN || cs == CS_START || cs == CS_PAUSE);
if (cs == CS_DOWN)
channel_do_start(c);
@@ -663,23 +1096,24 @@ channel_set_state(struct channel *c, uint state)
channel_do_up(c);
break;
- case CS_FLUSHING:
- ASSERT(cs == CS_START || cs == CS_UP);
+ case CS_PAUSE:
+ ASSERT(cs == CS_UP);
- if (es != ES_DOWN)
- channel_stop_export(c);
+ if (cs == CS_UP)
+ channel_do_pause(c);
+ break;
- if (c->in_table && (cs == CS_UP))
- channel_reset_import(c);
+ case CS_STOP:
+ ASSERT(cs == CS_UP || cs == CS_START || cs == CS_PAUSE);
- if (c->out_table && (cs == CS_UP))
- channel_reset_export(c);
+ if (cs == CS_UP)
+ channel_do_pause(c);
- channel_do_flush(c);
+ channel_do_stop(c);
break;
case CS_DOWN:
- ASSERT(cs == CS_FLUSHING);
+ ASSERT(cs == CS_STOP);
channel_do_down(c);
break;
@@ -701,50 +1135,62 @@ channel_set_state(struct channel *c, uint state)
* completed, it will switch back to ES_READY. This function can be called
* even when feeding is already running, in that case it is restarted.
*/
-void
-channel_request_feeding(struct channel *c)
+static void
+channel_request_table_feeding(struct channel *c)
{
- ASSERT(c->channel_state == CS_UP);
+ ASSERT(c->out_req.hook);
- CD(c, "Feeding requested");
+ c->refeed_pending = 1;
+ rt_stop_export(&c->out_req, channel_export_stopped);
+}
- /* Do nothing if we are still waiting for feeding */
- if (c->export_state == ES_DOWN)
+void
+channel_request_feeding(struct channel *c)
+{
+ if (c->gr_wait || !c->proto->rt_notify)
return;
- /* If we are already feeding, we want to restart it */
- if (c->export_state == ES_FEEDING)
- {
- /* Unless feeding is in initial state */
- if (!c->feed_active)
- return;
-
- rt_feed_channel_abort(c);
- }
+ CD(c, "Refeed requested");
- /* Track number of exported routes during refeed */
- c->refeed_count = 0;
+ ASSERT_DIE(c->out_req.hook);
- channel_schedule_feed(c, 0); /* Sets ES_FEEDING */
- channel_log_state_change(c);
+ if (c->out_table)
+ channel_aux_request_refeed(c->out_table);
+ else
+ channel_request_table_feeding(c);
}
-static void
+void
channel_request_reload(struct channel *c)
{
- ASSERT(c->channel_state == CS_UP);
+ ASSERT(c->in_req.hook);
ASSERT(channel_reloadable(c));
CD(c, "Reload requested");
- c->proto->reload_routes(c);
+ if (c->in_table)
+ channel_aux_request_refeed(c->in_table);
+ else
+ c->proto->reload_routes(c);
+}
- /*
- * Should this be done before reload_routes() hook?
- * Perhaps, but routes are updated asynchronously.
- */
- channel_reset_limit(&c->rx_limit);
- channel_reset_limit(&c->in_limit);
+void
+channel_refresh_begin(struct channel *c)
+{
+ CD(c, "Channel route refresh begin");
+ if (c->in_table)
+ rt_refresh_begin(&c->in_table->push);
+ else
+ rt_refresh_begin(&c->in_req);
+}
+
+void
+channel_refresh_end(struct channel *c)
+{
+ if (c->in_table)
+ rt_refresh_end(&c->in_table->push);
+ else
+ rt_refresh_end(&c->in_req);
}
const struct channel_class channel_basic = {
@@ -847,19 +1293,19 @@ channel_reconfigure(struct channel *c, struct channel_config *cf)
/* Reconfigure channel fields */
c->in_filter = cf->in_filter;
c->out_filter = cf->out_filter;
- c->rx_limit = cf->rx_limit;
- c->in_limit = cf->in_limit;
- c->out_limit = cf->out_limit;
+
+ channel_update_limit(c, &c->rx_limit, PLD_RX, &cf->rx_limit);
+ channel_update_limit(c, &c->in_limit, PLD_IN, &cf->in_limit);
+ channel_update_limit(c, &c->out_limit, PLD_OUT, &cf->out_limit);
// c->ra_mode = cf->ra_mode;
c->merge_limit = cf->merge_limit;
c->preference = cf->preference;
c->debug = cf->debug;
+ c->in_req.trace_routes = c->out_req.trace_routes = c->debug | c->proto->debug;
c->in_keep_filtered = cf->in_keep_filtered;
c->rpki_reload = cf->rpki_reload;
- channel_verify_limits(c);
-
/* Execute channel-specific reconfigure hook */
if (c->channel->reconfigure && !c->channel->reconfigure(c, cf, &import_changed, &export_changed))
return 0;
@@ -902,7 +1348,7 @@ channel_reconfigure(struct channel *c, struct channel_config *cf)
channel_request_reload(c);
if (export_changed)
- channel_request_feeding(c);
+ channel_request_table_feeding(c);
done:
CD(c, "Reconfigured");
@@ -950,34 +1396,50 @@ proto_configure_channel(struct proto *p, struct channel **pc, struct channel_con
return 1;
}
+static void
+proto_cleanup(struct proto *p)
+{
+ p->active = 0;
+ proto_log_state_change(p);
+ proto_rethink_goal(p);
+}
static void
-proto_event(void *ptr)
+proto_loop_stopped(void *ptr)
{
struct proto *p = ptr;
- if (p->do_start)
- {
- if_feed_baby(p);
- p->do_start = 0;
- }
+ ASSERT_DIE(birdloop_inside(&main_birdloop));
+
+ p->loop = &main_birdloop;
+ p->pool = NULL;
+ p->event->list = NULL;
+
+ proto_cleanup(p);
+}
+
+static void
+proto_event(void *ptr)
+{
+ struct proto *p = ptr;
if (p->do_stop)
{
if (p->proto == &proto_unix_iface)
if_flush_ifaces(p);
+
p->do_stop = 0;
}
if (proto_is_done(p))
- {
- if (p->proto->cleanup)
- p->proto->cleanup(p);
-
- p->active = 0;
- proto_log_state_change(p);
- proto_rethink_goal(p);
- }
+ if (p->loop != &main_birdloop)
+ birdloop_stop_self(p->loop, proto_loop_stopped, p);
+ else
+ {
+ rp_free(p->pool, proto_pool);
+ p->pool = NULL;
+ proto_cleanup(p);
+ }
}
@@ -1018,10 +1480,10 @@ proto_init(struct proto_config *c, node *n)
struct protocol *pr = c->protocol;
struct proto *p = pr->init(c);
+ p->loop = &main_birdloop;
p->proto_state = PS_DOWN;
p->last_state_change = current_time();
p->vrf = c->vrf;
- p->vrf_set = c->vrf_set;
insert_node(&p->n, n);
p->event = ev_new_init(proto_pool, proto_event, p);
@@ -1034,11 +1496,30 @@ proto_init(struct proto_config *c, node *n)
static void
proto_start(struct proto *p)
{
- /* Here we cannot use p->cf->name since it won't survive reconfiguration */
- p->pool = rp_new(proto_pool, p->proto->name);
+ DBG("Kicking %s up\n", p->name);
+ PD(p, "Starting");
+
+ int ns = strlen("Protocol ") + strlen(p->cf->name) + 1;
+ void *nb = mb_alloc(proto_pool, ns);
+ ASSERT_DIE(ns - 1 == bsnprintf(nb, ns, "Protocol %s", p->cf->name));
if (graceful_restart_state == GRS_INIT)
p->gr_recovery = 1;
+
+ if (p->cf->loop_order == DOMAIN_ORDER(the_bird))
+ p->pool = rp_new(proto_pool, &main_birdloop, nb);
+ else
+ {
+ p->loop = birdloop_new(proto_pool, p->cf->loop_order, nb);
+ p->pool = birdloop_pool(p->loop);
+ }
+
+ p->event->list = proto_event_list(p);
+
+ mb_move(nb, p->pool);
+
+ PROTO_LOCKED_FROM_MAIN(p)
+ proto_notify_state(p, (p->proto->start ? p->proto->start(p) : PS_UP));
}
@@ -1074,6 +1555,7 @@ proto_config_new(struct protocol *pr, int class)
cf->class = class;
cf->debug = new_config->proto_default_debug;
cf->mrtdump = new_config->proto_default_mrtdump;
+ cf->loop_order = DOMAIN_ORDER(the_bird);
init_list(&cf->channels);
@@ -1189,8 +1671,7 @@ proto_reconfigure(struct proto *p, struct proto_config *oc, struct proto_config
if ((nc->protocol != oc->protocol) ||
(nc->net_type != oc->net_type) ||
(nc->disabled != p->disabled) ||
- (nc->vrf != oc->vrf) ||
- (nc->vrf_set != oc->vrf_set))
+ (nc->vrf != oc->vrf))
return 0;
p->name = nc->name;
@@ -1279,8 +1760,14 @@ protos_commit(struct config *new, struct config *old, int force_reconfig, int ty
nc->proto = p;
/* We will try to reconfigure protocol p */
- if (! force_reconfig && proto_reconfigure(p, oc, nc, type))
- continue;
+ if (!force_reconfig)
+ {
+ int ok;
+ PROTO_LOCKED_FROM_MAIN(p)
+ ok = proto_reconfigure(p, oc, nc, type);
+ if (ok)
+ continue;
+ }
if (nc->parent)
{
@@ -1363,11 +1850,20 @@ protos_commit(struct config *new, struct config *old, int force_reconfig, int ty
}
static void
-proto_rethink_goal(struct proto *p)
+proto_shutdown(struct proto *p)
{
- struct protocol *q;
- byte goal;
+ if (p->proto_state == PS_START || p->proto_state == PS_UP)
+ {
+ /* Going down */
+ DBG("Kicking %s down\n", p->name);
+ PD(p, "Shutting down");
+ proto_notify_state(p, (p->proto->shutdown ? p->proto->shutdown(p) : PS_DOWN));
+ }
+}
+static void
+proto_rethink_goal(struct proto *p)
+{
if (p->reconfiguring && !p->active)
{
struct proto_config *nc = p->cf_new;
@@ -1387,32 +1883,12 @@ proto_rethink_goal(struct proto *p)
/* Determine what state we want to reach */
if (p->disabled || p->reconfiguring)
- goal = PS_DOWN;
- else
- goal = PS_UP;
-
- q = p->proto;
- if (goal == PS_UP)
- {
- if (!p->active)
- {
- /* Going up */
- DBG("Kicking %s up\n", p->name);
- PD(p, "Starting");
- proto_start(p);
- proto_notify_state(p, (q->start ? q->start(p) : PS_UP));
- }
- }
- else
{
- if (p->proto_state == PS_START || p->proto_state == PS_UP)
- {
- /* Going down */
- DBG("Kicking %s down\n", p->name);
- PD(p, "Shutting down");
- proto_notify_state(p, (q->shutdown ? q->shutdown(p) : PS_DOWN));
- }
+ PROTO_LOCKED_FROM_MAIN(p)
+ proto_shutdown(p);
}
+ else if (!p->active)
+ proto_start(p);
}
struct proto *
@@ -1524,7 +2000,7 @@ graceful_restart_done(timer *t UNUSED)
WALK_LIST(c, p->channels)
{
/* Resume postponed export of routes */
- if ((c->channel_state == CS_UP) && c->gr_wait && c->proto->rt_notify)
+ if ((c->channel_state == CS_UP) && c->gr_wait && p->rt_notify)
channel_start_export(c);
/* Cleanup */
@@ -1614,7 +2090,11 @@ protos_dump_all(void)
struct proto *p;
WALK_LIST(p, proto_list)
{
- debug(" protocol %s state %s\n", p->name, p_states[p->proto_state]);
+#define DPF(x) (p->x ? " " #x : "")
+ debug(" protocol %s (%p) state %s with %d active channels flags: %s%s%s%s\n",
+ p->name, p, p_states[p->proto_state], p->active_channels,
+ DPF(disabled), DPF(active), DPF(do_stop), DPF(reconfiguring));
+#undef DPF
struct channel *c;
WALK_LIST(c, p->channels)
@@ -1624,6 +2104,23 @@ protos_dump_all(void)
debug("\tInput filter: %s\n", filter_name(c->in_filter));
if (c->out_filter)
debug("\tOutput filter: %s\n", filter_name(c->out_filter));
+ debug("\tChannel state: %s/%s/%s\n", c_states[c->channel_state],
+ c->in_req.hook ? rt_import_state_name(rt_import_get_state(c->in_req.hook)) : "-",
+ c->out_req.hook ? rt_export_state_name(rt_export_get_state(c->out_req.hook)) : "-");
+ if (c->in_table)
+ {
+ debug("\tInput aux table:\n");
+ rt_dump_hooks(c->in_table->tab);
+ rt_dump(c->in_table->tab);
+ debug("\tEnd of input aux table.\n");
+ }
+ if (c->out_table)
+ {
+ debug("\tOutput aux table:\n");
+ rt_dump_hooks(c->in_table->tab);
+ rt_dump(c->in_table->tab);
+ debug("\tEnd of output aux table.\n");
+ }
}
if (p->proto->dump && (p->proto_state != PS_DOWN))
@@ -1702,9 +2199,7 @@ protos_build(void)
proto_build(&proto_perf);
#endif
- proto_pool = rp_new(&root_pool, "Protocols");
- proto_shutdown_timer = tm_new(proto_pool);
- proto_shutdown_timer->hook = proto_shutdown_loop;
+ proto_pool = rp_new(&root_pool, &main_birdloop, "Protocols");
}
@@ -1712,7 +2207,7 @@ protos_build(void)
int proto_restart;
static void
-proto_shutdown_loop(timer *t UNUSED)
+proto_shutdown_loop(void *data UNUSED)
{
struct proto *p, *p_next;
@@ -1731,6 +2226,11 @@ proto_shutdown_loop(timer *t UNUSED)
}
}
+static event proto_schedule_down_event = {
+ .hook = proto_shutdown_loop,
+ .list = &global_event_list,
+};
+
static inline void
proto_schedule_down(struct proto *p, byte restart, byte code)
{
@@ -1743,7 +2243,8 @@ proto_schedule_down(struct proto *p, byte restart, byte code)
p->down_sched = restart ? PDS_RESTART : PDS_DISABLE;
p->down_code = code;
- tm_start_max(proto_shutdown_timer, restart ? 250 MS : 0);
+
+ ev_send_self(&proto_schedule_down_event);
}
/**
@@ -1780,108 +2281,136 @@ proto_set_message(struct proto *p, char *msg, int len)
}
-static const char *
-channel_limit_name(struct channel_limit *l)
-{
- const char *actions[] = {
- [PLA_WARN] = "warn",
- [PLA_BLOCK] = "block",
- [PLA_RESTART] = "restart",
- [PLA_DISABLE] = "disable",
- };
+static const char * channel_limit_name[] = {
+ [PLA_WARN] = "warn",
+ [PLA_BLOCK] = "block",
+ [PLA_RESTART] = "restart",
+ [PLA_DISABLE] = "disable",
+};
- return actions[l->action];
-}
-/**
- * channel_notify_limit: notify about limit hit and take appropriate action
- * @c: channel
- * @l: limit being hit
- * @dir: limit direction (PLD_*)
- * @rt_count: the number of routes
- *
- * The function is called by the route processing core when limit @l
- * is breached. It activates the limit and tooks appropriate action
- * according to @l->action.
- */
-void
-channel_notify_limit(struct channel *c, struct channel_limit *l, int dir, u32 rt_count)
+static void
+channel_log_limit(struct channel *c, struct limit *l, int dir)
{
const char *dir_name[PLD_MAX] = { "receive", "import" , "export" };
- const byte dir_down[PLD_MAX] = { PDC_RX_LIMIT_HIT, PDC_IN_LIMIT_HIT, PDC_OUT_LIMIT_HIT };
- struct proto *p = c->proto;
+ log(L_WARN "Channel %s.%s hits route %s limit (%d), action: %s",
+ c->proto->name, c->name, dir_name[dir], l->max, channel_limit_name[c->limit_actions[dir]]);
+}
- if (l->state == PLS_BLOCKED)
+static void
+channel_activate_limit(struct channel *c, struct limit *l, int dir)
+{
+ if (c->limit_active & (1 << dir))
return;
- /* For warning action, we want the log message every time we hit the limit */
- if (!l->state || ((l->action == PLA_WARN) && (rt_count == l->limit)))
- log(L_WARN "Protocol %s hits route %s limit (%d), action: %s",
- p->name, dir_name[dir], l->limit, channel_limit_name(l));
+ c->limit_active |= (1 << dir);
+ channel_log_limit(c, l, dir);
+}
+
+static int
+channel_limit_warn(struct limit *l, void *data)
+{
+ struct channel_limit_data *cld = data;
+ struct channel *c = cld->c;
+ int dir = cld->dir;
- switch (l->action)
- {
- case PLA_WARN:
- l->state = PLS_ACTIVE;
- break;
+ channel_log_limit(c, l, dir);
- case PLA_BLOCK:
- l->state = PLS_BLOCKED;
- break;
+ return 0;
+}
- case PLA_RESTART:
- case PLA_DISABLE:
- l->state = PLS_BLOCKED;
- if (p->proto_state == PS_UP)
- proto_schedule_down(p, l->action == PLA_RESTART, dir_down[dir]);
- break;
- }
+static int
+channel_limit_block(struct limit *l, void *data)
+{
+ struct channel_limit_data *cld = data;
+ struct channel *c = cld->c;
+ int dir = cld->dir;
+
+ channel_activate_limit(c, l, dir);
+
+ return 1;
}
-static void
-channel_verify_limits(struct channel *c)
+static const byte chl_dir_down[PLD_MAX] = { PDC_RX_LIMIT_HIT, PDC_IN_LIMIT_HIT, PDC_OUT_LIMIT_HIT };
+
+static int
+channel_limit_down(struct limit *l, void *data)
{
- struct channel_limit *l;
- u32 all_routes = c->stats.imp_routes + c->stats.filt_routes;
+ struct channel_limit_data *cld = data;
+ struct channel *c = cld->c;
+ struct proto *p = c->proto;
+ int dir = cld->dir;
- l = &c->rx_limit;
- if (l->action && (all_routes > l->limit))
- channel_notify_limit(c, l, PLD_RX, all_routes);
+ channel_activate_limit(c, l, dir);
- l = &c->in_limit;
- if (l->action && (c->stats.imp_routes > l->limit))
- channel_notify_limit(c, l, PLD_IN, c->stats.imp_routes);
+ if (p->proto_state == PS_UP)
+ proto_schedule_down(p, c->limit_actions[dir] == PLA_RESTART, chl_dir_down[dir]);
- l = &c->out_limit;
- if (l->action && (c->stats.exp_routes > l->limit))
- channel_notify_limit(c, l, PLD_OUT, c->stats.exp_routes);
+ return 1;
}
-static inline void
-channel_reset_limit(struct channel_limit *l)
+static int (*channel_limit_action[])(struct limit *, void *) = {
+ [PLA_NONE] = NULL,
+ [PLA_WARN] = channel_limit_warn,
+ [PLA_BLOCK] = channel_limit_block,
+ [PLA_RESTART] = channel_limit_down,
+ [PLA_DISABLE] = channel_limit_down,
+};
+
+static void
+channel_update_limit(struct channel *c, struct limit *l, int dir, struct channel_limit *cf)
+{
+ l->action = channel_limit_action[cf->action];
+ c->limit_actions[dir] = cf->action;
+
+ struct channel_limit_data cld = { .c = c, .dir = dir };
+ limit_update(l, &cld, cf->action ? cf->limit : ~((u32) 0));
+}
+
+static void
+channel_init_limit(struct channel *c, struct limit *l, int dir, struct channel_limit *cf)
{
- if (l->action)
- l->state = PLS_INITIAL;
+ channel_reset_limit(c, l, dir);
+ channel_update_limit(c, l, dir, cf);
}
+static void
+channel_reset_limit(struct channel *c, struct limit *l, int dir)
+{
+ limit_reset(l);
+ c->limit_active &= ~(1 << dir);
+}
+
+static struct rte_owner_class default_rte_owner_class;
+
static inline void
proto_do_start(struct proto *p)
{
+ ASSERT_DIE(birdloop_inside(p->loop));
+
p->active = 1;
- p->do_start = 1;
- ev_schedule(p->event);
+
+ rt_init_sources(&p->sources, p->name, proto_work_list(p));
+ if (!p->sources.class)
+ p->sources.class = &default_rte_owner_class;
+
+ if (!p->cf->late_if_feed)
+ if_feed_baby(p);
}
static void
proto_do_up(struct proto *p)
{
+ ASSERT_DIE(birdloop_inside(p->loop));
+
if (!p->main_source)
- {
p->main_source = rt_get_source(p, 0);
- rt_lock_source(p->main_source);
- }
+ // Locked automaticaly
proto_start_channels(p);
+
+ if (p->cf->late_if_feed)
+ if_feed_baby(p);
}
static inline void
@@ -1896,9 +2425,6 @@ proto_do_stop(struct proto *p)
p->down_sched = 0;
p->gr_recovery = 0;
- p->do_stop = 1;
- ev_schedule(p->event);
-
if (p->main_source)
{
rt_unlock_source(p->main_source);
@@ -1906,19 +2432,21 @@ proto_do_stop(struct proto *p)
}
proto_stop_channels(p);
+ rt_destroy_sources(&p->sources, p->event);
+
+ p->do_stop = 1;
+ proto_send_event(p);
}
static void
proto_do_down(struct proto *p)
{
p->down_code = 0;
- neigh_prune();
- rfree(p->pool);
- p->pool = NULL;
+ neigh_prune(p);
/* Shutdown is finished in the protocol event */
if (proto_is_done(p))
- ev_schedule(p->event);
+ proto_send_event(p);
}
@@ -2009,38 +2537,58 @@ proto_state_name(struct proto *p)
static void
channel_show_stats(struct channel *c)
{
- struct proto_stats *s = &c->stats;
+ struct channel_import_stats *ch_is = &c->import_stats;
+ struct channel_export_stats *ch_es = &c->export_stats;
+ struct rt_import_stats *rt_is = c->in_req.hook ? &c->in_req.hook->stats : NULL;
+ struct rt_export_stats *rt_es = c->out_req.hook ? &c->out_req.hook->stats : NULL;
+
+#define SON(ie, item) ((ie) ? (ie)->item : 0)
+#define SCI(item) SON(ch_is, item)
+#define SCE(item) SON(ch_es, item)
+#define SRI(item) SON(rt_is, item)
+#define SRE(item) SON(rt_es, item)
+
+ u32 rx_routes = c->rx_limit.count;
+ u32 in_routes = c->in_limit.count;
+ u32 out_routes = c->out_limit.count;
if (c->in_keep_filtered)
cli_msg(-1006, " Routes: %u imported, %u filtered, %u exported, %u preferred",
- s->imp_routes, s->filt_routes, s->exp_routes, s->pref_routes);
+ in_routes, (rx_routes - in_routes), out_routes, SRI(pref));
else
cli_msg(-1006, " Routes: %u imported, %u exported, %u preferred",
- s->imp_routes, s->exp_routes, s->pref_routes);
-
- cli_msg(-1006, " Route change stats: received rejected filtered ignored accepted");
- cli_msg(-1006, " Import updates: %10u %10u %10u %10u %10u",
- s->imp_updates_received, s->imp_updates_invalid,
- s->imp_updates_filtered, s->imp_updates_ignored,
- s->imp_updates_accepted);
- cli_msg(-1006, " Import withdraws: %10u %10u --- %10u %10u",
- s->imp_withdraws_received, s->imp_withdraws_invalid,
- s->imp_withdraws_ignored, s->imp_withdraws_accepted);
- cli_msg(-1006, " Export updates: %10u %10u %10u --- %10u",
- s->exp_updates_received, s->exp_updates_rejected,
- s->exp_updates_filtered, s->exp_updates_accepted);
- cli_msg(-1006, " Export withdraws: %10u --- --- --- %10u",
- s->exp_withdraws_received, s->exp_withdraws_accepted);
+ in_routes, out_routes, SRI(pref));
+
+ cli_msg(-1006, " Route change stats: received rejected filtered ignored limited accepted");
+ cli_msg(-1006, " Import updates: %10u %10u %10u %10u %10u %10u",
+ SCI(updates_received), SCI(updates_invalid),
+ SCI(updates_filtered), SRI(updates_ignored),
+ SCI(updates_limited_rx) + SCI(updates_limited_in),
+ SRI(updates_accepted));
+ cli_msg(-1006, " Import withdraws: %10u %10u --- %10u --- %10u",
+ SCI(withdraws_received), SCI(withdraws_invalid),
+ SRI(withdraws_ignored), SRI(withdraws_accepted));
+ cli_msg(-1006, " Export updates: %10u %10u %10u --- %10u %10u",
+ SRE(updates_received), SCE(updates_rejected),
+ SCE(updates_filtered), SCE(updates_limited), SCE(updates_accepted));
+ cli_msg(-1006, " Export withdraws: %10u --- --- --- ---%10u",
+ SRE(withdraws_received), SCE(withdraws_accepted));
+
+#undef SRI
+#undef SRE
+#undef SCI
+#undef SCE
+#undef SON
}
void
-channel_show_limit(struct channel_limit *l, const char *dsc)
+channel_show_limit(struct limit *l, const char *dsc, int active, int action)
{
if (!l->action)
return;
- cli_msg(-1006, " %-16s%d%s", dsc, l->limit, l->state ? " [HIT]" : "");
- cli_msg(-1006, " Action: %s", channel_limit_name(l));
+ cli_msg(-1006, " %-16s%d%s", dsc, l->max, active ? " [HIT]" : "");
+ cli_msg(-1006, " Action: %s", channel_limit_name[action]);
}
void
@@ -2048,6 +2596,8 @@ channel_show_info(struct channel *c)
{
cli_msg(-1006, " Channel %s", c->name);
cli_msg(-1006, " State: %s", c_states[c->channel_state]);
+ cli_msg(-1006, " Import state: %s", rt_import_state_name(rt_import_get_state(c->in_req.hook)));
+ cli_msg(-1006, " Export state: %s", rt_export_state_name(rt_export_get_state(c->out_req.hook)));
cli_msg(-1006, " Table: %s", c->table->name);
cli_msg(-1006, " Preference: %d", c->preference);
cli_msg(-1006, " Input filter: %s", filter_name(c->in_filter));
@@ -2058,9 +2608,9 @@ channel_show_info(struct channel *c)
c->gr_lock ? " pending" : "",
c->gr_wait ? " waiting" : "");
- channel_show_limit(&c->rx_limit, "Receive limit:");
- channel_show_limit(&c->in_limit, "Import limit:");
- channel_show_limit(&c->out_limit, "Export limit:");
+ channel_show_limit(&c->rx_limit, "Receive limit:", c->limit_active & (1 << PLD_RX), c->limit_actions[PLD_RX]);
+ channel_show_limit(&c->in_limit, "Import limit:", c->limit_active & (1 << PLD_IN), c->limit_actions[PLD_IN]);
+ channel_show_limit(&c->out_limit, "Export limit:", c->limit_active & (1 << PLD_OUT), c->limit_actions[PLD_OUT]);
if (c->channel_state != CS_DOWN)
channel_show_stats(c);
@@ -2106,8 +2656,8 @@ proto_cmd_show(struct proto *p, uintptr_t verbose, int cnt)
cli_msg(-1006, " Message: %s", p->message);
if (p->cf->router_id)
cli_msg(-1006, " Router ID: %R", p->cf->router_id);
- if (p->vrf_set)
- cli_msg(-1006, " VRF: %s", p->vrf ? p->vrf->name : "default");
+ if (p->vrf)
+ cli_msg(-1006, " VRF: %s", p->vrf->name);
if (p->proto->show_proto_info)
p->proto->show_proto_info(p);
@@ -2135,7 +2685,7 @@ proto_cmd_disable(struct proto *p, uintptr_t arg, int cnt UNUSED)
p->disabled = 1;
p->down_code = PDC_CMD_DISABLE;
proto_set_message(p, (char *) arg, -1);
- proto_rethink_goal(p);
+ proto_shutdown(p);
cli_msg(-9, "%s: disabled", p->name);
}
@@ -2168,9 +2718,9 @@ proto_cmd_restart(struct proto *p, uintptr_t arg, int cnt UNUSED)
p->disabled = 1;
p->down_code = PDC_CMD_RESTART;
proto_set_message(p, (char *) arg, -1);
- proto_rethink_goal(p);
+ proto_shutdown(p);
p->disabled = 0;
- proto_rethink_goal(p);
+ /* After the protocol shuts down, proto_rethink_goal() is run from proto_event. */
cli_msg(-12, "%s: restarted", p->name);
}
@@ -2243,8 +2793,15 @@ proto_apply_cmd_symbol(const struct symbol *s, void (* cmd)(struct proto *, uint
return;
}
- cmd(s->proto->proto, arg, 0);
- cli_msg(0, "");
+ if (s->proto->proto)
+ {
+ struct proto *p = s->proto->proto;
+ PROTO_LOCKED_FROM_MAIN(p)
+ cmd(p, arg, 0);
+ cli_msg(0, "");
+ }
+ else
+ cli_msg(9002, "%s does not exist", s->name);
}
static void
@@ -2255,7 +2812,8 @@ proto_apply_cmd_patt(const char *patt, void (* cmd)(struct proto *, uintptr_t, i
WALK_LIST(p, proto_list)
if (!patt || patmatch(patt, p->name))
- cmd(p, arg, cnt++);
+ PROTO_LOCKED_FROM_MAIN(p)
+ cmd(p, arg, cnt++);
if (!cnt)
cli_msg(8003, "No protocols match");
diff --git a/nest/protocol.h b/nest/protocol.h
index abcc505d..a4b6152b 100644
--- a/nest/protocol.h
+++ b/nest/protocol.h
@@ -13,11 +13,11 @@
#include "lib/resource.h"
#include "lib/event.h"
#include "nest/route.h"
+#include "nest/limit.h"
#include "conf/conf.h"
struct iface;
struct ifa;
-struct rtable;
struct rte;
struct neighbor;
struct rta;
@@ -74,12 +74,9 @@ struct protocol {
struct proto * (*init)(struct proto_config *); /* Create new instance */
int (*reconfigure)(struct proto *, struct proto_config *); /* Try to reconfigure instance, returns success */
void (*dump)(struct proto *); /* Debugging dump */
- void (*dump_attrs)(struct rte *); /* Dump protocol-dependent attributes */
int (*start)(struct proto *); /* Start the instance */
int (*shutdown)(struct proto *); /* Stop the instance */
- void (*cleanup)(struct proto *); /* Called after shutdown when protocol became hungry/down */
void (*get_status)(struct proto *, byte *buf); /* Get instance status (for `show protocols' command) */
- void (*get_route_info)(struct rte *, byte *buf); /* Get route information (for `show route' command) */
int (*get_attr)(const struct eattr *, byte *buf, int buflen); /* ASCIIfy dynamic attribute (returns GA_*) */
void (*show_proto_info)(struct proto *); /* Show protocol info (for `show protocols all' command) */
void (*copy_config)(struct proto_config *, struct proto_config *); /* Copy config from given protocol instance */
@@ -120,9 +117,10 @@ struct proto_config {
int class; /* SYM_PROTO or SYM_TEMPLATE */
u8 net_type; /* Protocol network type (NET_*), 0 for undefined */
u8 disabled; /* Protocol enabled/disabled by default */
- u8 vrf_set; /* Related VRF instance (below) is defined */
+ u8 late_if_feed; /* Delay interface feed after channels are up */
u32 debug, mrtdump; /* Debugging bitfields, both use D_* constants */
u32 router_id; /* Protocol specific router ID */
+ uint loop_order; /* Launch a birdloop on this locking level; use DOMAIN_ORDER(the_bird) for mainloop */
list channels; /* List of channel configs (struct channel_config) */
struct iface *vrf; /* Related VRF instance, NULL if global */
@@ -133,31 +131,6 @@ struct proto_config {
};
/* Protocol statistics */
-struct proto_stats {
- /* Import - from protocol to core */
- u32 imp_routes; /* Number of routes successfully imported to the (adjacent) routing table */
- u32 filt_routes; /* Number of routes rejected in import filter but kept in the routing table */
- u32 pref_routes; /* Number of routes selected as best in the (adjacent) routing table */
- u32 imp_updates_received; /* Number of route updates received */
- u32 imp_updates_invalid; /* Number of route updates rejected as invalid */
- u32 imp_updates_filtered; /* Number of route updates rejected by filters */
- u32 imp_updates_ignored; /* Number of route updates rejected as already in route table */
- u32 imp_updates_accepted; /* Number of route updates accepted and imported */
- u32 imp_withdraws_received; /* Number of route withdraws received */
- u32 imp_withdraws_invalid; /* Number of route withdraws rejected as invalid */
- u32 imp_withdraws_ignored; /* Number of route withdraws rejected as already not in route table */
- u32 imp_withdraws_accepted; /* Number of route withdraws accepted and processed */
-
- /* Export - from core to protocol */
- u32 exp_routes; /* Number of routes successfully exported to the protocol */
- u32 exp_updates_received; /* Number of route updates received */
- u32 exp_updates_rejected; /* Number of route updates rejected by protocol */
- u32 exp_updates_filtered; /* Number of route updates rejected by filters */
- u32 exp_updates_accepted; /* Number of route updates accepted and exported */
- u32 exp_withdraws_received; /* Number of route withdraws received */
- u32 exp_withdraws_accepted; /* Number of route withdraws accepted and processed */
-};
-
struct proto {
node n; /* Node in global proto_list */
struct protocol *proto; /* Protocol */
@@ -165,22 +138,23 @@ struct proto {
struct proto_config *cf_new; /* Configuration we want to switch to after shutdown (NULL=delete) */
pool *pool; /* Pool containing local objects */
event *event; /* Protocol event */
+ struct birdloop *loop; /* BIRDloop running this protocol */
list channels; /* List of channels to rtables (struct channel) */
struct channel *main_channel; /* Primary channel */
struct rte_src *main_source; /* Primary route source */
+ struct rte_owner sources; /* Route source owner structure */
struct iface *vrf; /* Related VRF instance, NULL if global */
const char *name; /* Name of this instance (== cf->name) */
u32 debug; /* Debugging flags */
u32 mrtdump; /* MRTDump flags */
uint active_channels; /* Number of active channels */
+ uint active_coroutines; /* Number of active coroutines */
byte net_type; /* Protocol network type (NET_*), 0 for undefined */
byte disabled; /* Manually disabled */
- byte vrf_set; /* Related VRF instance (above) is defined */
byte proto_state; /* Protocol state machine (PS_*, see below) */
byte active; /* From PS_START to cleanup after PS_STOP */
- byte do_start; /* Start actions are scheduled */
byte do_stop; /* Stop actions are scheduled */
byte reconfiguring; /* We're shutting down due to reconfiguration */
byte gr_recovery; /* Protocol should participate in graceful restart recovery */
@@ -198,12 +172,11 @@ struct proto {
* ifa_notify Notify protocol about interface address changes.
* rt_notify Notify protocol about routing table updates.
* neigh_notify Notify protocol about neighbor cache events.
- * make_tmp_attrs Add attributes to rta from from private attrs stored in rte. The route and rta MUST NOT be cached.
- * store_tmp_attrs Store private attrs back to rte and undef added attributes. The route and rta MUST NOT be cached.
- * preexport Called as the first step of the route exporting process.
- * It can construct a new rte, add private attributes and
- * decide whether the route shall be exported: 1=yes, -1=no,
- * 0=process it through the export filter set by the user.
+ * preexport Called as the first step of the route exporting process.
+ * It can decide whether the route shall be exported:
+ * -1 = reject,
+ * 0 = continue to export filter
+ * 1 = accept immediately
* reload_routes Request channel to reload all its routes to the core
* (using rte_update()). Returns: 0=reload cannot be done,
* 1= reload is scheduled and will happen (asynchronously).
@@ -213,11 +186,9 @@ struct proto {
void (*if_notify)(struct proto *, unsigned flags, struct iface *i);
void (*ifa_notify)(struct proto *, unsigned flags, struct ifa *a);
- void (*rt_notify)(struct proto *, struct channel *, struct network *net, struct rte *new, struct rte *old);
+ void (*rt_notify)(struct proto *, struct channel *, const net_addr *net, struct rte *new, const struct rte *old);
void (*neigh_notify)(struct neighbor *neigh);
- void (*make_tmp_attrs)(struct rte *rt, struct linpool *pool);
- void (*store_tmp_attrs)(struct rte *rt, struct linpool *pool);
- int (*preexport)(struct proto *, struct rte **rt, struct linpool *pool);
+ int (*preexport)(struct channel *, struct rte *rt);
void (*reload_routes)(struct channel *);
void (*feed_begin)(struct channel *, int initial);
void (*feed_end)(struct channel *);
@@ -233,13 +204,12 @@ struct proto {
* rte_remove Called whenever a rte is removed from the routing table.
*/
- int (*rte_recalculate)(struct rtable *, struct network *, struct rte *, struct rte *, struct rte *);
+ int (*rte_recalculate)(rtable *, struct network *, struct rte *, struct rte *, struct rte *);
int (*rte_better)(struct rte *, struct rte *);
- int (*rte_same)(struct rte *, struct rte *);
int (*rte_mergable)(struct rte *, struct rte *);
- struct rte * (*rte_modify)(struct rte *, struct linpool *);
void (*rte_insert)(struct network *, struct rte *);
void (*rte_remove)(struct network *, struct rte *);
+ u32 (*rte_igp_metric)(struct rte *);
/* Hic sunt protocol-specific data */
};
@@ -279,7 +249,7 @@ void channel_graceful_restart_unlock(struct channel *c);
#define DEFAULT_GR_WAIT 240
-void channel_show_limit(struct channel_limit *l, const char *dsc);
+void channel_show_limit(struct limit *l, const char *dsc, int active, int action);
void channel_show_info(struct channel *c);
void channel_cmd_debug(struct channel *c, uint mask);
@@ -298,6 +268,18 @@ struct proto *proto_iterate_named(struct symbol *sym, struct protocol *proto, st
#define PROTO_WALK_CMD(sym,pr,p) for(struct proto *p = NULL; p = proto_iterate_named(sym, pr, p); )
+#define PROTO_ENTER_FROM_MAIN(p) ({ \
+ ASSERT_DIE(birdloop_inside(&main_birdloop)); \
+ struct birdloop *_loop = (p)->loop; \
+ if (_loop != &main_birdloop) birdloop_enter(_loop); \
+ _loop; \
+ })
+
+#define PROTO_LEAVE_FROM_MAIN(loop) ({ if (loop != &main_birdloop) birdloop_leave(loop); })
+
+#define PROTO_LOCKED_FROM_MAIN(p) for (struct birdloop *_proto_loop = PROTO_ENTER_FROM_MAIN(p); _proto_loop; PROTO_LEAVE_FROM_MAIN(_proto_loop), (_proto_loop = NULL))
+
+
#define CMD_RELOAD 0
#define CMD_RELOAD_IN 1
#define CMD_RELOAD_OUT 2
@@ -386,6 +368,8 @@ void proto_notify_state(struct proto *p, unsigned state);
* as a result of received ROUTE-REFRESH request).
*/
+static inline int proto_is_inactive(struct proto *p)
+{ return (p->active_channels == 0) && (p->active_coroutines == 0) && (p->sources.uc == 0); }
/*
@@ -434,18 +418,29 @@ extern struct proto_config *cf_dev_proto;
#define PLA_RESTART 4 /* Force protocol restart */
#define PLA_DISABLE 5 /* Shutdown and disable protocol */
-#define PLS_INITIAL 0 /* Initial limit state after protocol start */
-#define PLS_ACTIVE 1 /* Limit was hit */
-#define PLS_BLOCKED 2 /* Limit is active and blocking new routes */
-
struct channel_limit {
u32 limit; /* Maximum number of prefixes */
u8 action; /* Action to take (PLA_*) */
- u8 state; /* State of limit (PLS_*) */
};
-void channel_notify_limit(struct channel *c, struct channel_limit *l, int dir, u32 rt_count);
+struct channel_limit_data {
+ struct channel *c;
+ int dir;
+};
+
+#define CLP__RX(_c) (&(_c)->rx_limit)
+#define CLP__IN(_c) (&(_c)->in_limit)
+#define CLP__OUT(_c) (&(_c)->out_limit)
+
+
+#if 0
+#define CHANNEL_LIMIT_LOG(_c, _dir, _op) log(L_TRACE "%s.%s: %s limit %s %u", (_c)->proto->name, (_c)->name, #_dir, _op, (CLP__##_dir(_c))->count)
+#else
+#define CHANNEL_LIMIT_LOG(_c, _dir, _op)
+#endif
+#define CHANNEL_LIMIT_PUSH(_c, _dir) ({ CHANNEL_LIMIT_LOG(_c, _dir, "push from"); struct channel_limit_data cld = { .c = (_c), .dir = PLD_##_dir }; limit_push(CLP__##_dir(_c), &cld); })
+#define CHANNEL_LIMIT_POP(_c, _dir) ({ limit_pop(CLP__##_dir(_c)); CHANNEL_LIMIT_LOG(_c, _dir, "pop to"); })
/*
* Channels
@@ -469,7 +464,6 @@ struct channel_class {
void (*dump)(struct proto *); /* Debugging dump */
- void (*dump_attrs)(struct rte *); /* Dump protocol-dependent attributes */
void (*get_status)(struct proto *, byte *buf); /* Get instance status (for `show protocols' command) */
void (*get_route_info)(struct rte *, byte *buf); /* Get route information (for `show route' command) */
@@ -489,6 +483,7 @@ struct channel_config {
struct proto_config *parent; /* Where channel is defined (proto or template) */
struct rtable_config *table; /* Table we're attached to */
const struct filter *in_filter, *out_filter; /* Attached filters */
+
struct channel_limit rx_limit; /* Limit for receiving routes from protocol
(relevant when in_keep_filtered is active) */
struct channel_limit in_limit; /* Limit for importing routes from protocol */
@@ -505,23 +500,50 @@ struct channel_config {
struct channel {
node n; /* Node in proto->channels */
- node table_node; /* Node in table->channels */
const char *name; /* Channel name (may be NULL) */
const struct channel_class *channel;
struct proto *proto;
- struct rtable *table;
+ rtable *table;
const struct filter *in_filter; /* Input filter */
const struct filter *out_filter; /* Output filter */
- struct bmap export_map; /* Keeps track which routes passed export filter */
- struct channel_limit rx_limit; /* Receive limit (for in_keep_filtered) */
- struct channel_limit in_limit; /* Input limit */
- struct channel_limit out_limit; /* Output limit */
-
- struct event *feed_event; /* Event responsible for feeding */
- struct fib_iterator feed_fit; /* Routing table iterator used during feeding */
- struct proto_stats stats; /* Per-channel protocol statistics */
+ struct bmap export_map; /* Keeps track which routes were really exported */
+ struct bmap export_reject_map; /* Keeps track which routes were rejected by export filter */
+
+ struct limit rx_limit; /* Receive limit (for in_keep_filtered) */
+ struct limit in_limit; /* Input limit */
+ struct limit out_limit; /* Output limit */
+
+ u8 limit_actions[PLD_MAX]; /* Limit actions enum */
+ u8 limit_active; /* Flags for active limits */
+
+ linpool *rte_update_pool;
+ uint rte_update_nest_cnt;
+
+ struct channel_import_stats {
+ /* Import - from protocol to core */
+ u32 updates_received; /* Number of route updates received */
+ u32 updates_invalid; /* Number of route updates rejected as invalid */
+ u32 updates_filtered; /* Number of route updates rejected by filters */
+ u32 updates_limited_rx; /* Number of route updates exceeding the rx_limit */
+ u32 updates_limited_in; /* Number of route updates exceeding the in_limit */
+ u32 withdraws_received; /* Number of route withdraws received */
+ u32 withdraws_invalid; /* Number of route withdraws rejected as invalid */
+ } import_stats;
+
+ struct channel_export_stats {
+ /* Export - from core to protocol */
+ u32 updates_rejected; /* Number of route updates rejected by protocol */
+ u32 updates_filtered; /* Number of route updates rejected by filters */
+ u32 updates_accepted; /* Number of route updates accepted and exported */
+ u32 updates_limited; /* Number of route updates exceeding the out_limit */
+ u32 withdraws_accepted; /* Number of route withdraws accepted and processed */
+ } export_stats;
+
+ struct rt_import_request in_req; /* Table import connection */
+ struct rt_export_request out_req; /* Table export connection */
+
u32 refeed_count; /* Number of routes exported during refeed regardless of out_limit */
u8 net_type; /* Routing table network type (NET_*), 0 for undefined */
@@ -534,31 +556,36 @@ struct channel {
u8 stale; /* Used in reconfiguration */
u8 channel_state;
- u8 export_state; /* Route export state (ES_*, see below) */
- u8 feed_active;
- u8 flush_active;
- u8 refeeding; /* We are refeeding (valid only if export_state == ES_FEEDING) */
+ u8 refeeding; /* Refeeding the channel. */
u8 reloadable; /* Hook reload_routes() is allowed on the channel */
u8 gr_lock; /* Graceful restart mechanism should wait for this channel */
u8 gr_wait; /* Route export to channel is postponed until graceful restart */
+ u8 restart_export; /* Route export should restart as soon as it stops */
btime last_state_change; /* Time of last state transition */
- struct rtable *in_table; /* Internal table for received routes */
- struct event *reload_event; /* Event responsible for reloading from in_table */
- struct fib_iterator reload_fit; /* FIB iterator in in_table used during reloading */
- struct rte *reload_next_rte; /* Route iterator in in_table used during reloading */
- u8 reload_active; /* Iterator reload_fit is linked */
+ struct channel_aux_table *in_table; /* Internal table for received routes */
+ struct event in_stopped; /* Import stop callback */
u8 reload_pending; /* Reloading and another reload is scheduled */
u8 refeed_pending; /* Refeeding and another refeed is scheduled */
u8 rpki_reload; /* RPKI changes trigger channel reload */
- struct rtable *out_table; /* Internal table for exported routes */
+ struct channel_aux_table *out_table; /* Internal table for exported routes */
list roa_subscriptions; /* List of active ROA table subscriptions based on filters roa_check() */
};
+struct channel_aux_table {
+ struct channel *c;
+ struct rt_import_request push;
+ struct rt_export_request get;
+ event push_stopped;
+ rtable *tab;
+ event *stop;
+ u8 refeed_pending;
+ u8 stop_pending;
+};
/*
* Channel states
@@ -585,70 +612,59 @@ struct channel {
* restricted by that and is on volition of the protocol. Generally, channels
* are opened in protocols' start() hooks when going to PS_UP.
*
- * CS_FLUSHING - The transitional state between initialized channel and closed
+ * CS_STOP - The transitional state between initialized channel and closed
* channel. The channel is still initialized, but no route exchange is allowed.
* Instead, the associated table is running flush loop to remove routes imported
* through the channel. After that, the channel changes state to CS_DOWN and
* is detached from the table (the table is unlocked and the channel is unlinked
- * from it). Unlike other states, the CS_FLUSHING state is not explicitly
+ * from it). Unlike other states, the CS_STOP state is not explicitly
* entered or left by the protocol. A protocol may request to close a channel
* (by calling channel_close()), which causes the channel to change state to
- * CS_FLUSHING and later to CS_DOWN. Also note that channels are closed
+ * CS_STOP and later to CS_DOWN. Also note that channels are closed
* automatically by the core when the protocol is going down.
*
+ * CS_PAUSE - Almost the same as CS_STOP, just the table import is kept and
+ * the table export is stopped before transitioning to CS_START.
+ *
* Allowed transitions:
*
* CS_DOWN -> CS_START / CS_UP
- * CS_START -> CS_UP / CS_FLUSHING
- * CS_UP -> CS_START / CS_FLUSHING
- * CS_FLUSHING -> CS_DOWN (automatic)
+ * CS_START -> CS_UP / CS_STOP
+ * CS_UP -> CS_PAUSE / CS_STOP
+ * CS_PAUSE -> CS_START (automatic)
+ * CS_STOP -> CS_DOWN (automatic)
*/
#define CS_DOWN 0
#define CS_START 1
#define CS_UP 2
-#define CS_FLUSHING 3
-
-#define ES_DOWN 0
-#define ES_FEEDING 1
-#define ES_READY 2
-
+#define CS_STOP 3
+#define CS_PAUSE 4
struct channel_config *proto_cf_find_channel(struct proto_config *p, uint net_type);
static inline struct channel_config *proto_cf_main_channel(struct proto_config *pc)
{ return proto_cf_find_channel(pc, pc->net_type); }
-struct channel *proto_find_channel_by_table(struct proto *p, struct rtable *t);
+struct channel *proto_find_channel_by_table(struct proto *p, rtable *t);
struct channel *proto_find_channel_by_name(struct proto *p, const char *n);
struct channel *proto_add_channel(struct proto *p, struct channel_config *cf);
int proto_configure_channel(struct proto *p, struct channel **c, struct channel_config *cf);
void channel_set_state(struct channel *c, uint state);
-void channel_setup_in_table(struct channel *c);
+void channel_setup_in_table(struct channel *c, int best);
void channel_setup_out_table(struct channel *c);
void channel_schedule_reload(struct channel *c);
static inline void channel_init(struct channel *c) { channel_set_state(c, CS_START); }
static inline void channel_open(struct channel *c) { channel_set_state(c, CS_UP); }
-static inline void channel_close(struct channel *c) { channel_set_state(c, CS_FLUSHING); }
+static inline void channel_close(struct channel *c) { channel_set_state(c, CS_STOP); }
void channel_request_feeding(struct channel *c);
+void channel_request_reload(struct channel *c);
+void channel_refresh_begin(struct channel *c);
+void channel_refresh_end(struct channel *c);
void *channel_config_new(const struct channel_class *cc, const char *name, uint net_type, struct proto_config *proto);
void *channel_config_get(const struct channel_class *cc, const char *name, uint net_type, struct proto_config *proto);
int channel_reconfigure(struct channel *c, struct channel_config *cf);
-
-/* Moved from route.h to avoid dependency conflicts */
-static inline void rte_update(struct proto *p, const net_addr *n, rte *new) { rte_update2(p->main_channel, n, new, p->main_source); }
-
-static inline void
-rte_update3(struct channel *c, const net_addr *n, rte *new, struct rte_src *src)
-{
- if (c->in_table && !rte_update_in(c, n, new, src))
- return;
-
- rte_update2(c, n, new, src);
-}
-
-
#endif
diff --git a/nest/route.h b/nest/route.h
index f5fc9e31..9093108b 100644
--- a/nest/route.h
+++ b/nest/route.h
@@ -2,6 +2,7 @@
* BIRD Internet Routing Daemon -- Routing Table
*
* (c) 1998--2000 Martin Mares <mj@ucw.cz>
+ * (c) 2019--2021 Maria Matejka <mq@jmq.cz>
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
@@ -10,13 +11,19 @@
#define _BIRD_ROUTE_H_
#include "lib/lists.h"
+#include "lib/event.h"
#include "lib/bitmap.h"
#include "lib/resource.h"
#include "lib/net.h"
+#include "lib/hash.h"
+#include "lib/event.h"
+
+#include <stdatomic.h>
struct ea_list;
struct protocol;
struct proto;
+struct channel;
struct rte_src;
struct symbol;
struct timer;
@@ -139,61 +146,91 @@ void fit_copy(struct fib *f, struct fib_iterator *dst, struct fib_iterator *src)
* It's guaranteed that there is at most one RTE for every (prefix,proto) pair.
*/
-struct rtable_config {
- node n;
- char *name;
- struct rtable *table;
- struct proto_config *krt_attached; /* Kernel syncer attached to this table */
- uint addr_type; /* Type of address data stored in table (NET_*) */
- int gc_max_ops; /* Maximum number of operations before GC is run */
- int gc_min_time; /* Minimum time between two consecutive GC runs */
- byte sorted; /* Routes of network are sorted according to rte_better() */
- byte internal; /* Internal table of a protocol */
- btime min_settle_time; /* Minimum settle time for notifications */
- btime max_settle_time; /* Maximum settle time for notifications */
-};
-
-typedef struct rtable {
- resource r;
- node n; /* Node in list of all tables */
+typedef struct rtable_private {
+#define RTABLE_PUBLIC \
+ resource r; \
+ node n; /* Node in list of all tables */ \
+ struct birdloop *loop; /* This loop runs the table */ \
+ char *name; /* Name of this table */ \
+ uint addr_type; /* Type of address data stored in table (NET_*) */ \
+ struct rtable_config *config; /* Configuration of this table */ \
+ struct event *nhu_event; /* Event to update next hops */ \
+ _Atomic byte nhu_state; /* Next Hop Update state */ \
+
+ RTABLE_PUBLIC;
pool *rp; /* Resource pool to allocate everything from, including itself */
+ struct slab *rte_slab; /* Slab to allocate route objects */
struct fib fib;
- char *name; /* Name of this table */
- list channels; /* List of attached channels (struct channel) */
- uint addr_type; /* Type of address data stored in table (NET_*) */
- int pipe_busy; /* Pipe loop detection */
int use_count; /* Number of protocols using this table */
u32 rt_count; /* Number of routes in the table */
+ u32 rr_count; /* Number of running route refresh requests */
+ u32 imports_up; /* Number of imports in TIS_UP state */
- byte internal; /* Internal table of a protocol */
+ list imports; /* Registered route importers */
+ list exports; /* Registered route exporters */
struct hmap id_map;
struct hostcache *hostcache;
- struct rtable_config *config; /* Configuration of this table */
- struct config *deleted; /* Table doesn't exist in current configuration,
- * delete as soon as use_count becomes 0 and remove
- * obstacle from this routing table.
- */
- struct event *rt_event; /* Routing table event */
+ struct event *prune_event; /* Event to prune abandoned routes */
+ struct event *announce_event; /* Event to announce pending exports */
+ struct event *ec_event; /* Event to prune finished exports */
+ struct event *hcu_event; /* Event to update host cache */
+ void (*delete)(void *); /* Delete callback (in parent loop context) */
btime last_rt_change; /* Last time when route changed */
btime base_settle_time; /* Start time of rtable settling interval */
btime gc_time; /* Time of last GC */
int gc_counter; /* Number of operations since last GC */
byte prune_state; /* Table prune state, 1 -> scheduled, 2-> running */
- byte hcu_scheduled; /* Hostcache update is scheduled */
- byte nhu_state; /* Next Hop Update state */
+
+ byte cork_active; /* Congestion control activated */
+
struct fib_iterator prune_fit; /* Rtable prune FIB iterator */
struct fib_iterator nhu_fit; /* Next Hop Update FIB iterator */
+ struct tbf rl_pipe; /* Rate limiting token buffer for pipe collisions */
+
+ linpool *nhu_lp; /* Linpool used for NHU */
list subscribers; /* Subscribers for notifications */
struct timer *settle_timer; /* Settle time for notifications */
+
+ list pending_exports; /* List of packed struct rt_pending_export */
+
+ struct rt_pending_export *first_export; /* First export to announce */
+ u64 next_export_seq; /* The next export will have this ID */
+} rtable_private;
+
+typedef union {
+ struct { RTABLE_PUBLIC };
+ rtable_private priv;
} rtable;
+#define RT_LOCK(tab) ({ birdloop_enter((tab)->loop); &(tab)->priv; })
+#define RT_UNLOCK(tab) birdloop_leave((tab)->loop)
+#define RT_PRIV(tab) ({ ASSERT_DIE(birdloop_inside((tab)->loop)); &(tab)->priv; })
+
+#define RT_LOCKED(tpub, tpriv) for (rtable_private *tpriv = RT_LOCK(tpub); tpriv; RT_UNLOCK(tpriv), (tpriv = NULL))
+
+struct rtable_config {
+ node n;
+ char *name;
+ void *owner; /* Main config if global table, channel_aux_table if channel table */
+ rtable *table;
+ struct proto_config *krt_attached; /* Kernel syncer attached to this table */
+ uint addr_type; /* Type of address data stored in table (NET_*) */
+ int gc_max_ops; /* Maximum number of operations before GC is run */
+ int gc_min_time; /* Minimum time between two consecutive GC runs */
+ byte sorted; /* Routes of network are sorted according to rte_better() */
+ btime min_settle_time; /* Minimum settle time for notifications */
+ btime max_settle_time; /* Maximum settle time for notifications */
+ btime min_rr_settle_time; /* Minimum settle time for notifications when route refresh is running */
+ btime max_rr_settle_time; /* Maximum settle time for notifications when route refresh is running */
+ uint cork_limit; /* Amount of routes to be pending on export to cork imports */
+};
+
struct rt_subscription {
node n;
rtable *tab;
- void (*hook)(struct rt_subscription *b);
- void *data;
+ event *event;
};
#define NHU_CLEAN 0
@@ -202,7 +239,8 @@ struct rt_subscription {
#define NHU_DIRTY 3
typedef struct network {
- struct rte *routes; /* Available routes for this network */
+ struct rte_storage *routes; /* Available routes for this network */
+ struct rt_pending_export *last, *first; /* Routes with unfinished exports */
struct fib_node n; /* FIB flags reserved for kernel syncer */
} net;
@@ -223,7 +261,7 @@ struct hostentry {
ip_addr addr; /* IP address of host, part of key */
ip_addr link; /* (link-local) IP address of host, used as gw
if host is directly attached */
- struct rtable *tab; /* Dependent table, part of key */
+ rtable *tab; /* Dependent table, part of key */
struct hostentry *next; /* Next in hash chain */
unsigned hash_key; /* Hash key */
unsigned uc; /* Use count */
@@ -234,64 +272,178 @@ struct hostentry {
};
typedef struct rte {
- struct rte *next;
- net *net; /* Network this RTE belongs to */
- struct channel *sender; /* Channel used to send the route to the routing table */
struct rta *attrs; /* Attributes of this route */
+ const net_addr *net; /* Network this RTE belongs to */
+ struct rte_src *src; /* Route source that created the route */
+ struct rt_import_hook *sender; /* Import hook used to send the route to the routing table */
+ btime lastmod; /* Last modified (set by table) */
u32 id; /* Table specific route id */
- byte flags; /* Flags (REF_...) */
+ byte flags; /* Table-specific flags */
byte pflags; /* Protocol-specific flags */
- word pref; /* Route preference */
- btime lastmod; /* Last modified */
- union { /* Protocol-dependent data (metrics etc.) */
-#ifdef CONFIG_RIP
- struct {
- struct iface *from; /* Incoming iface */
- u8 metric; /* RIP metric */
- u16 tag; /* External route tag */
- } rip;
-#endif
-#ifdef CONFIG_OSPF
- struct {
- u32 metric1, metric2; /* OSPF Type 1 and Type 2 metrics */
- u32 tag; /* External route tag */
- u32 router_id; /* Router that originated this route */
- } ospf;
-#endif
-#ifdef CONFIG_BGP
- struct {
- u8 suppressed; /* Used for deterministic MED comparison */
- s8 stale; /* Route is LLGR_STALE, -1 if unknown */
- } bgp;
-#endif
-#ifdef CONFIG_BABEL
- struct {
- u16 seqno; /* Babel seqno */
- u16 metric; /* Babel metric */
- u64 router_id; /* Babel router id */
- } babel;
-#endif
- struct { /* Routes generated by krt sync (both temporary and inherited ones) */
- s8 src; /* Alleged route source (see krt.h) */
- u8 proto; /* Kernel source protocol ID */
- u8 seen; /* Seen during last scan */
- u8 best; /* Best route in network, propagated to core */
- u32 metric; /* Kernel metric */
- } krt;
- } u;
+ u8 generation; /* If this route import is based on other previously exported route,
+ this value should be 1 + MAX(generation of the parent routes).
+ Otherwise the route is independent and this value is zero. */
+ u8 stale_cycle; /* Auxiliary value for route refresh */
} rte;
-#define REF_COW 1 /* Copy this rte on write */
+struct rte_storage {
+ struct rte_storage *next; /* Next in chain */
+ struct rte rte; /* Route data */
+};
+
+#define RTES_CLONE(r, l) ((r) ? (((*(l)) = (r)->rte), (l)) : NULL)
+#define RTES_OR_NULL(r) ((r) ? &((r)->rte) : NULL)
+
#define REF_FILTERED 2 /* Route is rejected by import filter */
-#define REF_STALE 4 /* Route is stale in a refresh cycle */
-#define REF_DISCARD 8 /* Route is scheduled for discard */
-#define REF_MODIFY 16 /* Route is scheduled for modify */
+#define REF_USE_STALE 4 /* Do not reset route's stale_cycle to the actual value */
/* Route is valid for propagation (may depend on other flags in the future), accepts NULL */
-static inline int rte_is_valid(rte *r) { return r && !(r->flags & REF_FILTERED); }
+static inline int rte_is_valid(const rte *r) { return r && !(r->flags & REF_FILTERED); }
/* Route just has REF_FILTERED flag */
-static inline int rte_is_filtered(rte *r) { return !!(r->flags & REF_FILTERED); }
+static inline int rte_is_filtered(const rte *r) { return !!(r->flags & REF_FILTERED); }
+
+
+/* Table-channel connections */
+
+struct rt_import_request {
+ struct rt_import_hook *hook; /* The table part of importer */
+ char *name;
+ u8 trace_routes;
+
+ event_list *list; /* Where to schedule import events */
+
+ void (*dump_req)(struct rt_import_request *req);
+ void (*log_state_change)(struct rt_import_request *req, u8 state);
+ /* Preimport is called when the @new route is just-to-be inserted, replacing @old.
+ * Return a route (may be different or modified in-place) to continue or NULL to withdraw. */
+ struct rte *(*preimport)(struct rt_import_request *req, struct rte *new, struct rte *old);
+};
+
+struct rt_import_hook {
+ node n;
+ rtable *table; /* The connected table */
+ struct rt_import_request *req; /* The requestor */
+
+ struct rt_import_stats {
+ /* Import - from protocol to core */
+ u32 pref; /* Number of routes selected as best in the (adjacent) routing table */
+ u32 updates_ignored; /* Number of route updates rejected as already in route table */
+ u32 updates_accepted; /* Number of route updates accepted and imported */
+ u32 withdraws_ignored; /* Number of route withdraws rejected as already not in route table */
+ u32 withdraws_accepted; /* Number of route withdraws accepted and processed */
+ } stats;
+
+ u64 flush_seq; /* Table export seq when the channel announced flushing */
+ btime last_state_change; /* Time of last state transition */
+
+ u8 import_state; /* IS_* */
+ u8 stale_set; /* Set this stale_cycle to imported routes */
+ u8 stale_valid; /* Routes with this stale_cycle and bigger are considered valid */
+ u8 stale_pruned; /* Last prune finished when this value was set at stale_valid */
+ u8 stale_pruning; /* Last prune started when this value was set at stale_valid */
+
+ struct event *export_announce_event; /* Event to run to announce new exports */
+ struct event *stopped; /* Event to run when import is stopped */
+};
+
+struct rt_pending_export {
+ struct rt_pending_export * _Atomic next; /* Next export for the same destination */
+ struct rte_storage *new, *new_best, *old, *old_best;
+ u64 seq; /* Sequential ID (table-local) of the pending export */
+};
+
+struct rt_export_request {
+ struct rt_export_hook *hook; /* Table part of the export */
+ char *name;
+ u8 trace_routes;
+
+ event_list *list; /* Where to schedule export events */
+
+ /* There are two methods of export. You can either request feeding every single change
+ * or feeding the whole route feed. In case of regular export, &export_one is preferred.
+ * Anyway, when feeding, &export_bulk is preferred, falling back to &export_one.
+ * Thus, for RA_OPTIMAL, &export_one is only set,
+ * for RA_MERGED and RA_ACCEPTED, &export_bulk is only set
+ * and for RA_ANY, both are set to accomodate for feeding all routes but receiving single changes
+ */
+ void (*export_one)(struct rt_export_request *req, const net_addr *net, struct rt_pending_export *rpe);
+ void (*export_bulk)(struct rt_export_request *req, const net_addr *net, struct rt_pending_export *rpe, rte **feed, uint count);
+
+ void (*dump_req)(struct rt_export_request *req);
+ void (*log_state_change)(struct rt_export_request *req, u8);
+};
+
+struct rt_export_hook {
+ node n;
+ rtable *table; /* The connected table */
+
+ pool *pool;
+
+ struct rt_export_request *req; /* The requestor */
+
+ struct rt_export_stats {
+ /* Export - from core to protocol */
+ u32 updates_received; /* Number of route updates received */
+ u32 withdraws_received; /* Number of route withdraws received */
+ } stats;
+
+ struct fib_iterator feed_fit; /* Routing table iterator used during feeding */
+
+ struct bmap seq_map; /* Keep track which exports were already procesed */
+
+ struct rt_pending_export * _Atomic last_export;/* Last export processed */
+ struct rt_pending_export *rpe_next; /* Next pending export to process */
+
+ btime last_state_change; /* Time of last state transition */
+
+ u8 refeed_pending; /* Refeeding and another refeed is scheduled */
+ _Atomic u8 export_state; /* Route export state (TES_*, see below) */
+
+ struct event *event; /* Event running all the export operations */
+
+ void (*stopped)(struct rt_export_request *); /* Stored callback when export is stopped */
+};
+
+extern struct event_cork rt_cork;
+
+#define TIS_DOWN 0
+#define TIS_UP 1
+#define TIS_STOP 2
+#define TIS_FLUSHING 3
+#define TIS_WAITING 4
+#define TIS_CLEARED 5
+#define TIS_MAX 6
+
+#define TES_DOWN 0
+#define TES_HUNGRY 1
+#define TES_FEEDING 2
+#define TES_READY 3
+#define TES_STOP 4
+#define TES_MAX 5
+
+void rt_request_import(rtable *tab, struct rt_import_request *req);
+void rt_request_export(rtable *tab, struct rt_export_request *req);
+
+void rt_stop_import(struct rt_import_request *, struct event *stopped);
+void rt_stop_export(struct rt_export_request *, void (*stopped)(struct rt_export_request *));
+
+const char *rt_import_state_name(u8 state);
+const char *rt_export_state_name(u8 state);
+
+static inline u8 rt_import_get_state(struct rt_import_hook *ih) { return ih ? ih->import_state : TIS_DOWN; }
+static inline u8 rt_export_get_state(struct rt_export_hook *eh) { return eh ? eh->export_state : TES_DOWN; }
+
+void rte_import(struct rt_import_request *req, const net_addr *net, rte *new, struct rte_src *src);
+
+/* Get next rpe. If src is given, it must match. */
+struct rt_pending_export *rpe_next(struct rt_pending_export *rpe, struct rte_src *src);
+
+/* Mark the pending export processed */
+void rpe_mark_seen(struct rt_export_hook *hook, struct rt_pending_export *rpe);
+
+/* Get pending export seen status */
+int rpe_get_seen(struct rt_export_hook *hook, struct rt_pending_export *rpe);
/* Types of route announcement, also used as flags */
@@ -307,56 +459,74 @@ static inline int rte_is_filtered(rte *r) { return !!(r->flags & REF_FILTERED);
#define RIC_REJECT -1 /* Rejected by protocol */
#define RIC_DROP -2 /* Silently dropped by protocol */
+#define rte_update channel_rte_import
+/**
+ * rte_update - enter a new update to a routing table
+ * @c: channel doing the update
+ * @net: network address
+ * @rte: a &rte representing the new route
+ * @src: old route source identifier
+ *
+ * This function imports a new route to the appropriate table (via the channel).
+ * Table keys are @net (obligatory) and @rte->attrs->src.
+ * Both the @net and @rte pointers can be local.
+ *
+ * The route attributes (@rte->attrs) are obligatory. They can be also allocated
+ * locally. Anyway, if you use an already-cached attribute object, you shall
+ * call rta_clone() on that object yourself. (This semantics may change in future.)
+ *
+ * If the route attributes are local, you may set @rte->attrs->src to NULL, then
+ * the protocol's default route source will be supplied.
+ *
+ * When rte_update() gets a route, it automatically validates it. This includes
+ * checking for validity of the given network and next hop addresses and also
+ * checking for host-scope or link-scope routes. Then the import filters are
+ * processed and if accepted, the route is passed to route table recalculation.
+ *
+ * The accepted routes are then inserted into the table, replacing the old route
+ * for the same @net identified by @src. Then the route is announced
+ * to all the channels connected to the table using the standard export mechanism.
+ * Setting @rte to NULL makes this a withdraw, otherwise @rte->src must be the same
+ * as @src.
+ *
+ * All memory used for temporary allocations is taken from a special linpool
+ * @rte_update_pool and freed when rte_update() finishes.
+ */
+void rte_update(struct channel *c, const net_addr *net, struct rte *rte, struct rte_src *src);
+
extern list routing_tables;
struct config;
void rt_init(void);
void rt_preconfig(struct config *);
void rt_commit(struct config *new, struct config *old);
-void rt_lock_table(rtable *);
-void rt_unlock_table(rtable *);
+void rt_lock_table(rtable_private *);
+void rt_unlock_table(rtable_private *);
void rt_subscribe(rtable *tab, struct rt_subscription *s);
void rt_unsubscribe(struct rt_subscription *s);
rtable *rt_setup(pool *, struct rtable_config *);
-static inline void rt_shutdown(rtable *r) { rfree(r->rp); }
-static inline net *net_find(rtable *tab, const net_addr *addr) { return (net *) fib_find(&tab->fib, addr); }
-static inline net *net_find_valid(rtable *tab, const net_addr *addr)
-{ net *n = net_find(tab, addr); return (n && rte_is_valid(n->routes)) ? n : NULL; }
-static inline net *net_get(rtable *tab, const net_addr *addr) { return (net *) fib_get(&tab->fib, addr); }
-void *net_route(rtable *tab, const net_addr *n);
+static inline net *net_find(rtable_private *tab, const net_addr *addr) { return (net *) fib_find(&tab->fib, addr); }
+static inline net *net_find_valid(rtable_private *tab, const net_addr *addr)
+{ net *n = net_find(tab, addr); return (n && n->routes && rte_is_valid(&n->routes->rte)) ? n : NULL; }
+static inline net *net_get(rtable_private *tab, const net_addr *addr) { return (net *) fib_get(&tab->fib, addr); }
+void *net_route(rtable_private *tab, const net_addr *n);
int net_roa_check(rtable *tab, const net_addr *n, u32 asn);
-rte *rte_find(net *net, struct rte_src *src);
-rte *rte_get_temp(struct rta *);
-void rte_update2(struct channel *c, const net_addr *n, rte *new, struct rte_src *src);
-/* rte_update() moved to protocol.h to avoid dependency conflicts */
-int rt_examine(rtable *t, net_addr *a, struct proto *p, const struct filter *filter);
-rte *rt_export_merged(struct channel *c, net *net, rte **rt_free, linpool *pool, int silent);
-void rt_refresh_begin(rtable *t, struct channel *c);
-void rt_refresh_end(rtable *t, struct channel *c);
-void rt_modify_stale(rtable *t, struct channel *c);
-void rt_schedule_prune(rtable *t);
-void rte_dump(rte *);
-void rte_free(rte *);
-rte *rte_do_cow(rte *);
-static inline rte * rte_cow(rte *r) { return (r->flags & REF_COW) ? rte_do_cow(r) : r; }
-rte *rte_cow_rta(rte *r, linpool *lp);
-void rte_init_tmp_attrs(struct rte *r, linpool *lp, uint max);
-void rte_make_tmp_attr(struct rte *r, uint id, uint type, uintptr_t val);
-void rte_make_tmp_attrs(struct rte **r, struct linpool *pool, struct rta **old_attrs);
-uintptr_t rte_store_tmp_attr(struct rte *r, uint id);
+int rt_examine(rtable_private *t, net_addr *a, struct channel *c, const struct filter *filter);
+rte *rt_export_merged(struct channel *c, rte ** feed, uint count, linpool *pool, int silent);
+
+void rt_refresh_begin(struct rt_import_request *);
+void rt_refresh_end(struct rt_import_request *);
+void rt_schedule_prune(rtable_private *t);
+void rte_dump(struct rte_storage *);
+void rte_free(struct rte_storage *, rtable_private *);
void rt_dump(rtable *);
void rt_dump_all(void);
-int rt_feed_channel(struct channel *c);
-void rt_feed_channel_abort(struct channel *c);
-int rte_update_in(struct channel *c, const net_addr *n, rte *new, struct rte_src *src);
-int rt_reload_channel(struct channel *c);
-void rt_reload_channel_abort(struct channel *c);
+void rt_dump_hooks(rtable *);
+void rt_dump_hooks_all(void);
void rt_prune_sync(rtable *t, int all);
-int rte_update_out(struct channel *c, const net_addr *n, rte *new, rte *old0, int refeed);
struct rtable_config *rt_new_table(struct symbol *s, uint addr_type);
-
/* Default limit for ECMP next hops, defined in sysdep code */
extern const int rt_default_ecmp;
@@ -379,6 +549,7 @@ struct rt_show_data {
struct channel *export_channel;
struct config *running_on_config;
struct krt_proto *kernel;
+ struct rt_export_hook *kernel_export_hook;
int export_mode, primary_only, filtered, stats, show_for;
int table_open; /* Iteration (fit) is open */
@@ -430,30 +601,29 @@ struct nexthop {
struct rte_src {
struct rte_src *next; /* Hash chain */
- struct proto *proto; /* Protocol the source is based on */
+ struct rte_owner *owner; /* Route source owner */
u32 private_id; /* Private ID, assigned by the protocol */
u32 global_id; /* Globally unique ID of the source */
- unsigned uc; /* Use count */
+ _Atomic u64 uc; /* Use count */
};
typedef struct rta {
- struct rta *next, **pprev; /* Hash chain */
- u32 uc; /* Use count */
+ struct rta * _Atomic next, * _Atomic *pprev; /* Hash chain */
+ _Atomic u32 uc; /* Use count */
u32 hash_key; /* Hash over important fields */
struct ea_list *eattrs; /* Extended Attribute chain */
- struct rte_src *src; /* Route source that created the route */
struct hostentry *hostentry; /* Hostentry for recursive next-hops */
ip_addr from; /* Advertising router */
u32 igp_metric; /* IGP metric to next hop (for iBGP routes) */
- u8 source; /* Route source (RTS_...) */
- u8 scope; /* Route scope (SCOPE_... -- see ip.h) */
- u8 dest; /* Route destination type (RTD_...) */
- u8 aflags;
+ u16 cached:1; /* Are attributes cached? */
+ u16 source:7; /* Route source (RTS_...) */
+ u16 scope:4; /* Route scope (SCOPE_... -- see ip.h) */
+ u16 dest:4; /* Route destination type (RTD_...) */
+ word pref;
struct nexthop nh; /* Next hop */
} rta;
-#define RTS_DUMMY 0 /* Dummy route to be removed soon */
#define RTS_STATIC 1 /* Normal static route */
#define RTS_INHERIT 2 /* Route inherited from kernel */
#define RTS_DEVICE 3 /* Device route */
@@ -471,11 +641,6 @@ typedef struct rta {
#define RTS_PERF 15 /* Perf checker */
#define RTS_MAX 16
-#define RTC_UNICAST 0
-#define RTC_BROADCAST 1
-#define RTC_MULTICAST 2
-#define RTC_ANYCAST 3 /* IPv6 Anycast */
-
#define RTD_NONE 0 /* Undefined next hop */
#define RTD_UNICAST 1 /* Next hop is neighbor router */
#define RTD_BLACKHOLE 2 /* Silently drop packets */
@@ -483,8 +648,6 @@ typedef struct rta {
#define RTD_PROHIBIT 4 /* Administratively prohibited */
#define RTD_MAX 5
-#define RTAF_CACHED 1 /* This is a cached rta */
-
#define IGP_METRIC_UNKNOWN 0x80000000 /* Default igp_metric used when no other
protocol-specific metric is availabe */
@@ -508,8 +671,8 @@ typedef struct eattr {
byte flags; /* Protocol-dependent flags */
byte type; /* Attribute type and several flags (EAF_...) */
union {
- u32 data;
- const struct adata *ptr; /* Attribute data elsewhere */
+ uintptr_t data;
+ const struct adata *ptr; /* Attribute data elsewhere */
} u;
} eattr;
@@ -517,7 +680,6 @@ typedef struct eattr {
#define EA_CODE(proto,id) (((proto) << 8) | (id))
#define EA_ID(ea) ((ea) & 0xff)
#define EA_PROTO(ea) ((ea) >> 8)
-#define EA_ID_FLAG(ea) (1 << EA_ID(ea))
#define EA_CUSTOM(id) ((id) | EA_CUSTOM_BIT)
#define EA_IS_CUSTOM(ea) ((ea) & EA_CUSTOM_BIT)
#define EA_CUSTOM_ID(ea) ((ea) & ~EA_CUSTOM_BIT)
@@ -540,6 +702,7 @@ const char *ea_custom_name(uint ea);
#define EAF_TYPE_AS_PATH 0x06 /* BGP AS path (encoding per RFC 1771:4.3) */
#define EAF_TYPE_BITFIELD 0x09 /* 32-bit embedded bitfield */
#define EAF_TYPE_INT_SET 0x0a /* Set of u32's (e.g., a community list) */
+#define EAF_TYPE_PTR 0x0d /* Pointer to an object */
#define EAF_TYPE_EC_SET 0x0e /* Set of pairs of u32's - ext. community list */
#define EAF_TYPE_LC_SET 0x12 /* Set of triplets of u32's - large community list */
#define EAF_TYPE_UNDEF 0x1f /* `force undefined' entry */
@@ -578,13 +741,52 @@ typedef struct ea_list {
#define EALF_SORTED 1 /* Attributes are sorted by code */
#define EALF_BISECT 2 /* Use interval bisection for searching */
#define EALF_CACHED 4 /* Attributes belonging to cached rta */
-#define EALF_TEMP 8 /* Temporary ea_list added by make_tmp_attrs hooks */
-struct rte_src *rt_find_source(struct proto *p, u32 id);
-struct rte_src *rt_get_source(struct proto *p, u32 id);
-static inline void rt_lock_source(struct rte_src *src) { src->uc++; }
-static inline void rt_unlock_source(struct rte_src *src) { src->uc--; }
-void rt_prune_sources(void);
+struct rte_owner_class {
+ void (*get_route_info)(struct rte *, byte *buf); /* Get route information (for `show route' command) */
+ int (*rte_better)(struct rte *, struct rte *);
+ int (*rte_mergable)(struct rte *, struct rte *);
+ u32 (*rte_igp_metric)(struct rte *);
+};
+
+struct rte_owner {
+ struct rte_owner_class *class;
+ int (*rte_recalculate)(rtable_private *, struct network *, struct rte *, struct rte *, struct rte *);
+ HASH(struct rte_src) hash;
+ const char *name;
+ u32 hash_key;
+ u32 uc;
+ event_list *list;
+ event *prune;
+ event *stop;
+};
+
+#define RTE_SRC_PU_SHIFT 44
+#define RTE_SRC_IN_PROGRESS (1ULL << RTE_SRC_PU_SHIFT)
+
+struct rte_src *rt_get_source_o(struct rte_owner *o, u32 id);
+#define rt_get_source(p, id) rt_get_source_o(&(p)->sources, (id))
+static inline void rt_lock_source(struct rte_src *src)
+{
+ u64 uc = atomic_fetch_add_explicit(&src->uc, 1, memory_order_acq_rel);
+ ASSERT_DIE(uc > 0);
+}
+
+static inline void rt_unlock_source(struct rte_src *src)
+{
+ u64 uc = atomic_fetch_add_explicit(&src->uc, RTE_SRC_IN_PROGRESS, memory_order_acq_rel);
+ u64 pending = uc >> RTE_SRC_PU_SHIFT;
+ uc &= RTE_SRC_IN_PROGRESS - 1;
+
+ ASSERT_DIE(uc > pending);
+ if (uc == pending + 1)
+ ev_send(src->owner->list, src->owner->prune);
+
+ atomic_fetch_sub_explicit(&src->uc, RTE_SRC_IN_PROGRESS + 1, memory_order_acq_rel);
+}
+
+void rt_init_sources(struct rte_owner *, const char *name, event_list *list);
+void rt_destroy_sources(struct rte_owner *, event *);
struct ea_walk_state {
ea_list *eattrs; /* Ccurrent ea_list, initially set by caller */
@@ -594,7 +796,7 @@ struct ea_walk_state {
eattr *ea_find(ea_list *, unsigned ea);
eattr *ea_walk(struct ea_walk_state *s, uint id, uint max);
-int ea_get_int(ea_list *, unsigned ea, int def);
+uintptr_t ea_get_int(ea_list *, unsigned ea, uintptr_t def);
void ea_dump(ea_list *);
void ea_sort(ea_list *); /* Sort entries in all sub-lists */
unsigned ea_scan(ea_list *); /* How many bytes do we need for merged ea_list */
@@ -673,24 +875,47 @@ void rta_init(void);
static inline size_t rta_size(const rta *a) { return sizeof(rta) + sizeof(u32)*a->nh.labels; }
#define RTA_MAX_SIZE (sizeof(rta) + sizeof(u32)*MPLS_MAX_LABEL_STACK)
rta *rta_lookup(rta *); /* Get rta equivalent to this one, uc++ */
-static inline int rta_is_cached(rta *r) { return r->aflags & RTAF_CACHED; }
-static inline rta *rta_clone(rta *r) { r->uc++; return r; }
-void rta__free(rta *r);
-static inline void rta_free(rta *r) { if (r && !--r->uc) rta__free(r); }
+static inline int rta_is_cached(rta *r) { return r->cached; }
+
+static inline rta *rta_clone(rta *r) {
+ u32 uc = atomic_fetch_add_explicit(&r->uc, 1, memory_order_acq_rel);
+ ASSERT_DIE(uc > 0);
+ return r;
+}
+
+#define RTA_OBSOLETE_LIMIT 512
+
+extern _Atomic u32 rta_obsolete_count;
+extern event rta_cleanup_event;
+
+static inline void rta_free(rta *r) {
+ if (!r)
+ return;
+
+ u32 uc = atomic_fetch_sub_explicit(&r->uc, 1, memory_order_acq_rel);
+ if (uc > 1)
+ return;
+
+ u32 obs = atomic_fetch_add_explicit(&rta_obsolete_count, 1, memory_order_acq_rel);
+ if (obs == RTA_OBSOLETE_LIMIT)
+ ev_send(&global_work_list, &rta_cleanup_event);
+}
+
rta *rta_do_cow(rta *o, linpool *lp);
static inline rta * rta_cow(rta *r, linpool *lp) { return rta_is_cached(r) ? rta_do_cow(r, lp) : r; }
-void rta_dump(rta *);
+static inline void rta_uncache(rta *r) { r->cached = 0; r->uc = 0; }
+void rta_dump(const rta *);
void rta_dump_all(void);
-void rta_show(struct cli *, rta *);
+void rta_show(struct cli *, const rta *);
-u32 rt_get_igp_metric(rte *rt);
+u32 rt_get_igp_metric(rte *);
struct hostentry * rt_get_hostentry(rtable *tab, ip_addr a, ip_addr ll, rtable *dep);
-void rta_apply_hostentry(rta *a, struct hostentry *he, mpls_label_stack *mls);
+void rta_apply_hostentry(rta *a, struct hostentry *he, mpls_label_stack *mls, linpool *lp);
static inline void
-rta_set_recursive_next_hop(rtable *dep, rta *a, rtable *tab, ip_addr gw, ip_addr ll, mpls_label_stack *mls)
+rta_set_recursive_next_hop(rtable *dep, rta *a, rtable *tab, ip_addr gw, ip_addr ll, mpls_label_stack *mls, linpool *lp)
{
- rta_apply_hostentry(a, rt_get_hostentry(tab, gw, ll, dep), mls);
+ rta_apply_hostentry(a, rt_get_hostentry(tab, gw, ll, dep), mls, lp);
}
/*
diff --git a/nest/rt-attr.c b/nest/rt-attr.c
index c630aa95..c3da4782 100644
--- a/nest/rt-attr.c
+++ b/nest/rt-attr.c
@@ -54,6 +54,7 @@
#include "lib/hash.h"
#include "lib/idm.h"
#include "lib/resource.h"
+#include "lib/rcu.h"
#include "lib/string.h"
#include <stddef.h>
@@ -61,7 +62,6 @@
const adata null_adata; /* adata of length 0 */
const char * const rta_src_names[RTS_MAX] = {
- [RTS_DUMMY] = "",
[RTS_STATIC] = "static",
[RTS_INHERIT] = "inherit",
[RTS_DEVICE] = "device",
@@ -86,7 +86,13 @@ const char * rta_dest_names[RTD_MAX] = {
[RTD_PROHIBIT] = "prohibited",
};
+static DOMAIN(attrs) src_domain;
+
+#define SRC_LOCK LOCK_DOMAIN(attrs, src_domain)
+#define SRC_UNLOCK UNLOCK_DOMAIN(attrs, src_domain)
+
pool *rta_pool;
+pool *src_pool;
static slab *rta_slab_[4];
static slab *nexthop_slab_[4];
@@ -97,72 +103,151 @@ static struct idm src_ids;
/* rte source hash */
-#define RSH_KEY(n) n->proto, n->private_id
+#define RSH_KEY(n) n->private_id
#define RSH_NEXT(n) n->next
-#define RSH_EQ(p1,n1,p2,n2) p1 == p2 && n1 == n2
-#define RSH_FN(p,n) p->hash_key ^ u32_hash(n)
+#define RSH_EQ(n1,n2) n1 == n2
+#define RSH_FN(n) u32_hash(n)
#define RSH_REHASH rte_src_rehash
#define RSH_PARAMS /2, *2, 1, 1, 8, 20
-#define RSH_INIT_ORDER 6
-
-static HASH(struct rte_src) src_hash;
+#define RSH_INIT_ORDER 2
static void
rte_src_init(void)
{
- rte_src_slab = sl_new(rta_pool, sizeof(struct rte_src));
-
- idm_init(&src_ids, rta_pool, SRC_ID_INIT_SIZE);
+ src_domain = DOMAIN_NEW(attrs, "Route sources");
+ src_pool = rp_new(&root_pool, &main_birdloop, "Route sources");
+ rte_src_slab = sl_new(src_pool, sizeof(struct rte_src));
- HASH_INIT(src_hash, rta_pool, RSH_INIT_ORDER);
+ idm_init(&src_ids, src_pool, SRC_ID_INIT_SIZE);
}
-
HASH_DEFINE_REHASH_FN(RSH, struct rte_src)
-struct rte_src *
-rt_find_source(struct proto *p, u32 id)
+static struct rte_src *
+rt_find_source(struct rte_owner *p, u32 id)
{
- return HASH_FIND(src_hash, RSH, p, id);
+ return HASH_FIND(p->hash, RSH, id);
}
struct rte_src *
-rt_get_source(struct proto *p, u32 id)
+rt_get_source_o(struct rte_owner *p, u32 id)
{
+ if (p->stop)
+ bug("Stopping route owner asked for another source.");
+
struct rte_src *src = rt_find_source(p, id);
if (src)
+ {
+ UNUSED u64 uc = atomic_fetch_add_explicit(&src->uc, 1, memory_order_acq_rel);
return src;
+ }
+ SRC_LOCK;
src = sl_allocz(rte_src_slab);
- src->proto = p;
+ src->owner = p;
src->private_id = id;
src->global_id = idm_alloc(&src_ids);
- src->uc = 0;
- HASH_INSERT2(src_hash, RSH, rta_pool, src);
+ atomic_store_explicit(&src->uc, 1, memory_order_release);
+ p->uc++;
+
+ HASH_INSERT2(p->hash, RSH, src_pool, src);
+ if (config->table_debug)
+ log(L_TRACE "Allocated new rte_src for %s, ID %uL %uG, have %u sources now",
+ p->name, src->private_id, src->global_id, p->uc);
+
+ SRC_UNLOCK;
return src;
}
+static inline void
+rt_done_sources(struct rte_owner *o)
+{
+ if (o->stop->list)
+ ev_send(o->stop->list, o->stop);
+ else
+ ev_send(o->list, o->stop);
+}
+
void
-rt_prune_sources(void)
+rt_prune_sources(void *data)
{
- HASH_WALK_FILTER(src_hash, next, src, sp)
+ struct rte_owner *o = data;
+
+ HASH_WALK_FILTER(o->hash, next, src, sp)
{
- if (src->uc == 0)
+ u64 uc;
+ while ((uc = atomic_load_explicit(&src->uc, memory_order_acquire)) >> RTE_SRC_PU_SHIFT)
+ ;
+
+ if (uc == 0)
{
- HASH_DO_REMOVE(src_hash, RSH, sp);
+ o->uc--;
+
+ HASH_DO_REMOVE(o->hash, RSH, sp);
+
+ SRC_LOCK;
idm_free(&src_ids, src->global_id);
sl_free(rte_src_slab, src);
+ SRC_UNLOCK;
}
}
HASH_WALK_FILTER_END;
- HASH_MAY_RESIZE_DOWN(src_hash, RSH, rta_pool);
+ SRC_LOCK;
+ HASH_MAY_RESIZE_DOWN(o->hash, RSH, src_pool);
+
+ if (o->stop && !o->uc)
+ {
+ rfree(o->prune);
+ SRC_UNLOCK;
+
+ if (config->table_debug)
+ log(L_TRACE "All rte_src's for %s pruned, scheduling stop event", o->name);
+
+ rt_done_sources(o);
+ }
+ else
+ SRC_UNLOCK;
}
+void
+rt_init_sources(struct rte_owner *o, const char *name, event_list *list)
+{
+ SRC_LOCK;
+ HASH_INIT(o->hash, src_pool, RSH_INIT_ORDER);
+ o->hash_key = random_u32();
+ o->uc = 0;
+ o->name = name;
+ o->prune = ev_new_init(src_pool, rt_prune_sources, o);
+ o->stop = NULL;
+ o->list = list;
+ SRC_UNLOCK;
+}
+
+void
+rt_destroy_sources(struct rte_owner *o, event *done)
+{
+ o->stop = done;
+
+ if (!o->uc)
+ {
+ if (config->table_debug)
+ log(L_TRACE "Source owner %s destroy requested. All rte_src's already pruned, scheduling stop event", o->name);
+
+ SRC_LOCK;
+ rfree(o->prune);
+ SRC_UNLOCK;
+
+ rt_done_sources(o);
+ }
+ else
+ if (config->table_debug)
+ log(L_TRACE "Source owner %s destroy requested. Remaining %u rte_src's to prune.", o->name, o->uc);
+}
/*
* Multipath Next Hop
@@ -541,8 +626,8 @@ ea_walk(struct ea_walk_state *s, uint id, uint max)
* by calling ea_find() to find the attribute, extracting its value or returning
* a provided default if no such attribute is present.
*/
-int
-ea_get_int(ea_list *e, unsigned id, int def)
+uintptr_t
+ea_get_int(ea_list *e, unsigned id, uintptr_t def)
{
eattr *a = ea_find(e, id);
if (!a)
@@ -1081,21 +1166,28 @@ ea_append(ea_list *to, ea_list *what)
* rta's
*/
-static uint rta_cache_count;
-static uint rta_cache_size = 32;
-static uint rta_cache_limit;
-static uint rta_cache_mask;
-static rta **rta_hash_table;
+static DOMAIN(attrs) attrs_domain;
-static void
-rta_alloc_hash(void)
+#define RTA_LOCK LOCK_DOMAIN(attrs, attrs_domain)
+#define RTA_UNLOCK UNLOCK_DOMAIN(attrs, attrs_domain)
+
+struct rta_cache {
+ u32 count;
+ u32 size;
+ u32 limit;
+ u32 mask;
+ rta * _Atomic table[0];
+} * _Atomic rta_cache;
+// rta_aux, rta_cache = { .size = ATOMIC_VAR_INIT(32), };
+
+static struct rta_cache *
+rta_alloc_hash(u32 size)
{
- rta_hash_table = mb_allocz(rta_pool, sizeof(rta *) * rta_cache_size);
- if (rta_cache_size < 32768)
- rta_cache_limit = rta_cache_size * 2;
- else
- rta_cache_limit = ~0;
- rta_cache_mask = rta_cache_size - 1;
+ struct rta_cache *c = mb_allocz(rta_pool, sizeof(struct rta_cache) + sizeof(rta * _Atomic) * size);
+ c->size = size;
+ c->limit = (size >> 20) ? (~0U) : (size * 2);
+ c->mask = size - 1;
+ return c;
}
static inline uint
@@ -1104,13 +1196,14 @@ rta_hash(rta *a)
u64 h;
mem_hash_init(&h);
#define MIX(f) mem_hash_mix(&h, &(a->f), sizeof(a->f));
- MIX(src);
+#define BMIX(f) mem_hash_mix_num(&h, a->f);
MIX(hostentry);
MIX(from);
MIX(igp_metric);
- MIX(source);
- MIX(scope);
- MIX(dest);
+ BMIX(source);
+ BMIX(scope);
+ BMIX(dest);
+ MIX(pref);
#undef MIX
return mem_hash_value(&h) ^ nexthop_hash(&(a->nh)) ^ ea_hash(a->eattrs);
@@ -1119,8 +1212,7 @@ rta_hash(rta *a)
static inline int
rta_same(rta *x, rta *y)
{
- return (x->src == y->src &&
- x->source == y->source &&
+ return (x->source == y->source &&
x->scope == y->scope &&
x->dest == y->dest &&
x->igp_metric == y->igp_metric &&
@@ -1149,34 +1241,88 @@ rta_copy(rta *o)
}
static inline void
-rta_insert(rta *r)
+rta_insert(rta *r, struct rta_cache *c)
{
- uint h = r->hash_key & rta_cache_mask;
- r->next = rta_hash_table[h];
- if (r->next)
- r->next->pprev = &r->next;
- r->pprev = &rta_hash_table[h];
- rta_hash_table[h] = r;
+ uint h = r->hash_key & c->mask;
+ rta *next = atomic_load_explicit(&c->table[h], memory_order_relaxed);
+
+ atomic_store_explicit(&r->next, next, memory_order_relaxed);
+ r->pprev = &c->table[h];
+
+ if (next)
+ next->pprev = &r->next;
+
+ /* This store MUST be the last and MUST have release order for thread-safety */
+ atomic_store_explicit(&c->table[h], r, memory_order_release);
}
static void
-rta_rehash(void)
+rta_rehash(struct rta_cache *c)
{
- uint ohs = rta_cache_size;
- uint h;
- rta *r, *n;
- rta **oht = rta_hash_table;
-
- rta_cache_size = 2*rta_cache_size;
- DBG("Rehashing rta cache from %d to %d entries.\n", ohs, rta_cache_size);
- rta_alloc_hash();
- for(h=0; h<ohs; h++)
- for(r=oht[h]; r; r=n)
+ u32 os = c->size;
+
+ struct rta_cache *nc = rta_alloc_hash(os * 2);
+ nc->count = c->count;
+
+ /* First we simply copy every chain to both new locations */
+ for (u32 h = 0; h < os; h++)
+ {
+ rta *r = atomic_load_explicit(&c->table[h], memory_order_relaxed);
+ atomic_store_explicit(&nc->table[h], r, memory_order_relaxed);
+ atomic_store_explicit(&nc->table[h + os], r, memory_order_relaxed);
+ }
+
+ /* Then we exchange the hashes; release semantics forces the previous code to be already done */
+ atomic_store_explicit(&rta_cache, nc, memory_order_release);
+
+ /* And now we pass through both chains and filter them */
+ for (u32 h = 0; h < c->size; h++)
+ {
+ rta * _Atomic * ap = &nc->table[h];
+ rta * _Atomic * bp = &nc->table[h + os];
+
+ rta *r = atomic_load_explicit(ap, memory_order_relaxed);
+ ASSERT_DIE(r == atomic_load_explicit(bp, memory_order_relaxed));
+
+ while (r)
+ {
+ if (r->hash_key & os)
{
- n = r->next;
- rta_insert(r);
+ r->pprev = bp;
+ atomic_store_explicit(bp, r, memory_order_release);
+ bp = &r->next;
}
- mb_free(oht);
+ else
+ {
+ r->pprev = ap;
+ atomic_store_explicit(ap, r, memory_order_release);
+ ap = &r->next;
+ }
+
+ r = atomic_load_explicit(&r->next, memory_order_acquire);
+ }
+
+ atomic_store_explicit(ap, NULL, memory_order_release);
+ atomic_store_explicit(bp, NULL, memory_order_release);
+ }
+
+ synchronize_rcu();
+ mb_free(c);
+}
+
+static rta *
+rta_find(rta *o, u32 h, struct rta_cache *c)
+{
+ rta *r = NULL;
+
+ for (r = atomic_load_explicit(&c->table[h & c->mask], memory_order_acquire); r; r = atomic_load_explicit(&r->next, memory_order_acquire))
+ if (r->hash_key == h && rta_same(r, o))
+ {
+ atomic_fetch_add_explicit(&r->uc, 1, memory_order_acq_rel);
+ return r;
+ }
+
+ return NULL;
}
/**
@@ -1198,45 +1344,117 @@ rta_lookup(rta *o)
rta *r;
uint h;
- ASSERT(!(o->aflags & RTAF_CACHED));
+ ASSERT(!o->cached);
if (o->eattrs)
ea_normalize(o->eattrs);
h = rta_hash(o);
- for(r=rta_hash_table[h & rta_cache_mask]; r; r=r->next)
- if (r->hash_key == h && rta_same(r, o))
- return rta_clone(r);
+ /* Lockless lookup */
+ rcu_read_lock();
+ r = rta_find(o, h, atomic_load_explicit(&rta_cache, memory_order_acquire));
+ rcu_read_unlock();
+
+ if (r)
+ return r;
+
+ RTA_LOCK;
+
+ /* Locked lookup to avoid duplicates if possible */
+ struct rta_cache *c = atomic_load_explicit(&rta_cache, memory_order_acquire);
+ r = rta_find(o, h, c);
+ if (r)
+ {
+ RTA_UNLOCK;
+ return r;
+ }
+
+ /* Store the rta */
r = rta_copy(o);
r->hash_key = h;
- r->aflags = RTAF_CACHED;
- rt_lock_source(r->src);
+ r->cached = 1;
rt_lock_hostentry(r->hostentry);
- rta_insert(r);
+ rta_insert(r, c);
- if (++rta_cache_count > rta_cache_limit)
- rta_rehash();
+ if (++c->count > c->limit)
+ rta_rehash(c);
+ RTA_UNLOCK;
return r;
}
-void
-rta__free(rta *a)
+static void
+rta_cleanup(void *data UNUSED)
{
- ASSERT(rta_cache_count && (a->aflags & RTAF_CACHED));
- rta_cache_count--;
- *a->pprev = a->next;
- if (a->next)
- a->next->pprev = a->pprev;
- rt_unlock_hostentry(a->hostentry);
- rt_unlock_source(a->src);
- if (a->nh.next)
- nexthop_free(a->nh.next);
- ea_free(a->eattrs);
- a->aflags = 0; /* Poison the entry */
- sl_free(rta_slab(a), a);
+ u32 count = 0;
+ rta *ax[RTA_OBSOLETE_LIMIT];
+
+ RTA_LOCK;
+ struct rta_cache *c = atomic_load_explicit(&rta_cache, memory_order_acquire);
+
+ for(u32 h=0; h<c->size; h++)
+ for(rta *a = atomic_load_explicit(&c->table[h], memory_order_acquire), *next;
+ a;
+ a = next)
+ {
+ next = atomic_load_explicit(&a->next, memory_order_acquire);
+ if (atomic_load_explicit(&a->uc, memory_order_acquire) > 0)
+ continue;
+
+ /* Check if the cleanup fits in the buffer */
+ if (count == RTA_OBSOLETE_LIMIT)
+ {
+ ev_send(&global_work_list, &rta_cleanup_event);
+ goto wait;
+ }
+
+ /* Relink the forward pointer */
+ atomic_store_explicit(a->pprev, next, memory_order_release);
+
+ /* Relink the backwards pointer */
+ if (next)
+ next->pprev = a->pprev;
+
+ /* Store for freeing and go to the next */
+ ax[count++] = a;
+ a = next;
+ }
+
+wait:
+ /* Wait until nobody knows about us */
+ synchronize_rcu();
+
+ u32 freed = 0;
+
+ for (u32 i=0; i<count; i++)
+ {
+ rta *a = ax[i];
+ /* Acquired inbetween, relink back */
+ if (atomic_load_explicit(&a->uc, memory_order_acquire))
+ {
+ rta_insert(a, c);
+ continue;
+ }
+
+ /* Cleared to free the memory */
+ rt_unlock_hostentry(a->hostentry);
+ if (a->nh.next)
+ nexthop_free(a->nh.next);
+ ea_free(a->eattrs);
+ a->cached = 0;
+ c->count--;
+ sl_free(rta_slab(a), a);
+ freed++;
+ }
+
+ atomic_fetch_sub_explicit(&rta_obsolete_count, freed, memory_order_release);
+
+ RTA_UNLOCK;
}
+_Atomic u32 rta_obsolete_count;
+event rta_cleanup_event = { .hook = rta_cleanup, .list = &global_work_list };
+
rta *
rta_do_cow(rta *o, linpool *lp)
{
@@ -1248,8 +1466,7 @@ rta_do_cow(rta *o, linpool *lp)
memcpy(*nhn, nho, nexthop_size(nho));
nhn = &((*nhn)->next);
}
- r->aflags = 0;
- r->uc = 0;
+ rta_uncache(r);
return r;
}
@@ -1260,22 +1477,22 @@ rta_do_cow(rta *o, linpool *lp)
* This function takes a &rta and dumps its contents to the debug output.
*/
void
-rta_dump(rta *a)
+rta_dump(const rta *a)
{
- static char *rts[] = { "RTS_DUMMY", "RTS_STATIC", "RTS_INHERIT", "RTS_DEVICE",
+ static char *rts[] = { "", "RTS_STATIC", "RTS_INHERIT", "RTS_DEVICE",
"RTS_STAT_DEV", "RTS_REDIR", "RTS_RIP",
"RTS_OSPF", "RTS_OSPF_IA", "RTS_OSPF_EXT1",
"RTS_OSPF_EXT2", "RTS_BGP", "RTS_PIPE", "RTS_BABEL" };
static char *rtd[] = { "", " DEV", " HOLE", " UNREACH", " PROHIBIT" };
- debug("p=%s uc=%d %s %s%s h=%04x",
- a->src->proto->name, a->uc, rts[a->source], ip_scope_text(a->scope),
+ debug("pref=%d uc=%d %s %s%s h=%04x",
+ a->pref, a->uc, rts[a->source], ip_scope_text(a->scope),
rtd[a->dest], a->hash_key);
- if (!(a->aflags & RTAF_CACHED))
+ if (!a->cached)
debug(" !CACHED");
debug(" <-%I", a->from);
if (a->dest == RTD_UNICAST)
- for (struct nexthop *nh = &(a->nh); nh; nh = nh->next)
+ for (const struct nexthop *nh = &(a->nh); nh; nh = nh->next)
{
if (ipa_nonzero(nh->gw)) debug(" ->%I", nh->gw);
if (nh->labels) debug(" L %d", nh->label[0]);
@@ -1302,19 +1519,27 @@ rta_dump_all(void)
rta *a;
uint h;
- debug("Route attribute cache (%d entries, rehash at %d):\n", rta_cache_count, rta_cache_limit);
- for(h=0; h<rta_cache_size; h++)
- for(a=rta_hash_table[h]; a; a=a->next)
+ RTA_LOCK;
+
+ struct rta_cache *c = atomic_load_explicit(&rta_cache, memory_order_acquire);
+
+ debug("Route attribute cache (%d entries, rehash at %d):\n", c->count, c->limit);
+ for(h=0; h<c->size; h++)
+ for(a = atomic_load_explicit(&c->table[h], memory_order_acquire);
+ a;
+ a = atomic_load_explicit(&a->next, memory_order_acquire))
{
debug("%p ", a);
rta_dump(a);
debug("\n");
}
debug("\n");
+
+ RTA_UNLOCK;
}
void
-rta_show(struct cli *c, rta *a)
+rta_show(struct cli *c, const rta *a)
{
cli_printf(c, -1008, "\tType: %s %s", rta_src_names[a->source], ip_scope_text(a->scope));
@@ -1332,7 +1557,9 @@ rta_show(struct cli *c, rta *a)
void
rta_init(void)
{
- rta_pool = rp_new(&root_pool, "Attributes");
+ attrs_domain = DOMAIN_NEW(attrs, "Attributes");
+
+ rta_pool = rp_new(&root_pool, &main_birdloop, "Attributes");
rta_slab_[0] = sl_new(rta_pool, sizeof(rta));
rta_slab_[1] = sl_new(rta_pool, sizeof(rta) + sizeof(u32));
@@ -1344,7 +1571,7 @@ rta_init(void)
nexthop_slab_[2] = sl_new(rta_pool, sizeof(struct nexthop) + sizeof(u32)*2);
nexthop_slab_[3] = sl_new(rta_pool, sizeof(struct nexthop) + sizeof(u32)*MPLS_MAX_LABEL_STACK);
- rta_alloc_hash();
+ atomic_store_explicit(&rta_cache, rta_alloc_hash(32), memory_order_relaxed);
rte_src_init();
}
diff --git a/nest/rt-dev.c b/nest/rt-dev.c
index 61f025ce..c1251675 100644
--- a/nest/rt-dev.c
+++ b/nest/rt-dev.c
@@ -67,13 +67,11 @@ dev_ifa_notify(struct proto *P, uint flags, struct ifa *ad)
/* Use iface ID as local source ID */
struct rte_src *src = rt_get_source(P, ad->iface->index);
- rte_update2(c, net, NULL, src);
+ rte_update(c, net, NULL, src);
+ rt_unlock_source(src);
}
else if (flags & IF_CHANGE_UP)
{
- rta *a;
- rte *e;
-
DBG("dev_if_notify: %s:%I going up\n", ad->iface->name, ad->ip);
if (cf->check_link && !(ad->iface->flags & IF_LINK_UP))
@@ -83,17 +81,20 @@ dev_ifa_notify(struct proto *P, uint flags, struct ifa *ad)
struct rte_src *src = rt_get_source(P, ad->iface->index);
rta a0 = {
- .src = src,
+ .pref = c->preference,
.source = RTS_DEVICE,
.scope = SCOPE_UNIVERSE,
.dest = RTD_UNICAST,
.nh.iface = ad->iface,
};
- a = rta_lookup(&a0);
- e = rte_get_temp(a);
- e->pflags = 0;
- rte_update2(c, net, e, src);
+ rte e0 = {
+ .attrs = rta_lookup(&a0),
+ .src = src,
+ };
+
+ rte_update(c, net, &e0, src);
+ rt_unlock_source(src);
}
}
diff --git a/nest/rt-show.c b/nest/rt-show.c
index 7691878d..65b59af4 100644
--- a/nest/rt-show.c
+++ b/nest/rt-show.c
@@ -56,17 +56,17 @@ rt_show_rte(struct cli *c, byte *ia, rte *e, struct rt_show_data *d, int primary
if (d->verbose && !rta_is_cached(a) && a->eattrs)
ea_normalize(a->eattrs);
- get_route_info = a->src->proto->proto->get_route_info;
+ get_route_info = e->src->owner->class ? e->src->owner->class->get_route_info : NULL;
if (get_route_info)
get_route_info(e, info);
else
- bsprintf(info, " (%d)", e->pref);
+ bsprintf(info, " (%d)", a->pref);
if (d->last_table != d->tab)
rt_show_table(c, d);
cli_printf(c, -1007, "%-20s %s [%s %s%s]%s%s", ia, rta_dest_name(a->dest),
- a->src->proto->name, tm, from, primary ? (sync_error ? " !" : " *") : "", info);
+ e->src->owner->name, tm, from, primary ? (sync_error ? " !" : " *") : "", info);
if (a->dest == RTD_UNICAST)
for (nh = &(a->nh); nh; nh = nh->next)
@@ -95,13 +95,38 @@ rt_show_rte(struct cli *c, byte *ia, rte *e, struct rt_show_data *d, int primary
}
if (d->verbose)
+ {
+ cli_printf(c, -1008, "\tInternal route ID: %uL %uG %uS", e->src->private_id, e->src->global_id, e->stale_cycle);
rta_show(c, a);
+ }
+}
+
+static uint
+rte_feed_count(net *n)
+{
+ uint count = 0;
+ for (struct rte_storage *e = n->routes; e; e = e->next)
+ if (rte_is_valid(RTES_OR_NULL(e)))
+ count++;
+ return count;
+}
+
+static void
+rte_feed_obtain(net *n, rte **feed, uint count)
+{
+ uint i = 0;
+ for (struct rte_storage *e = n->routes; e; e = e->next)
+ if (rte_is_valid(RTES_OR_NULL(e)))
+ {
+ ASSERT_DIE(i < count);
+ feed[i++] = &e->rte;
+ }
+ ASSERT_DIE(i == count);
}
static void
rt_show_net(struct cli *c, net *n, struct rt_show_data *d)
{
- rte *e, *ee;
byte ia[NET_MAX_TEXT_LENGTH+1];
struct channel *ec = d->tab->export_channel;
@@ -114,9 +139,9 @@ rt_show_net(struct cli *c, net *n, struct rt_show_data *d)
bsnprintf(ia, sizeof(ia), "%N", n->n.addr);
- for (e = n->routes; e; e = e->next)
+ for (struct rte_storage *er = n->routes; er; er = er->next)
{
- if (rte_is_filtered(e) != d->filtered)
+ if (rte_is_filtered(&er->rte) != d->filtered)
continue;
d->rt_counter++;
@@ -126,16 +151,15 @@ rt_show_net(struct cli *c, net *n, struct rt_show_data *d)
if (pass)
continue;
- ee = e;
- rte_make_tmp_attrs(&e, c->show_pool, NULL);
+ struct rte e = er->rte;
/* Export channel is down, do not try to export routes to it */
- if (ec && (ec->export_state == ES_DOWN))
+ if (ec && !ec->out_req.hook)
goto skip;
if (d->export_mode == RSEM_EXPORTED)
{
- if (!bmap_test(&ec->export_map, ee->id))
+ if (!bmap_test(&ec->export_map, e.id))
goto skip;
// if (ec->ra_mode != RA_ANY)
@@ -144,17 +168,24 @@ rt_show_net(struct cli *c, net *n, struct rt_show_data *d)
else if ((d->export_mode == RSEM_EXPORT) && (ec->ra_mode == RA_MERGED))
{
/* Special case for merged export */
- rte *rt_free;
- e = rt_export_merged(ec, n, &rt_free, c->show_pool, 1);
pass = 1;
+ uint count = rte_feed_count(n);
+ if (!count)
+ goto skip;
- if (!e)
- { e = ee; goto skip; }
+ rte **feed = alloca(count * sizeof(rte *));
+ rte_feed_obtain(n, feed, count);
+ rte *em = rt_export_merged(ec, feed, count, c->show_pool, 1);
+
+ if (em)
+ e = *em;
+ else
+ goto skip;
}
else if (d->export_mode)
{
struct proto *ep = ec->proto;
- int ic = ep->preexport ? ep->preexport(ep, &e, c->show_pool) : 0;
+ int ic = ep->preexport ? ep->preexport(ec, &e) : 0;
if (ec->ra_mode == RA_OPTIMAL || ec->ra_mode == RA_MERGED)
pass = 1;
@@ -180,24 +211,19 @@ rt_show_net(struct cli *c, net *n, struct rt_show_data *d)
}
}
- if (d->show_protocol && (d->show_protocol != e->attrs->src->proto))
+ if (d->show_protocol && (&d->show_protocol->sources != e.src->owner))
goto skip;
if (f_run(d->filter, &e, c->show_pool, 0) > F_ACCEPT)
goto skip;
if (d->stats < 2)
- rt_show_rte(c, ia, e, d, (e->net->routes == ee));
+ rt_show_rte(c, ia, &e, d, (n->routes == er));
d->show_counter++;
ia[0] = 0;
skip:
- if (e != ee)
- {
- rte_free(e);
- e = ee;
- }
lp_flush(c->show_pool);
if (d->primary_only)
@@ -213,11 +239,13 @@ rt_show_cleanup(struct cli *c)
/* Unlink the iterator */
if (d->table_open)
- fit_get(&d->tab->table->fib, &d->fit);
+ RT_LOCKED(d->tab->table, t)
+ fit_get(&t->fib, &d->fit);
/* Unlock referenced tables */
WALK_LIST(tab, d->tables)
- rt_unlock_table(tab->table);
+ RT_LOCKED(tab->table, t)
+ rt_unlock_table(t);
}
static void
@@ -229,8 +257,6 @@ rt_show_cont(struct cli *c)
#else
unsigned max = 64;
#endif
- struct fib *fib = &d->tab->table->fib;
- struct fib_iterator *it = &d->fit;
if (d->running_on_config && (d->running_on_config != config))
{
@@ -238,9 +264,14 @@ rt_show_cont(struct cli *c)
goto done;
}
+ rtable_private *t = RT_LOCK(d->tab->table);
+
+ struct fib *fib = &t->fib;
+ struct fib_iterator *it = &d->fit;
+
if (!d->table_open)
{
- FIB_ITERATE_INIT(&d->fit, &d->tab->table->fib);
+ FIB_ITERATE_INIT(&d->fit, fib);
d->table_open = 1;
d->table_counter++;
d->kernel = rt_show_get_kernel(d);
@@ -258,6 +289,7 @@ rt_show_cont(struct cli *c)
if (!max--)
{
FIB_ITERATE_PUT(it);
+ RT_UNLOCK(d->tab->table);
return;
}
rt_show_net(c, n, d);
@@ -274,6 +306,8 @@ rt_show_cont(struct cli *c)
d->net_counter - d->net_counter_last, d->tab->table->name);
}
+ RT_UNLOCK(d->tab->table);
+
d->kernel = NULL;
d->table_open = 0;
d->tab = NODE_NEXT(d->tab);
@@ -322,7 +356,7 @@ rt_show_get_default_tables(struct rt_show_data *d)
{
WALK_LIST(c, d->export_protocol->channels)
{
- if (c->export_state == ES_DOWN)
+ if (!c->out_req.hook)
continue;
tab = rt_show_add_table(d, c->table);
@@ -339,7 +373,7 @@ rt_show_get_default_tables(struct rt_show_data *d)
}
for (int i=1; i<NET_MAX; i++)
- if (config->def_tables[i])
+ if (config->def_tables[i] && config->def_tables[i]->table)
rt_show_add_table(d, config->def_tables[i]->table);
}
@@ -405,7 +439,8 @@ rt_show(struct rt_show_data *d)
if (!d->addr)
{
WALK_LIST(tab, d->tables)
- rt_lock_table(tab->table);
+ RT_LOCKED(tab->table, t)
+ rt_lock_table(t);
/* There is at least one table */
d->tab = HEAD(d->tables);
@@ -420,13 +455,17 @@ rt_show(struct rt_show_data *d)
d->tab = tab;
d->kernel = rt_show_get_kernel(d);
+ RT_LOCK(tab->table);
+
if (d->show_for)
- n = net_route(tab->table, d->addr);
+ n = net_route(RT_PRIV(tab->table), d->addr);
else
- n = net_find(tab->table, d->addr);
+ n = net_find(RT_PRIV(tab->table), d->addr);
if (n)
rt_show_net(this_cli, n, d);
+
+ RT_UNLOCK(tab->table);
}
if (d->rt_counter)
diff --git a/nest/rt-table.c b/nest/rt-table.c
index 390b3277..5f1e1679 100644
--- a/nest/rt-table.c
+++ b/nest/rt-table.c
@@ -45,28 +45,85 @@
#include "lib/string.h"
#include "lib/alloca.h"
-#ifdef CONFIG_BGP
-#include "proto/bgp/bgp.h"
-#endif
+#include <stdatomic.h>
pool *rt_table_pool;
-static slab *rte_slab;
-static linpool *rte_update_pool;
-
list routing_tables;
-static void rt_free_hostcache(rtable *tab);
-static void rt_notify_hostcache(rtable *tab, net *net);
-static void rt_update_hostcache(rtable *tab);
-static void rt_next_hop_update(rtable *tab);
-static inline void rt_prune_table(rtable *tab);
-static inline void rt_schedule_notify(rtable *tab);
+/* Data structures for export journal */
+#define RT_PENDING_EXPORT_ITEMS (page_size - sizeof(struct rt_export_block)) / sizeof(struct rt_pending_export)
+struct rt_export_block {
+ node n;
+ _Atomic u32 end;
+ _Atomic _Bool not_last;
+ struct rt_pending_export export[];
+};
+
+static void rt_free_hostcache(rtable_private *tab);
+static void rt_notify_hostcache(rtable_private *tab, net *net);
+static void rt_update_hostcache(void *tab);
+static void rt_next_hop_update(void *tab);
+static inline void rt_prune_table(void *tab);
+static void rt_fast_prune_check(rtable_private *tab);
+static inline void rt_schedule_notify(rtable_private *tab);
+static void rt_feed_channel(void *);
+
+static inline void rt_export_used(rtable_private *tab);
+static void rt_export_cleanup(void *tab);
+
+const char *rt_import_state_name_array[TIS_MAX] = {
+ [TIS_DOWN] = "DOWN",
+ [TIS_UP] = "UP",
+ [TIS_STOP] = "STOP",
+ [TIS_FLUSHING] = "FLUSHING",
+ [TIS_WAITING] = "WAITING",
+ [TIS_CLEARED] = "CLEARED",
+};
+
+const char *rt_export_state_name_array[TES_MAX] = {
+ [TES_DOWN] = "DOWN",
+ [TES_HUNGRY] = "HUNGRY",
+ [TES_FEEDING] = "FEEDING",
+ [TES_READY] = "READY",
+ [TES_STOP] = "STOP"
+};
+
+const char *rt_import_state_name(u8 state)
+{
+ if (state >= TIS_MAX)
+ return "!! INVALID !!";
+ else
+ return rt_import_state_name_array[state];
+}
+
+const char *rt_export_state_name(u8 state)
+{
+ if (state >= TES_MAX)
+ return "!! INVALID !!";
+ else
+ return rt_export_state_name_array[state];
+}
+
+struct event_cork rt_cork;
+
+static inline void
+rte_update_lock(struct channel *c)
+{
+ c->rte_update_nest_cnt++;
+}
+
+static inline void
+rte_update_unlock(struct channel *c)
+{
+ if (!--c->rte_update_nest_cnt)
+ lp_flush(c->rte_update_pool);
+}
/* Like fib_route(), but skips empty net entries */
static inline void *
-net_route_ip4(rtable *t, net_addr_ip4 *n)
+net_route_ip4(rtable_private *t, net_addr_ip4 *n)
{
net *r;
@@ -80,7 +137,7 @@ net_route_ip4(rtable *t, net_addr_ip4 *n)
}
static inline void *
-net_route_ip6(rtable *t, net_addr_ip6 *n)
+net_route_ip6(rtable_private *t, net_addr_ip6 *n)
{
net *r;
@@ -94,7 +151,7 @@ net_route_ip6(rtable *t, net_addr_ip6 *n)
}
static inline void *
-net_route_ip6_sadr(rtable *t, net_addr_ip6_sadr *n)
+net_route_ip6_sadr(rtable_private *t, net_addr_ip6_sadr *n)
{
struct fib_node *fn;
@@ -133,7 +190,7 @@ net_route_ip6_sadr(rtable *t, net_addr_ip6_sadr *n)
}
void *
-net_route(rtable *tab, const net_addr *n)
+net_route(rtable_private *tab, const net_addr *n)
{
ASSERT(tab->addr_type == n->type);
@@ -162,12 +219,15 @@ net_route(rtable *tab, const net_addr *n)
static int
-net_roa_check_ip4(rtable *tab, const net_addr_ip4 *px, u32 asn)
+net_roa_check_ip4(rtable *t, const net_addr_ip4 *px, u32 asn)
{
struct net_addr_roa4 n = NET_ADDR_ROA4(px->prefix, px->pxlen, 0, 0);
struct fib_node *fn;
int anything = 0;
+ RT_LOCK(t);
+ rtable_private *tab = RT_PRIV(t);
+
while (1)
{
for (fn = fib_get_chain(&tab->fib, (net_addr *) &n); fn; fn = fn->next)
@@ -175,11 +235,14 @@ net_roa_check_ip4(rtable *tab, const net_addr_ip4 *px, u32 asn)
net_addr_roa4 *roa = (void *) fn->addr;
net *r = fib_node_to_user(&tab->fib, fn);
- if (net_equal_prefix_roa4(roa, &n) && rte_is_valid(r->routes))
+ if (net_equal_prefix_roa4(roa, &n) && r->routes && rte_is_valid(&r->routes->rte))
{
anything = 1;
if (asn && (roa->asn == asn) && (roa->max_pxlen >= px->pxlen))
+ {
+ RT_UNLOCK(tab);
return ROA_VALID;
+ }
}
}
@@ -190,16 +253,20 @@ net_roa_check_ip4(rtable *tab, const net_addr_ip4 *px, u32 asn)
ip4_clrbit(&n.prefix, n.pxlen);
}
+ RT_UNLOCK(tab);
return anything ? ROA_INVALID : ROA_UNKNOWN;
}
static int
-net_roa_check_ip6(rtable *tab, const net_addr_ip6 *px, u32 asn)
+net_roa_check_ip6(rtable *t, const net_addr_ip6 *px, u32 asn)
{
struct net_addr_roa6 n = NET_ADDR_ROA6(px->prefix, px->pxlen, 0, 0);
struct fib_node *fn;
int anything = 0;
+ RT_LOCK(t);
+ rtable_private *tab = RT_PRIV(t);
+
while (1)
{
for (fn = fib_get_chain(&tab->fib, (net_addr *) &n); fn; fn = fn->next)
@@ -207,11 +274,14 @@ net_roa_check_ip6(rtable *tab, const net_addr_ip6 *px, u32 asn)
net_addr_roa6 *roa = (void *) fn->addr;
net *r = fib_node_to_user(&tab->fib, fn);
- if (net_equal_prefix_roa6(roa, &n) && rte_is_valid(r->routes))
+ if (net_equal_prefix_roa6(roa, &n) && r->routes && rte_is_valid(&r->routes->rte))
{
anything = 1;
if (asn && (roa->asn == asn) && (roa->max_pxlen >= px->pxlen))
+ {
+ RT_UNLOCK(tab);
return ROA_VALID;
+ }
}
}
@@ -222,6 +292,7 @@ net_roa_check_ip6(rtable *tab, const net_addr_ip6 *px, u32 asn)
ip6_clrbit(&n.prefix, n.pxlen);
}
+ RT_UNLOCK(tab);
return anything ? ROA_INVALID : ROA_UNKNOWN;
}
@@ -256,253 +327,50 @@ net_roa_check(rtable *tab, const net_addr *n, u32 asn)
* @net: network node
* @src: route source
*
- * The rte_find() function returns a route for destination @net
- * which is from route source @src.
+ * The rte_find() function returns a pointer to a route for destination @net
+ * which is from route source @src. List end pointer is returned if no route is found.
*/
-rte *
+static struct rte_storage **
rte_find(net *net, struct rte_src *src)
{
- rte *e = net->routes;
-
- while (e && e->attrs->src != src)
- e = e->next;
- return e;
-}
-
-/**
- * rte_get_temp - get a temporary &rte
- * @a: attributes to assign to the new route (a &rta; in case it's
- * un-cached, rte_update() will create a cached copy automatically)
- *
- * Create a temporary &rte and bind it with the attributes @a.
- * Also set route preference to the default preference set for
- * the protocol.
- */
-rte *
-rte_get_temp(rta *a)
-{
- rte *e = sl_alloc(rte_slab);
+ struct rte_storage **e = &net->routes;
- e->attrs = a;
- e->id = 0;
- e->flags = 0;
- e->pref = 0;
- return e;
-}
+ while ((*e) && (*e)->rte.src != src)
+ e = &(*e)->next;
-rte *
-rte_do_cow(rte *r)
-{
- rte *e = sl_alloc(rte_slab);
-
- memcpy(e, r, sizeof(rte));
- e->attrs = rta_clone(r->attrs);
- e->flags = 0;
return e;
}
-/**
- * rte_cow_rta - get a private writable copy of &rte with writable &rta
- * @r: a route entry to be copied
- * @lp: a linpool from which to allocate &rta
- *
- * rte_cow_rta() takes a &rte and prepares it and associated &rta for
- * modification. There are three possibilities: First, both &rte and &rta are
- * private copies, in that case they are returned unchanged. Second, &rte is
- * private copy, but &rta is cached, in that case &rta is duplicated using
- * rta_do_cow(). Third, both &rte is shared and &rta is cached, in that case
- * both structures are duplicated by rte_do_cow() and rta_do_cow().
- *
- * Note that in the second case, cached &rta loses one reference, while private
- * copy created by rta_do_cow() is a shallow copy sharing indirect data (eattrs,
- * nexthops, ...) with it. To work properly, original shared &rta should have
- * another reference during the life of created private copy.
- *
- * Result: a pointer to the new writable &rte with writable &rta.
- */
-rte *
-rte_cow_rta(rte *r, linpool *lp)
-{
- if (!rta_is_cached(r->attrs))
- return r;
-
- r = rte_cow(r);
- rta *a = rta_do_cow(r->attrs, lp);
- rta_free(r->attrs);
- r->attrs = a;
- return r;
-}
-
-/**
- * rte_init_tmp_attrs - initialize temporary ea_list for route
- * @r: route entry to be modified
- * @lp: linpool from which to allocate attributes
- * @max: maximum number of added temporary attribus
- *
- * This function is supposed to be called from make_tmp_attrs() and
- * store_tmp_attrs() hooks before rte_make_tmp_attr() / rte_store_tmp_attr()
- * functions. It allocates &ea_list with length for @max items for temporary
- * attributes and puts it on top of eattrs stack.
- */
-void
-rte_init_tmp_attrs(rte *r, linpool *lp, uint max)
+static struct rte_storage *
+rte_store(const rte *r, net *net, rtable_private *tab)
{
- struct ea_list *e = lp_alloc(lp, sizeof(struct ea_list) + max * sizeof(eattr));
+ struct rte_storage *e = sl_alloc(tab->rte_slab);
- e->next = r->attrs->eattrs;
- e->flags = EALF_SORTED | EALF_TEMP;
- e->count = 0;
+ e->rte = *r;
+ e->rte.net = net->n.addr;
- r->attrs->eattrs = e;
-}
+ rt_lock_source(e->rte.src);
-/**
- * rte_make_tmp_attr - make temporary eattr from private route fields
- * @r: route entry to be modified
- * @id: attribute ID
- * @type: attribute type
- * @val: attribute value (u32 or adata ptr)
- *
- * This function is supposed to be called from make_tmp_attrs() hook for
- * each temporary attribute, after temporary &ea_list was initialized by
- * rte_init_tmp_attrs(). It checks whether temporary attribute is supposed to
- * be defined (based on route pflags) and if so then it fills &eattr field in
- * preallocated temporary &ea_list on top of route @r eattrs stack.
- *
- * Note that it may require free &eattr in temporary &ea_list, so it must not be
- * called more times than @max argument of rte_init_tmp_attrs().
- */
-void
-rte_make_tmp_attr(rte *r, uint id, uint type, uintptr_t val)
-{
- if (r->pflags & EA_ID_FLAG(id))
- {
- ea_list *e = r->attrs->eattrs;
- eattr *a = &e->attrs[e->count++];
- a->id = id;
- a->type = type;
- a->flags = 0;
-
- if (type & EAF_EMBEDDED)
- a->u.data = (u32) val;
- else
- a->u.ptr = (struct adata *) val;
- }
+ return e;
}
/**
- * rte_store_tmp_attr - store temporary eattr to private route fields
- * @r: route entry to be modified
- * @id: attribute ID
- *
- * This function is supposed to be called from store_tmp_attrs() hook for
- * each temporary attribute, after temporary &ea_list was initialized by
- * rte_init_tmp_attrs(). It checks whether temporary attribute is defined in
- * route @r eattrs stack, updates route pflags accordingly, undefines it by
- * filling &eattr field in preallocated temporary &ea_list on top of the eattrs
- * stack, and returns the value. Caller is supposed to store it in the
- * appropriate private field.
+ * rte_free - delete a &rte
+ * @e: &struct rte_storage to be deleted
+ * @tab: the table which the rte belongs to
*
- * Note that it may require free &eattr in temporary &ea_list, so it must not be
- * called more times than @max argument of rte_init_tmp_attrs()
+ * rte_free() deletes the given &rte from the routing table it's linked to.
*/
-uintptr_t
-rte_store_tmp_attr(rte *r, uint id)
-{
- ea_list *e = r->attrs->eattrs;
- eattr *a = ea_find(e->next, id);
- if (a)
- {
- e->attrs[e->count++] = (struct eattr) { .id = id, .type = EAF_TYPE_UNDEF };
- r->pflags |= EA_ID_FLAG(id);
- return (a->type & EAF_EMBEDDED) ? a->u.data : (uintptr_t) a->u.ptr;
- }
- else
- {
- r->pflags &= ~EA_ID_FLAG(id);
- return 0;
- }
-}
-
-/**
- * rte_make_tmp_attrs - prepare route by adding all relevant temporary route attributes
- * @r: route entry to be modified (may be replaced if COW)
- * @lp: linpool from which to allocate attributes
- * @old_attrs: temporary ref to old &rta (may be NULL)
- *
- * This function expands privately stored protocol-dependent route attributes
- * to a uniform &eattr / &ea_list representation. It is essentially a wrapper
- * around protocol make_tmp_attrs() hook, which does some additional work like
- * ensuring that route @r is writable.
- *
- * The route @r may be read-only (with %REF_COW flag), in that case rw copy is
- * obtained by rte_cow() and @r is replaced. If @rte is originally rw, it may be
- * directly modified (and it is never copied).
- *
- * If the @old_attrs ptr is supplied, the function obtains another reference of
- * old cached &rta, that is necessary in some cases (see rte_cow_rta() for
- * details). It is freed by rte_store_tmp_attrs(), or manually by rta_free().
- *
- * Generally, if caller ensures that @r is read-only (e.g. in route export) then
- * it may ignore @old_attrs (and set it to NULL), but must handle replacement of
- * @r. If caller ensures that @r is writable (e.g. in route import) then it may
- * ignore replacement of @r, but it must handle @old_attrs.
- */
void
-rte_make_tmp_attrs(rte **r, linpool *lp, rta **old_attrs)
+rte_free(struct rte_storage *e, rtable_private *tab)
{
- void (*make_tmp_attrs)(rte *r, linpool *lp);
- make_tmp_attrs = (*r)->attrs->src->proto->make_tmp_attrs;
-
- if (!make_tmp_attrs)
- return;
-
- /* We may need to keep ref to old attributes, will be freed in rte_store_tmp_attrs() */
- if (old_attrs)
- *old_attrs = rta_is_cached((*r)->attrs) ? rta_clone((*r)->attrs) : NULL;
-
- *r = rte_cow_rta(*r, lp);
- make_tmp_attrs(*r, lp);
+ rt_unlock_source(e->rte.src);
+ rta_free(e->rte.attrs);
+ sl_free(tab->rte_slab, e);
}
-/**
- * rte_store_tmp_attrs - store temporary route attributes back to private route fields
- * @r: route entry to be modified
- * @lp: linpool from which to allocate attributes
- * @old_attrs: temporary ref to old &rta
- *
- * This function stores temporary route attributes that were expanded by
- * rte_make_tmp_attrs() back to private route fields and also undefines them.
- * It is essentially a wrapper around protocol store_tmp_attrs() hook, which
- * does some additional work like shortcut if there is no change and cleanup
- * of @old_attrs reference obtained by rte_make_tmp_attrs().
- */
-static void
-rte_store_tmp_attrs(rte *r, linpool *lp, rta *old_attrs)
-{
- void (*store_tmp_attrs)(rte *rt, linpool *lp);
- store_tmp_attrs = r->attrs->src->proto->store_tmp_attrs;
-
- if (!store_tmp_attrs)
- return;
-
- ASSERT(!rta_is_cached(r->attrs));
-
- /* If there is no new ea_list, we just skip the temporary ea_list */
- ea_list *ea = r->attrs->eattrs;
- if (ea && (ea->flags & EALF_TEMP))
- r->attrs->eattrs = ea->next;
- else
- store_tmp_attrs(r, lp);
-
- /* Free ref we got in rte_make_tmp_attrs(), have to do rta_lookup() first */
- r->attrs = rta_lookup(r->attrs);
- rta_free(old_attrs);
-}
-
-
static int /* Actually better or at least as good as */
rte_better(rte *new, rte *old)
{
@@ -513,20 +381,20 @@ rte_better(rte *new, rte *old)
if (!rte_is_valid(new))
return 0;
- if (new->pref > old->pref)
+ if (new->attrs->pref > old->attrs->pref)
return 1;
- if (new->pref < old->pref)
+ if (new->attrs->pref < old->attrs->pref)
return 0;
- if (new->attrs->src->proto->proto != old->attrs->src->proto->proto)
+ if (new->src->owner->class != old->src->owner->class)
{
/*
* If the user has configured protocol preferences, so that two different protocols
* have the same preference, try to break the tie by comparing addresses. Not too
* useful, but keeps the ordering of routes unambiguous.
*/
- return new->attrs->src->proto->proto > old->attrs->src->proto->proto;
+ return new->src->owner->class > old->src->owner->class;
}
- if (better = new->attrs->src->proto->rte_better)
+ if (better = new->src->owner->class->rte_better)
return better(new, old);
return 0;
}
@@ -539,180 +407,209 @@ rte_mergable(rte *pri, rte *sec)
if (!rte_is_valid(pri) || !rte_is_valid(sec))
return 0;
- if (pri->pref != sec->pref)
+ if (pri->attrs->pref != sec->attrs->pref)
return 0;
- if (pri->attrs->src->proto->proto != sec->attrs->src->proto->proto)
+ if (pri->src->owner->class != sec->src->owner->class)
return 0;
- if (mergable = pri->attrs->src->proto->rte_mergable)
+ if (mergable = pri->src->owner->class->rte_mergable)
return mergable(pri, sec);
return 0;
}
static void
-rte_trace(struct channel *c, rte *e, int dir, char *msg)
+rte_trace(const char *name, const rte *e, int dir, const char *msg)
{
- log(L_TRACE "%s.%s %c %s %N %s",
- c->proto->name, c->name ?: "?", dir, msg, e->net->n.addr,
- rta_dest_name(e->attrs->dest));
+ log(L_TRACE "%s %c %s %N src %uL %uG %uS id %u %s%s",
+ name, dir, msg, e->net,
+ e->src->private_id, e->src->global_id, e->stale_cycle, e->id,
+ rta_dest_name(e->attrs->dest),
+ rte_is_filtered(e) ? " (filtered)" : "");
}
static inline void
-rte_trace_in(uint flag, struct channel *c, rte *e, char *msg)
+channel_rte_trace_in(uint flag, struct channel *c, const rte *e, const char *msg)
{
if ((c->debug & flag) || (c->proto->debug & flag))
- rte_trace(c, e, '>', msg);
+ rte_trace(c->in_req.name, e, '>', msg);
}
static inline void
-rte_trace_out(uint flag, struct channel *c, rte *e, char *msg)
+channel_rte_trace_out(uint flag, struct channel *c, const rte *e, const char *msg)
{
if ((c->debug & flag) || (c->proto->debug & flag))
- rte_trace(c, e, '<', msg);
+ rte_trace(c->out_req.name, e, '<', msg);
+}
+
+static inline void
+rt_rte_trace_in(uint flag, struct rt_import_request *req, const rte *e, const char *msg)
+{
+ if (req->trace_routes & flag)
+ rte_trace(req->name, e, '>', msg);
+}
+
+#if 0
+// seems to be unused at all
+static inline void
+rt_rte_trace_out(uint flag, struct rt_export_request *req, const rte *e, const char *msg)
+{
+ if (req->trace_routes & flag)
+ rte_trace(req->name, e, '<', msg);
+}
+#endif
+
+static uint
+rte_feed_count(net *n)
+{
+ uint count = 0;
+ for (struct rte_storage *e = n->routes; e; e = e->next)
+ if (rte_is_valid(RTES_OR_NULL(e)))
+ count++;
+ return count;
+}
+
+static void
+rte_feed_obtain(net *n, struct rte **feed, uint count)
+{
+ uint i = 0;
+ for (struct rte_storage *e = n->routes; e; e = e->next)
+ if (rte_is_valid(RTES_OR_NULL(e)))
+ {
+ ASSERT_DIE(i < count);
+ feed[i++] = &e->rte;
+ }
+ ASSERT_DIE(i == count);
}
static rte *
-export_filter_(struct channel *c, rte *rt0, rte **rt_free, linpool *pool, int silent)
+export_filter_(struct channel *c, rte *rt, linpool *pool, int silent)
{
struct proto *p = c->proto;
const struct filter *filter = c->out_filter;
- struct proto_stats *stats = &c->stats;
- rte *rt;
- int v;
+ struct channel_export_stats *stats = &c->export_stats;
- rt = rt0;
- *rt_free = NULL;
+ /* Do nothing if we have already rejected the route */
+ if (silent && bmap_test(&c->export_reject_map, rt->id))
+ goto reject_noset;
- v = p->preexport ? p->preexport(p, &rt, pool) : 0;
+ int v = p->preexport ? p->preexport(c, rt) : 0;
if (v < 0)
{
if (silent)
- goto reject;
+ goto reject_noset;
- stats->exp_updates_rejected++;
+ stats->updates_rejected++;
if (v == RIC_REJECT)
- rte_trace_out(D_FILTERS, c, rt, "rejected by protocol");
+ channel_rte_trace_out(D_FILTERS, c, rt, "rejected by protocol");
goto reject;
+
}
if (v > 0)
{
if (!silent)
- rte_trace_out(D_FILTERS, c, rt, "forced accept by protocol");
+ channel_rte_trace_out(D_FILTERS, c, rt, "forced accept by protocol");
goto accept;
}
- rte_make_tmp_attrs(&rt, pool, NULL);
-
v = filter && ((filter == FILTER_REJECT) ||
- (f_run(filter, &rt, pool,
+ (f_run(filter, rt, pool,
(silent ? FF_SILENT : 0)) > F_ACCEPT));
if (v)
{
if (silent)
goto reject;
- stats->exp_updates_filtered++;
- rte_trace_out(D_FILTERS, c, rt, "filtered out");
+ stats->updates_filtered++;
+ channel_rte_trace_out(D_FILTERS, c, rt, "filtered out");
goto reject;
}
-#ifdef CONFIG_PIPE
- /* Pipes need rte with stored tmpattrs, remaining protocols need expanded tmpattrs */
- if (p->proto == &proto_pipe)
- rte_store_tmp_attrs(rt, pool, NULL);
-#endif
-
accept:
- if (rt != rt0)
- *rt_free = rt;
+ /* We have accepted the route */
+ bmap_clear(&c->export_reject_map, rt->id);
return rt;
reject:
+ /* We have rejected the route by filter */
+ bmap_set(&c->export_reject_map, rt->id);
+
+reject_noset:
/* Discard temporary rte */
- if (rt != rt0)
- rte_free(rt);
return NULL;
}
static inline rte *
-export_filter(struct channel *c, rte *rt0, rte **rt_free, int silent)
+export_filter(struct channel *c, rte *rt, int silent)
{
- return export_filter_(c, rt0, rt_free, rte_update_pool, silent);
+ return export_filter_(c, rt, c->rte_update_pool, silent);
}
+void do_rt_notify_direct(struct channel *c, const net_addr *net, rte *new, const rte *old);
+
static void
-do_rt_notify(struct channel *c, net *net, rte *new, rte *old, int refeed)
+do_rt_notify(struct channel *c, const net_addr *net, rte *new, const rte *old)
{
- struct proto *p = c->proto;
- struct proto_stats *stats = &c->stats;
+ struct channel_export_stats *stats = &c->export_stats;
- if (refeed && new)
+ if (c->refeeding && new)
c->refeed_count++;
- /* Apply export limit */
- struct channel_limit *l = &c->out_limit;
- if (l->action && !old && new)
- {
- if (stats->exp_routes >= l->limit)
- channel_notify_limit(c, l, PLD_OUT, stats->exp_routes);
-
- if (l->state == PLS_BLOCKED)
+ if (!old && new)
+ if (CHANNEL_LIMIT_PUSH(c, OUT))
{
- stats->exp_updates_rejected++;
- rte_trace_out(D_FILTERS, c, new, "rejected [limit]");
+ stats->updates_rejected++;
+ channel_rte_trace_out(D_FILTERS, c, new, "rejected [limit]");
return;
}
- }
- /* Apply export table */
- if (c->out_table && !rte_update_out(c, net->n.addr, new, old, refeed))
- return;
-
- if (new)
- stats->exp_updates_accepted++;
- else
- stats->exp_withdraws_accepted++;
+ if (!new && old)
+ CHANNEL_LIMIT_POP(c, OUT);
+ /* Store route export state */
if (old)
- {
bmap_clear(&c->export_map, old->id);
- stats->exp_routes--;
- }
if (new)
- {
bmap_set(&c->export_map, new->id);
- stats->exp_routes++;
- }
+
+ /* Apply export table */
+ if (c->out_table)
+ rte_import(&c->out_table->push, net, new, old ? old->src : new->src);
+ else
+ do_rt_notify_direct(c, net, new, old);
+}
+
+void
+do_rt_notify_direct(struct channel *c, const net_addr *net, rte *new, const rte *old)
+{
+ struct proto *p = c->proto;
+ struct channel_export_stats *stats = &c->export_stats;
+
+ if (new)
+ stats->updates_accepted++;
+ else
+ stats->withdraws_accepted++;
if (p->debug & D_ROUTES)
{
if (new && old)
- rte_trace_out(D_ROUTES, c, new, "replaced");
+ channel_rte_trace_out(D_ROUTES, c, new, "replaced");
else if (new)
- rte_trace_out(D_ROUTES, c, new, "added");
+ channel_rte_trace_out(D_ROUTES, c, new, "added");
else if (old)
- rte_trace_out(D_ROUTES, c, old, "removed");
+ channel_rte_trace_out(D_ROUTES, c, old, "removed");
}
p->rt_notify(p, c, net, new, old);
}
static void
-rt_notify_basic(struct channel *c, net *net, rte *new, rte *old, int refeed)
+rt_notify_basic(struct channel *c, const net_addr *net, rte *new, rte *old)
{
- // struct proto *p = c->proto;
- rte *new_free = NULL;
-
- if (new)
- c->stats.exp_updates_received++;
- else
- c->stats.exp_withdraws_received++;
-
if (new)
- new = export_filter(c, new, &new_free, 0);
+ new = export_filter(c, new, 0);
if (old && !bmap_test(&c->export_map, old->id))
old = NULL;
@@ -720,87 +617,87 @@ rt_notify_basic(struct channel *c, net *net, rte *new, rte *old, int refeed)
if (!new && !old)
return;
- do_rt_notify(c, net, new, old, refeed);
-
- /* Discard temporary rte */
- if (new_free)
- rte_free(new_free);
+ do_rt_notify(c, net, new, old);
}
static void
-rt_notify_accepted(struct channel *c, net *net, rte *new_changed, rte *old_changed, int refeed)
+channel_rpe_mark_seen(struct rt_export_request *req, struct rt_pending_export *rpe)
{
- // struct proto *p = c->proto;
- rte *new_best = NULL;
- rte *old_best = NULL;
- rte *new_free = NULL;
- int new_first = 0;
-
- /*
- * We assume that there are no changes in net route order except (added)
- * new_changed and (removed) old_changed. Therefore, the function is not
- * compatible with deterministic_med (where nontrivial reordering can happen
- * as a result of a route change) and with recomputation of recursive routes
- * due to next hop update (where many routes can be changed in one step).
- *
- * Note that we need this assumption just for optimizations, we could just
- * run full new_best recomputation otherwise.
- *
- * There are three cases:
- * feed or old_best is old_changed -> we need to recompute new_best
- * old_best is before new_changed -> new_best is old_best, ignore
- * old_best is after new_changed -> try new_changed, otherwise old_best
- */
+ struct channel *c = SKIP_BACK(struct channel, out_req, req);
- if (net->routes)
- c->stats.exp_updates_received++;
- else
- c->stats.exp_withdraws_received++;
+ rpe_mark_seen(req->hook, rpe);
+ if (rpe->old)
+ bmap_clear(&c->export_reject_map, rpe->old->rte.id);
+}
- /* Find old_best - either old_changed, or route for net->routes */
- if (old_changed && bmap_test(&c->export_map, old_changed->id))
- old_best = old_changed;
- else
+void
+rt_notify_accepted(struct rt_export_request *req, const net_addr *n, struct rt_pending_export *rpe,
+ struct rte **feed, uint count)
+{
+ struct channel *c = SKIP_BACK(struct channel, out_req, req);
+
+ rte_update_lock(c);
+
+ rte nb0, *new_best = NULL;
+ const rte *old_best = NULL;
+
+ for (uint i = 0; i < count; i++)
{
- for (rte *r = net->routes; rte_is_valid(r); r = r->next)
+ if (!rte_is_valid(feed[i]))
+ continue;
+
+ /* Has been already rejected, won't bother with it */
+ if (!c->refeeding && bmap_test(&c->export_reject_map, feed[i]->id))
+ continue;
+
+ /* Previously exported */
+ if (!old_best && bmap_test(&c->export_map, feed[i]->id))
{
- if (bmap_test(&c->export_map, r->id))
+ /* is still best */
+ if (!new_best)
{
- old_best = r;
- break;
+ DBG("rt_notify_accepted: idempotent\n");
+ goto done;
}
- /* Note if new_changed found before old_best */
- if (r == new_changed)
- new_first = 1;
+ /* is superseded */
+ old_best = feed[i];
+ break;
}
- }
- /* Find new_best */
- if ((new_changed == old_changed) || (old_best == old_changed))
- {
- /* Feed or old_best changed -> find first accepted by filters */
- for (rte *r = net->routes; rte_is_valid(r); r = r->next)
- if (new_best = export_filter(c, r, &new_free, 0))
- break;
+ /* Have no new best route yet */
+ if (!new_best)
+ {
+ /* Try this route not seen before */
+ nb0 = *feed[i];
+ new_best = export_filter(c, &nb0, 0);
+ DBG("rt_notify_accepted: checking route id %u: %s\n", feed[i]->id, new_best ? "ok" : "no");
+ }
}
- else
+
+done:
+ /* Check obsolete routes for previously exported */
+ while (rpe)
{
- /* Other cases -> either new_changed, or old_best (and nothing changed) */
- if (new_first && (new_changed = export_filter(c, new_changed, &new_free, 0)))
- new_best = new_changed;
- else
- return;
+ channel_rpe_mark_seen(req, rpe);
+ if (rpe->old)
+ {
+ if (bmap_test(&c->export_map, rpe->old->rte.id))
+ {
+ ASSERT_DIE(old_best == NULL);
+ old_best = &rpe->old->rte;
+ }
+ }
+ rpe = rpe_next(rpe, NULL);
}
- if (!new_best && !old_best)
- return;
-
- do_rt_notify(c, net, new_best, old_best, refeed);
+ /* Nothing to export */
+ if (new_best || old_best)
+ do_rt_notify(c, n, new_best, old_best);
+ else
+ DBG("rt_notify_accepted: nothing to export\n");
- /* Discard temporary rte */
- if (new_free)
- rte_free(new_free);
+ rte_update_unlock(c);
}
@@ -811,38 +708,45 @@ nexthop_merge_rta(struct nexthop *nhs, rta *a, linpool *pool, int max)
}
rte *
-rt_export_merged(struct channel *c, net *net, rte **rt_free, linpool *pool, int silent)
+rt_export_merged(struct channel *c, struct rte **feed, uint count, linpool *pool, int silent)
{
+ _Thread_local static rte rloc;
+
// struct proto *p = c->proto;
struct nexthop *nhs = NULL;
- rte *best0, *best, *rt0, *rt, *tmp;
-
- best0 = net->routes;
- *rt_free = NULL;
+ rte *best0 = feed[0];
+ rte *best = NULL;
if (!rte_is_valid(best0))
return NULL;
- best = export_filter_(c, best0, rt_free, pool, silent);
+ /* Already rejected, no need to re-run the filter */
+ if (!c->refeeding && bmap_test(&c->export_reject_map, best0->id))
+ return NULL;
+
+ rloc = *best0;
+ best = export_filter_(c, &rloc, pool, silent);
+
+ if (!best)
+ /* Best route doesn't pass the filter */
+ return NULL;
- if (!best || !rte_is_reachable(best))
+ if (!rte_is_reachable(best))
+ /* Unreachable routes can't be merged */
return best;
- for (rt0 = best0->next; rt0; rt0 = rt0->next)
+ for (uint i = 1; i < count; i++)
{
- if (!rte_mergable(best0, rt0))
+ if (!rte_mergable(best0, feed[i]))
continue;
- rt = export_filter_(c, rt0, &tmp, pool, 1);
+ rte tmp0 = *feed[i];
+ rte *tmp = export_filter_(c, &tmp0, pool, 1);
- if (!rt)
+ if (!tmp || !rte_is_reachable(tmp))
continue;
- if (rte_is_reachable(rt))
- nhs = nexthop_merge_rta(nhs, rt->attrs, pool, c->merge_limit);
-
- if (tmp)
- rte_free(tmp);
+ nhs = nexthop_merge_rta(nhs, tmp->attrs, pool, c->merge_limit);
}
if (nhs)
@@ -851,66 +755,208 @@ rt_export_merged(struct channel *c, net *net, rte **rt_free, linpool *pool, int
if (nhs->next)
{
- best = rte_cow_rta(best, pool);
+ best->attrs = rta_cow(best->attrs, pool);
nexthop_link(best->attrs, nhs);
}
}
- if (best != best0)
- *rt_free = best;
-
return best;
}
-
-static void
-rt_notify_merged(struct channel *c, net *net, rte *new_changed, rte *old_changed,
- rte *new_best, rte *old_best, int refeed)
+void
+rt_notify_merged(struct rt_export_request *req, const net_addr *n, struct rt_pending_export *rpe,
+ struct rte **feed, uint count)
{
- // struct proto *p = c->proto;
- rte *new_free = NULL;
+ struct channel *c = SKIP_BACK(struct channel, out_req, req);
- /* We assume that all rte arguments are either NULL or rte_is_valid() */
-
- /* This check should be done by the caller */
- if (!new_best && !old_best)
- return;
+ rte_update_lock(c);
+ // struct proto *p = c->proto;
+#if 0 /* TODO: Find whether this check is possible when processing multiple changes at once. */
/* Check whether the change is relevant to the merged route */
if ((new_best == old_best) &&
(new_changed != old_changed) &&
!rte_mergable(new_best, new_changed) &&
!rte_mergable(old_best, old_changed))
return;
+#endif
- if (new_best)
- c->stats.exp_updates_received++;
- else
- c->stats.exp_withdraws_received++;
+ rte *old_best = NULL;
+ /* Find old best route */
+ for (uint i = 0; i < count; i++)
+ if (bmap_test(&c->export_map, feed[i]->id))
+ {
+ old_best = feed[i];
+ break;
+ }
+
+ /* Check obsolete routes for previously exported */
+ while (rpe)
+ {
+ channel_rpe_mark_seen(req, rpe);
+ if (rpe->old)
+ {
+ if (bmap_test(&c->export_map, rpe->old->rte.id))
+ {
+ ASSERT_DIE(old_best == NULL);
+ old_best = &rpe->old->rte;
+ }
+ }
+ rpe = rpe_next(rpe, NULL);
+ }
/* Prepare new merged route */
- if (new_best)
- new_best = rt_export_merged(c, net, &new_free, rte_update_pool, 0);
+ rte *new_merged = count ? rt_export_merged(c, feed, count, c->rte_update_pool, 0) : NULL;
- /* Check old merged route */
- if (old_best && !bmap_test(&c->export_map, old_best->id))
- old_best = NULL;
+ if (new_merged || old_best)
+ do_rt_notify(c, n, new_merged, old_best);
- if (!new_best && !old_best)
- return;
+ rte_update_unlock(c);
+}
- do_rt_notify(c, net, new_best, old_best, refeed);
+void
+rt_notify_optimal(struct rt_export_request *req, const net_addr *net, struct rt_pending_export *rpe)
+{
+ struct channel *c = SKIP_BACK(struct channel, out_req, req);
+ rte_update_lock(c);
+ rte *old = RTES_OR_NULL(rpe->old_best);
+ struct rte_storage *new_best = rpe->new_best;
- /* Discard temporary rte */
- if (new_free)
- rte_free(new_free);
+ while (rpe)
+ {
+ channel_rpe_mark_seen(req, rpe);
+ new_best = rpe->new_best;
+ rpe = rpe_next(rpe, NULL);
+ }
+
+ if (&new_best->rte != old)
+ {
+ rte n0, *new = RTES_CLONE(new_best, &n0);
+ rt_notify_basic(c, net, new, old);
+ }
+
+ rte_update_unlock(c);
+}
+
+void
+rt_notify_any(struct rt_export_request *req, const net_addr *net, struct rt_pending_export *rpe)
+{
+ struct channel *c = SKIP_BACK(struct channel, out_req, req);
+ rte_update_lock(c);
+ struct rte_src *src = rpe->new ? rpe->new->rte.src : rpe->old->rte.src;
+ rte *old = RTES_OR_NULL(rpe->old);
+ struct rte_storage *new_any = rpe->new;
+
+ while (rpe)
+ {
+ channel_rpe_mark_seen(req, rpe);
+ new_any = rpe->new;
+ rpe = rpe_next(rpe, src);
+ }
+
+ if (&new_any->rte != old)
+ {
+ rte n0, *new = RTES_CLONE(new_any, &n0);
+ rt_notify_basic(c, net, new, old);
+ }
+
+ rte_update_unlock(c);
+}
+
+void
+rt_feed_any(struct rt_export_request *req, const net_addr *net, struct rt_pending_export *rpe UNUSED, rte **feed, uint count)
+{
+ struct channel *c = SKIP_BACK(struct channel, out_req, req);
+ rte_update_lock(c);
+
+ for (uint i=0; i<count; i++)
+ {
+ rte n0 = *feed[i];
+ rt_notify_basic(c, net, &n0, NULL);
+ }
+
+ rte_update_unlock(c);
}
+void
+rpe_mark_seen(struct rt_export_hook *hook, struct rt_pending_export *rpe)
+{
+ bmap_set(&hook->seq_map, rpe->seq);
+}
+
+struct rt_pending_export *
+rpe_next(struct rt_pending_export *rpe, struct rte_src *src)
+{
+ struct rt_pending_export *next = atomic_load_explicit(&rpe->next, memory_order_acquire);
+
+ if (!next)
+ return NULL;
+
+ if (!src)
+ return next;
+
+ while (rpe = next)
+ if (src == (rpe->new ? rpe->new->rte.src : rpe->old->rte.src))
+ return rpe;
+ else
+ next = atomic_load_explicit(&rpe->next, memory_order_acquire);
+
+ return NULL;
+}
+
+static struct rt_pending_export * rt_next_export_fast(struct rt_pending_export *last);
+static void
+rte_export(struct rt_export_hook *hook, struct rt_pending_export *rpe)
+{
+ if (bmap_test(&hook->seq_map, rpe->seq))
+ goto seen;
+
+ const net_addr *n = rpe->new_best ? rpe->new_best->rte.net : rpe->old_best->rte.net;
+
+ if (rpe->new)
+ hook->stats.updates_received++;
+ else
+ hook->stats.withdraws_received++;
+
+ if (hook->req->export_one)
+ hook->req->export_one(hook->req, n, rpe);
+ else if (hook->req->export_bulk)
+ {
+ RT_LOCK(hook->table);
+ net *net = SKIP_BACK(struct network, n.addr, (net_addr (*)[0]) n);
+ uint count = rte_feed_count(net);
+ rte **feed = NULL;
+ if (count)
+ {
+ feed = alloca(count * sizeof(rte *));
+ rte_feed_obtain(net, feed, count);
+ }
+ RT_UNLOCK(hook->table);
+ hook->req->export_bulk(hook->req, n, rpe, feed, count);
+ }
+ else
+ bug("Export request must always provide an export method");
+
+seen:
+ /* Get the next export if exists */
+ hook->rpe_next = rt_next_export_fast(rpe);
+
+ /* The last block may be available to free */
+ if (PAGE_HEAD(hook->rpe_next) != PAGE_HEAD(rpe))
+ {
+ RT_LOCK(hook->table);
+ rt_export_used(RT_PRIV(hook->table));
+ RT_UNLOCK(hook->table);
+ }
+
+ /* Releasing this export for cleanup routine */
+ DBG("store hook=%p last_export=%p seq=%lu\n", hook, rpe, rpe->seq);
+ atomic_store_explicit(&hook->last_export, rpe, memory_order_release);
+}
/**
* rte_announce - announce a routing table change
* @tab: table the route has been added to
- * @type: type of route announcement (RA_UNDEF or RA_ANY)
* @net: network in question
* @new: the new or changed route
* @old: the previous route replaced by the new one
@@ -926,13 +972,6 @@ rt_notify_merged(struct channel *c, net *net, rte *new_changed, rte *old_changed
* and @new_best and @old_best describes best routes. Other routes are not
* affected, but in sorted table the order of other routes might change.
*
- * Second, There is a bulk change of multiple routes in @net, with shared best
- * route selection. In such case separate route changes are described using
- * @type of %RA_ANY, with @new and @old specifying the changed route, while
- * @new_best and @old_best are NULL. After that, another notification is done
- * where @new_best and @old_best are filled (may be the same), but @new and @old
- * are NULL.
- *
* The function announces the change to all associated channels. For each
* channel, an appropriate preprocessing is done according to channel &ra_mode.
* For example, %RA_OPTIMAL channels receive just changes of best routes.
@@ -947,306 +986,416 @@ rt_notify_merged(struct channel *c, net *net, rte *new_changed, rte *old_changed
* done outside of scope of rte_announce().
*/
static void
-rte_announce(rtable *tab, uint type, net *net, rte *new, rte *old,
- rte *new_best, rte *old_best)
+rte_announce(rtable_private *tab, net *net, struct rte_storage *new, struct rte_storage *old,
+ struct rte_storage *new_best, struct rte_storage *old_best)
{
- if (!rte_is_valid(new))
- new = NULL;
-
- if (!rte_is_valid(old))
- old = NULL;
-
- if (!rte_is_valid(new_best))
+ if (!new_best || !rte_is_valid(&new_best->rte))
new_best = NULL;
- if (!rte_is_valid(old_best))
+ if (!old_best || !rte_is_valid(&old_best->rte))
old_best = NULL;
- if (!new && !old && !new_best && !old_best)
+ if (!new || !rte_is_valid(&new->rte))
+ new = NULL;
+
+ if (old && !rte_is_valid(&old->rte))
+ {
+ /* Filtered old route isn't announced, should be freed immediately. */
+ rte_free(old, tab);
+ old = NULL;
+ }
+
+ if ((new == old) && (new_best == old_best))
return;
if (new_best != old_best)
{
if (new_best)
- new_best->sender->stats.pref_routes++;
+ new_best->rte.sender->stats.pref++;
if (old_best)
- old_best->sender->stats.pref_routes--;
+ old_best->rte.sender->stats.pref--;
if (tab->hostcache)
rt_notify_hostcache(tab, net);
}
+ if (EMPTY_LIST(tab->exports) && EMPTY_LIST(tab->pending_exports))
+ {
+ /* No export hook and no pending exports to cleanup. We may free the route immediately. */
+ if (!old)
+ return;
+
+ hmap_clear(&tab->id_map, old->rte.id);
+ rte_free(old, tab);
+ return;
+ }
+
+ /* Get the pending export structure */
+ struct rt_export_block *rpeb = NULL, *rpebsnl = NULL;
+ u32 end = 0;
+
+ if (!EMPTY_LIST(tab->pending_exports))
+ {
+ rpeb = TAIL(tab->pending_exports);
+ end = atomic_load_explicit(&rpeb->end, memory_order_relaxed);
+ if (end >= RT_PENDING_EXPORT_ITEMS)
+ {
+ ASSERT_DIE(end == RT_PENDING_EXPORT_ITEMS);
+ rpebsnl = rpeb;
+
+ rpeb = NULL;
+ end = 0;
+ }
+ }
+
+ if (!rpeb)
+ {
+ rpeb = alloc_page();
+ *rpeb = (struct rt_export_block) {};
+ add_tail(&tab->pending_exports, &rpeb->n);
+ }
+
+ /* Fill the pending export */
+ struct rt_pending_export *rpe = &rpeb->export[rpeb->end];
+ *rpe = (struct rt_pending_export) {
+ .new = new,
+ .new_best = new_best,
+ .old = old,
+ .old_best = old_best,
+ .seq = tab->next_export_seq++,
+ };
+
+ DBG("rte_announce: table=%s net=%N new=%p from %p old=%p from %p new_best=%p old_best=%p seq=%lu\n", tab->name, net->n.addr, new, new ? new->sender : NULL, old, old ? old->sender : NULL, new_best, old_best, rpe->seq);
+
+ ASSERT_DIE(atomic_fetch_add_explicit(&rpeb->end, 1, memory_order_release) == end);
+
+ if (rpebsnl)
+ {
+ _Bool f = 0;
+ ASSERT_DIE(atomic_compare_exchange_strong_explicit(&rpebsnl->not_last, &f, 1,
+ memory_order_release, memory_order_relaxed));
+ }
+
+ /* Append to the same-network squasher list */
+ if (net->last)
+ {
+ struct rt_pending_export *rpenull = NULL;
+ ASSERT_DIE(atomic_compare_exchange_strong_explicit(
+ &net->last->next, &rpenull, rpe,
+ memory_order_relaxed,
+ memory_order_relaxed));
+
+ }
+
+ net->last = rpe;
+
+ if (!net->first)
+ net->first = rpe;
+
+ if (tab->first_export == NULL)
+ tab->first_export = rpe;
+
+ if (!EMPTY_LIST(tab->exports) &&
+ (tab->first_export->seq + tab->config->cork_limit <= tab->next_export_seq) &&
+ !tab->cork_active)
+ {
+ if (config->table_debug)
+ log(L_TRACE "%s: cork activated", tab->name);
+
+ ev_cork(&rt_cork);
+ tab->cork_active = 1;
+ }
+}
+
+static struct rt_pending_export *
+rt_next_export_fast(struct rt_pending_export *last)
+{
+ /* Get the whole export block and find our position in there. */
+ struct rt_export_block *rpeb = PAGE_HEAD(last);
+ u32 pos = (last - &rpeb->export[0]);
+ u32 end = atomic_load_explicit(&rpeb->end, memory_order_acquire);
+ ASSERT_DIE(pos < end);
+
+ /* Next is in the same block. */
+ if (++pos < end)
+ return &rpeb->export[pos];
+
+ /* There is another block. */
+ if (atomic_load_explicit(&rpeb->not_last, memory_order_acquire))
+ {
+ /* This is OK to do non-atomically because of the not_last flag. */
+ rpeb = NODE_NEXT(rpeb);
+ return &rpeb->export[0];
+ }
+
+ /* There is nothing more. */
+ return NULL;
+}
+
+static struct rt_pending_export *
+rt_next_export(struct rt_export_hook *hook, rtable_private *tab)
+{
+ /* As the table is locked, it is safe to reload the last export pointer */
+ struct rt_pending_export *last = atomic_load_explicit(&hook->last_export, memory_order_acquire);
+
+ /* It is still valid, let's reuse it */
+ if (last)
+ return rt_next_export_fast(last);
+
+ /* No, therefore we must process the table's first pending export */
+ else
+ return tab->first_export;
+}
+
+static inline void
+rt_send_export_event(struct rt_export_hook *hook)
+{
+ ev_send(hook->req->list, hook->event);
+}
+
+static void
+rt_announce_exports(void *data)
+{
+ rtable_private *tab = data;
+ ASSERT_DIE(birdloop_inside(tab->loop));
+
rt_schedule_notify(tab);
- struct channel *c; node *n;
- WALK_LIST2(c, n, tab->channels, table_node)
+ struct rt_export_hook *c; node *n;
+ WALK_LIST2(c, n, tab->exports, n)
{
- if (c->export_state == ES_DOWN)
+ if (atomic_load_explicit(&c->export_state, memory_order_acquire) != TES_READY)
continue;
- if (type && (type != c->ra_mode))
- continue;
+ rt_send_export_event(c);
+ }
+}
- switch (c->ra_mode)
+static void
+rt_import_announce_exports(void *data)
+{
+ struct rt_import_hook *hook = data;
+ RT_LOCKED(hook->table, tab)
+ {
+ if (hook->import_state == TIS_CLEARED)
{
- case RA_OPTIMAL:
- if (new_best != old_best)
- rt_notify_basic(c, net, new_best, old_best, 0);
- break;
+ rfree(hook->export_announce_event);
- case RA_ANY:
- if (new != old)
- rt_notify_basic(c, net, new, old, 0);
- break;
+ ev_send(hook->stopped->list, hook->stopped);
+ rem_node(&hook->n);
+ mb_free(hook);
+ rt_unlock_table(tab);
+ }
+ else
+ ev_send_loop(tab->loop, tab->announce_event);
+ }
+}
- case RA_ACCEPTED:
- rt_notify_accepted(c, net, new, old, 0);
- break;
+static struct rt_pending_export *
+rt_last_export(rtable_private *tab)
+{
+ struct rt_pending_export *rpe = NULL;
- case RA_MERGED:
- rt_notify_merged(c, net, new, old, new_best, old_best, 0);
- break;
+ if (!EMPTY_LIST(tab->pending_exports))
+ {
+ /* We'll continue processing exports from this export on */
+ struct rt_export_block *reb = TAIL(tab->pending_exports);
+ ASSERT_DIE(reb->end);
+ rpe = &reb->export[reb->end - 1];
+ }
+
+ return rpe;
+}
+
+#define RT_EXPORT_BULK 1024
+
+static void
+rt_export_hook(void *_data)
+{
+ struct rt_export_hook *c = _data;
+
+ ASSERT_DIE(atomic_load_explicit(&c->export_state, memory_order_relaxed) == TES_READY);
+
+ if (!c->rpe_next)
+ {
+ RT_LOCK(c->table);
+ c->rpe_next = rt_next_export(c, RT_PRIV(c->table));
+
+ if (!c->rpe_next)
+ {
+ rt_export_used(RT_PRIV(c->table));
+ RT_UNLOCK(c->table);
+ return;
}
+
+ RT_UNLOCK(c->table);
+ }
+
+ /* Process the export */
+ for (uint i=0; i<RT_EXPORT_BULK; i++)
+ {
+ rte_export(c, c->rpe_next);
+
+ if (!c->rpe_next)
+ break;
}
+
+ rt_send_export_event(c);
}
+
static inline int
-rte_validate(rte *e)
+rte_validate(struct channel *ch, rte *e)
{
int c;
- net *n = e->net;
+ const net_addr *n = e->net;
- if (!net_validate(n->n.addr))
+ if (!net_validate(n))
{
log(L_WARN "Ignoring bogus prefix %N received via %s",
- n->n.addr, e->sender->proto->name);
+ n, ch->proto->name);
return 0;
}
/* FIXME: better handling different nettypes */
- c = !net_is_flow(n->n.addr) ?
- net_classify(n->n.addr): (IADDR_HOST | SCOPE_UNIVERSE);
+ c = !net_is_flow(n) ?
+ net_classify(n): (IADDR_HOST | SCOPE_UNIVERSE);
if ((c < 0) || !(c & IADDR_HOST) || ((c & IADDR_SCOPE_MASK) <= SCOPE_LINK))
{
log(L_WARN "Ignoring bogus route %N received via %s",
- n->n.addr, e->sender->proto->name);
+ n, ch->proto->name);
return 0;
}
- if (net_type_match(n->n.addr, NB_DEST) == !e->attrs->dest)
+ if (net_type_match(n, NB_DEST) == !e->attrs->dest)
{
log(L_WARN "Ignoring route %N with invalid dest %d received via %s",
- n->n.addr, e->attrs->dest, e->sender->proto->name);
+ n, e->attrs->dest, ch->proto->name);
return 0;
}
if ((e->attrs->dest == RTD_UNICAST) && !nexthop_is_sorted(&(e->attrs->nh)))
{
log(L_WARN "Ignoring unsorted multipath route %N received via %s",
- n->n.addr, e->sender->proto->name);
+ n, ch->proto->name);
return 0;
}
return 1;
}
-/**
- * rte_free - delete a &rte
- * @e: &rte to be deleted
- *
- * rte_free() deletes the given &rte from the routing table it's linked to.
- */
-void
-rte_free(rte *e)
-{
- if (rta_is_cached(e->attrs))
- rta_free(e->attrs);
- sl_free(rte_slab, e);
-}
-
-static inline void
-rte_free_quick(rte *e)
-{
- rta_free(e->attrs);
- sl_free(rte_slab, e);
-}
-
static int
rte_same(rte *x, rte *y)
{
+ ASSERT_DIE(x->attrs->cached && y->attrs->cached);
+
/* rte.flags are not checked, as they are mostly internal to rtable */
return
x->attrs == y->attrs &&
- x->pflags == y->pflags &&
- x->pref == y->pref &&
- (!x->attrs->src->proto->rte_same || x->attrs->src->proto->rte_same(x, y)) &&
+ x->src == y->src &&
rte_is_filtered(x) == rte_is_filtered(y);
}
static inline int rte_is_ok(rte *e) { return e && !rte_is_filtered(e); }
static void
-rte_recalculate(struct channel *c, net *net, rte *new, struct rte_src *src)
+rte_recalculate(rtable_private *table, struct rt_import_hook *c, net *net, rte *new, struct rte_src *src)
{
- struct proto *p = c->proto;
- struct rtable *table = c->table;
- struct proto_stats *stats = &c->stats;
- static struct tbf rl_pipe = TBF_DEFAULT_LOG_LIMITS;
- rte *before_old = NULL;
- rte *old_best = net->routes;
+ struct rt_import_request *req = c->req;
+ struct rt_import_stats *stats = &c->stats;
+ struct rte_storage *old_best_stored = net->routes, *old_stored = NULL;
+ rte *old_best = old_best_stored ? &old_best_stored->rte : NULL;
rte *old = NULL;
- rte **k;
-
- k = &net->routes; /* Find and remove original route from the same protocol */
- while (old = *k)
- {
- if (old->attrs->src == src)
- {
- /* If there is the same route in the routing table but from
- * a different sender, then there are two paths from the
- * source protocol to this routing table through transparent
- * pipes, which is not allowed.
- *
- * We log that and ignore the route. If it is withdraw, we
- * ignore it completely (there might be 'spurious withdraws',
- * see FIXME in do_rte_announce())
- */
- if (old->sender->proto != p)
- {
- if (new)
- {
- log_rl(&rl_pipe, L_ERR "Pipe collision detected when sending %N to table %s",
- net->n.addr, table->name);
- rte_free_quick(new);
- }
- return;
- }
- if (new && rte_same(old, new))
- {
- /* No changes, ignore the new route and refresh the old one */
+ /* Set the stale cycle unless already set */
+ if (new && !(new->flags & REF_USE_STALE))
+ new->stale_cycle = c->stale_set;
- old->flags &= ~(REF_STALE | REF_DISCARD | REF_MODIFY);
+ /* Find and remove original route from the same protocol */
+ struct rte_storage **before_old = rte_find(net, src);
- if (!rte_is_filtered(new))
- {
- stats->imp_updates_ignored++;
- rte_trace_in(D_ROUTES, c, new, "ignored");
- }
-
- rte_free_quick(new);
- return;
- }
- *k = old->next;
- table->rt_count--;
- break;
- }
- k = &old->next;
- before_old = old;
- }
-
- /* Save the last accessed position */
- rte **pos = k;
-
- if (!old)
- before_old = NULL;
-
- if (!old && !new)
+ if (!*before_old && !new)
{
- stats->imp_withdraws_ignored++;
+ stats->withdraws_ignored++;
return;
}
- int new_ok = rte_is_ok(new);
- int old_ok = rte_is_ok(old);
+ if (new)
+ new->attrs = rta_is_cached(new->attrs) ? rta_clone(new->attrs) : rta_lookup(new->attrs);
- struct channel_limit *l = &c->rx_limit;
- if (l->action && !old && new && !c->in_table)
+ if (*before_old)
{
- u32 all_routes = stats->imp_routes + stats->filt_routes;
-
- if (all_routes >= l->limit)
- channel_notify_limit(c, l, PLD_RX, all_routes);
-
- if (l->state == PLS_BLOCKED)
+ old = &(old_stored = (*before_old))->rte;
+
+ /* If there is the same route in the routing table but from
+ * a different sender, then there are two paths from the
+ * source protocol to this routing table through transparent
+ * pipes, which is not allowed.
+ * We log that and ignore the route. */
+ if (old->sender != c)
{
- /* In receive limit the situation is simple, old is NULL so
- we just free new and exit like nothing happened */
+ if (!old->generation && !new->generation)
+ bug("Two protocols claim to author a route with the same rte_src in table %s: %N %s/%u:%u",
+ c->table->name, net->n.addr, old->src->owner->name, old->src->private_id, old->src->global_id);
- stats->imp_updates_ignored++;
- rte_trace_in(D_FILTERS, c, new, "ignored [limit]");
- rte_free_quick(new);
- return;
+ log_rl(&table->rl_pipe, L_ERR "Route source collision in table %s: %N %s/%u:%u",
+ c->table->name, net->n.addr, old->src->owner->name, old->src->private_id, old->src->global_id);
}
- }
- l = &c->in_limit;
- if (l->action && !old_ok && new_ok)
- {
- if (stats->imp_routes >= l->limit)
- channel_notify_limit(c, l, PLD_IN, stats->imp_routes);
-
- if (l->state == PLS_BLOCKED)
- {
- /* In import limit the situation is more complicated. We
- shouldn't just drop the route, we should handle it like
- it was filtered. We also have to continue the route
- processing if old or new is non-NULL, but we should exit
- if both are NULL as this case is probably assumed to be
- already handled. */
+ if (new && rte_same(old, new))
+ {
+ /* No changes, ignore the new route and refresh the old one */
+ old->stale_cycle = new->stale_cycle;
- stats->imp_updates_ignored++;
- rte_trace_in(D_FILTERS, c, new, "ignored [limit]");
+ if (!rte_is_filtered(new))
+ {
+ stats->updates_ignored++;
+ rt_rte_trace_in(D_ROUTES, req, new, "ignored");
+ }
- if (c->in_keep_filtered)
- new->flags |= REF_FILTERED;
- else
- { rte_free_quick(new); new = NULL; }
+ rta_free(new->attrs);
+ return;
+ }
- /* Note that old && !new could be possible when
- c->in_keep_filtered changed in the recent past. */
+ *before_old = (*before_old)->next;
+ table->rt_count--;
+ }
- if (!old && !new)
- return;
+ if (req->preimport)
+ new = req->preimport(req, new, old);
- new_ok = 0;
- goto skip_stats1;
- }
- }
+ int new_ok = rte_is_ok(new);
+ int old_ok = rte_is_ok(old);
if (new_ok)
- stats->imp_updates_accepted++;
+ stats->updates_accepted++;
else if (old_ok)
- stats->imp_withdraws_accepted++;
+ stats->withdraws_accepted++;
else
- stats->imp_withdraws_ignored++;
+ stats->withdraws_ignored++;
if (old_ok || new_ok)
table->last_rt_change = current_time();
- skip_stats1:
-
- if (new)
- rte_is_filtered(new) ? stats->filt_routes++ : stats->imp_routes++;
- if (old)
- rte_is_filtered(old) ? stats->filt_routes-- : stats->imp_routes--;
+ struct rte_storage *new_stored = new ? rte_store(new, net, table) : NULL;
if (table->config->sorted)
{
/* If routes are sorted, just insert new route to appropriate position */
- if (new)
+ if (new_stored)
{
- if (before_old && !rte_better(new, before_old))
- k = &before_old->next;
+ struct rte_storage **k;
+ if ((before_old != &net->routes) && !rte_better(new, &SKIP_BACK(struct rte_storage, next, before_old)->rte))
+ k = before_old;
else
k = &net->routes;
for (; *k; k=&(*k)->next)
- if (rte_better(new, *k))
+ if (rte_better(new, &(*k)->rte))
break;
- new->next = *k;
- *k = new;
+ new_stored->next = *k;
+ *k = new_stored;
table->rt_count++;
}
@@ -1256,16 +1405,17 @@ rte_recalculate(struct channel *c, net *net, rte *new, struct rte_src *src)
/* If routes are not sorted, find the best route and move it on
the first position. There are several optimized cases. */
- if (src->proto->rte_recalculate && src->proto->rte_recalculate(table, net, new, old, old_best))
+ if (src->owner->rte_recalculate &&
+ src->owner->rte_recalculate(table, net, new_stored ? &new_stored->rte : NULL, old, old_best))
goto do_recalculate;
- if (new && rte_better(new, old_best))
+ if (new_stored && rte_better(&new_stored->rte, old_best))
{
/* The first case - the new route is cleary optimal,
we link it at the first position */
- new->next = net->routes;
- net->routes = new;
+ new_stored->next = net->routes;
+ net->routes = new_stored;
table->rt_count++;
}
@@ -1279,10 +1429,10 @@ rte_recalculate(struct channel *c, net *net, rte *new, struct rte_src *src)
do_recalculate:
/* Add the new route to the list */
- if (new)
+ if (new_stored)
{
- new->next = *pos;
- *pos = new;
+ new_stored->next = *before_old;
+ *before_old = new_stored;
table->rt_count++;
}
@@ -1290,335 +1440,408 @@ rte_recalculate(struct channel *c, net *net, rte *new, struct rte_src *src)
/* Find a new optimal route (if there is any) */
if (net->routes)
{
- rte **bp = &net->routes;
- for (k=&(*bp)->next; *k; k=&(*k)->next)
- if (rte_better(*k, *bp))
+ struct rte_storage **bp = &net->routes;
+ for (struct rte_storage **k=&(*bp)->next; *k; k=&(*k)->next)
+ if (rte_better(&(*k)->rte, &(*bp)->rte))
bp = k;
/* And relink it */
- rte *best = *bp;
+ struct rte_storage *best = *bp;
*bp = best->next;
best->next = net->routes;
net->routes = best;
}
}
- else if (new)
+ else if (new_stored)
{
/* The third case - the new route is not better than the old
best route (therefore old_best != NULL) and the old best
route was not removed (therefore old_best == net->routes).
We just link the new route to the old/last position. */
- new->next = *pos;
- *pos = new;
+ new_stored->next = *before_old;
+ *before_old = new_stored;
table->rt_count++;
}
/* The fourth (empty) case - suboptimal route was removed, nothing to do */
}
- if (new)
+ if (new_stored)
{
- new->lastmod = current_time();
-
- if (!old)
- {
- new->id = hmap_first_zero(&table->id_map);
- hmap_set(&table->id_map, new->id);
- }
- else
- new->id = old->id;
+ new_stored->rte.lastmod = current_time();
+ new_stored->rte.id = hmap_first_zero(&table->id_map);
+ hmap_set(&table->id_map, new_stored->rte.id);
}
+ _Bool nb = (new_stored == net->routes);
+ _Bool ob = (old_best == old);
+
/* Log the route change */
- if ((c->debug & D_ROUTES) || (p->debug & D_ROUTES))
+ if (new_ok && old_ok)
{
- if (new_ok)
- rte_trace(c, new, '>', new == net->routes ? "added [best]" : "added");
- else if (old_ok)
- {
- if (old != old_best)
- rte_trace(c, old, '>', "removed");
- else if (rte_is_ok(net->routes))
- rte_trace(c, old, '>', "removed [replaced]");
- else
- rte_trace(c, old, '>', "removed [sole]");
- }
+ const char *best_indicator[2][2] = { { "updated", "updated [-best]" }, { "updated [+best]", "updated [best]" } };
+ rt_rte_trace_in(D_ROUTES, req, &new_stored->rte, best_indicator[nb][ob]);
}
+ else if (new_ok)
+ rt_rte_trace_in(D_ROUTES, req, &new_stored->rte,
+ (!net->routes->next || !rte_is_ok(&net->routes->next->rte)) ? "added [sole]" :
+ nb ? "added [best]" : "added");
+ else if (old_ok)
+ rt_rte_trace_in(D_ROUTES, req, old,
+ (!net->routes || !rte_is_ok(&net->routes->rte)) ? "removed [sole]" :
+ ob ? "removed [best]" : "removed");
/* Propagate the route change */
- rte_announce(table, RA_UNDEF, net, new, old, net->routes, old_best);
+ rte_announce(table, net, new_stored, old_stored,
+ net->routes, old_best_stored);
if (!net->routes &&
(table->gc_counter++ >= table->config->gc_max_ops) &&
(table->gc_time + table->config->gc_min_time <= current_time()))
rt_schedule_prune(table);
+#if 0
+ /* Enable and reimplement these callbacks if anybody wants to use them */
if (old_ok && p->rte_remove)
p->rte_remove(net, old);
if (new_ok && p->rte_insert)
- p->rte_insert(net, new);
-
- if (old)
- {
- if (!new)
- hmap_clear(&table->id_map, old->id);
-
- rte_free_quick(old);
- }
-}
-
-static int rte_update_nest_cnt; /* Nesting counter to allow recursive updates */
+ p->rte_insert(net, &new_stored->rte);
+#endif
-static inline void
-rte_update_lock(void)
-{
- rte_update_nest_cnt++;
}
-static inline void
-rte_update_unlock(void)
+rte *
+channel_preimport(struct rt_import_request *req, rte *new, rte *old)
{
- if (!--rte_update_nest_cnt)
- lp_flush(rte_update_pool);
-}
+ struct channel *c = SKIP_BACK(struct channel, in_req, req);
-static inline void
-rte_hide_dummy_routes(net *net, rte **dummy)
-{
- if (net->routes && net->routes->attrs->source == RTS_DUMMY)
+ if (!c->in_table)
{
- *dummy = net->routes;
- net->routes = (*dummy)->next;
+ if (new && !old)
+ if (CHANNEL_LIMIT_PUSH(c, RX))
+ {
+ rta_free(new->attrs);
+ return NULL;
+ }
+
+ if (!new && old)
+ CHANNEL_LIMIT_POP(c, RX);
}
+
+ int new_in = new && !rte_is_filtered(new);
+ int old_in = old && !rte_is_filtered(old);
+
+ if (new_in && !old_in)
+ if (CHANNEL_LIMIT_PUSH(c, IN))
+ if (c->in_keep_filtered)
+ {
+ new->flags |= REF_FILTERED;
+ return new;
+ }
+ else
+ {
+ rta_free(new->attrs);
+ return NULL;
+ }
+
+ if (!new_in && old_in)
+ CHANNEL_LIMIT_POP(c, IN);
+
+ return new;
}
-static inline void
-rte_unhide_dummy_routes(net *net, rte **dummy)
+rte *
+channel_in_preimport(struct rt_import_request *req, rte *new, rte *old)
{
- if (*dummy)
- {
- (*dummy)->next = net->routes;
- net->routes = *dummy;
- }
+ struct channel_aux_table *cat = SKIP_BACK(struct channel_aux_table, push, req);
+
+ if (new && !old)
+ if (CHANNEL_LIMIT_PUSH(cat->c, RX))
+ return NULL;
+
+ if (!new && old)
+ CHANNEL_LIMIT_POP(cat->c, RX);
+
+ return new;
}
-/**
- * rte_update - enter a new update to a routing table
- * @table: table to be updated
- * @c: channel doing the update
- * @net: network node
- * @p: protocol submitting the update
- * @src: protocol originating the update
- * @new: a &rte representing the new route or %NULL for route removal.
- *
- * This function is called by the routing protocols whenever they discover
- * a new route or wish to update/remove an existing route. The right announcement
- * sequence is to build route attributes first (either un-cached with @aflags set
- * to zero or a cached one using rta_lookup(); in this case please note that
- * you need to increase the use count of the attributes yourself by calling
- * rta_clone()), call rte_get_temp() to obtain a temporary &rte, fill in all
- * the appropriate data and finally submit the new &rte by calling rte_update().
- *
- * @src specifies the protocol that originally created the route and the meaning
- * of protocol-dependent data of @new. If @new is not %NULL, @src have to be the
- * same value as @new->attrs->proto. @p specifies the protocol that called
- * rte_update(). In most cases it is the same protocol as @src. rte_update()
- * stores @p in @new->sender;
- *
- * When rte_update() gets any route, it automatically validates it (checks,
- * whether the network and next hop address are valid IP addresses and also
- * whether a normal routing protocol doesn't try to smuggle a host or link
- * scope route to the table), converts all protocol dependent attributes stored
- * in the &rte to temporary extended attributes, consults import filters of the
- * protocol to see if the route should be accepted and/or its attributes modified,
- * stores the temporary attributes back to the &rte.
- *
- * Now, having a "public" version of the route, we
- * automatically find any old route defined by the protocol @src
- * for network @n, replace it by the new one (or removing it if @new is %NULL),
- * recalculate the optimal route for this destination and finally broadcast
- * the change (if any) to all routing protocols by calling rte_announce().
- *
- * All memory used for attribute lists and other temporary allocations is taken
- * from a special linear pool @rte_update_pool and freed when rte_update()
- * finishes.
- */
+void rte_update_direct(struct channel *c, const net_addr *n, rte *new, struct rte_src *src);
void
-rte_update2(struct channel *c, const net_addr *n, rte *new, struct rte_src *src)
+rte_update(struct channel *c, const net_addr *n, rte *new, struct rte_src *src)
{
- // struct proto *p = c->proto;
- struct proto_stats *stats = &c->stats;
- const struct filter *filter = c->in_filter;
- rte *dummy = NULL;
- net *nn;
+ if (!c->in_req.hook)
+ return;
ASSERT(c->channel_state == CS_UP);
- rte_update_lock();
+ if (c->in_table)
+ rte_import(&c->in_table->push, n, new, src);
+ else
+ rte_update_direct(c, n, new, src);
+}
+
+void
+rte_update_direct(struct channel *c, const net_addr *n, rte *new, struct rte_src *src)
+{
+ const struct filter *filter = c->in_filter;
+ struct channel_import_stats *stats = &c->import_stats;
+
+ rte_update_lock(c);
if (new)
{
- /* Create a temporary table node */
- nn = alloca(sizeof(net) + n->length);
- memset(nn, 0, sizeof(net) + n->length);
- net_copy(nn->n.addr, n);
-
- new->net = nn;
- new->sender = c;
+ new->net = n;
- if (!new->pref)
- new->pref = c->preference;
+ int fr;
- stats->imp_updates_received++;
- if (!rte_validate(new))
+ stats->updates_received++;
+ if (!rte_validate(c, new))
{
- rte_trace_in(D_FILTERS, c, new, "invalid");
- stats->imp_updates_invalid++;
- goto drop;
+ channel_rte_trace_in(D_FILTERS, c, new, "invalid");
+ stats->updates_invalid++;
+ new = NULL;
}
-
- if (filter == FILTER_REJECT)
+ else if ((filter == FILTER_REJECT) ||
+ ((fr = f_run(filter, new, c->rte_update_pool, 0)) > F_ACCEPT))
{
- stats->imp_updates_filtered++;
- rte_trace_in(D_FILTERS, c, new, "filtered out");
-
- if (! c->in_keep_filtered)
- goto drop;
+ stats->updates_filtered++;
+ channel_rte_trace_in(D_FILTERS, c, new, "filtered out");
- /* new is a private copy, i could modify it */
- new->flags |= REF_FILTERED;
+ if (c->in_keep_filtered)
+ new->flags |= REF_FILTERED;
+ else
+ new = NULL;
}
- else if (filter)
- {
- rta *old_attrs = NULL;
- rte_make_tmp_attrs(&new, rte_update_pool, &old_attrs);
+ }
+ else
+ stats->withdraws_received++;
- int fr = f_run(filter, &new, rte_update_pool, 0);
- if (fr > F_ACCEPT)
- {
- stats->imp_updates_filtered++;
- rte_trace_in(D_FILTERS, c, new, "filtered out");
+ rte_import(&c->in_req, n, new, src);
- if (! c->in_keep_filtered)
- {
- rta_free(old_attrs);
- goto drop;
- }
+ rte_update_unlock(c);
+}
- new->flags |= REF_FILTERED;
- }
+void
+rte_import(struct rt_import_request *req, const net_addr *n, rte *new, struct rte_src *src)
+{
+ struct rt_import_hook *hook = req->hook;
+ if (!hook)
+ return;
- rte_store_tmp_attrs(new, rte_update_pool, old_attrs);
- }
- if (!rta_is_cached(new->attrs)) /* Need to copy attributes */
- new->attrs = rta_lookup(new->attrs);
- new->flags |= REF_COW;
+ RT_LOCK(hook->table);
+ rtable_private *tab = RT_PRIV(hook->table);
+ net *nn;
+ if (new)
+ {
/* Use the actual struct network, not the dummy one */
- nn = net_get(c->table, n);
- new->net = nn;
+ nn = net_get(tab, n);
+ new->net = nn->n.addr;
+ new->sender = hook;
}
- else
+ else if (!(nn = net_find(tab, n)))
{
- stats->imp_withdraws_received++;
-
- if (!(nn = net_find(c->table, n)) || !src)
- {
- stats->imp_withdraws_ignored++;
- rte_update_unlock();
- return;
- }
+ req->hook->stats.withdraws_ignored++;
+ RT_UNLOCK(tab);
+ return;
}
- recalc:
/* And recalculate the best route */
- rte_hide_dummy_routes(nn, &dummy);
- rte_recalculate(c, nn, new, src);
- rte_unhide_dummy_routes(nn, &dummy);
+ rte_recalculate(tab, hook, nn, new, src);
- rte_update_unlock();
- return;
+ /* Schedule export announcement */
+ ev_send(req->list, hook->export_announce_event);
- drop:
- rte_free(new);
- new = NULL;
- if (nn = net_find(c->table, n))
- goto recalc;
+ /* Done! */
+ RT_UNLOCK(tab);
+}
- rte_update_unlock();
+/* Check rtable for best route to given net whether it would be exported do p */
+int
+rt_examine(rtable_private *t, net_addr *a, struct channel *c, const struct filter *filter)
+{
+ net *n = net_find(t, a);
+
+ if (!n || !n->routes)
+ return 0;
+
+ rte rt = n->routes->rte;
+
+ if (!rte_is_valid(&rt))
+ return 0;
+
+ rte_update_lock(c);
+
+ /* Rest is stripped down export_filter() */
+ int v = c->proto->preexport ? c->proto->preexport(c, &rt) : 0;
+ if (v == RIC_PROCESS)
+ v = (f_run(filter, &rt, c->rte_update_pool, FF_SILENT) <= F_ACCEPT);
+
+ rte_update_unlock(c);
+
+ return v > 0;
}
-/* Independent call to rte_announce(), used from next hop
- recalculation, outside of rte_update(). new must be non-NULL */
-static inline void
-rte_announce_i(rtable *tab, uint type, net *net, rte *new, rte *old,
- rte *new_best, rte *old_best)
+static void
+rt_export_stopped(void *data)
{
- rte_update_lock();
- rte_announce(tab, type, net, new, old, new_best, old_best);
- rte_update_unlock();
+ struct rt_export_hook *hook = data;
+
+ RT_LOCKED(hook->table, tab)
+ {
+ /* Drop pending exports */
+ rt_export_used(tab);
+
+ /* Unlist */
+ rem_node(&hook->n);
+ }
+
+ /* Report the channel as stopped. */
+ hook->stopped(hook->req);
+
+ RT_LOCKED(hook->table, tab)
+ {
+ /* Free the hook together with its coroutine. */
+ rp_free(hook->pool, tab->rp);
+ rt_unlock_table(tab);
+
+ rt_fast_prune_check(tab);
+
+ DBG("Export hook %p in table %s finished uc=%u\n", hook, tab->name, tab->use_count);
+ }
}
+
static inline void
-rte_discard(rte *old) /* Non-filtered route deletion, used during garbage collection */
+rt_set_import_state(struct rt_import_hook *hook, u8 state)
{
- rte_update_lock();
- rte_recalculate(old->sender, old->net, NULL, old->attrs->src);
- rte_update_unlock();
+ hook->last_state_change = current_time();
+ hook->import_state = state;
+
+ if (hook->req->log_state_change)
+ hook->req->log_state_change(hook->req, state);
}
-/* Modify existing route by protocol hook, used for long-lived graceful restart */
static inline void
-rte_modify(rte *old)
+rt_set_export_state(struct rt_export_hook *hook, u8 state)
{
- rte_update_lock();
+ hook->last_state_change = current_time();
+ atomic_store_explicit(&hook->export_state, state, memory_order_release);
- rte *new = old->sender->proto->rte_modify(old, rte_update_pool);
- if (new != old)
- {
- if (new)
- {
- if (!rta_is_cached(new->attrs))
- new->attrs = rta_lookup(new->attrs);
- new->flags = (old->flags & ~REF_MODIFY) | REF_COW;
- }
+ if (hook->req->log_state_change)
+ hook->req->log_state_change(hook->req, state);
+}
- rte_recalculate(old->sender, old->net, new, old->attrs->src);
- }
+void
+rt_request_import(rtable *t, struct rt_import_request *req)
+{
+ RT_LOCK(t);
+ rtable_private *tab = RT_PRIV(t);
+ rt_lock_table(tab);
+
+ ASSERT_DIE(!tab->delete);
+
+ struct rt_import_hook *hook = req->hook = mb_allocz(tab->rp, sizeof(struct rt_import_hook));
+
+ DBG("Lock table %s for import %p req=%p uc=%u\n", tab->name, hook, req, tab->use_count);
- rte_update_unlock();
+ hook->req = req;
+ hook->table = t;
+
+ hook->export_announce_event = ev_new_init(tab->rp, rt_import_announce_exports, hook);
+
+ if (!hook->stale_set)
+ hook->stale_set = hook->stale_valid = hook->stale_pruning = hook->stale_pruned = 1;
+
+ rt_set_import_state(hook, TIS_UP);
+
+ hook->n = (node) {};
+ add_tail(&tab->imports, &hook->n);
+ tab->imports_up++;
+
+ RT_UNLOCK(t);
}
-/* Check rtable for best route to given net whether it would be exported do p */
-int
-rt_examine(rtable *t, net_addr *a, struct proto *p, const struct filter *filter)
+void
+rt_stop_import(struct rt_import_request *req, event *stopped)
{
- net *n = net_find(t, a);
- rte *rt = n ? n->routes : NULL;
+ ASSERT_DIE(req->hook);
+ struct rt_import_hook *hook = req->hook;
- if (!rte_is_valid(rt))
- return 0;
+ rtable_private *tab = RT_LOCK(hook->table);
- rte_update_lock();
+ rt_schedule_prune(tab);
- /* Rest is stripped down export_filter() */
- int v = p->preexport ? p->preexport(p, &rt, rte_update_pool) : 0;
- if (v == RIC_PROCESS)
- {
- rte_make_tmp_attrs(&rt, rte_update_pool, NULL);
- v = (f_run(filter, &rt, rte_update_pool, FF_SILENT) <= F_ACCEPT);
- }
+ tab->imports_up--;
+ rt_fast_prune_check(tab);
- /* Discard temporary rte */
- if (rt != n->routes)
- rte_free(rt);
+ rt_set_import_state(hook, TIS_STOP);
- rte_update_unlock();
+ hook->stopped = stopped;
- return v > 0;
+ if (hook->stale_set < hook->stale_valid)
+ if (!--tab->rr_count)
+ rt_schedule_notify(tab);
+
+ RT_UNLOCK(tab);
}
+void
+rt_request_export(rtable *t, struct rt_export_request *req)
+{
+ RT_LOCK(t);
+ rtable_private *tab = RT_PRIV(t);
+ rt_lock_table(tab);
+
+ pool *p = rp_new(tab->rp, tab->loop, "Export hook");
+ struct rt_export_hook *hook = req->hook = mb_allocz(p, sizeof(struct rt_export_hook));
+ hook->pool = p;
+
+ hook->req = req;
+ hook->table = t;
+
+ /* stats zeroed by mb_allocz */
+
+ bmap_init(&hook->seq_map, p, 1024);
+
+ rt_set_export_state(hook, TES_HUNGRY);
+
+ hook->n = (node) {};
+ add_tail(&tab->exports, &hook->n);
+
+ DBG("New export hook %p req %p in table %s uc=%u\n", hook, req, tab->name, tab->use_count);
+
+ hook->event = ev_new_init(p, rt_feed_channel, hook);
+ RT_UNLOCK(t);
+
+ rt_send_export_event(hook);
+}
+
+void
+rt_stop_export(struct rt_export_request *req, void (*stopped)(struct rt_export_request *))
+{
+ ASSERT_DIE(req->hook);
+ struct rt_export_hook *hook = req->hook;
+
+ RT_LOCK(hook->table);
+ rtable_private *tab = RT_PRIV(hook->table);
+
+ /* Stop feeding */
+ ev_postpone(hook->event);
+
+ if (atomic_load_explicit(&hook->export_state, memory_order_relaxed) == TES_FEEDING)
+ fit_get(&tab->fib, &hook->feed_fit);
+
+ hook->event->hook = rt_export_stopped;
+ hook->stopped = stopped;
+
+ rt_send_export_event(hook);
+
+ RT_UNLOCK(hook->table);
+
+ rt_set_export_state(hook, TES_STOP);
+}
/**
* rt_refresh_begin - start a refresh cycle
@@ -1630,21 +1853,48 @@ rt_examine(rtable *t, net_addr *a, struct proto *p, const struct filter *filter)
* routes to the routing table (by rte_update()). After that, all protocol
* routes (more precisely routes with @c as @sender) not sent during the
* refresh cycle but still in the table from the past are pruned. This is
- * implemented by marking all related routes as stale by REF_STALE flag in
- * rt_refresh_begin(), then marking all related stale routes with REF_DISCARD
- * flag in rt_refresh_end() and then removing such routes in the prune loop.
- */
+ * implemented by setting rte->stale_cycle to req->stale_set in rte_update()
+ * and then dropping all routes with old stale_cycle values in table prune loop. */
void
-rt_refresh_begin(rtable *t, struct channel *c)
+rt_refresh_begin(struct rt_import_request *req)
{
- FIB_WALK(&t->fib, net, n)
- {
- rte *e;
- for (e = n->routes; e; e = e->next)
- if (e->sender == c)
- e->flags |= REF_STALE;
- }
- FIB_WALK_END;
+ struct rt_import_hook *hook = req->hook;
+ ASSERT_DIE(hook);
+
+ RT_LOCK(hook->table);
+ rtable_private *tab = RT_PRIV(hook->table);
+
+ ASSERT_DIE(hook->stale_set == hook->stale_valid);
+
+ /* If the pruning routine is too slow */
+ if ((hook->stale_pruned < hook->stale_valid) && (hook->stale_pruned + 128 < hook->stale_valid)
+ || (hook->stale_pruned > hook->stale_valid) && (hook->stale_pruned > hook->stale_valid + 128))
+ {
+ log(L_WARN "Route refresh flood in table %s", tab->name);
+ FIB_WALK(&tab->fib, net, n)
+ {
+ for (struct rte_storage *e = n->routes; e; e = e->next)
+ if (e->rte.sender == req->hook)
+ e->rte.stale_cycle = 0;
+ }
+ FIB_WALK_END;
+ hook->stale_set = 1;
+ hook->stale_valid = 0;
+ hook->stale_pruned = 0;
+ }
+ else if (!++hook->stale_set)
+ {
+ /* Let's reserve the stale_cycle zero value for always-invalid routes */
+ hook->stale_set = 1;
+ hook->stale_valid = 0;
+ }
+
+ tab->rr_count++;
+
+ if (req->trace_routes & D_STATES)
+ log(L_TRACE "%s: route refresh begin [%u]", req->name, hook->stale_set);
+
+ RT_UNLOCK(tab);
}
/**
@@ -1656,45 +1906,24 @@ rt_refresh_begin(rtable *t, struct channel *c)
* hook. See rt_refresh_begin() for description of refresh cycles.
*/
void
-rt_refresh_end(rtable *t, struct channel *c)
+rt_refresh_end(struct rt_import_request *req)
{
- int prune = 0;
+ struct rt_import_hook *hook = req->hook;
+ ASSERT_DIE(hook);
- FIB_WALK(&t->fib, net, n)
- {
- rte *e;
- for (e = n->routes; e; e = e->next)
- if ((e->sender == c) && (e->flags & REF_STALE))
- {
- e->flags |= REF_DISCARD;
- prune = 1;
- }
- }
- FIB_WALK_END;
+ rtable_private *tab = RT_LOCK(hook->table);
+ hook->stale_valid++;
+ ASSERT_DIE(hook->stale_set == hook->stale_valid);
- if (prune)
- rt_schedule_prune(t);
-}
+ rt_schedule_prune(tab);
-void
-rt_modify_stale(rtable *t, struct channel *c)
-{
- int prune = 0;
+ if (req->trace_routes & D_STATES)
+ log(L_TRACE "%s: route refresh end [%u]", req->name, hook->stale_valid);
- FIB_WALK(&t->fib, net, n)
- {
- rte *e;
- for (e = n->routes; e; e = e->next)
- if ((e->sender == c) && (e->flags & REF_STALE) && !(e->flags & REF_FILTERED))
- {
- e->flags |= REF_MODIFY;
- prune = 1;
- }
- }
- FIB_WALK_END;
+ if (!--tab->rr_count)
+ rt_schedule_notify(tab);
- if (prune)
- rt_schedule_prune(t);
+ RT_UNLOCK(tab);
}
/**
@@ -1704,14 +1933,11 @@ rt_modify_stale(rtable *t, struct channel *c)
* This functions dumps contents of a &rte to debug output.
*/
void
-rte_dump(rte *e)
-{
- net *n = e->net;
- debug("%-1N ", n->n.addr);
- debug("PF=%02x pref=%d ", e->pflags, e->pref);
- rta_dump(e->attrs);
- if (e->attrs->src->proto->proto->dump_attrs)
- e->attrs->src->proto->proto->dump_attrs(e);
+rte_dump(struct rte_storage *e)
+{
+ debug("%-1N ", e->rte.net);
+ debug("PF=%02x ", e->rte.pflags);
+ rta_dump(e->rte.attrs);
debug("\n");
}
@@ -1722,20 +1948,22 @@ rte_dump(rte *e)
* This function dumps contents of a given routing table to debug output.
*/
void
-rt_dump(rtable *t)
+rt_dump(rtable *tab)
{
- debug("Dump of routing table <%s>\n", t->name);
+ RT_LOCK(tab);
+ rtable_private *t = RT_PRIV(tab);
+ debug("Dump of routing table <%s>%s\n", t->name, t->delete ? " (deleted)" : "");
#ifdef DEBUGGING
fib_check(&t->fib);
#endif
FIB_WALK(&t->fib, net, n)
{
- rte *e;
- for(e=n->routes; e; e=e->next)
+ for(struct rte_storage *e=n->routes; e; e=e->next)
rte_dump(e);
}
FIB_WALK_END;
debug("\n");
+ RT_UNLOCK(tab);
}
/**
@@ -1753,73 +1981,121 @@ rt_dump_all(void)
rt_dump(t);
}
-static inline void
-rt_schedule_hcu(rtable *tab)
+void
+rt_dump_hooks(rtable *t)
+{
+ RT_LOCK(t);
+ rtable_private *tab = RT_PRIV(t);
+ debug("Dump of hooks in routing table <%s>%s\n", tab->name, tab->delete ? " (deleted)" : "");
+ debug(" nhu_state=%u hcu_scheduled=%u use_count=%d rt_count=%u\n",
+ atomic_load(&tab->nhu_state), ev_active(tab->hcu_event), tab->use_count, tab->rt_count);
+ debug(" last_rt_change=%t gc_time=%t gc_counter=%d prune_state=%u\n",
+ tab->last_rt_change, tab->gc_time, tab->gc_counter, tab->prune_state);
+
+ struct rt_import_hook *ih;
+ WALK_LIST(ih, tab->imports)
+ {
+ ih->req->dump_req(ih->req);
+ debug(" Import hook %p requested by %p: pref=%u"
+ " last_state_change=%t import_state=%u stopped=%p\n",
+ ih, ih->req, ih->stats.pref,
+ ih->last_state_change, ih->import_state, ih->stopped);
+ }
+
+ struct rt_export_hook *eh;
+ WALK_LIST(eh, tab->exports)
+ {
+ eh->req->dump_req(eh->req);
+ debug(" Export hook %p requested by %p:"
+ " refeed_pending=%u last_state_change=%t export_state=%u\n",
+ eh, eh->req, eh->refeed_pending, eh->last_state_change, atomic_load_explicit(&eh->export_state, memory_order_relaxed));
+ }
+ debug("\n");
+ RT_UNLOCK(t);
+}
+
+void
+rt_dump_hooks_all(void)
{
- if (tab->hcu_scheduled)
- return;
+ rtable *t;
+ node *n;
- tab->hcu_scheduled = 1;
- ev_schedule(tab->rt_event);
+ debug("Dump of all table hooks\n");
+
+ WALK_LIST2(t, n, routing_tables, n)
+ rt_dump_hooks(t);
}
static inline void
rt_schedule_nhu(rtable *tab)
{
- if (tab->nhu_state == NHU_CLEAN)
- ev_schedule(tab->rt_event);
+ atomic_fetch_or_explicit(&tab->nhu_state, NHU_SCHEDULED, memory_order_acq_rel);
+ ev_send_loop(tab->loop, tab->nhu_event);
/* state change:
* NHU_CLEAN -> NHU_SCHEDULED
* NHU_RUNNING -> NHU_DIRTY
*/
- tab->nhu_state |= NHU_SCHEDULED;
}
void
-rt_schedule_prune(rtable *tab)
+rt_schedule_prune(rtable_private *tab)
{
if (tab->prune_state == 0)
- ev_schedule(tab->rt_event);
+ ev_send_loop(tab->loop, tab->prune_event);
/* state change 0->1, 2->3 */
tab->prune_state |= 1;
}
+static int
+rt_fast_prune_ready(rtable_private *tab)
+{
+ return EMPTY_LIST(tab->pending_exports) && EMPTY_LIST(tab->exports) && !tab->imports_up;
+}
static void
-rt_event(void *ptr)
+rt_fast_prune_check(rtable_private *tab)
{
- rtable *tab = ptr;
-
- rt_lock_table(tab);
-
- if (tab->hcu_scheduled)
- rt_update_hostcache(tab);
-
- if (tab->nhu_state)
- rt_next_hop_update(tab);
+ if (tab->delete && rt_fast_prune_ready(tab))
+ {
+ tab->prune_state |= 1;
+ ev_send_loop(tab->loop, tab->prune_event);
+ }
+}
- if (tab->prune_state)
- rt_prune_table(tab);
+void
+rt_export_used(rtable_private *tab)
+{
+ if (config->table_debug)
+ log(L_TRACE "%s: Export cleanup requested", tab->name);
- rt_unlock_table(tab);
+ ev_send_loop(tab->loop, tab->ec_event);
}
-
static inline btime
-rt_settled_time(rtable *tab)
+rt_settled_time(rtable_private *tab)
{
ASSUME(tab->base_settle_time != 0);
- return MIN(tab->last_rt_change + tab->config->min_settle_time,
- tab->base_settle_time + tab->config->max_settle_time);
+ btime min_settle_time = tab->rr_count ? tab->config->min_rr_settle_time : tab->config->min_settle_time;
+ btime max_settle_time = tab->rr_count ? tab->config->max_rr_settle_time : tab->config->max_settle_time;
+
+ DBG("settled time computed from %t %t %t %t as %t / %t, now is %t\n",
+ tab->name, tab->last_rt_change, min_settle_time,
+ tab->base_settle_time, max_settle_time,
+ tab->last_rt_change + min_settle_time,
+ tab->base_settle_time + max_settle_time, current_time());
+
+ return MIN(tab->last_rt_change + min_settle_time,
+ tab->base_settle_time + max_settle_time);
}
static void
rt_settle_timer(timer *t)
{
- rtable *tab = t->data;
+ rtable_private *tab = t->data;
+ ASSERT_DIE(birdloop_inside(tab->loop));
if (!tab->base_settle_time)
return;
@@ -1827,7 +2103,7 @@ rt_settle_timer(timer *t)
btime settled_time = rt_settled_time(tab);
if (current_time() < settled_time)
{
- tm_set(tab->settle_timer, settled_time);
+ tm_set_in(tab->settle_timer, settled_time, tab->loop);
return;
}
@@ -1836,11 +2112,11 @@ rt_settle_timer(timer *t)
struct rt_subscription *s;
WALK_LIST(s, tab->subscribers)
- s->hook(s);
+ ev_send(s->event->list, s->event);
}
static void
-rt_kick_settle_timer(rtable *tab)
+rt_kick_settle_timer(rtable_private *tab)
{
tab->base_settle_time = current_time();
@@ -1848,11 +2124,11 @@ rt_kick_settle_timer(rtable *tab)
tab->settle_timer = tm_new_init(tab->rp, rt_settle_timer, tab, 0, 0);
if (!tm_active(tab->settle_timer))
- tm_set(tab->settle_timer, rt_settled_time(tab));
+ tm_set_in(tab->settle_timer, rt_settled_time(tab), tab->loop);
}
static inline void
-rt_schedule_notify(rtable *tab)
+rt_schedule_notify(rtable_private *tab)
{
if (EMPTY_LIST(tab->subscribers))
return;
@@ -1864,33 +2140,40 @@ rt_schedule_notify(rtable *tab)
}
void
-rt_subscribe(rtable *tab, struct rt_subscription *s)
+rt_subscribe(rtable *t, struct rt_subscription *s)
{
- s->tab = tab;
- rt_lock_table(tab);
- add_tail(&tab->subscribers, &s->n);
+ s->tab = t;
+ RT_LOCKED(t, tab)
+ {
+ rt_lock_table(tab);
+ DBG("rt_subscribe(%s)\n", tab->name);
+ add_tail(&tab->subscribers, &s->n);
+ }
}
void
rt_unsubscribe(struct rt_subscription *s)
{
- rem_node(&s->n);
- rt_unlock_table(s->tab);
+ RT_LOCKED(s->tab, tab)
+ {
+ rem_node(&s->n);
+ if (EMPTY_LIST(tab->subscribers) && tm_active(tab->settle_timer))
+ tm_stop(tab->settle_timer);
+ rt_unlock_table(tab);
+ }
}
static void
rt_free(resource *_r)
{
- rtable *r = (rtable *) _r;
+ rtable_private *r = (rtable_private *) _r;
DBG("Deleting routing table %s\n", r->name);
ASSERT_DIE(r->use_count == 0);
-
- if (r->internal)
- return;
-
- r->config->table = NULL;
- rem_node(&r->n);
+ ASSERT_DIE(r->rt_count == 0);
+ ASSERT_DIE(!r->cork_active);
+ ASSERT_DIE(EMPTY_LIST(r->imports));
+ ASSERT_DIE(EMPTY_LIST(r->exports));
if (r->hostcache)
rt_free_hostcache(r);
@@ -1907,14 +2190,14 @@ rt_free(resource *_r)
static void
rt_res_dump(resource *_r)
{
- rtable *r = (rtable *) _r;
+ rtable_private *r = RT_PRIV((rtable *) _r);
debug("name \"%s\", addr_type=%s, rt_count=%u, use_count=%d\n",
r->name, net_label[r->addr_type], r->rt_count, r->use_count);
}
static struct resclass rt_class = {
.name = "Routing table",
- .size = sizeof(struct rtable),
+ .size = sizeof(rtable_private),
.free = rt_free,
.dump = rt_res_dump,
.lookup = NULL,
@@ -1928,11 +2211,16 @@ rt_setup(pool *pp, struct rtable_config *cf)
void *nb = mb_alloc(pp, ns);
ASSERT_DIE(ns - 1 == bsnprintf(nb, ns, "Routing table %s", cf->name));
- pool *p = rp_new(pp, nb);
- mb_move(nb, p);
+ struct birdloop *l = birdloop_new(pp, DOMAIN_ORDER(rtable), nb);
+ pool *p = birdloop_pool(l);
+
+ birdloop_enter(l);
- rtable *t = ralloc(p, &rt_class);
+ rtable_private *t = ralloc(p, &rt_class);
t->rp = p;
+ t->loop = l;
+
+ t->rte_slab = sl_new(p, sizeof(struct rte_storage));
t->name = cf->name;
t->config = cf;
@@ -1940,19 +2228,35 @@ rt_setup(pool *pp, struct rtable_config *cf)
fib_init(&t->fib, p, t->addr_type, sizeof(net), OFFSETOF(net, n), 0, NULL);
- if (!(t->internal = cf->internal))
- {
- init_list(&t->channels);
- hmap_init(&t->id_map, p, 1024);
- hmap_set(&t->id_map, 0);
+ init_list(&t->imports);
+ init_list(&t->exports);
- init_list(&t->subscribers);
+ hmap_init(&t->id_map, p, 1024);
+ hmap_set(&t->id_map, 0);
- t->rt_event = ev_new_init(p, rt_event, t);
- t->last_rt_change = t->gc_time = current_time();
- }
+ init_list(&t->pending_exports);
+ init_list(&t->subscribers);
+
+ t->announce_event = ev_new_init(p, rt_announce_exports, t);
+ t->ec_event = ev_new_init(p, rt_export_cleanup, t);
+ t->prune_event = ev_new_init(p, rt_prune_table, t);
+ t->hcu_event = ev_new_init(p, rt_update_hostcache, t);
+ t->nhu_event = ev_new_init(p, rt_next_hop_update, t);
+
+ t->nhu_event->cork = &rt_cork;
+ t->prune_event->cork = &rt_cork;
+
+ t->last_rt_change = t->gc_time = current_time();
+ t->next_export_seq = 1;
- return t;
+ t->rl_pipe = (struct tbf) TBF_DEFAULT_LOG_LIMITS;
+
+ t->nhu_lp = lp_new_default(p);
+
+ mb_move(nb, p);
+ birdloop_leave(l);
+
+ return (rtable *) t;
}
/**
@@ -1965,13 +2269,11 @@ void
rt_init(void)
{
rta_init();
- rt_table_pool = rp_new(&root_pool, "Routing tables");
- rte_update_pool = lp_new_default(rt_table_pool);
- rte_slab = sl_new(rt_table_pool, sizeof(rte));
+ rt_table_pool = rp_new(&root_pool, &main_birdloop, "Routing tables");
init_list(&routing_tables);
+ ev_init_cork(&rt_cork, "Route Table Cork");
}
-
/**
* rt_prune_table - prune a routing table
*
@@ -1987,12 +2289,15 @@ rt_init(void)
* iteration.
*/
static void
-rt_prune_table(rtable *tab)
+rt_prune_table(void *data)
{
+ rtable_private *tab = data;
+ ASSERT_DIE(birdloop_inside(tab->loop));
+
struct fib_iterator *fit = &tab->prune_fit;
- int limit = 512;
+ int limit = tab->delete ? 16384 : 512;
- struct channel *c;
+ struct rt_import_hook *ih;
node *n, *x;
DBG("Pruning route table %s\n", tab->name);
@@ -2003,12 +2308,24 @@ rt_prune_table(rtable *tab)
if (tab->prune_state == 0)
return;
+ if (tab->delete && !rt_fast_prune_ready(tab))
+ return;
+
+ rt_lock_table(tab);
+
if (tab->prune_state == 1)
{
/* Mark channels to flush */
- WALK_LIST2(c, n, tab->channels, table_node)
- if (c->channel_state == CS_FLUSHING)
- c->flush_active = 1;
+ WALK_LIST2(ih, n, tab->imports, n)
+ if (ih->import_state == TIS_STOP)
+ rt_set_import_state(ih, TIS_FLUSHING);
+ else if ((ih->stale_valid != ih->stale_pruning) && (ih->stale_pruning == ih->stale_pruned))
+ {
+ ih->stale_pruning = ih->stale_valid;
+
+ if (ih->req->trace_routes & D_STATES)
+ log(L_TRACE "%s: table prune after refresh begin [%u]", ih->req->name, ih->stale_pruning);
+ }
FIB_ITERATE_INIT(fit, &tab->fib);
tab->prune_state = 2;
@@ -2017,43 +2334,53 @@ rt_prune_table(rtable *tab)
again:
FIB_ITERATE_START(&tab->fib, fit, net, n)
{
- rte *e;
-
- rescan:
- for (e=n->routes; e; e=e->next)
+ if (tab->delete)
{
- if (e->sender->flush_active || (e->flags & REF_DISCARD))
- {
- if (limit <= 0)
- {
- FIB_ITERATE_PUT(fit);
- ev_schedule(tab->rt_event);
- return;
- }
+ ASSERT_DIE(!n->first);
- rte_discard(e);
- limit--;
+ for (struct rte_storage *e = n->routes, *next; e; e = next)
+ {
+ next = e->next;
- goto rescan;
- }
+ struct rt_import_request *req = e->rte.sender->req;
+ if (req->preimport)
+ req->preimport(req, NULL, &e->rte);
- if (e->flags & REF_MODIFY)
+ tab->rt_count--;
+ hmap_clear(&tab->id_map, e->rte.id);
+ rte_free(e, tab);
+ limit--;
+ }
+
+ n->routes = NULL;
+ }
+ else
+ rescan:
+ for (struct rte_storage *e=n->routes; e; e=e->next)
+ {
+ struct rt_import_hook *s = e->rte.sender;
+
+ if ((s->import_state == TIS_FLUSHING) ||
+ (e->rte.stale_cycle < s->stale_valid) ||
+ (e->rte.stale_cycle > s->stale_set))
{
if (limit <= 0)
{
FIB_ITERATE_PUT(fit);
- ev_schedule(tab->rt_event);
+ ev_send_loop(tab->loop, tab->prune_event);
+ ev_send_loop(tab->loop, tab->announce_event);
+ rt_unlock_table(tab);
return;
}
- rte_modify(e);
+ rte_recalculate(tab, e->rte.sender, n, NULL, e->rte.src);
limit--;
goto rescan;
}
}
- if (!n->routes) /* Orphaned FIB entry */
+ if (!n->routes && !n->first) /* Orphaned FIB entry */
{
FIB_ITERATE_PUT(fit);
fib_delete(&tab->fib, n);
@@ -2070,23 +2397,205 @@ again:
tab->gc_time = current_time();
/* state change 2->0, 3->1 */
- tab->prune_state &= 1;
-
- if (tab->prune_state > 0)
- ev_schedule(tab->rt_event);
+ if (tab->prune_state &= 1)
+ ev_send_loop(tab->loop, tab->prune_event);
- /* FIXME: This should be handled in a better way */
- rt_prune_sources();
+ uint flushed_channels = 0;
/* Close flushed channels */
- WALK_LIST2_DELSAFE(c, n, x, tab->channels, table_node)
- if (c->flush_active)
+ WALK_LIST2_DELSAFE(ih, n, x, tab->imports, n)
+ if (ih->import_state == TIS_FLUSHING)
+ {
+ ih->flush_seq = tab->next_export_seq;
+ rt_set_import_state(ih, TIS_WAITING);
+ flushed_channels++;
+ }
+ else if (ih->stale_pruning != ih->stale_pruned)
+ {
+ ih->stale_pruned = ih->stale_pruning;
+
+ if (ih->req->trace_routes & D_STATES)
+ log(L_TRACE "%s: table prune after refresh end [%u]", ih->req->name, ih->stale_pruned);
+ }
+
+ /* In some cases, we may want to directly proceed to export cleanup */
+ if (EMPTY_LIST(tab->exports) && flushed_channels)
+ rt_export_cleanup(tab);
+
+ ev_send_loop(tab->loop, tab->announce_event);
+ rt_unlock_table(tab);
+}
+
+static void
+rt_export_cleanup(void *data)
+{
+ rtable_private *tab = data;
+ ASSERT_DIE(birdloop_inside(tab->loop));
+
+ u64 min_seq = ~((u64) 0);
+ struct rt_pending_export *last_export_to_free = NULL;
+ struct rt_pending_export *first_export = tab->first_export;
+
+ struct rt_export_hook *eh;
+ node *n;
+ WALK_LIST2(eh, n, tab->exports, n)
+ {
+ switch (atomic_load_explicit(&eh->export_state, memory_order_acquire))
+ {
+ case TES_DOWN:
+ case TES_HUNGRY:
+ continue;
+
+ case TES_READY:
+ {
+ struct rt_pending_export *last = atomic_load_explicit(&eh->last_export, memory_order_acquire);
+ if (!last)
+ /* No last export means that the channel has exported nothing since last cleanup */
+ goto done;
+
+ else if (min_seq > last->seq)
+ {
+ min_seq = last->seq;
+ last_export_to_free = last;
+ }
+ continue;
+ }
+
+ default:
+ /* It's only safe to cleanup when the export state is idle or regular. No feeding or stopping allowed. */
+ goto done;
+ }
+ }
+
+ tab->first_export = last_export_to_free ? rt_next_export_fast(last_export_to_free) : NULL;
+
+ if (config->table_debug)
+ log(L_TRACE "%s: Export cleanup, old first_export seq %lu, new %lu, min_seq %ld",
+ tab->name,
+ first_export ? first_export->seq : 0,
+ tab->first_export ? tab->first_export->seq : 0,
+ min_seq);
+
+ WALK_LIST2(eh, n, tab->exports, n)
+ {
+ if (atomic_load_explicit(&eh->export_state, memory_order_acquire) != TES_READY)
+ continue;
+
+ struct rt_pending_export *last = atomic_load_explicit(&eh->last_export, memory_order_acquire);
+ if (last == last_export_to_free)
+ {
+ /* This may fail when the channel managed to export more inbetween. This is OK. */
+ atomic_compare_exchange_strong_explicit(
+ &eh->last_export, &last, NULL,
+ memory_order_release,
+ memory_order_relaxed);
+
+ DBG("store hook=%p last_export=NULL\n", eh);
+ }
+ }
+
+ while (first_export && (first_export->seq <= min_seq))
+ {
+ ASSERT_DIE(first_export->new || first_export->old);
+
+ const net_addr *n = first_export->new ?
+ first_export->new->rte.net :
+ first_export->old->rte.net;
+ net *net = SKIP_BACK(struct network, n.addr, (net_addr (*)[0]) n);
+
+ ASSERT_DIE(net->first == first_export);
+
+ if (first_export == net->last)
+ /* The only export here */
+ net->last = net->first = NULL;
+ else
+ /* First is now the next one */
+ net->first = atomic_load_explicit(&first_export->next, memory_order_relaxed);
+
+ /* For now, the old route may be finally freed */
+ if (first_export->old)
+ {
+ rt_rte_trace_in(D_ROUTES, first_export->old->rte.sender->req, &first_export->old->rte, "freed");
+ hmap_clear(&tab->id_map, first_export->old->rte.id);
+ rte_free(first_export->old, tab);
+ }
+
+#ifdef LOCAL_DEBUG
+ memset(first_export, 0xbd, sizeof(struct rt_pending_export));
+#endif
+
+ struct rt_export_block *reb = HEAD(tab->pending_exports);
+ ASSERT_DIE(reb == PAGE_HEAD(first_export));
+
+ u32 pos = (first_export - &reb->export[0]);
+ u32 end = atomic_load_explicit(&reb->end, memory_order_relaxed);
+ ASSERT_DIE(pos < end);
+
+ struct rt_pending_export *next = NULL;
+
+ if (++pos < end)
+ next = &reb->export[pos];
+ else
+ {
+ rem_node(&reb->n);
+
+#ifdef LOCAL_DEBUG
+ memset(reb, 0xbe, page_size);
+#endif
+
+ free_page(reb);
+
+ if (EMPTY_LIST(tab->pending_exports))
+ {
+ if (config->table_debug)
+ log(L_TRACE "%s: Resetting export seq", tab->name);
+
+ node *n;
+ WALK_LIST2(eh, n, tab->exports, n)
+ {
+ if (atomic_load_explicit(&eh->export_state, memory_order_acquire) != TES_READY)
+ continue;
+
+ ASSERT_DIE(atomic_load_explicit(&eh->last_export, memory_order_acquire) == NULL);
+ bmap_reset(&eh->seq_map, 1024);
+ }
+
+ tab->next_export_seq = 1;
+ }
+ else
{
- c->flush_active = 0;
- channel_set_state(c, CS_DOWN);
+ reb = HEAD(tab->pending_exports);
+ next = &reb->export[0];
}
+ }
- return;
+ first_export = next;
+ }
+
+done:;
+ struct rt_import_hook *ih; node *x;
+ WALK_LIST2_DELSAFE(ih, n, x, tab->imports, n)
+ if (ih->import_state == TIS_WAITING)
+ if (!first_export || (first_export->seq >= ih->flush_seq))
+ {
+ ih->import_state = TIS_CLEARED;
+ ev_send(ih->req->list, ih->export_announce_event);
+ }
+
+ if (EMPTY_LIST(tab->pending_exports) && ev_active(tab->announce_event))
+ ev_postpone(tab->announce_event);
+
+ /* If reduced to at most one export block pending */
+ if (tab->cork_active &&
+ ((!tab->first_export) || (tab->first_export->seq + 128 > tab->next_export_seq)))
+ {
+ tab->cork_active = 0;
+ ev_uncork(&rt_cork);
+ if (config->table_debug)
+ log(L_TRACE "%s: cork released", tab->name);
+ }
+
+ rt_fast_prune_check(tab);
}
void
@@ -2120,7 +2629,7 @@ rta_next_hop_outdated(rta *a)
}
void
-rta_apply_hostentry(rta *a, struct hostentry *he, mpls_label_stack *mls)
+rta_apply_hostentry(rta *a, struct hostentry *he, mpls_label_stack *mls, linpool *lp)
{
a->hostentry = he;
a->dest = he->dest;
@@ -2155,7 +2664,7 @@ no_nexthop:
else
{
nhr = nhp;
- nhp = (nhp ? (nhp->next = lp_alloc(rte_update_pool, NEXTHOP_MAX_SIZE)) : &(a->nh));
+ nhp = (nhp ? (nhp->next = lp_alloc(lp, NEXTHOP_MAX_SIZE)) : &(a->nh));
}
memset(nhp, 0, NEXTHOP_MAX_SIZE);
@@ -2210,8 +2719,8 @@ no_nexthop:
}
}
-static inline rte *
-rt_next_hop_update_rte(rtable *tab UNUSED, rte *old)
+static inline struct rte_storage *
+rt_next_hop_update_rte(rtable_private *tab, net *n, rte *old)
{
rta *a = alloca(RTA_MAX_SIZE);
memcpy(a, old->attrs, rta_size(old->attrs));
@@ -2219,58 +2728,71 @@ rt_next_hop_update_rte(rtable *tab UNUSED, rte *old)
mpls_label_stack mls = { .len = a->nh.labels_orig };
memcpy(mls.stack, &a->nh.label[a->nh.labels - mls.len], mls.len * sizeof(u32));
- rta_apply_hostentry(a, old->attrs->hostentry, &mls);
- a->aflags = 0;
+ rta_apply_hostentry(a, old->attrs->hostentry, &mls, tab->nhu_lp);
+ a->cached = 0;
- rte *e = sl_alloc(rte_slab);
- memcpy(e, old, sizeof(rte));
- e->attrs = rta_lookup(a);
+ rte e0 = *old;
+ e0.attrs = rta_lookup(a);
- return e;
+ return rte_store(&e0, n, tab);
}
static inline int
-rt_next_hop_update_net(rtable *tab, net *n)
+rt_next_hop_update_net(rtable_private *tab, net *n)
{
- rte **k, *e, *new, *old_best, **new_best;
+ struct rte_storage *new;
int count = 0;
- int free_old_best = 0;
- old_best = n->routes;
+ struct rte_storage *old_best = n->routes;
if (!old_best)
return 0;
- for (k = &n->routes; e = *k; k = &e->next)
- if (rta_next_hop_outdated(e->attrs))
- {
- new = rt_next_hop_update_rte(tab, e);
- *k = new;
+ for (struct rte_storage *e, **k = &n->routes; e = *k; k = &e->next)
+ if (rta_next_hop_outdated(e->rte.attrs))
+ count++;
+
+ if (!count)
+ return 0;
- rte_trace_in(D_ROUTES, new->sender, new, "updated");
- rte_announce_i(tab, RA_ANY, n, new, e, NULL, NULL);
+ struct rte_multiupdate {
+ struct rte_storage *old, *new;
+ } *updates = alloca(sizeof(struct rte_multiupdate) * count);
+
+ int pos = 0;
+ for (struct rte_storage *e, **k = &n->routes; e = *k; k = &e->next)
+ if (rta_next_hop_outdated(e->rte.attrs))
+ {
+ struct rte_storage *new = rt_next_hop_update_rte(tab, n, &e->rte);
/* Call a pre-comparison hook */
/* Not really an efficient way to compute this */
- if (e->attrs->src->proto->rte_recalculate)
- e->attrs->src->proto->rte_recalculate(tab, n, new, e, NULL);
+ if (e->rte.src->owner->rte_recalculate)
+ e->rte.src->owner->rte_recalculate(tab, n, &new->rte, &e->rte, &old_best->rte);
- if (e != old_best)
- rte_free_quick(e);
- else /* Freeing of the old best rte is postponed */
- free_old_best = 1;
+ updates[pos++] = (struct rte_multiupdate) {
+ .old = e,
+ .new = new,
+ };
- e = new;
- count++;
+ /* Replace the route in the list */
+ new->next = e->next;
+ *k = e = new;
+
+ /* Get a new ID for the route */
+ new->rte.lastmod = current_time();
+ new->rte.id = hmap_first_zero(&tab->id_map);
+ hmap_set(&tab->id_map, new->rte.id);
+
+ lp_flush(tab->nhu_lp);
}
- if (!count)
- return 0;
+ ASSERT_DIE(pos == count);
/* Find the new best route */
- new_best = NULL;
- for (k = &n->routes; e = *k; k = &e->next)
+ struct rte_storage **new_best = NULL;
+ for (struct rte_storage *e, **k = &n->routes; e = *k; k = &e->next)
{
- if (!new_best || rte_better(e, *new_best))
+ if (!new_best || rte_better(&e->rte, &(*new_best)->rte))
new_best = k;
}
@@ -2283,32 +2805,39 @@ rt_next_hop_update_net(rtable *tab, net *n)
n->routes = new;
}
- /* Announce the new best route */
- if (new != old_best)
- rte_trace_in(D_ROUTES, new->sender, new, "updated [best]");
-
- /* Propagate changes */
- rte_announce_i(tab, RA_UNDEF, n, NULL, NULL, n->routes, old_best);
-
- if (free_old_best)
- rte_free_quick(old_best);
+ /* Announce the changes */
+ for (int i=0; i<count; i++)
+ {
+ _Bool nb = (new == updates[i].new), ob = (old_best == updates[i].old);
+ const char *best_indicator[2][2] = {
+ { "autoupdated", "autoupdated [-best]" },
+ { "autoupdated [+best]", "autoupdated [best]" }
+ };
+ rt_rte_trace_in(D_ROUTES, updates[i].new->rte.sender->req, &updates[i].new->rte, best_indicator[nb][ob]);
+ rte_announce(tab, n, updates[i].new, updates[i].old, new, old_best);
+ }
return count;
}
static void
-rt_next_hop_update(rtable *tab)
+rt_next_hop_update(void *data)
{
+ rtable_private *tab = data;
+ ASSERT_DIE(birdloop_inside(tab->loop));
+
struct fib_iterator *fit = &tab->nhu_fit;
int max_feed = 32;
- if (tab->nhu_state == NHU_CLEAN)
+ if (atomic_load_explicit(&tab->nhu_state, memory_order_acquire) == NHU_CLEAN)
return;
- if (tab->nhu_state == NHU_SCHEDULED)
+ rt_lock_table(tab);
+
+ if (atomic_load_explicit(&tab->nhu_state, memory_order_acquire) == NHU_SCHEDULED)
{
FIB_ITERATE_INIT(fit, &tab->fib);
- tab->nhu_state = NHU_RUNNING;
+ ASSERT_DIE(atomic_exchange_explicit(&tab->nhu_state, NHU_RUNNING, memory_order_acq_rel) == NHU_SCHEDULED);
}
FIB_ITERATE_START(&tab->fib, fit, net, n)
@@ -2316,7 +2845,8 @@ rt_next_hop_update(rtable *tab)
if (max_feed <= 0)
{
FIB_ITERATE_PUT(fit);
- ev_schedule(tab->rt_event);
+ ev_send_loop(tab->loop, tab->nhu_event);
+ rt_unlock_table(tab);
return;
}
max_feed -= rt_next_hop_update_net(tab, n);
@@ -2327,10 +2857,12 @@ rt_next_hop_update(rtable *tab)
* NHU_DIRTY -> NHU_SCHEDULED
* NHU_RUNNING -> NHU_CLEAN
*/
- tab->nhu_state &= 1;
+ if (atomic_fetch_and_explicit(&tab->nhu_state, NHU_SCHEDULED, memory_order_acq_rel) != NHU_RUNNING)
+ ev_send_loop(tab->loop, tab->nhu_event);
+
+ ev_send_loop(tab->loop, tab->announce_event);
- if (tab->nhu_state != NHU_CLEAN)
- ev_schedule(tab->rt_event);
+ rt_unlock_table(tab);
}
@@ -2352,6 +2884,10 @@ rt_new_table(struct symbol *s, uint addr_type)
c->gc_min_time = 5;
c->min_settle_time = 1 S;
c->max_settle_time = 20 S;
+ c->min_rr_settle_time = 30 S;
+ c->max_rr_settle_time = 90 S;
+ c->cork_limit = 4 * page_size / sizeof(struct rt_pending_export);
+ c->owner = new_config;
add_tail(&new_config->tables, &c->n);
@@ -2371,7 +2907,7 @@ rt_new_table(struct symbol *s, uint addr_type)
* configuration.
*/
void
-rt_lock_table(rtable *r)
+rt_lock_table(rtable_private *r)
{
r->use_count++;
}
@@ -2385,16 +2921,11 @@ rt_lock_table(rtable *r)
* for deletion by configuration changes.
*/
void
-rt_unlock_table(rtable *r)
+rt_unlock_table(rtable_private *r)
{
- if (!--r->use_count && r->deleted)
- {
- struct config *conf = r->deleted;
-
- /* Delete the routing table by freeing its pool */
- rt_shutdown(r);
- config_del_obstacle(conf);
- }
+ if (!--r->use_count && r->delete &&
+ !r->prune_state && !atomic_load_explicit(&r->nhu_state, memory_order_acquire))
+ birdloop_stop_self(r->loop, r->delete, r);
}
static struct rtable_config *
@@ -2404,6 +2935,21 @@ rt_find_table_config(struct config *cf, char *name)
return (sym && (sym->class == SYM_TABLE)) ? sym->table : NULL;
}
+static void
+rt_done(void *data)
+{
+ RT_LOCKED((rtable *) data, t)
+ {
+ struct rtable_config *tc = t->config;
+ struct config *c = tc->owner;
+
+ tc->table = NULL;
+ rem_node(&t->n);
+
+ config_del_obstacle(c);
+ }
+}
+
/**
* rt_commit - commit new routing table configuration
* @new: new configuration
@@ -2426,14 +2972,15 @@ rt_commit(struct config *new, struct config *old)
{
WALK_LIST(o, old->tables)
{
- rtable *ot = o->table;
- if (!ot->deleted)
+ RT_LOCK(o->table);
+ rtable_private *ot = RT_PRIV(o->table);
+ if (!ot->delete)
{
r = rt_find_table_config(new, o->name);
if (r && (r->addr_type == o->addr_type) && !new->shutdown)
{
DBG("\t%s: same\n", o->name);
- r->table = ot;
+ r->table = (rtable *) ot;
ot->name = r->name;
ot->config = r;
if (o->sorted != r->sorted)
@@ -2442,12 +2989,13 @@ rt_commit(struct config *new, struct config *old)
else
{
DBG("\t%s: deleted\n", o->name);
- ot->deleted = old;
- config_add_obstacle(old);
rt_lock_table(ot);
+ ot->delete = rt_done;
+ config_add_obstacle(old);
rt_unlock_table(ot);
}
}
+ RT_UNLOCK(o->table);
}
}
@@ -2461,19 +3009,6 @@ rt_commit(struct config *new, struct config *old)
DBG("\tdone\n");
}
-static inline void
-do_feed_channel(struct channel *c, net *n, rte *e)
-{
- rte_update_lock();
- if (c->ra_mode == RA_ACCEPTED)
- rt_notify_accepted(c, n, NULL, NULL, c->refeeding);
- else if (c->ra_mode == RA_MERGED)
- rt_notify_merged(c, n, NULL, NULL, e, e, c->refeeding);
- else /* RA_BASIC */
- rt_notify_basic(c, n, e, e, c->refeeding);
- rte_update_unlock();
-}
-
/**
* rt_feed_channel - advertise all routes to a channel
* @c: channel to be fed
@@ -2483,370 +3018,120 @@ do_feed_channel(struct channel *c, net *n, rte *e)
* has something to do. (We avoid transferring all the routes in single pass in
* order not to monopolize CPU time.)
*/
-int
-rt_feed_channel(struct channel *c)
+static void
+rt_feed_channel(void *data)
{
+ struct rt_export_hook *c = data;
+
struct fib_iterator *fit = &c->feed_fit;
int max_feed = 256;
- ASSERT(c->export_state == ES_FEEDING);
-
- if (!c->feed_active)
- {
- FIB_ITERATE_INIT(fit, &c->table->fib);
- c->feed_active = 1;
- }
-
- FIB_ITERATE_START(&c->table->fib, fit, net, n)
- {
- rte *e = n->routes;
- if (max_feed <= 0)
- {
- FIB_ITERATE_PUT(fit);
- return 0;
- }
-
- if ((c->ra_mode == RA_OPTIMAL) ||
- (c->ra_mode == RA_ACCEPTED) ||
- (c->ra_mode == RA_MERGED))
- if (rte_is_valid(e))
- {
- /* In the meantime, the protocol may fell down */
- if (c->export_state != ES_FEEDING)
- goto done;
-
- do_feed_channel(c, n, e);
- max_feed--;
- }
-
- if (c->ra_mode == RA_ANY)
- for(e = n->routes; e; e = e->next)
- {
- /* In the meantime, the protocol may fell down */
- if (c->export_state != ES_FEEDING)
- goto done;
-
- if (!rte_is_valid(e))
- continue;
-
- do_feed_channel(c, n, e);
- max_feed--;
- }
- }
- FIB_ITERATE_END;
-
-done:
- c->feed_active = 0;
- return 1;
-}
-
-/**
- * rt_feed_baby_abort - abort protocol feeding
- * @c: channel
- *
- * This function is called by the protocol code when the protocol stops or
- * ceases to exist during the feeding.
- */
-void
-rt_feed_channel_abort(struct channel *c)
-{
- if (c->feed_active)
- {
- /* Unlink the iterator */
- fit_get(&c->table->fib, &c->feed_fit);
- c->feed_active = 0;
- }
-}
-
-
-/*
- * Import table
- */
-
-int
-rte_update_in(struct channel *c, const net_addr *n, rte *new, struct rte_src *src)
-{
- struct rtable *tab = c->in_table;
- rte *old, **pos;
- net *net;
-
- if (new)
+ rtable_private *tab;
+ if (c->export_state == TES_HUNGRY)
{
- net = net_get(tab, n);
+ rt_set_export_state(c, TES_FEEDING);
- if (!new->pref)
- new->pref = c->preference;
+ tab = RT_LOCK(c->table);
- if (!rta_is_cached(new->attrs))
- new->attrs = rta_lookup(new->attrs);
+ struct rt_pending_export *rpe = rt_last_export(tab);
+ DBG("store hook=%p last_export=%p seq=%lu\n", c, rpe, rpe ? rpe->seq : 0);
+ atomic_store_explicit(&c->last_export, rpe, memory_order_relaxed);
+
+ FIB_ITERATE_INIT(&c->feed_fit, &tab->fib);
}
else
- {
- net = net_find(tab, n);
+ tab = RT_LOCK(c->table);
- if (!net)
- goto drop_withdraw;
- }
+ ASSERT_DIE(c->export_state == TES_FEEDING);
- /* Find the old rte */
- for (pos = &net->routes; old = *pos; pos = &old->next)
- if (old->attrs->src == src)
+redo:
+ FIB_ITERATE_START(&tab->fib, fit, net, n)
{
- if (new && rte_same(old, new))
- {
- /* Refresh the old rte, continue with update to main rtable */
- if (old->flags & (REF_STALE | REF_DISCARD | REF_MODIFY))
+ if (max_feed <= 0)
{
- old->flags &= ~(REF_STALE | REF_DISCARD | REF_MODIFY);
- return 1;
- }
-
- goto drop_update;
- }
-
- /* Move iterator if needed */
- if (old == c->reload_next_rte)
- c->reload_next_rte = old->next;
-
- /* Remove the old rte */
- *pos = old->next;
- rte_free_quick(old);
- tab->rt_count--;
-
- break;
- }
-
- if (!new)
- {
- if (!old)
- goto drop_withdraw;
-
- if (!net->routes)
- fib_delete(&tab->fib, net);
-
- return 1;
- }
-
- struct channel_limit *l = &c->rx_limit;
- if (l->action && !old)
- {
- if (tab->rt_count >= l->limit)
- channel_notify_limit(c, l, PLD_RX, tab->rt_count);
-
- if (l->state == PLS_BLOCKED)
- {
- /* Required by rte_trace_in() */
- new->net = net;
-
- rte_trace_in(D_FILTERS, c, new, "ignored [limit]");
- goto drop_update;
- }
- }
-
- /* Insert the new rte */
- rte *e = rte_do_cow(new);
- e->flags |= REF_COW;
- e->net = net;
- e->sender = c;
- e->lastmod = current_time();
- e->next = *pos;
- *pos = e;
- tab->rt_count++;
- return 1;
-
-drop_update:
- c->stats.imp_updates_received++;
- c->stats.imp_updates_ignored++;
- rte_free(new);
-
- if (!net->routes)
- fib_delete(&tab->fib, net);
-
- return 0;
-
-drop_withdraw:
- c->stats.imp_withdraws_received++;
- c->stats.imp_withdraws_ignored++;
- return 0;
-}
-
-int
-rt_reload_channel(struct channel *c)
-{
- struct rtable *tab = c->in_table;
- struct fib_iterator *fit = &c->reload_fit;
- int max_feed = 64;
-
- ASSERT(c->channel_state == CS_UP);
+ FIB_ITERATE_PUT(fit);
+ rt_send_export_event(c);
- if (!c->reload_active)
- {
- FIB_ITERATE_INIT(fit, &tab->fib);
- c->reload_active = 1;
- }
+ RT_UNLOCK(c->table);
+ return;
+ }
- do {
- for (rte *e = c->reload_next_rte; e; e = e->next)
- {
- if (max_feed-- <= 0)
+ if (atomic_load_explicit(&c->export_state, memory_order_acquire) != TES_FEEDING)
{
- c->reload_next_rte = e;
- debug("%s channel reload burst split (max_feed=%d)", c->proto->name, max_feed);
- return 0;
+ RT_UNLOCK(c->table);
+ return;
}
- rte_update2(c, e->net->n.addr, rte_do_cow(e), e->attrs->src);
- }
-
- c->reload_next_rte = NULL;
-
- FIB_ITERATE_START(&tab->fib, fit, net, n)
- {
- if (c->reload_next_rte = n->routes)
+ if (!n->routes || !rte_is_valid(&n->routes->rte))
+ ; /* if no route, do nothing */
+ else if (c->req->export_bulk)
{
- FIB_ITERATE_PUT_NEXT(fit, &tab->fib);
- break;
- }
- }
- FIB_ITERATE_END;
- }
- while (c->reload_next_rte);
-
- c->reload_active = 0;
- return 1;
-}
-
-void
-rt_reload_channel_abort(struct channel *c)
-{
- if (c->reload_active)
- {
- /* Unlink the iterator */
- fit_get(&c->in_table->fib, &c->reload_fit);
- c->reload_next_rte = NULL;
- c->reload_active = 0;
- }
-}
-
-void
-rt_prune_sync(rtable *t, int all)
-{
- struct fib_iterator fit;
-
- FIB_ITERATE_INIT(&fit, &t->fib);
+ uint count = rte_feed_count(n);
+ if (count)
+ {
+ rte **feed = alloca(count * sizeof(rte *));
+ rte_feed_obtain(n, feed, count);
-again:
- FIB_ITERATE_START(&t->fib, &fit, net, n)
- {
- rte *e, **ee = &n->routes;
+ struct rt_pending_export *rpe_last, *rpe_first = n->first;
+ for (struct rt_pending_export *rpe = rpe_first; rpe; rpe = rpe_next(rpe, NULL))
+ rpe_last = rpe;
- while (e = *ee)
- {
- if (all || (e->flags & (REF_STALE | REF_DISCARD)))
- {
- *ee = e->next;
- rte_free_quick(e);
- t->rt_count--;
- }
- else
- ee = &e->next;
- }
+ FIB_ITERATE_PUT_NEXT(fit, &tab->fib);
+ RT_UNLOCK(c->table);
- if (all || !n->routes)
- {
- FIB_ITERATE_PUT(&fit);
- fib_delete(&t->fib, n);
- goto again;
- }
- }
- FIB_ITERATE_END;
-}
+ c->req->export_bulk(c->req, n->n.addr, NULL, feed, count);
+ RT_LOCK(c->table);
-/*
- * Export table
- */
+ for (struct rt_pending_export *rpe = rpe_first; rpe; rpe = rpe_next(rpe, NULL))
+ {
+ rpe_mark_seen(c, rpe);
+ if (rpe == rpe_last)
+ break;
+ ASSERT_DIE(rpe->seq < rpe_last->seq);
+ }
-int
-rte_update_out(struct channel *c, const net_addr *n, rte *new, rte *old0, int refeed)
-{
- struct rtable *tab = c->out_table;
- struct rte_src *src;
- rte *old, **pos;
- net *net;
+ max_feed -= count;
- if (new)
- {
- net = net_get(tab, n);
- src = new->attrs->src;
+ goto redo;
+ }
+ }
+ else if (c->req->export_one)
+ {
+ struct rt_pending_export rpe = { .new = n->routes, .new_best = n->routes };
- rte_store_tmp_attrs(new, rte_update_pool, NULL);
+ struct rt_pending_export *rpe_last, *rpe_first = n->first;
+ for (struct rt_pending_export *rpe = rpe_first; rpe; rpe = rpe_next(rpe, NULL))
+ rpe_last = rpe;
- if (!rta_is_cached(new->attrs))
- new->attrs = rta_lookup(new->attrs);
- }
- else
- {
- net = net_find(tab, n);
- src = old0->attrs->src;
+ FIB_ITERATE_PUT_NEXT(fit, &tab->fib);
+ RT_UNLOCK(c->table);
- if (!net)
- goto drop_withdraw;
- }
+ c->req->export_one(c->req, n->n.addr, &rpe);
- /* Find the old rte */
- for (pos = &net->routes; old = *pos; pos = &old->next)
- if ((c->ra_mode != RA_ANY) || (old->attrs->src == src))
- {
- if (new && rte_same(old, new))
- {
- /* REF_STALE / REF_DISCARD not used in export table */
- /*
- if (old->flags & (REF_STALE | REF_DISCARD | REF_MODIFY))
+ RT_LOCK(c->table);
+ for (struct rt_pending_export *rpe = rpe_first; rpe; rpe = rpe_next(rpe, NULL))
{
- old->flags &= ~(REF_STALE | REF_DISCARD | REF_MODIFY);
- return 1;
+ rpe_mark_seen(c, rpe);
+ if (rpe == rpe_last)
+ break;
+ ASSERT_DIE(rpe->seq < rpe_last->seq);
}
- */
- goto drop_update;
+ max_feed--;
+ goto redo;
}
-
- /* Remove the old rte */
- *pos = old->next;
- rte_free_quick(old);
- tab->rt_count--;
-
- break;
+ else
+ bug("Export request must always provide an export method");
}
+ FIB_ITERATE_END;
- if (!new)
- {
- if (!old)
- goto drop_withdraw;
-
- if (!net->routes)
- fib_delete(&tab->fib, net);
-
- return 1;
- }
-
- /* Insert the new rte */
- rte *e = rte_do_cow(new);
- e->flags |= REF_COW;
- e->net = net;
- e->sender = c;
- e->lastmod = current_time();
- e->next = *pos;
- *pos = e;
- tab->rt_count++;
- return 1;
+ c->event->hook = rt_export_hook;
+ rt_send_export_event(c);
-drop_update:
- return refeed;
+ RT_UNLOCK(c->table);
-drop_withdraw:
- return 0;
+ rt_set_export_state(c, TES_READY);
}
@@ -2953,7 +3238,7 @@ hc_delete_hostentry(struct hostcache *hc, pool *p, struct hostentry *he)
}
static void
-rt_init_hostcache(rtable *tab)
+rt_init_hostcache(rtable_private *tab)
{
struct hostcache *hc = mb_allocz(tab->rp, sizeof(struct hostcache));
init_list(&hc->hostentries);
@@ -2969,7 +3254,7 @@ rt_init_hostcache(rtable *tab)
}
static void
-rt_free_hostcache(rtable *tab)
+rt_free_hostcache(rtable_private *tab)
{
struct hostcache *hc = tab->hostcache;
@@ -2992,13 +3277,13 @@ rt_free_hostcache(rtable *tab)
}
static void
-rt_notify_hostcache(rtable *tab, net *net)
+rt_notify_hostcache(rtable_private *tab, net *net)
{
- if (tab->hcu_scheduled)
+ if (ev_active(tab->hcu_event))
return;
if (trie_match_net(tab->hostcache->trie, net->n.addr))
- rt_schedule_hcu(tab);
+ ev_send_loop(tab->loop, tab->hcu_event);
}
static int
@@ -3021,41 +3306,17 @@ rt_get_igp_metric(rte *rt)
if (ea)
return ea->u.data;
- rta *a = rt->attrs;
-
-#ifdef CONFIG_OSPF
- if ((a->source == RTS_OSPF) ||
- (a->source == RTS_OSPF_IA) ||
- (a->source == RTS_OSPF_EXT1))
- return rt->u.ospf.metric1;
-#endif
-
-#ifdef CONFIG_RIP
- if (a->source == RTS_RIP)
- return rt->u.rip.metric;
-#endif
-
-#ifdef CONFIG_BGP
- if (a->source == RTS_BGP)
- {
- u64 metric = bgp_total_aigp_metric(rt);
- return (u32) MIN(metric, (u64) IGP_METRIC_UNKNOWN);
- }
-#endif
-
-#ifdef CONFIG_BABEL
- if (a->source == RTS_BABEL)
- return rt->u.babel.metric;
-#endif
-
- if (a->source == RTS_DEVICE)
+ if (rt->attrs->source == RTS_DEVICE)
return 0;
+ if (rt->src->owner->class->rte_igp_metric)
+ return rt->src->owner->class->rte_igp_metric(rt);
+
return IGP_METRIC_UNKNOWN;
}
static int
-rt_update_hostentry(rtable *tab, struct hostentry *he)
+rt_update_hostentry(rtable_private *tab, struct hostentry *he)
{
rta *old_src = he->src;
int direct = 0;
@@ -3072,11 +3333,12 @@ rt_update_hostentry(rtable *tab, struct hostentry *he)
net *n = net_route(tab, &he_addr);
if (n)
{
- rte *e = n->routes;
- rta *a = e->attrs;
- pxlen = n->n.addr->pxlen;
+ struct rte_storage *e = n->routes;
+ rta *a = e->rte.attrs;
+ word pref = a->pref;
- if (a->hostentry)
+ for (struct rte_storage *ee = n->routes; ee; ee = ee->next)
+ if ((ee->rte.attrs->pref >= pref) && ee->rte.attrs->hostentry)
{
/* Recursive route should not depend on another recursive route */
log(L_WARN "Next hop address %I resolvable through recursive route for %N",
@@ -3084,6 +3346,8 @@ rt_update_hostentry(rtable *tab, struct hostentry *he)
goto done;
}
+ pxlen = n->n.addr->pxlen;
+
if (a->dest == RTD_UNICAST)
{
for (struct nexthop *nh = &(a->nh); nh; nh = nh->next)
@@ -3104,7 +3368,7 @@ rt_update_hostentry(rtable *tab, struct hostentry *he)
he->src = rta_clone(a);
he->dest = a->dest;
he->nexthop_linkable = !direct;
- he->igp_metric = rt_get_igp_metric(e);
+ he->igp_metric = rt_get_igp_metric(&e->rte);
}
done:
@@ -3116,8 +3380,11 @@ done:
}
static void
-rt_update_hostcache(rtable *tab)
+rt_update_hostcache(void *data)
{
+ rtable_private *tab = data;
+ ASSERT_DIE(birdloop_inside(tab->loop));
+
struct hostcache *hc = tab->hostcache;
struct hostentry *he;
node *n, *x;
@@ -3138,15 +3405,15 @@ rt_update_hostcache(rtable *tab)
if (rt_update_hostentry(tab, he))
rt_schedule_nhu(he->tab);
}
-
- tab->hcu_scheduled = 0;
}
struct hostentry *
-rt_get_hostentry(rtable *tab, ip_addr a, ip_addr ll, rtable *dep)
+rt_get_hostentry(rtable *t, ip_addr a, ip_addr ll, rtable *dep)
{
struct hostentry *he;
+ rtable_private *tab = RT_LOCK(t);
+
if (!tab->hostcache)
rt_init_hostcache(tab);
@@ -3154,10 +3421,13 @@ rt_get_hostentry(rtable *tab, ip_addr a, ip_addr ll, rtable *dep)
struct hostcache *hc = tab->hostcache;
for (he = hc->hash_table[k >> hc->hash_shift]; he != NULL; he = he->next)
if (ipa_equal(he->addr, a) && (he->tab == dep))
- return he;
+ goto done;
he = hc_new_hostentry(hc, tab->rp, a, ipa_zero(ll) ? a : ll, dep, k);
rt_update_hostentry(tab, he);
+
+done:
+ RT_UNLOCK(t);
return he;
}
diff --git a/proto/babel/babel.c b/proto/babel/babel.c
index 1e87212c..6d2a593e 100644
--- a/proto/babel/babel.c
+++ b/proto/babel/babel.c
@@ -641,13 +641,36 @@ babel_announce_rte(struct babel_proto *p, struct babel_entry *e)
if (r)
{
rta a0 = {
- .src = p->p.main_source,
.source = RTS_BABEL,
.scope = SCOPE_UNIVERSE,
.dest = RTD_UNICAST,
+ .pref = c->preference,
.from = r->neigh->addr,
.nh.gw = r->next_hop,
.nh.iface = r->neigh->ifa->iface,
+ .eattrs = alloca(sizeof(ea_list) + 3*sizeof(eattr)),
+ };
+
+ *a0.eattrs = (ea_list) { .count = 3 };
+ a0.eattrs->attrs[0] = (eattr) {
+ .id = EA_BABEL_METRIC,
+ .type = EAF_TYPE_INT,
+ .u.data = r->metric,
+ };
+
+ struct adata *ad = alloca(sizeof(struct adata) + sizeof(u64));
+ ad->length = sizeof(u64);
+ memcpy(ad->data, &(r->router_id), sizeof(u64));
+ a0.eattrs->attrs[1] = (eattr) {
+ .id = EA_BABEL_ROUTER_ID,
+ .type = EAF_TYPE_OPAQUE,
+ .u.ptr = ad,
+ };
+
+ a0.eattrs->attrs[2] = (eattr) {
+ .id = EA_BABEL_SEQNO,
+ .type = EAF_TYPE_INT,
+ .u.data = r->seqno,
};
/*
@@ -658,40 +681,37 @@ babel_announce_rte(struct babel_proto *p, struct babel_entry *e)
if (!neigh_find(&p->p, r->next_hop, r->neigh->ifa->iface, 0))
a0.nh.flags = RNF_ONLINK;
- rta *a = rta_lookup(&a0);
- rte *rte = rte_get_temp(a);
- rte->u.babel.seqno = r->seqno;
- rte->u.babel.metric = r->metric;
- rte->u.babel.router_id = r->router_id;
- rte->pflags = EA_ID_FLAG(EA_BABEL_METRIC) | EA_ID_FLAG(EA_BABEL_ROUTER_ID);
+ rte e0 = {
+ .attrs = &a0,
+ .src = p->p.main_source,
+ };
e->unreachable = 0;
- rte_update2(c, e->n.addr, rte, p->p.main_source);
+ rte_update(c, e->n.addr, &e0, p->p.main_source);
}
else if (e->valid && (e->router_id != p->router_id))
{
/* Unreachable */
rta a0 = {
- .src = p->p.main_source,
.source = RTS_BABEL,
.scope = SCOPE_UNIVERSE,
.dest = RTD_UNREACHABLE,
+ .pref = 1,
};
- rta *a = rta_lookup(&a0);
- rte *rte = rte_get_temp(a);
- memset(&rte->u.babel, 0, sizeof(rte->u.babel));
- rte->pflags = 0;
- rte->pref = 1;
+ rte e0 = {
+ .attrs = &a0,
+ .src = p->p.main_source,
+ };
e->unreachable = 1;
- rte_update2(c, e->n.addr, rte, p->p.main_source);
+ rte_update(c, e->n.addr, &e0, p->p.main_source);
}
else
{
/* Retraction */
e->unreachable = 0;
- rte_update2(c, e->n.addr, NULL, p->p.main_source);
+ rte_update(c, e->n.addr, NULL, p->p.main_source);
}
}
@@ -701,7 +721,7 @@ babel_announce_retraction(struct babel_proto *p, struct babel_entry *e)
{
struct channel *c = (e->n.addr->type == NET_IP4) ? p->ip4_channel : p->ip6_channel;
e->unreachable = 0;
- rte_update2(c, e->n.addr, NULL, p->p.main_source);
+ rte_update(c, e->n.addr, NULL, p->p.main_source);
}
@@ -1727,7 +1747,7 @@ babel_add_iface(struct babel_proto *p, struct iface *new, struct babel_iface_con
TRACE(D_EVENTS, "Adding interface %s", new->name);
- pool *pool = rp_new(p->p.pool, new->name);
+ pool *pool = rp_new(p->p.pool, p->p.loop, new->name);
ifa = mb_allocz(pool, sizeof(struct babel_iface));
ifa->proto = p;
@@ -1779,7 +1799,7 @@ babel_remove_iface(struct babel_proto *p, struct babel_iface *ifa)
rem_node(NODE ifa);
- rfree(ifa->pool); /* contains ifa itself, locks, socket, etc */
+ rp_free(ifa->pool, p->p.pool); /* contains ifa itself, locks, socket, etc */
}
static int
@@ -1887,7 +1907,8 @@ babel_reconfigure_ifaces(struct babel_proto *p, struct babel_config *cf)
{
struct iface *iface;
- WALK_LIST(iface, iface_list)
+ IFACE_LEGACY_ACCESS;
+ WALK_LIST(iface, global_iface_list)
{
if (!(iface->flags & IF_UP))
continue;
@@ -2010,7 +2031,13 @@ babel_dump(struct proto *P)
static void
babel_get_route_info(rte *rte, byte *buf)
{
- buf += bsprintf(buf, " (%d/%d) [%lR]", rte->pref, rte->u.babel.metric, rte->u.babel.router_id);
+ u64 rid = 0;
+ eattr *e = ea_find(rte->attrs->eattrs, EA_BABEL_ROUTER_ID);
+ if (e)
+ memcpy(&rid, e->u.ptr->data, sizeof(u64));
+
+ buf += bsprintf(buf, " (%d/%d) [%lR]", rte->attrs->pref,
+ ea_get_int(rte->attrs->eattrs, EA_BABEL_METRIC, BABEL_INFINITY), rid);
}
static int
@@ -2018,6 +2045,9 @@ babel_get_attr(const eattr *a, byte *buf, int buflen UNUSED)
{
switch (a->id)
{
+ case EA_BABEL_SEQNO:
+ return GA_FULL;
+
case EA_BABEL_METRIC:
bsprintf(buf, "metric: %d", a->u.data);
return GA_FULL;
@@ -2231,45 +2261,23 @@ babel_kick_timer(struct babel_proto *p)
static int
-babel_preexport(struct proto *P, struct rte **new, struct linpool *pool UNUSED)
+babel_preexport(struct channel *c, struct rte *new)
{
- struct rta *a = (*new)->attrs;
-
+ struct rta *a = new->attrs;
/* Reject our own unreachable routes */
- if ((a->dest == RTD_UNREACHABLE) && (a->src->proto == P))
+ if ((a->dest == RTD_UNREACHABLE) && (new->src->owner == &c->proto->sources))
return -1;
return 0;
}
-static void
-babel_make_tmp_attrs(struct rte *rt, struct linpool *pool)
-{
- struct adata *id = lp_alloc_adata(pool, sizeof(u64));
- memcpy(id->data, &rt->u.babel.router_id, sizeof(u64));
-
- rte_init_tmp_attrs(rt, pool, 2);
- rte_make_tmp_attr(rt, EA_BABEL_METRIC, EAF_TYPE_INT, rt->u.babel.metric);
- rte_make_tmp_attr(rt, EA_BABEL_ROUTER_ID, EAF_TYPE_OPAQUE, (uintptr_t) id);
-}
-
-static void
-babel_store_tmp_attrs(struct rte *rt, struct linpool *pool)
-{
- rte_init_tmp_attrs(rt, pool, 2);
- rt->u.babel.metric = rte_store_tmp_attr(rt, EA_BABEL_METRIC);
-
- /* EA_BABEL_ROUTER_ID is read-only, we do not really save the value */
- rte_store_tmp_attr(rt, EA_BABEL_ROUTER_ID);
-}
-
/*
* babel_rt_notify - core tells us about new route (possibly our own),
* so store it into our data structures.
*/
static void
-babel_rt_notify(struct proto *P, struct channel *c UNUSED, struct network *net,
- struct rte *new, struct rte *old UNUSED)
+babel_rt_notify(struct proto *P, struct channel *c UNUSED, const net_addr *net,
+ struct rte *new, const struct rte *old UNUSED)
{
struct babel_proto *p = (void *) P;
struct babel_entry *e;
@@ -2277,19 +2285,31 @@ babel_rt_notify(struct proto *P, struct channel *c UNUSED, struct network *net,
if (new)
{
/* Update */
- uint internal = (new->attrs->src->proto == P);
- uint rt_seqno = internal ? new->u.babel.seqno : p->update_seqno;
+ uint rt_seqno;
uint rt_metric = ea_get_int(new->attrs->eattrs, EA_BABEL_METRIC, 0);
- u64 rt_router_id = internal ? new->u.babel.router_id : p->router_id;
+ u64 rt_router_id = 0;
+
+ if (new->src->owner == &P->sources)
+ {
+ rt_seqno = ea_find(new->attrs->eattrs, EA_BABEL_SEQNO)->u.data;
+ eattr *e = ea_find(new->attrs->eattrs, EA_BABEL_ROUTER_ID);
+ if (e)
+ memcpy(&rt_router_id, e->u.ptr->data, sizeof(u64));
+ }
+ else
+ {
+ rt_seqno = p->update_seqno;
+ rt_router_id = p->router_id;
+ }
if (rt_metric > BABEL_INFINITY)
{
log(L_WARN "%s: Invalid babel_metric value %u for route %N",
- p->p.name, rt_metric, net->n.addr);
+ p->p.name, rt_metric, net);
rt_metric = BABEL_INFINITY;
}
- e = babel_get_entry(p, net->n.addr);
+ e = babel_get_entry(p, net);
/* Activate triggered updates */
if ((e->valid != BABEL_ENTRY_VALID) ||
@@ -2307,7 +2327,7 @@ babel_rt_notify(struct proto *P, struct channel *c UNUSED, struct network *net,
else
{
/* Withdraw */
- e = babel_find_entry(p, net->n.addr);
+ e = babel_find_entry(p, net);
if (!e || e->valid != BABEL_ENTRY_VALID)
return;
@@ -2323,15 +2343,16 @@ babel_rt_notify(struct proto *P, struct channel *c UNUSED, struct network *net,
static int
babel_rte_better(struct rte *new, struct rte *old)
{
- return new->u.babel.metric < old->u.babel.metric;
+ uint new_metric = ea_find(new->attrs->eattrs, EA_BABEL_SEQNO)->u.data;
+ uint old_metric = ea_find(old->attrs->eattrs, EA_BABEL_SEQNO)->u.data;
+
+ return new_metric < old_metric;
}
-static int
-babel_rte_same(struct rte *new, struct rte *old)
+static u32
+babel_rte_igp_metric(struct rte *rt)
{
- return ((new->u.babel.seqno == old->u.babel.seqno) &&
- (new->u.babel.metric == old->u.babel.metric) &&
- (new->u.babel.router_id == old->u.babel.router_id));
+ return ea_get_int(rt->attrs->eattrs, EA_BABEL_METRIC, BABEL_INFINITY);
}
@@ -2352,6 +2373,12 @@ babel_postconfig(struct proto_config *CF)
cf->ip6_channel = ip6 ?: ip6_sadr;
}
+static struct rte_owner_class babel_rte_owner_class = {
+ .get_route_info = babel_get_route_info,
+ .rte_better = babel_rte_better,
+ .rte_igp_metric = babel_rte_igp_metric,
+};
+
static struct proto *
babel_init(struct proto_config *CF)
{
@@ -2365,10 +2392,8 @@ babel_init(struct proto_config *CF)
P->if_notify = babel_if_notify;
P->rt_notify = babel_rt_notify;
P->preexport = babel_preexport;
- P->make_tmp_attrs = babel_make_tmp_attrs;
- P->store_tmp_attrs = babel_store_tmp_attrs;
- P->rte_better = babel_rte_better;
- P->rte_same = babel_rte_same;
+
+ P->sources.class = &babel_rte_owner_class;
return P;
}
@@ -2461,7 +2486,6 @@ babel_reconfigure(struct proto *P, struct proto_config *CF)
return 1;
}
-
struct protocol proto_babel = {
.name = "Babel",
.template = "babel%d",
@@ -2476,6 +2500,5 @@ struct protocol proto_babel = {
.start = babel_start,
.shutdown = babel_shutdown,
.reconfigure = babel_reconfigure,
- .get_route_info = babel_get_route_info,
.get_attr = babel_get_attr
};
diff --git a/proto/babel/babel.h b/proto/babel/babel.h
index 84feb085..8b6da3c8 100644
--- a/proto/babel/babel.h
+++ b/proto/babel/babel.h
@@ -28,6 +28,7 @@
#define EA_BABEL_METRIC EA_CODE(PROTOCOL_BABEL, 0)
#define EA_BABEL_ROUTER_ID EA_CODE(PROTOCOL_BABEL, 1)
+#define EA_BABEL_SEQNO EA_CODE(PROTOCOL_BABEL, 2)
#define BABEL_MAGIC 42
#define BABEL_VERSION 2
diff --git a/proto/bfd/Makefile b/proto/bfd/Makefile
index 402122fc..267dff98 100644
--- a/proto/bfd/Makefile
+++ b/proto/bfd/Makefile
@@ -1,6 +1,6 @@
-src := bfd.c io.c packets.c
+src := bfd.c packets.c
obj := $(src-o-files)
$(all-daemon)
$(cf-local)
-tests_objs := $(tests_objs) $(src-o-files) \ No newline at end of file
+tests_objs := $(tests_objs) $(src-o-files)
diff --git a/proto/bfd/bfd.c b/proto/bfd/bfd.c
index dac184c5..63e0deff 100644
--- a/proto/bfd/bfd.c
+++ b/proto/bfd/bfd.c
@@ -113,15 +113,26 @@
#define HASH_IP_EQ(a1,n1,a2,n2) ipa_equal(a1, a2) && n1 == n2
#define HASH_IP_FN(a,n) ipa_hash(a) ^ u32_hash(n)
-static list bfd_proto_list;
-static list bfd_wait_list;
+DEFINE_DOMAIN(rtable);
+#define BFD_LOCK LOCK_DOMAIN(rtable, bfd_global.lock)
+#define BFD_UNLOCK UNLOCK_DOMAIN(rtable, bfd_global.lock)
+#define BFD_ASSERT_LOCKED ASSERT_DIE(DOMAIN_IS_LOCKED(rtable, bfd_global.lock))
+
+static struct {
+ DOMAIN(rtable) lock;
+ list wait_list;
+ list proto_list;
+} bfd_global;
+
+static struct bfd_session bfd_admin_down = { .loc = { .state = BFD_STATE_ADMIN_DOWN }, };
const char *bfd_state_names[] = { "AdminDown", "Down", "Init", "Up" };
static void bfd_session_set_min_tx(struct bfd_session *s, u32 val);
static struct bfd_iface *bfd_get_iface(struct bfd_proto *p, ip_addr local, struct iface *iface);
static void bfd_free_iface(struct bfd_iface *ifa);
-static inline void bfd_notify_kick(struct bfd_proto *p);
+static void bfd_remove_session(struct bfd_proto *p, struct bfd_session *s);
+static void bfd_reconfigure_session_hook(void *vsession);
/*
@@ -140,37 +151,57 @@ bfd_merge_options(const struct bfd_iface_config *cf, const struct bfd_options *o
};
}
-static void
+static int
bfd_session_update_state(struct bfd_session *s, uint state, uint diag)
{
struct bfd_proto *p = s->ifa->bfd;
- uint old_state = s->loc_state;
- int notify;
+ uint old_state = BFD_LOC_STATE(s).state;
if (state == old_state)
- return;
+ {
+ if (current_time() > s->last_reqlist_check + 5 S)
+ {
+ BFD_LOCK;
+ if (EMPTY_LIST(s->request_list))
+ {
+ bfd_remove_session(p, s);
+ BFD_UNLOCK;
+ return 1;
+ }
+
+ s->last_reqlist_check = current_time();
+ BFD_UNLOCK;
+ }
+ return 0;
+ }
TRACE(D_EVENTS, "Session to %I changed state from %s to %s",
s->addr, bfd_state_names[old_state], bfd_state_names[state]);
- bfd_lock_sessions(p);
- s->loc_state = state;
- s->loc_diag = diag;
+ atomic_store_explicit(&s->loc, ((struct bfd_session_state) { .state = state, .diag = diag }), memory_order_release);
s->last_state_change = current_time();
- notify = !NODE_VALID(&s->n);
- if (notify)
- add_tail(&p->notify_list, &s->n);
- bfd_unlock_sessions(p);
-
if (state == BFD_STATE_UP)
bfd_session_set_min_tx(s, s->cf.min_tx_int);
if (old_state == BFD_STATE_UP)
bfd_session_set_min_tx(s, s->cf.idle_tx_int);
- if (notify)
- bfd_notify_kick(p);
+ BFD_LOCK;
+ if (EMPTY_LIST(s->request_list))
+ {
+ bfd_remove_session(p, s);
+ BFD_UNLOCK;
+ return 1;
+ }
+
+ struct bfd_request *req;
+ node *nn;
+ WALK_LIST2(req, nn, s->request_list, n)
+ ev_send_self(&req->event);
+
+ BFD_UNLOCK;
+ return 0;
}
static void
@@ -188,7 +219,7 @@ bfd_session_update_tx_interval(struct bfd_session *s)
return;
/* Set timer relative to last tx_timer event */
- tm_set(s->tx_timer, s->last_tx + tx_int_l);
+ tm_set_in(s->tx_timer, s->last_tx + tx_int_l, s->ifa->bfd->p.loop);
}
static void
@@ -202,7 +233,7 @@ bfd_session_update_detection_time(struct bfd_session *s, int kick)
if (!s->last_rx)
return;
- tm_set(s->hold_timer, s->last_rx + timeout);
+ tm_set_in(s->hold_timer, s->last_rx + timeout, s->ifa->bfd->p.loop);
}
static void
@@ -215,8 +246,8 @@ bfd_session_control_tx_timer(struct bfd_session *s, int reset)
if (s->rem_demand_mode &&
!s->poll_active &&
- (s->loc_state == BFD_STATE_UP) &&
- (s->rem_state == BFD_STATE_UP))
+ (BFD_LOC_STATE(s).state == BFD_STATE_UP) &&
+ (s->rem.state == BFD_STATE_UP))
goto stop;
if (s->rem_min_rx_int == 0)
@@ -226,7 +257,7 @@ bfd_session_control_tx_timer(struct bfd_session *s, int reset)
if (reset || !tm_active(s->tx_timer))
{
s->last_tx = 0;
- tm_start(s->tx_timer, 0);
+ tm_start_in(s->tx_timer, 0, s->ifa->bfd->p.loop);
}
return;
@@ -286,28 +317,29 @@ bfd_session_process_ctl(struct bfd_session *s, u8 flags, u32 old_tx_int, u32 old
int next_state = 0;
int diag = BFD_DIAG_NOTHING;
- switch (s->loc_state)
+ switch (BFD_LOC_STATE(s).state)
{
case BFD_STATE_ADMIN_DOWN:
return;
case BFD_STATE_DOWN:
- if (s->rem_state == BFD_STATE_DOWN) next_state = BFD_STATE_INIT;
- else if (s->rem_state == BFD_STATE_INIT) next_state = BFD_STATE_UP;
+ if (s->rem.state == BFD_STATE_DOWN) next_state = BFD_STATE_INIT;
+ else if (s->rem.state == BFD_STATE_INIT) next_state = BFD_STATE_UP;
break;
case BFD_STATE_INIT:
- if (s->rem_state == BFD_STATE_ADMIN_DOWN) next_state = BFD_STATE_DOWN, diag = BFD_DIAG_NEIGHBOR_DOWN;
- else if (s->rem_state >= BFD_STATE_INIT) next_state = BFD_STATE_UP;
+ if (s->rem.state == BFD_STATE_ADMIN_DOWN) next_state = BFD_STATE_DOWN, diag = BFD_DIAG_NEIGHBOR_DOWN;
+ else if (s->rem.state >= BFD_STATE_INIT) next_state = BFD_STATE_UP;
break;
case BFD_STATE_UP:
- if (s->rem_state <= BFD_STATE_DOWN) next_state = BFD_STATE_DOWN, diag = BFD_DIAG_NEIGHBOR_DOWN;
+ if (s->rem.state <= BFD_STATE_DOWN) next_state = BFD_STATE_DOWN, diag = BFD_DIAG_NEIGHBOR_DOWN;
break;
}
if (next_state)
- bfd_session_update_state(s, next_state, diag);
+ if (bfd_session_update_state(s, next_state, diag))
+ return;
bfd_session_control_tx_timer(s, 0);
@@ -322,7 +354,7 @@ bfd_session_timeout(struct bfd_session *s)
TRACE(D_EVENTS, "Session to %I expired", s->addr);
- s->rem_state = BFD_STATE_DOWN;
+ s->rem.state = BFD_STATE_DOWN;
s->rem_id = 0;
s->rem_min_tx_int = 0;
s->rem_min_rx_int = 1;
@@ -333,7 +365,8 @@ bfd_session_timeout(struct bfd_session *s)
s->poll_active = 0;
s->poll_scheduled = 0;
- bfd_session_update_state(s, BFD_STATE_DOWN, BFD_DIAG_TIMEOUT);
+ if (bfd_session_update_state(s, BFD_STATE_DOWN, BFD_DIAG_TIMEOUT))
+ return;
bfd_session_control_tx_timer(s, 1);
}
@@ -349,7 +382,7 @@ bfd_session_set_min_tx(struct bfd_session *s, u32 val)
s->des_min_tx_new = val;
/* Postpone timer update if des_min_tx_int increases and the session is up */
- if ((s->loc_state != BFD_STATE_UP) || (val < s->des_min_tx_int))
+ if ((BFD_LOC_STATE(s).state != BFD_STATE_UP) || (val < s->des_min_tx_int))
{
s->des_min_tx_int = val;
bfd_session_update_tx_interval(s);
@@ -369,7 +402,7 @@ bfd_session_set_min_rx(struct bfd_session *s, u32 val)
s->req_min_rx_new = val;
/* Postpone timer update if req_min_rx_int decreases and the session is up */
- if ((s->loc_state != BFD_STATE_UP) || (val > s->req_min_rx_int))
+ if ((BFD_LOC_STATE(s).state != BFD_STATE_UP) || (val > s->req_min_rx_int))
{
s->req_min_rx_int = val;
bfd_session_update_detection_time(s, 0);
@@ -381,12 +414,14 @@ bfd_session_set_min_rx(struct bfd_session *s, u32 val)
struct bfd_session *
bfd_find_session_by_id(struct bfd_proto *p, u32 id)
{
+ ASSERT_DIE(birdloop_inside(p->p.loop));
return HASH_FIND(p->session_hash_id, HASH_ID, id);
}
struct bfd_session *
bfd_find_session_by_addr(struct bfd_proto *p, ip_addr addr, uint ifindex)
{
+ ASSERT_DIE(birdloop_inside(p->p.loop));
return HASH_FIND(p->session_hash_ip, HASH_IP, addr, ifindex);
}
@@ -419,7 +454,8 @@ bfd_get_free_id(struct bfd_proto *p)
static struct bfd_session *
bfd_add_session(struct bfd_proto *p, ip_addr addr, ip_addr local, struct iface *iface, struct bfd_options *opts)
{
- birdloop_enter(p->loop);
+ ASSERT_DIE(birdloop_inside(p->p.loop));
+ BFD_ASSERT_LOCKED;
struct bfd_iface *ifa = bfd_get_iface(p, local, iface);
@@ -433,10 +469,15 @@ bfd_add_session(struct bfd_proto *p, ip_addr addr, ip_addr local, struct iface *
HASH_INSERT(p->session_hash_ip, HASH_IP, s);
s->cf = bfd_merge_options(ifa->cf, opts);
+ s->update_event = (event) {
+ .hook = bfd_reconfigure_session_hook,
+ .data = s,
+ .list = birdloop_event_list(p->p.loop),
+ };
/* Initialization of state variables - see RFC 5880 6.8.1 */
- s->loc_state = BFD_STATE_DOWN;
- s->rem_state = BFD_STATE_DOWN;
+ atomic_store_explicit(&s->loc, ((struct bfd_session_state) { .state = BFD_STATE_DOWN }), memory_order_relaxed);
+ s->rem.state = BFD_STATE_DOWN;
s->des_min_tx_int = s->des_min_tx_new = s->cf.idle_tx_int;
s->req_min_rx_int = s->req_min_rx_new = s->cf.min_rx_int;
s->rem_min_rx_int = 1;
@@ -444,8 +485,8 @@ bfd_add_session(struct bfd_proto *p, ip_addr addr, ip_addr local, struct iface *
s->passive = s->cf.passive;
s->tx_csn = random_u32();
- s->tx_timer = tm_new_init(p->tpool, bfd_tx_timer_hook, s, 0, 0);
- s->hold_timer = tm_new_init(p->tpool, bfd_hold_timer_hook, s, 0, 0);
+ s->tx_timer = tm_new_init(p->p.pool, bfd_tx_timer_hook, s, 0, 0);
+ s->hold_timer = tm_new_init(p->p.pool, bfd_hold_timer_hook, s, 0, 0);
bfd_session_update_tx_interval(s);
bfd_session_control_tx_timer(s, 1);
@@ -454,51 +495,15 @@ bfd_add_session(struct bfd_proto *p, ip_addr addr, ip_addr local, struct iface *
TRACE(D_EVENTS, "Session to %I added", s->addr);
- birdloop_leave(p->loop);
-
return s;
}
-/*
-static void
-bfd_open_session(struct bfd_proto *p, struct bfd_session *s, ip_addr local, struct iface *ifa)
-{
- birdloop_enter(p->loop);
-
- s->opened = 1;
-
- bfd_session_control_tx_timer(s);
-
- birdloop_leave(p->loop);
-}
-
-static void
-bfd_close_session(struct bfd_proto *p, struct bfd_session *s)
-{
- birdloop_enter(p->loop);
-
- s->opened = 0;
-
- bfd_session_update_state(s, BFD_STATE_DOWN, BFD_DIAG_PATH_DOWN);
- bfd_session_control_tx_timer(s);
-
- birdloop_leave(p->loop);
-}
-*/
-
static void
bfd_remove_session(struct bfd_proto *p, struct bfd_session *s)
{
- ip_addr ip = s->addr;
-
- /* Caller should ensure that request list is empty */
-
- birdloop_enter(p->loop);
-
- /* Remove session from notify list if scheduled for notification */
- /* No need for bfd_lock_sessions(), we are already protected by birdloop_enter() */
- if (NODE_VALID(&s->n))
- rem_node(&s->n);
+ ASSERT_DIE(birdloop_inside(p->p.loop));
+ BFD_ASSERT_LOCKED;
+ ASSERT_DIE(EMPTY_LIST(s->request_list));
bfd_free_iface(s->ifa);
@@ -508,25 +513,27 @@ bfd_remove_session(struct bfd_proto *p, struct bfd_session *s)
HASH_REMOVE(p->session_hash_id, HASH_ID, s);
HASH_REMOVE(p->session_hash_ip, HASH_IP, s);
- sl_free(p->session_slab, s);
-
- TRACE(D_EVENTS, "Session to %I removed", ip);
+ TRACE(D_EVENTS, "Session to %I removed", s->addr);
- birdloop_leave(p->loop);
+ sl_free(p->session_slab, s);
}
static void
bfd_reconfigure_session(struct bfd_proto *p, struct bfd_session *s)
{
+ ASSERT_DIE(birdloop_inside(p->p.loop));
+ BFD_LOCK;
if (EMPTY_LIST(s->request_list))
+ {
+ bfd_remove_session(p, s);
+ BFD_UNLOCK;
return;
-
- birdloop_enter(p->loop);
+ }
struct bfd_request *req = SKIP_BACK(struct bfd_request, n, HEAD(s->request_list));
s->cf = bfd_merge_options(s->ifa->cf, &req->opts);
- u32 tx = (s->loc_state == BFD_STATE_UP) ? s->cf.min_tx_int : s->cf.idle_tx_int;
+ u32 tx = (BFD_LOC_STATE(s).state == BFD_STATE_UP) ? s->cf.min_tx_int : s->cf.idle_tx_int;
bfd_session_set_min_tx(s, tx);
bfd_session_set_min_rx(s, s->cf.min_rx_int);
s->detect_mult = s->cf.multiplier;
@@ -534,9 +541,15 @@ bfd_reconfigure_session(struct bfd_proto *p, struct bfd_session *s)
bfd_session_control_tx_timer(s, 0);
- birdloop_leave(p->loop);
-
TRACE(D_EVENTS, "Session to %I reconfigured", s->addr);
+ BFD_UNLOCK;
+}
+
+static void
+bfd_reconfigure_session_hook(void *data)
+{
+ struct bfd_session *s = data;
+ return bfd_reconfigure_session(s->ifa->bfd, s);
}
@@ -573,7 +586,7 @@ bfd_get_iface(struct bfd_proto *p, ip_addr local, struct iface *iface)
struct bfd_config *cf = (struct bfd_config *) (p->p.cf);
struct bfd_iface_config *ic = bfd_find_iface_config(cf, iface);
- ifa = mb_allocz(p->tpool, sizeof(struct bfd_iface));
+ ifa = mb_allocz(p->p.pool, sizeof(struct bfd_iface));
ifa->local = local;
ifa->iface = iface;
ifa->cf = ic;
@@ -604,7 +617,7 @@ bfd_free_iface(struct bfd_iface *ifa)
}
static void
-bfd_reconfigure_iface(struct bfd_proto *p, struct bfd_iface *ifa, struct bfd_config *nc)
+bfd_reconfigure_iface(struct bfd_proto *p UNUSED, struct bfd_iface *ifa, struct bfd_config *nc)
{
struct bfd_iface_config *new = bfd_find_iface_config(nc, ifa->iface);
struct bfd_iface_config *old = ifa->cf;
@@ -618,9 +631,7 @@ bfd_reconfigure_iface(struct bfd_proto *p, struct bfd_iface *ifa, struct bfd_con
(new->passive != old->passive);
/* This should be probably changed to not access ifa->cf from the BFD thread */
- birdloop_enter(p->loop);
ifa->cf = new;
- birdloop_leave(p->loop);
}
@@ -629,17 +640,22 @@ bfd_reconfigure_iface(struct bfd_proto *p, struct bfd_iface *ifa, struct bfd_con
*/
static void
-bfd_request_notify(struct bfd_request *req, u8 state, u8 diag)
+bfd_request_notify(void *data)
{
- u8 old_state = req->state;
+ struct bfd_request *req = data;
+ struct bfd_session_state old = req->old_state;
- if (state == old_state)
+ BFD_LOCK; /* Needed to safely access req->session */
+ struct bfd_session_state new = atomic_load_explicit(&req->session->loc, memory_order_acquire);
+ BFD_UNLOCK;
+
+ if (new.state == old.state)
return;
- req->state = state;
- req->diag = diag;
- req->old_state = old_state;
- req->down = (old_state == BFD_STATE_UP) && (state == BFD_STATE_DOWN);
+ req->state = new.state;
+ req->diag = new.diag;
+ req->old_state = new;
+ req->down = (old.state == BFD_STATE_UP) && (new.state == BFD_STATE_DOWN);
if (req->hook)
req->hook(req);
@@ -648,9 +664,12 @@ bfd_request_notify(struct bfd_request *req, u8 state, u8 diag)
static int
bfd_add_request(struct bfd_proto *p, struct bfd_request *req)
{
+ BFD_ASSERT_LOCKED;
+ ASSERT_DIE(req->session == &bfd_admin_down);
+
struct bfd_config *cf = (struct bfd_config *) (p->p.cf);
- if (p->p.vrf_set && (p->p.vrf != req->vrf))
+ if (p->p.vrf && (p->p.vrf != req->vrf))
return 0;
if (ipa_is_ip4(req->addr) ? !cf->accept_ipv4 : !cf->accept_ipv6)
@@ -661,7 +680,6 @@ bfd_add_request(struct bfd_proto *p, struct bfd_request *req)
uint ifindex = req->iface ? req->iface->index : 0;
struct bfd_session *s = bfd_find_session_by_addr(p, req->addr, ifindex);
- u8 state, diag;
if (!s)
s = bfd_add_session(p, req->addr, req->local, req->iface, &req->opts);
@@ -670,52 +688,67 @@ bfd_add_request(struct bfd_proto *p, struct bfd_request *req)
add_tail(&s->request_list, &req->n);
req->session = s;
- bfd_lock_sessions(p);
- state = s->loc_state;
- diag = s->loc_diag;
- bfd_unlock_sessions(p);
-
- bfd_request_notify(req, state, diag);
+ ev_send_self(&req->event);
return 1;
}
static void
-bfd_submit_request(struct bfd_request *req)
+bfd_pickup_requests(void *_data UNUSED)
{
- node *n;
+ struct bfd_proto *p;
+ node *nn;
+ WALK_LIST2(p, nn, bfd_global.proto_list, bfd_node)
+ {
+ birdloop_enter(p->p.loop);
+ BFD_LOCK;
- WALK_LIST(n, bfd_proto_list)
- if (bfd_add_request(SKIP_BACK(struct bfd_proto, bfd_node, n), req))
- return;
+ struct bfd_request *req;
+ node *rn, *rnxt;
+ WALK_LIST2_DELSAFE(req, rn, rnxt, bfd_global.wait_list, n)
+ bfd_add_request(p, req);
- rem_node(&req->n);
- add_tail(&bfd_wait_list, &req->n);
- req->session = NULL;
- bfd_request_notify(req, BFD_STATE_ADMIN_DOWN, 0);
+ BFD_UNLOCK;
+ birdloop_ping(p->p.loop);
+ birdloop_leave(p->p.loop);
+ }
}
+static event bfd_pickup_event = { .hook = bfd_pickup_requests };
+#define bfd_schedule_pickup() ev_send(&global_event_list, &bfd_pickup_event)
+
static void
bfd_take_requests(struct bfd_proto *p)
{
+ struct bfd_request *req;
node *n, *nn;
-
- WALK_LIST_DELSAFE(n, nn, bfd_wait_list)
- bfd_add_request(p, SKIP_BACK(struct bfd_request, n, n));
+ BFD_LOCK;
+ WALK_LIST2_DELSAFE(req, n, nn, bfd_global.wait_list, n)
+ bfd_add_request(p, req);
+ BFD_UNLOCK;
}
static void
bfd_drop_requests(struct bfd_proto *p)
{
node *n;
-
- HASH_WALK(p->session_hash_id, next_id, s)
+ BFD_LOCK;
+ HASH_WALK_DELSAFE(p->session_hash_id, next_id, s)
{
- /* We assume that p is not in bfd_proto_list */
WALK_LIST_FIRST(n, s->request_list)
- bfd_submit_request(SKIP_BACK(struct bfd_request, n, n));
+ {
+ struct bfd_request *req = SKIP_BACK(struct bfd_request, n, n);
+ rem_node(&req->n);
+ add_tail(&bfd_global.wait_list, &req->n);
+ req->session = &bfd_admin_down;
+ ev_send_self(&req->event);
+ }
+
+ bfd_schedule_pickup();
+ bfd_remove_session(p, s);
}
HASH_WALK_END;
+ BFD_UNLOCK;
}
static struct resclass bfd_request_class;
@@ -724,13 +757,11 @@ struct bfd_request *
bfd_request_session(pool *p, ip_addr addr, ip_addr local,
struct iface *iface, struct iface *vrf,
void (*hook)(struct bfd_request *), void *data,
+ struct event_list *list,
const struct bfd_options *opts)
{
struct bfd_request *req = ralloc(p, &bfd_request_class);
- /* Hack: self-link req->n, we will call rem_node() on it */
- req->n.prev = req->n.next = &req->n;
-
req->addr = addr;
req->local = local;
req->iface = iface;
@@ -739,10 +770,19 @@ bfd_request_session(pool *p, ip_addr addr, ip_addr local,
if (opts)
req->opts = *opts;
- bfd_submit_request(req);
-
req->hook = hook;
req->data = data;
+ req->event = (event) {
+ .hook = bfd_request_notify,
+ .data = req,
+ .list = list,
+ };
+
+ BFD_LOCK;
+ req->session = &bfd_admin_down;
+ add_tail(&bfd_global.wait_list, &req->n);
+ bfd_schedule_pickup();
+ BFD_UNLOCK;
return req;
}
@@ -750,30 +790,29 @@ bfd_request_session(pool *p, ip_addr addr, ip_addr local,
void
bfd_update_request(struct bfd_request *req, const struct bfd_options *opts)
{
- struct bfd_session *s = req->session;
-
if (!memcmp(opts, &req->opts, sizeof(const struct bfd_options)))
return;
+ BFD_LOCK;
req->opts = *opts;
- if (s)
- bfd_reconfigure_session(s->ifa->bfd, s);
+ struct bfd_session *s = req->session;
+ if (s != &bfd_admin_down)
+ ev_send_self(&s->update_event);
+
+ BFD_UNLOCK;
}
static void
bfd_request_free(resource *r)
{
struct bfd_request *req = (struct bfd_request *) r;
- struct bfd_session *s = req->session;
+ BFD_LOCK;
rem_node(&req->n);
+ BFD_UNLOCK;
- /* Remove the session if there is no request for it. Skip that if
- inside notify hooks, will be handled by bfd_notify_hook() itself */
-
- if (s && EMPTY_LIST(s->request_list) && !s->notify_running)
- bfd_remove_session(s->ifa->bfd, s);
+ ev_postpone(&req->event);
}
static void
@@ -810,7 +849,7 @@ bfd_neigh_notify(struct neighbor *nb)
if ((nb->scope > 0) && !n->req)
{
ip_addr local = ipa_nonzero(n->local) ? n->local : nb->ifa->ip;
- n->req = bfd_request_session(p->p.pool, n->addr, local, nb->iface, p->p.vrf, NULL, NULL, NULL);
+ n->req = bfd_request_session(p->p.pool, n->addr, local, nb->iface, p->p.vrf, NULL, NULL, birdloop_event_list(p->p.loop), NULL);
}
if ((nb->scope <= 0) && n->req)
@@ -827,7 +866,7 @@ bfd_start_neighbor(struct bfd_proto *p, struct bfd_neighbor *n)
if (n->multihop)
{
- n->req = bfd_request_session(p->p.pool, n->addr, n->local, NULL, p->p.vrf, NULL, NULL, NULL);
+ n->req = bfd_request_session(p->p.pool, n->addr, n->local, NULL, p->p.vrf, NULL, NULL, birdloop_event_list(p->p.loop), NULL);
return;
}
@@ -902,107 +941,15 @@ bfd_reconfigure_neighbors(struct bfd_proto *p, struct bfd_config *new)
/*
- * BFD notify socket
- */
-
-/* This core notify code should be replaced after main loop transition to birdloop */
-
-int pipe(int pipefd[2]);
-void pipe_drain(int fd);
-void pipe_kick(int fd);
-
-static int
-bfd_notify_hook(sock *sk, uint len UNUSED)
-{
- struct bfd_proto *p = sk->data;
- struct bfd_session *s;
- list tmp_list;
- u8 state, diag;
- node *n, *nn;
-
- pipe_drain(sk->fd);
-
- bfd_lock_sessions(p);
- init_list(&tmp_list);
- add_tail_list(&tmp_list, &p->notify_list);
- init_list(&p->notify_list);
- bfd_unlock_sessions(p);
-
- WALK_LIST_FIRST(s, tmp_list)
- {
- bfd_lock_sessions(p);
- rem_node(&s->n);
- state = s->loc_state;
- diag = s->loc_diag;
- bfd_unlock_sessions(p);
-
- s->notify_running = 1;
- WALK_LIST_DELSAFE(n, nn, s->request_list)
- bfd_request_notify(SKIP_BACK(struct bfd_request, n, n), state, diag);
- s->notify_running = 0;
-
- /* Remove the session if all requests were removed in notify hooks */
- if (EMPTY_LIST(s->request_list))
- bfd_remove_session(p, s);
- }
-
- return 0;
-}
-
-static inline void
-bfd_notify_kick(struct bfd_proto *p)
-{
- pipe_kick(p->notify_ws->fd);
-}
-
-static void
-bfd_noterr_hook(sock *sk, int err)
-{
- struct bfd_proto *p = sk->data;
- log(L_ERR "%s: Notify socket error: %m", p->p.name, err);
-}
-
-static void
-bfd_notify_init(struct bfd_proto *p)
-{
- int pfds[2];
- sock *sk;
-
- int rv = pipe(pfds);
- if (rv < 0)
- die("pipe: %m");
-
- sk = sk_new(p->p.pool);
- sk->type = SK_MAGIC;
- sk->rx_hook = bfd_notify_hook;
- sk->err_hook = bfd_noterr_hook;
- sk->fd = pfds[0];
- sk->data = p;
- if (sk_open(sk) < 0)
- die("bfd: sk_open failed");
- p->notify_rs = sk;
-
- /* The write sock is not added to any event loop */
- sk = sk_new(p->p.pool);
- sk->type = SK_MAGIC;
- sk->fd = pfds[1];
- sk->data = p;
- sk->flags = SKF_THREAD;
- if (sk_open(sk) < 0)
- die("bfd: sk_open failed");
- p->notify_ws = sk;
-}
-
-
-/*
* BFD protocol glue
*/
void
bfd_init_all(void)
{
- init_list(&bfd_proto_list);
- init_list(&bfd_wait_list);
+ bfd_global.lock = DOMAIN_NEW(rtable, "BFD Global");
+ init_list(&bfd_global.wait_list);
+ init_list(&bfd_global.proto_list);
}
static struct proto *
@@ -1021,22 +968,13 @@ bfd_start(struct proto *P)
struct bfd_proto *p = (struct bfd_proto *) P;
struct bfd_config *cf = (struct bfd_config *) (P->cf);
- p->loop = birdloop_new();
- p->tpool = rp_new(NULL, "BFD thread root");
- pthread_spin_init(&p->lock, PTHREAD_PROCESS_PRIVATE);
-
p->session_slab = sl_new(P->pool, sizeof(struct bfd_session));
HASH_INIT(p->session_hash_id, P->pool, 8);
HASH_INIT(p->session_hash_ip, P->pool, 8);
init_list(&p->iface_list);
- init_list(&p->notify_list);
- bfd_notify_init(p);
-
- add_tail(&bfd_proto_list, &p->bfd_node);
-
- birdloop_enter(p->loop);
+ add_tail(&bfd_global.proto_list, &p->bfd_node);
if (cf->accept_ipv4 && cf->accept_direct)
p->rx4_1 = bfd_open_rx_sk(p, 0, SK_IPV4);
@@ -1050,42 +988,33 @@ bfd_start(struct proto *P)
if (cf->accept_ipv6 && cf->accept_multihop)
p->rx6_m = bfd_open_rx_sk(p, 1, SK_IPV6);
- birdloop_leave(p->loop);
-
bfd_take_requests(p);
struct bfd_neighbor *n;
WALK_LIST(n, cf->neigh_list)
bfd_start_neighbor(p, n);
- birdloop_start(p->loop);
-
return PS_UP;
}
-
static int
bfd_shutdown(struct proto *P)
{
struct bfd_proto *p = (struct bfd_proto *) P;
- struct bfd_config *cf = (struct bfd_config *) (P->cf);
+ struct bfd_config *cf = (struct bfd_config *) (p->p.cf);
rem_node(&p->bfd_node);
- birdloop_stop(p->loop);
-
- struct bfd_neighbor *n;
- WALK_LIST(n, cf->neigh_list)
- bfd_stop_neighbor(p, n);
+ struct bfd_neighbor *bn;
+ WALK_LIST(bn, cf->neigh_list)
+ bfd_stop_neighbor(p, bn);
bfd_drop_requests(p);
- /* FIXME: This is hack */
- birdloop_enter(p->loop);
- rfree(p->tpool);
- birdloop_leave(p->loop);
-
- birdloop_free(p->loop);
+ if (p->rx4_1) sk_stop(p->rx4_1);
+ if (p->rx4_m) sk_stop(p->rx4_m);
+ if (p->rx6_1) sk_stop(p->rx6_1);
+ if (p->rx6_m) sk_stop(p->rx6_m);
return PS_DOWN;
}
@@ -1098,6 +1027,8 @@ bfd_reconfigure(struct proto *P, struct proto_config *c)
struct bfd_config *new = (struct bfd_config *) c;
struct bfd_iface *ifa;
+ ASSERT_DIE(birdloop_inside(P->loop));
+
/* TODO: Improve accept reconfiguration */
if ((new->accept_ipv4 != old->accept_ipv4) ||
(new->accept_ipv6 != old->accept_ipv6) ||
@@ -1105,21 +1036,21 @@ bfd_reconfigure(struct proto *P, struct proto_config *c)
(new->accept_multihop != old->accept_multihop))
return 0;
- birdloop_mask_wakeups(p->loop);
+ birdloop_mask_wakeups(p->p.loop);
WALK_LIST(ifa, p->iface_list)
bfd_reconfigure_iface(p, ifa, new);
- HASH_WALK(p->session_hash_id, next_id, s)
+ HASH_WALK_DELSAFE(p->session_hash_id, next_id, s)
{
if (s->ifa->changed)
bfd_reconfigure_session(p, s);
}
- HASH_WALK_END;
+ HASH_WALK_DELSAFE_END;
bfd_reconfigure_neighbors(p, new);
- birdloop_unmask_wakeups(p->loop);
+ birdloop_unmask_wakeups(p->p.loop);
return 1;
}
@@ -1140,13 +1071,14 @@ bfd_show_sessions(struct proto *P)
{
byte tbuf[TM_DATETIME_BUFFER_SIZE];
struct bfd_proto *p = (struct bfd_proto *) P;
- uint state, diag UNUSED;
btime tx_int, timeout;
const char *ifname;
+ birdloop_enter(P->loop);
if (p->p.proto_state != PS_UP)
{
cli_msg(-1020, "%s: is not up", p->p.name);
+ birdloop_leave(P->loop);
return;
}
@@ -1154,12 +1086,9 @@ bfd_show_sessions(struct proto *P)
cli_msg(-1020, "%-25s %-10s %-10s %-12s %8s %8s",
"IP address", "Interface", "State", "Since", "Interval", "Timeout");
-
HASH_WALK(p->session_hash_id, next_id, s)
{
- /* FIXME: this is thread-unsafe, but perhaps harmless */
- state = s->loc_state;
- diag = s->loc_diag;
+ uint state = BFD_LOC_STATE(s).state;
ifname = (s->ifa && s->ifa->iface) ? s->ifa->iface->name : "---";
tx_int = s->last_tx ? MAX(s->des_min_tx_int, s->rem_min_rx_int) : 0;
timeout = (btime) MAX(s->req_min_rx_int, s->rem_min_tx_int) * s->rem_detect_mult;
@@ -1171,6 +1100,8 @@ bfd_show_sessions(struct proto *P)
s->addr, ifname, bfd_state_names[state], tbuf, tx_int, timeout);
}
HASH_WALK_END;
+
+ birdloop_leave(P->loop);
}
diff --git a/proto/bfd/bfd.h b/proto/bfd/bfd.h
index 91fdaa60..b3266857 100644
--- a/proto/bfd/bfd.h
+++ b/proto/bfd/bfd.h
@@ -17,12 +17,12 @@
#include "nest/password.h"
#include "conf/conf.h"
#include "lib/hash.h"
+#include "lib/io-loop.h"
#include "lib/resource.h"
#include "lib/socket.h"
#include "lib/string.h"
#include "nest/bfd.h"
-#include "io.h"
#define BFD_CONTROL_PORT 3784
@@ -87,19 +87,13 @@ struct bfd_neighbor
struct bfd_proto
{
struct proto p;
- struct birdloop *loop;
- pool *tpool;
- pthread_spinlock_t lock;
+
node bfd_node;
slab *session_slab;
HASH(struct bfd_session) session_hash_id;
HASH(struct bfd_session) session_hash_ip;
- sock *notify_rs;
- sock *notify_ws;
- list notify_list;
-
sock *rx4_1;
sock *rx6_1;
sock *rx4_m;
@@ -122,7 +116,6 @@ struct bfd_iface
struct bfd_session
{
- node n;
ip_addr addr; /* Address of session */
struct bfd_iface *ifa; /* Iface associated with session */
struct bfd_session *next_id; /* Next in bfd.session_hash_id */
@@ -133,14 +126,15 @@ struct bfd_session
u8 poll_active;
u8 poll_scheduled;
- u8 loc_state;
- u8 rem_state;
- u8 loc_diag;
- u8 rem_diag;
+ _Atomic struct bfd_session_state loc;
+ struct bfd_session_state rem;
+#define BFD_LOC_STATE(s) atomic_load_explicit(&(s)->loc, memory_order_relaxed)
+
u32 loc_id; /* Local session ID (local discriminator) */
u32 rem_id; /* Remote session ID (remote discriminator) */
- struct bfd_session_config cf; /* Static configuration parameters */
+ struct bfd_session_config cf; /* Static configuration parameers */
+ event update_event; /* Reconfiguration requested */
u32 des_min_tx_int; /* Desired min rx interval, local option */
u32 des_min_tx_new; /* Used for des_min_tx_int change */
@@ -162,6 +156,7 @@ struct bfd_session
list request_list; /* List of client requests (struct bfd_request) */
btime last_state_change; /* Time of last state change */
+ btime last_reqlist_check; /* Time of last check whether the request list is not empty */
u8 notify_running; /* 1 if notify hooks are running */
u8 rx_csn_known; /* Received crypto sequence number is known */
@@ -208,10 +203,6 @@ extern const char *bfd_state_names[];
extern const u8 bfd_auth_type_to_hash_alg[];
-
-static inline void bfd_lock_sessions(struct bfd_proto *p) { pthread_spin_lock(&p->lock); }
-static inline void bfd_unlock_sessions(struct bfd_proto *p) { pthread_spin_unlock(&p->lock); }
-
/* bfd.c */
struct bfd_session * bfd_find_session_by_id(struct bfd_proto *p, u32 id);
struct bfd_session * bfd_find_session_by_addr(struct bfd_proto *p, ip_addr addr, uint ifindex);
diff --git a/proto/bfd/config.Y b/proto/bfd/config.Y
index df1cba42..ed5479fb 100644
--- a/proto/bfd/config.Y
+++ b/proto/bfd/config.Y
@@ -36,6 +36,7 @@ proto: bfd_proto ;
bfd_proto_start: proto_start BFD
{
this_proto = proto_config_new(&proto_bfd, $1);
+ this_proto->loop_order = DOMAIN_ORDER(proto);
init_list(&BFD_CFG->patt_list);
init_list(&BFD_CFG->neigh_list);
BFD_CFG->accept_ipv4 = BFD_CFG->accept_ipv6 = 1;
diff --git a/proto/bfd/io.c b/proto/bfd/io.c
deleted file mode 100644
index 1cd9365a..00000000
--- a/proto/bfd/io.c
+++ /dev/null
@@ -1,535 +0,0 @@
-/*
- * BIRD -- I/O and event loop
- *
- * Can be freely distributed and used under the terms of the GNU GPL.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <poll.h>
-#include <pthread.h>
-#include <time.h>
-#include <sys/time.h>
-
-#include "nest/bird.h"
-#include "proto/bfd/io.h"
-
-#include "lib/buffer.h"
-#include "lib/lists.h"
-#include "lib/resource.h"
-#include "lib/event.h"
-#include "lib/timer.h"
-#include "lib/socket.h"
-
-
-struct birdloop
-{
- pool *pool;
- pthread_t thread;
- pthread_mutex_t mutex;
-
- u8 stop_called;
- u8 poll_active;
- u8 wakeup_masked;
- int wakeup_fds[2];
-
- struct timeloop time;
- list event_list;
- list sock_list;
- uint sock_num;
-
- BUFFER(sock *) poll_sk;
- BUFFER(struct pollfd) poll_fd;
- u8 poll_changed;
- u8 close_scheduled;
-};
-
-
-/*
- * Current thread context
- */
-
-static pthread_key_t current_loop_key;
-extern pthread_key_t current_time_key;
-
-static inline struct birdloop *
-birdloop_current(void)
-{
- return pthread_getspecific(current_loop_key);
-}
-
-static inline void
-birdloop_set_current(struct birdloop *loop)
-{
- pthread_setspecific(current_loop_key, loop);
- pthread_setspecific(current_time_key, loop ? &loop->time : &main_timeloop);
-}
-
-static inline void
-birdloop_init_current(void)
-{
- pthread_key_create(&current_loop_key, NULL);
-}
-
-
-/*
- * Wakeup code for birdloop
- */
-
-static void
-pipe_new(int *pfds)
-{
- int rv = pipe(pfds);
- if (rv < 0)
- die("pipe: %m");
-
- if (fcntl(pfds[0], F_SETFL, O_NONBLOCK) < 0)
- die("fcntl(O_NONBLOCK): %m");
-
- if (fcntl(pfds[1], F_SETFL, O_NONBLOCK) < 0)
- die("fcntl(O_NONBLOCK): %m");
-}
-
-void
-pipe_drain(int fd)
-{
- char buf[64];
- int rv;
-
- try:
- rv = read(fd, buf, 64);
- if (rv < 0)
- {
- if (errno == EINTR)
- goto try;
- if (errno == EAGAIN)
- return;
- die("wakeup read: %m");
- }
- if (rv == 64)
- goto try;
-}
-
-void
-pipe_kick(int fd)
-{
- u64 v = 1;
- int rv;
-
- try:
- rv = write(fd, &v, sizeof(u64));
- if (rv < 0)
- {
- if (errno == EINTR)
- goto try;
- if (errno == EAGAIN)
- return;
- die("wakeup write: %m");
- }
-}
-
-static inline void
-wakeup_init(struct birdloop *loop)
-{
- pipe_new(loop->wakeup_fds);
-}
-
-static inline void
-wakeup_drain(struct birdloop *loop)
-{
- pipe_drain(loop->wakeup_fds[0]);
-}
-
-static inline void
-wakeup_do_kick(struct birdloop *loop)
-{
- pipe_kick(loop->wakeup_fds[1]);
-}
-
-static inline void
-wakeup_kick(struct birdloop *loop)
-{
- if (!loop->wakeup_masked)
- wakeup_do_kick(loop);
- else
- loop->wakeup_masked = 2;
-}
-
-/* For notifications from outside */
-void
-wakeup_kick_current(void)
-{
- struct birdloop *loop = birdloop_current();
-
- if (loop && loop->poll_active)
- wakeup_kick(loop);
-}
-
-
-/*
- * Events
- */
-
-static inline uint
-events_waiting(struct birdloop *loop)
-{
- return !EMPTY_LIST(loop->event_list);
-}
-
-static inline void
-events_init(struct birdloop *loop)
-{
- init_list(&loop->event_list);
-}
-
-static void
-events_fire(struct birdloop *loop)
-{
- times_update(&loop->time);
- ev_run_list(&loop->event_list);
-}
-
-void
-ev2_schedule(event *e)
-{
- struct birdloop *loop = birdloop_current();
-
- if (loop->poll_active && EMPTY_LIST(loop->event_list))
- wakeup_kick(loop);
-
- if (e->n.next)
- rem_node(&e->n);
-
- add_tail(&loop->event_list, &e->n);
-}
-
-
-/*
- * Sockets
- */
-
-static void
-sockets_init(struct birdloop *loop)
-{
- init_list(&loop->sock_list);
- loop->sock_num = 0;
-
- BUFFER_INIT(loop->poll_sk, loop->pool, 4);
- BUFFER_INIT(loop->poll_fd, loop->pool, 4);
- loop->poll_changed = 1; /* add wakeup fd */
-}
-
-static void
-sockets_add(struct birdloop *loop, sock *s)
-{
- add_tail(&loop->sock_list, &s->n);
- loop->sock_num++;
-
- s->index = -1;
- loop->poll_changed = 1;
-
- if (loop->poll_active)
- wakeup_kick(loop);
-}
-
-void
-sk_start(sock *s)
-{
- struct birdloop *loop = birdloop_current();
-
- sockets_add(loop, s);
-}
-
-static void
-sockets_remove(struct birdloop *loop, sock *s)
-{
- rem_node(&s->n);
- loop->sock_num--;
-
- if (s->index >= 0)
- loop->poll_sk.data[s->index] = NULL;
-
- s->index = -1;
- loop->poll_changed = 1;
-
- /* Wakeup moved to sk_stop() */
-}
-
-void
-sk_stop(sock *s)
-{
- struct birdloop *loop = birdloop_current();
-
- sockets_remove(loop, s);
-
- if (loop->poll_active)
- {
- loop->close_scheduled = 1;
- wakeup_kick(loop);
- }
- else
- close(s->fd);
-
- s->fd = -1;
-}
-
-static inline uint sk_want_events(sock *s)
-{ return (s->rx_hook ? POLLIN : 0) | ((s->ttx != s->tpos) ? POLLOUT : 0); }
-
-/*
-FIXME: this should be called from sock code
-
-static void
-sockets_update(struct birdloop *loop, sock *s)
-{
- if (s->index >= 0)
- loop->poll_fd.data[s->index].events = sk_want_events(s);
-}
-*/
-
-static void
-sockets_prepare(struct birdloop *loop)
-{
- BUFFER_SET(loop->poll_sk, loop->sock_num + 1);
- BUFFER_SET(loop->poll_fd, loop->sock_num + 1);
-
- struct pollfd *pfd = loop->poll_fd.data;
- sock **psk = loop->poll_sk.data;
- uint i = 0;
- node *n;
-
- WALK_LIST(n, loop->sock_list)
- {
- sock *s = SKIP_BACK(sock, n, n);
-
- ASSERT(i < loop->sock_num);
-
- s->index = i;
- *psk = s;
- pfd->fd = s->fd;
- pfd->events = sk_want_events(s);
- pfd->revents = 0;
-
- pfd++;
- psk++;
- i++;
- }
-
- ASSERT(i == loop->sock_num);
-
- /* Add internal wakeup fd */
- *psk = NULL;
- pfd->fd = loop->wakeup_fds[0];
- pfd->events = POLLIN;
- pfd->revents = 0;
-
- loop->poll_changed = 0;
-}
-
-static void
-sockets_close_fds(struct birdloop *loop)
-{
- struct pollfd *pfd = loop->poll_fd.data;
- sock **psk = loop->poll_sk.data;
- int poll_num = loop->poll_fd.used - 1;
-
- int i;
- for (i = 0; i < poll_num; i++)
- if (psk[i] == NULL)
- close(pfd[i].fd);
-
- loop->close_scheduled = 0;
-}
-
-int sk_read(sock *s, int revents);
-int sk_write(sock *s);
-
-static void
-sockets_fire(struct birdloop *loop)
-{
- struct pollfd *pfd = loop->poll_fd.data;
- sock **psk = loop->poll_sk.data;
- int poll_num = loop->poll_fd.used - 1;
-
- times_update(&loop->time);
-
- /* Last fd is internal wakeup fd */
- if (pfd[poll_num].revents & POLLIN)
- wakeup_drain(loop);
-
- int i;
- for (i = 0; i < poll_num; pfd++, psk++, i++)
- {
- int e = 1;
-
- if (! pfd->revents)
- continue;
-
- if (pfd->revents & POLLNVAL)
- die("poll: invalid fd %d", pfd->fd);
-
- if (pfd->revents & POLLIN)
- while (e && *psk && (*psk)->rx_hook)
- e = sk_read(*psk, 0);
-
- e = 1;
- if (pfd->revents & POLLOUT)
- while (e && *psk)
- e = sk_write(*psk);
- }
-}
-
-
-/*
- * Birdloop
- */
-
-static void * birdloop_main(void *arg);
-
-struct birdloop *
-birdloop_new(void)
-{
- /* FIXME: this init should be elsewhere and thread-safe */
- static int init = 0;
- if (!init)
- { birdloop_init_current(); init = 1; }
-
- pool *p = rp_new(NULL, "Birdloop root");
- struct birdloop *loop = mb_allocz(p, sizeof(struct birdloop));
- loop->pool = p;
- pthread_mutex_init(&loop->mutex, NULL);
-
- wakeup_init(loop);
-
- events_init(loop);
- timers_init(&loop->time, p);
- sockets_init(loop);
-
- return loop;
-}
-
-void
-birdloop_start(struct birdloop *loop)
-{
- int rv = pthread_create(&loop->thread, NULL, birdloop_main, loop);
- if (rv)
- die("pthread_create(): %M", rv);
-}
-
-void
-birdloop_stop(struct birdloop *loop)
-{
- pthread_mutex_lock(&loop->mutex);
- loop->stop_called = 1;
- wakeup_do_kick(loop);
- pthread_mutex_unlock(&loop->mutex);
-
- int rv = pthread_join(loop->thread, NULL);
- if (rv)
- die("pthread_join(): %M", rv);
-}
-
-void
-birdloop_free(struct birdloop *loop)
-{
- rfree(loop->pool);
-}
-
-
-void
-birdloop_enter(struct birdloop *loop)
-{
- /* TODO: these functions could save and restore old context */
- pthread_mutex_lock(&loop->mutex);
- birdloop_set_current(loop);
-}
-
-void
-birdloop_leave(struct birdloop *loop)
-{
- /* TODO: these functions could save and restore old context */
- birdloop_set_current(NULL);
- pthread_mutex_unlock(&loop->mutex);
-}
-
-void
-birdloop_mask_wakeups(struct birdloop *loop)
-{
- pthread_mutex_lock(&loop->mutex);
- loop->wakeup_masked = 1;
- pthread_mutex_unlock(&loop->mutex);
-}
-
-void
-birdloop_unmask_wakeups(struct birdloop *loop)
-{
- pthread_mutex_lock(&loop->mutex);
- if (loop->wakeup_masked == 2)
- wakeup_do_kick(loop);
- loop->wakeup_masked = 0;
- pthread_mutex_unlock(&loop->mutex);
-}
-
-static void *
-birdloop_main(void *arg)
-{
- struct birdloop *loop = arg;
- timer *t;
- int rv, timeout;
-
- birdloop_set_current(loop);
-
- pthread_mutex_lock(&loop->mutex);
- while (1)
- {
- events_fire(loop);
- timers_fire(&loop->time);
-
- times_update(&loop->time);
- if (events_waiting(loop))
- timeout = 0;
- else if (t = timers_first(&loop->time))
- timeout = (tm_remains(t) TO_MS) + 1;
- else
- timeout = -1;
-
- if (loop->poll_changed)
- sockets_prepare(loop);
-
- loop->poll_active = 1;
- pthread_mutex_unlock(&loop->mutex);
-
- try:
- rv = poll(loop->poll_fd.data, loop->poll_fd.used, timeout);
- if (rv < 0)
- {
- if (errno == EINTR || errno == EAGAIN)
- goto try;
- die("poll: %m");
- }
-
- pthread_mutex_lock(&loop->mutex);
- loop->poll_active = 0;
-
- if (loop->close_scheduled)
- sockets_close_fds(loop);
-
- if (loop->stop_called)
- break;
-
- if (rv)
- sockets_fire(loop);
-
- timers_fire(&loop->time);
- }
-
- loop->stop_called = 0;
- pthread_mutex_unlock(&loop->mutex);
-
- return NULL;
-}
-
-
diff --git a/proto/bfd/io.h b/proto/bfd/io.h
deleted file mode 100644
index ec706e9a..00000000
--- a/proto/bfd/io.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * BIRD -- I/O and event loop
- *
- * Can be freely distributed and used under the terms of the GNU GPL.
- */
-
-#ifndef _BIRD_BFD_IO_H_
-#define _BIRD_BFD_IO_H_
-
-#include "nest/bird.h"
-#include "lib/lists.h"
-#include "lib/resource.h"
-#include "lib/event.h"
-#include "lib/timer.h"
-#include "lib/socket.h"
-
-
-void ev2_schedule(event *e);
-
-void sk_start(sock *s);
-void sk_stop(sock *s);
-
-struct birdloop *birdloop_new(void);
-void birdloop_start(struct birdloop *loop);
-void birdloop_stop(struct birdloop *loop);
-void birdloop_free(struct birdloop *loop);
-
-void birdloop_enter(struct birdloop *loop);
-void birdloop_leave(struct birdloop *loop);
-void birdloop_mask_wakeups(struct birdloop *loop);
-void birdloop_unmask_wakeups(struct birdloop *loop);
-
-
-#endif /* _BIRD_BFD_IO_H_ */
diff --git a/proto/bfd/packets.c b/proto/bfd/packets.c
index 7618e20f..893d582d 100644
--- a/proto/bfd/packets.c
+++ b/proto/bfd/packets.c
@@ -290,9 +290,11 @@ bfd_send_ctl(struct bfd_proto *p, struct bfd_session *s, int final)
if (!sk)
return;
+ struct bfd_session_state loc = BFD_LOC_STATE(s);
+
pkt = (struct bfd_ctl_packet *) sk->tbuf;
- pkt->vdiag = bfd_pack_vdiag(1, s->loc_diag);
- pkt->flags = bfd_pack_flags(s->loc_state, 0);
+ pkt->vdiag = bfd_pack_vdiag(1, loc.diag);
+ pkt->flags = bfd_pack_flags(loc.state, 0);
pkt->detect_mult = s->detect_mult;
pkt->length = BFD_BASE_LEN;
pkt->snd_id = htonl(s->loc_id);
@@ -313,7 +315,7 @@ bfd_send_ctl(struct bfd_proto *p, struct bfd_session *s, int final)
log(L_WARN "%s: Old packet overwritten in TX buffer", p->p.name);
TRACE(D_PACKETS, "Sending CTL to %I [%s%s]", s->addr,
- bfd_state_names[s->loc_state], bfd_format_flags(pkt->flags, fb));
+ bfd_state_names[loc.state], bfd_format_flags(pkt->flags, fb));
sk_send_to(sk, pkt->length, s->addr, sk->dport);
}
@@ -382,16 +384,17 @@ bfd_rx_hook(sock *sk, uint len)
u32 old_rx_int = s->rem_min_rx_int;
s->rem_id= ntohl(pkt->snd_id);
- s->rem_state = bfd_pkt_get_state(pkt);
- s->rem_diag = bfd_pkt_get_diag(pkt);
+ s->rem.state = bfd_pkt_get_state(pkt);
+ s->rem.diag = bfd_pkt_get_diag(pkt);
s->rem_demand_mode = pkt->flags & BFD_FLAG_DEMAND;
s->rem_min_tx_int = ntohl(pkt->des_min_tx_int);
s->rem_min_rx_int = ntohl(pkt->req_min_rx_int);
s->rem_detect_mult = pkt->detect_mult;
TRACE(D_PACKETS, "CTL received from %I [%s%s]", sk->faddr,
- bfd_state_names[s->rem_state], bfd_format_flags(pkt->flags, fb));
+ bfd_state_names[s->rem.state], bfd_format_flags(pkt->flags, fb));
+ /* This call may drop the session, must be called in tail position */
bfd_session_process_ctl(s, pkt->flags, old_tx_int, old_rx_int);
return 1;
@@ -410,7 +413,7 @@ bfd_err_hook(sock *sk, int err)
sock *
bfd_open_rx_sk(struct bfd_proto *p, int multihop, int af)
{
- sock *sk = sk_new(p->tpool);
+ sock *sk = sk_new(p->p.pool);
sk->type = SK_UDP;
sk->subtype = af;
sk->sport = !multihop ? BFD_CONTROL_PORT : BFD_MULTI_CTL_PORT;
@@ -425,6 +428,7 @@ bfd_open_rx_sk(struct bfd_proto *p, int multihop, int af)
sk->tos = IP_PREC_INTERNET_CONTROL;
sk->priority = sk_priority_control;
sk->flags = SKF_THREAD | SKF_LADDR_RX | (!multihop ? SKF_TTL_RX : 0);
+ sk->loop = p->p.loop;
if (sk_open(sk) < 0)
goto err;
@@ -441,7 +445,7 @@ bfd_open_rx_sk(struct bfd_proto *p, int multihop, int af)
sock *
bfd_open_tx_sk(struct bfd_proto *p, ip_addr local, struct iface *ifa)
{
- sock *sk = sk_new(p->tpool);
+ sock *sk = sk_new(p->p.pool);
sk->type = SK_UDP;
sk->saddr = local;
sk->dport = ifa ? BFD_CONTROL_PORT : BFD_MULTI_CTL_PORT;
@@ -457,6 +461,7 @@ bfd_open_tx_sk(struct bfd_proto *p, ip_addr local, struct iface *ifa)
sk->priority = sk_priority_control;
sk->ttl = ifa ? 255 : -1;
sk->flags = SKF_THREAD | SKF_BIND | SKF_HIGH_PORT;
+ sk->loop = p->p.loop;
if (sk_open(sk) < 0)
goto err;
diff --git a/proto/bgp/attrs.c b/proto/bgp/attrs.c
index 24ba00ba..02b07410 100644
--- a/proto/bgp/attrs.c
+++ b/proto/bgp/attrs.c
@@ -333,26 +333,26 @@ bgp_aigp_set_metric(struct linpool *pool, const struct adata *ad, u64 metric)
}
int
-bgp_total_aigp_metric_(rte *e, u64 *metric, const struct adata **ad)
+bgp_total_aigp_metric_(struct rta *a, u64 *metric, const struct adata **ad)
{
- eattr *a = ea_find(e->attrs->eattrs, EA_CODE(PROTOCOL_BGP, BA_AIGP));
- if (!a)
+ eattr *ea = ea_find(a->eattrs, EA_CODE(PROTOCOL_BGP, BA_AIGP));
+ if (!ea)
return 0;
- const byte *b = bgp_aigp_get_tlv(a->u.ptr, BGP_AIGP_METRIC);
+ const byte *b = bgp_aigp_get_tlv(ea->u.ptr, BGP_AIGP_METRIC);
if (!b)
return 0;
u64 aigp = get_u64(b + 3);
- u64 step = e->attrs->igp_metric;
+ u64 step = a->igp_metric;
- if (!rte_resolvable(e) || (step >= IGP_METRIC_UNKNOWN))
+ if (!rta_resolvable(a) || (step >= IGP_METRIC_UNKNOWN))
step = BGP_AIGP_MAX;
if (!step)
step = 1;
- *ad = a->u.ptr;
+ *ad = ea->u.ptr;
*metric = aigp + step;
if (*metric < aigp)
*metric = BGP_AIGP_MAX;
@@ -371,6 +371,13 @@ bgp_init_aigp_metric(rte *e, u64 *metric, const struct adata **ad)
return *metric < IGP_METRIC_UNKNOWN;
}
+u32
+bgp_rte_igp_metric(struct rte *rt)
+{
+ u64 metric = bgp_total_aigp_metric(rt->attrs);
+ return (u32) MIN(metric, (u64) IGP_METRIC_UNKNOWN);
+}
+
/*
* Attribute hooks
@@ -896,7 +903,7 @@ bgp_decode_large_community(struct bgp_parse_state *s, uint code UNUSED, uint fla
static void
bgp_export_mpls_label_stack(struct bgp_export_state *s, eattr *a)
{
- net_addr *n = s->route->net->n.addr;
+ const net_addr *n = s->route->net;
u32 *labels = (u32 *) a->u.ptr->data;
uint lnum = a->u.ptr->length / 4;
@@ -1617,7 +1624,7 @@ bgp_free_prefix_table(struct bgp_channel *c)
}
static struct bgp_prefix *
-bgp_get_prefix(struct bgp_channel *c, net_addr *net, u32 path_id)
+bgp_get_prefix(struct bgp_channel *c, const net_addr *net, u32 path_id)
{
u32 hash = net_hash(net) ^ u32_hash(path_id);
struct bgp_prefix *px = HASH_FIND(c->prefix_hash, PXH, net, path_id, hash);
@@ -1661,12 +1668,10 @@ bgp_free_prefix(struct bgp_channel *c, struct bgp_prefix *px)
*/
int
-bgp_preexport(struct proto *P, rte **new, struct linpool *pool UNUSED)
+bgp_preexport(struct channel *c, rte *e)
{
- rte *e = *new;
- struct proto *SRC = e->attrs->src->proto;
- struct bgp_proto *p = (struct bgp_proto *) P;
- struct bgp_proto *src = (SRC->proto == &proto_bgp) ? (struct bgp_proto *) SRC : NULL;
+ struct bgp_proto *p = (struct bgp_proto *) (c->proto);
+ struct bgp_proto *src = bgp_rte_proto(e);
/* Reject our routes */
if (src == p)
@@ -1690,11 +1695,11 @@ bgp_preexport(struct proto *P, rte **new, struct linpool *pool UNUSED)
}
/* Handle well-known communities, RFC 1997 */
- struct eattr *c;
+ struct eattr *com;
if (p->cf->interpret_communities &&
- (c = ea_find(e->attrs->eattrs, EA_CODE(PROTOCOL_BGP, BA_COMMUNITY))))
+ (com = ea_find(e->attrs->eattrs, EA_CODE(PROTOCOL_BGP, BA_COMMUNITY))))
{
- const struct adata *d = c->u.ptr;
+ const struct adata *d = com->u.ptr;
/* Do not export anywhere */
if (int_set_contains(d, BGP_COMM_NO_ADVERTISE))
@@ -1719,8 +1724,7 @@ bgp_preexport(struct proto *P, rte **new, struct linpool *pool UNUSED)
static ea_list *
bgp_update_attrs(struct bgp_proto *p, struct bgp_channel *c, rte *e, ea_list *attrs0, struct linpool *pool)
{
- struct proto *SRC = e->attrs->src->proto;
- struct bgp_proto *src = (SRC->proto == &proto_bgp) ? (void *) SRC : NULL;
+ struct bgp_proto *src = bgp_rte_proto(e);
struct bgp_export_state s = { .proto = p, .channel = c, .pool = pool, .src = src, .route = e, .mpls = c->desc->mpls };
ea_list *attrs = attrs0;
eattr *a;
@@ -1774,7 +1778,7 @@ bgp_update_attrs(struct bgp_proto *p, struct bgp_channel *c, rte *e, ea_list *at
/* AIGP attribute - accumulate local metric or originate new one */
u64 metric;
if (s.local_next_hop &&
- (bgp_total_aigp_metric_(e, &metric, &ad) ||
+ (bgp_total_aigp_metric_(e->attrs, &metric, &ad) ||
(c->cf->aigp_originate && bgp_init_aigp_metric(e, &metric, &ad))))
{
ad = bgp_aigp_set_metric(pool, ad, metric);
@@ -1833,7 +1837,7 @@ bgp_update_attrs(struct bgp_proto *p, struct bgp_channel *c, rte *e, ea_list *at
}
void
-bgp_rt_notify(struct proto *P, struct channel *C, net *n, rte *new, rte *old)
+bgp_rt_notify(struct proto *P, struct channel *C, const net_addr *n, rte *new, const rte *old)
{
struct bgp_proto *p = (void *) P;
struct bgp_channel *c = (void *) C;
@@ -1843,21 +1847,19 @@ bgp_rt_notify(struct proto *P, struct channel *C, net *n, rte *new, rte *old)
if (new)
{
- struct ea_list *attrs = bgp_update_attrs(p, c, new, new->attrs->eattrs, bgp_linpool2);
+ struct ea_list *attrs = bgp_update_attrs(p, c, new, new->attrs->eattrs, C->rte_update_pool);
/* If attributes are invalid, we fail back to withdraw */
buck = attrs ? bgp_get_bucket(c, attrs) : bgp_get_withdraw_bucket(c);
- path = new->attrs->src->global_id;
-
- lp_flush(bgp_linpool2);
+ path = new->src->global_id;
}
else
{
buck = bgp_get_withdraw_bucket(c);
- path = old->attrs->src->global_id;
+ path = old->src->global_id;
}
- px = bgp_get_prefix(c, n->n.addr, c->add_path_tx ? path : 0);
+ px = bgp_get_prefix(c, n, c->add_path_tx ? path : 0);
add_tail(&buck->prefixes, &px->buck_node);
bgp_schedule_packet(p->conn, c, PKT_UPDATE);
@@ -1874,42 +1876,52 @@ bgp_get_neighbor(rte *r)
return as;
/* If AS_PATH is not defined, we treat rte as locally originated */
- struct bgp_proto *p = (void *) r->attrs->src->proto;
+ struct bgp_proto *p = bgp_rte_proto(r);
return p->cf->confederation ?: p->local_as;
}
static inline int
rte_stale(rte *r)
{
- if (r->u.bgp.stale < 0)
+ if (r->pflags & BGP_REF_STALE)
+ return 1;
+
+ if (r->pflags & BGP_REF_NOT_STALE)
+ return 0;
+
+ /* If staleness is unknown, compute and cache it */
+ eattr *a = ea_find(r->attrs->eattrs, EA_CODE(PROTOCOL_BGP, BA_COMMUNITY));
+ if (a && int_set_contains(a->u.ptr, BGP_COMM_LLGR_STALE))
{
- /* If staleness is unknown, compute and cache it */
- eattr *a = ea_find(r->attrs->eattrs, EA_CODE(PROTOCOL_BGP, BA_COMMUNITY));
- r->u.bgp.stale = a && int_set_contains(a->u.ptr, BGP_COMM_LLGR_STALE);
+ r->pflags |= BGP_REF_STALE;
+ return 1;
+ }
+ else
+ {
+ r->pflags |= BGP_REF_NOT_STALE;
+ return 0;
}
-
- return r->u.bgp.stale;
}
int
bgp_rte_better(rte *new, rte *old)
{
- struct bgp_proto *new_bgp = (struct bgp_proto *) new->attrs->src->proto;
- struct bgp_proto *old_bgp = (struct bgp_proto *) old->attrs->src->proto;
+ struct bgp_proto *new_bgp = bgp_rte_proto(new);
+ struct bgp_proto *old_bgp = bgp_rte_proto(old);
eattr *x, *y;
u32 n, o;
/* Skip suppressed routes (see bgp_rte_recalculate()) */
- n = new->u.bgp.suppressed;
- o = old->u.bgp.suppressed;
+ n = new->pflags & BGP_REF_SUPPRESSED;
+ o = old->pflags & BGP_REF_SUPPRESSED;
if (n > o)
return 0;
if (n < o)
return 1;
/* RFC 4271 9.1.2.1. Route resolvability test */
- n = rte_resolvable(new);
- o = rte_resolvable(old);
+ n = rta_resolvable(new->attrs);
+ o = rta_resolvable(old->attrs);
if (n > o)
return 1;
if (n < o)
@@ -1934,8 +1946,8 @@ bgp_rte_better(rte *new, rte *old)
return 0;
/* RFC 7311 4.1 - Apply AIGP metric */
- u64 n2 = bgp_total_aigp_metric(new);
- u64 o2 = bgp_total_aigp_metric(old);
+ u64 n2 = bgp_total_aigp_metric(new->attrs);
+ u64 o2 = bgp_total_aigp_metric(old->attrs);
if (n2 < o2)
return 1;
if (n2 > o2)
@@ -2039,21 +2051,18 @@ bgp_rte_better(rte *new, rte *old)
int
bgp_rte_mergable(rte *pri, rte *sec)
{
- struct bgp_proto *pri_bgp = (struct bgp_proto *) pri->attrs->src->proto;
- struct bgp_proto *sec_bgp = (struct bgp_proto *) sec->attrs->src->proto;
+ struct bgp_proto *pri_bgp = bgp_rte_proto(pri);
+ struct bgp_proto *sec_bgp = bgp_rte_proto(sec);
eattr *x, *y;
u32 p, s;
/* Skip suppressed routes (see bgp_rte_recalculate()) */
- if (pri->u.bgp.suppressed != sec->u.bgp.suppressed)
+ /* LLGR draft - depreference stale routes */
+ if (pri->pflags != sec->pflags)
return 0;
/* RFC 4271 9.1.2.1. Route resolvability test */
- if (rte_resolvable(pri) != rte_resolvable(sec))
- return 0;
-
- /* LLGR draft - depreference stale routes */
- if (rte_stale(pri) != rte_stale(sec))
+ if (rta_resolvable(pri->attrs) != rta_resolvable(sec->attrs))
return 0;
/* Start with local preferences */
@@ -2118,24 +2127,23 @@ bgp_rte_mergable(rte *pri, rte *sec)
static inline int
same_group(rte *r, u32 lpref, u32 lasn)
{
- return (r->pref == lpref) && (bgp_get_neighbor(r) == lasn);
+ return (r->attrs->pref == lpref) && (bgp_get_neighbor(r) == lasn);
}
static inline int
-use_deterministic_med(rte *r)
+use_deterministic_med(struct rte_storage *r)
{
- struct proto *P = r->attrs->src->proto;
- return (P->proto == &proto_bgp) && ((struct bgp_proto *) P)->cf->deterministic_med;
+ struct bgp_proto *p = bgp_rte_proto(&r->rte);
+ return p && p->cf->deterministic_med;
}
int
-bgp_rte_recalculate(rtable *table, net *net, rte *new, rte *old, rte *old_best)
+bgp_rte_recalculate(rtable_private *table, net *net, rte *new, rte *old, rte *old_best)
{
- rte *r, *s;
rte *key = new ? new : old;
- u32 lpref = key->pref;
+ u32 lpref = key->attrs->pref;
u32 lasn = bgp_get_neighbor(key);
- int old_suppressed = old ? old->u.bgp.suppressed : 0;
+ int old_suppressed = old ? !!(old->pflags & BGP_REF_SUPPRESSED) : 0;
/*
* Proper RFC 4271 path selection is a bit complicated, it cannot be
@@ -2187,11 +2195,11 @@ bgp_rte_recalculate(rtable *table, net *net, rte *new, rte *old, rte *old_best)
*/
if (new)
- new->u.bgp.suppressed = 1;
+ new->pflags |= BGP_REF_SUPPRESSED;
if (old)
{
- old->u.bgp.suppressed = 1;
+ old->pflags |= BGP_REF_SUPPRESSED;
/* The fast case - replace not best with worse (or remove not best) */
if (old_suppressed && !(new && bgp_rte_better(new, old)))
@@ -2199,13 +2207,13 @@ bgp_rte_recalculate(rtable *table, net *net, rte *new, rte *old, rte *old_best)
}
/* The default case - find a new best-in-group route */
- r = new; /* new may not be in the list */
- for (s=net->routes; rte_is_valid(s); s=s->next)
- if (use_deterministic_med(s) && same_group(s, lpref, lasn))
+ rte *r = new; /* new may not be in the list */
+ for (struct rte_storage *s = net->routes; rte_is_valid(&s->rte); s = s->next)
+ if (use_deterministic_med(s) && same_group(&s->rte, lpref, lasn))
{
- s->u.bgp.suppressed = 1;
- if (!r || bgp_rte_better(s, r))
- r = s;
+ s->rte.pflags |= BGP_REF_SUPPRESSED;
+ if (!r || bgp_rte_better(&s->rte, r))
+ r = &s->rte;
}
/* Simple case - the last route in group disappears */
@@ -2214,16 +2222,16 @@ bgp_rte_recalculate(rtable *table, net *net, rte *new, rte *old, rte *old_best)
/* Found if new is mergable with best-in-group */
if (new && (new != r) && bgp_rte_mergable(r, new))
- new->u.bgp.suppressed = 0;
+ new->pflags &= ~BGP_REF_SUPPRESSED;
/* Found all existing routes mergable with best-in-group */
- for (s=net->routes; rte_is_valid(s); s=s->next)
- if (use_deterministic_med(s) && same_group(s, lpref, lasn))
- if ((s != r) && bgp_rte_mergable(r, s))
- s->u.bgp.suppressed = 0;
+ for (struct rte_storage *s = net->routes; rte_is_valid(&s->rte); s = s->next)
+ if (use_deterministic_med(s) && same_group(&s->rte, lpref, lasn))
+ if ((&s->rte != r) && bgp_rte_mergable(r, &s->rte))
+ s->rte.pflags &= ~BGP_REF_SUPPRESSED;
/* Found best-in-group */
- r->u.bgp.suppressed = 0;
+ r->pflags &= ~BGP_REF_SUPPRESSED;
/*
* There are generally two reasons why we have to force
@@ -2255,25 +2263,44 @@ bgp_rte_recalculate(rtable *table, net *net, rte *new, rte *old, rte *old_best)
return !old_suppressed;
}
-struct rte *
-bgp_rte_modify_stale(struct rte *r, struct linpool *pool)
+void
+bgp_rte_modify_stale(struct rt_export_request *req, const net_addr *n, struct rt_pending_export *rpe UNUSED, rte **feed, uint count)
{
- eattr *a = ea_find(r->attrs->eattrs, EA_CODE(PROTOCOL_BGP, BA_COMMUNITY));
- const struct adata *ad = a ? a->u.ptr : NULL;
- uint flags = a ? a->flags : BAF_PARTIAL;
+ struct bgp_channel *c = SKIP_BACK(struct bgp_channel, stale_feed, req);
- if (ad && int_set_contains(ad, BGP_COMM_NO_LLGR))
- return NULL;
+ do {
+ rte *r = feed[--count];
+ if (r->sender != c->c.in_req.hook)
+ continue;
+
+ /* A new route, do not mark as stale */
+ if (r->stale_cycle == c->c.in_req.hook->stale_set)
+ continue;
+
+ eattr *ea = ea_find(r->attrs->eattrs, EA_CODE(PROTOCOL_BGP, BA_COMMUNITY));
+ const struct adata *ad = ea ? ea->u.ptr : NULL;
+ uint flags = ea ? ea->flags : BAF_PARTIAL;
+
+ rte e0 = *r;
+ e0.flags |= REF_USE_STALE;
- if (ad && int_set_contains(ad, BGP_COMM_LLGR_STALE))
- return r;
+ if (ad && int_set_contains(ad, BGP_COMM_NO_LLGR))
+ rte_import(&c->c.in_req, n, NULL, r->src);
- r = rte_cow_rta(r, pool);
- bgp_set_attr_ptr(&(r->attrs->eattrs), pool, BA_COMMUNITY, flags,
- int_set_add(pool, ad, BGP_COMM_LLGR_STALE));
- r->u.bgp.stale = 1;
+ else if (ad && int_set_contains(ad, BGP_COMM_LLGR_STALE))
+ rte_import(&c->c.in_req, n, &e0, r->src);
- return r;
+ else {
+ rta *a = e0.attrs = rta_do_cow(r->attrs, c->c.rte_update_pool);
+
+ bgp_set_attr_ptr(&(a->eattrs), c->c.rte_update_pool, BA_COMMUNITY, flags,
+ int_set_add(c->c.rte_update_pool, ad, BGP_COMM_LLGR_STALE));
+ e0.pflags |= BGP_REF_STALE;
+
+ rte_import(&c->c.in_req, n, &e0, r->src);
+ lp_flush(c->c.rte_update_pool);
+ }
+ } while (count);
}
@@ -2356,22 +2383,22 @@ bgp_get_route_info(rte *e, byte *buf)
eattr *o = ea_find(e->attrs->eattrs, EA_CODE(PROTOCOL_BGP, BA_ORIGIN));
u32 origas;
- buf += bsprintf(buf, " (%d", e->pref);
+ buf += bsprintf(buf, " (%d", e->attrs->pref);
- if (e->u.bgp.suppressed)
+ if (e->pflags & BGP_REF_SUPPRESSED)
buf += bsprintf(buf, "-");
if (rte_stale(e))
buf += bsprintf(buf, "s");
- u64 metric = bgp_total_aigp_metric(e);
+ u64 metric = bgp_total_aigp_metric(e->attrs);
if (metric < BGP_AIGP_MAX)
{
buf += bsprintf(buf, "/%lu", metric);
}
else if (e->attrs->igp_metric)
{
- if (!rte_resolvable(e))
+ if (!rta_resolvable(e->attrs))
buf += bsprintf(buf, "/-");
else if (e->attrs->igp_metric >= IGP_METRIC_UNKNOWN)
buf += bsprintf(buf, "/?");
diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c
index e4d754b1..65cc3a40 100644
--- a/proto/bgp/bgp.c
+++ b/proto/bgp/bgp.c
@@ -124,11 +124,8 @@
#include "bgp.h"
-
-struct linpool *bgp_linpool; /* Global temporary pool */
-struct linpool *bgp_linpool2; /* Global temporary pool for bgp_rt_notify() */
-static list bgp_sockets; /* Global list of listening sockets */
-
+/* Global list of listening sockets */
+static list STATIC_LIST_INIT(bgp_sockets);
static void bgp_connect(struct bgp_proto *p);
static void bgp_active(struct bgp_proto *p);
@@ -140,6 +137,15 @@ static void bgp_update_bfd(struct bgp_proto *p, const struct bfd_options *bfd);
static int bgp_incoming_connection(sock *sk, uint dummy UNUSED);
static void bgp_listen_sock_err(sock *sk UNUSED, int err);
+static void bgp_graceful_restart_feed(struct bgp_channel *c);
+static inline void channel_refresh_end_reload(struct channel *c)
+{
+ channel_refresh_end(c);
+
+ if (c->in_table)
+ channel_request_reload(c);
+}
+
/**
* bgp_open - open a BGP instance
* @p: BGP instance
@@ -152,16 +158,14 @@ static void bgp_listen_sock_err(sock *sk UNUSED, int err);
static int
bgp_open(struct bgp_proto *p)
{
+ ASSERT_DIE(birdloop_inside(&main_birdloop));
+
struct bgp_socket *bs = NULL;
struct iface *ifa = p->cf->strict_bind ? p->cf->iface : NULL;
ip_addr addr = p->cf->strict_bind ? p->cf->local_ip :
(p->ipv4 ? IPA_NONE4 : IPA_NONE6);
uint port = p->cf->local_port;
- /* FIXME: Add some global init? */
- if (!bgp_linpool)
- init_list(&bgp_sockets);
-
/* We assume that cf->iface is defined iff cf->local_ip is link-local */
WALK_LIST(bs, bgp_sockets)
@@ -180,7 +184,7 @@ bgp_open(struct bgp_proto *p)
sk->sport = port;
sk->iface = ifa;
sk->vrf = p->p.vrf;
- sk->flags = 0;
+ sk->flags = SKF_PASSIVE_THREAD;
sk->tos = IP_PREC_INTERNET_CONTROL;
sk->rbsize = BGP_RX_BUFFER_SIZE;
sk->tbsize = BGP_TX_BUFFER_SIZE;
@@ -198,12 +202,6 @@ bgp_open(struct bgp_proto *p)
add_tail(&bgp_sockets, &bs->n);
- if (!bgp_linpool)
- {
- bgp_linpool = lp_new_default(proto_pool);
- bgp_linpool2 = lp_new_default(proto_pool);
- }
-
return 0;
err:
@@ -222,6 +220,7 @@ err:
static void
bgp_close(struct bgp_proto *p)
{
+ ASSERT_DIE(birdloop_inside(&main_birdloop));
struct bgp_socket *bs = p->sock;
ASSERT(bs && bs->uc);
@@ -232,15 +231,6 @@ bgp_close(struct bgp_proto *p)
rfree(bs->sk);
rem_node(&bs->n);
mb_free(bs);
-
- if (!EMPTY_LIST(bgp_sockets))
- return;
-
- rfree(bgp_linpool);
- bgp_linpool = NULL;
-
- rfree(bgp_linpool2);
- bgp_linpool2 = NULL;
}
static inline int
@@ -325,7 +315,7 @@ bgp_initiate(struct bgp_proto *p)
{
p->start_state = BSS_DELAY;
BGP_TRACE(D_EVENTS, "Startup delayed by %d seconds due to errors", p->startup_delay);
- bgp_start_timer(p->startup_timer, p->startup_delay);
+ bgp_start_timer(p, p->startup_timer, p->startup_delay);
}
else
bgp_startup(p);
@@ -346,6 +336,7 @@ err1:
/**
* bgp_start_timer - start a BGP timer
+ * @p: bgp_proto which the timer belongs to
* @t: timer
* @value: time (in seconds) to fire (0 to disable the timer)
*
@@ -354,14 +345,16 @@ err1:
* timers.
*/
void
-bgp_start_timer(timer *t, uint value)
+bgp_start_timer(struct bgp_proto *p, timer *t, uint value)
{
+ BGP_ASSERT_INSIDE(p);
+
if (value)
{
/* The randomization procedure is specified in RFC 4271 section 10 */
btime time = value S;
btime randomize = random() % ((time / 4) + 1);
- tm_start(t, time - randomize);
+ tm_start_in(t, time - randomize, p->p.loop);
}
else
tm_stop(t);
@@ -377,7 +370,7 @@ bgp_start_timer(timer *t, uint value)
void
bgp_close_conn(struct bgp_conn *conn)
{
- // struct bgp_proto *p = conn->bgp;
+ BGP_ASSERT_INSIDE(conn->bgp);
DBG("BGP: Closing connection\n");
conn->packets_to_send = 0;
@@ -469,6 +462,8 @@ bgp_graceful_close_conn(struct bgp_conn *conn, int subcode, byte *data, uint len
static void
bgp_down(struct bgp_proto *p)
{
+ bgp_start_timer(p, p->startup_timer, 0);
+
if (p->start_state > BSS_PREPARE)
{
bgp_setup_auth(p, 0);
@@ -482,21 +477,34 @@ bgp_down(struct bgp_proto *p)
}
static void
-bgp_decision(void *vp)
+bgp_active_event(void *vp)
{
struct bgp_proto *p = vp;
- DBG("BGP: Decision start\n");
+ BGP_ASSERT_INSIDE(p);
+
+ DBG("%s: Decision start\n", p->p.name);
if ((p->p.proto_state == PS_START) &&
(p->outgoing_conn.state == BS_IDLE) &&
(p->incoming_conn.state != BS_OPENCONFIRM) &&
!p->passive)
bgp_active(p);
+}
+
+static void
+bgp_down_event(void *vp)
+{
+ struct bgp_proto *p = vp;
+ BGP_ENTER(p);
+
+ DBG("%s: Down event\n", p->p.name);
if ((p->p.proto_state == PS_STOP) &&
(p->outgoing_conn.state == BS_IDLE) &&
(p->incoming_conn.state == BS_IDLE))
bgp_down(p);
+
+ BGP_LEAVE(p);
}
static struct bgp_proto *
@@ -528,7 +536,7 @@ bgp_stop(struct bgp_proto *p, int subcode, byte *data, uint len)
proto_notify_state(&p->p, PS_STOP);
bgp_graceful_close_conn(&p->outgoing_conn, subcode, data, len);
bgp_graceful_close_conn(&p->incoming_conn, subcode, data, len);
- ev_schedule(p->event);
+ ev_send_loop(&main_birdloop, p->down_event);
}
static inline void
@@ -575,6 +583,7 @@ bgp_conn_enter_established_state(struct bgp_conn *conn)
p->link_addr = p->neigh->iface->llv6->ip;
conn->sk->fast_rx = 0;
+ conn->sk->cork = &rt_cork;
p->conn = conn;
p->last_error_class = 0;
@@ -719,7 +728,7 @@ bgp_conn_enter_close_state(struct bgp_conn *conn)
conn->sk->rx_hook = NULL;
/* Timeout for CLOSE state, if we cannot send notification soon then we just hangup */
- bgp_start_timer(conn->hold_timer, 10);
+ bgp_start_timer(p, conn->hold_timer, 10);
if (os == BS_ESTABLISHED)
bgp_conn_leave_established_state(p);
@@ -733,7 +742,8 @@ bgp_conn_enter_idle_state(struct bgp_conn *conn)
bgp_close_conn(conn);
bgp_conn_set_state(conn, BS_IDLE);
- ev_schedule(p->event);
+ ev_send_loop(p->p.loop, p->active_event);
+ ev_send_loop(&main_birdloop, p->down_event);
if (os == BS_ESTABLISHED)
bgp_conn_leave_established_state(p);
@@ -775,25 +785,25 @@ bgp_handle_graceful_restart(struct bgp_proto *p)
{
case BGP_GRS_NONE:
c->gr_active = BGP_GRS_ACTIVE;
- rt_refresh_begin(c->c.table, &c->c);
+ channel_refresh_begin(&c->c);
break;
case BGP_GRS_ACTIVE:
- rt_refresh_end(c->c.table, &c->c);
- rt_refresh_begin(c->c.table, &c->c);
+ channel_refresh_end(&c->c);
+ channel_refresh_begin(&c->c);
break;
case BGP_GRS_LLGR:
- rt_refresh_begin(c->c.table, &c->c);
- rt_modify_stale(c->c.table, &c->c);
+ channel_refresh_begin(&c->c);
+ bgp_graceful_restart_feed(c);
break;
}
}
else
{
/* Just flush the routes */
- rt_refresh_begin(c->c.table, &c->c);
- rt_refresh_end(c->c.table, &c->c);
+ channel_refresh_begin(&c->c);
+ channel_refresh_end(&c->c);
}
/* Reset bucket and prefix tables */
@@ -808,9 +818,54 @@ bgp_handle_graceful_restart(struct bgp_proto *p)
ASSERT(p->gr_active_num > 0);
proto_notify_state(&p->p, PS_START);
- tm_start(p->gr_timer, p->conn->remote_caps->gr_time S);
+ tm_start_in(p->gr_timer, p->conn->remote_caps->gr_time S, p->p.loop);
+}
+
+static void
+bgp_graceful_restart_feed_done(struct rt_export_request *req)
+{
+ req->hook = NULL;
}
+static void
+bgp_graceful_restart_feed_dump_req(struct rt_export_request *req)
+{
+ struct bgp_channel *c = SKIP_BACK(struct bgp_channel, stale_feed, req);
+ debug(" BGP-GR %s.%s export request %p\n", c->c.proto->name, c->c.name, req);
+}
+
+static void
+bgp_graceful_restart_feed_log_state_change(struct rt_export_request *req, u8 state)
+{
+ struct bgp_channel *c = SKIP_BACK(struct bgp_channel, stale_feed, req);
+ struct bgp_proto *p = (void *) c->c.proto;
+ BGP_TRACE(D_EVENTS, "Long-lived graceful restart export state changed to %s", rt_export_state_name(state));
+
+ if (state == TES_READY)
+ rt_stop_export(req, bgp_graceful_restart_feed_done);
+}
+
+static void
+bgp_graceful_restart_drop_export(struct rt_export_request *req UNUSED, const net_addr *n UNUSED, struct rt_pending_export *rpe UNUSED)
+{ /* Nothing to do */ }
+
+static void
+bgp_graceful_restart_feed(struct bgp_channel *c)
+{
+ c->stale_feed = (struct rt_export_request) {
+ .name = "BGP-GR",
+ .list = &global_work_list,
+ .trace_routes = c->c.debug | c->c.proto->debug,
+ .dump_req = bgp_graceful_restart_feed_dump_req,
+ .log_state_change = bgp_graceful_restart_feed_log_state_change,
+ .export_bulk = bgp_rte_modify_stale,
+ .export_one = bgp_graceful_restart_drop_export,
+ };
+
+ rt_request_export(c->c.table, &c->stale_feed);
+}
+
+
/**
* bgp_graceful_restart_done - finish active BGP graceful restart
* @c: BGP channel
@@ -833,8 +888,11 @@ bgp_graceful_restart_done(struct bgp_channel *c)
if (!p->gr_active_num)
BGP_TRACE(D_EVENTS, "Neighbor graceful restart done");
+ if (c->stale_feed.hook)
+ rt_stop_export(&c->stale_feed, bgp_graceful_restart_feed_done);
+
tm_stop(c->stale_timer);
- rt_refresh_end(c->c.table, &c->c);
+ channel_refresh_end_reload(&c->c);
}
/**
@@ -875,8 +933,8 @@ bgp_graceful_restart_timeout(timer *t)
/* Channel is in GR, and supports LLGR -> start LLGR */
c->gr_active = BGP_GRS_LLGR;
- tm_start(c->stale_timer, c->stale_time S);
- rt_modify_stale(c->c.table, &c->c);
+ tm_start_in(c->stale_timer, c->stale_time S, p->p.loop);
+ bgp_graceful_restart_feed(c);
}
}
else
@@ -913,11 +971,11 @@ bgp_refresh_begin(struct bgp_channel *c)
if (c->load_state == BFS_LOADING)
{ log(L_WARN "%s: BEGIN-OF-RR received before END-OF-RIB, ignoring", p->p.name); return; }
- c->load_state = BFS_REFRESHING;
- rt_refresh_begin(c->c.table, &c->c);
+ if (c->load_state == BFS_REFRESHING)
+ channel_refresh_end(&c->c);
- if (c->c.in_table)
- rt_refresh_begin(c->c.in_table, &c->c);
+ c->load_state = BFS_REFRESHING;
+ channel_refresh_begin(&c->c);
}
/**
@@ -938,10 +996,7 @@ bgp_refresh_end(struct bgp_channel *c)
{ log(L_WARN "%s: END-OF-RR received without prior BEGIN-OF-RR, ignoring", p->p.name); return; }
c->load_state = BFS_NONE;
- rt_refresh_end(c->c.table, &c->c);
-
- if (c->c.in_table)
- rt_prune_sync(c->c.in_table, 0);
+ channel_refresh_end_reload(&c->c);
}
@@ -955,7 +1010,7 @@ bgp_send_open(struct bgp_conn *conn)
bgp_prepare_capabilities(conn);
bgp_schedule_packet(conn, NULL, PKT_OPEN);
bgp_conn_set_state(conn, BS_OPENSENT);
- bgp_start_timer(conn->hold_timer, conn->bgp->cf->initial_hold_time);
+ bgp_start_timer(conn->bgp, conn->hold_timer, conn->bgp->cf->initial_hold_time);
}
static void
@@ -1032,7 +1087,7 @@ bgp_hold_timeout(timer *t)
and perhaps just not processed BGP packets in time. */
if (sk_rx_ready(conn->sk) > 0)
- bgp_start_timer(conn->hold_timer, 10);
+ bgp_start_timer(p, conn->hold_timer, 10);
else if ((conn->state == BS_ESTABLISHED) && p->llgr_ready)
{
BGP_TRACE(D_EVENTS, "Hold timer expired");
@@ -1077,10 +1132,12 @@ bgp_setup_conn(struct bgp_proto *p, struct bgp_conn *conn)
static void
bgp_setup_sk(struct bgp_conn *conn, sock *s)
{
+ ASSERT_DIE(s->flags & SKF_THREAD);
s->data = conn;
s->err_hook = bgp_sock_err;
s->fast_rx = 1;
conn->sk = s;
+ sk_start(s);
}
static void
@@ -1089,10 +1146,12 @@ bgp_active(struct bgp_proto *p)
int delay = MAX(1, p->cf->connect_delay_time);
struct bgp_conn *conn = &p->outgoing_conn;
+ BGP_ASSERT_INSIDE(p);
+
BGP_TRACE(D_EVENTS, "Connect delayed by %d seconds", delay);
bgp_setup_conn(p, conn);
bgp_conn_set_state(conn, BS_ACTIVE);
- bgp_start_timer(conn->connect_timer, delay);
+ bgp_start_timer(p, conn->connect_timer, delay);
}
/**
@@ -1109,9 +1168,12 @@ bgp_connect(struct bgp_proto *p) /* Enter Connect state and start establishing c
struct bgp_conn *conn = &p->outgoing_conn;
int hops = p->cf->multihop ? : 1;
+ BGP_ASSERT_INSIDE(p);
+
DBG("BGP: Connecting\n");
sock *s = sk_new(p->p.pool);
s->type = SK_TCP_ACTIVE;
+ s->flags |= SKF_THREAD;
s->saddr = p->local_ip;
s->daddr = p->remote_ip;
s->dport = p->cf->remote_port;
@@ -1139,7 +1201,7 @@ bgp_connect(struct bgp_proto *p) /* Enter Connect state and start establishing c
goto err;
DBG("BGP: Waiting for connect success\n");
- bgp_start_timer(conn->connect_timer, p->cf->connect_retry_time);
+ bgp_start_timer(p, conn->connect_timer, p->cf->connect_retry_time);
return;
err:
@@ -1211,6 +1273,18 @@ bgp_incoming_connection(sock *sk, uint dummy UNUSED)
return 0;
}
+ if (p->p.loop == &main_birdloop)
+ {
+ /* Protocol is down for whatever reason. No need for locking. */
+ BGP_TRACE(D_EVENTS, "Incoming connection from %I%J (port %d) rejected (protocol is down)",
+ sk->daddr, ipa_is_link_local(sk->daddr) ? sk->iface : NULL,
+ sk->dport);
+ rfree(sk);
+ return 0;
+ }
+
+ BGP_ENTER(p);
+
/*
* BIRD should keep multiple incoming connections in OpenSent state (for
* details RFC 4271 8.2.1 par 3), but it keeps just one. Duplicate incoming
@@ -1240,6 +1314,7 @@ bgp_incoming_connection(sock *sk, uint dummy UNUSED)
if (!acc)
{
rfree(sk);
+ BGP_LEAVE(p);
return 0;
}
@@ -1265,6 +1340,7 @@ bgp_incoming_connection(sock *sk, uint dummy UNUSED)
p = bgp_spawn(p, sk->daddr);
p->postponed_sk = sk;
rmove(sk, p->p.pool);
+ BGP_LEAVE(p);
return 0;
}
@@ -1272,12 +1348,14 @@ bgp_incoming_connection(sock *sk, uint dummy UNUSED)
bgp_setup_conn(p, &p->incoming_conn);
bgp_setup_sk(&p->incoming_conn, sk);
bgp_send_open(&p->incoming_conn);
+ BGP_LEAVE(p);
return 0;
err:
sk_log_error(sk, p->p.name);
log(L_ERR "%s: Incoming connection aborted", p->p.name);
rfree(sk);
+ BGP_LEAVE(p);
return 0;
}
@@ -1312,10 +1390,9 @@ bgp_neigh_notify(neighbor *n)
struct bgp_proto *p = (struct bgp_proto *) n->proto;
int ps = p->p.proto_state;
- if (n != p->neigh)
- return;
+ BGP_ASSERT_INSIDE(p);
- if ((ps == PS_DOWN) || (ps == PS_STOP))
+ if ((n != p->neigh) || (ps == PS_DOWN) || (ps == PS_STOP))
return;
int prepare = (ps == PS_START) && (p->start_state == BSS_PREPARE);
@@ -1393,7 +1470,7 @@ bgp_update_bfd(struct bgp_proto *p, const struct bfd_options *bfd)
if (bfd && !p->bfd_req && !bgp_is_dynamic(p))
p->bfd_req = bfd_request_session(p->p.pool, p->remote_ip, p->local_ip,
p->cf->multihop ? NULL : p->neigh->iface,
- p->p.vrf, bgp_bfd_notify, p, bfd);
+ p->p.vrf, bgp_bfd_notify, p, birdloop_event_list(p->p.loop), bfd);
if (!bfd && p->bfd_req)
{
@@ -1408,12 +1485,9 @@ bgp_reload_routes(struct channel *C)
struct bgp_proto *p = (void *) C->proto;
struct bgp_channel *c = (void *) C;
- ASSERT(p->conn && (p->route_refresh || c->c.in_table));
+ ASSERT(p->conn && (p->route_refresh));
- if (c->c.in_table)
- channel_schedule_reload(C);
- else
- bgp_schedule_packet(p->conn, c, PKT_ROUTE_REFRESH);
+ bgp_schedule_packet(p->conn, c, PKT_ROUTE_REFRESH);
}
static void
@@ -1474,9 +1548,12 @@ bgp_start_locked(struct object_lock *lock)
struct bgp_proto *p = lock->data;
const struct bgp_config *cf = p->cf;
+ BGP_ENTER(p);
+
if (p->p.proto_state != PS_START)
{
DBG("BGP: Got lock in different state %d\n", p->p.proto_state);
+ BGP_LEAVE(p);
return;
}
@@ -1486,10 +1563,11 @@ bgp_start_locked(struct object_lock *lock)
{
/* Multi-hop sessions do not use neighbor entries */
bgp_initiate(p);
+ BGP_LEAVE(p);
return;
}
- neighbor *n = neigh_find(&p->p, p->remote_ip, cf->iface, NEF_STICKY);
+ neighbor *n = neigh_find(&p->p, p->remote_ip, cf->iface, NEF_STICKY | NEF_NOTIFY_MAIN);
if (!n)
{
log(L_ERR "%s: Invalid remote address %I%J", p->p.name, p->remote_ip, cf->iface);
@@ -1497,6 +1575,7 @@ bgp_start_locked(struct object_lock *lock)
p->p.disabled = 1;
bgp_store_error(p, NULL, BE_MISC, BEM_INVALID_NEXT_HOP);
proto_notify_state(&p->p, PS_DOWN);
+ BGP_LEAVE(p);
return;
}
@@ -1508,6 +1587,8 @@ bgp_start_locked(struct object_lock *lock)
BGP_TRACE(D_EVENTS, "Waiting for link on %s", n->iface->name);
else
bgp_start_neighbor(p);
+
+ BGP_LEAVE(p);
}
static int
@@ -1546,10 +1627,13 @@ bgp_start(struct proto *P)
p->stats.rx_bytes = p->stats.tx_bytes = 0;
p->last_rx_update = 0;
- p->event = ev_new_init(p->p.pool, bgp_decision, p);
+ p->active_event = ev_new_init(p->p.pool, bgp_active_event, p);
+ p->down_event = ev_new_init(p->p.pool, bgp_down_event, p);
p->startup_timer = tm_new_init(p->p.pool, bgp_startup_timeout, p, 0, 0);
p->gr_timer = tm_new_init(p->p.pool, bgp_graceful_restart_timeout, p, 0, 0);
+ p->rx_lp = lp_new_default(p->p.pool);
+
p->local_id = proto_get_router_id(P->cf);
if (p->rr_client)
p->rr_cluster_id = p->cf->rr_cluster_id ? p->cf->rr_cluster_id : p->local_id;
@@ -1677,6 +1761,13 @@ done:
return p->p.proto_state;
}
+struct rte_owner_class bgp_rte_owner_class = {
+ .get_route_info = bgp_get_route_info,
+ .rte_better = bgp_rte_better,
+ .rte_mergable = bgp_rte_mergable,
+ .rte_igp_metric = bgp_rte_igp_metric,
+};
+
static struct proto *
bgp_init(struct proto_config *CF)
{
@@ -1690,10 +1781,9 @@ bgp_init(struct proto_config *CF)
P->reload_routes = bgp_reload_routes;
P->feed_begin = bgp_feed_begin;
P->feed_end = bgp_feed_end;
- P->rte_better = bgp_rte_better;
- P->rte_mergable = bgp_rte_mergable;
- P->rte_recalculate = cf->deterministic_med ? bgp_rte_recalculate : NULL;
- P->rte_modify = bgp_rte_modify_stale;
+
+ P->sources.class = &bgp_rte_owner_class;
+ P->sources.rte_recalculate = cf->deterministic_med ? bgp_rte_recalculate : NULL;
p->cf = cf;
p->is_internal = (cf->local_as == cf->remote_as);
@@ -1745,17 +1835,19 @@ bgp_channel_start(struct channel *C)
ip_addr src = p->local_ip;
if (c->igp_table_ip4)
- rt_lock_table(c->igp_table_ip4);
+ RT_LOCKED(c->igp_table_ip4, t)
+ rt_lock_table(t);
if (c->igp_table_ip6)
- rt_lock_table(c->igp_table_ip6);
+ RT_LOCKED(c->igp_table_ip6, t)
+ rt_lock_table(t);
c->pool = p->p.pool; // XXXX
bgp_init_bucket_table(c);
bgp_init_prefix_table(c);
if (c->cf->import_table)
- channel_setup_in_table(C);
+ channel_setup_in_table(C, 0);
if (c->cf->export_table)
channel_setup_out_table(C);
@@ -1829,10 +1921,12 @@ bgp_channel_cleanup(struct channel *C)
struct bgp_channel *c = (void *) C;
if (c->igp_table_ip4)
- rt_unlock_table(c->igp_table_ip4);
+ RT_LOCKED(c->igp_table_ip4, t)
+ rt_unlock_table(t);
if (c->igp_table_ip6)
- rt_unlock_table(c->igp_table_ip6);
+ RT_LOCKED(c->igp_table_ip6, t)
+ rt_unlock_table(t);
c->index = 0;
@@ -2430,6 +2524,9 @@ bgp_show_proto_info(struct proto *P)
{
struct bgp_proto *p = (struct bgp_proto *) P;
+ if (p->p.proto_state != PS_DOWN)
+ BGP_ASSERT_INSIDE(p);
+
cli_msg(-1006, " BGP state: %s", bgp_state_dsc(p));
if (bgp_is_dynamic(p) && p->cf->remote_range)
@@ -2556,6 +2653,5 @@ struct protocol proto_bgp = {
.copy_config = bgp_copy_config,
.get_status = bgp_get_status,
.get_attr = bgp_get_attr,
- .get_route_info = bgp_get_route_info,
.show_proto_info = bgp_show_proto_info
};
diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h
index cca4b448..d5ac3bd9 100644
--- a/proto/bgp/bgp.h
+++ b/proto/bgp/bgp.h
@@ -200,6 +200,10 @@ struct bgp_channel_config {
#define BGP_BFD_GRACEFUL 2 /* BFD down triggers graceful restart */
+/* rte->pflags */
+#define BGP_REF_SUPPRESSED 0x1 /* Used for deterministic MED comparison */
+#define BGP_REF_STALE 0x2 /* Route is LLGR_STATE */
+#define BGP_REF_NOT_STALE 0x4 /* Route is NOT LLGR_STATE */
struct bgp_af_caps {
u32 afi;
@@ -308,6 +312,7 @@ struct bgp_proto {
struct bgp_conn *conn; /* Connection we have established */
struct bgp_conn outgoing_conn; /* Outgoing connection we're working with */
struct bgp_conn incoming_conn; /* Incoming connection we have neither accepted nor rejected yet */
+ struct linpool *rx_lp; /* Linpool for parsing received updates */
struct object_lock *lock; /* Lock for neighbor connection */
struct neighbor *neigh; /* Neighbor entry corresponding to remote ip, NULL if multihop */
struct bgp_socket *sock; /* Shared listening socket */
@@ -317,7 +322,8 @@ struct bgp_proto {
btime last_established; /* Last time of enter/leave of established state */
btime last_rx_update; /* Last time of RX update */
ip_addr link_addr; /* Link-local version of local_ip */
- event *event; /* Event for respawning and shutting process */
+ event *active_event; /* Event for respawning */
+ event *down_event; /* Event to shut down */
timer *startup_timer; /* Timer used to delay protocol startup due to previous errors (startup_delay) */
timer *gr_timer; /* Timer waiting for reestablishment after graceful restart */
int dynamic_name_counter; /* Counter for dynamic BGP names */
@@ -362,6 +368,7 @@ struct bgp_channel {
timer *stale_timer; /* Long-lived stale timer for LLGR */
u32 stale_time; /* Stored LLGR stale time from last session */
+ struct rt_export_request stale_feed; /* Feeder request for stale route modification */
u8 add_path_rx; /* Session expects receive of ADD-PATH extended NLRI */
u8 add_path_tx; /* Session expects transmit of ADD-PATH extended NLRI */
@@ -489,11 +496,8 @@ bgp_parse_error(struct bgp_parse_state *s, uint subcode)
longjmp(s->err_jmpbuf, 1);
}
-extern struct linpool *bgp_linpool;
-extern struct linpool *bgp_linpool2;
-
-void bgp_start_timer(timer *t, uint value);
+void bgp_start_timer(struct bgp_proto *p, timer *t, uint value);
void bgp_check_config(struct bgp_config *c);
void bgp_error(struct bgp_conn *c, unsigned code, unsigned subcode, byte *data, int len);
void bgp_close_conn(struct bgp_conn *c);
@@ -513,11 +517,12 @@ struct rte_source *bgp_find_source(struct bgp_proto *p, u32 path_id);
struct rte_source *bgp_get_source(struct bgp_proto *p, u32 path_id);
static inline int
-rte_resolvable(rte *rt)
+rta_resolvable(rta *a)
{
- return rt->attrs->dest == RTD_UNICAST;
+ return a->dest == RTD_UNICAST;
}
+extern struct rte_owner_class bgp_rte_owner_class;
#ifdef LOCAL_DEBUG
#define BGP_FORCE_DEBUG 1
@@ -580,24 +585,31 @@ void bgp_free_prefix(struct bgp_channel *c, struct bgp_prefix *bp);
int bgp_rte_better(struct rte *, struct rte *);
int bgp_rte_mergable(rte *pri, rte *sec);
-int bgp_rte_recalculate(rtable *table, net *net, rte *new, rte *old, rte *old_best);
-struct rte *bgp_rte_modify_stale(struct rte *r, struct linpool *pool);
-void bgp_rt_notify(struct proto *P, struct channel *C, net *n, rte *new, rte *old);
-int bgp_preexport(struct proto *, struct rte **, struct linpool *);
+int bgp_rte_recalculate(rtable_private *table, net *net, rte *new, rte *old, rte *old_best);
+void bgp_rte_modify_stale(struct rt_export_request *, const net_addr *, struct rt_pending_export *, rte **, uint);
+u32 bgp_rte_igp_metric(struct rte *);
+void bgp_rt_notify(struct proto *P, struct channel *C, const net_addr *n, rte *new, const rte *old);
+int bgp_preexport(struct channel *, struct rte *);
int bgp_get_attr(const struct eattr *e, byte *buf, int buflen);
-void bgp_get_route_info(struct rte *, byte *buf);
-int bgp_total_aigp_metric_(rte *e, u64 *metric, const struct adata **ad);
+void bgp_get_route_info(struct rte *, byte *);
+int bgp_total_aigp_metric_(rta *a, u64 *metric, const struct adata **ad);
+
+static inline struct bgp_proto *bgp_rte_proto(struct rte *rte)
+{
+ return (rte->src->owner->class == &bgp_rte_owner_class) ?
+ SKIP_BACK(struct bgp_proto, p.sources, rte->src->owner) : NULL;
+}
#define BGP_AIGP_METRIC 1
#define BGP_AIGP_MAX U64(0xffffffffffffffff)
static inline u64
-bgp_total_aigp_metric(rte *r)
+bgp_total_aigp_metric(rta *a)
{
u64 metric = BGP_AIGP_MAX;
const struct adata *ad;
- bgp_total_aigp_metric_(r, &metric, &ad);
+ bgp_total_aigp_metric_(a, &metric, &ad);
return metric;
}
@@ -749,5 +761,10 @@ void bgp_update_next_hop(struct bgp_export_state *s, eattr *a, ea_list **to);
#define ORIGIN_EGP 1
#define ORIGIN_INCOMPLETE 2
+/* Loop */
+
+#define BGP_ENTER(bgp) birdloop_enter(bgp->p.loop)
+#define BGP_LEAVE(bgp) birdloop_leave(bgp->p.loop)
+#define BGP_ASSERT_INSIDE(bgp) ASSERT_DIE((bgp->p.loop != &main_birdloop) && birdloop_inside(bgp->p.loop))
#endif
diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y
index 2dfbdca9..8e42bbdb 100644
--- a/proto/bgp/config.Y
+++ b/proto/bgp/config.Y
@@ -48,6 +48,7 @@ proto: bgp_proto '}' ;
bgp_proto_start: proto_start BGP {
this_proto = proto_config_new(&proto_bgp, $1);
+ this_proto->loop_order = DOMAIN_ORDER(proto);
BGP_CFG->local_port = BGP_PORT;
BGP_CFG->remote_port = BGP_PORT;
BGP_CFG->multihop = -1; /* undefined */
diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c
index 99b5d5b4..88b66040 100644
--- a/proto/bgp/packets.c
+++ b/proto/bgp/packets.c
@@ -914,7 +914,7 @@ bgp_rx_open(struct bgp_conn *conn, byte *pkt, uint len)
conn->hold_time, conn->keepalive_time, p->remote_as, p->remote_id, conn->as4_session);
bgp_schedule_packet(conn, NULL, PKT_KEEPALIVE);
- bgp_start_timer(conn->hold_timer, conn->hold_time);
+ bgp_start_timer(p, conn->hold_timer, conn->hold_time);
bgp_conn_enter_openconfirm_state(conn);
}
@@ -971,7 +971,7 @@ bgp_apply_next_hop(struct bgp_parse_state *s, rta *a, ip_addr gw, ip_addr ll)
s->hostentry = rt_get_hostentry(tab, gw, ll, c->c.table);
if (!s->mpls)
- rta_apply_hostentry(a, s->hostentry, NULL);
+ rta_apply_hostentry(a, s->hostentry, NULL, s->pool);
/* With MPLS, hostentry is applied later in bgp_apply_mpls_labels() */
}
@@ -1005,7 +1005,7 @@ bgp_apply_mpls_labels(struct bgp_parse_state *s, rta *a, u32 *labels, uint lnum)
ms.len = lnum;
memcpy(ms.stack, labels, 4*lnum);
- rta_apply_hostentry(a, s->hostentry, &ms);
+ rta_apply_hostentry(a, s->hostentry, &ms, s->pool);
}
}
@@ -1339,6 +1339,8 @@ bgp_rte_update(struct bgp_parse_state *s, net_addr *n, u32 path_id, rta *a0)
{
if (path_id != s->last_id)
{
+ rt_unlock_source(s->last_src);
+
s->last_src = rt_get_source(&s->proto->p, path_id);
s->last_id = path_id;
@@ -1349,28 +1351,25 @@ bgp_rte_update(struct bgp_parse_state *s, net_addr *n, u32 path_id, rta *a0)
if (!a0)
{
/* Route withdraw */
- rte_update3(&s->channel->c, n, NULL, s->last_src);
+ rte_update(&s->channel->c, n, NULL, s->last_src);
return;
}
/* Prepare cached route attributes */
if (s->cached_rta == NULL)
{
- a0->src = s->last_src;
-
/* Workaround for rta_lookup() breaking eattrs */
ea_list *ea = a0->eattrs;
s->cached_rta = rta_lookup(a0);
a0->eattrs = ea;
}
- rta *a = rta_clone(s->cached_rta);
- rte *e = rte_get_temp(a);
+ rte e0 = {
+ .attrs = s->cached_rta,
+ .src = s->last_src,
+ };
- e->pflags = 0;
- e->u.bgp.suppressed = 0;
- e->u.bgp.stale = -1;
- rte_update3(&s->channel->c, n, e, s->last_src);
+ rte_update(&s->channel->c, n, &e0, s->last_src);
}
static void
@@ -2296,7 +2295,7 @@ again: ;
struct bgp_write_state s = {
.proto = p,
.channel = c,
- .pool = bgp_linpool,
+ .pool = c->c.rte_update_pool,
.mp_reach = (c->afi != BGP_AF_IPV4) || c->ext_next_hop,
.as4_session = p->as4_session,
.add_path = c->add_path_tx,
@@ -2424,6 +2423,7 @@ bgp_decode_nlri(struct bgp_parse_state *s, u32 afi, byte *nlri, uint len, ea_lis
s->last_id = 0;
s->last_src = s->proto->p.main_source;
+ rt_lock_source(s->last_src);
/*
* IPv4 BGP and MP-BGP may be used together in one update, therefore we do not
@@ -2440,6 +2440,7 @@ bgp_decode_nlri(struct bgp_parse_state *s, u32 afi, byte *nlri, uint len, ea_lis
a->scope = SCOPE_UNIVERSE;
a->from = s->proto->remote_ip;
a->eattrs = ea;
+ a->pref = c->c.preference;
c->desc->decode_next_hop(s, nh, nh_len, a);
bgp_finish_attrs(s, a);
@@ -2453,6 +2454,8 @@ bgp_decode_nlri(struct bgp_parse_state *s, u32 afi, byte *nlri, uint len, ea_lis
rta_free(s->cached_rta);
s->cached_rta = NULL;
+
+ rt_unlock_source(s->last_src);
}
static void
@@ -2472,12 +2475,12 @@ bgp_rx_update(struct bgp_conn *conn, byte *pkt, uint len)
if (conn->state != BS_ESTABLISHED)
{ bgp_error(conn, 5, fsm_err_subcode[conn->state], NULL, 0); return; }
- bgp_start_timer(conn->hold_timer, conn->hold_time);
+ bgp_start_timer(p, conn->hold_timer, conn->hold_time);
/* Initialize parse state */
struct bgp_parse_state s = {
.proto = p,
- .pool = bgp_linpool,
+ .pool = p->rx_lp,
.as4_session = p->as4_session,
};
@@ -2808,7 +2811,7 @@ bgp_fire_tx(struct bgp_conn *conn)
{
conn->packets_to_send &= ~(1 << PKT_KEEPALIVE);
BGP_TRACE(D_PACKETS, "Sending KEEPALIVE");
- bgp_start_timer(conn->keepalive_timer, conn->keepalive_time);
+ bgp_start_timer(p, conn->keepalive_timer, conn->keepalive_time);
return bgp_send(conn, PKT_KEEPALIVE, BGP_HEADER_LENGTH);
}
else while (conn->channels_to_send)
@@ -2893,7 +2896,7 @@ bgp_schedule_packet(struct bgp_conn *conn, struct bgp_channel *c, int type)
conn->packets_to_send |= 1 << type;
if ((conn->sk->tpos == conn->sk->tbuf) && !ev_active(conn->tx_ev))
- ev_schedule(conn->tx_ev);
+ ev_send_loop(conn->bgp->p.loop, conn->tx_ev);
}
void
bgp_kick_tx(void *vconn)
@@ -2906,7 +2909,7 @@ bgp_kick_tx(void *vconn)
;
if (!max && !ev_active(conn->tx_ev))
- ev_schedule(conn->tx_ev);
+ ev_send_loop(conn->bgp->p.loop, conn->tx_ev);
}
void
@@ -2920,7 +2923,7 @@ bgp_tx(sock *sk)
;
if (!max && !ev_active(conn->tx_ev))
- ev_schedule(conn->tx_ev);
+ ev_send_loop(conn->bgp->p.loop, conn->tx_ev);
}
@@ -3102,7 +3105,7 @@ bgp_rx_keepalive(struct bgp_conn *conn)
struct bgp_proto *p = conn->bgp;
BGP_TRACE(D_PACKETS, "Got KEEPALIVE");
- bgp_start_timer(conn->hold_timer, conn->hold_time);
+ bgp_start_timer(p, conn->hold_timer, conn->hold_time);
if (conn->state == BS_OPENCONFIRM)
{ bgp_conn_enter_established_state(conn); return; }
diff --git a/proto/mrt/mrt.c b/proto/mrt/mrt.c
index 8d97c860..e12f7743 100644
--- a/proto/mrt/mrt.c
+++ b/proto/mrt/mrt.c
@@ -113,13 +113,13 @@ mrt_buffer_flush(buffer *b)
}
#define MRT_DEFINE_TYPE(S, T) \
- static inline void mrt_put_##S##_(buffer *b, T x) \
+ UNUSED static inline void mrt_put_##S##_(buffer *b, T x) \
{ \
put_##S(b->pos, x); \
b->pos += sizeof(T); \
} \
\
- static inline void mrt_put_##S(buffer *b, T x) \
+ UNUSED static inline void mrt_put_##S(buffer *b, T x) \
{ \
mrt_buffer_need(b, sizeof(T)); \
put_##S(b->pos, x); \
@@ -228,7 +228,7 @@ mrt_next_table_(rtable *tab, rtable *tab_ptr, const char *pattern)
NODE_VALID(tn);
tn = tn->next)
{
- tab = SKIP_BACK(struct rtable, n, tn);
+ tab = SKIP_BACK(rtable, n, tn);
if (patmatch(pattern, tab->name) &&
((tab->addr_type == NET_IP4) || (tab->addr_type == NET_IP6)))
return tab;
@@ -243,13 +243,21 @@ mrt_next_table(struct mrt_table_dump_state *s)
rtable *tab = mrt_next_table_(s->table, s->table_ptr, s->table_expr);
if (s->table)
- rt_unlock_table(s->table);
+ {
+ RT_LOCK(s->table);
+ rt_unlock_table(RT_PRIV(s->table));
+ RT_UNLOCK(s->table);
+ }
s->table = tab;
s->ipv4 = tab ? (tab->addr_type == NET_IP4) : 0;
if (s->table)
- rt_lock_table(s->table);
+ {
+ RT_LOCK(s->table);
+ rt_lock_table(RT_PRIV(s->table));
+ RT_UNLOCK(s->table);
+ }
return s->table;
}
@@ -460,7 +468,7 @@ mrt_rib_table_entry_bgp_attrs(struct mrt_table_dump_state *s, rte *r)
return;
fail:
- mrt_log(s, "Attribute list too long for %N", r->net->n.addr);
+ mrt_log(s, "Attribute list too long for %N", r->net);
}
#endif
@@ -472,9 +480,9 @@ mrt_rib_table_entry(struct mrt_table_dump_state *s, rte *r)
#ifdef CONFIG_BGP
/* Find peer index */
- if (r->attrs->src->proto->proto == &proto_bgp)
+ struct bgp_proto *p = bgp_rte_proto(r);
+ if (p)
{
- struct bgp_proto *p = (void *) r->attrs->src->proto;
struct mrt_peer_entry *n =
HASH_FIND(s->peer_hash, PEER, p->remote_id, p->remote_as, p->remote_ip);
@@ -488,7 +496,7 @@ mrt_rib_table_entry(struct mrt_table_dump_state *s, rte *r)
/* Path Identifier */
if (s->add_path)
- mrt_put_u32(b, r->attrs->src->private_id);
+ mrt_put_u32(b, r->src->private_id);
/* Route Attributes */
mrt_put_u16(b, 0);
@@ -512,26 +520,21 @@ mrt_rib_table_dump(struct mrt_table_dump_state *s, net *n, int add_path)
mrt_init_message(&s->buf, MRT_TABLE_DUMP_V2, subtype);
mrt_rib_table_header(s, n->n.addr);
- rte *rt, *rt0;
- for (rt0 = n->routes; rt = rt0; rt0 = rt0->next)
+ for (struct rte_storage *rt, *rt0 = n->routes; rt = rt0; rt0 = rt0->next)
{
- if (rte_is_filtered(rt))
+ if (rte_is_filtered(&rt->rte))
continue;
/* Skip routes that should be reported in the other phase */
- if (!s->always_add_path && (!rt->attrs->src->private_id != !s->add_path))
+ if (!s->always_add_path && (!rt->rte.src->private_id != !s->add_path))
{
s->want_add_path = 1;
continue;
}
- rte_make_tmp_attrs(&rt, s->linpool, NULL);
-
- if (f_run(s->filter, &rt, s->linpool, 0) <= F_ACCEPT)
- mrt_rib_table_entry(s, rt);
-
- if (rt != rt0)
- rte_free(rt);
+ rte e = rt->rte;
+ if (f_run(s->filter, &e, s->linpool, 0) <= F_ACCEPT)
+ mrt_rib_table_entry(s, &e);
lp_flush(s->linpool);
}
@@ -558,10 +561,11 @@ mrt_rib_table_dump(struct mrt_table_dump_state *s, net *n, int add_path)
static struct mrt_table_dump_state *
mrt_table_dump_init(pool *pp)
{
- pool *pool = rp_new(pp, "MRT Table Dump");
+ pool *pool = rp_new(pp, &main_birdloop, "MRT Table Dump");
struct mrt_table_dump_state *s = mb_allocz(pool, sizeof(struct mrt_table_dump_state));
s->pool = pool;
+ s->parent = pp;
s->linpool = lp_new(pool, 4080);
s->peer_lp = lp_new(pool, 4080);
mrt_buffer_init(&s->buf, pool, 2 * MRT_ATTR_BUFFER_SIZE);
@@ -578,18 +582,27 @@ mrt_table_dump_init(pool *pp)
static void
mrt_table_dump_free(struct mrt_table_dump_state *s)
{
- if (s->table_open)
- FIB_ITERATE_UNLINK(&s->fit, &s->table->fib);
-
if (s->table)
- rt_unlock_table(s->table);
+ {
+ RT_LOCK(s->table);
+
+ if (s->table_open)
+ FIB_ITERATE_UNLINK(&s->fit, &RT_PRIV(s->table)->fib);
+
+ rt_unlock_table(RT_PRIV(s->table));
+ RT_UNLOCK(s->table);
+ }
if (s->table_ptr)
- rt_unlock_table(s->table_ptr);
+ {
+ RT_LOCK(s->table_ptr);
+ rt_unlock_table(RT_PRIV(s->table_ptr));
+ RT_UNLOCK(s->table_ptr);
+ }
config_del_obstacle(s->config);
- rfree(s->pool);
+ rp_free(s->pool, s->parent);
}
@@ -601,8 +614,14 @@ mrt_table_dump_step(struct mrt_table_dump_state *s)
s->max = 2048;
s->bws = &bws;
+ rtable_private *tab;
+
if (s->table_open)
+ {
+ RT_LOCK(s->table);
+ tab = RT_PRIV(s->table);
goto step;
+ }
while (mrt_next_table(s))
{
@@ -611,15 +630,18 @@ mrt_table_dump_step(struct mrt_table_dump_state *s)
mrt_peer_table_dump(s);
- FIB_ITERATE_INIT(&s->fit, &s->table->fib);
+ RT_LOCK(s->table);
+ tab = RT_PRIV(s->table);
+ FIB_ITERATE_INIT(&s->fit, &tab->fib);
s->table_open = 1;
step:
- FIB_ITERATE_START(&s->table->fib, &s->fit, net, n)
+ FIB_ITERATE_START(&tab->fib, &s->fit, net, n)
{
if (s->max < 0)
{
FIB_ITERATE_PUT(&s->fit);
+ RT_UNLOCK(s->table);
return 0;
}
@@ -639,6 +661,7 @@ mrt_table_dump_step(struct mrt_table_dump_state *s)
mrt_peer_table_flush(s);
}
+ RT_UNLOCK(s->table);
return 1;
}
@@ -666,7 +689,11 @@ mrt_timer(timer *t)
s->always_add_path = cf->always_add_path;
if (s->table_ptr)
- rt_lock_table(s->table_ptr);
+ {
+ RT_LOCK(s->table_ptr);
+ rt_lock_table(RT_PRIV(s->table_ptr));
+ RT_UNLOCK(s->table_ptr);
+ }
p->table_dump = s;
ev_schedule(p->event);
@@ -739,7 +766,11 @@ mrt_dump_cmd(struct mrt_dump_data *d)
s->filename = d->filename;
if (s->table_ptr)
- rt_lock_table(s->table_ptr);
+ {
+ RT_LOCK(s->table_ptr);
+ rt_lock_table(RT_PRIV(s->table_ptr));
+ RT_UNLOCK(s->table_ptr);
+ }
this_cli->cont = mrt_dump_cont;
this_cli->cleanup = mrt_dump_cleanup;
diff --git a/proto/mrt/mrt.h b/proto/mrt/mrt.h
index 4ff94c12..2e616f6f 100644
--- a/proto/mrt/mrt.h
+++ b/proto/mrt/mrt.h
@@ -40,7 +40,7 @@ struct mrt_proto {
struct mrt_dump_data {
const char *table_expr;
- struct rtable *table_ptr;
+ rtable *table_ptr;
const struct filter *filter;
const char *filename;
};
@@ -60,20 +60,21 @@ struct mrt_table_dump_state {
/* Configuration information */
const char *table_expr; /* Wildcard for table name (or NULL) */
- struct rtable *table_ptr; /* Explicit table (or NULL) */
+ rtable *table_ptr; /* Explicit table (or NULL) */
const struct filter *filter; /* Optional filter */
const char *filename; /* Filename pattern */
int always_add_path; /* Always use *_ADDPATH message subtypes */
/* Allocated by mrt_table_dump_init() */
pool *pool; /* Pool for table dump */
+ pool *parent; /* Parent pool for cleanup */
linpool *linpool; /* Temporary linear pool */
linpool *peer_lp; /* Linear pool for peer entries in peer_hash */
buffer buf; /* Buffer for MRT messages */
HASH(struct mrt_peer_entry) peer_hash; /* Hash for peers to find the index */
- struct rtable *table; /* Processed table, NULL initially */
+ rtable *table; /* Processed table, NULL initially */
struct fib_iterator fit; /* Iterator in processed table */
int table_open; /* Whether iterator is linked */
diff --git a/proto/ospf/iface.c b/proto/ospf/iface.c
index f38b8210..049030ac 100644
--- a/proto/ospf/iface.c
+++ b/proto/ospf/iface.c
@@ -311,7 +311,7 @@ ospf_iface_remove(struct ospf_iface *ifa)
ospf_iface_sm(ifa, ISM_DOWN);
rem_node(NODE ifa);
- rfree(ifa->pool);
+ rp_free(ifa->pool, p->p.pool);
}
void
@@ -522,7 +522,10 @@ static inline void
add_nbma_node(struct ospf_iface *ifa, struct nbma_node *src, int found)
{
struct nbma_node *n = mb_alloc(ifa->pool, sizeof(struct nbma_node));
+
+ n->n = (node) {};
add_tail(&ifa->nbma_list, NODE n);
+
n->ip = src->ip;
n->eligible = src->eligible;
n->found = found;
@@ -564,7 +567,7 @@ ospf_iface_new(struct ospf_area *oa, struct ifa *addr, struct ospf_iface_patt *i
OSPF_TRACE(D_EVENTS, "Adding interface %s (%N) to area %R",
iface->name, &addr->prefix, oa->areaid);
- pool = rp_new(p->p.pool, "OSPF Interface");
+ pool = rp_new(p->p.pool, p->p.loop, "OSPF Interface");
ifa = mb_allocz(pool, sizeof(struct ospf_iface));
ifa->iface = iface;
ifa->addr = addr;
@@ -684,7 +687,7 @@ ospf_iface_new_vlink(struct ospf_proto *p, struct ospf_iface_patt *ip)
/* Vlink ifname is stored just after the ospf_iface structure */
- pool = rp_new(p->p.pool, "OSPF Vlink");
+ pool = rp_new(p->p.pool, p->p.loop, "OSPF Vlink");
ifa = mb_allocz(pool, sizeof(struct ospf_iface) + 16);
ifa->oa = p->backbone;
ifa->cf = ip;
@@ -1222,7 +1225,8 @@ ospf_reconfigure_ifaces2(struct ospf_proto *p)
struct iface *iface;
struct ifa *a;
- WALK_LIST(iface, iface_list)
+ IFACE_LEGACY_ACCESS;
+ WALK_LIST(iface, global_iface_list)
{
if (! (iface->flags & IF_UP))
continue;
@@ -1268,7 +1272,8 @@ ospf_reconfigure_ifaces3(struct ospf_proto *p)
struct iface *iface;
struct ifa *a;
- WALK_LIST(iface, iface_list)
+ IFACE_LEGACY_ACCESS;
+ WALK_LIST(iface, global_iface_list)
{
if (! (iface->flags & IF_UP))
continue;
diff --git a/proto/ospf/neighbor.c b/proto/ospf/neighbor.c
index ca369819..4ae0d3fa 100644
--- a/proto/ospf/neighbor.c
+++ b/proto/ospf/neighbor.c
@@ -80,7 +80,7 @@ struct ospf_neighbor *
ospf_neighbor_new(struct ospf_iface *ifa)
{
struct ospf_proto *p = ifa->oa->po;
- struct pool *pool = rp_new(p->p.pool, "OSPF Neighbor");
+ struct pool *pool = rp_new(p->p.pool, p->p.loop, "OSPF Neighbor");
struct ospf_neighbor *n = mb_allocz(pool, sizeof(struct ospf_neighbor));
n->pool = pool;
@@ -120,7 +120,7 @@ ospf_neigh_down(struct ospf_neighbor *n)
s_get(&(n->dbsi));
release_lsrtl(p, n);
rem_node(NODE n);
- rfree(n->pool);
+ rp_free(n->pool, p->p.pool);
OSPF_TRACE(D_EVENTS, "Neighbor %R on %s removed", rid, ifa->ifname);
}
@@ -777,7 +777,7 @@ ospf_neigh_update_bfd(struct ospf_neighbor *n, int use_bfd)
if (use_bfd && !n->bfd_req)
n->bfd_req = bfd_request_session(n->pool, n->ip, n->ifa->addr->ip,
n->ifa->iface, p->p.vrf,
- ospf_neigh_bfd_hook, n, NULL);
+ ospf_neigh_bfd_hook, n, birdloop_event_list(p->p.loop), NULL);
if (!use_bfd && n->bfd_req)
{
diff --git a/proto/ospf/ospf.c b/proto/ospf/ospf.c
index ba8c2e2b..16774df6 100644
--- a/proto/ospf/ospf.c
+++ b/proto/ospf/ospf.c
@@ -107,12 +107,10 @@
#include <stdlib.h>
#include "ospf.h"
-static int ospf_preexport(struct proto *P, rte **new, struct linpool *pool);
-static void ospf_make_tmp_attrs(struct rte *rt, struct linpool *pool);
-static void ospf_store_tmp_attrs(struct rte *rt, struct linpool *pool);
+static int ospf_preexport(struct channel *C, rte *new);
static void ospf_reload_routes(struct channel *C);
static int ospf_rte_better(struct rte *new, struct rte *old);
-static int ospf_rte_same(struct rte *new, struct rte *old);
+static u32 ospf_rte_igp_metric(struct rte *rt);
static void ospf_disp(timer *timer);
@@ -378,10 +376,8 @@ ospf_init(struct proto_config *CF)
P->reload_routes = ospf_reload_routes;
P->feed_begin = ospf_feed_begin;
P->feed_end = ospf_feed_end;
- P->make_tmp_attrs = ospf_make_tmp_attrs;
- P->store_tmp_attrs = ospf_store_tmp_attrs;
- P->rte_better = ospf_rte_better;
- P->rte_same = ospf_rte_same;
+
+ P->sources.class = &ospf_rte_owner_class;
return P;
}
@@ -390,7 +386,9 @@ ospf_init(struct proto_config *CF)
static int
ospf_rte_better(struct rte *new, struct rte *old)
{
- if (new->u.ospf.metric1 == LSINFINITY)
+ u32 new_metric1 = ea_get_int(new->attrs->eattrs, EA_OSPF_METRIC1, LSINFINITY);
+
+ if (new_metric1 == LSINFINITY)
return 0;
if(new->attrs->source < old->attrs->source) return 1;
@@ -398,27 +396,27 @@ ospf_rte_better(struct rte *new, struct rte *old)
if(new->attrs->source == RTS_OSPF_EXT2)
{
- if(new->u.ospf.metric2 < old->u.ospf.metric2) return 1;
- if(new->u.ospf.metric2 > old->u.ospf.metric2) return 0;
+ u32 old_metric2 = ea_get_int(old->attrs->eattrs, EA_OSPF_METRIC2, LSINFINITY);
+ u32 new_metric2 = ea_get_int(new->attrs->eattrs, EA_OSPF_METRIC2, LSINFINITY);
+ if(new_metric2 < old_metric2) return 1;
+ if(new_metric2 > old_metric2) return 0;
}
- if (new->u.ospf.metric1 < old->u.ospf.metric1)
+ u32 old_metric1 = ea_get_int(old->attrs->eattrs, EA_OSPF_METRIC1, LSINFINITY);
+ if (new_metric1 < old_metric1)
return 1;
return 0; /* Old is shorter or same */
}
-static int
-ospf_rte_same(struct rte *new, struct rte *old)
+static u32
+ospf_rte_igp_metric(struct rte *rt)
{
- /* new->attrs == old->attrs always */
- return
- new->u.ospf.metric1 == old->u.ospf.metric1 &&
- new->u.ospf.metric2 == old->u.ospf.metric2 &&
- new->u.ospf.tag == old->u.ospf.tag &&
- new->u.ospf.router_id == old->u.ospf.router_id;
-}
+ if (rt->attrs->source == RTS_OSPF_EXT2)
+ return IGP_METRIC_UNKNOWN;
+ return ea_get_int(rt->attrs->eattrs, EA_OSPF_METRIC1, LSINFINITY);
+}
void
ospf_schedule_rtcalc(struct ospf_proto *p)
@@ -484,14 +482,13 @@ ospf_disp(timer * timer)
* import to the filters.
*/
static int
-ospf_preexport(struct proto *P, rte **new, struct linpool *pool UNUSED)
+ospf_preexport(struct channel *c, rte *e)
{
- struct ospf_proto *p = (struct ospf_proto *) P;
+ struct ospf_proto *p = (struct ospf_proto *) c->proto;
struct ospf_area *oa = ospf_main_area(p);
- rte *e = *new;
/* Reject our own routes */
- if (e->attrs->src->proto == P)
+ if (e->sender == c->in_req.hook)
return -1;
/* Do not export routes to stub areas */
@@ -501,26 +498,6 @@ ospf_preexport(struct proto *P, rte **new, struct linpool *pool UNUSED)
return 0;
}
-static void
-ospf_make_tmp_attrs(struct rte *rt, struct linpool *pool)
-{
- rte_init_tmp_attrs(rt, pool, 4);
- rte_make_tmp_attr(rt, EA_OSPF_METRIC1, EAF_TYPE_INT, rt->u.ospf.metric1);
- rte_make_tmp_attr(rt, EA_OSPF_METRIC2, EAF_TYPE_INT, rt->u.ospf.metric2);
- rte_make_tmp_attr(rt, EA_OSPF_TAG, EAF_TYPE_INT, rt->u.ospf.tag);
- rte_make_tmp_attr(rt, EA_OSPF_ROUTER_ID, EAF_TYPE_ROUTER_ID, rt->u.ospf.router_id);
-}
-
-static void
-ospf_store_tmp_attrs(struct rte *rt, struct linpool *pool)
-{
- rte_init_tmp_attrs(rt, pool, 4);
- rt->u.ospf.metric1 = rte_store_tmp_attr(rt, EA_OSPF_METRIC1);
- rt->u.ospf.metric2 = rte_store_tmp_attr(rt, EA_OSPF_METRIC2);
- rt->u.ospf.tag = rte_store_tmp_attr(rt, EA_OSPF_TAG);
- rt->u.ospf.router_id = rte_store_tmp_attr(rt, EA_OSPF_ROUTER_ID);
-}
-
/**
* ospf_shutdown - Finish of OSPF instance
* @P: OSPF protocol instance
@@ -558,6 +535,9 @@ ospf_shutdown(struct proto *P)
}
FIB_WALK_END;
+ if (tm_active(p->disp_timer))
+ tm_stop(p->disp_timer);
+
return PS_DOWN;
}
@@ -607,16 +587,20 @@ ospf_get_route_info(rte * rte, byte * buf)
}
buf += bsprintf(buf, " %s", type);
- buf += bsprintf(buf, " (%d/%d", rte->pref, rte->u.ospf.metric1);
+ buf += bsprintf(buf, " (%d/%d", rte->attrs->pref, ea_get_int(rte->attrs->eattrs, EA_OSPF_METRIC1, LSINFINITY));
if (rte->attrs->source == RTS_OSPF_EXT2)
- buf += bsprintf(buf, "/%d", rte->u.ospf.metric2);
+ buf += bsprintf(buf, "/%d", ea_get_int(rte->attrs->eattrs, EA_OSPF_METRIC2, LSINFINITY));
buf += bsprintf(buf, ")");
- if ((rte->attrs->source == RTS_OSPF_EXT1 || rte->attrs->source == RTS_OSPF_EXT2) && rte->u.ospf.tag)
+ if (rte->attrs->source == RTS_OSPF_EXT1 || rte->attrs->source == RTS_OSPF_EXT2)
{
- buf += bsprintf(buf, " [%x]", rte->u.ospf.tag);
+ eattr *ea = ea_find(rte->attrs->eattrs, EA_OSPF_TAG);
+ if (ea && (ea->u.data > 0))
+ buf += bsprintf(buf, " [%x]", ea->u.data);
}
- if (rte->u.ospf.router_id)
- buf += bsprintf(buf, " [%R]", rte->u.ospf.router_id);
+
+ eattr *ea = ea_find(rte->attrs->eattrs, EA_OSPF_ROUTER_ID);
+ if (ea)
+ buf += bsprintf(buf, " [%R]", ea->u.data);
}
static int
@@ -1533,6 +1517,12 @@ ospf_sh_lsadb(struct lsadb_show_data *ld)
}
+struct rte_owner_class ospf_rte_owner_class = {
+ .get_route_info = ospf_get_route_info,
+ .rte_better = ospf_rte_better,
+ .rte_igp_metric = ospf_rte_igp_metric,
+};
+
struct protocol proto_ospf = {
.name = "OSPF",
.template = "ospf%d",
@@ -1548,5 +1538,4 @@ struct protocol proto_ospf = {
.reconfigure = ospf_reconfigure,
.get_status = ospf_get_status,
.get_attr = ospf_get_attr,
- .get_route_info = ospf_get_route_info
};
diff --git a/proto/ospf/ospf.h b/proto/ospf/ospf.h
index 3e704ae8..a5f83e79 100644
--- a/proto/ospf/ospf.h
+++ b/proto/ospf/ospf.h
@@ -1007,6 +1007,8 @@ void ospf_sh_state(struct proto *P, int verbose, int reachable);
void ospf_sh_lsadb(struct lsadb_show_data *ld);
+extern struct rte_owner_class ospf_rte_owner_class;
+
/* iface.c */
void ospf_iface_chstate(struct ospf_iface *ifa, u8 state);
void ospf_iface_sm(struct ospf_iface *ifa, int event);
diff --git a/proto/ospf/rt.c b/proto/ospf/rt.c
index faee49dc..3e208023 100644
--- a/proto/ospf/rt.c
+++ b/proto/ospf/rt.c
@@ -144,7 +144,7 @@ orta_compare(const struct ospf_proto *p, const orta *new, const orta *old)
{
int r;
- if (old->type == RTS_DUMMY)
+ if (!old->type)
return 1;
/* Prefer intra-area to inter-area to externals */
@@ -195,7 +195,7 @@ orta_compare_asbr(const struct ospf_proto *p, const orta *new, const orta *old)
{
int r;
- if (old->type == RTS_DUMMY)
+ if (!old->type)
return 1;
if (!p->rfc1583)
@@ -225,7 +225,7 @@ orta_compare_ext(const struct ospf_proto *p, const orta *new, const orta *old)
{
int r;
- if (old->type == RTS_DUMMY)
+ if (!old->type)
return 1;
/* 16.4 (6a) - prefer routes with lower type */
@@ -2053,36 +2053,61 @@ again1:
if (nf->n.type) /* Add the route */
{
rta a0 = {
- .src = p->p.main_source,
.source = nf->n.type,
.scope = SCOPE_UNIVERSE,
.dest = RTD_UNICAST,
.nh = *(nf->n.nhs),
+ .pref = p->p.main_channel->preference,
};
if (reload || ort_changed(nf, &a0))
{
- rta *a = rta_lookup(&a0);
- rte *e = rte_get_temp(a);
+ a0.eattrs = alloca(sizeof(ea_list) + 4 * sizeof(eattr));
+ memset(a0.eattrs, 0, sizeof(ea_list));
- rta_free(nf->old_rta);
- nf->old_rta = rta_clone(a);
- e->u.ospf.metric1 = nf->old_metric1 = nf->n.metric1;
- e->u.ospf.metric2 = nf->old_metric2 = nf->n.metric2;
- e->u.ospf.tag = nf->old_tag = nf->n.tag;
- e->u.ospf.router_id = nf->old_rid = nf->n.rid;
- e->pflags = EA_ID_FLAG(EA_OSPF_METRIC1) | EA_ID_FLAG(EA_OSPF_ROUTER_ID);
+ nf->old_metric1 = nf->n.metric1;
+ nf->old_metric2 = nf->n.metric2;
+ nf->old_tag = nf->n.tag;
+ nf->old_rid = nf->n.rid;
+
+ a0.eattrs->attrs[a0.eattrs->count++] = (eattr) {
+ .id = EA_OSPF_METRIC1,
+ .type = EAF_TYPE_INT,
+ .u.data = nf->n.metric1,
+ };
if (nf->n.type == RTS_OSPF_EXT2)
- e->pflags |= EA_ID_FLAG(EA_OSPF_METRIC2);
+ a0.eattrs->attrs[a0.eattrs->count++] = (eattr) {
+ .id = EA_OSPF_METRIC2,
+ .type = EAF_TYPE_INT,
+ .u.data = nf->n.metric2,
+ };
- /* Perhaps onfly if tag is non-zero? */
if ((nf->n.type == RTS_OSPF_EXT1) || (nf->n.type == RTS_OSPF_EXT2))
- e->pflags |= EA_ID_FLAG(EA_OSPF_TAG);
+ a0.eattrs->attrs[a0.eattrs->count++] = (eattr) {
+ .id = EA_OSPF_TAG,
+ .type = EAF_TYPE_INT,
+ .u.data = nf->n.tag,
+ };
+
+ a0.eattrs->attrs[a0.eattrs->count++] = (eattr) {
+ .id = EA_OSPF_ROUTER_ID,
+ .type = EAF_TYPE_ROUTER_ID,
+ .u.data = nf->n.rid,
+ };
+
+ rta_free(nf->old_rta);
+ nf->old_rta = rta_lookup(&a0);
+
+ rte e0 = {
+ .attrs = nf->old_rta,
+ .src = p->p.main_source,
+ };
DBG("Mod rte type %d - %N via %I on iface %s, met %d\n",
a0.source, nf->fn.addr, a0.gw, a0.iface ? a0.iface->name : "(none)", nf->n.metric1);
- rte_update(&p->p, nf->fn.addr, e);
+
+ rte_update(p->p.main_channel, nf->fn.addr, &e0, p->p.main_source);
}
}
else if (nf->old_rta)
@@ -2091,7 +2116,7 @@ again1:
rta_free(nf->old_rta);
nf->old_rta = NULL;
- rte_update(&p->p, nf->fn.addr, NULL);
+ rte_update(p->p.main_channel, nf->fn.addr, NULL, p->p.main_source);
}
/* Remove unused rt entry, some special entries are persistent */
@@ -2107,7 +2132,6 @@ again1:
}
FIB_ITERATE_END;
-
WALK_LIST(oa, p->area_list)
{
/* Cleanup ASBR hash tables */
diff --git a/proto/ospf/topology.c b/proto/ospf/topology.c
index 52c2a0ce..bb88d20a 100644
--- a/proto/ospf/topology.c
+++ b/proto/ospf/topology.c
@@ -1300,7 +1300,7 @@ find_surrogate_fwaddr(struct ospf_proto *p, struct ospf_area *oa)
}
void
-ospf_rt_notify(struct proto *P, struct channel *ch UNUSED, net *n, rte *new, rte *old UNUSED)
+ospf_rt_notify(struct proto *P, struct channel *ch UNUSED, const net_addr *n, rte *new, const rte *old UNUSED)
{
struct ospf_proto *p = (struct ospf_proto *) P;
struct ospf_area *oa = NULL; /* non-NULL for NSSA-LSA */
@@ -1319,7 +1319,7 @@ ospf_rt_notify(struct proto *P, struct channel *ch UNUSED, net *n, rte *new, rte
if (!new)
{
- nf = fib_find(&p->rtf, n->n.addr);
+ nf = fib_find(&p->rtf, n);
if (!nf || !nf->external_rte)
return;
@@ -1346,14 +1346,14 @@ ospf_rt_notify(struct proto *P, struct channel *ch UNUSED, net *n, rte *new, rte
if (m1 > LSINFINITY)
{
log(L_WARN "%s: Invalid ospf_metric1 value %u for route %N",
- p->p.name, m1, n->n.addr);
+ p->p.name, m1, n);
m1 = LSINFINITY;
}
if (m2 > LSINFINITY)
{
log(L_WARN "%s: Invalid ospf_metric2 value %u for route %N",
- p->p.name, m2, n->n.addr);
+ p->p.name, m2, n);
m2 = LSINFINITY;
}
@@ -1377,12 +1377,12 @@ ospf_rt_notify(struct proto *P, struct channel *ch UNUSED, net *n, rte *new, rte
if (ipa_zero(fwd))
{
log(L_ERR "%s: Cannot find forwarding address for NSSA-LSA %N",
- p->p.name, n->n.addr);
+ p->p.name, n);
return;
}
}
- nf = fib_get(&p->rtf, n->n.addr);
+ nf = fib_get(&p->rtf, n);
ospf_originate_ext_lsa(p, oa, nf, LSA_M_EXPORT, metric, ebit, fwd, tag, 1, p->vpn_pe);
nf->external_rte = 1;
}
diff --git a/proto/ospf/topology.h b/proto/ospf/topology.h
index 535d1f1b..c36d0b50 100644
--- a/proto/ospf/topology.h
+++ b/proto/ospf/topology.h
@@ -200,7 +200,7 @@ void ospf_originate_sum_rt_lsa(struct ospf_proto *p, struct ospf_area *oa, u32 d
void ospf_originate_ext_lsa(struct ospf_proto *p, struct ospf_area *oa, ort *nf, u8 mode, u32 metric, u32 ebit, ip_addr fwaddr, u32 tag, int pbit, int dn);
void ospf_originate_gr_lsa(struct ospf_proto *p, struct ospf_iface *ifa);
-void ospf_rt_notify(struct proto *P, struct channel *ch, net *n, rte *new, rte *old);
+void ospf_rt_notify(struct proto *P, struct channel *ch, const net_addr *n, rte *new, const rte *old);
void ospf_update_topology(struct ospf_proto *p);
struct top_hash_entry *ospf_hash_find(struct top_graph *, u32 domain, u32 lsa, u32 rtr, u32 type);
diff --git a/proto/perf/perf.c b/proto/perf/perf.c
index ba401a8a..aa688d88 100644
--- a/proto/perf/perf.c
+++ b/proto/perf/perf.c
@@ -143,10 +143,10 @@ perf_loop(void *data)
if (!p->attrs_per_rte || !(i % p->attrs_per_rte)) {
struct rta a0 = {
- .src = p->p.main_source,
.source = RTS_PERF,
.scope = SCOPE_UNIVERSE,
.dest = RTD_UNICAST,
+ .pref = p->p.main_channel->preference,
.nh.iface = p->ifa->iface,
.nh.gw = gw,
.nh.weight = 1,
@@ -160,18 +160,17 @@ perf_loop(void *data)
clock_gettime(CLOCK_MONOTONIC, &ts_generated);
- for (uint i=0; i<N; i++) {
- rte *e = rte_get_temp(p->data[i].a);
- e->pflags = 0;
-
- rte_update(P, &(p->data[i].net), e);
+ for (uint i=0; i<N; i++)
+ {
+ rte e0 = { .attrs = p->data[i].a, .src = P->main_source, };
+ rte_update(P->main_channel, &(p->data[i].net), &e0, P->main_source);
}
clock_gettime(CLOCK_MONOTONIC, &ts_update);
if (!p->keep)
for (uint i=0; i<N; i++)
- rte_update(P, &(p->data[i].net), NULL);
+ rte_update(P->main_channel, &(p->data[i].net), NULL, P->main_source);
clock_gettime(CLOCK_MONOTONIC, &ts_withdraw);
@@ -199,12 +198,14 @@ perf_loop(void *data)
p->exp++;
}
- rt_schedule_prune(P->main_channel->table);
+ RT_LOCK(P->main_channel->table);
+ rt_schedule_prune(RT_PRIV(P->main_channel->table));
+ RT_UNLOCK(P->main_channel->table);
ev_schedule(p->loop);
}
static void
-perf_rt_notify(struct proto *P, struct channel *c UNUSED, struct network *net UNUSED, struct rte *new UNUSED, struct rte *old UNUSED)
+perf_rt_notify(struct proto *P, struct channel *c UNUSED, const net_addr *net UNUSED, struct rte *new UNUSED, const struct rte *old UNUSED)
{
struct perf_proto *p = (struct perf_proto *) P;
p->exp++;
diff --git a/proto/pipe/config.Y b/proto/pipe/config.Y
index 1202c169..fc08445f 100644
--- a/proto/pipe/config.Y
+++ b/proto/pipe/config.Y
@@ -16,7 +16,7 @@ CF_DEFINES
CF_DECLS
-CF_KEYWORDS(PIPE, PEER, TABLE)
+CF_KEYWORDS(PIPE, PEER, TABLE, MAX, GENERATION)
CF_GRAMMAR
@@ -25,6 +25,8 @@ proto: pipe_proto '}' { this_channel = NULL; } ;
pipe_proto_start: proto_start PIPE
{
this_proto = proto_config_new(&proto_pipe, $1);
+ this_proto->loop_order = DOMAIN_ORDER(proto);
+ PIPE_CFG->max_generation = 16;
}
proto_name
{
@@ -41,6 +43,10 @@ pipe_proto:
| pipe_proto proto_item ';'
| pipe_proto channel_item_ ';'
| pipe_proto PEER TABLE rtable ';' { PIPE_CFG->peer = $4; }
+ | pipe_proto MAX GENERATION expr ';' {
+ if (($4 < 1) || ($4 > 254)) cf_error("Max generation must be in range 1..254, got %u", $4);
+ PIPE_CFG->max_generation = $4;
+ }
;
CF_CODE
diff --git a/proto/pipe/pipe.c b/proto/pipe/pipe.c
index 3532f114..270f7b92 100644
--- a/proto/pipe/pipe.c
+++ b/proto/pipe/pipe.c
@@ -43,67 +43,58 @@
#include "pipe.h"
+#ifdef CONFIG_BGP
+#include "proto/bgp/bgp.h"
+#endif
+
static void
-pipe_rt_notify(struct proto *P, struct channel *src_ch, net *n, rte *new, rte *old)
+pipe_rt_notify(struct proto *P, struct channel *src_ch, const net_addr *n, rte *new, const rte *old)
{
struct pipe_proto *p = (void *) P;
struct channel *dst = (src_ch == p->pri) ? p->sec : p->pri;
- struct rte_src *src;
-
- rte *e;
- rta *a;
if (!new && !old)
return;
- if (dst->table->pipe_busy)
- {
- log(L_ERR "Pipe loop detected when sending %N to table %s",
- n->n.addr, dst->table->name);
- return;
- }
-
if (new)
{
- a = alloca(rta_size(new->attrs));
+ rta *a = alloca(rta_size(new->attrs));
memcpy(a, new->attrs, rta_size(new->attrs));
- a->aflags = 0;
+ a->cached = 0;
a->hostentry = NULL;
- e = rte_get_temp(a);
- e->pflags = 0;
-
- /* Copy protocol specific embedded attributes. */
- memcpy(&(e->u), &(new->u), sizeof(e->u));
- e->pref = new->pref;
- e->pflags = new->pflags;
-#ifdef CONFIG_BGP
- /* Hack to cleanup cached value */
- if (e->attrs->src->proto->proto == &proto_bgp)
- e->u.bgp.stale = -1;
-#endif
+ rte e0 = {
+ .attrs = a,
+ .src = new->src,
+ .generation = new->generation + 1,
+ };
- src = a->src;
+ rte_update(dst, n, &e0, new->src);
}
else
- {
- e = NULL;
- src = old->attrs->src;
- }
-
- src_ch->table->pipe_busy = 1;
- rte_update2(dst, n->n.addr, e, src);
- src_ch->table->pipe_busy = 0;
+ rte_update(dst, n, NULL, old->src);
}
static int
-pipe_preexport(struct proto *P, rte **ee, struct linpool *p UNUSED)
+pipe_preexport(struct channel *c, rte *e)
{
- struct proto *pp = (*ee)->sender->proto;
+ struct pipe_proto *p = (void *) c->proto;
+
+ /* Avoid direct loopbacks */
+ if (e->sender == c->in_req.hook)
+ return -1;
- if (pp == P)
- return -1; /* Avoid local loops automatically */
+ /* Indirection check */
+ uint max_generation = ((struct pipe_config *) p->p.cf)->max_generation;
+ if (e->generation >= max_generation)
+ {
+ log_rl(&p->rl_gen, L_ERR "Route overpiped (%u hops of %u configured in %s) in table %s: %N %s/%u:%u",
+ e->generation, max_generation, c->proto->name,
+ c->table->name, e->net, e->src->owner->name, e->src->private_id, e->src->global_id);
+
+ return -1;
+ }
return 0;
}
@@ -117,6 +108,23 @@ pipe_reload_routes(struct channel *C)
channel_request_feeding((C == p->pri) ? p->sec : p->pri);
}
+static void
+pipe_feed_begin(struct channel *C, int refeeding UNUSED)
+{
+ struct pipe_proto *p = (void *) C->proto;
+ struct channel *dst = (C == p->pri) ? p->sec : p->pri;
+
+ channel_refresh_begin(dst);
+}
+
+static void
+pipe_feed_end(struct channel *C)
+{
+ struct pipe_proto *p = (void *) C->proto;
+ struct channel *dst = (C == p->pri) ? p->sec : p->pri;
+
+ channel_refresh_end(dst);
+}
static void
pipe_postconfig(struct proto_config *CF)
@@ -187,6 +195,10 @@ pipe_init(struct proto_config *CF)
P->rt_notify = pipe_rt_notify;
P->preexport = pipe_preexport;
P->reload_routes = pipe_reload_routes;
+ P->feed_begin = pipe_feed_begin;
+ P->feed_end = pipe_feed_end;
+
+ p->rl_gen = (struct tbf) TBF_DEFAULT_LOG_LIMITS;
pipe_configure_channels(p, cf);
@@ -219,8 +231,18 @@ pipe_get_status(struct proto *P, byte *buf)
static void
pipe_show_stats(struct pipe_proto *p)
{
- struct proto_stats *s1 = &p->pri->stats;
- struct proto_stats *s2 = &p->sec->stats;
+ struct channel_import_stats *s1i = &p->pri->import_stats;
+ struct channel_export_stats *s1e = &p->pri->export_stats;
+ struct channel_import_stats *s2i = &p->sec->import_stats;
+ struct channel_export_stats *s2e = &p->sec->export_stats;
+
+ struct rt_import_stats *rs1i = p->pri->in_req.hook ? &p->pri->in_req.hook->stats : NULL;
+ struct rt_export_stats *rs1e = p->pri->out_req.hook ? &p->pri->out_req.hook->stats : NULL;
+ struct rt_import_stats *rs2i = p->sec->in_req.hook ? &p->sec->in_req.hook->stats : NULL;
+ struct rt_export_stats *rs2e = p->sec->out_req.hook ? &p->sec->out_req.hook->stats : NULL;
+
+ u32 pri_routes = p->pri->in_limit.count;
+ u32 sec_routes = p->sec->in_limit.count;
/*
* Pipe stats (as anything related to pipes) are a bit tricky. There
@@ -244,24 +266,22 @@ pipe_show_stats(struct pipe_proto *p)
*/
cli_msg(-1006, " Routes: %u imported, %u exported",
- s1->imp_routes, s2->imp_routes);
+ pri_routes, sec_routes);
cli_msg(-1006, " Route change stats: received rejected filtered ignored accepted");
cli_msg(-1006, " Import updates: %10u %10u %10u %10u %10u",
- s2->exp_updates_received, s2->exp_updates_rejected + s1->imp_updates_invalid,
- s2->exp_updates_filtered, s1->imp_updates_ignored, s1->imp_updates_accepted);
+ rs2e->updates_received, s2e->updates_rejected + s1i->updates_invalid,
+ s2e->updates_filtered, rs1i->updates_ignored, rs1i->updates_accepted);
cli_msg(-1006, " Import withdraws: %10u %10u --- %10u %10u",
- s2->exp_withdraws_received, s1->imp_withdraws_invalid,
- s1->imp_withdraws_ignored, s1->imp_withdraws_accepted);
+ rs2e->withdraws_received, s1i->withdraws_invalid,
+ rs1i->withdraws_ignored, rs1i->withdraws_accepted);
cli_msg(-1006, " Export updates: %10u %10u %10u %10u %10u",
- s1->exp_updates_received, s1->exp_updates_rejected + s2->imp_updates_invalid,
- s1->exp_updates_filtered, s2->imp_updates_ignored, s2->imp_updates_accepted);
+ rs1e->updates_received, s1e->updates_rejected + s2i->updates_invalid,
+ s1e->updates_filtered, rs2i->updates_ignored, rs2i->updates_accepted);
cli_msg(-1006, " Export withdraws: %10u %10u --- %10u %10u",
- s1->exp_withdraws_received, s2->imp_withdraws_invalid,
- s2->imp_withdraws_ignored, s2->imp_withdraws_accepted);
+ rs1e->withdraws_received, s2i->withdraws_invalid,
+ rs2i->withdraws_ignored, rs2i->withdraws_accepted);
}
-static const char *pipe_feed_state[] = { [ES_DOWN] = "down", [ES_FEEDING] = "feed", [ES_READY] = "up" };
-
static void
pipe_show_proto_info(struct proto *P)
{
@@ -270,13 +290,17 @@ pipe_show_proto_info(struct proto *P)
cli_msg(-1006, " Channel %s", "main");
cli_msg(-1006, " Table: %s", p->pri->table->name);
cli_msg(-1006, " Peer table: %s", p->sec->table->name);
- cli_msg(-1006, " Import state: %s", pipe_feed_state[p->sec->export_state]);
- cli_msg(-1006, " Export state: %s", pipe_feed_state[p->pri->export_state]);
+ cli_msg(-1006, " Import state: %s", rt_export_state_name(rt_export_get_state(p->sec->out_req.hook)));
+ cli_msg(-1006, " Export state: %s", rt_export_state_name(rt_export_get_state(p->pri->out_req.hook)));
cli_msg(-1006, " Import filter: %s", filter_name(p->sec->out_filter));
cli_msg(-1006, " Export filter: %s", filter_name(p->pri->out_filter));
- channel_show_limit(&p->pri->in_limit, "Import limit:");
- channel_show_limit(&p->sec->in_limit, "Export limit:");
+
+
+ channel_show_limit(&p->pri->in_limit, "Import limit:",
+ (p->pri->limit_active & (1 << PLD_IN)), p->pri->limit_actions[PLD_IN]);
+ channel_show_limit(&p->sec->in_limit, "Export limit:",
+ (p->sec->limit_active & (1 << PLD_IN)), p->sec->limit_actions[PLD_IN]);
if (P->proto_state != PS_DOWN)
pipe_show_stats(p);
diff --git a/proto/pipe/pipe.h b/proto/pipe/pipe.h
index 038c6666..60c857eb 100644
--- a/proto/pipe/pipe.h
+++ b/proto/pipe/pipe.h
@@ -12,12 +12,14 @@
struct pipe_config {
struct proto_config c;
struct rtable_config *peer; /* Table we're connected to */
+ u8 max_generation;
};
struct pipe_proto {
struct proto p;
struct channel *pri;
struct channel *sec;
+ struct tbf rl_gen;
};
#endif
diff --git a/proto/radv/radv.c b/proto/radv/radv.c
index 66e8eb4b..15673555 100644
--- a/proto/radv/radv.c
+++ b/proto/radv/radv.c
@@ -284,7 +284,7 @@ radv_iface_new(struct radv_proto *p, struct iface *iface, struct radv_iface_conf
RADV_TRACE(D_EVENTS, "Adding interface %s", iface->name);
- pool *pool = rp_new(p->p.pool, iface->name);
+ pool *pool = rp_new(p->p.pool, p->p.loop, iface->name);
ifa = mb_allocz(pool, sizeof(struct radv_iface));
ifa->pool = pool;
ifa->ra = p;
@@ -317,7 +317,7 @@ radv_iface_remove(struct radv_iface *ifa)
rem_node(NODE ifa);
- rfree(ifa->pool);
+ rp_free(ifa->pool, p->p.pool);
}
static void
@@ -385,18 +385,18 @@ radv_trigger_valid(struct radv_config *cf)
}
static inline int
-radv_net_match_trigger(struct radv_config *cf, net *n)
+radv_net_match_trigger(struct radv_config *cf, const net_addr *n)
{
- return radv_trigger_valid(cf) && net_equal(n->n.addr, &cf->trigger);
+ return radv_trigger_valid(cf) && net_equal(n, &cf->trigger);
}
int
-radv_preexport(struct proto *P, rte **new, struct linpool *pool UNUSED)
+radv_preexport(struct channel *c, rte *new)
{
// struct radv_proto *p = (struct radv_proto *) P;
- struct radv_config *cf = (struct radv_config *) (P->cf);
+ struct radv_config *cf = (struct radv_config *) (c->proto->cf);
- if (radv_net_match_trigger(cf, (*new)->net))
+ if (radv_net_match_trigger(cf, new->net))
return RIC_PROCESS;
if (cf->propagate_routes)
@@ -406,7 +406,7 @@ radv_preexport(struct proto *P, rte **new, struct linpool *pool UNUSED)
}
static void
-radv_rt_notify(struct proto *P, struct channel *ch UNUSED, net *n, rte *new, rte *old UNUSED)
+radv_rt_notify(struct proto *P, struct channel *ch UNUSED, const net_addr *n, rte *new, const rte *old UNUSED)
{
struct radv_proto *p = (struct radv_proto *) P;
struct radv_config *cf = (struct radv_config *) (P->cf);
@@ -457,14 +457,14 @@ radv_rt_notify(struct proto *P, struct channel *ch UNUSED, net *n, rte *new, rte
(preference != RA_PREF_HIGH))
{
log(L_WARN "%s: Invalid ra_preference value %u on route %N",
- p->p.name, preference, n->n.addr);
+ p->p.name, preference, n);
preference = RA_PREF_MEDIUM;
preference_set = 1;
lifetime = 0;
lifetime_set = 1;
}
- rt = fib_get(&p->routes, n->n.addr);
+ rt = fib_get(&p->routes, n);
/* Ignore update if nothing changed */
if (rt->valid &&
@@ -487,7 +487,7 @@ radv_rt_notify(struct proto *P, struct channel *ch UNUSED, net *n, rte *new, rte
else
{
/* Withdraw */
- rt = fib_find(&p->routes, n->n.addr);
+ rt = fib_find(&p->routes, n);
if (!rt || !rt->valid)
return;
@@ -555,7 +555,10 @@ radv_check_active(struct radv_proto *p)
return 1;
struct channel *c = p->p.main_channel;
- return rt_examine(c->table, &cf->trigger, &p->p, c->out_filter);
+ RT_LOCK(c->table);
+ int active = rt_examine(RT_PRIV(c->table), &cf->trigger, c, c->out_filter);
+ RT_UNLOCK(c->table);
+ return active;
}
static void
@@ -660,8 +663,9 @@ radv_reconfigure(struct proto *P, struct proto_config *CF)
if (!old->propagate_routes && new->propagate_routes)
channel_request_feeding(p->p.main_channel);
+ IFACE_LEGACY_ACCESS;
struct iface *iface;
- WALK_LIST(iface, iface_list)
+ WALK_LIST(iface, global_iface_list)
{
if (!(iface->flags & IF_UP))
continue;
diff --git a/proto/rip/rip.c b/proto/rip/rip.c
index e1a235a0..0f8b10ad 100644
--- a/proto/rip/rip.c
+++ b/proto/rip/rip.c
@@ -145,7 +145,7 @@ rip_announce_rte(struct rip_proto *p, struct rip_entry *en)
{
/* Update */
rta a0 = {
- .src = p->p.main_source,
+ .pref = p->p.main_channel->preference,
.source = RTS_RIP,
.scope = SCOPE_UNIVERSE,
.dest = RTD_UNICAST,
@@ -188,21 +188,34 @@ rip_announce_rte(struct rip_proto *p, struct rip_entry *en)
a0.nh.iface = rt->from->ifa->iface;
}
- rta *a = rta_lookup(&a0);
- rte *e = rte_get_temp(a);
+ a0.eattrs = alloca(sizeof(ea_list) + 3*sizeof(eattr));
+ memset(a0.eattrs, 0, sizeof(ea_list)); /* Zero-ing only the ea_list header */
+ a0.eattrs->count = 3;
+ a0.eattrs->attrs[0] = (eattr) {
+ .id = EA_RIP_METRIC,
+ .type = EAF_TYPE_INT,
+ .u.data = rt_metric,
+ };
+ a0.eattrs->attrs[1] = (eattr) {
+ .id = EA_RIP_TAG,
+ .type = EAF_TYPE_INT,
+ .u.data = rt_tag,
+ };
+ a0.eattrs->attrs[2] = (eattr) {
+ .id = EA_RIP_FROM,
+ .type = EAF_TYPE_PTR,
+ .u.data = (uintptr_t) a0.nh.iface,
+ };
- e->u.rip.from = a0.nh.iface;
- e->u.rip.metric = rt_metric;
- e->u.rip.tag = rt_tag;
- e->pflags = EA_ID_FLAG(EA_RIP_METRIC) | EA_ID_FLAG(EA_RIP_TAG);
+ rte e0 = {
+ .attrs = &a0,
+ .src = p->p.main_source,
+ };
- rte_update(&p->p, en->n.addr, e);
+ rte_update(p->p.main_channel, en->n.addr, &e0, p->p.main_source);
}
else
- {
- /* Withdraw */
- rte_update(&p->p, en->n.addr, NULL);
- }
+ rte_update(p->p.main_channel, en->n.addr, NULL, p->p.main_source);
}
/**
@@ -297,8 +310,8 @@ rip_withdraw_rte(struct rip_proto *p, net_addr *n, struct rip_neighbor *from)
* it into our data structures.
*/
static void
-rip_rt_notify(struct proto *P, struct channel *ch UNUSED, struct network *net, struct rte *new,
- struct rte *old UNUSED)
+rip_rt_notify(struct proto *P, struct channel *ch UNUSED, const net_addr *net, struct rte *new,
+ const struct rte *old UNUSED)
{
struct rip_proto *p = (struct rip_proto *) P;
struct rip_entry *en;
@@ -307,20 +320,21 @@ rip_rt_notify(struct proto *P, struct channel *ch UNUSED, struct network *net, s
if (new)
{
/* Update */
- u32 rt_metric = ea_get_int(new->attrs->eattrs, EA_RIP_METRIC, 1);
u32 rt_tag = ea_get_int(new->attrs->eattrs, EA_RIP_TAG, 0);
+ u32 rt_metric = ea_get_int(new->attrs->eattrs, EA_RIP_METRIC, 1);
+ struct iface *rt_from = (struct iface *) ea_get_int(new->attrs->eattrs, EA_RIP_FROM, 0);
if (rt_metric > p->infinity)
{
log(L_WARN "%s: Invalid rip_metric value %u for route %N",
- p->p.name, rt_metric, net->n.addr);
+ p->p.name, rt_metric, net);
rt_metric = p->infinity;
}
if (rt_tag > 0xffff)
{
log(L_WARN "%s: Invalid rip_tag value %u for route %N",
- p->p.name, rt_tag, net->n.addr);
+ p->p.name, rt_tag, net);
rt_metric = p->infinity;
rt_tag = 0;
}
@@ -332,21 +346,21 @@ rip_rt_notify(struct proto *P, struct channel *ch UNUSED, struct network *net, s
* collection.
*/
- en = fib_get(&p->rtable, net->n.addr);
+ en = fib_get(&p->rtable, net);
old_metric = en->valid ? en->metric : -1;
en->valid = RIP_ENTRY_VALID;
en->metric = rt_metric;
en->tag = rt_tag;
- en->from = (new->attrs->src->proto == P) ? new->u.rip.from : NULL;
+ en->from = (new->src->owner == &P->sources) ? rt_from : NULL;
en->iface = new->attrs->nh.iface;
en->next_hop = new->attrs->nh.gw;
}
else
{
/* Withdraw */
- en = fib_find(&p->rtable, net->n.addr);
+ en = fib_find(&p->rtable, net);
if (!en || en->valid != RIP_ENTRY_VALID)
return;
@@ -499,7 +513,7 @@ rip_update_bfd(struct rip_proto *p, struct rip_neighbor *n)
ip_addr saddr = rip_is_v2(p) ? n->ifa->sk->saddr : n->nbr->ifa->ip;
n->bfd_req = bfd_request_session(p->p.pool, n->nbr->addr, saddr,
n->nbr->iface, p->p.vrf,
- rip_bfd_notify, n, NULL);
+ rip_bfd_notify, n, birdloop_event_list(p->p.loop), NULL);
}
if (!use_bfd && n->bfd_req)
@@ -762,7 +776,8 @@ rip_reconfigure_ifaces(struct rip_proto *p, struct rip_config *cf)
{
struct iface *iface;
- WALK_LIST(iface, iface_list)
+ IFACE_LEGACY_ACCESS;
+ WALK_LIST(iface, global_iface_list)
{
if (!(iface->flags & IF_UP))
continue;
@@ -1068,37 +1083,33 @@ rip_reload_routes(struct channel *C)
rip_kick_timer(p);
}
-static void
-rip_make_tmp_attrs(struct rte *rt, struct linpool *pool)
-{
- rte_init_tmp_attrs(rt, pool, 2);
- rte_make_tmp_attr(rt, EA_RIP_METRIC, EAF_TYPE_INT, rt->u.rip.metric);
- rte_make_tmp_attr(rt, EA_RIP_TAG, EAF_TYPE_INT, rt->u.rip.tag);
-}
+static struct rte_owner_class rip_rte_owner_class;
-static void
-rip_store_tmp_attrs(struct rte *rt, struct linpool *pool)
+static inline struct rip_proto *
+rip_rte_proto(struct rte *rte)
{
- rte_init_tmp_attrs(rt, pool, 2);
- rt->u.rip.metric = rte_store_tmp_attr(rt, EA_RIP_METRIC);
- rt->u.rip.tag = rte_store_tmp_attr(rt, EA_RIP_TAG);
+ return (rte->src->owner->class == &rip_rte_owner_class) ?
+ SKIP_BACK(struct rip_proto, p.sources, rte->src->owner) : NULL;
}
static int
rip_rte_better(struct rte *new, struct rte *old)
{
- return new->u.rip.metric < old->u.rip.metric;
+ ASSERT_DIE(new->src == old->src);
+ struct rip_proto *p = rip_rte_proto(new);
+
+ u32 new_metric = ea_get_int(new->attrs->eattrs, EA_RIP_METRIC, p->infinity);
+ u32 old_metric = ea_get_int(old->attrs->eattrs, EA_RIP_METRIC, p->infinity);
+
+ return new_metric < old_metric;
}
-static int
-rip_rte_same(struct rte *new, struct rte *old)
+static u32
+rip_rte_igp_metric(struct rte *rt)
{
- return ((new->u.rip.metric == old->u.rip.metric) &&
- (new->u.rip.tag == old->u.rip.tag) &&
- (new->u.rip.from == old->u.rip.from));
+ return ea_get_int(rt->attrs->eattrs, EA_RIP_METRIC, IGP_METRIC_UNKNOWN);
}
-
static void
rip_postconfig(struct proto_config *CF)
{
@@ -1120,10 +1131,7 @@ rip_init(struct proto_config *CF)
P->rt_notify = rip_rt_notify;
P->neigh_notify = rip_neigh_notify;
P->reload_routes = rip_reload_routes;
- P->make_tmp_attrs = rip_make_tmp_attrs;
- P->store_tmp_attrs = rip_store_tmp_attrs;
- P->rte_better = rip_rte_better;
- P->rte_same = rip_rte_same;
+ P->sources.class = &rip_rte_owner_class;
return P;
}
@@ -1198,10 +1206,14 @@ rip_reconfigure(struct proto *P, struct proto_config *CF)
static void
rip_get_route_info(rte *rte, byte *buf)
{
- buf += bsprintf(buf, " (%d/%d)", rte->pref, rte->u.rip.metric);
+ struct rip_proto *p = rip_rte_proto(rte);
+ u32 rt_metric = ea_get_int(rte->attrs->eattrs, EA_RIP_METRIC, p->infinity);
+ u32 rt_tag = ea_get_int(rte->attrs->eattrs, EA_RIP_TAG, 0);
+
+ buf += bsprintf(buf, " (%d/%d)", rte->attrs->pref, rt_metric);
- if (rte->u.rip.tag)
- bsprintf(buf, " [%04x]", rte->u.rip.tag);
+ if (rt_tag)
+ bsprintf(buf, " [%04x]", rt_tag);
}
static int
@@ -1321,6 +1333,12 @@ rip_dump(struct proto *P)
}
+static struct rte_owner_class rip_rte_owner_class = {
+ .get_route_info = rip_get_route_info,
+ .rte_better = rip_rte_better,
+ .rte_igp_metric = rip_rte_igp_metric,
+};
+
struct protocol proto_rip = {
.name = "RIP",
.template = "rip%d",
@@ -1335,6 +1353,5 @@ struct protocol proto_rip = {
.start = rip_start,
.shutdown = rip_shutdown,
.reconfigure = rip_reconfigure,
- .get_route_info = rip_get_route_info,
.get_attr = rip_get_attr
};
diff --git a/proto/rip/rip.h b/proto/rip/rip.h
index 8d347000..f8713c4a 100644
--- a/proto/rip/rip.h
+++ b/proto/rip/rip.h
@@ -197,6 +197,7 @@ struct rip_rte
#define EA_RIP_METRIC EA_CODE(PROTOCOL_RIP, 0)
#define EA_RIP_TAG EA_CODE(PROTOCOL_RIP, 1)
+#define EA_RIP_FROM EA_CODE(PROTOCOL_RIP, 2)
static inline int rip_is_v2(struct rip_proto *p)
{ return p->rip2; }
diff --git a/proto/rpki/config.Y b/proto/rpki/config.Y
index d6d326b8..743b5b42 100644
--- a/proto/rpki/config.Y
+++ b/proto/rpki/config.Y
@@ -42,6 +42,7 @@ proto: rpki_proto ;
rpki_proto_start: proto_start RPKI {
this_proto = proto_config_new(&proto_rpki, $1);
+ this_proto->loop_order = DOMAIN_ORDER(proto);
RPKI_CFG->retry_interval = RPKI_RETRY_INTERVAL;
RPKI_CFG->refresh_interval = RPKI_REFRESH_INTERVAL;
RPKI_CFG->expire_interval = RPKI_EXPIRE_INTERVAL;
diff --git a/proto/rpki/packets.c b/proto/rpki/packets.c
index d246dd50..d7895a22 100644
--- a/proto/rpki/packets.c
+++ b/proto/rpki/packets.c
@@ -233,7 +233,12 @@ static const size_t min_pdu_size[] = {
[ERROR] = 16,
};
-static int rpki_send_error_pdu(struct rpki_cache *cache, const enum pdu_error_type error_code, const u32 err_pdu_len, const struct pdu_header *erroneous_pdu, const char *fmt, ...);
+static int rpki_send_error_pdu_(struct rpki_cache *cache, const enum pdu_error_type error_code, const u32 err_pdu_len, const struct pdu_header *erroneous_pdu, const char *fmt, ...);
+
+#define rpki_send_error_pdu(cache, error_code, err_pdu_len, erroneous_pdu, fmt...) ({ \
+ rpki_send_error_pdu_(cache, error_code, err_pdu_len, erroneous_pdu, #fmt); \
+ CACHE_TRACE(D_PACKETS, cache, #fmt); \
+ })
static void
rpki_pdu_to_network_byte_order(struct pdu_header *pdu)
@@ -595,6 +600,7 @@ rpki_handle_error_pdu(struct rpki_cache *cache, const struct pdu_error *pdu)
case INTERNAL_ERROR:
case INVALID_REQUEST:
case UNSUPPORTED_PDU_TYPE:
+ CACHE_TRACE(D_PACKETS, cache, "Got UNSUPPORTED_PDU_TYPE");
rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL);
break;
@@ -652,21 +658,7 @@ rpki_handle_cache_response_pdu(struct rpki_cache *cache, const struct pdu_cache_
{
if (cache->request_session_id)
{
- if (cache->last_update)
- {
- /*
- * This isn't the first sync and we already received records. This point
- * is after Reset Query and before importing new records from cache
- * server. We need to load new ones and kick out missing ones. So start
- * a refresh cycle.
- */
- if (cache->p->roa4_channel)
- rt_refresh_begin(cache->p->roa4_channel->table, cache->p->roa4_channel);
- if (cache->p->roa6_channel)
- rt_refresh_begin(cache->p->roa6_channel->table, cache->p->roa6_channel);
-
- cache->p->refresh_channels = 1;
- }
+ rpki_start_refresh(cache->p);
cache->session_id = pdu->session_id;
cache->request_session_id = 0;
}
@@ -842,14 +834,7 @@ rpki_handle_end_of_data_pdu(struct rpki_cache *cache, const struct pdu_end_of_da
(cf->keep_expire_interval ? "keeps " : ""), cache->expire_interval);
}
- if (cache->p->refresh_channels)
- {
- cache->p->refresh_channels = 0;
- if (cache->p->roa4_channel)
- rt_refresh_end(cache->p->roa4_channel->table, cache->p->roa4_channel);
- if (cache->p->roa6_channel)
- rt_refresh_end(cache->p->roa6_channel->table, cache->p->roa6_channel);
- }
+ rpki_stop_refresh(cache->p);
cache->last_update = current_time();
cache->serial_num = pdu->serial_num;
@@ -924,6 +909,9 @@ rpki_rx_hook(struct birdsock *sk, uint size)
struct rpki_cache *cache = sk->data;
struct rpki_proto *p = cache->p;
+ if ((p->p.proto_state == PS_DOWN) || (p->cache != cache))
+ return 0;
+
byte *pkt_start = sk->rbuf;
byte *end = pkt_start + size;
@@ -980,6 +968,8 @@ rpki_err_hook(struct birdsock *sk, int error_num)
CACHE_TRACE(D_EVENTS, cache, "The other side closed a connection");
}
+ if (cache->p->cache != cache)
+ return;
rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT);
}
@@ -999,6 +989,9 @@ rpki_tx_hook(sock *sk)
{
struct rpki_cache *cache = sk->data;
+ if (cache->p->cache != cache)
+ return;
+
while (rpki_fire_tx(cache) > 0)
;
}
@@ -1008,6 +1001,9 @@ rpki_connected_hook(sock *sk)
{
struct rpki_cache *cache = sk->data;
+ if (cache->p->cache != cache)
+ return;
+
CACHE_TRACE(D_EVENTS, cache, "Connected");
proto_notify_state(&cache->p->p, PS_UP);
@@ -1029,7 +1025,7 @@ rpki_connected_hook(sock *sk)
* This function prepares Error PDU and sends it to a cache server.
*/
static int
-rpki_send_error_pdu(struct rpki_cache *cache, const enum pdu_error_type error_code, const u32 err_pdu_len, const struct pdu_header *erroneous_pdu, const char *fmt, ...)
+rpki_send_error_pdu_(struct rpki_cache *cache, const enum pdu_error_type error_code, const u32 err_pdu_len, const struct pdu_header *erroneous_pdu, const char *fmt, ...)
{
va_list args;
char msg[128];
diff --git a/proto/rpki/rpki.c b/proto/rpki/rpki.c
index ab0837f3..afba2216 100644
--- a/proto/rpki/rpki.c
+++ b/proto/rpki/rpki.c
@@ -109,6 +109,7 @@ static void rpki_schedule_next_expire_check(struct rpki_cache *cache);
static void rpki_stop_refresh_timer_event(struct rpki_cache *cache);
static void rpki_stop_retry_timer_event(struct rpki_cache *cache);
static void rpki_stop_expire_timer_event(struct rpki_cache *cache);
+static void rpki_stop_all_timers(struct rpki_cache *cache);
/*
@@ -121,27 +122,48 @@ rpki_table_add_roa(struct rpki_cache *cache, struct channel *channel, const net_
struct rpki_proto *p = cache->p;
rta a0 = {
- .src = p->p.main_source,
+ .pref = channel->preference,
.source = RTS_RPKI,
.scope = SCOPE_UNIVERSE,
.dest = RTD_NONE,
};
- rta *a = rta_lookup(&a0);
- rte *e = rte_get_temp(a);
+ rte e0 = { .attrs = &a0, .src = p->p.main_source, };
- e->pflags = 0;
-
- rte_update2(channel, &pfxr->n, e, a0.src);
+ rte_update(channel, &pfxr->n, &e0, p->p.main_source);
}
void
rpki_table_remove_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr)
{
struct rpki_proto *p = cache->p;
- rte_update2(channel, &pfxr->n, NULL, p->p.main_source);
+ rte_update(channel, &pfxr->n, NULL, p->p.main_source);
+}
+
+void
+rpki_start_refresh(struct rpki_proto *p)
+{
+ if (p->roa4_channel)
+ rt_refresh_begin(&p->roa4_channel->in_req);
+ if (p->roa6_channel)
+ rt_refresh_begin(&p->roa6_channel->in_req);
+
+ p->refresh_channels = 1;
}
+void
+rpki_stop_refresh(struct rpki_proto *p)
+{
+ if (!p->refresh_channels)
+ return;
+
+ p->refresh_channels = 0;
+
+ if (p->roa4_channel)
+ rt_refresh_end(&p->roa4_channel->in_req);
+ if (p->roa6_channel)
+ rt_refresh_end(&p->roa6_channel->in_req);
+}
/*
* RPKI Protocol Logic
@@ -198,6 +220,8 @@ rpki_force_restart_proto(struct rpki_proto *p)
{
if (p->cache)
{
+ rpki_tr_close(p->cache->tr_sock);
+ rpki_stop_all_timers(p->cache);
CACHE_DBG(p->cache, "Connection object destroying");
}
@@ -321,7 +345,7 @@ rpki_schedule_next_refresh(struct rpki_cache *cache)
btime t = cache->refresh_interval S;
CACHE_DBG(cache, "after %t s", t);
- tm_start(cache->refresh_timer, t);
+ tm_start_in(cache->refresh_timer, t, cache->p->p.loop);
}
static void
@@ -330,7 +354,7 @@ rpki_schedule_next_retry(struct rpki_cache *cache)
btime t = cache->retry_interval S;
CACHE_DBG(cache, "after %t s", t);
- tm_start(cache->retry_timer, t);
+ tm_start_in(cache->retry_timer, t, cache->p->p.loop);
}
static void
@@ -341,7 +365,7 @@ rpki_schedule_next_expire_check(struct rpki_cache *cache)
t = MAX(t, 1 S);
CACHE_DBG(cache, "after %t s", t);
- tm_start(cache->expire_timer, t);
+ tm_start_in(cache->expire_timer, t, cache->p->p.loop);
}
static void
@@ -358,13 +382,21 @@ rpki_stop_retry_timer_event(struct rpki_cache *cache)
tm_stop(cache->retry_timer);
}
-static void UNUSED
+static void
rpki_stop_expire_timer_event(struct rpki_cache *cache)
{
CACHE_DBG(cache, "Stop");
tm_stop(cache->expire_timer);
}
+static void
+rpki_stop_all_timers(struct rpki_cache *cache)
+{
+ rpki_stop_refresh_timer_event(cache);
+ rpki_stop_retry_timer_event(cache);
+ rpki_stop_expire_timer_event(cache);
+}
+
static int
rpki_do_we_recv_prefix_pdu_in_last_seconds(struct rpki_cache *cache)
{
@@ -387,6 +419,9 @@ rpki_refresh_hook(timer *tm)
{
struct rpki_cache *cache = tm->data;
+ if (cache->p->cache != cache)
+ return;
+
CACHE_DBG(cache, "%s", rpki_cache_state_to_str(cache->state));
switch (cache->state)
@@ -433,6 +468,9 @@ rpki_retry_hook(timer *tm)
{
struct rpki_cache *cache = tm->data;
+ if (cache->p->cache != cache)
+ return;
+
CACHE_DBG(cache, "%s", rpki_cache_state_to_str(cache->state));
switch (cache->state)
@@ -478,6 +516,9 @@ rpki_expire_hook(timer *tm)
{
struct rpki_cache *cache = tm->data;
+ if (cache->p->cache != cache)
+ return;
+
if (!cache->last_update)
return;
@@ -555,7 +596,7 @@ rpki_check_expire_interval(uint seconds)
static struct rpki_cache *
rpki_init_cache(struct rpki_proto *p, struct rpki_config *cf)
{
- pool *pool = rp_new(p->p.pool, cf->hostname);
+ pool *pool = rp_new(p->p.pool, p->p.loop, cf->hostname);
struct rpki_cache *cache = mb_allocz(pool, sizeof(struct rpki_cache));
@@ -620,6 +661,7 @@ rpki_close_connection(struct rpki_cache *cache)
{
CACHE_TRACE(D_EVENTS, cache, "Closing a connection");
rpki_tr_close(cache->tr_sock);
+ rpki_stop_refresh(cache->p);
proto_notify_state(&cache->p->p, PS_START);
}
diff --git a/proto/rpki/rpki.h b/proto/rpki/rpki.h
index 8a5c38fd..a70a2027 100644
--- a/proto/rpki/rpki.h
+++ b/proto/rpki/rpki.h
@@ -83,6 +83,8 @@ const char *rpki_cache_state_to_str(enum rpki_cache_state state);
void rpki_table_add_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr);
void rpki_table_remove_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr);
+void rpki_start_refresh(struct rpki_proto *p);
+void rpki_stop_refresh(struct rpki_proto *p);
/*
* RPKI Protocol Logic
diff --git a/proto/rpki/ssh_transport.c b/proto/rpki/ssh_transport.c
index 6333f367..223afa80 100644
--- a/proto/rpki/ssh_transport.c
+++ b/proto/rpki/ssh_transport.c
@@ -38,6 +38,8 @@ rpki_tr_ssh_open(struct rpki_tr_sock *tr)
if (sk_open(sk) != 0)
return RPKI_TR_ERROR;
+ sk_start(sk);
+
return RPKI_TR_SUCCESS;
}
diff --git a/proto/rpki/tcp_transport.c b/proto/rpki/tcp_transport.c
index 132f8e2d..4e850c44 100644
--- a/proto/rpki/tcp_transport.c
+++ b/proto/rpki/tcp_transport.c
@@ -31,6 +31,8 @@ rpki_tr_tcp_open(struct rpki_tr_sock *tr)
if (sk_open(sk) != 0)
return RPKI_TR_ERROR;
+ sk_start(sk);
+
return RPKI_TR_SUCCESS;
}
diff --git a/proto/rpki/transport.c b/proto/rpki/transport.c
index a1ac7587..26609764 100644
--- a/proto/rpki/transport.c
+++ b/proto/rpki/transport.c
@@ -85,6 +85,8 @@ rpki_tr_open(struct rpki_tr_sock *tr)
sk->rbsize = RPKI_RX_BUFFER_SIZE;
sk->tbsize = RPKI_TX_BUFFER_SIZE;
sk->tos = IP_PREC_INTERNET_CONTROL;
+ sk->flags |= SKF_THREAD;
+ sk->loop = cache->p->p.loop;
if (ipa_zero(sk->daddr) && sk->host)
{
@@ -119,6 +121,7 @@ rpki_tr_close(struct rpki_tr_sock *tr)
if (tr->sk)
{
+ sk_stop(tr->sk);
rfree(tr->sk);
tr->sk = NULL;
}
diff --git a/proto/static/static.c b/proto/static/static.c
index 2789c1bb..d89ca8b0 100644
--- a/proto/static/static.c
+++ b/proto/static/static.c
@@ -52,14 +52,18 @@ static linpool *static_lp;
static inline struct rte_src * static_get_source(struct static_proto *p, uint i)
{ return i ? rt_get_source(&p->p, i) : p->p.main_source; }
+static inline void static_free_source(struct rte_src *src, uint i)
+{ if (i) rt_unlock_source(src); }
+
static void
static_announce_rte(struct static_proto *p, struct static_route *r)
{
+ struct rte_src *src;
rta *a = allocz(RTA_MAX_SIZE);
- a->src = static_get_source(p, r->index);
a->source = RTS_STATIC;
a->scope = SCOPE_UNIVERSE;
a->dest = r->dest;
+ a->pref = p->p.main_channel->preference;
if (r->dest == RTD_UNICAST)
{
@@ -94,7 +98,7 @@ static_announce_rte(struct static_proto *p, struct static_route *r)
if (r->dest == RTDX_RECURSIVE)
{
rtable *tab = ipa_is_ip4(r->via) ? p->igp_table_ip4 : p->igp_table_ip6;
- rta_set_recursive_next_hop(p->p.main_channel->table, a, tab, r->via, IPA_NONE, r->mls);
+ rta_set_recursive_next_hop(p->p.main_channel->table, a, tab, r->via, IPA_NONE, r->mls, static_lp);
}
/* Already announced */
@@ -102,24 +106,16 @@ static_announce_rte(struct static_proto *p, struct static_route *r)
return;
/* We skip rta_lookup() here */
- rte *e = rte_get_temp(a);
- e->pflags = 0;
+ src = static_get_source(p, r->index);
+ rte e0 = { .attrs = a, .src = src, .net = r->net, }, *e = &e0;
+ /* Evaluate the filter */
if (r->cmds)
- {
- /* Create a temporary table node */
- e->net = alloca(sizeof(net) + r->net->length);
- memset(e->net, 0, sizeof(net) + r->net->length);
- net_copy(e->net->n.addr, r->net);
+ f_eval_rte(r->cmds, e, static_lp);
- /* Evaluate the filter */
- f_eval_rte(r->cmds, &e, static_lp);
+ rte_update(p->p.main_channel, r->net, e, src);
+ static_free_source(src, r->index);
- /* Remove the temporary node */
- e->net = NULL;
- }
-
- rte_update2(p->p.main_channel, r->net, e, a->src);
r->state = SRS_CLEAN;
if (r->cmds)
@@ -131,7 +127,9 @@ withdraw:
if (r->state == SRS_DOWN)
return;
- rte_update2(p->p.main_channel, r->net, NULL, a->src);
+ src = static_get_source(p, r->index);
+ rte_update(p->p.main_channel, r->net, NULL, src);
+ static_free_source(src, r->index);
r->state = SRS_DOWN;
}
@@ -208,7 +206,7 @@ static_update_bfd(struct static_proto *p, struct static_route *r)
// ip_addr local = ipa_nonzero(r->local) ? r->local : nb->ifa->ip;
r->bfd_req = bfd_request_session(p->p.pool, r->via, nb->ifa->ip,
nb->iface, p->p.vrf,
- static_bfd_notify, r, NULL);
+ static_bfd_notify, r, birdloop_event_list(p->p.loop), NULL);
}
if (!bfd_up && r->bfd_req)
@@ -297,7 +295,11 @@ static void
static_remove_rte(struct static_proto *p, struct static_route *r)
{
if (r->state)
- rte_update2(p->p.main_channel, r->net, NULL, static_get_source(p, r->index));
+ {
+ struct rte_src *src = static_get_source(p, r->index);
+ rte_update(p->p.main_channel, r->net, NULL, src);
+ static_free_source(src, r->index);
+ }
static_reset_rte(p, r);
}
@@ -454,6 +456,8 @@ static_postconfig(struct proto_config *CF)
static_index_routes(cf);
}
+static struct rte_owner_class static_rte_owner_class;
+
static struct proto *
static_init(struct proto_config *CF)
{
@@ -465,8 +469,7 @@ static_init(struct proto_config *CF)
P->neigh_notify = static_neigh_notify;
P->reload_routes = static_reload_routes;
- P->rte_better = static_rte_better;
- P->rte_mergable = static_rte_mergable;
+ P->sources.class = &static_rte_owner_class;
if (cf->igp_table_ip4)
p->igp_table_ip4 = cf->igp_table_ip4->table;
@@ -488,10 +491,12 @@ static_start(struct proto *P)
static_lp = lp_new(&root_pool, LP_GOOD_SIZE(1024));
if (p->igp_table_ip4)
- rt_lock_table(p->igp_table_ip4);
+ RT_LOCKED(p->igp_table_ip4, t)
+ rt_lock_table(t);
if (p->igp_table_ip6)
- rt_lock_table(p->igp_table_ip6);
+ RT_LOCKED(p->igp_table_ip6, t)
+ rt_lock_table(t);
p->event = ev_new_init(p->p.pool, static_announce_marked, p);
@@ -517,19 +522,15 @@ static_shutdown(struct proto *P)
WALK_LIST(r, cf->routes)
static_reset_rte(p, r);
- return PS_DOWN;
-}
-
-static void
-static_cleanup(struct proto *P)
-{
- struct static_proto *p = (void *) P;
-
if (p->igp_table_ip4)
- rt_unlock_table(p->igp_table_ip4);
+ RT_LOCKED(p->igp_table_ip4, t)
+ rt_unlock_table(t);
if (p->igp_table_ip6)
- rt_unlock_table(p->igp_table_ip6);
+ RT_LOCKED(p->igp_table_ip6, t)
+ rt_unlock_table(t);
+
+ return PS_DOWN;
}
static void
@@ -721,9 +722,9 @@ static_get_route_info(rte *rte, byte *buf)
{
eattr *a = ea_find(rte->attrs->eattrs, EA_GEN_IGP_METRIC);
if (a)
- buf += bsprintf(buf, " (%d/%u)", rte->pref, a->u.data);
+ buf += bsprintf(buf, " (%d/%u)", rte->attrs->pref, a->u.data);
else
- buf += bsprintf(buf, " (%d)", rte->pref);
+ buf += bsprintf(buf, " (%d)", rte->attrs->pref);
}
static void
@@ -773,6 +774,11 @@ static_show(struct proto *P)
static_show_rt(r);
}
+static struct rte_owner_class static_rte_owner_class = {
+ .get_route_info = static_get_route_info,
+ .rte_better = static_rte_better,
+ .rte_mergable = static_rte_mergable,
+};
struct protocol proto_static = {
.name = "Static",
@@ -787,8 +793,6 @@ struct protocol proto_static = {
.dump = static_dump,
.start = static_start,
.shutdown = static_shutdown,
- .cleanup = static_cleanup,
.reconfigure = static_reconfigure,
.copy_config = static_copy_config,
- .get_route_info = static_get_route_info,
};
diff --git a/sysdep/bsd/krt-sock.c b/sysdep/bsd/krt-sock.c
index 521f43f3..fb407739 100644
--- a/sysdep/bsd/krt-sock.c
+++ b/sysdep/bsd/krt-sock.c
@@ -197,9 +197,9 @@ sockaddr_fill_dl(struct sockaddr_dl *sa, struct iface *ifa)
}
static int
-krt_send_route(struct krt_proto *p, int cmd, rte *e)
+krt_send_route(struct krt_proto *p, int cmd, const rte *e)
{
- net *net = e->net;
+ const net_addr *net = e->net;
rta *a = e->attrs;
static int msg_seq;
struct iface *j, *i = a->nh.iface;
@@ -208,7 +208,7 @@ krt_send_route(struct krt_proto *p, int cmd, rte *e)
char *body = (char *)msg.buf;
sockaddr gate, mask, dst;
- DBG("krt-sock: send %I/%d via %I\n", net->n.prefix, net->n.pxlen, a->gw);
+// DBG("krt-sock: send %I/%d via %I\n", net->prefix, net->pxlen, a->gw);
bzero(&msg,sizeof (struct rt_msghdr));
msg.rtm.rtm_version = RTM_VERSION;
@@ -218,7 +218,7 @@ krt_send_route(struct krt_proto *p, int cmd, rte *e)
msg.rtm.rtm_flags = RTF_UP | RTF_PROTO1;
/* XXXX */
- if (net_pxlen(net->n.addr) == net_max_prefix_length[net->n.addr->type])
+ if (net_pxlen(net) == net_max_prefix_length[net->type])
msg.rtm.rtm_flags |= RTF_HOST;
else
msg.rtm.rtm_addrs |= RTA_NETMASK;
@@ -242,7 +242,8 @@ krt_send_route(struct krt_proto *p, int cmd, rte *e)
*/
if (!i)
{
- WALK_LIST(j, iface_list)
+ IFACE_LOCK;
+ WALK_LIST(j, global_iface_list)
{
if (j->flags & IF_LOOPBACK)
{
@@ -250,6 +251,7 @@ krt_send_route(struct krt_proto *p, int cmd, rte *e)
break;
}
}
+ IFACE_UNLOCK;
if (!i)
{
@@ -260,7 +262,7 @@ krt_send_route(struct krt_proto *p, int cmd, rte *e)
int af = AF_UNSPEC;
- switch (net->n.addr->type) {
+ switch (net->type) {
case NET_IP4:
af = AF_INET;
break;
@@ -268,12 +270,12 @@ krt_send_route(struct krt_proto *p, int cmd, rte *e)
af = AF_INET6;
break;
default:
- log(L_ERR "KRT: Not sending route %N to kernel", net->n.addr);
+ log(L_ERR "KRT: Not sending route %N to kernel", net);
return -1;
}
- sockaddr_fill(&dst, af, net_prefix(net->n.addr), NULL, 0);
- sockaddr_fill(&mask, af, net_pxmask(net->n.addr), NULL, 0);
+ sockaddr_fill(&dst, af, net_prefix(net), NULL, 0);
+ sockaddr_fill(&mask, af, net_pxmask(net), NULL, 0);
switch (a->dest)
{
@@ -303,7 +305,7 @@ krt_send_route(struct krt_proto *p, int cmd, rte *e)
#if __OpenBSD__
/* Keeping temporarily old code for OpenBSD */
- struct ifa *addr = (net->n.addr->type == NET_IP4) ? i->addr4 : (i->addr6 ?: i->llv6);
+ struct ifa *addr = (net->type == NET_IP4) ? i->addr4 : (i->addr6 ?: i->llv6);
if (!addr)
{
@@ -339,7 +341,7 @@ krt_send_route(struct krt_proto *p, int cmd, rte *e)
msg.rtm.rtm_msglen = l;
if ((l = write(p->sys.sk->fd, (char *)&msg, l)) < 0) {
- log(L_ERR "KRT: Error sending route %N to kernel: %m", net->n.addr);
+ log(L_ERR "KRT: Error sending route %N to kernel: %m", net);
return -1;
}
@@ -347,7 +349,7 @@ krt_send_route(struct krt_proto *p, int cmd, rte *e)
}
void
-krt_replace_rte(struct krt_proto *p, net *n, rte *new, rte *old)
+krt_replace_rte(struct krt_proto *p, const net_addr *n UNUSED, rte *new, const rte *old)
{
int err = 0;
@@ -398,8 +400,6 @@ krt_read_route(struct ks_msg *msg, struct krt_proto *p, int scan)
/* p is NULL iff KRT_SHARED_SOCKET and !scan */
int ipv6;
- rte *e;
- net *net;
sockaddr dst, gate, mask;
ip_addr idst, igate, imask;
net_addr ndst;
@@ -516,10 +516,7 @@ krt_read_route(struct ks_msg *msg, struct krt_proto *p, int scan)
else
src = KRT_SRC_KERNEL;
- net = net_get(p->p.main_channel->table, &ndst);
-
rta a = {
- .src = p->p.main_source,
.source = RTS_INHERIT,
.scope = SCOPE_UNIVERSE,
};
@@ -545,7 +542,7 @@ krt_read_route(struct ks_msg *msg, struct krt_proto *p, int scan)
if (!a.nh.iface)
{
log(L_ERR "KRT: Received route %N with unknown ifindex %u",
- net->n.addr, msg->rtm.rtm_index);
+ &ndst, msg->rtm.rtm_index);
return;
}
@@ -574,24 +571,28 @@ krt_read_route(struct ks_msg *msg, struct krt_proto *p, int scan)
return;
log(L_ERR "KRT: Received route %N with strange next-hop %I",
- net->n.addr, a.nh.gw);
+ &ndst, a.nh.gw);
return;
}
}
- done:
- e = rte_get_temp(&a);
- e->net = net;
- e->u.krt.src = src;
- e->u.krt.proto = src2;
- e->u.krt.seen = 0;
- e->u.krt.best = 0;
- e->u.krt.metric = 0;
+ done:;
+ rte e0 = { .attrs = &a, .net = &ndst, };
+
+ ea_list *ea = alloca(sizeof(ea_list) + 1 * sizeof(eattr));
+ *ea = (ea_list) { .count = 1, .next = e0.attrs->eattrs };
+ e0.attrs->eattrs = ea;
+
+ ea->attrs[0] = (eattr) {
+ .id = EA_KRT_SOURCE,
+ .type = EAF_TYPE_INT,
+ .u.data = src2,
+ };
if (scan)
- krt_got_route(p, e);
+ krt_got_route(p, &e0, src);
else
- krt_got_route_async(p, e, new);
+ krt_got_route_async(p, &e0, new, src);
}
static void
diff --git a/sysdep/linux/netlink.c b/sysdep/linux/netlink.c
index fdf3f2db..f52a796f 100644
--- a/sysdep/linux/netlink.c
+++ b/sysdep/linux/netlink.c
@@ -105,7 +105,7 @@ struct nl_parse_state
int scan;
int merge;
- net *net;
+ net_addr *net;
rta *attrs;
struct krt_proto *proto;
s8 new;
@@ -1147,8 +1147,9 @@ kif_do_scan(struct kif_proto *p UNUSED)
log(L_DEBUG "nl_scan_ifaces: Unknown packet received (type=%d)", h->nlmsg_type);
/* Re-resolve master interface for slaves */
+ IFACE_LEGACY_ACCESS;
struct iface *i;
- WALK_LIST(i, iface_list)
+ WALK_LIST(i, global_iface_list)
if (i->master_index)
{
struct iface f = {
@@ -1233,10 +1234,9 @@ nh_bufsize(struct nexthop *nh)
}
static int
-nl_send_route(struct krt_proto *p, rte *e, int op, int dest, struct nexthop *nh)
+nl_send_route(struct krt_proto *p, const rte *e, int op, int dest, struct nexthop *nh)
{
eattr *ea;
- net *net = e->net;
rta *a = e->attrs;
ea_list *eattrs = a->eattrs;
int bufsize = 128 + KRT_METRICS_MAX*8 + nh_bufsize(&(a->nh));
@@ -1251,7 +1251,7 @@ nl_send_route(struct krt_proto *p, rte *e, int op, int dest, struct nexthop *nh)
int rsize = sizeof(*r) + bufsize;
r = alloca(rsize);
- DBG("nl_send_route(%N,op=%x)\n", net->n.addr, op);
+ DBG("nl_send_route(%N,op=%x)\n", e->net, op);
bzero(&r->h, sizeof(r->h));
bzero(&r->r, sizeof(r->r));
@@ -1260,7 +1260,7 @@ nl_send_route(struct krt_proto *p, rte *e, int op, int dest, struct nexthop *nh)
r->h.nlmsg_flags = op | NLM_F_REQUEST | NLM_F_ACK;
r->r.rtm_family = p->af;
- r->r.rtm_dst_len = net_pxlen(net->n.addr);
+ r->r.rtm_dst_len = net_pxlen(e->net);
r->r.rtm_protocol = RTPROT_BIRD;
r->r.rtm_scope = RT_SCOPE_NOWHERE;
#ifdef HAVE_MPLS_KERNEL
@@ -1272,7 +1272,7 @@ nl_send_route(struct krt_proto *p, rte *e, int op, int dest, struct nexthop *nh)
* 2) Never use RTA_PRIORITY
*/
- u32 label = net_mpls(net->n.addr);
+ u32 label = net_mpls(e->net);
nl_add_attr_mpls(&r->h, rsize, RTA_DST, 1, &label);
r->r.rtm_scope = RT_SCOPE_UNIVERSE;
r->r.rtm_type = RTN_UNICAST;
@@ -1280,12 +1280,12 @@ nl_send_route(struct krt_proto *p, rte *e, int op, int dest, struct nexthop *nh)
else
#endif
{
- nl_add_attr_ipa(&r->h, rsize, RTA_DST, net_prefix(net->n.addr));
+ nl_add_attr_ipa(&r->h, rsize, RTA_DST, net_prefix(e->net));
/* Add source address for IPv6 SADR routes */
- if (net->n.addr->type == NET_IP6_SADR)
+ if (e->net->type == NET_IP6_SADR)
{
- net_addr_ip6_sadr *a = (void *) &net->n.addr;
+ net_addr_ip6_sadr *a = (void *) &e->net;
nl_add_attr_ip6(&r->h, rsize, RTA_SRC, a->src_prefix);
r->r.rtm_src_len = a->src_pxlen;
}
@@ -1305,8 +1305,6 @@ nl_send_route(struct krt_proto *p, rte *e, int op, int dest, struct nexthop *nh)
if (p->af == AF_MPLS)
priority = 0;
- else if (a->source == RTS_DUMMY)
- priority = e->u.krt.metric;
else if (KRT_CF->sys.metric)
priority = KRT_CF->sys.metric;
else if ((op != NL_OP_DELETE) && (ea = ea_find(eattrs, EA_KRT_METRIC)))
@@ -1408,7 +1406,7 @@ nl_add_rte(struct krt_proto *p, rte *e)
}
static inline int
-nl_delete_rte(struct krt_proto *p, rte *e)
+nl_delete_rte(struct krt_proto *p, const rte *e)
{
int err = 0;
@@ -1429,7 +1427,7 @@ nl_replace_rte(struct krt_proto *p, rte *e)
void
-krt_replace_rte(struct krt_proto *p, net *n UNUSED, rte *new, rte *old)
+krt_replace_rte(struct krt_proto *p, const net_addr *n UNUSED, rte *new, const rte *old)
{
int err = 0;
@@ -1468,7 +1466,7 @@ krt_replace_rte(struct krt_proto *p, net *n UNUSED, rte *new, rte *old)
}
static int
-nl_mergable_route(struct nl_parse_state *s, net *net, struct krt_proto *p, uint priority, uint krt_type, uint rtm_family)
+nl_mergable_route(struct nl_parse_state *s, const net_addr *net, struct krt_proto *p, uint priority, uint krt_type, uint rtm_family)
{
/* Route merging is used for IPv6 scans */
if (!s->scan || (rtm_family != AF_INET6))
@@ -1488,18 +1486,30 @@ nl_mergable_route(struct nl_parse_state *s, net *net, struct krt_proto *p, uint
static void
nl_announce_route(struct nl_parse_state *s)
{
- rte *e = rte_get_temp(s->attrs);
- e->net = s->net;
- e->u.krt.src = s->krt_src;
- e->u.krt.proto = s->krt_proto;
- e->u.krt.seen = 0;
- e->u.krt.best = 0;
- e->u.krt.metric = s->krt_metric;
+ rte e0 = {
+ .attrs = s->attrs,
+ .net = s->net,
+ };
+
+ ea_list *ea = alloca(sizeof(ea_list) + 2 * sizeof(eattr));
+ *ea = (ea_list) { .count = 2, .next = e0.attrs->eattrs };
+ e0.attrs->eattrs = ea;
+
+ ea->attrs[0] = (eattr) {
+ .id = EA_KRT_SOURCE,
+ .type = EAF_TYPE_INT,
+ .u.data = s->krt_proto,
+ };
+ ea->attrs[1] = (eattr) {
+ .id = EA_KRT_METRIC,
+ .type = EAF_TYPE_INT,
+ .u.data = s->krt_metric,
+ };
if (s->scan)
- krt_got_route(s->proto, e);
+ krt_got_route(s->proto, &e0, s->krt_src);
else
- krt_got_route_async(s->proto, e, s->new);
+ krt_got_route_async(s->proto, &e0, s->new, s->krt_src);
s->net = NULL;
s->attrs = NULL;
@@ -1645,21 +1655,18 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h)
krt_src = KRT_SRC_ALIEN;
}
- net_addr *n = &dst;
+ net_addr *net = &dst;
if (p->p.net_type == NET_IP6_SADR)
{
- n = alloca(sizeof(net_addr_ip6_sadr));
- net_fill_ip6_sadr(n, net6_prefix(&dst), net6_pxlen(&dst),
+ net = alloca(sizeof(net_addr_ip6_sadr));
+ net_fill_ip6_sadr(net, net6_prefix(&dst), net6_pxlen(&dst),
net6_prefix(&src), net6_pxlen(&src));
}
- net *net = net_get(p->p.main_channel->table, n);
-
if (s->net && !nl_mergable_route(s, net, p, priority, i->rtm_type, i->rtm_family))
nl_announce_route(s);
rta *ra = lp_allocz(s->pool, RTA_MAX_SIZE);
- ra->src = p->p.main_source;
ra->source = RTS_INHERIT;
ra->scope = SCOPE_UNIVERSE;
@@ -1678,7 +1685,7 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h)
struct nexthop *nh = nl_parse_multipath(s, p, a[RTA_MULTIPATH], i->rtm_family);
if (!nh)
{
- log(L_ERR "KRT: Received strange multipath route %N", net->n.addr);
+ log(L_ERR "KRT: Received strange multipath route %N", net);
return;
}
@@ -1692,7 +1699,7 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h)
ra->nh.iface = if_find_by_index(oif);
if (!ra->nh.iface)
{
- log(L_ERR "KRT: Received route %N with unknown ifindex %u", net->n.addr, oif);
+ log(L_ERR "KRT: Received route %N with unknown ifindex %u", net, oif);
return;
}
@@ -1719,8 +1726,7 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h)
(ra->nh.flags & RNF_ONLINK) ? NEF_ONLINK : 0);
if (!nbr || (nbr->scope == SCOPE_HOST))
{
- log(L_ERR "KRT: Received route %N with strange next-hop %I", net->n.addr,
- ra->nh.gw);
+ log(L_ERR "KRT: Received route %N with strange next-hop %I", net, ra->nh.gw);
return;
}
}
@@ -1819,7 +1825,7 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h)
if (nl_parse_metrics(a[RTA_METRICS], metrics, ARRAY_SIZE(metrics)) < 0)
{
- log(L_ERR "KRT: Received route %N with strange RTA_METRICS attribute", net->n.addr);
+ log(L_ERR "KRT: Received route %N with strange RTA_METRICS attribute", net);
return;
}
@@ -1853,7 +1859,9 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h)
if (!s->net)
{
/* Store the new route */
- s->net = net;
+ s->net = lp_alloc(s->pool, net->length);
+ net_copy(s->net, net);
+
s->attrs = ra;
s->proto = p;
s->new = new;
diff --git a/sysdep/unix/Makefile b/sysdep/unix/Makefile
index d0d36b5f..07f454ab 100644
--- a/sysdep/unix/Makefile
+++ b/sysdep/unix/Makefile
@@ -1,4 +1,4 @@
-src := alloc.c io.c krt.c log.c main.c random.c
+src := alloc.c io.c io-loop.c krt.c log.c main.c random.c coroutine.c
obj := $(src-o-files)
$(all-daemon)
$(cf-local)
diff --git a/sysdep/unix/alloc.c b/sysdep/unix/alloc.c
index 5dd70c99..c09a8356 100644
--- a/sysdep/unix/alloc.c
+++ b/sysdep/unix/alloc.c
@@ -11,77 +11,73 @@
#include "lib/lists.h"
#include "lib/event.h"
+#include "sysdep/unix/io-loop.h"
+
#include <stdlib.h>
#include <unistd.h>
+#include <stdatomic.h>
+#include <errno.h>
#ifdef HAVE_MMAP
#include <sys/mman.h>
#endif
-#ifdef HAVE_MMAP
-#define KEEP_PAGES 512
+long page_size = 0;
-static u64 page_size = 0;
+#ifdef HAVE_MMAP
+#if DEBUGGING
+#define FP_NODE_OFFSET 42
+#else
+#define FP_NODE_OFFSET 1
+#endif
static _Bool use_fake = 0;
-
-uint pages_kept = 0;
-static list pages_list;
-
-static void cleanup_pages(void *data);
-static event page_cleanup_event = { .hook = cleanup_pages };
-
#else
-static const u64 page_size = 4096; /* Fake page size */
+static _Bool use_fake = 1;
#endif
-u64 get_page_size(void)
+static void *
+alloc_sys_page(void)
{
- if (page_size)
- return page_size;
+ void *ptr = mmap(NULL, page_size, PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
-#ifdef HAVE_MMAP
- if (page_size = sysconf(_SC_PAGESIZE))
- {
- if ((u64_popcount(page_size) > 1) || (page_size > 16384))
- {
- /* Too big or strange page, use the aligned allocator instead */
- page_size = 4096;
- use_fake = 1;
- }
- return page_size;
- }
+ if (ptr == MAP_FAILED)
+ bug("mmap(%lu) failed: %m", page_size);
- bug("Page size must be non-zero");
-#endif
+ return ptr;
}
+extern int shutting_down; /* Shutdown requested. */
+
void *
alloc_page(void)
{
#ifdef HAVE_MMAP
- if (pages_kept)
- {
- node *page = TAIL(pages_list);
- rem_node(page);
- pages_kept--;
- memset(page, 0, get_page_size());
- return page;
- }
-
if (!use_fake)
{
- void *ret = mmap(NULL, get_page_size(), PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- if (ret == MAP_FAILED)
- bug("mmap(%lu) failed: %m", page_size);
- return ret;
+ struct free_pages *fp = &birdloop_current->pages;
+ if (!fp->cnt)
+ return alloc_sys_page();
+
+ node *n = HEAD(fp->list);
+ rem_node(n);
+ if ((--fp->cnt < fp->min) && !shutting_down)
+ ev_send(fp->cleanup->list, fp->cleanup);
+
+ void *ptr = n - FP_NODE_OFFSET;
+ memset(ptr, 0, page_size);
+ return ptr;
}
else
#endif
{
+#ifdef HAVE_ALIGNED_ALLOC
void *ret = aligned_alloc(page_size, page_size);
if (!ret)
bug("aligned_alloc(%lu) failed", page_size);
return ret;
+#else
+ bug("BIRD should have already died on fatal error.");
+#endif
}
}
@@ -91,14 +87,14 @@ free_page(void *ptr)
#ifdef HAVE_MMAP
if (!use_fake)
{
- if (!pages_kept)
- init_list(&pages_list);
-
- memset(ptr, 0, sizeof(node));
- add_tail(&pages_list, ptr);
-
- if (++pages_kept > KEEP_PAGES)
- ev_schedule(&page_cleanup_event);
+ struct free_pages *fp = &birdloop_current->pages;
+ struct node *n = ptr;
+ n += FP_NODE_OFFSET;
+
+ memset(n, 0, sizeof(node));
+ add_tail(&fp->list, n);
+ if ((++fp->cnt > fp->max) && !shutting_down)
+ ev_send(fp->cleanup->list, fp->cleanup);
}
else
#endif
@@ -106,24 +102,147 @@ free_page(void *ptr)
}
#ifdef HAVE_MMAP
+
+#define GFP (&main_birdloop.pages)
+
+void
+flush_pages(struct birdloop *loop)
+{
+ ASSERT_DIE(birdloop_inside(loop->parent->loop));
+
+ struct free_pages *fp = &loop->pages;
+ struct free_pages *pfp = &loop->parent->loop->pages;
+
+ add_tail_list(&pfp->list, &fp->list);
+ pfp->cnt += fp->cnt;
+
+ fp->cnt = 0;
+ fp->list = (list) {};
+ fp->min = 0;
+ fp->max = 0;
+
+ rfree(fp->cleanup);
+ fp->cleanup = NULL;
+}
+
+static void
+cleanup_pages(void *data)
+{
+ struct birdloop *loop = data;
+ birdloop_enter(loop);
+
+ ASSERT_DIE(birdloop_inside(loop->parent->loop));
+
+ struct free_pages *fp = &loop->pages;
+ struct free_pages *pfp = &loop->parent->loop->pages;
+
+ while ((fp->cnt < fp->min) && (pfp->cnt > pfp->min))
+ {
+ node *n = HEAD(pfp->list);
+ rem_node(n);
+ add_tail(&fp->list, n);
+ fp->cnt++;
+ pfp->cnt--;
+ }
+
+ while (fp->cnt < fp->min)
+ {
+ node *n = alloc_sys_page();
+ add_tail(&fp->list, n + FP_NODE_OFFSET);
+ fp->cnt++;
+ }
+
+ while (fp->cnt > fp->max)
+ {
+ node *n = HEAD(fp->list);
+ rem_node(n);
+ add_tail(&pfp->list, n);
+ fp->cnt--;
+ pfp->cnt++;
+ }
+
+ birdloop_leave(loop);
+
+ if (!shutting_down && (pfp->cnt > pfp->max))
+ ev_send(pfp->cleanup->list, pfp->cleanup);
+}
+
static void
-cleanup_pages(void *data UNUSED)
+cleanup_global_pages(void *data UNUSED)
{
- for (uint seen = 0; (pages_kept > KEEP_PAGES) && (seen < KEEP_PAGES); seen++)
+ while (GFP->cnt < GFP->max)
{
- void *ptr = HEAD(pages_list);
- rem_node(ptr);
- if (munmap(ptr, get_page_size()) == 0)
- pages_kept--;
-#ifdef ENOMEM
+ node *n = alloc_sys_page();
+ add_tail(&GFP->list, n + FP_NODE_OFFSET);
+ GFP->cnt++;
+ }
+
+ for (uint limit = GFP->cnt; (limit > 0) && (GFP->cnt > GFP->max); limit--)
+ {
+ node *n = TAIL(GFP->list);
+ rem_node(n);
+
+ if (munmap(n - FP_NODE_OFFSET, page_size) == 0)
+ GFP->cnt--;
else if (errno == ENOMEM)
- add_tail(&pages_list, ptr);
-#endif
+ add_head(&GFP->list, n);
else
- bug("munmap(%p) failed: %m", ptr);
+ bug("munmap(%p) failed: %m", n - FP_NODE_OFFSET);
+ }
+}
+
+void
+init_pages(struct birdloop *loop)
+{
+ struct free_pages *fp = &loop->pages;
+
+ init_list(&fp->list);
+ fp->cleanup = ev_new_init(loop->parent->loop->pool, cleanup_pages, loop);
+ fp->cleanup->list = (loop->parent->loop == &main_birdloop) ? &global_work_list : birdloop_event_list(loop->parent->loop);
+ fp->min = 4;
+ fp->max = 16;
+
+ for (fp->cnt = 0; fp->cnt < fp->min; fp->cnt++)
+ {
+ node *n = alloc_sys_page();
+ add_tail(&fp->list, n + FP_NODE_OFFSET);
+ }
+}
+
+static event global_free_pages_cleanup_event = { .hook = cleanup_global_pages, .list = &global_work_list };
+
+void resource_sys_init(void)
+{
+ if (!(page_size = sysconf(_SC_PAGESIZE)))
+ die("System page size must be non-zero");
+
+ if (u64_popcount(page_size) == 1)
+ {
+ init_list(&GFP->list);
+ GFP->cleanup = &global_free_pages_cleanup_event;
+ GFP->min = 0;
+ GFP->max = 256;
+ return;
}
- if (pages_kept > KEEP_PAGES)
- ev_schedule(&page_cleanup_event);
+#ifdef HAVE_ALIGNED_ALLOC
+ log(L_WARN "Got strange memory page size (%lu), using the aligned allocator instead", page_size);
+#else
+ die("Got strange memory page size (%lu) and aligned_alloc is not available", page_size);
+#endif
+
+ /* Too big or strange page, use the aligned allocator instead */
+ page_size = 4096;
+ use_fake = 1;
}
+
+#else
+
+void
+resource_sys_init(void)
+{
+ page_size = 4096;
+ use_fake = 1;
+}
+
#endif
diff --git a/sysdep/unix/coroutine.c b/sysdep/unix/coroutine.c
new file mode 100644
index 00000000..4747d01a
--- /dev/null
+++ b/sysdep/unix/coroutine.c
@@ -0,0 +1,206 @@
+/*
+ * BIRD Coroutines
+ *
+ * (c) 2017 Martin Mares <mj@ucw.cz>
+ * (c) 2020 Maria Matejka <mq@jmq.cz>
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#undef LOCAL_DEBUG
+
+#undef DEBUG_LOCKING
+
+#include "lib/birdlib.h"
+#include "lib/locking.h"
+#include "lib/coro.h"
+#include "lib/rcu.h"
+#include "lib/resource.h"
+#include "lib/timer.h"
+
+#include "conf/conf.h"
+
+#define CORO_STACK_SIZE 65536
+
+/*
+ * Implementation of coroutines based on POSIX threads
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <stdatomic.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * Locking subsystem
+ */
+
+_Thread_local struct lock_order locking_stack = {};
+_Thread_local struct domain_generic **last_locked = NULL;
+
+#define ASSERT_NO_LOCK ASSERT_DIE(last_locked == NULL)
+
+struct domain_generic {
+ pthread_mutex_t mutex;
+ uint order;
+ struct domain_generic **prev;
+ struct lock_order *locked_by;
+ const char *name;
+};
+
+#define DOMAIN_INIT(_name, _order) { .mutex = PTHREAD_MUTEX_INITIALIZER, .name = _name, .order = _order }
+
+static struct domain_generic the_bird_domain_gen = DOMAIN_INIT("The BIRD", OFFSETOF(struct lock_order, the_bird));
+
+DOMAIN(the_bird) the_bird_domain = { .the_bird = &the_bird_domain_gen };
+
+struct domain_generic *
+domain_new(const char *name, uint order)
+{
+ ASSERT_DIE(order < sizeof(struct lock_order));
+ struct domain_generic *dg = xmalloc(sizeof(struct domain_generic));
+ *dg = (struct domain_generic) DOMAIN_INIT(name, order);
+ return dg;
+}
+
+void
+domain_free(struct domain_generic *dg)
+{
+ pthread_mutex_destroy(&dg->mutex);
+ xfree(dg);
+}
+
+uint dg_order(struct domain_generic *dg)
+{
+ return dg->order;
+}
+
+void do_lock(struct domain_generic *dg, struct domain_generic **lsp)
+{
+ if ((char *) lsp - (char *) &locking_stack != dg->order)
+ bug("Trying to lock on bad position: order=%u, lsp=%p, base=%p", dg->order, lsp, &locking_stack);
+
+ if (lsp <= last_locked)
+ bug("Trying to lock in a bad order");
+ if (*lsp)
+ bug("Inconsistent locking stack state on lock");
+
+ btime lock_begin = current_time();
+ pthread_mutex_lock(&dg->mutex);
+ btime duration = current_time() - lock_begin;
+ if (config && (duration > config->watchdog_warning))
+ log(L_WARN "Locking of %s took %d ms", dg->name, (int) (duration TO_MS));
+
+ if (dg->prev || dg->locked_by)
+ bug("Previous unlock not finished correctly");
+ dg->prev = last_locked;
+ *lsp = dg;
+ last_locked = lsp;
+ dg->locked_by = &locking_stack;
+}
+
+void do_unlock(struct domain_generic *dg, struct domain_generic **lsp)
+{
+ if ((char *) lsp - (char *) &locking_stack != dg->order)
+ bug("Trying to unlock on bad position: order=%u, lsp=%p, base=%p", dg->order, lsp, &locking_stack);
+
+ if (dg->locked_by != &locking_stack)
+ bug("Inconsistent domain state on unlock");
+ if ((last_locked != lsp) || (*lsp != dg))
+ bug("Inconsistent locking stack state on unlock");
+ dg->locked_by = NULL;
+ last_locked = dg->prev;
+ *lsp = NULL;
+ dg->prev = NULL;
+ pthread_mutex_unlock(&dg->mutex);
+}
+
+/* Coroutines */
+struct coroutine {
+ resource r;
+ pthread_t id;
+ pthread_attr_t attr;
+ struct rcu_coro rcu;
+ void (*entry)(void *);
+ void *data;
+};
+
+static _Thread_local _Bool coro_cleaned_up = 0;
+
+static void coro_free(resource *r)
+{
+ struct coroutine *c = (void *) r;
+ rcu_coro_stop(&c->rcu);
+ ASSERT_DIE(pthread_equal(pthread_self(), c->id));
+ pthread_attr_destroy(&c->attr);
+ coro_cleaned_up = 1;
+}
+
+static void coro_dump(resource *r UNUSED) { }
+
+static struct resclass coro_class = {
+ .name = "Coroutine",
+ .size = sizeof(struct coroutine),
+ .free = coro_free,
+ .dump = coro_dump,
+};
+
+_Thread_local struct coroutine *this_coro = NULL;
+
+static void *coro_entry(void *p)
+{
+ struct coroutine *c = p;
+
+ ASSERT_DIE(c->entry);
+
+ this_coro = c;
+ rcu_coro_start(&c->rcu);
+
+ c->entry(c->data);
+ ASSERT_DIE(coro_cleaned_up);
+
+ return NULL;
+}
+
+struct coroutine *coro_run(pool *p, void (*entry)(void *), void *data)
+{
+ ASSERT_DIE(entry);
+ ASSERT_DIE(p);
+
+ struct coroutine *c = ralloc(p, &coro_class);
+
+ c->entry = entry;
+ c->data = data;
+
+ int e = 0;
+
+ if (e = pthread_attr_init(&c->attr))
+ die("pthread_attr_init() failed: %M", e);
+
+ if (e = pthread_attr_setstacksize(&c->attr, CORO_STACK_SIZE))
+ die("pthread_attr_setstacksize(%u) failed: %M", CORO_STACK_SIZE, e);
+
+ if (e = pthread_attr_setdetachstate(&c->attr, PTHREAD_CREATE_DETACHED))
+ die("pthread_attr_setdetachstate(PTHREAD_CREATE_DETACHED) failed: %M", e);
+
+ if (e = pthread_create(&c->id, &c->attr, coro_entry, c))
+ die("pthread_create() failed: %M", e);
+
+ return c;
+}
+
+void
+coro_yield(void)
+{
+ const struct timespec req = { .tv_nsec = 100 };
+ nanosleep(&req, NULL);
+}
diff --git a/sysdep/unix/io-loop.c b/sysdep/unix/io-loop.c
new file mode 100644
index 00000000..9b107a1a
--- /dev/null
+++ b/sysdep/unix/io-loop.c
@@ -0,0 +1,620 @@
+/*
+ * BIRD -- I/O and event loop
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <pthread.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include "nest/bird.h"
+
+#include "lib/buffer.h"
+#include "lib/coro.h"
+#include "lib/lists.h"
+#include "lib/resource.h"
+#include "lib/event.h"
+#include "lib/timer.h"
+#include "lib/socket.h"
+
+#include "lib/io-loop.h"
+#include "sysdep/unix/io-loop.h"
+#include "conf/conf.h"
+
+/*
+ * Current thread context
+ */
+
+_Thread_local struct birdloop *birdloop_current = NULL;
+static _Thread_local struct birdloop *birdloop_wakeup_masked;
+static _Thread_local uint birdloop_wakeup_masked_count;
+
+event_list *
+birdloop_event_list(struct birdloop *loop)
+{
+ return &loop->event_list;
+}
+
+struct timeloop *
+birdloop_time_loop(struct birdloop *loop)
+{
+ return &loop->time;
+}
+
+pool *
+birdloop_pool(struct birdloop *loop)
+{
+ return loop->pool;
+}
+
+_Bool
+birdloop_inside(struct birdloop *loop)
+{
+ for (struct birdloop *c = birdloop_current; c; c = c->prev_loop)
+ if (loop == c)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * Wakeup code for birdloop
+ */
+
+static void
+pipe_new(int *pfds)
+{
+ int rv = pipe(pfds);
+ if (rv < 0)
+ die("pipe: %m");
+
+ if (fcntl(pfds[0], F_SETFL, O_NONBLOCK) < 0)
+ die("fcntl(O_NONBLOCK): %m");
+
+ if (fcntl(pfds[1], F_SETFL, O_NONBLOCK) < 0)
+ die("fcntl(O_NONBLOCK): %m");
+}
+
+void
+pipe_drain(int fd)
+{
+ char buf[64];
+ int rv;
+
+ try:
+ rv = read(fd, buf, 64);
+ if (rv < 0)
+ {
+ if (errno == EINTR)
+ goto try;
+ if (errno == EAGAIN)
+ return;
+ die("wakeup read: %m");
+ }
+ if (rv == 64)
+ goto try;
+}
+
+void
+pipe_kick(int fd)
+{
+ u64 v = 1;
+ int rv;
+
+ try:
+ rv = write(fd, &v, sizeof(u64));
+ if (rv < 0)
+ {
+ if (errno == EINTR)
+ goto try;
+ if (errno == EAGAIN)
+ return;
+ die("wakeup write: %m");
+ }
+}
+
+static inline void
+wakeup_init(struct birdloop *loop)
+{
+ pipe_new(loop->wakeup_fds);
+}
+
+static inline void
+wakeup_drain(struct birdloop *loop)
+{
+ pipe_drain(loop->wakeup_fds[0]);
+}
+
+static inline void
+wakeup_do_kick(struct birdloop *loop)
+{
+ pipe_kick(loop->wakeup_fds[1]);
+}
+
+void
+birdloop_ping(struct birdloop *loop)
+{
+ u32 ping_sent = atomic_fetch_add_explicit(&loop->ping_sent, 1, memory_order_acq_rel);
+ if (ping_sent)
+ return;
+
+ if (loop == birdloop_wakeup_masked)
+ birdloop_wakeup_masked_count++;
+ else
+ wakeup_do_kick(loop);
+}
+
+
+/*
+ * Sockets
+ */
+
+static void
+sockets_init(struct birdloop *loop)
+{
+ init_list(&loop->sock_list);
+ loop->sock_num = 0;
+
+ BUFFER_INIT(loop->poll_sk, loop->pool, 4);
+ BUFFER_INIT(loop->poll_fd, loop->pool, 4);
+ loop->poll_changed = 1; /* add wakeup fd */
+}
+
+static void
+sockets_add(struct birdloop *loop, sock *s)
+{
+ ASSERT_DIE(!enlisted(&s->n));
+
+ add_tail(&loop->sock_list, &s->n);
+ loop->sock_num++;
+
+ s->loop = loop;
+ s->index = -1;
+ loop->poll_changed = 1;
+
+ birdloop_ping(loop);
+}
+
+void
+sk_start(sock *s)
+{
+ ASSERT_DIE(birdloop_current != &main_birdloop);
+ sockets_add(birdloop_current, s);
+}
+
+static void
+sockets_remove(struct birdloop *loop, sock *s)
+{
+ ASSERT_DIE(s->loop == loop);
+
+ if (!enlisted(&s->n))
+ return;
+
+ rem_node(&s->n);
+ loop->sock_num--;
+
+ if (s->index >= 0)
+ {
+ loop->poll_sk.data[s->index] = NULL;
+ s->index = -1;
+ loop->poll_changed = 1;
+ birdloop_ping(loop);
+ }
+
+ s->loop = NULL;
+}
+
+void
+sk_stop(sock *s)
+{
+ sockets_remove(birdloop_current, s);
+}
+
+static inline uint sk_want_events(sock *s)
+{
+ uint out = ((s->ttx != s->tpos) ? POLLOUT : 0);
+ if (s->rx_hook)
+ if (s->cork)
+ {
+ LOCK_DOMAIN(cork, s->cork->lock);
+ if (!enlisted(&s->cork_node))
+ if (s->cork->count)
+ {
+// log(L_TRACE "Socket %p corked", s);
+ add_tail(&s->cork->sockets, &s->cork_node);
+ }
+ else
+ out |= POLLIN;
+ UNLOCK_DOMAIN(cork, s->cork->lock);
+ }
+ else
+ out |= POLLIN;
+
+// log(L_TRACE "sk_want_events(%p) = %x", s, out);
+ return out;
+}
+
+
+void
+sk_ping(sock *s)
+{
+ s->loop->poll_changed = 1;
+ birdloop_ping(s->loop);
+}
+
+/*
+FIXME: this should be called from sock code
+
+static void
+sockets_update(struct birdloop *loop, sock *s)
+{
+ if (s->index >= 0)
+ loop->poll_fd.data[s->index].events = sk_want_events(s);
+}
+*/
+
+static void
+sockets_prepare(struct birdloop *loop)
+{
+ BUFFER_SET(loop->poll_sk, loop->sock_num + 1);
+ BUFFER_SET(loop->poll_fd, loop->sock_num + 1);
+
+ struct pollfd *pfd = loop->poll_fd.data;
+ sock **psk = loop->poll_sk.data;
+ uint i = 0;
+ node *n;
+
+ WALK_LIST(n, loop->sock_list)
+ {
+ sock *s = SKIP_BACK(sock, n, n);
+
+ ASSERT(i < loop->sock_num);
+
+ s->index = i;
+ *psk = s;
+ pfd->fd = s->fd;
+ pfd->events = sk_want_events(s);
+ pfd->revents = 0;
+
+ pfd++;
+ psk++;
+ i++;
+ }
+
+ ASSERT(i == loop->sock_num);
+
+ /* Add internal wakeup fd */
+ *psk = NULL;
+ pfd->fd = loop->wakeup_fds[0];
+ pfd->events = POLLIN;
+ pfd->revents = 0;
+
+ loop->poll_changed = 0;
+}
+
+int sk_read(sock *s, int revents);
+int sk_write(sock *s);
+
+static void
+sockets_fire(struct birdloop *loop)
+{
+ struct pollfd *pfd = loop->poll_fd.data;
+ sock **psk = loop->poll_sk.data;
+ int poll_num = loop->poll_fd.used - 1;
+
+ times_update();
+
+ /* Last fd is internal wakeup fd */
+ if (pfd[poll_num].revents & POLLIN)
+ {
+ wakeup_drain(loop);
+ loop->poll_changed = 1;
+ }
+
+ int i;
+ for (i = 0; i < poll_num; pfd++, psk++, i++)
+ {
+ if (!*psk)
+ continue;
+
+ if (! pfd->revents)
+ continue;
+
+ if (pfd->revents & POLLNVAL)
+ bug("poll: invalid fd %d", pfd->fd);
+
+ int e = 1;
+
+ if (pfd->revents & POLLIN)
+ while (e && *psk && (*psk)->rx_hook)
+ e = sk_read(*psk, pfd->revents);
+
+ e = 1;
+ if (pfd->revents & POLLOUT)
+ {
+ loop->poll_changed = 1;
+ while (e && *psk)
+ e = sk_write(*psk);
+ }
+ }
+}
+
+
+/*
+ * Birdloop
+ */
+
+struct birdloop main_birdloop;
+
+static void birdloop_enter_locked(struct birdloop *loop);
+
+void
+birdloop_init(void)
+{
+ wakeup_init(&main_birdloop);
+
+ main_birdloop.time.domain = the_bird_domain.the_bird;
+ main_birdloop.time.loop = &main_birdloop;
+
+ times_update();
+ timers_init(&main_birdloop.time, &root_pool);
+
+ root_pool.loop = &main_birdloop;
+ main_birdloop.pool = &root_pool;
+
+ birdloop_enter_locked(&main_birdloop);
+}
+
+static void birdloop_main(void *arg);
+
+void
+birdloop_free(resource *r)
+{
+ struct birdloop *loop = (void *) r;
+
+ ASSERT_DIE(loop->links == 0);
+ domain_free(loop->time.domain);
+}
+
+void
+birdloop_dump(resource *r)
+{
+ struct birdloop *loop = (void *) r;
+
+ debug("%s\n", loop->pool->name);
+}
+
+struct resmem birdloop_memsize(resource *r)
+{
+ struct birdloop *loop = (void *) r;
+
+ return (struct resmem) {
+ .effective = sizeof(struct birdloop) - sizeof(resource) - ALLOC_OVERHEAD,
+ .overhead = ALLOC_OVERHEAD + sizeof(resource) + page_size * list_length(&loop->pages.list),
+ };
+}
+
+struct resclass birdloop_class = {
+ .name = "IO Loop",
+ .size = sizeof(struct birdloop),
+ .free = birdloop_free,
+ .dump = birdloop_dump,
+ .memsize = birdloop_memsize,
+};
+
+struct birdloop *
+birdloop_new(pool *pp, uint order, const char *name)
+{
+ struct domain_generic *dg = domain_new(name, order);
+
+ struct birdloop *loop = ralloc(pp, &birdloop_class);
+
+ loop->time.domain = dg;
+ loop->time.loop = loop;
+
+ birdloop_enter(loop);
+
+ loop->pool = rp_new(pp, loop, name);
+ loop->parent = pp;
+ rmove(&loop->r, loop->pool);
+
+ wakeup_init(loop);
+ ev_init_list(&loop->event_list, loop, name);
+ timers_init(&loop->time, loop->pool);
+ sockets_init(loop);
+
+ init_pages(loop);
+
+ loop->time.coro = coro_run(loop->pool, birdloop_main, loop);
+
+ birdloop_leave(loop);
+
+ return loop;
+}
+
+static void
+birdloop_do_stop(struct birdloop *loop, void (*stopped)(void *data), void *data)
+{
+ loop->stopped = stopped;
+ loop->stop_data = data;
+ wakeup_do_kick(loop);
+}
+
+void
+birdloop_stop(struct birdloop *loop, void (*stopped)(void *data), void *data)
+{
+ DG_LOCK(loop->time.domain);
+ birdloop_do_stop(loop, stopped, data);
+ DG_UNLOCK(loop->time.domain);
+}
+
+void
+birdloop_stop_self(struct birdloop *loop, void (*stopped)(void *data), void *data)
+{
+ ASSERT_DIE(loop == birdloop_current);
+ ASSERT_DIE(DG_IS_LOCKED(loop->time.domain));
+
+ birdloop_do_stop(loop, stopped, data);
+}
+
+static void
+birdloop_enter_locked(struct birdloop *loop)
+{
+ ASSERT_DIE(DG_IS_LOCKED(loop->time.domain));
+ ASSERT_DIE(!birdloop_inside(loop));
+
+ /* Store the old context */
+ loop->prev_loop = birdloop_current;
+
+ /* Put the new context */
+ birdloop_current = loop;
+}
+
+void
+birdloop_enter(struct birdloop *loop)
+{
+ DG_LOCK(loop->time.domain);
+ return birdloop_enter_locked(loop);
+}
+
+static void
+birdloop_leave_locked(struct birdloop *loop)
+{
+ /* Check the current context */
+ ASSERT_DIE(birdloop_current == loop);
+
+ /* Restore the old context */
+ birdloop_current = loop->prev_loop;
+}
+
+void
+birdloop_leave(struct birdloop *loop)
+{
+ birdloop_leave_locked(loop);
+ DG_UNLOCK(loop->time.domain);
+}
+
+void
+birdloop_mask_wakeups(struct birdloop *loop)
+{
+ ASSERT_DIE(birdloop_wakeup_masked == NULL);
+ birdloop_wakeup_masked = loop;
+}
+
+void
+birdloop_unmask_wakeups(struct birdloop *loop)
+{
+ ASSERT_DIE(birdloop_wakeup_masked == loop);
+ birdloop_wakeup_masked = NULL;
+ if (birdloop_wakeup_masked_count)
+ wakeup_do_kick(loop);
+
+ birdloop_wakeup_masked_count = 0;
+}
+
+void
+birdloop_link(struct birdloop *loop)
+{
+ ASSERT_DIE(birdloop_inside(loop));
+ loop->links++;
+}
+
+void
+birdloop_unlink(struct birdloop *loop)
+{
+ ASSERT_DIE(birdloop_inside(loop));
+ ASSERT_DIE(loop->links);
+ if (!--loop->links)
+ birdloop_ping(loop);
+}
+
+static void
+birdloop_main(void *arg)
+{
+ struct birdloop *loop = arg;
+ timer *t;
+ int rv, timeout;
+
+ btime loop_begin = current_time();
+
+ birdloop_enter(loop);
+ while (1)
+ {
+ timers_fire(&loop->time, 0);
+ if (ev_run_list(&loop->event_list))
+ timeout = 0;
+ else if (t = timers_first(&loop->time))
+ timeout = (tm_remains(t) TO_MS) + 1;
+ else
+ timeout = -1;
+
+ if (loop->poll_changed)
+ sockets_prepare(loop);
+
+ btime duration = current_time_update() - loop_begin;
+ if (duration > config->watchdog_warning)
+ log(L_WARN "I/O loop cycle took %d ms", (int) (duration TO_MS));
+
+ birdloop_leave(loop);
+
+ try:
+ rv = poll(loop->poll_fd.data, loop->poll_fd.used, timeout);
+ if (rv < 0)
+ {
+ if (errno == EINTR || errno == EAGAIN)
+ goto try;
+ die("poll: %m");
+ }
+
+ birdloop_enter(loop);
+
+ if (loop->stopped && !loop->links)
+ break;
+
+ loop_begin = current_time_update();
+
+ if (rv)
+ sockets_fire(loop);
+
+ atomic_exchange_explicit(&loop->ping_sent, 0, memory_order_acq_rel);
+ }
+
+ /* Flush remaining events */
+ ASSERT_DIE(!ev_run_list(&loop->event_list));
+
+ /* No timers allowed */
+ ASSERT_DIE(timers_count(&loop->time) == 0);
+ ASSERT_DIE(EMPTY_LIST(loop->sock_list));
+ ASSERT_DIE(loop->sock_num == 0);
+
+ birdloop_leave(loop);
+
+ /* Lock parent loop */
+ pool *parent = loop->parent;
+ birdloop_enter(parent->loop);
+
+ /* Move the loop temporarily to parent pool */
+ birdloop_enter(loop);
+ rmove(&loop->r, parent);
+ birdloop_leave(loop);
+
+ /* Announce loop stop */
+ loop->stopped(loop->stop_data);
+
+ /* Free the pool and loop */
+ birdloop_enter(loop);
+ rp_free(loop->pool, parent);
+ flush_pages(loop);
+ birdloop_leave(loop);
+ rfree(&loop->r);
+
+ /* And finally leave the parent loop before finishing */
+ birdloop_leave(parent->loop);
+}
diff --git a/sysdep/unix/io-loop.h b/sysdep/unix/io-loop.h
new file mode 100644
index 00000000..1727637a
--- /dev/null
+++ b/sysdep/unix/io-loop.h
@@ -0,0 +1,58 @@
+/*
+ * BIRD -- I/O and event loop
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_SYSDEP_UNIX_IO_LOOP_H_
+#define _BIRD_SYSDEP_UNIX_IO_LOOP_H_
+
+#include "nest/bird.h"
+
+#include "lib/lists.h"
+#include "lib/event.h"
+#include "lib/timer.h"
+
+struct free_pages
+{
+ list list; /* List of empty pages */
+ event *cleanup; /* Event to call when number of pages is outside bounds */
+ u16 min, max; /* Minimal and maximal number of free pages kept */
+ uint cnt; /* Number of empty pages */
+};
+
+struct birdloop
+{
+ resource r;
+
+ pool *pool;
+ pool *parent;
+
+ struct timeloop time;
+ event_list event_list;
+ list sock_list;
+ uint sock_num;
+
+ BUFFER(sock *) poll_sk;
+ BUFFER(struct pollfd) poll_fd;
+ u8 poll_changed;
+
+ _Atomic u32 ping_sent;
+ int wakeup_fds[2];
+
+ uint links;
+
+ struct free_pages pages;
+
+ void (*stopped)(void *data);
+ void *stop_data;
+
+ struct birdloop *prev_loop;
+};
+
+extern _Thread_local struct birdloop *birdloop_current;
+
+void init_pages(struct birdloop *loop);
+void flush_pages(struct birdloop *loop);
+
+#endif
diff --git a/sysdep/unix/io.c b/sysdep/unix/io.c
index 3d67d0a7..e3e388af 100644
--- a/sysdep/unix/io.c
+++ b/sysdep/unix/io.c
@@ -36,12 +36,14 @@
#include "lib/resource.h"
#include "lib/socket.h"
#include "lib/event.h"
+#include "lib/locking.h"
#include "lib/timer.h"
#include "lib/string.h"
#include "nest/iface.h"
#include "conf/conf.h"
#include "sysdep/unix/unix.h"
+#include "sysdep/unix/io-loop.h"
#include CONFIG_INCLUDE_SYSIO_H
/* Maximum number of calls of tx handler for one socket in one
@@ -122,55 +124,50 @@ rf_fileno(struct rfile *f)
btime boot_time;
+
void
-times_init(struct timeloop *loop)
+times_update(void)
{
struct timespec ts;
int rv;
+ btime old_time = current_time();
+ btime old_real_time = current_real_time();
+
rv = clock_gettime(CLOCK_MONOTONIC, &ts);
if (rv < 0)
die("Monotonic clock is missing");
if ((ts.tv_sec < 0) || (((u64) ts.tv_sec) > ((u64) 1 << 40)))
log(L_WARN "Monotonic clock is crazy");
-
- loop->last_time = ts.tv_sec S + ts.tv_nsec NS;
- loop->real_time = 0;
-}
-
-void
-times_update(struct timeloop *loop)
-{
- struct timespec ts;
- int rv;
-
- rv = clock_gettime(CLOCK_MONOTONIC, &ts);
- if (rv < 0)
- die("clock_gettime: %m");
-
+
btime new_time = ts.tv_sec S + ts.tv_nsec NS;
- if (new_time < loop->last_time)
+ if (new_time < old_time)
log(L_ERR "Monotonic clock is broken");
- loop->last_time = new_time;
- loop->real_time = 0;
-}
-
-void
-times_update_real_time(struct timeloop *loop)
-{
- struct timespec ts;
- int rv;
-
rv = clock_gettime(CLOCK_REALTIME, &ts);
if (rv < 0)
die("clock_gettime: %m");
- loop->real_time = ts.tv_sec S + ts.tv_nsec NS;
-}
+ btime new_real_time = ts.tv_sec S + ts.tv_nsec NS;
+
+ if (!atomic_compare_exchange_strong_explicit(
+ &last_time,
+ &old_time,
+ new_time,
+ memory_order_acq_rel,
+ memory_order_relaxed))
+ DBG("Time update collision: last_time");
+ if (!atomic_compare_exchange_strong_explicit(
+ &real_time,
+ &old_real_time,
+ new_real_time,
+ memory_order_acq_rel,
+ memory_order_relaxed))
+ DBG("Time update collision: real_time");
+}
/**
* DOC: Sockets
@@ -797,6 +794,7 @@ sk_free(resource *r)
{
sock *s = (sock *) r;
+ ASSERT_DIE(!s->loop || birdloop_inside(s->loop));
sk_free_bufs(s);
#ifdef HAVE_LIBSSH
@@ -804,20 +802,30 @@ sk_free(resource *r)
sk_ssh_free(s);
#endif
- if (s->fd < 0)
- return;
+ if (s->cork)
+ {
+ LOCK_DOMAIN(cork, s->cork->lock);
+ if (enlisted(&s->cork_node))
+ rem_node(&s->cork_node);
+ UNLOCK_DOMAIN(cork, s->cork->lock);
+ }
- /* FIXME: we should call sk_stop() for SKF_THREAD sockets */
- if (!(s->flags & SKF_THREAD))
+ if (!s->loop)
+ ;
+ else if (s->flags & SKF_THREAD)
+ sk_stop(s);
+ else
{
if (s == current_sock)
current_sock = sk_next(s);
if (s == stored_sock)
stored_sock = sk_next(s);
- rem_node(&s->n);
+
+ if (enlisted(&s->n))
+ rem_node(&s->n);
}
- if (s->type != SK_SSH && s->type != SK_SSH_ACTIVE)
+ if (s->type != SK_SSH && s->type != SK_SSH_ACTIVE && s->fd != -1)
close(s->fd);
s->fd = -1;
@@ -1108,7 +1116,15 @@ sk_passive_connected(sock *s, int type)
return 1;
}
- sk_insert(t);
+ if (s->flags & SKF_PASSIVE_THREAD)
+ t->flags |= SKF_THREAD;
+ else
+ {
+ ASSERT_DIE(s->loop == &main_birdloop);
+ t->loop = &main_birdloop;
+ sk_insert(t);
+ }
+
sk_alloc_bufs(t);
s->rx_hook(t, 0);
return 1;
@@ -1329,6 +1345,17 @@ sk_open(sock *s)
ip_addr bind_addr = IPA_NONE;
sockaddr sa;
+ if (s->flags & SKF_THREAD)
+ {
+ ASSERT_DIE(s->loop && (s->loop != &main_birdloop));
+ ASSERT_DIE(birdloop_inside(s->loop));
+ }
+ else
+ {
+ ASSERT_DIE(!s->loop);
+ s->loop = &main_birdloop;
+ }
+
if (s->type <= SK_IP)
{
/*
@@ -1508,10 +1535,41 @@ sk_open_unix(sock *s, char *name)
return -1;
s->fd = fd;
+ s->loop = &main_birdloop;
sk_insert(s);
return 0;
}
+static void
+sk_reloop_hook(void *_vs)
+{
+ sock *s = _vs;
+ if (birdloop_inside(&main_birdloop))
+ {
+ s->flags &= ~SKF_THREAD;
+ sk_insert(s);
+ }
+ else
+ {
+ s->flags |= SKF_THREAD;
+ sk_start(s);
+ }
+}
+
+void
+sk_reloop(sock *s, struct birdloop *loop)
+{
+ if (enlisted(&s->n))
+ rem_node(&s->n);
+
+ s->reloop = (event) {
+ .hook = sk_reloop_hook,
+ .data = s,
+ };
+
+ ev_send_loop(loop, &s->reloop);
+}
+
#define CMSG_RX_SPACE MAX(CMSG4_SPACE_PKTINFO+CMSG4_SPACE_TTL, \
CMSG6_SPACE_PKTINFO+CMSG6_SPACE_TTL)
@@ -1657,6 +1715,7 @@ sk_maybe_write(sock *s)
s->err_hook(s, (errno != EPIPE) ? errno : 0);
return -1;
}
+ sk_ping(s);
return 0;
}
s->ttx += e;
@@ -1864,6 +1923,25 @@ sk_read(sock *s, int revents)
case SK_TCP:
case SK_UNIX:
{
+ if (s->cork)
+ {
+ int cont = 0;
+ LOCK_DOMAIN(cork, s->cork->lock);
+ if (!enlisted(&s->cork_node))
+ if (s->cork->count)
+ {
+// log(L_TRACE "Socket %p corked", s);
+ add_tail(&s->cork->sockets, &s->cork_node);
+ sk_ping(s);
+ }
+ else
+ cont = 1;
+ UNLOCK_DOMAIN(cork, s->cork->lock);
+
+ if (!cont)
+ return 0;
+ }
+
int c = read(s->fd, s->rpos, s->rbuf + s->rbsize - s->rpos);
if (c < 0)
@@ -2016,30 +2094,17 @@ struct event_log_entry
static struct event_log_entry event_log[EVENT_LOG_LENGTH];
static struct event_log_entry *event_open;
static int event_log_pos, event_log_num, watchdog_active;
-static btime last_time;
+static btime last_io_time;
static btime loop_time;
static void
io_update_time(void)
{
- struct timespec ts;
- int rv;
-
- /*
- * This is third time-tracking procedure (after update_times() above and
- * times_update() in BFD), dedicated to internal event log and latency
- * tracking. Hopefully, we consolidate these sometimes.
- */
-
- rv = clock_gettime(CLOCK_MONOTONIC, &ts);
- if (rv < 0)
- die("clock_gettime: %m");
-
- last_time = ts.tv_sec S + ts.tv_nsec NS;
+ last_io_time = current_time_update();
if (event_open)
{
- event_open->duration = last_time - event_open->timestamp;
+ event_open->duration = last_io_time - event_open->timestamp;
if (event_open->duration > config->latency_limit)
log(L_WARN "Event 0x%p 0x%p took %d ms",
@@ -2068,7 +2133,7 @@ io_log_event(void *hook, void *data)
en->hook = hook;
en->data = data;
- en->timestamp = last_time;
+ en->timestamp = last_io_time;
en->duration = 0;
event_log_num++;
@@ -2096,14 +2161,14 @@ io_log_dump(void)
struct event_log_entry *en = event_log + (event_log_pos + i) % EVENT_LOG_LENGTH;
if (en->hook)
log(L_DEBUG " Event 0x%p 0x%p at %8d for %d ms", en->hook, en->data,
- (int) ((last_time - en->timestamp) TO_MS), (int) (en->duration TO_MS));
+ (int) ((last_io_time - en->timestamp) TO_MS), (int) (en->duration TO_MS));
}
}
void
watchdog_sigalrm(int sig UNUSED)
{
- /* Update last_time and duration, but skip latency check */
+ /* Update last_io_time and duration, but skip latency check */
config->latency_limit = 0xffffffff;
io_update_time();
@@ -2116,7 +2181,7 @@ watchdog_start1(void)
{
io_update_time();
- loop_time = last_time;
+ loop_time = last_io_time;
}
static inline void
@@ -2124,7 +2189,7 @@ watchdog_start(void)
{
io_update_time();
- loop_time = last_time;
+ loop_time = last_io_time;
event_log_num = 0;
if (config->watchdog_timeout)
@@ -2145,7 +2210,7 @@ watchdog_stop(void)
watchdog_active = 0;
}
- btime duration = last_time - loop_time;
+ btime duration = last_io_time - loop_time;
if (duration > config->watchdog_warning)
log(L_WARN "I/O loop cycle took %d ms for %d events",
(int) (duration TO_MS), event_log_num);
@@ -2160,8 +2225,9 @@ void
io_init(void)
{
init_list(&sock_list);
- init_list(&global_event_list);
- init_list(&global_work_list);
+ ev_init_list(&global_event_list, &main_birdloop, "Global event list");
+ ev_init_list(&global_work_list, &main_birdloop, "Global work list");
+ ev_init_list(&main_birdloop.event_list, &main_birdloop, "Global fast event list");
krt_io_init();
// XXX init_times();
// XXX update_times();
@@ -2175,11 +2241,15 @@ static int short_loops = 0;
#define SHORT_LOOP_MAX 10
#define WORK_EVENTS_MAX 10
+void pipe_drain(int fd);
+void check_stored_pages(void);
+
void
io_loop(void)
{
int poll_tout, timeout;
int nfds, events, pout;
+ int reload_requested = 0;
timer *t;
sock *s;
node *n;
@@ -2189,27 +2259,39 @@ io_loop(void)
watchdog_start1();
for(;;)
{
- times_update(&main_timeloop);
+ times_update();
events = ev_run_list(&global_event_list);
events = ev_run_list_limited(&global_work_list, WORK_EVENTS_MAX) || events;
- timers_fire(&main_timeloop);
+ events = ev_run_list(&main_birdloop.event_list) || events;
+ timers_fire(&main_birdloop.time, 1);
io_close_event();
+#if DEBUGGING
+#define PERIODIC_WAKEUP 86400000
+#else
+#define PERIODIC_WAKEUP 3000
+#endif
+restart_poll:
// FIXME
- poll_tout = (events ? 0 : 3000); /* Time in milliseconds */
- if (t = timers_first(&main_timeloop))
+ poll_tout = ((reload_requested || events) ? 0 : PERIODIC_WAKEUP); /* Time in milliseconds */
+ if (t = timers_first(&main_birdloop.time))
{
- times_update(&main_timeloop);
+ times_update();
timeout = (tm_remains(t) TO_MS) + 1;
poll_tout = MIN(poll_tout, timeout);
}
- nfds = 0;
+ /* A hack to reload main io_loop() when something has changed asynchronously. */
+ pfd[0].fd = main_birdloop.wakeup_fds[0];
+ pfd[0].events = POLLIN;
+
+ nfds = 1;
+
WALK_LIST(n, sock_list)
{
pfd[nfds] = (struct pollfd) { .fd = -1 }; /* everything other set to 0 by this */
s = SKIP_BACK(sock, n, n);
- if (s->rx_hook)
+ if (s->rx_hook && !ev_corked(s->cork))
{
pfd[nfds].fd = s->fd;
pfd[nfds].events |= POLLIN;
@@ -2263,7 +2345,9 @@ io_loop(void)
/* And finally enter poll() to find active sockets */
watchdog_stop();
+ birdloop_leave(&main_birdloop);
pout = poll(pfd, nfds, poll_tout);
+ birdloop_enter(&main_birdloop);
watchdog_start();
if (pout < 0)
@@ -2272,9 +2356,24 @@ io_loop(void)
continue;
die("poll: %m");
}
+
+ if (pout && (pfd[0].revents & POLLIN))
+ {
+ /* IO loop reload requested */
+ pipe_drain(main_birdloop.wakeup_fds[0]);
+ reload_requested = 1;
+ goto restart_poll;
+ }
+
+ if (reload_requested)
+ {
+ reload_requested = 0;
+ atomic_exchange_explicit(&main_birdloop.ping_sent, 0, memory_order_acq_rel);
+ }
+
if (pout)
{
- times_update(&main_timeloop);
+ times_update();
/* guaranteed to be non-empty */
current_sock = SKIP_BACK(sock, n, HEAD(sock_list));
diff --git a/sysdep/unix/krt.c b/sysdep/unix/krt.c
index 7c2614b1..0cb86213 100644
--- a/sysdep/unix/krt.c
+++ b/sysdep/unix/krt.c
@@ -74,7 +74,7 @@ static list krt_proto_list;
void
krt_io_init(void)
{
- krt_pool = rp_new(&root_pool, "Kernel Syncer");
+ krt_pool = rp_new(&root_pool, &main_birdloop, "Kernel Syncer");
krt_filter_lp = lp_new_default(krt_pool);
init_list(&krt_proto_list);
krt_sys_io_init();
@@ -251,14 +251,14 @@ static inline void
krt_trace_in(struct krt_proto *p, rte *e, char *msg)
{
if (p->p.debug & D_PACKETS)
- log(L_TRACE "%s: %N: %s", p->p.name, e->net->n.addr, msg);
+ log(L_TRACE "%s: %N: %s", p->p.name, e->net, msg);
}
static inline void
krt_trace_in_rl(struct tbf *f, struct krt_proto *p, rte *e, char *msg)
{
if (p->p.debug & D_PACKETS)
- log_rl(f, L_TRACE "%s: %N: %s", p->p.name, e->net->n.addr, msg);
+ log_rl(f, L_TRACE "%s: %N: %s", p->p.name, e->net, msg);
}
/*
@@ -277,261 +277,33 @@ static struct tbf rl_alien = TBF_DEFAULT_LOG_LIMITS;
* the same key.
*/
-static inline int
-krt_same_key(rte *a, rte *b)
+static inline u32
+krt_metric(rte *a)
{
- return a->u.krt.metric == b->u.krt.metric;
+ eattr *ea = ea_find(a->attrs->eattrs, EA_KRT_METRIC);
+ return ea ? ea->u.data : 0;
}
static inline int
-krt_uptodate(rte *a, rte *b)
-{
- if (a->attrs != b->attrs)
- return 0;
-
- if (a->u.krt.proto != b->u.krt.proto)
- return 0;
-
- return 1;
-}
-
-static void
-krt_learn_announce_update(struct krt_proto *p, rte *e)
-{
- net *n = e->net;
- rta *aa = rta_clone(e->attrs);
- rte *ee = rte_get_temp(aa);
- ee->pflags = EA_ID_FLAG(EA_KRT_SOURCE) | EA_ID_FLAG(EA_KRT_METRIC);
- ee->u.krt = e->u.krt;
- rte_update(&p->p, n->n.addr, ee);
-}
-
-static void
-krt_learn_announce_delete(struct krt_proto *p, net *n)
+krt_rte_better(rte *a, rte *b)
{
- rte_update(&p->p, n->n.addr, NULL);
+ return (krt_metric(a) > krt_metric(b));
}
/* Called when alien route is discovered during scan */
static void
-krt_learn_scan(struct krt_proto *p, rte *e)
-{
- net *n0 = e->net;
- net *n = net_get(p->krt_table, n0->n.addr);
- rte *m, **mm;
-
- e->attrs = rta_lookup(e->attrs);
-
- for(mm=&n->routes; m = *mm; mm=&m->next)
- if (krt_same_key(m, e))
- break;
- if (m)
- {
- if (krt_uptodate(m, e))
- {
- krt_trace_in_rl(&rl_alien, p, e, "[alien] seen");
- rte_free(e);
- m->u.krt.seen = 1;
- }
- else
- {
- krt_trace_in(p, e, "[alien] updated");
- *mm = m->next;
- rte_free(m);
- m = NULL;
- }
- }
- else
- krt_trace_in(p, e, "[alien] created");
- if (!m)
- {
- e->next = n->routes;
- n->routes = e;
- e->u.krt.seen = 1;
- }
-}
-
-static void
-krt_learn_prune(struct krt_proto *p)
-{
- struct fib *fib = &p->krt_table->fib;
- struct fib_iterator fit;
-
- KRT_TRACE(p, D_EVENTS, "Pruning inherited routes");
-
- FIB_ITERATE_INIT(&fit, fib);
-again:
- FIB_ITERATE_START(fib, &fit, net, n)
- {
- rte *e, **ee, *best, **pbest, *old_best;
-
- /*
- * Note that old_best may be NULL even if there was an old best route in
- * the previous step, because it might be replaced in krt_learn_scan().
- * But in that case there is a new valid best route.
- */
-
- old_best = NULL;
- best = NULL;
- pbest = NULL;
- ee = &n->routes;
- while (e = *ee)
- {
- if (e->u.krt.best)
- old_best = e;
-
- if (!e->u.krt.seen)
- {
- *ee = e->next;
- rte_free(e);
- continue;
- }
-
- if (!best || best->u.krt.metric > e->u.krt.metric)
- {
- best = e;
- pbest = ee;
- }
-
- e->u.krt.seen = 0;
- e->u.krt.best = 0;
- ee = &e->next;
- }
- if (!n->routes)
- {
- DBG("%I/%d: deleting\n", n->n.prefix, n->n.pxlen);
- if (old_best)
- krt_learn_announce_delete(p, n);
-
- FIB_ITERATE_PUT(&fit);
- fib_delete(fib, n);
- goto again;
- }
-
- best->u.krt.best = 1;
- *pbest = best->next;
- best->next = n->routes;
- n->routes = best;
-
- if ((best != old_best) || p->reload)
- {
- DBG("%I/%d: announcing (metric=%d)\n", n->n.prefix, n->n.pxlen, best->u.krt.metric);
- krt_learn_announce_update(p, best);
- }
- else
- DBG("%I/%d: uptodate (metric=%d)\n", n->n.prefix, n->n.pxlen, best->u.krt.metric);
- }
- FIB_ITERATE_END;
-
- p->reload = 0;
-}
-
-static void
-krt_learn_async(struct krt_proto *p, rte *e, int new)
+krt_learn_rte(struct krt_proto *p, rte *e)
{
- net *n0 = e->net;
- net *n = net_get(p->krt_table, n0->n.addr);
- rte *g, **gg, *best, **bestp, *old_best;
-
- e->attrs = rta_lookup(e->attrs);
-
- old_best = n->routes;
- for(gg=&n->routes; g = *gg; gg = &g->next)
- if (krt_same_key(g, e))
- break;
- if (new)
- {
- if (g)
- {
- if (krt_uptodate(g, e))
- {
- krt_trace_in(p, e, "[alien async] same");
- rte_free(e);
- return;
- }
- krt_trace_in(p, e, "[alien async] updated");
- *gg = g->next;
- rte_free(g);
- }
- else
- krt_trace_in(p, e, "[alien async] created");
-
- e->next = n->routes;
- n->routes = e;
- }
- else if (!g)
- {
- krt_trace_in(p, e, "[alien async] delete failed");
- rte_free(e);
- return;
- }
- else
- {
- krt_trace_in(p, e, "[alien async] removed");
- *gg = g->next;
- rte_free(e);
- rte_free(g);
- }
- best = n->routes;
- bestp = &n->routes;
- for(gg=&n->routes; g=*gg; gg=&g->next)
- {
- if (best->u.krt.metric > g->u.krt.metric)
- {
- best = g;
- bestp = gg;
- }
-
- g->u.krt.best = 0;
- }
-
- if (best)
- {
- best->u.krt.best = 1;
- *bestp = best->next;
- best->next = n->routes;
- n->routes = best;
- }
-
- if (best != old_best)
- {
- DBG("krt_learn_async: distributing change\n");
- if (best)
- krt_learn_announce_update(p, best);
- else
- krt_learn_announce_delete(p, n);
- }
+ struct rte_src *src = e->src = rt_get_source(&p->p, krt_metric(e));
+ rte_update(p->p.main_channel, e->net, e, e->src);
+ rt_unlock_source(src);
}
static void
krt_learn_init(struct krt_proto *p)
{
if (KRT_CF->learn)
- {
- struct rtable_config *cf = mb_allocz(p->p.pool, sizeof(struct rtable_config));
- cf->name = "Inherited";
- cf->addr_type = p->p.net_type;
- cf->internal = 1;
-
- p->krt_table = rt_setup(p->p.pool, cf);
- }
-}
-
-static void
-krt_dump(struct proto *P)
-{
- struct krt_proto *p = (struct krt_proto *) P;
-
- if (!KRT_CF->learn)
- return;
- debug("KRT: Table of inheritable routes\n");
- rt_dump(p->krt_table);
-}
-
-static void
-krt_dump_attrs(rte *e)
-{
- debug(" [m=%d,p=%d]", e->u.krt.metric, e->u.krt.proto);
+ channel_setup_in_table(p->p.main_channel, 1);
}
#endif
@@ -543,47 +315,58 @@ krt_dump_attrs(rte *e)
static inline int
krt_is_installed(struct krt_proto *p, net *n)
{
- return n->routes && bmap_test(&p->p.main_channel->export_map, n->routes->id);
+ return n->routes && bmap_test(&p->p.main_channel->export_map, n->routes->rte.id);
}
-static void
-krt_flush_routes(struct krt_proto *p)
+static uint
+rte_feed_count(net *n)
{
- struct rtable *t = p->p.main_channel->table;
+ uint count = 0;
+ for (struct rte_storage *e = n->routes; e; e = e->next)
+ if (rte_is_valid(RTES_OR_NULL(e)))
+ count++;
+ return count;
+}
- KRT_TRACE(p, D_EVENTS, "Flushing kernel routes");
- FIB_WALK(&t->fib, net, n)
+static void
+rte_feed_obtain(net *n, rte **feed, uint count)
+{
+ uint i = 0;
+ for (struct rte_storage *e = n->routes; e; e = e->next)
+ if (rte_is_valid(RTES_OR_NULL(e)))
{
- if (krt_is_installed(p, n))
- {
- /* FIXME: this does not work if gw is changed in export filter */
- krt_replace_rte(p, n, NULL, n->routes);
- }
+ ASSERT_DIE(i < count);
+ feed[i++] = &e->rte;
}
- FIB_WALK_END;
+ ASSERT_DIE(i == count);
}
static struct rte *
-krt_export_net(struct krt_proto *p, net *net, rte **rt_free)
+krt_export_net(struct krt_proto *p, net *net)
{
struct channel *c = p->p.main_channel;
const struct filter *filter = c->out_filter;
- rte *rt;
if (c->ra_mode == RA_MERGED)
- return rt_export_merged(c, net, rt_free, krt_filter_lp, 1);
+ {
+ uint count = rte_feed_count(net);
+ if (!count)
+ return NULL;
- rt = net->routes;
- *rt_free = NULL;
+ rte **feed = alloca(count * sizeof(rte *));
+ rte_feed_obtain(net, feed, count);
+ return rt_export_merged(c, feed, count, krt_filter_lp, 1);
+ }
- if (!rte_is_valid(rt))
+ static _Thread_local rte rt;
+ rt = net->routes->rte;
+
+ if (!rte_is_valid(&rt))
return NULL;
if (filter == FILTER_REJECT)
return NULL;
- rte_make_tmp_attrs(&rt, krt_filter_lp, NULL);
-
/* We could run krt_preexport() here, but it is already handled by krt_is_installed() */
if (filter == FILTER_ACCEPT)
@@ -594,13 +377,9 @@ krt_export_net(struct krt_proto *p, net *net, rte **rt_free)
accept:
- if (rt != net->routes)
- *rt_free = rt;
- return rt;
+ return &rt;
reject:
- if (rt != net->routes)
- rte_free(rt);
return NULL;
}
@@ -624,13 +403,13 @@ krt_same_dest(rte *k, rte *e)
*/
void
-krt_got_route(struct krt_proto *p, rte *e)
+krt_got_route(struct krt_proto *p, rte *e, s8 src)
{
- rte *new = NULL, *rt_free = NULL;
- net *n = e->net;
+ rte *new = NULL;
+ e->pflags = 0;
#ifdef KRT_ALLOW_LEARN
- switch (e->u.krt.src)
+ switch (src)
{
case KRT_SRC_KERNEL:
goto ignore;
@@ -640,26 +419,28 @@ krt_got_route(struct krt_proto *p, rte *e)
case KRT_SRC_ALIEN:
if (KRT_CF->learn)
- krt_learn_scan(p, e);
+ krt_learn_rte(p, e);
else
- {
- krt_trace_in_rl(&rl_alien, p, e, "[alien] ignored");
- rte_free(e);
- }
+ krt_trace_in_rl(&rl_alien, p, e, "[alien] ignored");
return;
}
#endif
/* The rest is for KRT_SRC_BIRD (or KRT_SRC_UNKNOWN) */
+ RT_LOCK(p->p.main_channel->table);
+ /* Deleting all routes if flush is requested */
+ if (p->flush_routes)
+ goto delete;
/* We wait for the initial feed to have correct installed state */
if (!p->ready)
goto ignore;
- if (!krt_is_installed(p, n))
+ net *net = net_find(RT_PRIV(p->p.main_channel->table), e->net);
+ if (!net || !krt_is_installed(p, net))
goto delete;
- new = krt_export_net(p, n, &rt_free);
+ new = krt_export_net(p, net);
/* Rejected by filters */
if (!new)
@@ -692,20 +473,16 @@ ignore:
update:
krt_trace_in(p, new, "updating");
- krt_replace_rte(p, n, new, e);
+ krt_replace_rte(p, e->net, new, e);
goto done;
delete:
krt_trace_in(p, e, "deleting");
- krt_replace_rte(p, n, NULL, e);
+ krt_replace_rte(p, e->net, NULL, e);
goto done;
done:
- rte_free(e);
-
- if (rt_free)
- rte_free(rt_free);
-
+ RT_UNLOCK(p->p.main_channel->table);
lp_flush(krt_filter_lp);
}
@@ -713,50 +490,65 @@ static void
krt_init_scan(struct krt_proto *p)
{
bmap_reset(&p->seen_map, 1024);
+
+#ifdef KRT_ALLOW_LEARN
+ if (KRT_CF->learn)
+ channel_refresh_begin(p->p.main_channel);
+#endif
}
static void
krt_prune(struct krt_proto *p)
{
- struct rtable *t = p->p.main_channel->table;
+ RT_LOCK(p->p.main_channel->table);
+ rtable_private *t = RT_PRIV(p->p.main_channel->table);
KRT_TRACE(p, D_EVENTS, "Pruning table %s", t->name);
FIB_WALK(&t->fib, net, n)
{
- if (p->ready && krt_is_installed(p, n) && !bmap_test(&p->seen_map, n->routes->id))
+ if (p->ready && krt_is_installed(p, n) && !bmap_test(&p->seen_map, n->routes->rte.id))
{
- rte *rt_free = NULL;
- rte *new = krt_export_net(p, n, &rt_free);
+ rte *new = krt_export_net(p, n);
if (new)
{
krt_trace_in(p, new, "installing");
- krt_replace_rte(p, n, new, NULL);
+ krt_replace_rte(p, n->n.addr, new, NULL);
}
- if (rt_free)
- rte_free(rt_free);
-
lp_flush(krt_filter_lp);
}
}
FIB_WALK_END;
+ RT_UNLOCK(p->p.main_channel->table);
+
#ifdef KRT_ALLOW_LEARN
if (KRT_CF->learn)
- krt_learn_prune(p);
+ channel_refresh_end(p->p.main_channel);
#endif
if (p->ready)
p->initialized = 1;
}
+static void
+krt_flush_routes(struct krt_proto *p)
+{
+ KRT_TRACE(p, D_EVENTS, "Flushing kernel routes");
+ p->flush_routes = 1;
+ krt_init_scan(p);
+ krt_do_scan(p);
+ /* No prune! */
+ p->flush_routes = 0;
+}
+
void
-krt_got_route_async(struct krt_proto *p, rte *e, int new)
+krt_got_route_async(struct krt_proto *p, rte *e, int new, s8 src)
{
- net *net = e->net;
+ e->pflags = 0;
- switch (e->u.krt.src)
+ switch (src)
{
case KRT_SRC_BIRD:
/* Should be filtered by the back end */
@@ -766,7 +558,7 @@ krt_got_route_async(struct krt_proto *p, rte *e, int new)
if (new)
{
krt_trace_in(p, e, "[redirect] deleting");
- krt_replace_rte(p, net, NULL, e);
+ krt_replace_rte(p, e->net, NULL, e);
}
/* If !new, it is probably echo of our deletion */
break;
@@ -775,12 +567,11 @@ krt_got_route_async(struct krt_proto *p, rte *e, int new)
case KRT_SRC_ALIEN:
if (KRT_CF->learn)
{
- krt_learn_async(p, e, new);
+ krt_learn_rte(p, e);
return;
}
#endif
}
- rte_free(e);
}
/*
@@ -886,30 +677,15 @@ krt_scan_timer_kick(struct krt_proto *p)
* Updates
*/
-static void
-krt_make_tmp_attrs(struct rte *rt, struct linpool *pool)
-{
- rte_init_tmp_attrs(rt, pool, 2);
- rte_make_tmp_attr(rt, EA_KRT_SOURCE, EAF_TYPE_INT, rt->u.krt.proto);
- rte_make_tmp_attr(rt, EA_KRT_METRIC, EAF_TYPE_INT, rt->u.krt.metric);
-}
-
-static void
-krt_store_tmp_attrs(struct rte *rt, struct linpool *pool)
-{
- rte_init_tmp_attrs(rt, pool, 2);
- rt->u.krt.proto = rte_store_tmp_attr(rt, EA_KRT_SOURCE);
- rt->u.krt.metric = rte_store_tmp_attr(rt, EA_KRT_METRIC);
-}
-
static int
-krt_preexport(struct proto *P, rte **new, struct linpool *pool UNUSED)
+krt_preexport(struct channel *c, rte *e)
{
- // struct krt_proto *p = (struct krt_proto *) P;
- rte *e = *new;
-
- if (e->attrs->src->proto == P)
+ if (e->src->owner == &c->proto->sources)
+#ifdef CONFIG_SINGLE_ROUTE
+ return 1; /* Passing the route directly for rt_notify() to ignore */
+#else
return -1;
+#endif
if (!krt_capable(e))
return -1;
@@ -918,8 +694,8 @@ krt_preexport(struct proto *P, rte **new, struct linpool *pool UNUSED)
}
static void
-krt_rt_notify(struct proto *P, struct channel *ch UNUSED, net *net,
- rte *new, rte *old)
+krt_rt_notify(struct proto *P, struct channel *ch UNUSED, const net_addr *net,
+ rte *new, const rte *old)
{
struct krt_proto *p = (struct krt_proto *) P;
@@ -928,13 +704,10 @@ krt_rt_notify(struct proto *P, struct channel *ch UNUSED, net *net,
#ifdef CONFIG_SINGLE_ROUTE
/*
- * Implicit withdraw - when the imported kernel route becomes the best one,
- * we know that the previous one exported to the kernel was already removed,
- * but if we processed the update as usual, we would send withdraw to the
- * kernel, which would remove the new imported route instead.
+ * When the imported kernel route becomes the best one, we get it directly and
+ * we simply know that it is already there. Nothing to do.
*/
- rte *best = net->routes;
- if (!new && best && (best->attrs->src->proto == P))
+ if (new->src->owner == &P->sources)
return;
#endif
@@ -983,14 +756,6 @@ krt_feed_end(struct channel *C)
}
-static int
-krt_rte_same(rte *a, rte *b)
-{
- /* src is always KRT_SRC_ALIEN and type is irrelevant */
- return (a->u.krt.proto == b->u.krt.proto) && (a->u.krt.metric == b->u.krt.metric);
-}
-
-
/*
* Protocol glue
*/
@@ -1049,9 +814,7 @@ krt_init(struct proto_config *CF)
p->p.if_notify = krt_if_notify;
p->p.reload_routes = krt_reload_routes;
p->p.feed_end = krt_feed_end;
- p->p.make_tmp_attrs = krt_make_tmp_attrs;
- p->p.store_tmp_attrs = krt_store_tmp_attrs;
- p->p.rte_same = krt_rte_same;
+ p->p.rte_better = krt_rte_better;
krt_sys_init(p);
return &p->p;
@@ -1207,8 +970,4 @@ struct protocol proto_unix_kernel = {
.reconfigure = krt_reconfigure,
.copy_config = krt_copy_config,
.get_attr = krt_get_attr,
-#ifdef KRT_ALLOW_LEARN
- .dump = krt_dump,
- .dump_attrs = krt_dump_attrs,
-#endif
};
diff --git a/sysdep/unix/krt.h b/sysdep/unix/krt.h
index 62228f08..968c5b16 100644
--- a/sysdep/unix/krt.h
+++ b/sysdep/unix/krt.h
@@ -24,6 +24,9 @@ struct kif_proto;
#define EA_KRT_SOURCE EA_CODE(PROTOCOL_KERNEL, 0)
#define EA_KRT_METRIC EA_CODE(PROTOCOL_KERNEL, 1)
+#define KRT_REF_SEEN 0x1 /* Seen in table */
+#define KRT_REF_BEST 0x2 /* Best in table */
+
/* Whenever we recognize our own routes, we allow learing of foreign routes */
#ifdef CONFIG_SELF_CONSCIOUS
@@ -48,10 +51,6 @@ struct krt_proto {
struct proto p;
struct krt_state sys; /* Sysdep state */
-#ifdef KRT_ALLOW_LEARN
- struct rtable *krt_table; /* Internal table of inherited routes */
-#endif
-
#ifndef CONFIG_ALL_TABLES_AT_ONCE
timer *scan_timer;
#endif
@@ -63,6 +62,7 @@ struct krt_proto {
byte ready; /* Initial feed has been finished */
byte initialized; /* First scan has been finished */
byte reload; /* Next scan is doing reload */
+ byte flush_routes; /* Scanning to flush */
};
extern pool *krt_pool;
@@ -76,8 +76,8 @@ extern pool *krt_pool;
struct proto_config * kif_init_config(int class);
void kif_request_scan(void);
-void krt_got_route(struct krt_proto *p, struct rte *e);
-void krt_got_route_async(struct krt_proto *p, struct rte *e, int new);
+void krt_got_route(struct krt_proto *p, struct rte *e, s8 src);
+void krt_got_route_async(struct krt_proto *p, struct rte *e, int new, s8 src);
static inline int
krt_get_sync_error(struct krt_proto *p, struct rte *e)
@@ -140,7 +140,7 @@ void krt_sys_copy_config(struct krt_config *, struct krt_config *);
int krt_capable(rte *e);
void krt_do_scan(struct krt_proto *);
-void krt_replace_rte(struct krt_proto *p, net *n, rte *new, rte *old);
+void krt_replace_rte(struct krt_proto *p, const net_addr *n, rte *new, const rte *old);
int krt_sys_get_attr(const eattr *a, byte *buf, int buflen);
diff --git a/sysdep/unix/log.c b/sysdep/unix/log.c
index 14d18c01..68a04e78 100644
--- a/sysdep/unix/log.c
+++ b/sysdep/unix/log.c
@@ -15,6 +15,7 @@
* user's manual.
*/
+#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
@@ -35,8 +36,10 @@ static FILE *dbgf;
static list *current_log_list;
static char *current_syslog_name; /* NULL -> syslog closed */
+static _Atomic uint max_coro_id = ATOMIC_VAR_INIT(1);
+static _Thread_local uint this_coro_id;
-#ifdef USE_PTHREADS
+#define THIS_CORO_ID (this_coro_id ?: (this_coro_id = atomic_fetch_add_explicit(&max_coro_id, 1, memory_order_acq_rel)))
#include <pthread.h>
@@ -48,15 +51,6 @@ static pthread_t main_thread;
void main_thread_init(void) { main_thread = pthread_self(); }
static int main_thread_self(void) { return pthread_equal(pthread_self(), main_thread); }
-#else
-
-static inline void log_lock(void) { }
-static inline void log_unlock(void) { }
-void main_thread_init(void) { }
-static int main_thread_self(void) { return 1; }
-
-#endif
-
#ifdef HAVE_SYSLOG_H
#include <sys/syslog.h>
@@ -189,7 +183,7 @@ log_commit(int class, buffer *buf)
l->pos += msg_len;
}
- fprintf(l->fh, "%s <%s> ", tbuf, class_names[class]);
+ fprintf(l->fh, "%s [%04x] <%s> ", tbuf, THIS_CORO_ID, class_names[class]);
}
fputs(buf->start, l->fh);
fputc('\n', l->fh);
@@ -299,6 +293,8 @@ die(const char *msg, ...)
exit(1);
}
+static struct timespec dbg_time_start;
+
/**
* debug - write to debug output
* @msg: a printf-like message
@@ -309,22 +305,36 @@ die(const char *msg, ...)
void
debug(const char *msg, ...)
{
-#define MAX_DEBUG_BUFSIZE 65536
+#define MAX_DEBUG_BUFSIZE 16384
va_list args;
- static uint bufsize = 4096;
- static char *buf = NULL;
-
- if (!buf)
- buf = mb_alloc(&root_pool, bufsize);
+ char buf[MAX_DEBUG_BUFSIZE], *pos = buf;
+ int max = MAX_DEBUG_BUFSIZE;
va_start(args, msg);
if (dbgf)
{
- while (bvsnprintf(buf, bufsize, msg, args) < 0)
- if (bufsize >= MAX_DEBUG_BUFSIZE)
- bug("Extremely long debug output, split it.");
- else
- buf = mb_realloc(buf, (bufsize *= 2));
+ struct timespec dbg_time;
+ clock_gettime(CLOCK_MONOTONIC, &dbg_time);
+ uint nsec;
+ uint sec;
+
+ if (dbg_time.tv_nsec > dbg_time_start.tv_nsec)
+ {
+ nsec = dbg_time.tv_nsec - dbg_time_start.tv_nsec;
+ sec = dbg_time.tv_sec - dbg_time_start.tv_sec;
+ }
+ else
+ {
+ nsec = 1000000000 + dbg_time.tv_nsec - dbg_time_start.tv_nsec;
+ sec = dbg_time.tv_sec - dbg_time_start.tv_sec - 1;
+ }
+
+ int n = bsnprintf(pos, max, "%u.%09u: [%04x] ", sec, nsec, THIS_CORO_ID);
+ pos += n;
+ max -= n;
+
+ if (bvsnprintf(pos, max, msg, args) < 0)
+ bug("Extremely long debug output, split it.");
fputs(buf, dbgf);
}
@@ -378,6 +388,21 @@ default_log_list(int initial, const char **syslog_name)
}
void
+log_cleanup(int syslog)
+{
+ struct log_config *l;
+
+ if (current_log_list)
+ WALK_LIST(l, *current_log_list)
+ if (l->rf)
+ log_close(l);
+
+ if (syslog && current_syslog_name)
+ closelog();
+}
+
+
+void
log_switch(int initial, list *logs, const char *new_syslog_name)
{
struct log_config *l;
@@ -389,10 +414,7 @@ log_switch(int initial, list *logs, const char *new_syslog_name)
logs = default_log_list(initial, &new_syslog_name);
/* Close the logs to avoid pinning them on disk when deleted */
- if (current_log_list)
- WALK_LIST(l, *current_log_list)
- if (l->rf)
- log_close(l);
+ log_cleanup(0);
/* Reopen the logs, needed for 'configure undo' */
if (logs)
@@ -429,6 +451,8 @@ done:
void
log_init_debug(char *f)
{
+ clock_gettime(CLOCK_MONOTONIC, &dbg_time_start);
+
if (dbgf && dbgf != stderr)
fclose(dbgf);
if (!f)
diff --git a/sysdep/unix/main.c b/sysdep/unix/main.c
index 392aff9d..c326ba2b 100644
--- a/sysdep/unix/main.c
+++ b/sysdep/unix/main.c
@@ -28,6 +28,7 @@
#include "lib/resource.h"
#include "lib/socket.h"
#include "lib/event.h"
+#include "lib/locking.h"
#include "lib/timer.h"
#include "lib/string.h"
#include "nest/route.h"
@@ -51,7 +52,7 @@ async_dump(void)
{
debug("INTERNAL STATE DUMP\n\n");
- rdump(&root_pool);
+ rp_dump(&root_pool);
sk_dump_all();
// XXXX tm_dump_all();
if_dump_all();
@@ -201,7 +202,9 @@ sysdep_preconfig(struct config *c)
int
sysdep_commit(struct config *new, struct config *old UNUSED)
{
- log_switch(0, &new->logfiles, new->syslog_name);
+ if (!new->shutdown)
+ log_switch(0, &new->logfiles, new->syslog_name);
+
return 0;
}
@@ -479,6 +482,14 @@ cli_err(sock *s, int err)
cli_free(s->data);
}
+static void
+cli_connect_err(sock *s UNUSED, int err)
+{
+ ASSERT_DIE(err);
+ if (config->cli_debug)
+ log(L_INFO "Failed to accept CLI connection: %s", strerror(err));
+}
+
static int
cli_connect(sock *s, uint size UNUSED)
{
@@ -507,6 +518,7 @@ cli_init_unix(uid_t use_uid, gid_t use_gid)
s = cli_sk = sk_new(cli_pool);
s->type = SK_UNIX_PASSIVE;
s->rx_hook = cli_connect;
+ s->err_hook = cli_connect_err;
s->rbsize = 1024;
s->fast_rx = 1;
@@ -603,6 +615,7 @@ sysdep_shutdown_done(void)
unlink_pid_file();
unlink(path_control_socket);
log_msg(L_FATAL "Shutdown completed");
+ log_cleanup(1);
exit(0);
}
@@ -673,7 +686,7 @@ signal_init(void)
* Parsing of command-line arguments
*/
-static char *opt_list = "bc:dD:ps:P:u:g:flRh";
+static char *opt_list = "c:dD:ps:P:u:g:flRh";
int parse_and_exit;
char *bird_name;
static char *use_user;
@@ -852,6 +865,8 @@ parse_args(int argc, char **argv)
}
}
+void resource_sys_init(void);
+
/*
* Hic Est main()
*/
@@ -864,13 +879,17 @@ main(int argc, char **argv)
dmalloc_debug(0x2f03d00);
#endif
+ times_update();
+ resource_sys_init();
parse_args(argc, argv);
log_switch(1, NULL, NULL);
+ the_bird_lock();
+
random_init();
net_init();
resource_init();
- timer_init();
+ birdloop_init();
olock_init();
io_init();
rt_init();
@@ -920,6 +939,7 @@ main(int argc, char **argv)
dup2(0, 2);
}
+
main_thread_init();
write_pid_file();
diff --git a/sysdep/unix/unix.h b/sysdep/unix/unix.h
index ad85d1ea..51ec1b1e 100644
--- a/sysdep/unix/unix.h
+++ b/sysdep/unix/unix.h
@@ -122,6 +122,7 @@ void krt_io_init(void);
void main_thread_init(void);
void log_init_debug(char *); /* Initialize debug dump to given file (NULL=stderr, ""=off) */
void log_switch(int initial, list *l, const char *);
+void log_cleanup(int syslog);
struct log_config {
node n;
diff --git a/test/birdtest.c b/test/birdtest.c
index a1da078f..c6a09684 100644
--- a/test/birdtest.c
+++ b/test/birdtest.c
@@ -58,11 +58,14 @@ u64 bt_random_state[] = {
0x53d9772877c1b647, 0xab8ce3eb466de6c5, 0xad02844c8a8e865f, 0xe8cc78080295065d
};
+void resource_sys_init(void);
+
void
bt_init(int argc, char *argv[])
{
int c;
+ resource_sys_init();
initstate(BT_RANDOM_SEED, (char *) bt_random_state, sizeof(bt_random_state));
bt_verbose = 0;
@@ -240,7 +243,7 @@ bt_log_result(int result, u64 time, const char *fmt, va_list argptr)
printf("%s\n", result_str);
if (do_die && !result)
- abort();
+ exit(1);
}
static u64
diff --git a/test/bt-utils.c b/test/bt-utils.c
index cbca3a6b..98aaed3d 100644
--- a/test/bt-utils.c
+++ b/test/bt-utils.c
@@ -53,16 +53,20 @@ cf_file_read(byte *dest, uint max_len, int fd)
return l;
}
+void resource_sys_init(void);
+
void
bt_bird_init(void)
{
+ resource_sys_init();
if(bt_verbose)
log_init_debug("");
log_switch(bt_verbose != 0, NULL, NULL);
+ the_bird_lock();
resource_init();
+ birdloop_init();
olock_init();
- timer_init();
io_init();
rt_init();
if_init();
@@ -79,6 +83,7 @@ void bt_bird_cleanup(void)
class_to_protocol[i] = NULL;
config = new_config = NULL;
+ the_bird_unlock();
}
static char *