diff options
28 files changed, 795 insertions, 133 deletions
@@ -11,3 +11,4 @@ /config.status /configure /sysdep/autoconf.h.in +/sysdep/autoconf.h.in~ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..05989484 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,349 @@ +variables: + DEBIAN_FRONTEND: noninteractive + LC_ALL: C + GIT_STRATEGY: fetch + DOCKER_CMD: docker --config="$HOME/.docker/$CI_JOB_ID/" + IMG_BASE: registry.labs.nic.cz/labs/bird + +stages: + - image + - build + +.docker: &docker_build + stage: image + allow_failure: true + script: + - $DOCKER_CMD login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.labs.nic.cz + # Make sure we refresh the base image if it updates (eg. security updates, etc) + # If we do just the build, cache is always reused and the freshness of the + # base image is never checked. However, pull always asks and updates the + # image only if it changed ‒ therefore, the cache is used unless there's a + # change. + - $DOCKER_CMD pull `sed -ne 's/^FROM //p' "misc/docker/$IMG_NAME/Dockerfile"` + - $DOCKER_CMD build -t "bird:$IMG_NAME" "misc/docker/$IMG_NAME" + - $DOCKER_CMD tag "bird:$IMG_NAME" "$IMG_BASE:$IMG_NAME" + - $DOCKER_CMD push "$IMG_BASE:$IMG_NAME" + after_script: + - rm -f "$HOME/.docker/$CI_JOB_ID/" # cleanup the credentials + tags: + # That's Docker in Docker + - dind + +docker_debian-7-amd64: + variables: + IMG_NAME: "debian-7-amd64" + <<: *docker_build + +docker_debian-8-amd64: + variables: + IMG_NAME: "debian-8-amd64" + <<: *docker_build + +docker_debian-9-amd64: + variables: + IMG_NAME: "debian-9-amd64" + <<: *docker_build + +docker_debian-testing-amd64: + variables: + IMG_NAME: "debian-testing-amd64" + <<: *docker_build + +docker_debian-7-i386: + variables: + IMG_NAME: "debian-7-i386" + <<: *docker_build + +docker_debian-8-i386: + variables: + IMG_NAME: "debian-8-i386" + <<: *docker_build + +docker_debian-9-i386: + variables: + IMG_NAME: "debian-9-i386" + <<: *docker_build + +docker_debian-testing-i386: + variables: + IMG_NAME: "debian-testing-i386" + <<: *docker_build + +docker_fedora-25-amd64: + variables: + IMG_NAME: "fedora-25-amd64" + <<: *docker_build + +docker_fedora-26-amd64: + variables: + IMG_NAME: "fedora-26-amd64" + <<: *docker_build + +docker_centos-6-amd64: + variables: + IMG_NAME: "centos-6-amd64" + <<: *docker_build + +docker_centos-7-amd64: + variables: + IMG_NAME: "centos-7-amd64" + <<: *docker_build + +docker_opensuse-42_3-amd64: + variables: + IMG_NAME: "opensuse-42.3-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" + <<: *docker_build + +.debian-7-i386: &debian-7-i386_env + image: registry.labs.nic.cz/labs/bird:debian-7-i386 + tags: + - docker + - linux + - amd64 + +.debian-8-i386: &debian-8-i386_env + image: registry.labs.nic.cz/labs/bird:debian-8-i386 + tags: + - docker + - linux + - amd64 + +.debian-9-i386: &debian-9-i386_env + image: registry.labs.nic.cz/labs/bird:debian-9-i386 + tags: + - docker + - linux + - amd64 + +.debian-testing-i386: &debian-testing-i386_env + image: registry.labs.nic.cz/labs/bird:debian-testing-i386 + tags: + - docker + - linux + - amd64 + +.debian-7-amd64: &debian-7-amd64_env + image: registry.labs.nic.cz/labs/bird:debian-7-amd64 + tags: + - docker + - linux + - amd64 + +.debian-8-amd64: &debian-8-amd64_env + image: registry.labs.nic.cz/labs/bird:debian-8-amd64 + tags: + - docker + - linux + - amd64 + +.debian-9-amd64: &debian-9-amd64_env + image: registry.labs.nic.cz/labs/bird:debian-9-amd64 + tags: + - docker + - linux + - amd64 + +.debian-testing-amd64: &debian-testing-amd64_env + image: registry.labs.nic.cz/labs/bird:debian-testing-amd64 + tags: + - docker + - linux + - amd64 + +.fedora-25-amd64: &fedora-25-amd64_env + image: registry.labs.nic.cz/labs/bird:fedora-25-amd64 + tags: + - docker + - linux + - amd64 + +.fedora-26-amd64: &fedora-26-amd64_env + image: registry.labs.nic.cz/labs/bird:fedora-26-amd64 + tags: + - docker + - linux + - amd64 + +.centos-6-amd64: ¢os-6-amd64_env + image: registry.labs.nic.cz/labs/bird:centos-6-amd64 + tags: + - docker + - linux + - amd64 + +.centos-7-amd64: ¢os-7-amd64_env + image: registry.labs.nic.cz/labs/bird:centos-7-amd64 + tags: + - docker + - linux + - amd64 + +.opensuse-42_3-amd64: &opensuse-42_3-amd64_env + image: registry.labs.nic.cz/labs/bird:opensuse-42.3-amd64 + tags: + - docker + - linux + - amd64 + +.ubuntu-14_04-amd64: &ubuntu-14_04-amd64_env + image: registry.labs.nic.cz/labs/bird:ubuntu-14.04-amd64 + tags: + - docker + - linux + - amd64 + +.ubuntu-16_04-amd64: &ubuntu-16_04-amd64_env + image: registry.labs.nic.cz/labs/bird:ubuntu-16.04-amd64 + tags: + - docker + - linux + - amd64 + +# TODO We want to copy these BSDs to our own virtual machines, to make sure someone doesn't update them by accident. +.freebsd-11-i386: &freebsd-11-i386_env + variables: + CPPFLAGS: "-I/usr/local/include" + LDFLAGS: "-L/usr/local/lib" + tags: + - freebsd + - i386 + #only: + #- master + #- triggers + #- tags + +.freebsd-11-amd64: &freebsd-11-amd64_env + variables: + CPPFLAGS: "-I/usr/local/include" + LDFLAGS: "-L/usr/local/lib" + tags: + - freebsd + - amd64 + #only: + #- master + #- triggers + #- tags + +.build: &build_job + stage: build + script: + - autoreconf + - ./configure CPPFLAGS="$CPPFLAGS" LDFLAGS="$LDFLAGS" + # Detect which make is available + - MAKE=make + - which gmake 2>/dev/null >/dev/null && MAKE=gmake + - $MAKE + # Run tests if they are available (eg. don't fail if "check" isn't a valid make target) + - $MAKE check || [ "$?" = 2 ] + +build-debian-7-amd64: + variables: + IPV6: "no" + <<: *debian-7-amd64_env + <<: *build_job + +build-debian-8-amd64: + variables: + IPV6: "no" + <<: *debian-8-amd64_env + <<: *build_job + +build-debian-9-amd64: + variables: + IPV6: "no" + <<: *debian-9-amd64_env + <<: *build_job + +build-debian-testing-amd64: + variables: + IPV6: "no" + <<: *debian-testing-amd64_env + <<: *build_job + +build-fedora-25-amd64: + variables: + IPV6: "no" + <<: *fedora-25-amd64_env + <<: *build_job + +build-fedora-26-amd64: + variables: + IPV6: "no" + <<: *fedora-26-amd64_env + <<: *build_job + +build-centos-6-amd64: + variables: + IPV6: "no" + <<: *centos-6-amd64_env + <<: *build_job + +build-centos-7-amd64: + variables: + IPV6: "no" + <<: *centos-7-amd64_env + <<: *build_job + +build-opensuse-42_3-amd64: + variables: + IPV6: "no" + <<: *opensuse-42_3-amd64_env + <<: *build_job + +build-ubuntu-14_04-amd64: + variables: + IPV6: "no" + <<: *ubuntu-14_04-amd64_env + <<: *build_job + +build-ubuntu-16_04-amd64: + variables: + IPV6: "no" + <<: *ubuntu-16_04-amd64_env + <<: *build_job + +build-debian-7-i386: + variables: + IPV6: "no" + <<: *debian-7-i386_env + <<: *build_job + +build-debian-8-i386: + variables: + IPV6: "no" + <<: *debian-8-i386_env + <<: *build_job + +build-debian-9-i386: + variables: + IPV6: "no" + <<: *debian-9-i386_env + <<: *build_job + +build-debian-testing-i386: + variables: + IPV6: "no" + <<: *debian-testing-i386_env + <<: *build_job + +build-freebsd-11-amd64: + variables: + IPV6: "no" + <<: *freebsd-11-amd64_env + <<: *build_job + +build-freebsd-11-i386: + variables: + IPV6: "no" + <<: *freebsd-11-i386_env + <<: *build_job @@ -1,45 +0,0 @@ -Core -~~~~ -- socket open failure should not be fatal -- &&,||: priorities -- static: allow specifying a per-route filter program for setting route attributes? - -Globals -~~~~~~~ -- right usage of DBG vs. debug -- logging and tracing; use appropriate log levels -- check incoming packets and log errors!! -- check log calls for trailing newlines and log levels followed by comma -- check if all protocols set proper packet priorities and TTL's. -- try compiling with -Wunused -- does everybody test return value of sk_open? -- protocols: implement CLI hooks and per-procotol CLI commands -- protocols: implement reconfigure hook -- protocols: use locking -- check use of system includes and sprintf() - -Various ideas -~~~~~~~~~~~~~ -- client: Ctrl-R eats one more enter -- bgp: timing of updates? -- netlink: import Linux route attributes to our rta's, so that they can be filtered? -- config: executable config files -- filters: user defined attributes? -- io: use poll if available -- route recalculation timing and flap dampening [see RFC2439 for algorithms] -- aggregate engine: standard route aggregation and summarization [RFC2519] -- aggregate engine: injection of manually configured pseudo-static routes -- generate default route if any working BGP connection exists (aggregate engine again?) -- generate default route to IGP's (aggregate engine yet another time?) -- look at RFC 2386 (QoS-based routing) -- cli: show tables? - -OSPF -~~~~ - - check incoming packets using neighbor cache - - RFC2328 appendix E: Use a better algorithm - - automatic generation of external route tags (RFC1403) - - RFC2370 opaque LSA's - - Limit export rate of external LSAs (like Gated does) - - Bugfix in link state retransmission list (aging) - - Graceful OSPF restart - RFC3623 diff --git a/client/client.c b/client/client.c index 0d4bdf3e..97cf6639 100644 --- a/client/client.c +++ b/client/client.c @@ -25,9 +25,10 @@ #include <fcntl.h> #include <unistd.h> #include <errno.h> +#include <sys/select.h> #include <sys/socket.h> -#include <sys/un.h> #include <sys/types.h> +#include <sys/un.h> #include "nest/bird.h" #include "lib/resource.h" diff --git a/doc/bird.sgml b/doc/bird.sgml index 0ee86c18..4ee91206 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -3660,6 +3660,12 @@ dsc-iface as a default router. For <cf/sensitive/ option, see <ref id="radv-trigger" name="trigger">. Default: 3 * <cf/max ra interval/, <cf/sensitive/ yes. + <tag><label id="radv-iface-linger-time">linger time <m/expr/</tag> + When a prefix disappears, it is advertised for some time with 0 + lifetime, to inform clients the prefix is no longer usable. This option + sets the time for how long it is advertised (in seconds). Maximum is + 3600, 0 means disabled. Default: 300. + <tag><label id="radv-iface-default-preference-low">default preference low|medium|high</tag> This option specifies the Default Router Preference value to advertise to hosts. Default: medium. @@ -526,6 +526,18 @@ int net_classify(const net_addr *N); int net_format(const net_addr *N, char *buf, int buflen); int rd_format(const u64 rd, char *buf, int buflen); +static inline int ipa_in_net_ip4(ip4_addr a, const net_addr_ip4 *n) +{ return ip4_zero(ip4_and(ip4_xor(a, n->prefix), ip4_mkmask(n->pxlen))); } + +static inline int net_in_net_ip4(const net_addr_ip4 *a, const net_addr_ip4 *b) +{ return (a->pxlen >= b->pxlen) && ipa_in_net_ip4(a->prefix, b); } + +static inline int ipa_in_net_ip6(ip6_addr a, const net_addr_ip6 *n) +{ return ip6_zero(ip6_and(ip6_xor(a, n->prefix), ip6_mkmask(n->pxlen))); } + +static inline int net_in_net_ip6(const net_addr_ip6 *a, const net_addr_ip6 *b) +{ return (a->pxlen >= b->pxlen) && ipa_in_net_ip6(a->prefix, b); } + int ipa_in_netX(const ip_addr A, const net_addr *N); int net_in_netX(const net_addr *A, const net_addr *N); diff --git a/misc/docker/centos-6-amd64/Dockerfile b/misc/docker/centos-6-amd64/Dockerfile new file mode 100644 index 00000000..d92f64f9 --- /dev/null +++ b/misc/docker/centos-6-amd64/Dockerfile @@ -0,0 +1,11 @@ +FROM centos:6 +RUN yum -y upgrade +RUN yum -y install \ + autoconf \ + flex \ + bison \ + pkgconfig \ + 'readline-devel' \ + 'pkgconfig(ncurses)' \ + gcc \ + make diff --git a/misc/docker/centos-7-amd64/Dockerfile b/misc/docker/centos-7-amd64/Dockerfile new file mode 100644 index 00000000..c01731b2 --- /dev/null +++ b/misc/docker/centos-7-amd64/Dockerfile @@ -0,0 +1,11 @@ +FROM centos:7 +RUN yum -y upgrade +RUN yum -y install \ + autoconf \ + flex \ + bison \ + pkgconfig \ + 'readline-devel' \ + 'pkgconfig(ncurses)' \ + gcc \ + make diff --git a/misc/docker/debian-7-amd64/Dockerfile b/misc/docker/debian-7-amd64/Dockerfile new file mode 100644 index 00000000..dd5e9236 --- /dev/null +++ b/misc/docker/debian-7-amd64/Dockerfile @@ -0,0 +1,12 @@ +FROM debian:wheezy-slim +ENV DEBIAN_FRONTEND noninteractive +RUN sed -i 's/deb.debian.org/ftp.cz.debian.org/' /etc/apt/sources.list +RUN apt-get -y update +RUN apt-get -y upgrade +RUN apt-get -y install \ + autoconf \ + build-essential \ + flex \ + bison \ + ncurses-dev \ + libreadline-dev diff --git a/misc/docker/debian-7-i386/Dockerfile b/misc/docker/debian-7-i386/Dockerfile new file mode 100644 index 00000000..753ff1cb --- /dev/null +++ b/misc/docker/debian-7-i386/Dockerfile @@ -0,0 +1,12 @@ +FROM i386/debian:wheezy-slim +ENV DEBIAN_FRONTEND noninteractive +RUN sed -i 's/deb.debian.org/ftp.cz.debian.org/' /etc/apt/sources.list +RUN apt-get -y update +RUN apt-get -y upgrade +RUN apt-get -y install \ + autoconf \ + build-essential \ + flex \ + bison \ + ncurses-dev \ + libreadline-dev diff --git a/misc/docker/debian-8-amd64/Dockerfile b/misc/docker/debian-8-amd64/Dockerfile new file mode 100644 index 00000000..d0e8ed0a --- /dev/null +++ b/misc/docker/debian-8-amd64/Dockerfile @@ -0,0 +1,12 @@ +FROM debian:jessie-slim +ENV DEBIAN_FRONTEND noninteractive +RUN sed -i 's/deb.debian.org/ftp.cz.debian.org/' /etc/apt/sources.list +RUN apt-get -y update +RUN apt-get -y upgrade +RUN apt-get -y install \ + autoconf \ + build-essential \ + flex \ + bison \ + ncurses-dev \ + libreadline-dev diff --git a/misc/docker/debian-8-i386/Dockerfile b/misc/docker/debian-8-i386/Dockerfile new file mode 100644 index 00000000..9dd6f022 --- /dev/null +++ b/misc/docker/debian-8-i386/Dockerfile @@ -0,0 +1,12 @@ +FROM i386/debian:jessie-slim +ENV DEBIAN_FRONTEND noninteractive +RUN sed -i 's/deb.debian.org/ftp.cz.debian.org/' /etc/apt/sources.list +RUN apt-get -y update +RUN apt-get -y upgrade +RUN apt-get -y install \ + autoconf \ + build-essential \ + flex \ + bison \ + ncurses-dev \ + libreadline-dev diff --git a/misc/docker/debian-9-amd64/Dockerfile b/misc/docker/debian-9-amd64/Dockerfile new file mode 100644 index 00000000..e8a17efc --- /dev/null +++ b/misc/docker/debian-9-amd64/Dockerfile @@ -0,0 +1,12 @@ +FROM debian:stretch-slim +ENV DEBIAN_FRONTEND noninteractive +RUN sed -i 's/deb.debian.org/ftp.cz.debian.org/' /etc/apt/sources.list +RUN apt-get -y update +RUN apt-get -y upgrade +RUN apt-get -y install \ + autoconf \ + build-essential \ + flex \ + bison \ + ncurses-dev \ + libreadline-dev diff --git a/misc/docker/debian-9-i386/Dockerfile b/misc/docker/debian-9-i386/Dockerfile new file mode 100644 index 00000000..73e70750 --- /dev/null +++ b/misc/docker/debian-9-i386/Dockerfile @@ -0,0 +1,12 @@ +FROM i386/debian:stretch-slim +ENV DEBIAN_FRONTEND noninteractive +RUN sed -i 's/deb.debian.org/ftp.cz.debian.org/' /etc/apt/sources.list +RUN apt-get -y update +RUN apt-get -y upgrade +RUN apt-get -y install \ + autoconf \ + build-essential \ + flex \ + bison \ + ncurses-dev \ + libreadline-dev diff --git a/misc/docker/debian-testing-amd64/Dockerfile b/misc/docker/debian-testing-amd64/Dockerfile new file mode 100644 index 00000000..3b66e3fa --- /dev/null +++ b/misc/docker/debian-testing-amd64/Dockerfile @@ -0,0 +1,12 @@ +FROM debian:testing-slim +ENV DEBIAN_FRONTEND noninteractive +RUN sed -i 's/deb.debian.org/ftp.cz.debian.org/' /etc/apt/sources.list +RUN apt-get -y update +RUN apt-get -y upgrade +RUN apt-get -y install \ + autoconf \ + build-essential \ + flex \ + bison \ + ncurses-dev \ + libreadline-dev diff --git a/misc/docker/debian-testing-i386/Dockerfile b/misc/docker/debian-testing-i386/Dockerfile new file mode 100644 index 00000000..998ce969 --- /dev/null +++ b/misc/docker/debian-testing-i386/Dockerfile @@ -0,0 +1,12 @@ +FROM i386/debian:testing-slim +ENV DEBIAN_FRONTEND noninteractive +RUN sed -i 's/deb.debian.org/ftp.cz.debian.org/' /etc/apt/sources.list +RUN apt-get -y update +RUN apt-get -y upgrade +RUN apt-get -y install \ + autoconf \ + build-essential \ + flex \ + bison \ + ncurses-dev \ + libreadline-dev diff --git a/misc/docker/fedora-25-amd64/Dockerfile b/misc/docker/fedora-25-amd64/Dockerfile new file mode 100644 index 00000000..9cc4c3f6 --- /dev/null +++ b/misc/docker/fedora-25-amd64/Dockerfile @@ -0,0 +1,10 @@ +FROM fedora:25 +RUN dnf -y upgrade +RUN dnf -y install \ + autoconf \ + flex \ + bison \ + pkgconfig \ + 'readline-devel' \ + 'pkgconfig(ncurses)' \ + gcc diff --git a/misc/docker/fedora-26-amd64/Dockerfile b/misc/docker/fedora-26-amd64/Dockerfile new file mode 100644 index 00000000..d1e84439 --- /dev/null +++ b/misc/docker/fedora-26-amd64/Dockerfile @@ -0,0 +1,10 @@ +FROM fedora:26 +RUN dnf -y upgrade +RUN dnf -y install \ + autoconf \ + flex \ + bison \ + pkgconfig \ + 'readline-devel' \ + 'pkgconfig(ncurses)' \ + gcc diff --git a/misc/docker/opensuse-42.3-amd64/Dockerfile b/misc/docker/opensuse-42.3-amd64/Dockerfile new file mode 100644 index 00000000..c9f7127d --- /dev/null +++ b/misc/docker/opensuse-42.3-amd64/Dockerfile @@ -0,0 +1,11 @@ +FROM opensuse:42.3 +RUN zypper -n up +RUN zypper -n install \ + autoconf \ + flex \ + bison \ + pkgconfig \ + readline-devel \ + ncurses-devel \ + gcc \ + gmake diff --git a/misc/docker/ubuntu-14.04-amd64/Dockerfile b/misc/docker/ubuntu-14.04-amd64/Dockerfile new file mode 100644 index 00000000..ccac3eb4 --- /dev/null +++ b/misc/docker/ubuntu-14.04-amd64/Dockerfile @@ -0,0 +1,12 @@ +FROM ubuntu:14.04 +ENV DEBIAN_FRONTEND noninteractive +RUN sed -i 's/deb.debian.org/ftp.cz.debian.org/' /etc/apt/sources.list +RUN apt-get -y update +RUN apt-get -y upgrade +RUN apt-get -y install \ + autoconf \ + build-essential \ + flex \ + bison \ + ncurses-dev \ + libreadline-dev diff --git a/misc/docker/ubuntu-16.04-amd64/Dockerfile b/misc/docker/ubuntu-16.04-amd64/Dockerfile new file mode 100644 index 00000000..50a4918c --- /dev/null +++ b/misc/docker/ubuntu-16.04-amd64/Dockerfile @@ -0,0 +1,12 @@ +FROM ubuntu:16.04 +ENV DEBIAN_FRONTEND noninteractive +RUN sed -i 's/deb.debian.org/ftp.cz.debian.org/' /etc/apt/sources.list +RUN apt-get -y update +RUN apt-get -y upgrade +RUN apt-get -y install \ + autoconf \ + build-essential \ + flex \ + bison \ + ncurses-dev \ + libreadline-dev diff --git a/nest/iface.c b/nest/iface.c index 01b1aa48..dbc4debe 100644 --- a/nest/iface.c +++ b/nest/iface.c @@ -145,9 +145,9 @@ ifa_send_notify(struct proto *p, unsigned c, struct ifa *a) if (p->ifa_notify && (p->proto_state != PS_DOWN)) { if (p->debug & D_IFACES) - log(L_TRACE "%s < %s address %N on interface %s %s", - p->name, (a->flags & IA_PRIMARY) ? "primary" : "secondary", - &a->prefix, a->iface->name, (c & IF_CHANGE_UP) ? "added" : "removed"); + log(L_TRACE "%s < address %N on interface %s %s", + p->name, &a->prefix, a->iface->name, + (c & IF_CHANGE_UP) ? "added" : "removed"); p->ifa_notify(p, c, a); } } diff --git a/proto/radv/config.Y b/proto/radv/config.Y index b5f4b5f2..0e43c237 100644 --- a/proto/radv/config.Y +++ b/proto/radv/config.Y @@ -27,7 +27,7 @@ static u8 radv_mult_val; /* Used by radv_mult for second return value */ CF_DECLS CF_KEYWORDS(RADV, PREFIX, INTERFACE, MIN, MAX, RA, DELAY, INTERVAL, - MANAGED, OTHER, CONFIG, LINK, MTU, REACHABLE, TIME, RETRANS, + MANAGED, OTHER, CONFIG, LINGER, LINK, MTU, REACHABLE, TIME, RETRANS, TIMER, CURRENT, HOP, LIMIT, DEFAULT, VALID, PREFERRED, MULT, LIFETIME, SKIP, ONLINK, AUTONOMOUS, RDNSS, DNSSL, NS, DOMAIN, LOCAL, TRIGGER, SENSITIVE, PREFERENCE, LOW, MEDIUM, HIGH) @@ -80,6 +80,7 @@ radv_iface_start: RADV_IFACE->max_ra_int = DEFAULT_MAX_RA_INT; RADV_IFACE->min_delay = DEFAULT_MIN_DELAY; RADV_IFACE->current_hop_limit = DEFAULT_CURRENT_HOP_LIMIT; + RADV_IFACE->linger_time = DEFAULT_LINGER_TIME; RADV_IFACE->default_lifetime = -1; RADV_IFACE->default_lifetime_sensitive = 1; RADV_IFACE->default_preference = RA_PREF_MEDIUM; @@ -94,6 +95,7 @@ radv_iface_item: | LINK MTU expr { RADV_IFACE->link_mtu = $3; } | REACHABLE TIME expr { RADV_IFACE->reachable_time = $3; if ($3 > 3600000) cf_error("Reachable time must be in range 0-3600000"); } | RETRANS TIMER expr { RADV_IFACE->retrans_timer = $3; } + | LINGER TIME expr { RADV_IFACE->linger_time = $3; if ($3 > 3600) cf_error("Linger time must be in range 0-3600"); } | CURRENT HOP LIMIT expr { RADV_IFACE->current_hop_limit = $4; if ($4 > 255) cf_error("Current hop limit must be in range 0-255"); } | DEFAULT LIFETIME expr radv_sensitive { RADV_IFACE->default_lifetime = $3; diff --git a/proto/radv/packets.c b/proto/radv/packets.c index 9ea8feee..e07296e1 100644 --- a/proto/radv/packets.c +++ b/proto/radv/packets.c @@ -70,36 +70,6 @@ struct radv_opt_dnssl char domain[]; }; - -static struct radv_prefix_config default_prefix = { - .onlink = 1, - .autonomous = 1, - .valid_lifetime = DEFAULT_VALID_LIFETIME, - .preferred_lifetime = DEFAULT_PREFERRED_LIFETIME -}; - - -static struct radv_prefix_config * -radv_prefix_match(struct radv_iface *ifa, struct ifa *a) -{ - struct radv_proto *p = ifa->ra; - struct radv_config *cf = (struct radv_config *) (p->p.cf); - struct radv_prefix_config *pc; - - if (a->scope <= SCOPE_LINK) - return NULL; - - WALK_LIST(pc, ifa->cf->pref_list) - if (net_in_netX(&a->prefix, (net_addr *) &pc->prefix)) - return pc; - - WALK_LIST(pc, cf->pref_list) - if (net_in_netX(&a->prefix, (net_addr *) &pc->prefix)) - return pc; - - return &default_prefix; -} - static int radv_prepare_rdnss(struct radv_iface *ifa, list *rdnss_list, char **buf, char *bufend) { @@ -234,6 +204,36 @@ radv_prepare_dnssl(struct radv_iface *ifa, list *dnssl_list, char **buf, char *b return -1; } +static int +radv_prepare_prefix(struct radv_iface *ifa, struct radv_prefix *px, + char **buf, char *bufend) +{ + struct radv_prefix_config *pc = px->cf; + + if (*buf + sizeof(struct radv_opt_prefix) > bufend) + { + log(L_WARN "%s: Too many prefixes on interface %s", + ifa->ra->p.name, ifa->iface->name); + return -1; + } + + struct radv_opt_prefix *op = (void *) *buf; + op->type = OPT_PREFIX; + op->length = 4; + op->pxlen = px->prefix.pxlen; + op->flags = (pc->onlink ? OPT_PX_ONLINK : 0) | + (pc->autonomous ? OPT_PX_AUTONOMOUS : 0); + op->valid_lifetime = (ifa->ra->active || !pc->valid_lifetime_sensitive) ? + htonl(pc->valid_lifetime) : 0; + op->preferred_lifetime = (ifa->ra->active || !pc->preferred_lifetime_sensitive) ? + htonl(pc->preferred_lifetime) : 0; + op->reserved = 0; + op->prefix = ip6_hton(px->prefix.prefix); + *buf += sizeof(*op); + + return 0; +} + static void radv_prepare_ra(struct radv_iface *ifa) { @@ -269,37 +269,11 @@ radv_prepare_ra(struct radv_iface *ifa) buf += sizeof (*om); } - struct ifa *addr; - WALK_LIST(addr, ifa->iface->addrs) + struct radv_prefix *prefix; + WALK_LIST(prefix, ifa->prefixes) { - if (addr->prefix.type != NET_IP6) - continue; - - struct radv_prefix_config *pc; - pc = radv_prefix_match(ifa, addr); - - if (!pc || pc->skip) - continue; - - if (buf + sizeof(struct radv_opt_prefix) > bufend) - { - log(L_WARN "%s: Too many prefixes on interface %s", p->p.name, ifa->iface->name); + if (radv_prepare_prefix(ifa, prefix, &buf, bufend) < 0) goto done; - } - - struct radv_opt_prefix *op = (void *) buf; - op->type = OPT_PREFIX; - op->length = 4; - op->pxlen = net6_pxlen(&addr->prefix); - op->flags = (pc->onlink ? OPT_PX_ONLINK : 0) | - (pc->autonomous ? OPT_PX_AUTONOMOUS : 0); - op->valid_lifetime = (p->active || !pc->valid_lifetime_sensitive) ? - htonl(pc->valid_lifetime) : 0; - op->preferred_lifetime = (p->active || !pc->preferred_lifetime_sensitive) ? - htonl(pc->preferred_lifetime) : 0; - op->reserved = 0; - op->prefix = ip6_hton(net6_prefix(&addr->prefix)); - buf += sizeof(*op); } if (! ic->rdnss_local) @@ -408,7 +382,7 @@ radv_err_hook(sock *sk, int err) int radv_sk_open(struct radv_iface *ifa) { - sock *sk = sk_new(ifa->ra->p.pool); + sock *sk = sk_new(ifa->pool); sk->type = SK_IP; sk->subtype = SK_IPV6; sk->dport = ICMPV6_PROTO; diff --git a/proto/radv/radv.c b/proto/radv/radv.c index fe371ab4..c96d7724 100644 --- a/proto/radv/radv.c +++ b/proto/radv/radv.c @@ -51,6 +51,16 @@ radv_timer(timer *tm) RADV_TRACE(D_EVENTS, "Timer fired on %s", ifa->iface->name); + /* + * If some dead prefixes expired, regenerate the prefix list and the packet. + * We do so by pretending there was a change on the interface. + * + * This sets the timer, but we replace it just at the end of this function + * (replacing a timer is fine). + */ + if (ifa->prefix_expires && (ifa->prefix_expires <= current_time())) + radv_iface_notify(ifa, RA_EV_GC); + radv_send_ra(ifa, 0); /* Update timer */ @@ -68,7 +78,136 @@ radv_timer(timer *tm) tm_start(ifa->timer, t); } -static char* ev_name[] = { NULL, "Init", "Change", "RS" }; +static struct radv_prefix_config default_prefix = { + .onlink = 1, + .autonomous = 1, + .valid_lifetime = DEFAULT_VALID_LIFETIME, + .preferred_lifetime = DEFAULT_PREFERRED_LIFETIME +}; + +static struct radv_prefix_config dead_prefix = { +}; + +/* Find a corresponding config for the given prefix */ +static struct radv_prefix_config * +radv_prefix_match(struct radv_iface *ifa, net_addr_ip6 *px) +{ + struct radv_proto *p = ifa->ra; + struct radv_config *cf = (struct radv_config *) (p->p.cf); + struct radv_prefix_config *pc; + + WALK_LIST(pc, ifa->cf->pref_list) + if (net_in_net_ip6(px, &pc->prefix)) + return pc; + + WALK_LIST(pc, cf->pref_list) + if (net_in_net_ip6(px, &pc->prefix)) + return pc; + + return &default_prefix; +} + +/* + * Go through the list of prefixes, compare them with configs and decide if we + * want them or not. + */ +static void +radv_prepare_prefixes(struct radv_iface *ifa) +{ + struct radv_proto *p = ifa->ra; + struct radv_iface_config *cf = ifa->cf; + struct radv_prefix *pfx; + + /* First mark all the prefixes as unused */ + WALK_LIST(pfx, ifa->prefixes) + pfx->mark = 0; + + /* Find all the prefixes we want to use and make sure they are in the list. */ + struct ifa *addr; + WALK_LIST(addr, ifa->iface->addrs) + { + if ((addr->prefix.type != NET_IP6) || + (addr->scope <= SCOPE_LINK)) + continue; + + net_addr_ip6 *prefix = (void *) &addr->prefix; + struct radv_prefix_config *pc = radv_prefix_match(ifa, prefix); + + if (!pc || pc->skip) + continue; + + /* Do we have it already? */ + struct radv_prefix *existing = NULL; + WALK_LIST(pfx, ifa->prefixes) + if (net_equal_ip6(&pfx->prefix, prefix)) + { + existing = pfx; + break; + } + + if (!existing) + { + RADV_TRACE(D_EVENTS, "Adding new prefix %N on %s", + prefix, ifa->iface->name); + + existing = mb_allocz(ifa->pool, sizeof *existing); + net_copy_ip6(&existing->prefix, prefix); + add_tail(&ifa->prefixes, NODE existing); + } + + /* + * Update the information (it may have changed, or even bring a prefix back + * to life). + */ + existing->alive = 1; + existing->mark = 1; + existing->cf = pc; + } + + /* + * Garbage-collect the prefixes. If something isn't used, it dies (but isn't + * dropped just yet). If something is dead and rots there for long enough, + * clean it up. + */ + btime now_ = current_time(); + btime expires = now_ + cf->linger_time S; + btime expires_min = 0; + struct radv_prefix *next; + WALK_LIST_DELSAFE(pfx, next, ifa->prefixes) + { + if (pfx->alive && !pfx->mark) + { + RADV_TRACE(D_EVENTS, "Marking prefix %N on %s as dead", + pfx->prefix, ifa->iface->name); + + pfx->alive = 0; + pfx->expires = expires; + pfx->cf = &dead_prefix; + } + + if (!pfx->alive) + { + if (pfx->expires <= now_) + { + RADV_TRACE(D_EVENTS, "Removing prefix %N on %s", + pfx->prefix, ifa->iface->name); + + rem_node(NODE pfx); + mb_free(pfx); + } + else + { + /* Find minimum expiration time */ + if (!expires_min || (pfx->expires < expires_min)) + expires_min = pfx->expires; + } + } + } + + ifa->prefix_expires = expires_min; +} + +static char* ev_name[] = { NULL, "Init", "Change", "RS", "Garbage collect" }; void radv_iface_notify(struct radv_iface *ifa, int event) @@ -83,6 +222,7 @@ radv_iface_notify(struct radv_iface *ifa, int event) switch (event) { case RA_EV_CHANGE: + case RA_EV_GC: ifa->plen = 0; case RA_EV_INIT: ifa->initial = MAX_INITIAL_RTR_ADVERTISEMENTS; @@ -92,6 +232,8 @@ radv_iface_notify(struct radv_iface *ifa, int event) break; } + radv_prepare_prefixes(ifa); + /* Update timer */ btime t = ifa->last + ifa->cf->min_delay S - current_time(); tm_start(ifa->timer, t); @@ -137,16 +279,18 @@ radv_iface_add(struct object_lock *lock) static void radv_iface_new(struct radv_proto *p, struct iface *iface, struct radv_iface_config *cf) { - pool *pool = p->p.pool; struct radv_iface *ifa; RADV_TRACE(D_EVENTS, "Adding interface %s", iface->name); + pool *pool = rp_new(p->p.pool, iface->name); ifa = mb_allocz(pool, sizeof(struct radv_iface)); + ifa->pool = pool; ifa->ra = p; ifa->cf = cf; ifa->iface = iface; ifa->addr = iface->llv6; + init_list(&ifa->prefixes); add_tail(&p->iface_list, NODE ifa); @@ -172,11 +316,7 @@ radv_iface_remove(struct radv_iface *ifa) rem_node(NODE ifa); - rfree(ifa->sk); - rfree(ifa->timer); - rfree(ifa->lock); - - mb_free(ifa); + rfree(ifa->pool); } static void diff --git a/proto/radv/radv.h b/proto/radv/radv.h index 8324bb67..4672e3b2 100644 --- a/proto/radv/radv.h +++ b/proto/radv/radv.h @@ -35,6 +35,7 @@ #define DEFAULT_MAX_RA_INT 600 #define DEFAULT_MIN_DELAY 3 #define DEFAULT_CURRENT_HOP_LIMIT 64 +#define DEFAULT_LINGER_TIME 300 #define DEFAULT_VALID_LIFETIME 86400 #define DEFAULT_PREFERRED_LIFETIME 14400 @@ -64,6 +65,9 @@ struct radv_iface_config u32 max_ra_int; u32 min_delay; + u32 linger_time; /* How long a dead prefix should still be advertised with 0 + lifetime */ + u8 rdnss_local; /* Global list is not used for RDNSS */ u8 dnssl_local; /* Global list is not used for DNSSL */ @@ -75,7 +79,7 @@ struct radv_iface_config u32 current_hop_limit; u32 default_lifetime; u8 default_lifetime_sensitive; /* Whether default_lifetime depends on trigger */ - u8 default_preference; /* Default Router Preference (RFC 4191) */ + u8 default_preference; /* Default Router Preference (RFC 4191) */ }; struct radv_prefix_config @@ -118,6 +122,19 @@ struct radv_proto u8 active; /* Whether radv is active w.r.t. triggers */ }; +struct radv_prefix /* One prefix we advertise */ +{ + node n; + net_addr_ip6 prefix; + + u8 alive; /* Is the prefix alive? If not, we advertise it + with 0 lifetime, so clients stop using it */ + u8 mark; /* A temporary mark for processing */ + btime expires; /* The time when we drop this prefix from + advertising. It is valid only if !alive. */ + struct radv_prefix_config *cf; /* The config tied to this prefix */ +}; + struct radv_iface { node n; @@ -125,6 +142,9 @@ struct radv_iface struct radv_iface_config *cf; /* Related config, must be updated in reconfigure */ struct iface *iface; struct ifa *addr; /* Link-local address of iface */ + struct pool *pool; /* A pool for interface-specific things */ + list prefixes; /* The prefixes we advertise (struct radv_prefix) */ + btime prefix_expires; /* When the soonest prefix expires (0 = none dead) */ timer *timer; struct object_lock *lock; @@ -132,12 +152,13 @@ struct radv_iface btime last; /* Time of last sending of RA */ u16 plen; /* Length of prepared RA in tbuf, or 0 if not valid */ - byte initial; /* List of active ifaces */ + byte initial; /* How many RAs are still to be sent as initial */ }; #define RA_EV_INIT 1 /* Switch to initial mode */ #define RA_EV_CHANGE 2 /* Change of options or prefixes */ #define RA_EV_RS 3 /* Received RS */ +#define RA_EV_GC 4 /* Internal garbage collection of prefixes */ /* Default Router Preferences (RFC 4191) */ #define RA_PREF_LOW 0x18 diff --git a/sysdep/bsd/krt-sock.c b/sysdep/bsd/krt-sock.c index 08cfea39..c36ae558 100644 --- a/sysdep/bsd/krt-sock.c +++ b/sysdep/bsd/krt-sock.c @@ -165,7 +165,7 @@ struct ks_msg { struct rt_msghdr rtm; struct sockaddr_storage buf[RTAX_MAX]; -}; +} PACKED; #define ROUNDUP(a) \ ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) diff --git a/sysdep/linux/netlink.c b/sysdep/linux/netlink.c index 6cea2fa9..279f3c9c 100644 --- a/sysdep/linux/netlink.c +++ b/sysdep/linux/netlink.c @@ -83,22 +83,26 @@ struct rtvia { /* * Structure nl_parse_state keeps state of received route processing. Ideally, * we could just independently parse received Netlink messages and immediately - * propagate received routes to the rest of BIRD, but Linux kernel represents - * and announces IPv6 ECMP routes not as one route with multiple next hops (like - * RTA_MULTIPATH in IPv4 ECMP), but as a set of routes with the same prefix. + * propagate received routes to the rest of BIRD, but older Linux kernel (before + * version 4.11) represents and announces IPv6 ECMP routes not as one route with + * multiple next hops (like RTA_MULTIPATH in IPv4 ECMP), but as a sequence of + * routes with the same prefix. More recent kernels work as with IPv4. * * Therefore, BIRD keeps currently processed route in nl_parse_state structure * and postpones its propagation until we expect it to be final; i.e., when * non-matching route is received or when the scan ends. When another matching * route is received, it is merged with the already processed route to form an * ECMP route. Note that merging is done only for IPv6 (merge == 1), but the - * postponing is done in both cases (for simplicity). All IPv4 routes are just - * considered non-matching. + * postponing is done in both cases (for simplicity). All IPv4 routes or IPv6 + * routes with RTA_MULTIPATH set are just considered non-matching. * * This is ignored for asynchronous notifications (every notification is handled * as a separate route). It is not an issue for our routes, as we ignore such * notifications anyways. But importing alien IPv6 ECMP routes does not work - * properly. + * properly with older kernels. + * + * Whatever the kernel version is, IPv6 ECMP routes are sent as multiple routes + * for the same prefix. */ struct nl_parse_state @@ -348,6 +352,12 @@ static struct nl_want_attrs nexthop_attr_want4[BIRD_RTA_MAX] = { [RTA_ENCAP] = { 1, 0, 0 }, }; +static struct nl_want_attrs nexthop_attr_want6[BIRD_RTA_MAX] = { + [RTA_GATEWAY] = { 1, 1, sizeof(ip6_addr) }, + [RTA_ENCAP_TYPE]= { 1, 1, sizeof(u16) }, + [RTA_ENCAP] = { 1, 0, 0 }, +}; + static struct nl_want_attrs encap_mpls_want[BIRD_RTA_MAX] = { [RTA_DST] = { 1, 0, 0 }, }; @@ -374,6 +384,7 @@ static struct nl_want_attrs rtm_attr_want6[BIRD_RTA_MAX] = { [RTA_PRIORITY] = { 1, 1, sizeof(u32) }, [RTA_PREFSRC] = { 1, 1, sizeof(ip6_addr) }, [RTA_METRICS] = { 1, 0, 0 }, + [RTA_MULTIPATH] = { 1, 0, 0 }, [RTA_FLOW] = { 1, 1, sizeof(u32) }, [RTA_TABLE] = { 1, 1, sizeof(u32) }, [RTA_ENCAP_TYPE]= { 1, 1, sizeof(u16) }, @@ -631,7 +642,7 @@ nl_add_multipath(struct nlmsghdr *h, uint bufsize, struct nexthop *nh, int af) } static struct nexthop * -nl_parse_multipath(struct krt_proto *p, struct rtattr *ra) +nl_parse_multipath(struct krt_proto *p, struct rtattr *ra, int af) { /* Temporary buffer for multicast nexthops */ static struct nexthop *nh_buffer; @@ -670,7 +681,22 @@ nl_parse_multipath(struct krt_proto *p, struct rtattr *ra) /* Nonexistent RTNH_PAYLOAD ?? */ nl_attr_len = nh->rtnh_len - RTNH_LENGTH(0); - nl_parse_attrs(RTNH_DATA(nh), nexthop_attr_want4, a, sizeof(a)); + switch (af) + { + case AF_INET: + if (!nl_parse_attrs(RTNH_DATA(nh), nexthop_attr_want4, a, sizeof(a))) + return NULL; + break; + + case AF_INET6: + if (!nl_parse_attrs(RTNH_DATA(nh), nexthop_attr_want6, a, sizeof(a))) + return NULL; + break; + + default: + return NULL; + } + if (a[RTA_GATEWAY]) { rv->gw = rta_get_ipa(a[RTA_GATEWAY]); @@ -1520,9 +1546,9 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h) case RTN_UNICAST: ra->dest = RTD_UNICAST; - if (a[RTA_MULTIPATH] && (i->rtm_family == AF_INET)) - { - struct nexthop *nh = nl_parse_multipath(p, a[RTA_MULTIPATH]); + if (a[RTA_MULTIPATH]) + { + struct nexthop *nh = nl_parse_multipath(p, a[RTA_MULTIPATH], i->rtm_family); if (!nh) { log(L_ERR "KRT: Received strange multipath route %N", net->n.addr); @@ -1699,8 +1725,10 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h) /* * Ideally, now we would send the received route to the rest of kernel code. - * But IPv6 ECMP routes are sent as a sequence of routes, so we postpone it - * and merge next hops until the end of the sequence. + * But IPv6 ECMP routes before 4.11 are sent as a sequence of routes, so we + * postpone it and merge next hops until the end of the sequence. Note that + * proper multipath updates are rejected by nl_mergable_route(), so it is + * always the first case for them. */ if (!s->net) |