diff options
201 files changed, 26475 insertions, 10182 deletions
@@ -1,3 +1,4 @@ +D doc/prog-head.sgml C doc C nest C conf @@ -5,3 +6,4 @@ C filter C proto C sysdep C lib +D doc/prog-foot.sgml diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 00000000..25af140c --- /dev/null +++ b/Makefile.in @@ -0,0 +1,193 @@ +# Makefile for the BIRD Internet Routing Daemon +# (c) 1999--2000 Martin Mares <mj@ucw.cz> +# (c) 2016 Jan Moskyto Matejka <mq@ucw.cz> + +# Disable build-in rules +MAKEFLAGS += -r + +# Variable definitions +CPPFLAGS=-I$(objdir) -I$(srcdir) @CPPFLAGS@ +CFLAGS=$(CPPFLAGS) @CFLAGS@ +LDFLAGS=@LDFLAGS@ +LIBS=@LIBS@ +DAEMON_LIBS=@DAEMON_LIBS@ +CLIENT_LIBS=@CLIENT_LIBS@ +CC=@CC@ +M4=@M4@ +BISON=@BISON@ +FLEX=@FLEX@ +RANLIB=@RANLIB@ +INSTALL=@INSTALL@ +INSTALL_PROGRAM=@INSTALL_PROGRAM@ +INSTALL_DATA=@INSTALL_DATA@ + +client=$(addprefix $(exedir)/,@CLIENT@) +daemon=$(exedir)/bird +protocols=@protocols@ + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +bindir=@bindir@ +sbindir=@sbindir@ +sysconfdir=@sysconfdir@ +localstatedir=@localstatedir@ +docdir=@prefix@/doc + +srcdir := @srcdir@ +objdir := @objdir@ +exedir := @exedir@ + +ifeq ($(objdir),.) + objdir := $(realpath .) +endif + +ifeq ($(VERBOSE),) + E:=@ + Q:=@ +else + E:=@\# + Q:= +endif + +# Meta rules +docgoals := docs userdocs progdocs +testgoals := check test tests tests_run +cleangoals := clean distclean testsclean +.PHONY: all daemon cli $(docgoals) $(testgoals) $(cleangoals) tags +all: daemon cli + +daemon: $(daemon) +cli: $(client) + +$(daemon): LIBS += $(DAEMON_LIBS) + +# Include directories +dirs := client conf doc filter lib nest test $(addprefix proto/,$(protocols)) @sysdep_dirs@ + +conf-y-targets := $(addprefix $(objdir)/conf/,cf-parse.y keywords.h commands.h) +cf-local = $(conf-y-targets): $(s)config.Y + +src-o-files = $(patsubst %.c,$(o)%.o,$(src)) +tests-target-files = $(patsubst %.c,$(o)%,$(tests_src)) + +all-daemon = $(daemon): $(obj) +all-client = $(client): $(obj) + +s = $(dir $(lastword $(MAKEFILE_LIST))) +ifeq ($(srcdir),.) + o = $(objdir)/$(s) +else + o = $(patsubst $(srcdir)%,$(objdir)%,$(s)) +endif + +define clean_in = +clean:: + rm -f $(addprefix $(o),$(1)) +endef + +clean = $(eval $(call clean_in,$(1))) + +include $(addsuffix /Makefile,$(addprefix $(srcdir)/,$(dirs))) + +# Generic rules + +$(objdir)/%.o: $(srcdir)/%.c $(objdir)/.dir-stamp $(objdir)/sysdep/paths.h + $(E)echo CC -o $@ -c $< + $(Q)$(CC) $(CFLAGS) -MMD -MP -o $@ -c $< + +$(objdir)/%.o: $(objdir)/%.c $(objdir)/.dir-stamp $(objdir)/sysdep/paths.h + $(E)echo CC -o $@ -c $< + $(Q)$(CC) $(CFLAGS) -MMD -MP -o $@ -c $< + + +$(objdir)/%.S: $(srcdir)/%.c $(objdir)/.dir-stamp $(objdir)/sysdep/paths.h + $(E)echo CC -o $@ -S $< + $(Q)$(CC) $(CFLAGS) -MMD -MP -o $@ -S $< + +$(objdir)/%.S: $(objdir)/%.c $(objdir)/.dir-stamp $(objdir)/sysdep/paths.h + $(E)echo CC -o $@ -S $< + $(Q)$(CC) $(CFLAGS) -MMD -MP -o $@ -S $< + + + +$(objdir)/.dir-stamp: + $(E)echo MKDIR -p $(addprefix $(objdir)/,$(dirs) doc) + $(Q)mkdir -p $(addprefix $(objdir)/,$(dirs) doc) + $(Q)touch $@ + +$(client) $(daemon): + $(E)echo LD $(LDFLAGS) -o $@ $^ $(LIBS) + $(Q)$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +$(objdir)/sysdep/paths.h: Makefile + echo >$@ "/* Generated by Makefile, don't edit manually! */" + echo >>$@ "#define PATH_CONFIG_FILE \"@CONFIG_FILE@\"" + echo >>$@ "#define PATH_CONTROL_SOCKET \"@CONTROL_SOCKET@\"" + if test -n "@iproutedir@" ; then echo >>$@ "#define PATH_IPROUTE_DIR \"@iproutedir@\"" ; fi + +# Unit tests rules + +tests_targets_ok = $(addsuffix .ok,$(tests_targets)) + +$(tests_targets): %: %.o $(tests_objs) + $(E)echo LD $(LDFLAGS) -o $@ $^ $(LIBS) + $(Q)$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +$(tests_targets_ok): %.ok: % + $(Q)$* 2>/dev/null && touch $*.ok + +test: testsclean check +check: tests tests_run +tests: $(tests_targets) +tests_run: $(tests_targets_ok) + +# Finally include the computed dependencies + +ifneq ($(filter-out $(cleangoals),$(MAKECMDGOALS)),) +-include $(shell find $(objdir) -name "*.d") +endif + +ifeq ($(MAKECMDGOALS),) +-include $(shell find $(objdir) -name "*.d") +endif + +tags: + cd $(srcdir) ; etags -lc `find $(dirs) -name *.[chY]` + +# Install + +install: all + $(INSTALL) -d $(DESTDIR)/$(sbindir) $(DESTDIR)/$(sysconfdir) $(DESTDIR)/@runtimedir@ + $(INSTALL_PROGRAM) $(exedir)/bird $(DESTDIR)/$(sbindir)/bird + $(INSTALL_PROGRAM) $(exedir)/birdcl $(DESTDIR)/$(sbindir)/birdcl + if test -n "@CLIENT@" ; then \ + $(INSTALL_PROGRAM) $(exedir)/birdc $(DESTDIR)/$(sbindir)/birdc ; \ + fi + if ! test -f $(DESTDIR)/@CONFIG_FILE@ ; then \ + $(INSTALL_DATA) $(srcdir)/doc/bird.conf.example $(DESTDIR)/@CONFIG_FILE@ ; \ + else \ + echo "Not overwriting old bird.conf" ; \ + fi + +install-docs: + $(INSTALL) -d $(DESTDIR)/$(docdir) + $(INSTALL_DATA) $(objdir)/doc/{bird,prog}{,-*}.html $(DESTDIR)/$(docdir)/ + +# Cleanup +clean:: + rm -f $(objdir)/sysdep/paths.h + rm -f $(addprefix $(exedir)/,bird birdc birdcl) + find $(objdir) -name "*.[od]" -exec rm -f '{}' '+' + +testsclean: + rm -f $(tests_targets_ok) + +ifeq ($(objdir),obj) +distclean: clean + rm -rf $(objdir) + rm -f config.log config.status configure Makefile +else +distclean: clean + rm -rf * .dir-stamp + rm -f config.log config.status configure Makefile +endif @@ -1,3 +1,47 @@ +Version 2.0.0-pre1 (2017-04-29) + o Support for MPLS next hops + o VPNv4 and VPNv6 network types + o BGP with MPLS labels (RFC 3107) + o BGP MPLS/VPN support (RFC 4364) + o BGP 6PE - IPv6 NLRI over IPv4 MPLS (RFC 4798) + o BGP IPv4 NLRI with an IPv6 Next Hop (RFC 5549) + o BGP Confederations (RFC 5065) + o BGP: Simplify igp table options + o BGP: Allow exchanging LOCAL_PREF with eBGP peers + o BGP: Allow to specify interface for regular sessions + o Babel support restored + o Static: Minor overhaul + o Netlink: Default kernel metric changed to 32 + o Flowspec: Limit tcp mask length to 12 bits + o Update of show route command + + Notes: + + Definitions of OSPFv2, OSPFv3 and RIP NG protocols now use keywords + 'ospf v2', 'ospf v3' and 'rip ng' instead of 'ospf2', 'ospf3' and 'ripng'. + + Flows and ROAs no longer use phony next hops, so there is no need to use + 'drop' or 'unreachable' in their static route definitions. + + See doc/bird.conf.example2 for configuration examples. + + +Version 2.0.0-pre0 (2016-12-07) + o Integrated IPv4 + IPv6 design + o Major BGP protocol redesign + o BGP multicast support (SAFI 2) + o BGP flowspec support (RFC 5575) + o New RPKI-Router protocol + o New build system + o Unit tests + + Notes: + + Protocols and tables are now connected using explicit channels, most related + protocol options (table, import, export, ...) are now channel options. See + doc/bird.conf.example2 for configuration examples. + + Version 1.6.3 (2016-12-21) o Large BGP communities o BFD authentication (MD5, SHA1) @@ -1,44 +1,57 @@ /* - * This is an example configuration file. + * This is a simple example configuration file with no aim for completeness. + * See documentation for full description. */ -# Yet another comment - +# Router ID in IPv4 format router id 62.168.0.1; -define xyzzy = (120+10); +# Load device information from kernel. +protocol device {} -protocol device { +# Generate direct routes for interfaces. Useful on BSD. +protocol direct { + ipv4; disabled; -# interface "eth*", "ppp*"; } protocol direct { + ipv6; + disabled; } +# Feed routes to kernel FIB protocol kernel { - disabled; + ipv4 { export all; }; # learn; # Learn all routes from the kernel # scan time 10; # Scan kernel tables every 10 seconds } -protocol static { -# disabled; +protocol kernel { + ipv6; +} - route fec0:2::/64 blackhole; - route fec0:3::/64 unreachable; - route fec0:4::/64 prohibit; +# Static route feed +protocol static { + ipv4 { export all; }; + route 10.0.0.0/24 via 55.55.55.44; + route 10.10.0.0/16 blackhole; + route 10.20.0.0/20 unreachable; + route 10.30.50.0/28 prohibit; +} -# route 0.0.0.0/0 via 195.113.31.113; -# route 62.168.0.0/25 unreachable; -# route 1.2.3.4/32 via 195.113.31.124; -# route 10.0.0.0/8 unreachable; -# route 10.1.1.0:255.255.255.0 via 62.168.0.3; -# route 10.1.2.0:255.255.255.0 via 62.168.0.3; -# route 10.1.3.0:255.255.255.0 via 62.168.0.4; -# route 10.2.0.0/24 via "arc0"; - export all; +protocol static { + ipv6 { export all; }; + route 2001:db8:1::/48 via 5555::6666; + route 2001:db8:2::/48 blackhole; + route 2001:db8:3::/48 prohibit; + route 2001:db8:4::/48 unreachable; } protocol rip { + ipv4; +} + +protocol ripng { + ipv6; } diff --git a/client/Makefile b/client/Makefile index a1578766..fccb8346 100644 --- a/client/Makefile +++ b/client/Makefile @@ -1,11 +1,11 @@ -source=commands.c util.c client.c -root-rel=../ -dir-name=client +src := commands.c util.c client.c +obj := $(src-o-files) -clients := $(client) birdcl +$(all-client) -source-dep := $(source) $(addsuffix .c,$(clients)) +$(o)commands.o: $(objdir)/conf/commands.h -subdir: $(addsuffix .o,$(clients)) +$(exedir)/birdc: $(o)birdc.o +$(exedir)/birdc: LIBS += $(CLIENT_LIBS) -include ../Rules +$(exedir)/birdcl: $(o)birdcl.o diff --git a/client/birdc.c b/client/birdc.c index 8aa01c17..f1aea2fe 100644 --- a/client/birdc.c +++ b/client/birdc.c @@ -29,6 +29,9 @@ static int prompt_active; extern int _rl_vis_botlin; extern void _rl_move_vert(int); +#define HISTORY "/.birdc_history" +static char *history_file; + static void add_history_dedup(char *cmd) { @@ -138,8 +141,24 @@ input_help(int arg, int key UNUSED) } void +history_init(void) +{ + const char *homedir = getenv("HOME"); + if (!homedir) + homedir = "."; + history_file = malloc(strlen(homedir) + sizeof(HISTORY)); + if (!history_file) + die("couldn't alloc enough memory for history file name"); + + sprintf(history_file, "%s%s", homedir, HISTORY); + read_history(history_file); +} + +void input_init(void) { + if (interactive) + history_init(); rl_readline_name = "birdc"; rl_add_defun("bird-complete", input_complete, '\t'); rl_add_defun("bird-help", input_help, '?'); @@ -217,5 +236,7 @@ cleanup(void) return; input_hide(); + if (interactive) + write_history(history_file); rl_callback_handler_remove(); } diff --git a/client/commands.c b/client/commands.c index 29e71418..f2134c1b 100644 --- a/client/commands.c +++ b/client/commands.c @@ -7,8 +7,8 @@ */ #include <stdio.h> -#include <ctype.h> #include <stdlib.h> +#include <ctype.h> #include "nest/bird.h" #include "lib/resource.h" diff --git a/conf/Makefile b/conf/Makefile index cd78c821..fb3dd052 100644 --- a/conf/Makefile +++ b/conf/Makefile @@ -1,33 +1,33 @@ -source=cf-parse.tab.c cf-lex.c conf.c -root-rel=../ +src := cf-parse.tab.c cf-lex.c conf.c +obj := $(src-o-files) -include ../Rules +$(all-daemon) -conf-src=$(srcdir)/conf -conf-fragments=$(conf-src)/confbase.Y @CONFS@ $(addsuffix /config.Y,$(static-dir-paths)) +tests_objs := $(tests_objs) $(src-o-files) ifdef DEBUG BISON_DEBUG=-t #FLEX_DEBUG=-d endif -cf-parse.tab.h: cf-parse.tab.c +$(conf-y-targets): $(s)confbase.Y $(s)flowspec.Y + $(M4) -P $| $^ >$@ -cf-parse.tab.c: cf-parse.y - $(BISON) -bcf-parse -dv -pcf_ $(BISON_DEBUG) cf-parse.y +$(o)cf-parse.y: | $(s)gen_parser.m4 +$(o)keywords.h: | $(s)gen_keywords.m4 +$(o)commands.h: | $(s)gen_commands.m4 $(srcdir)/client/cmds.m4 -cf-parse.y: $(conf-fragments) $(conf-src)/gen_parser.m4 - $(M4) -P $(conf-src)/gen_parser.m4 $(conf-fragments) >cf-parse.y +$(o)cf-parse.tab.h: $(o)cf-parse.tab.c -keywords.h: $(conf-fragments) $(conf-src)/gen_keywords.m4 - $(M4) -P $(conf-src)/gen_keywords.m4 $(conf-fragments) >keywords.h +$(o)cf-parse.tab.c: $(o)cf-parse.y + $(BISON) $(BISON_DEBUG) -dv -pcf_ -b $(@:.tab.c=) $< -commands.h: $(conf-fragments) $(conf-src)/gen_commands.m4 $(srcdir)/client/cmds.m4 - $(M4) -P $(conf-src)/gen_commands.m4 $(srcdir)/client/cmds.m4 $(conf-fragments) | sort >commands.h +$(o)cf-lex.c: $(s)cf-lex.l + $(FLEX) $(FLEX_DEBUG) -s -B -8 -Pcf_ -o$@ $< -cf-lex.c: cf-lex.l - $(FLEX) $(FLEX_DEBUG) -s -B -8 -ocf-lex.c -Pcf_ cf-lex.l +$(o)cf-lex.o: $(o)cf-parse.tab.h $(o)keywords.h +$(o)cf-lex.o: CFLAGS+=-Wno-sign-compare -Wno-unused-function -depend: keywords.h commands.h cf-parse.tab.c cf-lex.c +$(addprefix $(o), cf-parse.y keywords.h commands.h cf-parse.tab.h cf-parse.tab.c cf-lex.c): $(objdir)/.dir-stamp -cf-lex.o: CFLAGS+=-Wno-sign-compare -Wno-unused-function +$(call clean,cf-parse.tab.h cf-parse.tab.c cf-parse.y keywords.h commands.h cf-lex.c cf-parse.output) diff --git a/conf/cf-lex.l b/conf/cf-lex.l index e9e385a6..a0e3c275 100644 --- a/conf/cf-lex.l +++ b/conf/cf-lex.l @@ -123,28 +123,101 @@ include ^{WHITE}*include{WHITE}*\".*\"{WHITE}*; cf_include(start, end-start); } +{DIGIT}+:{DIGIT}+ { + unsigned long int l, len1 UNUSED, len2; + char *e; + + errno = 0; + l = strtoul(yytext, &e, 10); + if (e && (*e != ':') || (errno == ERANGE) || (l >> 32)) + cf_error("ASN out of range"); + + if (l >> 16) + { + len1 = 32; + len2 = 16; + cf_lval.i64 = (2ULL << 48) | (((u64) l) << len2); + } + else + { + len1 = 16; + len2 = 32; + cf_lval.i64 = 0 | (((u64) l) << len2); + } + + errno = 0; + l = strtoul(e+1, &e, 10); + if (e && *e || (errno == ERANGE) || (l >> len2)) + cf_error("Number out of range"); + cf_lval.i64 |= l; + + return VPN_RD; +} + +[02]:{DIGIT}+:{DIGIT}+ { + unsigned long int l, len1, len2; + char *e; + + if (yytext[0] == '0') + { + cf_lval.i64 = 0; + len1 = 16; + len2 = 32; + } + else + { + cf_lval.i64 = 2ULL << 48; + len1 = 32; + len2 = 16; + } + + errno = 0; + l = strtoul(yytext+2, &e, 10); + if (e && (*e != ':') || (errno == ERANGE) || (l >> len1)) + cf_error("ASN out of range"); + cf_lval.i64 |= ((u64) l) << len2; + + errno = 0; + l = strtoul(e+1, &e, 10); + if (e && *e || (errno == ERANGE) || (l >> len2)) + cf_error("Number out of range"); + cf_lval.i64 |= l; + + return VPN_RD; +} + +{DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+:{DIGIT}+ { + unsigned long int l; + ip4_addr ip4; + char *e; + + cf_lval.i64 = 1ULL << 48; + + e = strchr(yytext, ':'); + *e++ = '\0'; + if (!ip4_pton(yytext, &ip4)) + cf_error("Invalid IPv4 address %s in Route Distinguisher", yytext); + cf_lval.i64 |= ((u64) ip4_to_u32(ip4)) << 16; + + errno = 0; + l = strtoul(e, &e, 10); + if (e && *e || (errno == ERANGE) || (l >> 16)) + cf_error("Number out of range"); + cf_lval.i64 |= l; + + return VPN_RD; +} + {DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+ { - ip4_addr a; - if (!ip4_pton(yytext, &a)) + if (!ip4_pton(yytext, &cf_lval.ip4)) cf_error("Invalid IPv4 address %s", yytext); - -#ifdef IPV6 - cf_lval.i32 = ip4_to_u32(a); - return RTRID; -#else - cf_lval.a = ipa_from_ip4(a); - return IPA; -#endif + return IP4; } ({XIGIT}*::|({XIGIT}*:){3,})({XIGIT}*|{DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+) { -#ifdef IPV6 - if (ipa_pton(yytext, &cf_lval.a)) - return IPA; - cf_error("Invalid IPv6 address %s", yytext); -#else - cf_error("This is an IPv4 router, therefore IPv6 addresses are not supported"); -#endif + if (!ip6_pton(yytext, &cf_lval.ip6)) + cf_error("Invalid IPv6 address %s", yytext); + return IP6; } 0x{XIGIT}+ { @@ -215,6 +288,7 @@ else: { ["][^"\n]*["] { yytext[yyleng-1] = 0; cf_lval.t = cfg_strdup(yytext+1); + yytext[yyleng-1] = '"'; return TEXT; } @@ -689,8 +763,6 @@ cf_symbol_class_name(struct symbol *sym) return "filter"; case SYM_TABLE: return "routing table"; - case SYM_ROA: - return "ROA table"; default: return "unknown type"; } diff --git a/conf/conf.c b/conf/conf.c index 0a4e3f8c..9978ef23 100644 --- a/conf/conf.c +++ b/conf/conf.c @@ -52,10 +52,11 @@ #include "lib/resource.h" #include "lib/string.h" #include "lib/event.h" -#include "lib/timer.h" +#include "sysdep/unix/timer.h" #include "conf/conf.h" #include "filter/filter.h" + static jmp_buf conf_jmpbuf; struct config *config, *new_config; @@ -85,10 +86,10 @@ int undo_available; /* Undo was not requested from last reconfiguration */ * further use. Returns a pointer to the structure. */ struct config * -config_alloc(const byte *name) +config_alloc(const char *name) { pool *p = rp_new(&root_pool, "Config"); - linpool *l = lp_new(p, 4080); + linpool *l = lp_new_default(p); struct config *c = lp_allocz(l, sizeof(struct config)); /* Duplication of name string in local linear pool */ @@ -96,6 +97,7 @@ config_alloc(const byte *name) char *ndup = lp_allocu(l, nlen); memcpy(ndup, name, nlen); + init_list(&c->tests); c->mrtdump_file = -1; /* Hack, this should be sysdep-specific */ c->pool = p; c->mem = l; @@ -135,15 +137,16 @@ config_parse(struct config *c) sysdep_preconfig(c); protos_preconfig(c); rt_preconfig(c); - roa_preconfig(c); cf_parse(); - protos_postconfig(c); + if (EMPTY_LIST(c->protos)) cf_error("No protocol is specified in the config file"); -#ifdef IPV6 + + /* if (!c->router_id) - cf_error("Router ID must be configured manually on IPv6 routers"); -#endif + cf_error("Router ID must be configured manually"); + */ + done = 1; cleanup: @@ -266,7 +269,6 @@ config_do_commit(struct config *c, int type) force_restart |= global_commit(c, old_config); DBG("rt_commit\n"); rt_commit(c, old_config); - roa_commit(c, old_config); DBG("protos_commit\n"); protos_commit(c, old_config, force_restart, type); @@ -504,7 +506,7 @@ order_shutdown(void) * error in the configuration. */ void -cf_error(char *msg, ...) +cf_error(const char *msg, ...) { char buf[1024]; va_list args; diff --git a/conf/conf.h b/conf/conf.h index 41cb434f..4b245e00 100644 --- a/conf/conf.h +++ b/conf/conf.h @@ -9,8 +9,10 @@ #ifndef _BIRD_CONF_H_ #define _BIRD_CONF_H_ +#include "sysdep/config.h" +#include "lib/ip.h" #include "lib/resource.h" -#include "lib/timer.h" +#include "sysdep/unix/timer.h" /* Configuration structure */ @@ -20,12 +22,12 @@ struct config { linpool *mem; /* Linear pool containing configuration data */ list protos; /* Configured protocol instances (struct proto_config) */ list tables; /* Configured routing tables (struct rtable_config) */ - list roa_tables; /* Configured ROA tables (struct roa_table_config) */ list logfiles; /* Configured log files (sysdep) */ + list tests; /* Configured unit tests (f_bt_test_suite) */ int mrtdump_file; /* Configured MRTDump file (sysdep, fd in unix) */ char *syslog_name; /* Name used for syslog (NULL -> no syslog) */ - struct rtable_config *master_rtc; /* Configuration of master routing table */ + struct rtable_config *def_tables[NET_MAX]; /* Default routing tables for each network */ struct iface_patt *router_id_from; /* Configured list of router ID iface patterns */ u32 router_id; /* Our Router ID */ @@ -61,7 +63,7 @@ struct config { extern struct config *config; /* Currently active configuration */ extern struct config *new_config; /* Configuration being parsed */ -struct config *config_alloc(const byte *name); +struct config *config_alloc(const char *name); int config_parse(struct config *); int cli_parse(struct config *); void config_free(struct config *); @@ -69,7 +71,7 @@ int config_commit(struct config *, int type, int timeout); int config_confirm(void); int config_undo(void); void config_init(void); -void cf_error(char *msg, ...) NORET; +void cf_error(const char *msg, ...) NORET; void config_add_obstacle(struct config *); void config_del_obstacle(struct config *); void order_shutdown(void); @@ -121,7 +123,6 @@ struct symbol { #define SYM_FUNCTION 3 #define SYM_FILTER 4 #define SYM_TABLE 5 -#define SYM_ROA 6 #define SYM_VARIABLE 0x100 /* 0x100-0x1ff are variable types */ #define SYM_CONSTANT 0x200 /* 0x200-0x2ff are variable types */ @@ -163,6 +164,7 @@ static inline int cf_symbol_is_constant(struct symbol *sym) /* Parser */ +extern char *cf_text; int cf_parse(void); /* Sysdep hooks */ diff --git a/conf/confbase.Y b/conf/confbase.Y index 96b32028..47ee07ce 100644 --- a/conf/confbase.Y +++ b/conf/confbase.Y @@ -14,7 +14,7 @@ CF_HDR #include "conf/conf.h" #include "lib/resource.h" #include "lib/socket.h" -#include "lib/timer.h" +#include "sysdep/unix/timer.h" #include "lib/string.h" #include "nest/protocol.h" #include "nest/iface.h" @@ -38,10 +38,16 @@ CF_DECLS %union { int i; u32 i32; + u64 i64; ip_addr a; + ip4_addr ip4; + ip6_addr ip6; + net_addr net; + net_addr *net_ptr; struct symbol *s; char *t; struct rtable_config *r; + struct channel_config *cc; struct f_inst *x; struct filter *f; struct f_tree *e; @@ -50,35 +56,38 @@ CF_DECLS struct f_path_mask *h; struct password_item *p; struct rt_show_data *ra; - struct roa_show_data *ro; struct sym_show_data *sd; struct lsadb_show_data *ld; struct iface *iface; - struct roa_table *rot; void *g; bird_clock_t time; - struct prefix px; + struct f_prefix px; struct proto_spec ps; + struct channel_limit cl; struct timeformat *tf; + mpls_label_stack *mls; } %token END CLI_MARKER INVALID_TOKEN ELSECOL DDOT %token GEQ LEQ NEQ AND OR %token PO PC %token <i> NUM ENUM -%token <i32> RTRID -%token <a> IPA +%token <ip4> IP4 +%token <ip6> IP6 +%token <i64> VPN_RD %token <s> SYM %token <t> TEXT %type <iface> ipa_scope -%type <i> expr bool pxlen +%type <i> expr bool pxlen4 %type <i32> expr_us %type <time> datetime %type <a> ipa -%type <px> prefix prefix_or_ipa -%type <t> text -%type <t> text_or_none +%type <net> net_ip4_ net_ip6_ net_ip6 net_ip_ net_ip net_or_ipa +%type <net_ptr> net_ net_any net_vpn4_ net_vpn6_ net_vpn_ net_roa4_ net_roa6_ net_roa_ +%type <mls> label_stack_start label_stack + +%type <t> text opttext %nonassoc PREFIX_DUMMY %left AND OR @@ -88,7 +97,7 @@ CF_DECLS %left '!' %nonassoc '.' -CF_KEYWORDS(DEFINE, ON, OFF, YES, NO, S, MS, US, PORT) +CF_KEYWORDS(DEFINE, ON, OFF, YES, NO, S, MS, US, PORT, VPN) CF_GRAMMAR @@ -133,8 +142,6 @@ expr_us: | expr US { $$ = (u32) $1 * 1; } ; -/* expr_u16: expr { check_u16($1); $$ = $1; }; */ - /* Switches */ bool: @@ -146,13 +153,15 @@ bool: | /* Silence means agreement */ { $$ = 1; } ; -/* Addresses, prefixes and netmasks */ + +/* Addresses */ ipa: - IPA + IP4 { $$ = ipa_from_ip4($1); } + | IP6 { $$ = ipa_from_ip6($1); } | SYM { if ($1->class != (SYM_CONSTANT | T_IP)) cf_error("IP address expected"); - $$ = SYM_VAL($1).px.ip; + $$ = SYM_VAL($1).ip; } ; @@ -161,29 +170,137 @@ ipa_scope: | '%' SYM { $$ = if_get_by_name($2->name); } ; -prefix: - ipa pxlen { - if (!ip_is_prefix($1, $2)) cf_error("Invalid prefix"); - $$.addr = $1; $$.len = $2; + +/* Networks - internal */ + +pxlen4: + '/' NUM { + if ($2 < 0 || $2 > IP4_MAX_PREFIX_LENGTH) cf_error("Invalid prefix length %d", $2); + $$ = $2; + } + | ':' IP4 { + $$ = ip4_masklen($2); + if ($$ == 255) cf_error("Invalid netmask %I4", $2); } ; -prefix_or_ipa: - prefix - | ipa { $$.addr = $1; $$.len = BITS_PER_IP_ADDRESS; } +net_ip4_: IP4 pxlen4 +{ + net_fill_ip4(&($$), $1, $2); + if (!net_validate_ip4((net_addr_ip4 *) &($$))) + cf_error("Invalid IPv4 prefix"); +}; + +net_ip6_: IP6 '/' NUM +{ + net_fill_ip6(&($$), $1, $3); + if ($3 < 0 || $3 > IP6_MAX_PREFIX_LENGTH) + cf_error("Invalid prefix length %d", $3); + if (!net_validate_ip6((net_addr_ip6 *) &($$))) + cf_error("Invalid IPv6 prefix"); +}; + +net_vpn4_: VPN_RD net_ip4_ +{ + $$ = cfg_alloc(sizeof(net_addr_vpn4)); + net_fill_vpn4($$, net4_prefix(&$2), net4_pxlen(&$2), $1); +} + +net_vpn6_: VPN_RD net_ip6_ +{ + $$ = cfg_alloc(sizeof(net_addr_vpn6)); + net_fill_vpn6($$, net6_prefix(&$2), net6_pxlen(&$2), $1); +} + +net_roa4_: net_ip4_ MAX NUM AS NUM +{ + $$ = cfg_alloc(sizeof(net_addr_roa4)); + net_fill_roa4($$, net4_prefix(&$1), net4_pxlen(&$1), $3, $5); + if ($3 < (int) net4_pxlen(&$1) || $3 > IP4_MAX_PREFIX_LENGTH) + cf_error("Invalid max prefix length %d", $3); +}; + +net_roa6_: net_ip6_ MAX NUM AS NUM +{ + $$ = cfg_alloc(sizeof(net_addr_roa6)); + net_fill_roa6($$, net6_prefix(&$1), net6_pxlen(&$1), $3, $5); + if ($3 < (int) net6_pxlen(&$1) || $3 > IP6_MAX_PREFIX_LENGTH) + cf_error("Invalid max prefix length %d", $3); +}; + +net_ip_: net_ip4_ | net_ip6_ ; +net_vpn_: net_vpn4_ | net_vpn6_ ; +net_roa_: net_roa4_ | net_roa6_ ; + +net_: + net_ip_ { $$ = cfg_alloc($1.length); net_copy($$, &($1)); } + | net_vpn_ + | net_roa_ + | net_flow_ ; -pxlen: - '/' expr { - if ($2 < 0 || $2 > BITS_PER_IP_ADDRESS) cf_error("Invalid prefix length %d", $2); - $$ = $2; + +/* Networks - regular */ + +net_ip6: + net_ip6_ + | SYM { + if (($1->class != (SYM_CONSTANT | T_NET)) || (SYM_VAL($1).net->type != NET_IP6)) + cf_error("IPv6 network expected"); + $$ = * SYM_VAL($1).net; + } + ; + +net_ip: + net_ip_ + | SYM { + if (($1->class != (SYM_CONSTANT | T_NET)) || !net_is_ip(SYM_VAL($1).net)) + cf_error("IP network expected"); + $$ = * SYM_VAL($1).net; + } + ; + +net_any: + net_ + | SYM { + if ($1->class != (SYM_CONSTANT | T_NET)) + cf_error("Network expected"); + $$ = (net_addr *) SYM_VAL($1).net; /* Avoid const warning */ } - | ':' ipa { - $$ = ipa_masklen($2); - if ($$ < 0) cf_error("Invalid netmask %I", $2); + ; + +net_or_ipa: + net_ip4_ + | net_ip6_ + | IP4 { net_fill_ip4(&($$), $1, IP4_MAX_PREFIX_LENGTH); } + | IP6 { net_fill_ip6(&($$), $1, IP6_MAX_PREFIX_LENGTH); } + | SYM { + if ($1->class == (SYM_CONSTANT | T_IP)) + net_fill_ip_host(&($$), SYM_VAL($1).ip); + else if (($1->class == (SYM_CONSTANT | T_NET)) && net_is_ip(SYM_VAL($1).net)) + $$ = * SYM_VAL($1).net; + else + cf_error("IP address or network expected"); } ; +label_stack_start: NUM +{ + $$ = cfg_allocz(sizeof(mpls_label_stack)); + $$->len = 1; + $$->stack[0] = $1; +}; + +label_stack: + label_stack_start + | label_stack '/' NUM { + if ($1->len >= MPLS_MAX_LABEL_STACK) + cf_error("Too many labels in stack"); + $1->stack[$1->len++] = $3; + $$ = $1; + } +; + datetime: TEXT { $$ = tm_parse_datetime($1); @@ -200,11 +317,12 @@ text: } ; -text_or_none: - TEXT { $$ = $1; } - | { $$ = NULL; } +opttext: + TEXT + | /* empty */ { $$ = NULL; } ; + CF_CODE CF_END diff --git a/conf/flowspec.Y b/conf/flowspec.Y new file mode 100644 index 00000000..a47d453b --- /dev/null +++ b/conf/flowspec.Y @@ -0,0 +1,219 @@ +/* + * BIRD -- Flow specification (RFC 5575) grammar + * + * (c) 2016 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +CF_HDR + +#define PARSER 1 + +#include "nest/bird.h" +#include "conf/conf.h" +#include "lib/resource.h" +#include "lib/socket.h" +#include "sysdep/unix/timer.h" +#include "lib/string.h" +#include "nest/protocol.h" +#include "nest/iface.h" +#include "nest/route.h" +#include "nest/cli.h" +#include "filter/filter.h" +#include "lib/flowspec.h" + + +CF_DEFINES + +struct flow_builder *this_flow; + + +CF_DECLS + +%type <i32> flow_num_op flow_srcdst flow_logic_op flow_num_type_ flow_frag_val flow_neg +%type <net_ptr> net_flow4_ net_flow6_ net_flow_ + +CF_KEYWORDS(FLOW4, FLOW6, DST, SRC, PROTO, NEXT, HEADER, DPORT, SPORT, ICMP, + TYPE, CODE, TCP, FLAGS, LENGTH, DSCP, DONT_FRAGMENT, IS_FRAGMENT, + FIRST_FRAGMENT, LAST_FRAGMENT, FRAGMENT, LABEL, OFFSET) + + +CF_GRAMMAR + +/* Network Flow Specification */ + +flow_num_op: + TRUE { $$ = 0b000; } + | '=' { $$ = 0b001; } + | NEQ { $$ = 0b110; } + | '<' { $$ = 0b100; } + | LEQ { $$ = 0b101; } + | '>' { $$ = 0b010; } + | GEQ { $$ = 0b011; } + | FALSE { $$ = 0b111; } + ; + +flow_logic_op: + OR { $$ = 0x00; } + | AND { $$ = 0x40; } + ; + +flow_num_type_: + PROTO { $$ = FLOW_TYPE_IP_PROTOCOL; } + | NEXT HEADER { $$ = FLOW_TYPE_NEXT_HEADER; } + | PORT { $$ = FLOW_TYPE_PORT; } + | DPORT { $$ = FLOW_TYPE_DST_PORT; } + | SPORT { $$ = FLOW_TYPE_SRC_PORT; } + | ICMP TYPE { $$ = FLOW_TYPE_ICMP_TYPE; } + | ICMP CODE { $$ = FLOW_TYPE_ICMP_CODE; } + | LENGTH { $$ = FLOW_TYPE_PACKET_LENGTH; } + | DSCP { $$ = FLOW_TYPE_DSCP; } + ; + +flow_num_type: flow_num_type_{ flow_builder_set_type(this_flow, $1); }; +flow_flag_type: TCP FLAGS { flow_builder_set_type(this_flow, FLOW_TYPE_TCP_FLAGS); }; +flow_frag_type: FRAGMENT { flow_builder_set_type(this_flow, FLOW_TYPE_FRAGMENT); }; +flow_label_type: LABEL { flow_builder_set_type(this_flow, FLOW_TYPE_LABEL); }; + +flow_srcdst: + DST { $$ = FLOW_TYPE_DST_PREFIX; } + | SRC { $$ = FLOW_TYPE_SRC_PREFIX; } + ; + +flow_num_opts: + flow_num_op expr { + flow_check_cf_value_length(this_flow, $2); + flow_builder_add_op_val(this_flow, $1, $2); + } + | flow_num_opts flow_logic_op flow_num_op expr { + flow_check_cf_value_length(this_flow, $4); + flow_builder_add_op_val(this_flow, $2 | $3, $4); + } + | flow_num_opt_ext + | flow_num_opts OR flow_num_opt_ext + ; + +flow_num_opt_ext_expr: + expr { + flow_check_cf_value_length(this_flow, $1); + flow_builder_add_op_val(this_flow, 0b001, $1); + } + | expr DDOT expr { + flow_check_cf_value_length(this_flow, $1); + flow_check_cf_value_length(this_flow, $3); + flow_builder_add_op_val(this_flow, 0b011, $1); /* >= */ + flow_builder_add_op_val(this_flow, 0x40 | 0b101, $3); /* AND <= */ + } + ; + +flow_num_opt_ext: + flow_num_opt_ext_expr + | flow_num_opt_ext ',' flow_num_opt_ext_expr + ; + +flow_bmk_opts: + flow_neg expr '/' expr { + flow_check_cf_bmk_values(this_flow, $1, $2, $4); + flow_builder_add_val_mask(this_flow, $1, $2, $4); + } + | flow_bmk_opts flow_logic_op flow_neg expr '/' expr { + flow_check_cf_bmk_values(this_flow, $3, $4, $6); + flow_builder_add_val_mask(this_flow, $2 | $3, $4, $6); + } + | flow_bmk_opts ',' flow_neg expr '/' expr { + flow_check_cf_bmk_values(this_flow, $3, $4, $6); + flow_builder_add_val_mask(this_flow, 0x40 | $3, $4, $6); /* AND */ + } + ; + +flow_neg: + /* empty */ { $$ = 0x00; } + | '!' { $$ = 0x02; } + ; + +flow_frag_val: + DONT_FRAGMENT { $$ = 1; } + | IS_FRAGMENT { $$ = 2; } + | FIRST_FRAGMENT { $$ = 4; } + | LAST_FRAGMENT { $$ = 8; } + ; + +flow_frag_opts: + flow_neg flow_frag_val { + flow_builder_add_val_mask(this_flow, 0, ($1 ? 0 : $2), $2); + } + | flow_frag_opts flow_logic_op flow_neg flow_frag_val { + flow_builder_add_val_mask(this_flow, $2, ($3 ? 0 : $4), $4); + } + | flow_frag_opts ',' flow_neg flow_frag_val { + flow_builder_add_val_mask(this_flow, 0x40, ($3 ? 0 : $4), $4); /* AND */ + } + ; + +flow4_item: + flow_srcdst net_ip { + flow_builder_set_type(this_flow, $1); + flow_builder4_add_pfx(this_flow, (net_addr_ip4 *) &($2)); + } + | flow_num_type flow_num_opts + | flow_flag_type flow_bmk_opts + | flow_frag_type flow_frag_opts + ; + +flow6_item: + flow_srcdst net_ip6 { + flow_builder_set_type(this_flow, $1); + flow_builder6_add_pfx(this_flow, (net_addr_ip6 *) &($2), 0); + } + | flow_srcdst net_ip6 OFFSET NUM { + if ($4 > $2.pxlen) + cf_error("Prefix offset is higher than prefix length"); + flow_builder_set_type(this_flow, $1); + flow_builder6_add_pfx(this_flow, (net_addr_ip6 *) &($2), $4); + } + | flow_num_type flow_num_opts + | flow_flag_type flow_bmk_opts + | flow_frag_type flow_frag_opts + | flow_label_type flow_bmk_opts + ; + +flow4_opts: + /* empty */ + | flow4_opts flow4_item ';' + ; + +flow6_opts: + /* empty */ + | flow6_opts flow6_item ';' + ; + +flow_builder_init: +{ + if (this_flow == NULL) + this_flow = flow_builder_init(&root_pool); + else + flow_builder_clear(this_flow); +}; + +flow_builder_set_ipv4: { this_flow->ipv6 = 0; }; +flow_builder_set_ipv6: { this_flow->ipv6 = 1; }; + +net_flow4_: FLOW4 '{' flow_builder_init flow_builder_set_ipv4 flow4_opts '}' +{ + $$ = (net_addr *) flow_builder4_finalize(this_flow, cfg_mem); + flow4_validate_cf((net_addr_flow4 *) $$); +}; + +net_flow6_: FLOW6 '{' flow_builder_init flow_builder_set_ipv6 flow6_opts '}' +{ + $$ = (net_addr *) flow_builder6_finalize(this_flow, cfg_mem); + flow6_validate_cf((net_addr_flow6 *) $$); +}; + +net_flow_: net_flow4_ | net_flow6_ ; + + +CF_CODE + +CF_END diff --git a/configure.ac b/configure.ac index 91f89edf..9ae119b4 100644 --- a/configure.ac +++ b/configure.ac @@ -18,12 +18,6 @@ AC_ARG_ENABLE([debug], [enable_debug=no] ) -AC_ARG_ENABLE([ipv6], - [AS_HELP_STRING([--enable-ipv6], [enable building of IPv6 version @<:@no@:>@])], - [], - [enable_ipv6=no] -) - AC_ARG_ENABLE([memcheck], [AS_HELP_STRING([--enable-memcheck], [check memory allocations when debugging @<:@yes@:>@])], [], @@ -36,17 +30,18 @@ AC_ARG_ENABLE([pthreads], [enable_pthreads=try] ) +AC_ARG_ENABLE([libssh], + [AS_HELP_STRING([--enable-libssh], [enable LibSSH support together with RPKI @<:@try@:>@])], + [], + [enable_libssh=try] +) + AC_ARG_WITH([protocols], [AS_HELP_STRING([--with-protocols=LIST], [include specified routing protocols @<:@all@:>@])], [], [with_protocols="all"] ) -AC_ARG_WITH([suffix], - [AS_HELP_STRING([--with-suffix=STRING], [use specified suffix for BIRD files @<:@6 for IPv6@:>@])], - [given_suffix="yes"] -) - AC_ARG_WITH([sysconfig], [AS_HELP_STRING([--with-sysconfig=FILE], [use specified BIRD system configuration file])], [] @@ -71,48 +66,25 @@ AC_ARG_VAR([M4], [location of the M4 program]) if test "$srcdir" = . ; then # Building in current directory => create obj directory holding all objects objdir=obj - mkdir -p obj - srcdir_rel=.. - makefiles="Makefile:tools/Makefile-top.in obj/Makefile:tools/Makefile.in obj/Rules:tools/Rules.in" - exedir=.. else # Building in separate directory objdir=. - srcdir_rel=$srcdir - makefiles="Makefile:tools/Makefile.in Rules:tools/Rules.in" - exedir=. fi -case $srcdir_rel in - /*) srcdir_rel_mf=$srcdir_rel ;; - *) srcdir_rel_mf="\$(root-rel)$srcdir_rel" ;; -esac +exedir=. AC_SUBST([objdir]) AC_SUBST([exedir]) -AC_SUBST([srcdir_rel_mf]) +AC_SUBST([srcdir]) AC_SUBST([runtimedir]) -if test "$enable_ipv6" = yes ; then - ip=ipv6 - SUFFIX=6 - proto_radv=radv -else - ip=ipv4 - SUFFIX="" -fi - -if test "$given_suffix" = yes ; then - SUFFIX="$with_suffix" -fi -AC_SUBST([SUFFIX]) if test "$enable_debug" = yes ; then - CONFIG_FILE="bird$SUFFIX.conf" - CONTROL_SOCKET="bird$SUFFIX.ctl" + CONFIG_FILE="bird.conf" + CONTROL_SOCKET="bird.ctl" else - CONFIG_FILE="\$(sysconfdir)/bird$SUFFIX.conf" - CONTROL_SOCKET="$runtimedir/bird$SUFFIX.ctl" + CONFIG_FILE="\$(sysconfdir)/bird.conf" + CONTROL_SOCKET="$runtimedir/bird.ctl" fi AC_SUBST([CONFIG_FILE]) AC_SUBST([CONTROL_SOCKET]) @@ -130,6 +102,7 @@ if test "$ac_test_CFLAGS" != set ; then fi AC_PROG_CC +AC_PROG_CC_C99 if test -z "$GCC" ; then AC_MSG_ERROR([This program requires the GNU C Compiler.]) fi @@ -195,54 +168,33 @@ if test -n "$with_sysconfig" -a "$with_sysconfig" != no ; then elif test -f sysconfig.h ; then sysdesc=sysconfig else - case "$ip:$host_os" in - ipv6:linux*) - sysdesc=linux-v6 - default_iproutedir="/etc/iproute2" - ;; - ipv4:linux*) + case "$host_os" in + linux*) sysdesc=linux default_iproutedir="/etc/iproute2" ;; - ipv6:netbsd*) - sysdesc=bsd-v6 - CPPFLAGS="$CPPFLAGS -I/usr/pkg/include" - LDFLAGS="$LDFLAGS -L/usr/pkg/lib -R/usr/pkg/lib" - ;; - ipv4:netbsd*) + freebsd*) sysdesc=bsd - CPPFLAGS="$CPPFLAGS -I/usr/pkg/include" - LDFLAGS="$LDFLAGS -L/usr/pkg/lib -R/usr/pkg/lib" ;; - ipv6:freebsd*) - sysdesc=bsd-v6 - ;; - ipv4:freebsd*) + kfreebsd*) sysdesc=bsd ;; - ipv6:dragonfly*) - sysdesc=bsd-v6 - ;; - ipv4:dragonfly*) + netbsd*) sysdesc=bsd + CPPFLAGS="$CPPFLAGS -I/usr/pkg/include" + LDFLAGS="$LDFLAGS -L/usr/pkg/lib -R/usr/pkg/lib" ;; - ipv6:kfreebsd*) - sysdesc=bsd-v6 - ;; - ipv4:kfreebsd*) + openbsd*) sysdesc=bsd ;; - ipv6:openbsd*) - sysdesc=bsd-v6 - ;; - ipv4:openbsd*) + dragonfly*) sysdesc=bsd ;; *) AC_MSG_ERROR([Cannot determine correct system configuration. Please use --with-sysconfig to set it manually.]) ;; - esac - sysdesc=$srcdir/sysdep/cf/$sysdesc.h + esac + sysdesc=$srcdir/sysdep/cf/$sysdesc.h fi AC_MSG_CHECKING([which OS configuration should we use]) AC_MSG_RESULT([$sysdesc]) @@ -253,7 +205,7 @@ sysname=`echo $sysdesc | sed 's/\.h$//'` AC_DEFINE_UNQUOTED([SYSCONF_INCLUDE], ["$sysdesc"], [Which sysdep header to include]) AC_MSG_CHECKING([system-dependent directories]) -sysdep_dirs="`sed <$sysdesc '/^Link: /!d;s/^Link: \(.*\)$/\1/' | tr '\012' ' '` lib" +sysdep_dirs="`sed <$sysdesc '/^Link: /!d;s/^Link: \(.*\)$/\1/' | tr '\012' ' '`" AC_MSG_RESULT([$sysdep_dirs]) AC_SUBST([sysdep_dirs]) @@ -266,10 +218,29 @@ fi AC_SUBST([iproutedir]) -all_protocols="$proto_bfd bgp ospf pipe $proto_radv rip static" -if test "$ip" = ipv6 ; then - all_protocols="$all_protocols babel" +DAEMON_LIBS= +AC_SUBST(DAEMON_LIBS) + +if test "$enable_libssh" != no ; then + AC_CHECK_HEADER([libssh/libssh.h], [ ], [fail=yes], [ ]) + AC_CHECK_LIB([ssh], [ssh_connect], [ ], [fail=yes]) + + if test "$fail" != yes ; then + AC_DEFINE([HAVE_LIBSSH], [1], [Define to 1 if you have the `ssh' library (-lssh).]) + DAEMON_LIBS="-lssh $DAEMON_LIBS" + proto_rpki=rpki + enable_libssh=yes + else + if test "$enable_libssh" = yes ; then + AC_MSG_ERROR([LibSSH not available.]) + else + enable_libssh=no + fi + fi fi + +all_protocols="$proto_bfd babel bgp ospf pipe radv rip $proto_rpki static" + all_protocols=`echo $all_protocols | sed 's/ /,/g'` if test "$with_protocols" = all ; then @@ -283,6 +254,7 @@ AH_TEMPLATE([CONFIG_OSPF], [OSPF protocol]) AH_TEMPLATE([CONFIG_PIPE], [Pipe protocol]) AH_TEMPLATE([CONFIG_RADV], [RAdv protocol]) AH_TEMPLATE([CONFIG_RIP], [RIP protocol]) +AH_TEMPLATE([CONFIG_RPKI], [RPKI protocol]) AH_TEMPLATE([CONFIG_STATIC], [Static protocol]) AC_MSG_CHECKING([protocols]) @@ -299,7 +271,7 @@ AC_MSG_RESULT([ok]) AC_SUBST([protocols]) case $sysdesc in - */linux*|*/linux-v6*) + */linux*) AC_CHECK_HEADER([linux/rtnetlink.h], [], [AC_MSG_ERROR([Appropriate version of Linux kernel headers not found.])], @@ -315,6 +287,18 @@ esac AC_CHECK_HEADERS_ONCE([alloca.h syslog.h]) AC_CHECK_MEMBERS([struct sockaddr.sa_len], [], [], [#include <sys/socket.h>]) +AC_CHECK_HEADERS([linux/lwtunnel.h], + [AC_DEFINE([HAVE_LWTUNNEL], [1], [Define to 1 if you have the <linux/lwtunnel.h> header file.])], + [], + [] dnl Force new AC_CHECK_HEADER semantics +) + +AC_CHECK_MEMBERS([struct rtvia.rtvia_family], + [AC_DEFINE([HAVE_STRUCT_RTVIA], [1], [Define to 1 if you have rtvia structure.])], + [], + [#include <linux/rtnetlink.h>] +) + 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])], @@ -323,6 +307,19 @@ AC_C_BIGENDIAN( if test "$enable_debug" = yes ; then AC_DEFINE([DEBUGGING], [1], [Define to 1 if debugging is enabled]) + LDFLAGS="$LDFLAGS -rdynamic" + CFLAGS="$CFLAGS -O0 -ggdb -g3 -gdwarf-4" + + AC_CHECK_HEADER([execinfo.h], + [ + AC_DEFINE([HAVE_EXECINFO_H], [1], [Define to 1 if you have the <execinfo.h> header file.]) + AC_SEARCH_LIBS([backtrace], [execinfo], + [] + [AC_MSG_ERROR([Function backtrace not available.])] + ) + ] + ) + if test "$enable_memcheck" = yes ; then AC_CHECK_LIB([dmalloc], [dmalloc_debug]) if test $ac_cv_lib_dmalloc_dmalloc_debug != yes ; then @@ -331,10 +328,10 @@ if test "$enable_debug" = yes ; then fi fi -CLIENT= +CLIENT=birdcl CLIENT_LIBS= if test "$enable_client" = yes ; then - CLIENT=birdc + CLIENT="$CLIENT birdc" BASE_LIBS="$LIBS" LIBS="" @@ -387,20 +384,9 @@ AC_SUBST([CLIENT_LIBS]) mkdir -p $objdir/sysdep AC_CONFIG_HEADERS([$objdir/sysdep/autoconf.h:sysdep/autoconf.h.in]) -AC_CONFIG_COMMANDS([merge], - [ export CPP="$CPP"; $srcdir/tools/mergedirs $srcdir $srcdir_rel $objdir $sysdep_dirs ], - [ - srcdir=$srcdir - srcdir_rel=$srcdir_rel - objdir=$objdir - sysdep_dirs="$sysdep_dirs" - ] -) -AC_CONFIG_FILES([$makefiles]) +AC_CONFIG_FILES([Makefile:Makefile.in]) AC_OUTPUT -rm -f $objdir/sysdep/paths.h - AC_MSG_RESULT() AC_MSG_RESULT([BIRD was configured with the following options:]) AC_MSG_RESULT([ Source directory: $srcdir]) @@ -411,4 +397,5 @@ AC_MSG_RESULT([ Debugging: $enable_debug]) AC_MSG_RESULT([ POSIX threads: $enable_pthreads]) AC_MSG_RESULT([ Routing protocols: $protocols]) AC_MSG_RESULT([ Client: $enable_client]) + rm -f $objdir/.*-stamp diff --git a/doc/LinuxDocTools.pm b/doc/LinuxDocTools.pm index 51d4a04c..39bb401d 100644 --- a/doc/LinuxDocTools.pm +++ b/doc/LinuxDocTools.pm @@ -372,6 +372,8 @@ sub process_file } } # + + local $ENV{PATH} = "$ENV{PATH}:/usr/lib/linuxdoc-tools"; my($precmd) = "|sgmlpre output=$global->{format} $global->{define}"; # diff --git a/doc/Makefile b/doc/Makefile index 70b73943..f36642be 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -1,50 +1,49 @@ -root-rel=../ -dir-name=doc - -ifneq ($(wildcard ../Rules),) -include ../Rules -else -srcdir=$(shell cd $(root-rel) ; pwd) -srcdir_abs=$(srcdir) -endif - # Force rebuilds -.PHONY: prog.sgml bird.sgml +.PHONY: progspell docs progdocs userdocs + +doc-srcdir := $(shell cd $(s) && pwd) +sgml2 := $(doc-srcdir)/sgml2 docs: progdocs userdocs -progdocs: prog.html prog.ps -userdocs: bird.html bird.pdf -prog.sgml: - $(srcdir)/tools/progdoc $(srcdir_abs) +doc-fmt = $(1): $(o)prog.$(1) $(o)bird.$(1) +$(call doc-fmt,html) +$(call doc-fmt,dvi) +$(call doc-fmt,ps) +$(call doc-fmt,pdf) -%.html: %.sgml - ./sgml2html $< +progdocs: $(o)prog.html $(o)prog.pdf +userdocs: $(o)bird.html $(o)bird.pdf +progspell: $(o)prog.spell -%.dvi: %.tex - latex $< - latex $< +$(o)prog.sgml: $(srcdir)/tools/progdoc $(objdir)/.dir-stamp + $(srcdir)/tools/progdoc $(srcdir) $@ -%.ps: %.dvi - dvips -D600 -ta4 -o $@ $< +$(o)%.sgml: $(s)%.sgml $(objdir)/.dir-stamp + cp $< $@ -%.pdf: %.tex - pdflatex $< - pdflatex $< +$(o)%.html: $(o)%.sgml + cd $(dir $@) && $(sgml2)html $(notdir $<) -%.tex: %.sgml - ./sgml2latex --output=tex $< +$(o)%.tex: $(o)%.sgml + cd $(dir $@) && $(sgml2)latex --output=tex $(notdir $<) + +$(o)%.dvi: $(o)%.tex + cd $(dir $@) && TEXINPUTS=$(TEXINPUTS):$(doc-srcdir)/tex latex $(notdir $<) + cd $(dir $@) && TEXINPUTS=$(TEXINPUTS):$(doc-srcdir)/tex latex $(notdir $<) + +$(o)%.ps: $(o)%.dvi + dvips -D600 -ta4 -o $@ $< -%.txt: %.sgml - ./sgml2txt $< +$(o)%.pdf: $(o)%.tex + TEXINPUTS=$(TEXINPUTS):$(doc-srcdir)/tex pdflatex -output-directory=$(dir $@) $< + TEXINPUTS=$(TEXINPUTS):$(doc-srcdir)/tex pdflatex -output-directory=$(dir $@) $< -progspell: prog.sgml - sed -f prog-spell.sed <prog.sgml >prog.spell - ispell prog.spell +$(o)%.txt: $(o)%.sgml + cd $(dir $@) && $(sgml2)txt $(notdir $<) -clean: - rm -f *.tex *.dvi *.log *.txt *.aux *.toc *.spell - rm -f prog.sgml +$(o)prog.spell: $(o)prog.sgml $(s)prog-spell.sed + sed -f $(lastword $^) <$< >$@ + ispell $@ -distclean: clean - rm -f *.html *.ps +$(call clean,prog.spell $(addprefix *.,html dvi ps pdf sgml tex txt aux log toc)) diff --git a/doc/bird.conf.example b/doc/bird.conf.example index bbfe0020..62c65ce9 100644 --- a/doc/bird.conf.example +++ b/doc/bird.conf.example @@ -1,5 +1,6 @@ /* - * This is an example configuration file. + * This is an example configuration file + * (for version 1.x.x, obsolete) */ # Yes, even shell-like comments work... diff --git a/doc/bird.conf.example2 b/doc/bird.conf.example2 new file mode 100644 index 00000000..d7048b64 --- /dev/null +++ b/doc/bird.conf.example2 @@ -0,0 +1,341 @@ +/* + * This is an example configuration file for MB-BGP setting + */ + + +log "bird.log" all; +# debug protocols all; + +router id 192.168.1.1; + +ipv4 table master4; +ipv6 table master6; + +ipv4 table mcast4; +ipv6 table mcast6; + +ipv4 table mtab4; +ipv6 table mtab6; + +vpn4 table vpntab4; +vpn6 table vpntab6; + +vpn4 table vpn4mc; +vpn6 table vpn6mc; + +flow4 table flowtab4; +flow6 table flowtab6; + + +protocol device { + scan time 10; +} + +protocol kernel kernel4 { + scan time 20; + + ipv4 { + export all; + }; +} + +protocol kernel kernel6 { + scan time 20; + + ipv6 { + export all; + }; +} + + +protocol static static4 { + ipv4; + + route 10.10.0.0/24 via 192.168.1.2; + route 10.10.1.0/24 via 192.168.1.2 { bgp_large_community.add((10,20,30)); bgp_large_community.add((10,(20*3),10)); }; +} + +protocol static static6 { + ipv6; + + route 2001:db8:10:10::/64 via 2001:db8:1:1::10; + route 2001:db8:10:11::/64 via 2001:db8:1:1::10; + + route 2001:db8:1:1::/64 via fe80::ec9b:67ff:fe60:fd5d % ve1; +} + +# VPNv4 routes with MPLS labels +protocol static statvpn4 { + vpn4; + + route 10:10 10.20.0.0/24 via 192.168.1.2 mpls 210; + route 10:10 10.20.1.0/24 via 192.168.1.2 mpls 210; + route 10:20 10.20.0.0/24 via 192.168.1.2 mpls 220; + route 10:20 10.20.1.0/24 via 192.168.1.2 mpls 220; +} + +protocol static statvpn6 { + vpn6; + + route 10:10 2001:db8:20:10::/64 via 2001:db8:1:1::10 mpls 200/210; + route 10:10 2001:db8:20:11::/64 via 2001:db8:1:1::10 mpls 200/210; + route 10:20 2001:db8:20:10::/64 via 2001:db8:1:1::10 mpls 200/220; + route 10:20 2001:db8:20:11::/64 via 2001:db8:1:1::10 mpls 200/220; +} + +# RFC 5575 flow specification +protocol static flowstat4 { + flow4; + + route flow4 { + dst 10.0.0.0/8; + proto = 23; + dport > 24 && < 30 || 40..50,60..70,80; + sport > 24 && < 30 || = 40 || 50,60..70,80; + icmp type 80; + icmp code 90; + tcp flags 0x03/0x0f; + length 2048..65535; + dscp = 63; + fragment dont_fragment, is_fragment || !first_fragment; + }; + + route flow4 { + dst 11.0.0.0/8; + proto = 0x12; + sport > 0x5678 && < 0x9abc || 0xdef0 || 0x1234,0x5678,0x9abc..0xdef0; + dport = 50; + tcp flags 0x000/0xf00; + }; + + route flow4 { + dst 12.0.0.0/32; + tcp flags ! 0/0x999; + }; + + route flow4 { + dst 220.0.254.0/24; + tcp flags 0x99/0x999; + }; + + route flow4 { + dst 220.0.254.192/28; + tcp flags ! 0xfff/0xfff; + }; + + route flow4 { + dst 15.0.0.0/8; + tcp flags ! 0x999/0x999; + }; +} + +protocol static flowstat6 { + flow6; + + route flow6 { + dst fec0:1122:3344:5566::1/128; + src 0000:0000:0000:0001:1234:5678:9800:0000/101 offset 63; + next header = 23; + sport 24..30, 42 || 50,60,70..80; + dport = 50; + tcp flags 0x03/0x0f, !0/0xff || 0x33/0x33; + fragment !is_fragment || !first_fragment; + label 0xaaaa/0xaaaa && 0x33/0x33; + }; + + route flow6 { + dst fec0:1122:3344:5566::1/128; + src ::1:1234:5678:9800:0/101 offset 63; + next header = 23; + dport = 50; + sport > 24 && < 30 || = 40 || = 50 || = 60 || >= 70 && <= 80; + tcp flags 0x3/0x3 && 0x0/0xc; + }; +} + + +protocol pipe { + table master4; + peer table mcast4; + import none; + export where source = RTS_OSPF; +} + +protocol pipe { + table master6; + peer table mcast6; + import none; + export where source = RTS_OSPF; +} + +protocol ospf v2 ospf4 { +# ecmp; + + ipv4 { + import all; +# export where source = RTS_STATIC; + }; + + area 0 { + interface "ve0" { stub; }; + interface "ve1" { hello 5; type ptp; }; + interface "ve2" { hello 5; type bcast; ttl security; }; + interface "ve3" { hello 5; type bcast; ttl security; }; + }; +} + + +protocol ospf v3 ospf6 { +# ecmp; + + ipv6 { + import all; +# export where source = RTS_STATIC; + }; + + area 0 { + interface "ve0" { stub; }; + interface "ve1" { hello 5; type ptp; }; + interface "ve2" { hello 5; type bcast; }; + }; +} + +protocol bgp { + local 192.168.11.1 as 1000; + neighbor 192.168.11.2 as 2000; +# local 192.168.1.1 as 1000; +# neighbor 192.168.2.1 as 2000; +# multihop; +# rr client; +# strict bind; +# debug all; + + # regular IPv4 unicast (1/1) + ipv4 { + # connects to master4 table by default + import all; + export where source ~ [ RTS_STATIC, RTS_BGP ]; + }; + + # regular IPv6 unicast (2/1) + ipv6 { + # connects to master6 table by default + import all; + export where source ~ [ RTS_STATIC, RTS_BGP ]; +# next hop address 2001:db8:1:1::1; + }; + + # IPv4 multicast topology (1/2) + ipv4 multicast { + # explicit IPv4 table + table mcast4; + import all; + export all; + }; + + # IPv6 multicast topology (2/2) + ipv6 multicast { + # explicit IPv6 table + table mcast6; + import all; + export all; +# next hop address 2001:db8:1:1::1; + }; + + # IPv4 with MPLS labels (1/4) + ipv4 mpls { + # explicit IPv4 table + table mtab4; + import all; + export all; + }; + + # IPv6 with MPLS labels (2/4) + ipv6 multicast { + # explicit IPv6 table + table mtab6; + import all; + export all; + # allows IPv4 next hops (6PE) + # extended next hop; + }; + + # VPNv4 with MPLS labels (1/128) + vpn4 mpls { + # connects to vpntab4 table by default + import all; + export all; + }; + + # VPNv6 with MPLS labels (2/128) + vpn6 mpls { + # connects to vpntab6 table by default + import all; + export all; + }; + + # VPNv4 multicast topology (1/129) + vpn4 multicast { + table vpn4mc; + import all; + export all; + }; + + # VPNv6 multicast topology (2/129) + vpn6 multicast { + table vpn6mc; + import all; + export all; + }; + + # IPv4 Flowspec (1/133) + flow4 { + # connects to flowtab4 table by default + import all; + export all; + }; + + # IPv6 Flowspec (2/133) + flow6 { + # connects to flowtab6 table by default + import all; + export all; + }; +} + +protocol bgp { + local 192.168.1.1 as 1000; + neighbor 192.168.3.1 as 1000; + multihop; + rr client; + + ipv4 { + import all; + export where source ~ [ RTS_STATIC, RTS_BGP ]; + }; + + ipv6 { + import all; + export where source ~ [ RTS_STATIC, RTS_BGP ]; + next hop address 2001:db8:1:1::1; + }; +} + +protocol bgp { + local 2001:db8:1:1::1 as 1000; + neighbor 2001:db8:4:1::1 as 1000; + multihop; + rr client; + + ipv4 { + import all; + export where source ~ [ RTS_STATIC, RTS_BGP ]; + next hop address 192.168.4.1; + }; + + ipv6 { + import all; + export where source ~ [ RTS_STATIC, RTS_BGP ]; + }; +} + diff --git a/doc/bird.sgml b/doc/bird.sgml index ffd28964..01b59c6a 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -715,6 +715,138 @@ agreement"). </descrip> + +<sect>Flowspec network type +<label id="flowspec-network-type"> + +<p>The flow specification are rules for routers and firewalls for filtering +purpose. It is described by <rfc id="5575">. There are 3 types of arguments: +<m/inet4/ or <m/inet6/ prefixes, bitmasks matching expressions and numbers +matching expressions. + +Bitmasks matching is written using <m/value/<cf>/</cf><m/mask/ or +<cf/!/<m/value/<cf>/</cf><m/mask/ pairs. It means that <cf/(/<m/data/ <cf/&/ +<m/mask/<cf/)/ is or is not equal to <m/value/. + +Numbers matching is a matching sequence of numbers and ranges separeted by a +commas (<cf/,/) (e.g. <cf/10,20,30/). Ranges can be written using double dots +<cf/../ notation (e.g. <cf/80..90,120..124/). An alternative notation are +sequence of one or more pairs of relational operators and values separated by +logical operators <cf/&&/ or <cf/||/. Allowed relational operators are <cf/=/, +<cf/!=/, <cf/</, <cf/<=/, <cf/>/, <cf/>=/, <cf/true/ and <cf/false/. + +<sect1>IPv4 Flowspec + +<p><descrip> + <tag><label id="flow-dst">dst <m/inet4/</tag> + Set a matching destination prefix (e.g. <cf>dst 192.168.0.0/16</cf>). + Only this option is mandatory in IPv4 Flowspec. + + <tag><label id="flow-src">src <m/inet4/</tag> + Set a matching source prefix (e.g. <cf>src 10.0.0.0/8</cf>). + + <tag><label id="flow-proto">proto <m/numbers-match/</tag> + Set a matching IP protocol numbers (e.g. <cf/proto 6/). + + <tag><label id="flow-port">port <m/numbers-match/</tag> + Set a matching source or destination TCP/UDP port numbers (e.g. + <cf>port 1..1023,1194,3306</cf>). + + <tag><label id="flow-dport">dport <m/numbers-match/</tag> + Set a mating destination port numbers (e.g. <cf>dport 49151</cf>). + + <tag><label id="flow-sport">sport <m/numbers-match/</tag> + Set a matching source port numbers (e.g. <cf>sport = 0</cf>). + + <tag><label id="flow-icmp-type">icmp type <m/numbers-match/</tag> + Set a matching type field number of an ICMP packet (e.g. <cf>icmp type + 3</cf>) + + <tag><label id="flow-icmp-code">icmp code <m/numbers-match/</tag> + Set a matching code field number of an ICMP packet (e.g. <cf>icmp code + 1</cf>) + + <tag><label id="flow-tcp-flags">tcp flags <m/bitmask-match/</tag> + Set a matching bitmask for TCP header flags (aka control bits) (e.g. + <cf>tcp flags 0x03/0x0f;</cf>). The maximum length of mask is 12 bits + (0xfff). + + <tag><label id="flow-length">length <m/numbers-match/</tag> + Set a matching packet length (e.g. <cf>length > 1500;</cf>) + + <tag><label id="flow-dscp">dscp <m/numbers-match/</tag> + Set a matching DiffServ Code Point number (e.g. <cf>length > 1500;</cf>). + + <tag><label id="flow-fragment">fragment <m/fragmentation-type/</tag> + Set a matching type of packet fragmentation. Allowed fragmentation + types are <cf/dont_fragment/, <cf/is_fragment/, <cf/first_fragment/, + <cf/last_fragment/ (e.g. <cf>fragment is_fragment && + !dont_fragment</cf>). +</descrip> + +<p><code> +protocol static { + flow4; + + route flow4 { + dst 10.0.0.0/8; + port > 24 && < 30 || 40..50,60..70,80 && >= 90; + tcp flags 0x03/0x0f; + length > 1024; + dscp = 63; + fragment dont_fragment, is_fragment || !first_fragment; + } drop; +} +</code> + +<sect1>Differences for IPv6 Flowspec + +<p>Flowspec IPv6 are same as Flowspec IPv4 with a few exceptions. +<itemize> + <item>Prefixes <m/inet6/ can be specified not only with prefix length, + but with prefix <cf/offset/ <m/num/ too (e.g. + <cf>::1234:5678:9800:0000/101 offset 64</cf>). Offset means to don't + care of <m/num/ first bits. + <item>IPv6 Flowspec hasn't mandatory any flowspec component. + <item>In IPv6 packets, there is a matching the last next header value + for a matching IP protocol number (e.g. <cf>next header 6</cf>). + <item>It is not possible to set <cf>dont_fragment</cf> as a type of + packet fragmentation. +</itemize> + +<p><descrip> + <tag><label id="flow6-dst">dst <m/inet6/ [offset <m/num/]</tag> + Set a matching destination IPv6 prefix (e.g. <cf>dst + ::1c77:3769:27ad:a11a/128 offset 64</cf>). + + <tag><label id="flow6-src">src <m/inet6/ [offset <m/num/]</tag> + Set a matching source IPv6 prefix (e.g. <cf>src fe80::/64</cf>). + + <tag><label id="flow6-next-header">next header <m/numbers-match/</tag> + Set a matching IP protocol numbers (e.g. <cf>next header != 6</cf>). + + <tag><label id="flow6-label">label <m/bitmask-match/</tag> + Set a 20-bit bitmask for matching Flow Label field in IPv6 packets + (e.g. <cf>label 0x8e5/0x8e5</cf>). +</descrip> + +<p><code> +protocol static { + flow6; + + route flow6 { + dst fec0:1122:3344:5566:7788:99aa:bbcc:ddee/128; + src 0000:0000:0000:0001:1234:5678:9800:0000/101 offset 63; + next header = 23; + sport > 24 && < 30 || = 40 || 50,60,70..80; + dport = 50; + tcp flags 0x03/0x0f, !0/0xff || 0x33/0x33; + fragment !is_fragment || !first_fragment; + label 0xaaaa/0xaaaa && 0x33/0x33; + } drop; +} +</code> + <chapt>Remote control <label id="remote-control"> @@ -793,9 +925,8 @@ This argument can be omitted if there exists only a single instance. Show the list of symbols defined in the configuration (names of protocols, routing tables etc.). - <tag><label id="cli-show-route">show route [[for] <m/prefix/|<m/IP/] [table <m/t/] [filter <m/f/|where <m/c/] [(export|preexport|noexport) <m/p/] [protocol <m/p/] [<m/options/]</tag> - Show contents of a routing table (by default of the main one or the - table attached to a respective protocol), that is routes, their metrics + <tag><label id="cli-show-route">show route [[for] <m/prefix/|<m/IP/] [table (<m/t/ | all)] [filter <m/f/|where <m/c/] [(export|preexport|noexport) <m/p/] [protocol <m/p/] [(stats|count)] [<m/options/]</tag> + Show contents of specified routing tables, that is routes, their metrics and (in case the <cf/all/ switch is given) all their attributes. <p>You can specify a <m/prefix/ if you want to print routes for a @@ -805,20 +936,31 @@ This argument can be omitted if there exists only a single instance. the selected one at the top, unless <cf/primary/ is given in which case only the selected route is shown. + <p>The <cf/show route/ command can process one or multiple routing + tables. The set of selected tables is determined on three levels: First, + tables can be explicitly selected by <cf/table/ switch, which could be + used multiple times, all tables are specified by <cf/table all/. Second, + tables can be implicitly selected by channels or protocols that are + arguments of several other switches (e.g., <cf/export/, <cf/protocol/). + Last, the set of default tables is used: <cf/master4/, <cf/master6/ and + each first table of any other network type. + <p>You can also ask for printing only routes processed and accepted by a given filter (<cf>filter <m/name/</cf> or <cf>filter { <m/filter/ } </cf> or matching a given condition (<cf>where <m/condition/</cf>). The <cf/export/, <cf/preexport/ and <cf/noexport/ switches ask for - printing of routes that are exported to the specified protocol. - With <cf/preexport/, the export filter of the protocol is skipped. - With <cf/noexport/, routes rejected by the export filter are printed - instead. Note that routes not exported to the protocol for other reasons + printing of routes that are exported to the specified protocol or + channel. With <cf/preexport/, the export filter of the channel is + skipped. With <cf/noexport/, routes rejected by the export filter are + printed instead. Note that routes not exported for other reasons (e.g. secondary routes or routes imported from that protocol) are not - printed even with <cf/noexport/. + printed even with <cf/noexport/. These switches also imply that + associated routing tables are selected instead of default ones. <p>You can also select just routes added by a specific protocol. - <cf>protocol <m/p/</cf>. + <cf>protocol <m/p/</cf>. This switch also implies that associated + routing tables are selected instead of default ones. <p>If BIRD is configured to keep filtered routes (see <cf/import keep filtered/ option), you can show them instead of routes by using @@ -828,27 +970,6 @@ This argument can be omitted if there exists only a single instance. number of networks, number of routes before and after filtering). If you use <cf/count/ instead, only the statistics will be printed. - <tag><label id="cli-show-roa">show roa [<m/prefix/ | in <m/prefix/ | for <m/prefix/] [as <m/num/] [table <m/t/]</tag> - Show contents of a ROA table (by default of the first one). You can - specify a <m/prefix/ to print ROA entries for a specific network. If you - use <cf>for <m/prefix/</cf>, you'll get all entries relevant for route - validation of the network prefix; i.e., ROA entries whose prefixes cover - the network prefix. Or you can use <cf>in <m/prefix/</cf> to get ROA - entries covered by the network prefix. You could also use <cf/as/ option - to show just entries for given AS. - - <tag><label id="cli-add-roa">add roa <m/prefix/ max <m/num/ as <m/num/ [table <m/t/]</tag> - Add a new ROA entry to a ROA table. Such entry is called <it/dynamic/ - compared to <it/static/ entries specified in the config file. These - dynamic entries survive reconfiguration. - - <tag><label id="cli-delete-roa">delete roa <m/prefix/ max <m/num/ as <m/num/ [table <m/t/]</tag> - Delete the specified ROA entry from a ROA table. Only dynamic ROA - entries (i.e., the ones added by <cf/add roa/ command) can be deleted. - - <tag><label id="cli-flush-roa">flush roa [table <m/t/]</tag> - Remove all dynamic ROA entries from a ROA table. - <tag><label id="cli-configure">configure [soft] ["<m/config file/"] [timeout [<m/num/]]</tag> Reload configuration from a given file. BIRD will smoothly switch itself to the new configuration, protocols are reconfigured if possible, @@ -1055,20 +1176,41 @@ foot). <tag><label id="type-ip">ip</tag> This type can hold a single IP address. Depending on the compile-time configuration of BIRD you are using, it is either an IPv4 or IPv6 - address. IP addresses are written in the standard notation + address; this may be checked by <cf>.is_ip4</cf> which returns <cf/bool/. + IP addresses are written in the standard notation (<cf/10.20.30.40/ or <cf/fec0:3:4::1/). You can apply special operator <cf>.mask(<M>num</M>)</cf> on values of type ip. It masks out all but first <cf><M>num</M></cf> bits from the IP address. So <cf/1.2.3.4.mask(8) = 1.0.0.0/ is true. <tag><label id="type-prefix">prefix</tag> - This type can hold a network prefix consisting of IP address and prefix - length. Prefix literals are written as <cf><m/ipaddress//<m/pxlen/</cf>, + This type can hold a network prefix consisting of IP address, prefix + length and several other values. This is the key in route tables. + + Prefixes may be of several types, which can be determined by the special + operator <cf/.type/. The type may be: + + <cf/NET_IP4/ and <cf/NET_IP6/ prefixes hold an IP prefix. The literals + are written as <cf><m/ipaddress//<m/pxlen/</cf>, or <cf><m>ipaddress</m>/<m>netmask</m></cf>. There are two special - operators on prefixes: <cf/.ip/ which extracts the IP address from the - pair, and <cf/.len/, which separates prefix length from the pair. + operators on IP prefixes: <cf/.ip/ which extracts the IP address from + the pair, and <cf/.len/, which separates prefix length from the pair. So <cf>1.2.0.0/16.len = 16</cf> is true. + <cf/NET_VPN4/ and <cf/NET_VPN6/ prefixes hold an IP prefix with VPN + Route Distinguisher (<rfc id="4364">). They support the same special + operators as IP prefixes, and also <cf/.rd/ which extracts the Route + Distinguisher. Their literals are written + as <cf><m/vpnrd/ <m/ipprefix/</cf> + + <cf/NET_ROA4/ and <cf/NET_ROA6/ prefixes hold an IP prefix range + together with an ASN. They support the same special operators as IP + prefixes, and also <cf/.maxlen/ which extracts maximal prefix length, + and <cf/.asn/ which extracts the ASN. + + <cf/NET_FLOW4/ and <cf/NET_FLOW6/ hold an IP prefix together with a + flowspec rule. Filters currently don't support flowspec parsing. + <tag><label id="type-ec">ec</tag> This is a specialized type used to represent BGP extended community values. It is essentially a 64bit value, literals of this type are @@ -1253,7 +1395,7 @@ foot). <cf/!˜/ membership operators) can be used to modify or test eclists, with ECs instead of pairs as arguments. - <tag/lclist/ + <tag><label id="type-lclist">lclist/</tag> Lclist is a data type used for BGP large community lists. Like eclists, lclists are very similar to clists, but they are sets of LCs instead of pairs. The same operations (like <cf/add/, <cf/delete/ or <cf/˜/ @@ -1812,17 +1954,39 @@ table it wishes to export along with complete path information (a list of AS'es the packet will travel through if it uses the particular route) in order to avoid routing loops. -<p>BIRD supports all requirements of the BGP4 standard as defined in -<rfc id="4271"> It also supports the community attributes (<rfc id="1997">), -capability negotiation (<rfc id="5492">), MD5 password authentication (<rfc -id="2385">), extended communities (<rfc id="4360">), route reflectors (<rfc -id="4456">), graceful restart (<rfc id="4724">), multiprotocol extensions -(<rfc id="4760">), 4B AS numbers (<rfc id="4893">), and 4B AS numbers in -extended communities (<rfc id="5668">). - +<sect1>Supported standards: +<label id="bgp-standards"> -For IPv6, it uses the standard multiprotocol extensions defined in -<rfc id="4760"> and applied to IPv6 according to <rfc id="2545">. +<itemize> +<item> <rfc id="4271"> - Border Gateway Protocol 4 (BGP) +<item> <rfc id="1997"> - BGP Communities Attribute +<item> <rfc id="2385"> - Protection of BGP Sessions via TCP MD5 Signature +<item> <rfc id="2545"> - Use of BGP Multiprotocol Extensions for IPv6 +<item> <rfc id="2918"> - Route Refresh Capability +<item> <rfc id="3107"> - Carrying Label Information in BGP +<item> <rfc id="4360"> - BGP Extended Communities Attribute +<item> <rfc id="4364"> - BGP/MPLS IPv4 Virtual Private Networks +<item> <rfc id="4456"> - BGP Route Reflection +<item> <rfc id="4486"> - Subcodes for BGP Cease Notification Message +<item> <rfc id="4659"> - BGP/MPLS IPv6 Virtual Private Networks +<item> <rfc id="4724"> - Graceful Restart Mechanism for BGP +<item> <rfc id="4760"> - Multiprotocol extensions for BGP +<item> <rfc id="4798"> - Connecting IPv6 Islands over IPv4 MPLS +<item> <rfc id="5065"> - AS confederations for BGP +<item> <rfc id="5082"> - Generalized TTL Security Mechanism +<item> <rfc id="5492"> - Capabilities Advertisement with BGP +<item> <rfc id="5549"> - Advertising IPv4 NLRI with an IPv6 Next Hop +<item> <rfc id="5575"> - Dissemination of Flow Specification Rules +<item> <rfc id="5668"> - 4-Octet AS Specific BGP Extended Community +<item> <rfc id="6286"> - AS-Wide Unique BGP Identifier +<item> <rfc id="6608"> - Subcodes for BGP Finite State Machine Error +<item> <rfc id="6793"> - BGP Support for 4-Octet AS Numbers +<item> <rfc id="7313"> - Enhanced Route Refresh Capability for BGP +<item> <rfc id="7606"> - Revised Error Handling for BGP UPDATE Messages +<item> <rfc id="7911"> - Advertisement of Multiple Paths in BGP +<item> <rfc id="7947"> - Internet Exchange BGP Route Server +<item> <rfc id="8092"> - BGP Large Communities Attribute +</itemize> <sect1>Route selection rules <label id="bgp-route-select-rules"> @@ -1911,6 +2075,16 @@ using the following configuration parameters: source address for the BGP session. Default: the address of the local end of the interface our neighbor is connected to. + <tag><label id="bgp-strict-bind">strict bind <m/switch/</tag> + Specify whether BGP listening socket should be bound to a specific local + address (the same as the <cf/source address/) and associated interface, + or to all addresses. Binding to a specific address could be useful in + cases like running multiple BIRD instances on a machine, each using its + IP address. Note that listening sockets bound to a specific address and + to all addresses collide, therefore either all BGP protocols (of the + same address family and using the same local port) should have set + <cf/strict bind/, or none of them. Default: disabled. + <tag><label id="bgp-next-hop-self">next hop self</tag> Avoid calculation of the Next Hop attribute and always advertise our own source address as a next hop. This needs to be used only occasionally to @@ -1983,7 +2157,7 @@ using the following configuration parameters: Note that full (ICMP protection, for example) <rfc id="5082"> support is provided by Linux only. Default: disabled. - <tag><label id="bgp-pass">password <m/string/</tag> + <tag><label id="bgp-password">password <m/string/</tag> Use this password for MD5 authentication of BGP sessions (<rfc id="2385">). When used on BSD systems, see also <cf/setkey/ option below. Default: no authentication. @@ -2004,6 +2178,21 @@ using the following configuration parameters: accepting incoming connections. In passive mode, outgoing connections are not initiated. Default: off. + <tag><label id="bgp-confederation">confederation <m/number/</tag> + BGP confederations (<rfc id="5065">) are collections of autonomous + systems that act as one entity to external systems, represented by one + confederation identifier (instead of AS numbers). This option allows to + enable BGP confederation behavior and to specify the local confederation + identifier. When BGP confederations are used, all BGP speakers that are + members of the BGP confederation should have the same confederation + identifier configured. Default: 0 (no confederation). + + <tag><label id="bgp-confederation-member">confederation member <m/switch/</tag> + When BGP confederations are used, this option allows to specify whether + the BGP neighbor is a member of the same confederation as the local BGP + speaker. The option is unnecessary (and ignored) for IBGP sessions, as + the same AS number implies the same confederation. Default: no. + <tag><label id="bgp-rr-client">rr client</tag> Be a route reflector and treat the neighbor as a route reflection client. Default: disabled. @@ -2134,13 +2323,6 @@ using the following configuration parameters: This option is relevant to IPv4 mode with enabled capability advertisement only. Default: on. - <tag><label id="bgp-route-limit">route limit <m/number/</tag> - The maximal number of routes that may be imported from the protocol. If - the route limit is exceeded, the connection is closed with an error. - Limit is currently implemented as <cf>import limit <m/number/ action - restart</cf>. This option is obsolete and it is replaced by - <ref id="proto-import-limit" name="import limit option">. Default: no limit. - <tag><label id="bgp-disable-after-error">disable after error <m/switch/</tag> When an error is encountered (either locally or by the other side), disable the instance automatically and wait for an administrator to fix @@ -2515,7 +2697,7 @@ limitations can be overcome using another routing table and the pipe protocol. routes from other sources (e.g. kernel device routes). Metric 0 has a special meaning of undefined metric, in which either OS default is used, or per-route metric can be set using <cf/krt_metric/ attribute. Default: - 0 (undefined). + 32. <tag><label id="krt-graceful-restart">graceful restart <m/switch/</tag> Participate in graceful restart recovery. If this option is enabled and @@ -2656,14 +2838,15 @@ each router detects all changes. <sect1>Configuration <label id="ospf-config"> -<p>In the main part of configuration, there can be multiple definitions of OSPF -areas, each with a different id. These definitions includes many other switches -and multiple definitions of interfaces. Definition of interface may contain many -switches and constant definitions and list of neighbors on nonbroadcast -networks. +<p>First, the desired OSPF version can be specified by using <cf/ospf v2/ or +<cf/ospf v3/ as a protocol type. By default, OSPFv2 is used. In the main part of +configuration, there can be multiple definitions of OSPF areas, each with a +different id. These definitions includes many other switches and multiple +definitions of interfaces. Definition of interface may contain many switches and +constant definitions and list of neighbors on nonbroadcast networks. <code> -protocol ospf <name> { +protocol ospf [v2|v3] <name> { rfc1583compat <switch>; instance id <num>; stub router <switch>; @@ -3589,10 +3772,12 @@ pretty much obsolete. It is still usable on very small networks. <label id="rip-config"> <p>RIP configuration consists mainly of common protocol options and interface -definitions, most RIP options are interface specific. +definitions, most RIP options are interface specific. RIPng (RIP for IPv6) +protocol instance can be configured by using <cf/rip ng/ instead of just +<cf/rip/ as a protocol type. <code> -protocol rip [<name>] { +protocol rip [ng] [<name>] { infinity <number>; ecmp <switch> [limit <number>]; interface <interface pattern> { @@ -3797,8 +3982,8 @@ protocol rip [<name>] { <p>RIP defines two route attributes: <descrip> - <tag><label id="rta-rip-metric">int rip_metric/</tag> - RIP metric of the route (ranging from 0 to <cf/infinity/). When routes + <tag>int <cf/rip_metric/</tag> + RIP metric of the route (ranging from 0 to <cf/infinity/). When routes from different RIP instances are available and all of them have the same preference, BIRD prefers the route with lowest <cf/rip_metric/. When a non-RIP route is exported to RIP, the default metric is 1. @@ -3829,6 +4014,184 @@ protocol rip { } </code> +<sect>RPKI + +<sect1>Introduction + +<p>The Resource Public Key Infrastructure (RPKI) is mechanism for origin +validation of BGP routes (RFC 6480). BIRD supports only so-called RPKI-based +origin validation. There is implemented RPKI to Router (RPKI-RTR) protocol (RFC +6810). It uses some of the RPKI data to allow a router to verify that the +autonomous system announcing an IP address prefix is in fact authorized to do +so. This is not crypto checked so can be violated. But it should prevent the +vast majority of accidental hijackings on the Internet today, e.g. the famous +Pakastani accidental announcement of YouTube's address space. + +<p>The RPKI-RTR protocol receives and maintains a set of ROAs from a cache +server (also called validator). You can validate routes (RFC 6483) using +function <cf/roa_check()/ in filter and set it as import filter at the BGP +protocol. BIRD should re-validate all of affected routes after RPKI update by +RFC 6811, but we don't support it yet! You can use a BIRD's client command +<cf>reload in <m/bgp_protocol_name/</cf> for manual call of revalidation of all +routes. + +<sect1>Supported transports +<itemize> + <item>Unprotected transport over TCP uses a port 323. The cache server + and BIRD router should be on the same trusted and controlled network + for security reasons. + <item>SSHv2 encrypted transport connection uses the normal SSH port + 22. +</itemize> + +<sect1>Configuration + +<p>We currently support just one cache server per protocol. However you can +define more RPKI protocols generally. + +<code> +protocol rpki [<name>] { + roa4 { table <tab>; }; + roa6 { table <tab>; }; + remote <ip> | "<domain>" [port <num>]; + port <num>; + refresh [keep] <num>; + retry [keep] <num>; + expire [keep] <num>; + transport tcp; + transport ssh { + bird private key "</path/to/id_rsa>"; + remote public key "</path/to/known_host>"; + user "<name>"; + }; +} +</code> + +<p>Alse note that you have to specify ROA table into which will be imported +routes from a cache server. If you want to import only IPv4 prefixes you have +to specify only roa4 table. Similarly with IPv6 prefixes only. If you want to +fetch both IPv4 and even IPv6 ROAs you have to specify both types of ROA +tables. + +<sect2>RPKI protocol options + +<descrip> + <tag>remote <m/ip/ | "<m/hostname/" [port <m/num/]</tag> Specifies + a destination address of the cache server. Can be specified by an IP + address or by full domain name string. Only one cache can be specified + per protocol. This option is required. + + <tag>port <m/num/</tag> Specifies the port number. The default port + number is 323 for transport without any encryption and 22 for transport + with SSH encryption. + + <tag>refresh [keep] <m/num/</tag> Time period in seconds. Tells how + long to wait before next attempting to poll the cache using a Serial + Query or a Reset Query packet. Must be lower than 86400 seconds (one + day). Too low value can caused a false positive detection of + network connection problems. A keyword <cf/keep/ suppresses updating + this value by a cache server. + Default: 3600 seconds + + <tag>retry [keep] <m/num/</tag> Time period in seconds between a failed + Serial/Reset Query and a next attempt. Maximum allowed value is 7200 + seconds (two hours). Too low value can caused a false positive + detection of network connection problems. A keyword <cf/keep/ + suppresses updating this value by a cache server. + Default: 600 seconds + + <tag>expire [keep] <m/num/</tag> Time period in seconds. Received + records are deleted if the client was unable to successfully refresh + data for this time period. Must be in range from 600 seconds (ten + minutes) to 172800 seconds (two days). A keyword <cf/keep/ + suppresses updating this value by a cache server. + Default: 7200 seconds + + <tag>transport tcp</tag> Unprotected transport over TCP. It's a default + transport. Should be used only on secure private networks. + Default: tcp + + <tag>transport ssh { <m/SSH transport options.../ }</tag> It enables a + SSHv2 transport encryption. Cannot be combined with a TCP transport. + Default: off +</descrip> + +<sect3>SSH transport options +<descrip> + <tag>bird private key "<m>/path/to/id_rsa</m>"</tag> + A path to the BIRD's private SSH key for authentication. + It can be a <cf><m>id_rsa</m></cf> file. + + <tag>remote public key "<m>/path/to/known_host</m>"</tag> + A path to the cache's public SSH key for verification identity + of the cache server. It could be a path to <cf><m>known_host</m></cf> file. + + <tag>user "<m/name/"</tag> + A SSH user name for authentication. This option is a required. +</descrip> + +<sect1>Examples +<sect2>BGP origin validation +<p>Policy: Don't import <cf/ROA_INVALID/ routes. +<code> +roa4 table r4; +roa6 table r6; + +protocol rpki { + debug all; + + roa4 { table r4; }; + roa6 { table r6; }; + + # Please, do not use rpki-validator.realmv6.org in production + remote "rpki-validator.realmv6.org" port 8282; + + retry keep 5; + refresh keep 30; + expire 600; +} + +filter peer_in { + if (roa_check(r4, net, bgp_path.last) = ROA_INVALID || + roa_check(r6, net, bgp_path.last) = ROA_INVALID) then + { + print "Ignore invalid ROA ", net, " for ASN ", bgp_path.last; + reject; + } + accept; +} + +protocol bgp { + debug all; + local as 65000; + neighbor 192.168.2.1 as 65001; + import filter peer_in; +} +</code> + +<sect2>SSHv2 transport encryption +<code> +roa4 table r4; +roa6 table r6; + +protocol rpki { + debug all; + + roa4 { table r4; }; + roa6 { table r6; }; + + remote 127.0.0.1 port 2345; + transport ssh { + bird private key "/home/birdgeek/.ssh/id_rsa"; + remote public key "/home/birdgeek/.ssh/known_hosts"; + user "birdgeek"; + }; + + # Default interval values +} +</code> + + <sect>Static <label id="static"> @@ -3841,12 +4204,12 @@ return packets as undeliverable if they are in your IP block, you don't have any specific destination for them and you don't want to send them out through the default route to prevent routing loops). -<p>There are five types of static routes: `classical' routes telling to forward -packets to a neighboring router, multipath routes specifying several (possibly -weighted) neighboring routers, device routes specifying forwarding to hosts on a -directly connected network, recursive routes computing their nexthops by doing -route table lookups for a given IP, and special routes (sink, blackhole etc.) -which specify a special action to be done instead of forwarding the packet. +<p>There are four types of static routes: `classical' routes telling to forward +packets to a neighboring router (single path or multipath, possibly weighted), +device routes specifying forwarding to hosts on a directly connected network, +recursive routes computing their nexthops by doing route table lookups for a +given IP, and special routes (sink, blackhole etc.) which specify a special +action to be done instead of forwarding the packet. <p>When the particular destination is not available (the interface is down or the next hop of the route is not a neighbor at the moment), Static just @@ -3875,14 +4238,14 @@ definition of the protocol contains mainly a list of static routes. <p>Route definitions (each may also contain a block of per-route options): <descrip> - <tag><label id="static-route-via-ip">route <m/prefix/ via <m/ip/</tag> - Static route through a neighboring router. For link-local next hops, + <tag><label id="static-route-via-ip">route <m/prefix/ via <m/ip/ [mpls <m/num/[/<m/num/[/<m/num/[...]]]]</tag> + Static single path route through a neighboring router. For link-local next hops, interface can be specified as a part of the address (e.g., - <cf/via fe80::1234%eth0/). + <cf/via fe80::1234%eth0/). MPLS labels should be specified in outer-first order. - <tag><label id="static-route-via-mpath">route <m/prefix/ multipath via <m/ip/ [weight <m/num/] [bfd <m/switch/] [via <m/.../]</tag> + <tag><label id="static-route-via-mpath">route <m/prefix/ via <m/ip/ [mpls <m/num/[/<m/num/[/<m/num/[...]]]] [weight <m/num/] [bfd <m/switch/] [via ...]</tag> Static multipath route. Contains several nexthops (gateways), possibly - with their weights. + with their weights and MPLS labels. <tag><label id="static-route-via-iface">route <m/prefix/ via <m/"interface"/</tag> Static device route through an interface to hosts on a directly diff --git a/doc/sgml2html b/doc/sgml2html index 774a03d5..a5bbee9e 100755 --- a/doc/sgml2html +++ b/doc/sgml2html @@ -17,8 +17,10 @@ use strict; use vars qw($prefix $DataDir $BinDir $progs); +use FindBin; + $prefix = "/usr"; -$DataDir = "sbase"; +$DataDir = "$FindBin::Bin/sbase"; $BinDir = "/usr/bin"; use lib "/usr/share/linuxdoc-tools"; @@ -32,9 +34,9 @@ $progs = { "GROFFMACRO" => "-ms", "AWK" => "/usr/share/linuxdoc-tools/awkwhich" }; -$ENV{"SGML_CATALOG_FILES"} = "sbase/dtd/catalog"; +$ENV{"SGML_CATALOG_FILES"} = "$DataDir/dtd/catalog"; -require "./LinuxDocTools.pm"; +require "$FindBin::Bin/LinuxDocTools.pm"; &LinuxDocTools::init; my @FileList = LinuxDocTools::process_options ("html", @ARGV); diff --git a/doc/sgml2latex b/doc/sgml2latex index 27aae4c8..02b60d94 100755 --- a/doc/sgml2latex +++ b/doc/sgml2latex @@ -17,8 +17,10 @@ use strict; use vars qw($prefix $DataDir $BinDir $progs); +use FindBin; + $prefix = "/usr"; -$DataDir = "sbase"; +$DataDir = "$FindBin::Bin/sbase"; $BinDir = "/usr/bin"; use lib "/usr/share/linuxdoc-tools"; @@ -32,9 +34,9 @@ $progs = { "GROFFMACRO" => "-ms", "AWK" => "/usr/share/linuxdoc-tools/awkwhich" }; -$ENV{"SGML_CATALOG_FILES"} = "sbase/dtd/catalog"; +$ENV{"SGML_CATALOG_FILES"} = "$DataDir/dtd/catalog"; -require "./LinuxDocTools.pm"; +require "$FindBin::Bin/LinuxDocTools.pm"; &LinuxDocTools::init; my @FileList = LinuxDocTools::process_options ("latex", @ARGV); diff --git a/doc/sgml2txt b/doc/sgml2txt index 90dc4855..dfc017de 100755 --- a/doc/sgml2txt +++ b/doc/sgml2txt @@ -17,8 +17,10 @@ use strict; use vars qw($prefix $DataDir $BinDir $progs); +use FindBin; + $prefix = "/usr"; -$DataDir = "sbase"; +$DataDir = "$FindBin::Bin/sbase"; $BinDir = "/usr/bin"; use lib "/usr/share/linuxdoc-tools"; @@ -32,9 +34,9 @@ $progs = { "GROFFMACRO" => "-ms", "AWK" => "/usr/share/linuxdoc-tools/awkwhich" }; -$ENV{"SGML_CATALOG_FILES"} = "sbase/dtd/catalog"; +$ENV{"SGML_CATALOG_FILES"} = "$DataDir/dtd/catalog"; -require "./LinuxDocTools.pm"; +require "$FindBin::Bin/LinuxDocTools.pm"; &LinuxDocTools::init; my @FileList = LinuxDocTools::process_options ("txt", @ARGV); diff --git a/filter/Makefile b/filter/Makefile index 2de598da..6bada8ca 100644 --- a/filter/Makefile +++ b/filter/Makefile @@ -1,5 +1,8 @@ -source=f-util.c filter.c tree.c trie.c -root-rel=../ -dir-name=filter +src := filter.c f-util.c tree.c trie.c +obj := $(src-o-files) +$(all-daemon) +$(cf-local) -include ../Rules +tests_src := tree_test.c filter_test.c trie_test.c +tests_targets := $(tests_targets) $(tests-target-files) +tests_objs := $(tests_objs) $(src-o-files) diff --git a/filter/config.Y b/filter/config.Y index 5ea83f81..2864d290 100644 --- a/filter/config.Y +++ b/filter/config.Y @@ -232,7 +232,6 @@ f_generate_ec(u16 kind, struct f_inst *tk, struct f_inst *tv) cf_error("Can't operate with key of non-integer/IPv4 type in EC constructor"); } -#ifndef IPV6 /* IP->Quad implicit conversion */ else if (tk->code == 'C') { c1 = 1; @@ -244,13 +243,12 @@ f_generate_ec(u16 kind, struct f_inst *tk, struct f_inst *tv) else if (val->type == T_QUAD) { ipv4_used = 1; key = val->val.i; } - else if (val->type == T_IP) { - ipv4_used = 1; key = ipa_to_u32(val->val.px.ip); + else if ((val->type == T_IP) && ipa_is_ip4(val->val.ip)) { + ipv4_used = 1; key = ipa_to_u32(val->val.ip); } else cf_error("Can't operate with key of non-integer/IPv4 type in EC constructor"); } -#endif if (tv->code == 'c') { if (tv->aux != T_INT) @@ -325,25 +323,91 @@ f_generate_lc(struct f_inst *t1, struct f_inst *t2, struct f_inst *t3) return rv; } +/* + * Remove all new lines and doubled whitespaces + * and convert all tabulators to spaces + * and return a copy of string + */ +char * +assert_copy_expr(const char *start, size_t len) +{ + /* XXX: Allocates maybe a little more memory than we really finally need */ + char *str = cfg_alloc(len + 1); + + char *dst = str; + const char *src = start - 1; + const char *end = start + len; + while (++src < end) + { + if (*src == '\n') + continue; + + /* Skip doubled whitespaces */ + if (src != start) + { + const char *prev = src - 1; + if ((*src == ' ' || *src == '\t') && (*prev == ' ' || *prev == '\t')) + continue; + } + + if (*src == '\t') + *dst = ' '; + else + *dst = *src; + + dst++; + } + *dst = '\0'; + + return str; +} + +/* + * assert_done - create f_instruction of bt_assert + * @expr: expression in bt_assert() + * @start: pointer to first char of test expression + * @end: pointer to the last char of test expression + */ +static struct f_inst * +assert_done(struct f_inst *expr, const char *start, const char *end) +{ + struct f_inst *i; + i = f_new_inst(); + i->code = P('a','s'); + i->a1.p = expr; + + if (end >= start) + { + i->a2.p = assert_copy_expr(start, end - start + 1); + } + else + { + /* this is a break of lexer buffer */ + i->a2.p = "???"; + } + return i; +} CF_DECLS CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN, ACCEPT, REJECT, ERROR, QUITBIRD, - INT, BOOL, IP, PREFIX, PAIR, QUAD, EC, LC, + INT, BOOL, IP, TYPE, PREFIX, RD, PAIR, QUAD, EC, LC, SET, STRING, BGPMASK, BGPPATH, CLIST, ECLIST, LCLIST, IF, THEN, ELSE, CASE, TRUE, FALSE, RT, RO, UNKNOWN, GENERIC, - FROM, GW, NET, MASK, PROTO, SOURCE, SCOPE, CAST, DEST, IFNAME, IFINDEX, + FROM, GW, NET, MASK, PROTO, SOURCE, SCOPE, DEST, IFNAME, IFINDEX, PREFERENCE, - LEN, + ROA_CHECK, ASN, + IS_V4, IS_V6, + LEN, MAXLEN, DEFINED, ADD, DELETE, CONTAINS, RESET, PREPEND, FIRST, LAST, LAST_NONAGGREGATED, MATCH, - ROA_CHECK, EMPTY, - FILTER, WHERE, EVAL) + FILTER, WHERE, EVAL, + BT_ASSERT, BT_TEST_SUITE, FORMAT) %nonassoc THEN %nonassoc ELSE @@ -354,9 +418,11 @@ CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN, %type <i32> cnum %type <e> pair_item ec_item lc_item set_item switch_item set_items switch_items switch_body %type <trie> fprefix_set -%type <v> set_atom switch_atom fprefix fprefix_s fipa +%type <v> set_atom switch_atom fipa +%type <px> fprefix %type <s> decls declsn one_decl function_params %type <h> bgp_path bgp_path_tail1 bgp_path_tail2 +%type <t> get_cf_position CF_GRAMMAR @@ -376,11 +442,27 @@ filter_eval: EVAL term { f_eval_int($2); } ; +CF_ADDTO(conf, bt_test_suite) +bt_test_suite: + BT_TEST_SUITE '(' SYM ',' text ')' { + if (!($3->class & SYM_FUNCTION)) + cf_error("Function expected"); + + struct f_bt_test_suite *t = cfg_alloc(sizeof(struct f_bt_test_suite)); + t->fn = $3->def; + t->fn_name = $3->name; + t->dsc = $5; + + add_tail(&new_config->tests, &t->n); + } + ; + type: INT { $$ = T_INT; } | BOOL { $$ = T_BOOL; } | IP { $$ = T_IP; } - | PREFIX { $$ = T_PREFIX; } + | RD { $$ = T_RD; } + | PREFIX { $$ = T_NET; } | PAIR { $$ = T_PAIR; } | QUAD { $$ = T_QUAD; } | EC { $$ = T_EC; } @@ -402,7 +484,7 @@ type: $$ = T_SET; break; - case T_PREFIX: + case T_NET: $$ = T_PREFIX_SET; break; @@ -537,7 +619,8 @@ block: * Complex types, their bison value is struct f_val */ fipa: - IPA %prec PREFIX_DUMMY { $$.type = T_IP; $$.val.px.ip = $1; } + IP4 %prec PREFIX_DUMMY { $$.type = T_IP; $$.val.ip = ipa_from_ip4($1); } + | IP6 %prec PREFIX_DUMMY { $$.type = T_IP; $$.val.ip = ipa_from_ip6($1); } ; @@ -551,7 +634,6 @@ fipa: set_atom: NUM { $$.type = T_INT; $$.val.i = $1; } - | RTRID { $$.type = T_QUAD; $$.val.i = $1; } | fipa { $$ = $1; } | ENUM { $$.type = pair_a($1); $$.val.i = pair_b($1); } | '(' term ')' { @@ -568,7 +650,6 @@ set_atom: switch_atom: NUM { $$.type = T_INT; $$.val.i = $1; } | '(' term ')' { $$.type = T_INT; $$.val.i = f_eval_int($2); } - | RTRID { $$.type = T_QUAD; $$.val.i = $1; } | fipa { $$ = $1; } | ENUM { $$.type = pair_a($1); $$.val.i = pair_b($1); } ; @@ -641,26 +722,20 @@ switch_items: | switch_items ',' switch_item { $$ = f_merge_items($1, $3); } ; -fprefix_s: - IPA '/' NUM %prec '/' { - if (($3 < 0) || ($3 > MAX_PREFIX_LENGTH) || !ip_is_prefix($1, $3)) cf_error("Invalid network prefix: %I/%d.", $1, $3); - $$.type = T_PREFIX; $$.val.px.ip = $1; $$.val.px.len = $3; - } - ; - fprefix: - fprefix_s { $$ = $1; } - | fprefix_s '+' { $$ = $1; $$.val.px.len |= LEN_PLUS; } - | fprefix_s '-' { $$ = $1; $$.val.px.len |= LEN_MINUS; } - | fprefix_s '{' NUM ',' NUM '}' { - if (! ((0 <= $3) && ($3 <= $5) && ($5 <= MAX_PREFIX_LENGTH))) cf_error("Invalid prefix pattern range: {%d, %d}.", $3, $5); - $$ = $1; $$.val.px.len |= LEN_RANGE | ($3 << 16) | ($5 << 8); + net_ip_ { $$.net = $1; $$.lo = $1.pxlen; $$.hi = $1.pxlen; } + | net_ip_ '+' { $$.net = $1; $$.lo = $1.pxlen; $$.hi = net_max_prefix_length[$1.type]; } + | net_ip_ '-' { $$.net = $1; $$.lo = 0; $$.hi = $1.pxlen; } + | net_ip_ '{' NUM ',' NUM '}' { + $$.net = $1; $$.lo = $3; $$.hi = $5; + if ((0 > $3) || ($3 > $5) || ($5 > net_max_prefix_length[$1.type])) + cf_error("Invalid prefix pattern range: {%d, %d}", $3, $5); } ; fprefix_set: - fprefix { $$ = f_new_trie(cfg_mem, sizeof(struct f_trie_node)); trie_add_fprefix($$, &($1.val.px)); } - | fprefix_set ',' fprefix { $$ = $1; trie_add_fprefix($$, &($3.val.px)); } + fprefix { $$ = f_new_trie(cfg_mem, sizeof(struct f_trie_node)); trie_add_prefix($$, &($1.net), $1.lo, $1.hi); } + | fprefix_set ',' fprefix { $$ = $1; trie_add_prefix($$, &($3.net), $3.lo, $3.hi); } ; switch_body: /* EMPTY */ { $$ = NULL; } @@ -712,9 +787,9 @@ constant: | TRUE { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_BOOL; $$->a2.i = 1; } | FALSE { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_BOOL; $$->a2.i = 0; } | TEXT { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_STRING; $$->a2.p = $1; } - | fipa { NEW_F_VAL; $$ = f_new_inst(); $$->code = 'C'; $$->a1.p = val; *val = $1; } - | fprefix_s {NEW_F_VAL; $$ = f_new_inst(); $$->code = 'C'; $$->a1.p = val; *val = $1; } - | RTRID { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_QUAD; $$->a2.i = $1; } + | fipa { NEW_F_VAL; $$ = f_new_inst(); $$->code = 'C'; $$->a1.p = val; *val = $1; } + | VPN_RD { NEW_F_VAL; $$ = f_new_inst(); $$->code = 'C'; val->type = T_RD; val->val.ec = $1; $$->a1.p = val; } + | net_ { NEW_F_VAL; $$ = f_new_inst(); $$->code = 'C'; val->type = T_NET; val->val.net = $1; $$->a1.p = val; } | '[' set_items ']' { DBG( "We've got a set here..." ); $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_SET; $$->a2.p = build_tree($2); DBG( "ook\n" ); } | '[' fprefix_set ']' { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_PREFIX_SET; $$->a2.p = $2; } | ENUM { $$ = f_new_inst(); $$->code = 'c'; $$->aux = $1 >> 16; $$->a2.i = $1 & 0xffff; } @@ -778,11 +853,10 @@ symbol: static_attr: FROM { $$ = f_new_inst(); $$->aux = T_IP; $$->a2.i = SA_FROM; $$->a1.i = 1; } | GW { $$ = f_new_inst(); $$->aux = T_IP; $$->a2.i = SA_GW; $$->a1.i = 1; } - | NET { $$ = f_new_inst(); $$->aux = T_PREFIX; $$->a2.i = SA_NET; } + | NET { $$ = f_new_inst(); $$->aux = T_NET; $$->a2.i = SA_NET; } | PROTO { $$ = f_new_inst(); $$->aux = T_STRING; $$->a2.i = SA_PROTO; } | SOURCE { $$ = f_new_inst(); $$->aux = T_ENUM_RTS; $$->a2.i = SA_SOURCE; } | SCOPE { $$ = f_new_inst(); $$->aux = T_ENUM_SCOPE; $$->a2.i = SA_SCOPE; $$->a1.i = 1; } - | CAST { $$ = f_new_inst(); $$->aux = T_ENUM_RTC; $$->a2.i = SA_CAST; } | DEST { $$ = f_new_inst(); $$->aux = T_ENUM_RTD; $$->a2.i = SA_DEST; $$->a1.i = 1; } | IFNAME { $$ = f_new_inst(); $$->aux = T_STRING; $$->a2.i = SA_IFNAME; } | IFINDEX { $$ = f_new_inst(); $$->aux = T_INT; $$->a2.i = SA_IFINDEX; } @@ -817,8 +891,13 @@ term: | rtadot dynamic_attr { $$ = $2; $$->code = P('e','a'); } + | term '.' IS_V4 { $$ = f_new_inst(); $$->code = P('I','i'); $$->a1.p = $1; } + | term '.' TYPE { $$ = f_new_inst(); $$->code = 'T'; $$->a1.p = $1; } | term '.' IP { $$ = f_new_inst(); $$->code = P('c','p'); $$->a1.p = $1; $$->aux = T_IP; } + | term '.' RD { $$ = f_new_inst(); $$->code = P('R','D'); $$->a1.p = $1; $$->aux = T_RD; } | term '.' LEN { $$ = f_new_inst(); $$->code = 'L'; $$->a1.p = $1; } + | term '.' MAXLEN { $$ = f_new_inst(); $$->code = P('R','m'); $$->a1.p = $1; } + | term '.' ASN { $$ = f_new_inst(); $$->code = P('R','a'); $$->a1.p = $1; } | term '.' MASK '(' term ')' { $$ = f_new_inst(); $$->code = P('i','M'); $$->a1.p = $1; $$->a2.p = $5; } | term '.' FIRST { $$ = f_new_inst(); $$->code = P('a','f'); $$->a1.p = $1; } | term '.' LAST { $$ = f_new_inst(); $$->code = P('a','l'); $$->a1.p = $1; } @@ -841,8 +920,10 @@ term: | DELETE '(' term ',' term ')' { $$ = f_new_inst(); $$->code = P('C','a'); $$->a1.p = $3; $$->a2.p = $5; $$->aux = 'd'; } | FILTER '(' term ',' term ')' { $$ = f_new_inst(); $$->code = P('C','a'); $$->a1.p = $3; $$->a2.p = $5; $$->aux = 'f'; } - | ROA_CHECK '(' SYM ')' { $$ = f_generate_roa_check($3, NULL, NULL); } - | ROA_CHECK '(' SYM ',' term ',' term ')' { $$ = f_generate_roa_check($3, $5, $7); } + | ROA_CHECK '(' rtable ')' { $$ = f_generate_roa_check($3, NULL, NULL); } + | ROA_CHECK '(' rtable ',' term ',' term ')' { $$ = f_generate_roa_check($3, $5, $7); } + + | FORMAT '(' term ')' { $$ = f_new_inst(); $$->code = P('f','m'); $$->a1.p = $3; } /* | term '.' LEN { $$->code = P('P','l'); } */ @@ -976,12 +1057,18 @@ cmd: $$->a2.p = build_tree( $4 ); } - | rtadot dynamic_attr '.' EMPTY ';' { $$ = f_generate_empty($2); } | rtadot dynamic_attr '.' PREPEND '(' term ')' ';' { $$ = f_generate_complex( P('A','p'), 'x', $2, $6 ); } | rtadot dynamic_attr '.' ADD '(' term ')' ';' { $$ = f_generate_complex( P('C','a'), 'a', $2, $6 ); } | rtadot dynamic_attr '.' DELETE '(' term ')' ';' { $$ = f_generate_complex( P('C','a'), 'd', $2, $6 ); } | rtadot dynamic_attr '.' FILTER '(' term ')' ';' { $$ = f_generate_complex( P('C','a'), 'f', $2, $6 ); } + | BT_ASSERT '(' get_cf_position term get_cf_position ')' ';' { $$ = assert_done($4, $3 + 1, $5 - 1); } ; +get_cf_position: +{ + $$ = cf_text; +}; + + CF_END diff --git a/filter/f-util.c b/filter/f-util.c index def2b248..661941ec 100644 --- a/filter/f-util.c +++ b/filter/f-util.c @@ -54,9 +54,8 @@ f_generate_complex(int operation, int operation_aux, struct f_inst *dyn, struct return set_dyn; } - struct f_inst * -f_generate_roa_check(struct symbol *sym, struct f_inst *prefix, struct f_inst *asn) +f_generate_roa_check(struct rtable_config *table, struct f_inst *prefix, struct f_inst *asn) { struct f_inst_roa_check *ret = cfg_allocz(sizeof(struct f_inst_roa_check)); ret->i.code = P('R','C'); @@ -65,9 +64,9 @@ f_generate_roa_check(struct symbol *sym, struct f_inst *prefix, struct f_inst *a ret->i.arg2 = asn; /* prefix == NULL <-> asn == NULL */ - if ((sym->class != SYM_ROA) || ! sym->def) - cf_error("%s is not a ROA table", sym->name); - ret->rtc = sym->def; + if (table->addr_type != NET_ROA4 && table->addr_type != NET_ROA6) + cf_error("%s is not a ROA table", table->name); + ret->rtc = table; return &ret->i; } diff --git a/filter/filter.c b/filter/filter.c index f18970e0..7dcf22ed 100644 --- a/filter/filter.c +++ b/filter/filter.c @@ -39,6 +39,8 @@ #include "lib/socket.h" #include "lib/string.h" #include "lib/unaligned.h" +#include "lib/net.h" +#include "lib/ip.h" #include "nest/route.h" #include "nest/protocol.h" #include "nest/iface.h" @@ -50,6 +52,8 @@ #define CMP_ERROR 999 +void (*bt_assert_hook)(int result, struct f_inst *assert); + static struct adata * adata_empty(struct linpool *pool, int l) { @@ -94,17 +98,8 @@ pm_format(struct f_path_mask *p, buffer *buf) buffer_puts(buf, "=]"); } -static inline int -uint_cmp(uint i1, uint i2) -{ - return (int)(i1 > i2) - (int)(i1 < i2); -} - -static inline int -u64_cmp(u64 i1, u64 i2) -{ - return (int)(i1 > i2) - (int)(i1 < i2); -} +static inline int val_is_ip4(const struct f_val v) +{ return (v.type == T_IP) && ipa_is_ip4(v.val.ip); } static inline int lcomm_cmp(lcomm v1, lcomm v2) @@ -130,21 +125,17 @@ lcomm_cmp(lcomm v1, lcomm v2) int val_compare(struct f_val v1, struct f_val v2) { - int rc; - if (v1.type != v2.type) { if (v1.type == T_VOID) /* Hack for else */ return -1; if (v2.type == T_VOID) return 1; -#ifndef IPV6 /* IP->Quad implicit conversion */ - if ((v1.type == T_QUAD) && (v2.type == T_IP)) - return uint_cmp(v1.val.i, ipa_to_u32(v2.val.px.ip)); - if ((v1.type == T_IP) && (v2.type == T_QUAD)) - return uint_cmp(ipa_to_u32(v1.val.px.ip), v2.val.i); -#endif + if ((v1.type == T_QUAD) && val_is_ip4(v2)) + return uint_cmp(v1.val.i, ipa_to_u32(v2.val.ip)); + if (val_is_ip4(v1) && (v2.type == T_QUAD)) + return uint_cmp(ipa_to_u32(v1.val.ip), v2.val.i); debug( "Types do not match in val_compare\n" ); return CMP_ERROR; @@ -160,15 +151,14 @@ val_compare(struct f_val v1, struct f_val v2) case T_QUAD: return uint_cmp(v1.val.i, v2.val.i); case T_EC: + case T_RD: return u64_cmp(v1.val.ec, v2.val.ec); case T_LC: return lcomm_cmp(v1.val.lc, v2.val.lc); case T_IP: - return ipa_compare(v1.val.px.ip, v2.val.px.ip); - case T_PREFIX: - if (rc = ipa_compare(v1.val.px.ip, v2.val.px.ip)) - return rc; - return uint_cmp(v1.val.px.len, v2.val.px.len); + return ipa_compare(v1.val.ip, v2.val.ip); + case T_NET: + return net_compare(v1.val.net, v2.val.net); case T_STRING: return strcmp(v1.val.s, v2.val.s); default: @@ -239,38 +229,26 @@ val_same(struct f_val v1, struct f_val v2) } } -void -fprefix_get_bounds(struct f_prefix *px, int *l, int *h) -{ - *l = *h = px->len & LEN_MASK; - - if (px->len & LEN_MINUS) - *l = 0; - - else if (px->len & LEN_PLUS) - *h = MAX_PREFIX_LENGTH; - - else if (px->len & LEN_RANGE) - { - *l = 0xff & (px->len >> 16); - *h = 0xff & (px->len >> 8); - } -} - static int clist_set_type(struct f_tree *set, struct f_val *v) { - switch (set->from.type) { + switch (set->from.type) + { case T_PAIR: v->type = T_PAIR; return 1; + case T_QUAD: -#ifndef IPV6 - case T_IP: -#endif v->type = T_QUAD; return 1; - break; + + case T_IP: + if (val_is_ip4(set->from) && val_is_ip4(set->to)) + { + v->type = T_QUAD; + return 1; + } + /* Fall through */ default: v->type = T_VOID; return 0; @@ -473,11 +451,10 @@ val_in_range(struct f_val v1, struct f_val v2) if (((v1.type == T_PAIR) || (v1.type == T_QUAD)) && (v2.type == T_CLIST)) return int_set_contains(v2.val.ad, v1.val.i); -#ifndef IPV6 + /* IP->Quad implicit conversion */ - if ((v1.type == T_IP) && (v2.type == T_CLIST)) - return int_set_contains(v2.val.ad, ipa_to_u32(v1.val.px.ip)); -#endif + if (val_is_ip4(v1) && (v2.type == T_CLIST)) + return int_set_contains(v2.val.ad, ipa_to_u32(v1.val.ip)); if ((v1.type == T_EC) && (v2.type == T_ECLIST)) return ec_set_contains(v2.val.ad, v1.val.ec); @@ -488,21 +465,21 @@ val_in_range(struct f_val v1, struct f_val v2) if ((v1.type == T_STRING) && (v2.type == T_STRING)) return patmatch(v2.val.s, v1.val.s); - if ((v1.type == T_IP) && (v2.type == T_PREFIX)) - return ipa_in_net(v1.val.px.ip, v2.val.px.ip, v2.val.px.len); + if ((v1.type == T_IP) && (v2.type == T_NET)) + return ipa_in_netX(v1.val.ip, v2.val.net); - if ((v1.type == T_PREFIX) && (v2.type == T_PREFIX)) - return net_in_net(v1.val.px.ip, v1.val.px.len, v2.val.px.ip, v2.val.px.len); + if ((v1.type == T_NET) && (v2.type == T_NET)) + return net_in_netX(v1.val.net, v2.val.net); - if ((v1.type == T_PREFIX) && (v2.type == T_PREFIX_SET)) - return trie_match_fprefix(v2.val.ti, &v1.val.px); + if ((v1.type == T_NET) && (v2.type == T_PREFIX_SET)) + return trie_match_net(v2.val.ti, v1.val.net); if (v2.type != T_SET) return CMP_ERROR; /* With integrated Quad<->IP implicit conversion */ if ((v1.type == v2.val.t->from.type) || - ((IP_VERSION == 4) && (v1.type == T_QUAD) && (v2.val.t->from.type == T_IP))) + ((v1.type == T_QUAD) && val_is_ip4(v2.val.t->from) && val_is_ip4(v2.val.t->to))) return !!find_tree(v2.val.t, v1); if (v1.type == T_CLIST) @@ -533,12 +510,13 @@ val_format(struct f_val v, buffer *buf) case T_BOOL: buffer_puts(buf, v.val.i ? "TRUE" : "FALSE"); return; case T_INT: buffer_print(buf, "%u", v.val.i); return; case T_STRING: buffer_print(buf, "%s", v.val.s); return; - case T_IP: buffer_print(buf, "%I", v.val.px.ip); return; - case T_PREFIX: buffer_print(buf, "%I/%d", v.val.px.ip, v.val.px.len); return; + case T_IP: buffer_print(buf, "%I", v.val.ip); return; + case T_NET: buffer_print(buf, "%N", v.val.net); return; case T_PAIR: buffer_print(buf, "(%u,%u)", v.val.i >> 16, v.val.i & 0xffff); return; case T_QUAD: buffer_print(buf, "%R", v.val.i); return; case T_EC: ec_format(buf2, v.val.ec); buffer_print(buf, "%s", buf2); return; case T_LC: lc_format(buf2, v.val.lc); buffer_print(buf, "%s", buf2); return; + case T_RD: rd_format(v.val.ec, buf2, 1024); buffer_print(buf, "%s", buf2); return; case T_PREFIX_SET: trie_format(v.val.ti, buf); return; case T_SET: tree_format(v.val.t, buf); return; case T_ENUM: buffer_print(buf, "(enum %x)%u", v.type, v.val.i); return; @@ -587,10 +565,18 @@ f_rta_cow(void) (*f_rte)->attrs = rta_do_cow((*f_rte)->attrs, f_pool); } +static char * +val_format_str(struct f_val v) { + buffer b; + LOG_BUFFER_INIT(b); + val_format(v, &b); + return lp_strdup(f_pool, b.start); +} + static struct tbf rl_runtime_err = TBF_DEFAULT_LOG_LIMITS; -#define runtime(x) do { \ - log_rl(&rl_runtime_err, L_ERR "filters, line %d: %s", what->lineno, x); \ +#define runtime(fmt, ...) do { \ + log_rl(&rl_runtime_err, L_ERR "filters, line %d: " fmt, what->lineno, ##__VA_ARGS__); \ res.type = T_RETURN; \ res.val.i = F_ERROR; \ return res; \ @@ -726,12 +712,10 @@ interpret(struct f_inst *what) else if (v1.type == T_QUAD) { ipv4_used = 1; key = v1.val.i; } -#ifndef IPV6 /* IP->Quad implicit conversion */ - else if (v1.type == T_IP) { - ipv4_used = 1; key = ipa_to_u32(v1.val.px.ip); + else if (val_is_ip4(v1)) { + ipv4_used = 1; key = ipa_to_u32(v1.val.ip); } -#endif else runtime("Can't operate with key of non-integer/IPv4 type in EC constructor"); @@ -833,21 +817,40 @@ interpret(struct f_inst *what) res.type = T_BOOL; res.val.i = (v1.type != T_VOID); break; + case 'T': + ONEARG; + switch (v1.type) + { + case T_NET: + res.type = T_ENUM_NETTYPE; + res.val.i = v1.val.net->type; + break; + default: + runtime( "Can't determine type of this item" ); + } + break; + case P('I','i'): + ONEARG; + if (v1.type != T_IP) + runtime( "IP version check needs an IP address" ); + res.type = T_BOOL; + res.val.i = ipa_is_ip4(v1.val.ip); + break; /* Set to indirect value, a1 = variable, a2 = value */ case 's': ARG(v2, a2.p); sym = what->a1.p; vp = sym->def; - if ((sym->class != (SYM_VARIABLE | v2.type)) && (v2.type != T_VOID)) { -#ifndef IPV6 + if ((sym->class != (SYM_VARIABLE | v2.type)) && (v2.type != T_VOID)) + { /* IP->Quad implicit conversion */ - if ((sym->class == (SYM_VARIABLE | T_QUAD)) && (v2.type == T_IP)) { + if ((sym->class == (SYM_VARIABLE | T_QUAD)) && val_is_ip4(v2)) + { vp->type = T_QUAD; - vp->val.i = ipa_to_u32(v2.val.px.ip); + vp->val.i = ipa_to_u32(v2.val.ip); break; } -#endif runtime( "Assigning to variable of incompatible type" ); } *vp = v2; @@ -917,17 +920,15 @@ interpret(struct f_inst *what) switch (what->a2.i) { - case SA_FROM: res.val.px.ip = rta->from; break; - case SA_GW: res.val.px.ip = rta->gw; break; - case SA_NET: res.val.px.ip = (*f_rte)->net->n.prefix; - res.val.px.len = (*f_rte)->net->n.pxlen; break; + case SA_FROM: res.val.ip = rta->from; break; + case SA_GW: res.val.ip = rta->nh.gw; break; + case SA_NET: res.val.net = (*f_rte)->net->n.addr; break; case SA_PROTO: res.val.s = rta->src->proto->name; break; case SA_SOURCE: res.val.i = rta->source; break; case SA_SCOPE: res.val.i = rta->scope; break; - case SA_CAST: res.val.i = rta->cast; break; case SA_DEST: res.val.i = rta->dest; break; - case SA_IFNAME: res.val.s = rta->iface ? rta->iface->name : ""; break; - case SA_IFINDEX: res.val.i = rta->iface ? rta->iface->index : 0; break; + case SA_IFNAME: res.val.s = rta->nh.iface ? rta->nh.iface->name : ""; break; + case SA_IFINDEX: res.val.i = rta->nh.iface ? rta->nh.iface->index : 0; break; default: bug("Invalid static attribute access (%x)", res.type); @@ -947,20 +948,20 @@ interpret(struct f_inst *what) switch (what->a2.i) { case SA_FROM: - rta->from = v1.val.px.ip; + rta->from = v1.val.ip; break; case SA_GW: { - ip_addr ip = v1.val.px.ip; + ip_addr ip = v1.val.ip; neighbor *n = neigh_find(rta->src->proto, &ip, 0); if (!n || (n->scope == SCOPE_HOST)) runtime( "Invalid gw address" ); - rta->dest = RTD_ROUTER; - rta->gw = ip; - rta->iface = n->iface; - rta->nexthops = NULL; + rta->dest = RTD_UNICAST; + rta->nh.gw = ip; + rta->nh.iface = n->iface; + rta->nh.next = NULL; rta->hostentry = NULL; } break; @@ -975,9 +976,9 @@ interpret(struct f_inst *what) runtime( "Destination can be changed only to blackhole, unreachable or prohibit" ); rta->dest = i; - rta->gw = IPA_NONE; - rta->iface = NULL; - rta->nexthops = NULL; + rta->nh.gw = IPA_NONE; + rta->nh.iface = NULL; + rta->nh.next = NULL; rta->hostentry = NULL; break; @@ -1042,7 +1043,7 @@ interpret(struct f_inst *what) case EAF_TYPE_IP_ADDRESS: res.type = T_IP; struct adata * ad = e->u.ptr; - res.val.px.ip = * (ip_addr *) ad->data; + res.val.ip = * (ip_addr *) ad->data; break; case EAF_TYPE_AS_PATH: res.type = T_PATH; @@ -1084,7 +1085,7 @@ interpret(struct f_inst *what) l->count = 1; l->attrs[0].id = code; l->attrs[0].flags = 0; - l->attrs[0].type = what->aux | EAF_ORIGINATED; + l->attrs[0].type = what->aux | EAF_ORIGINATED | EAF_FRESH; switch (what->aux & EAF_TYPE_MASK) { case EAF_TYPE_INT: @@ -1094,13 +1095,11 @@ interpret(struct f_inst *what) break; case EAF_TYPE_ROUTER_ID: -#ifndef IPV6 /* IP->Quad implicit conversion */ - if (v1.type == T_IP) { - l->attrs[0].u.data = ipa_to_u32(v1.val.px.ip); + if (val_is_ip4(v1)) { + l->attrs[0].u.data = ipa_to_u32(v1.val.ip); break; } -#endif /* T_INT for backward compatibility */ if ((v1.type != T_QUAD) && (v1.type != T_INT)) runtime( "Setting quad attribute to non-quad value" ); @@ -1116,7 +1115,7 @@ interpret(struct f_inst *what) int len = sizeof(ip_addr); struct adata *ad = lp_alloc(f_pool, sizeof(struct adata) + len); ad->length = len; - (* (ip_addr *) ad->data) = v1.val.px.ip; + (* (ip_addr *) ad->data) = v1.val.ip; l->attrs[0].u.ptr = ad; break; case EAF_TYPE_AS_PATH: @@ -1196,7 +1195,7 @@ interpret(struct f_inst *what) ONEARG; res.type = T_INT; switch(v1.type) { - case T_PREFIX: res.val.i = v1.val.px.len; break; + case T_NET: res.val.i = net_pxlen(v1.val.net); break; case T_PATH: res.val.i = as_path_getlen(v1.val.ad); break; case T_CLIST: res.val.i = int_set_get_size(v1.val.ad); break; case T_ECLIST: res.val.i = ec_set_get_size(v1.val.ad); break; @@ -1204,16 +1203,41 @@ interpret(struct f_inst *what) default: runtime( "Prefix, path, clist or eclist expected" ); } break; + case P('R','m'): /* Get ROA max prefix length */ + ONEARG; + if (v1.type != T_NET || !net_is_roa(v1.val.net)) + runtime( "ROA expected" ); + + res.type = T_INT; + res.val.i = (v1.val.net->type == NET_ROA4) ? + ((net_addr_roa4 *) v1.val.net)->max_pxlen : + ((net_addr_roa6 *) v1.val.net)->max_pxlen; + break; + case P('R','a'): /* Get ROA ASN */ + ONEARG; + if (v1.type != T_NET || !net_is_roa(v1.val.net)) + runtime( "ROA expected" ); + + res.type = T_INT; + res.val.i = (v1.val.net->type == NET_ROA4) ? + ((net_addr_roa4 *) v1.val.net)->asn : + ((net_addr_roa6 *) v1.val.net)->asn; + break; case P('c','p'): /* Convert prefix to ... */ ONEARG; - if (v1.type != T_PREFIX) + if (v1.type != T_NET) runtime( "Prefix expected" ); - res.type = what->aux; - switch(res.type) { - /* case T_INT: res.val.i = v1.val.px.len; break; Not needed any more */ - case T_IP: res.val.px.ip = v1.val.px.ip; break; - default: bug( "Unknown prefix to conversion" ); - } + res.type = T_IP; + res.val.ip = net_prefix(v1.val.net); + break; + case P('R','D'): + ONEARG; + if (v1.type != T_NET) + runtime( "Prefix expected" ); + if (!net_is_vpn(v1.val.net)) + runtime( "VPN address expected" ); + res.type = T_RD; + res.val.ec = net_rd(v1.val.net); break; case P('a','f'): /* Get first ASN from AS PATH */ ONEARG; @@ -1284,11 +1308,11 @@ interpret(struct f_inst *what) runtime( "Integer expected"); if (v1.type != T_IP) runtime( "You can mask only IP addresses" ); - { - ip_addr mask = ipa_mkmask(v2.val.i); - res.type = T_IP; - res.val.px.ip = ipa_and(mask, v1.val.px.ip); - } + + res.type = T_IP; + res.val.ip = ipa_is_ip4(v1.val.ip) ? + ipa_from_ip4(ip4_and(ipa_to_ip4(v1.val.ip), ip4_mkmask(v2.val.i))) : + ipa_from_ip6(ip6_and(ipa_to_ip6(v1.val.ip), ip6_mkmask(v2.val.i))); break; case 'E': /* Create empty attribute */ @@ -1344,11 +1368,9 @@ interpret(struct f_inst *what) if ((v2.type == T_PAIR) || (v2.type == T_QUAD)) n = v2.val.i; -#ifndef IPV6 /* IP->Quad implicit conversion */ - else if (v2.type == T_IP) - n = ipa_to_u32(v2.val.px.ip); -#endif + else if (val_is_ip4(v2)) + n = ipa_to_u32(v2.val.ip); else if ((v2.type == T_SET) && clist_set_type(v2.val.t, &dummy)) arg_set = 1; else if (v2.type == T_CLIST) @@ -1474,11 +1496,12 @@ interpret(struct f_inst *what) break; + case P('R','C'): /* ROA Check */ if (what->arg1) { TWOARGS; - if ((v1.type != T_PREFIX) || (v2.type != T_INT)) + if ((v1.type != T_NET) || (v2.type != T_INT)) runtime("Invalid argument to roa_check()"); as = v2.val.i; @@ -1486,8 +1509,7 @@ interpret(struct f_inst *what) else { ACCESS_RTE; - v1.val.px.ip = (*f_rte)->net->n.prefix; - v1.val.px.len = (*f_rte)->net->n.pxlen; + v1.val.net = (*f_rte)->net->n.addr; /* 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 */ @@ -1499,12 +1521,39 @@ interpret(struct f_inst *what) as_path_get_last(e->u.ptr, &as); } - struct roa_table_config *rtc = ((struct f_inst_roa_check *) what)->rtc; - if (!rtc->table) + struct rtable *table = ((struct f_inst_roa_check *) what)->rtc->table; + if (!table) runtime("Missing ROA table"); + if (table->addr_type != NET_ROA4 && table->addr_type != NET_ROA6) + runtime("Table type must be either ROA4 or ROA6"); + res.type = T_ENUM_ROA; - res.val.i = roa_check(rtc->table, v1.val.px.ip, v1.val.px.len, as); + + if (table->addr_type != (v1.val.net->type == NET_IP4 ? NET_ROA4 : NET_ROA6)) + res.val.i = ROA_UNKNOWN; /* Prefix and table type mismatch */ + else + res.val.i = net_roa_check(table, v1.val.net, as); + + break; + + case P('f','m'): /* Format */ + ONEARG; + + res.type = T_STRING; + res.val.s = val_format_str(v1); + break; + + case P('a','s'): /* Birdtest Assert */ + ONEARG; + + if (v1.type != T_BOOL) + runtime("Should be boolean value"); + + res.type = v1.type; + res.val = v1.val; + + CALL(bt_assert_hook, res.val.i, what); break; default: @@ -1562,6 +1611,8 @@ i_same(struct f_inst *f1, struct f_inst *f2) case P('!', '~'): case '~': TWOARGS; break; case P('d','e'): ONEARG; break; + case 'T': ONEARG; break; + case P('n','T'): break; case P('m','l'): TWOARGS; @@ -1627,6 +1678,7 @@ i_same(struct f_inst *f1, struct f_inst *f2) case 'r': ONEARG; break; case P('c','p'): ONEARG; break; + case P('R','D'): ONEARG; break; case P('c','a'): /* Call rewriting trickery to avoid exponential behaviour */ ONEARG; if (!i_same(f1->a2.p, f2->a2.p)) @@ -1643,7 +1695,7 @@ i_same(struct f_inst *f1, struct f_inst *f2) case P('a','L'): ONEARG; break; case P('R','C'): TWOARGS; - /* Does not really make sense - ROA check resuls may change anyway */ + /* Does not really make sense - ROA check results may change anyway */ if (strcmp(((struct f_inst_roa_check *) f1)->rtc->name, ((struct f_inst_roa_check *) f2)->rtc->name)) return 0; diff --git a/filter/filter.h b/filter/filter.h index 049ceb76..0beac679 100644 --- a/filter/filter.h +++ b/filter/filter.h @@ -16,16 +16,16 @@ struct f_inst { /* Instruction */ struct f_inst *next; /* Structure is 16 bytes, anyway */ - u16 code; - u16 aux; + u16 code; /* Instruction code, see the interpret() function and P() macro */ + u16 aux; /* Extension to instruction code, T_*, EA_*, EAF_* */ union { int i; void *p; - } a1; + } a1; /* The first argument */ union { int i; void *p; - } a2; + } a2; /* The second argument */ int lineno; }; @@ -35,7 +35,7 @@ struct f_inst { /* Instruction */ /* Not enough fields in f_inst for three args used by roa_check() */ struct f_inst_roa_check { struct f_inst i; - struct roa_table_config *rtc; + struct rtable_config *rtc; }; struct f_inst3 { @@ -50,23 +50,18 @@ struct f_inst3 { struct f_prefix { - ip_addr ip; - int len; -#define LEN_MASK 0xff -#define LEN_PLUS 0x1000000 -#define LEN_MINUS 0x2000000 -#define LEN_RANGE 0x4000000 - /* If range then prefix must be in range (len >> 16 & 0xff, len >> 8 & 0xff) */ + net_addr net; + u8 lo, hi; }; struct f_val { - int type; + int type; /* T_* */ union { uint i; u64 ec; lcomm lc; - /* ip_addr ip; Folded into prefix */ - struct f_prefix px; + ip_addr ip; + const net_addr *net; char *s; struct f_tree *t; struct f_trie *ti; @@ -84,7 +79,7 @@ struct f_inst *f_new_inst(void); struct f_inst *f_new_dynamic_attr(int type, int f_type, int code); /* Type as core knows it, type as filters know it, and code of dynamic attribute */ struct f_tree *f_new_tree(void); struct f_inst *f_generate_complex(int operation, int operation_aux, struct f_inst *dyn, struct f_inst *argument); -struct f_inst *f_generate_roa_check(struct symbol *sym, struct f_inst *prefix, struct f_inst *asn); +struct f_inst *f_generate_roa_check(struct rtable_config *table, struct f_inst *prefix, struct f_inst *asn); struct f_tree *build_tree(struct f_tree *); @@ -93,28 +88,11 @@ int same_tree(struct f_tree *t1, struct f_tree *t2); void tree_format(struct f_tree *t, buffer *buf); struct f_trie *f_new_trie(linpool *lp, uint node_size); -void *trie_add_prefix(struct f_trie *t, ip_addr px, int plen, int l, int h); -int trie_match_prefix(struct f_trie *t, ip_addr px, int plen); +void *trie_add_prefix(struct f_trie *t, const net_addr *n, uint l, uint h); +int trie_match_net(struct f_trie *t, const net_addr *n); int trie_same(struct f_trie *t1, struct f_trie *t2); void trie_format(struct f_trie *t, buffer *buf); -void fprefix_get_bounds(struct f_prefix *px, int *l, int *h); - -static inline void -trie_add_fprefix(struct f_trie *t, struct f_prefix *px) -{ - int l, h; - fprefix_get_bounds(px, &l, &h); - trie_add_prefix(t, px->ip, px->len & LEN_MASK, l, h); -} - -static inline int -trie_match_fprefix(struct f_trie *t, struct f_prefix *px) -{ - return trie_match_prefix(t, px->ip, px->len & LEN_MASK); -} - - struct ea_list; struct rte; @@ -168,6 +146,8 @@ void val_format(struct f_val v, buffer *buf); #define T_ENUM_RTC 0x33 #define T_ENUM_RTD 0x34 #define T_ENUM_ROA 0x35 +#define T_ENUM_NETTYPE 0x36 + /* new enums go here */ #define T_ENUM_EMPTY 0x3f /* Special hack for atomic_aggr */ @@ -175,7 +155,7 @@ void val_format(struct f_val v, buffer *buf); /* Bigger ones */ #define T_IP 0x20 -#define T_PREFIX 0x21 +#define T_NET 0x21 #define T_STRING 0x22 #define T_PATH_MASK 0x23 /* mask for BGP path */ #define T_PATH 0x24 /* BGP path */ @@ -184,6 +164,7 @@ void val_format(struct f_val v, buffer *buf); #define T_ECLIST 0x27 /* Extended community list */ #define T_LC 0x28 /* Large community value, lcomm */ #define T_LCLIST 0x29 /* Large community list */ +#define T_RD 0x2a /* Route distinguisher for VPN addresses */ #define T_RETURN 0x40 #define T_SET 0x80 @@ -196,10 +177,9 @@ void val_format(struct f_val v, buffer *buf); #define SA_PROTO 4 #define SA_SOURCE 5 #define SA_SCOPE 6 -#define SA_CAST 7 -#define SA_DEST 8 -#define SA_IFNAME 9 -#define SA_IFINDEX 10 +#define SA_DEST 7 +#define SA_IFNAME 8 +#define SA_IFINDEX 9 struct f_tree { @@ -211,7 +191,7 @@ struct f_tree { struct f_trie_node { ip_addr addr, mask, accept; - int plen; + uint plen; struct f_trie_node *c[2]; }; @@ -227,4 +207,15 @@ struct f_trie #define FF_FORCE_TMPATTR 1 /* Force all attributes to be temporary */ +/* Bird Tests */ +struct f_bt_test_suite { + node n; /* Node in config->tests */ + struct f_inst *fn; /* Root of function */ + const char *fn_name; /* Name of test */ + const char *dsc; /* Description */ +}; + +/* Hook for call bt_assert() function in configuration */ +extern void (*bt_assert_hook)(int result, struct f_inst *assert); + #endif diff --git a/filter/filter_test.c b/filter/filter_test.c new file mode 100644 index 00000000..be7fd521 --- /dev/null +++ b/filter/filter_test.c @@ -0,0 +1,87 @@ +/* + * Filters: Tests + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <string.h> +#include <stdlib.h> + +#include "test/birdtest.h" +#include "test/bt-utils.h" + +#include "filter/filter.h" +#include "conf/conf.h" + +#define BT_CONFIG_FILE "filter/test.conf" + + +static struct config * +parse_config_file(const void *filename_void) +{ + bt_bird_init(); + + size_t fn_size = strlen((const char *) filename_void) + 1; + char *filename = alloca(fn_size); + strncpy(filename, filename_void, fn_size); + + struct config *c = bt_config_file_parse(filename); + bt_bird_cleanup(); + + return c; +} + +static int +run_function(const void *parsed_fn_def) +{ + /* XXX: const -> non-const */ + struct f_inst *f = (struct f_inst *) parsed_fn_def; + + linpool *tmp = lp_new_default(&root_pool); + struct f_val res = f_eval(f, tmp); + rfree(tmp); + + if (res.type == T_RETURN && res.val.i >= F_REJECT) + return 0; + + return 1; +} + +static void +bt_assert_filter(int result, struct f_inst *assert) +{ + int bt_suit_case_result = 1; + if (!result) + { + bt_result = 0; + bt_suite_result = 0; + bt_suit_case_result = 0; + } + + bt_log_suite_case_result(bt_suit_case_result, "Assertion at line %d (%s)", assert->lineno, (char *) assert->a2.p); +} + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + struct config *c = parse_config_file(BT_CONFIG_FILE); + + if (c) + { + bt_assert_hook = bt_assert_filter; + + struct f_bt_test_suite *t; + WALK_LIST(t, c->tests) + bt_test_suite_base(run_function, t->fn_name, t->fn, BT_FORKING, BT_TIMEOUT, "%s", t->dsc); + } + + return bt_exit_value(); +} diff --git a/filter/test.conf b/filter/test.conf index 676b47d1..e25105d3 100644 --- a/filter/test.conf +++ b/filter/test.conf @@ -1,127 +1,636 @@ - /* - * This is an example configuration file. + * This is unit testing configuration file for testing filters + * * FIXME: add all examples from docs here. */ -# Yet another comment - router id 62.168.0.1; -define xyzzy = (120+10); -define '1a-a1' = (20+10); +/* We have to setup any protocol */ +protocol static { ipv4; } + + + + +/* + * Common definitions and functions + * -------------------------------- + */ + define one = 1; define ten = 10; -define p23 = (2, 3); -define ip1222 = 1.2.2.2; - function onef(int a) { return 1; } + + +/* + * Testing boolean expressions + * --------------------------- + */ + +function t_bool() +bool b; +{ + b = true; + bt_assert(b); + bt_assert(!!b); + + bt_assert(format(true) = "TRUE"); + bt_assert(format(false) = "FALSE"); + + if ( b = true ) then + bt_assert(b); + else + bt_assert(false); + + bt_assert(true && true); + bt_assert(true || false); + bt_assert(! false && ! false && true); + bt_assert(1 < 2 && 1 != 3); + bt_assert(true && true && ! false); + bt_assert(true || 1+"a"); + bt_assert(!(false && 1+"a")); + bt_assert(!(true && false)); +} + +bt_test_suite(t_bool, "Testing boolean expressions"); + + + + +/* + * Testing integers + * ---------------- + */ + +define four = 4; +define xyzzy = (120+10); +define '1a-a1' = (xyzzy-100); + +function t_int() +int i; +{ + bt_assert(xyzzy = 130); + bt_assert('1a-a1' = 30); + + i = four; + i = 12*100 + 60/2 + i; + i = (i + 0); + bt_assert(i = 1234); + + bt_assert(format(i) = "1234"); + + i = 4200000000; + bt_assert(i = 4200000000); + bt_assert(i > 4100000000); + bt_assert(!(i > 4250000000)); + + bt_assert(1 = 1); + bt_assert(!(1 != 1)); + + bt_assert(1 != 2); + bt_assert(1 <= 2); + + bt_assert(1 != "a"); + bt_assert(1 != (0,1)); + + bt_assert(!(i = 4)); + bt_assert(1 <= 1); + bt_assert(!(1234 < 1234)); +} + +bt_test_suite(t_int, "Testing integers"); + + + + +/* + * Testing sets of integers + * ------------------------ + */ + +define is1 = [ one, (2+1), (6-one), 8, 11, 15, 17, 19]; +define is2 = [(17+2), 17, 15, 11, 8, 5, 3, 2]; +define is3 = [5, 17, 2, 11, 8, 15, 3, 19]; + +function t_int_set() +int set is; +{ + bt_assert(1 ~ [1,2,3]); + bt_assert(5 ~ [1..20]); + bt_assert(2 ~ [ 1, 2, 3 ]); + bt_assert(5 ~ [ 4 .. 7 ]); + bt_assert(1 !~ [ 2, 3, 4 ]); + + is = [ 2, 3, 4, 7..11 ]; + bt_assert(10 ~ is); + bt_assert(5 !~ is); + + bt_assert(1 ~ is1); + bt_assert(3 ~ is1); + bt_assert(5 ~ is1); + bt_assert((one+2) ~ is1); + bt_assert(2 ~ is2); + bt_assert(2 ~ is3); + bt_assert(4 !~ is1); + bt_assert(4 !~ is2); + bt_assert(4 !~ is3); + bt_assert(10 !~ is1); + bt_assert(10 !~ is2); + bt_assert(10 !~ is3); + bt_assert(15 ~ is1); + bt_assert(15 ~ is2); + bt_assert(15 ~ is3); + bt_assert(18 !~ is1); + bt_assert(18 !~ is2); + bt_assert(18 !~ is3); + bt_assert(19 ~ is1); + bt_assert(19 ~ is2); + bt_assert(19 ~ is3); + bt_assert(20 !~ is1); + bt_assert(20 !~ is2); + bt_assert(20 !~ is3); + + bt_assert([1,2] != [1,3]); + bt_assert([1,4..10,20] = [1,4..10,20]); + + bt_assert(format([ 1, 2, 1, 1, 1, 3, 4, 1, 1, 1, 5 ]) = "[1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5]"); +} + +bt_test_suite(t_int_set, "Testing sets of integers"); + + + + +/* + * Testing string matching + * ----------------------- + */ + +function t_string() +string st; +{ + st = "Hello"; + bt_assert(format(st) = "Hello"); + bt_assert(st ~ "Hell*"); + bt_assert(st ~ "?ello"); + bt_assert(st ~ "Hello"); + bt_assert(st ~ "Hell?"); + bt_assert(st !~ "ell*"); +} + +bt_test_suite(t_string, "Testing string matching"); + + + + +/* + * Testing pairs + * ------------- + */ + function 'mkpair-a'(int a) { return (1, a); } -function mktrip(int a) +function t_pair() +pair pp; { - return (a, 2*a, 3*a); + pp = (1, 2); + bt_assert(format(pp) = "(1,2)"); + bt_assert((1,2) = pp); + bt_assert((1,1+1) = pp); + bt_assert('mkpair-a'(2) = pp); + bt_assert((1,2) = (1,1+1)); + bt_assert(((1,2) < (2,2))); + bt_assert(!((1,1) > (1,1))); } -function mkpath(int a; int b) +bt_test_suite(t_pair, "Testing pairs"); + + + + +/* + * Testing sets of pairs + * --------------------- + */ + +function t_pair_set() +pair pp; +pair set ps; { - return [= a b 3 2 1 =]; + pp = (1, 2); + ps = [(1,(one+one)), (3,4)..(4,8), (5,*), (6,3..6)]; + bt_assert(format(ps) = "[(1,2), (3,4)..(4,8), (5,0)..(5,65535), (6,3)..(6,6)]"); + bt_assert(pp ~ ps); + bt_assert((3,5) ~ ps); + bt_assert((4,1) ~ ps); + bt_assert((5,4) ~ ps); + bt_assert((5,65535) ~ ps); + bt_assert((6,4) ~ ps); + bt_assert((3, 10000) ~ ps); + bt_assert((3,3) !~ ps); + bt_assert((4,9) !~ ps); + bt_assert((4,65535) !~ ps); + bt_assert((6,2) !~ ps); + bt_assert((6,6+one) !~ ps); + bt_assert(((one+6),2) !~ ps); + bt_assert((1,1) !~ ps); + + ps = [(20..150, 200..300), (50100..50200, 1000..50000), (*, 5+5)]; + bt_assert((100,200) ~ ps); + bt_assert((150,300) ~ ps); + bt_assert((50180,1200) ~ ps); + bt_assert((50110,49000) ~ ps); + bt_assert((0,10) ~ ps); + bt_assert((64000,10) ~ ps); + bt_assert((20,199) !~ ps); + bt_assert((151,250) !~ ps); + bt_assert((50050,2000) !~ ps); + bt_assert((50150,50050) !~ ps); + bt_assert((10,9) !~ ps); + bt_assert((65535,11) !~ ps); } -function callme(int arg1; int arg2) -int local1; -int local2; -int i; +bt_test_suite(t_pair_set, "Testing sets of pairs"); + + + + +/* + * Testing quads + * ------------- + */ + +function t_quad() +quad qq; { - printn "Function callme called arguments ", arg1, " and ", arg2, ": " ; - i = arg2; + qq = 1.2.3.4; + bt_assert(format(qq) = "1.2.3.4"); + bt_assert(qq = 1.2.3.4); + bt_assert(qq != 4.3.2.1); +} - case arg1 { - 11, 1, 111: printn "jedna, "; printn "jedna"; - (one+onef(2)): printn "dva, "; printn "jeste jednou dva"; - (2+one) .. 5: if arg2 < 3 then printn "tri az pet"; - else: printn "neco jineho"; - } - print; +bt_test_suite(t_quad, "Testing quads"); + + + + +/* + * Testing sets of quads + * --------------------- + */ + +function t_quad_set() +quad qq; +{ + qq = 1.2.3.4; + bt_assert(qq ~ [1.2.3.4, 5.6.7.8]); + bt_assert(qq !~ [1.2.1.1, 1.2.3.5]); } -function fifteen() +bt_test_suite(t_quad_set, "Testing sets of quads"); + + + + +/* + * Testing ip address + * ------------------ + */ + +define onetwo = 1.2.3.4; + +function t_ip() +ip p; { - print "fifteen called"; - return 15; + p = 127.1.2.3; + bt_assert(p.is_v4); + bt_assert(p.mask(8) = 127.0.0.0); + bt_assert(1.2.3.4 = 1.2.3.4); + bt_assert(1.2.3.4 = onetwo); + bt_assert(format(p) = "127.1.2.3"); + + p = ::fffe:6:c0c:936d:88c7:35d3; + bt_assert(!p.is_v4); + bt_assert(format(p) = "::fffe:6:c0c:936d:88c7:35d3"); + + p = 1234:5678::; + bt_assert(!p.is_v4); + bt_assert(p.mask(24) = 1234:5600::); } -roa table rl +bt_test_suite(t_ip, "Testing ip address"); + + + + +/* + * Testing sets of ip address + * -------------------------- + */ + +define ip1222 = 1.2.2.2; + +function t_ip_set() +ip set ips; { - roa 10.110.0.0/16 max 16 as 1000; - roa 10.120.0.0/16 max 24 as 1000; - roa 10.130.0.0/16 max 24 as 2000; - roa 10.130.128.0/18 max 24 as 3000; + ips = [ 1.1.1.0 .. 1.1.1.255, ip1222]; + bt_assert(format(ips) = "[1.1.1.0..1.1.1.255, 1.2.2.2]"); + bt_assert(1.1.1.0 ~ ips); + bt_assert(1.1.1.100 ~ ips); + bt_assert(1.2.2.2 ~ ips); + bt_assert(1.1.0.255 !~ ips); + bt_assert(1.1.2.0 !~ ips); + bt_assert(1.2.2.3 !~ ips); + bt_assert(192.168.1.1 !~ ips); + + bt_assert(1.2.3.4 !~ [ 1.2.3.3, 1.2.3.5 ]); + bt_assert(1.2.3.4 ~ [ 1.2.3.3..1.2.3.5 ]); } -function test_roa() +bt_test_suite(t_ip_set, "Testing sets of ip address"); + + + + +/* + * Testing enums + * ------------- + */ + +function t_enum() { - # cannot be tested in __startup(), sorry - print "Testing ROA"; - print "Should be true: ", roa_check(rl, 10.10.0.0/16, 1000) = ROA_UNKNOWN, - " ", roa_check(rl, 10.0.0.0/8, 1000) = ROA_UNKNOWN, - " ", roa_check(rl, 10.110.0.0/16, 1000) = ROA_VALID, - " ", roa_check(rl, 10.110.0.0/16, 2000) = ROA_INVALID, - " ", roa_check(rl, 10.110.32.0/20, 1000) = ROA_INVALID, - " ", roa_check(rl, 10.120.32.0/20, 1000) = ROA_VALID; - print "Should be true: ", roa_check(rl, 10.120.32.0/20, 2000) = ROA_INVALID, - " ", roa_check(rl, 10.120.32.32/28, 1000) = ROA_INVALID, - " ", roa_check(rl, 10.130.130.0/24, 1000) = ROA_INVALID, - " ", roa_check(rl, 10.130.130.0/24, 2000) = ROA_VALID, - " ", roa_check(rl, 10.130.30.0/24, 3000) = ROA_INVALID, - " ", roa_check(rl, 10.130.130.0/24, 3000) = ROA_VALID; -} - -function path_test() + bt_assert(format(RTS_DUMMY) = "(enum 30)0"); + bt_assert(format(RTS_STATIC) = "(enum 30)1"); + bt_assert(RTS_STATIC ~ [RTS_STATIC, RTS_DEVICE]); + bt_assert(RTS_BGP !~ [RTS_STATIC, RTS_DEVICE]); +} + +bt_test_suite(t_enum, "Testing enums"); + + + + +/* + * Testing prefixes + * ---------------- + */ + +define netdoc = 2001:db8::/32; + +function t_prefix() +prefix px; +{ + px = 1.2.0.0/18; + bt_assert(format(px) = "1.2.0.0/18"); + bt_assert(192.168.0.0/16 ~ 192.168.0.0/16); + bt_assert(192.168.0.0/17 ~ 192.168.0.0/16); + bt_assert(192.168.254.0/24 ~ 192.168.0.0/16); + bt_assert(netdoc ~ 2001::/16); + bt_assert(192.168.0.0/15 !~ 192.168.0.0/16); + bt_assert(192.160.0.0/17 !~ 192.168.0.0/16); + bt_assert(px !~ netdoc); + + bt_assert(1.2.3.4 ~ 1.0.0.0/8); + bt_assert(1.0.0.0/8 ~ 1.0.0.0/8); +} + +bt_test_suite(t_prefix, "Testing prefixes"); + + + + +/* + * Testing prefix sets + * ------------------- + */ + +define net10 = 10.0.0.0/8; +define pxs2 = [ 10.0.0.0/16{8,12}, 20.0.0.0/16{24,28} ]; + +function test_pxset(prefix set pxs) +{ + bt_assert(net10 ~ pxs); + bt_assert(10.0.0.0/10 ~ pxs); + bt_assert(10.0.0.0/12 ~ pxs); + bt_assert(20.0.0.0/24 ~ pxs); + bt_assert(20.0.40.0/24 ~ pxs); + bt_assert(20.0.0.0/26 ~ pxs); + bt_assert(20.0.100.0/26 ~ pxs); + bt_assert(20.0.0.0/28 ~ pxs); + bt_assert(20.0.255.0/28 ~ pxs); + + bt_assert(10.0.0.0/7 !~ pxs); + bt_assert(10.0.0.0/13 !~ pxs); + bt_assert(10.0.0.0/16 !~ pxs); + bt_assert(20.0.0.0/16 !~ pxs); + bt_assert(20.0.0.0/23 !~ pxs); + bt_assert(20.0.0.0/29 !~ pxs); + bt_assert(11.0.0.0/10 !~ pxs); + bt_assert(20.1.0.0/26 !~ pxs); + + bt_assert(1.0.0.0/8 ~ [ 1.0.0.0/8+ ]); + bt_assert(1.0.0.0/9 !~ [ 1.0.0.0/8- ]); + bt_assert(1.2.0.0/17 !~ [ 1.0.0.0/8{ 15 , 16 } ]); + + bt_assert([ 10.0.0.0/8{ 15 , 17 } ] = [ 10.0.0.0/8{ 15 , 17 } ]); +} + +function t_prefix_set() +prefix set pxs; +{ + pxs = [ 1.2.0.0/16, 1.4.0.0/16+, 44.66.88.64/30{24,28}, 12.34.56.0/24{8,16} ]; + bt_assert(format(pxs) = "[1.2.0.0/112{::0.1.0.0}, 1.4.0.0/112{::0.1.255.255}, 12.34.0.0/112{::1.255.0.0}, 44.66.88.64/124{::1f0}]"); + bt_assert(1.2.0.0/16 ~ pxs); + bt_assert(1.4.0.0/16 ~ pxs); + bt_assert(1.4.0.0/18 ~ pxs); + bt_assert(1.4.0.0/32 ~ pxs); + bt_assert(1.1.0.0/16 !~ pxs); + bt_assert(1.3.0.0/16 !~ pxs); + bt_assert(1.2.0.0/15 !~ pxs); + bt_assert(1.2.0.0/17 !~ pxs); + bt_assert(1.2.0.0/32 !~ pxs); + bt_assert(1.4.0.0/15 !~ pxs); + + test_pxset(pxs2); + test_pxset([ 10.0.0.0/16{8,12}, 20.0.0.0/16{24,28} ]); + + bt_assert(1.2.0.0/16 ~ [ 1.0.0.0/8{ 15 , 17 } ]); + bt_assert([ 10.0.0.0/8{ 15 , 17 } ] != [ 11.0.0.0/8{ 15 , 17 } ]); +} + +bt_test_suite(t_prefix_set, "Testing prefix sets"); + + + + +/* + * Testing Prefix IPv6 + * ------------------- + */ + +function t_prefix6() +prefix px; +{ + px = 1020::/18; + bt_assert(format(px) = "1020::/18"); + bt_assert(1020:3040:5060:: ~ 1020:3040:5000::/40); + bt_assert(1020:3040::/32 ~ 1020:3040::/32); + bt_assert(1020:3040::/33 ~ 1020:3040::/32); + bt_assert(1020:3040:5060::/48 ~ 1020:3040::/32); + bt_assert(1020:3040::/31 !~ 1020:3040::/32); + bt_assert(1020:3041::/33 !~ 1020:3040::/32); +} + +bt_test_suite(t_prefix6, "Testing prefix IPv6"); + + + + +/* + * Testing prefix IPv6 sets + * ------------------------ + */ + +function t_prefix6_set() +prefix set pxs; +{ + bt_assert(1180::/16 ~ [ 1100::/8{15, 17} ]); + bt_assert(12::34 = 12::34); + bt_assert(12::34 ~ [ 12::33..12::35 ]); + bt_assert(1020::34 ~ 1000::/8); + bt_assert(1000::/8 ~ 1000::/8); + bt_assert(1000::/8 ~ [ 1000::/8+ ]); + bt_assert(12::34 !~ [ 12::33, 12::35 ]); + bt_assert(1000::/9 !~ [ 1000::/8- ]); + bt_assert(1000::/17 !~ [ 1000::/8{15, 16} ]); + + pxs = [ 1102::/16, 1104::/16+]; + bt_assert(1102::/16 ~ pxs); + bt_assert(1104::/16 ~ pxs); + bt_assert(1104::/18 ~ pxs); + bt_assert(1104::/32 ~ pxs); + bt_assert(1101::/16 !~ pxs); + bt_assert(1103::/16 !~ pxs); + bt_assert(1102::/15 !~ pxs); + bt_assert(1102::/17 !~ pxs); + bt_assert(1102::/32 !~ pxs); + bt_assert(1104::/15 !~ pxs); + + pxs = ([ 1000::/16{8,12}, 2000::/16{24,28} ]); + bt_assert(format(pxs) = "[1000::/12{1f0::}, 2000::/16{0:1f0::}]"); + bt_assert(1000::/8 ~ pxs); + bt_assert(1000::/10 ~ pxs); + bt_assert(1000::/12 ~ pxs); + bt_assert(2000::/24 ~ pxs); + bt_assert(2000:4000::/24 ~ pxs); + bt_assert(2000::/26 ~ pxs); + bt_assert(2000:8000::/26 ~ pxs); + bt_assert(2000::/28 ~ pxs); + bt_assert(2000:FFF0::/28 ~ pxs); + bt_assert(1000::/7 !~ pxs); + bt_assert(1000::/13 !~ pxs); + bt_assert(1000::/16 !~ pxs); + bt_assert(2000::/16 !~ pxs); + bt_assert(2000::/23 !~ pxs); + bt_assert(2000::/29 !~ pxs); + bt_assert(1100::/10 !~ pxs); + bt_assert(2010::/26 !~ pxs); +} + +bt_test_suite(t_prefix6_set, "Testing prefix IPv6 sets"); + + + + +function t_flowspec() +prefix p; +{ + p = flow4 { dst 10.0.0.0/8; }; + bt_assert(p !~ [ 10.0.0.0/8 ] ); + + bt_assert(format(flow4 { dst 10.0.0.0/8; proto = 23; }) = "flow4 { dst 10.0.0.0/8; proto 23; }"); + bt_assert(format(flow6 { dst ::1/128; src ::2/127; }) = "flow6 { dst ::1/128; src ::2/127; }"); + bt_assert(format(flow6 { next header false 42; }) = "flow6 { next header false 42; }"); + bt_assert(format(flow6 { port 80; }) = "flow6 { port 80; }"); + bt_assert(format(flow6 { dport > 24 && < 30 || 40..50,60..70,80 && >= 90; }) = "flow6 { dport > 24 && < 30 || 40..50,60..70,80 && >= 90; }"); + bt_assert(format(flow6 { sport 0..0x400; }) = "flow6 { sport 0..1024; }"); + bt_assert(format(flow6 { icmp type 80; }) = "flow6 { icmp type 80; }"); + bt_assert(format(flow6 { icmp code 90; }) = "flow6 { icmp code 90; }"); + bt_assert(format(flow6 { tcp flags 0x03/0x0f; }) = "flow6 { tcp flags 0x3/0x3,0x0/0xc; }"); + bt_assert(format(flow6 { length 0..65535; }) = "flow6 { length 0..65535; }"); + bt_assert(format(flow6 { dscp = 63; }) = "flow6 { dscp 63; }"); + bt_assert(format(flow6 { fragment is_fragment || !first_fragment; }) = "flow6 { fragment is_fragment || !first_fragment; }"); + bt_assert(format(flow6 { }) = "flow6 { }"); +} + +bt_test_suite(t_flowspec, "Testing flowspec routes"); + + + + +/* + * Testing Paths + * ------------- + */ + +function mkpath(int a; int b) +{ + return [= a b 3 2 1 =]; +} + +function t_path() bgpmask pm1; bgpmask pm2; bgppath p2; -clist l; -clist l2; -eclist el; -eclist el2; -lclist ll; -lclist ll2; { - print "Entering path test..."; pm1 = / 4 3 2 1 /; - pm2 = [= 3..6 3 2 1..2 =]; - print "Testing path masks: ", pm1, " ", pm2; + pm2 = [= 4 3 2 1 =]; + + bt_assert(pm1 = pm2); + bt_assert(format(pm2) = "[= 4 3 2 1 =]"); + + bt_assert(+empty+ = +empty+); + bt_assert(10 !~ +empty+); + p2 = prepend( + empty +, 1 ); p2 = prepend( p2, 2 ); p2 = prepend( p2, 3 ); p2 = prepend( p2, 4 ); - print "Testing path: (4 3 2 1) = ", p2; - print "Should be true: ", p2 ~ pm1, " ", p2 ~ pm2, " ", 3 ~ p2, " ", p2 ~ [2, 10..20], " ", p2 ~ [4, 10..20]; - print "4 = ", p2.len; - p2 = prepend( p2, 5 ); - print "Testing path: (5 4 3 2 1) = ", p2; - print "Should be false: ", p2 ~ pm1, " ", p2 ~ pm2, " ", 10 ~ p2, " ", p2 ~ [8, ten..(2*ten)], " ", p2 ~ [= 1..4 4 3 2 1 =], " ", p2 ~ [= 5 4 4..100 2 1 =]; - print "Should be true: ", p2 ~ / ? 4 3 2 1 /, " ", p2, " ", / ? 4 3 2 1 /; - print "Should be true: ", p2 ~ [= * 4 3 * 1 =], " ", p2, " ", [= * 4 3 * 1 =]; - print "Should be true: ", p2 ~ [= 5..6 4..10 1..3 1..3 1..65536 =]; - print "Should be true: ", p2 ~ [= (3+2) (2*2) 3 2 1 =], " ", p2 ~ mkpath(5, 4); - print "Should be true: ", p2.len = 5, " ", p2.first = 5, " ", p2.last = 1; - print "Should be true: ", pm1 = [= 4 3 2 1 =], " ", pm1 != [= 4 3 1 2 =], " ", - pm2 = [= 3..6 3 2 1..2 =], " ", pm2 != [= 3..6 3 2 1..3 =], " ", - [= 1 2 (1+2) =] = [= 1 2 (1+2) =], " ", [= 1 2 (1+2) =] != [= 1 2 (2+1) =]; - print "5 = ", p2.len; - print "Delete 3: ", delete(p2, 3); - print "Filter 1-3: ", filter(p2, [1..3]); + + bt_assert(format(p2) = "(path 4 3 2 1)"); + bt_assert(p2.len = 4); + bt_assert(p2 ~ pm1); + bt_assert(p2 ~ pm2); + bt_assert(3 ~ p2); + bt_assert(p2 ~ [2, 10..20]); + bt_assert(p2 ~ [4, 10..20]); + + p2 = prepend(p2, 5); + bt_assert(p2 !~ pm1); + bt_assert(p2 !~ pm2); + bt_assert(10 !~ p2); + bt_assert(p2 !~ [8, ten..(2*ten)]); + bt_assert(p2 ~ / ? 4 3 2 1 /); + bt_assert(p2 ~ [= * 4 3 * 1 =]); + bt_assert(p2 ~ [= (3+2) (2*2) 3 2 1 =]); + bt_assert(p2 ~ mkpath(5, 4)); + + bt_assert(p2.len = 5); + bt_assert(p2.first = 5); + bt_assert(p2.last = 1); + + bt_assert(p2.len = 5); + bt_assert(delete(p2, 3) = prepend(prepend(prepend(prepend(+empty+, 1), 2), 4), 5)); + bt_assert(filter(p2, [1..3]) = prepend(prepend(prepend(+empty+, 1), 2), 3)); pm1 = [= 1 2 * 3 4 5 =]; p2 = prepend( + empty +, 5 ); @@ -130,63 +639,176 @@ lclist ll2; p2 = prepend( p2, 3 ); p2 = prepend( p2, 2 ); p2 = prepend( p2, 1 ); - print "Should be true: ", p2 ~ pm1, " ", p2, " ", pm1; - print "Delete 3: ", delete(p2, 3); - print "Delete 4-5: ", delete(p2, [4..5]); + bt_assert(p2 ~ pm1); + bt_assert(delete(p2, 3) = prepend(prepend(prepend(prepend(+empty+, 5), 4), 2), 1)); + bt_assert(delete(p2, [4..5]) = prepend(prepend(prepend(prepend(+empty+, 3), 3), 2), 1)); +} + +bt_test_suite(t_path, "Testing paths"); + + + + +/* + * Testing Community List + * ---------------------- + */ + +define p23 = (2, 3); + +function t_clist() +clist l; +clist l2; +clist r; +{ l = - empty -; - print "Should be false in this special case: ", l ~ [(*,*)]; + bt_assert(l !~ [(*,*)]); + bt_assert((l ~ [(*,*)]) != (l !~ [(*,*)])); + + bt_assert(-empty- = -empty-); + l = add( l, (one,2) ); - print "Should be always true: ", l ~ [(*,*)]; + bt_assert(l ~ [(*,*)]); l = add( l, (2,one+2) ); - print "Community list (1,2) (2,3) ", l; - print "Should be true: ", (2,3) ~ l, " ", l ~ [(1,*)], " ", l ~ [p23]," ", l ~ [(2,2..3)], " ", l ~ [(1,1..2)], " ", l ~ [(1,1)..(1,2)]; - l = add( l, (2,5) ); - l = add( l, (5,one) ); - l = add( l, (6,one) ); - l = add( l, (one,one) ); - l = delete( l, [(5,1),(6,one),(one,1)] ); - l = delete( l, [(5,one),(6,one)] ); - l = filter( l, [(1,*)] ); - print "Community list (1,2) ", l; - print "Should be false: ", (2,3) ~ l, " ", l ~ [(2,*)], " ", l ~ [(one,3..6)]; - print "Should be always true: ", l ~ [(*,*)]; - l = add( l, (3,one) ); - l = add( l, (one+one+one,one+one) ); - l = add( l, (3,3) ); - l = add( l, (3,4) ); - l = add( l, (3,5) ); - l2 = filter( l, [(3,*)] ); - l = delete( l, [(3,2..4)] ); - print "Community list (1,2) (3,1) (3,5) ", l, " len: ", l.len; - l = add( l, (3,2) ); - l = add( l, (4,5) ); - print "Community list (1,2) (3,1) (3,5) (3,2) (4,5) ", l, " len: ", l.len; - print "Should be true: ", l ~ [(*,2)], " ", l ~ [(*,5)], " ", l ~ [(*, one)]; - print "Should be false: ", l ~ [(*,3)], " ", l ~ [(*,(one+6))], " ", l ~ [(*, (one+one+one))]; - l = delete( l, [(*,(one+onef(3)))] ); - l = delete( l, [(*,(4+one))] ); - print "Community list (3,1) ", l; - l = delete( l, [(*,(onef(5)))] ); - print "Community list empty ", l; - l2 = add( l2, (3,6) ); - l = filter( l2, [(3,1..4)] ); - l2 = filter( l2, [(3,3..6)] ); - print "clist A (1..4): ", l; - print "clist B (3..6): ", l2; - print "clist A union B: ", add( l2, l ); - print "clist A isect B: ", filter( l, l2 ); - print "clist A \ B: ", delete( l, l2 ); + bt_assert(format(l) = "(clist (1,2) (2,3))"); + + bt_assert((2,3) ~ l); + bt_assert(l ~ [(1,*)]); + bt_assert(l ~ [p23]); + bt_assert(l ~ [(2,2..3)]); + bt_assert(l ~ [(1,1..2)]); + bt_assert(l ~ [(1,1)..(1,2)]); + + l = add(l, (2,5)); + l = add(l, (5,one)); + l = add(l, (6,one)); + l = add(l, (one,one)); + l = delete(l, [(5,1),(6,one),(one,1)]); + l = delete(l, [(5,one),(6,one)]); + l = filter(l, [(1,*)]); + bt_assert(l = add(-empty-, (1,2))); + + bt_assert((2,3) !~ l); + bt_assert(l !~ [(2,*)]); + bt_assert(l !~ [(one,3..6)]); + bt_assert(l ~ [(*,*)]); + + l = add(l, (3,one)); + l = add(l, (one+one+one,one+one)); + l = add(l, (3,3)); + l = add(l, (3,4)); + l = add(l, (3,5)); + l2 = filter(l, [(3,*)]); + l = delete(l, [(3,2..4)]); + bt_assert(l = add(add(add(-empty-, (1,2)), (3,1)), (3,5))); + bt_assert(l.len = 3); + + l = add(l, (3,2)); + l = add(l, (4,5)); + bt_assert(l = add(add(add(add(add(-empty-, (1,2)), (3,1)), (3,5)), (3,2)), (4,5))); + + bt_assert(l.len = 5); + bt_assert(l ~ [(*,2)]); + bt_assert(l ~ [(*,5)]); + bt_assert(l ~ [(*, one)]); + bt_assert(l !~ [(*,3)]); + bt_assert(l !~ [(*,(one+6))]); + bt_assert(l !~ [(*, (one+one+one))]); + + l = delete(l, [(*,(one+onef(3)))]); + l = delete(l, [(*,(4+one))]); + bt_assert(l = add(-empty-, (3,1))); + l = delete(l, [(*,(onef(5)))]); + bt_assert(l = -empty-); + + l2 = add(l2, (3,6)); + l = filter(l2, [(3,1..4)]); + l2 = filter(l2, [(3,3..6)]); + + # clist A (10,20,30) + bt_assert(l = add(add(add(add(-empty-, (3,1)), (3,2)), (3,3)), (3,4))); + bt_assert(format(l) = "(clist (3,1) (3,2) (3,3) (3,4))"); + + # clist B (30,40,50) + bt_assert(l2 = add(add(add(add(-empty-, (3,3)), (3,4)), (3,5)), (3,6))); + bt_assert(format(l2) = "(clist (3,3) (3,4) (3,5) (3,6))"); + + # clist A union B + r = add(l, l2); + bt_assert(r = add(add(add(add(add(add(-empty-, (3,1)), (3,2)), (3,3)), (3,4)), (3,5)), (3,6))); + bt_assert(format(r) = "(clist (3,1) (3,2) (3,3) (3,4) (3,5) (3,6))"); + + # clist A isect B + r = filter(l, l2); + bt_assert(r = add(add(-empty-, (3,3)), (3,4))); + bt_assert(format(r) = "(clist (3,3) (3,4))"); + + # clist A \ B + r = delete(l, l2); + bt_assert(r = add(add(-empty-, (3,1)), (3,2))); + bt_assert(format(r) = "(clist (3,1) (3,2))"); + + # clist in c set + r = filter(l, [(3,1), (*,2)]); + bt_assert(r = add(add(-empty-, (3,1)), (3,2))); + bt_assert(format(r) = "(clist (3,1) (3,2))"); +} + +bt_test_suite(t_clist, "Testing lists of communities"); + + + + +/* + * Testing Extended Communities + * ---------------------------- + */ + +function t_ec() +ec cc; +{ + cc = (rt, 12345, 200000); + bt_assert(format(cc) = "(rt, 12345, 200000)"); + + bt_assert(cc = (rt, 12345, 200000)); + bt_assert(cc < (rt, 12345, 200010)); + bt_assert(cc != (rt, 12346, 200000)); + bt_assert(cc != (ro, 12345, 200000)); + bt_assert(!(cc > (rt, 12345, 200010))); + + bt_assert(format((ro, 100000, 20000)) = "(ro, 100000, 20000)"); +} + +bt_test_suite(t_ec, "Testing extended communities"); + + + + +/* + * Testing Extended Community List + * ------------------------------- + */ + +function t_eclist() +eclist el; +eclist el2; +eclist r; +{ el = -- empty --; el = add(el, (rt, 10, 20)); el = add(el, (ro, 10.20.30.40, 100)); el = add(el, (ro, 11.21.31.41.mask(16), 200)); - print "EC list (rt, 10, 20) (ro, 10.20.30.40, 100) (ro, 11.21.0.0, 200):"; - print el; - print "EC len: ", el.len; + + bt_assert(--empty-- = --empty--); + bt_assert(((rt, 10, 20)) !~ --empty--); + + bt_assert(format(el) = "(eclist (rt, 10, 20) (ro, 10.20.30.40, 100) (ro, 11.21.0.0, 200))"); + bt_assert(el.len = 3); el = delete(el, (rt, 10, 20)); el = delete(el, (rt, 10, 30)); + bt_assert(el = add(add(--empty--, (ro, 10.20.30.40, 100)), (ro, 11.21.0.0, 200))); el = add(el, (unknown 2, ten, 1)); el = add(el, (unknown 5, ten, 1)); el = add(el, (rt, ten, one+one)); @@ -195,31 +817,117 @@ lclist ll2; el = add(el, (rt, 10, 5)); el = add(el, (generic, 0x2000a, 3*ten)); el = delete(el, [(rt, 10, 2..ten)]); - print "EC list (ro, 10.20.30.40, 100) (ro, 11.21.0.0, 200) (rt, 10, 1) (unknown 0x5, 10, 1) (rt, 10, 30):"; - print el; + bt_assert(el = add(add(add(add(add(--empty--, (ro, 10.20.30.40, 100)), (ro, 11.21.0.0, 200)), (rt, 10, 1)), (unknown 5, 10, 1)), (rt, 10, 30))); + el = filter(el, [(rt, 10, *)]); - print "EC list (rt, 10, 1) (rt, 10, 30): ", el; - print "Testing EC list, true: ", (rt, 10, 1) ~ el, " ", el ~ [(rt, 10, ten..40)]; - print "Testing EC list, false: ", (rt, 10, 20) ~ el, " ", (ro, 10.20.30.40, 100) ~ el, " ", el ~ [(rt, 10, 35..40)], " ", el ~ [(ro, 10, *)]; + bt_assert(el = add(add(--empty--, (rt, 10, 1)), (rt, 10, 30))); + bt_assert((rt, 10, 1) ~ el); + bt_assert(el ~ [(rt, 10, ten..40)]); + bt_assert((rt, 10, 20) !~ el); + bt_assert((ro, 10.20.30.40, 100) !~ el); + bt_assert(el !~ [(rt, 10, 35..40)]); + bt_assert(el !~ [(ro, 10, *)]); + el = add(el, (rt, 10, 40)); el2 = filter(el, [(rt, 10, 20..40)] ); el2 = add(el2, (rt, 10, 50)); - print "eclist A (1,30,40): ", el; - print "eclist B (30,40,50): ", el2; - print "eclist A union B: ", add( el2, el ); - print "eclist A isect B: ", filter( el, el2 ); - print "eclist A \ B: ", delete( el, el2 ); + + # eclist A (1,30,40) + bt_assert(el = add(add(add(--empty--, (rt, 10, 1)), (rt, 10, 30)), (rt, 10, 40))); + bt_assert(format(el) = "(eclist (rt, 10, 1) (rt, 10, 30) (rt, 10, 40))"); + + # eclist B (30,40,50) + bt_assert(el2 = add(add(add(--empty--, (rt, 10, 30)), (rt, 10, 40)), (rt, 10, 50))); + bt_assert(format(el2) = "(eclist (rt, 10, 30) (rt, 10, 40) (rt, 10, 50))"); + + # eclist A union B + r = add(el2, el); + bt_assert(r = add(add(add(add(--empty--, (rt, 10, 30)), (rt, 10, 40)), (rt, 10, 50)), (rt, 10, 1))); + bt_assert(format(r) = "(eclist (rt, 10, 30) (rt, 10, 40) (rt, 10, 50) (rt, 10, 1))"); + + # eclist A isect B + r = filter(el, el2); + bt_assert(r = add(add(--empty--, (rt, 10, 30)), (rt, 10, 40))); + bt_assert(format(r) = "(eclist (rt, 10, 30) (rt, 10, 40))"); + + # eclist A \ B + r = delete(el, el2); + bt_assert(r = add(--empty--, (rt, 10, 1))); + bt_assert(format(r) = "(eclist (rt, 10, 1))"); + + # eclist in ec set + r = filter(el, [(rt, 10, 1), (rt, 10, 25..30), (ro, 10, 40)]); + bt_assert(r = add(add(--empty--, (rt, 10, 1)), (rt, 10, 30))); + bt_assert(format(r) = "(eclist (rt, 10, 1) (rt, 10, 30))"); +} + +bt_test_suite(t_eclist, "Testing lists of extended communities"); + + + + +/* + * Testing sets of Extended Communities + * ------------------------------------ + */ + +define ecs2 = [(rt, ten, (one+onef(0))*10), (ro, 100000, 100..200), (rt, 12345, *)]; + +function t_ec_set() +ec set ecs; +{ + ecs = [(rt, ten, (one+onef(0))*10), (ro, 100000, 100..200), (rt, 12345, *)]; + bt_assert(format(ecs) = "[(rt, 10, 20), (rt, 12345, 0)..(rt, 12345, 4294967295), (ro, 100000, 100)..(ro, 100000, 200)]"); + bt_assert(format(ecs2) = "[(rt, 10, 20), (rt, 12345, 0)..(rt, 12345, 4294967295), (ro, 100000, 100)..(ro, 100000, 200)]"); + + bt_assert((rt, 10, 20) ~ ecs); + bt_assert((ro, 100000, 100) ~ ecs); + bt_assert((ro, 100000, 128) ~ ecs); + bt_assert((ro, 100000, 200) ~ ecs); + bt_assert((rt, 12345, 0) ~ ecs); + bt_assert((rt, 12345, 200000) ~ ecs); + bt_assert((rt, 12345, 4000000) ~ ecs); + bt_assert((ro, 10, 20) !~ ecs); + bt_assert((rt, 10, 21) !~ ecs); + bt_assert((ro, 100000, 99) !~ ecs); + bt_assert((ro, 12345, 10) !~ ecs); + bt_assert((rt, 12346, 0) !~ ecs); + bt_assert((ro, 0.1.134.160, 150) !~ ecs); +} + +bt_test_suite(t_ec_set, "Testing sets of extended communities"); + + + + +/* + * Testing Large Communities + * ------------------------- + */ + +function mktrip(int a) +{ + return (a, 2*a, 3*a); +} + +function t_lclist() +lclist ll; +lclist ll2; +lclist r; +{ + bt_assert(---empty--- = ---empty---); + bt_assert((10, 20, 30) !~ ---empty---); ll = --- empty ---; ll = add(ll, (ten, 20, 30)); ll = add(ll, (1000, 2000, 3000)); ll = add(ll, mktrip(100000)); - print "LC list (10, 20, 30) (1000, 2000, 3000) (100000, 200000, 300000):"; - print ll; - print "LC len: ", el.len; - print "Should be true: ", mktrip(1000) ~ ll, " ", ll ~ [(5,10,15), (10,20,30)], " ", ll ~ [(10,15..25,*)], " ", ll ~ [(ten, *, *)]; - print "Should be false: ", mktrip(100) ~ ll, " ", ll ~ [(5,10,15), (10,21,30)], " ", ll ~ [(10,21..25,*)], " ", ll ~ [(11, *, *)]; - print "LC filtered: ", filter(ll, [(5..15, *, *), (100000, 500..500000, *)]); + bt_assert(format(ll) = "(lclist (10, 20, 30) (1000, 2000, 3000) (100000, 200000, 300000))"); + bt_assert(ll.len = 3); + bt_assert(ll = add(add(add(---empty---, (10, 20, 30)), (1000, 2000, 3000)), (100000, 200000, 300000))); + + bt_assert(mktrip(1000) ~ ll); + bt_assert(mktrip(100) !~ ll); ll = --- empty ---; ll = add(ll, (10, 10, 10)); @@ -231,240 +939,375 @@ lclist ll2; ll2 = add(ll2, (30, 30, 30)); ll2 = add(ll2, (40, 40, 40)); - print "lclist A (10,20,30): ", ll; - print "lclist B (20,30,40): ", ll2; - print "lclist A union B: ", add(ll, ll2); - print "lclist A isect B: ", filter(ll, ll2); - print "lclist A \ B: ", delete(ll, ll2); + # lclist A (10, 20, 30) + bt_assert(format(ll) = "(lclist (10, 10, 10) (20, 20, 20) (30, 30, 30))"); -# test_roa(); -} + # lclist B (20, 30, 40) + bt_assert(format(ll2) = "(lclist (20, 20, 20) (30, 30, 30) (40, 40, 40))"); -function bla() -{ - print "fifteen called"; - return 15; -} + # lclist A union B + r = add(ll, ll2); + bt_assert(r = add(add(add(add(---empty---, (10,10,10)), (20,20,20)), (30,30,30)), (40,40,40))); + bt_assert(format(r) = "(lclist (10, 10, 10) (20, 20, 20) (30, 30, 30) (40, 40, 40))"); -define four=4; -define onetwo=1.2.3.4; + # lclist A isect B + r = filter(ll, ll2); + bt_assert(r = add(add(---empty---, (20, 20, 20)), (30, 30, 30))); + bt_assert(format(r) = "(lclist (20, 20, 20) (30, 30, 30))"); -function __test1() -{ - if source ~ [ RTS_BGP, RTS_STATIC ] then { -# ospf_metric1 = 65535; -# ospf_metric2 = 1000; - ospf_tag = 0x12345678; - accept; - } reject; -} + # lclist A \ B + r = delete(ll, ll2); + bt_assert(r = add(---empty---, (10, 10, 10))); + bt_assert(format(r) = "(lclist (10, 10, 10))"); -function __test2() -{ - if source ~ [ RTS_BGP, RTS_STATIC ] then { -# ospf_metric1 = 65535; -# ospf_metric2 = 1000; - ospf_tag = 0x12345678; - accept; - } reject; + # lclist in lc set + r = filter(ll, [(5..15, *, *), (20, 15..25, *)]); + bt_assert(r = add(add(---empty---, (10, 10, 10)), (20, 20, 20))); + bt_assert(format(r) = "(lclist (10, 10, 10) (20, 20, 20))"); } -function test_pxset(prefix set pxs) +bt_test_suite(t_lclist, "Testing lists of large communities"); + + + + +/* + * Testing sets of Large Communities + * --------------------------------- + */ + +function t_lclist_set() +lclist ll; +lc set lls; { - print pxs; - print " must be true: ", 10.0.0.0/8 ~ pxs, ",", 10.0.0.0/10 ~ pxs, ",", 10.0.0.0/12 ~ pxs, ",", - 20.0.0.0/24 ~ pxs, ",", 20.0.40.0/24 ~ pxs, ",", 20.0.0.0/26 ~ pxs, ",", - 20.0.100.0/26 ~ pxs, ",", 20.0.0.0/28 ~ pxs, ",", 20.0.255.0/28 ~ pxs; - print " must be false: ", 10.0.0.0/7 ~ pxs, ",", 10.0.0.0/13 ~ pxs, ",", 10.0.0.0/16 ~ pxs, ",", - 20.0.0.0/16 ~ pxs, ",", 20.0.0.0/23 ~ pxs, ",", 20.0.0.0/29 ~ pxs, ",", - 11.0.0.0/10 ~ pxs, ",", 20.1.0.0/26 ~ pxs; + ll = --- empty ---; + ll = add(ll, (10, 20, 30)); + ll = add(ll, (1000, 2000, 3000)); + ll = add(ll, mktrip(100000)); + + bt_assert(ll ~ [(5,10,15), (10,20,30)]); + bt_assert(ll ~ [(10,15..25,*)]); + bt_assert(ll ~ [(ten, *, *)]); + + bt_assert(ll !~ [(5,10,15), (10,21,30)]); + bt_assert(ll !~ [(10,21..25,*)]); + bt_assert(ll !~ [(11, *, *)]); + + lls = [(10, 10, 10), (20, 20, 15..25), (30, 30, *), (40, 35..45, *), (50, *, *), (55..65, *, *)]; + bt_assert(format(lls) = "[(10, 10, 10), (20, 20, 15)..(20, 20, 25), (30, 30, 0)..(30, 30, 4294967295), (40, 35, 0)..(40, 45, 4294967295), (50, 0, 0)..(50, 4294967295, 4294967295), (55, 0, 0)..(65, 4294967295, 4294967295)]"); + bt_assert((10, 10, 10) ~ lls); + bt_assert((20, 20, 25) ~ lls); + bt_assert((20, 20, 26) !~ lls); + bt_assert((30, 30, 0) ~ lls); + bt_assert((40, 35, 40) ~ lls); + bt_assert((40, 34, 40) !~ lls); + bt_assert((50, 0, 0) ~ lls); + bt_assert((60, 60, 60) ~ lls); + bt_assert((70, 60, 60) !~ lls); } +bt_test_suite(t_lclist_set, "Testing sets of large communities"); + + + + +/* + * Testing defined() function + * -------------------------- + */ + function test_undef(int a) int b; { - if a = 3 - then b = 4; - print "Defined: ", a, " ", b, " ", defined(b); + if a = 3 then { + b = 4; + bt_assert(defined(b)); + } + else { + bt_assert(!defined(b)); + } } -define is1 = [ one, (2+1), (6-one), 8, 11, 15, 17, 19]; -define is2 = [(17+2), 17, 15, 11, 8, 5, 3, 2]; -define is3 = [5, 17, 2, 11, 8, 15, 3, 19]; +function t_define() +int i; +{ + test_undef(2); + test_undef(3); + test_undef(2); -define pxs2 = [ 10.0.0.0/16{8,12}, 20.0.0.0/16{24,28} ]; + bt_assert(defined(1)); + bt_assert(defined(1.2.3.4)); +} -define ecs2 = [(rt, ten, (one+onef(0))*10), (ro, 100000, 100..200), (rt, 12345, *)]; +bt_test_suite(t_define, "Testing defined() function"); -function __startup() + + +/* + * Testing calling functions + * ------------------------- + */ + +function callme(int arg1; int arg2) int i; -bool b; -prefix px; -ip p; -pair pp; -quad qq; -ec cc; -int set is; -pair set ps; -ec set ecs; -ip set ips; -prefix set pxs; -string st; { - print "1a-a1 = 30: ", '1a-a1'; - print "Testing filter language:"; - i = four; - i = 12*100 + 60/2 + i; - i = ( i + 0 ); - print " arithmetics: 1234 = ", i; - printn " if statements "; - print "what happens here?"; - printn "."; - if (i = 4) then { print "*** FAIL: if 0"; quitbird; } else printn "."; -# if !(i = 3) then { print "*** FAIL: if 0"; quitbird; } else printn "."; - if 1234 = i then printn "."; else { print "*** FAIL: if 1 else"; } -# if 1 <= 1 then printn "."; else { print "*** FAIL: test 3"; } - if 1234 < 1234 then { print "*** FAIL: test 4"; quitbird; } else print "ok"; - is = [ 2, 3, 4, 7..11 ]; + case arg1 { + 1, 42: return 42; + else: return arg1 * arg2; + } - print "must be true: ", 1 = 1, " ", 1 != (0,1), " ", 1 != "a", " ", +empty+ = +empty+, " ", -empty- = -empty-, " ", --empty-- = --empty-- , - " ", [1,4..10,20] = [1,4..10,20] , " ", [ 10.0.0.0/8{ 15 , 17 } ] = [ 10.0.0.0/8{ 15 , 17 } ]; - print "must be false: ", 1 != 1, " ", 1 = (0,1), " ", 1 = "a", " ", +empty+ = -empty-, " ", -empty- = --empty--, " ", --empty-- = +empty+ , - " ", [1,2] = [1,3], " ", [ 10.0.0.0/8{ 15 , 17 } ] = [ 11.0.0.0/8{ 15 , 17 } ]; + return 0; +} - print " must be true: ", 1.2.0.0/16 ~ [ 1.0.0.0/8{ 15 , 17 } ]; - print " data types; must be true: ", 1.2.3.4 = 1.2.3.4, ",", 1 ~ [1,2,3], ",", 5 ~ [1..20], ",", 10 ~ is, ",", 2 ~ [ 1, 2, 3 ], ",", 5 ~ [ 4 .. 7 ], ",", 1.2.3.4 ~ [ 1.2.3.3..1.2.3.5 ], ",", 1.2.3.4 ~ 1.0.0.0/8, ",", 1.0.0.0/8 ~ 1.0.0.0/8, ",", 1.0.0.0/8 ~ [ 1.0.0.0/8+ ]; - print " must be true: ", true && true, ",", true || false, ",", ! false && ! false && true, ",", 1 < 2 && 1 != 3, ",", true && true && ! false, ",", true || 1+"a", ",", !(false && 1+"a"); +function fifteen() +{ + return 15; +} - print " must be true: ", defined(1), ",", defined(1.2.3.4), ",", 1 != 2, ",", 1 <= 2; - print " data types: must be false: ", 1 ~ [ 2, 3, 4 ], ",", 5 ~ is, ",", 1.2.3.4 ~ [ 1.2.3.3, 1.2.3.5 ], ",", (1,2) > (2,2), ",", (1,1) > (1,1), ",", 1.0.0.0/9 ~ [ 1.0.0.0/8- ], ",", 1.2.0.0/17 ~ [ 1.0.0.0/8{ 15 , 16 } ], ",", true && false; +function t_call_function() +{ + bt_assert(fifteen() = 15); + bt_assert(callme(1, 2) = 42); + bt_assert(callme(42, 2) = 42); - print " must be true: ", 1 ~ is1, " ", 3 ~ is1, " ", 5 ~ is1; - print " must be true: ", (one+2) ~ is1, " ", 2 ~ is2, " ", 2 ~ is3; - print " must be false: ", 4 ~ is1, " ", 4 ~ is2, " ", 4 ~ is3; - print " must be false: ", 10 ~ is1, " ", 10 ~ is2, " ", 10 ~ is3; - print " must be true: ", 15 ~ is1, " ", 15 ~ is2, " ", 15 ~ is3; - print " must be false: ", 18 ~ is1, " ", 18 ~ is2, " ", 18 ~ is3; - print " must be true: ", 19 ~ is1, " ", 19 ~ is2, " ", 19 ~ is3; - print " must be false: ", 20 ~ is1, " ", 20 ~ is2, " ", 20 ~ is3; + bt_assert(callme(2, 2) = 4); + bt_assert(callme(3, 2) = 6); + bt_assert(callme(4, 4) = 16); + bt_assert(callme(7, 2) = 14); +} - px = 1.2.0.0/18; - print "Testing prefixes: 1.2.0.0/18 = ", px; - print " must be true: ", 192.168.0.0/16 ~ 192.168.0.0/16, " ", 192.168.0.0/17 ~ 192.168.0.0/16, " ", 192.168.254.0/24 ~ 192.168.0.0/16; - print " must be false: ", 192.168.0.0/15 ~ 192.168.0.0/16, " ", 192.160.0.0/17 ~ 192.168.0.0/16; +bt_test_suite(t_call_function, "Testing calling functions"); - p = 127.1.2.3; - print "Testing mask : 127.0.0.0 = ", p.mask(8); - - pp = (1, 2); - print "Testing pairs: (1,2) = ", (1,2), " = ", pp, " = ", (1,1+1), " = ", 'mkpair-a'(2); - print " must be true: ", (1,2) = (1,1+1); - print "Testing enums: ", RTS_DUMMY, " ", RTS_STATIC, " ", - ", true: ", RTS_STATIC ~ [RTS_STATIC, RTS_DEVICE], - ", false: ", RTS_BGP ~ [RTS_STATIC, RTS_DEVICE]; - ps = [(1,(one+one)), (3,4)..(4,8), (5,*), (6,3..6)]; - print "Pair set: ", ps; - print "Testing pair set, true: ", pp ~ ps, " ", (3,5) ~ ps, " ", (4,1) ~ ps, " ", (5,4) ~ ps, " ", (5,65535) ~ ps, " ", (6,4) ~ ps, " ", (3, 10000) ~ ps; - print "Testing pair set, false: ", (3,3) ~ ps, " ", (4,9) ~ ps, " ", (4,65535) ~ ps, " ", (6,2) ~ ps, " ", (6,6+one) ~ ps, " ", ((one+6),2) ~ ps, " ", (1,1) ~ ps; - ps = [(20..150, 200..300), (50100..50200, 1000..50000), (*, 5+5)]; - print "Pair set: .. too long .."; - print "Testing pair set, true: ", (100,200) ~ ps, " ", (150,300) ~ ps, " ", (50180,1200) ~ ps, " ", (50110,49000) ~ ps, " ", (0,10) ~ ps, " ", (64000,10) ~ ps; - print "Testing pair set, false: ", (20,199) ~ ps, " ", (151,250) ~ ps, " ", (50050,2000) ~ ps, " ", (50150,50050) ~ ps, " ", (10,9) ~ ps, " ", (65535,11) ~ ps ; - qq = 1.2.3.4; - print "Testinq quad: 1.2.3.4 = ", qq, - ", true: ", qq = 1.2.3.4, " ", qq ~ [1.2.3.4, 5.6.7.8], - ", false: ", qq = 4.3.2.1, " ", qq ~ [1.2.1.1, 1.2.3.5]; +/* + * Test including another config file + * ---------------------------------- + */ - cc = (rt, 12345, 200000); - print "Testing EC: (rt, 12345, 200000) = ", cc; - print "Testing EC: (ro, 100000, 20000) = ", (ro, 100000, 20000); - print "Testing EC: (rt, 10.20.30.40, 20000) = ", (rt, 10.20.30.40, 20000); - print " true: ", cc = (rt, 12345, 200000), " ", cc < (rt, 12345, 200010), - ", false: ", cc = (rt, 12346, 200000), " ", cc = (ro, 12345, 200000), " ", cc > (rt, 12345, 200010); +function t_include() +int i; +{ + i = 1; + include "test.conf.inc"; + bt_assert(i = 42); +} - ecs = [(rt, ten, (one+onef(0))*10), (ro, 100000, 100..200), (rt, 12345, *)]; - print "EC set: ", ecs; - print "EC set: ", ecs2; - print "Testing EC set, true: ", (rt, 10, 20) ~ ecs, " ", (ro, 100000, 100) ~ ecs, " ", (ro, 100000, 200) ~ ecs, - " ", (rt, 12345, 0) ~ ecs, " ", cc ~ ecs, " ", (rt, 12345, 4000000) ~ ecs; - print "Testing EC set, false: ", (ro, 10, 20) ~ ecs, " ", (rt, 10, 21) ~ ecs, " ", (ro, 100000, 99) ~ ecs, - " ", (ro, 12345, 10) ~ ecs, " ", (rt, 12346, 0) ~ ecs, " ", (ro, 0.1.134.160, 150) ~ ecs; +bt_test_suite(t_include, "Testing including another config file"); - st = "Hello"; - print "Testing string: ", st, " true: ", st ~ "Hell*", " false: ", st ~ "ell*"; - - b = true; - print "Testing bool: ", b, ", ", !b; - if ( b = true ) then print "Testing bool comparison b = true: ", b; - else { print "*** FAIL: TRUE test failed" ; quitbird; } - - ips = [ 1.1.1.0 .. 1.1.1.255, ip1222]; - print "Testing IP sets: "; - print ips; - print " must be true: ", 1.1.1.0 ~ ips, ",", 1.1.1.100 ~ ips, ",", 1.2.2.2 ~ ips; - print " must be false: ", 1.1.0.255 ~ ips, ",", 1.1.2.0 ~ ips, ",", 1.2.2.3 ~ ips, ",", 192.168.1.1 ~ ips; - - pxs = [ 1.2.0.0/16, 1.4.0.0/16+]; - print "Testing prefix sets: "; - print pxs; - print " must be true: ", 1.2.0.0/16 ~ pxs, ",", 1.4.0.0/16 ~ pxs, ",", 1.4.0.0/18 ~ pxs, ",", 1.4.0.0/32 ~ pxs; - print " must be false: ", 1.1.0.0/16 ~ pxs, ",", 1.3.0.0/16 ~ pxs, ",", 1.2.0.0/15 ~ pxs, ",", 1.2.0.0/17 ~ pxs, ",", - 1.2.0.0/32 ~ pxs, ",", 1.4.0.0/15 ~ pxs; - test_pxset(pxs2); - test_pxset([ 10.0.0.0/16{8,12}, 20.0.0.0/16{24,28} ]); - print "What will this do? ", [ 1, 2, 1, 1, 1, 3, 4, 1, 1, 1, 5 ]; - print "Testing functions..."; - callme ( 1, 2 ); - callme ( 2, 2 ); - callme ( 2, 2 ); - callme ( 3, 2 ); - callme ( 4, 4 ); - callme ( 7, 2 ); +/* + * Test if-else statement + * ---------------------- + */ + +function t_if_else() +int i; +{ + if true then + bt_assert(true); - i = fifteen(); - print "Testing function calls: 15 = ", i; + if false then + bt_assert(false); + else if true then + bt_assert(true); + else + bt_assert(false); +} - path_test(); +bt_test_suite(t_if_else, "Testing if-else statement"); - print "1.2.3.4 = ", onetwo; - i = 4200000000; - print "4200000000 = ", i, " false: ", i = 4200000000, " ", i > 4100000000, " false: ", i > 4250000000; - test_undef(2); - test_undef(3); - test_undef(2); - print "Testing include"; - include "test.conf.inc"; +/* + * Unused functions -- testing only parsing + * ---------------------------------------- + */ + +function __test1() +{ + if source ~ [ RTS_BGP, RTS_STATIC ] then { +# ospf_metric1 = 65535; +# ospf_metric2 = 1000; + ospf_tag = 0x12345678; + accept; + } + reject; +} - print "done"; - quitbird; -# print "*** FAIL: this is unreachable"; +function __test2() +{ + if source ~ [ RTS_BGP, RTS_STATIC ] then { +# ospf_metric1 = 65535; +# ospf_metric2 = 1000; + ospf_tag = 0x12345678; + accept; + } + reject; } -filter testf -int j; -{ +filter testf +int j; +{ print "Heya, filtering route to ", net.ip, " prefixlen ", net.len, " source ", source; print "This route was from ", from; j = 7; j = 17; if rip_metric > 15 then { - reject "RIP Metric is more than infinity"; + reject "RIP Metric is more than infinity"; } rip_metric = 14; unset(rip_metric); - + accept "ok I take that"; } -eval __startup(); +filter roa_filter +{ + if net ~ [ 10.0.0.0/8{16,24}, 2000::/3{16,96} ] then { + accept; + } + reject; +} + +roa4 table r4; +roa6 table r6; + +protocol static +{ + roa4 { table r4; import filter roa_filter; }; + route 10.110.0.0/16 max 16 as 1000; + route 10.120.0.0/16 max 24 as 1000; + route 10.130.0.0/16 max 24 as 2000; + route 10.130.128.0/18 max 24 as 3000; +} + +protocol static +{ + roa6 { table r6; import filter roa_filter; }; + route 2001:0db8:85a3:8a2e::/64 max 96 as 1000; +} + +function test_roa_check() +prefix pfx; +{ + # cannot be tested in __startup(), sorry + bt_assert(roa_check(r4, 10.10.0.0/16, 1000) = ROA_UNKNOWN); + bt_assert(roa_check(r4, 10.0.0.0/8, 1000) = ROA_UNKNOWN); + bt_assert(roa_check(r4, 10.110.0.0/16, 1000) = ROA_VALID); + bt_assert(roa_check(r4, 10.110.0.0/16, 2000) = ROA_INVALID); + bt_assert(roa_check(r4, 10.110.32.0/20, 1000) = ROA_INVALID); + bt_assert(roa_check(r4, 10.120.32.0/20, 1000) = ROA_VALID); + bt_assert(roa_check(r4, 10.120.32.0/20, 2000) = ROA_INVALID); + bt_assert(roa_check(r4, 10.120.32.32/28, 1000) = ROA_INVALID); + bt_assert(roa_check(r4, 10.130.130.0/24, 1000) = ROA_INVALID); + bt_assert(roa_check(r4, 10.130.130.0/24, 2000) = ROA_VALID); + bt_assert(roa_check(r4, 10.130.30.0/24, 3000) = ROA_INVALID); + bt_assert(roa_check(r4, 10.130.130.0/24, 3000) = ROA_VALID); + + bt_assert(roa_check(r6, 2001:0db8:85a3:8a2e:1234::/80, 1000) = ROA_VALID); + bt_assert(roa_check(r6, 2001:0db8:85a3:8a2e:1234::/97, 1000) = ROA_INVALID); + bt_assert(roa_check(r6, 2001:0db8:85a3:8a2e::/64, 1000) = ROA_VALID); + bt_assert(roa_check(r6, 2001:0db8:85a3::/48, 1000) = ROA_UNKNOWN); + + bt_assert(roa_check(r4, 10.10.0.0/16, 1000) = ROA_UNKNOWN); + bt_assert(roa_check(r4, 10.0.0.0/8, 1000) = ROA_UNKNOWN); + bt_assert(roa_check(r4, 10.110.0.0/16, 1000) = ROA_VALID); + bt_assert(roa_check(r4, 10.110.0.0/16, 2000) = ROA_INVALID); + bt_assert(roa_check(r4, 10.110.32.0/20, 1000) = ROA_INVALID); + bt_assert(roa_check(r4, 10.120.32.0/20, 1000) = ROA_VALID); + + bt_assert(roa_check(r6, 2001:0db8:85a3:8a2e:1234::/80, 1000) = ROA_VALID); + bt_assert(roa_check(r6, 2001:0db8:85a3:8a2e:1234::/97, 1000) = ROA_INVALID); + bt_assert(roa_check(r6, 2001:0db8:85a3:8a2e::/64, 1000) = ROA_VALID); + bt_assert(roa_check(r6, 2001:0db8:85a3::/48, 1000) = ROA_UNKNOWN); + + bt_assert(roa_check(r4, 2001:0db8:85a3:8a2e:1234::/97, 1000) = ROA_UNKNOWN); + bt_assert(roa_check(r6, 2001:0db8:85a3:8a2e:1234::/97, 1000) = ROA_INVALID); + + bt_assert(roa_check(r4, 2001:0db8:85a3:8a2e:1234::/80, 1000) = ROA_UNKNOWN); + bt_assert(roa_check(r6, 2001:0db8:85a3:8a2e:1234::/80, 1000) = ROA_VALID); + bt_assert(roa_check(r4, 2001:0db8:85a3::/48, 1000) = ROA_UNKNOWN); + bt_assert(roa_check(r6, 2001:0db8:85a3::/48, 1000) = ROA_UNKNOWN); + + bt_assert(10.130.130.0/24 ~ 0.0.0.0/0); + bt_assert(2001:0db8:85a3:8a2e::/64 ~ ::/0); + bt_assert(10.130.130.0/24 !~ ::/0); + bt_assert(2001:0db8:85a3:8a2e::/64 !~ 0.0.0.0/0); + + pfx = 12.13.0.0/16 max 24 as 1234; + bt_assert(pfx.len = 16); + bt_assert(pfx.maxlen = 24); + bt_assert(pfx.asn = 1234); + + pfx = 1000::/8 max 32 as 1234; + bt_assert(pfx.len = 8); + bt_assert(pfx.maxlen = 32); + bt_assert(pfx.asn = 1234); +} + +bt_test_suite(test_roa_check, "Testing ROA"); + +/* + * Testing Mixed Net Types + * ----------------------- + */ + +function t_mixed_prefix() +prefix set pxs; +prefix set pxt; +{ + pxs = [ 98.45.0.0/16, 128.128.0.0/12+, 2200::/42-, ::ffff:d000:0/100{98,102}]; + bt_assert(format(pxs) = "[::/0, ::/2{c000::}, 98.45.0.0/112{::0.1.0.0}, 128.128.0.0/108{::0.31.255.255}, 208.0.0.0/100{::124.0.0.0}, 2200::/42{ffff:ffff:ffc0::}]"); + bt_assert(::fe00:0:0/88 !~ pxs); + bt_assert(::fffe:0:0/95 !~ pxs); + bt_assert(::ffff:d800:0/101 ~ pxs); + bt_assert(216.0.0.0/5 ~ pxs); + bt_assert(212.0.0.0/6 ~ pxs); + bt_assert(212.0.0.0/7 !~ pxs); + bt_assert(::ffff:8080:8080/121 ~ pxs); + bt_assert(::/0 ~ pxs); + bt_assert(0.0.0.0/0 !~ pxs); + bt_assert(128.135.64.17/32 ~ pxs); + +# pxt = [ 0:1:2 10.1.10.0/24, 0:5:10000 10.1.10.0/24 ]; +# print pxt; + + bt_assert(format(NET_IP4) = "(enum 36)1"); ## if (net.type = NET_IP4) ... + bt_assert(format(NET_VPN6) = "(enum 36)4"); + bt_assert(format(0:1:2) = "1:2"); +} + +bt_test_suite(t_mixed_prefix, "Testing mixed net types"); + + +filter vpn_filter +{ + bt_assert(format(net) = "0:1:2 10.1.10.0/24"); + bt_assert(net.type = NET_VPN4); + bt_assert(net.type != NET_IP4); + bt_assert(net.type != NET_IP6); + bt_assert(net.rd = 0:1:2); + + case (net.type) { + NET_IP4: print "IPV4"; + NET_IP6: print "IPV6"; + } + + accept; +} + +vpn4 table v4; +vpn4 table v6; + +protocol static +{ + vpn4 { table v4; import filter vpn_filter; }; + route 0:1:2 10.1.10.0/24 unreachable; +} diff --git a/filter/test.conf.inc b/filter/test.conf.inc index 109a49c5..8ede2d18 100644 --- a/filter/test.conf.inc +++ b/filter/test.conf.inc @@ -1,5 +1,3 @@ -print "Entering include"; -print "Should be 2: ", 1+1; -print "Leaving include"; - +bt_assert(1+1 = 2); +i = 42; diff --git a/filter/test.conf2 b/filter/test.conf2 index 60bdd965..2a5d896b 100644 --- a/filter/test.conf2 +++ b/filter/test.conf2 @@ -18,6 +18,7 @@ protocol direct { protocol kernel { disabled; + ipv4; # Must be specified at least one channel # learn; # Learn all routes from the kernel # scan time 10; # Scan kernel tables every 10 seconds } @@ -25,51 +26,58 @@ protocol kernel { protocol static { # disabled; - import filter { print "ahoj"; - print source; - if source = RTS_STATIC then { - print "It is from static"; - } - print from; - from = 1.2.3.4; - print from; - print scope; - scope = SCOPE_HOST; - print scope; - if !(scope ~ [ SCOPE_HOST, SCOPE_SITE ]) then { - print "Failed in test"; - quitbird; - } - - preference = 15; - print preference; - preference = 29; - print preference; - rip_metric = 1; - print rip_metric; - rip_metric = rip_metric + 5; - print rip_metric; - bgp_community = - empty - ; - print "nazdar"; - bgp_community = add(bgp_community, (1,2)); - print "cau"; - bgp_community = add(bgp_community, (2,3)); - bgp_community.add((4,5)); - print "community = ", bgp_community; - bgp_community.delete((2,3)); - print "community = ", bgp_community; - bgp_community.empty; - print "community = ", bgp_community; - print "done"; - }; + ipv4 { + export all; + + import filter { + print "ahoj"; + print source; + if source = RTS_STATIC then { + print "It is from static"; + } + print from; + from = 1.2.3.4; + print from; + print scope; + scope = SCOPE_HOST; + print scope; + if !(scope ~ [ SCOPE_HOST, SCOPE_SITE ]) then { + print "Failed in test"; + quitbird; + } + preference = 15; + print preference; + preference = 29; + print preference; + rip_metric = 1; + print rip_metric; + rip_metric = rip_metric + 5; + print rip_metric; + +# +# TODO: uncomment this part after finishing BGP integration version +# +# bgp_community = -empty-; +# print "hi"; +# bgp_community = add(bgp_community, (1,2)); +# print "hello"; +# bgp_community = add(bgp_community, (2,3)); +# bgp_community.add((4,5)); +# print "community = ", bgp_community; +# bgp_community.delete((2,3)); +# print "community = ", bgp_community; +# bgp_community.empty; +# print "community = ", bgp_community; +# print "done"; + }; + }; route 0.0.0.0/0 via 195.113.31.113; route 62.168.0.0/25 reject; route 1.2.3.4/32 via 195.113.31.124; -# route 10.0.0.0/8 reject; -# route 10.1.1.0:255.255.255.0 via 62.168.0.3; -# route 10.1.2.0:255.255.255.0 via 62.168.0.3; -# route 10.1.3.0:255.255.255.0 via 62.168.0.4; -# route 10.2.0.0/24 via "arc0"; - export all; + route 10.0.0.0/8 reject; + route 10.1.1.0:255.255.255.0 via 62.168.0.3; + route 10.1.2.0:255.255.255.0 via 62.168.0.3; + route 10.1.3.0:255.255.255.0 via 62.168.0.4; + route 10.2.0.0/24 via "arc0"; } diff --git a/filter/test6.conf b/filter/test6.conf deleted file mode 100644 index f25ffc47..00000000 --- a/filter/test6.conf +++ /dev/null @@ -1,182 +0,0 @@ -/* - * This is an example configuration file. - * FIXME: add all examples from docs here. - */ - -# Yet another comment - -router id 62.168.0.1; - -define xyzzy = (120+10); - -function callme(int arg1; int arg2) -int local1; -int local2; -int i; -{ - printn "Function callme called arguments ", arg1, " and ", arg2, ":" ; - i = arg2; - - case arg1 { - 2: print "dva"; print "jeste jednou dva"; - 3 .. 5: print "tri az pet"; - else: print "neco jineho"; - } -} - -function fifteen() -{ - print "fifteen called"; - return 15; -} - -function paths() -bgpmask pm1; -bgpmask pm2; -bgppath p2; -clist l; -{ - pm1 = / 4 3 2 1 /; - pm2 = [= 4 3 2 1 =]; - print "Testing path masks: ", pm1, " ", pm2; - p2 = prepend( + empty +, 1 ); - p2 = prepend( p2, 2 ); - p2 = prepend( p2, 3 ); - p2 = prepend( p2, 4 ); - print "Testing paths: ", p2; - print "Should be true: ", p2 ~ pm1, " ", p2 ~ pm2; - print "4 = ", p2.len; - p2 = prepend( p2, 5 ); - print "Should be false: ", p2 ~ pm1, " ", p2 ~ pm2; - print "Should be true: ", p2 ~ / ? 4 3 2 1 /, " ", p2, " ", / ? 4 3 2 1 /; - print "Should be true: ", p2 ~ [= * 4 3 * 1 =], " ", p2, " ", [= * 4 3 * 1 =]; - print "5 = ", p2.len; - - pm1 = [= 1 2 * 3 4 5 =]; - p2 = prepend( + empty +, 5 ); - p2 = prepend( p2, 4 ); - p2 = prepend( p2, 3 ); - p2 = prepend( p2, 3 ); - p2 = prepend( p2, 2 ); - p2 = prepend( p2, 1 ); - print "Should be true: ", p2 ~ pm1, " ", p2, " ", pm1; - - l = - empty -; - l = add( l, (1,2) ); - l = add( l, (2,3) ); - print "Community list (1,2) (2,3) ", l; - print "Should be true: ", (2,3) ~ l; - l = delete( l, (2,3) ); - print "Community list (1,2) ", l; - print "Should be false: ", (2,3) ~ l; -} - -function bla() -{ - print "fifteen called"; - return 15; -} - -define four=4; - -function test_pxset(prefix set pxs) -{ - print " must be true: ", 1000::/8 ~ pxs, ",", 1000::/10 ~ pxs, ",", 1000::/12 ~ pxs, ",", - 2000::/24 ~ pxs, ",", 2000:4000::/24 ~ pxs, ",", 2000::/26 ~ pxs, ",", - 2000:8000::/26 ~ pxs, ",", 2000::/28 ~ pxs, ",", 2000:FFF0::/28 ~ pxs; - print " must be false: ", 1000::/7 ~ pxs, ",", 1000::/13 ~ pxs, ",", 1000::/16 ~ pxs, ",", - 2000::/16 ~ pxs, ",", 2000::/23 ~ pxs, ",", 2000::/29 ~ pxs, ",", - 1100::/10 ~ pxs, ",", 2010::/26 ~ pxs; -} - -function __startup() -int i; -bool b; -prefix px; -ip p; -pair pp; -int set is; -prefix set pxs; -string s; -{ - print "Testing filter language:"; - i = four; - i = 12*100 + 60/2 + i; - i = ( i + 0 ); - print " arithmetics: 1234 = ", i; - printn " if statements "; - print "what happens here?"; - printn "."; - if (i = 4) then { print "*** FAIL: if 0"; quitbird; } else printn "."; -# if !(i = 3) then { print "*** FAIL: if 0"; quitbird; } else printn "."; - if 1234 = i then printn "."; else { print "*** FAIL: if 1 else"; } -# if 1 <= 1 then printn "."; else { print "*** FAIL: test 3"; } - if 1234 < 1234 then { print "*** FAIL: test 4"; quitbird; } else print "ok"; - is = [ 2, 3, 4, 7..11 ]; - print " must be true: ", 1180::/16 ~ [ 1100::/8{ 15 , 17 } ]; - print " data types; must be true: ", 12::34 = 12::34, ",", 1 ~ [1,2,3], ",", 5 ~ [1..20], ",", 10 ~ is, ",", 2 ~ [ 1, 2, 3 ], ",", 5 ~ [ 4 .. 7 ], ",", 12::34 ~ [ 12::33..12::35 ], ",", 1020::34 ~ 1000::/8, ",", 1000::/8 ~ 1000::/8, ",", 1000::/8 ~ [ 1000::/8+ ]; - print " must be true: ", true && true, ",", true || false; - -# print " must be true: ", defined(1), ",", defined(1.2.3.4), ",", 1 != 2, ",", 1 <= 2; - print " data types: must be false: ", 1 ~ [ 2, 3, 4 ], ",", 5 ~ is, ",", 12::34 ~ [ 12::33, 12::35 ], ",", (1,2) > (2,2), ",", (1,1) > (1,1), ",", 1000::/9 ~ [ 1000::/8- ], ",", 1000::/17 ~ [ 1000::/8{ 15 , 16 } ], ",", true && false; - - px = 1020::/18; - print "Testing prefixes: 1020::/18 = ", px; - p = 1234:5678::; - print "Testing mask : 1200:: = ", p.mask(8); - - pp = (1, 2); - print "Testing pairs: (1,2) = ", (1,2), " = ", pp; - print "Testing enums: ", RTS_DUMMY, " ", RTS_STATIC; - - s = "Hello"; - print "Testing string: ", s, " true: ", s ~ "Hell*", " false: ", s ~ "ell*"; - - b = true; - print "Testing bool: ", b, ", ", !b; - - pxs = [ 1102::/16, 1104::/16+]; - print "Testing prefix sets: "; - print pxs; - print " must be true: ", 1102::/16 ~ pxs, ",", 1104::/16 ~ pxs, ",", 1104::/18 ~ pxs, ",", 1104::/32 ~ pxs; - print " must be false: ", 1101::/16 ~ pxs, ",", 1103::/16 ~ pxs, ",", 1102::/15 ~ pxs, ",", 1102::/17 ~ pxs, ",", - 1102::/32 ~ pxs, ",", 1104::/15 ~ pxs; - - test_pxset([ 1000::/16{8,12}, 2000::/16{24,28} ]); - print "What will this do? ", [ 1, 2, 1, 1, 1, 3, 4, 1, 1, 1, 5 ]; - - print "Testing functions..."; -# callme ( 1, 2 ); - callme ( 2, 2 ); - callme ( 2, 2 ); - callme ( 3, 2 ); - callme ( 4, 2 ); - callme ( 7, 2 ); - - i = fifteen(); - print "Testing function calls: 15 = ", i; - - paths(); - - print "done"; - quitbird; -# print "*** FAIL: this is unreachable"; -} - -filter testf -int j; -{ - print "Heya, filtering route to ", net.ip, " prefixlen ", net.len, " source ", source; - print "This route was from ", from; - j = 7; - j = 17; - if rip_metric > 15 then { - reject "RIP Metric is more than infinity"; - } - rip_metric = 14; - unset(rip_metric); - - accept "ok I take that"; -} - -eval __startup();
\ No newline at end of file diff --git a/filter/tree_test.c b/filter/tree_test.c new file mode 100644 index 00000000..5b22a9fe --- /dev/null +++ b/filter/tree_test.c @@ -0,0 +1,304 @@ +/* + * Filters: Utility Functions Tests + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include "test/birdtest.h" +#include "test/bt-utils.h" + +#include "filter/filter.h" +#include "conf/conf.h" + +#define MAX_TREE_HEIGHT 13 + +static void +start_conf_env(void) +{ + bt_bird_init(); + + pool *p = rp_new(&root_pool, "helper_pool"); + linpool *l = lp_new_default(p); + cfg_mem = l; +} + +static struct f_tree * +new_tree(uint id) +{ + struct f_tree *tree = f_new_tree(); + tree->from.type = tree->to.type = T_INT; + tree->from.val.i = tree->to.val.i = id; + + return tree; +} + +/* + * Show subtree in infix notation + */ +static void +show_subtree(struct f_tree *node) +{ + if (!node) + return; + + show_subtree(node->left); + + if (node->from.val.i == node->to.val.i) + bt_debug("%u ", node->from.val.i); + else + bt_debug("%u..%u ", node->from.val.i, node->to.val.i); + + show_subtree(node->right); +} + +static void +show_tree2(struct f_tree *root_node, const char *tree_name) +{ + bt_debug("%s: \n", tree_name); + bt_debug("[ "); + show_subtree(root_node); + bt_debug("]\n\n"); +} + +#define show_tree(tree) show_tree2(tree, #tree); + +static uint +get_nodes_count_full_bin_tree(uint height) +{ + return (bt_naive_pow(2, height+1) - 1); +} + +static struct f_tree * +get_balanced_full_subtree(uint height, uint idx) +{ + struct f_tree *node = new_tree(idx); + if (height > 0) + { + uint nodes_in_subtree = get_nodes_count_full_bin_tree(--height); + node->left = get_balanced_full_subtree(height, idx - nodes_in_subtree/2 - 1); + node->right = get_balanced_full_subtree(height, idx + nodes_in_subtree/2 + 1); + } + return node; +} + +static struct f_tree * +get_balanced_full_tree(uint height) +{ + return get_balanced_full_subtree(height, get_nodes_count_full_bin_tree(height)/2); +} + +static struct f_tree * +get_degenerated_left_tree(uint nodes_count) +{ + struct f_tree *old = NULL; + struct f_tree *new = NULL; + uint i; + + for (i = 0; i < nodes_count; i++) + { + old = new; + new = new_tree(nodes_count-1-i); + new->left = old; + } + + return new; +} + +static struct f_tree * +get_random_degenerated_left_tree(uint nodes_count) +{ + struct f_tree *tree = get_degenerated_left_tree(nodes_count); + + size_t avaible_indexes_size = nodes_count * sizeof(byte); + byte *avaible_indexes = malloc(avaible_indexes_size); + memset(avaible_indexes, 0, avaible_indexes_size); + + struct f_tree *n; + for (n = tree; n; n = n->left) + { + uint selected_idx; + do + { + selected_idx = bt_random() % nodes_count; + } while(avaible_indexes[selected_idx] != 0); + + avaible_indexes[selected_idx] = 1; + n->from.type = n->to.type = T_INT; + n->from.val.i = n->to.val.i = selected_idx; + } + + free(avaible_indexes); + return tree; +} + +static struct f_tree * +get_balanced_tree_with_ranged_values(uint nodes_count) +{ + struct f_tree *tree = get_degenerated_left_tree(nodes_count); + + uint idx = 0; + struct f_tree *n; + for (n = tree; n; n = n->left) + { + n->from.type = n->to.type = T_INT; + n->from.val.i = idx; + idx += (uint)bt_random() / nodes_count; /* (... / nodes_count) preventing overflow an uint idx */ + n->to.val.i = idx++; + } + + return build_tree(tree); +} + + +static int +t_balancing(void) +{ + start_conf_env(); + + uint height; + for (height = 1; height < MAX_TREE_HEIGHT; height++) + { + uint nodes_count = get_nodes_count_full_bin_tree(height); + + struct f_tree *simple_degenerated_tree = get_degenerated_left_tree(nodes_count); + show_tree(simple_degenerated_tree); + + struct f_tree *expected_balanced_tree = get_balanced_full_tree(height); + show_tree(expected_balanced_tree); + + struct f_tree *balanced_tree_from_simple = build_tree(simple_degenerated_tree); + show_tree(balanced_tree_from_simple); + + bt_assert(same_tree(balanced_tree_from_simple, expected_balanced_tree)); + } + + return 1; +} + + +static int +t_balancing_random(void) +{ + start_conf_env(); + + uint height; + for (height = 1; height < MAX_TREE_HEIGHT; height++) + { + uint nodes_count = get_nodes_count_full_bin_tree(height); + + struct f_tree *expected_balanced_tree = get_balanced_full_tree(height); + + uint i; + for(i = 0; i < 10; i++) + { + struct f_tree *random_degenerated_tree = get_random_degenerated_left_tree(nodes_count); + show_tree(random_degenerated_tree); + + struct f_tree *balanced_tree_from_random = build_tree(random_degenerated_tree); + + show_tree(expected_balanced_tree); + show_tree(balanced_tree_from_random); + + bt_assert(same_tree(balanced_tree_from_random, expected_balanced_tree)); + } + } + + return 1; +} + +static int +t_find(void) +{ + start_conf_env(); + + uint height; + for (height = 1; height < MAX_TREE_HEIGHT; height++) + { + uint nodes_count = get_nodes_count_full_bin_tree(height); + + struct f_tree *tree = get_balanced_full_tree(height); + show_tree(tree); + + struct f_val looking_up_value = { + .type = T_INT + }; + for(looking_up_value.val.i = 0; looking_up_value.val.i < nodes_count; looking_up_value.val.i++) + { + struct f_tree *found_tree = find_tree(tree, looking_up_value); + bt_assert((val_compare(looking_up_value, found_tree->from) == 0) && (val_compare(looking_up_value, found_tree->to) == 0)); + } + } + + return 1; +} + +static uint +get_max_value_in_unbalanced_tree(struct f_tree *node, uint max) +{ + if (!node) + return max; + + if (node->to.val.i > max) + max = node->to.val.i; + + uint max_left = get_max_value_in_unbalanced_tree(node->left, max); + if (max_left > max) + max = max_left; + + uint max_right = get_max_value_in_unbalanced_tree(node->right, max); + if (max_right > max) + max = max_right; + + return max; +} + +static int +t_find_ranges(void) +{ + start_conf_env(); + + uint height; + for (height = 1; height < MAX_TREE_HEIGHT; height++) + { + uint nodes_count = get_nodes_count_full_bin_tree(height); + + struct f_tree *tree = get_balanced_tree_with_ranged_values(nodes_count); + uint max_value = get_max_value_in_unbalanced_tree(tree, 0); + + show_tree(tree); + + bt_debug("max_value: %u \n", max_value); + + struct f_val needle = { + .type = T_INT + }; + uint *i = &needle.val.i; + + for(*i = 0; *i <= max_value; *i += (uint)bt_random()/nodes_count) + { + struct f_tree *found_tree = find_tree(tree, needle); + bt_debug("searching: %u \n", *i); + bt_assert( + (val_compare(needle, found_tree->from) == 0) || (val_compare(needle, found_tree->to) == 0) || + ((val_compare(needle, found_tree->from) == 1) && (val_compare(needle, found_tree->to) == -1)) + ); + } + } + + return 1; +} + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + bt_test_suite(t_balancing, "Balancing strong unbalanced trees"); + bt_test_suite(t_balancing_random, "Balancing random unbalanced trees"); + bt_test_suite(t_find, "Finding values in trees"); + bt_test_suite(t_find_ranges, "Finding values in trees with random ranged values"); + + return bt_exit_value(); +} diff --git a/filter/trie.c b/filter/trie.c index 565ae82f..adcfcdf3 100644 --- a/filter/trie.c +++ b/filter/trie.c @@ -74,6 +74,19 @@ #include "conf/conf.h" #include "filter/filter.h" + +/* + * In the trie code, the prefix length is internally treated as for the whole + * ip_addr, regardless whether it contains an IPv4 or IPv6 address. Therefore, + * remaining definitions make sense. + */ + +#define ipa_mkmask(x) ip6_mkmask(x) +#define ipa_masklen(x) ip6_masklen(&x) +#define ipa_pxlen(x,y) ip6_pxlen(x,y) +#define ipa_getbit(x,n) ip6_getbit(x,n) + + /** * f_new_trie - allocates and returns a new empty trie * @lp: linear pool to allocate items from @@ -109,12 +122,11 @@ attach_node(struct f_trie_node *parent, struct f_trie_node *child) /** * trie_add_prefix * @t: trie to add to - * @px: prefix address - * @plen: prefix length + * @net: IP network prefix * @l: prefix lower bound * @h: prefix upper bound * - * Adds prefix (prefix pattern) @px/@plen to trie @t. @l and @h are lower + * Adds prefix (prefix pattern) @n to trie @t. @l and @h are lower * and upper bounds on accepted prefix lengths, both inclusive. * 0 <= l, h <= 32 (128 for IPv6). * @@ -124,8 +136,19 @@ attach_node(struct f_trie_node *parent, struct f_trie_node *child) */ void * -trie_add_prefix(struct f_trie *t, ip_addr px, int plen, int l, int h) +trie_add_prefix(struct f_trie *t, const net_addr *net, uint l, uint h) { + ip_addr px = net_prefix(net); + uint plen = net_pxlen(net); + + if (net->type == NET_IP4) + { + const uint delta = IP6_MAX_PREFIX_LENGTH - IP4_MAX_PREFIX_LENGTH; + plen += delta; + l += delta; + h += delta; + } + if (l == 0) t->zero = 1; else @@ -140,7 +163,7 @@ trie_add_prefix(struct f_trie *t, ip_addr px, int plen, int l, int h) struct f_trie_node *o = NULL; struct f_trie_node *n = t->root; - while(n) + while (n) { ip_addr cmask = ipa_and(n->mask, pmask); @@ -196,18 +219,8 @@ trie_add_prefix(struct f_trie *t, ip_addr px, int plen, int l, int h) return a; } -/** - * trie_match_prefix - * @t: trie - * @px: prefix address - * @plen: prefix length - * - * Tries to find a matching prefix pattern in the trie such that - * prefix @px/@plen matches that prefix pattern. Returns 1 if there - * is such prefix pattern in the trie. - */ -int -trie_match_prefix(struct f_trie *t, ip_addr px, int plen) +static int +trie_match_prefix(struct f_trie *t, ip_addr px, uint plen) { ip_addr pmask = ipa_mkmask(plen); ip_addr paddr = ipa_and(px, pmask); @@ -241,6 +254,30 @@ trie_match_prefix(struct f_trie *t, ip_addr px, int plen) return 0; } +/** + * trie_match_net + * @t: trie + * @n: net address + * + * Tries to find a matching net in the trie such that + * prefix @n matches that prefix pattern. Returns 1 if there + * is such prefix pattern in the trie. + */ +int +trie_match_net(struct f_trie *t, const net_addr *n) +{ + uint add = 0; + + switch (n->type) { + case NET_IP4: + case NET_VPN4: + case NET_ROA4: + add = IP6_MAX_PREFIX_LENGTH - IP4_MAX_PREFIX_LENGTH; + } + + return trie_match_prefix(t, net_prefix(n), net_pxlen(n) + add); +} + static int trie_node_same(struct f_trie_node *t1, struct f_trie_node *t2) { diff --git a/filter/trie_test.c b/filter/trie_test.c new file mode 100644 index 00000000..7529a5c5 --- /dev/null +++ b/filter/trie_test.c @@ -0,0 +1,185 @@ +/* + * Filters: Utility Functions Tests + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include "test/birdtest.h" +#include "test/bt-utils.h" + +#include "filter/filter.h" +#include "conf/conf.h" + +#define TESTS_NUM 10 +#define PREFIXES_NUM 10 +#define PREFIX_TESTS_NUM 10000 + +#define BIG_BUFFER_SIZE 10000 + +/* Wrapping structure for storing f_prefixes structures in list */ +struct f_prefix_node { + node n; + struct f_prefix prefix; +}; + +static u32 +xrandom(u32 max) +{ + return (bt_random() % max); +} + +static int +is_prefix_included(list *prefixes, struct f_prefix *needle) +{ + struct f_prefix_node *n; + WALK_LIST(n, *prefixes) + { + ip6_addr cmask = ip6_mkmask(MIN(n->prefix.net.pxlen, needle->net.pxlen)); + + ip6_addr ip = net6_prefix(&n->prefix.net); + ip6_addr needle_ip = net6_prefix(&needle->net); + + if ((ipa_compare(ipa_and(ip, cmask), ipa_and(needle_ip, cmask)) == 0) && + (n->prefix.lo <= needle->net.pxlen) && (needle->net.pxlen <= n->prefix.hi)) + { + bt_debug("FOUND\t" PRIip6 "/%d %d-%d\n", ARGip6(net6_prefix(&n->prefix.net)), n->prefix.net.pxlen, n->prefix.lo, n->prefix.hi); + return 1; /* OK */ + } + } + return 0; /* FAIL */ +} + +static struct f_prefix +get_random_ip6_prefix(void) +{ + struct f_prefix p; + u8 pxlen = xrandom(120)+8; + ip6_addr ip6 = ip6_build(bt_random(),bt_random(),bt_random(),bt_random()); + net_addr_ip6 net6 = NET_ADDR_IP6(ip6, pxlen); + + p.net = *((net_addr*) &net6); + + if (bt_random() % 2) + { + p.lo = 0; + p.hi = p.net.pxlen; + } + else + { + p.lo = p.net.pxlen; + p.hi = net_max_prefix_length[p.net.type]; + } + + return p; +} + +static void +generate_random_ipv6_prefixes(list *prefixes) +{ + int i; + for (i = 0; i < PREFIXES_NUM; i++) + { + struct f_prefix f = get_random_ip6_prefix(); + + struct f_prefix_node *px = calloc(1, sizeof(struct f_prefix_node)); + px->prefix = f; + + bt_debug("ADD\t" PRIip6 "/%d %d-%d\n", ARGip6(net6_prefix(&px->prefix.net)), px->prefix.net.pxlen, px->prefix.lo, px->prefix.hi); + add_tail(prefixes, &px->n); + } +} + +static int +t_match_net(void) +{ + bt_bird_init(); + bt_config_parse(BT_CONFIG_SIMPLE); + + uint round; + for (round = 0; round < TESTS_NUM; round++) + { + list prefixes; /* of structs f_extended_prefix */ + init_list(&prefixes); + struct f_trie *trie = f_new_trie(config->mem, sizeof(struct f_trie_node)); + + generate_random_ipv6_prefixes(&prefixes); + struct f_prefix_node *n; + WALK_LIST(n, prefixes) + { + trie_add_prefix(trie, &n->prefix.net, n->prefix.lo, n->prefix.hi); + } + + int i; + for (i = 0; i < PREFIX_TESTS_NUM; i++) + { + struct f_prefix f = get_random_ip6_prefix(); + bt_debug("TEST\t" PRIip6 "/%d\n", ARGip6(net6_prefix(&f.net)), f.net.pxlen); + + int should_be = is_prefix_included(&prefixes, &f); + int is_there = trie_match_net(trie, &f.net); + bt_assert_msg(should_be == is_there, "Prefix " PRIip6 "/%d %s", ARGip6(net6_prefix(&f.net)), f.net.pxlen, (should_be ? "should be found in trie" : "should not be found in trie")); + } + + struct f_prefix_node *nxt; + WALK_LIST_DELSAFE(n, nxt, prefixes) + { + free(n); + } + } + + bt_bird_cleanup(); + return 1; +} + +static int +t_trie_same(void) +{ + bt_bird_init(); + bt_config_parse(BT_CONFIG_SIMPLE); + + int round; + for (round = 0; round < TESTS_NUM*4; round++) + { + struct f_trie * trie1 = f_new_trie(config->mem, sizeof(struct f_trie_node)); + struct f_trie * trie2 = f_new_trie(config->mem, sizeof(struct f_trie_node)); + + list prefixes; /* a list of f_extended_prefix structures */ + init_list(&prefixes); + int i; + for (i = 0; i < 100; i++) + generate_random_ipv6_prefixes(&prefixes); + + struct f_prefix_node *n; + WALK_LIST(n, prefixes) + { + trie_add_prefix(trie1, &n->prefix.net, n->prefix.lo, n->prefix.hi); + } + WALK_LIST_BACKWARDS(n, prefixes) + { + trie_add_prefix(trie2, &n->prefix.net, n->prefix.lo, n->prefix.hi); + } + + bt_assert(trie_same(trie1, trie2)); + + struct f_prefix_node *nxt; + WALK_LIST_DELSAFE(n, nxt, prefixes) + { + free(n); + } + } + + return 1; +} + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + bt_test_suite(t_match_net, "Testing random prefix matching"); + bt_test_suite(t_trie_same, "A trie filled forward should be same with a trie filled backward."); + + return bt_exit_value(); +} @@ -3,6 +3,7 @@ S ip.c S lists.c S checksum.c bitops.c patmatch.c printf.c xmalloc.c tbf.c S mac.c +S flowspec.c D resource.sgml S resource.c S mempool.c diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 00000000..0c352869 --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,7 @@ +src := bitops.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 tbf.c xmalloc.c +obj := $(src-o-files) +$(all-daemon) + +tests_src := heap_test.c buffer_test.c event_test.c flowspec_test.c bitops_test.c patmatch_test.c fletcher16_test.c slist_test.c checksum_test.c lists_test.c mac_test.c ip_test.c hash_test.c printf_test.c +tests_targets := $(tests_targets) $(tests-target-files) +tests_objs := $(tests_objs) $(src-o-files) diff --git a/lib/Modules b/lib/Modules deleted file mode 100644 index 0e30b488..00000000 --- a/lib/Modules +++ /dev/null @@ -1,35 +0,0 @@ -sha256.c -sha256.h -sha512.c -sha512.h -sha1.c -sha1.h -birdlib.h -bitops.c -bitops.h -ip.h -ip.c -lists.c -lists.h -mac.c -mac.h -md5.c -md5.h -mempool.c -resource.c -resource.h -slab.c -socket.h -tbf.c -unaligned.h -xmalloc.c -printf.c -string.h -patmatch.c -slists.c -slists.h -event.c -event.h -checksum.c -checksum.h -alloca.h diff --git a/lib/alloca.h b/lib/alloca.h index f0d61bb4..e5557cdb 100644 --- a/lib/alloca.h +++ b/lib/alloca.h @@ -15,4 +15,6 @@ #include <stdlib.h> #endif +#define allocz(len) ({ void *_x = alloca(len); memset(_x, 0, len); _x; }) + #endif diff --git a/lib/birdlib.h b/lib/birdlib.h index d21cdf1f..317b5202 100644 --- a/lib/birdlib.h +++ b/lib/birdlib.h @@ -9,8 +9,8 @@ #ifndef _BIRD_BIRDLIB_H_ #define _BIRD_BIRDLIB_H_ -#include "timer.h" -#include "alloca.h" +#include "sysdep/unix/timer.h" +#include "lib/alloca.h" /* Ugly structure offset handling macros */ @@ -37,6 +37,15 @@ struct align_probe { char x; long int y; }; #define ABS(a) ((a)>=0 ? (a) : -(a)) #define DELTA(a,b) (((a)>=(b))?(a)-(b):(b)-(a)) #define ARRAY_SIZE(a) (sizeof(a)/sizeof(*(a))) +#define BYTES(n) ((((uint) (n)) + 7) / 8) +#define CALL(fn, args...) ({ if (fn) fn(args); }) +#define ADVANCE(w, r, l) ({ r -= l; w += l; }) + +static inline int uint_cmp(uint i1, uint i2) +{ return (int)(i1 > i2) - (int)(i1 < i2); } + +static inline int u64_cmp(u64 i1, u64 i2) +{ return (int)(i1 > i2) - (int)(i1 < i2); } /* Bitfield macros */ @@ -52,27 +61,12 @@ struct align_probe { char x; long int y; }; #define NULL ((void *) 0) #endif -#ifndef IPV6 -#define IP_VERSION 4 -#else -#define IP_VERSION 6 -#endif - - /* Macros for gcc attributes */ #define NORET __attribute__((noreturn)) #define UNUSED __attribute__((unused)) #define PACKED __attribute__((packed)) -#ifdef IPV6 -#define UNUSED4 -#define UNUSED6 UNUSED -#else -#define UNUSED4 UNUSED -#define UNUSED6 -#endif - /* Microsecond time */ typedef s64 btime; @@ -160,7 +154,7 @@ void bug(const char *msg, ...) NORET; #define L_FATAL "\010" /* Fatal errors */ #define L_BUG "\011" /* BIRD bugs */ -void debug(const char *msg, ...); /* Printf to debug output */ +void debug(const char *msg, ...); /* Printf to debug output */ /* Debugging */ diff --git a/lib/bitops.c b/lib/bitops.c index 81586e87..efb8710e 100644 --- a/lib/bitops.c +++ b/lib/bitops.c @@ -28,15 +28,15 @@ u32_mkmask(uint n) * * This function checks whether the given integer @x represents * a valid bit mask (binary representation contains first ones, then - * zeroes) and returns the number of ones or -1 if the mask is invalid. + * zeroes) and returns the number of ones or 255 if the mask is invalid. */ -int +uint u32_masklen(u32 x) { int l = 0; u32 n = ~x; - if (n & (n+1)) return -1; + if (n & (n+1)) return 255; if (x & 0x0000ffff) { x &= 0x0000ffff; l += 16; } if (x & 0x00ff00ff) { x &= 0x00ff00ff; l += 8; } if (x & 0x0f0f0f0f) { x &= 0x0f0f0f0f; l += 4; } diff --git a/lib/bitops.h b/lib/bitops.h index ce13732a..af648c26 100644 --- a/lib/bitops.h +++ b/lib/bitops.h @@ -9,6 +9,8 @@ #ifndef _BIRD_BITOPTS_H_ #define _BIRD_BITOPTS_H_ +#include "sysdep/config.h" + /* * Bit mask operations: * @@ -19,7 +21,7 @@ */ u32 u32_mkmask(uint n); -int u32_masklen(u32 x); +uint u32_masklen(u32 x); u32 u32_log2(u32 v); diff --git a/lib/bitops_test.c b/lib/bitops_test.c new file mode 100644 index 00000000..f816b9d1 --- /dev/null +++ b/lib/bitops_test.c @@ -0,0 +1,123 @@ +/* + * BIRD Library -- Generic Bit Operations Tests + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include "test/birdtest.h" +#include "test/bt-utils.h" /* naive_pow() */ + +#include "lib/bitops.h" + +#define MAX_NUM 1000 +#define CHECK_BIT(var,pos) ((var) & (u32)(1<<(pos))) + +static int +t_mkmask(void) +{ + int i; + u32 compute, expect; + + bt_assert(u32_mkmask(0) == 0x00000000); + for (i = 1; i <= 32; i++) + { + compute = u32_mkmask(i); + expect = (u32) (0xffffffff << (32-i)); + bt_assert_msg(compute == expect, "u32_mkmask(%d) = 0x%08X, expected 0x%08X", i, compute, expect); + } + + return 1; +} + +static int +u32_masklen_expected(u32 mask) +{ + int j, expect = 0; + + int valid = 0; + for (j = 0; j <= 32; j++) + if (mask == (j ? (0xffffffff << (32-j)) : 0)) /* Shifting 32-bit value by 32 bits is undefined behavior */ + valid = 1; + + if (!valid && mask != 0) + expect = 255; + else + for (j = 0; j <= 31; j++) + if (CHECK_BIT(mask, (31-j))) + expect = j+1; + else + break; + return expect; +} + +static void +check_mask(u32 mask) +{ + int expected, masklen; + + expected = u32_masklen_expected(mask); + masklen = u32_masklen(mask); + int ok = (expected == masklen); + bt_debug("u32_masklen(Ox%08x) = %d, expected %d %s\n", mask, masklen, expected, ok ? "OK" : "FAIL!"); + bt_assert(ok); +} + +static int +t_masklen(void) +{ + u32 i; + + check_mask(0x82828282); + check_mask(0x00000000); + + for (i = 0; i <= 32; i++) + check_mask(((u32) (i ? (0xffffffff << (32-i)) : 0)) & 0xffffffff); /* Shifting 32-bit value by 32 bits is undefined behavior */ + + for (i = 0; i <= MAX_NUM; i++) + check_mask(bt_random()); + + return 1; +} + +static void +check_log2(u32 n) +{ + u32 log = u32_log2(n); + u32 low = bt_naive_pow(2, log); + u32 high = bt_naive_pow(2, log+1); + + bt_assert_msg(n >= low && n < high, + "u32_log2(%u) = %u, %u should be in the range <%u, %u)", + n, log, n, low, high); +} + +static int +t_log2(void) +{ + u32 i; + + for (i = 0; i < 31; i++) + bt_assert(u32_log2(bt_naive_pow(2, i+1)) == i+1); + + for (i = 1; i < MAX_NUM; i++) + check_log2(i); + + for (i = 1; i < MAX_NUM; i++) + check_log2(((u32) bt_random()) % 0x0fffffff); + + return 1; +} + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + bt_test_suite(t_mkmask, "u32_mkmask()"); + bt_test_suite(t_masklen, "u32_masklen()"); + bt_test_suite(t_log2, "u32_log2()"); + + return bt_exit_value(); +} diff --git a/lib/buffer.h b/lib/buffer.h index 2a53f211..6fc18852 100644 --- a/lib/buffer.h +++ b/lib/buffer.h @@ -14,7 +14,7 @@ #include "sysdep/config.h" #define BUFFER(type) struct { type *data; uint used, size; } - +#define BUFFER_TYPE(v) typeof(* (v).data) #define BUFFER_SIZE(v) ((v).size * sizeof(* (v).data)) #define BUFFER_INIT(v,pool,isize) \ @@ -46,4 +46,14 @@ #define BUFFER_FLUSH(v) ({ (v).used = 0; }) +#define BUFFER_WALK(v,n) \ + for (BUFFER_TYPE(v) *_n = (v).data, n; _n < ((v).data + (v).used) && (n = *_n, 1); _n++) + +#define BUFFER_SHALLOW_COPY(dst, src) \ + ({ \ + (dst).used = (src).used; \ + (dst).size = (src).size; \ + (dst).data = (src).data; \ + }) + #endif /* _BIRD_BUFFER_H_ */ diff --git a/lib/buffer_test.c b/lib/buffer_test.c new file mode 100644 index 00000000..5b7de330 --- /dev/null +++ b/lib/buffer_test.c @@ -0,0 +1,167 @@ +/* + * BIRD Library -- Buffer Tests + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include <stdlib.h> + +#include "test/birdtest.h" + +#include "lib/buffer.h" + +#define MAX_NUM 33 + +typedef BUFFER(int) buffer_int; +static int expected[MAX_NUM]; +static buffer_int buf; +static struct pool *buffer_pool; + +static void +show_buf(buffer_int *b) +{ + uint i; + bt_debug(".used = %d, .size = %d\n", b->used, b->size); + + for (i = 0; i < b->used; i++) + bt_debug(" .data[%3u] = %-16d expected %-16d %s\n", i, b->data[i], expected[i], (b->data[i] == expected[i] ? "OK" : "FAIL!")); +} + +static void +fill_expected_array(void) +{ + int i; + + for (i = 0; i < MAX_NUM; i++) + expected[i] = bt_random(); +} + +static void +init_buffer(void) +{ + resource_init(); + buffer_pool = &root_pool; + BUFFER_INIT(buf, buffer_pool, MAX_NUM); +} + +static int +is_buffer_as_expected(buffer_int *b) +{ + show_buf(b); + + int i; + for (i = 0; i < MAX_NUM; i++) + bt_assert(b->data[i] == expected[i]); + return 1; +} + +static int +t_buffer_push(void) +{ + int i; + + init_buffer(); + fill_expected_array(); + + for (i = 0; i < MAX_NUM; i++) + BUFFER_PUSH(buf) = expected[i]; + is_buffer_as_expected(&buf); + + return 1; +} + +static int +t_buffer_pop(void) +{ + int i; + + init_buffer(); + fill_expected_array(); + + /* POP a half of elements */ + for (i = 0; i < MAX_NUM; i++) + BUFFER_PUSH(buf) = expected[i]; + for (i = MAX_NUM-1; i >= MAX_NUM/2; i--) + BUFFER_POP(buf); + for (i = MAX_NUM/2; i < MAX_NUM; i++) + BUFFER_PUSH(buf) = expected[i] = bt_random(); + is_buffer_as_expected(&buf); + + /* POP all of elements */ + for (i = MAX_NUM-1; i >= 0; i--) + BUFFER_POP(buf); + bt_assert(buf.used == 0); + for (i = 0; i < MAX_NUM; i++) + BUFFER_PUSH(buf) = expected[i]; + is_buffer_as_expected(&buf); + + return 1; +} + +static int +t_buffer_resize(void) +{ + int i; + + init_buffer(); + BUFFER_INIT(buf, buffer_pool, 0); + fill_expected_array(); + + for (i = 0; i < MAX_NUM; i++) + BUFFER_PUSH(buf) = expected[i]; + is_buffer_as_expected(&buf); + bt_assert(buf.size >= MAX_NUM); + + return 1; +} + +static int +t_buffer_flush(void) +{ + int i; + + init_buffer(); + fill_expected_array(); + for (i = 0; i < MAX_NUM; i++) + BUFFER_PUSH(buf) = expected[i]; + + BUFFER_FLUSH(buf); + bt_assert(buf.used == 0); + + return 1; +} + +static int +t_buffer_walk(void) +{ + int i; + + init_buffer(); + fill_expected_array(); + for (i = 0; i < MAX_NUM; i++) + BUFFER_PUSH(buf) = expected[i]; + + i = 0; + BUFFER_WALK(buf, v) + bt_assert(v == expected[i++]); + + bt_assert(i == MAX_NUM); + + return 1; +} + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + bt_test_suite(t_buffer_push, "Pushing new elements"); + bt_test_suite(t_buffer_pop, "Fill whole buffer (PUSH), a half of elements POP and PUSH new elements"); + bt_test_suite(t_buffer_resize, "Init a small buffer and try overfill"); + bt_test_suite(t_buffer_flush, "Fill and flush all elements"); + bt_test_suite(t_buffer_walk, "Fill and walk through buffer"); + + return bt_exit_value(); +} diff --git a/lib/checksum_test.c b/lib/checksum_test.c new file mode 100644 index 00000000..7e5658eb --- /dev/null +++ b/lib/checksum_test.c @@ -0,0 +1,94 @@ +/* + * BIRD Library -- IP One-Complement Checksum Tests + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include <stdio.h> + +#include "test/birdtest.h" + +#include "lib/checksum.h" + +#define MAX_NUM 10000 + +static u16 +ipsum_calculate_expected(u32 *a) +{ + int i; + u32 sum = 0; + + for(i = 0; i < MAX_NUM; i++) + { + sum += a[i] & 0xffff; + bt_debug("low) \t0x%08X \n", sum); + + sum += a[i] >> 16; + bt_debug("high) \t0x%08X \n", sum); + + u16 carry = sum >> 16; + sum = (sum & 0xffff) + carry; + bt_debug("carry) \t0x%08X \n\n", sum); + } + bt_debug("sum) \t0x%08X \n", sum); + + sum = sum ^ 0xffff; + bt_debug("~sum) \t0x%08X \n", sum); + + return sum; +} + +static int +t_calculate(void) +{ + u32 a[MAX_NUM]; + int i; + + for (i = 0; i < MAX_NUM; i++) + a[i] = bt_random(); + + u16 sum_calculated = ipsum_calculate(a, sizeof(a), NULL); + u16 sum_calculated_2 = ipsum_calculate(&a[0], sizeof(u32)*(MAX_NUM/2), &a[MAX_NUM/2], sizeof(u32)*(MAX_NUM - MAX_NUM/2), NULL); + bt_assert(sum_calculated == sum_calculated_2); + + u16 sum_expected = ipsum_calculate_expected(a); + + bt_debug("sum_calculated: %08X \n", sum_calculated); + bt_debug("sum_expected: %08X \n", sum_expected); + + bt_assert(sum_calculated == sum_expected); + + return 1; +} + +static int +t_verify(void) +{ + u32 a[MAX_NUM+1]; + int i; + + for (i = 0; i < MAX_NUM; i++) + a[i] = bt_random(); + + u16 sum = ipsum_calculate_expected(a); + + a[MAX_NUM] = sum; + + bt_assert(ipsum_verify(a, sizeof(a), NULL)); + + return 1; +} + + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + bt_test_suite(t_calculate, "Checksum of pseudo-random data"); + bt_test_suite(t_verify, "Verification of pseudo-random data."); + + return bt_exit_value(); +} diff --git a/lib/event_test.c b/lib/event_test.c new file mode 100644 index 00000000..92172389 --- /dev/null +++ b/lib/event_test.c @@ -0,0 +1,88 @@ +/* + * BIRD Library -- Event Processing Tests + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + + +#include "test/birdtest.h" + +#include "lib/net.h" +#include "lib/event.h" +#include "conf/conf.h" +#include "nest/locks.h" +#include "sysdep/unix/unix.h" +#include "nest/iface.h" +#include "nest/route.h" + +#define MAX_NUM 4 + +int event_check_points[MAX_NUM]; + +#define event_hook_body(num) \ + do { \ + bt_debug("Event Hook " #num "\n"); \ + event_check_points[num] = 1; \ + bt_assert_msg(event_check_points[num-1], "Events should be run in right order"); \ + } while (0) + +static void event_hook_1(void *data UNUSED) { event_hook_body(1); } +static void event_hook_2(void *data UNUSED) { event_hook_body(2); } +static void event_hook_3(void *data UNUSED) { event_hook_body(3); } + +#define schedule_event(num) \ + do { \ + struct event *event_##num = ev_new(&root_pool); \ + event_##num->hook = event_hook_##num; \ + ev_schedule(event_##num); \ + } while (0) + +static void +init_event_check_points(void) +{ + int i; + event_check_points[0] = 1; + for (i = 1; i < MAX_NUM; i++) + event_check_points[i] = 0; +} + +static int +t_ev_run_list(void) +{ + int i; + + resource_init(); + olock_init(); + io_init(); + rt_init(); + if_init(); +// roa_init(); + config_init(); + config = config_alloc(""); + + init_event_check_points(); + + schedule_event(1); + schedule_event(2); + schedule_event(3); + + ev_run_list(&global_event_list); + + for (i = 1; i < MAX_NUM; i++) + bt_assert(event_check_points[i]); + + return 1; +} + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + bt_test_suite(t_ev_run_list, "Schedule and run 3 events in right order."); + + return bt_exit_value(); +} + diff --git a/lib/fletcher16_test.c b/lib/fletcher16_test.c new file mode 100644 index 00000000..1020e6ec --- /dev/null +++ b/lib/fletcher16_test.c @@ -0,0 +1,169 @@ +/* + * BIRD Library -- Fletcher-16 Tests + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include "test/birdtest.h" +#include "lib/fletcher16.h" + +static u16 +straightforward_fletcher16_compute(const char *data) +{ + int count = strlen(data); + + u16 sum1 = 0; + u16 sum2 = 0; + int index; + + for (index = 0; index < count; ++index) + { + sum1 = (sum1 + data[index]) % 255; + sum2 = (sum2 + sum1) % 255; + } + + return (sum2 << 8) | sum1; +} + +static u16 +straightforward_fletcher16_checksum(const char *data) +{ + u16 csum; + u8 c0,c1,f0,f1; + + csum = straightforward_fletcher16_compute(data); + f0 = csum & 0xff; + f1 = (csum >> 8) & 0xff; + c0 = 0xff - ((f0 + f1) % 0xff); + c1 = 0xff - ((f0 + c0) % 0xff); + + return (c1 << 8) | c0; +} + +static int +test_fletcher16(void *out_, const void *in_, const void *expected_out_) +{ + u16 *out = out_; + const char *in = in_; + const u16 *expected_out = expected_out_; + + struct fletcher16_context ctxt; + + fletcher16_init(&ctxt); + fletcher16_update(&ctxt, in, strlen(in)); + put_u16(out, fletcher16_compute(&ctxt)); + + return *out == *expected_out; +} + +static int +test_fletcher16_checksum(void *out_, const void *in_, const void *expected_out_) +{ + u16 *out = out_; + const char *in = in_; + const u16 *expected_out = expected_out_; + + struct fletcher16_context ctxt; + int len = strlen(in); + + fletcher16_init(&ctxt); + fletcher16_update(&ctxt, in, len); + put_u16(out, fletcher16_final(&ctxt, len, len)); + + return *out == *expected_out; +} + +static int +t_fletcher16_compute(void) +{ + struct bt_pair test_vectors[] = { + { + .in = "\001\002", + .out = & (const u16) { 0x0403 }, + }, + { + .in = "", + .out = & ((const u16) { straightforward_fletcher16_compute("") }), + }, + { + .in = "a", + .out = & ((const u16) { straightforward_fletcher16_compute("a") }), + }, + { + .in = "abcd", + .out = & ((const u16) { straightforward_fletcher16_compute("abcd") }), + }, + { + .in = "message digest", + .out = & ((const u16) { straightforward_fletcher16_compute("message digest") }), + }, + { + .in = "abcdefghijklmnopqrstuvwxyz", + .out = & ((const u16) { straightforward_fletcher16_compute("abcdefghijklmnopqrstuvwxyz") }), + }, + { + .in = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + .out = & ((const u16) { straightforward_fletcher16_compute("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") }), + }, + { + .in = "12345678901234567890123456789012345678901234567890123456789012345678901234567890", + .out = & ((const u16) { straightforward_fletcher16_compute("12345678901234567890123456789012345678901234567890123456789012345678901234567890") }), + }, + }; + + return bt_assert_batch(test_vectors, test_fletcher16, bt_fmt_str, bt_fmt_unsigned); +} + +static int +t_fletcher16_checksum(void) +{ + struct bt_pair test_vectors[] = { + { + .in = "\001\002", + .out = & ((const u16) { straightforward_fletcher16_checksum("\001\002") }), + }, + { + .in = "", + .out = & ((const u16) { straightforward_fletcher16_checksum("") }), + }, + { + .in = "a", + .out = & ((const u16) { straightforward_fletcher16_checksum("a") }), + }, + { + .in = "abcd", + .out = & ((const u16) { straightforward_fletcher16_checksum("abcd") }), + }, + { + .in = "message digest", + .out = & ((const u16) { straightforward_fletcher16_checksum("message digest") }), + }, + { + .in = "abcdefghijklmnopqrstuvwxyz", + .out = & ((const u16) { straightforward_fletcher16_checksum("abcdefghijklmnopqrstuvwxyz") }), + }, + { + .in = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + .out = & ((const u16) { straightforward_fletcher16_checksum("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") }), + }, + { + .in = "12345678901234567890123456789012345678901234567890123456789012345678901234567890", + .out = & ((const u16) { straightforward_fletcher16_checksum("12345678901234567890123456789012345678901234567890123456789012345678901234567890") }), + }, + }; + + return bt_assert_batch(test_vectors, test_fletcher16_checksum, bt_fmt_str, bt_fmt_unsigned); +} + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + bt_test_suite(t_fletcher16_compute, "Fletcher-16 Compute Tests"); + bt_test_suite(t_fletcher16_checksum, "Fletcher-16 Checksum Tests"); + + return bt_exit_value(); +} diff --git a/lib/flowspec.c b/lib/flowspec.c new file mode 100644 index 00000000..0b863ed9 --- /dev/null +++ b/lib/flowspec.c @@ -0,0 +1,1183 @@ +/* + * BIRD Library -- Flow specification (RFC 5575) + * + * (c) 2016 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +/** + * DOC: Flow specification (flowspec) + * + * Flowspec are rules (RFC 5575) for firewalls disseminated using BGP protocol. + * The |flowspec.c| is a library for handling flowspec binary streams and + * flowspec data structures. You will find there functions for validation + * incoming flowspec binary streams, iterators for jumping over components, + * functions for handling a length and functions for formatting flowspec data + * structure into user-friendly text representation. + * + * In this library, you will find also flowspec builder. In |confbase.Y|, there + * are grammar's rules for parsing and building new flowspec data structure + * from BIRD's configuration files and from BIRD's command line interface. + * Finalize function will assemble final &net_addr_flow4 or &net_addr_flow6 + * data structure. + * + * The data structures &net_addr_flow4 and &net_addr_flow6 are defined in + * |net.h| file. The attribute length is size of whole data structure plus + * binary stream representation of flowspec including a compressed encoded + * length of flowspec. + * + * Sometimes in code, it is used expression flowspec type, it should mean + * flowspec component type. + */ + +#include "nest/bird.h" +#include "lib/flowspec.h" +#include "conf/conf.h" + + +static const char* flow4_type_str[] = { + [FLOW_TYPE_DST_PREFIX] = "dst", + [FLOW_TYPE_SRC_PREFIX] = "src", + [FLOW_TYPE_IP_PROTOCOL] = "proto", + [FLOW_TYPE_PORT] = "port", + [FLOW_TYPE_DST_PORT] = "dport", + [FLOW_TYPE_SRC_PORT] = "sport", + [FLOW_TYPE_ICMP_TYPE] = "icmp type", + [FLOW_TYPE_ICMP_CODE] = "icmp code", + [FLOW_TYPE_TCP_FLAGS] = "tcp flags", + [FLOW_TYPE_PACKET_LENGTH] = "length", + [FLOW_TYPE_DSCP] = "dscp", + [FLOW_TYPE_FRAGMENT] = "fragment" +}; + +static const char* flow6_type_str[] = { + [FLOW_TYPE_DST_PREFIX] = "dst", + [FLOW_TYPE_SRC_PREFIX] = "src", + [FLOW_TYPE_NEXT_HEADER] = "next header", + [FLOW_TYPE_PORT] = "port", + [FLOW_TYPE_DST_PORT] = "dport", + [FLOW_TYPE_SRC_PORT] = "sport", + [FLOW_TYPE_ICMP_TYPE] = "icmp type", + [FLOW_TYPE_ICMP_CODE] = "icmp code", + [FLOW_TYPE_TCP_FLAGS] = "tcp flags", + [FLOW_TYPE_PACKET_LENGTH] = "length", + [FLOW_TYPE_DSCP] = "dscp", + [FLOW_TYPE_FRAGMENT] = "fragment", + [FLOW_TYPE_LABEL] = "label" +}; + +/** + * flow_type_str - get stringified flowspec name of component + * @type: flowspec component type + * @ipv6: IPv4/IPv6 decide flag, use zero for IPv4 and one for IPv6 + * + * This function returns flowspec name of component @type in string. + */ +const char * +flow_type_str(enum flow_type type, int ipv6) +{ + return ipv6 ? flow6_type_str[type] : flow4_type_str[type]; +} + +/* + * Length + */ + +/** + * flow_write_length - write compressed length value + * @data: destination buffer to write + * @len: the value of the length (0 to 0xfff) for writing + * + * This function writes appropriate as (1- or 2-bytes) the value of @len into + * buffer @data. The function returns number of written bytes, thus 1 or 2 bytes. + */ +uint +flow_write_length(byte *data, u16 len) +{ + if (len >= 0xf0) + { + put_u16(data, len | 0xf000); + return 2; + } + + *data = len; + return 1; +} + +inline static uint +get_value_length(const byte *op) +{ + return (1 << ((*op & 0x30) >> 4)); +} + + + +/* + * Flowspec iterators + */ + +static inline u8 num_op(const byte *op) { return (*op & 0x07); } +static inline int isset_and(const byte *op) { return ((*op & 0x40) == 0x40); } +static inline int isset_end(const byte *op) { return ((*op & 0x80) == 0x80); } + +static const byte * +flow_first_part(const byte *data) +{ + if (!data || flow_read_length(data) == 0) + return NULL; + + /* It is allowed to encode the value of length less then 240 into 2-bytes too */ + if ((data[0] & 0xf0) == 0xf0) + return data + 2; + + return data + 1; +} + +/** + * flow4_first_part - get position of the first flowspec component + * @f: flowspec data structure &net_addr_flow4 + * + * This function return a position to the beginning of the first flowspec + * component in IPv4 flowspec @f. + */ +inline const byte * +flow4_first_part(const net_addr_flow4 *f) +{ + return f ? flow_first_part(f->data) : NULL; +} + +/** + * flow6_first_part - get position of the first flowspec component + * @f: flowspec data structure &net_addr_flow6 + * + * This function return a position to the beginning of the first flowspec + * component in IPv6 flowspec @f. + */ +inline const byte * +flow6_first_part(const net_addr_flow6 *f) +{ + return f ? flow_first_part(f->data) : NULL; +} + +static const byte * +flow_next_part(const byte *pos, const byte *end, int ipv6) +{ + switch (*pos++) + { + case FLOW_TYPE_DST_PREFIX: + case FLOW_TYPE_SRC_PREFIX: + { + uint pxlen = *pos++; + uint bytes = BYTES(pxlen); + if (ipv6) + { + uint offset = *pos++ / 8; + pos += bytes - offset; + } + else + { + pos += bytes; + } + break; + } + + case FLOW_TYPE_IP_PROTOCOL: /* == FLOW_TYPE_NEXT_HEADER */ + case FLOW_TYPE_PORT: + case FLOW_TYPE_DST_PORT: + case FLOW_TYPE_SRC_PORT: + case FLOW_TYPE_ICMP_TYPE: + case FLOW_TYPE_ICMP_CODE: + case FLOW_TYPE_TCP_FLAGS: + case FLOW_TYPE_PACKET_LENGTH: + case FLOW_TYPE_DSCP: + case FLOW_TYPE_FRAGMENT: + case FLOW_TYPE_LABEL: + { + /* Is this the end of list operator-value pair? */ + uint last = 0; + + while (!last) + { + last = isset_end(pos); + + /* Value length of operator */ + uint len = get_value_length(pos); + pos += 1+len; + } + break; + } + default: + return NULL; + } + + return (pos < end) ? pos : NULL; +} + +/** + * flow4_next_part - an iterator over flowspec components in flowspec binary stream + * @pos: the beginning of a previous or the first component in flowspec binary + * stream + * @end: the last valid byte in scanned flowspec binary stream + * + * This function returns a position to the beginning of the next component + * (to a component type byte) in flowspec binary stream or %NULL for the end. + */ +inline const byte * +flow4_next_part(const byte *pos, const byte *end) +{ + return flow_next_part(pos, end, 0); +} + +/** + * flow6_next_part - an iterator over flowspec components in flowspec binary stream + * @pos: the beginning of a previous or the first component in flowspec binary + * stream + * @end: the last valid byte in scanned flowspec binary stream + * + * This function returns a position to the beginning of the next component + * (to a component type byte) in flowspec binary stream or %NULL for the end. + */ +inline const byte * +flow6_next_part(const byte *pos, const byte *end) +{ + return flow_next_part(pos, end, 1); +} + + +/* + * Flowspec validation + */ + +static const char* flow_validated_state_str_[] = { + [FLOW_ST_UNKNOWN_COMPONENT] = "Unknown component", + [FLOW_ST_VALID] = "Valid", + [FLOW_ST_NOT_COMPLETE] = "Not complete", + [FLOW_ST_EXCEED_MAX_PREFIX_LENGTH] = "Exceed maximal prefix length", + [FLOW_ST_EXCEED_MAX_PREFIX_OFFSET] = "Exceed maximal prefix offset", + [FLOW_ST_EXCEED_MAX_VALUE_LENGTH] = "Exceed maximal value length", + [FLOW_ST_BAD_TYPE_ORDER] = "Bad component order", + [FLOW_ST_AND_BIT_SHOULD_BE_UNSET] = "The AND-bit should be unset", + [FLOW_ST_ZERO_BIT_SHOULD_BE_UNSED] = "The Zero-bit should be unset", + [FLOW_ST_DEST_PREFIX_REQUIRED] = "Destination prefix is missing", + [FLOW_ST_INVALID_TCP_FLAGS] = "TCP flags exceeding 0xfff", + [FLOW_ST_CANNOT_USE_DONT_FRAGMENT] = "Cannot use Don't fragment flag in IPv6 flow" +}; + +/** + * flow_validated_state_str - return a textual description of validation process + * @code: validation result + * + * This function return well described validation state in string. + */ +const char * +flow_validated_state_str(enum flow_validated_state code) +{ + return flow_validated_state_str_[code]; +} + +static const u8 flow4_max_value_length[] = { + [FLOW_TYPE_DST_PREFIX] = 0, + [FLOW_TYPE_SRC_PREFIX] = 0, + [FLOW_TYPE_IP_PROTOCOL] = 1, + [FLOW_TYPE_PORT] = 2, + [FLOW_TYPE_DST_PORT] = 2, + [FLOW_TYPE_SRC_PORT] = 2, + [FLOW_TYPE_ICMP_TYPE] = 1, + [FLOW_TYPE_ICMP_CODE] = 1, + [FLOW_TYPE_TCP_FLAGS] = 2, + [FLOW_TYPE_PACKET_LENGTH] = 2, + [FLOW_TYPE_DSCP] = 1, + [FLOW_TYPE_FRAGMENT] = 1 /* XXX */ +}; + +static const u8 flow6_max_value_length[] = { + [FLOW_TYPE_DST_PREFIX] = 0, + [FLOW_TYPE_SRC_PREFIX] = 0, + [FLOW_TYPE_NEXT_HEADER] = 1, + [FLOW_TYPE_PORT] = 2, + [FLOW_TYPE_DST_PORT] = 2, + [FLOW_TYPE_SRC_PORT] = 2, + [FLOW_TYPE_ICMP_TYPE] = 1, + [FLOW_TYPE_ICMP_CODE] = 1, + [FLOW_TYPE_TCP_FLAGS] = 2, + [FLOW_TYPE_PACKET_LENGTH] = 2, + [FLOW_TYPE_DSCP] = 1, + [FLOW_TYPE_FRAGMENT] = 1, /* XXX */ + [FLOW_TYPE_LABEL] = 4 +}; + +static u8 +flow_max_value_length(enum flow_type type, int ipv6) +{ + return ipv6 ? flow6_max_value_length[type] : flow4_max_value_length[type]; +} + +/** + * flow_check_cf_bmk_values - check value/bitmask part of flowspec component + * @fb: flow builder instance + * @neg: negation operand + * @val: value from value/mask pair + * @mask: bitmap mask from value/mask pair + * + * This function checks value/bitmask pair. If some problem will appear, the + * function calls cf_error() function with a textual description of reason + * to failing of validation. + */ +void +flow_check_cf_bmk_values(struct flow_builder *fb, u8 neg, u32 val, u32 mask) +{ + flow_check_cf_value_length(fb, val); + flow_check_cf_value_length(fb, mask); + + if (neg && !(val == 0 || val == mask)) + cf_error("For negation, value must be zero or bitmask"); + + if ((fb->this_type == FLOW_TYPE_TCP_FLAGS) && (mask & 0xf000)) + cf_error("Invalid mask 0x%x, must not exceed 0xfff", mask); + + if ((fb->this_type == FLOW_TYPE_FRAGMENT) && fb->ipv6 && (mask & 0x01)) + cf_error("Invalid mask 0x%x, bit 0 must be 0", mask); + + if (val & ~mask) + cf_error("Value 0x%x outside bitmask 0x%x", val, mask); +} + +/** + * flow_check_cf_value_length - check value by flowspec component type + * @fb: flow builder instance + * @val: value + * + * This function checks if the value is in range of component's type support. + * If some problem will appear, the function calls cf_error() function with + * a textual description of reason to failing of validation. + */ +void +flow_check_cf_value_length(struct flow_builder *fb, u32 val) +{ + enum flow_type t = fb->this_type; + u8 max = flow_max_value_length(t, fb->ipv6); + + if (t == FLOW_TYPE_DSCP && val > 0x3f) + cf_error("%s value %u out of range (0-63)", flow_type_str(t, fb->ipv6), val); + + if (max == 1 && (val > 0xff)) + cf_error("%s value %u out of range (0-255)", flow_type_str(t, fb->ipv6), val); + + if (max == 2 && (val > 0xffff)) + cf_error("%s value %u out of range (0-65535)", flow_type_str(t, fb->ipv6), val); +} + +static enum flow_validated_state +flow_validate(const byte *nlri, uint len, int ipv6) +{ + enum flow_type type = 0; + const byte *pos = nlri; + const byte *end = nlri + len; + int met_dst_pfx = 0; + + while (pos < end) + { + /* Check increasing type ordering */ + if (*pos <= type) + return FLOW_ST_BAD_TYPE_ORDER; + type = *pos++; + + switch (type) + { + case FLOW_TYPE_DST_PREFIX: + met_dst_pfx = 1; + /* Fall through */ + case FLOW_TYPE_SRC_PREFIX: + { + uint pxlen = *pos++; + if (pxlen > (ipv6 ? IP6_MAX_PREFIX_LENGTH : IP4_MAX_PREFIX_LENGTH)) + return FLOW_ST_EXCEED_MAX_PREFIX_LENGTH; + + uint bytes = BYTES(pxlen); + if (ipv6) + { + uint pxoffset = *pos++; + if (pxoffset > IP6_MAX_PREFIX_LENGTH || pxoffset > pxlen) + return FLOW_ST_EXCEED_MAX_PREFIX_OFFSET; + bytes -= pxoffset / 8; + } + pos += bytes; + + break; + } + + case FLOW_TYPE_LABEL: + if (!ipv6) + return FLOW_ST_UNKNOWN_COMPONENT; + /* fall through */ + case FLOW_TYPE_IP_PROTOCOL: /* == FLOW_TYPE_NEXT_HEADER */ + case FLOW_TYPE_PORT: + case FLOW_TYPE_DST_PORT: + case FLOW_TYPE_SRC_PORT: + case FLOW_TYPE_ICMP_TYPE: + case FLOW_TYPE_ICMP_CODE: + case FLOW_TYPE_TCP_FLAGS: + case FLOW_TYPE_PACKET_LENGTH: + case FLOW_TYPE_DSCP: + case FLOW_TYPE_FRAGMENT: + { + uint last = 0; + uint first = 1; + + while (!last) + { + /* + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | e | a | len | 0 |lt |gt |eq | + * +---+---+---+---+---+---+---+---+ + * + * Numeric operator + */ + + last = isset_end(pos); + + /* The AND bit should in the first operator byte of a sequence */ + if (first && isset_and(pos)) + return FLOW_ST_AND_BIT_SHOULD_BE_UNSET; + + /* This bit should be zero */ + if (*pos & 0x08) + return FLOW_ST_ZERO_BIT_SHOULD_BE_UNSED; + + if (type == FLOW_TYPE_TCP_FLAGS || type == FLOW_TYPE_FRAGMENT) + { + /* + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | e | a | len | 0 | 0 |not| m | + * +---+---+---+---+---+---+---+---+ + * + * Bitmask operand + */ + if (*pos & 0x04) + return FLOW_ST_ZERO_BIT_SHOULD_BE_UNSED; + } + + /* Value length of operator */ + uint len = get_value_length(pos); + if (len > flow_max_value_length(type, ipv6)) + return FLOW_ST_EXCEED_MAX_VALUE_LENGTH; + + /* TCP Flags component must not check highest nibble (just 12 valid bits) */ + if ((type == FLOW_TYPE_TCP_FLAGS) && (len == 2) && (pos[1] & 0xf0)) + return FLOW_ST_INVALID_TCP_FLAGS; + + /* Bit-7 must be 0 [draft-ietf-idr-flow-spec-v6] */ + if ((type == FLOW_TYPE_FRAGMENT) && ipv6 && (pos[1] & 0x01)) + return FLOW_ST_CANNOT_USE_DONT_FRAGMENT; + /* XXX: Could be a fragment component encoded in 2-bytes? */ + + pos += 1+len; + + if (pos > end && !last) + return FLOW_ST_NOT_COMPLETE; + + if (pos > (end+1)) + return FLOW_ST_NOT_COMPLETE; + + first = 0; + } + break; + } + default: + return FLOW_ST_UNKNOWN_COMPONENT; + } + } + + if (pos != end) + return FLOW_ST_NOT_COMPLETE; + + if (!ipv6 && !met_dst_pfx) + return FLOW_ST_DEST_PREFIX_REQUIRED; + + return FLOW_ST_VALID; +} + +/** + * flow4_validate - check untrustworthy IPv4 flowspec data stream + * @nlri: flowspec data stream without compressed encoded length value + * @len: length of @nlri + * + * This function checks meaningfulness of binary flowspec. It should return + * %FLOW_ST_VALID or %FLOW_ST_UNKNOWN_COMPONENT. If some problem appears, it + * returns some other %FLOW_ST_xxx state. + */ +inline enum flow_validated_state +flow4_validate(const byte *nlri, uint len) +{ + return flow_validate(nlri, len, 0); +} + +/** + * flow6_validate - check untrustworthy IPv6 flowspec data stream + * @nlri: flowspec binary stream without encoded length value + * @len: length of @nlri + * + * This function checks meaningfulness of binary flowspec. It should return + * %FLOW_ST_VALID or %FLOW_ST_UNKNOWN_COMPONENT. If some problem appears, it + * returns some other %FLOW_ST_xxx state. + */ +inline enum flow_validated_state +flow6_validate(const byte *nlri, uint len) +{ + return flow_validate(nlri, len, 1); +} + +/** + * flow4_validate_cf - validate flowspec data structure &net_addr_flow4 in parsing time + * @f: flowspec data structure &net_addr_flow4 + * + * Check if @f is valid flowspec data structure. Can call cf_error() function + * with a textual description of reason to failing of validation. + */ +void +flow4_validate_cf(net_addr_flow4 *f) +{ + enum flow_validated_state r = flow4_validate(flow4_first_part(f), flow_read_length(f->data)); + + if (r != FLOW_ST_VALID) + cf_error("Invalid flow route: %s", flow_validated_state_str(r)); +} + +/** + * flow6_validate_cf - validate flowspec data structure &net_addr_flow6 in parsing time + * @f: flowspec data structure &net_addr_flow6 + * + * Check if @f is valid flowspec data structure. Can call cf_error() function + * with a textual description of reason to failing of validation. + */ +void +flow6_validate_cf(net_addr_flow6 *f) +{ + enum flow_validated_state r = flow6_validate(flow6_first_part(f), flow_read_length(f->data)); + + if (r != FLOW_ST_VALID) + cf_error("Invalid flow route: %s", flow_validated_state_str(r)); +} + + +/* + * Flowspec Builder + */ + +/** + * flow_builder_init - constructor for flowspec builder instance + * @pool: memory pool + * + * This function prepares flowspec builder instance using memory pool @pool. + */ +struct flow_builder * +flow_builder_init(pool *pool) +{ + struct flow_builder *fb = mb_allocz(pool, sizeof(struct flow_builder)); + BUFFER_INIT(fb->data, pool, 4); + return fb; +} + +static int +is_stackable_type(enum flow_type type) +{ + switch (type) + { + case FLOW_TYPE_IP_PROTOCOL: + case FLOW_TYPE_PORT: + case FLOW_TYPE_DST_PORT: + case FLOW_TYPE_SRC_PORT: + case FLOW_TYPE_ICMP_TYPE: + case FLOW_TYPE_ICMP_CODE: + case FLOW_TYPE_TCP_FLAGS: + case FLOW_TYPE_PACKET_LENGTH: + case FLOW_TYPE_DSCP: + case FLOW_TYPE_FRAGMENT: + case FLOW_TYPE_LABEL: + return 1; + + default: + /* The unknown components are not stack-able in default */ + return 0; + } +} + +static int +builder_add_prepare(struct flow_builder *fb) +{ + if (fb->parts[fb->this_type].length) + { + if (fb->last_type != fb->this_type) + return 0; + + if (!is_stackable_type(fb->this_type)) + return 0; + } + else + { + fb->parts[fb->this_type].offset = fb->data.used; + } + + return 1; +} + +static void +builder_add_finish(struct flow_builder *fb) +{ + fb->parts[fb->this_type].length = fb->data.used - fb->parts[fb->this_type].offset; + flow_builder_set_type(fb, fb->this_type); +} + +static void +push_pfx_to_buffer(struct flow_builder *fb, u8 pxlen_bytes, byte *ip) +{ + for (int i = 0; i < pxlen_bytes; i++) + BUFFER_PUSH(fb->data) = *ip++; +} + +/** + * flow_builder4_add_pfx - add IPv4 prefix + * @fb: flowspec builder instance + * @n4: net address of type IPv4 + * + * This function add IPv4 prefix into flowspec builder instance. + */ +int +flow_builder4_add_pfx(struct flow_builder *fb, const net_addr_ip4 *n4) +{ + if (!builder_add_prepare(fb)) + return 0; + + ip4_addr ip4 = ip4_hton(n4->prefix); + + BUFFER_PUSH(fb->data) = fb->this_type; + BUFFER_PUSH(fb->data) = n4->pxlen; + push_pfx_to_buffer(fb, BYTES(n4->pxlen), (byte *) &ip4); + + builder_add_finish(fb); + return 1; +} + +/** + * flow_builder6_add_pfx - add IPv6 prefix + * @fb: flowspec builder instance + * @n6: net address of type IPv4 + * @pxoffset: prefix offset for @n6 + * + * This function add IPv4 prefix into flowspec builder instance. This function + * should return 1 for successful adding, otherwise returns %0. + */ +int +flow_builder6_add_pfx(struct flow_builder *fb, const net_addr_ip6 *n6, u32 pxoffset) +{ + if (!builder_add_prepare(fb)) + return 0; + + ip6_addr ip6 = ip6_hton(n6->prefix); + + BUFFER_PUSH(fb->data) = fb->this_type; + BUFFER_PUSH(fb->data) = n6->pxlen; + BUFFER_PUSH(fb->data) = pxoffset; + push_pfx_to_buffer(fb, BYTES(n6->pxlen) - (pxoffset / 8), ((byte *) &ip6) + (pxoffset / 8)); + + builder_add_finish(fb); + return 1; +} + +/** + * flow_builder_add_op_val - add operator/value pair + * @fb: flowspec builder instance + * @op: operator + * @value: value + * + * This function add operator/value pair as a part of a flowspec component. It + * is required to set appropriate flowspec component type using function + * flow_builder_set_type(). This function should return 1 for successful + * adding, otherwise returns 0. + */ +int +flow_builder_add_op_val(struct flow_builder *fb, byte op, u32 value) +{ + if (!builder_add_prepare(fb)) + return 0; + + if (fb->this_type == fb->last_type) + { + /* Remove the end-bit from last operand-value pair of the component */ + fb->data.data[fb->last_op_offset] &= 0x7f; + } + else + { + BUFFER_PUSH(fb->data) = fb->this_type; + } + + fb->last_op_offset = fb->data.used; + + /* Set the end-bit for operand-value pair of the component */ + op |= 0x80; + + if (value & 0xff00) + { + BUFFER_PUSH(fb->data) = op | 0x10; + put_u16(BUFFER_INC(fb->data, 2), value); + } + else + { + BUFFER_PUSH(fb->data) = op; + BUFFER_PUSH(fb->data) = (u8) value; + } + + builder_add_finish(fb); + return 1; +} + +/** + * flow_builder_add_val_mask - add value/bitmask pair + * @fb: flowspec builder instance + * @op: operator + * @value: value + * @mask: bitmask + * + * It is required to set appropriate flowspec component type using function + * flow_builder_set_type(). This function should return 1 for successful adding, + * otherwise returns 0. + */ +int +flow_builder_add_val_mask(struct flow_builder *fb, byte op, u32 value, u32 mask) +{ + u32 a = value & mask; + u32 b = ~value & mask; + + if (a) + { + flow_builder_add_op_val(fb, op ^ 0x01, a); + op |= 0x40; + } + + if (b) + flow_builder_add_op_val(fb, op ^ 0x02, b); + + return 1; +} + + +/** + * flow_builder_set_type - set type of next flowspec component + * @fb: flowspec builder instance + * @type: flowspec component type + * + * This function sets type of next flowspec component. It is necessary to call + * this function before each changing of adding flowspec component. + */ +void +flow_builder_set_type(struct flow_builder *fb, enum flow_type type) +{ + fb->last_type = fb->this_type; + fb->this_type = type; +} + +static ip4_addr +flow_read_ip4(const byte *px, uint pxlen) +{ + ip4_addr ip = IP4_NONE; + memcpy(&ip, px, BYTES(pxlen)); + return ip4_ntoh(ip); +} + +static ip6_addr +flow_read_ip6(const byte *px, uint pxlen, uint pxoffset) +{ + uint floor_offset = BYTES(pxoffset - (pxoffset % 8)); + uint ceil_len = BYTES(pxlen); + ip6_addr ip = IP6_NONE; + + memcpy(((byte *) &ip) + floor_offset, px, ceil_len - floor_offset); + + return ip6_ntoh(ip); +} + +static void +builder_write_parts(struct flow_builder *fb, byte *buf) +{ + for (int i = 1; i < FLOW_TYPE_MAX; i++) + { + if (fb->parts[i].length) + { + memcpy(buf, fb->data.data + fb->parts[i].offset, fb->parts[i].length); + buf += fb->parts[i].length; + } + } +} + +/** + * flow_builder4_finalize - assemble final flowspec data structure &net_addr_flow4 + * @fb: flowspec builder instance + * @lpool: linear memory pool + * + * This function returns final flowspec data structure &net_addr_flow4 allocated + * onto @lpool linear memory pool. + */ +net_addr_flow4 * +flow_builder4_finalize(struct flow_builder *fb, linpool *lpool) +{ + uint data_len = fb->data.used + (fb->data.used < 0xf0 ? 1 : 2); + net_addr_flow4 *f = lp_alloc(lpool, sizeof(struct net_addr_flow4) + data_len); + + ip4_addr prefix = IP4_NONE; + uint pxlen = 0; + + if (fb->parts[FLOW_TYPE_DST_PREFIX].length) + { + byte *p = fb->data.data + fb->parts[FLOW_TYPE_DST_PREFIX].offset + 1; + pxlen = *p++; + prefix = flow_read_ip4(p, pxlen); + } + *f = NET_ADDR_FLOW4(prefix, pxlen, data_len); + + builder_write_parts(fb, f->data + flow_write_length(f->data, fb->data.used)); + + return f; +} + +/** + * flow_builder6_finalize - assemble final flowspec data structure &net_addr_flow6 + * @fb: flowspec builder instance + * @lpool: linear memory pool for allocation of + * + * This function returns final flowspec data structure &net_addr_flow6 allocated + * onto @lpool linear memory pool. + */ +net_addr_flow6 * +flow_builder6_finalize(struct flow_builder *fb, linpool *lpool) +{ + uint data_len = fb->data.used + (fb->data.used < 0xf0 ? 1 : 2); + net_addr_flow6 *n = lp_alloc(lpool, sizeof(net_addr_flow6) + data_len); + + ip6_addr prefix = IP6_NONE; + uint pxlen = 0; + + if (fb->parts[FLOW_TYPE_DST_PREFIX].length) + { + byte *p = fb->data.data + fb->parts[FLOW_TYPE_DST_PREFIX].offset + 1; + pxlen = *p++; + uint pxoffset = *p++; + prefix = flow_read_ip6(p, pxlen, pxoffset); + } + *n = NET_ADDR_FLOW6(prefix, pxlen, data_len); + + builder_write_parts(fb, n->data + flow_write_length(n->data, fb->data.used)); + + return n; +} + +/** + * flow_builder_clear - flush flowspec builder instance for another flowspec creation + * @fb: flowspec builder instance + * + * This function flushes all data from builder but it maintains pre-allocated + * buffer space. + */ +void +flow_builder_clear(struct flow_builder *fb) +{ + BUFFER(byte) data; + BUFFER_FLUSH(fb->data); + + BUFFER_SHALLOW_COPY(data, fb->data); + memset(fb, 0, sizeof(struct flow_builder)); + BUFFER_SHALLOW_COPY(fb->data, data); +} + + +/* + * Net Formatting + */ + +/* Flowspec operators for [op, value]+ pairs */ +#define FLOW_TRUE 0b000 +#define FLOW_EQ 0b001 +#define FLOW_GT 0b010 +#define FLOW_GTE 0b011 +#define FLOW_LT 0b100 +#define FLOW_LTE 0b101 +#define FLOW_NEQ 0b110 +#define FLOW_FALSE 0b111 + +static const char * +num_op_str(const byte *op) +{ + switch (*op & 0x07) + { + case FLOW_TRUE: return "true"; + case FLOW_EQ: return "="; + case FLOW_GT: return ">"; + case FLOW_GTE: return ">="; + case FLOW_LT: return "<"; + case FLOW_LTE: return "<="; + case FLOW_NEQ: return "!="; + case FLOW_FALSE: return "false"; + } + + return NULL; +} + +static u64 +get_value(const byte *val, u8 len) +{ + switch (len) + { + case 1: return *val; + case 2: return get_u16(val); + case 4: return get_u32(val); + case 8: return get_u64(val); + } + + return 0; +} + +static const char * +fragment_val_str(u8 val) +{ + switch (val) + { + case 1: return "dont_fragment"; + case 2: return "is_fragment"; + case 4: return "first_fragment"; + case 8: return "last_fragment"; + } + return "???"; +} + +static void +net_format_flow_ip(buffer *b, const byte *part, int ipv6) +{ + uint pxlen = *(part+1); + if (ipv6) + { + uint pxoffset = *(part+2); + if (pxoffset) + buffer_print(b, "%I6/%u offset %u; ", flow_read_ip6(part+3,pxlen,pxoffset), pxlen, pxoffset); + else + buffer_print(b, "%I6/%u; ", flow_read_ip6(part+3,pxlen,0), pxlen); + } + else + { + buffer_print(b, "%I4/%u; ", flow_read_ip4(part+2,pxlen), pxlen); + } +} + +static void +net_format_flow_num(buffer *b, const byte *part) +{ + const byte *last_op = NULL; + const byte *op = part+1; + u64 val; + uint len; + uint first = 1; + + while (1) + { + if (!first) + { + /* XXX: I don't like this so complicated if-tree */ + if (!isset_and(op) && + ((num_op( op) == FLOW_EQ) || (num_op( op) == FLOW_GTE)) && + ((num_op(last_op) == FLOW_EQ) || (num_op(last_op) == FLOW_LTE))) + { + b->pos--; /* Remove last char (it is a space) */ + buffer_puts(b, ","); + } + else + { + buffer_puts(b, isset_and(op) ? "&& " : "|| "); + } + } + first = 0; + + len = get_value_length(op); + val = get_value(op+1, len); + + if (!isset_end(op) && !isset_and(op) && isset_and(op+1+len) && + (num_op(op) == FLOW_GTE) && (num_op(op+1+len) == FLOW_LTE)) + { + /* Display interval */ + buffer_print(b, "%u..", val); + op += 1 + len; + len = get_value_length(op); + val = get_value(op+1, len); + buffer_print(b, "%u", val); + } + else if (num_op(op) == FLOW_EQ) + { + buffer_print(b, "%u", val); + } + else + { + buffer_print(b, "%s %u", num_op_str(op), val); + } + + if (isset_end(op)) + { + buffer_puts(b, "; "); + break; + } + else + { + buffer_puts(b, " "); + } + + last_op = op; + op += 1 + len; + } +} + +static void +net_format_flow_bitmask(buffer *b, const byte *part) +{ + const byte *op = part+1; + u64 val; + uint len; + uint first = 1; + + while (1) + { + if (!first) + { + if (isset_and(op)) + { + b->pos--; /* Remove last char (it is a space) */ + buffer_puts(b, ","); + } + else + { + buffer_puts(b, "|| "); + } + } + first = 0; + + len = get_value_length(op); + val = get_value(op+1, len); + + /* + * Not Match Show + * ------------------ + * 0 0 !0/B + * 0 1 B/B + * 1 0 0/B + * 1 1 !B/B + */ + + if ((*op & 0x3) == 0x3 || (*op & 0x3) == 0) + buffer_puts(b, "!"); + + if (*part == FLOW_TYPE_FRAGMENT && (val == 1 || val == 2 || val == 4 || val == 8)) + buffer_print(b, "%s%s", ((*op & 0x1) ? "" : "!"), fragment_val_str(val)); + else + buffer_print(b, "0x%x/0x%x", ((*op & 0x1) ? val : 0), val); + + if (isset_end(op)) + { + buffer_puts(b, "; "); + break; + } + else + { + buffer_puts(b, " "); + } + + op += 1 + len; + } +} + +static uint +net_format_flow(char *buf, uint blen, const byte *data, uint dlen, int ipv6) +{ + buffer b = { + .start = buf, + .pos = buf, + .end = buf + blen, + }; + + const byte *part = flow_first_part(data); + *buf = 0; + + if (ipv6) + buffer_puts(&b, "flow6 { "); + else + buffer_puts(&b, "flow4 { "); + + while (part) + { + buffer_print(&b, "%s ", flow_type_str(*part, ipv6)); + + switch (*part) + { + case FLOW_TYPE_DST_PREFIX: + case FLOW_TYPE_SRC_PREFIX: + net_format_flow_ip(&b, part, ipv6); + break; + case FLOW_TYPE_IP_PROTOCOL: /* == FLOW_TYPE_NEXT_HEADER */ + case FLOW_TYPE_PORT: + case FLOW_TYPE_DST_PORT: + case FLOW_TYPE_SRC_PORT: + case FLOW_TYPE_ICMP_TYPE: + case FLOW_TYPE_ICMP_CODE: + case FLOW_TYPE_PACKET_LENGTH: + case FLOW_TYPE_DSCP: + net_format_flow_num(&b, part); + break; + case FLOW_TYPE_TCP_FLAGS: + case FLOW_TYPE_FRAGMENT: + case FLOW_TYPE_LABEL: + net_format_flow_bitmask(&b, part); + break; + } + + part = flow_next_part(part, data+dlen, ipv6); + } + + buffer_puts(&b, "}"); + + if (b.pos == b.end) + { + b.pos = b.start + MIN(blen - 6, strlen(b.start)); + buffer_puts(&b, " ...}"); + } + + return b.pos - b.start; +} + +/** + * flow4_net_format - stringify flowspec data structure &net_addr_flow4 + * @buf: pre-allocated buffer for writing a stringify net address flowspec + * @blen: free allocated space in @buf + * @f: flowspec data structure &net_addr_flow4 for stringify + * + * This function writes stringified @f into @buf. The function returns number + * of written chars. If final string is too large, the string will ends the with + * ' ...}' sequence and zero-terminator. + */ +uint +flow4_net_format(char *buf, uint blen, const net_addr_flow4 *f) +{ + return net_format_flow(buf, blen, f->data, f->length - sizeof(net_addr_flow4), 0); +} + +/** + * flow6_net_format - stringify flowspec data structure &net_addr_flow6 + * @buf: pre-allocated buffer for writing a stringify net address flowspec + * @blen: free allocated space in @buf + * @f: flowspec data structure &net_addr_flow4 for stringify + * + * This function writes stringified @f into @buf. The function returns number + * of written chars. If final string is too large, the string will ends the with + * ' ...}' sequence and zero-terminator. + */ +uint +flow6_net_format(char *buf, uint blen, const net_addr_flow6 *f) +{ + return net_format_flow(buf, blen, f->data, f->length - sizeof(net_addr_flow6), 1); +} diff --git a/lib/flowspec.h b/lib/flowspec.h new file mode 100644 index 00000000..185d5a1c --- /dev/null +++ b/lib/flowspec.h @@ -0,0 +1,138 @@ +/* + * BIRD Library -- Flow specification (RFC 5575) + * + * (c) 2016 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef _BIRD_FLOWSPEC_H_ +#define _BIRD_FLOWSPEC_H_ + +#include "nest/bird.h" +#include "lib/buffer.h" +#include "lib/net.h" + + +/* Types of components in flowspec */ +enum flow_type { + FLOW_TYPE_DST_PREFIX = 1, + FLOW_TYPE_SRC_PREFIX = 2, + FLOW_TYPE_IP_PROTOCOL = 3, + FLOW_TYPE_NEXT_HEADER = 3, /* IPv6 */ + FLOW_TYPE_PORT = 4, + FLOW_TYPE_DST_PORT = 5, + FLOW_TYPE_SRC_PORT = 6, + FLOW_TYPE_ICMP_TYPE = 7, + FLOW_TYPE_ICMP_CODE = 8, + FLOW_TYPE_TCP_FLAGS = 9, + FLOW_TYPE_PACKET_LENGTH = 10, + FLOW_TYPE_DSCP = 11, /* DiffServ Code Point */ + FLOW_TYPE_FRAGMENT = 12, + FLOW_TYPE_LABEL = 13, /* IPv6 */ + FLOW_TYPE_MAX +}; + +const char *flow_type_str(enum flow_type type, int ipv6); + + +/* + * Length + */ + +uint flow_write_length(byte *data, u16 len); + +static inline u16 flow_hdr_length(const byte *data) +{ return ((*data & 0xf0) == 0xf0) ? 2 : 1; } + +static inline u16 flow_read_length(const byte *data) +{ return ((*data & 0xf0) == 0xf0) ? get_u16(data) & 0x0fff : *data; } + +static inline u16 flow4_get_length(const net_addr_flow4 *f) +{ return f->length - sizeof(net_addr_flow4); } + +static inline u16 flow6_get_length(const net_addr_flow6 *f) +{ return f->length - sizeof(net_addr_flow6); } + +static inline void flow4_set_length(net_addr_flow4 *f, u16 len) +{ f->length = sizeof(net_addr_flow4) + flow_write_length(f->data, len) + len; } + +static inline void flow6_set_length(net_addr_flow6 *f, u16 len) +{ f->length = sizeof(net_addr_flow6) + flow_write_length(f->data, len) + len; } + + +/* + * Iterators + */ + +const byte *flow4_first_part(const net_addr_flow4 *f); +const byte *flow6_first_part(const net_addr_flow6 *f); +const byte *flow4_next_part(const byte *pos, const byte *end); +const byte *flow6_next_part(const byte *pos, const byte *end); + + +/* + * Flowspec Builder + */ + +/* A data structure for keep a state of flow builder */ +struct flow_builder { + BUFFER(byte) data; + enum flow_type this_type; + enum flow_type last_type; + u16 last_op_offset; /* Position of last operator in data.data */ + int ipv6; + struct { + u16 offset; /* Beginning of a component */ + u16 length; /* Length of a component */ + } parts[FLOW_TYPE_MAX]; /* Indexing all components */ +}; + +struct flow_builder *flow_builder_init(pool *pool); +void flow_builder_clear(struct flow_builder *fb); +void flow_builder_set_type(struct flow_builder *fb, enum flow_type p); +int flow_builder4_add_pfx(struct flow_builder *fb, const net_addr_ip4 *n4); +int flow_builder6_add_pfx(struct flow_builder *fb, const net_addr_ip6 *n6, u32 offset); +int flow_builder_add_op_val(struct flow_builder *fb, byte op, u32 value); +int flow_builder_add_val_mask(struct flow_builder *fb, byte op, u32 value, u32 mask); +net_addr_flow4 *flow_builder4_finalize(struct flow_builder *fb, linpool *lpool); +net_addr_flow6 *flow_builder6_finalize(struct flow_builder *fb, linpool *lpool); + + +/* + * Validation + */ + +/* Results of validation Flow specification */ +enum flow_validated_state { + FLOW_ST_UNKNOWN_COMPONENT, + FLOW_ST_VALID, + FLOW_ST_NOT_COMPLETE, + FLOW_ST_EXCEED_MAX_PREFIX_LENGTH, + FLOW_ST_EXCEED_MAX_PREFIX_OFFSET, + FLOW_ST_EXCEED_MAX_VALUE_LENGTH, + FLOW_ST_BAD_TYPE_ORDER, + FLOW_ST_AND_BIT_SHOULD_BE_UNSET, + FLOW_ST_ZERO_BIT_SHOULD_BE_UNSED, + FLOW_ST_DEST_PREFIX_REQUIRED, + FLOW_ST_INVALID_TCP_FLAGS, + FLOW_ST_CANNOT_USE_DONT_FRAGMENT +}; + +const char *flow_validated_state_str(enum flow_validated_state code); +enum flow_validated_state flow4_validate(const byte *nlri, uint len); +enum flow_validated_state flow6_validate(const byte *nlri, uint len); +void flow_check_cf_value_length(struct flow_builder *fb, u32 expr); +void flow_check_cf_bmk_values(struct flow_builder *fb, u8 neg, u32 val, u32 mask); +void flow4_validate_cf(net_addr_flow4 *f); +void flow6_validate_cf(net_addr_flow6 *f); + + +/* + * Net Formatting + */ + +uint flow4_net_format(char *buf, uint blen, const net_addr_flow4 *f); +uint flow6_net_format(char *buf, uint blen, const net_addr_flow6 *f); + +#endif /* _BIRD_FLOWSPEC_H_ */ diff --git a/lib/flowspec_test.c b/lib/flowspec_test.c new file mode 100644 index 00000000..69bc279d --- /dev/null +++ b/lib/flowspec_test.c @@ -0,0 +1,639 @@ +/* + * BIRD Library -- Flow specification (RFC 5575) Tests + * + * (c) 2016 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include "test/birdtest.h" +#include "lib/flowspec.h" + +#define NET_ADDR_FLOW4_(what,prefix,pxlen,data_) \ + do \ + { \ + what = alloca(sizeof(net_addr_flow4) + 128); \ + *what = NET_ADDR_FLOW4(prefix, pxlen, sizeof(data_)); \ + memcpy(what->data, &(data_), sizeof(data_)); \ + } while(0) + +#define NET_ADDR_FLOW6_(what,prefix,pxlen,data_) \ + do \ + { \ + what = alloca(sizeof(net_addr_flow6) + 128); \ + *what = NET_ADDR_FLOW6(prefix, pxlen, sizeof(data_)); \ + memcpy(what->data, &(data_), sizeof(data_)); \ + } while(0) + +static int +t_read_length(void) +{ + byte data[] = { 0xcc, 0xcc, 0xcc }; + + for (uint expect = 0; expect < 0xf0; expect++) + { + *data = expect; + uint get = flow_read_length(data); + bt_assert_msg(get == expect, "Testing get length 0x%02x (get 0x%02x)", expect, get); + } + + for (uint expect = 0; expect <= 0xfff; expect++) + { + put_u16(data, expect | 0xf000); + uint get = flow_read_length(data); + bt_assert_msg(get == expect, "Testing get length 0x%03x (get 0x%03x)", expect, get); + } + + return 1; +} + +static int +t_write_length(void) +{ + byte data[] = { 0xcc, 0xcc, 0xcc }; + + for (uint expect = 0; expect <= 0xfff; expect++) + { + uint offset = flow_write_length(data, expect); + + uint set = (expect < 0xf0) ? *data : (get_u16(data) & 0x0fff); + bt_assert_msg(set == expect, "Testing set length 0x%03x (set 0x%03x)", expect, set); + bt_assert(offset == (expect < 0xf0 ? 1 : 2)); + } + + return 1; +} + +static int +t_first_part(void) +{ + net_addr_flow4 *f; + NET_ADDR_FLOW4_(f, ip4_build(10,0,0,1), 24, ((byte[]) { 0x00, 0x00, 0xab })); + + const byte const *under240 = &f->data[1]; + const byte const *above240 = &f->data[2]; + + /* Case 0x00 0x00 */ + bt_assert(flow4_first_part(f) == NULL); + + /* Case 0x01 0x00 */ + f->data[0] = 0x01; + bt_assert(flow4_first_part(f) == under240); + + /* Case 0xef 0x00 */ + f->data[0] = 0xef; + bt_assert(flow4_first_part(f) == under240); + + /* Case 0xf0 0x00 */ + f->data[0] = 0xf0; + bt_assert(flow4_first_part(f) == NULL); + + /* Case 0xf0 0x01 */ + f->data[1] = 0x01; + bt_assert(flow4_first_part(f) == above240); + + /* Case 0xff 0xff */ + f->data[0] = 0xff; + f->data[1] = 0xff; + bt_assert(flow4_first_part(f) == above240); + + return 1; +} + +static int +t_iterators4(void) +{ + net_addr_flow4 *f; + NET_ADDR_FLOW4_(f, ip4_build(5,6,7,0), 24, ((byte[]) { + 25, /* Length */ + FLOW_TYPE_DST_PREFIX, 24, 5, 6, 7, + FLOW_TYPE_SRC_PREFIX, 32, 10, 11, 12, 13, + FLOW_TYPE_IP_PROTOCOL, 0x81, 0x06, + FLOW_TYPE_PORT, 0x03, 0x89, 0x45, 0x8b, 0x91, 0x1f, 0x90, + FLOW_TYPE_TCP_FLAGS, 0x80, 0x55, + })); + + const byte *start = f->data; + const byte *p1_dst_pfx = &f->data[1]; + const byte *p2_src_pfx = &f->data[6]; + const byte *p3_ip_proto = &f->data[12]; + const byte *p4_port = &f->data[15]; + const byte *p5_tcp_flags = &f->data[23]; + const byte *end = &f->data[25]; + + bt_assert(flow_read_length(f->data) == (end-start)); + bt_assert(flow4_first_part(f) == p1_dst_pfx); + + bt_assert(flow4_next_part(p1_dst_pfx, end) == p2_src_pfx); + bt_assert(flow4_next_part(p2_src_pfx, end) == p3_ip_proto); + bt_assert(flow4_next_part(p3_ip_proto, end) == p4_port); + bt_assert(flow4_next_part(p4_port, end) == p5_tcp_flags); + bt_assert(flow4_next_part(p5_tcp_flags, end) == NULL); + + return 1; +} + +static int +t_iterators6(void) +{ + net_addr_flow6 *f; + NET_ADDR_FLOW6_(f, ip6_build(0,0,0x12345678,0x9a000000), 64, ((byte[]) { + 26, /* Length */ + FLOW_TYPE_DST_PREFIX, 0x68, 0x40, 0x12, 0x34, 0x56, 0x78, 0x9a, + FLOW_TYPE_SRC_PREFIX, 0x08, 0x0, 0xc0, + FLOW_TYPE_NEXT_HEADER, 0x81, 0x06, + FLOW_TYPE_PORT, 0x03, 0x89, 0x45, 0x8b, 0x91, 0x1f, 0x90, + FLOW_TYPE_LABEL, 0x80, 0x55, + })); + + const byte *start = f->data; + const byte *p1_dst_pfx = &f->data[1]; + const byte *p2_src_pfx = &f->data[9]; + const byte *p3_next_header = &f->data[13]; + const byte *p4_port = &f->data[16]; + const byte *p5_label = &f->data[24]; + const byte *end = &f->data[26]; + + bt_assert(flow_read_length(f->data) == (end-start)); + bt_assert(flow6_first_part(f) == p1_dst_pfx); + + bt_assert(flow6_next_part(p1_dst_pfx, end) == p2_src_pfx); + bt_assert(flow6_next_part(p2_src_pfx, end) == p3_next_header); + bt_assert(flow6_next_part(p3_next_header, end) == p4_port); + bt_assert(flow6_next_part(p4_port, end) == p5_label); + bt_assert(flow6_next_part(p5_label, end) == NULL); + + return 1; +} + +static int +t_validation4(void) +{ + enum flow_validated_state res; + + byte nlri1[] = { + FLOW_TYPE_DST_PREFIX, 24, 5, 6, 7, + FLOW_TYPE_SRC_PREFIX, 32, 10, 11, 12, 13, + FLOW_TYPE_IP_PROTOCOL, 0x81, 0x06, + FLOW_TYPE_PORT, 0x03, 0x89, 0x45, 0x8b, 0x91, 0x1f, 0x90, + FLOW_TYPE_TCP_FLAGS, 0x80, 0x55, + }; + + /* Isn't included destination prefix */ + res = flow4_validate(nlri1, 0); + bt_assert(res == FLOW_ST_DEST_PREFIX_REQUIRED); + res = flow4_validate(&nlri1[5], sizeof(nlri1)-5); + bt_assert(res == FLOW_ST_DEST_PREFIX_REQUIRED); + + /* Valid / Not Complete testing */ + uint valid_sizes[] = {5, 11, 14, 22, 25, 0}; + uint valid_idx = 0; + for (uint size = 1; size <= sizeof(nlri1); size++) + { + res = flow4_validate(nlri1, size); + bt_debug("size %u, result: %s\n", size, flow_validated_state_str(res)); + if (size == valid_sizes[valid_idx]) + { + valid_idx++; + bt_assert(res == FLOW_ST_VALID); + } + else + { + bt_assert(res == FLOW_ST_NOT_COMPLETE); + } + } + + /* Misc err tests */ + + struct tset { + enum flow_validated_state expect; + char *description; + u16 size; + byte *nlri; + }; + +#define TS(type, msg, data) ((struct tset) {type, msg, sizeof(data), (data)}) + struct tset tset[] = { + TS( + FLOW_ST_EXCEED_MAX_PREFIX_LENGTH, + "33-length IPv4 prefix", + ((byte []) { + FLOW_TYPE_DST_PREFIX, 33, 5, 6, 7, 8, 9 + }) + ), + TS( + FLOW_ST_BAD_TYPE_ORDER, + "Bad flowspec component type order", + ((byte []) { + FLOW_TYPE_SRC_PREFIX, 32, 10, 11, 12, 13, + FLOW_TYPE_DST_PREFIX, 24, 5, 6, 7, + }) + ), + TS( + FLOW_ST_BAD_TYPE_ORDER, + "Doubled destination prefix component", + ((byte []) { + FLOW_TYPE_DST_PREFIX, 24, 5, 6, 7, + FLOW_TYPE_DST_PREFIX, 24, 5, 6, 7, + }) + ), + TS( + FLOW_ST_AND_BIT_SHOULD_BE_UNSET, + "The first numeric operator has set the AND bit", + ((byte []) { + FLOW_TYPE_PORT, 0x43, 0x89, 0x45, 0x8b, 0x91, 0x1f, 0x90, + }) + ), + TS( + FLOW_ST_ZERO_BIT_SHOULD_BE_UNSED, + "Set zero bit in operand to one", + ((byte []) { + FLOW_TYPE_IP_PROTOCOL, 0x89, 0x06, + }) + ), + TS( + FLOW_ST_UNKNOWN_COMPONENT, + "Unknown component of type number 13", + ((byte []) { + FLOW_TYPE_DST_PREFIX, 24, 5, 6, 7, + FLOW_TYPE_TCP_FLAGS, 0x80, 0x55, + 13 /*something new*/, 0x80, 0x55, + }) + ), + }; +#undef TS + + for (uint tcase = 0; tcase < ARRAY_SIZE(tset); tcase++) + { + res = flow4_validate(tset[tcase].nlri, tset[tcase].size); + bt_assert_msg(res == tset[tcase].expect, "Assertion (%s == %s) %s", flow_validated_state_str(res), flow_validated_state_str(tset[tcase].expect), tset[tcase].description); + } + + return 1; +} + +static int +t_validation6(void) +{ + enum flow_validated_state res; + + byte nlri1[] = { + FLOW_TYPE_DST_PREFIX, 103, 61, 0x01, 0x12, 0x34, 0x56, 0x78, 0x98, + FLOW_TYPE_SRC_PREFIX, 8, 0, 0xc0, + FLOW_TYPE_NEXT_HEADER, 0x81, 0x06, + FLOW_TYPE_PORT, 0x03, 0x89, 0x45, 0x8b, 0x91, 0x1f, 0x90, + FLOW_TYPE_LABEL, 0x80, 0x55, + }; + + /* Isn't included destination prefix */ + res = flow6_validate(nlri1, 0); + bt_assert(res == FLOW_ST_VALID); + + /* Valid / Not Complete testing */ + uint valid_sizes[] = {0, 9, 13, 16, 24, 27, 0}; + uint valid_idx = 0; + for (uint size = 0; size <= sizeof(nlri1); size++) + { + res = flow6_validate(nlri1, size); + bt_debug("size %u, result: %s\n", size, flow_validated_state_str(res)); + if (size == valid_sizes[valid_idx]) + { + valid_idx++; + bt_assert(res == FLOW_ST_VALID); + } + else + { + bt_assert(res == FLOW_ST_NOT_COMPLETE); + } + } + + /* Misc err tests */ + + struct tset { + enum flow_validated_state expect; + char *description; + u16 size; + byte *nlri; + }; + +#define TS(type, msg, data) ((struct tset) {type, msg, sizeof(data), (data)}) + struct tset tset[] = { + TS( + FLOW_ST_EXCEED_MAX_PREFIX_LENGTH, + "129-length IPv6 prefix", + ((byte []) { + FLOW_TYPE_DST_PREFIX, 129, 64, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12 + }) + ), + TS( + FLOW_ST_EXCEED_MAX_PREFIX_OFFSET, + "Prefix offset is higher than prefix length", + ((byte []) { + FLOW_TYPE_DST_PREFIX, 48, 64, 0x40, 0x12, 0x34 + }) + ), + TS( + FLOW_ST_BAD_TYPE_ORDER, + "Bad flowspec component type order", + ((byte []) { + FLOW_TYPE_NEXT_HEADER, 0x81, 0x06, + FLOW_TYPE_SRC_PREFIX, 8, 0, 0xc0, + }) + ), + TS( + FLOW_ST_BAD_TYPE_ORDER, + "Doubled destination prefix component", + ((byte []) { + FLOW_TYPE_DST_PREFIX, 103, 61, 0x01, 0x12, 0x34, 0x56, 0x78, 0x98, + FLOW_TYPE_DST_PREFIX, 103, 61, 0x01, 0x12, 0x34, 0x56, 0x78, 0x98, + }) + ), + TS( + FLOW_ST_AND_BIT_SHOULD_BE_UNSET, + "The first numeric operator has set the AND bit", + ((byte []) { + FLOW_TYPE_PORT, 0x43, 0x89, 0x45, 0x8b, 0x91, 0x1f, 0x90 + }) + ), + TS( + FLOW_ST_ZERO_BIT_SHOULD_BE_UNSED, + "Set zero bit in operand to one", + ((byte []) { + FLOW_TYPE_NEXT_HEADER, 0x89, 0x06 + }) + ), + TS( + FLOW_ST_VALID, + "Component of type number 13 (Label) is well-known in IPv6", + ((byte []) { + FLOW_TYPE_LABEL, 0x80, 0x55 + }) + ), + TS( + FLOW_ST_UNKNOWN_COMPONENT, + "Unknown component of type number 14", + ((byte []) { + FLOW_TYPE_LABEL, 0x80, 0x55, + 14 /*something new*/, 0x80, 0x55, + }) + ) + }; +#undef TS + + for (uint tcase = 0; tcase < ARRAY_SIZE(tset); tcase++) + { + res = flow6_validate(tset[tcase].nlri, tset[tcase].size); + bt_assert_msg(res == tset[tcase].expect, "Assertion (%s == %s) %s", flow_validated_state_str(res), flow_validated_state_str(tset[tcase].expect), tset[tcase].description); + } + + return 1; +} + + + +/* + * Builder tests + */ + +static int +t_builder4(void) +{ + resource_init(); + + struct flow_builder *fb = flow_builder_init(&root_pool); + linpool *lp = lp_new_default(&root_pool); + + /* Expectation */ + + static byte nlri[] = { + 25, + FLOW_TYPE_DST_PREFIX, 24, 5, 6, 7, + FLOW_TYPE_SRC_PREFIX, 32, 10, 11, 12, 13, + FLOW_TYPE_IP_PROTOCOL, 0x80, 0x06, + FLOW_TYPE_PORT, 0x03, 0x89, 0x45, 0x8b, 0x91, 0x1f, 0x90, + FLOW_TYPE_TCP_FLAGS, 0x80, 0x55 + }; + + net_addr_flow4 *expect; + NET_ADDR_FLOW4_(expect, ip4_build(5, 6, 7, 0), 24, nlri); + + /* Normal order */ + + net_addr_ip4 n1; + net_fill_ip4((net_addr *) &n1, ip4_build(5,6,7,0), 24); + flow_builder_set_type(fb, FLOW_TYPE_DST_PREFIX); + flow_builder4_add_pfx(fb, &n1); + + net_addr_ip4 n2; + net_fill_ip4((net_addr *) &n2, ip4_build(10,11,12,13), 32); + flow_builder_set_type(fb, FLOW_TYPE_SRC_PREFIX); + flow_builder4_add_pfx(fb, &n2); + + flow_builder_set_type(fb, FLOW_TYPE_IP_PROTOCOL); + flow_builder_add_op_val(fb, 0, 0x06); + + flow_builder_set_type(fb, FLOW_TYPE_PORT); + flow_builder_add_op_val(fb, 0x03, 0x89); + flow_builder_add_op_val(fb, 0x45, 0x8b); + flow_builder_add_op_val(fb, 0x01, 0x1f90); + + /* Try put a component twice time */ + flow_builder_set_type(fb, FLOW_TYPE_IP_PROTOCOL); + flow_builder_add_op_val(fb, 0, 0x06); + + flow_builder_set_type(fb, FLOW_TYPE_TCP_FLAGS); + flow_builder_add_op_val(fb, 0, 0x55); + + net_addr_flow4 *res = flow_builder4_finalize(fb, lp); + + bt_assert(memcmp(res, expect, expect->length) == 0); + + /* Reverse order */ + + flow_builder_clear(fb); + + flow_builder_set_type(fb, FLOW_TYPE_TCP_FLAGS); + flow_builder_add_op_val(fb, 0, 0x55); + + flow_builder_set_type(fb, FLOW_TYPE_PORT); + flow_builder_add_op_val(fb, 0x03, 0x89); + flow_builder_add_op_val(fb, 0x45, 0x8b); + flow_builder_add_op_val(fb, 0x01, 0x1f90); + + flow_builder_set_type(fb, FLOW_TYPE_IP_PROTOCOL); + flow_builder_add_op_val(fb, 0, 0x06); + + net_fill_ip4((net_addr *) &n2, ip4_build(10,11,12,13), 32); + flow_builder_set_type(fb, FLOW_TYPE_SRC_PREFIX); + flow_builder4_add_pfx(fb, &n2); + + net_fill_ip4((net_addr *) &n1, ip4_build(5,6,7,0), 24); + flow_builder_set_type(fb, FLOW_TYPE_DST_PREFIX); + flow_builder4_add_pfx(fb, &n1); + + bt_assert(memcmp(res, expect, expect->length) == 0); + + return 1; +} + +static int +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; + + /* Expectation */ + + byte nlri[] = { + 27, + FLOW_TYPE_DST_PREFIX, 103, 61, 0x01, 0x12, 0x34, 0x56, 0x78, 0x98, + FLOW_TYPE_SRC_PREFIX, 8, 0, 0xc0, + FLOW_TYPE_NEXT_HEADER, 0x80, 0x06, + FLOW_TYPE_PORT, 0x03, 0x89, 0x45, 0x8b, 0x91, 0x1f, 0x90, + FLOW_TYPE_LABEL, 0x80, 0x55, + }; + + net_addr_flow6 *expect; + NET_ADDR_FLOW6_(expect, ip6_build(0, 1, 0x12345678, 0x98000000), 103, nlri); + + /* Normal order */ + + net_fill_ip6((net_addr *) &ip, ip6_build(0, 1, 0x12345678, 0x98000000), 103); + flow_builder_set_type(fb, FLOW_TYPE_DST_PREFIX); + flow_builder6_add_pfx(fb, &ip, 61); + + /* Try put a component twice time */ + net_fill_ip6((net_addr *) &ip, ip6_build(0, 1, 0x12345678, 0x98000000), 103); + flow_builder_set_type(fb, FLOW_TYPE_DST_PREFIX); + bt_assert(flow_builder6_add_pfx(fb, &ip, 61) == 0); + + net_fill_ip6((net_addr *) &ip, ip6_build(0xc0000000,0,0,0), 8); + flow_builder_set_type(fb, FLOW_TYPE_SRC_PREFIX); + flow_builder6_add_pfx(fb, &ip, 0); + + flow_builder_set_type(fb, FLOW_TYPE_NEXT_HEADER); + flow_builder_add_op_val(fb, 0, 0x06); + + flow_builder_set_type(fb, FLOW_TYPE_PORT); + flow_builder_add_op_val(fb, 0x03, 0x89); + flow_builder_add_op_val(fb, 0x45, 0x8b); + flow_builder_add_op_val(fb, 0x01, 0x1f90); + + flow_builder_set_type(fb, FLOW_TYPE_LABEL); + flow_builder_add_op_val(fb, 0, 0x55); + + net_addr_flow6 *res = flow_builder6_finalize(fb, lp); + bt_assert(memcmp(res, expect, expect->length) == 0); + + /* Reverse order */ + + flow_builder_clear(fb); + fb->ipv6 = 1; + + flow_builder_set_type(fb, FLOW_TYPE_LABEL); + flow_builder_add_op_val(fb, 0, 0x55); + + flow_builder_set_type(fb, FLOW_TYPE_PORT); + flow_builder_add_op_val(fb, 0x03, 0x89); + flow_builder_add_op_val(fb, 0x45, 0x8b); + flow_builder_add_op_val(fb, 0x01, 0x1f90); + + flow_builder_set_type(fb, FLOW_TYPE_NEXT_HEADER); + flow_builder_add_op_val(fb, 0, 0x06); + + net_fill_ip6((net_addr *) &ip, ip6_build(0xc0000000,0,0,0), 8); + flow_builder_set_type(fb, FLOW_TYPE_SRC_PREFIX); + flow_builder6_add_pfx(fb, &ip, 0); + + net_fill_ip6((net_addr *) &ip, ip6_build(0, 1, 0x12345678, 0x98000000), 103); + flow_builder_set_type(fb, FLOW_TYPE_DST_PREFIX); + flow_builder6_add_pfx(fb, &ip, 61); + + res = flow_builder6_finalize(fb, lp); + bt_assert(memcmp(res, expect, expect->length) == 0); + + return 1; +} + +static int +t_formatting4(void) +{ + char b[1024]; + + byte nlri[] = { + 0, + FLOW_TYPE_DST_PREFIX, 0x08, 10, + FLOW_TYPE_IP_PROTOCOL, 0x81, 23, + FLOW_TYPE_DST_PORT, 0x02, 24, 0x44, 30, 0x03, 40, 0x45, 50, 0x03, 60, 0x45, 70, 0x01, 80, 0xc3, 90, + FLOW_TYPE_SRC_PORT, 0x02, 24, 0x44, 0x1e, 0x01, 0x28, 0x01, 0x32, 0x03, 0x3c, 0x45, 0x46, 0x81, 0x50, + FLOW_TYPE_ICMP_TYPE, 0x81, 0x50, + FLOW_TYPE_ICMP_CODE, 0x81, 0x5a, + FLOW_TYPE_TCP_FLAGS, 0x01, 0x03, 0xc2, 0x0c, + FLOW_TYPE_PACKET_LENGTH, 0x03, 0, 0xd5, 0xff, 0xff, + FLOW_TYPE_DSCP, 0x81, 63, + FLOW_TYPE_FRAGMENT, 0x01, 0x01, 0x82, 0x02 + }; + *nlri = (u8) sizeof(nlri); + + net_addr_flow4 *input; + NET_ADDR_FLOW4_(input, ip4_build(5, 6, 7, 0), 24, nlri); + + const char *expect = "flow4 { dst 10.0.0.0/8; proto 23; dport > 24 && < 30 || 40..50,60..70,80 && >= 90; sport > 24 && < 30 || 40,50,60..70,80; icmp type 80; icmp code 90; tcp flags 0x3/0x3,0x0/0xc; length 0..65535; dscp 63; fragment dont_fragment || !is_fragment; }"; + + bt_assert(flow4_net_format(b, sizeof(b), input) == strlen(expect)); + bt_debug(" expect: '%s',\n output: '%s'\n", expect, b); + bt_assert(strcmp(b, expect) == 0); + + return 1; +} + +static int +t_formatting6(void) +{ + char b[1024]; + + byte nlri[] = { + 0, + FLOW_TYPE_DST_PREFIX, 103, 61, 0x01, 0x12, 0x34, 0x56, 0x78, 0x98, + FLOW_TYPE_SRC_PREFIX, 8, 0, 0xc0, + FLOW_TYPE_NEXT_HEADER, 0x81, 0x06, + FLOW_TYPE_PORT, 0x03, 20, 0x45, 40, 0x91, 0x01, 0x11, + FLOW_TYPE_LABEL, 0xa0, 0x12, 0x34, 0x56, 0x78, + }; + *nlri = (u8) sizeof(nlri); + + net_addr_flow6 *input; + NET_ADDR_FLOW6_(input, ip6_build(0, 1, 0x12345678, 0x98000000), 103, nlri); + + const char *expect = "flow6 { dst ::1:1234:5678:9800:0/103 offset 61; src c000::/8; next header 6; port 20..40,273; label !0x0/0x12345678; }"; + + bt_assert(flow6_net_format(b, sizeof(b), input) == strlen(expect)); + bt_debug(" expect: '%s',\n output: '%s'\n", expect, b); + bt_assert(strcmp(b, expect) == 0); + + return 1; +} + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + bt_test_suite(t_read_length, "Testing get NLRI length"); + bt_test_suite(t_write_length, "Testing set NLRI length"); + bt_test_suite(t_first_part, "Searching first part in net_addr_flow"); + bt_test_suite(t_iterators4, "Testing iterators (IPv4)"); + bt_test_suite(t_iterators6, "Testing iterators (IPv6)"); + bt_test_suite(t_validation4, "Testing validation (IPv4)"); + bt_test_suite(t_validation6, "Testing validation (IPv6)"); + bt_test_suite(t_builder4, "Inserting components into existing Flow Specification (IPv4)"); + bt_test_suite(t_builder6, "Inserting components into existing Flow Specification (IPv6)"); + bt_test_suite(t_formatting4, "Formatting Flow Specification (IPv4) into text representation"); + bt_test_suite(t_formatting6, "Formatting Flow Specification (IPv6) into text representation"); + + return bt_exit_value(); +} @@ -194,4 +194,40 @@ #define HASH_WALK_FILTER_END } while (0) + +static inline void +mem_hash_init(u64 *h) +{ + *h = 0x001047d54778bcafULL; +} + +static inline void +mem_hash_mix(u64 *h, void *p, uint s) +{ + const u64 multiplier = 0xb38bc09a61202731ULL; + const char *pp = p; + uint i; + + for (i=0; i<s/4; i++) + *h = *h * multiplier + ((const u32 *)pp)[i]; + + for (i=s & ~0x3; i<s; i++) + *h = *h * multiplier + pp[i]; +} + +static inline uint +mem_hash_value(u64 *h) +{ + return ((*h >> 32) ^ (*h & 0xffffffff)); +} + +static inline uint +mem_hash(void *p, uint s) +{ + static u64 h; + mem_hash_init(&h); + mem_hash_mix(&h, p, s); + return mem_hash_value(&h); +} + #endif diff --git a/lib/hash_test.c b/lib/hash_test.c new file mode 100644 index 00000000..59beb7c0 --- /dev/null +++ b/lib/hash_test.c @@ -0,0 +1,305 @@ +/* + * BIRD Library -- Hash Tests + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#undef LOCAL_DEBUG + +#include "test/birdtest.h" + +#include "lib/hash.h" + +struct test_node { + struct test_node *next; /* Hash chain */ + u32 key; +}; + +#define TEST_KEY(n) n->key +#define TEST_NEXT(n) n->next +#define TEST_EQ(n1,n2) n1 == n2 +#define TEST_FN(n) (n) ^ u32_hash((n)) +#define TEST_ORDER 13 +#define TEST_PARAMS /TEST_ORDER, *2, 2, 2, TEST_ORDER, 20 +#define TEST_REHASH test_rehash + +HASH_DEFINE_REHASH_FN(TEST, struct test_node); + +HASH(struct test_node) hash; +struct pool *my_pool; + +#define MAX_NUM (1 << TEST_ORDER) + +struct test_node nodes[MAX_NUM]; + +static void +print_rate_of_fulfilment(void) +{ + int i; + int num_stacked_items = 0; + + for (i = 0; i < MAX_NUM; i++) + if (!hash.data[i]) + num_stacked_items++; + + double percent_stacked_items = ((double)num_stacked_items/(double)MAX_NUM)*100.; + bt_debug("%d (%.2f %%) chained of %d hashes \n", num_stacked_items, percent_stacked_items, MAX_NUM); +} + +#ifdef LOCAL_DEBUG +static void +dump_nodes(void) +{ + int i; + for (i = 0; i < MAX_NUM; i++) + bt_debug("nodes[%3d] is at address %14p has .key %3d, .next %14p \n", i, &nodes[i], nodes[i].key, nodes[i].next); +} +#endif + +static void +init_hash_(uint order) +{ + resource_init(); + my_pool = rp_new(&root_pool, "Test pool"); + + HASH_INIT(hash, my_pool, order); + + int i; + for (i = 0; i < MAX_NUM; i++) + { + nodes[i].key = i; + nodes[i].next = NULL; + } + + bt_debug("MAX_NUM %d \n", MAX_NUM); +} + +static void +init_hash(void) +{ + init_hash_(TEST_ORDER); +} + +static void +validate_filled_hash(void) +{ + int i; + struct test_node *node; + for (i = 0; i < MAX_NUM; i++) + { + node = HASH_FIND(hash, TEST, nodes[i].key); + bt_assert_msg(node->key == nodes[i].key, "Hash should be filled, to find (%p) the node[%d] (%p) with .key = %u, .next %p", node, i, &nodes[i], nodes[i].key, nodes[i].next); + } + + print_rate_of_fulfilment(); +} + +static void +validate_empty_hash(void) +{ + int i; + struct test_node *node; + for (i = 0; i < MAX_NUM; i++) + { + node = HASH_FIND(hash, TEST, nodes[i].key); + bt_assert_msg(node == NULL, "Hash should be empty, to find (%p) the node[%d] (%p) with .key %u, .next %p", node, i, &nodes[i], nodes[i].key, nodes[i].next); + } +} + +static void +fill_hash(void) +{ + int i; + struct test_node *node; + + for (i = 0; i < MAX_NUM; i++) + { + nodes[i].key = i; + node = &nodes[i]; + HASH_INSERT(hash, TEST, node); + } +} + +static int +t_insert_find(void) +{ + init_hash(); + fill_hash(); + validate_filled_hash(); + + return 1; +} + +static int +t_insert_find_random(void) +{ + init_hash(); + + int i; + struct test_node *node; + for (i = 0; i < MAX_NUM; i++) + { + nodes[i].key = bt_random(); + node = &nodes[i]; + HASH_INSERT(hash, TEST, node); + } + + validate_filled_hash(); + + return 1; +} + +static int +t_insert2_find(void) +{ + init_hash_(1); + + int i; + struct test_node *node; + for (i = 0; i < MAX_NUM; i++) + { + nodes[i].key = i; + node = &nodes[i]; + HASH_INSERT2(hash, TEST, my_pool, node); + } + bt_assert_msg(hash.order != 1, "The hash should auto-resize from order 2^1. The order of the hash is 2^%u.", hash.order); + + validate_filled_hash(); + + return 1; +} + +static int +t_walk(void) +{ + init_hash(); + fill_hash(); + + uint i; + uint check[MAX_NUM]; + for (i = 0; i < MAX_NUM; i++) + check[i] = 0; + + HASH_WALK(hash, next, n) + { + check[n->key]++; + } + HASH_WALK_END; + + for (i = 0; i < MAX_NUM; i++) + bt_assert(check[i] == 1); + + return 1; +} + +static int +t_walk_delsafe_delete(void) +{ + init_hash(); + fill_hash(); + + HASH_WALK_DELSAFE(hash, next, n) + { + HASH_DELETE(hash, TEST, n->key); + } + HASH_WALK_DELSAFE_END; + + validate_empty_hash(); + + return 1; +} + +static int +t_walk_delsafe_remove(void) +{ + init_hash(); + fill_hash(); + + HASH_WALK_DELSAFE(hash, next, n) + { + HASH_REMOVE(hash, TEST, n); + } + HASH_WALK_DELSAFE_END; + + validate_empty_hash(); + + return 1; +} + +static int +t_walk_delsafe_delete2(void) +{ + init_hash(); + fill_hash(); + + HASH_WALK_DELSAFE(hash, next, n) + { + HASH_DELETE2(hash, TEST, my_pool, n->key); + } + HASH_WALK_DELSAFE_END; + + validate_empty_hash(); + + return 1; +} + +static int +t_walk_delsafe_remove2(void) +{ + init_hash(); + fill_hash(); + + HASH_WALK_DELSAFE(hash, next, n) + { + HASH_REMOVE2(hash, TEST, my_pool, n); + } + HASH_WALK_DELSAFE_END; + + validate_empty_hash(); + + return 1; +} + +static int +t_walk_filter(void) +{ + init_hash(); + fill_hash(); + + uint i; + uint check[MAX_NUM]; + for (i = 0; i < MAX_NUM; i++) + check[i] = 0; + + HASH_WALK_FILTER(hash, next, n, m) + { + bt_assert(n == *m); + check[n->key]++; + } + HASH_WALK_FILTER_END; + + for (i = 0; i < MAX_NUM; i++) + bt_assert(check[i] == 1); + + return 1; +} + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + 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"); + bt_test_suite(t_insert2_find, "HASH_INSERT2 and HASH_FIND. HASH_INSERT2 is HASH_INSERT and a smart auto-resize function"); + bt_test_suite(t_walk, "HASH_WALK"); + bt_test_suite(t_walk_delsafe_delete, "HASH_WALK_DELSAFE and HASH_DELETE"); + bt_test_suite(t_walk_delsafe_delete2, "HASH_WALK_DELSAFE and HASH_DELETE2. HASH_DELETE2 is HASH_DELETE and smart auto-resize function"); + bt_test_suite(t_walk_delsafe_remove, "HASH_WALK_DELSAFE and HASH_REMOVE"); + bt_test_suite(t_walk_delsafe_remove2, "HASH_WALK_DELSAFE and HASH_REMOVE2. HASH_REMOVE2 is HASH_REMOVE and smart auto-resize function"); + bt_test_suite(t_walk_filter, "HASH_WALK_FILTER"); + + return bt_exit_value(); +} diff --git a/lib/heap_test.c b/lib/heap_test.c new file mode 100644 index 00000000..c04a0450 --- /dev/null +++ b/lib/heap_test.c @@ -0,0 +1,186 @@ +/* + * BIRD Library -- Universal Heap Macros Tests + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include "test/birdtest.h" +#include "sysdep/config.h" +#include "lib/heap.h" + +#define MAX_NUM 1000 +#define SPECIAL_KEY -3213 + +#define MY_CMP(x, y) ((x) < (y)) + +#define MY_HEAP_SWAP(heap,a,b,t) \ + do { \ + bt_debug("swap(%u %u) ", a, b); \ + HEAP_SWAP(heap,a,b,t); \ + } while(0) + +static int heap[MAX_NUM+1]; +static uint num; + +/* + * A valid heap must follow these rules: + * - `num >= 0` + * - `heap[i] >= heap[i / 2]` for each `i` in `[2, num]` + */ +static int +is_heap_valid(int heap[], uint num) +{ + uint i; + + if (num > MAX_NUM) + return 0; + + for (i = 2; i <= num; i++) + if (heap[i] < heap[i / 2]) + return 0; + + return 1; +} + +static void +show_heap(void) +{ + uint i; + bt_debug("\n"); + bt_debug("numbers %u; ", num); + for (i = 0; i <= num; i++) + bt_debug("%d ", heap[i]); + bt_debug(is_heap_valid(heap, num) ? "OK" : "NON-VALID HEAP!"); + bt_debug("\n"); +} + +static void +init_heap(void) +{ + uint i; + num = 0; + heap[0] = SPECIAL_KEY; /* heap[0] should be unused */ + for (i = 1; i <= MAX_NUM; i++) + heap[i] = 0; +} + +static int +t_heap_insert(void) +{ + uint i; + + init_heap(); + + for (i = MAX_NUM; i >= 1; i--) + { + bt_debug("ins %u at pos %u ", i, MAX_NUM - i); + heap[MAX_NUM - i + 1] = i; + HEAP_INSERT(heap, ++num, int, MY_CMP, MY_HEAP_SWAP); + show_heap(); + bt_assert(is_heap_valid(heap, num)); + } + + return 1; +} + +static int +t_heap_increase_decrease(void) +{ + uint i; + + t_heap_insert(); + + for (i = 1; i <= MAX_NUM; i++) + { + if ((int)i > heap[i]) + { + bt_debug("inc %u ", i); + heap[i] = i; + HEAP_INCREASE(heap, num, int, MY_CMP, MY_HEAP_SWAP, i); + } + else if ((int)i < heap[i]) + { + bt_debug("dec %u ", i); + heap[i] = i; + HEAP_INCREASE(heap, num, int, MY_CMP, MY_HEAP_SWAP, i); + } + show_heap(); + bt_assert(is_heap_valid(heap, num)); + } + + return 1; +} + +static int +t_heap_delete(void) +{ + uint i; + + t_heap_insert(); + + for (i = 1; i <= num; i++) + { + bt_debug("del at pos %u ", i); + HEAP_DELETE(heap, num, int, MY_CMP, MY_HEAP_SWAP, i); + show_heap(); + bt_assert(is_heap_valid(heap, num)); + } + + return 1; +} + +static int +t_heap_0(void) +{ + init_heap(); + t_heap_insert(); + t_heap_increase_decrease(); + t_heap_delete(); + + return heap[0] == SPECIAL_KEY; +} + +static int +t_heap_insert_random(void) +{ + int i, j; + int expected[MAX_NUM+1]; + + init_heap(); + + for (i = 1; i <= MAX_NUM; i++) + { + heap[i] = expected[i] = bt_random(); + HEAP_INSERT(heap, ++num, int, MY_CMP, MY_HEAP_SWAP); + show_heap(); + bt_assert(is_heap_valid(heap, num)); + } + + for (i = 1; i <= MAX_NUM; i++) + for (j = 1; j <= MAX_NUM; j++) + if(expected[i] == heap[j]) + break; + else if (j == MAX_NUM) + { + show_heap(); + bt_abort_msg("Did not find a number %d in heap.", expected[i]); + } + + return 1; +} + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + bt_test_suite(t_heap_insert, "Inserting a descending sequence of numbers (the worst case)"); + bt_test_suite(t_heap_insert_random, "Inserting pseudo-random numbers"); + bt_test_suite(t_heap_increase_decrease, "Increasing/Decreasing"); + bt_test_suite(t_heap_delete, "Deleting"); + bt_test_suite(t_heap_0, "Is a heap[0] really unused?"); + + return bt_exit_value(); +} diff --git a/lib/idm.c b/lib/idm.c new file mode 100644 index 00000000..66e311c6 --- /dev/null +++ b/lib/idm.c @@ -0,0 +1,76 @@ +/* + * BIRD Library -- ID Map + * + * (c) 2013--2015 Ondrej Zajicek <santiago@crfreenet.org> + * (c) 2013--2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include <stdlib.h> + +#include "nest/bird.h" +#include "lib/idm.h" +#include "lib/resource.h" +#include "lib/string.h" + + +void +idm_init(struct idm *m, pool *p, uint size) +{ + m->pos = 0; + m->used = 1; + m->size = size; + m->data = mb_allocz(p, m->size * sizeof(u32)); + + /* ID 0 is reserved */ + m->data[0] = 1; +} + +static inline int u32_cto(uint x) { return ffs(~x) - 1; } + +u32 +idm_alloc(struct idm *m) +{ + uint i, j; + + for (i = m->pos; i < m->size; i++) + if (m->data[i] != 0xffffffff) + goto found; + + /* If we are at least 7/8 full, expand */ + if (m->used > (m->size * 28)) + { + m->size *= 2; + m->data = mb_realloc(m->data, m->size * sizeof(u32)); + memset(m->data + i, 0, (m->size - i) * sizeof(u32)); + goto found; + } + + for (i = 0; i < m->pos; i++) + if (m->data[i] != 0xffffffff) + goto found; + + ASSERT(0); + +found: + ASSERT(i < 0x8000000); + + m->pos = i; + j = u32_cto(m->data[i]); + + m->data[i] |= (1 << j); + m->used++; + return 32 * i + j; +} + +void +idm_free(struct idm *m, u32 id) +{ + uint i = id / 32; + uint j = id % 32; + + ASSERT((i < m->size) && (m->data[i] & (1 << j))); + m->data[i] &= ~(1 << j); + m->used--; +} diff --git a/lib/idm.h b/lib/idm.h new file mode 100644 index 00000000..e3380cce --- /dev/null +++ b/lib/idm.h @@ -0,0 +1,25 @@ +/* + * BIRD Library -- ID Map + * + * (c) 2013--2015 Ondrej Zajicek <santiago@crfreenet.org> + * (c) 2013--2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef _BIRD_IDM_H_ +#define _BIRD_IDM_H_ + +struct idm +{ + u32 *data; + u32 pos; + u32 used; + u32 size; +}; + +void idm_init(struct idm *m, pool *p, uint size); +u32 idm_alloc(struct idm *m); +void idm_free(struct idm *m, u32 id); + +#endif @@ -58,7 +58,7 @@ ip6_mkmask(uint n) return a; } -int +uint ip6_masklen(ip6_addr *a) { int i, j, n; @@ -67,12 +67,12 @@ ip6_masklen(ip6_addr *a) if (a->addr[i] != ~0U) { j = u32_masklen(a->addr[i]); - if (j < 0) + if (j == 255) return j; n += j; while (++i < 4) if (a->addr[i]) - return -1; + return 255; break; } @@ -306,7 +306,7 @@ ip6_pton(const char *a, ip6_addr *o) if (*a == ':' && a[1]) a++; - else if (*a == '.' && (i == 6 || i < 6 && hfil >= 0)) + else if (*a == '.' && (i == 6 || (i < 6 && hfil >= 0))) { /* Embedded IPv4 address */ ip4_addr x; if (!ip4_pton(start, &x)) @@ -9,7 +9,7 @@ #ifndef _BIRD_IP_H_ #define _BIRD_IP_H_ -#include "lib/endian.h" +#include "sysdep/unix/endian.h" #include "lib/string.h" #include "lib/bitops.h" #include "lib/unaligned.h" @@ -31,6 +31,13 @@ #define IP4_NONE _MI4(0) #define IP6_NONE _MI6(0,0,0,0) +#define IP4_MAX_PREFIX_LENGTH 32 +#define IP6_MAX_PREFIX_LENGTH 128 + +#define IP4_MAX_TEXT_LENGTH 15 /* "255.255.255.255" */ +#define IP6_MAX_TEXT_LENGTH 39 /* "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" */ +#define IPA_MAX_TEXT_LENGTH 39 + #define IP4_MIN_MTU 576 #define IP6_MIN_MTU 1280 @@ -41,19 +48,6 @@ #define UDP_HEADER_LENGTH 8 -#ifdef IPV6 -#define MAX_PREFIX_LENGTH 128 -#define BITS_PER_IP_ADDRESS 128 -#define STD_ADDRESS_P_LENGTH 39 -#define SIZE_OF_IP_HEADER 40 -#else -#define MAX_PREFIX_LENGTH 32 -#define BITS_PER_IP_ADDRESS 32 -#define STD_ADDRESS_P_LENGTH 15 -#define SIZE_OF_IP_HEADER 24 -#endif - - #ifdef DEBUGGING typedef struct ip4_addr { @@ -84,8 +78,6 @@ typedef struct ip6_addr { #define _I3(a) ((a).addr[3]) -#ifdef IPV6 - /* Structure ip_addr may contain both IPv4 and IPv6 addresses */ typedef ip6_addr ip_addr; #define IPA_NONE IP6_NONE @@ -99,24 +91,10 @@ typedef ip6_addr ip_addr; #define ipa_to_u32(x) ip4_to_u32(ipa_to_ip4(x)) #define ipa_is_ip4(a) ip6_is_v4mapped(a) +#define ipa_is_ip6(a) (! ip6_is_v4mapped(a)) -#else - -/* Provisionary ip_addr definition same as ip4_addr */ -typedef ip4_addr ip_addr; -#define IPA_NONE IP4_NONE - -#define ipa_from_ip4(x) x -#define ipa_from_ip6(x) IPA_NONE -#define ipa_from_u32(x) ipa_from_ip4(ip4_from_u32(x)) - -#define ipa_to_ip4(x) x -#define ipa_to_ip6(x) IP6_NONE -#define ipa_to_u32(x) ip4_to_u32(ipa_to_ip4(x)) - -#define ipa_is_ip4(a) 1 - -#endif +#define IPA_NONE4 ipa_from_ip4(IP4_NONE) +#define IPA_NONE6 ipa_from_ip6(IP6_NONE) /* @@ -181,7 +159,6 @@ static inline ip6_addr ip6_not(ip6_addr a) { return _MI6(~_I0(a), ~_I1(a), ~_I2(a), ~_I3(a)); } -#ifdef IPV6 #define ipa_equal(x,y) ip6_equal(x,y) #define ipa_zero(x) ip6_zero(x) #define ipa_nonzero(x) ip6_nonzero(x) @@ -189,19 +166,8 @@ static inline ip6_addr ip6_not(ip6_addr a) #define ipa_or(x,y) ip6_or(x,y) #define ipa_xor(x,y) ip6_xor(x,y) #define ipa_not(x) ip6_not(x) -#else -#define ipa_equal(x,y) ip4_equal(x,y) -#define ipa_zero(x) ip4_zero(x) -#define ipa_nonzero(x) ip4_nonzero(x) -#define ipa_and(x,y) ip4_and(x,y) -#define ipa_or(x,y) ip4_or(x,y) -#define ipa_xor(x,y) ip4_xor(x,y) -#define ipa_not(x) ip4_not(x) -#endif - -#ifdef IPV6 /* * A zero address is either a token for invalid/unused, or the prefix of default * routes. These functions should be used in the second case, where both IPv4 @@ -214,26 +180,12 @@ static inline int ipa_zero2(ip_addr a) static inline int ipa_nonzero2(ip_addr a) { return _I0(a) || _I1(a) || ((_I2(a) != 0) && (_I2(a) != 0xffff)) || _I3(a); } -#else -#define ipa_zero2(x) ip4_zero(x) -#define ipa_nonzero2(x) ip4_nonzero(x) -#endif - /* * Hash and compare functions */ -static inline uint ip4_hash(ip4_addr a) -{ - /* Returns a 16-bit value */ - u32 x = _I(a); - x ^= x >> 16; - x ^= x << 10; - return x & 0xffff; -} - -static inline u32 ip4_hash32(ip4_addr a) +static inline u32 ip4_hash(ip4_addr a) { /* Returns a 32-bit value, although low-order bits are not mixed */ u32 x = _I(a); @@ -242,14 +194,7 @@ static inline u32 ip4_hash32(ip4_addr a) return x; } -static inline uint ip6_hash(ip6_addr a) -{ - /* Returns a 16-bit hash key */ - u32 x = _I0(a) ^ _I1(a) ^ _I2(a) ^ _I3(a); - return (x ^ (x >> 16) ^ (x >> 8)) & 0xffff; -} - -static inline u32 ip6_hash32(ip6_addr a) +static inline u32 ip6_hash(ip6_addr a) { /* Returns a 32-bit hash key, although low-order bits are not mixed */ u32 x = _I0(a) ^ _I1(a) ^ _I2(a) ^ _I3(a); @@ -261,16 +206,8 @@ static inline int ip4_compare(ip4_addr a, ip4_addr b) int ip6_compare(ip6_addr a, ip6_addr b); - -#ifdef IPV6 #define ipa_hash(x) ip6_hash(x) -#define ipa_hash32(x) ip6_hash32(x) #define ipa_compare(x,y) ip6_compare(x,y) -#else -#define ipa_hash(x) ip4_hash(x) -#define ipa_hash32(x) ip4_hash32(x) -#define ipa_compare(x,y) ip4_compare(x,y) -#endif /* @@ -301,14 +238,10 @@ static inline int ip6_is_link_local(ip6_addr a) static inline int ip6_is_v4mapped(ip6_addr a) { return _I0(a) == 0 && _I1(a) == 0 && _I2(a) == 0xffff; } -#ifdef IPV6 #define ipa_classify(x) ip6_classify(&(x)) #define ipa_is_link_local(x) ip6_is_link_local(x) -#else -#define ipa_classify(x) ip4_classify(x) -#define ipa_is_link_local(x) 0 -#endif +/* XXXX remove */ static inline int ipa_classify_net(ip_addr a) { return ipa_zero2(a) ? (IADDR_HOST | SCOPE_UNIVERSE) : ipa_classify(a); } @@ -320,11 +253,11 @@ static inline int ipa_classify_net(ip_addr a) static inline ip4_addr ip4_mkmask(uint n) { return _MI4(u32_mkmask(n)); } -static inline int ip4_masklen(ip4_addr a) +static inline uint ip4_masklen(ip4_addr a) { return u32_masklen(_I(a)); } ip6_addr ip6_mkmask(uint n); -int ip6_masklen(ip6_addr *a); +uint ip6_masklen(ip6_addr *a); /* ipX_pxlen() requires that x != y */ static inline uint ip4_pxlen(ip4_addr a, ip4_addr b) @@ -346,6 +279,18 @@ static inline u32 ip4_getbit(ip4_addr a, uint pos) static inline u32 ip6_getbit(ip6_addr a, uint pos) { return a.addr[pos / 32] & (0x80000000 >> (pos % 32)); } +static inline u32 ip4_setbit(ip4_addr *a, uint pos) +{ return _I(*a) |= (0x80000000 >> pos); } + +static inline u32 ip6_setbit(ip6_addr *a, uint pos) +{ return a->addr[pos / 32] |= (0x80000000 >> (pos % 32)); } + +static inline u32 ip4_clrbit(ip4_addr *a, uint pos) +{ return _I(*a) &= ~(0x80000000 >> pos); } + +static inline u32 ip6_clrbit(ip6_addr *a, uint pos) +{ return a->addr[pos / 32] &= ~(0x80000000 >> (pos % 32)); } + static inline ip4_addr ip4_opposite_m1(ip4_addr a) { return _MI4(_I(a) ^ 1); } @@ -360,21 +305,8 @@ static inline ip6_addr ip6_opposite_m2(ip6_addr a) ip4_addr ip4_class_mask(ip4_addr ad); -#ifdef IPV6 -#define ipa_mkmask(x) ip6_mkmask(x) -#define ipa_masklen(x) ip6_masklen(&x) -#define ipa_pxlen(x,y) ip6_pxlen(x,y) -#define ipa_getbit(x,n) ip6_getbit(x,n) #define ipa_opposite_m1(x) ip6_opposite_m1(x) #define ipa_opposite_m2(x) ip6_opposite_m2(x) -#else -#define ipa_mkmask(x) ip4_mkmask(x) -#define ipa_masklen(x) ip4_masklen(x) -#define ipa_pxlen(x,y) ip4_pxlen(x,y) -#define ipa_getbit(x,n) ip4_getbit(x,n) -#define ipa_opposite_m1(x) ip4_opposite_m1(x) -#define ipa_opposite_m2(x) ip4_opposite_m2(x) -#endif /* @@ -393,14 +325,33 @@ static inline ip6_addr ip6_hton(ip6_addr a) static inline ip6_addr ip6_ntoh(ip6_addr a) { return _MI6(ntohl(_I0(a)), ntohl(_I1(a)), ntohl(_I2(a)), ntohl(_I3(a))); } -#ifdef IPV6 -#define ipa_hton(x) x = ip6_hton(x) -#define ipa_ntoh(x) x = ip6_ntoh(x) -#else -#define ipa_hton(x) x = ip4_hton(x) -#define ipa_ntoh(x) x = ip4_ntoh(x) -#endif +#define MPLS_MAX_LABEL_STACK 8 +typedef struct mpls_label_stack { + uint len; + u32 stack[MPLS_MAX_LABEL_STACK]; +} mpls_label_stack; + +static inline int +mpls_get(const char *buf, int buflen, u32 *stack) +{ + for (int i=0; (i<MPLS_MAX_LABEL_STACK) && (i*4+3 < buflen); i++) + { + u32 s = get_u32(buf + i*4); + stack[i] = s >> 12; + if (s & 0x100) + return i+1; + } + return -1; +} + +static inline int +mpls_put(char *buf, int len, u32 *stack) +{ + for (int i=0; i<len; i++) + put_u32(buf + i*4, stack[i] << 12 | (i+1 == len ? 0x100 : 0)); + return len*4; +} /* * Unaligned data access (in network order) @@ -431,15 +382,6 @@ static inline void * put_ip6(void *buf, ip6_addr a) return buf+16; } -// XXXX these functions must be redesigned or removed -#ifdef IPV6 -#define get_ipa(x) get_ip6(x) -#define put_ipa(x,y) put_ip6(x,y) -#else -#define get_ipa(x) get_ip4(x) -#define put_ipa(x,y) put_ip4(x,y) -#endif - /* * Binary/text form conversions @@ -457,34 +399,11 @@ static inline char * ip6_ntox(ip6_addr a, char *b) int ip4_pton(const char *a, ip4_addr *o); int ip6_pton(const char *a, ip6_addr *o); -// XXXX these functions must be redesigned or removed -#ifdef IPV6 -#define ipa_ntop(x,y) ip6_ntop(x,y) -#define ipa_ntox(x,y) ip6_ntox(x,y) -#define ipa_pton(x,y) ip6_pton(x,y) -#else -#define ipa_ntop(x,y) ip4_ntop(x,y) -#define ipa_ntox(x,y) ip4_ntox(x,y) -#define ipa_pton(x,y) ip4_pton(x,y) -#endif - /* * Miscellaneous */ -// XXXX review this - -#define ip_is_prefix(a,l) (!ipa_nonzero(ipa_and(a, ipa_not(ipa_mkmask(l))))) -#define ipa_in_net(x,n,p) (ipa_zero(ipa_and(ipa_xor((n),(x)),ipa_mkmask(p)))) -#define net_in_net(n1,l1,n2,l2) (((l1) >= (l2)) && (ipa_zero(ipa_and(ipa_xor((n1),(n2)),ipa_mkmask(l2))))) - char *ip_scope_text(uint); -struct prefix { - ip_addr addr; - uint len; -}; - - #endif diff --git a/lib/ip_test.c b/lib/ip_test.c new file mode 100644 index 00000000..fd70c957 --- /dev/null +++ b/lib/ip_test.c @@ -0,0 +1,161 @@ +/* + * BIRD Library -- IP address functions Tests + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include "test/birdtest.h" + +#include "lib/ip.h" + +#define IP4_MAX_LEN 16 + +static int +test_ipa_pton(void *out_, const void *in_, const void *expected_out_) +{ + ip_addr *out = out_; + const char *in = in_; + const ip_addr *expected_out = expected_out_; + + if (ipa_is_ip4(*expected_out)) + { + ip4_addr ip4; + bt_assert(ip4_pton(in, &ip4)); + *out = ipa_from_ip4(ip4); + } + else + { + bt_assert(ip6_pton(in, out)); + /* ip_addr == ip6_addr */ + } + + return ipa_equal(*out, *expected_out); +} + +static int +t_ip4_pton(void) +{ + struct bt_pair test_vectors[] = { + { + .in = "192.168.1.128", + .out = & ipa_build4(192, 168, 1, 128), + }, + { + .in = "255.255.255.255", + .out = & ipa_build4(255, 255, 255, 255), + }, + { + .in = "0.0.0.0", + .out = & ipa_build4(0, 0, 0, 0), + }, + }; + + return bt_assert_batch(test_vectors, test_ipa_pton, bt_fmt_str, bt_fmt_ipa); +} + +static int +t_ip6_pton(void) +{ + struct bt_pair test_vectors[] = { + { + .in = "2001:0db8:0000:0000:0000:0000:1428:57ab", + .out = & ipa_build6(0x20010DB8, 0x00000000, 0x00000000, 0x142857AB), + }, + { + .in = "2001:0db8:0000:0000:0000::1428:57ab", + .out = & ipa_build6(0x20010DB8, 0x00000000, 0x00000000, 0x142857AB), + }, + { + .in = "2001:0db8::1428:57ab", + .out = & ipa_build6(0x20010DB8, 0x00000000, 0x00000000, 0x142857AB), + }, + { + .in = "2001:db8::1428:57ab", + .out = & ipa_build6(0x20010DB8, 0x00000000, 0x00000000, 0x142857AB), + }, + { + .in = "::1", + .out = & ipa_build6(0x00000000, 0x00000000, 0x00000000, 0x00000001), + }, + { + .in = "::", + .out = & ipa_build6(0x00000000, 0x00000000, 0x00000000, 0x00000000), + }, + { + .in = "2605:2700:0:3::4713:93e3", + .out = & ipa_build6(0x26052700, 0x00000003, 0x00000000, 0x471393E3), + }, + }; + + return bt_assert_batch(test_vectors, test_ipa_pton, bt_fmt_str, bt_fmt_ipa); +} + +static int +test_ipa_ntop(void *out_, const void *in_, const void *expected_out_) +{ + char *out = out_; + const ip_addr *in = in_; + const char *expected_out = expected_out_; + + if (ipa_is_ip4(*in)) + ip4_ntop(ipa_to_ip4(*in), out); + else + ip6_ntop(ipa_to_ip6(*in), out); + + int result = strncmp(out, expected_out, ipa_is_ip4(*in) ? IP4_MAX_TEXT_LENGTH : IP6_MAX_TEXT_LENGTH) == 0; + return result; +} + +static int +t_ip4_ntop(void) +{ + struct bt_pair test_vectors[] = { + { + .in = & ipa_build4(192, 168, 1, 128), + .out = "192.168.1.128", + }, + { + .in = & ipa_build4(255, 255, 255, 255), + .out = "255.255.255.255", + }, + { + .in = & ipa_build4(0, 0, 0, 1), + .out = "0.0.0.1", + }, + }; + + return bt_assert_batch(test_vectors, test_ipa_ntop, bt_fmt_ipa, bt_fmt_str); +} + +static int +t_ip6_ntop(void) +{ + struct bt_pair test_vectors[] = { + { + .in = & ipa_build6(0x20010DB8, 0x00000000, 0x00000000, 0x142857AB), + .out = "2001:db8::1428:57ab", + }, + { + .in = & ipa_build6(0x26052700, 0x00000003, 0x00000000, 0x471393E3), + .out = "2605:2700:0:3::4713:93e3", + }, + }; + + return bt_assert_batch(test_vectors, test_ipa_ntop, bt_fmt_ipa, bt_fmt_str); +} + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + bt_test_suite(t_ip4_pton, "Converting IPv4 string to ip4_addr struct"); + bt_test_suite(t_ip6_pton, "Converting IPv6 string to ip6_addr struct"); + bt_test_suite(t_ip4_ntop, "Converting ip4_addr struct to IPv4 string"); + bt_test_suite(t_ip6_ntop, "Converting ip6_addr struct to IPv6 string"); + + return bt_exit_value(); +} + diff --git a/lib/lists.c b/lib/lists.c index 12ef3cc6..4a48d3b7 100644 --- a/lib/lists.c +++ b/lib/lists.c @@ -158,3 +158,15 @@ add_tail_list(list *to, list *l) q->next = &to->tail_node; to->tail = q; } + +LIST_INLINE uint +list_length(list *l) +{ + uint len = 0; + node *n; + + WALK_LIST(n, *l) + len++; + + return len; +} diff --git a/lib/lists.h b/lib/lists.h index 51856b05..066eafbb 100644 --- a/lib/lists.h +++ b/lib/lists.h @@ -52,7 +52,10 @@ typedef union list { /* In fact two overlayed nodes */ #define WALK_LIST2(n,nn,list,pos) \ for(nn=(list).head; NODE_VALID(nn) && (n=SKIP_BACK(typeof(*n),pos,nn)); nn=nn->next) #define WALK_LIST_DELSAFE(n,nxt,list) \ - for(n=HEAD(list); nxt=NODE_NEXT(n); n=(void *) nxt) + for(n=HEAD(list); nxt=NODE_NEXT(n); n=(void *) nxt) +#define WALK_LIST2_DELSAFE(n,nn,nxt,list,pos) \ + for(nn=HEAD(list); (nxt=nn->next) && (n=SKIP_BACK(typeof(*n),pos,nn)); nn=nxt) + /* WALK_LIST_FIRST supposes that called code removes each processed node */ #define WALK_LIST_FIRST(n,list) \ while(n=HEAD(list), (NODE (n))->next) @@ -77,6 +80,7 @@ void rem_node(node *); void add_tail_list(list *, list *); void init_list(list *); void insert_node(node *, node *); +uint list_length(list *); #endif #endif diff --git a/lib/lists_test.c b/lib/lists_test.c new file mode 100644 index 00000000..f26a88e2 --- /dev/null +++ b/lib/lists_test.c @@ -0,0 +1,287 @@ +/* + * BIRD Library -- Linked Lists Tests + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include "test/birdtest.h" +#include "lib/lists.h" + +#define MAX_NUM 1000 + +static node nodes[MAX_NUM]; +static list l; + +static void +show_list(void) +{ + bt_debug("\n"); + bt_debug("list.null is at %p and point to %p\n", &l.null, l.null); + bt_debug("list.head is at %p and point to %p\n", &l.head, l.head); + bt_debug("list.tail is at %p and point to %p\n", &l.tail, l.tail); + + int i; + for (i = 0; i < MAX_NUM; i++) + { + bt_debug("n[%3i] is at %p\n", i, &nodes[i]); + bt_debug(" prev is at %p and point to %p\n", &(nodes[i].prev), nodes[i].prev); + bt_debug(" next is at %p and point to %p\n", &(nodes[i].next), nodes[i].next); + } +} + +static int +is_filled_list_well_linked(void) +{ + int i; + bt_assert(l.head == &nodes[0]); + bt_assert(l.tail == &nodes[MAX_NUM-1]); + bt_assert((void *) nodes[0].prev == (void *) &l.head); + bt_assert((void *) nodes[MAX_NUM-1].next == (void *) &l.null); + + for (i = 0; i < MAX_NUM; i++) + { + if (i < (MAX_NUM-1)) + bt_assert(nodes[i].next == &nodes[i+1]); + + if (i > 0) + bt_assert(nodes[i].prev == &nodes[i-1]); + } + + return 1; +} + +static int +is_empty_list_well_unlinked(void) +{ + int i; + + bt_assert(l.head == NODE &l.null); + bt_assert(l.tail == NODE &l.head); + bt_assert(EMPTY_LIST(l)); + + for (i = 0; i < MAX_NUM; i++) + { + bt_assert(nodes[i].next == NULL); + bt_assert(nodes[i].prev == NULL); + } + + return 1; +} + +static void +init_list__(list *l, struct node nodes[]) +{ + init_list(l); + + int i; + for (i = 0; i < MAX_NUM; i++) + { + nodes[i].next = NULL; + nodes[i].prev = NULL; + } +} + +static void +init_list_(void) +{ + init_list__(&l, (node *) nodes); +} + +static int +t_add_tail(void) +{ + int i; + + init_list_(); + for (i = 0; i < MAX_NUM; i++) + { + add_tail(&l, &nodes[i]); + bt_debug("."); + bt_assert(l.tail == &nodes[i]); + bt_assert(l.head == &nodes[0]); + bt_assert((void *) nodes[i].next == (void *) &l.null); + if (i > 0) + { + bt_assert(nodes[i-1].next == &nodes[i]); + bt_assert(nodes[i].prev == &nodes[i-1]); + } + } + show_list(); + bt_assert(is_filled_list_well_linked()); + + return 1; +} + +static int +t_add_head(void) +{ + int i; + + init_list_(); + for (i = MAX_NUM-1; i >= 0; i--) + { + add_head(&l, &nodes[i]); + bt_debug("."); + bt_assert(l.head == &nodes[i]); + bt_assert(l.tail == &nodes[MAX_NUM-1]); + if (i < MAX_NUM-1) + { + bt_assert(nodes[i+1].prev == &nodes[i]); + bt_assert(nodes[i].next == &nodes[i+1]); + } + } + show_list(); + bt_assert(is_filled_list_well_linked()); + + return 1; +} + +static void +insert_node_(node *n, node *after) +{ + insert_node(n, after); + bt_debug("."); +} + +static int +t_insert_node(void) +{ + int i; + + init_list_(); + + // add first node + insert_node_(&nodes[0], NODE &l.head); + + // add odd nodes + for (i = 2; i < MAX_NUM; i+=2) + insert_node_(&nodes[i], &nodes[i-2]); + + // add even nodes + for (i = 1; i < MAX_NUM; i+=2) + insert_node_(&nodes[i], &nodes[i-1]); + + bt_debug("\n"); + bt_assert(is_filled_list_well_linked()); + + return 1; +} + +static void +fill_list2(list *l, node nodes[]) +{ + int i; + for (i = 0; i < MAX_NUM; i++) + add_tail(l, &nodes[i]); +} + +static void +fill_list(void) +{ + fill_list2(&l, (node *) nodes); +} + +static int +t_remove_node(void) +{ + int i; + + init_list_(); + + /* Fill & Remove & Check */ + fill_list(); + for (i = 0; i < MAX_NUM; i++) + rem_node(&nodes[i]); + bt_assert(is_empty_list_well_unlinked()); + + /* Fill & Remove the half of nodes & Check & Remove the rest nodes & Check */ + fill_list(); + for (i = 0; i < MAX_NUM; i+=2) + rem_node(&nodes[i]); + + int tail_node_index = (MAX_NUM % 2) ? MAX_NUM - 2 : MAX_NUM - 1; + bt_assert(l.head == &nodes[1]); + bt_assert(l.tail == &nodes[tail_node_index]); + bt_assert(nodes[tail_node_index].next == NODE &l.null); + + for (i = 1; i < MAX_NUM; i+=2) + { + if (i > 1) + bt_assert(nodes[i].prev == &nodes[i-2]); + if (i < tail_node_index) + bt_assert(nodes[i].next == &nodes[i+2]); + } + + for (i = 1; i < MAX_NUM; i+=2) + rem_node(&nodes[i]); + bt_assert(is_empty_list_well_unlinked()); + + return 1; +} + +static int +t_replace_node(void) +{ + node head, inside, tail; + + init_list_(); + fill_list(); + + replace_node(&nodes[0], &head); + bt_assert(l.head == &head); + bt_assert(head.prev == NODE &l.head); + bt_assert(head.next == &nodes[1]); + bt_assert(nodes[1].prev == &head); + + replace_node(&nodes[MAX_NUM/2], &inside); + bt_assert(nodes[MAX_NUM/2-1].next == &inside); + bt_assert(nodes[MAX_NUM/2+1].prev == &inside); + bt_assert(inside.prev == &nodes[MAX_NUM/2-1]); + bt_assert(inside.next == &nodes[MAX_NUM/2+1]); + + replace_node(&nodes[MAX_NUM-1], &tail); + bt_assert(l.tail == &tail); + bt_assert(tail.prev == &nodes[MAX_NUM-2]); + bt_assert(tail.next == NODE &l.null); + bt_assert(nodes[MAX_NUM-2].next == &tail); + + return 1; +} + +static int +t_add_tail_list(void) +{ + node nodes2[MAX_NUM]; + list l2; + + init_list__(&l, (node *) nodes); + fill_list2(&l, (node *) nodes); + + init_list__(&l2, (node *) nodes2); + fill_list2(&l2, (node *) nodes2); + + add_tail_list(&l, &l2); + + bt_assert(nodes[MAX_NUM-1].next == &nodes2[0]); + bt_assert(nodes2[0].prev == &nodes[MAX_NUM-1]); + bt_assert(l.tail == &nodes2[MAX_NUM-1]); + + return 1; +} + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + bt_test_suite(t_add_tail, "Adding nodes to tail of list"); + bt_test_suite(t_add_head, "Adding nodes to head of list"); + bt_test_suite(t_insert_node, "Inserting nodes to list"); + bt_test_suite(t_remove_node, "Removing nodes from list"); + bt_test_suite(t_replace_node, "Replacing nodes in list"); + bt_test_suite(t_add_tail_list, "At the tail of a list adding the another list"); + + return bt_exit_value(); +} diff --git a/lib/mac_test.c b/lib/mac_test.c new file mode 100644 index 00000000..806fe3e4 --- /dev/null +++ b/lib/mac_test.c @@ -0,0 +1,1159 @@ +/* + * BIRD Library -- SHA and HMAC-SHA functions tests + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include "test/birdtest.h" +#include "test/bt-utils.h" + +#include "lib/mac.h" + + +#define define_test_hash_fn(name,id) \ +static int \ +test_##name(void *out_, const void *in_, const void *expected_out_) \ +{ \ + char *out = out_; \ + const char *in = in_; \ + const char *expected_out = expected_out_; \ + \ + struct mac_context ctx; \ + mac_init(&ctx, id, NULL, 0); \ + mac_update(&ctx, in, strlen(in)); \ + byte *out_bin = mac_final(&ctx); \ + \ + uint len = mac_type_length(id); \ + bt_bytes_to_hex(out, out_bin, len); \ + \ + return strncmp(out, expected_out, 2*len+1) == 0; \ +} + +define_test_hash_fn(md5, ALG_MD5) +define_test_hash_fn(sha1, ALG_SHA1) +define_test_hash_fn(sha224, ALG_SHA224) +define_test_hash_fn(sha256, ALG_SHA256) +define_test_hash_fn(sha384, ALG_SHA384) +define_test_hash_fn(sha512, ALG_SHA512) + + +static int +t_md5(void) +{ + struct bt_pair test_vectors[] = { + { + .in = "", + .out = "d41d8cd98f00b204e9800998ecf8427e", + }, + { + .in = "a", + .out = "0cc175b9c0f1b6a831c399e269772661", + }, + { + .in = "abc", + .out = "900150983cd24fb0d6963f7d28e17f72", + }, + { + .in = "message digest", + .out = "f96b697d7cb7938d525a2f31aaf161d0", + }, + { + .in = "abcdefghijklmnopqrstuvwxyz", + .out = "c3fcd3d76192e4007dfb496cca67e13b", + }, + { + .in = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + .out = "d174ab98d277d9f5a5611c2c9f419d9f", + }, + { + .in = "12345678901234567890123456789012345678901234567890123456789012345678901234567890", + .out = "57edf4a22be3c955ac49da2e2107b67a", + }, + }; + + return bt_assert_batch(test_vectors, test_md5, bt_fmt_str, bt_fmt_str); +} + + +/* + * Testing SHAxxx functions + */ + + +static int +t_sha1(void) +{ + struct bt_pair test_vectors[] = { + { + .in = "", + .out = "da39a3ee5e6b4b0d3255bfef95601890afd80709", + }, + { + .in = "a", + .out = "86f7e437faa5a7fce15d1ddcb9eaeaea377667b8", + }, + { + .in = "abc", + .out = "a9993e364706816aba3e25717850c26c9cd0d89d", + }, + { + .in = "message digest", + .out = "c12252ceda8be8994d5fa0290a47231c1d16aae3", + }, + { + .in = "abcdefghijklmnopqrstuvwxyz", + .out = "32d10c7b8cf96570ca04ce37f2a19d84240d3a89", + }, + { + .in = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + .out = "761c457bf73b14d27e9e9265c46f4b4dda11f940", + }, + { + .in = "12345678901234567890123456789012345678901234567890123456789012345678901234567890", + .out = "50abf5706a150990a08b2c5ea40fa0e585554732", + }, + { + .in = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + .out = "6a64fcc1fb970f7339ce886601775d2efea5cd4b", + }, + }; + + return bt_assert_batch(test_vectors, test_sha1, bt_fmt_str, bt_fmt_str); +} + +static int +t_sha224(void) +{ + struct bt_pair test_vectors[] = { + { + .in = "", + .out = "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", + }, + { + .in = "a", + .out = "abd37534c7d9a2efb9465de931cd7055ffdb8879563ae98078d6d6d5", + }, + { + .in = "abc", + .out = "23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7", + }, + { + .in = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + .out = "75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525", + }, + { + .in = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + .out = "cca7dd1a332a17775d8b0429bdb45055c2d4368ebaab0c7cf385586e", + }, + }; + + return bt_assert_batch(test_vectors, test_sha224, bt_fmt_str, bt_fmt_str); +} + +static int +t_sha256(void) +{ + struct bt_pair test_vectors[] = { + { + .in = "", + .out = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, + { + .in = "a", + .out = "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", + }, + { + .in = "abc", + .out = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", + }, + { + .in = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + .out = "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1", + }, + { + .in = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + .out = "bf18b43b61652b5d73f41ebf3d72e5e43aebf5076f497dde31ea3de9de4998ef", + }, + }; + + return bt_assert_batch(test_vectors, test_sha256, bt_fmt_str, bt_fmt_str); +} + +static int +t_sha384(void) +{ + struct bt_pair test_vectors[] = { + { + .in = "", + .out = "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", + }, + { + .in = "a", + .out = "54a59b9f22b0b80880d8427e548b7c23abd873486e1f035dce9cd697e85175033caa88e6d57bc35efae0b5afd3145f31", + }, + { + .in = "abc", + .out = "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7", + }, + { + .in = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + .out = "3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b", + }, + { + .in = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + .out = "6452928a62ca915a60f2d16ea22cc832d8ecb35443d78a3ff6986e7def9174a1dc16ce2ff65d3ed1666db98357f3c05e", + }, + }; + + return bt_assert_batch(test_vectors, test_sha384, bt_fmt_str, bt_fmt_str); +} + +static int +t_sha512(void) +{ + struct bt_pair test_vectors[] = { + { + .in = "", + .out = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", + }, + { + .in = "a", + .out = "1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75", + }, + { + .in = "abc", + .out = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f", + }, + { + .in = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + .out = "204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445", + }, + { + .in = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + .out = "415509a1c345371acb3e27a88b3835e3b6dfebcbbab5134850596f4db64d7bb22ac42c3cd179446a80c92b8be955460eb536eac01389a7e1fdf09d1dca83922f", + }, + }; + + return bt_assert_batch(test_vectors, test_sha512, bt_fmt_str, bt_fmt_str); +} + + +/* + * Testing SHAxxx HMAC functions + */ + +#define HMAC_BUFFER_SIZE 160 +struct hmac_data_in { + byte key[HMAC_BUFFER_SIZE]; + uint key_len; + byte data[HMAC_BUFFER_SIZE]; + uint data_len; +}; + +static void +hmac_in_fmt(char *buf, size_t size, const void *data_) +{ + uint i; + const struct hmac_data_in *data = data_; + + snprintf(buf, size, "data: '"); + for (i = 0; i < data->data_len; i++) + snprintf(buf+strlen(buf), size-strlen(buf), bt_is_char(data->data[i]) ? "%c" : " 0x%02x", data->data[i]); + + snprintf(buf+strlen(buf), size-strlen(buf), "', key: '"); + for (i = 0; i < data->key_len; i++) + snprintf(buf+strlen(buf), size-strlen(buf), bt_is_char(data->key[i]) ? "%c" : " 0x%02x", data->key[i]); + snprintf(buf+strlen(buf), size-strlen(buf), "'"); +} + +#define define_test_hmac_fn(name,id) \ +static int \ +test_##name##_hmac(void *out_, const void *in_, const void *expected_out_) \ +{ \ + char *out = out_; \ + const struct hmac_data_in *in = in_; \ + const char *expected_out = expected_out_; \ + \ + struct mac_context ctx; \ + mac_init(&ctx, id, in->key, in->key_len); \ + mac_update(&ctx, in->data, in->data_len); \ + byte *out_bin = mac_final(&ctx); \ + \ + uint len = mac_type_length(id); \ + bt_bytes_to_hex(out, out_bin, len); \ + \ + return strncmp(out, expected_out, 2*len+1) == 0; \ +} + +define_test_hmac_fn(md5, ALG_HMAC_MD5) +define_test_hmac_fn(sha1, ALG_HMAC_SHA1) +define_test_hmac_fn(sha224, ALG_HMAC_SHA224) +define_test_hmac_fn(sha256, ALG_HMAC_SHA256) +define_test_hmac_fn(sha384, ALG_HMAC_SHA384) +define_test_hmac_fn(sha512, ALG_HMAC_SHA512) + + +static int +t_md5_hmac(void) +{ + struct bt_pair test_vectors[] = { + { + .in = & (struct hmac_data_in) { + .key = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + }, + .key_len = 16, + .data = "Hi There", + .data_len = 8, + }, + .out = "9294727a3638bb1c13f48ef8158bfc9d", + }, + { + .in = & (struct hmac_data_in) { + .key = "Jefe", + .key_len = 4, + .data = "what do ya want for nothing?", + .data_len = 28, + }, + .out = "750c783e6ab0b503eaa86e310a5db738", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + }, + .key_len = 16, + .data = { + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + }, + .data_len = 50, + }, + .out = "56be34521d144c88dbb8c733f0e8b3f6", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, + }, + .key_len = 25, + .data = { + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + }, + .data_len = 50, + }, + .out = "697eaf0aca3a3aea3a75164746ffaa79", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + }, + .key_len = 16, + .data = "Test With Truncation", + .data_len = 20, + }, + .out = "56461ef2342edc00f9bab995690efd4c", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + }, + .key_len = 80, + .data = "Test Using Larger Than Block-Size Key - Hash Key First", + .data_len = 54, + }, + .out = "6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + }, + .key_len = 80, + .data = "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", + .data_len = 73, + }, + .out = "6f630fad67cda0ee1fb1f562db3aa53e", + }, + }; + + return bt_assert_batch(test_vectors, test_md5_hmac, hmac_in_fmt, bt_fmt_str); +} + +static int +t_sha1_hmac(void) +{ + struct bt_pair test_vectors[] = { + { + .in = & (struct hmac_data_in) { + .key = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + }, + .key_len = 20, + .data = "Hi There", + .data_len = 8, + }, + .out = "b617318655057264e28bc0b6fb378c8ef146be00", + }, + { + .in = & (struct hmac_data_in) { + .key = "Jefe", + .key_len = 4, + .data = "what do ya want for nothing?", + .data_len = 28, + }, + .out = "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + }, + .key_len = 20, + .data = { + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + }, + .data_len = 50, + }, + .out = "125d7342b9ac11cd91a39af48aa17b4f63f175d3", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, + }, + .key_len = 25, + .data = { + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + }, + .data_len = 50, + }, + .out = "4c9007f4026250c6bc8414f9bf50c86c2d7235da", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + }, + .key_len = 20, + .data = "Test With Truncation", + .data_len = 20, + }, + .out = "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + }, + .key_len = 80, + .data = "Test Using Larger Than Block-Size Key - Hash Key First", + .data_len = 54, + }, + .out = "aa4ae5e15272d00e95705637ce8a3b55ed402112", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + }, + .key_len = 80, + .data = "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", + .data_len = 73, + }, + .out = "e8e99d0f45237d786d6bbaa7965c7808bbff1a91", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, + }, + .key_len = 64, + .data = "Test Using key 64 bytes sized", + .data_len = 29, + }, + .out = "a55d4fb80962a6b3d2e720705314bee417d68cf6", + }, + }; + + return bt_assert_batch(test_vectors, test_sha1_hmac, hmac_in_fmt, bt_fmt_str); +} + +static int +t_sha224_hmac(void) +{ + struct bt_pair test_vectors[] = { + { + .in = & (struct hmac_data_in) { + .key = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + }, + .key_len = 20, + .data = "Hi There", + .data_len = 8, + }, + .out = "896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22", + }, + { + .in = & (struct hmac_data_in) { + .key = "Jefe", + .key_len = 4, + .data = "what do ya want for nothing?", + .data_len = 28, + }, + .out = "a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + }, + .key_len = 20, + .data = { + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + }, + .data_len = 50, + }, + .out = "7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, + }, + .key_len = 25, + .data = { + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + }, + .data_len = 50, + }, + .out = "6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + }, + .key_len = 20, + .data = "Test With Truncation", + .data_len = 20, + }, + .out = "0e2aea68a90c8d37c988bcdb9fca6fa8099cd857c7ec4a1815cac54c", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, + }, + .key_len = 131, + .data = "Test Using Larger Than Block-Size Key - Hash Key First", + .data_len = 54, + }, + .out = "95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, + }, + .key_len = 131, + .data = "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", + .data_len = 152, + }, + .out = "3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1", + }, + }; + + return bt_assert_batch(test_vectors, test_sha224_hmac, hmac_in_fmt, bt_fmt_str); +} + +static int +t_sha256_hmac(void) +{ + struct bt_pair test_vectors[] = { + { + .in = & (struct hmac_data_in) { + .key = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + }, + .key_len = 20, + .data = "Hi There", + .data_len = 8, + }, + .out = "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7", + }, + { + .in = & (struct hmac_data_in) { + .key = "Jefe", + .key_len = 4, + .data = "what do ya want for nothing?", + .data_len = 28, + }, + .out = "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + }, + .key_len = 20, + .data = { + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + }, + .data_len = 50, + }, + .out = "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, + }, + .key_len = 25, + .data = { + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + }, + .data_len = 50, + }, + .out = "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + }, + .key_len = 20, + .data = "Test With Truncation", + .data_len = 20, + }, + .out = "a3b6167473100ee06e0c796c2955552bfa6f7c0a6a8aef8b93f860aab0cd20c5", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, + }, + .key_len = 131, + .data = "Test Using Larger Than Block-Size Key - Hash Key First", + .data_len = 54, + }, + .out = "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, + }, + .key_len = 131, + .data = "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", + .data_len = 152, + }, + .out = "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2", + }, + }; + + return bt_assert_batch(test_vectors, test_sha256_hmac, hmac_in_fmt, bt_fmt_str); +} + +static int +t_sha384_hmac(void) +{ + struct bt_pair test_vectors[] = { + { + .in = & (struct hmac_data_in) { + .key = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + }, + .key_len = 20, + .data = "Hi There", + .data_len = 8, + }, + .out = "afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6", + }, + { + .in = & (struct hmac_data_in) { + .key = "Jefe", + .key_len = 4, + .data = "what do ya want for nothing?", + .data_len = 28, + }, + .out = "af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + }, + .key_len = 20, + .data = { + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + }, + .data_len = 50, + }, + .out = "88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, + }, + .key_len = 25, + .data = { + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + }, + .data_len = 50, + }, + .out = "3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + }, + .key_len = 20, + .data = "Test With Truncation", + .data_len = 20, + }, + .out = "3abf34c3503b2a23a46efc619baef897f4c8e42c934ce55ccbae9740fcbc1af4ca62269e2a37cd88ba926341efe4aeea", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, + }, + .key_len = 131, + .data = "Test Using Larger Than Block-Size Key - Hash Key First", + .data_len = 54, + }, + .out = "4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, + }, + .key_len = 131, + .data = "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", + .data_len = 152, + }, + .out = "6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e", + }, + }; + + return bt_assert_batch(test_vectors, test_sha384_hmac, hmac_in_fmt, bt_fmt_str); +} + +static int +t_sha512_hmac(void) +{ + struct bt_pair test_vectors[] = { + { + .in = & (struct hmac_data_in) { + .key = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + }, + .key_len = 20, + .data = "Hi There", + .data_len = 8, + }, + .out = "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854", + }, + { + .in = & (struct hmac_data_in) { + .key = "Jefe", + .key_len = 4, + .data = "what do ya want for nothing?", + .data_len = 28, + }, + .out = "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + }, + .key_len = 20, + .data = { + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + }, + .data_len = 50, + }, + .out = "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, + }, + .key_len = 25, + .data = { + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + }, + .data_len = 50, + }, + .out = "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + }, + .key_len = 20, + .data = "Test With Truncation", + .data_len = 20, + }, + .out = "415fad6271580a531d4179bc891d87a650188707922a4fbb36663a1eb16da008711c5b50ddd0fc235084eb9d3364a1454fb2ef67cd1d29fe6773068ea266e96b", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, + }, + .key_len = 131, + .data = "Test Using Larger Than Block-Size Key - Hash Key First", + .data_len = 54, + }, + .out = "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598", + }, + { + .in = & (struct hmac_data_in) { + .key = { + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, + }, + .key_len = 131, + .data = "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", + .data_len = 152, + }, + .out = "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58", + }, + }; + + return bt_assert_batch(test_vectors, test_sha512_hmac, hmac_in_fmt, bt_fmt_str); +} + + +/* + * Testing SHAxxx concating independence + */ + +#include "lib/sha256.h" +#include "lib/sha512.h" + +static int +t_sha256_concating(void) +{ + char hash_a[SHA256_HEX_SIZE]; + char hash_b[SHA256_HEX_SIZE]; + + char *str_a = "a" "bb" "ccc" "dddd" "eeeee" "ffffff"; + char *str_b1 = "a" ; + char *str_b2 = "bb" ; + char *str_b3 = "ccc" ; + char *str_b4 = "dddd" ; + char *str_b5 = "eeeee" ; + char *str_b6 = "ffffff"; + + struct hash_context ctx_a; + sha256_init(&ctx_a); + sha256_update(&ctx_a, str_a, strlen(str_a)); + byte *hash_a_ = sha256_final(&ctx_a); + bt_bytes_to_hex(hash_a, hash_a_, SHA256_SIZE); + + struct hash_context ctx_b; + sha256_init(&ctx_b); + sha256_update(&ctx_b, str_b1, strlen(str_b1)); + sha256_update(&ctx_b, str_b2, strlen(str_b2)); + sha256_update(&ctx_b, str_b3, strlen(str_b3)); + sha256_update(&ctx_b, str_b4, strlen(str_b4)); + sha256_update(&ctx_b, str_b5, strlen(str_b5)); + sha256_update(&ctx_b, str_b6, strlen(str_b6)); + byte *hash_b_ = sha256_final(&ctx_b); + bt_bytes_to_hex(hash_b, hash_b_, SHA256_SIZE); + + int are_hash_a_b_equal = (strncmp(hash_a, hash_b, sizeof(hash_a)) == 0); + bt_assert_msg(are_hash_a_b_equal, "Hashes A: %s, B: %s should be same", hash_a, hash_b); + + return 1; +} + + +static int +t_sha512_concating(void) +{ + char hash_a[SHA512_HEX_SIZE]; + char hash_b[SHA512_HEX_SIZE]; + + char *str_a = "a" "bb" "ccc" "dddd" "eeeee" "ffffff"; + char *str_b1 = "a" ; + char *str_b2 = "bb" ; + char *str_b3 = "ccc" ; + char *str_b4 = "dddd" ; + char *str_b5 = "eeeee" ; + char *str_b6 = "ffffff"; + + struct hash_context ctx_a; + sha512_init(&ctx_a); + sha512_update(&ctx_a, str_a, strlen(str_a)); + byte *hash_a_ = sha512_final(&ctx_a); + bt_bytes_to_hex(hash_a, hash_a_, SHA512_SIZE); + + struct hash_context ctx_b; + sha512_init(&ctx_b); + sha512_update(&ctx_b, str_b1, strlen(str_b1)); + sha512_update(&ctx_b, str_b2, strlen(str_b2)); + sha512_update(&ctx_b, str_b3, strlen(str_b3)); + sha512_update(&ctx_b, str_b4, strlen(str_b4)); + sha512_update(&ctx_b, str_b5, strlen(str_b5)); + sha512_update(&ctx_b, str_b6, strlen(str_b6)); + byte *hash_b_ = sha512_final(&ctx_b); + bt_bytes_to_hex(hash_b, hash_b_, SHA512_SIZE); + + int are_hash_a_b_equal = (strncmp(hash_a, hash_b, sizeof(hash_a)) == 0); + bt_assert_msg(are_hash_a_b_equal, "Hashes A: %s, B: %s should be same", hash_a, hash_b); + + return 1; +} + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + bt_test_suite(t_md5, "Testing MD5 by RFC 1321"); + bt_test_suite(t_sha1, "Testing SHA-1"); + bt_test_suite(t_sha224, "Testing SHA-224"); + bt_test_suite(t_sha256, "Testing SHA-256"); + bt_test_suite(t_sha384, "Testing SHA-384"); + bt_test_suite(t_sha512, "Testing SHA-512"); + + bt_test_suite(t_md5_hmac, "Testing HMAC-MD5 by RFC 2202"); + bt_test_suite(t_sha1_hmac, "Testing HMAC-SHA-1 by RFC 2202"); + bt_test_suite(t_sha224_hmac, "Testing HMAC-SHA-224 by RFC 4231"); + bt_test_suite(t_sha256_hmac, "Testing HMAC-SHA-256 by RFC 4231"); + bt_test_suite(t_sha384_hmac, "Testing HMAC-SHA-384 by RFC 4231"); + bt_test_suite(t_sha512_hmac, "Testing HMAC-SHA-512 by RFC 4231"); + + bt_test_suite(t_sha256_concating, "Testing concatenation input string to hash using sha256_update"); + bt_test_suite(t_sha512_concating, "Testing concatenation input string to hash using sha512_update"); + + return bt_exit_value(); +} diff --git a/lib/mempool.c b/lib/mempool.c index a8281041..3cf9c2d3 100644 --- a/lib/mempool.c +++ b/lib/mempool.c @@ -32,6 +32,8 @@ struct lp_chunk { byte data[0]; }; +const int lp_chunk_size = sizeof(struct lp_chunk); + struct linpool { resource r; byte *ptr, *end; diff --git a/lib/net.c b/lib/net.c new file mode 100644 index 00000000..a00ff272 --- /dev/null +++ b/lib/net.c @@ -0,0 +1,290 @@ + +#include "nest/bird.h" +#include "lib/ip.h" +#include "lib/net.h" +#include "lib/flowspec.h" + + +const char * const net_label[] = { + [NET_IP4] = "ipv4", + [NET_IP6] = "ipv6", + [NET_VPN4] = "vpn4", + [NET_VPN6] = "vpn6", + [NET_ROA4] = "roa4", + [NET_ROA6] = "roa6", + [NET_FLOW4] = "flow4", + [NET_FLOW6] = "flow6", + [NET_MPLS] = "mpls", +}; + +const u16 net_addr_length[] = { + [NET_IP4] = sizeof(net_addr_ip4), + [NET_IP6] = sizeof(net_addr_ip6), + [NET_VPN4] = sizeof(net_addr_vpn4), + [NET_VPN6] = sizeof(net_addr_vpn6), + [NET_ROA4] = sizeof(net_addr_roa4), + [NET_ROA6] = sizeof(net_addr_roa6), + [NET_FLOW4] = 0, + [NET_FLOW6] = 0, + [NET_MPLS] = sizeof(net_addr_mpls), +}; + +const u8 net_max_prefix_length[] = { + [NET_IP4] = IP4_MAX_PREFIX_LENGTH, + [NET_IP6] = IP6_MAX_PREFIX_LENGTH, + [NET_VPN4] = IP4_MAX_PREFIX_LENGTH, + [NET_VPN6] = IP6_MAX_PREFIX_LENGTH, + [NET_ROA4] = IP4_MAX_PREFIX_LENGTH, + [NET_ROA6] = IP6_MAX_PREFIX_LENGTH, + [NET_FLOW4] = IP4_MAX_PREFIX_LENGTH, + [NET_FLOW6] = IP6_MAX_PREFIX_LENGTH, + [NET_MPLS] = 0, +}; + +const u16 net_max_text_length[] = { + [NET_IP4] = 18, /* "255.255.255.255/32" */ + [NET_IP6] = 43, /* "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128" */ + [NET_VPN4] = 40, /* "4294967296:4294967296 255.255.255.255/32" */ + [NET_VPN6] = 65, /* "4294967296:4294967296 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128" */ + [NET_ROA4] = 34, /* "255.255.255.255/32-32 AS4294967295" */ + [NET_ROA6] = 60, /* "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128-128 AS4294967295" */ + [NET_FLOW4] = 0, /* "flow4 { ... }" */ + [NET_FLOW6] = 0, /* "flow6 { ... }" */ + [NET_MPLS] = 7, /* "1048575" */ +}; + + +int +rd_format(const u64 rd, char *buf, int buflen) +{ + switch (rd >> 48) + { + case 0: return bsnprintf(buf, buflen, "%u:%u", (u32) (rd >> 32), (u32) rd); + case 1: return bsnprintf(buf, buflen, "%I4:%u", ip4_from_u32(rd >> 16), (u32) (rd & 0xffff)); + case 2: if (((u32) (rd >> 16)) >> 16) + return bsnprintf(buf, buflen, "%u:%u", (u32) (rd >> 16), (u32) (rd & 0xffff)); + else + return bsnprintf(buf, buflen, "2:%u:%u", (u32) (rd >> 16), (u32) (rd & 0xffff)); + default: return bsnprintf(buf, buflen, "X:%08x:%08x", (u32) (rd >> 32), (u32) rd); + } +} + +int +net_format(const net_addr *N, char *buf, int buflen) +{ + net_addr_union *n = (void *) N; + buf[0] = 0; + + switch (n->n.type) + { + case NET_IP4: + return bsnprintf(buf, buflen, "%I4/%d", n->ip4.prefix, n->ip4.pxlen); + case NET_IP6: + return bsnprintf(buf, buflen, "%I6/%d", n->ip6.prefix, n->ip6.pxlen); + case NET_VPN4: + { + int c = rd_format(n->vpn4.rd, buf, buflen); + ADVANCE(buf, buflen, c); + return bsnprintf(buf, buflen, " %I4/%d", n->vpn4.prefix, n->vpn4.pxlen); + } + case NET_VPN6: + { + /* XXX: RD format is specified for VPN4; not found any for VPN6, reusing the same as for VPN4 */ + int c = rd_format(n->vpn6.rd, buf, buflen); + ADVANCE(buf, buflen, c); + return bsnprintf(buf, buflen, " %I6/%d", n->vpn6.prefix, n->vpn6.pxlen); + } + case NET_ROA4: + return bsnprintf(buf, buflen, "%I4/%u-%u AS%u", n->roa4.prefix, n->roa4.pxlen, n->roa4.max_pxlen, n->roa4.asn); + case NET_ROA6: + return bsnprintf(buf, buflen, "%I6/%u-%u AS%u", n->roa6.prefix, n->roa6.pxlen, n->roa6.max_pxlen, n->roa6.asn); + case NET_FLOW4: + return flow4_net_format(buf, buflen, &n->flow4); + case NET_FLOW6: + return flow6_net_format(buf, buflen, &n->flow6); + case NET_MPLS: + return bsnprintf(buf, buflen, "%u", n->mpls.label); + } + + bug("unknown network type"); +} + +ip_addr +net_pxmask(const net_addr *a) +{ + switch (a->type) + { + case NET_IP4: + case NET_VPN4: + case NET_ROA4: + case NET_FLOW4: + return ipa_from_ip4(ip4_mkmask(net4_pxlen(a))); + + case NET_IP6: + case NET_VPN6: + case NET_ROA6: + case NET_FLOW6: + return ipa_from_ip6(ip6_mkmask(net6_pxlen(a))); + + case NET_MPLS: + default: + return IPA_NONE; + } +} + +int +net_compare(const net_addr *a, const net_addr *b) +{ + if (a->type != b->type) + return uint_cmp(a->type, b->type); + + switch (a->type) + { + case NET_IP4: + return net_compare_ip4((const net_addr_ip4 *) a, (const net_addr_ip4 *) b); + case NET_IP6: + return net_compare_ip6((const net_addr_ip6 *) a, (const net_addr_ip6 *) b); + case NET_VPN4: + return net_compare_vpn4((const net_addr_vpn4 *) a, (const net_addr_vpn4 *) b); + case NET_VPN6: + return net_compare_vpn6((const net_addr_vpn6 *) a, (const net_addr_vpn6 *) b); + case NET_ROA4: + return net_compare_roa4((const net_addr_roa4 *) a, (const net_addr_roa4 *) b); + case NET_ROA6: + return net_compare_roa6((const net_addr_roa6 *) a, (const net_addr_roa6 *) b); + case NET_FLOW4: + return net_compare_flow4((const net_addr_flow4 *) a, (const net_addr_flow4 *) b); + case NET_FLOW6: + return net_compare_flow6((const net_addr_flow6 *) a, (const net_addr_flow6 *) b); + case NET_MPLS: + return net_compare_mpls((const net_addr_mpls *) a, (const net_addr_mpls *) b); + } + return 0; +} + +#define NET_HASH(a,t) net_hash_##t((const net_addr_##t *) a) + +u32 +net_hash(const net_addr *n) +{ + switch (n->type) + { + case NET_IP4: return NET_HASH(n, ip4); + case NET_IP6: return NET_HASH(n, ip6); + case NET_VPN4: return NET_HASH(n, vpn4); + case NET_VPN6: return NET_HASH(n, vpn6); + case NET_ROA4: return NET_HASH(n, roa4); + case NET_ROA6: return NET_HASH(n, roa6); + case NET_FLOW4: return NET_HASH(n, flow4); + case NET_FLOW6: return NET_HASH(n, flow6); + case NET_MPLS: return NET_HASH(n, mpls); + default: bug("invalid type"); + } +} + + +#define NET_VALIDATE(a,t) net_validate_##t((const net_addr_##t *) a) + +int +net_validate(const net_addr *n) +{ + switch (n->type) + { + case NET_IP4: return NET_VALIDATE(n, ip4); + case NET_IP6: return NET_VALIDATE(n, ip6); + case NET_VPN4: return NET_VALIDATE(n, vpn4); + case NET_VPN6: return NET_VALIDATE(n, vpn6); + case NET_ROA4: return NET_VALIDATE(n, roa4); + case NET_ROA6: return NET_VALIDATE(n, roa6); + case NET_FLOW4: return NET_VALIDATE(n, flow4); + case NET_FLOW6: return NET_VALIDATE(n, flow6); + case NET_MPLS: return NET_VALIDATE(n, mpls); + default: return 0; + } +} + +void +net_normalize(net_addr *N) +{ + net_addr_union *n = (void *) N; + + switch (n->n.type) + { + case NET_IP4: + case NET_VPN4: + case NET_ROA4: + case NET_FLOW4: + return net_normalize_ip4(&n->ip4); + + case NET_IP6: + case NET_VPN6: + case NET_ROA6: + case NET_FLOW6: + return net_normalize_ip6(&n->ip6); + + case NET_MPLS: + return; + } +} + +int +net_classify(const net_addr *N) +{ + net_addr_union *n = (void *) N; + + switch (n->n.type) + { + case NET_IP4: + case NET_VPN4: + case NET_ROA4: + case NET_FLOW4: + return ip4_zero(n->ip4.prefix) ? (IADDR_HOST | SCOPE_UNIVERSE) : ip4_classify(n->ip4.prefix); + + case NET_IP6: + case NET_VPN6: + case NET_ROA6: + case NET_FLOW6: + return ip6_zero(n->ip6.prefix) ? (IADDR_HOST | SCOPE_UNIVERSE) : ip6_classify(&n->ip6.prefix); + + case NET_MPLS: + return IADDR_HOST | SCOPE_UNIVERSE; + } + + return IADDR_INVALID; +} + +int +ipa_in_netX(const ip_addr a, const net_addr *n) +{ + switch (n->type) + { + case NET_IP4: + case NET_VPN4: + case NET_ROA4: + case NET_FLOW4: + if (!ipa_is_ip4(a)) return 0; + return ip4_zero(ip4_and(ip4_xor(ipa_to_ip4(a), net4_prefix(n)), + ip4_mkmask(net4_pxlen(n)))); + + case NET_IP6: + case NET_VPN6: + case NET_ROA6: + case NET_FLOW6: + if (ipa_is_ip4(a)) return 0; + return ip6_zero(ip6_and(ip6_xor(ipa_to_ip6(a), net6_prefix(n)), + ip6_mkmask(net6_pxlen(n)))); + + case NET_MPLS: + default: + return 0; + } +} + +int +net_in_netX(const net_addr *a, const net_addr *n) +{ + if (a->type != n->type) + return 0; + + return (net_pxlen(n) <= net_pxlen(a)) && ipa_in_netX(net_prefix(a), n); +} diff --git a/lib/net.h b/lib/net.h new file mode 100644 index 00000000..332f4c9a --- /dev/null +++ b/lib/net.h @@ -0,0 +1,533 @@ +/* + * BIRD Internet Routing Daemon -- Network addresses + * + * (c) 2015 Ondrej Zajicek <santiago@crfreenet.org> + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef _BIRD_NET_H_ +#define _BIRD_NET_H_ + +#include "lib/ip.h" + + +#define NET_IP4 1 +#define NET_IP6 2 +#define NET_VPN4 3 +#define NET_VPN6 4 +#define NET_ROA4 5 +#define NET_ROA6 6 +#define NET_FLOW4 7 +#define NET_FLOW6 8 +#define NET_MPLS 9 +#define NET_MAX 10 + +#define NB_IP4 (1 << NET_IP4) +#define NB_IP6 (1 << NET_IP6) +#define NB_VPN4 (1 << NET_VPN4) +#define NB_VPN6 (1 << NET_VPN6) +#define NB_ROA4 (1 << NET_ROA4) +#define NB_ROA6 (1 << NET_ROA6) +#define NB_FLOW4 (1 << NET_FLOW4) +#define NB_FLOW6 (1 << NET_FLOW6) +#define NB_MPLS (1 << NET_MPLS) + +#define NB_IP (NB_IP4 | NB_IP6) +#define NB_VPN (NB_VPN4 | NB_VPN6) +#define NB_FLOW (NB_FLOW4 | NB_FLOW6) +#define NB_DEST (NB_IP | NB_VPN | NB_MPLS) +#define NB_ANY 0xffffffff + + +typedef struct net_addr { + u8 type; + u8 pxlen; + u16 length; + u8 data[16]; + u64 align[0]; +} net_addr; + +typedef struct net_addr_ip4 { + u8 type; + u8 pxlen; + u16 length; + ip4_addr prefix; +} net_addr_ip4; + +typedef struct net_addr_ip6 { + u8 type; + u8 pxlen; + u16 length; + ip6_addr prefix; +} net_addr_ip6; + +typedef struct net_addr_vpn4 { + u8 type; + u8 pxlen; + u16 length; + ip4_addr prefix; + u64 rd; +} net_addr_vpn4; + +typedef struct net_addr_vpn6 { + u8 type; + u8 pxlen; + u16 length; + ip6_addr prefix; + u64 rd; +} net_addr_vpn6; + +typedef struct net_addr_roa4 { + u8 type; + u8 pxlen; + u16 length; + ip4_addr prefix; + u32 max_pxlen; + u32 asn; +} net_addr_roa4; + +typedef struct net_addr_roa6 { + u8 type; + u8 pxlen; + u16 length; + ip6_addr prefix; + u32 max_pxlen; + u32 asn; +} net_addr_roa6; + +typedef struct net_addr_flow4 { + u8 type; + u8 pxlen; + u16 length; + ip4_addr prefix; + byte data[0]; +} net_addr_flow4; + +typedef struct net_addr_flow6 { + u8 type; + u8 pxlen; + u16 length; + ip6_addr prefix; + byte data[0]; +} net_addr_flow6; + +typedef struct net_addr_mpls { + u8 type; + u8 pxlen; + u16 length; + u32 label; +} net_addr_mpls; + +typedef union net_addr_union { + net_addr n; + net_addr_ip4 ip4; + net_addr_ip6 ip6; + net_addr_vpn4 vpn4; + net_addr_vpn6 vpn6; + net_addr_roa4 roa4; + net_addr_roa6 roa6; + net_addr_flow4 flow4; + net_addr_flow6 flow6; + net_addr_mpls mpls; +} net_addr_union; + + +extern const char * const net_label[]; +extern const u16 net_addr_length[]; +extern const u8 net_max_prefix_length[]; +extern const u16 net_max_text_length[]; + +#define NET_MAX_TEXT_LENGTH 256 + + +#define NET_ADDR_IP4(prefix,pxlen) \ + ((net_addr_ip4) { NET_IP4, pxlen, sizeof(net_addr_ip4), prefix }) + +#define NET_ADDR_IP6(prefix,pxlen) \ + ((net_addr_ip6) { NET_IP6, pxlen, sizeof(net_addr_ip6), prefix }) + +#define NET_ADDR_VPN4(prefix,pxlen,rd) \ + ((net_addr_vpn4) { NET_VPN4, pxlen, sizeof(net_addr_vpn4), prefix, rd }) + +#define NET_ADDR_VPN6(prefix,pxlen,rd) \ + ((net_addr_vpn6) { NET_VPN6, pxlen, sizeof(net_addr_vpn6), prefix, rd }) + +#define NET_ADDR_ROA4(prefix,pxlen,max_pxlen,asn) \ + ((net_addr_roa4) { NET_ROA4, pxlen, sizeof(net_addr_roa4), prefix, max_pxlen, asn }) + +#define NET_ADDR_ROA6(prefix,pxlen,max_pxlen,asn) \ + ((net_addr_roa6) { NET_ROA6, pxlen, sizeof(net_addr_roa6), prefix, max_pxlen, asn }) + +#define NET_ADDR_FLOW4(prefix,pxlen,dlen) \ + ((net_addr_flow4) { NET_FLOW4, pxlen, sizeof(net_addr_ip4) + dlen, prefix }) + +#define NET_ADDR_FLOW6(prefix,pxlen,dlen) \ + ((net_addr_flow6) { NET_FLOW6, pxlen, sizeof(net_addr_ip6) + dlen, prefix }) + +#define NET_ADDR_MPLS(label) \ + ((net_addr_mpls) { NET_MPLS, 20, sizeof(net_addr_mpls), label }) + + +static inline void net_fill_ip4(net_addr *a, ip4_addr prefix, uint pxlen) +{ *(net_addr_ip4 *)a = NET_ADDR_IP4(prefix, pxlen); } + +static inline void net_fill_ip6(net_addr *a, ip6_addr prefix, uint pxlen) +{ *(net_addr_ip6 *)a = NET_ADDR_IP6(prefix, pxlen); } + +static inline void net_fill_vpn4(net_addr *a, ip4_addr prefix, uint pxlen, u64 rd) +{ *(net_addr_vpn4 *)a = NET_ADDR_VPN4(prefix, pxlen, rd); } + +static inline void net_fill_vpn6(net_addr *a, ip6_addr prefix, uint pxlen, u64 rd) +{ *(net_addr_vpn6 *)a = NET_ADDR_VPN6(prefix, pxlen, rd); } + +static inline void net_fill_roa4(net_addr *a, ip4_addr prefix, uint pxlen, uint max_pxlen, u32 asn) +{ *(net_addr_roa4 *)a = NET_ADDR_ROA4(prefix, pxlen, max_pxlen, asn); } + +static inline void net_fill_roa6(net_addr *a, ip6_addr prefix, uint pxlen, uint max_pxlen, u32 asn) +{ *(net_addr_roa6 *)a = NET_ADDR_ROA6(prefix, pxlen, max_pxlen, asn); } + +static inline void net_fill_mpls(net_addr *a, u32 label) +{ *(net_addr_mpls *)a = NET_ADDR_MPLS(label); } + +static inline void net_fill_ipa(net_addr *a, ip_addr prefix, uint pxlen) +{ + if (ipa_is_ip4(prefix)) + net_fill_ip4(a, ipa_to_ip4(prefix), pxlen); + else + net_fill_ip6(a, ipa_to_ip6(prefix), pxlen); +} + +static inline void net_fill_ip_host(net_addr *a, ip_addr prefix) +{ + if (ipa_is_ip4(prefix)) + net_fill_ip4(a, ipa_to_ip4(prefix), IP4_MAX_PREFIX_LENGTH); + else + net_fill_ip6(a, ipa_to_ip6(prefix), IP6_MAX_PREFIX_LENGTH); +} + +static inline void net_fill_flow4(net_addr *a, ip4_addr prefix, uint pxlen, byte *data, uint dlen) +{ + net_addr_flow4 *f = (void *) a; + *f = NET_ADDR_FLOW4(prefix, pxlen, dlen); + memcpy(f->data, data, dlen); +} + +static inline void net_fill_flow6(net_addr *a, ip6_addr prefix, uint pxlen, byte *data, uint dlen) +{ + net_addr_flow6 *f = (void *) a; + *f = NET_ADDR_FLOW6(prefix, pxlen, dlen); + memcpy(f->data, data, dlen); +} + +static inline int net_val_match(u8 type, u32 mask) +{ return !!((1 << type) & mask); } + +static inline int net_type_match(const net_addr *a, u32 mask) +{ return net_val_match(a->type, mask); } + +static inline int net_is_ip(const net_addr *a) +{ return (a->type == NET_IP4) || (a->type == NET_IP6); } + +static inline int net_is_roa(const net_addr *a) +{ return (a->type == NET_ROA4) || (a->type == NET_ROA6); } + +static inline int net_is_vpn(const net_addr *a) +{ return (a->type == NET_VPN4) || (a->type == NET_VPN6); } + + +static inline ip4_addr net4_prefix(const net_addr *a) +{ return ((net_addr_ip4 *) a)->prefix; } + +static inline ip6_addr net6_prefix(const net_addr *a) +{ return ((net_addr_ip6 *) a)->prefix; } + +static inline ip_addr net_prefix(const net_addr *a) +{ + switch (a->type) + { + case NET_IP4: + case NET_VPN4: + case NET_ROA4: + case NET_FLOW4: + return ipa_from_ip4(net4_prefix(a)); + + case NET_IP6: + case NET_VPN6: + case NET_ROA6: + case NET_FLOW6: + return ipa_from_ip6(net6_prefix(a)); + + case NET_MPLS: + default: + return IPA_NONE; + } +} + +static inline u32 net_mpls(const net_addr *a) +{ + if (a->type == NET_MPLS) + return ((net_addr_mpls *) a)->label; + + bug("Can't call net_mpls on non-mpls net_addr"); +} + +static inline uint net4_pxlen(const net_addr *a) +{ return a->pxlen; } + +static inline uint net6_pxlen(const net_addr *a) +{ return a->pxlen; } + +static inline uint net_pxlen(const net_addr *a) +{ return a->pxlen; } + +ip_addr net_pxmask(const net_addr *a); + +static inline u64 net_rd(const net_addr *a) +{ + switch (a->type) + { + case NET_VPN4: + return ((net_addr_vpn4 *)a)->rd; + case NET_VPN6: + return ((net_addr_vpn6 *)a)->rd; + } + return 0; +} + + +static inline int net_equal(const net_addr *a, const net_addr *b) +{ return (a->length == b->length) && !memcmp(a, b, a->length); } + +static inline int net_equal_ip4(const net_addr_ip4 *a, const net_addr_ip4 *b) +{ return !memcmp(a, b, sizeof(net_addr_ip4)); } + +static inline int net_equal_ip6(const net_addr_ip6 *a, const net_addr_ip6 *b) +{ return !memcmp(a, b, sizeof(net_addr_ip6)); } + +static inline int net_equal_vpn4(const net_addr_vpn4 *a, const net_addr_vpn4 *b) +{ return !memcmp(a, b, sizeof(net_addr_vpn4)); } + +static inline int net_equal_vpn6(const net_addr_vpn6 *a, const net_addr_vpn6 *b) +{ return !memcmp(a, b, sizeof(net_addr_vpn6)); } + +static inline int net_equal_roa4(const net_addr_roa4 *a, const net_addr_roa4 *b) +{ return !memcmp(a, b, sizeof(net_addr_roa4)); } + +static inline int net_equal_roa6(const net_addr_roa6 *a, const net_addr_roa6 *b) +{ return !memcmp(a, b, sizeof(net_addr_roa6)); } + +static inline int net_equal_flow4(const net_addr_flow4 *a, const net_addr_flow4 *b) +{ return net_equal((const net_addr *) a, (const net_addr *) b); } + +static inline int net_equal_flow6(const net_addr_flow6 *a, const net_addr_flow6 *b) +{ return net_equal((const net_addr *) a, (const net_addr *) b); } + +static inline int net_equal_mpls(const net_addr_mpls *a, const net_addr_mpls *b) +{ return !memcmp(a, b, sizeof(net_addr_mpls)); } + + +static inline int net_equal_prefix_roa4(const net_addr_roa4 *a, const net_addr_roa4 *b) +{ return ip4_equal(a->prefix, b->prefix) && (a->pxlen == b->pxlen); } + +static inline int net_equal_prefix_roa6(const net_addr_roa6 *a, const net_addr_roa6 *b) +{ return ip6_equal(a->prefix, b->prefix) && (a->pxlen == b->pxlen); } + + +static inline int net_zero_ip4(const net_addr_ip4 *a) +{ return !a->pxlen && ip4_zero(a->prefix); } + +static inline int net_zero_ip6(const net_addr_ip6 *a) +{ return !a->pxlen && ip6_zero(a->prefix); } + +static inline int net_zero_vpn4(const net_addr_vpn4 *a) +{ return !a->pxlen && ip4_zero(a->prefix) && !a->rd; } + +static inline int net_zero_vpn6(const net_addr_vpn6 *a) +{ return !a->pxlen && ip6_zero(a->prefix) && !a->rd; } + +static inline int net_zero_roa4(const net_addr_roa4 *a) +{ return !a->pxlen && ip4_zero(a->prefix) && !a->max_pxlen && !a->asn; } + +static inline int net_zero_roa6(const net_addr_roa6 *a) +{ return !a->pxlen && ip6_zero(a->prefix) && !a->max_pxlen && !a->asn; } + +static inline int net_zero_flow4(const net_addr_flow4 *a) +{ return !a->pxlen && ip4_zero(a->prefix) && !a->data; } + +static inline int net_zero_flow6(const net_addr_flow6 *a) +{ return !a->pxlen && ip6_zero(a->prefix) && !a->data; } + +static inline int net_zero_mpls(const net_addr_mpls *a) +{ return !a->label; } + + +static inline int net_compare_ip4(const net_addr_ip4 *a, const net_addr_ip4 *b) +{ return ip4_compare(a->prefix, b->prefix) ?: uint_cmp(a->pxlen, b->pxlen); } + +static inline int net_compare_ip6(const net_addr_ip6 *a, const net_addr_ip6 *b) +{ return ip6_compare(a->prefix, b->prefix) ?: uint_cmp(a->pxlen, b->pxlen); } + +static inline int net_compare_vpn4(const net_addr_vpn4 *a, const net_addr_vpn4 *b) +{ return u64_cmp(a->rd, b->rd) ?: ip4_compare(a->prefix, b->prefix) ?: uint_cmp(a->pxlen, b->pxlen); } + +static inline int net_compare_vpn6(const net_addr_vpn6 *a, const net_addr_vpn6 *b) +{ return u64_cmp(a->rd, b->rd) ?: ip6_compare(a->prefix, b->prefix) ?: uint_cmp(a->pxlen, b->pxlen); } + +static inline int net_compare_roa4(const net_addr_roa4 *a, const net_addr_roa4 *b) +{ return ip4_compare(a->prefix, b->prefix) ?: uint_cmp(a->pxlen, b->pxlen) ?: uint_cmp(a->max_pxlen, b->max_pxlen) ?: uint_cmp(a->asn, b->asn); } + +static inline int net_compare_roa6(const net_addr_roa6 *a, const net_addr_roa6 *b) +{ return ip6_compare(a->prefix, b->prefix) ?: uint_cmp(a->pxlen, b->pxlen) ?: uint_cmp(a->max_pxlen, b->max_pxlen) ?: uint_cmp(a->asn, b->asn); } + +static inline int net_compare_flow4(const net_addr_flow4 *a, const net_addr_flow4 *b) +{ return ip4_compare(a->prefix, b->prefix) ?: uint_cmp(a->pxlen, b->pxlen) ?: uint_cmp(a->length, b->length) ?: memcmp(a->data, b->data, a->length - sizeof(net_addr_flow4)); } + +static inline int net_compare_flow6(const net_addr_flow6 *a, const net_addr_flow6 *b) +{ return ip6_compare(a->prefix, b->prefix) ?: uint_cmp(a->pxlen, b->pxlen) ?: uint_cmp(a->length, b->length) ?: memcmp(a->data, b->data, a->length - sizeof(net_addr_flow6)); } + +static inline int net_compare_mpls(const net_addr_mpls *a, const net_addr_mpls *b) +{ return uint_cmp(a->label, b->label); } + +int net_compare(const net_addr *a, const net_addr *b); + + +static inline void net_copy(net_addr *dst, const net_addr *src) +{ memcpy(dst, src, src->length); } + +static inline void net_copy_ip4(net_addr_ip4 *dst, const net_addr_ip4 *src) +{ memcpy(dst, src, sizeof(net_addr_ip4)); } + +static inline void net_copy_ip6(net_addr_ip6 *dst, const net_addr_ip6 *src) +{ memcpy(dst, src, sizeof(net_addr_ip6)); } + +static inline void net_copy_vpn4(net_addr_vpn4 *dst, const net_addr_vpn4 *src) +{ memcpy(dst, src, sizeof(net_addr_vpn4)); } + +static inline void net_copy_vpn6(net_addr_vpn6 *dst, const net_addr_vpn6 *src) +{ memcpy(dst, src, sizeof(net_addr_vpn6)); } + +static inline void net_copy_roa4(net_addr_roa4 *dst, const net_addr_roa4 *src) +{ memcpy(dst, src, sizeof(net_addr_roa4)); } + +static inline void net_copy_roa6(net_addr_roa6 *dst, const net_addr_roa6 *src) +{ memcpy(dst, src, sizeof(net_addr_roa6)); } + +static inline void net_copy_flow4(net_addr_flow4 *dst, const net_addr_flow4 *src) +{ memcpy(dst, src, src->length); } + +static inline void net_copy_flow6(net_addr_flow6 *dst, const net_addr_flow6 *src) +{ memcpy(dst, src, src->length); } + +static inline void net_copy_mpls(net_addr_mpls *dst, const net_addr_mpls *src) +{ memcpy(dst, src, sizeof(net_addr_mpls)); } + + +/* XXXX */ +static inline u32 u64_hash(u64 a) +{ return u32_hash(a); } + +static inline u32 net_hash_ip4(const net_addr_ip4 *n) +{ return ip4_hash(n->prefix) ^ ((u32) n->pxlen << 26); } + +static inline u32 net_hash_ip6(const net_addr_ip6 *n) +{ return ip6_hash(n->prefix) ^ ((u32) n->pxlen << 26); } + +static inline u32 net_hash_vpn4(const net_addr_vpn4 *n) +{ return ip4_hash(n->prefix) ^ ((u32) n->pxlen << 26) ^ u64_hash(n->rd); } + +static inline u32 net_hash_vpn6(const net_addr_vpn6 *n) +{ return ip6_hash(n->prefix) ^ ((u32) n->pxlen << 26) ^ u64_hash(n->rd); } + +static inline u32 net_hash_roa4(const net_addr_roa4 *n) +{ return ip4_hash(n->prefix) ^ ((u32) n->pxlen << 26); } + +static inline u32 net_hash_roa6(const net_addr_roa6 *n) +{ return ip6_hash(n->prefix) ^ ((u32) n->pxlen << 26); } + +static inline u32 net_hash_flow4(const net_addr_flow4 *n) +{ return ip4_hash(n->prefix) ^ ((u32) n->pxlen << 26); } + +static inline u32 net_hash_flow6(const net_addr_flow6 *n) +{ return ip6_hash(n->prefix) ^ ((u32) n->pxlen << 26); } + +static inline u32 net_hash_mpls(const net_addr_mpls *n) +{ return n->label; } + +u32 net_hash(const net_addr *a); + + +static inline int net_validate_px4(const ip4_addr prefix, uint pxlen) +{ + return (pxlen <= IP4_MAX_PREFIX_LENGTH) && + ip4_zero(ip4_and(prefix, ip4_not(ip4_mkmask(pxlen)))); +} + +static inline int net_validate_px6(const ip6_addr prefix, uint pxlen) +{ + return (pxlen <= IP6_MAX_PREFIX_LENGTH) && + ip6_zero(ip6_and(prefix, ip6_not(ip6_mkmask(pxlen)))); +} + +static inline int net_validate_ip4(const net_addr_ip4 *n) +{ return net_validate_px4(n->prefix, n->pxlen); } + +static inline int net_validate_ip6(const net_addr_ip6 *n) +{ return net_validate_px6(n->prefix, n->pxlen); } + +static inline int net_validate_vpn4(const net_addr_vpn4 *n) +{ return net_validate_px4(n->prefix, n->pxlen); } + +static inline int net_validate_vpn6(const net_addr_vpn6 *n) +{ return net_validate_px6(n->prefix, n->pxlen); } + +static inline int net_validate_roa4(const net_addr_roa4 *n) +{ + return net_validate_px4(n->prefix, n->pxlen) && + (n->pxlen <= n->max_pxlen) && (n->max_pxlen <= IP4_MAX_PREFIX_LENGTH); +} + +static inline int net_validate_roa6(const net_addr_roa6 *n) +{ + return net_validate_px6(n->prefix, n->pxlen) && + (n->pxlen <= n->max_pxlen) && (n->max_pxlen <= IP6_MAX_PREFIX_LENGTH); +} + +// FIXME: Better check, call flow_validate? +static inline int net_validate_flow4(const net_addr_flow4 *n) +{ return net_validate_px4(n->prefix, n->pxlen); } + +static inline int net_validate_flow6(const net_addr_flow6 *n) +{ return net_validate_px6(n->prefix, n->pxlen); } + +static inline int net_validate_mpls(const net_addr_mpls *n) +{ return n->label < (1 << 20); } + +int net_validate(const net_addr *N); + + +static inline void net_normalize_ip4(net_addr_ip4 *n) +{ n->prefix = ip4_and(n->prefix, ip4_mkmask(n->pxlen)); } + +static inline void net_normalize_ip6(net_addr_ip6 *n) +{ n->prefix = ip6_and(n->prefix, ip6_mkmask(n->pxlen)); } + +static inline void net_normalize_vpn4(net_addr_vpn4 *n) +{ net_normalize_ip4((net_addr_ip4 *) n); } + +static inline void net_normalize_vpn6(net_addr_vpn6 *n) +{ net_normalize_ip6((net_addr_ip6 *) n); } + +void net_normalize(net_addr *N); + + +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); + +int ipa_in_netX(const ip_addr A, const net_addr *N); +int net_in_netX(const net_addr *A, const net_addr *N); + + +#endif diff --git a/lib/patmatch_test.c b/lib/patmatch_test.c new file mode 100644 index 00000000..d65f316f --- /dev/null +++ b/lib/patmatch_test.c @@ -0,0 +1,149 @@ +/* + * BIRD Library -- Pattern Matching Tests + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include "test/birdtest.h" + +#include "nest/bird.h" +#include "lib/string.h" + +#define MATCH (int) { 1 } +#define NOMATCH (int) { 0 } + +struct match_pair { + byte *pattern; + byte *data; +}; + +static int +test_matching(void *out_, const void *in_, const void *expected_out_) +{ + int *out = out_; + const struct match_pair *in = in_; + const int *expected_out = expected_out_; + + *out = patmatch(in->pattern, in->data); + + return *out == *expected_out; +} + +static void +fmt_match_pair(char *buf, size_t size, const void *data) +{ + const struct match_pair *mp = data; + snprintf(buf, size, "pattern: '%s', subject: '%s'", mp->pattern, mp->data); +} + +static void +fmt_match_result(char *buf, size_t size, const void *data) +{ + const int *result = data; + snprintf(buf, size, *result ? "match" : "no-match"); +} + +static int +t_matching(void) +{ + struct bt_pair test_vectors[] = { + { + .in = & (struct match_pair) { + .pattern = "", + .data = "", + }, + .out = & MATCH, + }, + { + .in = & (struct match_pair) { + .pattern = "*", + .data = "", + }, + .out = & MATCH, + }, + { + .in = & (struct match_pair) { + .pattern = "\\*", + .data = "*", + }, + .out = & MATCH, + }, + { + .in = & (struct match_pair) { + .pattern = "\\*", + .data = "a", + }, + .out = & NOMATCH, + }, + { + .in = & (struct match_pair) { + .pattern = "?", + .data = "", + }, + .out = & NOMATCH, + }, + { + .in = & (struct match_pair) { + .pattern = "abcdefghijklmnopqrstuvwxyz", + .data = "abcdefghijklmnopqrstuvwxyz", + }, + .out = & MATCH, + }, + { + .in = & (struct match_pair) { + .pattern = "??????????????????????????", + .data = "abcdefghijklmnopqrstuvwxyz", + }, + .out = & MATCH, + }, + { + .in = & (struct match_pair) { + .pattern = "*abcdefghijklmnopqrstuvwxyz*", + .data = "abcdefghijklmnopqrstuvwxyz", + }, + .out = & MATCH, + }, + { + .in = & (struct match_pair) { + .pattern = "ab?defg*jklmnop*stu*wxy*z", + .data = "abcdefghijklmnopqrstuvwxyz", + }, + .out = & MATCH, + }, + { + .in = & (struct match_pair) { + .pattern = "abcdefghijklmnopqrstuvwxyz", + .data = "abcdefghijklmnopqrtuvwxyz", + }, + .out = & NOMATCH, + }, + { + .in = & (struct match_pair) { + .pattern = "abcdefghijklmnopqr?uvwxyz", + .data = "abcdefghijklmnopqrstuvwxyz", + }, + .out = & NOMATCH, + }, + { + .in = & (struct match_pair) { + .pattern = "aa*aaaaa?aaaaaaaaaaaaaaaaaaa", + .data = "aaaaaaaaaaaaaaaaaaaaaaaaaa", + }, + .out = & NOMATCH, + }, + }; + + return bt_assert_batch(test_vectors, test_matching, fmt_match_pair, fmt_match_result); +} + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + bt_test_suite(t_matching, "Pattern matching"); + + return bt_exit_value(); +} diff --git a/lib/printf.c b/lib/printf.c index 4fd75c9b..8e3cbbcf 100644 --- a/lib/printf.c +++ b/lib/printf.c @@ -118,16 +118,16 @@ static char * number(char * str, long num, int base, int size, int precision, * @fmt: format string * @args: a list of arguments to be formatted * - * This functions acts like ordinary sprintf() except that it checks - * available space to avoid buffer overflows and it allows some more - * format specifiers: |%I| for formatting of IP addresses (any non-zero - * width is automatically replaced by standard IP address width which - * depends on whether we use IPv4 or IPv6; |%#I| gives hexadecimal format), - * |%R| for Router / Network ID (u32 value printed as IPv4 address) - * |%lR| for 64bit Router / Network ID (u64 value printed as eight :-separated octets) - * and |%m| resp. |%M| for error messages (uses strerror() to translate @errno code to - * message text). On the other hand, it doesn't support floating - * point numbers. + * This functions acts like ordinary sprintf() except that it checks available + * space to avoid buffer overflows and it allows some more format specifiers: + * |%I| for formatting of IP addresses (width of 1 is automatically replaced by + * standard IP address width which depends on whether we use IPv4 or IPv6; |%I4| + * or |%I6| can be used for explicit ip4_addr / ip6_addr arguments, |%N| for + * generic network addresses (net_addr *), |%R| for Router / Network ID (u32 + * value printed as IPv4 address), |%lR| for 64bit Router / Network ID (u64 + * value printed as eight :-separated octets) and |%m| resp. |%M| for error + * messages (uses strerror() to translate @errno code to message text). On the + * other hand, it doesn't support floating point numbers. * * Result: number of characters of the output string or -1 if * the buffer space was insufficient. @@ -141,7 +141,7 @@ int bvsnprintf(char *buf, int size, const char *fmt, va_list args) u64 X; char *str, *start; const char *s; - char ipbuf[MAX(STD_ADDRESS_P_LENGTH,ROUTER_ID_64_LENGTH)+1]; + char ipbuf[NET_MAX_TEXT_LENGTH+1]; struct iface *iface; int flags; /* flags to number() */ @@ -158,7 +158,7 @@ int bvsnprintf(char *buf, int size, const char *fmt, va_list args) *str++ = *fmt; continue; } - + /* process flags */ flags = 0; repeat: @@ -170,7 +170,7 @@ int bvsnprintf(char *buf, int size, const char *fmt, va_list args) case '#': flags |= SPECIAL; goto repeat; case '0': flags |= ZEROPAD; goto repeat; } - + /* get field width */ field_width = -1; if (is_digit(*fmt)) @@ -188,7 +188,7 @@ int bvsnprintf(char *buf, int size, const char *fmt, va_list args) /* get the precision */ precision = -1; if (*fmt == '.') { - ++fmt; + ++fmt; if (is_digit(*fmt)) precision = skip_atoi(&fmt); else if (*fmt == '*') { @@ -238,6 +238,14 @@ int bvsnprintf(char *buf, int size, const char *fmt, va_list args) case 'M': s = strerror(va_arg(args, int)); goto str; + case 'N': { + net_addr *n = va_arg(args, net_addr *); + if (field_width == 1) + field_width = net_max_text_length[n->type]; + net_format(n, ipbuf, sizeof(ipbuf)); + s = ipbuf; + goto str; + } case 's': s = va_arg(args, char *); if (!s) @@ -284,14 +292,35 @@ int bvsnprintf(char *buf, int size, const char *fmt, va_list args) /* IP address */ case 'I': - if (flags & SPECIAL) - ipa_ntox(va_arg(args, ip_addr), ipbuf); - else { - ipa_ntop(va_arg(args, ip_addr), ipbuf); - if (field_width == 1) - field_width = STD_ADDRESS_P_LENGTH; + if (fmt[1] == '4') { + /* Explicit IPv4 address */ + ip4_addr a = va_arg(args, ip4_addr); + ip4_ntop(a, ipbuf); + i = IP4_MAX_TEXT_LENGTH; + fmt++; + } else if (fmt[1] == '6') { + /* Explicit IPv6 address */ + ip6_addr a = va_arg(args, ip6_addr); + ip6_ntop(a, ipbuf); + i = IP6_MAX_TEXT_LENGTH; + fmt++; + } else { + /* Just IP address */ + ip_addr a = va_arg(args, ip_addr); + + if (ipa_is_ip4(a)) { + ip4_ntop(ipa_to_ip4(a), ipbuf); + i = IP4_MAX_TEXT_LENGTH; + } else { + ip6_ntop(ipa_to_ip6(a), ipbuf); + i = IP6_MAX_TEXT_LENGTH; + } } + s = ipbuf; + if (field_width == 1) + field_width = i; + goto str; /* Interface scope after link-local IP address */ @@ -311,7 +340,7 @@ int bvsnprintf(char *buf, int size, const char *fmt, va_list args) /* Router/Network ID - essentially IPv4 address in u32 value */ case 'R': - if(qualifier == 'l') { + if (qualifier == 'l') { X = va_arg(args, u64); bsprintf(ipbuf, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", ((X >> 56) & 0xff), @@ -326,11 +355,7 @@ int bvsnprintf(char *buf, int size, const char *fmt, va_list args) else { x = va_arg(args, u32); - bsprintf(ipbuf, "%d.%d.%d.%d", - ((x >> 24) & 0xff), - ((x >> 16) & 0xff), - ((x >> 8) & 0xff), - (x & 0xff)); + ip4_ntop(ip4_from_u32(x), ipbuf); } s = ipbuf; goto str; @@ -442,6 +467,10 @@ int buffer_vprint(buffer *buf, const char *fmt, va_list args) { int i = bvsnprintf((char *) buf->pos, buf->end - buf->pos, fmt, args); + + if ((i < 0) && (buf->pos < buf->end)) + *buf->pos = 0; + buf->pos = (i >= 0) ? (buf->pos + i) : buf->end; return i; } @@ -453,9 +482,12 @@ buffer_print(buffer *buf, const char *fmt, ...) int i; va_start(args, fmt); - i=bvsnprintf((char *) buf->pos, buf->end - buf->pos, fmt, args); + i = bvsnprintf((char *) buf->pos, buf->end - buf->pos, fmt, args); va_end(args); + if ((i < 0) && (buf->pos < buf->end)) + *buf->pos = 0; + buf->pos = (i >= 0) ? (buf->pos + i) : buf->end; return i; } @@ -464,13 +496,13 @@ void buffer_puts(buffer *buf, const char *str) { byte *bp = buf->pos; - byte *be = buf->end; + byte *be = buf->end - 1; while (bp < be && *str) *bp++ = *str++; - if (bp < be) + if (bp <= be) *bp = 0; - buf->pos = bp; + buf->pos = (bp < be) ? bp : buf->end; } diff --git a/lib/printf_test.c b/lib/printf_test.c new file mode 100644 index 00000000..3981d3da --- /dev/null +++ b/lib/printf_test.c @@ -0,0 +1,70 @@ +/* + * BIRD Library -- String Functions Tests + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include "test/birdtest.h" + +#include "lib/string.h" + +#define BSPRINTF(nw, res, buf, fmt, ...) \ + do { \ + int n = bsprintf(buf, fmt, ##__VA_ARGS__); \ + bt_assert_msg(n == nw, "fmt=\"%s\" returns n=%d, want %d", fmt, n, nw); \ + bt_assert_msg(buf[n] == 0, "fmt=\"%s\" buf[%d] should be \'\\0\', found 0x%02x", fmt, n, buf[n]); \ + bt_assert_msg(memcmp(buf, res, nw) == 0, "fmt=\"%s\" writes \"%*s\", want \"%*s\"", fmt, (n < nw ? n : nw), buf, nw, res); \ + } while (0) + +static int +t_simple(void) +{ + char buf[256]; + memset(buf, 0xa5, 256); + + BSPRINTF(0, "", buf, "", NULL); + BSPRINTF(1, "%", buf, "%%", NULL); + BSPRINTF(2, "%%", buf, "%%%%", NULL); + + BSPRINTF(1, "\x00", buf, "%c", 0); + BSPRINTF(1, "@", buf, "@", 64); + BSPRINTF(1, "\xff", buf, "%c", 0xff); + + errno = 5; + BSPRINTF(18, "Input/output error", buf, "%m"); + errno = 0; + + BSPRINTF(18, "Input/output error", buf, "%M", 5); + + BSPRINTF(11, "TeSt%StRiNg", buf, "%s", "TeSt%StRiNg"); + + if (sizeof(void *) == 4) + BSPRINTF(8, "1a15600d", buf, "%p", (void *) 0x1a15600d); + else + BSPRINTF(16, "00000fee1a15600d", buf, "%p", (void *) 0xfee1a15600d); + + long ln = 0; + BSPRINTF(10, "TeStStRiNg", buf, "TeStS%lntRiNg", &ln); + bt_assert_msg(ln == 5, "fmt=\"TeStS%%lntRiNg\", &ln makes ln=%ld, want 5", ln); + + BSPRINTF(2, "%d", buf, "%%d", 1); + BSPRINTF(1, "1", buf, "%d", 1); + BSPRINTF(2, "+1", buf, "%+d", 1); + BSPRINTF(2, " 1", buf, "% d", 1); + BSPRINTF(2, "-1", buf, "%d", -1); + BSPRINTF(11, "-2147483648", buf, "%d", -2147483648); + + return 1; +} + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + bt_test_suite(t_simple, "printf without varargs"); + + return bt_exit_value(); +} diff --git a/lib/resource.c b/lib/resource.c index 68718dfb..ab8c800f 100644 --- a/lib/resource.c +++ b/lib/resource.c @@ -31,7 +31,7 @@ struct pool { resource r; list inside; - char *name; + const char *name; }; static void pool_dump(resource *); @@ -61,7 +61,7 @@ static int indent; * parent pool. */ pool * -rp_new(pool *p, char *name) +rp_new(pool *p, const char *name) { pool *z = ralloc(p, &pool_class); z->name = name; diff --git a/lib/resource.h b/lib/resource.h index 1a62d389..761c6adc 100644 --- a/lib/resource.h +++ b/lib/resource.h @@ -37,7 +37,7 @@ struct resclass { typedef struct pool pool; void resource_init(void); -pool *rp_new(pool *, char *); /* Create new pool */ +pool *rp_new(pool *, const char *); /* Create new pool */ void rfree(void *); /* Free single resource */ void rdump(void *); /* Dump to debug output */ size_t rmemsize(void *res); /* Return size of memory used by the resource */ @@ -65,6 +65,11 @@ void *lp_allocu(linpool *, unsigned size); /* Unaligned */ void *lp_allocz(linpool *, unsigned size); /* With clear */ void lp_flush(linpool *); /* Free everything, but leave linpool */ +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)) + /* Slabs */ typedef struct slab slab; diff --git a/lib/slist_test.c b/lib/slist_test.c new file mode 100644 index 00000000..069e361c --- /dev/null +++ b/lib/slist_test.c @@ -0,0 +1,384 @@ +/* + * BIRD Library -- Safe Linked Lists Tests + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include "test/birdtest.h" + +#include "lib/slists.h" + +#define MAX_NUM 1000 + +static snode nodes[MAX_NUM]; +static slist lst; + +static void +show_list(void) +{ + bt_debug("\n"); + bt_debug("list.null is at %p and point to %p \n", &lst.null, lst.null); + bt_debug("list.head is at %p and point to %p \n", &lst.head, lst.head); + bt_debug("list.tail is at %p and point to %p \n", &lst.tail, lst.tail); + bt_debug("list.tail_readers is at %p and point to %p \n", &lst.tail_readers, lst.tail_readers); + + int i; + for (i = 0; i < MAX_NUM; i++) + bt_debug("n[%3i] is at %p, .prev (%p) points to %p, .next (%p) points to %p, .readers (%p) points to %p \n", + i, &nodes[i], &(nodes[i].prev), nodes[i].prev, &(nodes[i].next), nodes[i].next, &(nodes[i].readers), nodes[i].readers); +} + +static int +is_filled_list_well_linked(void) +{ + int i; + bt_assert(lst.head == &nodes[0]); + bt_assert(lst.tail == &nodes[MAX_NUM-1]); + bt_assert((void *) nodes[0].prev == (void *) &lst.head); + bt_assert((void *) nodes[MAX_NUM-1].next == (void *) &lst.null); + + for (i = 0; i < MAX_NUM; i++) + { + if (i < (MAX_NUM-1)) + bt_assert(nodes[i].next == &nodes[i+1]); + + if (i > 0) + bt_assert(nodes[i].prev == &nodes[i-1]); + } + + return 1; +} + +static int +is_empty_list_well_unlinked(void) +{ + bt_assert(lst.head == SNODE &lst.null); + bt_assert(lst.tail == SNODE &lst.head); + + bt_assert(EMPTY_SLIST(lst)); + + return 1; +} + +static void +init_list__(slist *l, struct snode nodes[]) +{ + s_init_list(l); + + int i; + for (i = 0; i < MAX_NUM; i++) + { + nodes[i].next = NULL; + nodes[i].prev = NULL; + } +} + +static void +init_list_(void) +{ + init_list__(&lst, nodes); +} + +static int +t_add_tail(void) +{ + int i; + + init_list_(); + for (i = 0; i < MAX_NUM; i++) + { + s_add_tail(&lst, &nodes[i]); + bt_debug("."); + bt_assert(lst.tail == &nodes[i]); + bt_assert(lst.head == &nodes[0]); + bt_assert((void *) nodes[i].next == (void *) &lst.null); + if (i > 0) + { + bt_assert(nodes[i-1].next == &nodes[i]); + bt_assert(nodes[i].prev == &nodes[i-1]); + } + } + + bt_assert(is_filled_list_well_linked()); + + return 1; +} + +static int +t_add_head(void) +{ + int i; + + init_list_(); + for (i = MAX_NUM-1; i >= 0; i--) + { + s_add_head(&lst, &nodes[i]); + bt_debug("."); + bt_assert(lst.head == &nodes[i]); + bt_assert(lst.tail == &nodes[MAX_NUM-1]); + if (i < MAX_NUM-1) + { + bt_assert(nodes[i+1].prev == &nodes[i]); + bt_assert(nodes[i].next == &nodes[i+1]); + } + } + + bt_assert(is_filled_list_well_linked()); + + return 1; +} + +static void +insert_node_(snode *n, snode *after) +{ + s_insert_node(n, after); + bt_debug("."); +} + +static int +t_insert_node(void) +{ + int i; + + init_list_(); + + // add first node + insert_node_(&nodes[0], SNODE &lst.head); + + // add odd nodes + for (i = 2; i < MAX_NUM; i+=2) + insert_node_(&nodes[i], &nodes[i-2]); + + // add even nodes + for (i = 1; i < MAX_NUM; i+=2) + insert_node_(&nodes[i], &nodes[i-1]); + + bt_debug("\n"); + bt_assert(is_filled_list_well_linked()); + + return 1; +} + +static void +fill_list2(slist *l, snode nodes[]) +{ + int i; + for (i = 0; i < MAX_NUM; i++) + s_add_tail(l, &nodes[i]); +} + +static void +fill_list(void) +{ + fill_list2(&lst, SNODE nodes); +} + + +static int +t_remove_node(void) +{ + int i; + + init_list_(); + + /* Fill & Remove & Check */ + fill_list(); + for (i = 0; i < MAX_NUM; i++) + s_rem_node(&nodes[i]); + bt_assert(is_empty_list_well_unlinked()); + + /* Fill & Remove the half of nodes & Check & Remove the rest nodes & Check */ + fill_list(); + for (i = 0; i < MAX_NUM; i+=2) + s_rem_node(&nodes[i]); + + int tail_node_index = (MAX_NUM % 2) ? MAX_NUM - 2 : MAX_NUM - 1; + bt_assert(lst.head == &nodes[1]); + bt_assert(lst.tail == &nodes[tail_node_index]); + bt_assert(nodes[tail_node_index].next == SNODE &lst.null); + + for (i = 1; i < MAX_NUM; i+=2) + { + if (i > 1) + bt_assert(nodes[i].prev == &nodes[i-2]); + if (i < tail_node_index) + bt_assert(nodes[i].next == &nodes[i+2]); + } + + for (i = 1; i < MAX_NUM; i+=2) + s_rem_node(&nodes[i]); + bt_assert(is_empty_list_well_unlinked()); + + return 1; +} + +static int +t_add_tail_list(void) +{ + snode nodes2[MAX_NUM]; + slist l2; + + init_list__(&lst, SNODE &nodes); + fill_list2(&lst, SNODE &nodes); + + init_list__(&l2, SNODE &nodes2); + fill_list2(&l2, SNODE &nodes2); + + s_add_tail_list(&lst, &l2); + + bt_assert(nodes[MAX_NUM-1].next == &nodes2[0]); + bt_assert(nodes2[0].prev == &nodes[MAX_NUM-1]); + bt_assert(lst.tail == &nodes2[MAX_NUM-1]); + + return 1; +} + +void +dump(const char *str, slist *a) +{ + snode *x; + + bt_debug("%s \n", str); + for (x = SHEAD(*a); x; x = x->next) + { + siterator *i, *j; + bt_debug("%p", x); + j = (siterator *) x; + for (i = x->readers; i; i = i->next) + { + if (i->prev != j) + bt_debug(" ???"); + j = i; + bt_debug(" [%p:%p]", i, i->node); + } + bt_debug("\n"); + } + bt_debug("---\n"); +} + +static int +t_iterator_walk(void) +{ + snode *node; + siterator iter; + + init_list_(); + fill_list(); + + int k; + int i = 0; + + show_list(); + + s_init(&iter, &lst); + WALK_SLIST(node, lst) + { + s_get(&iter); + s_put(&iter, node); + bt_debug("node->readers: %p, iter: %p, nodes[%d].readers: %p, node: %p, nodes[i]: %p, node->next: %p \n", + node->readers, &iter, i, nodes[i].readers, node, &(nodes[i]), node->next); + bt_assert(node->readers == &iter); + bt_assert(node->readers == nodes[i].readers); + bt_assert(node == &(nodes[i])); + for (k = 0; k < MAX_NUM; k++) + if (k != i) + bt_assert(nodes[k].readers == NULL); + + dump("",&lst); + i++; + } + + return 1; +} + +static int +t_original(void) +{ + slist a, b; + snode *x, *y; + siterator i, j; + + s_init_list(&a); + s_init_list(&b); + x = xmalloc(sizeof(*x)); + s_add_tail(&a, x); + x = xmalloc(sizeof(*x)); + s_add_tail(&a, x); + x = xmalloc(sizeof(*x)); + s_add_tail(&a, x); + dump("1", &a); + + s_init(&i, &a); + s_init(&j, &a); + dump("2", &a); + + x = s_get(&i); + bt_debug("Got %p\n", x); + dump("3", &a); + + s_put(&i, x->next); + dump("4", &a); + + y = s_get(&j); + while (y) + { + s_put(&j, y); + dump("5*", &a); + y = s_get(&j)->next; + } + + dump("5 done", &a); + + s_rem_node(a.head->next); + dump("6 (deletion)", &a); + + s_put(&i, s_get(&i)->next); + dump("6 (relink)", &a); + + x = xmalloc(sizeof(*x)); + s_add_tail(&b, x); + dump("7 (second list)", &b); + + s_add_tail_list(&b, &a); + dump("8 (after merge)", &b); + + return 1; +} + +static int +t_safe_del_walk(void) +{ + init_list_(); + fill_list(); + + show_list(); + + snode *node, *node_next; + WALK_SLIST_DELSAFE(node,node_next, lst) + { + bt_debug("Will remove node %p \n", node); + s_rem_node(SNODE node); + } + bt_assert(is_empty_list_well_unlinked()); + + return 1; +} + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + bt_test_suite(t_add_tail, "Adding nodes to tail of list"); + bt_test_suite(t_add_head, "Adding nodes to head of list"); + bt_test_suite(t_insert_node, "Inserting nodes to list"); + bt_test_suite(t_remove_node, "Removing nodes from list"); + bt_test_suite(t_add_tail_list, "At the tail of a list adding the another list"); + bt_test_suite(t_iterator_walk, "Iterator walk"); + bt_test_suite(t_safe_del_walk, "WALK_SLIST_DELSAFE and s_rem_node all nodes"); + bt_test_suite(t_original, "The original BIRD test suit for SLIST"); + + return bt_exit_value(); +} diff --git a/lib/slists.c b/lib/slists.c index 6e0df39e..00f3c84f 100644 --- a/lib/slists.c +++ b/lib/slists.c @@ -150,85 +150,3 @@ s_add_tail_list(slist *to, slist *l) to->tail = q; s_merge((snode *) &l->null, (snode *) &to->null); } - -#ifdef TEST - -#include "lib/resource.h" -#include <stdio.h> - -void dump(char *c, slist *a) -{ - snode *x; - - puts(c); - for(x=SHEAD(*a); x; x=x->next) - { - siterator *i, *j; - printf("%p", x); - j = (siterator *) x; - for(i=x->readers; i; i=i->next) - { - if (i->prev != j) - printf(" ???"); - j = i; - printf(" [%p:%p]", i, i->node); - } - putchar('\n'); - } - puts("---"); -} - -int main(void) -{ - slist a, b; - snode *x, *y; - siterator i, j; - - s_init_list(&a); - s_init_list(&b); - x = xmalloc(sizeof(*x)); - s_add_tail(&a, x); - x = xmalloc(sizeof(*x)); - s_add_tail(&a, x); - x = xmalloc(sizeof(*x)); - s_add_tail(&a, x); - dump("1", &a); - - s_init(&i, &a); - s_init(&j, &a); - dump("2", &a); - - x = s_get(&i); - printf("Got %p\n", x); - dump("3", &a); - - s_put(&i, x->next); - dump("4", &a); - - y = s_get(&j); - while (y) - { - s_put(&j, y); - dump("5*", &a); - y = s_get(&j)->next; - } - - dump("5 done", &a); - - s_rem_node(a.head->next); - dump("6 (deletion)", &a); - - s_put(&i, s_get(&i)->next); - dump("6 (relink)", &a); - - x = xmalloc(sizeof(*x)); - s_add_tail(&b, x); - dump("7 (second list)", &b); - - s_add_tail_list(&b, &a); - dump("8 (after merge)", &b); - - return 0; -} - -#endif diff --git a/lib/socket.h b/lib/socket.h index 2da52127..d5281b83 100644 --- a/lib/socket.h +++ b/lib/socket.h @@ -10,16 +10,40 @@ #define _BIRD_SOCKET_H_ #include <errno.h> -// #include <sys/socket.h> #include "lib/resource.h" +#ifdef HAVE_LIBSSH +#define LIBSSH_LEGACY_0_4 +#include <libssh/libssh.h> +#endif + +#ifdef HAVE_LIBSSH +struct ssh_sock { + const char *username; /* (Required) SSH user name */ + const char *server_hostkey_path; /* (Optional) Filepath to the SSH public key of remote side, can be knownhost file */ + const char *client_privkey_path; /* (Optional) Filepath to the SSH private key of BIRD */ + const char *subsystem; /* (Optional) Name of SSH subsytem */ + ssh_session session; /* Internal */ + ssh_channel channel; /* Internal */ + int state; /* Internal */ +#define SK_SSH_CONNECT 0 /* Start state */ +#define SK_SSH_SERVER_KNOWN 1 /* Internal */ +#define SK_SSH_USERAUTH 2 /* Internal */ +#define SK_SSH_CHANNEL 3 /* Internal */ +#define SK_SSH_SESSION 4 /* Internal */ +#define SK_SSH_SUBSYSTEM 5 /* Internal */ +#define SK_SSH_ESTABLISHED 6 /* Final state */ +}; +#endif typedef struct birdsock { resource r; pool *pool; /* Pool where incoming connections should be allocated (for SK_xxx_PASSIVE) */ int type; /* Socket type */ + int subtype; /* Socket subtype */ void *data; /* User data */ ip_addr saddr, daddr; /* IPA_NONE = unspecified */ + const char *host; /* Alternative to daddr, NULL = unspecified */ uint sport, dport; /* 0 = unspecified (for IP: protocol type) */ int tos; /* TOS / traffic class, -1 = default */ int priority; /* Local socket priority, -1 = default */ @@ -45,14 +69,15 @@ typedef struct birdsock { uint lifindex; /* local interface that received the datagram */ /* laddr and lifindex are valid only if SKF_LADDR_RX flag is set to request it */ - int af; /* Address family (AF_INET, AF_INET6 or 0 for non-IP) of fd */ + int af; /* System-dependend adress family (e.g. AF_INET) */ int fd; /* System-dependent data */ int index; /* Index in poll buffer */ int rcv_ttl; /* TTL of last received datagram */ node n; void *rbuf_alloc, *tbuf_alloc; char *password; /* Password for MD5 authentication */ - char *err; /* Error message */ + const char *err; /* Error message */ + struct ssh_sock *ssh; /* Used in SK_SSH */ } sock; sock *sock_new(pool *); /* Allocate new socket */ @@ -68,19 +93,12 @@ void sk_set_tbsize(sock *s, uint val); /* Resize TX buffer, keeping content */ void sk_set_tbuf(sock *s, void *tbuf); /* Switch TX buffer, NULL-> return to internal */ void sk_dump_all(void); +int sk_is_ipv4(sock *s); /* True if socket is IPv4 */ +int sk_is_ipv6(sock *s); /* True if socket is IPv6 */ + static inline int sk_send_buffer_empty(sock *sk) { return sk->tbuf == sk->tpos; } - -#ifdef IPV6 -#define sk_is_ipv4(X) 0 -#define sk_is_ipv6(X) 1 -#else -#define sk_is_ipv4(X) 1 -#define sk_is_ipv6(X) 0 -#endif - - int sk_setup_multicast(sock *s); /* Prepare UDP or IP socket for multicasting */ int sk_join_group(sock *s, ip_addr maddr); /* Join multicast group on sk iface */ int sk_leave_group(sock *s, ip_addr maddr); /* Leave multicast group on sk iface */ @@ -99,7 +117,6 @@ extern int sk_priority_control; /* Suggested priority for control traffic, shou /* Socket flags */ -#define SKF_V4ONLY 0x01 /* Use IPv4 for IP sockets */ #define SKF_V6ONLY 0x02 /* Use IPV6_V6ONLY socket option */ #define SKF_LADDR_RX 0x04 /* Report local address for RX packets */ #define SKF_TTL_RX 0x08 /* Report TTL / Hop Limit for RX packets */ @@ -123,26 +140,38 @@ extern int sk_priority_control; /* Suggested priority for control traffic, shou #define SK_MAGIC 7 /* Internal use by sysdep code */ #define SK_UNIX_PASSIVE 8 #define SK_UNIX 9 +#define SK_SSH_ACTIVE 10 /* - - * * - ? - DA = host */ +#define SK_SSH 11 + +/* + * Socket subtypes + */ + +#define SK_IPV4 1 +#define SK_IPV6 2 /* - * For SK_UDP or SK_IP sockets setting DA/DP allows to use sk_send(), - * otherwise sk_send_to() must be used. + * For TCP/IP sockets, Address family (IPv4 or IPv6) can be specified either + * explicitly (SK_IPV4 or SK_IPV6) or implicitly (based on saddr, daddr). But + * these specifications must be consistent. + * + * For SK_UDP or SK_IP sockets setting DA/DP allows to use sk_send(), otherwise + * sk_send_to() must be used. * - * For SK_IP sockets setting DP specifies protocol number, which is used - * for both receiving and sending. + * For SK_IP sockets setting DP specifies protocol number, which is used for + * both receiving and sending. * - * For multicast on SK_UDP or SK_IP sockets set IF and TTL, - * call sk_setup_multicast() to enable multicast on that socket, - * and then use sk_join_group() and sk_leave_group() to manage - * a set of received multicast groups. + * For multicast on SK_UDP or SK_IP sockets set IF and TTL, call + * sk_setup_multicast() to enable multicast on that socket, and then use + * sk_join_group() and sk_leave_group() to manage a set of received multicast + * groups. * - * For datagram (SK_UDP, SK_IP) sockets, there are two ways to handle - * source address. The socket could be bound to it using bind() - * syscall, but that also forbids the reception of multicast packets, - * or the address could be set on per-packet basis using platform - * dependent options (but these are not available in some corner - * cases). The first way is used when SKF_BIND is specified, the - * second way is used otherwise. + * For datagram (SK_UDP, SK_IP) sockets, there are two ways to handle source + * address. The socket could be bound to it using bind() syscall, but that also + * forbids the reception of multicast packets, or the address could be set on + * per-packet basis using platform dependent options (but these are not + * available in some corner cases). The first way is used when SKF_BIND is + * specified, the second way is used otherwise. */ #endif diff --git a/lib/string.h b/lib/string.h index 75cb88dd..0d34f9c5 100644 --- a/lib/string.h +++ b/lib/string.h @@ -13,6 +13,8 @@ #include <string.h> #include <strings.h> +#include "lib/resource.h" + int bsprintf(char *str, const char *fmt, ...); int bvsprintf(char *str, const char *fmt, va_list args); int bsnprintf(char *str, int size, const char *fmt, ...); @@ -39,6 +41,15 @@ xstrdup(const char *c) return z; } +static inline char * +lp_strdup(linpool *lp, const char *c) +{ + size_t l = strlen(c) + 1; + char *z = lp_allocu(lp, l); + memcpy(z, c, l); + return z; +} + static inline void memset32(void *D, u32 val, uint n) { diff --git a/lib/unaligned.h b/lib/unaligned.h index dc777fbf..0da1fdb4 100644 --- a/lib/unaligned.h +++ b/lib/unaligned.h @@ -17,6 +17,7 @@ * if possible. */ +#include "sysdep/unix/endian.h" #include "lib/string.h" static inline u16 @@ -28,6 +29,13 @@ get_u16(const void *p) } static inline u32 +get_u24(const void *P) +{ + const byte *p = P; + return (p[0] << 16) + (p[1] << 8) + p[2]; +} + +static inline u32 get_u32(const void *p) { u32 x; @@ -52,6 +60,13 @@ put_u16(void *p, u16 x) } static inline void +put_u24(void *p, u32 x) +{ + x = htonl(x); + memcpy(p, ((char *) &x) + 1, 3); +} + +static inline void put_u32(void *p, u32 x) { x = htonl(x); @@ -68,4 +83,22 @@ put_u64(void *p, u64 x) memcpy(p+4, &xl, 4); } +static inline void +get_u32s(const void *p, u32 *x, int n) +{ + int i; + memcpy(x, p, 4*n); + for (i = 0; i < n; i++) + x[i] = ntohl(x[i]); +} + +static inline void +put_u32s(void *p, const u32 *x, int n) +{ + int i; + for (i = 0; i < n; i++) + put_u32((byte *) p + 4*i, x[i]); +} + + #endif diff --git a/misc/bird.spec b/misc/bird.spec index bfbfc484..1da0c813 100644 --- a/misc/bird.spec +++ b/misc/bird.spec @@ -1,6 +1,6 @@ Summary: BIRD Internet Routing Daemon Name: bird -Version: 1.6.3 +Version: 2.0.0 Release: 1 Copyright: GPL Group: Networking/Daemons diff --git a/nest/Makefile b/nest/Makefile index e6928668..884d3950 100644 --- a/nest/Makefile +++ b/nest/Makefile @@ -1,6 +1,8 @@ -source=rt-table.c rt-fib.c rt-attr.c rt-roa.c proto.c iface.c rt-dev.c password.c cli.c locks.c cmds.c neighbor.c \ - a-path.c a-set.c -root-rel=../ -dir-name=nest +src := a-path.c a-set.c cli.c cmds.c iface.c locks.c neighbor.c password.c proto.c rt-attr.c rt-dev.c rt-fib.c rt-show.c rt-table.c +obj := $(src-o-files) +$(all-daemon) +$(cf-local) -include ../Rules +tests_src := a-set_test.c a-path_test.c +tests_targets := $(tests_targets) $(tests-target-files) +tests_objs := $(tests_objs) $(src-o-files) diff --git a/nest/a-path.c b/nest/a-path.c index b453f702..32ffc72c 100644 --- a/nest/a-path.c +++ b/nest/a-path.c @@ -20,263 +20,528 @@ #define put_as put_u32 #define get_as get_u32 -#define BS 4 +#define BS 4 /* Default block size of ASN (autonomous system number) */ -struct adata * -as_path_prepend(struct linpool *pool, struct adata *olda, u32 as) +#define BAD(DSC, VAL) ({ err_dsc = DSC; err_val = VAL; goto bad; }) + +int +as_path_valid(byte *data, uint len, int bs, int confed, char *err, uint elen) { - struct adata *newa; + byte *pos = data; + char *err_dsc = NULL; + uint err_val = 0; + + while (len) + { + if (len < 2) + BAD("segment framing error", 0); + + /* Process one AS path segment */ + uint type = pos[0]; + uint slen = 2 + bs * pos[1]; + + if (len < slen) + BAD("segment framing error", len); - if (olda->length && olda->data[0] == AS_PATH_SEQUENCE && olda->data[1] < 255) - /* Starting with sequence => just prepend the AS number */ + switch (type) { - int nl = olda->length + BS; - newa = lp_alloc(pool, sizeof(struct adata) + nl); - newa->length = nl; - newa->data[0] = AS_PATH_SEQUENCE; - newa->data[1] = olda->data[1] + 1; - memcpy(newa->data + BS + 2, olda->data + 2, olda->length - 2); + case AS_PATH_SET: + case AS_PATH_SEQUENCE: + break; + + case AS_PATH_CONFED_SEQUENCE: + case AS_PATH_CONFED_SET: + if (!confed) + BAD("AS_CONFED* segment", type); + break; + + default: + BAD("unknown segment", type); } - else /* Create new path segment */ + + if (pos[1] == 0) + BAD("zero-length segment", type); + + pos += slen; + len -= slen; + } + + return 1; + +bad: + if (err) + if (bsnprintf(err, elen, "%s (%u) at %d", err_dsc, err_val, (int) (pos - data)) < 0) + err[0] = 0; + + return 0; +} + +int +as_path_16to32(byte *dst, byte *src, uint len) +{ + byte *dst0 = dst; + byte *end = src + len; + uint i, n; + + while (src < end) + { + n = src[1]; + *dst++ = *src++; + *dst++ = *src++; + + for (i = 0; i < n; i++) { - int nl = olda->length + BS + 2; - newa = lp_alloc(pool, sizeof(struct adata) + nl); - newa->length = nl; - newa->data[0] = AS_PATH_SEQUENCE; - newa->data[1] = 1; - memcpy(newa->data + BS + 2, olda->data, olda->length); + put_u32(dst, get_u16(src)); + src += 2; + dst += 4; } - put_as(newa->data + 2, as); - return newa; + } + + return dst - dst0; } int -as_path_convert_to_old(struct adata *path, byte *dst, int *new_used) +as_path_32to16(byte *dst, byte *src, uint len) { - byte *src = path->data; - byte *src_end = src + path->length; - byte *dst_start = dst; - u32 as; - int i, n; - *new_used = 0; + byte *dst0 = dst; + byte *end = src + len; + uint i, n; + + while (src < end) + { + n = src[1]; + *dst++ = *src++; + *dst++ = *src++; - while (src < src_end) + for (i = 0; i < n; i++) { - n = src[1]; - *dst++ = *src++; - *dst++ = *src++; + put_u16(dst, get_u32(src)); + src += 4; + dst += 2; + } + } - for(i=0; i<n; i++) - { - as = get_u32(src); - if (as > 0xFFFF) - { - as = AS_TRANS; - *new_used = 1; - } - put_u16(dst, as); - src += 4; - dst += 2; - } + return dst - dst0; +} + +int +as_path_contains_as4(const struct adata *path) +{ + const byte *pos = path->data; + const byte *end = pos + path->length; + uint i, n; + + while (pos < end) + { + n = pos[1]; + pos += 2; + + for (i = 0; i < n; i++) + { + if (get_as(pos) > 0xFFFF) + return 1; + + pos += BS; } + } - return dst - dst_start; + return 0; } int -as_path_convert_to_new(struct adata *path, byte *dst, int req_as) +as_path_contains_confed(const struct adata *path) { - byte *src = path->data; - byte *src_end = src + path->length; - byte *dst_start = dst; - u32 as; - int i, t, n; + const byte *pos = path->data; + const byte *end = pos + path->length; + while (pos < end) + { + uint type = pos[0]; + uint slen = 2 + BS * pos[1]; + + if ((type == AS_PATH_CONFED_SEQUENCE) || + (type == AS_PATH_CONFED_SET)) + return 1; - while ((src < src_end) && (req_as > 0)) + pos += slen; + } + + return 0; +} + +struct adata * +as_path_strip_confed(struct linpool *pool, const struct adata *path) +{ + struct adata *res = lp_alloc_adata(pool, path->length); + const byte *src = path->data; + const byte *end = src + path->length; + byte *dst = res->data; + + while (src < end) + { + uint type = src[0]; + uint slen = 2 + BS * src[1]; + + /* Copy regular segments */ + if ((type == AS_PATH_SET) || (type == AS_PATH_SEQUENCE)) { - t = *src++; - n = *src++; + memcpy(dst, src, slen); + dst += slen; + } - if (t == AS_PATH_SEQUENCE) - { - if (n > req_as) - n = req_as; + src += slen; + } - req_as -= n; - } - else // t == AS_PATH_SET - req_as--; + /* Fix the result length */ + res->length = dst - res->data; + + return res; +} - *dst++ = t; - *dst++ = n; +struct adata * +as_path_prepend2(struct linpool *pool, const struct adata *op, int seq, u32 as) +{ + struct adata *np; + const byte *pos = op->data; + uint len = op->length; - for(i=0; i<n; i++) - { - as = get_u16(src); - put_u32(dst, as); - src += 2; - dst += 4; - } + if (len && (pos[0] == seq) && (pos[1] < 255)) + { + /* Starting with matching segment => just prepend the AS number */ + np = lp_alloc_adata(pool, len + BS); + np->data[0] = seq; + np->data[1] = pos[1] + 1; + put_as(np->data + 2, as); + + uint dlen = BS * pos[1]; + memcpy(np->data + 2 + BS, pos + 2, dlen); + ADVANCE(pos, len, 2 + dlen); + } + else + { + /* Create a new path segment */ + np = lp_alloc_adata(pool, len + 2 + BS); + np->data[0] = seq; + np->data[1] = 1; + put_as(np->data + 2, as); + } + + if (len) + { + byte *dst = np->data + 2 + BS * np->data[1]; + + memcpy(dst, pos, len); + } + + return np; +} + + +struct adata * +as_path_to_old(struct linpool *pool, const struct adata *path) +{ + struct adata *res = lp_alloc_adata(pool, path->length); + byte *pos = res->data; + byte *end = pos + res->length; + uint i, n; + u32 as; + + /* Copy the whole path */ + memcpy(res->data, path->data, path->length); + + /* Replace 32-bit AS numbers with AS_TRANS */ + while (pos < end) + { + n = pos[1]; + pos += 2; + + for (i = 0; i < n; i++) + { + as = get_as(pos); + if (as > 0xFFFF) + put_as(pos, AS_TRANS); + + pos += BS; } + } - return dst - dst_start; + return res; } +/* + * Cut the path to the length @num, measured to the usual path metric. Note that + * AS_CONFED_* segments have zero length and must be added if they are on edge. + * In contrast to other as_path_* functions, @path is modified in place. + */ void -as_path_format(struct adata *path, byte *buf, uint size) +as_path_cut(struct adata *path, uint num) { - byte *p = path->data; - byte *e = p + path->length; - byte *end = buf + size - 16; - int sp = 1; - int l, isset; + byte *pos = path->data; + byte *end = pos + path->length; - while (p < e) + while (pos < end) + { + uint t = pos[0]; + uint l = pos[1]; + uint n = 0; + + switch (t) { - if (buf > end) - { - strcpy(buf, " ..."); - return; - } - isset = (*p++ == AS_PATH_SET); - l = *p++; - if (isset) - { - if (!sp) - *buf++ = ' '; - *buf++ = '{'; - sp = 0; - } - while (l-- && buf <= end) - { - if (!sp) - *buf++ = ' '; - buf += bsprintf(buf, "%u", get_as(p)); - p += BS; - sp = 0; - } - if (isset) - { - *buf++ = ' '; - *buf++ = '}'; - sp = 0; - } + case AS_PATH_SET: n = 1; break; + case AS_PATH_SEQUENCE: n = l; break; + case AS_PATH_CONFED_SEQUENCE: n = 0; break; + case AS_PATH_CONFED_SET: n = 0; break; + default: bug("as_path_cut: Invalid path segment"); } - *buf = 0; + + /* Cannot add whole segment, so try partial one and finish */ + if (num < n) + { + if (num) + { + pos[1] = num; + pos += 2 + BS * num; + } + + break; + } + + num -= n; + pos += 2 + BS * l; + } + + path->length = pos - path->data; } -int -as_path_getlen(struct adata *path) +/* + * Merge (concatenate) paths @p1 and @p2 and return the result. + * In contrast to other as_path_* functions, @p1 and @p2 may be reused. + */ +struct adata * +as_path_merge(struct linpool *pool, struct adata *p1, struct adata *p2) +{ + if (p1->length == 0) + return p2; + + if (p2->length == 0) + return p1; + + struct adata *res = lp_alloc_adata(pool, p1->length + p2->length); + memcpy(res->data, p1->data, p1->length); + memcpy(res->data + p1->length, p2->data, p2->length); + + return res; +} + +void +as_path_format(const struct adata *path, byte *bb, uint size) { - return as_path_getlen_int(path, BS); + buffer buf = { .start = bb, .pos = bb, .end = bb + size }, *b = &buf; + const byte *pos = path->data; + const byte *end = pos + path->length; + const char *ops, *cls; + + b->pos[0] = 0; + + while (pos < end) + { + uint type = pos[0]; + uint len = pos[1]; + pos += 2; + + switch (type) + { + case AS_PATH_SET: ops = "{"; cls = "}"; break; + case AS_PATH_SEQUENCE: ops = NULL; cls = NULL; break; + case AS_PATH_CONFED_SEQUENCE: ops = "("; cls = ")"; break; + case AS_PATH_CONFED_SET: ops = "({"; cls = "})"; break; + default: bug("Invalid path segment"); + } + + if (ops) + buffer_puts(b, ops); + + while (len--) + { + buffer_print(b, len ? "%u " : "%u", get_as(pos)); + pos += BS; + } + + if (cls) + buffer_puts(b, cls); + + if (pos < end) + buffer_puts(b, " "); + } + + /* Handle overflow */ + if (b->pos == b->end) + strcpy(b->end - 12, "..."); } int -as_path_getlen_int(struct adata *path, int bs) +as_path_getlen(const struct adata *path) { - int res = 0; - u8 *p = path->data; - u8 *q = p+path->length; - int len; + const byte *pos = path->data; + const byte *end = pos + path->length; + uint res = 0; - while (p<q) + while (pos < end) + { + uint t = pos[0]; + uint l = pos[1]; + uint n = 0; + + switch (t) { - switch (*p++) - { - case AS_PATH_SET: len = *p++; res++; p += bs * len; break; - case AS_PATH_SEQUENCE: len = *p++; res += len; p += bs * len; break; - default: bug("as_path_getlen: Invalid path segment"); - } + case AS_PATH_SET: n = 1; break; + case AS_PATH_SEQUENCE: n = l; break; + case AS_PATH_CONFED_SEQUENCE: n = 0; break; + case AS_PATH_CONFED_SET: n = 0; break; + default: bug("as_path_getlen: Invalid path segment"); } + + res += n; + pos += 2 + BS * l; + } + return res; } int -as_path_get_last(struct adata *path, u32 *orig_as) +as_path_get_last(const struct adata *path, u32 *orig_as) { + const byte *pos = path->data; + const byte *end = pos + path->length; int found = 0; - u32 res = 0; - u8 *p = path->data; - u8 *q = p+path->length; - int len; + u32 val = 0; - while (p<q) + while (pos < end) + { + uint type = pos[0]; + uint len = pos[1]; + pos += 2; + + if (!len) + continue; + + switch (type) { - switch (*p++) - { - case AS_PATH_SET: - if (len = *p++) - { - found = 0; - p += BS * len; - } - break; - case AS_PATH_SEQUENCE: - if (len = *p++) - { - found = 1; - res = get_as(p + BS * (len - 1)); - p += BS * len; - } - break; - default: bug("Invalid path segment"); - } + case AS_PATH_SET: + case AS_PATH_CONFED_SET: + found = 0; + break; + + case AS_PATH_SEQUENCE: + case AS_PATH_CONFED_SEQUENCE: + val = get_as(pos + BS * (len - 1)); + found = 1; + break; + + default: + bug("Invalid path segment"); } + pos += BS * len; + } + if (found) - *orig_as = res; + *orig_as = val; return found; } u32 -as_path_get_last_nonaggregated(struct adata *path) +as_path_get_last_nonaggregated(const struct adata *path) { - u8 *p = path->data; - u8 *q = p+path->length; - u32 res = 0; - int len; + const byte *pos = path->data; + const byte *end = pos + path->length; + u32 val = 0; - while (p<q) + while (pos < end) + { + uint type = pos[0]; + uint len = pos[1]; + pos += 2; + + if (!len) + continue; + + switch (type) { - switch (*p++) - { - case AS_PATH_SET: - return res; + case AS_PATH_SET: + case AS_PATH_CONFED_SET: + return val; - case AS_PATH_SEQUENCE: - if (len = *p++) - res = get_as(p + BS * (len - 1)); - p += BS * len; - break; + case AS_PATH_SEQUENCE: + case AS_PATH_CONFED_SEQUENCE: + val = get_as(pos + BS * (len - 1)); + break; - default: bug("Invalid path segment"); - } + default: + bug("Invalid path segment"); } - return res; -} + pos += BS * len; + } + return val; +} int -as_path_get_first(struct adata *path, u32 *last_as) +as_path_get_first(const struct adata *path, u32 *last_as) { - u8 *p = path->data; + const u8 *p = path->data; if ((path->length == 0) || (p[0] != AS_PATH_SEQUENCE) || (p[1] == 0)) return 0; - else + + *last_as = get_as(p+2); + return 1; +} + +int +as_path_get_first_regular(const struct adata *path, u32 *last_as) +{ + const byte *pos = path->data; + const byte *end = pos + path->length; + + while (pos < end) + { + uint type = pos[0]; + uint len = pos[1]; + pos += 2; + + switch (type) { - *last_as = get_as(p+2); + case AS_PATH_SET: + return 0; + + case AS_PATH_SEQUENCE: + if (len == 0) + return 0; + + *last_as = get_as(pos); return 1; + + case AS_PATH_CONFED_SEQUENCE: + case AS_PATH_CONFED_SET: + break; + + default: + bug("Invalid path segment"); } + + pos += BS * len; + } + + return 0; } int -as_path_contains(struct adata *path, u32 as, int min) +as_path_contains(const struct adata *path, u32 as, int min) { - u8 *p = path->data; - u8 *q = p+path->length; + const u8 *p = path->data; + const u8 *q = p+path->length; int num = 0; int i, n; @@ -296,10 +561,10 @@ as_path_contains(struct adata *path, u32 as, int min) } int -as_path_match_set(struct adata *path, struct f_tree *set) +as_path_match_set(const struct adata *path, struct f_tree *set) { - u8 *p = path->data; - u8 *q = p+path->length; + const u8 *p = path->data; + const u8 *q = p+path->length; int i, n; while (p<q) @@ -325,8 +590,8 @@ as_path_filter(struct linpool *pool, struct adata *path, struct f_tree *set, u32 return NULL; int len = path->length; - u8 *p = path->data; - u8 *q = path->data + len; + const u8 *p = path->data; + const u8 *q = path->data + len; u8 *d, *d2; int i, bt, sn, dn; u8 buf[len]; @@ -388,51 +653,57 @@ struct pm_pos u8 mark; union { - char *sp; + const char *sp; u32 asn; } val; }; static int -parse_path(struct adata *path, struct pm_pos *pos) +parse_path(const struct adata *path, struct pm_pos *pp) { - u8 *p = path->data; - u8 *q = p + path->length; - struct pm_pos *opos = pos; - int i, len; + const byte *pos = path->data; + const byte *end = pos + path->length; + struct pm_pos *op = pp; + uint i; + while (pos < end) + { + uint type = pos[0]; + uint len = pos[1]; + pos += 2; - while (p < q) - switch (*p++) + switch (type) + { + case AS_PATH_SET: + case AS_PATH_CONFED_SET: + pp->set = 1; + pp->mark = 0; + pp->val.sp = pos - 1; + pp++; + + pos += BS * len; + break; + + case AS_PATH_SEQUENCE: + case AS_PATH_CONFED_SEQUENCE: + for (i = 0; i < len; i++) { - case AS_PATH_SET: - pos->set = 1; - pos->mark = 0; - pos->val.sp = p; - len = *p; - p += 1 + BS * len; - pos++; - break; - - case AS_PATH_SEQUENCE: - len = *p++; - for (i = 0; i < len; i++) - { - pos->set = 0; - pos->mark = 0; - pos->val.asn = get_as(p); - p += BS; - pos++; - } - break; - - default: - bug("as_path_match: Invalid path component"); + pp->set = 0; + pp->mark = 0; + pp->val.asn = get_as(pos); + pp++; + + pos += BS; } - - return pos - opos; -} + break; + + default: + bug("Invalid path segment"); + } + } + return pp - op; +} static int pm_match(struct pm_pos *pos, u32 asn, u32 asn2) @@ -441,7 +712,7 @@ pm_match(struct pm_pos *pos, u32 asn, u32 asn2) if (! pos->set) return ((pos->val.asn >= asn) && (pos->val.asn <= asn2)); - u8 *p = pos->val.sp; + const u8 *p = pos->val.sp; int len = *p++; int i; @@ -463,7 +734,7 @@ pm_mark(struct pm_pos *pos, int i, int plen, int *nl, int *nh) if (pos[i].set) pos[i].mark = 1; - + for (j = i + 1; (j < plen) && pos[j].set && (! pos[j].mark); j++) pos[j].mark = 1; pos[j].mark = 1; @@ -478,7 +749,7 @@ pm_mark(struct pm_pos *pos, int i, int plen, int *nl, int *nh) } /* AS path matching is nontrivial. Because AS path can - * contain sets, it is not a plain wildcard matching. A set + * contain sets, it is not a plain wildcard matching. A set * in an AS path is interpreted as it might represent any * sequence of AS numbers from that set (possibly with * repetitions). So it is also a kind of a pattern, @@ -499,9 +770,8 @@ pm_mark(struct pm_pos *pos, int i, int plen, int *nl, int *nh) * (auxiliary position after last real position in AS path) * is marked. */ - int -as_path_match(struct adata *path, struct f_path_mask *mask) +as_path_match(const struct adata *path, struct f_path_mask *mask) { struct pm_pos pos[2048 + 1]; int plen = parse_path(path, pos); @@ -517,7 +787,7 @@ as_path_match(struct adata *path, struct f_path_mask *mask) l = h = 0; pos[0].mark = 1; - + while (mask) { /* We remove this mark to not step after pos[plen] */ diff --git a/nest/a-path_test.c b/nest/a-path_test.c new file mode 100644 index 00000000..5e122396 --- /dev/null +++ b/nest/a-path_test.c @@ -0,0 +1,220 @@ +/* + * BIRD -- Path Operations Tests + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include "test/birdtest.h" +#include "test/bt-utils.h" + +#include "nest/route.h" +#include "nest/attrs.h" +#include "lib/resource.h" + +#define TESTS_NUM 30 +#define AS_PATH_LENGTH 1000 + +#if AS_PATH_LENGTH > AS_PATH_MAXLEN +#warning "AS_PATH_LENGTH should be <= AS_PATH_MAXLEN" +#endif + +static int +t_as_path_match(void) +{ + resource_init(); + + int round; + for (round = 0; round < TESTS_NUM; round++) + { + struct adata empty_as_path = {}; + struct adata *as_path = &empty_as_path; + u32 first_prepended, last_prepended; + first_prepended = last_prepended = 0; + struct linpool *lp = lp_new_default(&root_pool); + + struct f_path_mask mask[AS_PATH_LENGTH] = {}; + int i; + for (i = 0; i < AS_PATH_LENGTH; i++) + { + u32 val = bt_random(); + as_path = as_path_prepend(lp, as_path, val); + bt_debug("Prepending ASN: %10u \n", val); + + if (i == 0) + first_prepended = val; + if (i == AS_PATH_LENGTH-1) + last_prepended = val; + + mask[i].kind = PM_ASN; + mask[i].val = val; + if (i) + mask[i].next = &mask[i-1]; + } + + bt_assert_msg(as_path_match(as_path, &mask[AS_PATH_LENGTH-1]), "Mask should match with AS path"); + + u32 asn; + + bt_assert(as_path_get_first(as_path, &asn)); + bt_assert_msg(asn == last_prepended, "as_path_get_first() should return the last prepended ASN"); + + bt_assert(as_path_get_last(as_path, &asn)); + bt_assert_msg(asn == first_prepended, "as_path_get_last() should return the first prepended ASN"); + + rfree(lp); + } + + return 1; +} + +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); + + uint i; + for (i = 4294967285; i <= 4294967294; i++) + { + as_path = as_path_prepend(lp, as_path, i); + bt_debug("Prepending ASN: %10u \n", i); + } + +#define BUFFER_SIZE 120 + byte buf[BUFFER_SIZE] = {}; + + as_path_format(&empty_as_path, buf, BUFFER_SIZE); + bt_assert_msg(strcmp(buf, "") == 0, "Buffer(%zu): '%s'", strlen(buf), buf); + + as_path_format(as_path, buf, BUFFER_SIZE); + bt_assert_msg(strcmp(buf, "4294967294 4294967293 4294967292 4294967291 4294967290 4294967289 4294967288 4294967287 4294967286 4294967285") == 0, "Buffer(%zu): '%s'", strlen(buf), buf); + +#define SMALL_BUFFER_SIZE 25 + byte buf2[SMALL_BUFFER_SIZE] = {}; + as_path_format(as_path, buf2, SMALL_BUFFER_SIZE); + bt_assert_msg(strcmp(buf2, "4294967294 42...") == 0, "Small Buffer(%zu): '%s'", strlen(buf2), buf2); + + rfree(lp); + + return 1; +} + +static int +count_asn_in_array(const u32 *array, u32 asn) +{ + int counts_of_contains = 0; + int u; + for (u = 0; u < AS_PATH_LENGTH; u++) + if (array[u] == asn) + counts_of_contains++; + return counts_of_contains; +} + +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); + + u32 as_nums[AS_PATH_LENGTH] = {}; + int i; + for (i = 0; i < AS_PATH_LENGTH; i++) + { + u32 val = bt_random(); + as_nums[i] = val; + as_path = as_path_prepend(lp, as_path, val); + } + + for (i = 0; i < AS_PATH_LENGTH; i++) + { + int counts_of_contains = count_asn_in_array(as_nums, as_nums[i]); + bt_assert_msg(as_path_contains(as_path, as_nums[i], counts_of_contains), "AS Path should contains %d-times number %d", counts_of_contains, as_nums[i]); + + bt_assert(as_path_filter(lp, as_path, NULL, as_nums[i], 0) != NULL); + bt_assert(as_path_filter(lp, as_path, NULL, as_nums[i], 1) != NULL); + } + + for (i = 0; i < 10000; i++) + { + u32 test_val = bt_random(); + int counts_of_contains = count_asn_in_array(as_nums, test_val); + int result = as_path_contains(as_path, test_val, (counts_of_contains == 0 ? 1 : counts_of_contains)); + + if (counts_of_contains) + bt_assert_msg(result, "As path should contain %d-times the number %u", counts_of_contains, test_val); + else + bt_assert_msg(result == 0, "As path should not contain the number %u", test_val); + } + + rfree(lp); + + return 1; +} + +#if 0 +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); +#define AS_PATH_LENGTH_FOR_CONVERTING_TEST 10 + + int i; + for (i = 0; i < AS_PATH_LENGTH_FOR_CONVERTING_TEST; i++) + as_path = as_path_prepend(lp, as_path, i); + + bt_debug("data length: %u \n", as_path->length); + + byte buffer[100] = {}; + int used_size = as_path_convert_to_new(as_path, buffer, AS_PATH_LENGTH_FOR_CONVERTING_TEST-1); + bt_debug("as_path_convert_to_new: len %d \n%s\n", used_size, buffer); + for (i = 0; i < used_size; i++) + { + bt_debug("\\03%d", buffer[i]); + } + bt_debug("\n"); + bt_assert(memcmp(buffer, + "\032\039\030\030\030\030\030\030\030\039\030\030\030\030\030\030\030\038\030\030\030\030\030\030" + "\030\037\030\030\030\030\030\030\030\036\030\030\030\030", + 38)); + + bzero(buffer, sizeof(buffer)); + int new_used; + used_size = as_path_convert_to_old(as_path, buffer, &new_used); + bt_debug("as_path_convert_to_old: len %d, new_used: %d \n", used_size, new_used); + for (i = 0; i < used_size; i++) + { + bt_debug("\\03%d", buffer[i]); + } + bt_debug("\n"); + bt_assert(memcmp(buffer, + "\032\0310\030\039\030\038\030\037\030\036\030\035\030\034\030\033\030\032\030\031\030\030", + 22)); + + return 1; +} +#endif + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + 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"); + bt_test_suite(t_path_include, "Testing including a AS number in AS path"); + // bt_test_suite(t_as_path_converting, "Testing as_path_convert_to_*() output constancy"); + + return bt_exit_value(); +} diff --git a/nest/a-set.c b/nest/a-set.c index a6c07f45..a2fb6953 100644 --- a/nest/a-set.c +++ b/nest/a-set.c @@ -7,6 +7,8 @@ * Can be freely distributed and used under the terms of the GNU GPL. */ +#include <stdlib.h> + #include "nest/bird.h" #include "nest/route.h" #include "nest/attrs.h" @@ -474,3 +476,85 @@ lc_set_union(struct linpool *pool, struct adata *l1, struct adata *l2) memcpy(res->data + l1->length, tmp, len); return res; } + + +struct adata * +ec_set_del_nontrans(struct linpool *pool, struct adata *set) +{ + adata *res = lp_alloc_adata(pool, set->length); + u32 *src = int_set_get_data(set); + u32 *dst = int_set_get_data(res); + int len = int_set_get_size(set); + int i; + + /* Remove non-transitive communities (EC_TBIT set) */ + for (i = 0; i < len; i += 2) + { + if (src[i] & EC_TBIT) + continue; + + *dst++ = src[i]; + *dst++ = src[i+1]; + } + + res->length = ((byte *) dst) - res->data; + + return res; +} + +static int +int_set_cmp(const void *X, const void *Y) +{ + const u32 *x = X, *y = Y; + return (*x < *y) ? -1 : (*x > *y) ? 1 : 0; +} + +struct adata * +int_set_sort(struct linpool *pool, struct adata *src) +{ + struct adata *dst = lp_alloc_adata(pool, src->length); + memcpy(dst->data, src->data, src->length); + qsort(dst->data, dst->length / 4, 4, int_set_cmp); + return dst; +} + + +static int +ec_set_cmp(const void *X, const void *Y) +{ + u64 x = ec_get(X, 0); + u64 y = ec_get(Y, 0); + return (x < y) ? -1 : (x > y) ? 1 : 0; +} + +struct adata * +ec_set_sort(struct linpool *pool, struct adata *src) +{ + struct adata *dst = lp_alloc_adata(pool, src->length); + memcpy(dst->data, src->data, src->length); + qsort(dst->data, dst->length / 8, 8, ec_set_cmp); + return dst; +} + + +static int +lc_set_cmp(const void *X, const void *Y) +{ + const u32 *x = X, *y = Y; + if (x[0] != y[0]) + return (x[0] > y[0]) ? 1 : -1; + if (x[1] != y[1]) + return (x[1] > y[1]) ? 1 : -1; + if (x[2] != y[2]) + return (x[2] > y[2]) ? 1 : -1; + return 0; +} + +struct adata * +lc_set_sort(struct linpool *pool, struct adata *src) +{ + struct adata *dst = lp_alloc_adata(pool, src->length); + memcpy(dst->data, src->data, src->length); + qsort(dst->data, dst->length / LCOMM_LENGTH, LCOMM_LENGTH, lc_set_cmp); + return dst; +} diff --git a/nest/a-set_test.c b/nest/a-set_test.c new file mode 100644 index 00000000..a5081f9f --- /dev/null +++ b/nest/a-set_test.c @@ -0,0 +1,260 @@ +/* + * BIRD -- Set/Community-list Operations Tests + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include "test/birdtest.h" +#include "test/bt-utils.h" + +#include "lib/net.h" +#include "nest/route.h" +#include "nest/attrs.h" +#include "lib/resource.h" + +#define SET_SIZE 10 +static struct adata *set_sequence; /* <0; SET_SIZE) */ +static struct adata *set_sequence_same; /* <0; SET_SIZE) */ +static struct adata *set_sequence_higher; /* <SET_SIZE; 2*SET_SIZE) */ +static struct adata *set_random; + +#define BUFFER_SIZE 1000 +static byte buf[BUFFER_SIZE] = {}; + +#define SET_SIZE_FOR_FORMAT_OUTPUT 10 + +struct linpool *lp; + +enum set_type +{ + SET_TYPE_INT, + SET_TYPE_EC +}; + +static void +generate_set_sequence(enum set_type type) +{ + struct adata empty_as_path = {}; + set_sequence = set_sequence_same = set_sequence_higher = set_random = &empty_as_path; + lp = lp_new_default(&root_pool); + + int i; + for (i = 0; i < SET_SIZE; i++) + { + if (type == SET_TYPE_INT) + { + set_sequence = int_set_add(lp, set_sequence, i); + set_sequence_same = int_set_add(lp, set_sequence_same, i); + set_sequence_higher = int_set_add(lp, set_sequence_higher, i + SET_SIZE); + set_random = int_set_add(lp, set_random, bt_random()); + } + else if (type == SET_TYPE_EC) + { + set_sequence = ec_set_add(lp, set_sequence, i); + set_sequence_same = ec_set_add(lp, set_sequence_same, i); + set_sequence_higher = ec_set_add(lp, set_sequence_higher, i + SET_SIZE); + set_random = ec_set_add(lp, set_random, (bt_random() << 32 | bt_random())); + } + else + bt_abort_msg("This should be unreachable"); + } +} + +/* + * SET INT TESTS + */ + +static int +t_set_int_contains(void) +{ + int i; + + resource_init(); + generate_set_sequence(SET_TYPE_INT); + + bt_assert(int_set_get_size(set_sequence) == SET_SIZE); + + for (i = 0; i < SET_SIZE; i++) + bt_assert(int_set_contains(set_sequence, i)); + bt_assert(int_set_contains(set_sequence, -1) == 0); + bt_assert(int_set_contains(set_sequence, SET_SIZE) == 0); + + int *data = int_set_get_data(set_sequence); + for (i = 0; i < SET_SIZE; i++) + bt_assert_msg(data[i] == i, "(data[i] = %d) == i = %d)", data[i], i); + + rfree(lp); + return 1; +} + +static int +t_set_int_union(void) +{ + resource_init(); + generate_set_sequence(SET_TYPE_INT); + + struct adata *set_union; + set_union = int_set_union(lp, set_sequence, set_sequence_same); + bt_assert(int_set_get_size(set_union) == SET_SIZE); + bt_assert(int_set_format(set_union, 0, 2, buf, BUFFER_SIZE) == 0); + + set_union = int_set_union(lp, set_sequence, set_sequence_higher); + bt_assert_msg(int_set_get_size(set_union) == SET_SIZE*2, "int_set_get_size(set_union) %d, SET_SIZE*2 %d", int_set_get_size(set_union), SET_SIZE*2); + bt_assert(int_set_format(set_union, 0, 2, buf, BUFFER_SIZE) == 0); + + rfree(lp); + return 1; +} + +static int +t_set_int_format(void) +{ + resource_init(); + generate_set_sequence(SET_TYPE_INT); + + set_sequence->length = 4 * SET_SIZE_FOR_FORMAT_OUTPUT; /* dirty */ + bt_assert(int_set_format(set_sequence, 0, 0, buf, BUFFER_SIZE) == 0); + bt_assert(strcmp(buf, "0.0.0.0 0.0.0.1 0.0.0.2 0.0.0.3 0.0.0.4 0.0.0.5 0.0.0.6 0.0.0.7 0.0.0.8 0.0.0.9") == 0); + + bzero(buf, BUFFER_SIZE); + bt_assert(int_set_format(set_sequence, 0, 2, buf, BUFFER_SIZE) == 0); + bt_assert(strcmp(buf, "0.0.0.2 0.0.0.3 0.0.0.4 0.0.0.5 0.0.0.6 0.0.0.7 0.0.0.8 0.0.0.9") == 0); + + bzero(buf, BUFFER_SIZE); + bt_assert(int_set_format(set_sequence, 1, 0, buf, BUFFER_SIZE) == 0); + bt_assert(strcmp(buf, "(0,0) (0,1) (0,2) (0,3) (0,4) (0,5) (0,6) (0,7) (0,8) (0,9)") == 0); + + rfree(lp); + return 1; +} + +static int +t_set_int_delete(void) +{ + resource_init(); + generate_set_sequence(SET_TYPE_INT); + + struct adata *deleting_sequence = set_sequence; + u32 i; + for (i = 0; i < SET_SIZE; i++) + { + deleting_sequence = int_set_del(lp, deleting_sequence, i); + bt_assert_msg(int_set_get_size(deleting_sequence) == (int) (SET_SIZE-1-i), + "int_set_get_size(deleting_sequence) %d == SET_SIZE-1-i %d", + int_set_get_size(deleting_sequence), + SET_SIZE-1-i); + } + + bt_assert(int_set_get_size(set_sequence) == SET_SIZE); + + return 1; +} + +/* + * SET EC TESTS + */ + +static int +t_set_ec_contains(void) +{ + u32 i; + + resource_init(); + generate_set_sequence(SET_TYPE_EC); + + bt_assert(ec_set_get_size(set_sequence) == SET_SIZE); + + for (i = 0; i < SET_SIZE; i++) + bt_assert(ec_set_contains(set_sequence, i)); + bt_assert(ec_set_contains(set_sequence, -1) == 0); + bt_assert(ec_set_contains(set_sequence, SET_SIZE) == 0); + +// int *data = ec_set_get_data(set_sequence); +// for (i = 0; i < SET_SIZE; i++) +// bt_assert_msg(data[i] == (SET_SIZE-1-i), "(data[i] = %d) == ((SET_SIZE-1-i) = %d)", data[i], SET_SIZE-1-i); + + rfree(lp); + return 1; +} + +static int +t_set_ec_union(void) +{ + resource_init(); + generate_set_sequence(SET_TYPE_EC); + + struct adata *set_union; + set_union = ec_set_union(lp, set_sequence, set_sequence_same); + bt_assert(ec_set_get_size(set_union) == SET_SIZE); + bt_assert(ec_set_format(set_union, 0, buf, BUFFER_SIZE) == 0); + + set_union = ec_set_union(lp, set_sequence, set_sequence_higher); + bt_assert_msg(ec_set_get_size(set_union) == SET_SIZE*2, "ec_set_get_size(set_union) %d, SET_SIZE*2 %d", ec_set_get_size(set_union), SET_SIZE*2); + bt_assert(ec_set_format(set_union, 0, buf, BUFFER_SIZE) == 0); + + rfree(lp); + return 1; +} + +static int +t_set_ec_format(void) +{ + resource_init(); + + struct adata empty_as_path = {}; + set_sequence = set_sequence_same = set_sequence_higher = set_random = &empty_as_path; + lp = lp_new_default(&root_pool); + + u64 i = 0; + set_sequence = ec_set_add(lp, set_sequence, i); + for (i = 1; i < SET_SIZE_FOR_FORMAT_OUTPUT; i++) + set_sequence = ec_set_add(lp, set_sequence, i + ((i%2) ? ((u64)EC_RO << 48) : ((u64)EC_RT << 48))); + + bt_assert(ec_set_format(set_sequence, 0, buf, BUFFER_SIZE) == 0); + bt_assert_msg(strcmp(buf, "(unknown 0x0, 0, 0) (ro, 0, 1) (rt, 0, 2) (ro, 0, 3) (rt, 0, 4) (ro, 0, 5) (rt, 0, 6) (ro, 0, 7) (rt, 0, 8) (ro, 0, 9)") == 0, + "ec_set_format() returns '%s'", buf); + + rfree(lp); + return 1; +} + +static int +t_set_ec_delete(void) +{ + resource_init(); + generate_set_sequence(SET_TYPE_EC); + + struct adata *deleting_sequence = set_sequence; + u32 i; + for (i = 0; i < SET_SIZE; i++) + { + deleting_sequence = ec_set_del(lp, deleting_sequence, i); + bt_assert_msg(ec_set_get_size(deleting_sequence) == (int) (SET_SIZE-1-i), + "ec_set_get_size(deleting_sequence) %d == SET_SIZE-1-i %d", + ec_set_get_size(deleting_sequence), SET_SIZE-1-i); + } + + bt_assert(ec_set_get_size(set_sequence) == SET_SIZE); + + return 1; +} + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + 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"); + bt_test_suite(t_set_int_union, "Testing sets of integers: union"); + bt_test_suite(t_set_int_delete, "Testing sets of integers: delete"); + + bt_test_suite(t_set_ec_contains, "Testing sets of Extended Community values: contains, get_data"); + bt_test_suite(t_set_ec_format, "Testing sets of Extended Community values: format"); + bt_test_suite(t_set_ec_union, "Testing sets of Extended Community values: union"); + bt_test_suite(t_set_ec_delete, "Testing sets of Extended Community values: delete"); + + return bt_exit_value(); +} diff --git a/nest/attrs.h b/nest/attrs.h index a34e64d3..f66d4f04 100644 --- a/nest/attrs.h +++ b/nest/attrs.h @@ -10,6 +10,9 @@ #define _BIRD_ATTRS_H_ #include <stdint.h> +#include "lib/unaligned.h" +#include "nest/route.h" + /* a-path.c */ @@ -27,19 +30,30 @@ struct f_tree; -struct adata *as_path_prepend(struct linpool *pool, struct adata *olda, u32 as); -int as_path_convert_to_old(struct adata *path, byte *dst, int *new_used); -int as_path_convert_to_new(struct adata *path, byte *dst, int req_as); -void as_path_format(struct adata *path, byte *buf, uint size); -int as_path_getlen(struct adata *path); -int as_path_getlen_int(struct adata *path, int bs); -int as_path_get_first(struct adata *path, u32 *orig_as); -int as_path_get_last(struct adata *path, u32 *last_as); -u32 as_path_get_last_nonaggregated(struct adata *path); -int as_path_contains(struct adata *path, u32 as, int min); -int as_path_match_set(struct adata *path, struct f_tree *set); +int as_path_valid(byte *data, uint len, int bs, int confed, char *err, uint elen); +int as_path_16to32(byte *dst, byte *src, uint len); +int as_path_32to16(byte *dst, byte *src, uint len); +int as_path_contains_as4(const struct adata *path); +int as_path_contains_confed(const struct adata *path); +struct adata *as_path_strip_confed(struct linpool *pool, const struct adata *op); +struct adata *as_path_prepend2(struct linpool *pool, const struct adata *op, int seq, u32 as); +struct adata *as_path_to_old(struct linpool *pool, const struct adata *path); +void as_path_cut(struct adata *path, uint num); +struct adata *as_path_merge(struct linpool *pool, struct adata *p1, struct adata *p2); +void as_path_format(const struct adata *path, byte *buf, uint size); +int as_path_getlen(const struct adata *path); +int as_path_getlen_int(const struct adata *path, int bs); +int as_path_get_first(const struct adata *path, u32 *orig_as); +int as_path_get_first_regular(const struct adata *path, u32 *last_as); +int as_path_get_last(const struct adata *path, u32 *last_as); +u32 as_path_get_last_nonaggregated(const struct adata *path); +int as_path_contains(const struct adata *path, u32 as, int min); +int as_path_match_set(const struct adata *path, struct f_tree *set); struct adata *as_path_filter(struct linpool *pool, struct adata *path, struct f_tree *set, u32 key, int pos); +static inline struct adata *as_path_prepend(struct linpool *pool, const struct adata *path, u32 as) +{ return as_path_prepend2(pool, path, AS_PATH_SEQUENCE, as); } + #define PM_ASN 0 #define PM_QUESTION 1 @@ -54,7 +68,42 @@ struct f_path_mask { uintptr_t val2; }; -int as_path_match(struct adata *path, struct f_path_mask *mask); +int as_path_match(const struct adata *path, struct f_path_mask *mask); + + +/* Counterparts to appropriate as_path_* functions */ + +static inline int +aggregator_16to32(byte *dst, byte *src) +{ + put_u32(dst, get_u16(src)); + memcpy(dst+4, src+2, 4); + return 8; +} + +static inline int +aggregator_32to16(byte *dst, byte *src) +{ + put_u16(dst, get_u32(src)); + memcpy(dst+2, src+4, 4); + return 6; +} + +static inline int +aggregator_contains_as4(struct adata *a) +{ + return get_u32(a->data) > 0xFFFF; +} + +static inline struct adata * +aggregator_to_old(struct linpool *pool, struct adata *a) +{ + struct adata *d = lp_alloc_adata(pool, 8); + put_u32(d->data, 0xFFFF); + memcpy(d->data + 4, a->data + 4, 4); + return d; +} + /* a-set.c */ @@ -143,5 +192,9 @@ struct adata *int_set_union(struct linpool *pool, struct adata *l1, struct adata struct adata *ec_set_union(struct linpool *pool, struct adata *l1, struct adata *l2); struct adata *lc_set_union(struct linpool *pool, struct adata *l1, struct adata *l2); +struct adata *ec_set_del_nontrans(struct linpool *pool, struct adata *set); +struct adata *int_set_sort(struct linpool *pool, struct adata *src); +struct adata *ec_set_sort(struct linpool *pool, struct adata *src); +struct adata *lc_set_sort(struct linpool *pool, struct adata *src); #endif diff --git a/nest/bird.h b/nest/bird.h index 3c7d749b..55712abe 100644 --- a/nest/bird.h +++ b/nest/bird.h @@ -12,5 +12,6 @@ #include "sysdep/config.h" #include "lib/birdlib.h" #include "lib/ip.h" +#include "lib/net.h" #endif @@ -313,7 +313,8 @@ cli_new(void *priv) c->event->hook = cli_event; c->event->data = c; c->cont = cli_hello; - c->parser_pool = lp_new(c->pool, 4096); + c->parser_pool = lp_new_default(c->pool); + c->show_pool = lp_new_default(c->pool); c->rx_buf = mb_alloc(c->pool, CLI_RX_BUF_SIZE); ev_schedule(c->event); return c; @@ -38,6 +38,7 @@ typedef struct cli { int last_reply; int restricted; /* CLI is restricted to read-only commands */ struct linpool *parser_pool; /* Pool used during parsing */ + struct linpool *show_pool; /* Pool used during route show */ byte *ring_buf; /* Ring buffer for asynchronous messages */ byte *ring_end, *ring_read, *ring_write; /* Pointers to the ring buffer */ uint ring_overflow; /* Counter of ring overflows */ diff --git a/nest/cmds.c b/nest/cmds.c index 70fbdaf8..f0a14425 100644 --- a/nest/cmds.c +++ b/nest/cmds.c @@ -80,8 +80,6 @@ print_size(char *dsc, size_t val) extern pool *rt_table_pool; extern pool *rta_pool; -extern pool *roa_pool; -extern pool *proto_pool; void cmd_show_memory(void) @@ -89,7 +87,6 @@ cmd_show_memory(void) cli_msg(-1018, "BIRD memory usage"); print_size("Routing tables:", rmemsize(rt_table_pool)); print_size("Route attributes:", rmemsize(rta_pool)); - print_size("ROA tables:", rmemsize(roa_pool)); print_size("Protocols:", rmemsize(proto_pool)); print_size("Total:", rmemsize(&root_pool)); cli_msg(0, ""); diff --git a/nest/config.Y b/nest/config.Y index 878224fe..e672d730 100644 --- a/nest/config.Y +++ b/nest/config.Y @@ -18,9 +18,10 @@ CF_HDR CF_DEFINES static struct proto_config *this_proto; +static struct channel_config *this_channel; static struct iface_patt *this_ipatt; static struct iface_patt_node *this_ipn; -static struct roa_table_config *this_roa_table; +/* static struct roa_table_config *this_roa_table; */ static list *this_p_list; static struct password_item *this_p_item; static int password_id; @@ -31,7 +32,7 @@ iface_patt_check(void) struct iface_patt_node *pn; WALK_LIST(pn, this_ipatt->ipn_list) - if (!pn->pattern || pn->pxlen) + if (!pn->pattern || pn->prefix.type) cf_error("Interface name/mask expected, not IP prefix"); } @@ -50,25 +51,37 @@ get_passwords(void) return rv; } +static void +proto_postconfig(void) +{ + CALL(this_proto->protocol->postconfig, this_proto); + this_channel = NULL; + this_proto = NULL; +} + + #define DIRECT_CFG ((struct rt_dev_config *) this_proto) CF_DECLS CF_KEYWORDS(ROUTER, ID, PROTOCOL, TEMPLATE, PREFERENCE, DISABLED, DEBUG, ALL, OFF, DIRECT) CF_KEYWORDS(INTERFACE, IMPORT, EXPORT, FILTER, NONE, TABLE, STATES, ROUTES, FILTERS) +CF_KEYWORDS(IPV4, IPV6, VPN4, VPN6, ROA4, ROA6) CF_KEYWORDS(RECEIVE, LIMIT, ACTION, WARN, BLOCK, RESTART, DISABLE, KEEP, FILTERED) CF_KEYWORDS(PASSWORD, FROM, PASSIVE, TO, ID, EVENTS, PACKETS, PROTOCOLS, INTERFACES) CF_KEYWORDS(ALGORITHM, KEYED, HMAC, MD5, SHA1, SHA256, SHA384, SHA512) -CF_KEYWORDS(PRIMARY, STATS, COUNT, FOR, COMMANDS, PREEXPORT, NOEXPORT, GENERATE, ROA) +CF_KEYWORDS(PRIMARY, STATS, COUNT, BY, FOR, COMMANDS, PREEXPORT, NOEXPORT, GENERATE) CF_KEYWORDS(LISTEN, BGP, V6ONLY, DUAL, ADDRESS, PORT, PASSWORDS, DESCRIPTION, SORTED) CF_KEYWORDS(RELOAD, IN, OUT, MRTDUMP, MESSAGES, RESTRICT, MEMORY, IGP_METRIC, CLASS, DSCP) CF_KEYWORDS(GRACEFUL, RESTART, WAIT, MAX, FLUSH, AS) +/* For r_args_channel */ +CF_KEYWORDS(IPV4, IPV4_MC, IPV4_MPLS, IPV6, IPV6_MC, IPV6_MPLS, 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, 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_RTC, RTC_, UNICAST, BROADCAST, MULTICAST, ANYCAST) -CF_ENUM(T_ENUM_RTD, RTD_, ROUTER, DEVICE, BLACKHOLE, UNREACHABLE, PROHIBIT, MULTIPATH) +CF_ENUM(T_ENUM_RTD, RTD_, UNICAST, BLACKHOLE, UNREACHABLE, PROHIBIT) CF_ENUM(T_ENUM_ROA, ROA_, UNKNOWN, VALID, INVALID) %type <i32> idval @@ -76,12 +89,14 @@ CF_ENUM(T_ENUM_ROA, ROA_, UNKNOWN, VALID, INVALID) %type <r> rtable %type <s> optsym %type <ra> r_args -%type <ro> roa_args -%type <rot> roa_table_arg %type <sd> sym_args -%type <i> proto_start echo_mask echo_size debug_mask debug_list debug_flag mrtdump_mask mrtdump_list mrtdump_flag export_mode roa_mode limit_action tab_sorted tos password_algorithm +%type <i> proto_start echo_mask echo_size debug_mask debug_list debug_flag mrtdump_mask mrtdump_list mrtdump_flag export_mode limit_action net_type table_sorted tos password_algorithm %type <ps> proto_patt proto_patt2 -%type <g> limit_spec +%type <cc> channel_start proto_channel +%type <cl> limit_spec +%type <net> r_args_for_val +%type <net_ptr> r_args_for +%type <t> r_args_channel CF_GRAMMAR @@ -97,21 +112,12 @@ rtrid: idval: NUM { $$ = $1; } | '(' term ')' { $$ = f_eval_int($2); } - | RTRID - | IPA { -#ifndef IPV6 - $$ = ipa_to_u32($1); -#else - cf_error("Router IDs must be entered as hexadecimal numbers or IPv4 addresses in IPv6 version"); -#endif - } + | IP4 { $$ = ip4_to_u32($1); } | SYM { if ($1->class == (SYM_CONSTANT | T_INT) || $1->class == (SYM_CONSTANT | T_QUAD)) $$ = SYM_VAL($1).i; -#ifndef IPV6 - else if ($1->class == (SYM_CONSTANT | T_IP)) - $$ = ipa_to_u32(SYM_VAL($1).px.ip); -#endif + else if (($1->class == (SYM_CONSTANT | T_IP)) && ipa_is_ip4(SYM_VAL($1).ip)) + $$ = ipa_to_u32(SYM_VAL($1).ip); else cf_error("Number or IPv4 address constant expected"); } @@ -140,43 +146,42 @@ CF_ADDTO(conf, gr_opts) gr_opts: GRACEFUL RESTART WAIT expr ';' { new_config->gr_wait = $4; } ; -/* Creation of routing tables */ +/* Network types (for tables, channels) */ -tab_sorted: - { $$ = 0; } - | SORTED { $$ = 1; } +net_type: + IPV4 { $$ = NET_IP4; } + | IPV6 { $$ = NET_IP6; } + | VPN4 { $$ = NET_VPN4; } + | VPN6 { $$ = NET_VPN6; } + | ROA4 { $$ = NET_ROA4; } + | ROA6 { $$ = NET_ROA6; } + | FLOW4{ $$ = NET_FLOW4; } + | FLOW6{ $$ = NET_FLOW6; } ; -CF_ADDTO(conf, newtab) +CF_ENUM(T_ENUM_NETTYPE, NET_, IP4, IP6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6) -newtab: TABLE SYM tab_sorted { - struct rtable_config *cf; - cf = rt_new_table($2); - cf->sorted = $3; - } - ; -CF_ADDTO(conf, roa_table) +/* Creation of routing tables */ -roa_table_start: ROA TABLE SYM { - this_roa_table = roa_new_table_config($3); -}; +CF_ADDTO(conf, table) -roa_table_opts: - /* empty */ - | roa_table_opts ROA prefix MAX NUM AS NUM ';' { - roa_add_item_config(this_roa_table, $3.addr, $3.len, $5, $7); - } +table_sorted: + { $$ = 0; } + | SORTED { $$ = 1; } ; -roa_table: - roa_table_start - | roa_table_start '{' roa_table_opts '}' +table: net_type TABLE SYM table_sorted { + struct rtable_config *cf; + cf = rt_new_table($3, $1); + cf->sorted = $4; + } ; + /* Definition of protocols */ -CF_ADDTO(conf, proto) +CF_ADDTO(conf, proto { proto_postconfig(); }) proto_start: PROTOCOL { $$ = SYM_PROTO; } @@ -214,24 +219,62 @@ proto_name: proto_item: /* EMPTY */ - | PREFERENCE expr { - if ($2 < 0 || $2 > 0xFFFF) cf_error("Invalid preference"); - this_proto->preference = $2; - } | DISABLED bool { this_proto->disabled = $2; } | DEBUG debug_mask { this_proto->debug = $2; } | MRTDUMP mrtdump_mask { this_proto->mrtdump = $2; } - | IMPORT imexport { this_proto->in_filter = $2; } - | EXPORT imexport { this_proto->out_filter = $2; } - | RECEIVE LIMIT limit_spec { this_proto->rx_limit = $3; } - | IMPORT LIMIT limit_spec { this_proto->in_limit = $3; } - | EXPORT LIMIT limit_spec { this_proto->out_limit = $3; } - | IMPORT KEEP FILTERED bool { this_proto->in_keep_filtered = $4; } - | TABLE rtable { this_proto->table = $2; } | ROUTER ID idval { this_proto->router_id = $3; } | DESCRIPTION text { this_proto->dsc = $2; } ; + +channel_start: net_type +{ + $$ = this_channel = channel_config_new(NULL, $1, this_proto); +}; + +channel_item: + TABLE rtable { + if (this_channel->net_type && ($2->addr_type != this_channel->net_type)) + cf_error("Incompatible table type"); + this_channel->table = $2; + } + | IMPORT imexport { this_channel->in_filter = $2; } + | EXPORT imexport { this_channel->out_filter = $2; } + | RECEIVE LIMIT limit_spec { this_channel->rx_limit = $3; } + | IMPORT LIMIT limit_spec { this_channel->in_limit = $3; } + | EXPORT LIMIT limit_spec { this_channel->out_limit = $3; } + | PREFERENCE expr { this_channel->preference = $2; check_u16($2); } + | IMPORT KEEP FILTERED bool { this_channel->in_keep_filtered = $4; } + ; + +channel_opts: + /* empty */ + | channel_opts channel_item ';' + ; + +channel_opt_list: + /* empty */ + | '{' channel_opts '}' + ; + +channel_end: +{ + if (!this_channel->table) + cf_error("Routing table not specified"); + + this_channel = NULL; +}; + +proto_channel: channel_start channel_opt_list channel_end; + + +rtable: + SYM { + if ($1->class != SYM_TABLE) cf_error("Table expected"); + $$ = $1->def; + } + ; + imexport: FILTER filter { $$ = $2; } | where_filter @@ -248,20 +291,8 @@ limit_action: ; limit_spec: - expr limit_action { - struct proto_limit *l = cfg_allocz(sizeof(struct proto_limit)); - l->limit = $1; - l->action = $2; - $$ = l; - } - | OFF { $$ = NULL; } - ; - -rtable: - SYM { - if ($1->class != SYM_TABLE) cf_error("Table name expected"); - $$ = $1->def; - } + expr limit_action { $$ = (struct channel_limit){ .limit = $1, $$.action = $2 }; } + | OFF { $$ = (struct channel_limit){}; } ; CF_ADDTO(conf, debug_default) @@ -284,9 +315,8 @@ iface_patt_node_init: ; iface_patt_node_body: - TEXT { this_ipn->pattern = $1; this_ipn->prefix = IPA_NONE; this_ipn->pxlen = 0; } - | prefix_or_ipa { this_ipn->pattern = NULL; this_ipn->prefix = $1.addr; this_ipn->pxlen = $1.len; } - | TEXT prefix_or_ipa { this_ipn->pattern = $1; this_ipn->prefix = $2.addr; this_ipn->pxlen = $2.len; } + TEXT { this_ipn->pattern = $1; /* this_ipn->prefix stays zero */ } + | opttext net_or_ipa { this_ipn->pattern = $1; this_ipn->prefix = $2; } ; iface_negate: @@ -336,6 +366,7 @@ dev_proto_start: proto_start DIRECT { dev_proto: dev_proto_start proto_name '{' | dev_proto proto_item ';' + | dev_proto proto_channel ';' | dev_proto dev_iface_patt ';' | dev_proto CHECK LINK bool ';' { DIRECT_CFG->check_link = $4; } ; @@ -487,26 +518,32 @@ CF_CLI(SHOW ROUTE, r_args, [[[<prefix>|for <prefix>|for <ip>] [table <t>] [filte r_args: /* empty */ { $$ = cfg_allocz(sizeof(struct rt_show_data)); - $$->pxlen = 256; + init_list(&($$->tables)); $$->filter = FILTER_ACCEPT; } - | r_args prefix { + | r_args net_any { $$ = $1; - if ($$->pxlen != 256) cf_error("Only one prefix expected"); - $$->prefix = $2.addr; - $$->pxlen = $2.len; + if ($$->addr) cf_error("Only one prefix expected"); + $$->addr = $2; } - | r_args FOR prefix_or_ipa { + | r_args FOR r_args_for { $$ = $1; - if ($$->pxlen != 256) cf_error("Only one prefix expected"); - $$->prefix = $3.addr; - $$->pxlen = $3.len; + if ($$->addr) cf_error("Only one prefix expected"); $$->show_for = 1; + $$->addr = $3; } | r_args TABLE SYM { $$ = $1; if ($3->class != SYM_TABLE) cf_error("%s is not a table", $3->name); - $$->table = ((struct rtable_config *)$3->def)->table; + rt_show_add_table($$, ((struct rtable_config *)$3->def)->table); + $$->tables_defined_by = RSD_TDB_DIRECT; + } + | r_args TABLE ALL { + struct rtable_config *t; + $$ = $1; + WALK_LIST(t, config->tables) + rt_show_add_table($$, t->table); + $$->tables_defined_by = RSD_TDB_ALL; } | r_args FILTER filter { $$ = $1; @@ -533,11 +570,23 @@ r_args: | r_args export_mode SYM { struct proto_config *c = (struct proto_config *) $3->def; $$ = $1; - if ($$->export_mode) cf_error("Protocol specified twice"); + if ($$->export_mode) cf_error("Export specified twice"); if ($3->class != SYM_PROTO || !c->proto) cf_error("%s is not a protocol", $3->name); $$->export_mode = $2; $$->export_protocol = c->proto; $$->running_on_config = c->proto->cf->global; + $$->tables_defined_by = RSD_TDB_INDIRECT; + } + | r_args export_mode SYM '.' r_args_channel { + struct proto_config *c = (struct proto_config *) $3->def; + $$ = $1; + if ($$->export_mode) cf_error("Export specified twice"); + if ($3->class != SYM_PROTO || !c->proto) cf_error("%s is not a protocol", $3->name); + $$->export_mode = $2; + $$->export_channel = proto_find_channel_by_name(c->proto, $5); + if (!$$->export_channel) cf_error("Export channel not found"); + $$->running_on_config = c->proto->cf->global; + $$->tables_defined_by = RSD_TDB_INDIRECT; } | r_args PROTOCOL SYM { struct proto_config *c = (struct proto_config *) $3->def; @@ -546,6 +595,7 @@ r_args: if ($3->class != SYM_PROTO || !c->proto) cf_error("%s is not a protocol", $3->name); $$->show_protocol = c->proto; $$->running_on_config = c->proto->cf->global; + $$->tables_defined_by = RSD_TDB_INDIRECT; } | r_args STATS { $$ = $1; @@ -557,52 +607,71 @@ r_args: } ; +r_args_for: + r_args_for_val { + $$ = cfg_alloc($1.length); + net_copy($$, &$1); + } + | net_vpn4_ + | net_vpn6_ + | VPN_RD IP4 { + $$ = cfg_alloc(sizeof(net_addr_vpn4)); + net_fill_vpn4($$, $2, IP4_MAX_PREFIX_LENGTH, $1); + } + | VPN_RD IP6 { + $$ = cfg_alloc(sizeof(net_addr_vpn6)); + net_fill_vpn6($$, $2, IP6_MAX_PREFIX_LENGTH, $1); + } + | SYM { + if ($1->class == (SYM_CONSTANT | T_IP)) + { + $$ = cfg_alloc(ipa_is_ip4(SYM_VAL($1).ip) ? sizeof(net_addr_ip4) : sizeof(net_addr_ip6)); + net_fill_ip_host($$, SYM_VAL($1).ip); + } + else if (($1->class == (SYM_CONSTANT | T_NET)) && net_type_match(SYM_VAL($1).net, NB_IP | NB_VPN)) + $$ = (net_addr *) SYM_VAL($1).net; /* Avoid const warning */ + else + cf_error("IP address or network expected"); + } + ; + +r_args_for_val: + net_ip4_ + | net_ip6_ + | IP4 { net_fill_ip4(&($$), $1, IP4_MAX_PREFIX_LENGTH); } + | IP6 { net_fill_ip6(&($$), $1, IP6_MAX_PREFIX_LENGTH); } + export_mode: PREEXPORT { $$ = RSEM_PREEXPORT; } | EXPORT { $$ = RSEM_EXPORT; } | NOEXPORT { $$ = RSEM_NOEXPORT; } ; - -CF_CLI_HELP(SHOW ROA, ..., [[Show ROA table]]) -CF_CLI(SHOW ROA, roa_args, [<prefix> | in <prefix> | for <prefix>] [as <num>] [table <t>], [[Show ROA table]]) -{ roa_show($3); } ; - -roa_args: - /* empty */ { - $$ = cfg_allocz(sizeof(struct roa_show_data)); - $$->mode = ROA_SHOW_ALL; - $$->table = roa_table_default; - if (roa_table_default == NULL) - cf_error("No ROA table defined"); - } - | roa_args roa_mode prefix { - $$ = $1; - if ($$->mode != ROA_SHOW_ALL) cf_error("Only one prefix expected"); - $$->prefix = $3.addr; - $$->pxlen = $3.len; - $$->mode = $2; - } - | roa_args AS NUM { - $$ = $1; - $$->asn = $3; - } - | roa_args TABLE SYM { - $$ = $1; - if ($3->class != SYM_ROA) cf_error("%s is not a ROA table", $3->name); - $$->table = ((struct roa_table_config *)$3->def)->table; - } +/* This is ugly hack */ +r_args_channel: + IPV4 { $$ = "ipv4"; } + | IPV4_MC { $$ = "ipv4-mc"; } + | IPV4_MPLS { $$ = "ipv4-mpls"; } + | IPV6 { $$ = "ipv6"; } + | IPV6_MC { $$ = "ipv6-mc"; } + | IPV6_MPLS { $$ = "ipv6-mpls"; } + | VPN4 { $$ = "vpn4"; } + | VPN4_MC { $$ = "vpn4-mc"; } + | VPN4_MPLS { $$ = "vpn4-mpls"; } + | VPN6 { $$ = "vpn6"; } + | VPN6_MC { $$ = "vpn6-mc"; } + | VPN6_MPLS { $$ = "vpn6-mpls"; } + | ROA4 { $$ = "roa4"; } + | ROA6 { $$ = "roa6"; } + | FLOW4 { $$ = "flow4"; } + | FLOW6 { $$ = "flow6"; } + | MPLS { $$ = "mpls"; } + | PRI { $$ = "pri"; } + | SEC { $$ = "sec"; } ; -roa_mode: - { $$ = ROA_SHOW_PX; } - | IN { $$ = ROA_SHOW_IN; } - | FOR { $$ = ROA_SHOW_FOR; } - ; - - CF_CLI_HELP(SHOW SYMBOLS, ..., [[Show all known symbolic names]]) -CF_CLI(SHOW SYMBOLS, sym_args, [table|filter|function|protocol|template|roa|<symbol>], [[Show all known symbolic names]]) +CF_CLI(SHOW SYMBOLS, sym_args, [table|filter|function|protocol|template|<symbol>], [[Show all known symbolic names]]) { cmd_show_symbols($3); } ; sym_args: @@ -614,46 +683,10 @@ sym_args: | sym_args FILTER { $$ = $1; $$->type = SYM_FILTER; } | sym_args PROTOCOL { $$ = $1; $$->type = SYM_PROTO; } | sym_args TEMPLATE { $$ = $1; $$->type = SYM_TEMPLATE; } - | sym_args ROA { $$ = $1; $$->type = SYM_ROA; } | sym_args SYM { $$ = $1; $$->sym = $2; } ; -roa_table_arg: - /* empty */ { - if (roa_table_default == NULL) - cf_error("No ROA table defined"); - $$ = roa_table_default; - } - | TABLE SYM { - if ($2->class != SYM_ROA) - cf_error("%s is not a ROA table", $2->name); - $$ = ((struct roa_table_config *)$2->def)->table; - } - ; - -CF_CLI_HELP(ADD, roa ..., [[Add ROA record]]) -CF_CLI(ADD ROA, prefix MAX NUM AS NUM roa_table_arg, <prefix> max <num> as <num> [table <name>], [[Add ROA record]]) -{ - if (! cli_access_restricted()) - { roa_add_item($8, $3.addr, $3.len, $5, $7, ROA_SRC_DYNAMIC); cli_msg(0, ""); } -}; - -CF_CLI_HELP(DELETE, roa ..., [[Delete ROA record]]) -CF_CLI(DELETE ROA, prefix MAX NUM AS NUM roa_table_arg, <prefix> max <num> as <num> [table <name>], [[Delete ROA record]]) -{ - if (! cli_access_restricted()) - { roa_delete_item($8, $3.addr, $3.len, $5, $7, ROA_SRC_DYNAMIC); cli_msg(0, ""); } -}; - -CF_CLI_HELP(FLUSH, roa [table <name>], [[Removes all dynamic ROA records]]) -CF_CLI(FLUSH ROA, roa_table_arg, [table <name>], [[Removes all dynamic ROA records]]) -{ - if (! cli_access_restricted()) - { roa_flush($3, ROA_SRC_DYNAMIC); cli_msg(0, ""); } -}; - - CF_CLI_HELP(DUMP, ..., [[Dump debugging information]]) CF_CLI(DUMP RESOURCES,,, [[Dump all allocated resource]]) { rdump(&root_pool); cli_msg(0, ""); } ; diff --git a/nest/iface.c b/nest/iface.c index a23cdf4f..00af5052 100644 --- a/nest/iface.c +++ b/nest/iface.c @@ -46,7 +46,7 @@ list iface_list; void ifa_dump(struct ifa *a) { - debug("\t%I, net %I/%-2d bc %I -> %I%s%s%s\n", a->ip, a->prefix, a->pxlen, a->brd, a->opposite, + debug("\t%I, net %N bc %I -> %I%s%s%s\n", a->ip, &a->prefix, a->brd, a->opposite, (a->flags & IF_UP) ? "" : " DOWN", (a->flags & IA_PRIMARY) ? "" : " SEC", (a->flags & IA_PEER) ? "PEER" : ""); @@ -138,13 +138,12 @@ if_copy(struct iface *to, struct iface *from) static inline void ifa_send_notify(struct proto *p, unsigned c, struct ifa *a) { - if (p->ifa_notify) + if (p->ifa_notify && (p->proto_state != PS_DOWN)) { if (p->debug & D_IFACES) - log(L_TRACE "%s < %s address %I/%d on interface %s %s", + log(L_TRACE "%s < %s address %N on interface %s %s", p->name, (a->flags & IA_PRIMARY) ? "primary" : "secondary", - a->prefix, a->pxlen, a->iface->name, - (c & IF_CHANGE_UP) ? "added" : "removed"); + &a->prefix, a->iface->name, (c & IF_CHANGE_UP) ? "added" : "removed"); p->ifa_notify(p, c, a); } } @@ -156,7 +155,7 @@ 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, active_proto_list) + WALK_LIST(p, proto_list) ifa_send_notify(p, c, a); } @@ -175,7 +174,7 @@ ifa_notify_change(unsigned c, struct ifa *a) static inline void if_send_notify(struct proto *p, unsigned c, struct iface *i) { - if (p->if_notify) + if (p->if_notify && (p->proto_state != PS_DOWN)) { if (p->debug & D_IFACES) log(L_TRACE "%s < interface %s %s", p->name, i->name, @@ -216,7 +215,7 @@ if_notify_change(unsigned c, struct iface *i) ifa_notify_change_(IF_CHANGE_DOWN, a); } - WALK_LIST(p, active_proto_list) + WALK_LIST(p, proto_list) if_send_notify(p, c, i); if (c & IF_CHANGE_UP) @@ -500,8 +499,7 @@ ifa_recalc_all_primary_addresses(void) static inline int ifa_same(struct ifa *a, struct ifa *b) { - return ipa_equal(a->ip, b->ip) && ipa_equal(a->prefix, b->prefix) && - a->pxlen == b->pxlen; + return ipa_equal(a->ip, b->ip) && net_equal(&a->prefix, &b->prefix); } @@ -534,10 +532,8 @@ ifa_update(struct ifa *a) break; } -#ifndef IPV6 - if ((i->flags & IF_BROADCAST) && !ipa_nonzero(a->brd)) + if ((a->prefix.type == NET_IP4) && (i->flags & IF_BROADCAST) && ipa_zero(a->brd)) log(L_ERR "Missing broadcast address for interface %s", i->name); -#endif b = mb_alloc(if_pool, sizeof(struct ifa)); memcpy(b, a, sizeof(struct ifa)); @@ -584,9 +580,8 @@ ifa_delete(struct ifa *a) } u32 -if_choose_router_id(struct iface_patt *mask UNUSED6, u32 old_id UNUSED6) +if_choose_router_id(struct iface_patt *mask, u32 old_id) { -#ifndef IPV6 struct iface *i; struct ifa *a, *b; @@ -599,6 +594,9 @@ if_choose_router_id(struct iface_patt *mask UNUSED6, u32 old_id UNUSED6) WALK_LIST(a, i->addrs) { + if (a->prefix.type != NET_IP4) + continue; + if (a->flags & IA_SECONDARY) continue; @@ -623,10 +621,6 @@ if_choose_router_id(struct iface_patt *mask UNUSED6, u32 old_id UNUSED6) log(L_INFO "Chosen router ID %R according to interface %s", id, b->iface->name); return id; - -#else - return 0; -#endif } /** @@ -669,17 +663,17 @@ iface_patt_match(struct iface_patt *ifp, struct iface *i, struct ifa *a) continue; } - if (p->pxlen == 0) + if (p->prefix.pxlen == 0) return pos; if (!a) continue; - if (ipa_in_net(a->ip, p->prefix, p->pxlen)) + if (ipa_in_netX(a->ip, &p->prefix)) return pos; if ((a->flags & IA_PEER) && - ipa_in_net(a->opposite, p->prefix, p->pxlen)) + ipa_in_netX(a->opposite, &p->prefix)) return pos; continue; @@ -713,8 +707,7 @@ iface_plists_equal(struct iface_patt *pa, struct iface_patt *pb) (!x->pattern && y->pattern) || /* This nasty lines where written by me... :-( Feela */ (!y->pattern && x->pattern) || ((x->pattern != y->pattern) && strcmp(x->pattern, y->pattern)) || - !ipa_equal(x->prefix, y->prefix) || - (x->pxlen != y->pxlen)) + !net_equal(&x->prefix, &y->prefix)) return 0; x = (void *) x->n.next; y = (void *) y->n.next; @@ -747,14 +740,14 @@ iface_patts_equal(list *a, list *b, int (*comp)(struct iface_patt *, struct ifac static void if_show_addr(struct ifa *a) { - byte opp[STD_ADDRESS_P_LENGTH + 16]; + byte opp[IPA_MAX_TEXT_LENGTH + 16]; if (ipa_nonzero(a->opposite)) bsprintf(opp, ", opposite %I", a->opposite); else opp[0] = 0; cli_msg(-1003, "\t%I/%d (%s%s, scope %s)", - a->ip, a->pxlen, + a->ip, a->prefix.pxlen, (a->flags & IA_PRIMARY) ? "Primary" : (a->flags & IA_SECONDARY) ? "Secondary" : "Unselected", opp, ip_scope_text(a->scope)); } @@ -798,13 +791,13 @@ void if_show_summary(void) { struct iface *i; - byte addr[STD_ADDRESS_P_LENGTH + 16]; + byte addr[IPA_MAX_TEXT_LENGTH + 16]; cli_msg(-2005, "interface state address"); WALK_LIST(i, iface_list) { if (i->addr) - bsprintf(addr, "%I/%d", i->addr->ip, i->addr->pxlen); + bsprintf(addr, "%I/%d", i->addr->ip, i->addr->prefix.pxlen); else addr[0] = 0; cli_msg(-1005, "%-9s %-5s %s", i->name, (i->flags & IF_UP) ? "up" : "DOWN", addr); diff --git a/nest/iface.h b/nest/iface.h index 56710e4a..de5070d6 100644 --- a/nest/iface.h +++ b/nest/iface.h @@ -10,6 +10,7 @@ #define _BIRD_IFACE_H_ #include "lib/lists.h" +#include "lib/ip.h" extern list iface_list; @@ -19,9 +20,8 @@ struct pool; struct ifa { /* Interface address */ node n; struct iface *iface; /* Interface this address belongs to */ + net_addr prefix; /* Network prefix */ ip_addr ip; /* IP address of this host */ - ip_addr prefix; /* Network prefix */ - unsigned pxlen; /* Prefix length */ ip_addr brd; /* Broadcast address */ ip_addr opposite; /* Opposite end of a point-to-point link */ unsigned scope; /* Interface address scope */ @@ -118,12 +118,15 @@ typedef struct neighbor { SCOPE_HOST when it's our own address */ } neighbor; -#define NEF_STICKY 1 -#define NEF_ONLINK 2 -#define NEF_BIND 4 /* Used internally for neighbors bound to an iface */ +#define NEF_STICKY 1 +#define NEF_ONLINK 2 +#define NEF_BIND 4 /* Used internally for neighbors bound to an iface */ +#define NEF_IFACE 8 /* Neighbors bound to iface */ + neighbor *neigh_find(struct proto *, ip_addr *, unsigned flags); neighbor *neigh_find2(struct proto *p, ip_addr *a, struct iface *ifa, unsigned flags); +neighbor *neigh_find_iface(struct proto *p, struct iface *ifa); static inline int neigh_connected_to(struct proto *p, ip_addr *a, struct iface *i) { @@ -148,8 +151,7 @@ struct iface_patt_node { node n; int positive; byte *pattern; - ip_addr prefix; - int pxlen; + net_addr prefix; }; struct iface_patt { diff --git a/nest/neighbor.c b/nest/neighbor.c index d974fa51..96475a50 100644 --- a/nest/neighbor.c +++ b/nest/neighbor.c @@ -45,14 +45,15 @@ #include "lib/resource.h" #define NEIGH_HASH_SIZE 256 +#define NEIGH_HASH_OFFSET 24 static slab *neigh_slab; -static list sticky_neigh_list, neigh_hash_table[NEIGH_HASH_SIZE]; +static list sticky_neigh_list, iface_neigh_list, neigh_hash_table[NEIGH_HASH_SIZE]; static inline uint neigh_hash(struct proto *p, ip_addr *a) { - return (p->hash_key ^ ipa_hash(*a)) & (NEIGH_HASH_SIZE-1); + return (p->hash_key ^ ipa_hash(*a)) >> NEIGH_HASH_OFFSET; } static int @@ -79,17 +80,17 @@ if_connected(ip_addr *a, struct iface *i, struct ifa **ap) } else { - if (ipa_in_net(*a, b->prefix, b->pxlen)) + if (ipa_in_netX(*a, &b->prefix)) { -#ifndef IPV6 - if ((b->pxlen < (BITS_PER_IP_ADDRESS - 1)) && - (ipa_equal(*a, b->prefix) || /* Network address */ + /* Do not allow IPv4 network and broadcast addresses */ + if (ipa_is_ip4(*a) && + (net_pxlen(&b->prefix) < (IP4_MAX_PREFIX_LENGTH - 1)) && + (ipa_equal(*a, net_prefix(&b->prefix)) || /* Network address */ ipa_equal(*a, b->brd))) /* Broadcast */ { *ap = NULL; return -1; } -#endif return b->scope; } @@ -165,6 +166,8 @@ neigh_find2(struct proto *p, ip_addr *a, struct iface *ifa, unsigned flags) return NULL; n = sl_alloc(neigh_slab); + memset(n, 0, sizeof(neighbor)); + n->addr = *a; if (scope >= 0) { @@ -186,6 +189,35 @@ neigh_find2(struct proto *p, ip_addr *a, struct iface *ifa, unsigned flags) return n; } +neighbor * +neigh_find_iface(struct proto *p, struct iface *ifa) +{ + neighbor *n; + node *nn; + + /* We keep neighbors with NEF_IFACE foremost in ifa->neighbors list */ + WALK_LIST2(n, nn, ifa->neighbors, if_n) + { + if (! (n->flags & NEF_IFACE)) + break; + + if (n->proto == p) + return n; + } + + n = sl_alloc(neigh_slab); + memset(n, 0, sizeof(neighbor)); + + add_tail(&iface_neigh_list, &n->n); + add_head(&ifa->neighbors, &n->if_n); + n->iface = ifa; + n->proto = p; + n->flags = NEF_IFACE; + n->scope = (ifa->flags & IF_UP) ? SCOPE_HOST : -1; + + return n; +} + /** * neigh_dump - dump specified neighbor entry. * @n: the entry to dump @@ -204,6 +236,8 @@ neigh_dump(neighbor *n) debug("%s %p %08x scope %s", n->proto->name, n->data, n->aux, ip_scope_text(n->scope)); if (n->flags & NEF_STICKY) debug(" STICKY"); + if (n->flags & NEF_IFACE) + debug(" IFACE"); debug("\n"); } @@ -222,6 +256,8 @@ neigh_dump_all(void) debug("Known neighbors:\n"); WALK_LIST(n, sticky_neigh_list) neigh_dump(n); + WALK_LIST(n, iface_neigh_list) + neigh_dump(n); for(i=0; i<NEIGH_HASH_SIZE; i++) WALK_LIST(n, neigh_hash_table[i]) neigh_dump(n); @@ -231,14 +267,19 @@ neigh_dump_all(void) static void neigh_up(neighbor *n, struct iface *i, int scope, struct ifa *a) { + DBG("Waking up sticky neighbor %I\n", n->addr); n->iface = i; n->ifa = a; n->scope = scope; - add_tail(&i->neighbors, &n->if_n); - rem_node(&n->n); - add_tail(&neigh_hash_table[neigh_hash(n->proto, &n->addr)], &n->n); - DBG("Waking up sticky neighbor %I\n", n->addr); - if (n->proto->neigh_notify && n->proto->core_state != FS_FLUSHING) + + if (! (n->flags & NEF_IFACE)) + { + add_tail(&i->neighbors, &n->if_n); + rem_node(&n->n); + add_tail(&neigh_hash_table[neigh_hash(n->proto, &n->addr)], &n->n); + } + + if (n->proto->neigh_notify && (n->proto->proto_state != PS_STOP)) n->proto->neigh_notify(n); } @@ -246,14 +287,20 @@ static void neigh_down(neighbor *n) { DBG("Flushing neighbor %I on %s\n", n->addr, n->iface->name); - rem_node(&n->if_n); - if (! (n->flags & NEF_BIND)) + if (! (n->flags & (NEF_BIND | NEF_IFACE))) n->iface = NULL; n->ifa = NULL; n->scope = -1; - if (n->proto->neigh_notify && n->proto->core_state != FS_FLUSHING) + + if (! (n->flags & NEF_IFACE)) + { + rem_node(&n->if_n); + rem_node(&n->n); + } + + if (n->proto->neigh_notify && (n->proto->proto_state != PS_STOP)) n->proto->neigh_notify(n); - rem_node(&n->n); + if (n->flags & NEF_STICKY) { add_tail(&sticky_neigh_list, &n->n); @@ -271,7 +318,8 @@ neigh_down(neighbor *n) return; } } - else + + if (! (n->flags & (NEF_STICKY | NEF_IFACE))) sl_free(neigh_slab, n); } @@ -289,10 +337,17 @@ void neigh_if_up(struct iface *i) { struct ifa *a; - neighbor *n, *next; + neighbor *n; + node *x, *y; int scope; - WALK_LIST_DELSAFE(n, next, sticky_neigh_list) + /* Wake up all iface neighbors */ + WALK_LIST2_DELSAFE(n, x, y, i->neighbors, if_n) + if ((n->scope < 0) && (n->flags & NEF_IFACE)) + neigh_up(n, i, SCOPE_HOST, NULL); + + /* Wake up appropriate sticky neighbors */ + WALK_LIST_DELSAFE(n, x, sticky_neigh_list) if ((!n->iface || n->iface == i) && ((scope = if_connected(&n->addr, i, &a)) >= 0)) neigh_up(n, i, scope, a); @@ -310,10 +365,11 @@ neigh_if_up(struct iface *i) void neigh_if_down(struct iface *i) { + neighbor *n; node *x, *y; - WALK_LIST_DELSAFE(x, y, i->neighbors) - neigh_down(SKIP_BACK(neighbor, if_n, x)); + WALK_LIST2_DELSAFE(n, x, y, i->neighbors, if_n) + neigh_down(n); } /** @@ -327,14 +383,12 @@ neigh_if_down(struct iface *i) void neigh_if_link(struct iface *i) { + neighbor *n; node *x, *y; - WALK_LIST_DELSAFE(x, y, i->neighbors) - { - neighbor *n = SKIP_BACK(neighbor, if_n, x); - if (n->proto->neigh_notify && n->proto->core_state != FS_FLUSHING) - n->proto->neigh_notify(n); - } + WALK_LIST2_DELSAFE(n, x, y, i->neighbors, if_n) + if (n->proto->neigh_notify && (n->proto->proto_state != PS_STOP)) + n->proto->neigh_notify(n); } /** @@ -351,19 +405,21 @@ void neigh_ifa_update(struct ifa *a) { struct iface *i = a->iface; + struct ifa *aa; node *x, *y; - + neighbor *n; + int scope; + /* Remove all neighbors whose scope has changed */ - WALK_LIST_DELSAFE(x, y, i->neighbors) - { - struct ifa *aa; - neighbor *n = SKIP_BACK(neighbor, if_n, x); - if (if_connected(&n->addr, i, &aa) != n->scope) - neigh_down(n); - } + WALK_LIST2_DELSAFE(n, x, y, i->neighbors, if_n) + if (n->ifa && (if_connected(&n->addr, i, &aa) != n->scope)) + neigh_down(n); /* Wake up all sticky neighbors that are reachable now */ - neigh_if_up(i); + WALK_LIST_DELSAFE(n, x, sticky_neigh_list) + if ((!n->iface || n->iface == i) && + ((scope = if_connected(&n->addr, i, &aa)) >= 0)) + neigh_up(n, i, scope, aa); } static inline void @@ -372,7 +428,7 @@ neigh_prune_one(neighbor *n) if (n->proto->proto_state != PS_DOWN) return; rem_node(&n->n); - if (n->scope >= 0) + if (n->if_n.next) rem_node(&n->if_n); sl_free(neigh_slab, n); } @@ -397,6 +453,8 @@ neigh_prune(void) neigh_prune_one(n); WALK_LIST_DELSAFE(n, m, sticky_neigh_list) neigh_prune_one(n); + WALK_LIST_DELSAFE(n, m, iface_neigh_list) + neigh_prune_one(n); } /** @@ -409,10 +467,11 @@ neigh_prune(void) void neigh_init(pool *if_pool) { - int i; - neigh_slab = sl_new(if_pool, sizeof(neighbor)); + init_list(&sticky_neigh_list); - for(i=0; i<NEIGH_HASH_SIZE; i++) + init_list(&iface_neigh_list); + + for(int i = 0; i < NEIGH_HASH_SIZE; i++) init_list(&neigh_hash_table[i]); } diff --git a/nest/password.h b/nest/password.h index f21483c4..78244985 100644 --- a/nest/password.h +++ b/nest/password.h @@ -10,7 +10,7 @@ #ifndef PASSWORD_H #define PASSWORD_H -#include "lib/timer.h" +#include "sysdep/unix/timer.h" struct password_item { node n; diff --git a/nest/proto-hooks.c b/nest/proto-hooks.c index 7395b45e..92863f8e 100644 --- a/nest/proto-hooks.c +++ b/nest/proto-hooks.c @@ -190,7 +190,7 @@ void ifa_notify(struct proto *p, unsigned flags, struct ifa *a) /** * rt_notify - notify instance about routing table change * @p: protocol instance - * @table: a routing table + * @channel: notifying channel * @net: a network entry * @new: new route for the network * @old: old route for the network diff --git a/nest/proto.c b/nest/proto.c index 1091b321..3d764df0 100644 --- a/nest/proto.c +++ b/nest/proto.c @@ -21,19 +21,12 @@ #include "filter/filter.h" pool *proto_pool; +list proto_list; static list protocol_list; -static list proto_list; #define PD(pr, msg, args...) do { if (pr->debug & D_STATES) { log(L_TRACE "%s: " msg, pr->name , ## args); } } while(0) -list active_proto_list; -static list inactive_proto_list; -static list initial_proto_list; -static list flush_proto_list; -static struct proto *initial_device_proto; - -static event *proto_flush_event; static timer *proto_shutdown_timer; static timer *gr_wait_timer; @@ -46,199 +39,659 @@ static int graceful_restart_state; static u32 graceful_restart_locks; static char *p_states[] = { "DOWN", "START", "UP", "STOP" }; -static char *c_states[] = { "HUNGRY", "???", "HAPPY", "FLUSHING" }; +static char *c_states[] = { "DOWN", "START", "UP", "FLUSHING" }; + +extern struct protocol proto_unix_iface; -static void proto_flush_loop(void *); static void proto_shutdown_loop(struct timer *); static void proto_rethink_goal(struct proto *p); -static void proto_want_export_up(struct proto *p); -static void proto_fell_down(struct proto *p); static char *proto_state_name(struct proto *p); +static void channel_verify_limits(struct channel *c); +static void channel_reset_limit(struct channel_limit *l); -static void -proto_relink(struct proto *p) -{ - list *l = NULL; - switch (p->core_state) - { - case FS_HUNGRY: - l = &inactive_proto_list; - break; - case FS_HAPPY: - l = &active_proto_list; - break; - case FS_FLUSHING: - l = &flush_proto_list; - break; - default: - ASSERT(0); - } +static inline int proto_is_done(struct proto *p) +{ return (p->proto_state == PS_DOWN) && (p->active_channels == 0); } - rem_node(&p->n); - add_tail(l, &p->n); -} +static inline int channel_is_active(struct channel *c) +{ return (c->channel_state == CS_START) || (c->channel_state == CS_UP); } static void proto_log_state_change(struct proto *p) { if (p->debug & D_STATES) + { + char *name = proto_state_name(p); + if (name != p->last_state_name_announced) { - char *name = proto_state_name(p); - if (name != p->last_state_name_announced) - { - p->last_state_name_announced = name; - PD(p, "State changed to %s", proto_state_name(p)); - } + p->last_state_name_announced = name; + PD(p, "State changed to %s", proto_state_name(p)); } + } else p->last_state_name_announced = NULL; } +struct channel_config * +proto_cf_find_channel(struct proto_config *pc, uint net_type) +{ + struct channel_config *cc; + + WALK_LIST(cc, pc->channels) + if (cc->net_type == net_type) + return cc; + + return NULL; +} + /** - * proto_new - create a new protocol instance - * @c: protocol configuration - * @size: size of protocol data structure (each protocol instance is represented by - * a structure starting with generic part [struct &proto] and continued - * with data specific to the protocol) + * proto_find_channel_by_table - find channel connected to a routing table + * @p: protocol instance + * @t: routing table * - * When a new configuration has been read in, the core code starts - * initializing all the protocol instances configured by calling their - * init() hooks with the corresponding instance configuration. The initialization - * code of the protocol is expected to create a new instance according to the - * configuration by calling this function and then modifying the default settings - * to values wanted by the protocol. + * Returns pointer to channel or NULL */ -void * -proto_new(struct proto_config *c, unsigned size) +struct channel * +proto_find_channel_by_table(struct proto *p, struct rtable *t) { - struct protocol *pr = c->protocol; - struct proto *p = mb_allocz(proto_pool, size); - - p->cf = c; - p->debug = c->debug; - p->mrtdump = c->mrtdump; - p->name = c->name; - p->preference = c->preference; - p->disabled = c->disabled; - p->proto = pr; - p->table = c->table->table; - p->hash_key = random_u32(); - c->proto = p; - return p; + struct channel *c; + + WALK_LIST(c, p->channels) + if (c->table == t) + return c; + + return NULL; } -static void -proto_init_instance(struct proto *p) +/** + * proto_find_channel_by_name - find channel by its name + * @p: protocol instance + * @n: channel name + * + * Returns pointer to channel or NULL + */ +struct channel * +proto_find_channel_by_name(struct proto *p, const char *n) { - /* Here we cannot use p->cf->name since it won't survive reconfiguration */ - p->pool = rp_new(proto_pool, p->proto->name); - p->attn = ev_new(p->pool); - p->attn->data = p; + struct channel *c; - if (graceful_restart_state == GRS_INIT) - p->gr_recovery = 1; + WALK_LIST(c, p->channels) + if (!strcmp(c->name, n)) + return c; - if (! p->proto->multitable) - rt_lock_table(p->table); + return NULL; } -extern pool *rt_table_pool; /** - * proto_add_announce_hook - connect protocol to a routing table + * proto_add_channel - connect protocol to a routing table * @p: protocol instance - * @t: routing table to connect to - * @stats: per-table protocol statistics + * @cf: channel configuration * - * This function creates a connection between the protocol instance @p and the - * routing table @t, making the protocol hear all changes in the table. + * This function creates a channel between the protocol instance @p and the + * routing table specified in the configuration @cf, making the protocol hear + * all changes in the table and allowing the protocol to update routes in the + * table. * - * The announce hook is linked in the protocol ahook list. Announce hooks are - * allocated from the routing table resource pool and when protocol accepts - * routes also in the table ahook list. The are linked to the table ahook list - * and unlinked from it depending on export_state (in proto_want_export_up() and - * proto_want_export_down()) and they are automatically freed after the protocol - * is flushed (in proto_fell_down()). - * - * Unless you want to listen to multiple routing tables (as the Pipe protocol - * does), you needn't to worry about this function since the connection to the - * protocol's primary routing table is initialized automatically by the core - * code. + * The channel is linked in the protocol channel list and when active also in + * the table channel list. Channels are allocated from the global resource pool + * (@proto_pool) and they are automatically freed when the protocol is removed. */ -struct announce_hook * -proto_add_announce_hook(struct proto *p, struct rtable *t, struct proto_stats *stats) + +struct channel * +proto_add_channel(struct proto *p, struct channel_config *cf) +{ + struct channel *c = mb_allocz(proto_pool, cf->channel->channel_size); + + c->name = cf->name; + c->channel = cf->channel; + c->proto = p; + c->table = cf->table->table; + + 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; + + c->net_type = cf->net_type; + c->ra_mode = cf->ra_mode; + c->preference = cf->preference; + c->merge_limit = cf->merge_limit; + c->in_keep_filtered = cf->in_keep_filtered; + + c->channel_state = CS_DOWN; + c->export_state = ES_DOWN; + c->last_state_change = now; + c->reloadable = 1; + + CALL(c->channel->init, c, cf); + + add_tail(&p->channels, &c->n); + + PD(p, "Channel %s connected to table %s", c->name, c->table->name); + + return c; +} + +void +proto_remove_channel(struct proto *p, struct channel *c) { - struct announce_hook *h; + ASSERT(c->channel_state == CS_DOWN); - DBG("Connecting protocol %s to table %s\n", p->name, t->name); - PD(p, "Connected to table %s", t->name); + PD(p, "Channel %s removed", c->name); - h = mb_allocz(rt_table_pool, sizeof(struct announce_hook)); - h->table = t; - h->proto = p; - h->stats = stats; + rem_node(&c->n); + mb_free(c); +} - h->next = p->ahooks; - p->ahooks = h; - if (p->rt_notify && (p->export_state != ES_DOWN)) - add_tail(&t->hooks, &h->n); - return h; +static void +proto_start_channels(struct proto *p) +{ + struct channel *c; + WALK_LIST(c, p->channels) + if (!c->disabled) + channel_set_state(c, CS_UP); +} + +static void +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); +} + +static void +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); +} + +static void +proto_remove_channels(struct proto *p) +{ + struct channel *c; + WALK_LIST_FIRST(c, p->channels) + proto_remove_channel(p, c); +} + +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(c->feed_event); +} + +static void +channel_feed_loop(void *ptr) +{ + struct channel *c = ptr; + + if (c->export_state != ES_FEEDING) + return; + + if (!c->feed_active) + if (c->proto->feed_begin) + c->proto->feed_begin(c, !c->refeeding); + + // DBG("Feeding protocol %s continued\n", p->name); + if (!rt_feed_channel(c)) + { + ev_schedule(c->feed_event); + return; + } + + // DBG("Feeding protocol %s finished\n", p->name); + c->export_state = ES_READY; + // proto_log_state_change(p); + + if (c->proto->feed_end) + c->proto->feed_end(c); +} + + +static void +channel_start_export(struct channel *c) +{ + ASSERT(c->channel_state == CS_UP); + ASSERT(c->export_state == ES_DOWN); + + channel_schedule_feed(c, 1); /* Sets ES_FEEDING */ +} + +static void +channel_stop_export(struct channel *c) +{ + /* Need to abort feeding */ + if (c->export_state == ES_FEEDING) + rt_feed_channel_abort(c); + + c->export_state = ES_DOWN; + c->stats.exp_routes = 0; +} + +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(c->proto->pool); + c->feed_event->data = c; + c->feed_event->hook = channel_feed_loop; + + channel_reset_limit(&c->rx_limit); + channel_reset_limit(&c->in_limit); + channel_reset_limit(&c->out_limit); + + CALL(c->channel->start, c); +} + +static void +channel_do_flush(struct channel *c) +{ + rt_schedule_prune(c->table); + + c->gr_wait = 0; + if (c->gr_lock) + channel_graceful_restart_unlock(c); + + CALL(c->channel->shutdown, c); +} + +static void +channel_do_down(struct channel *c) +{ + 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); + + memset(&c->stats, 0, sizeof(struct proto_stats)); + + CALL(c->channel->cleanup, c); + + /* Schedule protocol shutddown */ + if (proto_is_done(c->proto)) + ev_schedule(c->proto->event); +} + +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) + return; + + c->channel_state = state; + c->last_state_change = now; + + switch (state) + { + case CS_START: + ASSERT(cs == CS_DOWN || cs == CS_UP); + + if (cs == CS_DOWN) + channel_do_start(c); + + if (es != ES_DOWN) + channel_stop_export(c); + + break; + + case CS_UP: + ASSERT(cs == CS_DOWN || cs == CS_START); + + if (cs == CS_DOWN) + channel_do_start(c); + + if (!c->gr_wait && c->proto->rt_notify) + channel_start_export(c); + + break; + + case CS_FLUSHING: + ASSERT(cs == CS_START || cs == CS_UP); + + if (es != ES_DOWN) + channel_stop_export(c); + + channel_do_flush(c); + break; + + case CS_DOWN: + ASSERT(cs == CS_FLUSHING); + + channel_do_down(c); + break; + + default: + ASSERT(0); + } + // XXXX proto_log_state_change(c); } /** - * proto_find_announce_hook - find announce hooks - * @p: protocol instance - * @t: routing table + * channel_request_feeding - request feeding routes to the channel + * @c: given channel * - * Returns pointer to announce hook or NULL + * Sometimes it is needed to send again all routes to the channel. This is + * called feeding and can be requested by this function. This would cause + * channel export state transition to ES_FEEDING (during feeding) and when + * 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. */ -struct announce_hook * -proto_find_announce_hook(struct proto *p, struct rtable *t) +void +channel_request_feeding(struct channel *c) { - struct announce_hook *a; + ASSERT(c->channel_state == CS_UP); - for (a = p->ahooks; a; a = a->next) - if (a->table == t) - return a; + /* Do nothing if we are still waiting for feeding */ + if (c->export_state == ES_DOWN) + return; - return NULL; + /* 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); + } + + channel_reset_limit(&c->out_limit); + + /* Hack: reset exp_routes during refeed, and do not decrease it later */ + c->stats.exp_routes = 0; + + channel_schedule_feed(c, 0); /* Sets ES_FEEDING */ + // proto_log_state_change(c); +} + +static inline int +channel_reloadable(struct channel *c) +{ + return c->proto->reload_routes && c->reloadable; } static void -proto_link_ahooks(struct proto *p) +channel_request_reload(struct channel *c) { - struct announce_hook *h; + ASSERT(c->channel_state == CS_UP); + // ASSERT(channel_reloadable(c)); - if (p->rt_notify) - for(h=p->ahooks; h; h=h->next) - add_tail(&h->table->hooks, &h->n); + 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); } -static void -proto_unlink_ahooks(struct proto *p) +const struct channel_class channel_basic = { + .channel_size = sizeof(struct channel), + .config_size = sizeof(struct channel_config) +}; + +void * +channel_config_new(const struct channel_class *cc, uint net_type, struct proto_config *proto) +{ + struct channel_config *cf = NULL; + struct rtable_config *tab = NULL; + const char *name = NULL; + + if (net_type) + { + if (!net_val_match(net_type, proto->protocol->channel_mask)) + cf_error("Unsupported channel type"); + + if (proto->net_type && (net_type != proto->net_type)) + cf_error("Different channel type"); + + tab = new_config->def_tables[net_type]; + name = net_label[net_type]; + } + + if (!cc) + cc = &channel_basic; + + cf = cfg_allocz(cc->config_size); + cf->name = name; + cf->channel = cc; + cf->table = tab; + cf->out_filter = FILTER_REJECT; + + cf->net_type = net_type; + cf->ra_mode = RA_OPTIMAL; + cf->preference = proto->protocol->preference; + + add_tail(&proto->channels, &cf->n); + + return cf; +} + +struct channel_config * +channel_copy_config(struct channel_config *src, struct proto_config *proto) +{ + struct channel_config *dst = cfg_alloc(src->channel->config_size); + + memcpy(dst, src, src->channel->config_size); + add_tail(&proto->channels, &dst->n); + CALL(src->channel->copy_config, dst, src); + + return dst; +} + + +static int reconfigure_type; /* Hack to propagate type info to channel_reconfigure() */ + +int +channel_reconfigure(struct channel *c, struct channel_config *cf) +{ + /* FIXME: better handle these changes, also handle in_keep_filtered */ + if ((c->table != cf->table->table) || (cf->ra_mode && (c->ra_mode != cf->ra_mode))) + return 0; + + int import_changed = !filter_same(c->in_filter, cf->in_filter); + int export_changed = !filter_same(c->out_filter, cf->out_filter); + + if (c->preference != cf->preference) + import_changed = 1; + + if (c->merge_limit != cf->merge_limit) + export_changed = 1; + + /* 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; + + // c->ra_mode = cf->ra_mode; + c->merge_limit = cf->merge_limit; + c->preference = cf->preference; + c->in_keep_filtered = cf->in_keep_filtered; + + channel_verify_limits(c); + + /* Execute channel-specific reconfigure hook */ + if (c->channel->reconfigure && !c->channel->reconfigure(c, cf)) + return 0; + + /* If the channel is not open, it has no routes and we cannot reload it anyways */ + if (c->channel_state != CS_UP) + return 1; + + if (reconfigure_type == RECONFIG_SOFT) + { + if (import_changed) + log(L_INFO "Channel %s.%s changed import", c->proto->name, c->name); + + if (export_changed) + log(L_INFO "Channel %s.%s changed export", c->proto->name, c->name); + + return 1; + } + + /* Route reload may be not supported */ + if (import_changed && !channel_reloadable(c)) + return 0; + + if (import_changed || export_changed) + log(L_INFO "Reloading channel %s.%s", c->proto->name, c->name); + + if (import_changed) + channel_request_reload(c); + + if (export_changed) + channel_request_feeding(c); + + return 1; +} + + +int +proto_configure_channel(struct proto *p, struct channel **pc, struct channel_config *cf) { - struct announce_hook *h; + struct channel *c = *pc; + + if (!c && cf) + { + *pc = proto_add_channel(p, cf); + } + else if (c && !cf) + { + if (c->channel_state != CS_DOWN) + { + log(L_INFO "Cannot remove channel %s.%s", c->proto->name, c->name); + return 0; + } + + proto_remove_channel(p, c); + *pc = NULL; + } + else if (c && cf) + { + if (!channel_reconfigure(c, cf)) + { + log(L_INFO "Cannot reconfigure channel %s.%s", c->proto->name, c->name); + return 0; + } + } - if (p->rt_notify) - for(h=p->ahooks; h; h=h->next) - rem_node(&h->n); + return 1; } + static void -proto_free_ahooks(struct proto *p) +proto_event(void *ptr) { - struct announce_hook *h, *hn; + struct proto *p = ptr; - for(h = p->ahooks; h; h = hn) + if (p->do_start) { - hn = h->next; - mb_free(h); + if_feed_baby(p); + p->do_start = 0; } - p->ahooks = NULL; - p->main_ahook = NULL; + 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); + } +} + + +/** + * proto_new - create a new protocol instance + * @c: protocol configuration + * + * When a new configuration has been read in, the core code starts + * initializing all the protocol instances configured by calling their + * init() hooks with the corresponding instance configuration. The initialization + * code of the protocol is expected to create a new instance according to the + * configuration by calling this function and then modifying the default settings + * to values wanted by the protocol. + */ +void * +proto_new(struct proto_config *cf) +{ + struct proto *p = mb_allocz(proto_pool, cf->protocol->proto_size); + + p->cf = cf; + p->debug = cf->debug; + p->mrtdump = cf->mrtdump; + p->name = cf->name; + p->proto = cf->protocol; + p->net_type = cf->net_type; + p->disabled = cf->disabled; + p->hash_key = random_u32(); + cf->proto = p; + + init_list(&p->channels); + + return p; +} + +static struct proto * +proto_init(struct proto_config *c, node *n) +{ + struct protocol *pr = c->protocol; + struct proto *p = pr->init(c); + + p->proto_state = PS_DOWN; + p->last_state_change = now; + insert_node(&p->n, n); + + p->event = ev_new(proto_pool); + p->event->hook = proto_event; + p->event->data = p; + + PD(p, "Initializing%s", p->disabled ? " [disabled]" : ""); + + return p; +} + +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); + + if (graceful_restart_state == GRS_INIT) + p->gr_recovery = 1; } @@ -263,22 +716,24 @@ proto_free_ahooks(struct proto *p) void * proto_config_new(struct protocol *pr, int class) { - struct proto_config *c = cfg_allocz(pr->config_size); + struct proto_config *cf = cfg_allocz(pr->config_size); if (class == SYM_PROTO) - add_tail(&new_config->protos, &c->n); - c->global = new_config; - c->protocol = pr; - c->name = pr->name; - c->preference = pr->preference; - c->class = class; - c->out_filter = FILTER_REJECT; - c->table = c->global->master_rtc; - c->debug = new_config->proto_default_debug; - c->mrtdump = new_config->proto_default_mrtdump; - return c; + add_tail(&new_config->protos, &cf->n); + + cf->global = new_config; + cf->protocol = pr; + cf->name = pr->name; + cf->class = class; + cf->debug = new_config->proto_default_debug; + cf->mrtdump = new_config->proto_default_mrtdump; + + init_list(&cf->channels); + + return cf; } + /** * proto_copy_config - copy a protocol configuration * @dest: destination protocol configuration @@ -293,6 +748,7 @@ proto_config_new(struct protocol *pr, int class) void proto_copy_config(struct proto_config *dest, struct proto_config *src) { + struct channel_config *cc; node old_node; int old_class; char *old_name; @@ -305,7 +761,7 @@ proto_copy_config(struct proto_config *dest, struct proto_config *src) DBG("Copying configuration from %s to %s\n", src->name, dest->name); - /* + /* * Copy struct proto_config here. Keep original node, class and name. * protocol-specific config copy is handled by protocol copy_config() hook */ @@ -314,12 +770,17 @@ proto_copy_config(struct proto_config *dest, struct proto_config *src) old_class = dest->class; old_name = dest->name; - memcpy(dest, src, sizeof(struct proto_config)); + memcpy(dest, src, src->protocol->config_size); dest->n = old_node; dest->class = old_class; dest->name = old_name; + init_list(&dest->channels); + + WALK_LIST(cc, src->channels) + channel_copy_config(cc, dest); + /* FIXME: allow for undefined copy_config */ dest->protocol->copy_config(dest, src); } @@ -339,66 +800,15 @@ protos_preconfig(struct config *c) init_list(&c->protos); DBG("Protocol preconfig:"); WALK_LIST(p, protocol_list) - { - DBG(" %s", p->name); - p->name_counter = 0; - if (p->preconfig) - p->preconfig(p, c); - } - DBG("\n"); -} - -/** - * protos_postconfig - post-configuration processing - * @c: new configuration - * - * This function calls the postconfig() hooks of all protocol - * instances specified in configuration @c. The hooks are not - * called for protocol templates. - */ -void -protos_postconfig(struct config *c) -{ - struct proto_config *x; - struct protocol *p; - - DBG("Protocol postconfig:"); - WALK_LIST(x, c->protos) - { - DBG(" %s", x->name); - - p = x->protocol; - if (p->postconfig) - p->postconfig(x); - } + { + DBG(" %s", p->name); + p->name_counter = 0; + if (p->preconfig) + p->preconfig(p, c); + } DBG("\n"); } -extern struct protocol proto_unix_iface; - -static struct proto * -proto_init(struct proto_config *c) -{ - struct protocol *p = c->protocol; - struct proto *q = p->init(c); - - q->proto_state = PS_DOWN; - q->core_state = FS_HUNGRY; - q->export_state = ES_DOWN; - q->last_state_change = now; - - add_tail(&initial_proto_list, &q->n); - - if (p == &proto_unix_iface) - initial_device_proto = q; - - add_tail(&proto_list, &q->glob_node); - PD(q, "Initializing%s", q->disabled ? " [disabled]" : ""); - return q; -} - -int proto_reconfig_type; /* Hack to propagate type info to pipe reconfigure hook */ - static int proto_reconfigure(struct proto *p, struct proto_config *oc, struct proto_config *nc, int type) { @@ -408,74 +818,22 @@ proto_reconfigure(struct proto *p, struct proto_config *oc, struct proto_config /* If there is a too big change in core attributes, ... */ if ((nc->protocol != oc->protocol) || - (nc->disabled != p->disabled) || - (nc->table->table != oc->table->table)) + (nc->net_type != oc->net_type) || + (nc->disabled != p->disabled)) return 0; + p->name = nc->name; p->debug = nc->debug; p->mrtdump = nc->mrtdump; - proto_reconfig_type = type; + reconfigure_type = type; /* Execute protocol specific reconfigure hook */ - if (! (p->proto->reconfigure && p->proto->reconfigure(p, nc))) + if (!p->proto->reconfigure || !p->proto->reconfigure(p, nc)) return 0; DBG("\t%s: same\n", oc->name); PD(p, "Reconfigured"); p->cf = nc; - p->name = nc->name; - p->preference = nc->preference; - - - /* Multitable protocols handle rest in their reconfigure hooks */ - if (p->proto->multitable) - return 1; - - /* Update filters and limits in the main announce hook - Note that this also resets limit state */ - if (p->main_ahook) - { - struct announce_hook *ah = p->main_ahook; - ah->in_filter = nc->in_filter; - ah->out_filter = nc->out_filter; - ah->rx_limit = nc->rx_limit; - ah->in_limit = nc->in_limit; - ah->out_limit = nc->out_limit; - ah->in_keep_filtered = nc->in_keep_filtered; - proto_verify_limits(ah); - } - - /* Update routes when filters changed. If the protocol in not UP, - it has no routes and we can ignore such changes */ - if ((p->proto_state != PS_UP) || (type == RECONFIG_SOFT)) - return 1; - - int import_changed = ! filter_same(nc->in_filter, oc->in_filter); - int export_changed = ! filter_same(nc->out_filter, oc->out_filter); - - /* We treat a change in preferences by reimporting routes */ - if (nc->preference != oc->preference) - import_changed = 1; - - if (import_changed || export_changed) - log(L_INFO "Reloading protocol %s", p->name); - - /* If import filter changed, call reload hook */ - if (import_changed && ! (p->reload_routes && p->reload_routes(p))) - { - /* Now, the protocol is reconfigured. But route reload failed - and we have to do regular protocol restart. */ - log(L_INFO "Restarting protocol %s", p->name); - p->disabled = 1; - p->down_code = PDC_CF_RESTART; - proto_rethink_goal(p); - p->disabled = 0; - proto_rethink_goal(p); - return 1; - } - - if (export_changed) - proto_request_feeding(p); return 1; } @@ -512,85 +870,94 @@ void protos_commit(struct config *new, struct config *old, int force_reconfig, int type) { struct proto_config *oc, *nc; - struct proto *p, *n; struct symbol *sym; + struct proto *p; + node *n; + DBG("protos_commit:\n"); if (old) + { + WALK_LIST(oc, old->protos) { - WALK_LIST(oc, old->protos) - { - p = oc->proto; - sym = cf_find_symbol(new, oc->name); - if (sym && sym->class == SYM_PROTO && !new->shutdown) - { - /* Found match, let's check if we can smoothly switch to new configuration */ - /* No need to check description */ - nc = sym->def; - nc->proto = p; - - /* We will try to reconfigure protocol p */ - if (! force_reconfig && proto_reconfigure(p, oc, nc, type)) - continue; - - /* Unsuccessful, we will restart it */ - if (!p->disabled && !nc->disabled) - log(L_INFO "Restarting protocol %s", p->name); - else if (p->disabled && !nc->disabled) - log(L_INFO "Enabling protocol %s", p->name); - else if (!p->disabled && nc->disabled) - log(L_INFO "Disabling protocol %s", p->name); - - p->down_code = nc->disabled ? PDC_CF_DISABLE : PDC_CF_RESTART; - p->cf_new = nc; - } - else if (!new->shutdown) - { - log(L_INFO "Removing protocol %s", p->name); - p->down_code = PDC_CF_REMOVE; - p->cf_new = NULL; - } - else /* global shutdown */ - { - p->down_code = PDC_CMD_SHUTDOWN; - p->cf_new = NULL; - } - - p->reconfiguring = 1; - config_add_obstacle(old); - proto_rethink_goal(p); - } + p = oc->proto; + sym = cf_find_symbol(new, oc->name); + if (sym && sym->class == SYM_PROTO && !new->shutdown) + { + /* Found match, let's check if we can smoothly switch to new configuration */ + /* No need to check description */ + nc = sym->def; + nc->proto = p; + + /* We will try to reconfigure protocol p */ + if (! force_reconfig && proto_reconfigure(p, oc, nc, type)) + continue; + + /* Unsuccessful, we will restart it */ + if (!p->disabled && !nc->disabled) + log(L_INFO "Restarting protocol %s", p->name); + else if (p->disabled && !nc->disabled) + log(L_INFO "Enabling protocol %s", p->name); + else if (!p->disabled && nc->disabled) + log(L_INFO "Disabling protocol %s", p->name); + + p->down_code = nc->disabled ? PDC_CF_DISABLE : PDC_CF_RESTART; + p->cf_new = nc; + } + else if (!new->shutdown) + { + log(L_INFO "Removing protocol %s", p->name); + p->down_code = PDC_CF_REMOVE; + p->cf_new = NULL; + } + else /* global shutdown */ + { + p->down_code = PDC_CMD_SHUTDOWN; + p->cf_new = NULL; + } + + p->reconfiguring = 1; + config_add_obstacle(old); + proto_rethink_goal(p); } + } + + struct proto *first_dev_proto = NULL; + n = NODE &(proto_list.head); WALK_LIST(nc, new->protos) if (!nc->proto) - { - if (old) /* Not a first-time configuration */ - log(L_INFO "Adding protocol %s", nc->name); - proto_init(nc); - } - DBG("\tdone\n"); + { + /* Not a first-time configuration */ + if (old) + log(L_INFO "Adding protocol %s", nc->name); + + p = proto_init(nc, n); + n = NODE p; + + if (p->proto == &proto_unix_iface) + first_dev_proto = p; + } + else + n = NODE nc->proto; DBG("Protocol start\n"); /* Start device protocol first */ - if (initial_device_proto) - { - proto_rethink_goal(initial_device_proto); - initial_device_proto = NULL; - } + if (first_dev_proto) + proto_rethink_goal(first_dev_proto); /* Determine router ID for the first time - it has to be here and not in global_commit() because it is postponed after start of device protocol */ if (!config->router_id) - { - config->router_id = if_choose_router_id(config->router_id_from, 0); - if (!config->router_id) - die("Cannot determine router ID, please configure it manually"); - } + { + config->router_id = if_choose_router_id(config->router_id_from, 0); + if (!config->router_id) + die("Cannot determine router ID, please configure it manually"); + } - /* Start all other protocols */ - WALK_LIST_DELSAFE(p, n, initial_proto_list) + /* Start all new protocols */ + WALK_LIST_DELSAFE(p, n, proto_list) proto_rethink_goal(p); } @@ -600,19 +967,21 @@ proto_rethink_goal(struct proto *p) struct protocol *q; byte goal; - if (p->reconfiguring && p->core_state == FS_HUNGRY && p->proto_state == PS_DOWN) - { - struct proto_config *nc = p->cf_new; - DBG("%s has shut down for reconfiguration\n", p->name); - p->cf->proto = NULL; - config_del_obstacle(p->cf->global); - rem_node(&p->n); - rem_node(&p->glob_node); - mb_free(p); - if (!nc) - return; - p = proto_init(nc); - } + if (p->reconfiguring && !p->active) + { + struct proto_config *nc = p->cf_new; + node *n = p->n.prev; + DBG("%s has shut down for reconfiguration\n", p->name); + p->cf->proto = NULL; + config_del_obstacle(p->cf->global); + proto_remove_channels(p); + rem_node(&p->n); + rfree(p->event); + mb_free(p); + if (!nc) + return; + p = proto_init(nc, n); + } /* Determine what state we want to reach */ if (p->disabled || p->reconfiguring) @@ -621,25 +990,27 @@ proto_rethink_goal(struct proto *p) goal = PS_UP; q = p->proto; - if (goal == PS_UP) /* Going up */ + if (goal == PS_UP) + { + if (!p->active) { - if (p->proto_state == PS_DOWN && p->core_state == FS_HUNGRY) - { - DBG("Kicking %s up\n", p->name); - PD(p, "Starting"); - proto_init_instance(p); - proto_notify_state(p, (q->start ? q->start(p) : PS_UP)); - } + /* 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 /* Going down */ + } + else + { + if (p->proto_state == PS_START || p->proto_state == PS_UP) { - if (p->proto_state == PS_START || p->proto_state == PS_UP) - { - DBG("Kicking %s down\n", p->name); - PD(p, "Shutting down"); - proto_notify_state(p, (q->shutdown ? q->shutdown(p) : PS_DOWN)); - } + /* Going down */ + DBG("Kicking %s down\n", p->name); + PD(p, "Shutting down"); + proto_notify_state(p, (q->shutdown ? q->shutdown(p) : PS_DOWN)); } + } } @@ -661,13 +1032,14 @@ proto_rethink_goal(struct proto *p) * When graceful restart recovery need is detected during initialization, then * enabled protocols are marked with @gr_recovery flag before start. Such * protocols then decide how to proceed with graceful restart, participation is - * voluntary. Protocols could lock the recovery by proto_graceful_restart_lock() - * (stored in @gr_lock flag), which means that they want to postpone the end of - * the recovery until they converge and then unlock it. They also could set - * @gr_wait before advancing to %PS_UP, which means that the core should defer - * route export to that protocol until the end of the recovery. This should be - * done by protocols that expect their neigbors to keep the proper routes - * (kernel table, BGP sessions with BGP graceful restart capability). + * voluntary. Protocols could lock the recovery for each channel by function + * channel_graceful_restart_lock() (state stored in @gr_lock flag), which means + * that they want to postpone the end of the recovery until they converge and + * then unlock it. They also could set @gr_wait before advancing to %PS_UP, + * which means that the core should defer route export to that channel until + * the end of the recovery. This should be done by protocols that expect their + * neigbors to keep the proper routes (kernel table, BGP sessions with BGP + * graceful restart capability). * * The graceful restart recovery is finished when either all graceful restart * locks are unlocked or when graceful restart wait timer fires. @@ -705,10 +1077,10 @@ graceful_restart_init(void) log(L_INFO "Graceful restart started"); if (!graceful_restart_locks) - { - graceful_restart_done(NULL); - return; - } + { + graceful_restart_done(NULL); + return; + } graceful_restart_state = GRS_ACTIVE; gr_wait_timer = tm_new(proto_pool); @@ -729,30 +1101,30 @@ graceful_restart_init(void) static void graceful_restart_done(struct timer *t UNUSED) { - struct proto *p; - node *n; - log(L_INFO "Graceful restart done"); graceful_restart_state = GRS_DONE; - WALK_LIST2(p, n, proto_list, glob_node) - { - if (!p->gr_recovery) - continue; + struct proto *p; + WALK_LIST(p, proto_list) + { + if (!p->gr_recovery) + continue; + struct channel *c; + WALK_LIST(c, p->channels) + { /* Resume postponed export of routes */ - if ((p->proto_state == PS_UP) && p->gr_wait) - { - proto_want_export_up(p); - proto_log_state_change(p); - } + if ((c->channel_state == CS_UP) && c->gr_wait && c->proto->rt_notify) + channel_start_export(c); /* Cleanup */ - p->gr_recovery = 0; - p->gr_wait = 0; - p->gr_lock = 0; + c->gr_wait = 0; + c->gr_lock = 0; } + p->gr_recovery = 0; + } + graceful_restart_locks = 0; } @@ -763,17 +1135,17 @@ graceful_restart_show_status(void) return; cli_msg(-24, "Graceful restart recovery in progress"); - cli_msg(-24, " Waiting for %d protocols to recover", graceful_restart_locks); + cli_msg(-24, " Waiting for %d channels to recover", graceful_restart_locks); cli_msg(-24, " Wait timer is %d/%d", tm_remains(gr_wait_timer), config->gr_wait); } /** - * proto_graceful_restart_lock - lock graceful restart by protocol - * @p: protocol instance + * channel_graceful_restart_lock - lock graceful restart by channel + * @p: channel instance * * This function allows a protocol to postpone the end of graceful restart * recovery until it converges. The lock is removed when the protocol calls - * proto_graceful_restart_unlock() or when the protocol is stopped. + * channel_graceful_restart_unlock() or when the channel is closed. * * The function have to be called during the initial phase of graceful restart * recovery and only for protocols that are part of graceful restart (i.e. their @@ -781,32 +1153,32 @@ graceful_restart_show_status(void) * hooks. */ void -proto_graceful_restart_lock(struct proto *p) +channel_graceful_restart_lock(struct channel *c) { ASSERT(graceful_restart_state == GRS_INIT); - ASSERT(p->gr_recovery); + ASSERT(c->proto->gr_recovery); - if (p->gr_lock) + if (c->gr_lock) return; - p->gr_lock = 1; + c->gr_lock = 1; graceful_restart_locks++; } /** - * proto_graceful_restart_unlock - unlock graceful restart by protocol - * @p: protocol instance + * channel_graceful_restart_unlock - unlock graceful restart by channel + * @p: channel instance * - * This function unlocks a lock from proto_graceful_restart_lock(). It is also + * This function unlocks a lock from channel_graceful_restart_lock(). It is also * automatically called when the lock holding protocol went down. */ void -proto_graceful_restart_unlock(struct proto *p) +channel_graceful_restart_unlock(struct channel *c) { - if (!p->gr_lock) + if (!c->gr_lock) return; - p->gr_lock = 0; + c->gr_lock = 0; graceful_restart_locks--; if ((graceful_restart_state == GRS_ACTIVE) && !graceful_restart_locks) @@ -827,34 +1199,26 @@ proto_graceful_restart_unlock(struct proto *p) void protos_dump_all(void) { - struct proto *p; - struct announce_hook *a; - debug("Protocols:\n"); - WALK_LIST(p, active_proto_list) + struct proto *p; + WALK_LIST(p, proto_list) + { + debug(" protocol %s state %s\n", p->name, p_states[p->proto_state]); + + struct channel *c; + WALK_LIST(c, p->channels) { - debug(" protocol %s state %s/%s\n", p->name, - p_states[p->proto_state], c_states[p->core_state]); - for (a = p->ahooks; a; a = a->next) - { - debug("\tTABLE %s\n", a->table->name); - if (a->in_filter) - debug("\tInput filter: %s\n", filter_name(a->in_filter)); - if (a->out_filter != FILTER_REJECT) - debug("\tOutput filter: %s\n", filter_name(a->out_filter)); - } - if (p->disabled) - debug("\tDISABLED\n"); - else if (p->proto->dump) - p->proto->dump(p); + debug("\tTABLE %s\n", c->table->name); + if (c->in_filter) + debug("\tInput filter: %s\n", filter_name(c->in_filter)); + if (c->out_filter) + debug("\tOutput filter: %s\n", filter_name(c->out_filter)); } - WALK_LIST(p, inactive_proto_list) - debug(" inactive %s: state %s/%s\n", p->name, p_states[p->proto_state], c_states[p->core_state]); - WALK_LIST(p, initial_proto_list) - debug(" initial %s\n", p->name); - WALK_LIST(p, flush_proto_list) - debug(" flushing %s\n", p->name); + + if (p->proto->dump && (p->proto_state != PS_DOWN)) + p->proto->dump(p); + } } /** @@ -891,12 +1255,9 @@ extern void bfd_init_all(void); void protos_build(void) { - init_list(&protocol_list); init_list(&proto_list); - init_list(&active_proto_list); - init_list(&inactive_proto_list); - init_list(&initial_proto_list); - init_list(&flush_proto_list); + init_list(&protocol_list); + proto_build(&proto_device); #ifdef CONFIG_RADV proto_build(&proto_radv); @@ -923,138 +1284,15 @@ protos_build(void) #ifdef CONFIG_BABEL proto_build(&proto_babel); #endif +#ifdef CONFIG_RPKI + proto_build(&proto_rpki); +#endif proto_pool = rp_new(&root_pool, "Protocols"); - proto_flush_event = ev_new(proto_pool); - proto_flush_event->hook = proto_flush_loop; proto_shutdown_timer = tm_new(proto_pool); proto_shutdown_timer->hook = proto_shutdown_loop; } -static void -proto_feed_more(void *P) -{ - struct proto *p = P; - - if (p->export_state != ES_FEEDING) - return; - - DBG("Feeding protocol %s continued\n", p->name); - if (rt_feed_baby(p)) - { - DBG("Feeding protocol %s finished\n", p->name); - p->export_state = ES_READY; - proto_log_state_change(p); - - if (p->feed_end) - p->feed_end(p); - } - else - { - p->attn->hook = proto_feed_more; - ev_schedule(p->attn); /* Will continue later... */ - } -} - -static void -proto_feed_initial(void *P) -{ - struct proto *p = P; - - if (p->export_state != ES_FEEDING) - return; - - DBG("Feeding protocol %s\n", p->name); - - if_feed_baby(p); - proto_feed_more(P); -} - -static void -proto_schedule_feed(struct proto *p, int initial) -{ - DBG("%s: Scheduling meal\n", p->name); - - p->export_state = ES_FEEDING; - p->refeeding = !initial; - - p->attn->hook = initial ? proto_feed_initial : proto_feed_more; - ev_schedule(p->attn); - - if (p->feed_begin) - p->feed_begin(p, initial); -} - -/* - * Flushing loop is responsible for flushing routes and protocols - * after they went down. It runs in proto_flush_event. At the start of - * one round, protocols waiting to flush are marked in - * proto_schedule_flush_loop(). At the end of the round (when routing - * table flush is complete), marked protocols are flushed and a next - * round may start. - */ - -static int flush_loop_state; /* 1 -> running */ - -static void -proto_schedule_flush_loop(void) -{ - struct proto *p; - struct announce_hook *h; - - if (flush_loop_state) - return; - flush_loop_state = 1; - - WALK_LIST(p, flush_proto_list) - { - p->flushing = 1; - for (h=p->ahooks; h; h=h->next) - rt_mark_for_prune(h->table); - } - - ev_schedule(proto_flush_event); -} - -static void -proto_flush_loop(void *unused UNUSED) -{ - struct proto *p; - - if (! rt_prune_loop()) - { - /* Rtable pruning is not finished */ - ev_schedule(proto_flush_event); - return; - } - - rt_prune_sources(); - - again: - WALK_LIST(p, flush_proto_list) - if (p->flushing) - { - /* This will flush interfaces in the same manner - like rt_prune_all() flushes routes */ - if (p->proto == &proto_unix_iface) - if_flush_ifaces(p); - - DBG("Flushing protocol %s\n", p->name); - p->flushing = 0; - p->core_state = FS_HUNGRY; - proto_relink(p); - proto_log_state_change(p); - if (p->proto_state == PS_DOWN) - proto_fell_down(p); - goto again; - } - - /* This round finished, perhaps there will be another one */ - flush_loop_state = 0; - if (!EMPTY_LIST(flush_proto_list)) - proto_schedule_flush_loop(); -} - /* Temporary hack to propagate restart to BGP */ int proto_restart; @@ -1064,19 +1302,19 @@ proto_shutdown_loop(struct timer *t UNUSED) { struct proto *p, *p_next; - WALK_LIST_DELSAFE(p, p_next, active_proto_list) + WALK_LIST_DELSAFE(p, p_next, proto_list) if (p->down_sched) - { - proto_restart = (p->down_sched == PDS_RESTART); + { + proto_restart = (p->down_sched == PDS_RESTART); - p->disabled = 1; + p->disabled = 1; + proto_rethink_goal(p); + if (proto_restart) + { + p->disabled = 0; proto_rethink_goal(p); - if (proto_restart) - { - p->disabled = 0; - proto_rethink_goal(p); - } } + } } static inline void @@ -1095,50 +1333,8 @@ proto_schedule_down(struct proto *p, byte restart, byte code) } -/** - * proto_request_feeding - request feeding routes to the protocol - * @p: given protocol - * - * Sometimes it is needed to send again all routes to the - * protocol. This is called feeding and can be requested by this - * function. This would cause protocol export state transition - * to ES_FEEDING (during feeding) and when 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 -proto_request_feeding(struct proto *p) -{ - ASSERT(p->proto_state == PS_UP); - - /* Do nothing if we are still waiting for feeding */ - if (p->export_state == ES_DOWN) - return; - - /* If we are already feeding, we want to restart it */ - if (p->export_state == ES_FEEDING) - { - /* Unless feeding is in initial state */ - if (p->attn->hook == proto_feed_initial) - return; - - rt_feed_baby_abort(p); - } - - /* FIXME: This should be changed for better support of multitable protos */ - struct announce_hook *ah; - for (ah = p->ahooks; ah; ah = ah->next) - proto_reset_limit(ah->out_limit); - - /* Hack: reset exp_routes during refeed, and do not decrease it later */ - p->stats.exp_routes = 0; - - proto_schedule_feed(p, 0); - proto_log_state_change(p); -} - static const char * -proto_limit_name(struct proto_limit *l) +channel_limit_name(struct channel_limit *l) { const char *actions[] = { [PLA_WARN] = "warn", @@ -1151,22 +1347,22 @@ proto_limit_name(struct proto_limit *l) } /** - * proto_notify_limit: notify about limit hit and take appropriate action - * @ah: announce hook + * 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 + * @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 -proto_notify_limit(struct announce_hook *ah, struct proto_limit *l, int dir, u32 rt_count) +channel_notify_limit(struct channel *c, struct channel_limit *l, int dir, u32 rt_count) { 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 = ah->proto; + struct proto *p = c->proto; if (l->state == PLS_BLOCKED) return; @@ -1174,148 +1370,112 @@ proto_notify_limit(struct announce_hook *ah, struct proto_limit *l, int dir, u32 /* 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, proto_limit_name(l)); + p->name, dir_name[dir], l->limit, channel_limit_name(l)); switch (l->action) - { - case PLA_WARN: - l->state = PLS_ACTIVE; - break; - - case PLA_BLOCK: - l->state = PLS_BLOCKED; - break; - - 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; - } + { + case PLA_WARN: + l->state = PLS_ACTIVE; + break; + + case PLA_BLOCK: + l->state = PLS_BLOCKED; + break; + + 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; + } } -void -proto_verify_limits(struct announce_hook *ah) +static void +channel_verify_limits(struct channel *c) { - struct proto_limit *l; - struct proto_stats *stats = ah->stats; - u32 all_routes = stats->imp_routes + stats->filt_routes; + struct channel_limit *l; + u32 all_routes = c->stats.imp_routes + c->stats.filt_routes; - l = ah->rx_limit; - if (l && (all_routes > l->limit)) - proto_notify_limit(ah, l, PLD_RX, all_routes); + l = &c->rx_limit; + if (l->action && (all_routes > l->limit)) + channel_notify_limit(c, l, PLD_RX, all_routes); - l = ah->in_limit; - if (l && (stats->imp_routes > l->limit)) - proto_notify_limit(ah, l, PLD_IN, stats->imp_routes); + l = &c->in_limit; + if (l->action && (c->stats.imp_routes > l->limit)) + channel_notify_limit(c, l, PLD_IN, c->stats.imp_routes); - l = ah->out_limit; - if (l && (stats->exp_routes > l->limit)) - proto_notify_limit(ah, l, PLD_OUT, stats->exp_routes); + l = &c->out_limit; + if (l->action && (c->stats.exp_routes > l->limit)) + channel_notify_limit(c, l, PLD_OUT, c->stats.exp_routes); } - -static void -proto_want_core_up(struct proto *p) +static inline void +channel_reset_limit(struct channel_limit *l) { - ASSERT(p->core_state == FS_HUNGRY); - - if (!p->proto->multitable) - { - p->main_source = rt_get_source(p, 0); - rt_lock_source(p->main_source); - - /* Connect protocol to routing table */ - p->main_ahook = proto_add_announce_hook(p, p->table, &p->stats); - p->main_ahook->in_filter = p->cf->in_filter; - p->main_ahook->out_filter = p->cf->out_filter; - p->main_ahook->rx_limit = p->cf->rx_limit; - p->main_ahook->in_limit = p->cf->in_limit; - p->main_ahook->out_limit = p->cf->out_limit; - p->main_ahook->in_keep_filtered = p->cf->in_keep_filtered; - - proto_reset_limit(p->main_ahook->rx_limit); - proto_reset_limit(p->main_ahook->in_limit); - proto_reset_limit(p->main_ahook->out_limit); - } - - p->core_state = FS_HAPPY; - proto_relink(p); + if (l->action) + l->state = PLS_INITIAL; } -static void -proto_want_export_up(struct proto *p) +static inline void +proto_do_start(struct proto *p) { - ASSERT(p->core_state == FS_HAPPY); - ASSERT(p->export_state == ES_DOWN); - - proto_link_ahooks(p); - proto_schedule_feed(p, 1); /* Sets ES_FEEDING */ + p->active = 1; + p->do_start = 1; + ev_schedule(p->event); } static void -proto_want_export_down(struct proto *p) +proto_do_up(struct proto *p) { - ASSERT(p->export_state != ES_DOWN); - - /* Need to abort feeding */ - if (p->export_state == ES_FEEDING) - rt_feed_baby_abort(p); + if (!p->main_source) + { + p->main_source = rt_get_source(p, 0); + rt_lock_source(p->main_source); + } - p->export_state = ES_DOWN; - p->stats.exp_routes = 0; - proto_unlink_ahooks(p); + proto_start_channels(p); } -static void -proto_want_core_down(struct proto *p) +static inline void +proto_do_pause(struct proto *p) { - ASSERT(p->core_state == FS_HAPPY); - ASSERT(p->export_state == ES_DOWN); - - p->core_state = FS_FLUSHING; - proto_relink(p); - proto_schedule_flush_loop(); - - if (!p->proto->multitable) - { - rt_unlock_source(p->main_source); - p->main_source = NULL; - } + proto_pause_channels(p); } static void -proto_falling_down(struct proto *p) +proto_do_stop(struct proto *p) { + p->down_sched = 0; p->gr_recovery = 0; - p->gr_wait = 0; - if (p->gr_lock) - proto_graceful_restart_unlock(p); -} - -static void -proto_fell_down(struct proto *p) -{ - DBG("Protocol %s down\n", p->name); - - u32 all_routes = p->stats.imp_routes + p->stats.filt_routes; - if (all_routes != 0) - log(L_ERR "Protocol %s is down but still has %d routes", p->name, all_routes); - bzero(&p->stats, sizeof(struct proto_stats)); - proto_free_ahooks(p); + p->do_stop = 1; + ev_schedule(p->event); - if (! p->proto->multitable) - rt_unlock_table(p->table); + if (p->main_source) + { + rt_unlock_source(p->main_source); + p->main_source = NULL; + } - if (p->proto->cleanup) - p->proto->cleanup(p); + proto_stop_channels(p); +} - proto_rethink_goal(p); +static void +proto_do_down(struct proto *p) +{ + p->down_code = 0; + neigh_prune(); + rfree(p->pool); + p->pool = NULL; + + /* Shutdown is finished in the protocol event */ + if (proto_is_done(p)) + ev_schedule(p->event); } + /** * proto_notify_state - notify core about protocol state change * @p: protocol the state of which has changed @@ -1331,78 +1491,53 @@ proto_fell_down(struct proto *p) * it should be used at tail positions of protocol callbacks. */ void -proto_notify_state(struct proto *p, unsigned ps) +proto_notify_state(struct proto *p, uint state) { - unsigned ops = p->proto_state; - unsigned cs = p->core_state; - unsigned es = p->export_state; + uint ps = p->proto_state; - DBG("%s reporting state transition %s/%s -> */%s\n", p->name, c_states[cs], p_states[ops], p_states[ps]); - if (ops == ps) + DBG("%s reporting state transition %s -> %s\n", p->name, p_states[ps], p_states[state]); + if (state == ps) return; - p->proto_state = ps; + p->proto_state = state; p->last_state_change = now; - switch (ps) - { - case PS_START: - ASSERT(ops == PS_DOWN || ops == PS_UP); - ASSERT(cs == FS_HUNGRY || cs == FS_HAPPY); - - if (es != ES_DOWN) - proto_want_export_down(p); - break; - - case PS_UP: - ASSERT(ops == PS_DOWN || ops == PS_START); - ASSERT(cs == FS_HUNGRY || cs == FS_HAPPY); - ASSERT(es == ES_DOWN); - - if (cs == FS_HUNGRY) - proto_want_core_up(p); - if (!p->gr_wait) - proto_want_export_up(p); - break; - - case PS_STOP: - ASSERT(ops == PS_START || ops == PS_UP); - - p->down_sched = 0; - - if (es != ES_DOWN) - proto_want_export_down(p); - if (cs == FS_HAPPY) - proto_want_core_down(p); - proto_falling_down(p); - break; - - case PS_DOWN: - p->down_code = 0; - p->down_sched = 0; - - if (es != ES_DOWN) - proto_want_export_down(p); - if (cs == FS_HAPPY) - proto_want_core_down(p); - if (ops != PS_STOP) - proto_falling_down(p); - - neigh_prune(); // FIXME convert neighbors to resource? - rfree(p->pool); - p->pool = NULL; - - if (cs == FS_HUNGRY) /* Shutdown finished */ - { - proto_log_state_change(p); - proto_fell_down(p); - return; /* The protocol might have ceased to exist */ - } - break; - - default: - bug("%s: Invalid state %d", p->name, ps); - } + switch (state) + { + case PS_START: + ASSERT(ps == PS_DOWN || ps == PS_UP); + + if (ps == PS_DOWN) + proto_do_start(p); + else + proto_do_pause(p); + break; + + case PS_UP: + ASSERT(ps == PS_DOWN || ps == PS_START); + + if (ps == PS_DOWN) + proto_do_start(p); + + proto_do_up(p); + break; + + case PS_STOP: + ASSERT(ps == PS_START || ps == PS_UP); + + proto_do_stop(p); + break; + + case PS_DOWN: + if (ps != PS_STOP) + proto_do_stop(p); + + proto_do_down(p); + break; + + default: + bug("%s: Invalid state %d", p->name, ps); + } proto_log_state_change(p); } @@ -1414,82 +1549,74 @@ proto_notify_state(struct proto *p, unsigned ps) static char * proto_state_name(struct proto *p) { -#define P(x,y) ((x << 4) | y) - switch (P(p->proto_state, p->core_state)) - { - case P(PS_DOWN, FS_HUNGRY): return "down"; - case P(PS_START, FS_HUNGRY): - case P(PS_START, FS_HAPPY): return "start"; - case P(PS_UP, FS_HAPPY): - switch (p->export_state) - { - case ES_DOWN: return "wait"; - case ES_FEEDING: return "feed"; - case ES_READY: return "up"; - default: return "???"; - } - case P(PS_STOP, FS_HUNGRY): - case P(PS_STOP, FS_FLUSHING): return "stop"; - case P(PS_DOWN, FS_FLUSHING): return "flush"; - default: return "???"; - } -#undef P + switch (p->proto_state) + { + case PS_DOWN: return p->active ? "flush" : "down"; + case PS_START: return "start"; + case PS_UP: return "up"; + case PS_STOP: return "stop"; + default: return "???"; + } } static void -proto_show_stats(struct proto_stats *s, int in_keep_filtered) +channel_show_stats(struct channel *c) { - if (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); + struct proto_stats *s = &c->stats; + + if (c->in_keep_filtered) + cli_msg(-1006, " Routes: %u imported, %u filtered, %u exported", + s->imp_routes, s->filt_routes, s->exp_routes); else - cli_msg(-1006, " Routes: %u imported, %u exported, %u preferred", - s->imp_routes, s->exp_routes, s->pref_routes); + cli_msg(-1006, " Routes: %u imported, %u exported", + s->imp_routes, s->exp_routes); - cli_msg(-1006, " Route change stats: received rejected filtered ignored accepted"); - cli_msg(-1006, " Import updates: %10u %10u %10u %10u %10u", + 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", + 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", + 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", + cli_msg(-1006, " Export withdraws: %10u --- --- --- %10u", s->exp_withdraws_received, s->exp_withdraws_accepted); } void -proto_show_limit(struct proto_limit *l, const char *dsc) +channel_show_limit(struct channel_limit *l, const char *dsc) { - if (!l) + if (!l->action) return; - cli_msg(-1006, " %-16s%d%s", dsc, l->limit, l->state ? " [HIT]" : ""); - cli_msg(-1006, " Action: %s", proto_limit_name(l)); + cli_msg(-1006, " %-16s%d%s", dsc, l->limit, l->state ? " [HIT]" : ""); + cli_msg(-1006, " Action: %s", channel_limit_name(l)); } void -proto_show_basic_info(struct proto *p) +channel_show_info(struct channel *c) { - // cli_msg(-1006, " Table: %s", p->table->name); - cli_msg(-1006, " Preference: %d", p->preference); - cli_msg(-1006, " Input filter: %s", filter_name(p->cf->in_filter)); - cli_msg(-1006, " Output filter: %s", filter_name(p->cf->out_filter)); + cli_msg(-1006, " Channel %s", c->name); + cli_msg(-1006, " State: %s", c_states[c->channel_state]); + 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)); + cli_msg(-1006, " Output filter: %s", filter_name(c->out_filter)); if (graceful_restart_state == GRS_ACTIVE) - cli_msg(-1006, " GR recovery: %s%s", - p->gr_lock ? " pending" : "", - p->gr_wait ? " waiting" : ""); + cli_msg(-1006, " GR recovery: %s%s", + c->gr_lock ? " pending" : "", + c->gr_wait ? " waiting" : ""); - proto_show_limit(p->cf->rx_limit, "Receive limit:"); - proto_show_limit(p->cf->in_limit, "Import limit:"); - proto_show_limit(p->cf->out_limit, "Export limit:"); + channel_show_limit(&c->rx_limit, "Receive limit:"); + channel_show_limit(&c->in_limit, "Import limit:"); + channel_show_limit(&c->out_limit, "Export limit:"); - if (p->proto_state != PS_DOWN) - proto_show_stats(&p->stats, p->cf->in_keep_filtered); + if (c->channel_state != CS_DOWN) + channel_show_stats(c); } void @@ -1508,34 +1635,39 @@ proto_cmd_show(struct proto *p, uint verbose, int cnt) cli_msg(-1002, "%-8s %-8s %-8s %-5s %-10s %s", p->name, p->proto->name, - p->table->name, + p->main_channel ? p->main_channel->table->name : "---", proto_state_name(p), tbuf, buf); + if (verbose) + { + if (p->cf->dsc) + cli_msg(-1006, " Description: %s", p->cf->dsc); + if (p->cf->router_id) + cli_msg(-1006, " Router ID: %R", p->cf->router_id); + + if (p->proto->show_proto_info) + p->proto->show_proto_info(p); + else { - if (p->cf->dsc) - cli_msg(-1006, " Description: %s", p->cf->dsc); - if (p->cf->router_id) - cli_msg(-1006, " Router ID: %R", p->cf->router_id); - - if (p->proto->show_proto_info) - p->proto->show_proto_info(p); - else - proto_show_basic_info(p); - - cli_msg(-1006, ""); + struct channel *c; + WALK_LIST(c, p->channels) + channel_show_info(c); } + + cli_msg(-1006, ""); + } } void proto_cmd_disable(struct proto *p, uint arg UNUSED, int cnt UNUSED) { if (p->disabled) - { - cli_msg(-8, "%s: already disabled", p->name); - return; - } + { + cli_msg(-8, "%s: already disabled", p->name); + return; + } log(L_INFO "Disabling protocol %s", p->name); p->disabled = 1; @@ -1548,10 +1680,10 @@ void proto_cmd_enable(struct proto *p, uint arg UNUSED, int cnt UNUSED) { if (!p->disabled) - { - cli_msg(-10, "%s: already enabled", p->name); - return; - } + { + cli_msg(-10, "%s: already enabled", p->name); + return; + } log(L_INFO "Enabling protocol %s", p->name); p->disabled = 0; @@ -1563,10 +1695,10 @@ void proto_cmd_restart(struct proto *p, uint arg UNUSED, int cnt UNUSED) { if (p->disabled) - { - cli_msg(-8, "%s: already disabled", p->name); - return; - } + { + cli_msg(-8, "%s: already disabled", p->name); + return; + } log(L_INFO "Restarting protocol %s", p->name); p->disabled = 1; @@ -1580,41 +1712,38 @@ proto_cmd_restart(struct proto *p, uint arg UNUSED, int cnt UNUSED) void proto_cmd_reload(struct proto *p, uint dir, int cnt UNUSED) { + struct channel *c; + if (p->disabled) - { - cli_msg(-8, "%s: already disabled", p->name); - return; - } + { + cli_msg(-8, "%s: already disabled", p->name); + return; + } /* If the protocol in not UP, it has no routes */ if (p->proto_state != PS_UP) return; + /* All channels must support reload */ + if (dir != CMD_RELOAD_OUT) + WALK_LIST(c, p->channels) + if (!channel_reloadable(c)) + { + cli_msg(-8006, "%s: reload failed", p->name); + return; + } + log(L_INFO "Reloading protocol %s", p->name); /* re-importing routes */ if (dir != CMD_RELOAD_OUT) - { - if (! (p->reload_routes && p->reload_routes(p))) - { - cli_msg(-8006, "%s: reload failed", p->name); - return; - } - - /* - * Should be done before reload_routes() hook? - * Perhaps, but these hooks work asynchronously. - */ - if (!p->proto->multitable) - { - proto_reset_limit(p->main_ahook->rx_limit); - proto_reset_limit(p->main_ahook->in_limit); - } - } + WALK_LIST(c, p->channels) + channel_request_reload(c); /* re-exporting routes */ if (dir != CMD_RELOAD_IN) - proto_request_feeding(p); + WALK_LIST(c, p->channels) + channel_request_feeding(c); cli_msg(-15, "%s: reloading", p->name); } @@ -1635,10 +1764,10 @@ static void proto_apply_cmd_symbol(struct symbol *s, void (* cmd)(struct proto *, uint, int), uint arg) { if (s->class != SYM_PROTO) - { - cli_msg(9002, "%s is not a protocol", s->name); - return; - } + { + cli_msg(9002, "%s is not a protocol", s->name); + return; + } cmd(((struct proto_config *)s->def)->proto, arg, 0); cli_msg(0, ""); @@ -1647,16 +1776,12 @@ proto_apply_cmd_symbol(struct symbol *s, void (* cmd)(struct proto *, uint, int) static void proto_apply_cmd_patt(char *patt, void (* cmd)(struct proto *, uint, int), uint arg) { + struct proto *p; int cnt = 0; - node *nn; - WALK_LIST(nn, proto_list) - { - struct proto *p = SKIP_BACK(struct proto, glob_node, nn); - - if (!patt || patmatch(patt, p->name)) - cmd(p, arg, cnt++); - } + WALK_LIST(p, proto_list) + if (!patt || patmatch(patt, p->name)) + cmd(p, arg, cnt++); if (!cnt) cli_msg(8003, "No protocols match"); @@ -1683,25 +1808,27 @@ proto_get_named(struct symbol *sym, struct protocol *pr) struct proto *p, *q; if (sym) - { - if (sym->class != SYM_PROTO) - cf_error("%s: Not a protocol", sym->name); - p = ((struct proto_config *)sym->def)->proto; - if (!p || p->proto != pr) - cf_error("%s: Not a %s protocol", sym->name, pr->name); - } + { + if (sym->class != SYM_PROTO) + cf_error("%s: Not a protocol", sym->name); + + p = ((struct proto_config *) sym->def)->proto; + if (!p || p->proto != pr) + cf_error("%s: Not a %s protocol", sym->name, pr->name); + } else - { - p = NULL; - WALK_LIST(q, active_proto_list) - if (q->proto == pr) - { - if (p) - cf_error("There are multiple %s protocols running", pr->name); - p = q; - } - if (!p) - cf_error("There is no %s protocol running", pr->name); - } + { + p = NULL; + WALK_LIST(q, proto_list) + if ((q->proto == pr) && (q->proto_state != PS_DOWN)) + { + if (p) + cf_error("There are multiple %s protocols running", pr->name); + p = q; + } + if (!p) + cf_error("There is no %s protocol running", pr->name); + } + return p; } diff --git a/nest/proto.sgml b/nest/proto.sgml index 1d4c31a7..53da78b8 100644 --- a/nest/proto.sgml +++ b/nest/proto.sgml @@ -69,23 +69,6 @@ its state by calling the <func/proto_notify_state/ function. <p>At any time, the core code can ask the protocol to shut itself down by calling its stop() hook. -<p>The <em/core state machine/ takes care of the core view of protocol state. -The states are traversed according to changes of the protocol state machine, but -sometimes the transitions are delayed if the core needs to finish some actions -(for example sending of new routes to the protocol) before proceeding to the -new state. There are the following core states: - -<descrip> - <tag/FS_HUNGRY/ The protocol is down, it doesn't have any routes and - doesn't want them. - <tag/FS_FEEDING/ The protocol has reached the <tt/PS_UP/ state, but - we are still busy sending the initial set of routes to it. - <tag/FS_HAPPY/ The protocol is up and has complete routing information. - <tag/FS_FLUSHING/ The protocol is shutting down (it's in either <tt/PS_STOP/ - or <tt/PS_DOWN/ state) and we're flushing all of its routes from the - routing tables. -</descrip> - <sect1>Functions of the protocol module <p>The protocol module provides the following functions: diff --git a/nest/protocol.h b/nest/protocol.h index ec787355..eed0a291 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -11,7 +11,9 @@ #include "lib/lists.h" #include "lib/resource.h" -#include "lib/timer.h" +#include "lib/event.h" +#include "sysdep/unix/timer.h" +#include "nest/route.h" #include "conf/conf.h" struct iface; @@ -22,13 +24,16 @@ struct neighbor; struct rta; struct network; struct proto_config; +struct channel_limit; +struct channel_config; struct config; struct proto; -struct event; +struct channel; struct ea_list; struct eattr; struct symbol; + /* * Routing Protocol */ @@ -39,9 +44,10 @@ struct protocol { char *template; /* Template for automatic generation of names */ int name_counter; /* Counter for automatic name generation */ int attr_class; /* Attribute class known to this protocol */ - int multitable; /* Protocol handles all announce hooks itself */ uint preference; /* Default protocol preference */ - uint config_size; /* Size of protocol config */ + uint channel_mask; /* Mask of accepted channel types (NB_*) */ + uint proto_size; /* Size of protocol data structure */ + uint config_size; /* Size of protocol config data structure */ void (*preconfig)(struct protocol *, struct config *); /* Just before configuring */ void (*postconfig)(struct proto_config *); /* After configuring each instance */ @@ -62,7 +68,6 @@ struct protocol { void protos_build(void); void proto_build(struct protocol *); void protos_preconfig(struct config *); -void protos_postconfig(struct config *); void protos_commit(struct config *new, struct config *old, int force_restart, int type); void protos_dump_all(void); @@ -76,7 +81,7 @@ void protos_dump_all(void); extern struct protocol proto_device, proto_radv, proto_rip, proto_static, - proto_ospf, proto_pipe, proto_bgp, proto_bfd, proto_babel; + proto_ospf, proto_pipe, proto_bgp, proto_bfd, proto_babel, proto_rpki; /* * Routing Protocol Instance @@ -90,16 +95,12 @@ struct proto_config { char *name; char *dsc; int class; /* SYM_PROTO or SYM_TEMPLATE */ + u8 net_type; /* Protocol network type (NET_*), 0 for undefined */ + u8 disabled; /* Protocol enabled/disabled by default */ u32 debug, mrtdump; /* Debugging bitfields, both use D_* constants */ - unsigned preference, disabled; /* Generic parameters */ - int in_keep_filtered; /* Routes rejected in import filter are kept */ u32 router_id; /* Protocol specific router ID */ - struct rtable_config *table; /* Table we're attached to */ - struct filter *in_filter, *out_filter; /* Attached filters */ - struct proto_limit *rx_limit; /* Limit for receiving routes from protocol - (relevant when in_keep_filtered is active) */ - struct proto_limit *in_limit; /* Limit for importing routes from protocol */ - struct proto_limit *out_limit; /* Limit for exporting routes to protocol */ + + list channels; /* List of channel configs (struct channel_config) */ /* Check proto_reconfigure() and proto_copy_config() after changing struct proto_config */ @@ -111,7 +112,6 @@ 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 that are preferred, sum over all routing tables */ 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 */ @@ -133,36 +133,34 @@ struct proto_stats { }; struct proto { - node n; /* Node in *_proto_list */ - node glob_node; /* Node in global proto_list */ + node n; /* Node in global proto_list */ struct protocol *proto; /* Protocol */ struct proto_config *cf; /* Configuration data */ struct proto_config *cf_new; /* Configuration we want to switch to after shutdown (NULL=delete) */ pool *pool; /* Pool containing local objects */ - struct event *attn; /* "Pay attention" event */ + event *event; /* Protocol event */ + + list channels; /* List of channels to rtables (struct channel) */ + struct channel *main_channel; /* Primary channel */ + struct rte_src *main_source; /* Primary route source */ char *name; /* Name of this instance (== cf->name) */ u32 debug; /* Debugging flags */ u32 mrtdump; /* MRTDump flags */ - unsigned preference; /* Default route preference */ - byte accept_ra_types; /* Which types of route announcements are accepted (RA_OPTIMAL or RA_ANY) */ + uint active_channels; /* Number of active channels */ + byte net_type; /* Protocol network type (NET_*), 0 for undefined */ byte disabled; /* Manually disabled */ byte proto_state; /* Protocol state machine (PS_*, see below) */ - byte core_state; /* Core state machine (FS_*, see below) */ - byte export_state; /* Route export state (ES_*, 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 refeeding; /* We are refeeding (valid only if export_state == ES_FEEDING) */ - byte flushing; /* Protocol is flushed in current flush loop round */ byte gr_recovery; /* Protocol should participate in graceful restart recovery */ - byte gr_lock; /* Graceful restart mechanism should wait for this proto */ - byte gr_wait; /* Route export to protocol is postponed until graceful restart */ byte down_sched; /* Shutdown is scheduled for later (PDS_*) */ byte down_code; /* Reason for shutdown (PDC_* codes) */ - byte merge_limit; /* Maximal number of nexthops for RA_MERGED */ u32 hash_key; /* Random key used for hashing of neighbors */ bird_clock_t last_state_change; /* Time of last state transition */ char *last_state_name_announced; /* Last state name we've announced to the user */ - struct proto_stats stats; /* Current protocol statistics */ /* * General protocol hooks: @@ -177,23 +175,23 @@ struct proto { * It can construct a new rte, add private attributes and * decide whether the route shall be imported: 1=yes, -1=no, * 0=process it through the import filter set by the user. - * reload_routes Request protocol to reload all its routes to the core + * 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). - * feed_begin Notify protocol about beginning of route feeding. - * feed_end Notify protocol about finish of route feeding. + * feed_begin Notify channel about beginning of route feeding. + * feed_end Notify channel about finish of route feeding. */ 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 rtable *table, struct network *net, struct rte *new, struct rte *old, struct ea_list *attrs); + void (*rt_notify)(struct proto *, struct channel *, struct network *net, struct rte *new, struct rte *old, struct ea_list *attrs); void (*neigh_notify)(struct neighbor *neigh); struct ea_list *(*make_tmp_attrs)(struct rte *rt, struct linpool *pool); void (*store_tmp_attrs)(struct rte *rt, struct ea_list *attrs); int (*import_control)(struct proto *, struct rte **rt, struct ea_list **attrs, struct linpool *pool); - int (*reload_routes)(struct proto *); - void (*feed_begin)(struct proto *, int initial); - void (*feed_end)(struct proto *); + void (*reload_routes)(struct channel *); + void (*feed_begin)(struct channel *, int initial); + void (*feed_end)(struct channel *); /* * Routing entry hooks (called only for routes belonging to this protocol): @@ -213,14 +211,6 @@ struct proto { void (*rte_insert)(struct network *, struct rte *); void (*rte_remove)(struct network *, struct rte *); - struct rtable *table; /* Our primary routing table */ - struct rte_src *main_source; /* Primary route source */ - struct announce_hook *main_ahook; /* Primary announcement hook */ - struct announce_hook *ahooks; /* Announcement hooks for this protocol */ - - struct fib_iterator *feed_iterator; /* Routing table iterator used during protocol feeding */ - struct announce_hook *feed_ahook; /* Announce hook we currently feed */ - /* Hic sunt protocol-specific data */ }; @@ -244,25 +234,20 @@ struct proto_spec { #define PDC_OUT_LIMIT_HIT 0x23 /* Route export limit reached */ -void *proto_new(struct proto_config *, unsigned size); +void *proto_new(struct proto_config *); void *proto_config_new(struct protocol *, int class); void proto_copy_config(struct proto_config *dest, struct proto_config *src); -void proto_request_feeding(struct proto *p); - -static inline void -proto_copy_rest(struct proto_config *dest, struct proto_config *src, unsigned size) -{ memcpy(dest + 1, src + 1, size - sizeof(struct proto_config)); } void graceful_restart_recovery(void); void graceful_restart_init(void); void graceful_restart_show_status(void); -void proto_graceful_restart_lock(struct proto *p); -void proto_graceful_restart_unlock(struct proto *p); +void channel_graceful_restart_lock(struct channel *c); +void channel_graceful_restart_unlock(struct channel *c); #define DEFAULT_GR_WAIT 240 -void proto_show_limit(struct proto_limit *l, const char *dsc); -void proto_show_basic_info(struct proto *p); +void channel_show_limit(struct channel_limit *l, const char *dsc); +void channel_show_info(struct channel *c); void proto_cmd_show(struct proto *, uint, int); void proto_cmd_disable(struct proto *, uint, int); @@ -285,7 +270,19 @@ proto_get_router_id(struct proto_config *pc) return pc->router_id ? pc->router_id : pc->global->router_id; } -extern list active_proto_list; +static inline struct ea_list * +rte_make_tmp_attrs(struct rte *rt, struct linpool *pool) +{ + struct ea_list *(*mta)(struct rte *rt, struct linpool *pool); + mta = rt->attrs->src->proto->make_tmp_attrs; + return mta ? mta(rt, pool) : NULL; +} + +/* 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); } + +extern pool *proto_pool; +extern list proto_list; /* * Each protocol instance runs two different state machines: @@ -361,16 +358,6 @@ void proto_notify_state(struct proto *p, unsigned state); * as a result of received ROUTE-REFRESH request). */ -#define FS_HUNGRY 0 -#define FS_FEEDING 1 /* obsolete */ -#define FS_HAPPY 2 -#define FS_FLUSHING 3 - - -#define ES_DOWN 0 -#define ES_FEEDING 1 -#define ES_READY 2 - /* @@ -413,6 +400,7 @@ extern struct proto_config *cf_dev_proto; #define PLD_OUT 2 /* Export limit */ #define PLD_MAX 3 +#define PLA_NONE 0 /* No limit */ #define PLA_WARN 1 /* Issue log warning */ #define PLA_BLOCK 2 /* Block new routes */ #define PLA_RESTART 4 /* Force protocol restart */ @@ -422,42 +410,180 @@ extern struct proto_config *cf_dev_proto; #define PLS_ACTIVE 1 /* Limit was hit */ #define PLS_BLOCKED 2 /* Limit is active and blocking new routes */ -struct proto_limit { +struct channel_limit { u32 limit; /* Maximum number of prefixes */ - byte action; /* Action to take (PLA_*) */ - byte state; /* State of limit (PLS_*) */ + u8 action; /* Action to take (PLA_*) */ + u8 state; /* State of limit (PLS_*) */ }; -void proto_notify_limit(struct announce_hook *ah, struct proto_limit *l, int dir, u32 rt_count); -void proto_verify_limits(struct announce_hook *ah); - -static inline void -proto_reset_limit(struct proto_limit *l) -{ - if (l) - l->state = PLS_INITIAL; -} +void channel_notify_limit(struct channel *c, struct channel_limit *l, int dir, u32 rt_count); /* - * Route Announcement Hook + * Channels */ -struct announce_hook { +struct channel_class { + uint channel_size; /* Size of channel data structure */ + uint config_size; /* Size of channel config data structure */ + + void (*init)(struct channel *, struct channel_config *); /* Create new instance */ + int (*reconfigure)(struct channel *, struct channel_config *); /* Try to reconfigure instance, returns success */ + int (*start)(struct channel *); /* Start the instance */ + void (*shutdown)(struct channel *); /* Stop the instance */ + void (*cleanup)(struct channel *); /* Channel finished flush */ + + void (*copy_config)(struct channel_config *, struct channel_config *); /* Copy config from given channel instance */ +#if 0 + XXXX; + void (*preconfig)(struct protocol *, struct config *); /* Just before configuring */ + void (*postconfig)(struct proto_config *); /* After configuring each instance */ + + + 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, struct ea_list *attrs); /* Get route information (for `show route' command) */ + int (*get_attr)(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) */ + +#endif +}; + +extern struct channel_class channel_bgp; + +struct channel_config { node n; - struct rtable *table; + const char *name; + const struct channel_class *channel; + + struct rtable_config *table; /* Table we're attached to */ + 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 */ + struct channel_limit out_limit; /* Limit for exporting routes to protocol */ + + u8 net_type; /* Routing table network type (NET_*), 0 for undefined */ + u8 ra_mode; /* Mode of received route advertisements (RA_*) */ + u16 preference; /* Default route preference */ + u8 merge_limit; /* Maximal number of nexthops for RA_MERGED */ + u8 in_keep_filtered; /* Routes rejected in import filter are kept */ +}; + +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; struct filter *in_filter; /* Input filter */ struct filter *out_filter; /* Output filter */ - struct proto_limit *rx_limit; /* Receive limit (for in_keep_filtered) */ - struct proto_limit *in_limit; /* Input limit */ - struct proto_limit *out_limit; /* Output limit */ - struct proto_stats *stats; /* Per-table protocol statistics */ - struct announce_hook *next; /* Next hook for the same protocol */ - int in_keep_filtered; /* Routes rejected in import filter are kept */ + 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 */ + + u8 net_type; /* Routing table network type (NET_*), 0 for undefined */ + u8 ra_mode; /* Mode of received route advertisements (RA_*) */ + u16 preference; /* Default route preference */ + u8 merge_limit; /* Maximal number of nexthops for RA_MERGED */ + u8 in_keep_filtered; /* Routes rejected in import filter are kept */ + u8 disabled; + 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 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 */ + + bird_clock_t last_state_change; /* Time of last state transition */ }; -struct announce_hook *proto_add_announce_hook(struct proto *p, struct rtable *t, struct proto_stats *stats); -struct announce_hook *proto_find_announce_hook(struct proto *p, struct rtable *t); + +/* + * Channel states + * + * CS_DOWN - The initial and the final state of a channel. There is no route + * exchange between the protocol and the table. Channel is not counted as + * active. Channel keeps a ptr to the table, but do not lock the table and is + * not linked in the table. Generally, new closed channels are created in + * protocols' init() hooks. The protocol is expected to explicitly activate its + * channels (by calling channel_init() or channel_open()). + * + * CS_START - The channel as a connection between the protocol and the table is + * initialized (counted as active by the protocol, linked in the table and keeps + * the table locked), but there is no current route exchange. There still may be + * routes associated with the channel in the routing table if the channel falls + * to CS_START from CS_UP. Generally, channels are initialized in protocols' + * start() hooks when going to PS_START. + * + * CS_UP - The channel is initialized and the route exchange is allowed. Note + * that even in CS_UP state, route export may still be down (ES_DOWN) by the + * core decision (e.g. waiting for table convergence after graceful restart). + * I.e., the protocol decides to open the channel but the core decides to start + * route export. Route import (caused by rte_update() from the protocol) is not + * 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 + * 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 + * 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 + * automatically by the core when the protocol is going down. + * + * 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) + */ + +#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 + + +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) +{ struct channel_config *cc = HEAD(pc->channels); return NODE_VALID(cc) ? cc : NULL; } + +struct channel *proto_find_channel_by_table(struct proto *p, struct 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); + +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); } + +void channel_request_feeding(struct channel *c); +void *channel_config_new(const struct channel_class *cc, uint net_type, struct proto_config *proto); +int channel_reconfigure(struct channel *c, struct channel_config *cf); + #endif diff --git a/nest/route.h b/nest/route.h index 383f4def..6c9b00c2 100644 --- a/nest/route.h +++ b/nest/route.h @@ -11,11 +11,13 @@ #include "lib/lists.h" #include "lib/resource.h" -#include "lib/timer.h" -#include "nest/protocol.h" +#include "sysdep/unix/timer.h" +#include "lib/net.h" +struct ea_list; struct protocol; struct proto; +struct rte_src; struct symbol; struct filter; struct cli; @@ -35,11 +37,8 @@ struct cli; struct fib_node { struct fib_node *next; /* Next in hash chain */ struct fib_iterator *readers; /* List of readers of this node */ - byte pxlen; - byte flags; /* User-defined */ - byte x0, x1; /* User-defined */ - u32 uid; /* Unique ID based on hash */ - ip_addr prefix; /* In host order */ + byte flags; /* User-defined, will be removed */ + net_addr addr[0]; }; struct fib_iterator { /* See lib/slists.h for an explanation */ @@ -50,7 +49,7 @@ struct fib_iterator { /* See lib/slists.h for an explanation */ uint hash; }; -typedef void (*fib_init_func)(struct fib_node *); +typedef void (*fib_init_fn)(void *); struct fib { pool *fib_pool; /* Pool holding all our data */ @@ -58,16 +57,26 @@ struct fib { struct fib_node **hash_table; /* Node hash table */ uint hash_size; /* Number of hash table entries (a power of two) */ uint hash_order; /* Binary logarithm of hash_size */ - uint hash_shift; /* 16 - hash_log */ + uint hash_shift; /* 32 - hash_order */ + uint addr_type; /* Type of address data stored in fib (NET_*) */ + uint node_size; /* FIB node size, 0 for nonuniform */ + uint node_offset; /* Offset of fib_node struct inside of user data */ uint entries; /* Number of entries */ uint entries_min, entries_max; /* Entry count limits (else start rehashing) */ - fib_init_func init; /* Constructor */ + fib_init_fn init; /* Constructor */ }; -void fib_init(struct fib *, pool *, unsigned node_size, unsigned hash_order, fib_init_func init); -void *fib_find(struct fib *, ip_addr *, int); /* Find or return NULL if doesn't exist */ -void *fib_get(struct fib *, ip_addr *, int); /* Find or create new if nonexistent */ -void *fib_route(struct fib *, ip_addr, int); /* Longest-match routing lookup */ +static inline void * fib_node_to_user(struct fib *f, struct fib_node *e) +{ return e ? (void *) ((char *) e - f->node_offset) : NULL; } + +static inline struct fib_node * fib_user_to_node(struct fib *f, void *e) +{ return e ? (void *) ((char *) e + f->node_offset) : NULL; } + +void fib_init(struct fib *f, pool *p, uint addr_type, uint node_size, uint node_offset, uint hash_order, fib_init_fn init); +void *fib_find(struct fib *, const net_addr *); /* Find or return NULL if doesn't exist */ +void *fib_get_chain(struct fib *f, const net_addr *a); /* Find first node in linked list from hash table */ +void *fib_get(struct fib *, const net_addr *); /* Find or create new if nonexistent */ +void *fib_route(struct fib *, const net_addr *); /* Longest-match routing lookup */ void fib_delete(struct fib *, void *); /* Remove fib entry */ void fib_free(struct fib *); /* Destroy the fib */ void fib_check(struct fib *); /* Consistency check for debugging */ @@ -78,34 +87,37 @@ void fit_put(struct fib_iterator *, struct fib_node *); void fit_put_next(struct fib *f, struct fib_iterator *i, struct fib_node *n, uint hpos); -#define FIB_WALK(fib, z) do { \ - struct fib_node *z, **ff = (fib)->hash_table; \ - uint count = (fib)->hash_size; \ - while (count--) \ - for(z = *ff++; z; z=z->next) +#define FIB_WALK(fib, type, z) do { \ + struct fib_node *fn_, **ff_ = (fib)->hash_table; \ + uint count_ = (fib)->hash_size; \ + type *z; \ + while (count_--) \ + for (fn_ = *ff_++; z = fib_node_to_user(fib, fn_); fn_=fn_->next) #define FIB_WALK_END } while (0) #define FIB_ITERATE_INIT(it, fib) fit_init(it, fib) -#define FIB_ITERATE_START(fib, it, z) do { \ - struct fib_node *z = fit_get(fib, it); \ - uint count = (fib)->hash_size; \ - uint hpos = (it)->hash; \ +#define FIB_ITERATE_START(fib, it, type, z) do { \ + struct fib_node *fn_ = fit_get(fib, it); \ + uint count_ = (fib)->hash_size; \ + uint hpos_ = (it)->hash; \ + type *z; \ for(;;) { \ - if (!z) \ + if (!fn_) \ { \ - if (++hpos >= count) \ + if (++hpos_ >= count_) \ break; \ - z = (fib)->hash_table[hpos]; \ + fn_ = (fib)->hash_table[hpos_]; \ continue; \ - } + } \ + z = fib_node_to_user(fib, fn_); -#define FIB_ITERATE_END(z) z = z->next; } } while(0) +#define FIB_ITERATE_END fn_ = fn_->next; } } while(0) -#define FIB_ITERATE_PUT(it, z) fit_put(it, z) +#define FIB_ITERATE_PUT(it) fit_put(it, fn_) -#define FIB_ITERATE_PUT_NEXT(it, fib, z) fit_put_next(fib, it, z, hpos) +#define FIB_ITERATE_PUT_NEXT(it, fib) fit_put_next(fib, it, fn_, hpos_) #define FIB_ITERATE_UNLINK(it, fib) fit_get(fib, it) @@ -126,6 +138,7 @@ struct rtable_config { 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() */ @@ -135,7 +148,8 @@ typedef struct rtable { node n; /* Node in list of all tables */ struct fib fib; char *name; /* Name of this table */ - list hooks; /* List of announcement hooks */ + 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 */ struct hostcache *hostcache; @@ -147,7 +161,6 @@ typedef struct rtable { struct event *rt_event; /* Routing table event */ int gc_counter; /* Number of operations since last GC */ bird_clock_t gc_time; /* Time of last GC */ - byte gc_scheduled; /* GC is scheduled */ byte prune_state; /* Table prune state, 1 -> scheduled, 2-> running */ byte hcu_scheduled; /* Hostcache update is scheduled */ byte nhu_state; /* Next Hop Update state */ @@ -155,13 +168,14 @@ typedef struct rtable { struct fib_iterator nhu_fit; /* Next Hop Update FIB iterator */ } rtable; -#define RPS_NONE 0 -#define RPS_SCHEDULED 1 -#define RPS_RUNNING 2 +#define NHU_CLEAN 0 +#define NHU_SCHEDULED 1 +#define NHU_RUNNING 2 +#define NHU_DIRTY 3 typedef struct network { - struct fib_node n; /* FIB flags reserved for kernel syncer */ struct rte *routes; /* Available routes for this network */ + struct fib_node n; /* FIB flags reserved for kernel syncer */ } net; struct hostcache { @@ -186,15 +200,15 @@ struct hostentry { unsigned hash_key; /* Hash key */ unsigned uc; /* Use count */ struct rta *src; /* Source rta entry */ - ip_addr gw; /* Chosen next hop */ byte dest; /* Chosen route destination type (RTD_...) */ + byte nexthop_linkable; /* Nexthop list is completely non-device */ u32 igp_metric; /* Chosen route IGP metric */ }; typedef struct rte { struct rte *next; net *net; /* Network this RTE belongs to */ - struct announce_hook *sender; /* Announce hook used to send the route to the routing table */ + struct channel *sender; /* Channel used to send the route to the routing table */ struct rta *attrs; /* Attributes of this route */ byte flags; /* Flags (REF_...) */ byte pflags; /* Protocol-specific flags */ @@ -249,6 +263,7 @@ static inline int rte_is_filtered(rte *r) { return !!(r->flags & REF_FILTERED); /* Types of route announcement, also used as flags */ +#define RA_UNDEF 0 /* Undefined RA type */ #define RA_OPTIMAL 1 /* Announcement of optimal route change */ #define RA_ACCEPTED 2 /* Announcement of first accepted route */ #define RA_ANY 3 /* Announcement of any route change */ @@ -268,16 +283,21 @@ void rt_commit(struct config *new, struct config *old); void rt_lock_table(rtable *); void rt_unlock_table(rtable *); void rt_setup(pool *, rtable *, char *, struct rtable_config *); -static inline net *net_find(rtable *tab, ip_addr addr, unsigned len) { return (net *) fib_find(&tab->fib, &addr, len); } -static inline net *net_get(rtable *tab, ip_addr addr, unsigned len) { return (net *) fib_get(&tab->fib, &addr, len); } +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); +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 announce_hook *ah, net *net, rte *new, struct rte_src *src); -static inline void rte_update(struct proto *p, net *net, rte *new) { rte_update2(p->main_ahook, net, new, p->main_source); } -int rt_examine(rtable *t, ip_addr prefix, int pxlen, struct proto *p, struct filter *filter); -rte *rt_export_merged(struct announce_hook *ah, net *net, rte **rt_free, struct ea_list **tmpa, linpool *pool, int silent); -void rt_refresh_begin(rtable *t, struct announce_hook *ah); -void rt_refresh_end(rtable *t, struct announce_hook *ah); +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, struct filter *filter); +rte *rt_export_merged(struct channel *c, net *net, rte **rt_free, struct ea_list **tmpa, linpool *pool, int silent); +void rt_refresh_begin(rtable *t, struct channel *c); +void rt_refresh_end(rtable *t, struct channel *c); +void rt_schedule_prune(rtable *t); void rte_dump(rte *); void rte_free(rte *); rte *rte_do_cow(rte *); @@ -285,35 +305,47 @@ static inline rte * rte_cow(rte *r) { return (r->flags & REF_COW) ? rte_do_cow(r rte *rte_cow_rta(rte *r, linpool *lp); void rt_dump(rtable *); void rt_dump_all(void); -int rt_feed_baby(struct proto *p); -void rt_feed_baby_abort(struct proto *p); -int rt_prune_loop(void); -struct rtable_config *rt_new_table(struct symbol *s); +int rt_feed_channel(struct channel *c); +void rt_feed_channel_abort(struct channel *c); +struct rtable_config *rt_new_table(struct symbol *s, uint addr_type); -static inline void -rt_mark_for_prune(rtable *tab) -{ - if (tab->prune_state == RPS_RUNNING) - fit_get(&tab->fib, &tab->prune_fit); - tab->prune_state = RPS_SCHEDULED; -} +struct rt_show_data_rtable { + node n; + rtable *table; + struct channel *export_channel; +}; struct rt_show_data { - ip_addr prefix; - unsigned pxlen; - rtable *table; + net_addr *addr; + list tables; + struct rt_show_data_rtable *tab; /* Iterator over table list */ + struct rt_show_data_rtable *last_table; /* Last table in output */ + struct fib_iterator fit; /* Iterator over networks in table */ + int verbose, tables_defined_by; struct filter *filter; - int verbose; - struct fib_iterator fit; struct proto *show_protocol; struct proto *export_protocol; - int export_mode, primary_only, filtered; + struct channel *export_channel; struct config *running_on_config; - int net_counter, rt_counter, show_counter; - int stats, show_for; + int export_mode, primary_only, filtered, stats, show_for; + + int table_open; /* Iteration (fit) is open */ + int net_counter, rt_counter, show_counter, table_counter; + int net_counter_last, rt_counter_last, show_counter_last; }; + void rt_show(struct rt_show_data *); +struct rt_show_data_rtable * rt_show_add_table(struct rt_show_data *d, rtable *t); + +/* Value of table definition mode in struct rt_show_data */ +#define RSD_TDB_DEFAULT 0 /* no table specified */ +#define RSD_TDB_INDIRECT 0 /* show route ... protocol P ... */ +#define RSD_TDB_ALL RSD_TDB_SET /* show route ... table all ... */ +#define RSD_TDB_DIRECT RSD_TDB_SET | RSD_TDB_NMN /* show route ... table X table Y ... */ + +#define RSD_TDB_SET 0x1 /* internal: show empty tables */ +#define RSD_TDB_NMN 0x2 /* internal: need matching net */ /* Value of export_mode in struct rt_show_data */ #define RSEM_NONE 0 /* Export mode not used */ @@ -329,12 +361,15 @@ void rt_show(struct rt_show_data *); * construction of BGP route attribute lists. */ -/* Multipath next-hop */ -struct mpnh { +/* Nexthop structure */ +struct nexthop { ip_addr gw; /* Next hop */ struct iface *iface; /* Outgoing interface */ - struct mpnh *next; + struct nexthop *next; byte weight; + byte labels_orig; /* Number of labels before hostentry was applied */ + byte labels; /* Number of all labels */ + u32 label[0]; }; struct rte_src { @@ -348,22 +383,18 @@ struct rte_src { typedef struct rta { struct rta *next, **pprev; /* Hash chain */ + 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 */ - unsigned uc; /* Use count */ - byte source; /* Route source (RTS_...) */ - byte scope; /* Route scope (SCOPE_... -- see ip.h) */ - byte cast; /* Casting type (RTC_...) */ - byte dest; /* Route destination type (RTD_...) */ - byte flags; /* Route flags (RTF_...), now unused */ - byte aflags; /* Attribute cache flags (RTAF_...) */ - u16 hash_key; /* Hash over important fields */ - u32 igp_metric; /* IGP metric to next hop (for iBGP routes) */ - ip_addr gw; /* Next hop */ - ip_addr from; /* Advertising router */ struct hostentry *hostentry; /* Hostentry for recursive next-hops */ - struct iface *iface; /* Outgoing interface */ - struct mpnh *nexthops; /* Next-hops for multipath routes */ - struct ea_list *eattrs; /* Extended Attribute chain */ + 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; + struct nexthop nh; /* Next hop */ } rta; #define RTS_DUMMY 0 /* Dummy route to be removed soon */ @@ -380,19 +411,20 @@ typedef struct rta { #define RTS_BGP 11 /* BGP route */ #define RTS_PIPE 12 /* Inter-table wormhole */ #define RTS_BABEL 13 /* Babel route */ +#define RTS_RPKI 14 /* Route Origin Authorization */ + #define RTC_UNICAST 0 #define RTC_BROADCAST 1 #define RTC_MULTICAST 2 #define RTC_ANYCAST 3 /* IPv6 Anycast */ -#define RTD_ROUTER 0 /* Next hop is neighbor router */ -#define RTD_DEVICE 1 /* Points to device */ +#define RTD_NONE 0 /* Undefined next hop */ +#define RTD_UNICAST 1 /* Next hop is neighbor router */ #define RTD_BLACKHOLE 2 /* Silently drop packets */ #define RTD_UNREACHABLE 3 /* Reject as unreachable */ #define RTD_PROHIBIT 4 /* Administratively prohibited */ -#define RTD_MULTIPATH 5 /* Multipath route (nexthops != NULL) */ -#define RTD_NONE 6 /* Invalid RTD */ +#define RTD_MAX 5 /* Flags for net->n.flags, used by kernel syncer */ #define KRF_INSTALLED 0x80 /* This route should be installed in the kernel */ @@ -404,9 +436,14 @@ typedef struct rta { protocol-specific metric is availabe */ +const char * rta_dest_names[RTD_MAX]; + +static inline const char *rta_dest_name(uint n) +{ return (n < RTD_MAX) ? rta_dest_names[n] : "???"; } + /* Route has regular, reachable nexthop (i.e. not RTD_UNREACHABLE and like) */ static inline int rte_is_reachable(rte *r) -{ uint d = r->attrs->dest; return (d == RTD_ROUTER) || (d == RTD_DEVICE) || (d == RTD_MULTIPATH); } +{ return r->attrs->dest == RTD_UNICAST; } /* @@ -454,13 +491,22 @@ typedef struct eattr { #define EAF_TYPE_UNDEF 0x1f /* `force undefined' entry */ #define EAF_EMBEDDED 0x01 /* Data stored in eattr.u.data (part of type spec) */ #define EAF_VAR_LENGTH 0x02 /* Attribute length is variable (part of type spec) */ -#define EAF_ORIGINATED 0x40 /* The attribute has originated locally */ +#define EAF_ORIGINATED 0x20 /* The attribute has originated locally */ +#define EAF_FRESH 0x40 /* An uncached attribute (e.g. modified in export filter) */ #define EAF_TEMP 0x80 /* A temporary attribute (the one stored in the tmp attr list) */ -struct adata { +typedef struct adata { uint length; /* Length of data */ byte data[0]; -}; +} adata; + +static inline struct adata * +lp_alloc_adata(struct linpool *pool, uint len) +{ + struct adata *ad = lp_alloc(pool, sizeof(struct adata) + len); + ad->length = len; + return ad; +} static inline int adata_same(struct adata *a, struct adata *b) { return (a->length == b->length && !memcmp(a->data, b->data, a->length)); } @@ -502,14 +548,22 @@ uint ea_hash(ea_list *e); /* Calculate 16-bit hash value */ ea_list *ea_append(ea_list *to, ea_list *what); void ea_format_bitfield(struct eattr *a, byte *buf, int bufsize, const char **names, int min, int max); -int mpnh__same(struct mpnh *x, struct mpnh *y); /* Compare multipath nexthops */ -static inline int mpnh_same(struct mpnh *x, struct mpnh *y) -{ return (x == y) || mpnh__same(x, y); } -struct mpnh *mpnh_merge(struct mpnh *x, struct mpnh *y, int rx, int ry, int max, linpool *lp); -void mpnh_insert(struct mpnh **n, struct mpnh *y); -int mpnh_is_sorted(struct mpnh *x); +#define NEXTHOP_MAX_SIZE (sizeof(struct nexthop) + sizeof(u32)*MPLS_MAX_LABEL_STACK) + +static inline size_t nexthop_size(const struct nexthop *nh) +{ return sizeof(struct nexthop) + sizeof(u32)*nh->labels; } +int nexthop__same(struct nexthop *x, struct nexthop *y); /* Compare multipath nexthops */ +static inline int nexthop_same(struct nexthop *x, struct nexthop *y) +{ return (x == y) || nexthop__same(x, y); } +struct nexthop *nexthop_merge(struct nexthop *x, struct nexthop *y, int rx, int ry, int max, linpool *lp); +static inline void nexthop_link(struct rta *a, struct nexthop *from) +{ memcpy(&a->nh, from, nexthop_size(from)); } +void nexthop_insert(struct nexthop **n, struct nexthop *y); +int nexthop_is_sorted(struct nexthop *x); 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; } @@ -520,7 +574,15 @@ static inline rta * rta_cow(rta *r, linpool *lp) { return rta_is_cached(r) ? rta void rta_dump(rta *); void rta_dump_all(void); void rta_show(struct cli *, rta *, ea_list *); -void rta_set_recursive_next_hop(rtable *dep, rta *a, rtable *tab, ip_addr *gw, ip_addr *ll); + +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); + +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_apply_hostentry(a, rt_get_hostentry(tab, gw, ll, dep), mls); +} /* * rta_set_recursive_next_hop() acquires hostentry from hostcache and fills @@ -560,87 +622,15 @@ extern struct protocol *attr_class_to_protocol[EAP_MAX]; #define DEF_PREF_BABEL 130 /* Babel */ #define DEF_PREF_RIP 120 /* RIP */ #define DEF_PREF_BGP 100 /* BGP */ -#define DEF_PREF_PIPE 70 /* Routes piped from other tables */ +#define DEF_PREF_RPKI 100 /* RPKI */ #define DEF_PREF_INHERITED 10 /* Routes inherited from other routing daemons */ - /* * Route Origin Authorization */ -struct roa_item { - u32 asn; - byte maxlen; - byte src; - struct roa_item *next; -}; - -struct roa_node { - struct fib_node n; - struct roa_item *items; - // u32 cached_asn; -}; - -struct roa_table { - node n; /* Node in roa_table_list */ - struct fib fib; - char *name; /* Name of this ROA table */ - struct roa_table_config *cf; /* Configuration of this ROA table */ -}; - -struct roa_item_config { - ip_addr prefix; - byte pxlen, maxlen; - u32 asn; - struct roa_item_config *next; -}; - -struct roa_table_config { - node n; /* Node in config->rpa_tables */ - char *name; /* Name of this ROA table */ - struct roa_table *table; - - struct roa_item_config *roa_items; /* Preconfigured ROA items */ - - // char *filename; - // int gc_max_ops; /* Maximum number of operations before GC is run */ - // int gc_min_time; /* Minimum time between two consecutive GC runs */ -}; - -struct roa_show_data { - struct fib_iterator fit; - struct roa_table *table; - ip_addr prefix; - byte pxlen; - byte mode; /* ROA_SHOW_* values */ - u32 asn; /* Filter ASN, 0 -> all */ -}; - #define ROA_UNKNOWN 0 #define ROA_VALID 1 #define ROA_INVALID 2 -#define ROA_SRC_ANY 0 -#define ROA_SRC_CONFIG 1 -#define ROA_SRC_DYNAMIC 2 - -#define ROA_SHOW_ALL 0 -#define ROA_SHOW_PX 1 -#define ROA_SHOW_IN 2 -#define ROA_SHOW_FOR 3 - -extern struct roa_table *roa_table_default; - -void roa_add_item(struct roa_table *t, ip_addr prefix, byte pxlen, byte maxlen, u32 asn, byte src); -void roa_delete_item(struct roa_table *t, ip_addr prefix, byte pxlen, byte maxlen, u32 asn, byte src); -void roa_flush(struct roa_table *t, byte src); -byte roa_check(struct roa_table *t, ip_addr prefix, byte pxlen, u32 asn); -struct roa_table_config * roa_new_table_config(struct symbol *s); -void roa_add_item_config(struct roa_table_config *rtc, ip_addr prefix, byte pxlen, byte maxlen, u32 asn); -void roa_init(void); -void roa_preconfig(struct config *c); -void roa_commit(struct config *new, struct config *old); -void roa_show(struct roa_show_data *d); - - #endif diff --git a/nest/rt-attr.c b/nest/rt-attr.c index edf27d44..1b7f5836 100644 --- a/nest/rt-attr.c +++ b/nest/rt-attr.c @@ -52,18 +52,27 @@ #include "nest/attrs.h" #include "lib/alloca.h" #include "lib/hash.h" +#include "lib/idm.h" #include "lib/resource.h" #include "lib/string.h" +#include <stddef.h> + +const char * rta_dest_names[RTD_MAX] = { + [RTD_NONE] = "", + [RTD_UNICAST] = "unicast", + [RTD_BLACKHOLE] = "blackhole", + [RTD_UNREACHABLE] = "unreachable", + [RTD_PROHIBIT] = "prohibited", +}; + pool *rta_pool; -static slab *rta_slab; -static slab *mpnh_slab; +static slab *rta_slab_[4]; +static slab *nexthop_slab_[4]; static slab *rte_src_slab; -/* rte source ID bitmap */ -static u32 *src_ids; -static u32 src_id_size, src_id_used, src_id_pos; +static struct idm src_ids; #define SRC_ID_INIT_SIZE 4 /* rte source hash */ @@ -87,64 +96,11 @@ rte_src_init(void) { rte_src_slab = sl_new(rta_pool, sizeof(struct rte_src)); - src_id_pos = 0; - src_id_size = SRC_ID_INIT_SIZE; - src_ids = mb_allocz(rta_pool, src_id_size * sizeof(u32)); - - /* ID 0 is reserved */ - src_ids[0] = 1; - src_id_used = 1; + idm_init(&src_ids, rta_pool, SRC_ID_INIT_SIZE); HASH_INIT(src_hash, rta_pool, RSH_INIT_ORDER); } -static inline int u32_cto(uint x) { return ffs(~x) - 1; } - -static inline u32 -rte_src_alloc_id(void) -{ - uint i, j; - for (i = src_id_pos; i < src_id_size; i++) - if (src_ids[i] != 0xffffffff) - goto found; - - /* If we are at least 7/8 full, expand */ - if (src_id_used > (src_id_size * 28)) - { - src_id_size *= 2; - src_ids = mb_realloc(src_ids, src_id_size * sizeof(u32)); - bzero(src_ids + i, (src_id_size - i) * sizeof(u32)); - goto found; - } - - for (i = 0; i < src_id_pos; i++) - if (src_ids[i] != 0xffffffff) - goto found; - - ASSERT(0); - - found: - ASSERT(i < 0x8000000); - - src_id_pos = i; - j = u32_cto(src_ids[i]); - - src_ids[i] |= (1 << j); - src_id_used++; - return 32 * i + j; -} - -static inline void -rte_src_free_id(u32 id) -{ - int i = id / 32; - int j = id % 32; - - ASSERT((i < src_id_size) && (src_ids[i] & (1 << j))); - src_ids[i] &= ~(1 << j); - src_id_used--; -} - HASH_DEFINE_REHASH_FN(RSH, struct rte_src) @@ -165,7 +121,7 @@ rt_get_source(struct proto *p, u32 id) src = sl_alloc(rte_src_slab); src->proto = p; src->private_id = id; - src->global_id = rte_src_alloc_id(); + src->global_id = idm_alloc(&src_ids); src->uc = 0; HASH_INSERT2(src_hash, RSH, rta_pool, src); @@ -181,7 +137,7 @@ rt_prune_sources(void) if (src->uc == 0) { HASH_DO_REMOVE(src_hash, RSH, sp); - rte_src_free_id(src->global_id); + idm_free(&src_ids, src->global_id); sl_free(rte_src_slab, src); } } @@ -195,28 +151,39 @@ rt_prune_sources(void) * Multipath Next Hop */ -static inline uint -mpnh_hash(struct mpnh *x) +static inline u32 +nexthop_hash(struct nexthop *x) { - uint h = 0; + u32 h = 0; for (; x; x = x->next) - h ^= ipa_hash(x->gw); + { + h ^= ipa_hash(x->gw) ^ (h << 5) ^ (h >> 9); + + for (int i = 0; i < x->labels; i++) + h ^= x->label[i] ^ (h << 6) ^ (h >> 7); + } return h; } int -mpnh__same(struct mpnh *x, struct mpnh *y) +nexthop__same(struct nexthop *x, struct nexthop *y) { for (; x && y; x = x->next, y = y->next) - if (!ipa_equal(x->gw, y->gw) || (x->iface != y->iface) || (x->weight != y->weight)) + { + if (!ipa_equal(x->gw, y->gw) || (x->iface != y->iface) || (x->weight != y->weight) || (x->labels != y->labels)) return 0; + for (int i = 0; i < x->labels; i++) + if (x->label[i] != y->label[i]) + return 0; + } + return x == y; } static int -mpnh_compare_node(struct mpnh *x, struct mpnh *y) +nexthop_compare_node(struct nexthop *x, struct nexthop *y) { int r; @@ -234,22 +201,33 @@ mpnh_compare_node(struct mpnh *x, struct mpnh *y) if (r) return r; + r = ((int) y->labels) - ((int) x->labels); + if (r) + return r; + + for (int i = 0; i < y->labels; i++) + { + r = ((int) y->label[i]) - ((int) x->label[i]); + if (r) + return r; + } + return ((int) x->iface->index) - ((int) y->iface->index); } -static inline struct mpnh * -mpnh_copy_node(const struct mpnh *src, linpool *lp) +static inline struct nexthop * +nexthop_copy_node(const struct nexthop *src, linpool *lp) { - struct mpnh *n = lp_alloc(lp, sizeof(struct mpnh)); - n->gw = src->gw; - n->iface = src->iface; + struct nexthop *n = lp_alloc(lp, nexthop_size(src)); + + memcpy(n, src, nexthop_size(src)); n->next = NULL; - n->weight = src->weight; + return n; } /** - * mpnh_merge - merge nexthop lists + * nexthop_merge - merge nexthop lists * @x: list 1 * @y: list 2 * @rx: reusability of list @x @@ -257,7 +235,7 @@ mpnh_copy_node(const struct mpnh *src, linpool *lp) * @max: max number of nexthops * @lp: linpool for allocating nexthops * - * The mpnh_merge() function takes two nexthop lists @x and @y and merges them, + * The nexthop_merge() function takes two nexthop lists @x and @y and merges them, * eliminating possible duplicates. The input lists must be sorted and the * result is sorted too. The number of nexthops in result is limited by @max. * New nodes are allocated from linpool @lp. @@ -270,28 +248,28 @@ mpnh_copy_node(const struct mpnh *src, linpool *lp) * resulting list is no longer needed. When reusability is not set, the * corresponding lists are not modified nor linked from the resulting list. */ -struct mpnh * -mpnh_merge(struct mpnh *x, struct mpnh *y, int rx, int ry, int max, linpool *lp) +struct nexthop * +nexthop_merge(struct nexthop *x, struct nexthop *y, int rx, int ry, int max, linpool *lp) { - struct mpnh *root = NULL; - struct mpnh **n = &root; + struct nexthop *root = NULL; + struct nexthop **n = &root; while ((x || y) && max--) { - int cmp = mpnh_compare_node(x, y); + int cmp = nexthop_compare_node(x, y); if (cmp < 0) { - *n = rx ? x : mpnh_copy_node(x, lp); + *n = rx ? x : nexthop_copy_node(x, lp); x = x->next; } else if (cmp > 0) { - *n = ry ? y : mpnh_copy_node(y, lp); + *n = ry ? y : nexthop_copy_node(y, lp); y = y->next; } else { - *n = rx ? x : (ry ? y : mpnh_copy_node(x, lp)); + *n = rx ? x : (ry ? y : nexthop_copy_node(x, lp)); x = x->next; y = y->next; } @@ -303,11 +281,11 @@ mpnh_merge(struct mpnh *x, struct mpnh *y, int rx, int ry, int max, linpool *lp) } void -mpnh_insert(struct mpnh **n, struct mpnh *x) +nexthop_insert(struct nexthop **n, struct nexthop *x) { for (; *n; n = &((*n)->next)) { - int cmp = mpnh_compare_node(*n, x); + int cmp = nexthop_compare_node(*n, x); if (cmp < 0) continue; @@ -322,28 +300,37 @@ mpnh_insert(struct mpnh **n, struct mpnh *x) } int -mpnh_is_sorted(struct mpnh *x) +nexthop_is_sorted(struct nexthop *x) { for (; x && x->next; x = x->next) - if (mpnh_compare_node(x, x->next) >= 0) + if (nexthop_compare_node(x, x->next) >= 0) return 0; return 1; } -static struct mpnh * -mpnh_copy(struct mpnh *o) +static inline slab * +nexthop_slab(struct nexthop *nh) +{ + return nexthop_slab_[MIN(nh->labels, 3)]; +} + +static struct nexthop * +nexthop_copy(struct nexthop *o) { - struct mpnh *first = NULL; - struct mpnh **last = &first; + struct nexthop *first = NULL; + struct nexthop **last = &first; for (; o; o = o->next) { - struct mpnh *n = sl_alloc(mpnh_slab); + struct nexthop *n = sl_alloc(nexthop_slab(o)); n->gw = o->gw; n->iface = o->iface; n->next = NULL; n->weight = o->weight; + n->labels = o->labels; + for (int i=0; i<o->labels; i++) + n->label[i] = o->label[i]; *last = n; last = &(n->next); @@ -353,14 +340,14 @@ mpnh_copy(struct mpnh *o) } static void -mpnh_free(struct mpnh *o) +nexthop_free(struct nexthop *o) { - struct mpnh *n; + struct nexthop *n; while (o) { n = o->next; - sl_free(mpnh_slab, o); + sl_free(nexthop_slab(o), o); o = n; } } @@ -580,7 +567,7 @@ ea_do_prune(ea_list *e) if ((s0->type & EAF_TYPE_MASK) != EAF_TYPE_UNDEF) { *d = *s0; - d->type = (d->type & ~EAF_ORIGINATED) | (s[-1].type & EAF_ORIGINATED); + d->type = (d->type & ~(EAF_ORIGINATED|EAF_FRESH)) | (s[-1].type & EAF_ORIGINATED); d++; i++; } @@ -972,7 +959,8 @@ ea_dump(ea_list *e) inline uint ea_hash(ea_list *e) { - u32 h = 0; + const u64 mul = 0x68576150f3d6847; + u64 h = 0xafcef24eda8b29; int i; if (e) /* Assuming chain of length 1 */ @@ -980,29 +968,18 @@ ea_hash(ea_list *e) for(i=0; i<e->count; i++) { struct eattr *a = &e->attrs[i]; - h ^= a->id; + h ^= a->id; h *= mul; if (a->type & EAF_EMBEDDED) h ^= a->u.data; else { struct adata *d = a->u.ptr; - int size = d->length; - byte *z = d->data; - while (size >= 4) - { - h ^= *(u32 *)z; - z += 4; - size -= 4; - } - while (size--) - h = (h >> 24) ^ (h << 8) ^ *z++; + h ^= mem_hash(d->data, d->length); } + h *= mul; } - h ^= h >> 16; - h ^= h >> 6; - h &= 0xffff; } - return h; + return (h >> 32) ^ (h & 0xffffffff); } /** @@ -1051,8 +1028,19 @@ rta_alloc_hash(void) static inline uint rta_hash(rta *a) { - return (((uint) (uintptr_t) a->src) ^ ipa_hash(a->gw) ^ - mpnh_hash(a->nexthops) ^ ea_hash(a->eattrs)) & 0xffff; + u64 h; + mem_hash_init(&h); +#define MIX(f) mem_hash_mix(&h, &(a->f), sizeof(a->f)); + MIX(src); + MIX(hostentry); + MIX(from); + MIX(igp_metric); + MIX(source); + MIX(scope); + MIX(dest); +#undef MIX + + return mem_hash_value(&h) ^ nexthop_hash(&(a->nh)) ^ ea_hash(a->eattrs); } static inline int @@ -1061,26 +1049,28 @@ rta_same(rta *x, rta *y) return (x->src == y->src && x->source == y->source && x->scope == y->scope && - x->cast == y->cast && x->dest == y->dest && - x->flags == y->flags && x->igp_metric == y->igp_metric && - ipa_equal(x->gw, y->gw) && ipa_equal(x->from, y->from) && - x->iface == y->iface && x->hostentry == y->hostentry && - mpnh_same(x->nexthops, y->nexthops) && + nexthop_same(&(x->nh), &(y->nh)) && ea_same(x->eattrs, y->eattrs)); } +static inline slab * +rta_slab(rta *a) +{ + return rta_slab_[a->nh.labels > 2 ? 3 : a->nh.labels]; +} + static rta * rta_copy(rta *o) { - rta *r = sl_alloc(rta_slab); + rta *r = sl_alloc(rta_slab(o)); - memcpy(r, o, sizeof(rta)); + memcpy(r, o, rta_size(o)); r->uc = 1; - r->nexthops = mpnh_copy(o->nexthops); + r->nh.next = nexthop_copy(o->nh.next); r->eattrs = ea_list_copy(o->eattrs); return r; } @@ -1173,19 +1163,26 @@ rta__free(rta *a) *a->pprev = a->next; if (a->next) a->next->pprev = a->pprev; - a->aflags = 0; /* Poison the entry */ rt_unlock_hostentry(a->hostentry); rt_unlock_source(a->src); - mpnh_free(a->nexthops); + if (a->nh.next) + nexthop_free(a->nh.next); ea_free(a->eattrs); - sl_free(rta_slab, a); + a->aflags = 0; /* Poison the entry */ + sl_free(rta_slab(a), a); } rta * rta_do_cow(rta *o, linpool *lp) { - rta *r = lp_alloc(lp, sizeof(rta)); - memcpy(r, o, sizeof(rta)); + rta *r = lp_alloc(lp, rta_size(o)); + memcpy(r, o, rta_size(o)); + for (struct nexthop **nhn = &(r->nh.next), *nho = o->nh.next; nho; nho = nho->next) + { + *nhn = lp_alloc(lp, nexthop_size(nho)); + memcpy(*nhn, nho, nexthop_size(nho)); + nhn = &((*nhn)->next); + } r->aflags = 0; r->uc = 0; return r; @@ -1204,19 +1201,23 @@ rta_dump(rta *a) "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 *rtc[] = { "", " BC", " MC", " AC" }; static char *rtd[] = { "", " DEV", " HOLE", " UNREACH", " PROHIBIT" }; - debug("p=%s uc=%d %s %s%s%s h=%04x", - a->src->proto->name, a->uc, rts[a->source], ip_scope_text(a->scope), rtc[a->cast], + debug("p=%s uc=%d %s %s%s h=%04x", + a->src->proto->name, a->uc, rts[a->source], ip_scope_text(a->scope), rtd[a->dest], a->hash_key); if (!(a->aflags & RTAF_CACHED)) debug(" !CACHED"); debug(" <-%I", a->from); - if (a->dest == RTD_ROUTER) - debug(" ->%I", a->gw); - if (a->dest == RTD_DEVICE || a->dest == RTD_ROUTER) - debug(" [%s]", a->iface ? a->iface->name : "???" ); + if (a->dest == RTD_UNICAST) + for (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]); + for (int i=1; i<nh->labels; i++) + debug("/%d", nh->label[i]); + debug(" [%s]", nh->iface ? nh->iface->name : "???"); + } if (a->eattrs) { debug(" EA: "); @@ -1252,10 +1253,9 @@ rta_show(struct cli *c, rta *a, ea_list *eal) { static char *src_names[] = { "dummy", "static", "inherit", "device", "static-device", "redirect", "RIP", "OSPF", "OSPF-IA", "OSPF-E1", "OSPF-E2", "BGP", "pipe" }; - static char *cast_names[] = { "unicast", "broadcast", "multicast", "anycast" }; int i; - cli_printf(c, -1008, "\tType: %s %s %s", src_names[a->source], cast_names[a->cast], ip_scope_text(a->scope)); + cli_printf(c, -1008, "\tType: %s %s", src_names[a->source], ip_scope_text(a->scope)); if (!eal) eal = a->eattrs; for(; eal; eal=eal->next) @@ -1273,8 +1273,17 @@ void rta_init(void) { rta_pool = rp_new(&root_pool, "Attributes"); - rta_slab = sl_new(rta_pool, sizeof(rta)); - mpnh_slab = sl_new(rta_pool, sizeof(struct mpnh)); + + rta_slab_[0] = sl_new(rta_pool, sizeof(rta)); + rta_slab_[1] = sl_new(rta_pool, sizeof(rta) + sizeof(u32)); + rta_slab_[2] = sl_new(rta_pool, sizeof(rta) + sizeof(u32)*2); + rta_slab_[3] = sl_new(rta_pool, sizeof(rta) + sizeof(u32)*MPLS_MAX_LABEL_STACK); + + nexthop_slab_[0] = sl_new(rta_pool, sizeof(struct nexthop)); + nexthop_slab_[1] = sl_new(rta_pool, sizeof(struct nexthop) + sizeof(u32)); + 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(); rte_src_init(); } diff --git a/nest/rt-dev.c b/nest/rt-dev.c index ed6c06af..9993da24 100644 --- a/nest/rt-dev.c +++ b/nest/rt-dev.c @@ -24,14 +24,17 @@ #include "lib/resource.h" #include "lib/string.h" + static void -dev_ifa_notify(struct proto *p, unsigned c, struct ifa *ad) +dev_ifa_notify(struct proto *P, uint flags, struct ifa *ad) { - struct rt_dev_config *P = (void *) p->cf; + struct rt_dev_proto *p = (void *) P; + struct rt_dev_config *cf = (void *) P->cf; + struct channel *c; - if (!EMPTY_LIST(P->iface_list) && - !iface_patt_find(&P->iface_list, ad->iface, ad->iface->addr)) - /* Empty list is automagically treated as "*" */ + if (!EMPTY_LIST(cf->iface_list) && + !iface_patt_find(&cf->iface_list, ad->iface, ad->iface->addr)) + /* Empty list is automatically treated as "*" */ return; if (ad->flags & IA_SECONDARY) @@ -40,51 +43,49 @@ dev_ifa_notify(struct proto *p, unsigned c, struct ifa *ad) if (ad->scope <= SCOPE_LINK) return; - if (c & IF_CHANGE_DOWN) - { - net *n; + if (ad->prefix.type == NET_IP4) + c = p->ip4_channel; + else if (ad->prefix.type == NET_IP6) + c = p->ip6_channel; + else + return; + + if (!c) + return; + if (flags & IF_CHANGE_DOWN) + { DBG("dev_if_notify: %s:%I going down\n", ad->iface->name, ad->ip); - n = net_find(p->table, ad->prefix, ad->pxlen); - if (!n) - { - DBG("dev_if_notify: device shutdown: prefix not found\n"); - return; - } /* Use iface ID as local source ID */ - struct rte_src *src = rt_get_source(p, ad->iface->index); - rte_update2(p->main_ahook, n, NULL, src); + struct rte_src *src = rt_get_source(P, ad->iface->index); + rte_update2(c, &ad->prefix, NULL, src); } - else if (c & IF_CHANGE_UP) + else if (flags & IF_CHANGE_UP) { rta *a; - net *n; rte *e; DBG("dev_if_notify: %s:%I going up\n", ad->iface->name, ad->ip); - if (P->check_link && !(ad->iface->flags & IF_LINK_UP)) + if (cf->check_link && !(ad->iface->flags & IF_LINK_UP)) return; /* Use iface ID as local source ID */ - struct rte_src *src = rt_get_source(p, ad->iface->index); + struct rte_src *src = rt_get_source(P, ad->iface->index); rta a0 = { .src = src, .source = RTS_DEVICE, .scope = SCOPE_UNIVERSE, - .cast = RTC_UNICAST, - .dest = RTD_DEVICE, - .iface = ad->iface + .dest = RTD_UNICAST, + .nh.iface = ad->iface, }; a = rta_lookup(&a0); - n = net_get(p->table, ad->prefix, ad->pxlen); e = rte_get_temp(a); - e->net = n; e->pflags = 0; - rte_update2(p->main_ahook, n, e, src); + rte_update2(c, &ad->prefix, e, src); } } @@ -108,30 +109,44 @@ dev_if_notify(struct proto *p, uint c, struct iface *iface) static struct proto * -dev_init(struct proto_config *c) +dev_init(struct proto_config *CF) { - struct proto *p = proto_new(c, sizeof(struct proto)); + struct proto *P = proto_new(CF); + struct rt_dev_proto *p = (void *) P; + // struct rt_dev_config *cf = (void *) CF; + + proto_configure_channel(P, &p->ip4_channel, proto_cf_find_channel(CF, NET_IP4)); + proto_configure_channel(P, &p->ip6_channel, proto_cf_find_channel(CF, NET_IP6)); + + P->if_notify = dev_if_notify; + P->ifa_notify = dev_ifa_notify; - p->if_notify = dev_if_notify; - p->ifa_notify = dev_ifa_notify; - return p; + return P; } static int -dev_reconfigure(struct proto *p, struct proto_config *new) +dev_reconfigure(struct proto *P, struct proto_config *CF) { - struct rt_dev_config *o = (struct rt_dev_config *) p->cf; - struct rt_dev_config *n = (struct rt_dev_config *) new; + struct rt_dev_proto *p = (void *) P; + struct rt_dev_config *o = (void *) P->cf; + struct rt_dev_config *n = (void *) CF; + + if (!iface_patts_equal(&o->iface_list, &n->iface_list, NULL) || + (o->check_link != n->check_link)) + return 0; + + return + proto_configure_channel(P, &p->ip4_channel, proto_cf_find_channel(CF, NET_IP4)) && + proto_configure_channel(P, &p->ip6_channel, proto_cf_find_channel(CF, NET_IP6)); - return iface_patts_equal(&o->iface_list, &n->iface_list, NULL) && - (o->check_link == n->check_link); + return 1; } static void dev_copy_config(struct proto_config *dest, struct proto_config *src) { - struct rt_dev_config *d = (struct rt_dev_config *) dest; - struct rt_dev_config *s = (struct rt_dev_config *) src; + struct rt_dev_config *d = (void *) dest; + struct rt_dev_config *s = (void *) src; /* * We copy iface_list as ifaces can be shared by more direct protocols. @@ -146,7 +161,9 @@ dev_copy_config(struct proto_config *dest, struct proto_config *src) struct protocol proto_device = { .name = "Direct", .template = "direct%d", - .preference = DEF_PREF_DIRECT, + .preference = DEF_PREF_DIRECT, + .channel_mask = NB_IP, + .proto_size = sizeof(struct rt_dev_proto), .config_size = sizeof(struct rt_dev_config), .init = dev_init, .reconfigure = dev_reconfigure, diff --git a/nest/rt-dev.h b/nest/rt-dev.h index 191b9a02..20b88a64 100644 --- a/nest/rt-dev.h +++ b/nest/rt-dev.h @@ -15,4 +15,10 @@ struct rt_dev_config { int check_link; }; +struct rt_dev_proto { + struct proto p; + struct channel *ip4_channel; + struct channel *ip6_channel; +}; + #endif diff --git a/nest/rt-fib.c b/nest/rt-fib.c index a73de1fd..11c31d0d 100644 --- a/nest/rt-fib.c +++ b/nest/rt-fib.c @@ -43,16 +43,17 @@ #define HASH_DEF_ORDER 10 #define HASH_HI_MARK *4 #define HASH_HI_STEP 2 -#define HASH_HI_MAX 16 /* Must be at most 16 */ +#define HASH_HI_MAX 16 #define HASH_LO_MARK /5 #define HASH_LO_STEP 2 #define HASH_LO_MIN 10 + static void fib_ht_alloc(struct fib *f) { f->hash_size = 1 << f->hash_order; - f->hash_shift = 16 - f->hash_order; + f->hash_shift = 32 - f->hash_order; if (f->hash_order > HASH_HI_MAX - HASH_HI_STEP) f->entries_max = ~0; else @@ -72,16 +73,9 @@ fib_ht_free(struct fib_node **h) mb_free(h); } -static inline unsigned -fib_hash(struct fib *f, ip_addr *a) -{ - return ipa_hash(*a) >> f->hash_shift; -} -static void -fib_dummy_init(struct fib_node *dummy UNUSED) -{ -} +static u32 +fib_hash(struct fib *f, const net_addr *a); /** * fib_init - initialize a new FIB @@ -96,18 +90,23 @@ fib_dummy_init(struct fib_node *dummy UNUSED) * This function initializes a newly allocated FIB and prepares it for use. */ void -fib_init(struct fib *f, pool *p, unsigned node_size, unsigned hash_order, fib_init_func init) +fib_init(struct fib *f, pool *p, uint addr_type, uint node_size, uint node_offset, uint hash_order, fib_init_fn init) { + uint addr_length = net_addr_length[addr_type]; + if (!hash_order) hash_order = HASH_DEF_ORDER; f->fib_pool = p; - f->fib_slab = sl_new(p, node_size); + f->fib_slab = addr_length ? sl_new(p, node_size + addr_length) : NULL; + f->addr_type = addr_type; + f->node_size = node_size; + f->node_offset = node_offset; f->hash_order = hash_order; fib_ht_alloc(f); bzero(f->hash_table, f->hash_size * sizeof(struct fib_node *)); f->entries = 0; f->entries_min = 0; - f->init = init ? : fib_dummy_init; + f->init = init; } static void @@ -133,7 +132,7 @@ fib_rehash(struct fib *f, int step) while (e = x) { x = e->next; - nh = fib_hash(f, &e->prefix); + nh = fib_hash(f, e->addr); while (nh > ni) { *t = NULL; @@ -153,127 +152,209 @@ fib_rehash(struct fib *f, int step) fib_ht_free(m); } +#define CAST(t) (const net_addr_##t *) +#define CAST2(t) (net_addr_##t *) + +#define FIB_HASH(f,a,t) (net_hash_##t(CAST(t) a) >> f->hash_shift) + +#define FIB_FIND(f,a,t) \ + ({ \ + struct fib_node *e = f->hash_table[FIB_HASH(f, a, t)]; \ + while (e && !net_equal_##t(CAST(t) e->addr, CAST(t) a)) \ + e = e->next; \ + fib_node_to_user(f, e); \ + }) + +#define FIB_INSERT(f,a,e,t) \ + ({ \ + u32 h = net_hash_##t(CAST(t) a); \ + struct fib_node **ee = f->hash_table + (h >> f->hash_shift); \ + struct fib_node *g; \ + \ + while ((g = *ee) && (net_hash_##t(CAST(t) g->addr) < h)) \ + ee = &g->next; \ + \ + net_copy_##t(CAST2(t) e->addr, CAST(t) a); \ + e->next = *ee; \ + *ee = e; \ + }) + + +static u32 +fib_hash(struct fib *f, const net_addr *a) +{ + ASSERT(f->addr_type == a->type); + + switch (f->addr_type) + { + case NET_IP4: return FIB_HASH(f, a, ip4); + case NET_IP6: return FIB_HASH(f, a, ip6); + case NET_VPN4: return FIB_HASH(f, a, vpn4); + case NET_VPN6: return FIB_HASH(f, a, vpn6); + case NET_ROA4: return FIB_HASH(f, a, roa4); + case NET_ROA6: return FIB_HASH(f, a, roa6); + case NET_FLOW4: return FIB_HASH(f, a, flow4); + case NET_FLOW6: return FIB_HASH(f, a, flow6); + default: bug("invalid type"); + } +} + +void * +fib_get_chain(struct fib *f, const net_addr *a) +{ + ASSERT(f->addr_type == a->type); + + struct fib_node *e = f->hash_table[fib_hash(f, a)]; + return e; +} + /** * fib_find - search for FIB node by prefix * @f: FIB to search in - * @a: pointer to IP address of the prefix - * @len: prefix length + * @n: network address * * Search for a FIB node corresponding to the given prefix, return * a pointer to it or %NULL if no such node exists. */ void * -fib_find(struct fib *f, ip_addr *a, int len) +fib_find(struct fib *f, const net_addr *a) { - struct fib_node *e = f->hash_table[fib_hash(f, a)]; - - while (e && (e->pxlen != len || !ipa_equal(*a, e->prefix))) - e = e->next; - return e; + ASSERT(f->addr_type == a->type); + + switch (f->addr_type) + { + case NET_IP4: return FIB_FIND(f, a, ip4); + case NET_IP6: return FIB_FIND(f, a, ip6); + case NET_VPN4: return FIB_FIND(f, a, vpn4); + case NET_VPN6: return FIB_FIND(f, a, vpn6); + case NET_ROA4: return FIB_FIND(f, a, roa4); + case NET_ROA6: return FIB_FIND(f, a, roa6); + case NET_FLOW4: return FIB_FIND(f, a, flow4); + case NET_FLOW6: return FIB_FIND(f, a, flow6); + default: bug("invalid type"); + } } -/* -int -fib_histogram(struct fib *f) +static void +fib_insert(struct fib *f, const net_addr *a, struct fib_node *e) { - log(L_WARN "Histogram dump start %d %d", f->hash_size, f->entries); - - int i, j; - struct fib_node *e; - - for (i = 0; i < f->hash_size; i++) - { - j = 0; - for (e = f->hash_table[i]; e != NULL; e = e->next) - j++; - if (j > 0) - log(L_WARN "Histogram line %d: %d", i, j); - } - - log(L_WARN "Histogram dump end"); + ASSERT(f->addr_type == a->type); + + switch (f->addr_type) + { + case NET_IP4: FIB_INSERT(f, a, e, ip4); return; + case NET_IP6: FIB_INSERT(f, a, e, ip6); return; + case NET_VPN4: FIB_INSERT(f, a, e, vpn4); return; + case NET_VPN6: FIB_INSERT(f, a, e, vpn6); return; + case NET_ROA4: FIB_INSERT(f, a, e, roa4); return; + case NET_ROA6: FIB_INSERT(f, a, e, roa6); return; + case NET_FLOW4: FIB_INSERT(f, a, e, flow4); return; + case NET_FLOW6: FIB_INSERT(f, a, e, flow6); return; + default: bug("invalid type"); + } } -*/ + /** * fib_get - find or create a FIB node * @f: FIB to work with - * @a: pointer to IP address of the prefix - * @len: prefix length + * @n: network address * * Search for a FIB node corresponding to the given prefix and * return a pointer to it. If no such node exists, create it. */ void * -fib_get(struct fib *f, ip_addr *a, int len) +fib_get(struct fib *f, const net_addr *a) { - uint h = ipa_hash(*a); - struct fib_node **ee = f->hash_table + (h >> f->hash_shift); - struct fib_node *g, *e = *ee; - u32 uid = h << 16; - - while (e && (e->pxlen != len || !ipa_equal(*a, e->prefix))) - e = e->next; - if (e) - return e; -#ifdef DEBUGGING - if (len < 0 || len > BITS_PER_IP_ADDRESS || !ip_is_prefix(*a,len)) - bug("fib_get() called for invalid address"); -#endif + void *b = fib_find(f, a); + if (b) + return b; - while ((g = *ee) && g->uid < uid) - ee = &g->next; - while ((g = *ee) && g->uid == uid) - { - ee = &g->next; - uid++; - } + if (f->fib_slab) + b = sl_alloc(f->fib_slab); + else + b = mb_alloc(f->fib_pool, f->node_size + a->length); - if ((uid >> 16) != h) - log(L_ERR "FIB hash table chains are too long"); + struct fib_node *e = fib_user_to_node(f, b); + e->readers = NULL; + e->flags = 0; + fib_insert(f, a, e); - // log (L_WARN "FIB_GET %I %x %x", *a, h, uid); + memset(b, 0, f->node_offset); + if (f->init) + f->init(b); - e = sl_alloc(f->fib_slab); - e->prefix = *a; - e->pxlen = len; - e->next = *ee; - e->uid = uid; - *ee = e; - e->readers = NULL; - f->init(e); if (f->entries++ > f->entries_max) fib_rehash(f, HASH_HI_STEP); - return e; + return b; +} + +static inline void * +fib_route_ip4(struct fib *f, net_addr_ip4 *n) +{ + void *r; + + while (!(r = fib_find(f, (net_addr *) n)) && (n->pxlen > 0)) + { + n->pxlen--; + ip4_clrbit(&n->prefix, n->pxlen); + } + + return r; +} + +static inline void * +fib_route_ip6(struct fib *f, net_addr_ip6 *n) +{ + void *r; + + while (!(r = fib_find(f, (net_addr *) n)) && (n->pxlen > 0)) + { + n->pxlen--; + ip6_clrbit(&n->prefix, n->pxlen); + } + + return r; } /** * fib_route - CIDR routing lookup * @f: FIB to search in - * @a: pointer to IP address of the prefix - * @len: prefix length + * @n: network address * * Search for a FIB node with longest prefix matching the given * network, that is a node which a CIDR router would use for routing * that network. */ void * -fib_route(struct fib *f, ip_addr a, int len) +fib_route(struct fib *f, const net_addr *n) { - ip_addr a0; - void *t; - - while (len >= 0) - { - a0 = ipa_and(a, ipa_mkmask(len)); - t = fib_find(f, &a0, len); - if (t) - return t; - len--; - } - return NULL; + ASSERT(f->addr_type == n->type); + + net_addr *n0 = alloca(n->length); + net_copy(n0, n); + + switch (n->type) + { + case NET_IP4: + case NET_VPN4: + case NET_ROA4: + case NET_FLOW4: + return fib_route_ip4(f, (net_addr_ip4 *) n0); + + case NET_IP6: + case NET_VPN6: + case NET_ROA6: + case NET_FLOW6: + return fib_route_ip6(f, (net_addr_ip6 *) n0); + + default: + return NULL; + } } + static inline void fib_merge_readers(struct fib_iterator *i, struct fib_node *to) { @@ -320,8 +401,8 @@ fib_merge_readers(struct fib_iterator *i, struct fib_node *to) void fib_delete(struct fib *f, void *E) { - struct fib_node *e = E; - uint h = fib_hash(f, &e->prefix); + struct fib_node *e = fib_user_to_node(f, E); + uint h = fib_hash(f, e->addr); struct fib_node **ee = f->hash_table + h; struct fib_iterator *it; @@ -343,7 +424,12 @@ fib_delete(struct fib *f, void *E) } fib_merge_readers(it, l); } - sl_free(f->fib_slab, e); + + if (f->fib_slab) + sl_free(f->fib_slab, E); + else + mb_free(E); + if (f->entries-- < f->entries_min) fib_rehash(f, -HASH_LO_STEP); return; @@ -413,7 +499,7 @@ fit_get(struct fib *f, struct fib_iterator *i) if (k = i->next) k->prev = j; j->next = k; - i->hash = fib_hash(f, &n->prefix); + i->hash = fib_hash(f, n->addr); return n; } @@ -461,6 +547,7 @@ found: void fib_check(struct fib *f) { +#if 0 uint i, ec, lo, nulls; ec = 0; @@ -496,8 +583,32 @@ fib_check(struct fib *f) } if (ec != f->entries) bug("fib_check: invalid entry count (%d != %d)", ec, f->entries); +#endif + return; } +/* +int +fib_histogram(struct fib *f) +{ + log(L_WARN "Histogram dump start %d %d", f->hash_size, f->entries); + + int i, j; + struct fib_node *e; + + for (i = 0; i < f->hash_size; i++) + { + j = 0; + for (e = f->hash_table[i]; e != NULL; e = e->next) + j++; + if (j > 0) + log(L_WARN "Histogram line %d: %d", i, j); + } + + log(L_WARN "Histogram dump end"); +} +*/ + #endif #ifdef TEST @@ -517,7 +628,7 @@ void dump(char *m) struct fib_iterator *j; for(n=f.hash_table[i]; n; n=n->next) { - debug("%04x %04x %p %I/%2d", i, ipa_hash(n->prefix), n, n->prefix, n->pxlen); + debug("%04x %08x %p %N", i, ipa_hash(n->prefix), n, n->addr); for(j=n->readers; j; j=j->next) debug(" %p[%p]", j, j->node); debug("\n"); diff --git a/nest/rt-roa.c b/nest/rt-roa.c deleted file mode 100644 index 0fd89667..00000000 --- a/nest/rt-roa.c +++ /dev/null @@ -1,440 +0,0 @@ -/* - * BIRD -- Route Origin Authorization - * - * - * Can be freely distributed and used under the terms of the GNU GPL. - */ - -#undef LOCAL_DEBUG - -#include "nest/bird.h" -#include "nest/route.h" -#include "nest/cli.h" -#include "lib/lists.h" -#include "lib/resource.h" -#include "lib/event.h" -#include "lib/string.h" -#include "conf/conf.h" - - -pool *roa_pool; -static slab *roa_slab; /* Slab of struct roa_item */ -static list roa_table_list; /* List of struct roa_table */ -struct roa_table *roa_table_default; /* The first ROA table in the config */ - -static inline int -src_match(struct roa_item *it, byte src) -{ return !src || it->src == src; } - -/** - * roa_add_item - add a ROA entry - * @t: ROA table - * @prefix: prefix of the ROA entry - * @pxlen: prefix length of the ROA entry - * @maxlen: max length field of the ROA entry - * @asn: AS number field of the ROA entry - * @src: source of the ROA entry (ROA_SRC_*) - * - * The function adds a new ROA entry to the ROA table. If the same ROA - * is already in the table, nothing is added. @src field is used to - * distinguish different sources of ROAs. - */ -void -roa_add_item(struct roa_table *t, ip_addr prefix, byte pxlen, byte maxlen, u32 asn, byte src) -{ - struct roa_node *n = fib_get(&t->fib, &prefix, pxlen); - - // if ((n->items == NULL) && (n->n.x0 != ROA_INVALID)) - // t->cached_items--; - - struct roa_item *it; - for (it = n->items; it; it = it->next) - if ((it->maxlen == maxlen) && (it->asn == asn) && src_match(it, src)) - return; - - it = sl_alloc(roa_slab); - it->asn = asn; - it->maxlen = maxlen; - it->src = src; - it->next = n->items; - n->items = it; -} - -/** - * roa_delete_item - delete a ROA entry - * @t: ROA table - * @prefix: prefix of the ROA entry - * @pxlen: prefix length of the ROA entry - * @maxlen: max length field of the ROA entry - * @asn: AS number field of the ROA entry - * @src: source of the ROA entry (ROA_SRC_*) - * - * The function removes a specified ROA entry from the ROA table and - * frees it. If @src field is not ROA_SRC_ANY, only entries from - * that source are considered. - */ -void -roa_delete_item(struct roa_table *t, ip_addr prefix, byte pxlen, byte maxlen, u32 asn, byte src) -{ - struct roa_node *n = fib_find(&t->fib, &prefix, pxlen); - - if (!n) - return; - - struct roa_item *it, **itp; - for (itp = &n->items; it = *itp; itp = &it->next) - if ((it->maxlen == maxlen) && (it->asn == asn) && src_match(it, src)) - break; - - if (!it) - return; - - *itp = it->next; - sl_free(roa_slab, it); - - // if ((n->items == NULL) && (n->n.x0 != ROA_INVALID)) - // t->cached_items++; -} - - -/** - * roa_flush - flush a ROA table - * @t: ROA table - * @src: source of ROA entries (ROA_SRC_*) - * - * The function removes and frees ROA entries from the ROA table. If - * @src is ROA_SRC_ANY, all entries in the table are removed, - * otherwise only all entries from that source are removed. - */ -void -roa_flush(struct roa_table *t, byte src) -{ - struct roa_item *it, **itp; - struct roa_node *n; - - FIB_WALK(&t->fib, fn) - { - n = (struct roa_node *) fn; - - itp = &n->items; - while (it = *itp) - if (src_match(it, src)) - { - *itp = it->next; - sl_free(roa_slab, it); - } - else - itp = &it->next; - } - FIB_WALK_END; - - // TODO add cleanup of roa_nodes -} - - - -/* -byte -roa_check(struct roa_table *t, ip_addr prefix, byte pxlen, u32 asn) -{ - struct roa_node *n = fib_find(&t->fib, &prefix, pxlen); - - if (n && n->n.x0 == ROA_UNKNOWN) - return ROA_UNKNOWN; - - if (n && n->n.x0 == ROA_VALID && asn == n->cached_asn) - return ROA_VALID; - - byte rv = roa_match(t, n, prefix, pxlen, asn); - - if (rv != ROA_INVALID) - { - if (!n) - { - if (t->cached_items >= t->cached_items_max) - n = fib_get(&t->fib, &prefix, pxlen); - t->cached_items++; - } - - n->cached_asn = asn; - n->n.x0 = rv; - } - - return rv; -} -*/ - -/** - * roa_check - check validity of route origination in a ROA table - * @t: ROA table - * @prefix: network prefix to check - * @pxlen: length of network prefix - * @asn: AS number of network prefix - * - * Implements RFC 6483 route validation for the given network - * prefix. The procedure is to find all candidate ROAs - ROAs whose - * prefixes cover the give network prefix. If there is no candidate - * ROA, return ROA_UNKNOWN. If there is a candidate ROA with matching - * ASN and maxlen field greater than or equal to the given prefix - * length, return ROA_VALID. Otherwise return ROA_INVALID. If caller - * cannot determine origin AS, 0 could be used (in that case ROA_VALID - * cannot happen). - */ -byte -roa_check(struct roa_table *t, ip_addr prefix, byte pxlen, u32 asn) -{ - struct roa_node *n; - ip_addr px; - byte anything = 0; - - int len; - for (len = pxlen; len >= 0; len--) - { - px = ipa_and(prefix, ipa_mkmask(len)); - n = fib_find(&t->fib, &px, len); - - if (!n) - continue; - - struct roa_item *it; - for (it = n->items; it; it = it->next) - { - anything = 1; - if ((it->maxlen >= pxlen) && (it->asn == asn) && asn) - return ROA_VALID; - } - } - - return anything ? ROA_INVALID : ROA_UNKNOWN; -} - -static void -roa_node_init(struct fib_node *fn) -{ - struct roa_node *n = (struct roa_node *) fn; - n->items = NULL; -} - -static inline void -roa_populate(struct roa_table *t) -{ - struct roa_item_config *ric; - for (ric = t->cf->roa_items; ric; ric = ric->next) - roa_add_item(t, ric->prefix, ric->pxlen, ric->maxlen, ric->asn, ROA_SRC_CONFIG); -} - -static void -roa_new_table(struct roa_table_config *cf) -{ - struct roa_table *t; - - t = mb_allocz(roa_pool, sizeof(struct roa_table)); - fib_init(&t->fib, roa_pool, sizeof(struct roa_node), 0, roa_node_init); - t->name = cf->name; - t->cf = cf; - - cf->table = t; - add_tail(&roa_table_list, &t->n); - - roa_populate(t); -} - -struct roa_table_config * -roa_new_table_config(struct symbol *s) -{ - struct roa_table_config *rtc = cfg_allocz(sizeof(struct roa_table_config)); - - cf_define_symbol(s, SYM_ROA, rtc); - rtc->name = s->name; - add_tail(&new_config->roa_tables, &rtc->n); - return rtc; -} - -/** - * roa_add_item_config - add a static ROA entry to a ROA table configuration - * - * Arguments are self-explanatory. The first is the ROA table config, rest - * are specifying the ROA entry. - */ -void -roa_add_item_config(struct roa_table_config *rtc, ip_addr prefix, byte pxlen, byte maxlen, u32 asn) -{ - struct roa_item_config *ric = cfg_allocz(sizeof(struct roa_item_config)); - - ric->prefix = prefix; - ric->pxlen = pxlen; - ric->maxlen = maxlen; - ric->asn = asn; - ric->next = rtc->roa_items; - rtc->roa_items = ric; -} - -/** - * roa_init - initialize ROA tables - * - * This function is called during BIRD startup. It initializes - * the ROA table module. - */ -void -roa_init(void) -{ - roa_pool = rp_new(&root_pool, "ROA tables"); - roa_slab = sl_new(roa_pool, sizeof(struct roa_item)); - init_list(&roa_table_list); -} - -void -roa_preconfig(struct config *c) -{ - init_list(&c->roa_tables); -} - - -/** - * roa_commit - commit new ROA table configuration - * @new: new configuration - * @old: original configuration or %NULL if it's boot time config - * - * Scan differences between @old and @new configuration and modify the - * ROA tables according to these changes. If @new defines a previously - * unknown table, create it, if it omits a table existing in @old, - * delete it (there are no references, only indirect through struct - * roa_table_config). If it exists in both configurations, update the - * configured ROA entries. - */ -void -roa_commit(struct config *new, struct config *old) -{ - struct roa_table_config *cf; - struct roa_table *t; - - if (old) - WALK_LIST(t, roa_table_list) - { - struct symbol *sym = cf_find_symbol(new, t->name); - if (sym && sym->class == SYM_ROA) - { - /* Found old table in new config */ - cf = sym->def; - cf->table = t; - t->name = cf->name; - t->cf = cf; - - /* Reconfigure it */ - roa_flush(t, ROA_SRC_CONFIG); - roa_populate(t); - } - else - { - t->cf->table = NULL; - - /* Free it now */ - roa_flush(t, ROA_SRC_ANY); - rem_node(&t->n); - fib_free(&t->fib); - mb_free(t); - } - } - - /* Add new tables */ - WALK_LIST(cf, new->roa_tables) - if (! cf->table) - roa_new_table(cf); - - roa_table_default = EMPTY_LIST(new->roa_tables) ? NULL : - ((struct roa_table_config *) HEAD(new->roa_tables))->table; -} - - - -static void -roa_show_node(struct cli *c, struct roa_node *rn, int len, u32 asn) -{ - struct roa_item *ri; - - for (ri = rn->items; ri; ri = ri->next) - if ((ri->maxlen >= len) && (!asn || (ri->asn == asn))) - cli_printf(c, -1019, "%I/%d max %d as %u", rn->n.prefix, rn->n.pxlen, ri->maxlen, ri->asn); -} - -static void -roa_show_cont(struct cli *c) -{ - struct roa_show_data *d = c->rover; - struct fib *fib = &d->table->fib; - struct fib_iterator *it = &d->fit; - struct roa_node *rn; - unsigned max = 32; - - FIB_ITERATE_START(fib, it, f) - { - rn = (struct roa_node *) f; - - if (!max--) - { - FIB_ITERATE_PUT(it, f); - return; - } - - if ((d->mode == ROA_SHOW_ALL) || - net_in_net(rn->n.prefix, rn->n.pxlen, d->prefix, d->pxlen)) - roa_show_node(c, rn, 0, d->asn); - } - FIB_ITERATE_END(f); - - cli_printf(c, 0, ""); - c->cont = c->cleanup = NULL; -} - -static void -roa_show_cleanup(struct cli *c) -{ - struct roa_show_data *d = c->rover; - - /* Unlink the iterator */ - fit_get(&d->table->fib, &d->fit); -} - -void -roa_show(struct roa_show_data *d) -{ - struct roa_node *rn; - ip_addr px; - int len; - - switch (d->mode) - { - case ROA_SHOW_ALL: - case ROA_SHOW_IN: - FIB_ITERATE_INIT(&d->fit, &d->table->fib); - this_cli->cont = roa_show_cont; - this_cli->cleanup = roa_show_cleanup; - this_cli->rover = d; - break; - - case ROA_SHOW_PX: - rn = fib_find(&d->table->fib, &d->prefix, d->pxlen); - if (rn) - { - roa_show_node(this_cli, rn, 0, d->asn); - cli_msg(0, ""); - } - else - cli_msg(-8001, "Network not in table"); - break; - - case ROA_SHOW_FOR: - for (len = d->pxlen; len >= 0; len--) - { - px = ipa_and(d->prefix, ipa_mkmask(len)); - rn = fib_find(&d->table->fib, &px, len); - - if (!rn) - continue; - - roa_show_node(this_cli, rn, 0, d->asn); - } - cli_msg(0, ""); - break; - } -} diff --git a/nest/rt-show.c b/nest/rt-show.c new file mode 100644 index 00000000..3f5ef04d --- /dev/null +++ b/nest/rt-show.c @@ -0,0 +1,414 @@ +/* + * BIRD -- Route Display Routines + * + * (c) 1998--2000 Martin Mares <mj@ucw.cz> + * (c) 2017 Jan Moskyto Matejka <mq@jmq.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#undef LOCAL_DEBUG + +#include "nest/bird.h" +#include "nest/route.h" +#include "nest/protocol.h" +#include "nest/cli.h" +#include "nest/iface.h" +#include "filter/filter.h" + +static void +rt_show_table(struct cli *c, struct rt_show_data *d) +{ + /* No table blocks in 'show route count' */ + if (d->stats == 2) + return; + + if (d->last_table) cli_printf(c, -1007, ""); + cli_printf(c, -1007, "Table %s:", d->tab->table->name); + d->last_table = d->tab; +} + +static void +rt_show_rte(struct cli *c, byte *ia, rte *e, struct rt_show_data *d, ea_list *tmpa) +{ + byte from[IPA_MAX_TEXT_LENGTH+8]; + byte tm[TM_DATETIME_BUFFER_SIZE], info[256]; + rta *a = e->attrs; + int primary = (e->net->routes == e); + int sync_error = (e->net->n.flags & KRF_SYNC_ERROR); + void (*get_route_info)(struct rte *, byte *buf, struct ea_list *attrs); + struct nexthop *nh; + + tm_format_datetime(tm, &config->tf_route, e->lastmod); + if (ipa_nonzero(a->from) && !ipa_equal(a->from, a->nh.gw)) + bsprintf(from, " from %I", a->from); + else + from[0] = 0; + + get_route_info = a->src->proto->proto->get_route_info; + if (get_route_info || d->verbose) + { + /* Need to normalize the extended attributes */ + ea_list *t = tmpa; + t = ea_append(t, a->eattrs); + tmpa = alloca(ea_scan(t)); + ea_merge(t, tmpa); + ea_sort(tmpa); + } + if (get_route_info) + get_route_info(e, info, tmpa); + else + bsprintf(info, " (%d)", e->pref); + + if (d->last_table != d->tab) + rt_show_table(c, d); + + cli_printf(c, -1007, "%-18s %s [%s %s%s]%s%s", ia, rta_dest_name(a->dest), + a->src->proto->name, tm, from, primary ? (sync_error ? " !" : " *") : "", info); + + if (a->dest == RTD_UNICAST) + for (nh = &(a->nh); nh; nh = nh->next) + { + char mpls[MPLS_MAX_LABEL_STACK*12 + 5], *lsp = mpls; + + if (nh->labels) + { + lsp += bsprintf(lsp, " mpls %d", nh->label[0]); + for (int i=1;i<nh->labels; i++) + lsp += bsprintf(lsp, "/%d", nh->label[i]); + } + *lsp = '\0'; + + if (a->nh.next) + cli_printf(c, -1007, "\tvia %I%s on %s weight %d", nh->gw, mpls, nh->iface->name, nh->weight + 1); + else + cli_printf(c, -1007, "\tvia %I%s on %s", nh->gw, mpls, nh->iface->name); + } + + if (d->verbose) + rta_show(c, a, tmpa); +} + +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 ea_list *tmpa; + struct channel *ec = d->tab->export_channel; + int first = 1; + int pass = 0; + + bsnprintf(ia, sizeof(ia), "%N", n->n.addr); + + for (e = n->routes; e; e = e->next) + { + if (rte_is_filtered(e) != d->filtered) + continue; + + d->rt_counter++; + d->net_counter += first; + first = 0; + + if (pass) + continue; + + ee = e; + tmpa = rte_make_tmp_attrs(e, c->show_pool); + + /* Export channel is down, do not try to export routes to it */ + if (ec && (ec->export_state == ES_DOWN)) + goto skip; + + /* Special case for merged export */ + if ((d->export_mode == RSEM_EXPORT) && (ec->ra_mode == RA_MERGED)) + { + rte *rt_free; + e = rt_export_merged(ec, n, &rt_free, &tmpa, c->show_pool, 1); + pass = 1; + + if (!e) + { e = ee; goto skip; } + } + else if (d->export_mode) + { + struct proto *ep = ec->proto; + int ic = ep->import_control ? ep->import_control(ep, &e, &tmpa, c->show_pool) : 0; + + if (ec->ra_mode == RA_OPTIMAL || ec->ra_mode == RA_MERGED) + pass = 1; + + if (ic < 0) + goto skip; + + if (d->export_mode > RSEM_PREEXPORT) + { + /* + * FIXME - This shows what should be exported according to current + * filters, but not what was really exported. 'configure soft' + * command may change the export filter and do not update routes. + */ + int do_export = (ic > 0) || + (f_run(ec->out_filter, &e, &tmpa, c->show_pool, FF_FORCE_TMPATTR) <= F_ACCEPT); + + if (do_export != (d->export_mode == RSEM_EXPORT)) + goto skip; + + if ((d->export_mode == RSEM_EXPORT) && (ec->ra_mode == RA_ACCEPTED)) + pass = 1; + } + } + + if (d->show_protocol && (d->show_protocol != e->attrs->src->proto)) + goto skip; + + if (f_run(d->filter, &e, &tmpa, c->show_pool, FF_FORCE_TMPATTR) > F_ACCEPT) + goto skip; + + if (d->stats < 2) + rt_show_rte(c, ia, e, d, tmpa); + + d->show_counter++; + ia[0] = 0; + + skip: + if (e != ee) + { + rte_free(e); + e = ee; + } + lp_flush(c->show_pool); + + if (d->primary_only) + break; + } +} + +static void +rt_show_cleanup(struct cli *c) +{ + struct rt_show_data *d = c->rover; + struct rt_show_data_rtable *tab; + + /* Unlink the iterator */ + if (d->table_open) + fit_get(&d->tab->table->fib, &d->fit); + + /* Unlock referenced tables */ + WALK_LIST(tab, d->tables) + rt_unlock_table(tab->table); +} + +static void +rt_show_cont(struct cli *c) +{ + struct rt_show_data *d = c->rover; +#ifdef DEBUGGING + unsigned max = 4; +#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)) + { + cli_printf(c, 8004, "Stopped due to reconfiguration"); + goto done; + } + + if (!d->table_open) + { + FIB_ITERATE_INIT(&d->fit, &d->tab->table->fib); + d->table_open = 1; + d->table_counter++; + + d->show_counter_last = d->show_counter; + d->rt_counter_last = d->rt_counter; + d->net_counter_last = d->net_counter; + + if (d->tables_defined_by & RSD_TDB_SET) + rt_show_table(c, d); + } + + FIB_ITERATE_START(fib, it, net, n) + { + if (!max--) + { + FIB_ITERATE_PUT(it); + return; + } + rt_show_net(c, n, d); + } + FIB_ITERATE_END; + + if (d->stats) + { + if (d->last_table != d->tab) + rt_show_table(c, d); + + cli_printf(c, -1007, "%d of %d routes for %d networks in table %s", + d->show_counter - d->show_counter_last, d->rt_counter - d->rt_counter_last, + d->net_counter - d->net_counter_last, d->tab->table->name); + } + + d->table_open = 0; + d->tab = NODE_NEXT(d->tab); + + if (NODE_VALID(d->tab)) + return; + + if (d->stats && (d->table_counter > 1)) + { + if (d->last_table) cli_printf(c, -1007, ""); + cli_printf(c, 14, "Total: %d of %d routes for %d networks in %d tables", + d->show_counter, d->rt_counter, d->net_counter, d->table_counter); + } + else + cli_printf(c, 0, ""); + +done: + rt_show_cleanup(c); + c->cont = c->cleanup = NULL; +} + +struct rt_show_data_rtable * +rt_show_add_table(struct rt_show_data *d, rtable *t) +{ + struct rt_show_data_rtable *tab = cfg_allocz(sizeof(struct rt_show_data_rtable)); + tab->table = t; + add_tail(&(d->tables), &(tab->n)); + return tab; +} + +static inline void +rt_show_get_default_tables(struct rt_show_data *d) +{ + struct channel *c; + struct rt_show_data_rtable *tab; + + if (d->export_channel) + { + c = d->export_channel; + tab = rt_show_add_table(d, c->table); + tab->export_channel = c; + return; + } + + if (d->export_protocol) + { + WALK_LIST(c, d->export_protocol->channels) + { + if (c->export_state == ES_DOWN) + continue; + + tab = rt_show_add_table(d, c->table); + tab->export_channel = c; + } + return; + } + + if (d->show_protocol) + { + WALK_LIST(c, d->show_protocol->channels) + rt_show_add_table(d, c->table); + return; + } + + for (int i=1; i<NET_MAX; i++) + if (config->def_tables[i]) + rt_show_add_table(d, config->def_tables[i]->table); +} + +static inline void +rt_show_prepare_tables(struct rt_show_data *d) +{ + struct rt_show_data_rtable *tab, *tabx; + + /* Add implicit tables if no table is specified */ + if (EMPTY_LIST(d->tables)) + rt_show_get_default_tables(d); + + WALK_LIST_DELSAFE(tab, tabx, d->tables) + { + /* Ensure there is defined export_channel for each table */ + if (d->export_mode) + { + if (!tab->export_channel && d->export_channel && + (tab->table == d->export_channel->table)) + tab->export_channel = d->export_channel; + + if (!tab->export_channel && d->export_protocol) + tab->export_channel = proto_find_channel_by_table(d->export_protocol, tab->table); + + if (!tab->export_channel) + { + if (d->tables_defined_by & RSD_TDB_NMN) + cf_error("No export channel for table %s", tab->table->name); + + rem_node(&(tab->n)); + continue; + } + } + + /* Ensure specified network is compatible with each table */ + if (d->addr && (tab->table->addr_type != d->addr->type)) + { + if (d->tables_defined_by & RSD_TDB_NMN) + cf_error("Incompatible type of prefix/ip for table %s", tab->table->name); + + rem_node(&(tab->n)); + continue; + } + } + + /* Ensure there is at least one table */ + if (EMPTY_LIST(d->tables)) + cf_error("No valid tables"); +} + +void +rt_show(struct rt_show_data *d) +{ + struct rt_show_data_rtable *tab; + net *n; + + /* Filtered routes are neither exported nor have sensible ordering */ + if (d->filtered && (d->export_mode || d->primary_only)) + cf_error("Incompatible show route options"); + + rt_show_prepare_tables(d); + + if (!d->addr) + { + WALK_LIST(tab, d->tables) + rt_lock_table(tab->table); + + /* There is at least one table */ + d->tab = HEAD(d->tables); + this_cli->cont = rt_show_cont; + this_cli->cleanup = rt_show_cleanup; + this_cli->rover = d; + } + else + { + WALK_LIST(tab, d->tables) + { + d->tab = tab; + + if (d->show_for) + n = net_route(tab->table, d->addr); + else + n = net_find(tab->table, d->addr); + + if (n) + rt_show_net(this_cli, n, d); + } + + if (d->rt_counter) + cli_msg(0, ""); + else + cli_msg(8001, "Network not found"); + } +} + diff --git a/nest/rt-table.c b/nest/rt-table.c index c6e48c38..85a951b8 100644 --- a/nest/rt-table.c +++ b/nest/rt-table.c @@ -33,7 +33,6 @@ #include "nest/bird.h" #include "nest/route.h" #include "nest/protocol.h" -#include "nest/cli.h" #include "nest/iface.h" #include "lib/resource.h" #include "lib/event.h" @@ -50,49 +49,156 @@ static linpool *rte_update_pool; static list routing_tables; -static byte *rt_format_via(rte *e); 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 int rt_prune_table(rtable *tab); -static inline void rt_schedule_gc(rtable *tab); -static inline void rt_schedule_prune(rtable *tab); +static inline void rt_prune_table(rtable *tab); -static inline struct ea_list * -make_tmp_attrs(struct rte *rt, struct linpool *pool) +/* Like fib_route(), but skips empty net entries */ +static inline void * +net_route_ip4(rtable *t, net_addr_ip4 *n) { - struct ea_list *(*mta)(struct rte *rt, struct linpool *pool); - mta = rt->attrs->src->proto->make_tmp_attrs; - return mta ? mta(rt, pool) : NULL; + net *r; + + while (r = net_find_valid(t, (net_addr *) n), (!r) && (n->pxlen > 0)) + { + n->pxlen--; + ip4_clrbit(&n->prefix, n->pxlen); + } + + return r; } -/* Like fib_route(), but skips empty net entries */ -static net * -net_route(rtable *tab, ip_addr a, int len) +static inline void * +net_route_ip6(rtable *t, net_addr_ip6 *n) +{ + net *r; + + while (r = net_find_valid(t, (net_addr *) n), (!r) && (n->pxlen > 0)) + { + n->pxlen--; + ip6_clrbit(&n->prefix, n->pxlen); + } + + return r; +} + +void * +net_route(rtable *tab, const net_addr *n) +{ + ASSERT(tab->addr_type == n->type); + + net_addr *n0 = alloca(n->length); + net_copy(n0, n); + + switch (n->type) + { + case NET_IP4: + case NET_VPN4: + case NET_ROA4: + return net_route_ip4(tab, (net_addr_ip4 *) n0); + + case NET_IP6: + case NET_VPN6: + case NET_ROA6: + return net_route_ip6(tab, (net_addr_ip6 *) n0); + + default: + return NULL; + } +} + + +static int +net_roa_check_ip4(rtable *tab, const net_addr_ip4 *px, u32 asn) { - ip_addr a0; - net *n; + struct net_addr_roa4 n = NET_ADDR_ROA4(px->prefix, px->pxlen, 0, 0); + struct fib_node *fn; + int anything = 0; - while (len >= 0) + while (1) + { + for (fn = fib_get_chain(&tab->fib, (net_addr *) &n); fn; fn = fn->next) { - a0 = ipa_and(a, ipa_mkmask(len)); - n = fib_find(&tab->fib, &a0, len); - if (n && rte_is_valid(n->routes)) - return n; - len--; + 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)) + { + anything = 1; + if (asn && (roa->asn == asn) && (roa->max_pxlen >= px->pxlen)) + return ROA_VALID; + } } - return NULL; + + if (n.pxlen == 0) + break; + + n.pxlen--; + ip4_clrbit(&n.prefix, n.pxlen); + } + + return anything ? ROA_INVALID : ROA_UNKNOWN; } -static void -rte_init(struct fib_node *N) +static int +net_roa_check_ip6(rtable *tab, const net_addr_ip6 *px, u32 asn) { - net *n = (net *) N; + struct net_addr_roa6 n = NET_ADDR_ROA6(px->prefix, px->pxlen, 0, 0); + struct fib_node *fn; + int anything = 0; + + while (1) + { + for (fn = fib_get_chain(&tab->fib, (net_addr *) &n); fn; fn = fn->next) + { + 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)) + { + anything = 1; + if (asn && (roa->asn == asn) && (roa->max_pxlen >= px->pxlen)) + return ROA_VALID; + } + } + + if (n.pxlen == 0) + break; - N->flags = 0; - n->routes = NULL; + n.pxlen--; + ip6_clrbit(&n.prefix, n.pxlen); + } + + return anything ? ROA_INVALID : ROA_UNKNOWN; +} + +/** + * roa_check - check validity of route origination in a ROA table + * @tab: ROA table + * @n: network prefix to check + * @asn: AS number of network prefix + * + * Implements RFC 6483 route validation for the given network prefix. The + * procedure is to find all candidate ROAs - ROAs whose prefixes cover the given + * network prefix. If there is no candidate ROA, return ROA_UNKNOWN. If there is + * a candidate ROA with matching ASN and maxlen field greater than or equal to + * the given prefix length, return ROA_VALID. Otherwise, return ROA_INVALID. If + * caller cannot determine origin AS, 0 could be used (in that case ROA_VALID + * cannot happen). Table @tab must have type NET_ROA4 or NET_ROA6, network @n + * must have type NET_IP4 or NET_IP6, respectively. + */ +int +net_roa_check(rtable *tab, const net_addr *n, u32 asn) +{ + if ((tab->addr_type == NET_ROA4) && (n->type == NET_IP4)) + return net_roa_check_ip4(tab, (const net_addr_ip4 *) n, asn); + else if ((tab->addr_type == NET_ROA6) && (n->type == NET_IP6)) + return net_roa_check_ip6(tab, (const net_addr_ip6 *) n, asn); + else + return ROA_UNKNOWN; /* Should not happen */ } /** @@ -129,7 +235,7 @@ rte_get_temp(rta *a) e->attrs = a; e->flags = 0; - e->pref = a->src->proto->preference; + e->pref = 0; return e; } @@ -227,7 +333,7 @@ rte_mergable(rte *pri, rte *sec) static void rte_trace(struct proto *p, rte *e, int dir, char *msg) { - log(L_TRACE "%s %c %s %I/%d %s", p->name, dir, msg, e->net->n.prefix, e->net->n.pxlen, rt_format_via(e)); + log(L_TRACE "%s %c %s %N %s", p->name, dir, msg, e->net->n.addr, rta_dest_name(e->attrs->dest)); } static inline void @@ -245,11 +351,11 @@ rte_trace_out(uint flag, struct proto *p, rte *e, char *msg) } static rte * -export_filter_(struct announce_hook *ah, rte *rt0, rte **rt_free, ea_list **tmpa, linpool *pool, int silent) +export_filter_(struct channel *c, rte *rt0, rte **rt_free, ea_list **tmpa, linpool *pool, int silent) { - struct proto *p = ah->proto; - struct filter *filter = ah->out_filter; - struct proto_stats *stats = ah->stats; + struct proto *p = c->proto; + struct filter *filter = c->out_filter; + struct proto_stats *stats = &c->stats; ea_list *tmpb = NULL; rte *rt; int v; @@ -260,7 +366,7 @@ export_filter_(struct announce_hook *ah, rte *rt0, rte **rt_free, ea_list **tmpa if (!tmpa) tmpa = &tmpb; - *tmpa = make_tmp_attrs(rt, pool); + *tmpa = rte_make_tmp_attrs(rt, pool); v = p->import_control ? p->import_control(p, &rt, tmpa, pool) : 0; if (v < 0) @@ -305,16 +411,16 @@ export_filter_(struct announce_hook *ah, rte *rt0, rte **rt_free, ea_list **tmpa } static inline rte * -export_filter(struct announce_hook *ah, rte *rt0, rte **rt_free, ea_list **tmpa, int silent) +export_filter(struct channel *c, rte *rt0, rte **rt_free, ea_list **tmpa, int silent) { - return export_filter_(ah, rt0, rt_free, tmpa, rte_update_pool, silent); + return export_filter_(c, rt0, rt_free, tmpa, rte_update_pool, silent); } static void -do_rt_notify(struct announce_hook *ah, net *net, rte *new, rte *old, ea_list *tmpa, int refeed) +do_rt_notify(struct channel *c, net *net, rte *new, rte *old, ea_list *tmpa, int refeed) { - struct proto *p = ah->proto; - struct proto_stats *stats = ah->stats; + struct proto *p = c->proto; + struct proto_stats *stats = &c->stats; /* @@ -344,11 +450,11 @@ do_rt_notify(struct announce_hook *ah, net *net, rte *new, rte *old, ea_list *tm * also non-new updates (contrary to import blocking). */ - struct proto_limit *l = ah->out_limit; - if (l && new) + struct channel_limit *l = &c->out_limit; + if (l->action && new) { if ((!old || refeed) && (stats->exp_routes >= l->limit)) - proto_notify_limit(ah, l, PLD_OUT, stats->exp_routes); + channel_notify_limit(c, l, PLD_OUT, stats->exp_routes); if (l->state == PLS_BLOCKED) { @@ -385,25 +491,24 @@ do_rt_notify(struct announce_hook *ah, net *net, rte *new, rte *old, ea_list *tm rte_trace_out(D_ROUTES, p, old, "removed"); } if (!new) - p->rt_notify(p, ah->table, net, NULL, old, NULL); + p->rt_notify(p, c, net, NULL, old, NULL); else if (tmpa) { ea_list *t = tmpa; while (t->next) t = t->next; t->next = new->attrs->eattrs; - p->rt_notify(p, ah->table, net, new, old, tmpa); + p->rt_notify(p, c, net, new, old, tmpa); t->next = NULL; } else - p->rt_notify(p, ah->table, net, new, old, new->attrs->eattrs); + p->rt_notify(p, c, net, new, old, new->attrs->eattrs); } static void -rt_notify_basic(struct announce_hook *ah, net *net, rte *new0, rte *old0, int refeed) +rt_notify_basic(struct channel *c, net *net, rte *new0, rte *old0, int refeed) { - struct proto *p = ah->proto; - struct proto_stats *stats = ah->stats; + struct proto *p = c->proto; rte *new = new0; rte *old = old0; @@ -412,9 +517,9 @@ rt_notify_basic(struct announce_hook *ah, net *net, rte *new0, rte *old0, int re ea_list *tmpa = NULL; if (new) - stats->exp_updates_received++; + c->stats.exp_updates_received++; else - stats->exp_withdraws_received++; + c->stats.exp_withdraws_received++; /* * This is a tricky part - we don't know whether route 'old' was @@ -437,10 +542,10 @@ rt_notify_basic(struct announce_hook *ah, net *net, rte *new0, rte *old0, int re */ if (new) - new = export_filter(ah, new, &new_free, &tmpa, 0); + new = export_filter(c, new, &new_free, &tmpa, 0); if (old && !refeed) - old = export_filter(ah, old, &old_free, NULL, 1); + old = export_filter(c, old, &old_free, NULL, 1); if (!new && !old) { @@ -457,13 +562,13 @@ rt_notify_basic(struct announce_hook *ah, net *net, rte *new0, rte *old0, int re #ifdef CONFIG_PIPE if ((p->proto == &proto_pipe) && !new0 && (p != old0->sender->proto)) - p->rt_notify(p, ah->table, net, NULL, old0, NULL); + p->rt_notify(p, c, net, NULL, old0, NULL); #endif return; } - do_rt_notify(ah, net, new, old, tmpa, refeed); + do_rt_notify(c, net, new, old, tmpa, refeed); /* Discard temporary rte's */ if (new_free) @@ -473,10 +578,9 @@ rt_notify_basic(struct announce_hook *ah, net *net, rte *new0, rte *old0, int re } static void -rt_notify_accepted(struct announce_hook *ah, net *net, rte *new_changed, rte *old_changed, rte *before_old, int feed) +rt_notify_accepted(struct channel *c, net *net, rte *new_changed, rte *old_changed, rte *before_old, int feed) { - // struct proto *p = ah->proto; - struct proto_stats *stats = ah->stats; + // struct proto *p = c->proto; rte *r; rte *new_best = NULL; @@ -494,14 +598,14 @@ rt_notify_accepted(struct announce_hook *ah, net *net, rte *new_changed, rte *ol was not valid, caller must use NULL for both old_changed and before_old. */ if (new_changed) - stats->exp_updates_received++; + c->stats.exp_updates_received++; else - stats->exp_withdraws_received++; + c->stats.exp_withdraws_received++; /* First, find the new_best route - first accepted by filters */ for (r=net->routes; rte_is_valid(r); r=r->next) { - if (new_best = export_filter(ah, r, &new_free, &tmpa, 0)) + if (new_best = export_filter(c, r, &new_free, &tmpa, 0)) break; /* Note if we walked around the position of old_changed route */ @@ -552,7 +656,7 @@ rt_notify_accepted(struct announce_hook *ah, net *net, rte *new_changed, rte *ol /* First case */ if (old_meet) - if (old_best = export_filter(ah, old_changed, &old_free, NULL, 1)) + if (old_best = export_filter(c, old_changed, &old_free, NULL, 1)) goto found; /* Second case */ @@ -570,18 +674,18 @@ rt_notify_accepted(struct announce_hook *ah, net *net, rte *new_changed, rte *ol /* Fourth case */ for (r=r->next; rte_is_valid(r); r=r->next) { - if (old_best = export_filter(ah, r, &old_free, NULL, 1)) + if (old_best = export_filter(c, r, &old_free, NULL, 1)) goto found; if (r == before_old) - if (old_best = export_filter(ah, old_changed, &old_free, NULL, 1)) + if (old_best = export_filter(c, old_changed, &old_free, NULL, 1)) goto found; } /* Implicitly, old_best is NULL and new_best is non-NULL */ found: - do_rt_notify(ah, net, new_best, old_best, tmpa, (feed == 2)); + do_rt_notify(c, net, new_best, old_best, tmpa, (feed == 2)); /* Discard temporary rte's */ if (new_free) @@ -591,19 +695,17 @@ rt_notify_accepted(struct announce_hook *ah, net *net, rte *new_changed, rte *ol } -static struct mpnh * -mpnh_merge_rta(struct mpnh *nhs, rta *a, linpool *pool, int max) +static struct nexthop * +nexthop_merge_rta(struct nexthop *nhs, rta *a, linpool *pool, int max) { - struct mpnh nh = { .gw = a->gw, .iface = a->iface }; - struct mpnh *nh2 = (a->dest == RTD_MULTIPATH) ? a->nexthops : &nh; - return mpnh_merge(nhs, nh2, 1, 0, max, pool); + return nexthop_merge(nhs, &(a->nh), 1, 0, max, pool); } rte * -rt_export_merged(struct announce_hook *ah, net *net, rte **rt_free, ea_list **tmpa, linpool *pool, int silent) +rt_export_merged(struct channel *c, net *net, rte **rt_free, ea_list **tmpa, linpool *pool, int silent) { - // struct proto *p = ah->proto; - struct mpnh *nhs = NULL; + // struct proto *p = c->proto; + struct nexthop *nhs = NULL; rte *best0, *best, *rt0, *rt, *tmp; best0 = net->routes; @@ -612,7 +714,7 @@ rt_export_merged(struct announce_hook *ah, net *net, rte **rt_free, ea_list **tm if (!rte_is_valid(best0)) return NULL; - best = export_filter_(ah, best0, rt_free, tmpa, pool, silent); + best = export_filter_(c, best0, rt_free, tmpa, pool, silent); if (!best || !rte_is_reachable(best)) return best; @@ -622,13 +724,13 @@ rt_export_merged(struct announce_hook *ah, net *net, rte **rt_free, ea_list **tm if (!rte_mergable(best0, rt0)) continue; - rt = export_filter_(ah, rt0, &tmp, NULL, pool, 1); + rt = export_filter_(c, rt0, &tmp, NULL, pool, 1); if (!rt) continue; if (rte_is_reachable(rt)) - nhs = mpnh_merge_rta(nhs, rt->attrs, pool, ah->proto->merge_limit); + nhs = nexthop_merge_rta(nhs, rt->attrs, pool, c->merge_limit); if (tmp) rte_free(tmp); @@ -636,13 +738,12 @@ rt_export_merged(struct announce_hook *ah, net *net, rte **rt_free, ea_list **tm if (nhs) { - nhs = mpnh_merge_rta(nhs, best->attrs, pool, ah->proto->merge_limit); + nhs = nexthop_merge_rta(nhs, best->attrs, pool, c->merge_limit); if (nhs->next) { best = rte_cow_rta(best, pool); - best->attrs->dest = RTD_MULTIPATH; - best->attrs->nexthops = nhs; + nexthop_link(best->attrs, nhs); } } @@ -654,10 +755,10 @@ rt_export_merged(struct announce_hook *ah, net *net, rte **rt_free, ea_list **tm static void -rt_notify_merged(struct announce_hook *ah, net *net, rte *new_changed, rte *old_changed, +rt_notify_merged(struct channel *c, net *net, rte *new_changed, rte *old_changed, rte *new_best, rte*old_best, int refeed) { - // struct proto *p = ah->proto; + // struct proto *p = c->proto; rte *new_best_free = NULL; rte *old_best_free = NULL; @@ -675,31 +776,31 @@ rt_notify_merged(struct announce_hook *ah, net *net, rte *new_changed, rte *old_ if ((new_best == old_best) && !refeed) { new_changed = rte_mergable(new_best, new_changed) ? - export_filter(ah, new_changed, &new_changed_free, NULL, 1) : NULL; + export_filter(c, new_changed, &new_changed_free, NULL, 1) : NULL; old_changed = rte_mergable(old_best, old_changed) ? - export_filter(ah, old_changed, &old_changed_free, NULL, 1) : NULL; + export_filter(c, old_changed, &old_changed_free, NULL, 1) : NULL; if (!new_changed && !old_changed) return; } if (new_best) - ah->stats->exp_updates_received++; + c->stats.exp_updates_received++; else - ah->stats->exp_withdraws_received++; + c->stats.exp_withdraws_received++; /* Prepare new merged route */ if (new_best) - new_best = rt_export_merged(ah, net, &new_best_free, &tmpa, rte_update_pool, 0); + new_best = rt_export_merged(c, net, &new_best_free, &tmpa, rte_update_pool, 0); /* Prepare old merged route (without proper merged next hops) */ /* There are some issues with running filter on old route - see rt_notify_basic() */ if (old_best && !refeed) - old_best = export_filter(ah, old_best, &old_best_free, NULL, 1); + old_best = export_filter(c, old_best, &old_best_free, NULL, 1); if (new_best || old_best) - do_rt_notify(ah, net, new_best, old_best, tmpa, refeed); + do_rt_notify(c, net, new_best, old_best, tmpa, refeed); /* Discard temporary rte's */ if (new_best_free) @@ -764,28 +865,22 @@ rte_announce(rtable *tab, unsigned type, net *net, rte *new, rte *old, if (!old && !new) return; - if (type == RA_OPTIMAL) - { - if (new) - new->attrs->src->proto->stats.pref_routes++; - if (old) - old->attrs->src->proto->stats.pref_routes--; - - if (tab->hostcache) - rt_notify_hostcache(tab, net); - } + if ((type == RA_OPTIMAL) && tab->hostcache) + rt_notify_hostcache(tab, net); - struct announce_hook *a; - WALK_LIST(a, tab->hooks) + struct channel *c; node *n; + WALK_LIST2(c, n, tab->channels, table_node) { - ASSERT(a->proto->export_state != ES_DOWN); - if (a->proto->accept_ra_types == type) + if (c->export_state == ES_DOWN) + continue; + + if (c->ra_mode == type) if (type == RA_ACCEPTED) - rt_notify_accepted(a, net, new, old, before_old, 0); + rt_notify_accepted(c, net, new, old, before_old, 0); else if (type == RA_MERGED) - rt_notify_merged(a, net, new, old, new_best, old_best, 0); + rt_notify_merged(c, net, new, old, new_best, old_best, 0); else - rt_notify_basic(a, net, new, old, 0); + rt_notify_basic(c, net, new, old, 0); } } @@ -795,27 +890,34 @@ rte_validate(rte *e) int c; net *n = e->net; - if ((n->n.pxlen > BITS_PER_IP_ADDRESS) || !ip_is_prefix(n->n.prefix,n->n.pxlen)) - { - log(L_WARN "Ignoring bogus prefix %I/%d received via %s", - n->n.prefix, n->n.pxlen, e->sender->proto->name); - return 0; - } + if (!net_validate(n->n.addr)) + { + log(L_WARN "Ignoring bogus prefix %N received via %s", + n->n.addr, e->sender->proto->name); + return 0; + } - c = ipa_classify_net(n->n.prefix); + c = net_classify(n->n.addr); if ((c < 0) || !(c & IADDR_HOST) || ((c & IADDR_SCOPE_MASK) <= SCOPE_LINK)) - { - log(L_WARN "Ignoring bogus route %I/%d received via %s", - n->n.prefix, n->n.pxlen, e->sender->proto->name); - return 0; - } + { + log(L_WARN "Ignoring bogus route %N received via %s", + n->n.addr, e->sender->proto->name); + return 0; + } - if ((e->attrs->dest == RTD_MULTIPATH) && !mpnh_is_sorted(e->attrs->nexthops)) - { - log(L_WARN "Ignoring unsorted multipath route %I/%d received via %s", - n->n.prefix, n->n.pxlen, e->sender->proto->name); - return 0; - } + if (net_type_match(n->n.addr, 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); + 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); + return 0; + } return 1; } @@ -855,11 +957,11 @@ rte_same(rte *x, rte *y) static inline int rte_is_ok(rte *e) { return e && !rte_is_filtered(e); } static void -rte_recalculate(struct announce_hook *ah, net *net, rte *new, struct rte_src *src) +rte_recalculate(struct channel *c, net *net, rte *new, struct rte_src *src) { - struct proto *p = ah->proto; - struct rtable *table = ah->table; - struct proto_stats *stats = ah->stats; + 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; @@ -884,8 +986,8 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, struct rte_src *sr { if (new) { - log_rl(&rl_pipe, L_ERR "Pipe collision detected when sending %I/%d to table %s", - net->n.prefix, net->n.pxlen, table->name); + 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; @@ -923,13 +1025,13 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, struct rte_src *sr int new_ok = rte_is_ok(new); int old_ok = rte_is_ok(old); - struct proto_limit *l = ah->rx_limit; - if (l && !old && new) + struct channel_limit *l = &c->rx_limit; + if (l->action && !old && new) { u32 all_routes = stats->imp_routes + stats->filt_routes; if (all_routes >= l->limit) - proto_notify_limit(ah, l, PLD_RX, all_routes); + channel_notify_limit(c, l, PLD_RX, all_routes); if (l->state == PLS_BLOCKED) { @@ -943,11 +1045,11 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, struct rte_src *sr } } - l = ah->in_limit; - if (l && !old_ok && new_ok) + l = &c->in_limit; + if (l->action && !old_ok && new_ok) { if (stats->imp_routes >= l->limit) - proto_notify_limit(ah, l, PLD_IN, stats->imp_routes); + channel_notify_limit(c, l, PLD_IN, stats->imp_routes); if (l->state == PLS_BLOCKED) { @@ -961,13 +1063,13 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, struct rte_src *sr stats->imp_updates_ignored++; rte_trace_in(D_FILTERS, p, new, "ignored [limit]"); - if (ah->in_keep_filtered) + if (c->in_keep_filtered) new->flags |= REF_FILTERED; else { rte_free_quick(new); new = NULL; } /* Note that old && !new could be possible when - ah->in_keep_filtered changed in the recent past. */ + c->in_keep_filtered changed in the recent past. */ if (!old && !new) return; @@ -1100,7 +1202,7 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, struct rte_src *sr if (!net->routes && (table->gc_counter++ >= table->config->gc_max_ops) && (table->gc_time + table->config->gc_min_time <= now)) - rt_schedule_gc(table); + rt_schedule_prune(table); if (old_ok && p->rte_remove) p->rte_remove(net, old); @@ -1149,7 +1251,7 @@ rte_unhide_dummy_routes(net *net, rte **dummy) /** * rte_update - enter a new update to a routing table * @table: table to be updated - * @ah: pointer to table announce hook + * @c: channel doing the update * @net: network node * @p: protocol submitting the update * @src: protocol originating the update @@ -1189,18 +1291,27 @@ rte_unhide_dummy_routes(net *net, rte **dummy) */ void -rte_update2(struct announce_hook *ah, net *net, rte *new, struct rte_src *src) +rte_update2(struct channel *c, const net_addr *n, rte *new, struct rte_src *src) { - struct proto *p = ah->proto; - struct proto_stats *stats = ah->stats; - struct filter *filter = ah->in_filter; + struct proto *p = c->proto; + struct proto_stats *stats = &c->stats; + struct filter *filter = c->in_filter; ea_list *tmpa = NULL; rte *dummy = NULL; + net *nn; + + ASSERT(c->channel_state == CS_UP); rte_update_lock(); if (new) { - new->sender = ah; + nn = net_get(c->table, n); + + new->net = nn; + new->sender = c; + + if (!new->pref) + new->pref = c->preference; stats->imp_updates_received++; if (!rte_validate(new)) @@ -1215,7 +1326,7 @@ rte_update2(struct announce_hook *ah, net *net, rte *new, struct rte_src *src) stats->imp_updates_filtered++; rte_trace_in(D_FILTERS, p, new, "filtered out"); - if (! ah->in_keep_filtered) + if (! c->in_keep_filtered) goto drop; /* new is a private copy, i could modify it */ @@ -1223,7 +1334,7 @@ rte_update2(struct announce_hook *ah, net *net, rte *new, struct rte_src *src) } else { - tmpa = make_tmp_attrs(new, rte_update_pool); + tmpa = rte_make_tmp_attrs(new, rte_update_pool); if (filter && (filter != FILTER_REJECT)) { ea_list *old_tmpa = tmpa; @@ -1233,7 +1344,7 @@ rte_update2(struct announce_hook *ah, net *net, rte *new, struct rte_src *src) stats->imp_updates_filtered++; rte_trace_in(D_FILTERS, p, new, "filtered out"); - if (! ah->in_keep_filtered) + if (! c->in_keep_filtered) goto drop; new->flags |= REF_FILTERED; @@ -1250,7 +1361,7 @@ rte_update2(struct announce_hook *ah, net *net, rte *new, struct rte_src *src) { stats->imp_withdraws_received++; - if (!net || !src) + if (!(nn = net_find(c->table, n)) || !src) { stats->imp_withdraws_ignored++; rte_update_unlock(); @@ -1259,9 +1370,9 @@ rte_update2(struct announce_hook *ah, net *net, rte *new, struct rte_src *src) } recalc: - rte_hide_dummy_routes(net, &dummy); - rte_recalculate(ah, net, new, src); - rte_unhide_dummy_routes(net, &dummy); + rte_hide_dummy_routes(nn, &dummy); + rte_recalculate(c, nn, new, src); + rte_unhide_dummy_routes(nn, &dummy); rte_update_unlock(); return; @@ -1292,9 +1403,9 @@ rte_discard(rte *old) /* Non-filtered route deletion, used during garbage collec /* Check rtable for best route to given net whether it would be exported do p */ int -rt_examine(rtable *t, ip_addr prefix, int pxlen, struct proto *p, struct filter *filter) +rt_examine(rtable *t, net_addr *a, struct proto *p, struct filter *filter) { - net *n = net_find(t, prefix, pxlen); + net *n = net_find(t, a); rte *rt = n ? n->routes : NULL; if (!rte_is_valid(rt)) @@ -1303,7 +1414,7 @@ rt_examine(rtable *t, ip_addr prefix, int pxlen, struct proto *p, struct filter rte_update_lock(); /* Rest is stripped down export_filter() */ - ea_list *tmpa = make_tmp_attrs(rt, rte_update_pool); + ea_list *tmpa = rte_make_tmp_attrs(rt, rte_update_pool); int v = p->import_control ? p->import_control(p, &rt, &tmpa, rte_update_pool) : 0; if (v == RIC_PROCESS) v = (f_run(filter, &rt, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) <= F_ACCEPT); @@ -1321,28 +1432,25 @@ rt_examine(rtable *t, ip_addr prefix, int pxlen, struct proto *p, struct filter /** * rt_refresh_begin - start a refresh cycle * @t: related routing table - * @ah: related announce hook + * @c related channel * * This function starts a refresh cycle for given routing table and announce * hook. The refresh cycle is a sequence where the protocol sends all its valid * routes to the routing table (by rte_update()). After that, all protocol - * routes (more precisely routes with @ah as @sender) not sent during the + * 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. */ void -rt_refresh_begin(rtable *t, struct announce_hook *ah) +rt_refresh_begin(rtable *t, struct channel *c) { - net *n; - rte *e; - - FIB_WALK(&t->fib, fn) + FIB_WALK(&t->fib, net, n) { - n = (net *) fn; + rte *e; for (e = n->routes; e; e = e->next) - if (e->sender == ah) + if (e->sender == c) e->flags |= REF_STALE; } FIB_WALK_END; @@ -1351,23 +1459,21 @@ rt_refresh_begin(rtable *t, struct announce_hook *ah) /** * rt_refresh_end - end a refresh cycle * @t: related routing table - * @ah: related announce hook + * @c: related channel * - * This function starts a refresh cycle for given routing table and announce + * This function ends a refresh cycle for given routing table and announce * hook. See rt_refresh_begin() for description of refresh cycles. */ void -rt_refresh_end(rtable *t, struct announce_hook *ah) +rt_refresh_end(rtable *t, struct channel *c) { int prune = 0; - net *n; - rte *e; - FIB_WALK(&t->fib, fn) + FIB_WALK(&t->fib, net, n) { - n = (net *) fn; + rte *e; for (e = n->routes; e; e = e->next) - if ((e->sender == ah) && (e->flags & REF_STALE)) + if ((e->sender == c) && (e->flags & REF_STALE)) { e->flags |= REF_DISCARD; prune = 1; @@ -1390,7 +1496,7 @@ void rte_dump(rte *e) { net *n = e->net; - debug("%-1I/%2d ", n->n.prefix, n->n.pxlen); + debug("%-1N ", n->n.addr); debug("KF=%02x PF=%02x pref=%d lm=%d ", n->n.flags, e->pflags, e->pref, now-e->lastmod); rta_dump(e->attrs); if (e->attrs->src->proto->proto->dump_attrs) @@ -1407,23 +1513,17 @@ rte_dump(rte *e) void rt_dump(rtable *t) { - rte *e; - net *n; - struct announce_hook *a; - debug("Dump of routing table <%s>\n", t->name); #ifdef DEBUGGING fib_check(&t->fib); #endif - FIB_WALK(&t->fib, fn) + FIB_WALK(&t->fib, net, n) { - n = (net *) fn; + rte *e; for(e=n->routes; e; e=e->next) rte_dump(e); } FIB_WALK_END; - WALK_LIST(a, t->hooks) - debug("\tAnnounces routes to protocol %s\n", a->proto->name); debug("\n"); } @@ -1442,23 +1542,6 @@ rt_dump_all(void) } static inline void -rt_schedule_prune(rtable *tab) -{ - rt_mark_for_prune(tab); - ev_schedule(tab->rt_event); -} - -static inline void -rt_schedule_gc(rtable *tab) -{ - if (tab->gc_scheduled) - return; - - tab->gc_scheduled = 1; - ev_schedule(tab->rt_event); -} - -static inline void rt_schedule_hcu(rtable *tab) { if (tab->hcu_scheduled) @@ -1471,51 +1554,34 @@ rt_schedule_hcu(rtable *tab) static inline void rt_schedule_nhu(rtable *tab) { - if (tab->nhu_state == 0) + if (tab->nhu_state == NHU_CLEAN) ev_schedule(tab->rt_event); - /* state change 0->1, 2->3 */ - tab->nhu_state |= 1; + /* state change: + * NHU_CLEAN -> NHU_SCHEDULED + * NHU_RUNNING -> NHU_DIRTY + */ + tab->nhu_state |= NHU_SCHEDULED; } - -static void -rt_prune_nets(rtable *tab) +void +rt_schedule_prune(rtable *tab) { - struct fib_iterator fit; - int ncnt = 0, ndel = 0; - -#ifdef DEBUGGING - fib_check(&tab->fib); -#endif - - FIB_ITERATE_INIT(&fit, &tab->fib); -again: - FIB_ITERATE_START(&tab->fib, &fit, f) - { - net *n = (net *) f; - ncnt++; - if (!n->routes) /* Orphaned FIB entry */ - { - FIB_ITERATE_PUT(&fit, f); - fib_delete(&tab->fib, f); - ndel++; - goto again; - } - } - FIB_ITERATE_END(f); - DBG("Pruned %d of %d networks\n", ndel, ncnt); + if (tab->prune_state == 0) + ev_schedule(tab->rt_event); - tab->gc_counter = 0; - tab->gc_time = now; - tab->gc_scheduled = 0; + /* state change 0->1, 2->3 */ + tab->prune_state |= 1; } + static void rt_event(void *ptr) { rtable *tab = ptr; + rt_lock_table(tab); + if (tab->hcu_scheduled) rt_update_hostcache(tab); @@ -1523,28 +1589,21 @@ rt_event(void *ptr) rt_next_hop_update(tab); if (tab->prune_state) - if (!rt_prune_table(tab)) - { - /* Table prune unfinished */ - ev_schedule(tab->rt_event); - return; - } + rt_prune_table(tab); - if (tab->gc_scheduled) - { - rt_prune_nets(tab); - rt_prune_sources(); // FIXME this should be moved to independent event - } + rt_unlock_table(tab); } void rt_setup(pool *p, rtable *t, char *name, struct rtable_config *cf) { bzero(t, sizeof(*t)); - fib_init(&t->fib, p, sizeof(net), 0, rte_init); t->name = name; t->config = cf; - init_list(&t->hooks); + t->addr_type = cf ? cf->addr_type : NET_IP4; + fib_init(&t->fib, p, t->addr_type, sizeof(net), OFFSETOF(net, n), 0, NULL); + init_list(&t->channels); + if (cf) { t->rt_event = ev_new(p); @@ -1565,121 +1624,123 @@ rt_init(void) { rta_init(); rt_table_pool = rp_new(&root_pool, "Routing tables"); - rte_update_pool = lp_new(rt_table_pool, 4080); + rte_update_pool = lp_new_default(rt_table_pool); rte_slab = sl_new(rt_table_pool, sizeof(rte)); init_list(&routing_tables); } -static int -rt_prune_step(rtable *tab, int *limit) +/** + * rt_prune_table - prune a routing table + * + * The prune loop scans routing tables and removes routes belonging to flushing + * protocols, discarded routes and also stale network entries. It is called from + * rt_event(). The event is rescheduled if the current iteration do not finish + * the table. The pruning is directed by the prune state (@prune_state), + * specifying whether the prune cycle is scheduled or running, and there + * is also a persistent pruning iterator (@prune_fit). + * + * The prune loop is used also for channel flushing. For this purpose, the + * channels to flush are marked before the iteration and notified after the + * iteration. + */ +static void +rt_prune_table(rtable *tab) { struct fib_iterator *fit = &tab->prune_fit; + int limit = 512; + + struct channel *c; + node *n, *x; DBG("Pruning route table %s\n", tab->name); #ifdef DEBUGGING fib_check(&tab->fib); #endif - if (tab->prune_state == RPS_NONE) - return 1; + if (tab->prune_state == 0) + return; - if (tab->prune_state == RPS_SCHEDULED) - { - FIB_ITERATE_INIT(fit, &tab->fib); - tab->prune_state = RPS_RUNNING; - } + 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; + + FIB_ITERATE_INIT(fit, &tab->fib); + tab->prune_state = 2; + } again: - FIB_ITERATE_START(&tab->fib, fit, fn) + FIB_ITERATE_START(&tab->fib, fit, net, n) { - net *n = (net *) fn; rte *e; rescan: for (e=n->routes; e; e=e->next) - if (e->sender->proto->flushing || (e->flags & REF_DISCARD)) + if (e->sender->flush_active || (e->flags & REF_DISCARD)) { - if (*limit <= 0) + if (limit <= 0) { - FIB_ITERATE_PUT(fit, fn); - return 0; + FIB_ITERATE_PUT(fit); + ev_schedule(tab->rt_event); + return; } rte_discard(e); - (*limit)--; + limit--; goto rescan; } + if (!n->routes) /* Orphaned FIB entry */ { - FIB_ITERATE_PUT(fit, fn); - fib_delete(&tab->fib, fn); + FIB_ITERATE_PUT(fit); + fib_delete(&tab->fib, n); goto again; } } - FIB_ITERATE_END(fn); + FIB_ITERATE_END; #ifdef DEBUGGING fib_check(&tab->fib); #endif - tab->prune_state = RPS_NONE; - return 1; -} + tab->gc_counter = 0; + tab->gc_time = now; -/** - * rt_prune_table - prune a routing table - * @tab: a routing table for pruning - * - * This function scans the routing table @tab and removes routes belonging to - * flushing protocols, discarded routes and also stale network entries, in a - * similar fashion like rt_prune_loop(). Returns 1 when all such routes are - * pruned. Contrary to rt_prune_loop(), this function is not a part of the - * protocol flushing loop, but it is called from rt_event() for just one routing - * table. - * - * Note that rt_prune_table() and rt_prune_loop() share (for each table) the - * prune state (@prune_state) and also the pruning iterator (@prune_fit). - */ -static inline int -rt_prune_table(rtable *tab) -{ - int limit = 512; - return rt_prune_step(tab, &limit); -} + /* state change 2->0, 3->1 */ + tab->prune_state &= 1; -/** - * rt_prune_loop - prune routing tables - * - * The prune loop scans routing tables and removes routes belonging to flushing - * protocols, discarded routes and also stale network entries. Returns 1 when - * all such routes are pruned. It is a part of the protocol flushing loop. - */ -int -rt_prune_loop(void) -{ - int limit = 512; - rtable *t; + if (tab->prune_state > 0) + ev_schedule(tab->rt_event); - WALK_LIST(t, routing_tables) - if (! rt_prune_step(t, &limit)) - return 0; + /* FIXME: This should be handled in a better way */ + rt_prune_sources(); - return 1; + /* Close flushed channels */ + WALK_LIST2_DELSAFE(c, n, x, tab->channels, table_node) + if (c->flush_active) + { + c->flush_active = 0; + channel_set_state(c, CS_DOWN); + } + + return; } void rt_preconfig(struct config *c) { - struct symbol *s = cf_get_symbol("master"); - init_list(&c->tables); - c->master_rtc = rt_new_table(s); + + rt_new_table(cf_get_symbol("master4"), NET_IP4); + rt_new_table(cf_get_symbol("master6"), NET_IP6); } -/* +/* * Some functions for handing internal next hop updates * triggered by rt_schedule_nhu(). */ @@ -1695,33 +1756,102 @@ rta_next_hop_outdated(rta *a) if (!he->src) return a->dest != RTD_UNREACHABLE; - return (a->iface != he->src->iface) || !ipa_equal(a->gw, he->gw) || - (a->dest != he->dest) || (a->igp_metric != he->igp_metric) || - !mpnh_same(a->nexthops, he->src->nexthops); + return (a->dest != he->dest) || (a->igp_metric != he->igp_metric) || + (!he->nexthop_linkable) || !nexthop_same(&(a->nh), &(he->src->nh)); } -static inline void -rta_apply_hostentry(rta *a, struct hostentry *he) +void +rta_apply_hostentry(rta *a, struct hostentry *he, mpls_label_stack *mls) { a->hostentry = he; - a->iface = he->src ? he->src->iface : NULL; - a->gw = he->gw; a->dest = he->dest; a->igp_metric = he->igp_metric; - a->nexthops = he->src ? he->src->nexthops : NULL; + + if (a->dest != RTD_UNICAST) + { + /* No nexthop */ +no_nexthop: + a->nh = (struct nexthop) {}; + if (mls) + { /* Store the label stack for later changes */ + a->nh.labels_orig = a->nh.labels = mls->len; + memcpy(a->nh.label, mls->stack, mls->len * sizeof(u32)); + } + return; + } + + if (((!mls) || (!mls->len)) && he->nexthop_linkable) + { /* Just link the nexthop chain, no label append happens. */ + memcpy(&(a->nh), &(he->src->nh), nexthop_size(&(he->src->nh))); + return; + } + + struct nexthop *nhp = NULL, *nhr = NULL; + int skip_nexthop = 0; + + for (struct nexthop *nh = &(he->src->nh); nh; nh = nh->next) + { + if (skip_nexthop) + skip_nexthop--; + else + { + nhr = nhp; + nhp = (nhp ? (nhp->next = lp_allocz(rte_update_pool, NEXTHOP_MAX_SIZE)) : &(a->nh)); + } + + nhp->iface = nh->iface; + nhp->weight = nh->weight; + if (mls) + { + nhp->labels = nh->labels + mls->len; + nhp->labels_orig = mls->len; + if (nhp->labels <= MPLS_MAX_LABEL_STACK) + { + memcpy(nhp->label, nh->label, nh->labels * sizeof(u32)); /* First the hostentry labels */ + memcpy(&(nhp->label[nh->labels]), mls->stack, mls->len * sizeof(u32)); /* Then the bottom labels */ + } + else + { + log(L_WARN "Sum of label stack sizes %d + %d = %d exceedes allowed maximum (%d)", + nh->labels, mls->len, nhp->labels, MPLS_MAX_LABEL_STACK); + skip_nexthop++; + continue; + } + } + if (ipa_nonzero(nh->gw)) + nhp->gw = nh->gw; /* Router nexthop */ + else if (ipa_nonzero(he->link)) + nhp->gw = he->link; /* Device nexthop with link-local address known */ + else + nhp->gw = he->addr; /* Device nexthop with link-local address unknown */ + } + + if (skip_nexthop) + if (nhr) + nhr->next = NULL; + else + { + a->dest = RTD_UNREACHABLE; + log(L_WARN "No valid nexthop remaining, setting route unreachable"); + goto no_nexthop; + } } static inline rte * rt_next_hop_update_rte(rtable *tab UNUSED, rte *old) { - rta a; - memcpy(&a, old->attrs, sizeof(rta)); - rta_apply_hostentry(&a, old->attrs->hostentry); - a.aflags = 0; + rta *a = alloca(RTA_MAX_SIZE); + memcpy(a, old->attrs, rta_size(old->attrs)); + + 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; rte *e = sl_alloc(rte_slab); memcpy(e, old, sizeof(rte)); - e->attrs = rta_lookup(&a); + e->attrs = rta_lookup(a); return e; } @@ -1802,49 +1932,61 @@ rt_next_hop_update(rtable *tab) struct fib_iterator *fit = &tab->nhu_fit; int max_feed = 32; - if (tab->nhu_state == 0) + if (tab->nhu_state == NHU_CLEAN) return; - if (tab->nhu_state == 1) + if (tab->nhu_state == NHU_SCHEDULED) { FIB_ITERATE_INIT(fit, &tab->fib); - tab->nhu_state = 2; + tab->nhu_state = NHU_RUNNING; } - FIB_ITERATE_START(&tab->fib, fit, fn) + FIB_ITERATE_START(&tab->fib, fit, net, n) { if (max_feed <= 0) { - FIB_ITERATE_PUT(fit, fn); + FIB_ITERATE_PUT(fit); ev_schedule(tab->rt_event); return; } - max_feed -= rt_next_hop_update_net(tab, (net *) fn); + max_feed -= rt_next_hop_update_net(tab, n); } - FIB_ITERATE_END(fn); + FIB_ITERATE_END; - /* state change 2->0, 3->1 */ + /* State change: + * NHU_DIRTY -> NHU_SCHEDULED + * NHU_RUNNING -> NHU_CLEAN + */ tab->nhu_state &= 1; - if (tab->nhu_state > 0) + if (tab->nhu_state != NHU_CLEAN) ev_schedule(tab->rt_event); } struct rtable_config * -rt_new_table(struct symbol *s) +rt_new_table(struct symbol *s, uint addr_type) { /* Hack that allows to 'redefine' the master table */ - if ((s->class == SYM_TABLE) && (s->def == new_config->master_rtc)) + if ((s->class == SYM_TABLE) && + (s->def == new_config->def_tables[addr_type]) && + ((addr_type == NET_IP4) || (addr_type == NET_IP6))) return s->def; struct rtable_config *c = cfg_allocz(sizeof(struct rtable_config)); cf_define_symbol(s, SYM_TABLE, c); c->name = s->name; - add_tail(&new_config->tables, &c->n); + c->addr_type = addr_type; c->gc_max_ops = 1000; c->gc_min_time = 5; + + add_tail(&new_config->tables, &c->n); + + /* First table of each type is kept as default */ + if (! new_config->def_tables[addr_type]) + new_config->def_tables[addr_type] = c; + return c; } @@ -1949,119 +2091,104 @@ rt_commit(struct config *new, struct config *old) } static inline void -do_feed_baby(struct proto *p, int type, struct announce_hook *h, net *n, rte *e) +do_feed_channel(struct channel *c, net *n, rte *e) { rte_update_lock(); - if (type == RA_ACCEPTED) - rt_notify_accepted(h, n, e, NULL, NULL, p->refeeding ? 2 : 1); - else if (type == RA_MERGED) - rt_notify_merged(h, n, NULL, NULL, e, p->refeeding ? e : NULL, p->refeeding); - else - rt_notify_basic(h, n, e, p->refeeding ? e : NULL, p->refeeding); + if (c->ra_mode == RA_ACCEPTED) + rt_notify_accepted(c, n, e, NULL, NULL, c->refeeding ? 2 : 1); + else if (c->ra_mode == RA_MERGED) + rt_notify_merged(c, n, NULL, NULL, e, c->refeeding ? e : NULL, c->refeeding); + else /* RA_BASIC */ + rt_notify_basic(c, n, e, c->refeeding ? e : NULL, c->refeeding); rte_update_unlock(); } /** - * rt_feed_baby - advertise routes to a new protocol - * @p: protocol to be fed + * rt_feed_channel - advertise all routes to a channel + * @c: channel to be fed * - * This function performs one pass of advertisement of routes to a newly - * initialized protocol. It's called by the protocol code as long as it - * has something to do. (We avoid transferring all the routes in single - * pass in order not to monopolize CPU time.) + * This function performs one pass of advertisement of routes to a channel that + * is in the ES_FEEDING state. It is called by the protocol code as long as it + * has something to do. (We avoid transferring all the routes in single pass in + * order not to monopolize CPU time.) */ int -rt_feed_baby(struct proto *p) +rt_feed_channel(struct channel *c) { - struct announce_hook *h; - struct fib_iterator *fit; + struct fib_iterator *fit = &c->feed_fit; int max_feed = 256; - if (!p->feed_ahook) /* Need to initialize first */ + ASSERT(c->export_state == ES_FEEDING); + + if (!c->feed_active) { - if (!p->ahooks) - return 1; - DBG("Announcing routes to new protocol %s\n", p->name); - p->feed_ahook = p->ahooks; - fit = p->feed_iterator = mb_alloc(p->pool, sizeof(struct fib_iterator)); - goto next_hook; + FIB_ITERATE_INIT(fit, &c->table->fib); + c->feed_active = 1; } - fit = p->feed_iterator; -again: - h = p->feed_ahook; - FIB_ITERATE_START(&h->table->fib, fit, fn) + FIB_ITERATE_START(&c->table->fib, fit, net, n) { - net *n = (net *) fn; rte *e = n->routes; if (max_feed <= 0) { - FIB_ITERATE_PUT(fit, fn); + FIB_ITERATE_PUT(fit); return 0; } - /* XXXX perhaps we should change feed for RA_ACCEPTED to not use 'new' */ + /* FIXME: perhaps we should change feed for RA_ACCEPTED to not use 'new' */ - if ((p->accept_ra_types == RA_OPTIMAL) || - (p->accept_ra_types == RA_ACCEPTED) || - (p->accept_ra_types == RA_MERGED)) + if ((c->ra_mode == RA_OPTIMAL) || + (c->ra_mode == RA_ACCEPTED) || + (c->ra_mode == RA_MERGED)) if (rte_is_valid(e)) { - if (p->export_state != ES_FEEDING) - return 1; /* In the meantime, the protocol fell down. */ + /* In the meantime, the protocol may fell down */ + if (c->export_state != ES_FEEDING) + goto done; - do_feed_baby(p, p->accept_ra_types, h, n, e); + do_feed_channel(c, n, e); max_feed--; } - if (p->accept_ra_types == RA_ANY) + if (c->ra_mode == RA_ANY) for(e = n->routes; e; e = e->next) { - if (p->export_state != ES_FEEDING) - return 1; /* In the meantime, the protocol fell down. */ + /* In the meantime, the protocol may fell down */ + if (c->export_state != ES_FEEDING) + goto done; if (!rte_is_valid(e)) continue; - do_feed_baby(p, RA_ANY, h, n, e); + do_feed_channel(c, n, e); max_feed--; } } - FIB_ITERATE_END(fn); - p->feed_ahook = h->next; - if (!p->feed_ahook) - { - mb_free(p->feed_iterator); - p->feed_iterator = NULL; - return 1; - } + FIB_ITERATE_END; -next_hook: - h = p->feed_ahook; - FIB_ITERATE_INIT(fit, &h->table->fib); - goto again; +done: + c->feed_active = 0; + return 1; } /** * rt_feed_baby_abort - abort protocol feeding - * @p: protocol + * @c: channel * - * This function is called by the protocol code when the protocol - * stops or ceases to exist before the last iteration of rt_feed_baby() - * has finished. + * This function is called by the protocol code when the protocol stops or + * ceases to exist during the feeding. */ void -rt_feed_baby_abort(struct proto *p) +rt_feed_channel_abort(struct channel *c) { - if (p->feed_ahook) + if (c->feed_active) { - /* Unlink the iterator and exit */ - fit_get(&p->feed_ahook->table->fib, p->feed_iterator); - p->feed_ahook = NULL; + /* Unlink the iterator */ + fit_get(&c->table->fib, &c->feed_fit); + c->feed_active = 0; } } - static inline unsigned ptr_hash(void *ptr) { @@ -2069,10 +2196,10 @@ ptr_hash(void *ptr) return p ^ (p << 8) ^ (p >> 16); } -static inline unsigned +static inline u32 hc_hash(ip_addr a, rtable *dep) { - return (ipa_hash(a) ^ ptr_hash(dep)) & 0xffff; + return ipa_hash(a) ^ ptr_hash(dep); } static inline void @@ -2106,7 +2233,7 @@ hc_alloc_table(struct hostcache *hc, unsigned order) { uint hsize = 1 << order; hc->hash_order = order; - hc->hash_shift = 16 - order; + hc->hash_shift = 32 - order; hc->hash_max = (order >= HC_HI_ORDER) ? ~0U : (hsize HC_HI_MARK); hc->hash_min = (order <= HC_LO_ORDER) ? 0U : (hsize HC_LO_MARK); @@ -2136,12 +2263,12 @@ hc_new_hostentry(struct hostcache *hc, ip_addr a, ip_addr ll, rtable *dep, unsig { struct hostentry *he = sl_alloc(hc->slab); - he->addr = a; - he->link = ll; - he->tab = dep; - he->hash_key = k; - he->uc = 0; - he->src = NULL; + *he = (struct hostentry) { + .addr = a, + .link = ll, + .tab = dep, + .hash_key = k, + }; add_tail(&hc->hostentries, &he->ln); hc_insert(hc, he); @@ -2177,7 +2304,7 @@ rt_init_hostcache(rtable *tab) hc_alloc_table(hc, HC_DEF_ORDER); hc->slab = sl_new(rt_table_pool, sizeof(struct hostentry)); - hc->lp = lp_new(rt_table_pool, 1008); + hc->lp = lp_new(rt_table_pool, LP_GOOD_SIZE(1024)); hc->trie = f_new_trie(hc->lp, sizeof(struct f_trie_node)); tab->hostcache = hc; @@ -2207,12 +2334,10 @@ rt_free_hostcache(rtable *tab) static void rt_notify_hostcache(rtable *tab, net *net) { - struct hostcache *hc = tab->hostcache; - if (tab->hcu_scheduled) return; - if (trie_match_prefix(hc->trie, net->n.prefix, net->n.pxlen)) + if (trie_match_net(tab->hostcache->trie, net->n.addr)) rt_schedule_hcu(tab); } @@ -2250,8 +2375,7 @@ rt_get_igp_metric(rte *rt) return rt->u.rip.metric; #endif - /* Device routes */ - if ((a->dest != RTD_ROUTER) && (a->dest != RTD_MULTIPATH)) + if (a->source == RTS_DEVICE) return 0; return IGP_METRIC_UNKNOWN; @@ -2263,55 +2387,56 @@ rt_update_hostentry(rtable *tab, struct hostentry *he) rta *old_src = he->src; int pxlen = 0; - /* Reset the hostentry */ + /* Reset the hostentry */ he->src = NULL; - he->gw = IPA_NONE; + he->nexthop_linkable = 0; he->dest = RTD_UNREACHABLE; he->igp_metric = 0; - net *n = net_route(tab, he->addr, MAX_PREFIX_LENGTH); + net_addr he_addr; + net_fill_ip_host(&he_addr, he->addr); + net *n = net_route(tab, &he_addr); if (n) { rte *e = n->routes; rta *a = e->attrs; - pxlen = n->n.pxlen; + pxlen = n->n.addr->pxlen; if (a->hostentry) { /* Recursive route should not depend on another recursive route */ - log(L_WARN "Next hop address %I resolvable through recursive route for %I/%d", - he->addr, n->n.prefix, pxlen); + log(L_WARN "Next hop address %I resolvable through recursive route for %N", + he->addr, n->n.addr); goto done; } - if (a->dest == RTD_DEVICE) + he->dest = a->dest; + he->nexthop_linkable = 1; + if (he->dest == RTD_UNICAST) { - if (if_local_addr(he->addr, a->iface)) - { - /* The host address is a local address, this is not valid */ - log(L_WARN "Next hop address %I is a local address of iface %s", - he->addr, a->iface->name); - goto done; - } - - /* The host is directly reachable, use link as a gateway */ - he->gw = he->link; - he->dest = RTD_ROUTER; - } - else - { - /* The host is reachable through some route entry */ - he->gw = a->gw; - he->dest = a->dest; + for (struct nexthop *nh = &(a->nh); nh; nh = nh->next) + if (ipa_zero(nh->gw)) + { + if (if_local_addr(he->addr, nh->iface)) + { + /* The host address is a local address, this is not valid */ + log(L_WARN "Next hop address %I is a local address of iface %s", + he->addr, nh->iface->name); + goto done; + } + + he->nexthop_linkable = 0; + break; + } } he->src = rta_clone(a); he->igp_metric = rt_get_igp_metric(e); } - done: +done: /* Add a prefix range to the trie */ - trie_add_prefix(tab->hostcache->trie, he->addr, MAX_PREFIX_LENGTH, pxlen, MAX_PREFIX_LENGTH); + trie_add_prefix(tab->hostcache->trie, &he_addr, pxlen, he_addr.pxlen); rta_free(old_src); return old_src != he->src; @@ -2344,7 +2469,7 @@ rt_update_hostcache(rtable *tab) tab->hcu_scheduled = 0; } -static struct hostentry * +struct hostentry * rt_get_hostentry(rtable *tab, ip_addr a, ip_addr ll, rtable *dep) { struct hostentry *he; @@ -2352,276 +2477,17 @@ rt_get_hostentry(rtable *tab, ip_addr a, ip_addr ll, rtable *dep) if (!tab->hostcache) rt_init_hostcache(tab); - uint k = hc_hash(a, dep); + u32 k = hc_hash(a, 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; - he = hc_new_hostentry(hc, a, ll, dep, k); + he = hc_new_hostentry(hc, a, ipa_zero(ll) ? a : ll, dep, k); rt_update_hostentry(tab, he); return he; } -void -rta_set_recursive_next_hop(rtable *dep, rta *a, rtable *tab, ip_addr *gw, ip_addr *ll) -{ - rta_apply_hostentry(a, rt_get_hostentry(tab, *gw, *ll, dep)); -} - - -/* - * CLI commands - */ - -static byte * -rt_format_via(rte *e) -{ - rta *a = e->attrs; - - /* Max text length w/o IP addr and interface name is 16 */ - static byte via[STD_ADDRESS_P_LENGTH+sizeof(a->iface->name)+16]; - - switch (a->dest) - { - case RTD_ROUTER: bsprintf(via, "via %I on %s", a->gw, a->iface->name); break; - case RTD_DEVICE: bsprintf(via, "dev %s", a->iface->name); break; - case RTD_BLACKHOLE: bsprintf(via, "blackhole"); break; - case RTD_UNREACHABLE: bsprintf(via, "unreachable"); break; - case RTD_PROHIBIT: bsprintf(via, "prohibited"); break; - case RTD_MULTIPATH: bsprintf(via, "multipath"); break; - default: bsprintf(via, "???"); - } - return via; -} - -static void -rt_show_rte(struct cli *c, byte *ia, rte *e, struct rt_show_data *d, ea_list *tmpa) -{ - byte from[STD_ADDRESS_P_LENGTH+8]; - byte tm[TM_DATETIME_BUFFER_SIZE], info[256]; - rta *a = e->attrs; - int primary = (e->net->routes == e); - int sync_error = (e->net->n.flags & KRF_SYNC_ERROR); - void (*get_route_info)(struct rte *, byte *buf, struct ea_list *attrs); - struct mpnh *nh; - - tm_format_datetime(tm, &config->tf_route, e->lastmod); - if (ipa_nonzero(a->from) && !ipa_equal(a->from, a->gw)) - bsprintf(from, " from %I", a->from); - else - from[0] = 0; - - get_route_info = a->src->proto->proto->get_route_info; - if (get_route_info || d->verbose) - { - /* Need to normalize the extended attributes */ - ea_list *t = tmpa; - t = ea_append(t, a->eattrs); - tmpa = alloca(ea_scan(t)); - ea_merge(t, tmpa); - ea_sort(tmpa); - } - if (get_route_info) - get_route_info(e, info, tmpa); - else - bsprintf(info, " (%d)", e->pref); - cli_printf(c, -1007, "%-18s %s [%s %s%s]%s%s", ia, rt_format_via(e), a->src->proto->name, - tm, from, primary ? (sync_error ? " !" : " *") : "", info); - for (nh = a->nexthops; nh; nh = nh->next) - cli_printf(c, -1007, "\tvia %I on %s weight %d", nh->gw, nh->iface->name, nh->weight + 1); - if (d->verbose) - rta_show(c, a, tmpa); -} - -static void -rt_show_net(struct cli *c, net *n, struct rt_show_data *d) -{ - rte *e, *ee; - byte ia[STD_ADDRESS_P_LENGTH+8]; - struct ea_list *tmpa; - struct announce_hook *a = NULL; - int first = 1; - int pass = 0; - - bsprintf(ia, "%I/%d", n->n.prefix, n->n.pxlen); - - if (d->export_mode) - { - if (! d->export_protocol->rt_notify) - return; - - a = proto_find_announce_hook(d->export_protocol, d->table); - if (!a) - return; - } - - for (e = n->routes; e; e = e->next) - { - if (rte_is_filtered(e) != d->filtered) - continue; - - d->rt_counter++; - d->net_counter += first; - first = 0; - - if (pass) - continue; - - ee = e; - rte_update_lock(); /* We use the update buffer for filtering */ - tmpa = make_tmp_attrs(e, rte_update_pool); - - /* Special case for merged export */ - if ((d->export_mode == RSEM_EXPORT) && (d->export_protocol->accept_ra_types == RA_MERGED)) - { - rte *rt_free; - e = rt_export_merged(a, n, &rt_free, &tmpa, rte_update_pool, 1); - pass = 1; - - if (!e) - { e = ee; goto skip; } - } - else if (d->export_mode) - { - struct proto *ep = d->export_protocol; - int ic = ep->import_control ? ep->import_control(ep, &e, &tmpa, rte_update_pool) : 0; - - if (ep->accept_ra_types == RA_OPTIMAL || ep->accept_ra_types == RA_MERGED) - pass = 1; - - if (ic < 0) - goto skip; - - if (d->export_mode > RSEM_PREEXPORT) - { - /* - * FIXME - This shows what should be exported according to current - * filters, but not what was really exported. 'configure soft' - * command may change the export filter and do not update routes. - */ - int do_export = (ic > 0) || - (f_run(a->out_filter, &e, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) <= F_ACCEPT); - - if (do_export != (d->export_mode == RSEM_EXPORT)) - goto skip; - - if ((d->export_mode == RSEM_EXPORT) && (ep->accept_ra_types == RA_ACCEPTED)) - pass = 1; - } - } - - if (d->show_protocol && (d->show_protocol != e->attrs->src->proto)) - goto skip; - - if (f_run(d->filter, &e, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT) - goto skip; - - d->show_counter++; - if (d->stats < 2) - rt_show_rte(c, ia, e, d, tmpa); - ia[0] = 0; - - skip: - if (e != ee) - { - rte_free(e); - e = ee; - } - rte_update_unlock(); - - if (d->primary_only) - break; - } -} - -static void -rt_show_cont(struct cli *c) -{ - struct rt_show_data *d = c->rover; -#ifdef DEBUGGING - unsigned max = 4; -#else - unsigned max = 64; -#endif - struct fib *fib = &d->table->fib; - struct fib_iterator *it = &d->fit; - - FIB_ITERATE_START(fib, it, f) - { - net *n = (net *) f; - if (d->running_on_config && d->running_on_config != config) - { - cli_printf(c, 8004, "Stopped due to reconfiguration"); - goto done; - } - if (d->export_protocol && (d->export_protocol->export_state == ES_DOWN)) - { - cli_printf(c, 8005, "Protocol is down"); - goto done; - } - if (!max--) - { - FIB_ITERATE_PUT(it, f); - return; - } - rt_show_net(c, n, d); - } - FIB_ITERATE_END(f); - if (d->stats) - cli_printf(c, 14, "%d of %d routes for %d networks", d->show_counter, d->rt_counter, d->net_counter); - else - cli_printf(c, 0, ""); -done: - c->cont = c->cleanup = NULL; -} - -static void -rt_show_cleanup(struct cli *c) -{ - struct rt_show_data *d = c->rover; - - /* Unlink the iterator */ - fit_get(&d->table->fib, &d->fit); -} - -void -rt_show(struct rt_show_data *d) -{ - net *n; - - /* Default is either a master table or a table related to a respective protocol */ - if (!d->table && d->export_protocol) d->table = d->export_protocol->table; - if (!d->table && d->show_protocol) d->table = d->show_protocol->table; - if (!d->table) d->table = config->master_rtc->table; - - /* Filtered routes are neither exported nor have sensible ordering */ - if (d->filtered && (d->export_mode || d->primary_only)) - cli_msg(0, ""); - - if (d->pxlen == 256) - { - FIB_ITERATE_INIT(&d->fit, &d->table->fib); - this_cli->cont = rt_show_cont; - this_cli->cleanup = rt_show_cleanup; - this_cli->rover = d; - } - else - { - if (d->show_for) - n = net_route(d->table, d->prefix, d->pxlen); - else - n = net_find(d->table, d->prefix, d->pxlen); - - if (n) - rt_show_net(this_cli, n, d); - - if (d->rt_counter) - cli_msg(0, ""); - else - cli_msg(8001, "Network not in table"); - } -} /* * Documentation for functions declared inline in route.h @@ -2632,26 +2498,24 @@ rt_show(struct rt_show_data *d) * net_find - find a network entry * @tab: a routing table * @addr: address of the network - * @len: length of the network prefix * * net_find() looks up the given network in routing table @tab and * returns a pointer to its &net entry or %NULL if no such network * exists. */ -static inline net *net_find(rtable *tab, ip_addr addr, unsigned len) +static inline net *net_find(rtable *tab, net_addr *addr) { DUMMY; } /** * net_get - obtain a network entry * @tab: a routing table * @addr: address of the network - * @len: length of the network prefix * * net_get() looks up the given network in routing table @tab and * returns a pointer to its &net entry. If no such entry exists, it's * created. */ -static inline net *net_get(rtable *tab, ip_addr addr, unsigned len) +static inline net *net_get(rtable *tab, net_addr *addr) { DUMMY; } /** @@ -4,7 +4,8 @@ C bfd C bgp C ospf C pipe -C rip C radv +C rip +C rpki C static S ../nest/rt-dev.c diff --git a/proto/babel/Makefile b/proto/babel/Makefile index 400ffbac..a5b4a13b 100644 --- a/proto/babel/Makefile +++ b/proto/babel/Makefile @@ -1,5 +1,6 @@ -source=babel.c packets.c -root-rel=../../ -dir-name=proto/babel +src := babel.c packets.c +obj := $(src-o-files) +$(all-daemon) +$(cf-local) -include ../../Rules +tests_objs := $(tests_objs) $(src-o-files)
\ No newline at end of file diff --git a/proto/babel/babel.c b/proto/babel/babel.c index 38be6909..1b1d9f62 100644 --- a/proto/babel/babel.c +++ b/proto/babel/babel.c @@ -53,8 +53,7 @@ static void babel_dump_route(struct babel_route *r); static void babel_select_route(struct babel_entry *e); static void babel_send_route_request(struct babel_entry *e, struct babel_neighbor *n); static void babel_send_wildcard_request(struct babel_iface *ifa); -static int babel_cache_seqno_request(struct babel_proto *p, ip_addr prefix, u8 plen, - u64 router_id, u16 seqno); +static int babel_cache_seqno_request(struct babel_proto *p, net_addr *n, u64 router_id, u16 seqno); static void babel_trigger_iface_update(struct babel_iface *ifa); static void babel_trigger_update(struct babel_proto *p); static void babel_send_seqno_request(struct babel_entry *e); @@ -67,27 +66,25 @@ static inline void babel_iface_kick_timer(struct babel_iface *ifa); */ static void -babel_init_entry(struct fib_node *n) +babel_init_entry(void *E) { - struct babel_entry *e = (void *) n; - e->proto = NULL; - e->selected_in = NULL; - e->selected_out = NULL; + struct babel_entry *e = E; + e->updated = now; init_list(&e->sources); init_list(&e->routes); } static inline struct babel_entry * -babel_find_entry(struct babel_proto *p, ip_addr prefix, u8 plen) +babel_find_entry(struct babel_proto *p, const net_addr *n) { - return fib_find(&p->rtable, &prefix, plen); + return fib_find(&p->rtable, n); } static struct babel_entry * -babel_get_entry(struct babel_proto *p, ip_addr prefix, u8 plen) +babel_get_entry(struct babel_proto *p, const net_addr *n) { - struct babel_entry *e = fib_get(&p->rtable, &prefix, plen); + struct babel_entry *e = fib_get(&p->rtable, n); e->proto = p; return e; } @@ -180,8 +177,8 @@ babel_flush_route(struct babel_route *r) { struct babel_proto *p = r->e->proto; - DBG("Babel: Flush route %I/%d router_id %lR neigh %I\n", - r->e->n.prefix, r->e->n.pxlen, r->router_id, r->neigh ? r->neigh->addr : IPA_NONE); + DBG("Babel: Flush route %N router_id %lR neigh %I\n", + r->e->n.addr, r->router_id, r->neigh ? r->neigh->addr : IPA_NONE); rem_node(NODE r); @@ -203,8 +200,8 @@ babel_expire_route(struct babel_route *r) struct babel_proto *p = r->e->proto; struct babel_entry *e = r->e; - TRACE(D_EVENTS, "Route expiry timer for %I/%d router-id %lR fired", - e->n.prefix, e->n.pxlen, r->router_id); + TRACE(D_EVENTS, "Route expiry timer for %N router-id %lR fired", + e->n.addr, r->router_id); if (r->metric < BABEL_INFINITY) { @@ -229,16 +226,14 @@ babel_refresh_route(struct babel_route *r) static void babel_expire_routes(struct babel_proto *p) { - struct babel_entry *e; struct babel_route *r, *rx; struct fib_iterator fit; FIB_ITERATE_INIT(&fit, &p->rtable); loop: - FIB_ITERATE_START(&p->rtable, &fit, n) + FIB_ITERATE_START(&p->rtable, &fit, struct babel_entry, e) { - e = (struct babel_entry *) n; int changed = 0; WALK_LIST_DELSAFE(r, rx, e->routes) @@ -261,7 +256,7 @@ loop: * babel_rt_notify() -> p->rtable change, invalidating hidden variables. */ - FIB_ITERATE_PUT(&fit, n); + FIB_ITERATE_PUT(&fit); babel_select_route(e); goto loop; } @@ -271,12 +266,12 @@ loop: /* Remove empty entries */ if (EMPTY_LIST(e->sources) && EMPTY_LIST(e->routes)) { - FIB_ITERATE_PUT(&fit, n); + FIB_ITERATE_PUT(&fit); fib_delete(&p->rtable, e); goto loop; } } - FIB_ITERATE_END(n); + FIB_ITERATE_END; } static struct babel_neighbor * @@ -476,35 +471,31 @@ babel_announce_rte(struct babel_proto *p, struct babel_entry *e) if (r) { - net *n = net_get(p->p.table, e->n.prefix, e->n.pxlen); - rta A = { + rta *ap0 = allocz(RTA_MAX_SIZE); + *ap0 = (rta) { .src = p->p.main_source, .source = RTS_BABEL, .scope = SCOPE_UNIVERSE, - .cast = RTC_UNICAST, - .dest = r->metric == BABEL_INFINITY ? RTD_UNREACHABLE : RTD_ROUTER, - .flags = 0, + .dest = r->metric == BABEL_INFINITY ? RTD_UNREACHABLE : RTD_UNICAST, .from = r->neigh->addr, - .iface = r->neigh->ifa->iface, + .nh.iface = r->neigh->ifa->iface, }; if (r->metric < BABEL_INFINITY) - A.gw = r->next_hop; + ap0->nh.gw = r->next_hop; - rta *a = rta_lookup(&A); + rta *a = rta_lookup(ap0); rte *rte = rte_get_temp(a); rte->u.babel.metric = r->metric; rte->u.babel.router_id = r->router_id; - rte->net = n; rte->pflags = 0; - rte_update(&p->p, n, rte); + rte_update(&p->p, e->n.addr, rte); } else { /* Retraction */ - net *n = net_find(p->p.table, e->n.prefix, e->n.pxlen); - rte_update(&p->p, n, NULL); + rte_update(&p->p, e->n.addr, NULL); } } @@ -541,8 +532,8 @@ babel_select_route(struct babel_entry *e) ((!e->selected_in && cur->metric < BABEL_INFINITY) || (e->selected_in && cur->metric < e->selected_in->metric))) { - TRACE(D_EVENTS, "Picked new route for prefix %I/%d: router id %lR metric %d", - e->n.prefix, e->n.pxlen, cur->router_id, cur->metric); + TRACE(D_EVENTS, "Picked new route for prefix %N: router id %lR metric %d", + e->n.addr, cur->router_id, cur->metric); e->selected_in = cur; e->updated = now; @@ -557,8 +548,8 @@ babel_select_route(struct babel_entry *e) babel_build_rte() will set the unreachable flag if the metric is BABEL_INFINITY.*/ if (e->selected_in) { - TRACE(D_EVENTS, "Lost feasible route for prefix %I/%d", - e->n.prefix, e->n.pxlen); + TRACE(D_EVENTS, "Lost feasible route for prefix %N", + e->n.addr); e->selected_in->metric = BABEL_INFINITY; e->updated = now; @@ -576,7 +567,7 @@ babel_select_route(struct babel_entry *e) /* No route currently selected, and no new one selected; this means we don't have a route to this destination anymore (and were probably called from an expiry timer). Remove the route from the nest. */ - TRACE(D_EVENTS, "Flushing route for prefix %I/%d", e->n.prefix, e->n.pxlen); + TRACE(D_EVENTS, "Flushing route for prefix %N", e->n.addr); e->selected_in = NULL; e->updated = now; @@ -663,12 +654,11 @@ babel_send_route_request(struct babel_entry *e, struct babel_neighbor *n) struct babel_iface *ifa = n->ifa; union babel_msg msg = {}; - TRACE(D_PACKETS, "Sending route request for %I/%d to %I", - e->n.prefix, e->n.pxlen, n->addr); + TRACE(D_PACKETS, "Sending route request for %N to %I", + e->n.addr, n->addr); msg.type = BABEL_TLV_ROUTE_REQUEST; - msg.route_request.prefix = e->n.prefix; - msg.route_request.plen = e->n.pxlen; + net_copy(&msg.route_request.net, e->n.addr); babel_send_unicast(&msg, ifa, n->addr); } @@ -698,18 +688,17 @@ babel_send_seqno_request(struct babel_entry *e) union babel_msg msg = {}; s = babel_find_source(e, r->router_id); - if (!s || !babel_cache_seqno_request(p, e->n.prefix, e->n.pxlen, r->router_id, s->seqno + 1)) + if (!s || !babel_cache_seqno_request(p, e->n.addr, r->router_id, s->seqno + 1)) return; - TRACE(D_PACKETS, "Sending seqno request for %I/%d router-id %lR seqno %d", - e->n.prefix, e->n.pxlen, r->router_id, s->seqno + 1); + TRACE(D_PACKETS, "Sending seqno request for %N router-id %lR seqno %d", + e->n.addr, r->router_id, s->seqno + 1); msg.type = BABEL_TLV_SEQNO_REQUEST; - msg.seqno_request.plen = e->n.pxlen; - msg.seqno_request.seqno = s->seqno + 1; msg.seqno_request.hop_count = BABEL_INITIAL_HOP_COUNT; + msg.seqno_request.seqno = s->seqno + 1; msg.seqno_request.router_id = r->router_id; - msg.seqno_request.prefix = e->n.prefix; + net_copy(&msg.seqno_request.net, e->n.addr); WALK_LIST(ifa, p->interfaces) babel_enqueue(&msg, ifa); @@ -725,18 +714,17 @@ babel_unicast_seqno_request(struct babel_route *r) union babel_msg msg = {}; s = babel_find_source(e, r->router_id); - if (!s || !babel_cache_seqno_request(p, e->n.prefix, e->n.pxlen, r->router_id, s->seqno + 1)) + if (!s || !babel_cache_seqno_request(p, e->n.addr, r->router_id, s->seqno + 1)) return; - TRACE(D_PACKETS, "Sending seqno request for %I/%d router-id %lR seqno %d", - e->n.prefix, e->n.pxlen, r->router_id, s->seqno + 1); + TRACE(D_PACKETS, "Sending seqno request for %N router-id %lR seqno %d", + e->n.addr, r->router_id, s->seqno + 1); msg.type = BABEL_TLV_SEQNO_REQUEST; - msg.seqno_request.plen = e->n.pxlen; - msg.seqno_request.seqno = s->seqno + 1; msg.seqno_request.hop_count = BABEL_INITIAL_HOP_COUNT; + msg.seqno_request.seqno = s->seqno + 1; msg.seqno_request.router_id = r->router_id; - msg.seqno_request.prefix = e->n.prefix; + net_copy(&msg.seqno_request.net, e->n.addr); babel_send_unicast(&msg, ifa, r->neigh->addr); } @@ -756,9 +744,8 @@ babel_send_update(struct babel_iface *ifa, bird_clock_t changed) { struct babel_proto *p = ifa->proto; - FIB_WALK(&p->rtable, n) + FIB_WALK(&p->rtable, struct babel_entry, e) { - struct babel_entry *e = (void *) n; struct babel_route *r = e->selected_out; if (!r) @@ -776,17 +763,16 @@ babel_send_update(struct babel_iface *ifa, bird_clock_t changed) if (e->updated < changed) continue; - TRACE(D_PACKETS, "Sending update for %I/%d router-id %lR seqno %d metric %d", - e->n.prefix, e->n.pxlen, r->router_id, r->seqno, r->metric); + TRACE(D_PACKETS, "Sending update for %N router-id %lR seqno %d metric %d", + e->n.addr, r->router_id, r->seqno, r->metric); union babel_msg msg = {}; msg.type = BABEL_TLV_UPDATE; - msg.update.plen = e->n.pxlen; msg.update.interval = ifa->cf->update_interval; msg.update.seqno = r->seqno; msg.update.metric = r->metric; - msg.update.prefix = e->n.prefix; msg.update.router_id = r->router_id; + net_copy(&msg.update.net, e->n.addr); babel_enqueue(&msg, ifa); @@ -839,20 +825,18 @@ babel_trigger_update(struct babel_proto *p) /* A retraction is an update with an infinite metric */ static void -babel_send_retraction(struct babel_iface *ifa, ip_addr prefix, int plen) +babel_send_retraction(struct babel_iface *ifa, net_addr *n) { struct babel_proto *p = ifa->proto; union babel_msg msg = {}; - TRACE(D_PACKETS, "Sending retraction for %I/%d seqno %d", - prefix, plen, p->update_seqno); + TRACE(D_PACKETS, "Sending retraction for %N seqno %d", n, p->update_seqno); msg.type = BABEL_TLV_UPDATE; - msg.update.plen = plen; msg.update.interval = ifa->cf->update_interval; msg.update.seqno = p->update_seqno; msg.update.metric = BABEL_INFINITY; - msg.update.prefix = prefix; + msg.update.net = *n; babel_enqueue(&msg, ifa); } @@ -941,22 +925,20 @@ babel_expire_seqno_requests(struct babel_proto *p) * found. Otherwise, a new entry is stored in the cache. */ static int -babel_cache_seqno_request(struct babel_proto *p, ip_addr prefix, u8 plen, +babel_cache_seqno_request(struct babel_proto *p, net_addr *n, u64 router_id, u16 seqno) { struct babel_seqno_request *r; WALK_LIST(r, p->seqno_cache) { - if (ipa_equal(r->prefix, prefix) && (r->plen == plen) && - (r->router_id == router_id) && (r->seqno == seqno)) + if (net_equal(&r->net, n) && (r->router_id == router_id) && (r->seqno == seqno)) return 0; } /* no entries found */ r = sl_alloc(p->seqno_slab); - r->prefix = prefix; - r->plen = plen; + net_copy(&r->net, n); r->router_id = router_id; r->seqno = seqno; r->updated = now; @@ -973,8 +955,8 @@ babel_forward_seqno_request(struct babel_entry *e, struct babel_proto *p = e->proto; struct babel_route *r; - TRACE(D_PACKETS, "Forwarding seqno request for %I/%d router-id %lR seqno %d", - e->n.prefix, e->n.pxlen, in->router_id, in->seqno); + TRACE(D_PACKETS, "Forwarding seqno request for %N router-id %lR seqno %d", + e->n.addr, in->router_id, in->seqno); WALK_LIST(r, e->routes) { @@ -982,16 +964,15 @@ babel_forward_seqno_request(struct babel_entry *e, !OUR_ROUTE(r) && !ipa_equal(r->neigh->addr, sender)) { - if (!babel_cache_seqno_request(p, e->n.prefix, e->n.pxlen, in->router_id, in->seqno)) + if (!babel_cache_seqno_request(p, e->n.addr, in->router_id, in->seqno)) return; union babel_msg msg = {}; msg.type = BABEL_TLV_SEQNO_REQUEST; - msg.seqno_request.plen = in->plen; - msg.seqno_request.seqno = in->seqno; msg.seqno_request.hop_count = in->hop_count-1; + msg.seqno_request.seqno = in->seqno; msg.seqno_request.router_id = in->router_id; - msg.seqno_request.prefix = e->n.prefix; + net_copy(&msg.seqno_request.net, e->n.addr); babel_send_unicast(&msg, r->neigh->ifa, r->neigh->addr); return; @@ -1073,8 +1054,11 @@ babel_handle_update(union babel_msg *m, struct babel_iface *ifa) node *n; int feasible; - TRACE(D_PACKETS, "Handling update for %I/%d with seqno %d metric %d", - msg->prefix, msg->plen, msg->seqno, msg->metric); + if (msg->wildcard) + TRACE(D_PACKETS, "Handling wildcard retraction", msg->seqno); + else + TRACE(D_PACKETS, "Handling update for %N with seqno %d metric %d", + &msg->net, msg->seqno, msg->metric); nbr = babel_find_neighbor(ifa, msg->sender); if (!nbr) @@ -1140,7 +1124,7 @@ babel_handle_update(union babel_msg *m, struct babel_iface *ifa) } else { - e = babel_find_entry(p, msg->prefix, msg->plen); + e = babel_find_entry(p, &msg->net); if (!e) return; @@ -1159,7 +1143,7 @@ babel_handle_update(union babel_msg *m, struct babel_iface *ifa) return; } - e = babel_get_entry(p, msg->prefix, msg->plen); + e = babel_get_entry(p, &msg->net); r = babel_find_route(e, nbr); /* the route entry indexed by neighbour */ s = babel_find_source(e, msg->router_id); /* for feasibility */ feasible = babel_is_feasible(s, msg->seqno, msg->metric); @@ -1231,14 +1215,14 @@ babel_handle_route_request(union babel_msg *m, struct babel_iface *ifa) return; } - TRACE(D_PACKETS, "Handling route request for %I/%d", msg->prefix, msg->plen); + TRACE(D_PACKETS, "Handling route request for %N", &msg->net); /* Non-wildcard request - see if we have an entry for the route. If not, send a retraction, otherwise send an update. */ - struct babel_entry *e = babel_find_entry(p, msg->prefix, msg->plen); + struct babel_entry *e = babel_find_entry(p, &msg->net); if (!e) { - babel_send_retraction(ifa, msg->prefix, msg->plen); + babel_send_retraction(ifa, &msg->net); } else { @@ -1256,11 +1240,11 @@ babel_handle_seqno_request(union babel_msg *m, struct babel_iface *ifa) /* RFC 6126 3.8.1.2 */ - TRACE(D_PACKETS, "Handling seqno request for %I/%d router-id %lR seqno %d hop count %d", - msg->prefix, msg->plen, msg->router_id, msg->seqno, msg->hop_count); + TRACE(D_PACKETS, "Handling seqno request for %N router-id %lR seqno %d hop count %d", + &msg->net, msg->router_id, msg->seqno, msg->hop_count); /* Ignore if we have no such entry or entry has infinite metric */ - struct babel_entry *e = babel_find_entry(p, msg->prefix, msg->plen); + struct babel_entry *e = babel_find_entry(p, &msg->net); if (!e || !e->selected_out || (e->selected_out->metric == BABEL_INFINITY)) return; @@ -1668,7 +1652,7 @@ babel_dump_entry(struct babel_entry *e) struct babel_source *s; struct babel_route *r; - debug("Babel: Entry %I/%d:\n", e->n.prefix, e->n.pxlen); + debug("Babel: Entry %N:\n", e->n.addr); WALK_LIST(s,e->sources) { debug(" "); babel_dump_source(s); } @@ -1715,9 +1699,9 @@ babel_dump(struct proto *P) WALK_LIST(ifa, p->interfaces) babel_dump_iface(ifa); - FIB_WALK(&p->rtable, n) + FIB_WALK(&p->rtable, struct babel_entry, e) { - babel_dump_entry((struct babel_entry *) n); + babel_dump_entry(e); } FIB_WALK_END; } @@ -1828,11 +1812,9 @@ void babel_show_entries(struct proto *P) { struct babel_proto *p = (void *) P; - struct babel_entry *e = NULL; struct babel_source *s = NULL; struct babel_route *r = NULL; - char ipbuf[STD_ADDRESS_P_LENGTH+5]; char ridbuf[ROUTER_ID_64_LENGTH+1]; if (p->p.proto_state != PS_UP) @@ -1846,17 +1828,14 @@ babel_show_entries(struct proto *P) cli_msg(-1025, "%-29s %-23s %6s %5s %7s %7s", "Prefix", "Router ID", "Metric", "Seqno", "Expires", "Sources"); - FIB_WALK(&p->rtable, n) + FIB_WALK(&p->rtable, struct babel_entry, e) { - e = (struct babel_entry *) n; r = e->selected_in ? e->selected_in : e->selected_out; int srcs = 0; WALK_LIST(s, e->sources) srcs++; - bsprintf(ipbuf, "%I/%u", e->n.prefix, e->n.pxlen); - if (r) { if (r->router_id == p->router_id) @@ -1865,12 +1844,12 @@ babel_show_entries(struct proto *P) bsprintf(ridbuf, "%lR", r->router_id); int time = r->expires ? r->expires - now : 0; - cli_msg(-1025, "%-29s %-23s %6u %5u %7u %7u", - ipbuf, ridbuf, r->metric, r->seqno, MAX(time, 0), srcs); + cli_msg(-1025, "%-29N %-23s %6u %5u %7u %7u", + e->n.addr, ridbuf, r->metric, r->seqno, MAX(time, 0), srcs); } else { - cli_msg(-1025, "%-29s %-44s %7u", ipbuf, "<pending>", srcs); + cli_msg(-1025, "%-29N %-44s %7u", e->n.addr, "<pending>", srcs); } } FIB_WALK_END; @@ -1964,7 +1943,7 @@ babel_store_tmp_attrs(struct rte *rt, struct ea_list *attrs) * so store it into our data structures. */ static void -babel_rt_notify(struct proto *P, struct rtable *table UNUSED, struct network *net, +babel_rt_notify(struct proto *P, struct channel *c UNUSED, struct network *net, struct rte *new, struct rte *old UNUSED, struct ea_list *attrs UNUSED) { struct babel_proto *p = (void *) P; @@ -1974,7 +1953,7 @@ babel_rt_notify(struct proto *P, struct rtable *table UNUSED, struct network *ne if (new) { /* Update */ - e = babel_get_entry(p, net->n.prefix, net->n.pxlen); + e = babel_get_entry(p, net->n.addr); if (new->attrs->src->proto != P) { @@ -1996,7 +1975,7 @@ babel_rt_notify(struct proto *P, struct rtable *table UNUSED, struct network *ne else { /* Withdraw */ - e = babel_find_entry(p, net->n.prefix, net->n.pxlen); + e = babel_find_entry(p, net->n.addr); if (!e || !e->selected_out) return; @@ -2046,11 +2025,12 @@ babel_rte_same(struct rte *new, struct rte *old) static struct proto * -babel_init(struct proto_config *cfg) +babel_init(struct proto_config *CF) { - struct proto *P = proto_new(cfg, sizeof(struct babel_proto)); + struct proto *P = proto_new(CF); + + P->main_channel = proto_add_channel(P, proto_cf_main_channel(CF)); - P->accept_ra_types = RA_OPTIMAL; P->if_notify = babel_if_notify; P->rt_notify = babel_rt_notify; P->import_control = babel_import_control; @@ -2068,7 +2048,8 @@ babel_start(struct proto *P) struct babel_proto *p = (void *) P; struct babel_config *cf = (void *) P->cf; - fib_init(&p->rtable, P->pool, sizeof(struct babel_entry), 0, babel_init_entry); + fib_init(&p->rtable, P->pool, NET_IP6, sizeof(struct babel_entry), + OFFSETOF(struct babel_entry, n), 0, babel_init_entry); init_list(&p->interfaces); p->timer = tm_new_set(P->pool, babel_timer, p, 0, 1); tm_start(p->timer, 2); @@ -2111,14 +2092,17 @@ babel_shutdown(struct proto *P) } static int -babel_reconfigure(struct proto *P, struct proto_config *c) +babel_reconfigure(struct proto *P, struct proto_config *CF) { struct babel_proto *p = (void *) P; - struct babel_config *new = (void *) c; + struct babel_config *new = (void *) CF; TRACE(D_EVENTS, "Reconfiguring"); - p->p.cf = c; + if (!proto_configure_channel(P, &P->main_channel, proto_cf_main_channel(CF))) + return 0; + + p->p.cf = CF; babel_reconfigure_ifaces(p, new); babel_trigger_update(p); @@ -2133,6 +2117,8 @@ struct protocol proto_babel = { .template = "babel%d", .attr_class = EAP_BABEL, .preference = DEF_PREF_BABEL, + .channel_mask = NB_IP6, + .proto_size = sizeof(struct babel_proto), .config_size = sizeof(struct babel_config), .init = babel_init, .dump = babel_dump, diff --git a/proto/babel/babel.h b/proto/babel/babel.h index 6a95d82f..792c9d60 100644 --- a/proto/babel/babel.h +++ b/proto/babel/babel.h @@ -21,11 +21,7 @@ #include "lib/lists.h" #include "lib/socket.h" #include "lib/string.h" -#include "lib/timer.h" - -#ifndef IPV6 -#error "The Babel protocol only speaks IPv6" -#endif +#include "sysdep/unix/timer.h" #define EA_BABEL_METRIC EA_CODE(EAP_BABEL, 0) #define EA_BABEL_ROUTER_ID EA_CODE(EAP_BABEL, 1) @@ -56,7 +52,7 @@ /* Max interval that will not overflow when carried as 16-bit centiseconds */ #define BABEL_MAX_INTERVAL (0xFFFF/BABEL_TIME_UNITS) -#define BABEL_OVERHEAD (SIZE_OF_IP_HEADER+UDP_HEADER_LENGTH) +#define BABEL_OVERHEAD (IP6_HEADER_LENGTH+UDP_HEADER_LENGTH) #define BABEL_MIN_MTU (512 + BABEL_OVERHEAD) @@ -212,7 +208,6 @@ struct babel_route { }; struct babel_entry { - struct fib_node n; struct babel_proto *proto; struct babel_route *selected_in; struct babel_route *selected_out; @@ -221,13 +216,14 @@ struct babel_entry { list sources; /* Source entries for this prefix (struct babel_source). */ list routes; /* Routes for this prefix (struct babel_route) */ + + struct fib_node n; }; /* Stores forwarded seqno requests for duplicate suppression. */ struct babel_seqno_request { node n; - ip_addr prefix; - u8 plen; + net_addr net; u64 router_id; u16 seqno; bird_clock_t updated; @@ -269,12 +265,11 @@ struct babel_msg_ihu { struct babel_msg_update { u8 type; u8 wildcard; - u8 plen; u16 interval; u16 seqno; u16 metric; - ip_addr prefix; u64 router_id; + net_addr net; ip_addr next_hop; ip_addr sender; }; @@ -282,17 +277,15 @@ struct babel_msg_update { struct babel_msg_route_request { u8 type; u8 full; - u8 plen; - ip_addr prefix; + net_addr net; }; struct babel_msg_seqno_request { u8 type; - u8 plen; - u16 seqno; u8 hop_count; + u16 seqno; u64 router_id; - ip_addr prefix; + net_addr net; ip_addr sender; }; diff --git a/proto/babel/config.Y b/proto/babel/config.Y index b6170852..cf8983fa 100644 --- a/proto/babel/config.Y +++ b/proto/babel/config.Y @@ -30,11 +30,13 @@ CF_ADDTO(proto, babel_proto) babel_proto_start: proto_start BABEL { this_proto = proto_config_new(&proto_babel, $1); + this_proto->net_type = NET_IP6; init_list(&BABEL_CFG->iface_list); }; babel_proto_item: proto_item + | proto_channel | INTERFACE babel_iface ; diff --git a/proto/babel/packets.c b/proto/babel/packets.c index 08054832..70cfc196 100644 --- a/proto/babel/packets.c +++ b/proto/babel/packets.c @@ -146,7 +146,8 @@ struct babel_write_state { #define TLV_HDR(tlv,t,l) ({ tlv->type = t; tlv->length = l - sizeof(struct babel_tlv); }) #define TLV_HDR0(tlv,t) TLV_HDR(tlv, t, tlv_data[t].min_length) -#define BYTES(n) ((((uint) n) + 7) / 8) +#define NET_SIZE(n) BYTES(net_pxlen(n)) + static inline u16 get_time16(const void *p) @@ -161,19 +162,19 @@ put_time16(void *p, u16 v) put_u16(p, v * BABEL_TIME_UNITS); } -static inline ip6_addr -get_ip6_px(const void *p, uint plen) +static inline void +read_ip6_px(net_addr *n, const void *p, uint plen) { ip6_addr addr = IPA_NONE; memcpy(&addr, p, BYTES(plen)); - return ip6_ntoh(addr); + net_fill_ip6(n, ip6_ntoh(addr), plen); } static inline void -put_ip6_px(void *p, ip6_addr addr, uint plen) +put_ip6_px(void *p, net_addr *n) { - addr = ip6_hton(addr); - memcpy(p, &addr, BYTES(plen)); + ip6_addr addr = ip6_hton(net6_prefix(n)); + memcpy(p, &addr, NET_SIZE(n)); } static inline ip6_addr @@ -480,6 +481,9 @@ babel_read_update(struct babel_tlv *hdr, union babel_msg *m, if (tlv->plen > 0) return PARSE_ERROR; + if (msg->metric != 65535) + return PARSE_ERROR; + msg->wildcard = 1; break; @@ -488,7 +492,7 @@ babel_read_update(struct babel_tlv *hdr, union babel_msg *m, return PARSE_IGNORE; case BABEL_AE_IP6: - if (tlv->plen > MAX_PREFIX_LENGTH) + if (tlv->plen > IP6_MAX_PREFIX_LENGTH) return PARSE_ERROR; /* Cannot omit data if there is no saved prefix */ @@ -499,18 +503,18 @@ babel_read_update(struct babel_tlv *hdr, union babel_msg *m, memcpy(buf, state->def_ip6_prefix, tlv->omitted); memcpy(buf + tlv->omitted, tlv->addr, len); - msg->plen = tlv->plen; - msg->prefix = ipa_from_ip6(get_ip6(buf)); + ip6_addr prefix = get_ip6(buf); + net_fill_ip6(&msg->net, prefix, tlv->plen); if (tlv->flags & BABEL_FLAG_DEF_PREFIX) { - put_ip6(state->def_ip6_prefix, msg->prefix); + put_ip6(state->def_ip6_prefix, prefix); state->def_ip6_prefix_seen = 1; } if (tlv->flags & BABEL_FLAG_ROUTER_ID) { - state->router_id = ((u64) _I2(msg->prefix)) << 32 | _I3(msg->prefix); + state->router_id = ((u64) _I2(prefix)) << 32 | _I3(prefix); state->router_id_seen = 1; } break; @@ -559,7 +563,7 @@ babel_write_update(struct babel_tlv *hdr, union babel_msg *m, tlv = (struct babel_tlv_update *) NEXT_TLV(tlv); } - uint len = sizeof(struct babel_tlv_update) + BYTES(msg->plen); + uint len = sizeof(struct babel_tlv_update) + NET_SIZE(&msg->net); if (len0 + len > max_len) return 0; @@ -575,8 +579,8 @@ babel_write_update(struct babel_tlv *hdr, union babel_msg *m, else { tlv->ae = BABEL_AE_IP6; - tlv->plen = msg->plen; - put_ip6_px(tlv->addr, msg->prefix, msg->plen); + tlv->plen = net6_pxlen(&msg->net); + put_ip6_px(tlv->addr, &msg->net); } put_time16(&tlv->interval, msg->interval); @@ -610,14 +614,13 @@ babel_read_route_request(struct babel_tlv *hdr, union babel_msg *m, return PARSE_IGNORE; case BABEL_AE_IP6: - if (tlv->plen > MAX_PREFIX_LENGTH) + if (tlv->plen > IP6_MAX_PREFIX_LENGTH) return PARSE_ERROR; if (TLV_OPT_LENGTH(tlv) < BYTES(tlv->plen)) return PARSE_ERROR; - msg->plen = tlv->plen; - msg->prefix = get_ip6_px(tlv->addr, tlv->plen); + read_ip6_px(&msg->net, tlv->addr, tlv->plen); return PARSE_SUCCESS; case BABEL_AE_IP6_LL: @@ -637,7 +640,7 @@ babel_write_route_request(struct babel_tlv *hdr, union babel_msg *m, struct babel_tlv_route_request *tlv = (void *) hdr; struct babel_msg_route_request *msg = &m->route_request; - uint len = sizeof(struct babel_tlv_route_request) + BYTES(msg->plen); + uint len = sizeof(struct babel_tlv_route_request) + NET_SIZE(&msg->net); if (len > max_len) return 0; @@ -652,8 +655,8 @@ babel_write_route_request(struct babel_tlv *hdr, union babel_msg *m, else { tlv->ae = BABEL_AE_IP6; - tlv->plen = msg->plen; - put_ip6_px(tlv->addr, msg->prefix, msg->plen); + tlv->plen = net6_pxlen(&msg->net); + put_ip6_px(tlv->addr, &msg->net); } return len; @@ -685,14 +688,13 @@ babel_read_seqno_request(struct babel_tlv *hdr, union babel_msg *m, return PARSE_IGNORE; case BABEL_AE_IP6: - if (tlv->plen > MAX_PREFIX_LENGTH) + if (tlv->plen > IP6_MAX_PREFIX_LENGTH) return PARSE_ERROR; if (TLV_OPT_LENGTH(tlv) < BYTES(tlv->plen)) return PARSE_ERROR; - msg->plen = tlv->plen; - msg->prefix = get_ip6_px(tlv->addr, tlv->plen); + read_ip6_px(&msg->net, tlv->addr, tlv->plen); return PARSE_SUCCESS; case BABEL_AE_IP6_LL: @@ -712,18 +714,18 @@ babel_write_seqno_request(struct babel_tlv *hdr, union babel_msg *m, struct babel_tlv_seqno_request *tlv = (void *) hdr; struct babel_msg_seqno_request *msg = &m->seqno_request; - uint len = sizeof(struct babel_tlv_seqno_request) + BYTES(msg->plen); + uint len = sizeof(struct babel_tlv_seqno_request) + NET_SIZE(&msg->net); if (len > max_len) return 0; TLV_HDR(tlv, BABEL_TLV_SEQNO_REQUEST, len); tlv->ae = BABEL_AE_IP6; - tlv->plen = msg->plen; + tlv->plen = net6_pxlen(&msg->net); put_u16(&tlv->seqno, msg->seqno); tlv->hop_count = msg->hop_count; put_u64(&tlv->router_id, msg->router_id); - put_ip6_px(tlv->addr, msg->prefix, msg->plen); + put_ip6_px(tlv->addr, &msg->net); return len; } diff --git a/proto/bfd/Makefile b/proto/bfd/Makefile index c28cedec..402122fc 100644 --- a/proto/bfd/Makefile +++ b/proto/bfd/Makefile @@ -1,5 +1,6 @@ -source=bfd.c packets.c io.c -root-rel=../../ -dir-name=proto/bfd +src := bfd.c io.c packets.c +obj := $(src-o-files) +$(all-daemon) +$(cf-local) -include ../../Rules +tests_objs := $(tests_objs) $(src-o-files)
\ No newline at end of file diff --git a/proto/bfd/bfd.c b/proto/bfd/bfd.c index 79135fae..e2c6050b 100644 --- a/proto/bfd/bfd.c +++ b/proto/bfd/bfd.c @@ -112,7 +112,7 @@ #define HASH_IP_KEY(n) n->addr #define HASH_IP_NEXT(n) n->next_ip #define HASH_IP_EQ(a,b) ipa_equal(a,b) -#define HASH_IP_FN(k) ipa_hash32(k) +#define HASH_IP_FN(k) ipa_hash(k) static list bfd_proto_list; static list bfd_wait_list; @@ -954,7 +954,7 @@ bfd_init_all(void) static struct proto * bfd_init(struct proto_config *c) { - struct proto *p = proto_new(c, sizeof(struct bfd_proto)); + struct proto *p = proto_new(c); p->neigh_notify = bfd_neigh_notify; @@ -983,8 +983,10 @@ bfd_start(struct proto *P) add_tail(&bfd_proto_list, &p->bfd_node); birdloop_enter(p->loop); - p->rx_1 = bfd_open_rx_sk(p, 0); - p->rx_m = bfd_open_rx_sk(p, 1); + p->rx4_1 = bfd_open_rx_sk(p, 0, SK_IPV4); + p->rx4_m = bfd_open_rx_sk(p, 1, SK_IPV4); + p->rx6_1 = bfd_open_rx_sk(p, 0, SK_IPV6); + p->rx6_m = bfd_open_rx_sk(p, 1, SK_IPV6); birdloop_leave(p->loop); bfd_take_requests(p); @@ -1118,6 +1120,7 @@ bfd_show_sessions(struct proto *P) struct protocol proto_bfd = { .name = "BFD", .template = "bfd%d", + .proto_size = sizeof(struct bfd_proto), .config_size = sizeof(struct bfd_config), .init = bfd_init, .start = bfd_start, diff --git a/proto/bfd/bfd.h b/proto/bfd/bfd.h index 46e09879..e2e75753 100644 --- a/proto/bfd/bfd.h +++ b/proto/bfd/bfd.h @@ -87,8 +87,10 @@ struct bfd_proto sock *notify_ws; list notify_list; - sock *rx_1; - sock *rx_m; + sock *rx4_1; + sock *rx6_1; + sock *rx4_m; + sock *rx6_m; list iface_list; }; @@ -201,7 +203,7 @@ void bfd_show_sessions(struct proto *P); /* packets.c */ void bfd_send_ctl(struct bfd_proto *p, struct bfd_session *s, int final); -sock * bfd_open_rx_sk(struct bfd_proto *p, int multihop); +sock * bfd_open_rx_sk(struct bfd_proto *p, int multihop, int inet_version); sock * bfd_open_tx_sk(struct bfd_proto *p, ip_addr local, struct iface *ifa); diff --git a/proto/bfd/io.h b/proto/bfd/io.h index 641ee054..45836f84 100644 --- a/proto/bfd/io.h +++ b/proto/bfd/io.h @@ -12,7 +12,7 @@ #include "lib/resource.h" #include "lib/event.h" #include "lib/socket.h" -// #include "lib/timer.h" +// #include "sysdep/unix/timer.h" typedef struct timer2 diff --git a/proto/bfd/packets.c b/proto/bfd/packets.c index 129db72f..06cde4d3 100644 --- a/proto/bfd/packets.c +++ b/proto/bfd/packets.c @@ -405,10 +405,11 @@ bfd_err_hook(sock *sk, int err) } sock * -bfd_open_rx_sk(struct bfd_proto *p, int multihop) +bfd_open_rx_sk(struct bfd_proto *p, int multihop, int af) { sock *sk = sk_new(p->tpool); sk->type = SK_UDP; + sk->subtype = af; sk->sport = !multihop ? BFD_CONTROL_PORT : BFD_MULTI_CTL_PORT; sk->data = p; @@ -421,10 +422,6 @@ bfd_open_rx_sk(struct bfd_proto *p, int multihop) sk->priority = sk_priority_control; sk->flags = SKF_THREAD | SKF_LADDR_RX | (!multihop ? SKF_TTL_RX : 0); -#ifdef IPV6 - sk->flags |= SKF_V6ONLY; -#endif - if (sk_open(sk) < 0) goto err; @@ -456,10 +453,6 @@ bfd_open_tx_sk(struct bfd_proto *p, ip_addr local, struct iface *ifa) sk->ttl = ifa ? 255 : -1; sk->flags = SKF_THREAD | SKF_BIND | SKF_HIGH_PORT; -#ifdef IPV6 - sk->flags |= SKF_V6ONLY; -#endif - if (sk_open(sk) < 0) goto err; diff --git a/proto/bgp/Makefile b/proto/bgp/Makefile index a634cf0d..00aaef5e 100644 --- a/proto/bgp/Makefile +++ b/proto/bgp/Makefile @@ -1,5 +1,6 @@ -source=bgp.c attrs.c packets.c -root-rel=../../ -dir-name=proto/bgp +src := attrs.c bgp.c packets.c +obj := $(src-o-files) +$(all-daemon) +$(cf-local) -include ../../Rules +tests_objs := $(tests_objs) $(src-o-files)
\ No newline at end of file diff --git a/proto/bgp/attrs.c b/proto/bgp/attrs.c index b9e2490d..882ba44e 100644 --- a/proto/bgp/attrs.c +++ b/proto/bgp/attrs.c @@ -2,6 +2,8 @@ * BIRD -- BGP Attributes * * (c) 2000 Martin Mares <mj@ucw.cz> + * (c) 2008--2016 Ondrej Zajicek <santiago@crfreenet.org> + * (c) 2008--2016 CZ.NIC z.s.p.o. * * Can be freely distributed and used under the terms of the GNU GPL. */ @@ -39,890 +41,1249 @@ * specifies that such updates should be ignored, but that is generally * a bad idea. * - * Error checking of optional transitive attributes is done according to - * draft-ietf-idr-optional-transitive-03, but errors are handled always - * as withdraws. + * BGP attribute table has several hooks: * - * Unexpected AS_CONFED_* segments in AS_PATH are logged and removed, - * but unknown segments cause a session drop with Malformed AS_PATH - * error (see validate_path()). The behavior in such case is not - * explicitly specified by RFC 4271. RFC 5065 specifies that - * inconsistent AS_CONFED_* segments should cause a session drop, but - * implementations that pass invalid AS_CONFED_* segments are - * widespread. + * export - Hook that validates and normalizes attribute during export phase. + * Receives eattr, may modify it (e.g., sort community lists for canonical + * representation), UNSET() it (e.g., skip empty lists), or WITHDRAW() it if + * necessary. May assume that eattr has value valid w.r.t. its type, but may be + * invalid w.r.t. BGP constraints. Optional. * - * Error handling of AS4_* attributes is done as specified by RFC 6793. There - * are several possible inconsistencies between AGGREGATOR and AS4_AGGREGATOR - * that are not handled by that RFC, these are logged and ignored (see - * bgp_reconstruct_4b_attrs()). + * encode - Hook that converts internal representation to external one during + * packet writing. Receives eattr and puts it in the buffer (including attribute + * header). Returns number of bytes, or -1 if not enough space. May assume that + * eattr has value valid w.r.t. its type and validated by export hook. Mandatory + * for all known attributes that exist internally after export phase (i.e., all + * except pseudoattributes MP_(UN)REACH_NLRI). + * + * decode - Hook that converts external representation to internal one during + * packet parsing. Receives attribute data in buffer, validates it and adds + * attribute to ea_list. If data are invalid, steps DISCARD(), WITHDRAW() or + * bgp_parse_error() may be used to escape. Mandatory for all known attributes. + * + * format - Optional hook that converts eattr to textual representation. */ +// XXXX review pool usage : c->c.proto->pool -static byte bgp_mandatory_attrs[] = { BA_ORIGIN, BA_AS_PATH -#ifndef IPV6 -,BA_NEXT_HOP -#endif -}; -struct attr_desc { - char *name; - int expected_length; - int expected_flags; - int type; - int allow_in_ebgp; - int (*validate)(struct bgp_proto *p, byte *attr, int len); - void (*format)(eattr *ea, byte *buf, int buflen); +struct bgp_attr_desc { + const char *name; + uint type; + uint flags; + void (*export)(struct bgp_export_state *s, eattr *a); + int (*encode)(struct bgp_write_state *s, eattr *a, byte *buf, uint size); + void (*decode)(struct bgp_parse_state *s, uint code, uint flags, byte *data, uint len, ea_list **to); + void (*format)(eattr *ea, byte *buf, uint size); }; -#define IGNORE -1 -#define WITHDRAW -2 +static const struct bgp_attr_desc bgp_attr_table[]; + +static inline int bgp_attr_known(uint code); + +eattr * +bgp_set_attr(ea_list **attrs, struct linpool *pool, uint code, uint flags, uintptr_t val) +{ + ASSERT(bgp_attr_known(code)); + + ea_list *a = lp_alloc(pool, sizeof(ea_list) + sizeof(eattr)); + eattr *e = &a->attrs[0]; + + a->flags = EALF_SORTED; + a->count = 1; + a->next = *attrs; + *attrs = a; + + e->id = EA_CODE(EAP_BGP, code); + e->type = bgp_attr_table[code].type; + e->flags = flags; + + if (e->type & EAF_EMBEDDED) + e->u.data = (u32) val; + else + e->u.ptr = (struct adata *) val; + + return e; +} + + + +#define REPORT(msg, args...) \ + ({ log(L_REMOTE "%s: " msg, s->proto->p.name, ## args); }) + +#define DISCARD(msg, args...) \ + ({ REPORT(msg, ## args); return; }) + +#define WITHDRAW(msg, args...) \ + ({ REPORT(msg, ## args); s->err_withdraw = 1; return; }) + +#define UNSET(a) \ + ({ a->type = EAF_TYPE_UNDEF; return; }) + +#define NEW_BGP "Discarding %s attribute received from AS4-aware neighbor" +#define BAD_EBGP "Discarding %s attribute received from EBGP neighbor" +#define BAD_LENGTH "Malformed %s attribute - invalid length (%u)" +#define BAD_VALUE "Malformed %s attribute - invalid value (%u)" +#define NO_MANDATORY "Missing mandatory %s attribute" + + +static inline int +bgp_put_attr_hdr3(byte *buf, uint code, uint flags, uint len) +{ + *buf++ = flags; + *buf++ = code; + *buf++ = len; + return 3; +} + +static inline int +bgp_put_attr_hdr4(byte *buf, uint code, uint flags, uint len) +{ + *buf++ = flags | BAF_EXT_LEN; + *buf++ = code; + put_u16(buf, len); + return 4; +} + +static inline int +bgp_put_attr_hdr(byte *buf, uint code, uint flags, uint len) +{ + if (len < 256) + return bgp_put_attr_hdr3(buf, code, flags, len); + else + return bgp_put_attr_hdr4(buf, code, flags, len); +} static int -bgp_check_origin(struct bgp_proto *p UNUSED, byte *a, int len UNUSED) +bgp_encode_u8(struct bgp_write_state *s UNUSED, eattr *a, byte *buf, uint size) { - if (*a > 2) - return 6; - return 0; + if (size < (3+1)) + return -1; + + bgp_put_attr_hdr3(buf, EA_ID(a->id), a->flags, 1); + buf[3] = a->u.data; + + return 3+1; } -static void -bgp_format_origin(eattr *a, byte *buf, int buflen UNUSED) +static int +bgp_encode_u32(struct bgp_write_state *s UNUSED, eattr *a, byte *buf, uint size) { - static char *bgp_origin_names[] = { "IGP", "EGP", "Incomplete" }; + if (size < (3+4)) + return -1; + + bgp_put_attr_hdr3(buf, EA_ID(a->id), a->flags, 4); + put_u32(buf+3, a->u.data); - bsprintf(buf, bgp_origin_names[a->u.data]); + return 3+4; } static int -path_segment_contains(byte *p, int bs, u32 asn) +bgp_encode_u32s(struct bgp_write_state *s UNUSED, eattr *a, byte *buf, uint size) { - int i; - int len = p[1]; - p += 2; + uint len = a->u.ptr->length; - for(i=0; i<len; i++) - { - u32 asn2 = (bs == 4) ? get_u32(p) : get_u16(p); - if (asn2 == asn) - return 1; - p += bs; - } + if (size < (4+len)) + return -1; - return 0; + uint hdr = bgp_put_attr_hdr(buf, EA_ID(a->id), a->flags, len); + put_u32s(buf + hdr, (u32 *) a->u.ptr->data, len / 4); + + return hdr + len; } -/* Validates path attribute, removes AS_CONFED_* segments, and also returns path length */ static int -validate_path(struct bgp_proto *p, int as_path, int bs, byte *idata, uint *ilength) +bgp_put_attr(byte *buf, uint size, uint code, uint flags, byte *data, uint len) { - int res = 0; - u8 *a, *dst; - int len, plen; + if (size < (4+len)) + return -1; - dst = a = idata; - len = *ilength; + uint hdr = bgp_put_attr_hdr(buf, code, flags, len); + memcpy(buf + hdr, data, len); - while (len) - { - if (len < 2) - return -1; - - plen = 2 + bs * a[1]; - if (len < plen) - return -1; - - if (a[1] == 0) - { - log(L_WARN "%s: %s_PATH attribute contains empty segment, skipping it", - p->p.name, as_path ? "AS" : "AS4"); - goto skip; - } - - switch (a[0]) - { - case AS_PATH_SET: - res++; - break; - - case AS_PATH_SEQUENCE: - res += a[1]; - break; - - case AS_PATH_CONFED_SEQUENCE: - case AS_PATH_CONFED_SET: - if (as_path && path_segment_contains(a, bs, p->remote_as)) - { - log(L_WARN "%s: AS_CONFED_* segment with peer ASN found, misconfigured confederation?", p->p.name); - return -1; - } - - log(L_WARN "%s: %s_PATH attribute contains AS_CONFED_* segment, skipping segment", - p->p.name, as_path ? "AS" : "AS4"); - goto skip; - - default: - return -1; - } - - if (dst != a) - memmove(dst, a, plen); - dst += plen; - - skip: - len -= plen; - a += plen; - } + return hdr + len; +} - *ilength = dst - idata; - return res; +static int +bgp_encode_raw(struct bgp_write_state *s UNUSED, eattr *a, byte *buf, uint size) +{ + return bgp_put_attr(buf, size, EA_ID(a->id), a->flags, a->u.ptr->data, a->u.ptr->length); } -static inline int -validate_as_path(struct bgp_proto *p, byte *a, int *len) + +/* + * Attribute hooks + */ + +static void +bgp_export_origin(struct bgp_export_state *s, eattr *a) { - return validate_path(p, 1, p->as4_session ? 4 : 2, a, len); + if (a->u.data > 2) + WITHDRAW(BAD_VALUE, "ORIGIN", a->u.data); } -static inline int -validate_as4_path(struct bgp_proto *p, struct adata *path) +static void +bgp_decode_origin(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to) { - return validate_path(p, 0, 4, path->data, &path->length); + if (len != 1) + WITHDRAW(BAD_LENGTH, "ORIGIN", len); + + if (data[0] > 2) + WITHDRAW(BAD_VALUE, "ORIGIN", data[0]); + + bgp_set_attr_u32(to, s->pool, BA_ORIGIN, flags, data[0]); } +static void +bgp_format_origin(eattr *a, byte *buf, uint size UNUSED) +{ + static const char *bgp_origin_names[] = { "IGP", "EGP", "Incomplete" }; + + bsprintf(buf, (a->u.data <= 2) ? bgp_origin_names[a->u.data] : "?"); +} + + static int -bgp_check_next_hop(struct bgp_proto *p UNUSED, byte *a UNUSED6, int len UNUSED6) +bgp_encode_as_path(struct bgp_write_state *s, eattr *a, byte *buf, uint size) { -#ifdef IPV6 - return IGNORE; -#else - ip_addr addr; + byte *data = a->u.ptr->data; + uint len = a->u.ptr->length; + + if (!s->as4_session) + { + /* Prepare 16-bit AS_PATH (from 32-bit one) in a temporary buffer */ + byte *src = data; + data = alloca(len); + len = as_path_32to16(data, src, len); + } + + return bgp_put_attr(buf, size, BA_AS_PATH, a->flags, data, len); +} - memcpy(&addr, a, len); - ipa_ntoh(addr); - if (ipa_classify(addr) & IADDR_HOST) +static void +bgp_decode_as_path(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to) +{ + struct bgp_proto *p = s->proto; + int as_length = s->as4_session ? 4 : 2; + int as_confed = p->cf->confederation && p->is_interior; + char err[128]; + + if (!as_path_valid(data, len, as_length, as_confed, err, sizeof(err))) + WITHDRAW("Malformed AS_PATH attribute - %s", err); + + /* In some circumstances check for initial AS_CONFED_SEQUENCE; RFC 5065 5.0 */ + if (p->is_interior && !p->is_internal && + ((len < 2) || (data[0] != AS_PATH_CONFED_SEQUENCE))) + WITHDRAW("Malformed AS_PATH attribute - %s", "missing initial AS_CONFED_SEQUENCE"); + + if (!s->as4_session) + { + /* Prepare 32-bit AS_PATH (from 16-bit one) in a temporary buffer */ + byte *src = data; + data = alloca(2*len); + len = as_path_16to32(data, src, len); + } + + bgp_set_attr_data(to, s->pool, BA_AS_PATH, flags, data, len); +} + + +static int +bgp_encode_next_hop(struct bgp_write_state *s, eattr *a, byte *buf, uint size) +{ + /* + * The NEXT_HOP attribute is used only in traditional (IPv4) BGP. In MP-BGP, + * the next hop is encoded as a part of the MP_REACH_NLRI attribute, so we + * store it and encode it later by AFI-specific hooks. + */ + + if (s->channel->afi == BGP_AF_IPV4) + { + ASSERT(a->u.ptr->length == sizeof(ip_addr)); + + if (size < (3+4)) + return -1; + + bgp_put_attr_hdr3(buf, BA_NEXT_HOP, a->flags, 4); + put_ip4(buf+3, ipa_to_ip4( *(ip_addr *) a->u.ptr->data )); + + return 3+4; + } + else + { + s->mp_next_hop = a; return 0; + } +} + +static void +bgp_decode_next_hop(struct bgp_parse_state *s, uint code UNUSED, uint flags UNUSED, byte *data, uint len, ea_list **to UNUSED) +{ + if (len != 4) + WITHDRAW(BAD_LENGTH, "NEXT_HOP", len); + + /* Semantic checks are done later */ + s->ip_next_hop_len = len; + s->ip_next_hop_data = data; +} + +/* TODO: This function should use AF-specific hook */ +static void +bgp_format_next_hop(eattr *a, byte *buf, uint size UNUSED) +{ + ip_addr *nh = (void *) a->u.ptr->data; + uint len = a->u.ptr->length; + + ASSERT((len == 16) || (len == 32)); + + /* in IPv6, we may have two addresses in NEXT HOP */ + if ((len == 16) || ipa_zero(nh[1])) + bsprintf(buf, "%I", nh[0]); else - return 8; -#endif + bsprintf(buf, "%I %I", nh[0], nh[1]); } + static void -bgp_format_next_hop(eattr *a, byte *buf, int buflen UNUSED) +bgp_decode_med(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to) { - ip_addr *ipp = (ip_addr *) a->u.ptr->data; -#ifdef IPV6 - /* in IPv6, we might have two addresses in NEXT HOP */ - if ((a->u.ptr->length == NEXT_HOP_LENGTH) && ipa_nonzero(ipp[1])) - { - bsprintf(buf, "%I %I", ipp[0], ipp[1]); - return; - } -#endif + if (len != 4) + WITHDRAW(BAD_LENGTH, "MULTI_EXIT_DISC", len); - bsprintf(buf, "%I", ipp[0]); + u32 val = get_u32(data); + bgp_set_attr_u32(to, s->pool, BA_MULTI_EXIT_DISC, flags, val); } -static int -bgp_check_aggregator(struct bgp_proto *p, byte *a UNUSED, int len) + +static void +bgp_export_local_pref(struct bgp_export_state *s, eattr *a) { - int exp_len = p->as4_session ? 8 : 6; - - return (len == exp_len) ? 0 : WITHDRAW; + if (!s->proto->is_interior && !s->proto->cf->allow_local_pref) + UNSET(a); } static void -bgp_format_aggregator(eattr *a, byte *buf, int buflen UNUSED) +bgp_decode_local_pref(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to) { - struct adata *ad = a->u.ptr; - byte *data = ad->data; - u32 as; + if (!s->proto->is_interior && !s->proto->cf->allow_local_pref) + DISCARD(BAD_EBGP, "LOCAL_PREF"); - as = get_u32(data); - data += 4; + if (len != 4) + WITHDRAW(BAD_LENGTH, "LOCAL_PREF", len); - bsprintf(buf, "%d.%d.%d.%d AS%u", data[0], data[1], data[2], data[3], as); + u32 val = get_u32(data); + bgp_set_attr_u32(to, s->pool, BA_LOCAL_PREF, flags, val); } -static int -bgp_check_community(struct bgp_proto *p UNUSED, byte *a UNUSED, int len) + +static void +bgp_decode_atomic_aggr(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data UNUSED, uint len, ea_list **to) { - return ((len % 4) == 0) ? 0 : WITHDRAW; + if (len != 0) + DISCARD(BAD_LENGTH, "ATOMIC_AGGR", len); + + bgp_set_attr_data(to, s->pool, BA_ATOMIC_AGGR, flags, NULL, 0); } static int -bgp_check_cluster_list(struct bgp_proto *p UNUSED, byte *a UNUSED, int len) +bgp_encode_aggregator(struct bgp_write_state *s, eattr *a, byte *buf, uint size) { - return ((len % 4) == 0) ? 0 : 5; + byte *data = a->u.ptr->data; + uint len = a->u.ptr->length; + + if (!s->as4_session) + { + /* Prepare 16-bit AGGREGATOR (from 32-bit one) in a temporary buffer */ + byte *src = data; + data = alloca(6); + len = aggregator_32to16(data, src); + } + + return bgp_put_attr(buf, size, BA_AGGREGATOR, a->flags, data, len); } static void -bgp_format_cluster_list(eattr *a, byte *buf, int buflen) +bgp_decode_aggregator(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to) { - /* Truncates cluster lists larger than buflen, probably not a problem */ - int_set_format(a->u.ptr, 0, -1, buf, buflen); + if (len != (s->as4_session ? 8 : 6)) + DISCARD(BAD_LENGTH, "AGGREGATOR", len); + + if (!s->as4_session) + { + /* Prepare 32-bit AGGREGATOR (from 16-bit one) in a temporary buffer */ + byte *src = data; + data = alloca(8); + len = aggregator_16to32(data, src); + } + + bgp_set_attr_data(to, s->pool, BA_AGGREGATOR, flags, data, len); } -static int -bgp_check_reach_nlri(struct bgp_proto *p UNUSED, byte *a UNUSED, int len UNUSED) +static void +bgp_format_aggregator(eattr *a, byte *buf, uint size UNUSED) { -#ifdef IPV6 - p->mp_reach_start = a; - p->mp_reach_len = len; -#endif - return IGNORE; + byte *data = a->u.ptr->data; + + bsprintf(buf, "%I4 AS%u", get_ip4(data+4), get_u32(data+0)); } -static int -bgp_check_unreach_nlri(struct bgp_proto *p UNUSED, byte *a UNUSED, int len UNUSED) + +static void +bgp_export_community(struct bgp_export_state *s, eattr *a) { -#ifdef IPV6 - p->mp_unreach_start = a; - p->mp_unreach_len = len; -#endif - return IGNORE; + if (a->u.ptr->length == 0) + UNSET(a); + + a->u.ptr = int_set_sort(s->pool, a->u.ptr); } -static int -bgp_check_ext_community(struct bgp_proto *p UNUSED, byte *a UNUSED, int len) +static void +bgp_decode_community(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to) { - return ((len % 8) == 0) ? 0 : WITHDRAW; + if (!len || (len % 4)) + WITHDRAW(BAD_LENGTH, "COMMUNITY", len); + + struct adata *ad = lp_alloc_adata(s->pool, len); + get_u32s(data, (u32 *) ad->data, len / 4); + bgp_set_attr_ptr(to, s->pool, BA_COMMUNITY, flags, ad); } -static int -bgp_check_large_community(struct bgp_proto *p UNUSED, byte *a UNUSED, int len) -{ - return ((len % 12) == 0) ? 0 : WITHDRAW; -} - - -static struct attr_desc bgp_attr_table[] = { - { NULL, -1, 0, 0, 0, /* Undefined */ - NULL, NULL }, - { "origin", 1, BAF_TRANSITIVE, EAF_TYPE_INT, 1, /* BA_ORIGIN */ - bgp_check_origin, bgp_format_origin }, - { "as_path", -1, BAF_TRANSITIVE, EAF_TYPE_AS_PATH, 1, /* BA_AS_PATH */ - NULL, NULL }, /* is checked by validate_as_path() as a special case */ - { "next_hop", 4, BAF_TRANSITIVE, EAF_TYPE_IP_ADDRESS, 1, /* BA_NEXT_HOP */ - bgp_check_next_hop, bgp_format_next_hop }, - { "med", 4, BAF_OPTIONAL, EAF_TYPE_INT, 1, /* BA_MULTI_EXIT_DISC */ - NULL, NULL }, - { "local_pref", 4, BAF_TRANSITIVE, EAF_TYPE_INT, 1, /* BA_LOCAL_PREF */ - NULL, NULL }, - { "atomic_aggr", 0, BAF_TRANSITIVE, EAF_TYPE_OPAQUE, 1, /* BA_ATOMIC_AGGR */ - NULL, NULL }, - { "aggregator", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_OPAQUE, 1, /* BA_AGGREGATOR */ - bgp_check_aggregator, bgp_format_aggregator }, - { "community", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_INT_SET, 1, /* BA_COMMUNITY */ - bgp_check_community, NULL }, - { "originator_id", 4, BAF_OPTIONAL, EAF_TYPE_ROUTER_ID, 0, /* BA_ORIGINATOR_ID */ - NULL, NULL }, - { "cluster_list", -1, BAF_OPTIONAL, EAF_TYPE_INT_SET, 0, /* BA_CLUSTER_LIST */ - bgp_check_cluster_list, bgp_format_cluster_list }, - { .name = NULL }, /* BA_DPA */ - { .name = NULL }, /* BA_ADVERTISER */ - { .name = NULL }, /* BA_RCID_PATH */ - { "mp_reach_nlri", -1, BAF_OPTIONAL, EAF_TYPE_OPAQUE, 1, /* BA_MP_REACH_NLRI */ - bgp_check_reach_nlri, NULL }, - { "mp_unreach_nlri", -1, BAF_OPTIONAL, EAF_TYPE_OPAQUE, 1, /* BA_MP_UNREACH_NLRI */ - bgp_check_unreach_nlri, NULL }, - { "ext_community", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_EC_SET, 1, /* BA_EXT_COMMUNITY */ - bgp_check_ext_community, NULL }, - { "as4_path", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_OPAQUE, 1, /* BA_AS4_PATH */ - NULL, NULL }, - { "as4_aggregator", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_OPAQUE, 1, /* BA_AS4_PATH */ - NULL, NULL }, - [BA_LARGE_COMMUNITY] = - { "large_community", -1, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_LC_SET, 1, - bgp_check_large_community, NULL } -}; -/* BA_AS4_PATH is type EAF_TYPE_OPAQUE and not type EAF_TYPE_AS_PATH. - * It does not matter as this attribute does not appear on routes in the routing table. - */ +static void +bgp_export_originator_id(struct bgp_export_state *s, eattr *a) +{ + if (!s->proto->is_internal) + UNSET(a); +} + +static void +bgp_decode_originator_id(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to) +{ + if (!s->proto->is_internal) + DISCARD(BAD_EBGP, "ORIGINATOR_ID"); -#define ATTR_KNOWN(code) ((code) < ARRAY_SIZE(bgp_attr_table) && bgp_attr_table[code].name) + if (len != 4) + WITHDRAW(BAD_LENGTH, "ORIGINATOR_ID", len); -static inline struct adata * -bgp_alloc_adata(struct linpool *pool, unsigned len) + u32 val = get_u32(data); + bgp_set_attr_u32(to, s->pool, BA_ORIGINATOR_ID, flags, val); +} + + +static void +bgp_export_cluster_list(struct bgp_export_state *s UNUSED, eattr *a) { - struct adata *ad = lp_alloc(pool, sizeof(struct adata) + len); - ad->length = len; - return ad; + if (!s->proto->is_internal) + UNSET(a); + + if (a->u.ptr->length == 0) + UNSET(a); } static void -bgp_set_attr(eattr *e, unsigned attr, uintptr_t val) +bgp_decode_cluster_list(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to) { - ASSERT(ATTR_KNOWN(attr)); - e->id = EA_CODE(EAP_BGP, attr); - e->type = bgp_attr_table[attr].type; - e->flags = bgp_attr_table[attr].expected_flags; - if (e->type & EAF_EMBEDDED) - e->u.data = val; - else - e->u.ptr = (struct adata *) val; + if (!s->proto->is_internal) + DISCARD(BAD_EBGP, "CLUSTER_LIST"); + + if (!len || (len % 4)) + WITHDRAW(BAD_LENGTH, "CLUSTER_LIST", len); + + struct adata *ad = lp_alloc_adata(s->pool, len); + get_u32s(data, (u32 *) ad->data, len / 4); + bgp_set_attr_ptr(to, s->pool, BA_CLUSTER_LIST, flags, ad); } -static byte * -bgp_set_attr_wa(eattr *e, struct linpool *pool, unsigned attr, unsigned len) +static void +bgp_format_cluster_list(eattr *a, byte *buf, uint size) { - struct adata *ad = bgp_alloc_adata(pool, len); - bgp_set_attr(e, attr, (uintptr_t) ad); - return ad->data; + /* Truncates cluster lists larger than buflen, probably not a problem */ + int_set_format(a->u.ptr, 0, -1, buf, size); } -void -bgp_attach_attr(ea_list **to, struct linpool *pool, unsigned attr, uintptr_t val) + +static inline u32 +get_af3(byte *buf) { - ea_list *a = lp_alloc(pool, sizeof(ea_list) + sizeof(eattr)); - a->next = *to; - *to = a; - a->flags = EALF_SORTED; - a->count = 1; - bgp_set_attr(a->attrs, attr, val); + return (get_u16(buf) << 16) | buf[2]; } -byte * -bgp_attach_attr_wa(ea_list **to, struct linpool *pool, unsigned attr, unsigned len) +static void +bgp_decode_mp_reach_nlri(struct bgp_parse_state *s, uint code UNUSED, uint flags UNUSED, byte *data, uint len, ea_list **to UNUSED) { - struct adata *ad = bgp_alloc_adata(pool, len); - bgp_attach_attr(to, pool, attr, (uintptr_t) ad); - return ad->data; + /* + * 2 B MP_REACH_NLRI data - Address Family Identifier + * 1 B MP_REACH_NLRI data - Subsequent Address Family Identifier + * 1 B MP_REACH_NLRI data - Length of Next Hop Network Address + * var MP_REACH_NLRI data - Network Address of Next Hop + * 1 B MP_REACH_NLRI data - Reserved (zero) + * var MP_REACH_NLRI data - Network Layer Reachability Information + */ + + if ((len < 5) || (len < (5 + (uint) data[3]))) + bgp_parse_error(s, 9); + + s->mp_reach_af = get_af3(data); + s->mp_next_hop_len = data[3]; + s->mp_next_hop_data = data + 4; + s->mp_reach_len = len - 5 - s->mp_next_hop_len; + s->mp_reach_nlri = data + 5 + s->mp_next_hop_len; } -static int -bgp_encode_attr_hdr(byte *dst, uint flags, unsigned code, int len) + +static void +bgp_decode_mp_unreach_nlri(struct bgp_parse_state *s, uint code UNUSED, uint flags UNUSED, byte *data, uint len, ea_list **to UNUSED) { - int wlen; + /* + * 2 B MP_UNREACH_NLRI data - Address Family Identifier + * 1 B MP_UNREACH_NLRI data - Subsequent Address Family Identifier + * var MP_UNREACH_NLRI data - Network Layer Reachability Information + */ - DBG("\tAttribute %02x (%d bytes, flags %02x)\n", code, len, flags); + if (len < 3) + bgp_parse_error(s, 9); + + s->mp_unreach_af = get_af3(data); + s->mp_unreach_len = len - 3; + s->mp_unreach_nlri = data + 3; +} - if (len < 256) - { - *dst++ = flags; - *dst++ = code; - *dst++ = len; - wlen = 3; - } - else - { - *dst++ = flags | BAF_EXT_LEN; - *dst++ = code; - put_u16(dst, len); - wlen = 4; - } - return wlen; +static void +bgp_export_ext_community(struct bgp_export_state *s, eattr *a) +{ + if (a->u.ptr->length == 0) + UNSET(a); + + a->u.ptr = ec_set_sort(s->pool, a->u.ptr); } static void -aggregator_convert_to_old(struct adata *aggr, byte *dst, int *new_used) +bgp_decode_ext_community(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to) { - byte *src = aggr->data; - *new_used = 0; + if (!len || (len % 8)) + WITHDRAW(BAD_LENGTH, "EXT_COMMUNITY", len); - u32 as = get_u32(src); - if (as > 0xFFFF) - { - as = AS_TRANS; - *new_used = 1; - } - put_u16(dst, as); + struct adata *ad = lp_alloc_adata(s->pool, len); + get_u32s(data, (u32 *) ad->data, len / 4); + bgp_set_attr_ptr(to, s->pool, BA_EXT_COMMUNITY, flags, ad); +} - /* Copy IPv4 address */ - memcpy(dst + 2, src + 4, 4); + +static void +bgp_decode_as4_aggregator(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to) +{ + if (s->as4_session) + DISCARD(NEW_BGP, "AS4_AGGREGATOR"); + + if (len != 8) + DISCARD(BAD_LENGTH, "AS4_AGGREGATOR", len); + + bgp_set_attr_data(to, s->pool, BA_AS4_AGGREGATOR, flags, data, len); } static void -aggregator_convert_to_new(struct adata *aggr, byte *dst) +bgp_decode_as4_path(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to) { - byte *src = aggr->data; + char err[128]; - u32 as = get_u16(src); - put_u32(dst, as); + if (s->as4_session) + DISCARD(NEW_BGP, "AS4_PATH"); - /* Copy IPv4 address */ - memcpy(dst + 4, src + 2, 4); + if (len < 6) + DISCARD(BAD_LENGTH, "AS4_PATH", len); + + if (!as_path_valid(data, len, 4, 1, err, sizeof(err))) + DISCARD("Malformed AS4_PATH attribute - %s", err); + + struct adata *a = lp_alloc_adata(s->pool, len); + memcpy(a->data, data, len); + + /* AS_CONFED* segments are invalid in AS4_PATH; RFC 6793 6 */ + if (as_path_contains_confed(a)) + { + REPORT("Discarding AS_CONFED* segment from AS4_PATH attribute"); + a = as_path_strip_confed(s->pool, a); + } + + bgp_set_attr_ptr(to, s->pool, BA_AS4_PATH, flags, a); +} + +static void +bgp_export_large_community(struct bgp_export_state *s, eattr *a) +{ + if (a->u.ptr->length == 0) + UNSET(a); + + a->u.ptr = lc_set_sort(s->pool, a->u.ptr); +} + +static void +bgp_decode_large_community(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to) +{ + if (!len || (len % 12)) + WITHDRAW(BAD_LENGTH, "LARGE_COMMUNITY", len); + + struct adata *ad = lp_alloc_adata(s->pool, len); + get_u32s(data, (u32 *) ad->data, len / 4); + bgp_set_attr_ptr(to, s->pool, BA_LARGE_COMMUNITY, flags, ad); +} + +static void +bgp_export_mpls_label_stack(struct bgp_export_state *s, eattr *a) +{ + net_addr *n = s->route->net->n.addr; + u32 *labels = (u32 *) a->u.ptr->data; + uint lnum = a->u.ptr->length / 4; + + /* Perhaps we should just ignore it? */ + if (!s->mpls) + WITHDRAW("Unexpected MPLS stack"); + + /* Empty MPLS stack is not allowed */ + if (!lnum) + WITHDRAW("Malformed MPLS stack - empty"); + + /* This is ugly, but we must ensure that labels fit into NLRI field */ + if ((24*lnum + (net_is_vpn(n) ? 64 : 0) + net_pxlen(n)) > 255) + WITHDRAW("Malformed MPLS stack - too many labels (%u)", lnum); + + for (uint i = 0; i < lnum; i++) + { + if (labels[i] > 0xfffff) + WITHDRAW("Malformed MPLS stack - invalid label (%u)", labels[i]); + + /* TODO: Check for special-purpose label values? */ + } } static int -bgp_get_attr_len(eattr *a) +bgp_encode_mpls_label_stack(struct bgp_write_state *s, eattr *a, byte *buf UNUSED, uint size UNUSED) { - int len; - if (ATTR_KNOWN(EA_ID(a->id))) + /* + * MPLS labels are encoded as a part of the NLRI in MP_REACH_NLRI attribute, + * so we store MPLS_LABEL_STACK and encode it later by AFI-specific hooks. + */ + + s->mpls_labels = a->u.ptr; + return 0; +} + +static void +bgp_decode_mpls_label_stack(struct bgp_parse_state *s, uint code UNUSED, uint flags UNUSED, byte *data UNUSED, uint len UNUSED, ea_list **to UNUSED) +{ + DISCARD("Discarding received attribute #0"); +} + +static void +bgp_format_mpls_label_stack(eattr *a, byte *buf, uint size) +{ + u32 *labels = (u32 *) a->u.ptr->data; + uint lnum = a->u.ptr->length / 4; + char *pos = buf; + + for (uint i = 0; i < lnum; i++) + { + if (size < 20) { - int code = EA_ID(a->id); - struct attr_desc *desc = &bgp_attr_table[code]; - len = desc->expected_length; - if (len < 0) - { - ASSERT(!(a->type & EAF_EMBEDDED)); - len = a->u.ptr->length; - } + bsprintf(pos, "..."); + return; } + + uint l = bsprintf(pos, "%d/", labels[i]); + ADVANCE(pos, size, l); + } + + /* Clear last slash or terminate empty string */ + pos[lnum ? -1 : 0] = 0; +} + +static inline void +bgp_decode_unknown(struct bgp_parse_state *s, uint code, uint flags, byte *data, uint len, ea_list **to) +{ + bgp_set_attr_data(to, s->pool, code, flags, data, len); +} + + +/* + * Attribute table + */ + +static const struct bgp_attr_desc bgp_attr_table[] = { + [BA_ORIGIN] = { + .name = "origin", + .type = EAF_TYPE_INT, + .flags = BAF_TRANSITIVE, + .export = bgp_export_origin, + .encode = bgp_encode_u8, + .decode = bgp_decode_origin, + .format = bgp_format_origin, + }, + [BA_AS_PATH] = { + .name = "as_path", + .type = EAF_TYPE_AS_PATH, + .flags = BAF_TRANSITIVE, + .encode = bgp_encode_as_path, + .decode = bgp_decode_as_path, + }, + [BA_NEXT_HOP] = { + .name = "next_hop", + .type = EAF_TYPE_IP_ADDRESS, + .flags = BAF_TRANSITIVE, + .encode = bgp_encode_next_hop, + .decode = bgp_decode_next_hop, + .format = bgp_format_next_hop, + }, + [BA_MULTI_EXIT_DISC] = { + .name = "med", + .type = EAF_TYPE_INT, + .flags = BAF_OPTIONAL, + .encode = bgp_encode_u32, + .decode = bgp_decode_med, + }, + [BA_LOCAL_PREF] = { + .name = "local_pref", + .type = EAF_TYPE_INT, + .flags = BAF_TRANSITIVE, + .export = bgp_export_local_pref, + .encode = bgp_encode_u32, + .decode = bgp_decode_local_pref, + }, + [BA_ATOMIC_AGGR] = { + .name = "atomic_aggr", + .type = EAF_TYPE_OPAQUE, + .flags = BAF_TRANSITIVE, + .encode = bgp_encode_raw, + .decode = bgp_decode_atomic_aggr, + }, + [BA_AGGREGATOR] = { + .name = "aggregator", + .type = EAF_TYPE_OPAQUE, + .flags = BAF_OPTIONAL | BAF_TRANSITIVE, + .encode = bgp_encode_aggregator, + .decode = bgp_decode_aggregator, + .format = bgp_format_aggregator, + }, + [BA_COMMUNITY] = { + .name = "community", + .type = EAF_TYPE_INT_SET, + .flags = BAF_OPTIONAL | BAF_TRANSITIVE, + .export = bgp_export_community, + .encode = bgp_encode_u32s, + .decode = bgp_decode_community, + }, + [BA_ORIGINATOR_ID] = { + .name = "originator_id", + .type = EAF_TYPE_ROUTER_ID, + .flags = BAF_OPTIONAL, + .export = bgp_export_originator_id, + .encode = bgp_encode_u32, + .decode = bgp_decode_originator_id, + }, + [BA_CLUSTER_LIST] = { + .name = "cluster_list", + .type = EAF_TYPE_INT_SET, + .flags = BAF_OPTIONAL, + .export = bgp_export_cluster_list, + .encode = bgp_encode_u32s, + .decode = bgp_decode_cluster_list, + .format = bgp_format_cluster_list, + }, + [BA_MP_REACH_NLRI] = { + .name = "mp_reach_nlri", + .type = EAF_TYPE_OPAQUE, + .flags = BAF_OPTIONAL, + .decode = bgp_decode_mp_reach_nlri, + }, + [BA_MP_UNREACH_NLRI] = { + .name = "mp_unreach_nlri", + .type = EAF_TYPE_OPAQUE, + .flags = BAF_OPTIONAL, + .decode = bgp_decode_mp_unreach_nlri, + }, + [BA_EXT_COMMUNITY] = { + .name = "ext_community", + .type = EAF_TYPE_EC_SET, + .flags = BAF_OPTIONAL | BAF_TRANSITIVE, + .export = bgp_export_ext_community, + .encode = bgp_encode_u32s, + .decode = bgp_decode_ext_community, + }, + [BA_AS4_PATH] = { + .name = "as4_path", + .type = EAF_TYPE_AS_PATH, + .flags = BAF_OPTIONAL | BAF_TRANSITIVE, + .encode = bgp_encode_raw, + .decode = bgp_decode_as4_path, + }, + [BA_AS4_AGGREGATOR] = { + .name = "as4_aggregator", + .type = EAF_TYPE_OPAQUE, + .flags = BAF_OPTIONAL | BAF_TRANSITIVE, + .encode = bgp_encode_raw, + .decode = bgp_decode_as4_aggregator, + .format = bgp_format_aggregator, + }, + [BA_LARGE_COMMUNITY] = { + .name = "large_community", + .type = EAF_TYPE_LC_SET, + .flags = BAF_OPTIONAL | BAF_TRANSITIVE, + .export = bgp_export_large_community, + .encode = bgp_encode_u32s, + .decode = bgp_decode_large_community, + }, + [BA_MPLS_LABEL_STACK] = { + .name = "mpls_label_stack", + .type = EAF_TYPE_INT_SET, + .export = bgp_export_mpls_label_stack, + .encode = bgp_encode_mpls_label_stack, + .decode = bgp_decode_mpls_label_stack, + .format = bgp_format_mpls_label_stack, + }, +}; + +static inline int +bgp_attr_known(uint code) +{ + return (code < ARRAY_SIZE(bgp_attr_table)) && bgp_attr_table[code].name; +} + + +/* + * Attribute export + */ + +static inline void +bgp_export_attr(struct bgp_export_state *s, eattr *a, ea_list *to) +{ + if (EA_PROTO(a->id) != EAP_BGP) + return; + + uint code = EA_ID(a->id); + + if (bgp_attr_known(code)) + { + const struct bgp_attr_desc *desc = &bgp_attr_table[code]; + + /* The flags might have been zero if the attr was added by filters */ + a->flags = (a->flags & BAF_PARTIAL) | desc->flags; + + /* Set partial bit if new opt-trans attribute is attached to non-local route */ + if ((s->src != NULL) && (a->type & EAF_ORIGINATED) && + (a->flags & BAF_OPTIONAL) && (a->flags & BAF_TRANSITIVE)) + a->flags |= BAF_PARTIAL; + + /* Call specific hook */ + CALL(desc->export, s, a); + + /* Attribute might become undefined in hook */ + if ((a->type & EAF_TYPE_MASK) == EAF_TYPE_UNDEF) + return; + } else - { - ASSERT((a->type & EAF_TYPE_MASK) == EAF_TYPE_OPAQUE); - len = a->u.ptr->length; - } - - return len; + { + /* Don't re-export unknown non-transitive attributes */ + if (!(a->flags & BAF_TRANSITIVE)) + return; + + a->flags |= BAF_PARTIAL; + } + + /* Append updated attribute */ + to->attrs[to->count++] = *a; } -#define ADVANCE(w, r, l) do { r -= l; w += l; } while (0) +/** + * bgp_export_attrs - export BGP attributes + * @s: BGP export state + * @attrs: a list of extended attributes + * + * The bgp_export_attrs() function takes a list of attributes and merges it to + * one newly allocated and sorted segment. Attributes are validated and + * normalized by type-specific export hooks and attribute flags are updated. + * Some attributes may be eliminated (e.g. unknown non-tranitive attributes, or + * empty community sets). + * + * Result: one sorted attribute list segment, or NULL if attributes are unsuitable. + */ +static inline ea_list * +bgp_export_attrs(struct bgp_export_state *s, ea_list *attrs) +{ + /* Merge the attribute list */ + ea_list *new = lp_alloc(s->pool, ea_scan(attrs)); + ea_merge(attrs, new); + ea_sort(new); + + uint i, count; + count = new->count; + new->count = 0; + + /* Export each attribute */ + for (i = 0; i < count; i++) + bgp_export_attr(s, &new->attrs[i], new); + + if (s->err_withdraw) + return NULL; + + return new; +} + + +/* + * Attribute encoding + */ + +static inline int +bgp_encode_attr(struct bgp_write_state *s, eattr *a, byte *buf, uint size) +{ + ASSERT(EA_PROTO(a->id) == EAP_BGP); + + uint code = EA_ID(a->id); + + if (bgp_attr_known(code)) + return bgp_attr_table[code].encode(s, a, buf, size); + else + return bgp_encode_raw(s, a, buf, size); +} /** * bgp_encode_attrs - encode BGP attributes - * @p: BGP instance - * @w: buffer + * @s: BGP write state * @attrs: a list of extended attributes - * @remains: remaining space in the buffer + * @buf: buffer + * @end: buffer end * * The bgp_encode_attrs() function takes a list of extended attributes * and converts it to its BGP representation (a part of an Update message). * * Result: Length of the attribute block generated or -1 if not enough space. */ -uint -bgp_encode_attrs(struct bgp_proto *p, byte *w, ea_list *attrs, int remains) +int +bgp_encode_attrs(struct bgp_write_state *s, ea_list *attrs, byte *buf, byte *end) { - uint i, code, type, flags; - byte *start = w; - int len, rv; + byte *pos = buf; + int i, len; - for(i=0; i<attrs->count; i++) - { - eattr *a = &attrs->attrs[i]; - ASSERT(EA_PROTO(a->id) == EAP_BGP); - code = EA_ID(a->id); - -#ifdef IPV6 - /* When talking multiprotocol BGP, the NEXT_HOP attributes are used only temporarily. */ - if (code == BA_NEXT_HOP) - continue; -#endif - - /* When AS4-aware BGP speaker is talking to non-AS4-aware BGP speaker, - * we have to convert our 4B AS_PATH to 2B AS_PATH and send our AS_PATH - * as optional AS4_PATH attribute. - */ - if ((code == BA_AS_PATH) && (! p->as4_session)) - { - len = a->u.ptr->length; - - if (remains < (len + 4)) - goto err_no_buffer; - - /* Using temporary buffer because don't know a length of created attr - * and therefore a length of a header. Perhaps i should better always - * use BAF_EXT_LEN. */ - - byte buf[len]; - int new_used; - int nl = as_path_convert_to_old(a->u.ptr, buf, &new_used); - - DBG("BGP: Encoding old AS_PATH\n"); - rv = bgp_encode_attr_hdr(w, BAF_TRANSITIVE, BA_AS_PATH, nl); - ADVANCE(w, remains, rv); - memcpy(w, buf, nl); - ADVANCE(w, remains, nl); - - if (! new_used) - continue; - - if (remains < (len + 4)) - goto err_no_buffer; - - /* We should discard AS_CONFED_SEQUENCE or AS_CONFED_SET path segments - * here but we don't support confederations and such paths we already - * discarded in bgp_check_as_path(). - */ - - DBG("BGP: Encoding AS4_PATH\n"); - rv = bgp_encode_attr_hdr(w, BAF_OPTIONAL | BAF_TRANSITIVE, BA_AS4_PATH, len); - ADVANCE(w, remains, rv); - memcpy(w, a->u.ptr->data, len); - ADVANCE(w, remains, len); - - continue; - } - - /* The same issue with AGGREGATOR attribute */ - if ((code == BA_AGGREGATOR) && (! p->as4_session)) - { - int new_used; - - len = 6; - if (remains < (len + 3)) - goto err_no_buffer; - - rv = bgp_encode_attr_hdr(w, BAF_OPTIONAL | BAF_TRANSITIVE, BA_AGGREGATOR, len); - ADVANCE(w, remains, rv); - aggregator_convert_to_old(a->u.ptr, w, &new_used); - ADVANCE(w, remains, len); - - if (! new_used) - continue; - - len = 8; - if (remains < (len + 3)) - goto err_no_buffer; - - rv = bgp_encode_attr_hdr(w, BAF_OPTIONAL | BAF_TRANSITIVE, BA_AS4_AGGREGATOR, len); - ADVANCE(w, remains, rv); - memcpy(w, a->u.ptr->data, len); - ADVANCE(w, remains, len); - - continue; - } - - /* Standard path continues here ... */ - - type = a->type & EAF_TYPE_MASK; - flags = a->flags & (BAF_OPTIONAL | BAF_TRANSITIVE | BAF_PARTIAL); - len = bgp_get_attr_len(a); - - /* Skip empty sets */ - if (((type == EAF_TYPE_INT_SET) || (type == EAF_TYPE_EC_SET) || (type == EAF_TYPE_LC_SET)) && (len == 0)) - continue; - - if (remains < len + 4) - goto err_no_buffer; - - rv = bgp_encode_attr_hdr(w, flags, code, len); - ADVANCE(w, remains, rv); - - switch (type) - { - case EAF_TYPE_INT: - case EAF_TYPE_ROUTER_ID: - if (len == 4) - put_u32(w, a->u.data); - else - *w = a->u.data; - break; - case EAF_TYPE_IP_ADDRESS: - { - ip_addr ip = *(ip_addr *)a->u.ptr->data; - ipa_hton(ip); - memcpy(w, &ip, len); - break; - } - case EAF_TYPE_INT_SET: - case EAF_TYPE_LC_SET: - case EAF_TYPE_EC_SET: - { - u32 *z = int_set_get_data(a->u.ptr); - int i; - for(i=0; i<len; i+=4) - put_u32(w+i, *z++); - break; - } - case EAF_TYPE_OPAQUE: - case EAF_TYPE_AS_PATH: - memcpy(w, a->u.ptr->data, len); - break; - default: - bug("bgp_encode_attrs: unknown attribute type %02x", a->type); - } - ADVANCE(w, remains, len); - } - return w - start; + for (i = 0; i < attrs->count; i++) + { + len = bgp_encode_attr(s, &attrs->attrs[i], pos, end - pos); - err_no_buffer: - return -1; + if (len < 0) + return -1; + + pos += len; + } + + return pos - buf; } + /* -static void -bgp_init_prefix(struct fib_node *N) + * Attribute decoding + */ + +static void bgp_process_as4_attrs(ea_list **attrs, struct linpool *pool); + +static inline int +bgp_as_path_loopy(struct bgp_proto *p, ea_list *attrs, u32 asn) { - struct bgp_prefix *p = (struct bgp_prefix *) N; - p->bucket_node.next = NULL; + eattr *e = bgp_find_attr(attrs, BA_AS_PATH); + int num = p->cf->allow_local_as + 1; + return (e && (num > 0) && as_path_contains(e->u.ptr, asn, num)); } -*/ -static int -bgp_compare_u32(const u32 *x, const u32 *y) +static inline int +bgp_originator_id_loopy(struct bgp_proto *p, ea_list *attrs) { - return (*x < *y) ? -1 : (*x > *y) ? 1 : 0; + eattr *e = bgp_find_attr(attrs, BA_ORIGINATOR_ID); + return (e && (e->u.data == p->local_id)); } -static inline void -bgp_normalize_int_set(u32 *dest, u32 *src, unsigned cnt) +static inline int +bgp_cluster_list_loopy(struct bgp_proto *p, ea_list *attrs) { - memcpy(dest, src, sizeof(u32) * cnt); - qsort(dest, cnt, sizeof(u32), (int(*)(const void *, const void *)) bgp_compare_u32); + eattr *e = bgp_find_attr(attrs, BA_CLUSTER_LIST); + return (e && int_set_contains(e->u.ptr, p->rr_cluster_id)); } -static int -bgp_compare_ec(const u32 *xp, const u32 *yp) +static inline void +bgp_decode_attr(struct bgp_parse_state *s, uint code, uint flags, byte *data, uint len, ea_list **to) { - u64 x = ec_get(xp, 0); - u64 y = ec_get(yp, 0); - return (x < y) ? -1 : (x > y) ? 1 : 0; + /* Handle duplicate attributes; RFC 7606 3 (g) */ + if (BIT32_TEST(s->attrs_seen, code)) + { + if ((code == BA_MP_REACH_NLRI) || (code == BA_MP_UNREACH_NLRI)) + bgp_parse_error(s, 1); + else + DISCARD("Discarding duplicate attribute (code %u)", code); + } + BIT32_SET(s->attrs_seen, code); + + if (bgp_attr_known(code)) + { + const struct bgp_attr_desc *desc = &bgp_attr_table[code]; + + /* Handle conflicting flags; RFC 7606 3 (c) */ + if ((flags ^ desc->flags) & (BAF_OPTIONAL | BAF_TRANSITIVE)) + WITHDRAW("Malformed %s attribute - conflicting flags (%02x)", desc->name, flags); + + desc->decode(s, code, flags, data, len, to); + } + else /* Unknown attribute */ + { + if (!(flags & BAF_OPTIONAL)) + WITHDRAW("Unknown attribute (code %u) - conflicting flags (%02x)", code, flags); + + bgp_decode_unknown(s, code, flags, data, len, to); + } } -static inline void -bgp_normalize_ec_set(struct adata *ad, u32 *src, int internal) +/** + * bgp_decode_attrs - check and decode BGP attributes + * @s: BGP parse state + * @data: start of attribute block + * @len: length of attribute block + * + * This function takes a BGP attribute block (a part of an Update message), checks + * its consistency and converts it to a list of BIRD route attributes represented + * by an (uncached) &rta. + */ +ea_list * +bgp_decode_attrs(struct bgp_parse_state *s, byte *data, uint len) { - u32 *dst = int_set_get_data(ad); + struct bgp_proto *p = s->proto; + ea_list *attrs = NULL; + uint code, flags, alen; + byte *pos = data; + + /* Parse the attributes */ + while (len) + { + alen = 0; - /* Remove non-transitive communities (EC_TBIT active) on external sessions */ - if (! internal) + /* Read attribute type */ + if (len < 2) + goto framing_error; + flags = pos[0]; + code = pos[1]; + ADVANCE(pos, len, 2); + + /* Read attribute length */ + if (flags & BAF_EXT_LEN) { - int len = int_set_get_size(ad); - u32 *t = dst; - int i; - - for (i=0; i < len; i += 2) - { - if (src[i] & EC_TBIT) - continue; - - *t++ = src[i]; - *t++ = src[i+1]; - } - - ad->length = (t - dst) * 4; + if (len < 2) + goto framing_error; + alen = get_u16(pos); + ADVANCE(pos, len, 2); + } + else + { + if (len < 1) + goto framing_error; + alen = *pos; + ADVANCE(pos, len, 1); } - else - memcpy(dst, src, ad->length); - qsort(dst, ad->length / 8, 8, (int(*)(const void *, const void *)) bgp_compare_ec); -} + if (alen > len) + goto framing_error; -static int -bgp_compare_lc(const u32 *x, const u32 *y) -{ - if (x[0] != y[0]) - return (x[0] > y[0]) ? 1 : -1; - if (x[1] != y[1]) - return (x[1] > y[1]) ? 1 : -1; - if (x[2] != y[2]) - return (x[2] > y[2]) ? 1 : -1; - return 0; + DBG("Attr %02x %02x %u\n", code, flags, alen); + + bgp_decode_attr(s, code, flags, pos, alen, &attrs); + ADVANCE(pos, len, alen); + } + + if (s->err_withdraw) + goto withdraw; + + /* If there is no reachability NLRI, we are finished */ + if (!s->ip_reach_len && !s->mp_reach_len) + return NULL; + + + /* Handle missing mandatory attributes; RFC 7606 3 (d) */ + if (!BIT32_TEST(s->attrs_seen, BA_ORIGIN)) + { REPORT(NO_MANDATORY, "ORIGIN"); goto withdraw; } + + if (!BIT32_TEST(s->attrs_seen, BA_AS_PATH)) + { REPORT(NO_MANDATORY, "AS_PATH"); goto withdraw; } + + /* When receiving attributes from non-AS4-aware BGP speaker, we have to + reconstruct AS_PATH and AGGREGATOR attributes; RFC 6793 4.2.3 */ + if (!p->as4_session) + bgp_process_as4_attrs(&attrs, s->pool); + + /* Reject routes with our ASN in AS_PATH attribute */ + if (bgp_as_path_loopy(p, attrs, p->local_as)) + goto withdraw; + + /* Reject routes with our Confederation ID in AS_PATH attribute; RFC 5065 4.0 */ + if ((p->public_as != p->local_as) && bgp_as_path_loopy(p, attrs, p->public_as)) + goto withdraw; + + /* Reject routes with our Router ID in ORIGINATOR_ID attribute; RFC 4456 8 */ + if (p->is_internal && bgp_originator_id_loopy(p, attrs)) + goto withdraw; + + /* Reject routes with our Cluster ID in CLUSTER_LIST attribute; RFC 4456 8 */ + if (p->rr_client && bgp_cluster_list_loopy(p, attrs)) + goto withdraw; + + /* If there is no local preference, define one */ + if (!BIT32_TEST(s->attrs_seen, BA_LOCAL_PREF)) + bgp_set_attr_u32(&attrs, s->pool, BA_LOCAL_PREF, 0, p->cf->default_local_pref); + + return attrs; + + +framing_error: + /* RFC 7606 4 - handle attribute framing errors */ + REPORT("Malformed attribute list - framing error (%u/%u) at %d", + alen, len, (int) (pos - s->attrs)); + +withdraw: + /* RFC 7606 5.2 - handle missing NLRI during errors */ + if (!s->ip_reach_len && !s->mp_reach_len) + bgp_parse_error(s, 1); + + s->err_withdraw = 1; + return NULL; } -static inline void -bgp_normalize_lc_set(u32 *dest, u32 *src, unsigned cnt) + +/* + * Route bucket hash table + */ + +#define RBH_KEY(b) b->eattrs, b->hash +#define RBH_NEXT(b) b->next +#define RBH_EQ(a1,h1,a2,h2) h1 == h2 && ea_same(a1, a2) +#define RBH_FN(a,h) h + +#define RBH_REHASH bgp_rbh_rehash +#define RBH_PARAMS /8, *2, 2, 2, 8, 20 + + +HASH_DEFINE_REHASH_FN(RBH, struct bgp_bucket) + +void +bgp_init_bucket_table(struct bgp_channel *c) { - memcpy(dest, src, LCOMM_LENGTH * cnt); - qsort(dest, cnt, LCOMM_LENGTH, (int(*)(const void *, const void *)) bgp_compare_lc); -} + HASH_INIT(c->bucket_hash, c->pool, 8); -static void -bgp_rehash_buckets(struct bgp_proto *p) -{ - struct bgp_bucket **old = p->bucket_hash; - struct bgp_bucket **new; - unsigned oldn = p->hash_size; - unsigned i, e, mask; - struct bgp_bucket *b; - - p->hash_size = p->hash_limit; - DBG("BGP: Rehashing bucket table from %d to %d\n", oldn, p->hash_size); - p->hash_limit *= 4; - if (p->hash_limit >= 65536) - p->hash_limit = ~0; - new = p->bucket_hash = mb_allocz(p->p.pool, p->hash_size * sizeof(struct bgp_bucket *)); - mask = p->hash_size - 1; - for (i=0; i<oldn; i++) - while (b = old[i]) - { - old[i] = b->hash_next; - e = b->hash & mask; - b->hash_next = new[e]; - if (b->hash_next) - b->hash_next->hash_prev = b; - b->hash_prev = NULL; - new[e] = b; - } - mb_free(old); + init_list(&c->bucket_queue); + c->withdraw_bucket = NULL; } static struct bgp_bucket * -bgp_new_bucket(struct bgp_proto *p, ea_list *new, unsigned hash) +bgp_get_bucket(struct bgp_channel *c, ea_list *new) { - struct bgp_bucket *b; - unsigned ea_size = sizeof(ea_list) + new->count * sizeof(eattr); - unsigned ea_size_aligned = BIRD_ALIGN(ea_size, CPU_STRUCT_ALIGN); - unsigned size = sizeof(struct bgp_bucket) + ea_size_aligned; - unsigned i; + /* Hash and lookup */ + u32 hash = ea_hash(new); + struct bgp_bucket *b = HASH_FIND(c->bucket_hash, RBH, new, hash); + + if (b) + return b; + + uint ea_size = sizeof(ea_list) + new->count * sizeof(eattr); + uint ea_size_aligned = BIRD_ALIGN(ea_size, CPU_STRUCT_ALIGN); + uint size = sizeof(struct bgp_bucket) + ea_size_aligned; + uint i; byte *dest; - unsigned index = hash & (p->hash_size - 1); /* Gather total size of non-inline attributes */ - for (i=0; i<new->count; i++) - { - eattr *a = &new->attrs[i]; - if (!(a->type & EAF_EMBEDDED)) - size += BIRD_ALIGN(sizeof(struct adata) + a->u.ptr->length, CPU_STRUCT_ALIGN); - } + for (i = 0; i < new->count; i++) + { + eattr *a = &new->attrs[i]; - /* Create the bucket and hash it */ - b = mb_alloc(p->p.pool, size); - b->hash_next = p->bucket_hash[index]; - if (b->hash_next) - b->hash_next->hash_prev = b; - p->bucket_hash[index] = b; - b->hash_prev = NULL; - b->hash = hash; - add_tail(&p->bucket_queue, &b->send_node); + if (!(a->type & EAF_EMBEDDED)) + size += BIRD_ALIGN(sizeof(struct adata) + a->u.ptr->length, CPU_STRUCT_ALIGN); + } + + /* Create the bucket */ + b = mb_alloc(c->pool, size); init_list(&b->prefixes); + b->hash = hash; + + /* Copy list of extended attributes */ memcpy(b->eattrs, new, ea_size); - dest = ((byte *)b->eattrs) + ea_size_aligned; + dest = ((byte *) b->eattrs) + ea_size_aligned; /* Copy values of non-inline attributes */ - for (i=0; i<new->count; i++) + for (i = 0; i < new->count; i++) + { + eattr *a = &b->eattrs->attrs[i]; + + if (!(a->type & EAF_EMBEDDED)) { - eattr *a = &b->eattrs->attrs[i]; - if (!(a->type & EAF_EMBEDDED)) - { - struct adata *oa = a->u.ptr; - struct adata *na = (struct adata *) dest; - memcpy(na, oa, sizeof(struct adata) + oa->length); - a->u.ptr = na; - dest += BIRD_ALIGN(sizeof(struct adata) + na->length, CPU_STRUCT_ALIGN); - } + struct adata *oa = a->u.ptr; + struct adata *na = (struct adata *) dest; + memcpy(na, oa, sizeof(struct adata) + oa->length); + a->u.ptr = na; + dest += BIRD_ALIGN(sizeof(struct adata) + na->length, CPU_STRUCT_ALIGN); } + } - /* If needed, rehash */ - p->hash_count++; - if (p->hash_count > p->hash_limit) - bgp_rehash_buckets(p); + /* Insert the bucket to send queue and bucket hash */ + add_tail(&c->bucket_queue, &b->send_node); + HASH_INSERT2(c->bucket_hash, RBH, c->pool, b); return b; } static struct bgp_bucket * -bgp_get_bucket(struct bgp_proto *p, net *n, ea_list *attrs, int originate) +bgp_get_withdraw_bucket(struct bgp_channel *c) { - ea_list *new; - unsigned i, cnt, hash, code; - eattr *a, *d; - u32 seen = 0; - struct bgp_bucket *b; - - /* Merge the attribute list */ - new = alloca(ea_scan(attrs)); - ea_merge(attrs, new); - ea_sort(new); + if (!c->withdraw_bucket) + { + c->withdraw_bucket = mb_allocz(c->pool, sizeof(struct bgp_bucket)); + init_list(&c->withdraw_bucket->prefixes); + } - /* Normalize attributes */ - d = new->attrs; - cnt = new->count; - new->count = 0; - for(i=0; i<cnt; i++) - { - a = &new->attrs[i]; - if (EA_PROTO(a->id) != EAP_BGP) - continue; - code = EA_ID(a->id); - if (ATTR_KNOWN(code)) - { - if (!p->is_internal) - { - if (!bgp_attr_table[code].allow_in_ebgp) - continue; - if ((code == BA_LOCAL_PREF) && !p->cf->allow_local_pref) - continue; - } - /* The flags might have been zero if the attr was added by filters */ - a->flags = (a->flags & BAF_PARTIAL) | bgp_attr_table[code].expected_flags; - if (code < 32) - seen |= 1 << code; - } - else - { - /* Don't re-export unknown non-transitive attributes */ - if (!(a->flags & BAF_TRANSITIVE)) - continue; - } - *d = *a; - if ((d->type & EAF_ORIGINATED) && !originate && (d->flags & BAF_TRANSITIVE) && (d->flags & BAF_OPTIONAL)) - d->flags |= BAF_PARTIAL; - switch (d->type & EAF_TYPE_MASK) - { - case EAF_TYPE_INT_SET: - { - struct adata *z = alloca(sizeof(struct adata) + d->u.ptr->length); - z->length = d->u.ptr->length; - bgp_normalize_int_set((u32 *) z->data, (u32 *) d->u.ptr->data, z->length / 4); - d->u.ptr = z; - break; - } - case EAF_TYPE_EC_SET: - { - struct adata *z = alloca(sizeof(struct adata) + d->u.ptr->length); - z->length = d->u.ptr->length; - bgp_normalize_ec_set(z, (u32 *) d->u.ptr->data, p->is_internal); - d->u.ptr = z; - break; - } - case EAF_TYPE_LC_SET: - { - struct adata *z = alloca(sizeof(struct adata) + d->u.ptr->length); - z->length = d->u.ptr->length; - bgp_normalize_lc_set((u32 *) z->data, (u32 *) d->u.ptr->data, z->length / LCOMM_LENGTH); - d->u.ptr = z; - break; - } - default: ; - } - d++; - new->count++; - } + return c->withdraw_bucket; +} - /* Hash */ - hash = ea_hash(new); - for(b=p->bucket_hash[hash & (p->hash_size - 1)]; b; b=b->hash_next) - if (b->hash == hash && ea_same(b->eattrs, new)) - { - DBG("Found bucket.\n"); - return b; - } - - /* Ensure that there are all mandatory attributes */ - for(i=0; i<ARRAY_SIZE(bgp_mandatory_attrs); i++) - if (!(seen & (1 << bgp_mandatory_attrs[i]))) - { - log(L_ERR "%s: Mandatory attribute %s missing in route %I/%d", p->p.name, bgp_attr_table[bgp_mandatory_attrs[i]].name, n->n.prefix, n->n.pxlen); - return NULL; - } - - /* Check if next hop is valid */ - a = ea_find(new, EA_CODE(EAP_BGP, BA_NEXT_HOP)); - if (!a || ipa_equal(p->cf->remote_ip, *(ip_addr *)a->u.ptr->data)) - { - log(L_ERR "%s: Invalid NEXT_HOP attribute in route %I/%d", p->p.name, n->n.prefix, n->n.pxlen); - return NULL; - } +void +bgp_free_bucket(struct bgp_channel *c, struct bgp_bucket *b) +{ + rem_node(&b->send_node); + HASH_REMOVE2(c->bucket_hash, RBH, c->pool, b); + mb_free(b); +} - /* Create new bucket */ - DBG("Creating bucket.\n"); - return bgp_new_bucket(p, new, hash); +void +bgp_defer_bucket(struct bgp_channel *c, struct bgp_bucket *b) +{ + rem_node(&b->send_node); + add_tail(&c->bucket_queue, &b->send_node); } void -bgp_free_bucket(struct bgp_proto *p, struct bgp_bucket *buck) +bgp_withdraw_bucket(struct bgp_channel *c, struct bgp_bucket *b) { - if (buck->hash_next) - buck->hash_next->hash_prev = buck->hash_prev; - if (buck->hash_prev) - buck->hash_prev->hash_next = buck->hash_next; - else - p->bucket_hash[buck->hash & (p->hash_size-1)] = buck->hash_next; - mb_free(buck); + struct bgp_proto *p = (void *) c->c.proto; + struct bgp_bucket *wb = bgp_get_withdraw_bucket(c); + + log(L_ERR "%s: Attribute list too long", p->p.name); + while (!EMPTY_LIST(b->prefixes)) + { + struct bgp_prefix *px = HEAD(b->prefixes); + + log(L_ERR "%s: - withdrawing %N", p->p.name, &px->net); + rem_node(&px->buck_node); + add_tail(&wb->prefixes, &px->buck_node); + } } -/* Prefix hash table */ +/* + * Prefix hash table + */ -#define PXH_KEY(n1) n1->n.prefix, n1->n.pxlen, n1->path_id -#define PXH_NEXT(n) n->next -#define PXH_EQ(p1,l1,i1,p2,l2,i2) ipa_equal(p1, p2) && l1 == l2 && i1 == i2 -#define PXH_FN(p,l,i) ipa_hash32(p) ^ u32_hash((l << 16) ^ i) +#define PXH_KEY(px) px->net, px->path_id, px->hash +#define PXH_NEXT(px) px->next +#define PXH_EQ(n1,i1,h1,n2,i2,h2) h1 == h2 && i1 == i2 && net_equal(n1, n2) +#define PXH_FN(n,i,h) h #define PXH_REHASH bgp_pxh_rehash #define PXH_PARAMS /8, *2, 2, 2, 8, 20 @@ -931,308 +1292,282 @@ bgp_free_bucket(struct bgp_proto *p, struct bgp_bucket *buck) HASH_DEFINE_REHASH_FN(PXH, struct bgp_prefix) void -bgp_init_prefix_table(struct bgp_proto *p, u32 order) +bgp_init_prefix_table(struct bgp_channel *c) { - HASH_INIT(p->prefix_hash, p->p.pool, order); + HASH_INIT(c->prefix_hash, c->pool, 8); - p->prefix_slab = sl_new(p->p.pool, sizeof(struct bgp_prefix)); + uint alen = net_addr_length[c->c.net_type]; + c->prefix_slab = alen ? sl_new(c->pool, sizeof(struct bgp_prefix) + alen) : NULL; } void -bgp_free_prefix_table(struct bgp_proto *p) +bgp_free_prefix_table(struct bgp_channel *c) { - HASH_FREE(p->prefix_hash); + HASH_FREE(c->prefix_hash); - rfree(p->prefix_slab); - p->prefix_slab = NULL; + rfree(c->prefix_slab); + c->prefix_slab = NULL; } static struct bgp_prefix * -bgp_get_prefix(struct bgp_proto *p, ip_addr prefix, int pxlen, u32 path_id) +bgp_get_prefix(struct bgp_channel *c, net_addr *net, u32 path_id) { - struct bgp_prefix *bp = HASH_FIND(p->prefix_hash, PXH, prefix, pxlen, 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); + + if (px) + { + rem_node(&px->buck_node); + return px; + } - if (bp) - return bp; + if (c->prefix_slab) + px = sl_alloc(c->prefix_slab); + else + px = mb_alloc(c->pool, sizeof(struct bgp_prefix) + net->length); - bp = sl_alloc(p->prefix_slab); - bp->n.prefix = prefix; - bp->n.pxlen = pxlen; - bp->path_id = path_id; - bp->bucket_node.next = NULL; + px->buck_node.next = NULL; + px->buck_node.prev = NULL; + px->hash = hash; + px->path_id = path_id; + net_copy(px->net, net); - HASH_INSERT2(p->prefix_hash, PXH, p->p.pool, bp); + HASH_INSERT2(c->prefix_hash, PXH, c->pool, px); - return bp; + return px; } void -bgp_free_prefix(struct bgp_proto *p, struct bgp_prefix *bp) +bgp_free_prefix(struct bgp_channel *c, struct bgp_prefix *px) { - HASH_REMOVE2(p->prefix_hash, PXH, p->p.pool, bp); - sl_free(p->prefix_slab, bp); + rem_node(&px->buck_node); + HASH_REMOVE2(c->prefix_hash, PXH, c->pool, px); + + if (c->prefix_slab) + sl_free(c->prefix_slab, px); + else + mb_free(px); } -void -bgp_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old UNUSED, ea_list *attrs) +/* + * BGP protocol glue + */ + +int +bgp_import_control(struct proto *P, rte **new, ea_list **attrs UNUSED, struct linpool *pool UNUSED) { + rte *e = *new; + struct proto *SRC = e->attrs->src->proto; struct bgp_proto *p = (struct bgp_proto *) P; - struct bgp_bucket *buck; - struct bgp_prefix *px; - rte *key; - u32 path_id; + struct bgp_proto *src = (SRC->proto == &proto_bgp) ? (struct bgp_proto *) SRC : NULL; - DBG("BGP: Got route %I/%d %s\n", n->n.prefix, n->n.pxlen, new ? "up" : "down"); + /* Reject our routes */ + if (src == p) + return -1; - if (new) - { - key = new; - buck = bgp_get_bucket(p, n, attrs, new->attrs->source != RTS_BGP); - if (!buck) /* Inconsistent attribute list */ - return; - } - else - { - key = old; - if (!(buck = p->withdraw_bucket)) - { - buck = p->withdraw_bucket = mb_alloc(P->pool, sizeof(struct bgp_bucket)); - init_list(&buck->prefixes); - } - } - path_id = p->add_path_tx ? key->attrs->src->global_id : 0; - px = bgp_get_prefix(p, n->n.prefix, n->n.pxlen, path_id); - if (px->bucket_node.next) - { - DBG("\tRemoving old entry.\n"); - rem_node(&px->bucket_node); - } - add_tail(&buck->prefixes, &px->bucket_node); - bgp_schedule_packet(p->conn, PKT_UPDATE); -} + /* Accept non-BGP routes */ + if (src == NULL) + return 0; -static int -bgp_create_attrs(struct bgp_proto *p, rte *e, ea_list **attrs, struct linpool *pool) -{ - ea_list *ea = lp_alloc(pool, sizeof(ea_list) + 4*sizeof(eattr)); - rta *rta = e->attrs; - byte *z; + // XXXX: Check next hop AF - ea->next = *attrs; - *attrs = ea; - ea->flags = EALF_SORTED; - ea->count = 4; + /* IBGP route reflection, RFC 4456 */ + if (p->is_internal && src->is_internal && (p->local_as == src->local_as)) + { + /* Rejected unless configured as route reflector */ + if (!p->rr_client && !src->rr_client) + return -1; + + /* Generally, this should be handled when path is received, but we check it + also here as rr_cluster_id may be undefined or different in src. */ + if (p->rr_cluster_id && bgp_cluster_list_loopy(p, e->attrs->eattrs)) + return -1; + } - bgp_set_attr(ea->attrs, BA_ORIGIN, - ((rta->source == RTS_OSPF_EXT1) || (rta->source == RTS_OSPF_EXT2)) ? ORIGIN_INCOMPLETE : ORIGIN_IGP); + /* Handle well-known communities, RFC 1997 */ + struct eattr *c; + if (p->cf->interpret_communities && + (c = ea_find(e->attrs->eattrs, EA_CODE(EAP_BGP, BA_COMMUNITY)))) + { + struct adata *d = c->u.ptr; - if (p->is_internal) - bgp_set_attr_wa(ea->attrs+1, pool, BA_AS_PATH, 0); - else - { - z = bgp_set_attr_wa(ea->attrs+1, pool, BA_AS_PATH, 6); - z[0] = AS_PATH_SEQUENCE; - z[1] = 1; /* 1 AS */ - put_u32(z+2, p->local_as); - } + /* Do not export anywhere */ + if (int_set_contains(d, BGP_COMM_NO_ADVERTISE)) + return -1; - /* iBGP -> use gw, eBGP multi-hop -> use source_addr, - eBGP single-hop -> use gw if on the same iface */ - z = bgp_set_attr_wa(ea->attrs+2, pool, BA_NEXT_HOP, NEXT_HOP_LENGTH); - if (p->cf->next_hop_self || - rta->dest != RTD_ROUTER || - ipa_equal(rta->gw, IPA_NONE) || - ipa_is_link_local(rta->gw) || - (!p->is_internal && !p->cf->next_hop_keep && - (!p->neigh || (rta->iface != p->neigh->iface)))) - set_next_hop(z, p->source_addr); - else - set_next_hop(z, rta->gw); + /* Do not export outside of AS (or member-AS) */ + if (!p->is_internal && int_set_contains(d, BGP_COMM_NO_EXPORT_SUBCONFED)) + return -1; - bgp_set_attr(ea->attrs+3, BA_LOCAL_PREF, p->cf->default_local_pref); + /* Do not export outside of AS (or confederation) */ + if (!p->is_interior && int_set_contains(d, BGP_COMM_NO_EXPORT)) + return -1; + } - return 0; /* Leave decision to the filters */ + return 0; } -static inline int -bgp_as_path_loopy(struct bgp_proto *p, rta *a) -{ - int num = p->cf->allow_local_as + 1; - eattr *e = ea_find(a->eattrs, EA_CODE(EAP_BGP, BA_AS_PATH)); - return (e && (num > 0) && as_path_contains(e->u.ptr, p->local_as, num)); -} +static adata null_adata; /* adata of length 0 */ -static inline int -bgp_originator_id_loopy(struct bgp_proto *p, rta *a) -{ - eattr *e = ea_find(a->eattrs, EA_CODE(EAP_BGP, BA_ORIGINATOR_ID)); - return (e && (e->u.data == p->local_id)); -} - -static inline int -bgp_cluster_list_loopy(struct bgp_proto *p, rta *a) +static ea_list * +bgp_update_attrs(struct bgp_proto *p, struct bgp_channel *c, rte *e, ea_list *attrs0, struct linpool *pool) { - eattr *e = ea_find(a->eattrs, EA_CODE(EAP_BGP, BA_CLUSTER_LIST)); - return (e && p->rr_client && int_set_contains(e->u.ptr, p->rr_cluster_id)); -} + struct proto *SRC = e->attrs->src->proto; + struct bgp_proto *src = (SRC->proto == &proto_bgp) ? (void *) SRC : NULL; + 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; + adata *ad; + /* ORIGIN attribute - mandatory, attach if missing */ + if (! bgp_find_attr(attrs0, BA_ORIGIN)) + bgp_set_attr_u32(&attrs, pool, BA_ORIGIN, 0, src ? ORIGIN_INCOMPLETE : ORIGIN_IGP); -static inline void -bgp_path_prepend(rte *e, ea_list **attrs, struct linpool *pool, u32 as) -{ - eattr *a = ea_find(e->attrs->eattrs, EA_CODE(EAP_BGP, BA_AS_PATH)); - bgp_attach_attr(attrs, pool, BA_AS_PATH, (uintptr_t) as_path_prepend(pool, a->u.ptr, as)); -} + /* AS_PATH attribute - mandatory */ + a = bgp_find_attr(attrs0, BA_AS_PATH); + ad = a ? a->u.ptr : &null_adata; -static inline void -bgp_cluster_list_prepend(rte *e, ea_list **attrs, struct linpool *pool, u32 cid) -{ - eattr *a = ea_find(e->attrs->eattrs, EA_CODE(EAP_BGP, BA_CLUSTER_LIST)); - bgp_attach_attr(attrs, pool, BA_CLUSTER_LIST, (uintptr_t) int_set_prepend(pool, a ? a->u.ptr : NULL, cid)); -} + /* AS_PATH attribute - strip AS_CONFED* segments outside confederation */ + if ((!p->cf->confederation || !p->is_interior) && as_path_contains_confed(ad)) + ad = as_path_strip_confed(pool, ad); -static int -bgp_update_attrs(struct bgp_proto *p, rte *e, ea_list **attrs, struct linpool *pool, int rr) -{ - eattr *a; + /* AS_PATH attribute - keep or prepend ASN */ + if (p->is_internal || + (p->rs_client && src && src->rs_client)) + { + /* IBGP or route server -> just ensure there is one */ + if (!a) + bgp_set_attr_ptr(&attrs, pool, BA_AS_PATH, 0, &null_adata); + } + else if (p->is_interior) + { + /* Confederation -> prepend ASN as AS_CONFED_SEQUENCE */ + ad = as_path_prepend2(pool, ad, AS_PATH_CONFED_SEQUENCE, p->public_as); + bgp_set_attr_ptr(&attrs, pool, BA_AS_PATH, 0, ad); + } + else /* Regular EBGP (no RS, no confederation) */ + { + /* Regular EBGP -> prepend ASN as regular sequence */ + ad = as_path_prepend2(pool, ad, AS_PATH_SEQUENCE, p->public_as); + bgp_set_attr_ptr(&attrs, pool, BA_AS_PATH, 0, ad); + + /* MULTI_EXIT_DESC attribute - accept only if set in export filter */ + a = bgp_find_attr(attrs0, BA_MULTI_EXIT_DISC); + if (a && !(a->type & EAF_FRESH)) + bgp_unset_attr(&attrs, pool, BA_MULTI_EXIT_DISC); + } - if (!p->is_internal && !p->rs_client) - { - bgp_path_prepend(e, attrs, pool, p->local_as); - - /* The MULTI_EXIT_DISC attribute received from a neighboring AS MUST NOT be - * propagated to other neighboring ASes. - * Perhaps it would be better to undefine it. - */ - a = ea_find(e->attrs->eattrs, EA_CODE(EAP_BGP, BA_MULTI_EXIT_DISC)); - if (a) - bgp_attach_attr(attrs, pool, BA_MULTI_EXIT_DISC, 0); - } + /* NEXT_HOP attribute - delegated to AF-specific hook */ + a = bgp_find_attr(attrs0, BA_NEXT_HOP); + bgp_update_next_hop(&s, a, &attrs); - /* iBGP -> keep next_hop, eBGP multi-hop -> use source_addr, - * eBGP single-hop -> keep next_hop if on the same iface. - * If the next_hop is zero (i.e. link-local), keep only if on the same iface. - * - * Note that same-iface-check uses iface from route, which is based on gw. - */ - a = ea_find(e->attrs->eattrs, EA_CODE(EAP_BGP, BA_NEXT_HOP)); - if (a && !p->cf->next_hop_self && - (p->cf->next_hop_keep || - (p->is_internal && ipa_nonzero(*((ip_addr *) a->u.ptr->data))) || - (p->neigh && (e->attrs->iface == p->neigh->iface)))) - { - /* Leave the original next hop attribute, will check later where does it point */ - } - else - { - /* Need to create new one */ - byte *b = bgp_attach_attr_wa(attrs, pool, BA_NEXT_HOP, NEXT_HOP_LENGTH); - set_next_hop(b, p->source_addr); - } + /* LOCAL_PREF attribute - required for IBGP, attach if missing */ + if (p->is_interior && ! bgp_find_attr(attrs0, BA_LOCAL_PREF)) + bgp_set_attr_u32(&attrs, pool, BA_LOCAL_PREF, 0, p->cf->default_local_pref); - if (rr) - { - /* Handling route reflection, RFC 4456 */ - struct bgp_proto *src = (struct bgp_proto *) e->attrs->src->proto; + /* IBGP route reflection, RFC 4456 */ + if (src && src->is_internal && p->is_internal && (src->local_as == p->local_as)) + { + /* ORIGINATOR_ID attribute - attach if not already set */ + if (! bgp_find_attr(attrs0, BA_ORIGINATOR_ID)) + bgp_set_attr_u32(&attrs, pool, BA_ORIGINATOR_ID, 0, src->remote_id); - a = ea_find(e->attrs->eattrs, EA_CODE(EAP_BGP, BA_ORIGINATOR_ID)); - if (!a) - bgp_attach_attr(attrs, pool, BA_ORIGINATOR_ID, src->remote_id); + /* CLUSTER_LIST attribute - prepend cluster ID */ + a = bgp_find_attr(attrs0, BA_CLUSTER_LIST); + ad = a ? a->u.ptr : NULL; - /* We attach proper cluster ID according to whether the route is entering or leaving the cluster */ - bgp_cluster_list_prepend(e, attrs, pool, src->rr_client ? src->rr_cluster_id : p->rr_cluster_id); + /* Prepend src cluster ID */ + if (src->rr_cluster_id) + ad = int_set_prepend(pool, ad, src->rr_cluster_id); - /* Two RR clients with different cluster ID, hmmm */ - if (src->rr_client && p->rr_client && (src->rr_cluster_id != p->rr_cluster_id)) - bgp_cluster_list_prepend(e, attrs, pool, p->rr_cluster_id); - } + /* Prepend dst cluster ID if src and dst clusters are different */ + if (p->rr_cluster_id && (src->rr_cluster_id != p->rr_cluster_id)) + ad = int_set_prepend(pool, ad, p->rr_cluster_id); - return 0; /* Leave decision to the filters */ -} + /* Should be at least one prepended cluster ID */ + bgp_set_attr_ptr(&attrs, pool, BA_CLUSTER_LIST, 0, ad); + } -static int -bgp_community_filter(struct bgp_proto *p, rte *e) -{ - eattr *a; - struct adata *d; + /* AS4_* transition attributes, RFC 6793 4.2.2 */ + if (! p->as4_session) + { + a = bgp_find_attr(attrs, BA_AS_PATH); + if (a && as_path_contains_as4(a->u.ptr)) + { + bgp_set_attr_ptr(&attrs, pool, BA_AS_PATH, 0, as_path_to_old(pool, a->u.ptr)); + bgp_set_attr_ptr(&attrs, pool, BA_AS4_PATH, 0, as_path_strip_confed(pool, a->u.ptr)); + } - /* Check if we aren't forbidden to export the route by communities */ - a = ea_find(e->attrs->eattrs, EA_CODE(EAP_BGP, BA_COMMUNITY)); - if (a) + a = bgp_find_attr(attrs, BA_AGGREGATOR); + if (a && aggregator_contains_as4(a->u.ptr)) { - d = a->u.ptr; - if (int_set_contains(d, BGP_COMM_NO_ADVERTISE)) - { - DBG("\tNO_ADVERTISE\n"); - return 1; - } - if (!p->is_internal && - (int_set_contains(d, BGP_COMM_NO_EXPORT) || - int_set_contains(d, BGP_COMM_NO_EXPORT_SUBCONFED))) - { - DBG("\tNO_EXPORT\n"); - return 1; - } + bgp_set_attr_ptr(&attrs, pool, BA_AGGREGATOR, 0, aggregator_to_old(pool, a->u.ptr)); + bgp_set_attr_ptr(&attrs, pool, BA_AS4_AGGREGATOR, 0, a->u.ptr); } + } - return 0; + /* + * Presence of mandatory attributes ORIGIN and AS_PATH is ensured by above + * conditions. Presence and validity of quasi-mandatory NEXT_HOP attribute + * should be checked in AF-specific hooks. + */ + + /* Apply per-attribute export hooks for validatation and normalization */ + return bgp_export_attrs(&s, attrs); } -int -bgp_import_control(struct proto *P, rte **new, ea_list **attrs, struct linpool *pool) +void +bgp_rt_notify(struct proto *P, struct channel *C, net *n, rte *new, rte *old, ea_list *attrs) { - rte *e = *new; - struct bgp_proto *p = (struct bgp_proto *) P; - struct bgp_proto *new_bgp = (e->attrs->src->proto->proto == &proto_bgp) ? - (struct bgp_proto *) e->attrs->src->proto : NULL; + struct bgp_proto *p = (void *) P; + struct bgp_channel *c = (void *) C; + struct bgp_bucket *buck; + struct bgp_prefix *px; + u32 path; - if (p == new_bgp) /* Poison reverse updates */ - return -1; - if (new_bgp) - { - /* We should check here for cluster list loop, because the receiving BGP instance - might have different cluster ID */ - if (bgp_cluster_list_loopy(p, e->attrs)) - return -1; - - if (p->cf->interpret_communities && bgp_community_filter(p, e)) - return -1; - - if (p->local_as == new_bgp->local_as && p->is_internal && new_bgp->is_internal) - { - /* Redistribution of internal routes with IBGP */ - if (p->rr_client || new_bgp->rr_client) - /* Route reflection, RFC 4456 */ - return bgp_update_attrs(p, e, attrs, pool, 1); - else - return -1; - } - else - return bgp_update_attrs(p, e, attrs, pool, 0); - } + if (new) + { + attrs = bgp_update_attrs(p, c, new, attrs, bgp_linpool2); + + /* 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); + } else - return bgp_create_attrs(p, e, attrs, pool); + { + buck = bgp_get_withdraw_bucket(c); + path = old->attrs->src->global_id; + } + + px = bgp_get_prefix(c, n->n.addr, c->add_path_tx ? path : 0); + add_tail(&buck->prefixes, &px->buck_node); + + bgp_schedule_packet(p->conn, c, PKT_UPDATE); } + static inline u32 bgp_get_neighbor(rte *r) { eattr *e = ea_find(r->attrs->eattrs, EA_CODE(EAP_BGP, BA_AS_PATH)); u32 as; - if (e && as_path_get_first(e->u.ptr, &as)) + if (e && as_path_get_first_regular(e->u.ptr, &as)) return as; - else - return ((struct bgp_proto *) r->attrs->src->proto)->remote_as; + + /* If AS_PATH is not defined, we treat rte as locally originated */ + struct bgp_proto *p = (void *) r->attrs->src->proto; + return p->cf->confederation ?: p->local_as; } static inline int rte_resolvable(rte *rt) { - int rd = rt->attrs->dest; - return (rd == RTD_ROUTER) || (rd == RTD_DEVICE) || (rd == RTD_MULTIPATH); + return rt->attrs->dest == RTD_UNICAST; } int @@ -1271,16 +1606,16 @@ bgp_rte_better(rte *new, rte *old) /* RFC 4271 9.1.2.2. a) Use AS path lengths */ if (new_bgp->cf->compare_path_lengths || old_bgp->cf->compare_path_lengths) - { - x = ea_find(new->attrs->eattrs, EA_CODE(EAP_BGP, BA_AS_PATH)); - y = ea_find(old->attrs->eattrs, EA_CODE(EAP_BGP, BA_AS_PATH)); - n = x ? as_path_getlen(x->u.ptr) : AS_PATH_MAXLEN; - o = y ? as_path_getlen(y->u.ptr) : AS_PATH_MAXLEN; - if (n < o) - return 1; - if (n > o) - return 0; - } + { + x = ea_find(new->attrs->eattrs, EA_CODE(EAP_BGP, BA_AS_PATH)); + y = ea_find(old->attrs->eattrs, EA_CODE(EAP_BGP, BA_AS_PATH)); + n = x ? as_path_getlen(x->u.ptr) : AS_PATH_MAXLEN; + o = y ? as_path_getlen(y->u.ptr) : AS_PATH_MAXLEN; + if (n < o) + return 1; + if (n > o) + return 0; + } /* RFC 4271 9.1.2.2. b) Use origins */ x = ea_find(new->attrs->eattrs, EA_CODE(EAP_BGP, BA_ORIGIN)); @@ -1305,21 +1640,21 @@ bgp_rte_better(rte *new, rte *old) */ if (new_bgp->cf->med_metric || old_bgp->cf->med_metric || (bgp_get_neighbor(new) == bgp_get_neighbor(old))) - { - x = ea_find(new->attrs->eattrs, EA_CODE(EAP_BGP, BA_MULTI_EXIT_DISC)); - y = ea_find(old->attrs->eattrs, EA_CODE(EAP_BGP, BA_MULTI_EXIT_DISC)); - n = x ? x->u.data : new_bgp->cf->default_med; - o = y ? y->u.data : old_bgp->cf->default_med; - if (n < o) - return 1; - if (n > o) - return 0; - } + { + x = ea_find(new->attrs->eattrs, EA_CODE(EAP_BGP, BA_MULTI_EXIT_DISC)); + y = ea_find(old->attrs->eattrs, EA_CODE(EAP_BGP, BA_MULTI_EXIT_DISC)); + n = x ? x->u.data : new_bgp->cf->default_med; + o = y ? y->u.data : old_bgp->cf->default_med; + if (n < o) + return 1; + if (n > o) + return 0; + } /* RFC 4271 9.1.2.2. d) Prefer external peers */ - if (new_bgp->is_internal > old_bgp->is_internal) + if (new_bgp->is_interior > old_bgp->is_interior) return 0; - if (new_bgp->is_internal < old_bgp->is_internal) + if (new_bgp->is_interior < old_bgp->is_interior) return 1; /* RFC 4271 9.1.2.2. e) Compare IGP metrics */ @@ -1331,7 +1666,7 @@ bgp_rte_better(rte *new, rte *old) return 0; /* RFC 4271 9.1.2.2. f) Compare BGP identifiers */ - /* RFC 4456 9. a) Use ORIGINATOR_ID instead of local neighor ID */ + /* RFC 4456 9. a) Use ORIGINATOR_ID instead of local neighbor ID */ x = ea_find(new->attrs->eattrs, EA_CODE(EAP_BGP, BA_ORIGINATOR_ID)); y = ea_find(old->attrs->eattrs, EA_CODE(EAP_BGP, BA_ORIGINATOR_ID)); n = x ? x->u.data : new_bgp->remote_id; @@ -1390,18 +1725,18 @@ bgp_rte_mergable(rte *pri, rte *sec) /* RFC 4271 9.1.2.2. a) Use AS path lengths */ if (pri_bgp->cf->compare_path_lengths || sec_bgp->cf->compare_path_lengths) - { - x = ea_find(pri->attrs->eattrs, EA_CODE(EAP_BGP, BA_AS_PATH)); - y = ea_find(sec->attrs->eattrs, EA_CODE(EAP_BGP, BA_AS_PATH)); - p = x ? as_path_getlen(x->u.ptr) : AS_PATH_MAXLEN; - s = y ? as_path_getlen(y->u.ptr) : AS_PATH_MAXLEN; + { + x = ea_find(pri->attrs->eattrs, EA_CODE(EAP_BGP, BA_AS_PATH)); + y = ea_find(sec->attrs->eattrs, EA_CODE(EAP_BGP, BA_AS_PATH)); + p = x ? as_path_getlen(x->u.ptr) : AS_PATH_MAXLEN; + s = y ? as_path_getlen(y->u.ptr) : AS_PATH_MAXLEN; - if (p != s) - return 0; + if (p != s) + return 0; -// if (DELTA(p, s) > pri_bgp->cf->relax_multipath) -// return 0; - } +// if (DELTA(p, s) > pri_bgp->cf->relax_multipath) +// return 0; + } /* RFC 4271 9.1.2.2. b) Use origins */ x = ea_find(pri->attrs->eattrs, EA_CODE(EAP_BGP, BA_ORIGIN)); @@ -1414,17 +1749,17 @@ bgp_rte_mergable(rte *pri, rte *sec) /* RFC 4271 9.1.2.2. c) Compare MED's */ if (pri_bgp->cf->med_metric || sec_bgp->cf->med_metric || (bgp_get_neighbor(pri) == bgp_get_neighbor(sec))) - { - x = ea_find(pri->attrs->eattrs, EA_CODE(EAP_BGP, BA_MULTI_EXIT_DISC)); - y = ea_find(sec->attrs->eattrs, EA_CODE(EAP_BGP, BA_MULTI_EXIT_DISC)); - p = x ? x->u.data : pri_bgp->cf->default_med; - s = y ? y->u.data : sec_bgp->cf->default_med; - if (p != s) - return 0; - } + { + x = ea_find(pri->attrs->eattrs, EA_CODE(EAP_BGP, BA_MULTI_EXIT_DISC)); + y = ea_find(sec->attrs->eattrs, EA_CODE(EAP_BGP, BA_MULTI_EXIT_DISC)); + p = x ? x->u.data : pri_bgp->cf->default_med; + s = y ? y->u.data : sec_bgp->cf->default_med; + if (p != s) + return 0; + } /* RFC 4271 9.1.2.2. d) Prefer external peers */ - if (pri_bgp->is_internal != sec_bgp->is_internal) + if (pri_bgp->is_interior != sec_bgp->is_interior) return 0; /* RFC 4271 9.1.2.2. e) Compare IGP metrics */ @@ -1439,7 +1774,6 @@ bgp_rte_mergable(rte *pri, rte *sec) } - static inline int same_group(rte *r, u32 lpref, u32 lasn) { @@ -1484,7 +1818,7 @@ bgp_rte_recalculate(rtable *table, net *net, rte *new, rte *old, rte *old_best) * that this fn is not called for them. * * The idea is simple, the implementation is more problematic, - * mostly because of optimizations in rte_recalculate() that + * mostly because of optimizations in rte_recalculate() that * avoids full recalculation in most cases. * * We can assume that at least one of new, old is non-NULL and both @@ -1496,14 +1830,14 @@ bgp_rte_recalculate(rtable *table, net *net, rte *new, rte *old, rte *old_best) /* If new and old are from different groups, we just process that as two independent events */ if (new && old && !same_group(old, lpref, lasn)) - { - int i1, i2; - i1 = bgp_rte_recalculate(table, net, NULL, old, old_best); - i2 = bgp_rte_recalculate(table, net, new, NULL, old_best); - return i1 || i2; - } + { + int i1, i2; + i1 = bgp_rte_recalculate(table, net, NULL, old, old_best); + i2 = bgp_rte_recalculate(table, net, new, NULL, old_best); + return i1 || i2; + } - /* + /* * We could find the best-in-group and then make some shortcuts like * in rte_recalculate, but as we would have to walk through all * net->routes just to find it, it is probably not worth. So we @@ -1515,35 +1849,35 @@ bgp_rte_recalculate(rtable *table, net *net, rte *new, rte *old, rte *old_best) new->u.bgp.suppressed = 1; if (old) + { + old_is_group_best = !old->u.bgp.suppressed; + old->u.bgp.suppressed = 1; + int new_is_better = new && bgp_rte_better(new, old); + + /* The first case - replace not best with worse (or remove not best) */ + if (!old_is_group_best && !new_is_better) + return 0; + + /* The second case - replace the best with better */ + if (old_is_group_best && new_is_better) { - old_is_group_best = !old->u.bgp.suppressed; - old->u.bgp.suppressed = 1; - int new_is_better = new && bgp_rte_better(new, old); - - /* The first case - replace not best with worse (or remove not best) */ - if (!old_is_group_best && !new_is_better) - return 0; - - /* The second case - replace the best with better */ - if (old_is_group_best && new_is_better) - { - /* new is best-in-group, the see discussion below - this is - a special variant of NBG && OBG. From OBG we can deduce - that same_group(old_best) iff (old == old_best) */ - new->u.bgp.suppressed = 0; - return (old == old_best); - } + /* new is best-in-group, the see discussion below - this is + a special variant of NBG && OBG. From OBG we can deduce + that same_group(old_best) iff (old == old_best) */ + new->u.bgp.suppressed = 0; + return (old == 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)) - { - s->u.bgp.suppressed = 1; - if (!r || bgp_rte_better(s, r)) - r = s; - } + { + s->u.bgp.suppressed = 1; + if (!r || bgp_rte_better(s, r)) + r = s; + } /* Simple case - the last route in group disappears */ if (!r) @@ -1582,397 +1916,77 @@ bgp_rte_recalculate(rtable *table, net *net, rte *new, rte *old, rte *old_best) return old_is_group_best; } -static struct adata * -bgp_aggregator_convert_to_new(struct adata *old, struct linpool *pool) -{ - struct adata *newa = lp_alloc(pool, sizeof(struct adata) + 8); - newa->length = 8; - aggregator_convert_to_new(old, newa->data); - return newa; -} - -/* Take last req_as ASNs from path old2 (in 2B format), convert to 4B format - * and append path old4 (in 4B format). +/* + * Reconstruct AS_PATH and AGGREGATOR according to RFC 6793 4.2.3 */ -static struct adata * -bgp_merge_as_paths(struct adata *old2, struct adata *old4, int req_as, struct linpool *pool) -{ - byte buf[old2->length * 2]; - - int ol = as_path_convert_to_new(old2, buf, req_as); - int nl = ol + (old4 ? old4->length : 0); - - struct adata *newa = lp_alloc(pool, sizeof(struct adata) + nl); - newa->length = nl; - memcpy(newa->data, buf, ol); - if (old4) memcpy(newa->data + ol, old4->data, old4->length); - - return newa; -} - -static int -as4_aggregator_valid(struct adata *aggr) -{ - return aggr->length == 8; -} - - -/* Reconstruct 4B AS_PATH and AGGREGATOR according to RFC 4893 4.2.3 */ -static void -bgp_reconstruct_4b_atts(struct bgp_proto *p, rta *a, struct linpool *pool) -{ - eattr *p2 =ea_find(a->eattrs, EA_CODE(EAP_BGP, BA_AS_PATH)); - eattr *p4 =ea_find(a->eattrs, EA_CODE(EAP_BGP, BA_AS4_PATH)); - eattr *a2 =ea_find(a->eattrs, EA_CODE(EAP_BGP, BA_AGGREGATOR)); - eattr *a4 =ea_find(a->eattrs, EA_CODE(EAP_BGP, BA_AS4_AGGREGATOR)); - int a4_removed = 0; - - if (a4 && !as4_aggregator_valid(a4->u.ptr)) - { - log(L_WARN "%s: AS4_AGGREGATOR attribute is invalid, skipping attribute", p->p.name); - a4 = NULL; - a4_removed = 1; - } - - if (a2) - { - u32 a2_as = get_u16(a2->u.ptr->data); - - if (a4) - { - if (a2_as != AS_TRANS) - { - /* Routes were aggregated by old router and therefore AS4_PATH - * and AS4_AGGREGATOR is invalid - * - * Convert AS_PATH and AGGREGATOR to 4B format and finish. - */ - - a2->u.ptr = bgp_aggregator_convert_to_new(a2->u.ptr, pool); - p2->u.ptr = bgp_merge_as_paths(p2->u.ptr, NULL, AS_PATH_MAXLEN, pool); - - return; - } - else - { - /* Common case, use AS4_AGGREGATOR attribute */ - a2->u.ptr = a4->u.ptr; - } - } - else - { - /* Common case, use old AGGREGATOR attribute */ - a2->u.ptr = bgp_aggregator_convert_to_new(a2->u.ptr, pool); - - if ((a2_as == AS_TRANS) && !a4_removed) - log(L_WARN "%s: AGGREGATOR attribute contain AS_TRANS, but AS4_AGGREGATOR is missing", p->p.name); - } - } - else - if (a4) - log(L_WARN "%s: AS4_AGGREGATOR attribute received, but AGGREGATOR attribute is missing", p->p.name); - - int p2_len = as_path_getlen_int(p2->u.ptr, 2); - int p4_len = p4 ? validate_as4_path(p, p4->u.ptr) : -1; - - if (p4 && (p4_len < 0)) - log(L_WARN "%s: AS4_PATH attribute is malformed, skipping attribute", p->p.name); - - if ((p4_len <= 0) || (p2_len < p4_len)) - p2->u.ptr = bgp_merge_as_paths(p2->u.ptr, NULL, AS_PATH_MAXLEN, pool); - else - p2->u.ptr = bgp_merge_as_paths(p2->u.ptr, p4->u.ptr, p2_len - p4_len, pool); -} - static void -bgp_remove_as4_attrs(struct bgp_proto *p, rta *a) +bgp_process_as4_attrs(ea_list **attrs, struct linpool *pool) { - unsigned id1 = EA_CODE(EAP_BGP, BA_AS4_PATH); - unsigned id2 = EA_CODE(EAP_BGP, BA_AS4_AGGREGATOR); - ea_list **el = &(a->eattrs); + eattr *p2 = bgp_find_attr(*attrs, BA_AS_PATH); + eattr *p4 = bgp_find_attr(*attrs, BA_AS4_PATH); + eattr *a2 = bgp_find_attr(*attrs, BA_AGGREGATOR); + eattr *a4 = bgp_find_attr(*attrs, BA_AS4_AGGREGATOR); - /* We know that ea_lists constructed in bgp_decode attrs have one attribute per ea_list struct */ - while (*el != NULL) - { - unsigned fid = (*el)->attrs[0].id; - - if ((fid == id1) || (fid == id2)) - { - *el = (*el)->next; - if (p->as4_session) - log(L_WARN "%s: Unexpected AS4_* attributes received", p->p.name); - } - else - el = &((*el)->next); - } -} + /* First, unset AS4_* attributes */ + if (p4) bgp_unset_attr(attrs, pool, BA_AS4_PATH); + if (a4) bgp_unset_attr(attrs, pool, BA_AS4_AGGREGATOR); -/** - * bgp_decode_attrs - check and decode BGP attributes - * @conn: connection - * @attr: start of attribute block - * @len: length of attribute block - * @pool: linear pool to make all the allocations in - * @mandatory: 1 iff presence of mandatory attributes has to be checked - * - * This function takes a BGP attribute block (a part of an Update message), checks - * its consistency and converts it to a list of BIRD route attributes represented - * by a &rta. - */ -struct rta * -bgp_decode_attrs(struct bgp_conn *conn, byte *attr, uint len, struct linpool *pool, int mandatory) -{ - struct bgp_proto *bgp = conn->bgp; - rta *a = lp_alloc(pool, sizeof(struct rta)); - uint flags, code, l, i, type; - int errcode; - byte *z, *attr_start; - byte seen[256/8]; - ea_list *ea; - struct adata *ad; - int withdraw = 0; - - bzero(a, sizeof(rta)); - a->source = RTS_BGP; - a->scope = SCOPE_UNIVERSE; - a->cast = RTC_UNICAST; - /* a->dest = RTD_ROUTER; -- set in bgp_set_next_hop() */ - a->from = bgp->cf->remote_ip; - - /* Parse the attributes */ - bzero(seen, sizeof(seen)); - DBG("BGP: Parsing attributes\n"); - while (len) - { - if (len < 2) - goto malformed; - attr_start = attr; - flags = *attr++; - code = *attr++; - len -= 2; - if (flags & BAF_EXT_LEN) - { - if (len < 2) - goto malformed; - l = get_u16(attr); - attr += 2; - len -= 2; - } - else - { - if (len < 1) - goto malformed; - l = *attr++; - len--; - } - if (l > len) - goto malformed; - len -= l; - z = attr; - attr += l; - DBG("Attr %02x %02x %d\n", code, flags, l); - if (seen[code/8] & (1 << (code%8))) - goto malformed; - if (ATTR_KNOWN(code)) - { - struct attr_desc *desc = &bgp_attr_table[code]; - if (desc->expected_length >= 0 && desc->expected_length != (int) l) - { errcode = 5; goto err; } - if ((desc->expected_flags ^ flags) & (BAF_OPTIONAL | BAF_TRANSITIVE)) - { errcode = 4; goto err; } - if (!bgp->is_internal) - { - if (!desc->allow_in_ebgp) - continue; - if ((code == BA_LOCAL_PREF) && !bgp->cf->allow_local_pref) - continue; - } - if (desc->validate) - { - errcode = desc->validate(bgp, z, l); - if (errcode > 0) - goto err; - if (errcode == IGNORE) - continue; - if (errcode <= WITHDRAW) - { - log(L_WARN "%s: Attribute %s is malformed, withdrawing update", - bgp->p.name, desc->name); - withdraw = 1; - } - } - else if (code == BA_AS_PATH) - { - /* Special case as it might also trim the attribute */ - if (validate_as_path(bgp, z, &l) < 0) - { errcode = 11; goto err; } - } - type = desc->type; - } - else /* Unknown attribute */ - { - if (!(flags & BAF_OPTIONAL)) - { errcode = 2; goto err; } - type = EAF_TYPE_OPAQUE; - } - - // Only OPTIONAL and TRANSITIVE attributes may have non-zero PARTIAL flag - // if (!((flags & BAF_OPTIONAL) && (flags & BAF_TRANSITIVE)) && (flags & BAF_PARTIAL)) - // { errcode = 4; goto err; } - - seen[code/8] |= (1 << (code%8)); - ea = lp_alloc(pool, sizeof(ea_list) + sizeof(eattr)); - ea->next = a->eattrs; - a->eattrs = ea; - ea->flags = 0; - ea->count = 1; - ea->attrs[0].id = EA_CODE(EAP_BGP, code); - ea->attrs[0].flags = flags; - ea->attrs[0].type = type; - if (type & EAF_EMBEDDED) - ad = NULL; - else - { - ad = lp_alloc(pool, sizeof(struct adata) + l); - ea->attrs[0].u.ptr = ad; - ad->length = l; - memcpy(ad->data, z, l); - } - switch (type) - { - case EAF_TYPE_ROUTER_ID: - case EAF_TYPE_INT: - if (l == 1) - ea->attrs[0].u.data = *z; - else - ea->attrs[0].u.data = get_u32(z); - break; - case EAF_TYPE_IP_ADDRESS: - ipa_ntoh(*(ip_addr *)ad->data); - break; - case EAF_TYPE_INT_SET: - case EAF_TYPE_LC_SET: - case EAF_TYPE_EC_SET: - { - u32 *z = (u32 *) ad->data; - for(i=0; i<ad->length/4; i++) - z[i] = ntohl(z[i]); - break; - } - } - } - - if (withdraw) - goto withdraw; - -#ifdef IPV6 - /* If we received MP_REACH_NLRI we should check mandatory attributes */ - if (bgp->mp_reach_len != 0) - mandatory = 1; -#endif - - /* If there is no (reachability) NLRI, we should exit now */ - if (! mandatory) - return a; - - /* Check if all mandatory attributes are present */ - for(i=0; i < ARRAY_SIZE(bgp_mandatory_attrs); i++) - { - code = bgp_mandatory_attrs[i]; - if (!(seen[code/8] & (1 << (code%8)))) - { - bgp_error(conn, 3, 3, &bgp_mandatory_attrs[i], 1); - return NULL; - } - } - - /* When receiving attributes from non-AS4-aware BGP speaker, - * we have to reconstruct 4B AS_PATH and AGGREGATOR attributes - */ - if (! bgp->as4_session) - bgp_reconstruct_4b_atts(bgp, a, pool); - - bgp_remove_as4_attrs(bgp, a); - - /* If the AS path attribute contains our AS, reject the routes */ - if (bgp_as_path_loopy(bgp, a)) - goto withdraw; - - /* Two checks for IBGP loops caused by route reflection, RFC 4456 */ - if (bgp_originator_id_loopy(bgp, a) || - bgp_cluster_list_loopy(bgp, a)) - goto withdraw; + /* Handle AGGREGATOR attribute */ + if (a2 && a4) + { + u32 a2_asn = get_u32(a2->u.ptr->data); - /* If there's no local preference, define one */ - if (!(seen[0] & (1 << BA_LOCAL_PREF))) - bgp_attach_attr(&a->eattrs, pool, BA_LOCAL_PREF, bgp->cf->default_local_pref); + /* If routes were aggregated by an old router, then AS4_PATH and + AS4_AGGREGATOR are invalid. In that case we give up. */ + if (a2_asn != AS_TRANS) + return; - return a; + /* Use AS4_AGGREGATOR instead of AGGREGATOR */ + a2->u.ptr = a4->u.ptr; + } -withdraw: - return NULL; + /* Handle AS_PATH attribute */ + if (p2 && p4) + { + /* Both as_path_getlen() and as_path_cut() take AS_CONFED* as zero length */ + int p2_len = as_path_getlen(p2->u.ptr); + int p4_len = as_path_getlen(p4->u.ptr); -malformed: - bgp_error(conn, 3, 1, NULL, 0); - return NULL; + /* AS_PATH is too short, give up */ + if (p2_len < p4_len) + return; -err: - bgp_error(conn, 3, errcode, attr_start, z+l-attr_start); - return NULL; + /* Merge AS_PATH and AS4_PATH */ + as_path_cut(p2->u.ptr, p2_len - p4_len); + p2->u.ptr = as_path_merge(pool, p2->u.ptr, p4->u.ptr); + } } int bgp_get_attr(eattr *a, byte *buf, int buflen) { uint i = EA_ID(a->id); - struct attr_desc *d; + const struct bgp_attr_desc *d; int len; - if (ATTR_KNOWN(i)) + if (bgp_attr_known(i)) + { + d = &bgp_attr_table[i]; + len = bsprintf(buf, "%s", d->name); + buf += len; + if (d->format) { - d = &bgp_attr_table[i]; - len = bsprintf(buf, "%s", d->name); - buf += len; - if (d->format) - { - *buf++ = ':'; - *buf++ = ' '; - d->format(a, buf, buflen - len - 2); - return GA_FULL; - } - return GA_NAME; + *buf++ = ':'; + *buf++ = ' '; + d->format(a, buf, buflen - len - 2); + return GA_FULL; } - bsprintf(buf, "%02x%s", i, (a->flags & BAF_TRANSITIVE) ? " [t]" : ""); - return GA_NAME; -} - -void -bgp_init_bucket_table(struct bgp_proto *p) -{ - p->hash_size = 256; - p->hash_limit = p->hash_size * 4; - p->bucket_hash = mb_allocz(p->p.pool, p->hash_size * sizeof(struct bgp_bucket *)); - init_list(&p->bucket_queue); - p->withdraw_bucket = NULL; - // fib_init(&p->prefix_fib, p->p.pool, sizeof(struct bgp_prefix), 0, bgp_init_prefix); -} - -void -bgp_free_bucket_table(struct bgp_proto *p) -{ - mb_free(p->bucket_hash); - p->bucket_hash = NULL; - - struct bgp_bucket *b; - WALK_LIST_FIRST(b, p->bucket_queue) - { - rem_node(&b->send_node); - mb_free(b); + return GA_NAME; } - mb_free(p->withdraw_bucket); - p->withdraw_bucket = NULL; + bsprintf(buf, "%02x%s", i, (a->flags & BAF_TRANSITIVE) ? " [t]" : ""); + return GA_NAME; } void @@ -1988,14 +2002,14 @@ bgp_get_route_info(rte *e, byte *buf, ea_list *attrs) buf += bsprintf(buf, "-"); if (e->attrs->hostentry) - { - if (!rte_resolvable(e)) - buf += bsprintf(buf, "/-"); - else if (e->attrs->igp_metric >= IGP_METRIC_UNKNOWN) - buf += bsprintf(buf, "/?"); - else - buf += bsprintf(buf, "/%d", e->attrs->igp_metric); - } + { + if (!rte_resolvable(e)) + buf += bsprintf(buf, "/-"); + else if (e->attrs->igp_metric >= IGP_METRIC_UNKNOWN) + buf += bsprintf(buf, "/?"); + else + buf += bsprintf(buf, "/%d", e->attrs->igp_metric); + } buf += bsprintf(buf, ") ["); if (p && as_path_get_last(p->u.ptr, &origas)) diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c index f706e76e..cccced57 100644 --- a/proto/bgp/bgp.c +++ b/proto/bgp/bgp.c @@ -2,6 +2,8 @@ * BIRD -- The Border Gateway Protocol * * (c) 2000 Martin Mares <mj@ucw.cz> + * (c) 2008--2016 Ondrej Zajicek <santiago@crfreenet.org> + * (c) 2008--2016 CZ.NIC z.s.p.o. * * Can be freely distributed and used under the terms of the GNU GPL. */ @@ -9,48 +11,52 @@ /** * DOC: Border Gateway Protocol * - * The BGP protocol is implemented in three parts: |bgp.c| which takes care of the - * connection and most of the interface with BIRD core, |packets.c| handling + * The BGP protocol is implemented in three parts: |bgp.c| which takes care of + * the connection and most of the interface with BIRD core, |packets.c| handling * both incoming and outgoing BGP packets and |attrs.c| containing functions for * manipulation with BGP attribute lists. * - * As opposed to the other existing routing daemons, BIRD has a sophisticated core - * architecture which is able to keep all the information needed by BGP in the - * primary routing table, therefore no complex data structures like a central - * BGP table are needed. This increases memory footprint of a BGP router with - * many connections, but not too much and, which is more important, it makes - * BGP much easier to implement. + * As opposed to the other existing routing daemons, BIRD has a sophisticated + * core architecture which is able to keep all the information needed by BGP in + * the primary routing table, therefore no complex data structures like a + * central BGP table are needed. This increases memory footprint of a BGP router + * with many connections, but not too much and, which is more important, it + * makes BGP much easier to implement. * - * Each instance of BGP (corresponding to a single BGP peer) is described by a &bgp_proto - * structure to which are attached individual connections represented by &bgp_connection - * (usually, there exists only one connection, but during BGP session setup, there - * can be more of them). The connections are handled according to the BGP state machine - * defined in the RFC with all the timers and all the parameters configurable. + * Each instance of BGP (corresponding to a single BGP peer) is described by a + * &bgp_proto structure to which are attached individual connections represented + * by &bgp_connection (usually, there exists only one connection, but during BGP + * session setup, there can be more of them). The connections are handled + * according to the BGP state machine defined in the RFC with all the timers and + * all the parameters configurable. * - * In incoming direction, we listen on the connection's socket and each time we receive - * some input, we pass it to bgp_rx(). It decodes packet headers and the markers and - * passes complete packets to bgp_rx_packet() which distributes the packet according - * to its type. + * In incoming direction, we listen on the connection's socket and each time we + * receive some input, we pass it to bgp_rx(). It decodes packet headers and the + * markers and passes complete packets to bgp_rx_packet() which distributes the + * packet according to its type. * - * In outgoing direction, we gather all the routing updates and sort them to buckets - * (&bgp_bucket) according to their attributes (we keep a hash table for fast comparison - * of &rta's and a &fib which helps us to find if we already have another route for - * the same destination queued for sending, so that we can replace it with the new one - * immediately instead of sending both updates). There also exists a special bucket holding - * all the route withdrawals which cannot be queued anywhere else as they don't have any - * attributes. If we have any packet to send (due to either new routes or the connection - * tracking code wanting to send a Open, Keepalive or Notification message), we call - * bgp_schedule_packet() which sets the corresponding bit in a @packet_to_send - * bit field in &bgp_conn and as soon as the transmit socket buffer becomes empty, - * we call bgp_fire_tx(). It inspects state of all the packet type bits and calls - * the corresponding bgp_create_xx() functions, eventually rescheduling the same packet - * type if we have more data of the same type to send. + * In outgoing direction, we gather all the routing updates and sort them to + * buckets (&bgp_bucket) according to their attributes (we keep a hash table for + * fast comparison of &rta's and a &fib which helps us to find if we already + * have another route for the same destination queued for sending, so that we + * can replace it with the new one immediately instead of sending both + * updates). There also exists a special bucket holding all the route + * withdrawals which cannot be queued anywhere else as they don't have any + * attributes. If we have any packet to send (due to either new routes or the + * connection tracking code wanting to send a Open, Keepalive or Notification + * message), we call bgp_schedule_packet() which sets the corresponding bit in a + * @packet_to_send bit field in &bgp_conn and as soon as the transmit socket + * buffer becomes empty, we call bgp_fire_tx(). It inspects state of all the + * packet type bits and calls the corresponding bgp_create_xx() functions, + * eventually rescheduling the same packet type if we have more data of the same + * type to send. * - * The processing of attributes consists of two functions: bgp_decode_attrs() for checking - * of the attribute blocks and translating them to the language of BIRD's extended attributes - * and bgp_encode_attrs() which does the converse. Both functions are built around a - * @bgp_attr_table array describing all important characteristics of all known attributes. - * Unknown transitive attributes are attached to the route as %EAF_TYPE_OPAQUE byte streams. + * The processing of attributes consists of two functions: bgp_decode_attrs() + * for checking of the attribute blocks and translating them to the language of + * BIRD's extended attributes and bgp_encode_attrs() which does the + * converse. Both functions are built around a @bgp_attr_table array describing + * all important characteristics of all known attributes. Unknown transitive + * attributes are attached to the route as %EAF_TYPE_OPAQUE byte streams. * * BGP protocol implements graceful restart in both restarting (local restart) * and receiving (neighbor restart) roles. The first is handled mostly by the @@ -61,7 +67,39 @@ * point of view and therefore maintaining received routes. Routing table * refresh cycle (rt_refresh_begin(), rt_refresh_end()) is used for removing * stale routes after reestablishment of BGP session during graceful restart. - */ + * + * Supported standards: + * <itemize> + * <item> <rfc id="4271"> - Border Gateway Protocol 4 (BGP) + * <item> <rfc id="1997"> - BGP Communities Attribute + * <item> <rfc id="2385"> - Protection of BGP Sessions via TCP MD5 Signature + * <item> <rfc id="2545"> - Use of BGP Multiprotocol Extensions for IPv6 + * <item> <rfc id="2918"> - Route Refresh Capability + * <item> <rfc id="3107"> - Carrying Label Information in BGP + * <item> <rfc id="4360"> - BGP Extended Communities Attribute + * <item> <rfc id="4364"> - BGP/MPLS IPv4 Virtual Private Networks + * <item> <rfc id="4456"> - BGP Route Reflection + * <item> <rfc id="4486"> - Subcodes for BGP Cease Notification Message + * <item> <rfc id="4659"> - BGP/MPLS IPv6 Virtual Private Networks + * <item> <rfc id="4724"> - Graceful Restart Mechanism for BGP + * <item> <rfc id="4760"> - Multiprotocol extensions for BGP + * <item> <rfc id="4798"> - Connecting IPv6 Islands over IPv4 MPLS + * <item> <rfc id="5065"> - AS confederations for BGP + * <item> <rfc id="5082"> - Generalized TTL Security Mechanism + * <item> <rfc id="5492"> - Capabilities Advertisement with BGP + * <item> <rfc id="5549"> - Advertising IPv4 NLRI with an IPv6 Next Hop + * <item> <rfc id="5575"> - Dissemination of Flow Specification Rules + * <item> <rfc id="5668"> - 4-Octet AS Specific BGP Extended Community + * <item> <rfc id="6286"> - AS-Wide Unique BGP Identifier + * <item> <rfc id="6608"> - Subcodes for BGP Finite State Machine Error + * <item> <rfc id="6793"> - BGP Support for 4-Octet AS Numbers + * <item> <rfc id="7313"> - Enhanced Route Refresh Capability for BGP + * <item> <rfc id="7606"> - Revised Error Handling for BGP UPDATE Messages + * <item> <rfc id="7911"> - Advertisement of Multiple Paths in BGP + * <item> <rfc id="7947"> - Internet Exchange BGP Route Server + * <item> <rfc id="8092"> - BGP Large Communities Attribute + * </itemize> +*/ #undef LOCAL_DEBUG @@ -80,70 +118,150 @@ struct linpool *bgp_linpool; /* Global temporary pool */ -static sock *bgp_listen_sk; /* Global listening socket */ -static int bgp_counter; /* Number of protocol instances using the listening socket */ +struct linpool *bgp_linpool2; /* Global temporary pool for bgp_rt_notify() */ +static list bgp_sockets; /* Global list of listening sockets */ + -static void bgp_close(struct bgp_proto *p, int apply_md5); static void bgp_connect(struct bgp_proto *p); static void bgp_active(struct bgp_proto *p); -static sock *bgp_setup_listen_sk(ip_addr addr, unsigned port, u32 flags); static void bgp_update_bfd(struct bgp_proto *p, int use_bfd); +static int bgp_incoming_connection(sock *sk, uint dummy UNUSED); +static void bgp_listen_sock_err(sock *sk UNUSED, int err); /** * bgp_open - open a BGP instance * @p: BGP instance * - * This function allocates and configures shared BGP resources. - * Should be called as the last step during initialization - * (when lock is acquired and neighbor is ready). - * When error, state changed to PS_DOWN, -1 is returned and caller - * should return immediately. + * This function allocates and configures shared BGP resources, mainly listening + * sockets. Should be called as the last step during initialization (when lock + * is acquired and neighbor is ready). When error, caller should change state to + * PS_DOWN and return immediately. */ static int bgp_open(struct bgp_proto *p) { - struct config *cfg = p->cf->c.global; - int errcode; + 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 : + (ipa_is_ip4(p->cf->remote_ip) ? IPA_NONE4 : IPA_NONE6); + uint port = p->cf->local_port; - if (!bgp_listen_sk) - bgp_listen_sk = bgp_setup_listen_sk(cfg->listen_bgp_addr, cfg->listen_bgp_port, cfg->listen_bgp_flags); + /* FIXME: Add some global init? */ + if (!bgp_linpool) + init_list(&bgp_sockets); - if (!bgp_listen_sk) + /* We assume that cf->iface is defined iff cf->local_ip is link-local */ + + WALK_LIST(bs, bgp_sockets) + if (ipa_equal(bs->sk->saddr, addr) && (bs->sk->iface == ifa) && (bs->sk->sport == port)) { - errcode = BEM_NO_SOCKET; - goto err; + bs->uc++; + p->sock = bs; + return 0; } - if (!bgp_linpool) - bgp_linpool = lp_new(&root_pool, 4080); + sock *sk = sk_new(proto_pool); + sk->type = SK_TCP_PASSIVE; + sk->ttl = 255; + sk->saddr = addr; + sk->sport = port; + sk->flags = 0; + sk->tos = IP_PREC_INTERNET_CONTROL; + sk->rbsize = BGP_RX_BUFFER_SIZE; + sk->tbsize = BGP_TX_BUFFER_SIZE; + sk->rx_hook = bgp_incoming_connection; + sk->err_hook = bgp_listen_sock_err; + + if (sk_open(sk) < 0) + goto err; - bgp_counter++; + bs = mb_allocz(proto_pool, sizeof(struct bgp_socket)); + bs->sk = sk; + bs->uc = 1; + p->sock = bs; - if (p->cf->password) - if (sk_set_md5_auth(bgp_listen_sk, p->cf->source_addr, p->cf->remote_ip, - p->cf->iface, p->cf->password, p->cf->setkey) < 0) - { - sk_log_error(bgp_listen_sk, p->p.name); - bgp_close(p, 0); - errcode = BEM_INVALID_MD5; - goto err; - } + 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: - p->p.disabled = 1; - bgp_store_error(p, NULL, BE_MISC, errcode); - proto_notify_state(&p->p, PS_DOWN); + sk_log_error(sk, p->p.name); + log(L_ERR "%s: Cannot open listening socket", p->p.name); + rfree(sk); return -1; } +/** + * bgp_close - close a BGP instance + * @p: BGP instance + * + * This function frees and deconfigures shared BGP resources. + */ +static void +bgp_close(struct bgp_proto *p) +{ + struct bgp_socket *bs = p->sock; + + ASSERT(bs && bs->uc); + + if (--bs->uc) + return; + + 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 +bgp_setup_auth(struct bgp_proto *p, int enable) +{ + if (p->cf->password) + { + int rv = sk_set_md5_auth(p->sock->sk, + p->cf->local_ip, p->cf->remote_ip, p->cf->iface, + enable ? p->cf->password : NULL, p->cf->setkey); + + if (rv < 0) + sk_log_error(p->sock->sk, p->p.name); + + return rv; + } + else + return 0; +} + +static inline struct bgp_channel * +bgp_find_channel(struct bgp_proto *p, u32 afi) +{ + struct bgp_channel *c; + WALK_LIST(c, p->p.channels) + if (c->afi == afi) + return c; + + return NULL; +} + static void bgp_startup(struct bgp_proto *p) { BGP_TRACE(D_EVENTS, "Started"); - p->start_state = p->cf->capabilities ? BSS_CONNECT : BSS_CONNECT_NOCAP; + p->start_state = BSS_CONNECT; if (!p->cf->passive) bgp_active(p); @@ -159,50 +277,36 @@ bgp_startup_timeout(timer *t) static void bgp_initiate(struct bgp_proto *p) { - int rv = bgp_open(p); - if (rv < 0) - return; + int err_val; + + if (bgp_open(p) < 0) + { err_val = BEM_NO_SOCKET; goto err1; } + + if (bgp_setup_auth(p, 1) < 0) + { err_val = BEM_INVALID_MD5; goto err2; } if (p->cf->bfd) bgp_update_bfd(p, p->cf->bfd); if (p->startup_delay) - { - 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); - } + { + 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); + } else bgp_startup(p); -} -/** - * bgp_close - close a BGP instance - * @p: BGP instance - * @apply_md5: 0 to disable unsetting MD5 auth - * - * This function frees and deconfigures shared BGP resources. - * @apply_md5 is set to 0 when bgp_close is called as a cleanup - * from failed bgp_open(). - */ -static void -bgp_close(struct bgp_proto *p, int apply_md5) -{ - ASSERT(bgp_counter); - bgp_counter--; + return; - if (p->cf->password && apply_md5) - if (sk_set_md5_auth(bgp_listen_sk, p->cf->source_addr, p->cf->remote_ip, - p->cf->iface, NULL, p->cf->setkey) < 0) - sk_log_error(bgp_listen_sk, p->p.name); +err2: + bgp_close(p); +err1: + p->p.disabled = 1; + bgp_store_error(p, NULL, BE_MISC, err_val); + proto_notify_state(&p->p, PS_DOWN); - if (!bgp_counter) - { - rfree(bgp_listen_sk); - bgp_listen_sk = NULL; - rfree(bgp_linpool); - bgp_linpool = NULL; - } + return; } /** @@ -210,19 +314,19 @@ bgp_close(struct bgp_proto *p, int apply_md5) * @t: timer * @value: time to fire (0 to disable the timer) * - * This functions calls tm_start() on @t with time @value and the - * amount of randomization suggested by the BGP standard. Please use - * it for all BGP timers. + * This functions calls tm_start() on @t with time @value and the amount of + * randomization suggested by the BGP standard. Please use it for all BGP + * timers. */ void bgp_start_timer(timer *t, int value) { if (value) - { - /* The randomization procedure is specified in RFC 1771: 9.2.3.3 */ - t->randomize = value / 4; - tm_start(t, value - t->randomize); - } + { + /* The randomization procedure is specified in RFC 1771: 9.2.3.3 */ + t->randomize = value / 4; + tm_start(t, value - t->randomize); + } else tm_stop(t); } @@ -231,8 +335,8 @@ bgp_start_timer(timer *t, int value) * bgp_close_conn - close a BGP connection * @conn: connection to close * - * This function takes a connection described by the &bgp_conn structure, - * closes its socket and frees all resources associated with it. + * This function takes a connection described by the &bgp_conn structure, closes + * its socket and frees all resources associated with it. */ void bgp_close_conn(struct bgp_conn *conn) @@ -241,16 +345,22 @@ bgp_close_conn(struct bgp_conn *conn) DBG("BGP: Closing connection\n"); conn->packets_to_send = 0; - rfree(conn->connect_retry_timer); - conn->connect_retry_timer = NULL; + conn->channels_to_send = 0; + rfree(conn->connect_timer); + conn->connect_timer = NULL; rfree(conn->keepalive_timer); conn->keepalive_timer = NULL; rfree(conn->hold_timer); conn->hold_timer = NULL; - rfree(conn->sk); - conn->sk = NULL; rfree(conn->tx_ev); conn->tx_ev = NULL; + rfree(conn->sk); + conn->sk = NULL; + + mb_free(conn->local_caps); + conn->local_caps = NULL; + mb_free(conn->remote_caps); + conn->remote_caps = NULL; } @@ -258,9 +368,9 @@ bgp_close_conn(struct bgp_conn *conn) * bgp_update_startup_delay - update a startup delay * @p: BGP instance * - * This function updates a startup delay that is used to postpone next BGP connect. - * It also handles disable_after_error and might stop BGP instance when error - * happened and disable_after_error is on. + * This function updates a startup delay that is used to postpone next BGP + * connect. It also handles disable_after_error and might stop BGP instance + * when error happened and disable_after_error is on. * * It should be called when BGP protocol error happened. */ @@ -277,11 +387,11 @@ bgp_update_startup_delay(struct bgp_proto *p) p->last_proto_error = now; if (cf->disable_after_error) - { - p->startup_delay = 0; - p->p.disabled = 1; - return; - } + { + p->startup_delay = 0; + p->p.disabled = 1; + return; + } if (!p->startup_delay) p->startup_delay = cf->error_delay_time_min; @@ -290,32 +400,38 @@ bgp_update_startup_delay(struct bgp_proto *p) } static void -bgp_graceful_close_conn(struct bgp_conn *conn, unsigned subcode) +bgp_graceful_close_conn(struct bgp_conn *conn, uint subcode) { switch (conn->state) - { - case BS_IDLE: - case BS_CLOSE: - return; - case BS_CONNECT: - case BS_ACTIVE: - bgp_conn_enter_idle_state(conn); - return; - case BS_OPENSENT: - case BS_OPENCONFIRM: - case BS_ESTABLISHED: - bgp_error(conn, 6, subcode, NULL, 0); - return; - default: - bug("bgp_graceful_close_conn: Unknown state %d", conn->state); - } + { + case BS_IDLE: + case BS_CLOSE: + return; + + case BS_CONNECT: + case BS_ACTIVE: + bgp_conn_enter_idle_state(conn); + return; + + case BS_OPENSENT: + case BS_OPENCONFIRM: + case BS_ESTABLISHED: + bgp_error(conn, 6, subcode, NULL, 0); + return; + + default: + bug("bgp_graceful_close_conn: Unknown state %d", conn->state); + } } static void bgp_down(struct bgp_proto *p) { if (p->start_state > BSS_PREPARE) - bgp_close(p, 1); + { + bgp_setup_auth(p, 0); + bgp_close(p); + } BGP_TRACE(D_EVENTS, "Down"); proto_notify_state(&p->p, PS_DOWN); @@ -327,20 +443,20 @@ bgp_decision(void *vp) struct bgp_proto *p = vp; DBG("BGP: Decision start\n"); - if ((p->p.proto_state == PS_START) - && (p->outgoing_conn.state == BS_IDLE) - && (p->incoming_conn.state != BS_OPENCONFIRM) - && (!p->cf->passive)) + if ((p->p.proto_state == PS_START) && + (p->outgoing_conn.state == BS_IDLE) && + (p->incoming_conn.state != BS_OPENCONFIRM) && + !p->cf->passive) bgp_active(p); - if ((p->p.proto_state == PS_STOP) - && (p->outgoing_conn.state == BS_IDLE) - && (p->incoming_conn.state == BS_IDLE)) + if ((p->p.proto_state == PS_STOP) && + (p->outgoing_conn.state == BS_IDLE) && + (p->incoming_conn.state == BS_IDLE)) bgp_down(p); } void -bgp_stop(struct bgp_proto *p, unsigned subcode) +bgp_stop(struct bgp_proto *p, uint subcode) { proto_notify_state(&p->p, PS_STOP); bgp_graceful_close_conn(&p->outgoing_conn, subcode); @@ -349,7 +465,7 @@ bgp_stop(struct bgp_proto *p, unsigned subcode) } static inline void -bgp_conn_set_state(struct bgp_conn *conn, unsigned new_state) +bgp_conn_set_state(struct bgp_conn *conn, uint new_state) { if (conn->bgp->p.mrtdump & MD_STATES) mrt_dump_bgp_state_change(conn, conn->state, new_state); @@ -364,13 +480,17 @@ bgp_conn_enter_openconfirm_state(struct bgp_conn *conn) bgp_conn_set_state(conn, BS_OPENCONFIRM); } +static const struct bgp_af_caps dummy_af_caps = { }; + void bgp_conn_enter_established_state(struct bgp_conn *conn) { struct bgp_proto *p = conn->bgp; + struct bgp_caps *local = conn->local_caps; + struct bgp_caps *peer = conn->remote_caps; + struct bgp_channel *c; BGP_TRACE(D_EVENTS, "BGP session established"); - DBG("BGP: UP!!!\n"); /* For multi-hop BGP sessions */ if (ipa_zero(p->source_addr)) @@ -381,30 +501,92 @@ bgp_conn_enter_established_state(struct bgp_conn *conn) p->conn = conn; p->last_error_class = 0; p->last_error_code = 0; - p->feed_state = BFS_NONE; - p->load_state = BFS_NONE; - bgp_init_bucket_table(p); - bgp_init_prefix_table(p, 8); - int peer_gr_ready = conn->peer_gr_aware && !(conn->peer_gr_flags & BGP_GRF_RESTART); + p->as4_session = conn->as4_session; - if (p->p.gr_recovery && !peer_gr_ready) - proto_graceful_restart_unlock(&p->p); + p->route_refresh = peer->route_refresh; + p->enhanced_refresh = local->enhanced_refresh && peer->enhanced_refresh; - if (p->p.gr_recovery && (p->cf->gr_mode == BGP_GR_ABLE) && peer_gr_ready) - p->p.gr_wait = 1; + /* Whether we may handle possible GR of peer (it has some AF GR-able) */ + p->gr_ready = 0; /* Updated later */ - if (p->gr_active) + /* Whether peer is ready to handle our GR recovery */ + int peer_gr_ready = peer->gr_aware && !(peer->gr_flags & BGP_GRF_RESTART); + + if (p->gr_active_num) tm_stop(p->gr_timer); - if (p->gr_active && (!conn->peer_gr_able || !(conn->peer_gr_aflags & BGP_GRF_FORWARDING))) - bgp_graceful_restart_done(p); + /* Number of active channels */ + int num = 0; + + WALK_LIST(c, p->p.channels) + { + const struct bgp_af_caps *loc = bgp_find_af_caps(local, c->afi); + const struct bgp_af_caps *rem = bgp_find_af_caps(peer, c->afi); + + /* Ignore AFIs that were not announced in multiprotocol capability */ + if (!loc || !loc->ready) + loc = &dummy_af_caps; + + if (!rem || !rem->ready) + rem = &dummy_af_caps; + + int active = loc->ready && rem->ready; + c->c.disabled = !active; + c->c.reloadable = p->route_refresh; + + c->index = active ? num++ : 0; + + c->feed_state = BFS_NONE; + c->load_state = BFS_NONE; + + /* Channels where peer may do GR */ + c->gr_ready = active && local->gr_aware && rem->gr_able; + p->gr_ready = p->gr_ready || c->gr_ready; + + /* Channels not able to recover gracefully */ + if (p->p.gr_recovery && (!active || !peer_gr_ready)) + channel_graceful_restart_unlock(&c->c); + + /* Channels waiting for local convergence */ + if (p->p.gr_recovery && loc->gr_able && peer_gr_ready) + c->c.gr_wait = 1; + + /* Channels where peer is not able to recover gracefully */ + if (c->gr_active && ! (c->gr_ready && (rem->gr_af_flags & BGP_GRF_FORWARDING))) + bgp_graceful_restart_done(c); + + /* GR capability implies that neighbor will send End-of-RIB */ + if (peer->gr_aware) + c->load_state = BFS_LOADING; + + c->ext_next_hop = c->cf->ext_next_hop && (bgp_channel_is_ipv6(c) || rem->ext_next_hop); + c->add_path_rx = (loc->add_path & BGP_ADD_PATH_RX) && (rem->add_path & BGP_ADD_PATH_TX); + c->add_path_tx = (loc->add_path & BGP_ADD_PATH_TX) && (rem->add_path & BGP_ADD_PATH_RX); - /* GR capability implies that neighbor will send End-of-RIB */ - if (conn->peer_gr_aware) - p->load_state = BFS_LOADING; + /* Update RA mode */ + if (c->add_path_tx) + c->c.ra_mode = RA_ANY; + else if (c->cf->secondary) + c->c.ra_mode = RA_ACCEPTED; + else + c->c.ra_mode = RA_OPTIMAL; + } + + p->afi_map = mb_alloc(p->p.pool, num * sizeof(u32)); + p->channel_map = mb_alloc(p->p.pool, num * sizeof(void *)); + p->channel_count = num; - /* proto_notify_state() will likely call bgp_feed_begin(), setting p->feed_state */ + WALK_LIST(c, p->p.channels) + { + if (c->c.disabled) + continue; + + p->afi_map[c->index] = c->afi; + p->channel_map[c->index] = c; + } + + /* proto_notify_state() will likely call bgp_feed_begin(), setting c->feed_state */ bgp_conn_set_state(conn, BS_ESTABLISHED); proto_notify_state(&p->p, PS_UP); @@ -416,8 +598,9 @@ bgp_conn_leave_established_state(struct bgp_proto *p) BGP_TRACE(D_EVENTS, "BGP session closed"); p->conn = NULL; - bgp_free_prefix_table(p); - bgp_free_bucket_table(p); + // XXXX free these tables to avoid memory leak during graceful restart + // bgp_free_prefix_table(p); + // bgp_free_bucket_table(p); if (p->p.proto_state == PS_UP) bgp_stop(p, 0); @@ -471,34 +654,57 @@ bgp_handle_graceful_restart(struct bgp_proto *p) ASSERT(p->conn && (p->conn->state == BS_ESTABLISHED) && p->gr_ready); BGP_TRACE(D_EVENTS, "Neighbor graceful restart detected%s", - p->gr_active ? " - already pending" : ""); - proto_notify_state(&p->p, PS_START); + p->gr_active_num ? " - already pending" : ""); - if (p->gr_active) - rt_refresh_end(p->p.main_ahook->table, p->p.main_ahook); + p->gr_active_num = 0; - p->gr_active = 1; - bgp_start_timer(p->gr_timer, p->conn->peer_gr_time); - rt_refresh_begin(p->p.main_ahook->table, p->p.main_ahook); + struct bgp_channel *c; + WALK_LIST(c, p->p.channels) + { + if (c->gr_ready) + { + if (c->gr_active) + rt_refresh_end(c->c.table, &c->c); + + c->gr_active = 1; + p->gr_active_num++; + rt_refresh_begin(c->c.table, &c->c); + } + else + { + /* Just flush the routes */ + rt_refresh_begin(c->c.table, &c->c); + rt_refresh_end(c->c.table, &c->c); + } + } + + proto_notify_state(&p->p, PS_START); + bgp_start_timer(p->gr_timer, p->conn->local_caps->gr_time); } /** * bgp_graceful_restart_done - finish active BGP graceful restart - * @p: BGP instance + * @c: BGP channel * * This function is called when the active BGP graceful restart of the neighbor - * should be finished - either successfully (the neighbor sends all paths and - * reports end-of-RIB on the new session) or unsuccessfully (the neighbor does - * not support BGP graceful restart on the new session). The function ends - * routing table refresh cycle and stops BGP restart timer. + * should be finished for channel @c - either successfully (the neighbor sends + * all paths and reports end-of-RIB for given AFI/SAFI on the new session) or + * unsuccessfully (the neighbor does not support BGP graceful restart on the new + * session). The function ends the routing table refresh cycle. */ void -bgp_graceful_restart_done(struct bgp_proto *p) +bgp_graceful_restart_done(struct bgp_channel *c) { - BGP_TRACE(D_EVENTS, "Neighbor graceful restart done"); - p->gr_active = 0; - tm_stop(p->gr_timer); - rt_refresh_end(p->p.main_ahook->table, p->p.main_ahook); + struct bgp_proto *p = (void *) c->c.proto; + + ASSERT(c->gr_active); + c->gr_active = 0; + p->gr_active_num--; + + if (!p->gr_active_num) + BGP_TRACE(D_EVENTS, "Neighbor graceful restart done"); + + rt_refresh_end(c->c.table, &c->c); } /** @@ -522,7 +728,7 @@ bgp_graceful_restart_timeout(timer *t) /** * bgp_refresh_begin - start incoming enhanced route refresh sequence - * @p: BGP instance + * @c: BGP channel * * This function is called when an incoming enhanced route refresh sequence is * started by the neighbor, demarcated by the BoRR packet. The function updates @@ -531,18 +737,20 @@ bgp_graceful_restart_timeout(timer *t) * ensure that these two sequences do not overlap. */ void -bgp_refresh_begin(struct bgp_proto *p) +bgp_refresh_begin(struct bgp_channel *c) { - if (p->load_state == BFS_LOADING) - { log(L_WARN "%s: BEGIN-OF-RR received before END-OF-RIB, ignoring", p->p.name); return; } + struct bgp_proto *p = (void *) c->c.proto; - p->load_state = BFS_REFRESHING; - rt_refresh_begin(p->p.main_ahook->table, p->p.main_ahook); + 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); } /** * bgp_refresh_end - finish incoming enhanced route refresh sequence - * @p: BGP instance + * @c: BGP channel * * This function is called when an incoming enhanced route refresh sequence is * finished by the neighbor, demarcated by the EoRR packet. The function updates @@ -550,39 +758,26 @@ bgp_refresh_begin(struct bgp_proto *p) * during the sequence are removed by the nest. */ void -bgp_refresh_end(struct bgp_proto *p) +bgp_refresh_end(struct bgp_channel *c) { - if (p->load_state != BFS_REFRESHING) - { log(L_WARN "%s: END-OF-RR received without prior BEGIN-OF-RR, ignoring", p->p.name); return; } + struct bgp_proto *p = (void *) c->c.proto; - p->load_state = BFS_NONE; - rt_refresh_end(p->p.main_ahook->table, p->p.main_ahook); + if (c->load_state != BFS_REFRESHING) + { 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); } static void bgp_send_open(struct bgp_conn *conn) { - conn->start_state = conn->bgp->start_state; - - // Default values, possibly changed by receiving capabilities. - conn->advertised_as = 0; - conn->peer_refresh_support = 0; - conn->peer_as4_support = 0; - conn->peer_add_path = 0; - conn->peer_enhanced_refresh_support = 0; - conn->peer_gr_aware = 0; - conn->peer_gr_able = 0; - conn->peer_gr_time = 0; - conn->peer_gr_flags = 0; - conn->peer_gr_aflags = 0; - conn->peer_ext_messages_support = 0; - DBG("BGP: Sending open\n"); conn->sk->rx_hook = bgp_rx; conn->sk->tx_hook = bgp_tx; - tm_stop(conn->connect_retry_timer); - bgp_schedule_packet(conn, PKT_OPEN); + tm_stop(conn->connect_timer); + 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); } @@ -605,10 +800,10 @@ bgp_connect_timeout(timer *t) DBG("BGP: connect_timeout\n"); if (p->p.proto_state == PS_START) - { - bgp_close_conn(conn); - bgp_connect(p); - } + { + bgp_close_conn(conn); + bgp_connect(p); + } else bgp_conn_enter_idle_state(conn); } @@ -672,7 +867,7 @@ bgp_keepalive_timeout(timer *t) struct bgp_conn *conn = t->data; DBG("BGP: Keepalive timer\n"); - bgp_schedule_packet(conn, PKT_KEEPALIVE); + bgp_schedule_packet(conn, NULL, PKT_KEEPALIVE); /* Kick TX a bit faster */ if (ev_active(conn->tx_ev)) @@ -682,21 +877,18 @@ bgp_keepalive_timeout(timer *t) static void bgp_setup_conn(struct bgp_proto *p, struct bgp_conn *conn) { - timer *t; - conn->sk = NULL; conn->bgp = p; + conn->packets_to_send = 0; + conn->channels_to_send = 0; + conn->last_channel = 0; + conn->last_channel_count = 0; + + conn->connect_timer = tm_new_set(p->p.pool, bgp_connect_timeout, conn, 0, 0); + conn->hold_timer = tm_new_set(p->p.pool, bgp_hold_timeout, conn, 0, 0); + conn->keepalive_timer = tm_new_set(p->p.pool, bgp_keepalive_timeout, conn, 0, 0); - t = conn->connect_retry_timer = tm_new(p->p.pool); - t->hook = bgp_connect_timeout; - t->data = conn; - t = conn->hold_timer = tm_new(p->p.pool); - t->hook = bgp_hold_timeout; - t->data = conn; - t = conn->keepalive_timer = tm_new(p->p.pool); - t->hook = bgp_keepalive_timeout; - t->data = conn; conn->tx_ev = ev_new(p->p.pool); conn->tx_ev->hook = bgp_kick_tx; conn->tx_ev->data = conn; @@ -720,7 +912,7 @@ bgp_active(struct bgp_proto *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_retry_timer, delay); + bgp_start_timer(conn->connect_timer, delay); } /** @@ -734,12 +926,11 @@ bgp_active(struct bgp_proto *p) static void bgp_connect(struct bgp_proto *p) /* Enter Connect state and start establishing connection */ { - sock *s; struct bgp_conn *conn = &p->outgoing_conn; int hops = p->cf->multihop ? : 1; DBG("BGP: Connecting\n"); - s = sk_new(p->p.pool); + sock *s = sk_new(p->p.pool); s->type = SK_TCP_ACTIVE; s->saddr = p->source_addr; s->daddr = p->cf->remote_ip; @@ -766,10 +957,10 @@ 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_retry_timer, p->cf->connect_retry_time); + bgp_start_timer(conn->connect_timer, p->cf->connect_retry_time); return; - err: +err: sk_log_error(s, p->p.name); bgp_sock_err(s, 0); return; @@ -783,16 +974,15 @@ bgp_connect(struct bgp_proto *p) /* Enter Connect state and start establishing c static struct bgp_proto * bgp_find_proto(sock *sk) { - struct proto_config *pc; + struct bgp_proto *p; - WALK_LIST(pc, config->protos) - if ((pc->protocol == &proto_bgp) && pc->proto) - { - struct bgp_proto *p = (struct bgp_proto *) pc->proto; - if (ipa_equal(p->cf->remote_ip, sk->daddr) && - (!p->cf->iface || (p->cf->iface == sk->iface))) - return p; - } + WALK_LIST(p, proto_list) + if ((p->p.proto == &proto_bgp) && + ipa_equal(p->cf->remote_ip, sk->daddr) && + (!p->cf->iface || (p->cf->iface == sk->iface)) && + (ipa_zero(p->cf->local_ip) || ipa_equal(p->cf->local_ip, sk->saddr)) && + (p->cf->local_port == sk->sport)) + return p; return NULL; } @@ -818,12 +1008,12 @@ bgp_incoming_connection(sock *sk, uint dummy UNUSED) DBG("BGP: Incoming connection from %I port %d\n", sk->daddr, sk->dport); p = bgp_find_proto(sk); if (!p) - { - log(L_WARN "BGP: Unexpected connect from unknown address %I%J (port %d)", - sk->daddr, ipa_is_link_local(sk->daddr) ? sk->iface : NULL, sk->dport); - rfree(sk); - return 0; - } + { + log(L_WARN "BGP: Unexpected connect from unknown address %I%J (port %d)", + sk->daddr, ipa_is_link_local(sk->daddr) ? sk->iface : NULL, sk->dport); + rfree(sk); + return 0; + } /* * BIRD should keep multiple incoming connections in OpenSent state (for @@ -836,26 +1026,26 @@ bgp_incoming_connection(sock *sk, uint dummy UNUSED) (p->start_state >= BSS_CONNECT) && (!p->incoming_conn.sk); if (p->conn && (p->conn->state == BS_ESTABLISHED) && p->gr_ready) - { - bgp_store_error(p, NULL, BE_MISC, BEM_GRACEFUL_RESTART); - bgp_handle_graceful_restart(p); - bgp_conn_enter_idle_state(p->conn); - acc = 1; - - /* There might be separate incoming connection in OpenSent state */ - if (p->incoming_conn.state > BS_ACTIVE) - bgp_close_conn(&p->incoming_conn); - } + { + bgp_store_error(p, NULL, BE_MISC, BEM_GRACEFUL_RESTART); + bgp_handle_graceful_restart(p); + bgp_conn_enter_idle_state(p->conn); + acc = 1; + + /* There might be separate incoming connection in OpenSent state */ + if (p->incoming_conn.state > BS_ACTIVE) + bgp_close_conn(&p->incoming_conn); + } BGP_TRACE(D_EVENTS, "Incoming connection from %I%J (port %d) %s", sk->daddr, ipa_is_link_local(sk->daddr) ? sk->iface : NULL, sk->dport, acc ? "accepted" : "rejected"); if (!acc) - { - rfree(sk); - return 0; - } + { + rfree(sk); + return 0; + } hops = p->cf->multihop ? : 1; @@ -867,11 +1057,11 @@ bgp_incoming_connection(sock *sk, uint dummy UNUSED) goto err; if (p->cf->enable_extended_messages) - { - sk->rbsize = BGP_RX_BUFFER_EXT_SIZE; - sk->tbsize = BGP_TX_BUFFER_EXT_SIZE; - sk_reallocate(sk); - } + { + sk->rbsize = BGP_RX_BUFFER_EXT_SIZE; + sk->tbsize = BGP_TX_BUFFER_EXT_SIZE; + sk_reallocate(sk); + } bgp_setup_conn(p, &p->incoming_conn); bgp_setup_sk(&p->incoming_conn, sk); @@ -894,34 +1084,6 @@ bgp_listen_sock_err(sock *sk UNUSED, int err) log(L_ERR "BGP: Error on listening socket: %M", err); } -static sock * -bgp_setup_listen_sk(ip_addr addr, unsigned port, u32 flags) -{ - sock *s = sk_new(&root_pool); - DBG("BGP: Creating listening socket\n"); - s->type = SK_TCP_PASSIVE; - s->ttl = 255; - s->saddr = addr; - s->sport = port ? port : BGP_PORT; - s->flags = flags ? 0 : SKF_V6ONLY; - s->tos = IP_PREC_INTERNET_CONTROL; - s->rbsize = BGP_RX_BUFFER_SIZE; - s->tbsize = BGP_TX_BUFFER_SIZE; - s->rx_hook = bgp_incoming_connection; - s->err_hook = bgp_listen_sock_err; - - if (sk_open(s) < 0) - goto err; - - return s; - - err: - sk_log_error(s, "BGP"); - log(L_ERR "BGP: Cannot open listening socket"); - rfree(s); - return NULL; -} - static void bgp_start_neighbor(struct bgp_proto *p) { @@ -930,23 +1092,21 @@ bgp_start_neighbor(struct bgp_proto *p) if (ipa_zero(p->source_addr)) p->source_addr = p->neigh->ifa->ip; -#ifdef IPV6 + if (ipa_is_link_local(p->source_addr)) + p->link_addr = p->source_addr; + else { + /* Find some link-local address for given iface */ struct ifa *a; - p->local_link = IPA_NONE; WALK_LIST(a, p->neigh->iface->addrs) if (a->scope == SCOPE_LINK) - { - p->local_link = a->ip; - break; - } - - if (! ipa_nonzero(p->local_link)) - log(L_WARN "%s: Missing link local address on interface %s", p->p.name, p->neigh->iface->name); + { + p->link_addr = a->ip; + break; + } - DBG("BGP: Selected link-level address %I\n", p->local_link); + DBG("%s: Selected link-local address %I\n", p->p.name, p->link_addr); } -#endif bgp_initiate(p); } @@ -966,34 +1126,34 @@ bgp_neigh_notify(neighbor *n) int prepare = (ps == PS_START) && (p->start_state == BSS_PREPARE); if (n->scope <= 0) + { + if (!prepare) { - if (!prepare) - { - BGP_TRACE(D_EVENTS, "Neighbor lost"); - bgp_store_error(p, NULL, BE_MISC, BEM_NEIGHBOR_LOST); - /* Perhaps also run bgp_update_startup_delay(p)? */ - bgp_stop(p, 0); - } + BGP_TRACE(D_EVENTS, "Neighbor lost"); + bgp_store_error(p, NULL, BE_MISC, BEM_NEIGHBOR_LOST); + /* Perhaps also run bgp_update_startup_delay(p)? */ + bgp_stop(p, 0); } + } else if (p->cf->check_link && !(n->iface->flags & IF_LINK_UP)) + { + if (!prepare) { - if (!prepare) - { - BGP_TRACE(D_EVENTS, "Link down"); - bgp_store_error(p, NULL, BE_MISC, BEM_LINK_DOWN); - if (ps == PS_UP) - bgp_update_startup_delay(p); - bgp_stop(p, 0); - } + BGP_TRACE(D_EVENTS, "Link down"); + bgp_store_error(p, NULL, BE_MISC, BEM_LINK_DOWN); + if (ps == PS_UP) + bgp_update_startup_delay(p); + bgp_stop(p, 0); } + } else + { + if (prepare) { - if (prepare) - { - BGP_TRACE(D_EVENTS, "Neighbor ready"); - bgp_start_neighbor(p); - } + BGP_TRACE(D_EVENTS, "Neighbor ready"); + bgp_start_neighbor(p); } + } } static void @@ -1003,13 +1163,13 @@ bgp_bfd_notify(struct bfd_request *req) int ps = p->p.proto_state; if (req->down && ((ps == PS_START) || (ps == PS_UP))) - { - BGP_TRACE(D_EVENTS, "BFD session down"); - bgp_store_error(p, NULL, BE_MISC, BEM_BFD_DOWN); - if (ps == PS_UP) - bgp_update_startup_delay(p); - bgp_stop(p, 0); - } + { + BGP_TRACE(D_EVENTS, "BFD session down"); + bgp_store_error(p, NULL, BE_MISC, BEM_BFD_DOWN); + if (ps == PS_UP) + bgp_update_startup_delay(p); + bgp_stop(p, 0); + } } static void @@ -1021,71 +1181,72 @@ bgp_update_bfd(struct bgp_proto *p, int use_bfd) bgp_bfd_notify, p); if (!use_bfd && p->bfd_req) - { - rfree(p->bfd_req); - p->bfd_req = NULL; - } + { + rfree(p->bfd_req); + p->bfd_req = NULL; + } } -static int -bgp_reload_routes(struct proto *P) +static void +bgp_reload_routes(struct channel *C) { - struct bgp_proto *p = (struct bgp_proto *) P; - if (!p->conn || !p->conn->peer_refresh_support) - return 0; + struct bgp_proto *p = (void *) C->proto; + struct bgp_channel *c = (void *) C; - bgp_schedule_packet(p->conn, PKT_ROUTE_REFRESH); - return 1; + ASSERT(p->conn && p->route_refresh); + + bgp_schedule_packet(p->conn, c, PKT_ROUTE_REFRESH); } static void -bgp_feed_begin(struct proto *P, int initial) +bgp_feed_begin(struct channel *C, int initial) { - struct bgp_proto *p = (struct bgp_proto *) P; + struct bgp_proto *p = (void *) C->proto; + struct bgp_channel *c = (void *) C; /* This should not happen */ if (!p->conn) return; if (initial && p->cf->gr_mode) - p->feed_state = BFS_LOADING; + c->feed_state = BFS_LOADING; /* It is refeed and both sides support enhanced route refresh */ - if (!initial && p->cf->enable_refresh && - p->conn->peer_enhanced_refresh_support) - { - /* BoRR must not be sent before End-of-RIB */ - if (p->feed_state == BFS_LOADING || p->feed_state == BFS_LOADED) - return; + if (!initial && p->enhanced_refresh) + { + /* BoRR must not be sent before End-of-RIB */ + if (c->feed_state == BFS_LOADING || c->feed_state == BFS_LOADED) + return; - p->feed_state = BFS_REFRESHING; - bgp_schedule_packet(p->conn, PKT_BEGIN_REFRESH); - } + c->feed_state = BFS_REFRESHING; + bgp_schedule_packet(p->conn, c, PKT_BEGIN_REFRESH); + } } static void -bgp_feed_end(struct proto *P) +bgp_feed_end(struct channel *C) { - struct bgp_proto *p = (struct bgp_proto *) P; + struct bgp_proto *p = (void *) C->proto; + struct bgp_channel *c = (void *) C; /* This should not happen */ if (!p->conn) return; /* Non-demarcated feed ended, nothing to do */ - if (p->feed_state == BFS_NONE) + if (c->feed_state == BFS_NONE) return; /* Schedule End-of-RIB packet */ - if (p->feed_state == BFS_LOADING) - p->feed_state = BFS_LOADED; + if (c->feed_state == BFS_LOADING) + c->feed_state = BFS_LOADED; /* Schedule EoRR packet */ - if (p->feed_state == BFS_REFRESHING) - p->feed_state = BFS_REFRESHED; + if (c->feed_state == BFS_REFRESHING) + c->feed_state = BFS_REFRESHED; /* Kick TX hook */ - bgp_schedule_packet(p->conn, PKT_UPDATE); + bgp_schedule_packet(p->conn, c, PKT_UPDATE); } @@ -1096,30 +1257,30 @@ bgp_start_locked(struct object_lock *lock) struct bgp_config *cf = p->cf; if (p->p.proto_state != PS_START) - { - DBG("BGP: Got lock in different state %d\n", p->p.proto_state); - return; - } + { + DBG("BGP: Got lock in different state %d\n", p->p.proto_state); + return; + } DBG("BGP: Got lock\n"); if (cf->multihop) - { - /* Multi-hop sessions do not use neighbor entries */ - bgp_initiate(p); - return; - } + { + /* Multi-hop sessions do not use neighbor entries */ + bgp_initiate(p); + return; + } neighbor *n = neigh_find2(&p->p, &cf->remote_ip, cf->iface, NEF_STICKY); if (!n) - { - log(L_ERR "%s: Invalid remote address %I%J", p->p.name, cf->remote_ip, cf->iface); - /* As we do not start yet, we can just disable protocol */ - p->p.disabled = 1; - bgp_store_error(p, NULL, BE_MISC, BEM_INVALID_NEXT_HOP); - proto_notify_state(&p->p, PS_DOWN); - return; - } + { + log(L_ERR "%s: Invalid remote address %I%J", p->p.name, cf->remote_ip, cf->iface); + /* As we do not start yet, we can just disable protocol */ + p->p.disabled = 1; + bgp_store_error(p, NULL, BE_MISC, BEM_INVALID_NEXT_HOP); + proto_notify_state(&p->p, PS_DOWN); + return; + } p->neigh = n; @@ -1144,9 +1305,7 @@ bgp_start(struct proto *P) p->neigh = NULL; p->bfd_req = NULL; p->gr_ready = 0; - p->gr_active = 0; - - rt_lock_table(p->igp_table); + p->gr_active_num = 0; p->event = ev_new(p->p.pool); p->event->hook = bgp_decision; @@ -1165,15 +1324,20 @@ bgp_start(struct proto *P) p->rr_cluster_id = p->cf->rr_cluster_id ? p->cf->rr_cluster_id : p->local_id; p->remote_id = 0; - p->source_addr = p->cf->source_addr; + p->source_addr = p->cf->local_ip; + p->link_addr = IPA_NONE; + /* XXXX */ if (p->p.gr_recovery && p->cf->gr_mode) - proto_graceful_restart_lock(P); + { + struct bgp_channel *c; + WALK_LIST(c, p->p.channels) + channel_graceful_restart_lock(&c->c); + } /* - * Before attempting to create the connection, we need to lock the - * port, so that are sure we're the only instance attempting to talk - * with that neighbor. + * Before attempting to create the connection, we need to lock the port, + * so that we are the only instance attempting to talk with that neighbor. */ lock = p->lock = olock_new(P->pool); @@ -1194,78 +1358,64 @@ static int bgp_shutdown(struct proto *P) { struct bgp_proto *p = (struct bgp_proto *) P; - unsigned subcode = 0; + uint subcode = 0; BGP_TRACE(D_EVENTS, "Shutdown requested"); switch (P->down_code) - { - case PDC_CF_REMOVE: - case PDC_CF_DISABLE: - subcode = 3; // Errcode 6, 3 - peer de-configured - break; - - case PDC_CF_RESTART: - subcode = 6; // Errcode 6, 6 - other configuration change - break; - - case PDC_CMD_DISABLE: - case PDC_CMD_SHUTDOWN: - subcode = 2; // Errcode 6, 2 - administrative shutdown - break; - - case PDC_CMD_RESTART: - subcode = 4; // Errcode 6, 4 - administrative reset - break; - - case PDC_RX_LIMIT_HIT: - case PDC_IN_LIMIT_HIT: - subcode = 1; // Errcode 6, 1 - max number of prefixes reached - /* log message for compatibility */ - log(L_WARN "%s: Route limit exceeded, shutting down", p->p.name); - goto limit; - - case PDC_OUT_LIMIT_HIT: - subcode = proto_restart ? 4 : 2; // Administrative reset or shutdown - - limit: - bgp_store_error(p, NULL, BE_AUTO_DOWN, BEA_ROUTE_LIMIT_EXCEEDED); - if (proto_restart) - bgp_update_startup_delay(p); - else - p->startup_delay = 0; - goto done; - } + { + case PDC_CF_REMOVE: + case PDC_CF_DISABLE: + subcode = 3; // Errcode 6, 3 - peer de-configured + break; + + case PDC_CF_RESTART: + subcode = 6; // Errcode 6, 6 - other configuration change + break; + + case PDC_CMD_DISABLE: + case PDC_CMD_SHUTDOWN: + subcode = 2; // Errcode 6, 2 - administrative shutdown + break; + + case PDC_CMD_RESTART: + subcode = 4; // Errcode 6, 4 - administrative reset + break; + + case PDC_RX_LIMIT_HIT: + case PDC_IN_LIMIT_HIT: + subcode = 1; // Errcode 6, 1 - max number of prefixes reached + /* log message for compatibility */ + log(L_WARN "%s: Route limit exceeded, shutting down", p->p.name); + goto limit; + + case PDC_OUT_LIMIT_HIT: + subcode = proto_restart ? 4 : 2; // Administrative reset or shutdown + + limit: + bgp_store_error(p, NULL, BE_AUTO_DOWN, BEA_ROUTE_LIMIT_EXCEEDED); + if (proto_restart) + bgp_update_startup_delay(p); + else + p->startup_delay = 0; + goto done; + } bgp_store_error(p, NULL, BE_MAN_DOWN, 0); p->startup_delay = 0; - done: +done: bgp_stop(p, subcode); return p->p.proto_state; } -static void -bgp_cleanup(struct proto *P) -{ - struct bgp_proto *p = (struct bgp_proto *) P; - rt_unlock_table(p->igp_table); -} - -static rtable * -get_igp_table(struct bgp_config *cf) -{ - return cf->igp_table ? cf->igp_table->table : cf->c.table->table; -} - static struct proto * -bgp_init(struct proto_config *C) +bgp_init(struct proto_config *CF) { - struct proto *P = proto_new(C, sizeof(struct bgp_proto)); - struct bgp_config *c = (struct bgp_config *) C; + struct proto *P = proto_new(CF); struct bgp_proto *p = (struct bgp_proto *) P; + struct bgp_config *cf = (struct bgp_config *) CF; - P->accept_ra_types = c->secondary ? RA_ACCEPTED : RA_OPTIMAL; P->rt_notify = bgp_rt_notify; P->import_control = bgp_import_control; P->neigh_notify = bgp_neigh_notify; @@ -1274,102 +1424,276 @@ bgp_init(struct proto_config *C) P->feed_end = bgp_feed_end; P->rte_better = bgp_rte_better; P->rte_mergable = bgp_rte_mergable; - P->rte_recalculate = c->deterministic_med ? bgp_rte_recalculate : NULL; - - p->cf = c; - p->local_as = c->local_as; - p->remote_as = c->remote_as; - p->is_internal = (c->local_as == c->remote_as); - p->rs_client = c->rs_client; - p->rr_client = c->rr_client; - p->igp_table = get_igp_table(c); + P->rte_recalculate = cf->deterministic_med ? bgp_rte_recalculate : NULL; + + p->cf = cf; + p->local_as = cf->local_as; + p->remote_as = cf->remote_as; + p->public_as = cf->local_as; + p->is_internal = (cf->local_as == cf->remote_as); + p->is_interior = p->is_internal || cf->confederation_member; + p->rs_client = cf->rs_client; + p->rr_client = cf->rr_client; + + /* Confederation ID is used for truly external peers */ + if (cf->confederation && !p->is_interior) + p->public_as = cf->confederation; + + /* Add all channels */ + struct bgp_channel_config *cc; + WALK_LIST(cc, CF->channels) + proto_add_channel(P, &cc->c); return P; } +static void +bgp_channel_init(struct channel *C, struct channel_config *CF) +{ + struct bgp_channel *c = (void *) C; + struct bgp_channel_config *cf = (void *) CF; + + c->cf = cf; + c->afi = cf->afi; + c->desc = cf->desc; + + if (cf->igp_table_ip4) + c->igp_table_ip4 = cf->igp_table_ip4->table; + + if (cf->igp_table_ip6) + c->igp_table_ip6 = cf->igp_table_ip6->table; +} + +static int +bgp_channel_start(struct channel *C) +{ + struct bgp_proto *p = (void *) C->proto; + struct bgp_channel *c = (void *) C; + ip_addr src = p->source_addr; + + if (c->igp_table_ip4) + rt_lock_table(c->igp_table_ip4); + + if (c->igp_table_ip6) + rt_lock_table(c->igp_table_ip6); + + c->pool = p->p.pool; // XXXX + bgp_init_bucket_table(c); + bgp_init_prefix_table(c); + + c->next_hop_addr = c->cf->next_hop_addr; + c->link_addr = IPA_NONE; + c->packets_to_send = 0; + + /* Try to use source address as next hop address */ + if (ipa_zero(c->next_hop_addr)) + { + if (bgp_channel_is_ipv4(c) && (ipa_is_ip4(src) || c->ext_next_hop)) + c->next_hop_addr = src; + + if (bgp_channel_is_ipv6(c) && (ipa_is_ip6(src) || c->ext_next_hop)) + c->next_hop_addr = src; + } + + /* Exit if no feasible next hop address is found */ + if (ipa_zero(c->next_hop_addr)) + { + log(L_WARN "%s: Missing next hop address", p->p.name); + return 0; + } + + /* Set link-local address for IPv6 single-hop BGP */ + if (ipa_is_ip6(c->next_hop_addr) && p->neigh) + { + c->link_addr = p->link_addr; + + if (ipa_zero(c->link_addr)) + log(L_WARN "%s: Missing link-local address", p->p.name); + } + + /* Link local address is already in c->link_addr */ + if (ipa_is_link_local(c->next_hop_addr)) + c->next_hop_addr = IPA_NONE; + + return 0; /* XXXX: Currently undefined */ +} + +static void +bgp_channel_shutdown(struct channel *C) +{ + struct bgp_channel *c = (void *) C; + + /* XXXX: cleanup bucket and prefix tables */ + + c->next_hop_addr = IPA_NONE; + c->link_addr = IPA_NONE; +} + +static void +bgp_channel_cleanup(struct channel *C) +{ + struct bgp_channel *c = (void *) C; + + if (c->igp_table_ip4) + rt_unlock_table(c->igp_table_ip4); + + if (c->igp_table_ip6) + rt_unlock_table(c->igp_table_ip6); +} + +static inline struct bgp_channel_config * +bgp_find_channel_config(struct bgp_config *cf, u32 afi) +{ + struct bgp_channel_config *cc; + + WALK_LIST(cc, cf->c.channels) + if (cc->afi == afi) + return cc; + + return NULL; +} + +struct rtable_config * +bgp_default_igp_table(struct bgp_config *cf, struct bgp_channel_config *cc, u32 type) +{ + struct bgp_channel_config *cc2; + struct rtable_config *tab; + + /* First, try table connected by the channel */ + if (cc->c.table->addr_type == type) + return cc->c.table; + + /* Find paired channel with the same SAFI but the other AFI */ + u32 afi2 = cc->afi ^ 0x30000; + cc2 = bgp_find_channel_config(cf, afi2); + + /* Second, try IGP table configured in the paired channel */ + if (cc2 && (tab = (type == NET_IP4) ? cc2->igp_table_ip4 : cc2->igp_table_ip6)) + return tab; + + /* Third, try table connected by the paired channel */ + if (cc2 && (cc2->c.table->addr_type == type)) + return cc2->c.table; + + /* Last, try default table of given type */ + if (tab = cf->c.global->def_tables[type]) + return tab; + + cf_error("Undefined IGP table"); +} + void -bgp_check_config(struct bgp_config *c) +bgp_postconfig(struct proto_config *CF) { - int internal = (c->local_as == c->remote_as); + struct bgp_config *cf = (void *) CF; + int internal = (cf->local_as == cf->remote_as); /* Do not check templates at all */ - if (c->c.class == SYM_TEMPLATE) + if (cf->c.class == SYM_TEMPLATE) return; /* EBGP direct by default, IBGP multihop by default */ - if (c->multihop < 0) - c->multihop = internal ? 64 : 0; - - /* Different default for gw_mode */ - if (!c->gw_mode) - c->gw_mode = c->multihop ? GW_RECURSIVE : GW_DIRECT; - - /* Different default based on rs_client */ - if (!c->missing_lladdr) - c->missing_lladdr = c->rs_client ? MLL_IGNORE : MLL_SELF; - - /* Disable after error incompatible with restart limit action */ - if (c->c.in_limit && (c->c.in_limit->action == PLA_RESTART) && c->disable_after_error) - c->c.in_limit->action = PLA_DISABLE; + if (cf->multihop < 0) + cf->multihop = internal ? 64 : 0; - if (!c->local_as) + if (!cf->local_as) cf_error("Local AS number must be set"); - if (ipa_zero(c->remote_ip)) + if (ipa_zero(cf->remote_ip)) cf_error("Neighbor must be configured"); - if (!c->remote_as) + if (!cf->remote_as) cf_error("Remote AS number must be set"); - if (ipa_is_link_local(c->remote_ip) && !c->iface) + if (ipa_is_link_local(cf->remote_ip) && !cf->iface) cf_error("Link-local neighbor address requires specified interface"); - if (!(c->capabilities && c->enable_as4) && (c->remote_as > 0xFFFF)) + if (!(cf->capabilities && cf->enable_as4) && (cf->remote_as > 0xFFFF)) cf_error("Neighbor AS number out of range (AS4 not available)"); - if (!internal && c->rr_client) + if (!internal && cf->rr_client) cf_error("Only internal neighbor can be RR client"); - if (internal && c->rs_client) + if (internal && cf->rs_client) cf_error("Only external neighbor can be RS client"); - if (c->multihop && (c->gw_mode == GW_DIRECT)) - cf_error("Multihop BGP cannot use direct gateway mode"); + if (!cf->confederation && cf->confederation_member) + cf_error("Confederation ID must be set for member sessions"); - if (c->multihop && (ipa_is_link_local(c->remote_ip) || - ipa_is_link_local(c->source_addr))) + if (cf->multihop && (ipa_is_link_local(cf->local_ip) || + ipa_is_link_local(cf->remote_ip))) cf_error("Multihop BGP cannot be used with link-local addresses"); - if (c->multihop && c->iface) + if (cf->multihop && cf->iface) cf_error("Multihop BGP cannot be bound to interface"); - if (c->multihop && c->check_link) + if (cf->multihop && cf->check_link) cf_error("Multihop BGP cannot depend on link state"); - if (c->multihop && c->bfd && ipa_zero(c->source_addr)) - cf_error("Multihop BGP with BFD requires specified source address"); + if (cf->multihop && cf->bfd && ipa_zero(cf->local_ip)) + cf_error("Multihop BGP with BFD requires specified local address"); - if ((c->gw_mode == GW_RECURSIVE) && c->c.table->sorted) - cf_error("BGP in recursive mode prohibits sorted table"); - if (c->deterministic_med && c->c.table->sorted) - cf_error("BGP with deterministic MED prohibits sorted table"); + struct bgp_channel_config *cc; + WALK_LIST(cc, CF->channels) + { + /* Disable after error incompatible with restart limit action */ + if ((cc->c.in_limit.action == PLA_RESTART) && cf->disable_after_error) + cc->c.in_limit.action = PLA_DISABLE; + + /* Different default based on rs_client */ + if (!cc->missing_lladdr) + cc->missing_lladdr = cf->rs_client ? MLL_IGNORE : MLL_SELF; + + /* Different default for gw_mode */ + if (!cc->gw_mode) + cc->gw_mode = cf->multihop ? GW_RECURSIVE : GW_DIRECT; + + /* Default based on proto config */ + if (cc->gr_able == 0xff) + cc->gr_able = (cf->gr_mode == BGP_GR_ABLE); + + /* Default values of IGP tables */ + if ((cc->gw_mode == GW_RECURSIVE) && !cc->desc->no_igp) + { + if (!cc->igp_table_ip4 && (bgp_cc_is_ipv4(cc) || cc->ext_next_hop)) + cc->igp_table_ip4 = bgp_default_igp_table(cf, cc, NET_IP4); + + if (!cc->igp_table_ip6 && (bgp_cc_is_ipv6(cc) || cc->ext_next_hop)) + cc->igp_table_ip6 = bgp_default_igp_table(cf, cc, NET_IP6); - if (c->secondary && !c->c.table->sorted) - cf_error("BGP with secondary option requires sorted table"); + if (cc->igp_table_ip4 && bgp_cc_is_ipv6(cc) && !cc->ext_next_hop) + cf_error("Mismatched IGP table type"); + + if (cc->igp_table_ip6 && bgp_cc_is_ipv4(cc) && !cc->ext_next_hop) + cf_error("Mismatched IGP table type"); + } + + if (cf->multihop && (cc->gw_mode == GW_DIRECT)) + cf_error("Multihop BGP cannot use direct gateway mode"); + + if ((cc->gw_mode == GW_RECURSIVE) && cc->c.table->sorted) + cf_error("BGP in recursive mode prohibits sorted table"); + + if (cf->deterministic_med && cc->c.table->sorted) + cf_error("BGP with deterministic MED prohibits sorted table"); + + if (cc->secondary && !cc->c.table->sorted) + cf_error("BGP with secondary option requires sorted table"); + } } static int -bgp_reconfigure(struct proto *P, struct proto_config *C) +bgp_reconfigure(struct proto *P, struct proto_config *CF) { - struct bgp_config *new = (struct bgp_config *) C; - struct bgp_proto *p = (struct bgp_proto *) P; + struct bgp_proto *p = (void *) P; + struct bgp_config *new = (void *) CF; struct bgp_config *old = p->cf; - if (proto_get_router_id(C) != p->local_id) + if (proto_get_router_id(CF) != p->local_id) return 0; int same = !memcmp(((byte *) old) + sizeof(struct proto_config), @@ -1377,8 +1701,26 @@ bgp_reconfigure(struct proto *P, struct proto_config *C) // password item is last and must be checked separately OFFSETOF(struct bgp_config, password) - sizeof(struct proto_config)) && ((!old->password && !new->password) - || (old->password && new->password && !strcmp(old->password, new->password))) - && (get_igp_table(old) == get_igp_table(new)); + || (old->password && new->password && !strcmp(old->password, new->password))); + + /* FIXME: Move channel reconfiguration to generic protocol code ? */ + struct channel *C, *C2; + struct bgp_channel_config *cc; + + WALK_LIST(C, p->p.channels) + C->stale = 1; + + WALK_LIST(cc, new->c.channels) + { + C = (struct channel *) bgp_find_channel(p, cc->afi); + same = proto_configure_channel(P, &C, &cc->c) && same; + C->stale = 0; + } + + WALK_LIST_DELSAFE(C, C2, p->p.channels) + if (C->stale) + same = proto_configure_channel(P, &C, NULL) && same; + if (same && (p->start_state > BSS_PREPARE)) bgp_update_bfd(p, new->bfd); @@ -1390,11 +1732,34 @@ bgp_reconfigure(struct proto *P, struct proto_config *C) return same; } +#define IGP_TABLE(cf, sym) ((cf)->igp_table_##sym ? (cf)->igp_table_##sym ->table : NULL ) + +static int +bgp_channel_reconfigure(struct channel *C, struct channel_config *CC) +{ + struct bgp_channel *c = (void *) C; + struct bgp_channel_config *new = (void *) CC; + struct bgp_channel_config *old = c->cf; + + if (memcmp(((byte *) old) + sizeof(struct channel_config), + ((byte *) new) + sizeof(struct channel_config), + /* Remaining items must be checked separately */ + OFFSETOF(struct bgp_channel_config, rest) - sizeof(struct channel_config))) + return 0; + + /* Check change in IGP tables */ + if ((IGP_TABLE(old, ip4) != IGP_TABLE(new, ip4)) || + (IGP_TABLE(old, ip6) != IGP_TABLE(new, ip6))) + return 0; + + c->cf = new; + return 1; +} + static void -bgp_copy_config(struct proto_config *dest, struct proto_config *src) +bgp_copy_config(struct proto_config *dest UNUSED, struct proto_config *src UNUSED) { /* Just a shallow copy */ - proto_copy_rest(dest, src, sizeof(struct bgp_config)); } @@ -1411,14 +1776,14 @@ bgp_copy_config(struct proto_config *dest, struct proto_config *src) * closes the connection. */ void -bgp_error(struct bgp_conn *c, unsigned code, unsigned subcode, byte *data, int len) +bgp_error(struct bgp_conn *c, uint code, uint subcode, byte *data, int len) { struct bgp_proto *p = c->bgp; if (c->state == BS_CLOSE) return; - bgp_log_error(p, BE_BGP_TX, "Error", code, subcode, data, (len > 0) ? len : -len); + bgp_log_error(p, BE_BGP_TX, "Error", code, subcode, data, ABS(len)); bgp_store_error(p, c, BE_BGP_TX, (code << 16) | subcode); bgp_conn_enter_close_state(c); @@ -1426,13 +1791,13 @@ bgp_error(struct bgp_conn *c, unsigned code, unsigned subcode, byte *data, int l c->notify_subcode = subcode; c->notify_data = data; c->notify_size = (len > 0) ? len : 0; - bgp_schedule_packet(c, PKT_NOTIFICATION); + bgp_schedule_packet(c, NULL, PKT_NOTIFICATION); if (code != 6) - { - bgp_update_startup_delay(p); - bgp_stop(p, 0); - } + { + bgp_update_startup_delay(p); + bgp_stop(p, 0); + } } /** @@ -1471,19 +1836,19 @@ static const char * bgp_last_errmsg(struct bgp_proto *p) { switch (p->last_error_class) - { - case BE_MISC: - return bgp_misc_errors[p->last_error_code]; - case BE_SOCKET: - return (p->last_error_code == 0) ? "Connection closed" : strerror(p->last_error_code); - case BE_BGP_RX: - case BE_BGP_TX: - return bgp_error_dsc(p->last_error_code >> 16, p->last_error_code & 0xFF); - case BE_AUTO_DOWN: - return bgp_auto_errors[p->last_error_code]; - default: - return ""; - } + { + case BE_MISC: + return bgp_misc_errors[p->last_error_code]; + case BE_SOCKET: + return (p->last_error_code == 0) ? "Connection closed" : strerror(p->last_error_code); + case BE_BGP_RX: + case BE_BGP_TX: + return bgp_error_dsc(p->last_error_code >> 16, p->last_error_code & 0xFF); + case BE_AUTO_DOWN: + return bgp_auto_errors[p->last_error_code]; + default: + return ""; + } } static const char * @@ -1514,48 +1879,164 @@ bgp_get_status(struct proto *P, byte *buf) } static void +bgp_show_afis(int code, char *s, u32 *afis, uint count) +{ + buffer b; + LOG_BUFFER_INIT(b); + + buffer_puts(&b, s); + + for (u32 *af = afis; af < (afis + count); af++) + { + const struct bgp_af_desc *desc = bgp_get_af_desc(*af); + if (desc) + buffer_print(&b, " %s", desc->name); + else + buffer_print(&b, " <%u/%u>", BGP_AFI(*af), BGP_SAFI(*af)); + } + + if (b.pos == b.end) + strcpy(b.end - 32, " ... <too long>"); + + cli_msg(code, b.start); +} + +static void +bgp_show_capabilities(struct bgp_proto *p UNUSED, struct bgp_caps *caps) +{ + struct bgp_af_caps *ac; + uint any_mp_bgp = 0; + uint any_gr_able = 0; + uint any_add_path = 0; + uint any_ext_next_hop = 0; + u32 *afl1 = alloca(caps->af_count * sizeof(u32)); + u32 *afl2 = alloca(caps->af_count * sizeof(u32)); + uint afn1, afn2; + + WALK_AF_CAPS(caps, ac) + { + any_mp_bgp |= ac->ready; + any_gr_able |= ac->gr_able; + any_add_path |= ac->add_path; + any_ext_next_hop |= ac->ext_next_hop; + } + + if (any_mp_bgp) + { + cli_msg(-1006, " Multiprotocol"); + + afn1 = 0; + WALK_AF_CAPS(caps, ac) + if (ac->ready) + afl1[afn1++] = ac->afi; + + bgp_show_afis(-1006, " AF announced:", afl1, afn1); + } + + if (caps->route_refresh) + cli_msg(-1006, " Route refresh"); + + if (any_ext_next_hop) + { + cli_msg(-1006, " Extended next hop"); + + afn1 = 0; + WALK_AF_CAPS(caps, ac) + if (ac->ext_next_hop) + afl1[afn1++] = ac->afi; + + bgp_show_afis(-1006, " IPv6 nexthop:", afl1, afn1); + } + + if (caps->ext_messages) + cli_msg(-1006, " Extended message"); + + if (caps->gr_aware) + cli_msg(-1006, " Graceful restart"); + + if (any_gr_able) + { + /* Continues from gr_aware */ + cli_msg(-1006, " Restart time: %u", caps->gr_time); + if (caps->gr_flags & BGP_GRF_RESTART) + cli_msg(-1006, " Restart recovery"); + + afn1 = afn2 = 0; + WALK_AF_CAPS(caps, ac) + { + if (ac->gr_able) + afl1[afn1++] = ac->afi; + + if (ac->gr_af_flags & BGP_GRF_FORWARDING) + afl2[afn2++] = ac->afi; + } + + bgp_show_afis(-1006, " AF supported:", afl1, afn1); + bgp_show_afis(-1006, " AF preserved:", afl2, afn2); + } + + if (caps->as4_support) + cli_msg(-1006, " 4-octet AS numbers"); + + if (any_add_path) + { + cli_msg(-1006, " ADD-PATH"); + + afn1 = afn2 = 0; + WALK_AF_CAPS(caps, ac) + { + if (ac->add_path & BGP_ADD_PATH_RX) + afl1[afn1++] = ac->afi; + + if (ac->add_path & BGP_ADD_PATH_TX) + afl2[afn2++] = ac->afi; + } + + bgp_show_afis(-1006, " RX:", afl1, afn1); + bgp_show_afis(-1006, " TX:", afl2, afn2); + } + + if (caps->enhanced_refresh) + cli_msg(-1006, " Enhanced refresh"); +} + +static void bgp_show_proto_info(struct proto *P) { struct bgp_proto *p = (struct bgp_proto *) P; - struct bgp_conn *c = p->conn; - - proto_show_basic_info(P); cli_msg(-1006, " BGP state: %s", bgp_state_dsc(p)); cli_msg(-1006, " Neighbor address: %I%J", p->cf->remote_ip, p->cf->iface); cli_msg(-1006, " Neighbor AS: %u", p->remote_as); - if (p->gr_active) + if (p->gr_active_num) cli_msg(-1006, " Neighbor graceful restart active"); if (P->proto_state == PS_START) - { - struct bgp_conn *oc = &p->outgoing_conn; + { + struct bgp_conn *oc = &p->outgoing_conn; - if ((p->start_state < BSS_CONNECT) && - (p->startup_timer->expires)) - cli_msg(-1006, " Error wait: %d/%d", - p->startup_timer->expires - now, p->startup_delay); + if ((p->start_state < BSS_CONNECT) && + (p->startup_timer->expires)) + cli_msg(-1006, " Error wait: %d/%d", + p->startup_timer->expires - now, p->startup_delay); - if ((oc->state == BS_ACTIVE) && - (oc->connect_retry_timer->expires)) - cli_msg(-1006, " Connect delay: %d/%d", - oc->connect_retry_timer->expires - now, p->cf->connect_delay_time); + if ((oc->state == BS_ACTIVE) && + (oc->connect_timer->expires)) + cli_msg(-1006, " Connect delay: %d/%d", + oc->connect_timer->expires - now, p->cf->connect_delay_time); - if (p->gr_active && p->gr_timer->expires) - cli_msg(-1006, " Restart timer: %d/-", p->gr_timer->expires - now); - } + if (p->gr_active_num && p->gr_timer->expires) + cli_msg(-1006, " Restart timer: %d/-", p->gr_timer->expires - now); + } else if (P->proto_state == PS_UP) - { - cli_msg(-1006, " Neighbor ID: %R", p->remote_id); - cli_msg(-1006, " Neighbor caps: %s%s%s%s%s%s%s", - c->peer_refresh_support ? " refresh" : "", - c->peer_enhanced_refresh_support ? " enhanced-refresh" : "", - c->peer_gr_able ? " restart-able" : (c->peer_gr_aware ? " restart-aware" : ""), - c->peer_as4_support ? " AS4" : "", - (c->peer_add_path & ADD_PATH_RX) ? " add-path-rx" : "", - (c->peer_add_path & ADD_PATH_TX) ? " add-path-tx" : "", - c->peer_ext_messages_support ? " ext-messages" : ""); + { + cli_msg(-1006, " Neighbor ID: %R", p->remote_id); + cli_msg(-1006, " Local capabilities"); + bgp_show_capabilities(p, p->conn->local_caps); + cli_msg(-1006, " Neighbor capabilities"); + bgp_show_capabilities(p, p->conn->remote_caps); +/* XXXX cli_msg(-1006, " Session: %s%s%s%s%s%s%s%s", p->is_internal ? "internal" : "external", p->cf->multihop ? " multihop" : "", @@ -1565,35 +2046,60 @@ bgp_show_proto_info(struct proto *P) p->add_path_rx ? " add-path-rx" : "", p->add_path_tx ? " add-path-tx" : "", p->ext_messages ? " ext-messages" : ""); - cli_msg(-1006, " Source address: %I", p->source_addr); - if (P->cf->in_limit) - cli_msg(-1006, " Route limit: %d/%d", - p->p.stats.imp_routes + p->p.stats.filt_routes, P->cf->in_limit->limit); - cli_msg(-1006, " Hold timer: %d/%d", - tm_remains(c->hold_timer), c->hold_time); - cli_msg(-1006, " Keepalive timer: %d/%d", - tm_remains(c->keepalive_timer), c->keepalive_time); - } +*/ + cli_msg(-1006, " Source address: %I", p->source_addr); + cli_msg(-1006, " Hold timer: %d/%d", + tm_remains(p->conn->hold_timer), p->conn->hold_time); + cli_msg(-1006, " Keepalive timer: %d/%d", + tm_remains(p->conn->keepalive_timer), p->conn->keepalive_time); + } if ((p->last_error_class != BE_NONE) && (p->last_error_class != BE_MAN_DOWN)) + { + const char *err1 = bgp_err_classes[p->last_error_class]; + const char *err2 = bgp_last_errmsg(p); + cli_msg(-1006, " Last error: %s%s", err1, err2); + } + + { + /* XXXX ?? */ + struct bgp_channel *c; + WALK_LIST(c, p->p.channels) { - const char *err1 = bgp_err_classes[p->last_error_class]; - const char *err2 = bgp_last_errmsg(p); - cli_msg(-1006, " Last error: %s%s", err1, err2); + channel_show_info(&c->c); + + if (c->igp_table_ip4) + cli_msg(-1006, " IGP IPv4 table: %s", c->igp_table_ip4->name); + + if (c->igp_table_ip6) + cli_msg(-1006, " IGP IPv6 table: %s", c->igp_table_ip6->name); } + } } +struct channel_class channel_bgp = { + .channel_size = sizeof(struct bgp_channel), + .config_size = sizeof(struct bgp_channel_config), + .init = bgp_channel_init, + .start = bgp_channel_start, + .shutdown = bgp_channel_shutdown, + .cleanup = bgp_channel_cleanup, + .reconfigure = bgp_channel_reconfigure, +}; + struct protocol proto_bgp = { .name = "BGP", .template = "bgp%d", .attr_class = EAP_BGP, .preference = DEF_PREF_BGP, + .channel_mask = NB_IP | NB_VPN | NB_FLOW, + .proto_size = sizeof(struct bgp_proto), .config_size = sizeof(struct bgp_config), + .postconfig = bgp_postconfig, .init = bgp_init, .start = bgp_start, .shutdown = bgp_shutdown, - .cleanup = bgp_cleanup, .reconfigure = bgp_reconfigure, .copy_config = bgp_copy_config, .get_status = bgp_get_status, diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h index e47a0eb1..7ffcb68a 100644 --- a/proto/bgp/bgp.h +++ b/proto/bgp/bgp.h @@ -2,6 +2,8 @@ * BIRD -- The Border Gateway Protocol * * (c) 2000 Martin Mares <mj@ucw.cz> + * (c) 2008--2016 Ondrej Zajicek <santiago@crfreenet.org> + * (c) 2008--2016 CZ.NIC z.s.p.o. * * Can be freely distributed and used under the terms of the GNU GPL. */ @@ -10,26 +12,80 @@ #define _BIRD_BGP_H_ #include <stdint.h> +#include <setjmp.h> +#include "nest/bird.h" #include "nest/route.h" #include "nest/bfd.h" +//#include "lib/lists.h" #include "lib/hash.h" +#include "lib/socket.h" struct linpool; struct eattr; + +/* Address families */ + +#define BGP_AFI_IPV4 1 +#define BGP_AFI_IPV6 2 + +#define BGP_SAFI_UNICAST 1 +#define BGP_SAFI_MULTICAST 2 +#define BGP_SAFI_MPLS 4 +#define BGP_SAFI_MPLS_VPN 128 +#define BGP_SAFI_VPN_MULTICAST 129 +#define BGP_SAFI_FLOW 133 + +/* Internal AF codes */ + +#define BGP_AF(A, B) (((u32)(A) << 16) | (u32)(B)) +#define BGP_AFI(A) ((u32)(A) >> 16) +#define BGP_SAFI(A) ((u32)(A) & 0xFFFF) + +#define BGP_AF_IPV4 BGP_AF( BGP_AFI_IPV4, BGP_SAFI_UNICAST ) +#define BGP_AF_IPV6 BGP_AF( BGP_AFI_IPV6, BGP_SAFI_UNICAST ) +#define BGP_AF_IPV4_MC BGP_AF( BGP_AFI_IPV4, BGP_SAFI_MULTICAST ) +#define BGP_AF_IPV6_MC BGP_AF( BGP_AFI_IPV6, BGP_SAFI_MULTICAST ) +#define BGP_AF_IPV4_MPLS BGP_AF( BGP_AFI_IPV4, BGP_SAFI_MPLS ) +#define BGP_AF_IPV6_MPLS BGP_AF( BGP_AFI_IPV6, BGP_SAFI_MPLS ) +#define BGP_AF_VPN4_MPLS BGP_AF( BGP_AFI_IPV4, BGP_SAFI_MPLS_VPN ) +#define BGP_AF_VPN6_MPLS BGP_AF( BGP_AFI_IPV6, BGP_SAFI_MPLS_VPN ) +#define BGP_AF_VPN4_MC BGP_AF( BGP_AFI_IPV4, BGP_SAFI_VPN_MULTICAST ) +#define BGP_AF_VPN6_MC BGP_AF( BGP_AFI_IPV6, BGP_SAFI_VPN_MULTICAST ) +#define BGP_AF_FLOW4 BGP_AF( BGP_AFI_IPV4, BGP_SAFI_FLOW ) +#define BGP_AF_FLOW6 BGP_AF( BGP_AFI_IPV6, BGP_SAFI_FLOW ) + + +struct bgp_write_state; +struct bgp_parse_state; +struct bgp_export_state; +struct bgp_bucket; + +struct bgp_af_desc { + u32 afi; + u32 net; + u8 mpls; + u8 no_igp; + const char *name; + uint (*encode_nlri)(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, uint size); + void (*decode_nlri)(struct bgp_parse_state *s, byte *pos, uint len, rta *a); + void (*update_next_hop)(struct bgp_export_state *s, eattr *nh, ea_list **to); + uint (*encode_next_hop)(struct bgp_write_state *s, eattr *nh, byte *buf, uint size); + void (*decode_next_hop)(struct bgp_parse_state *s, byte *pos, uint len, rta *a); +}; + + struct bgp_config { struct proto_config c; u32 local_as, remote_as; + ip_addr local_ip; /* Source address to use */ ip_addr remote_ip; - ip_addr source_addr; /* Source address to use */ struct iface *iface; /* Interface for link-local addresses */ + u16 local_port; /* Local listening port */ u16 remote_port; /* Neighbor destination port */ int multihop; /* Number of hops if multihop */ - int ttl_security; /* Enable TTL security [RFC5082] */ - int next_hop_self; /* Always set next hop to local IP address */ - int next_hop_keep; /* Do not touch next hop attribute */ - int missing_lladdr; /* What we will do when we don' know link-local addr, see MLL_* */ - int gw_mode; /* How we compute route gateway from next_hop attr, see GW_* */ + int strict_bind; /* Bind listening socket to local address */ + int ttl_security; /* Enable TTL security [RFC 5082] */ int compare_path_lengths; /* Use path lengths when selecting best route */ int med_metric; /* Compare MULTI_EXIT_DISC even between routes from differen ASes */ int igp_metric; /* Use IGP metrics when selecting best route */ @@ -37,18 +93,17 @@ struct bgp_config { int deterministic_med; /* Use more complicated algo to have strict RFC 4271 MED comparison */ u32 default_local_pref; /* Default value for LOCAL_PREF attribute */ u32 default_med; /* Default value for MULTI_EXIT_DISC attribute */ - int capabilities; /* Enable capability handshake [RFC3392] */ - int enable_refresh; /* Enable local support for route refresh [RFC2918] */ - int enable_as4; /* Enable local support for 4B AS numbers [RFC4893] */ + int capabilities; /* Enable capability handshake [RFC 5492] */ + int enable_refresh; /* Enable local support for route refresh [RFC 2918] */ + int enable_as4; /* Enable local support for 4B AS numbers [RFC 6793] */ int enable_extended_messages; /* Enable local support for extended messages [draft] */ u32 rr_cluster_id; /* Route reflector cluster ID, if different from local ID */ int rr_client; /* Whether neighbor is RR client of me */ int rs_client; /* Whether neighbor is RS client of me */ - int advertise_ipv4; /* Whether we should add IPv4 capability advertisement to OPEN message */ + u32 confederation; /* Confederation ID, or zero if confeds not active */ + int confederation_member; /* Whether neighbor AS is member of our confederation */ int passive; /* Do not initiate outgoing connection */ int interpret_communities; /* Hardwired handling of well-known communities */ - int secondary; /* Accept also non-best routes (i.e. RA_ACCEPTED) */ - int add_path; /* Use ADD-PATH extension [RFC7911] */ int allow_local_as; /* Allow that number of local ASNs in incoming AS_PATHs */ int allow_local_pref; /* Allow LOCAL_PREF in EBGP sessions */ int gr_mode; /* Graceful restart mode (BGP_GR_*) */ @@ -64,11 +119,31 @@ struct bgp_config { unsigned disable_after_error; /* Disable the protocol when error is detected */ char *password; /* Password used for MD5 authentication */ - struct rtable_config *igp_table; /* Table used for recursive next hop lookups */ int check_link; /* Use iface link state for liveness detection */ int bfd; /* Use BFD for liveness detection */ }; +struct bgp_channel_config { + struct channel_config c; + + u32 afi; + const struct bgp_af_desc *desc; + + ip_addr next_hop_addr; /* Local address for NEXT_HOP attribute */ + u8 next_hop_self; /* Always set next hop to local IP address */ + u8 next_hop_keep; /* Do not touch next hop attribute */ + u8 missing_lladdr; /* What we will do when we don' know link-local addr, see MLL_* */ + u8 gw_mode; /* How we compute route gateway from next_hop attr, see GW_* */ + u8 secondary; /* Accept also non-best routes (i.e. RA_ACCEPTED) */ + u8 gr_able; /* Allow full graceful restart for the channel */ + u8 ext_next_hop; /* Allow both IPv4 and IPv6 next hops */ + u8 add_path; /* Use ADD-PATH extension [RFC 7911] */ + + uint rest[0]; /* Remaining items are reconfigured separately */ + struct rtable_config *igp_table_ip4; /* Table for recursive IPv4 next hop lookups */ + struct rtable_config *igp_table_ip6; /* Table for recursive IPv6 next hop lookups */ +}; + #define MLL_SELF 1 #define MLL_DROP 2 #define MLL_IGNORE 3 @@ -76,112 +151,241 @@ struct bgp_config { #define GW_DIRECT 1 #define GW_RECURSIVE 2 -#define ADD_PATH_RX 1 -#define ADD_PATH_TX 2 -#define ADD_PATH_FULL 3 +#define BGP_ADD_PATH_RX 1 +#define BGP_ADD_PATH_TX 2 +#define BGP_ADD_PATH_FULL 3 -#define BGP_GR_ABLE 1 -#define BGP_GR_AWARE 2 +#define BGP_GR_ABLE 1 +#define BGP_GR_AWARE 2 -/* For peer_gr_flags */ +/* For GR capability common flags */ #define BGP_GRF_RESTART 0x80 -/* For peer_gr_aflags */ +/* For GR capability per-AF flags */ #define BGP_GRF_FORWARDING 0x80 +struct bgp_af_caps { + u32 afi; + u8 ready; /* Multiprotocol capability, RFC 4760 */ + u8 gr_able; /* Graceful restart support, RFC 4724 */ + u8 gr_af_flags; /* Graceful restart per-AF flags */ + u8 ext_next_hop; /* Extended IPv6 next hop, RFC 5549 */ + u8 add_path; /* Multiple paths support, RFC 7911 */ +}; + +struct bgp_caps { + u32 as4_number; /* Announced ASN */ + + u8 as4_support; /* Four-octet AS capability, RFC 6793 */ + u8 ext_messages; /* Extended message length, RFC draft */ + u8 route_refresh; /* Route refresh capability, RFC 2918 */ + u8 enhanced_refresh; /* Enhanced route refresh, RFC 7313 */ + + u8 gr_aware; /* Graceful restart capability, RFC 4724 */ + u8 gr_flags; /* Graceful restart flags */ + u16 gr_time; /* Graceful restart time in seconds */ + + u16 af_count; /* Number of af_data items */ + + struct bgp_af_caps af_data[0]; /* Per-AF capability data */ +}; + +#define WALK_AF_CAPS(caps,ac) \ + for (ac = caps->af_data; ac < &caps->af_data[caps->af_count]; ac++) + + +struct bgp_socket { + node n; /* Node in global bgp_sockets */ + sock *sk; /* Real listening socket */ + u32 uc; /* Use count */ +}; + struct bgp_conn { struct bgp_proto *bgp; struct birdsock *sk; - uint state; /* State of connection state machine */ - struct timer *connect_retry_timer; + u8 state; /* State of connection state machine */ + u8 as4_session; /* Session uses 4B AS numbers in AS_PATH (both sides support it) */ + u8 ext_messages; /* Session uses extended message length */ + + struct bgp_caps *local_caps; + struct bgp_caps *remote_caps; + struct timer *connect_timer; struct timer *hold_timer; struct timer *keepalive_timer; struct event *tx_ev; - int packets_to_send; /* Bitmap of packet types to be sent */ + u32 packets_to_send; /* Bitmap of packet types to be sent */ + u32 channels_to_send; /* Bitmap of channels with packets to be sent */ + u8 last_channel; /* Channel used last time for TX */ + u8 last_channel_count; /* Number of times the last channel was used in succession */ int notify_code, notify_subcode, notify_size; byte *notify_data; - u32 advertised_as; /* Temporary value for AS number received */ - int start_state; /* protocol start_state snapshot when connection established */ - u8 peer_refresh_support; /* Peer supports route refresh [RFC2918] */ - u8 peer_as4_support; /* Peer supports 4B AS numbers [RFC4893] */ - u8 peer_add_path; /* Peer supports ADD-PATH [RFC7911] */ - u8 peer_enhanced_refresh_support; /* Peer supports enhanced refresh [RFC7313] */ - u8 peer_gr_aware; - u8 peer_gr_able; - u16 peer_gr_time; - u8 peer_gr_flags; - u8 peer_gr_aflags; - u8 peer_ext_messages_support; /* Peer supports extended message length [draft] */ - unsigned hold_time, keepalive_time; /* Times calculated from my and neighbor's requirements */ + + uint hold_time, keepalive_time; /* Times calculated from my and neighbor's requirements */ }; struct bgp_proto { struct proto p; struct bgp_config *cf; /* Shortcut to BGP configuration */ u32 local_as, remote_as; - int start_state; /* Substates that partitions BS_START */ - u8 is_internal; /* Internal BGP connection (local_as == remote_as) */ - u8 as4_session; /* Session uses 4B AS numbers in AS_PATH (both sides support it) */ - u8 add_path_rx; /* Session expects receive of ADD-PATH extended NLRI */ - u8 add_path_tx; /* Session expects transmit of ADD-PATH extended NLRI */ - u8 ext_messages; /* Session allows to use extended messages (both sides support it) */ + u32 public_as; /* Externally visible ASN (local_as or confederation id) */ u32 local_id; /* BGP identifier of this router */ u32 remote_id; /* BGP identifier of the neighbor */ u32 rr_cluster_id; /* Route reflector cluster ID */ - int rr_client; /* Whether neighbor is RR client of me */ - int rs_client; /* Whether neighbor is RS client of me */ + int start_state; /* Substates that partitions BS_START */ + u8 is_internal; /* Internal BGP session (local_as == remote_as) */ + u8 is_interior; /* Internal or intra-confederation BGP session */ + u8 as4_session; /* Session uses 4B AS numbers in AS_PATH (both sides support it) */ + u8 rr_client; /* Whether neighbor is RR client of me */ + u8 rs_client; /* Whether neighbor is RS client of me */ + u8 route_refresh; /* Route refresh allowed to send [RFC 2918] */ + u8 enhanced_refresh; /* Enhanced refresh is negotiated [RFC 7313] */ u8 gr_ready; /* Neighbor could do graceful restart */ - u8 gr_active; /* Neighbor is doing graceful restart */ - u8 feed_state; /* Feed state (TX) for EoR, RR packets, see BFS_* */ - u8 load_state; /* Load state (RX) for EoR, RR packets, see BFS_* */ + u8 gr_active_num; /* Neighbor is doing GR, number of active channels */ + u8 channel_count; /* Number of active channels */ + u32 *afi_map; /* Map channel index -> AFI */ + struct bgp_channel **channel_map; /* Map channel index -> channel */ 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 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 */ struct bfd_request *bfd_req; /* BFD request, if BFD is used */ ip_addr source_addr; /* Local address used as an advertised next hop */ - rtable *igp_table; /* Table used for recursive next hop lookups */ + ip_addr link_addr; /* Link-local version of source_addr */ struct event *event; /* Event for respawning and shutting process */ struct timer *startup_timer; /* Timer used to delay protocol startup due to previous errors (startup_delay) */ struct timer *gr_timer; /* Timer waiting for reestablishment after graceful restart */ - struct bgp_bucket **bucket_hash; /* Hash table of attribute buckets */ - uint hash_size, hash_count, hash_limit; - HASH(struct bgp_prefix) prefix_hash; /* Prefixes to be sent */ - slab *prefix_slab; /* Slab holding prefix nodes */ - list bucket_queue; /* Queue of buckets to send */ - struct bgp_bucket *withdraw_bucket; /* Withdrawn routes */ unsigned startup_delay; /* Time to delay protocol startup by due to errors */ bird_clock_t last_proto_error; /* Time of last error that leads to protocol stop */ u8 last_error_class; /* Error class of last error */ u32 last_error_code; /* Error code of last error. BGP protocol errors are encoded as (bgp_err_code << 16 | bgp_err_subcode) */ -#ifdef IPV6 - byte *mp_reach_start, *mp_unreach_start; /* Multiprotocol BGP attribute notes */ - unsigned mp_reach_len, mp_unreach_len; - ip_addr local_link; /* Link-level version of source_addr */ -#endif +}; + +struct bgp_channel { + struct channel c; + + /* Rest are BGP specific data */ + struct bgp_channel_config *cf; + pool *pool; /* XXXX */ + + u32 afi; + u32 index; + const struct bgp_af_desc *desc; + + HASH(struct bgp_bucket) bucket_hash; /* Hash table of route buckets */ + struct bgp_bucket *withdraw_bucket; /* Withdrawn routes */ + list bucket_queue; /* Queue of buckets to send (struct bgp_bucket) */ + + HASH(struct bgp_prefix) prefix_hash; /* Prefixes to be sent */ + slab *prefix_slab; /* Slab holding prefix nodes */ + + rtable *igp_table_ip4; /* Table for recursive IPv4 next hop lookups */ + rtable *igp_table_ip6; /* Table for recursive IPv6 next hop lookups */ + ip_addr next_hop_addr; /* Local address for NEXT_HOP attribute */ + ip_addr link_addr; /* Link-local version of next_hop_addr */ + + u32 packets_to_send; /* Bitmap of packet types to be sent */ + + u8 gr_ready; /* Neighbor could do GR on this AF */ + u8 gr_active; /* Neighbor is doing GR and keeping fwd state */ + + u8 ext_next_hop; /* Session allows both IPv4 and IPv6 next hops */ + + u8 add_path_rx; /* Session expects receive of ADD-PATH extended NLRI */ + u8 add_path_tx; /* Session expects transmit of ADD-PATH extended NLRI */ + + u8 feed_state; /* Feed state (TX) for EoR, RR packets, see BFS_* */ + u8 load_state; /* Load state (RX) for EoR, RR packets, see BFS_* */ }; struct bgp_prefix { - struct { - ip_addr prefix; - int pxlen; - } n; + node buck_node; /* Node in per-bucket list */ + struct bgp_prefix *next; /* Node in prefix hash table */ + u32 hash; u32 path_id; - struct bgp_prefix *next; - node bucket_node; /* Node in per-bucket list */ + net_addr net[0]; }; struct bgp_bucket { node send_node; /* Node in send queue */ - struct bgp_bucket *hash_next, *hash_prev; /* Node in bucket hash table */ - unsigned hash; /* Hash over extended attributes */ - list prefixes; /* Prefixes in this buckets */ + struct bgp_bucket *next; /* Node in bucket hash table */ + list prefixes; /* Prefixes in this bucket (struct bgp_prefix) */ + u32 hash; /* Hash over extended attributes */ ea_list eattrs[0]; /* Per-bucket extended attributes */ }; +struct bgp_export_state { + struct bgp_proto *proto; + struct bgp_channel *channel; + struct linpool *pool; + + struct bgp_proto *src; + rte *route; + int mpls; + + u32 attrs_seen[1]; + uint err_withdraw; +}; + +struct bgp_write_state { + struct bgp_proto *proto; + struct bgp_channel *channel; + struct linpool *pool; + + int as4_session; + int add_path; + int mpls; + + eattr *mp_next_hop; + adata *mpls_labels; +}; + +struct bgp_parse_state { + struct bgp_proto *proto; + struct bgp_channel *channel; + struct linpool *pool; + + int as4_session; + int add_path; + int mpls; + + u32 attrs_seen[256/32]; + + u32 mp_reach_af; + u32 mp_unreach_af; + + uint attr_len; + uint ip_reach_len; + uint ip_unreach_len; + uint ip_next_hop_len; + uint mp_reach_len; + uint mp_unreach_len; + uint mp_next_hop_len; + + byte *attrs; + byte *ip_reach_nlri; + byte *ip_unreach_nlri; + byte *ip_next_hop_data; + byte *mp_reach_nlri; + byte *mp_unreach_nlri; + byte *mp_next_hop_data; + + uint err_withdraw; + uint err_subcode; + jmp_buf err_jmpbuf; + + struct hostentry *hostentry; + adata *mpls_labels; + + /* Cached state for bgp_rte_update() */ + u32 last_id; + struct rte_src *last_src; + rta *cached_rta; +}; + #define BGP_PORT 179 #define BGP_VERSION 4 #define BGP_HEADER_LENGTH 19 @@ -192,10 +396,30 @@ struct bgp_bucket { #define BGP_RX_BUFFER_EXT_SIZE 65535 #define BGP_TX_BUFFER_EXT_SIZE 65535 -static inline uint bgp_max_packet_length(struct bgp_proto *p) -{ return p->ext_messages ? BGP_MAX_EXT_MSG_LENGTH : BGP_MAX_MESSAGE_LENGTH; } +static inline int bgp_channel_is_ipv4(struct bgp_channel *c) +{ return BGP_AFI(c->afi) == BGP_AFI_IPV4; } + +static inline int bgp_channel_is_ipv6(struct bgp_channel *c) +{ return BGP_AFI(c->afi) == BGP_AFI_IPV6; } + +static inline int bgp_cc_is_ipv4(struct bgp_channel_config *c) +{ return BGP_AFI(c->afi) == BGP_AFI_IPV4; } + +static inline int bgp_cc_is_ipv6(struct bgp_channel_config *c) +{ return BGP_AFI(c->afi) == BGP_AFI_IPV6; } + +static inline uint bgp_max_packet_length(struct bgp_conn *conn) +{ return conn->ext_messages ? BGP_MAX_EXT_MSG_LENGTH : BGP_MAX_MESSAGE_LENGTH; } + +static inline void +bgp_parse_error(struct bgp_parse_state *s, uint subcode) +{ + s->err_subcode = subcode; + longjmp(s->err_jmpbuf, 1); +} extern struct linpool *bgp_linpool; +extern struct linpool *bgp_linpool2; void bgp_start_timer(struct timer *t, int value); @@ -208,9 +432,9 @@ void bgp_conn_enter_established_state(struct bgp_conn *conn); void bgp_conn_enter_close_state(struct bgp_conn *conn); void bgp_conn_enter_idle_state(struct bgp_conn *conn); void bgp_handle_graceful_restart(struct bgp_proto *p); -void bgp_graceful_restart_done(struct bgp_proto *p); -void bgp_refresh_begin(struct bgp_proto *p); -void bgp_refresh_end(struct bgp_proto *p); +void bgp_graceful_restart_done(struct bgp_channel *c); +void bgp_refresh_begin(struct bgp_channel *c); +void bgp_refresh_end(struct bgp_channel *c); void bgp_store_error(struct bgp_proto *p, struct bgp_conn *c, u8 class, u32 code); void bgp_stop(struct bgp_proto *p, unsigned subcode); @@ -233,48 +457,71 @@ struct rte_source *bgp_get_source(struct bgp_proto *p, u32 path_id); /* attrs.c */ -/* Hack: although BA_NEXT_HOP attribute has type EAF_TYPE_IP_ADDRESS, in IPv6 - * we store two addesses in it - a global address and a link local address. - */ -#ifdef IPV6 -#define NEXT_HOP_LENGTH (2*sizeof(ip_addr)) -static inline void set_next_hop(byte *b, ip_addr addr) { ((ip_addr *) b)[0] = addr; ((ip_addr *) b)[1] = IPA_NONE; } -#else -#define NEXT_HOP_LENGTH sizeof(ip_addr) -static inline void set_next_hop(byte *b, ip_addr addr) { ((ip_addr *) b)[0] = addr; } -#endif +static inline eattr * +bgp_find_attr(ea_list *attrs, uint code) +{ + return ea_find(attrs, EA_CODE(EAP_BGP, code)); +} + +eattr * +bgp_set_attr(ea_list **attrs, struct linpool *pool, uint code, uint flags, uintptr_t val); + +static inline void +bgp_set_attr_u32(ea_list **to, struct linpool *pool, uint code, uint flags, u32 val) +{ bgp_set_attr(to, pool, code, flags, (uintptr_t) val); } + +static inline void +bgp_set_attr_ptr(ea_list **to, struct linpool *pool, uint code, uint flags, struct adata *val) +{ bgp_set_attr(to, pool, code, flags, (uintptr_t) val); } + +static inline void +bgp_set_attr_data(ea_list **to, struct linpool *pool, uint code, uint flags, void *data, uint len) +{ + struct adata *a = lp_alloc_adata(pool, len); + memcpy(a->data, data, len); + bgp_set_attr(to, pool, code, flags, (uintptr_t) a); +} + +static inline void +bgp_unset_attr(ea_list **to, struct linpool *pool, uint code) +{ eattr *e = bgp_set_attr(to, pool, code, 0, 0); e->type = EAF_TYPE_UNDEF; } + + +int bgp_encode_attrs(struct bgp_write_state *s, ea_list *attrs, byte *buf, byte *end); +ea_list * bgp_decode_attrs(struct bgp_parse_state *s, byte *data, uint len); + +void bgp_init_bucket_table(struct bgp_channel *c); +void bgp_free_bucket(struct bgp_channel *c, struct bgp_bucket *b); +void bgp_defer_bucket(struct bgp_channel *c, struct bgp_bucket *b); +void bgp_withdraw_bucket(struct bgp_channel *c, struct bgp_bucket *b); + +void bgp_init_prefix_table(struct bgp_channel *c); +void bgp_free_prefix(struct bgp_channel *c, struct bgp_prefix *bp); -void bgp_attach_attr(struct ea_list **to, struct linpool *pool, unsigned attr, uintptr_t val); -byte *bgp_attach_attr_wa(struct ea_list **to, struct linpool *pool, unsigned attr, unsigned len); -struct rta *bgp_decode_attrs(struct bgp_conn *conn, byte *a, uint len, struct linpool *pool, int mandatory); -int bgp_get_attr(struct eattr *e, byte *buf, int buflen); 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); -void bgp_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old UNUSED, ea_list *attrs); +void bgp_rt_notify(struct proto *P, struct channel *C, net *n, rte *new, rte *old, ea_list *attrs); int bgp_import_control(struct proto *, struct rte **, struct ea_list **, struct linpool *); -void bgp_init_bucket_table(struct bgp_proto *); -void bgp_free_bucket_table(struct bgp_proto *p); -void bgp_free_bucket(struct bgp_proto *p, struct bgp_bucket *buck); -void bgp_init_prefix_table(struct bgp_proto *p, u32 order); -void bgp_free_prefix_table(struct bgp_proto *p); -void bgp_free_prefix(struct bgp_proto *p, struct bgp_prefix *bp); -uint bgp_encode_attrs(struct bgp_proto *p, byte *w, ea_list *attrs, int remains); +int bgp_get_attr(struct eattr *e, byte *buf, int buflen); void bgp_get_route_info(struct rte *, byte *buf, struct ea_list *attrs); -inline static void bgp_attach_attr_ip(struct ea_list **to, struct linpool *pool, unsigned attr, ip_addr a) -{ *(ip_addr *) bgp_attach_attr_wa(to, pool, attr, sizeof(ip_addr)) = a; } /* packets.c */ void mrt_dump_bgp_state_change(struct bgp_conn *conn, unsigned old, unsigned new); -void bgp_schedule_packet(struct bgp_conn *conn, int type); +const struct bgp_af_desc *bgp_get_af_desc(u32 afi); +const struct bgp_af_caps *bgp_find_af_caps(struct bgp_caps *caps, u32 afi); +void bgp_schedule_packet(struct bgp_conn *conn, struct bgp_channel *c, int type); void bgp_kick_tx(void *vconn); void bgp_tx(struct birdsock *sk); int bgp_rx(struct birdsock *sk, uint size); const char * bgp_error_dsc(unsigned code, unsigned subcode); void bgp_log_error(struct bgp_proto *p, u8 class, char *msg, unsigned code, unsigned subcode, byte *data, unsigned len); +void bgp_update_next_hop(struct bgp_export_state *s, eattr *a, ea_list **to); + + /* Packet types */ #define PKT_OPEN 0x01 @@ -292,26 +539,25 @@ void bgp_log_error(struct bgp_proto *p, u8 class, char *msg, unsigned code, unsi #define BAF_PARTIAL 0x20 #define BAF_EXT_LEN 0x10 -#define BA_ORIGIN 0x01 /* [RFC1771] */ /* WM */ +#define BA_ORIGIN 0x01 /* RFC 4271 */ /* WM */ #define BA_AS_PATH 0x02 /* WM */ #define BA_NEXT_HOP 0x03 /* WM */ #define BA_MULTI_EXIT_DISC 0x04 /* ON */ #define BA_LOCAL_PREF 0x05 /* WD */ #define BA_ATOMIC_AGGR 0x06 /* WD */ #define BA_AGGREGATOR 0x07 /* OT */ -#define BA_COMMUNITY 0x08 /* [RFC1997] */ /* OT */ -#define BA_ORIGINATOR_ID 0x09 /* [RFC1966] */ /* ON */ -#define BA_CLUSTER_LIST 0x0a /* ON */ -/* We don't support these: */ -#define BA_DPA 0x0b /* ??? */ -#define BA_ADVERTISER 0x0c /* [RFC1863] */ -#define BA_RCID_PATH 0x0d -#define BA_MP_REACH_NLRI 0x0e /* [RFC2283] */ -#define BA_MP_UNREACH_NLRI 0x0f -#define BA_EXT_COMMUNITY 0x10 /* [RFC4360] */ -#define BA_AS4_PATH 0x11 /* [RFC4893] */ -#define BA_AS4_AGGREGATOR 0x12 -#define BA_LARGE_COMMUNITY 0x20 /* [RFC8092] */ +#define BA_COMMUNITY 0x08 /* RFC 1997 */ /* OT */ +#define BA_ORIGINATOR_ID 0x09 /* RFC 4456 */ /* ON */ +#define BA_CLUSTER_LIST 0x0a /* RFC 4456 */ /* ON */ +#define BA_MP_REACH_NLRI 0x0e /* RFC 4760 */ +#define BA_MP_UNREACH_NLRI 0x0f /* RFC 4760 */ +#define BA_EXT_COMMUNITY 0x10 /* RFC 4360 */ +#define BA_AS4_PATH 0x11 /* RFC 6793 */ +#define BA_AS4_AGGREGATOR 0x12 /* RFC 6793 */ +#define BA_LARGE_COMMUNITY 0x20 /* RFC 8092 */ + +/* Bird's private internal BGP attributes */ +#define BA_MPLS_LABEL_STACK 0xfe /* MPLS label stack transfer attribute */ /* BGP connection states */ @@ -331,14 +577,12 @@ void bgp_log_error(struct bgp_proto *p, u8 class, char *msg, unsigned code, unsi * * When BGP protocol is started by core, it goes to BSS_PREPARE. When BGP * protocol done what is neccessary to start itself (like acquiring the lock), - * it goes to BSS_CONNECT. When some connection attempt failed because of - * option or capability error, it goes to BSS_CONNECT_NOCAP. + * it goes to BSS_CONNECT. */ #define BSS_PREPARE 0 /* Used before ordinary BGP started, i. e. waiting for lock */ #define BSS_DELAY 1 /* Startup delay due to previous errors */ #define BSS_CONNECT 2 /* Ordinary BGP connecting */ -#define BSS_CONNECT_NOCAP 3 /* Legacy BGP connecting (without capabilities) */ /* BGP feed states (TX) @@ -347,7 +591,7 @@ void bgp_log_error(struct bgp_proto *p, u8 class, char *msg, unsigned code, unsi * * RFC 7313 specifies that a route refresh should be demarcated by BoRR and EoRR packets. * - * These states (stored in p->feed_state) are used to keep track of these + * These states (stored in c->feed_state) are used to keep track of these * requirements. When such feed is started, BFS_LOADING / BFS_REFRESHING is * set. When it ended, BFS_LOADED / BFS_REFRESHED is set to schedule End-of-RIB * or EoRR packet. When the packet is sent, the state returned to BFS_NONE. @@ -403,15 +647,5 @@ void bgp_log_error(struct bgp_proto *p, u8 class, char *msg, unsigned code, unsi #define ORIGIN_EGP 1 #define ORIGIN_INCOMPLETE 2 -/* Address families */ - -#define BGP_AF_IPV4 1 -#define BGP_AF_IPV6 2 - -#ifdef IPV6 -#define BGP_AF BGP_AF_IPV6 -#else -#define BGP_AF BGP_AF_IPV4 -#endif #endif diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y index 55c602f1..941ae5b6 100644 --- a/proto/bgp/config.Y +++ b/proto/bgp/config.Y @@ -13,28 +13,32 @@ CF_HDR CF_DEFINES #define BGP_CFG ((struct bgp_config *) this_proto) +#define BGP_CC ((struct bgp_channel_config *) this_channel) CF_DECLS -CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, - KEEPALIVE, MULTIHOP, STARTUP, VIA, NEXT, HOP, SELF, DEFAULT, - PATH, METRIC, ERROR, START, DELAY, FORGET, WAIT, ENABLE, - DISABLE, AFTER, BGP_PATH, BGP_LOCAL_PREF, BGP_MED, BGP_ORIGIN, - BGP_NEXT_HOP, BGP_ATOMIC_AGGR, BGP_AGGREGATOR, BGP_COMMUNITY, - BGP_EXT_COMMUNITY, SOURCE, ADDRESS, PASSWORD, RR, RS, CLIENT, - CLUSTER, ID, AS4, ADVERTISE, IPV4, CAPABILITIES, LIMIT, PASSIVE, - PREFER, OLDER, MISSING, LLADDR, DROP, IGNORE, ROUTE, REFRESH, - INTERPRET, COMMUNITIES, BGP_ORIGINATOR_ID, BGP_CLUSTER_LIST, IGP, - TABLE, GATEWAY, DIRECT, RECURSIVE, MED, TTL, SECURITY, DETERMINISTIC, - SECONDARY, ALLOW, BFD, ADD, PATHS, RX, TX, GRACEFUL, RESTART, AWARE, - CHECK, LINK, PORT, EXTENDED, MESSAGES, SETKEY, BGP_LARGE_COMMUNITY) +CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, KEEPALIVE, + MULTIHOP, STARTUP, VIA, NEXT, HOP, SELF, DEFAULT, PATH, METRIC, ERROR, + START, DELAY, FORGET, WAIT, ENABLE, DISABLE, AFTER, BGP_PATH, + BGP_LOCAL_PREF, BGP_MED, BGP_ORIGIN, BGP_NEXT_HOP, BGP_ATOMIC_AGGR, + BGP_AGGREGATOR, BGP_COMMUNITY, BGP_EXT_COMMUNITY, BGP_LARGE_COMMUNITY, + SOURCE, ADDRESS, PASSWORD, RR, RS, CLIENT, CLUSTER, ID, AS4, ADVERTISE, + IPV4, CAPABILITIES, LIMIT, PASSIVE, PREFER, OLDER, MISSING, LLADDR, + DROP, IGNORE, ROUTE, REFRESH, INTERPRET, COMMUNITIES, BGP_ORIGINATOR_ID, + BGP_CLUSTER_LIST, IGP, TABLE, GATEWAY, DIRECT, RECURSIVE, MED, TTL, + SECURITY, DETERMINISTIC, SECONDARY, ALLOW, BFD, ADD, PATHS, RX, TX, + GRACEFUL, RESTART, AWARE, CHECK, LINK, PORT, EXTENDED, MESSAGES, SETKEY, + STRICT, BIND, CONFEDERATION, MEMBER, MULTICAST, FLOW4, FLOW6) + +%type <i32> bgp_afi CF_GRAMMAR -CF_ADDTO(proto, bgp_proto '}' { bgp_check_config(BGP_CFG); } ) +CF_ADDTO(proto, bgp_proto '}' ) bgp_proto_start: proto_start BGP { this_proto = proto_config_new(&proto_bgp, $1); + BGP_CFG->local_port = BGP_PORT; BGP_CFG->remote_port = BGP_PORT; BGP_CFG->multihop = -1; /* undefined */ BGP_CFG->hold_time = 240; @@ -49,26 +53,35 @@ bgp_proto_start: proto_start BGP { BGP_CFG->enable_refresh = 1; BGP_CFG->enable_as4 = 1; BGP_CFG->capabilities = 2; - BGP_CFG->advertise_ipv4 = 1; BGP_CFG->interpret_communities = 1; BGP_CFG->default_local_pref = 100; BGP_CFG->gr_mode = BGP_GR_AWARE; BGP_CFG->gr_time = 120; BGP_CFG->setkey = 1; - } + } + ; + +bgp_loc_opts: + /* empty */ + | bgp_loc_opts PORT expr { BGP_CFG->local_port = $3; if (($3<1) || ($3>65535)) cf_error("Invalid port number"); } + | bgp_loc_opts AS expr { BGP_CFG->local_as = $3; } ; bgp_nbr_opts: /* empty */ - | bgp_nbr_opts PORT expr { BGP_CFG->remote_port = $3; if (($3<1) || ($3>65535)) cf_error("Invalid port number"); } + | bgp_nbr_opts PORT expr { BGP_CFG->remote_port = $3; if (($3<1) || ($3>65535)) cf_error("Invalid port number"); } | bgp_nbr_opts AS expr { BGP_CFG->remote_as = $3; } ; bgp_proto: bgp_proto_start proto_name '{' | bgp_proto proto_item ';' - | bgp_proto LOCAL AS expr ';' { BGP_CFG->local_as = $4; } - | bgp_proto LOCAL ipa AS expr ';' { BGP_CFG->source_addr = $3; BGP_CFG->local_as = $5; } + | bgp_proto bgp_proto_channel ';' + | bgp_proto LOCAL bgp_loc_opts ';' + | bgp_proto LOCAL ipa ipa_scope bgp_loc_opts ';' { + BGP_CFG->local_ip = $3; + if ($4) BGP_CFG->iface = $4; + } | bgp_proto NEIGHBOR bgp_nbr_opts ';' | bgp_proto NEIGHBOR ipa ipa_scope bgp_nbr_opts ';' { if (ipa_nonzero(BGP_CFG->remote_ip)) @@ -78,20 +91,16 @@ bgp_proto: } | bgp_proto INTERFACE TEXT ';' { BGP_CFG->iface = if_get_by_name($3); } | bgp_proto RR CLUSTER ID idval ';' { BGP_CFG->rr_cluster_id = $5; } - | bgp_proto RR CLIENT ';' { BGP_CFG->rr_client = 1; } - | bgp_proto RS CLIENT ';' { BGP_CFG->rs_client = 1; } + | bgp_proto RR CLIENT bool ';' { BGP_CFG->rr_client = $4; } + | bgp_proto RS CLIENT bool ';' { BGP_CFG->rs_client = $4; } + | bgp_proto CONFEDERATION expr ';' { BGP_CFG->confederation = $3; } + | bgp_proto CONFEDERATION MEMBER bool ';' { BGP_CFG->confederation_member = $4; } | bgp_proto HOLD TIME expr ';' { BGP_CFG->hold_time = $4; } | bgp_proto STARTUP HOLD TIME expr ';' { BGP_CFG->initial_hold_time = $5; } | bgp_proto DIRECT ';' { BGP_CFG->multihop = 0; } | bgp_proto MULTIHOP ';' { BGP_CFG->multihop = 64; } | bgp_proto MULTIHOP expr ';' { BGP_CFG->multihop = $3; if (($3<1) || ($3>255)) cf_error("Multihop must be in range 1-255"); } - | bgp_proto NEXT HOP SELF ';' { BGP_CFG->next_hop_self = 1; BGP_CFG->next_hop_keep = 0; } - | bgp_proto NEXT HOP KEEP ';' { BGP_CFG->next_hop_keep = 1; BGP_CFG->next_hop_self = 0; } - | bgp_proto MISSING LLADDR SELF ';' { BGP_CFG->missing_lladdr = MLL_SELF; } - | bgp_proto MISSING LLADDR DROP ';' { BGP_CFG->missing_lladdr = MLL_DROP; } - | bgp_proto MISSING LLADDR IGNORE ';' { BGP_CFG->missing_lladdr = MLL_IGNORE; } - | bgp_proto GATEWAY DIRECT ';' { BGP_CFG->gw_mode = GW_DIRECT; } - | bgp_proto GATEWAY RECURSIVE ';' { BGP_CFG->gw_mode = GW_RECURSIVE; } + | bgp_proto STRICT BIND bool ';' { BGP_CFG->strict_bind = $4; } | bgp_proto PATH METRIC bool ';' { BGP_CFG->compare_path_lengths = $4; } | bgp_proto MED METRIC bool ';' { BGP_CFG->med_metric = $4; } | bgp_proto IGP METRIC bool ';' { BGP_CFG->igp_metric = $4; } @@ -99,7 +108,7 @@ bgp_proto: | bgp_proto DETERMINISTIC MED bool ';' { BGP_CFG->deterministic_med = $4; } | bgp_proto DEFAULT BGP_MED expr ';' { BGP_CFG->default_med = $4; } | bgp_proto DEFAULT BGP_LOCAL_PREF expr ';' { BGP_CFG->default_local_pref = $4; } - | bgp_proto SOURCE ADDRESS ipa ';' { BGP_CFG->source_addr = $4; } + | bgp_proto SOURCE ADDRESS ipa ';' { BGP_CFG->local_ip = $4; } | bgp_proto START DELAY TIME expr ';' { BGP_CFG->connect_delay_time = $5; log(L_WARN "%s: Start delay time option is deprecated, use connect delay time", this_proto->name); } | bgp_proto CONNECT DELAY TIME expr ';' { BGP_CFG->connect_delay_time = $5; } | bgp_proto CONNECT RETRY TIME expr ';' { BGP_CFG->connect_retry_time = $5; } @@ -111,33 +120,101 @@ bgp_proto: | bgp_proto ENABLE AS4 bool ';' { BGP_CFG->enable_as4 = $4; } | bgp_proto ENABLE EXTENDED MESSAGES bool ';' { BGP_CFG->enable_extended_messages = $5; } | bgp_proto CAPABILITIES bool ';' { BGP_CFG->capabilities = $3; } - | bgp_proto ADVERTISE IPV4 bool ';' { BGP_CFG->advertise_ipv4 = $4; } | bgp_proto PASSWORD text ';' { BGP_CFG->password = $3; } | bgp_proto SETKEY bool ';' { BGP_CFG->setkey = $3; } - | bgp_proto ROUTE LIMIT expr ';' { - this_proto->in_limit = cfg_allocz(sizeof(struct proto_limit)); - this_proto->in_limit->limit = $4; - this_proto->in_limit->action = PLA_RESTART; - log(L_WARN "%s: Route limit option is deprecated, use import limit", this_proto->name); - } | bgp_proto PASSIVE bool ';' { BGP_CFG->passive = $3; } | bgp_proto INTERPRET COMMUNITIES bool ';' { BGP_CFG->interpret_communities = $4; } - | bgp_proto SECONDARY bool ';' { BGP_CFG->secondary = $3; } - | bgp_proto ADD PATHS RX ';' { BGP_CFG->add_path = ADD_PATH_RX; } - | bgp_proto ADD PATHS TX ';' { BGP_CFG->add_path = ADD_PATH_TX; } - | bgp_proto ADD PATHS bool ';' { BGP_CFG->add_path = $4 ? ADD_PATH_FULL : 0; } - | bgp_proto ALLOW BGP_LOCAL_PREF bool ';' { BGP_CFG->allow_local_pref = $4; } | bgp_proto ALLOW LOCAL AS ';' { BGP_CFG->allow_local_as = -1; } | bgp_proto ALLOW LOCAL AS expr ';' { BGP_CFG->allow_local_as = $5; } + | bgp_proto ALLOW BGP_LOCAL_PREF bool ';' { BGP_CFG->allow_local_pref = $4; } | bgp_proto GRACEFUL RESTART bool ';' { BGP_CFG->gr_mode = $4; } | bgp_proto GRACEFUL RESTART AWARE ';' { BGP_CFG->gr_mode = BGP_GR_AWARE; } | bgp_proto GRACEFUL RESTART TIME expr ';' { BGP_CFG->gr_time = $5; } - | bgp_proto IGP TABLE rtable ';' { BGP_CFG->igp_table = $4; } | bgp_proto TTL SECURITY bool ';' { BGP_CFG->ttl_security = $4; } | bgp_proto CHECK LINK bool ';' { BGP_CFG->check_link = $4; } | bgp_proto BFD bool ';' { BGP_CFG->bfd = $3; cf_check_bfd($3); } ; +bgp_afi: + IPV4 { $$ = BGP_AF_IPV4; } + | IPV6 { $$ = BGP_AF_IPV6; } + | IPV4 MULTICAST { $$ = BGP_AF_IPV4_MC; } + | IPV6 MULTICAST { $$ = BGP_AF_IPV6_MC; } + | IPV4 MPLS { $$ = BGP_AF_IPV4_MPLS; } + | IPV6 MPLS { $$ = BGP_AF_IPV6_MPLS; } + | VPN4 MPLS { $$ = BGP_AF_VPN4_MPLS; } + | VPN6 MPLS { $$ = BGP_AF_VPN6_MPLS; } + | VPN4 MULTICAST { $$ = BGP_AF_VPN4_MC; } + | VPN6 MULTICAST { $$ = BGP_AF_VPN6_MC; } + | FLOW4 { $$ = BGP_AF_FLOW4; } + | FLOW6 { $$ = BGP_AF_FLOW6; } + ; + +bgp_channel_start: bgp_afi +{ + const struct bgp_af_desc *desc = bgp_get_af_desc($1); + + if (!desc) + cf_error("Unknown AFI/SAFI"); + + this_channel = channel_config_new(&channel_bgp, desc->net, this_proto); + BGP_CC->c.name = desc->name; + BGP_CC->c.ra_mode = RA_UNDEF; + BGP_CC->afi = $1; + BGP_CC->desc = desc; + BGP_CC->gr_able = 0xff; /* undefined */ +}; + +bgp_channel_item: + channel_item + | NEXT HOP ADDRESS ipa { BGP_CC->next_hop_addr = $4; } + | NEXT HOP SELF { BGP_CC->next_hop_self = 1; BGP_CC->next_hop_keep = 0; } + | NEXT HOP KEEP { BGP_CC->next_hop_keep = 1; BGP_CC->next_hop_self = 0; } + | MISSING LLADDR SELF { BGP_CC->missing_lladdr = MLL_SELF; } + | MISSING LLADDR DROP { BGP_CC->missing_lladdr = MLL_DROP; } + | MISSING LLADDR IGNORE { BGP_CC->missing_lladdr = MLL_IGNORE; } + | GATEWAY DIRECT { BGP_CC->gw_mode = GW_DIRECT; } + | GATEWAY RECURSIVE { BGP_CC->gw_mode = GW_RECURSIVE; } + | SECONDARY bool { BGP_CC->secondary = $2; } + | GRACEFUL RESTART bool { BGP_CC->gr_able = $3; } + | EXTENDED NEXT HOP bool { BGP_CC->ext_next_hop = $4; } + | ADD PATHS RX { BGP_CC->add_path = BGP_ADD_PATH_RX; } + | ADD PATHS TX { BGP_CC->add_path = BGP_ADD_PATH_TX; } + | ADD PATHS bool { BGP_CC->add_path = $3 ? BGP_ADD_PATH_FULL : 0; } + | IGP TABLE rtable { + if (BGP_CC->desc->no_igp) + cf_error("IGP table not allowed here"); + + if ($3->addr_type == NET_IP4) + BGP_CC->igp_table_ip4 = $3; + else if ($3->addr_type == NET_IP6) + BGP_CC->igp_table_ip6 = $3; + else + cf_error("Mismatched IGP table type"); + } + ; + +bgp_channel_opts: + /* empty */ + | bgp_channel_opts bgp_channel_item ';' + ; + +bgp_channel_opt_list: + /* empty */ + | '{' bgp_channel_opts '}' + ; + +bgp_channel_end: +{ + if (!this_channel->table) + cf_error("Routing table not specified"); + + this_channel = NULL; +}; + +bgp_proto_channel: bgp_channel_start bgp_channel_opt_list bgp_channel_end; + + CF_ADDTO(dynamic_attr, BGP_ORIGIN { $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_ENUM_BGP_ORIGIN, EA_CODE(EAP_BGP, BA_ORIGIN)); }) CF_ADDTO(dynamic_attr, BGP_PATH diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c index ab87bdcc..0e974746 100644 --- a/proto/bgp/packets.c +++ b/proto/bgp/packets.c @@ -2,12 +2,16 @@ * BIRD -- BGP Packet Processing * * (c) 2000 Martin Mares <mj@ucw.cz> + * (c) 2008--2016 Ondrej Zajicek <santiago@crfreenet.org> + * (c) 2008--2016 CZ.NIC z.s.p.o. * * Can be freely distributed and used under the terms of the GNU GPL. */ #undef LOCAL_DEBUG +#include <stdlib.h> + #include "nest/bird.h" #include "nest/iface.h" #include "nest/protocol.h" @@ -16,6 +20,7 @@ #include "nest/mrtdump.h" #include "conf/conf.h" #include "lib/unaligned.h" +#include "lib/flowspec.h" #include "lib/socket.h" #include "nest/cli.h" @@ -27,6 +32,13 @@ #define BGP_RR_BEGIN 1 #define BGP_RR_END 2 +#define BGP_NLRI_MAX (4 + 1 + 32) + +#define BGP_MPLS_BOS 1 /* Bottom-of-stack bit */ +#define BGP_MPLS_MAX 10 /* Max number of labels that 24*n <= 255 */ +#define BGP_MPLS_NULL 3 /* Implicit NULL label */ +#define BGP_MPLS_MAGIC 0x800000 /* Magic withdraw label value, RFC 3107 3 */ + static struct tbf rl_rcv_update = TBF_DEFAULT_LOG_LIMITS; static struct tbf rl_snd_update = TBF_DEFAULT_LOG_LIMITS; @@ -38,6 +50,46 @@ static byte fsm_err_subcode[BS_MAX] = { [BS_ESTABLISHED] = 3 }; + +static struct bgp_channel * +bgp_get_channel(struct bgp_proto *p, u32 afi) +{ + uint i; + + for (i = 0; i < p->channel_count; i++) + if (p->afi_map[i] == afi) + return p->channel_map[i]; + + return NULL; +} + +static inline void +put_af3(byte *buf, u32 id) +{ + put_u16(buf, id >> 16); + buf[2] = id & 0xff; +} + +static inline void +put_af4(byte *buf, u32 id) +{ + put_u16(buf, id >> 16); + buf[2] = 0; + buf[3] = id & 0xff; +} + +static inline u32 +get_af3(byte *buf) +{ + return (get_u16(buf) << 16) | buf[2]; +} + +static inline u32 +get_af4(byte *buf) +{ + return (get_u16(buf) << 16) | buf[3]; +} + /* * MRT Dump format is not semantically specified. * We will use these values in appropriate fields: @@ -58,31 +110,41 @@ static byte * mrt_put_bgp4_hdr(byte *buf, struct bgp_conn *conn, int as4) { struct bgp_proto *p = conn->bgp; + uint v4 = ipa_is_ip4(p->cf->remote_ip); if (as4) - { - put_u32(buf+0, p->remote_as); - put_u32(buf+4, p->local_as); - buf+=8; - } + { + put_u32(buf+0, p->remote_as); + put_u32(buf+4, p->public_as); + buf+=8; + } else - { - put_u16(buf+0, (p->remote_as <= 0xFFFF) ? p->remote_as : AS_TRANS); - put_u16(buf+2, (p->local_as <= 0xFFFF) ? p->local_as : AS_TRANS); - buf+=4; - } + { + put_u16(buf+0, (p->remote_as <= 0xFFFF) ? p->remote_as : AS_TRANS); + put_u16(buf+2, (p->public_as <= 0xFFFF) ? p->public_as : AS_TRANS); + buf+=4; + } put_u16(buf+0, (p->neigh && p->neigh->iface) ? p->neigh->iface->index : 0); - put_u16(buf+2, BGP_AF); + put_u16(buf+2, v4 ? BGP_AFI_IPV4 : BGP_AFI_IPV6); buf+=4; - buf = put_ipa(buf, conn->sk ? conn->sk->daddr : IPA_NONE); - buf = put_ipa(buf, conn->sk ? conn->sk->saddr : IPA_NONE); + + if (v4) + { + buf = put_ip4(buf, conn->sk ? ipa_to_ip4(conn->sk->daddr) : IP4_NONE); + buf = put_ip4(buf, conn->sk ? ipa_to_ip4(conn->sk->saddr) : IP4_NONE); + } + else + { + buf = put_ip6(buf, conn->sk ? ipa_to_ip6(conn->sk->daddr) : IP6_NONE); + buf = put_ip6(buf, conn->sk ? ipa_to_ip6(conn->sk->saddr) : IP6_NONE); + } return buf; } static void -mrt_dump_bgp_packet(struct bgp_conn *conn, byte *pkt, unsigned len) +mrt_dump_bgp_packet(struct bgp_conn *conn, byte *pkt, uint len) { byte *buf = alloca(128+len); /* 128 is enough for MRT headers */ byte *bp = buf + MRTDUMP_HDR_LENGTH; @@ -96,14 +158,14 @@ mrt_dump_bgp_packet(struct bgp_conn *conn, byte *pkt, unsigned len) } static inline u16 -convert_state(unsigned state) +convert_state(uint state) { /* Convert state from our BS_* values to values used in MRTDump */ return (state == BS_CLOSE) ? 1 : state + 1; } void -mrt_dump_bgp_state_change(struct bgp_conn *conn, unsigned old, unsigned new) +mrt_dump_bgp_state_change(struct bgp_conn *conn, uint old, uint new) { byte buf[128]; byte *bp = buf + MRTDUMP_HDR_LENGTH; @@ -127,1303 +189,2426 @@ bgp_create_notification(struct bgp_conn *conn, byte *buf) return buf + 2 + conn->notify_size; } -#ifdef IPV6 -static byte * -bgp_put_cap_ipv6(struct bgp_proto *p UNUSED, byte *buf) -{ - *buf++ = 1; /* Capability 1: Multiprotocol extensions */ - *buf++ = 4; /* Capability data length */ - *buf++ = 0; /* We support AF IPv6 */ - *buf++ = BGP_AF_IPV6; - *buf++ = 0; /* RFU */ - *buf++ = 1; /* and SAFI 1 */ - return buf; -} -#else +/* Capability negotiation as per RFC 5492 */ -static byte * -bgp_put_cap_ipv4(struct bgp_proto *p UNUSED, byte *buf) -{ - *buf++ = 1; /* Capability 1: Multiprotocol extensions */ - *buf++ = 4; /* Capability data length */ - *buf++ = 0; /* We support AF IPv4 */ - *buf++ = BGP_AF_IPV4; - *buf++ = 0; /* RFU */ - *buf++ = 1; /* and SAFI 1 */ - return buf; +const struct bgp_af_caps * +bgp_find_af_caps(struct bgp_caps *caps, u32 afi) +{ + struct bgp_af_caps *ac; + + WALK_AF_CAPS(caps, ac) + if (ac->afi == afi) + return ac; + + return NULL; } -#endif -static byte * -bgp_put_cap_rr(struct bgp_proto *p UNUSED, byte *buf) +static struct bgp_af_caps * +bgp_get_af_caps(struct bgp_caps *caps, u32 afi) { - *buf++ = 2; /* Capability 2: Support for route refresh */ - *buf++ = 0; /* Capability data length */ - return buf; + struct bgp_af_caps *ac; + + WALK_AF_CAPS(caps, ac) + if (ac->afi == afi) + return ac; + + ac = &caps->af_data[caps->af_count++]; + memset(ac, 0, sizeof(struct bgp_af_caps)); + ac->afi = afi; + + return ac; } -static byte * -bgp_put_cap_ext_msg(struct bgp_proto *p UNUSED, byte *buf) +static int +bgp_af_caps_cmp(const void *X, const void *Y) { - *buf++ = 6; /* Capability 6: Support for extended messages */ - *buf++ = 0; /* Capability data length */ - return buf; + const struct bgp_af_caps *x = X, *y = Y; + return (x->afi < y->afi) ? -1 : (x->afi > y->afi) ? 1 : 0; } + static byte * -bgp_put_cap_gr1(struct bgp_proto *p, byte *buf) +bgp_write_capabilities(struct bgp_conn *conn, byte *buf) { - *buf++ = 64; /* Capability 64: Support for graceful restart */ - *buf++ = 6; /* Capability data length */ + struct bgp_proto *p = conn->bgp; + struct bgp_channel *c; + struct bgp_caps *caps; + struct bgp_af_caps *ac; + uint any_ext_next_hop = 0; + uint any_add_path = 0; + byte *data; - put_u16(buf, p->cf->gr_time); - if (p->p.gr_recovery) - buf[0] |= BGP_GRF_RESTART; - buf += 2; + /* Prepare bgp_caps structure */ + + int n = list_length(&p->p.channels); + caps = mb_allocz(p->p.pool, sizeof(struct bgp_caps) + n * sizeof(struct bgp_af_caps)); + conn->local_caps = caps; + + caps->as4_support = p->cf->enable_as4; + caps->ext_messages = p->cf->enable_extended_messages; + caps->route_refresh = p->cf->enable_refresh; + caps->enhanced_refresh = p->cf->enable_refresh; + + if (caps->as4_support) + caps->as4_number = p->public_as; + + if (p->cf->gr_mode) + { + caps->gr_aware = 1; + caps->gr_time = p->cf->gr_time; + caps->gr_flags = p->p.gr_recovery ? BGP_GRF_RESTART : 0; + } + + /* Allocate and fill per-AF fields */ + WALK_LIST(c, p->p.channels) + { + ac = &caps->af_data[caps->af_count++]; + ac->afi = c->afi; + ac->ready = 1; + + ac->ext_next_hop = bgp_channel_is_ipv4(c) && c->cf->ext_next_hop; + any_ext_next_hop |= ac->ext_next_hop; + + ac->add_path = c->cf->add_path; + any_add_path |= ac->add_path; + + if (c->cf->gr_able) + { + ac->gr_able = 1; + + if (p->p.gr_recovery) + ac->gr_af_flags |= BGP_GRF_FORWARDING; + } + } + + /* Sort capability fields by AFI/SAFI */ + qsort(caps->af_data, caps->af_count, sizeof(struct bgp_af_caps), bgp_af_caps_cmp); - *buf++ = 0; /* Appropriate AF */ - *buf++ = BGP_AF; - *buf++ = 1; /* and SAFI 1 */ - *buf++ = p->p.gr_recovery ? BGP_GRF_FORWARDING : 0; + + /* Create capability list in buffer */ + + /* + * Note that max length is ~ 20+14*af_count. With max 12 channels that is + * 188. Option limit is 253 and buffer size is 4096, so we cannot overflow + * unless we add new capabilities or more AFs. + */ + + WALK_AF_CAPS(caps, ac) + if (ac->ready) + { + *buf++ = 1; /* Capability 1: Multiprotocol extensions */ + *buf++ = 4; /* Capability data length */ + put_af4(buf, ac->afi); + buf += 4; + } + + if (caps->route_refresh) + { + *buf++ = 2; /* Capability 2: Support for route refresh */ + *buf++ = 0; /* Capability data length */ + } + + if (any_ext_next_hop) + { + *buf++ = 5; /* Capability 5: Support for extended next hop */ + *buf++ = 0; /* Capability data length, will be fixed later */ + data = buf; + + WALK_AF_CAPS(caps, ac) + if (ac->ext_next_hop) + { + put_af4(buf, ac->afi); + put_u16(buf+4, BGP_AFI_IPV6); + buf += 6; + } + + data[-1] = buf - data; + } + + if (caps->ext_messages) + { + *buf++ = 6; /* Capability 6: Support for extended messages */ + *buf++ = 0; /* Capability data length */ + } + + if (caps->gr_aware) + { + *buf++ = 64; /* Capability 64: Support for graceful restart */ + *buf++ = 0; /* Capability data length, will be fixed later */ + data = buf; + + put_u16(buf, caps->gr_time); + buf[0] |= caps->gr_flags; + buf += 2; + + WALK_AF_CAPS(caps, ac) + if (ac->gr_able) + { + put_af3(buf, ac->afi); + buf[3] = ac->gr_af_flags; + buf += 4; + } + + data[-1] = buf - data; + } + + if (caps->as4_support) + { + *buf++ = 65; /* Capability 65: Support for 4-octet AS number */ + *buf++ = 4; /* Capability data length */ + put_u32(buf, p->public_as); + buf += 4; + } + + if (any_add_path) + { + *buf++ = 69; /* Capability 69: Support for ADD-PATH */ + *buf++ = 0; /* Capability data length, will be fixed later */ + data = buf; + + WALK_AF_CAPS(caps, ac) + if (ac->add_path) + { + put_af3(buf, ac->afi); + buf[3] = ac->add_path; + buf += 4; + } + + data[-1] = buf - data; + } + + if (caps->enhanced_refresh) + { + *buf++ = 70; /* Capability 70: Support for enhanced route refresh */ + *buf++ = 0; /* Capability data length */ + } return buf; } -static byte * -bgp_put_cap_gr2(struct bgp_proto *p UNUSED, byte *buf) +static void +bgp_read_capabilities(struct bgp_conn *conn, struct bgp_caps *caps, byte *pos, int len) { - *buf++ = 64; /* Capability 64: Support for graceful restart */ - *buf++ = 2; /* Capability data length */ - put_u16(buf, 0); - return buf + 2; -} + struct bgp_proto *p = conn->bgp; + struct bgp_af_caps *ac; + int i, cl; + u32 af; -static byte * -bgp_put_cap_as4(struct bgp_proto *p, byte *buf) -{ - *buf++ = 65; /* Capability 65: Support for 4-octet AS number */ - *buf++ = 4; /* Capability data length */ - put_u32(buf, p->local_as); - return buf + 4; -} + while (len > 0) + { + if (len < 2 || len < (2 + pos[1])) + goto err; -static byte * -bgp_put_cap_add_path(struct bgp_proto *p, byte *buf) -{ - *buf++ = 69; /* Capability 69: Support for ADD-PATH */ - *buf++ = 4; /* Capability data length */ + /* Capability length */ + cl = pos[1]; - *buf++ = 0; /* Appropriate AF */ - *buf++ = BGP_AF; - *buf++ = 1; /* SAFI 1 */ + /* Capability type */ + switch (pos[0]) + { + case 1: /* Multiprotocol capability, RFC 4760 */ + if (cl != 4) + goto err; - *buf++ = p->cf->add_path; + af = get_af4(pos+2); + ac = bgp_get_af_caps(caps, af); + ac->ready = 1; + break; - return buf; + case 2: /* Route refresh capability, RFC 2918 */ + if (cl != 0) + goto err; + + caps->route_refresh = 1; + break; + + case 5: /* Extended next hop encoding capability, RFC 5549 */ + if (cl % 6) + goto err; + + for (i = 0; i < cl; i += 6) + { + /* Specified only for IPv4 prefixes with IPv6 next hops */ + if ((get_u16(pos+2+i+0) != BGP_AFI_IPV4) || + (get_u16(pos+2+i+4) != BGP_AFI_IPV6)) + continue; + + af = get_af4(pos+2+i); + ac = bgp_get_af_caps(caps, af); + ac->ext_next_hop = 1; + } + break; + + case 6: /* Extended message length capability, RFC draft */ + if (cl != 0) + goto err; + + caps->ext_messages = 1; + break; + + case 64: /* Graceful restart capability, RFC 4724 */ + if (cl % 4 != 2) + goto err; + + /* Only the last instance is valid */ + WALK_AF_CAPS(caps, ac) + { + ac->gr_able = 0; + ac->gr_af_flags = 0; + } + + caps->gr_aware = 1; + caps->gr_flags = pos[2] & 0xf0; + caps->gr_time = get_u16(pos + 2) & 0x0fff; + + for (i = 2; i < cl; i += 4) + { + af = get_af3(pos+2+i); + ac = bgp_get_af_caps(caps, af); + ac->gr_able = 1; + ac->gr_af_flags = pos[2+i+3]; + } + break; + + case 65: /* AS4 capability, RFC 6793 */ + if (cl != 4) + goto err; + + caps->as4_support = 1; + caps->as4_number = get_u32(pos + 2); + break; + + case 69: /* ADD-PATH capability, RFC 7911 */ + if (cl % 4) + goto err; + + for (i = 0; i < cl; i += 4) + { + byte val = pos[2+i+3]; + if (!val || (val > BGP_ADD_PATH_FULL)) + { + log(L_WARN "%s: Got ADD-PATH capability with unknown value %u, ignoring", + p->p.name, val); + break; + } + } + + for (i = 0; i < cl; i += 4) + { + af = get_af3(pos+2+i); + ac = bgp_get_af_caps(caps, af); + ac->add_path = pos[2+i+3]; + } + break; + + case 70: /* Enhanced route refresh capability, RFC 7313 */ + if (cl != 0) + goto err; + + caps->enhanced_refresh = 1; + break; + + /* We can safely ignore all other capabilities */ + } + + ADVANCE(pos, len, 2 + cl); + } + return; + +err: + bgp_error(conn, 2, 0, NULL, 0); + return; } -static byte * -bgp_put_cap_err(struct bgp_proto *p UNUSED, byte *buf) +static int +bgp_read_options(struct bgp_conn *conn, byte *pos, int len) { - *buf++ = 70; /* Capability 70: Support for enhanced route refresh */ - *buf++ = 0; /* Capability data length */ - return buf; -} + struct bgp_proto *p = conn->bgp; + struct bgp_caps *caps; + int ol; + + /* Max number of announced AFIs is limited by max option length (255) */ + caps = alloca(sizeof(struct bgp_caps) + 64 * sizeof(struct bgp_af_caps)); + memset(caps, 0, sizeof(struct bgp_caps)); + + while (len > 0) + { + if ((len < 2) || (len < (2 + pos[1]))) + { bgp_error(conn, 2, 0, NULL, 0); return -1; } + ol = pos[1]; + if (pos[0] == 2) + { + /* BGP capabilities, RFC 5492 */ + if (p->cf->capabilities) + bgp_read_capabilities(conn, caps, pos + 2, ol); + } + else + { + /* Unknown option */ + bgp_error(conn, 2, 4, pos, ol); /* FIXME: ol or ol+2 ? */ + return -1; + } + + ADVANCE(pos, len, 2 + ol); + } + + uint n = sizeof(struct bgp_caps) + caps->af_count * sizeof(struct bgp_af_caps); + conn->remote_caps = mb_allocz(p->p.pool, n); + memcpy(conn->remote_caps, caps, n); + + return 0; +} static byte * bgp_create_open(struct bgp_conn *conn, byte *buf) { struct bgp_proto *p = conn->bgp; - byte *cap; - int cap_len; BGP_TRACE(D_PACKETS, "Sending OPEN(ver=%d,as=%d,hold=%d,id=%08x)", - BGP_VERSION, p->local_as, p->cf->hold_time, p->local_id); + BGP_VERSION, p->public_as, p->cf->hold_time, p->local_id); + buf[0] = BGP_VERSION; - put_u16(buf+1, (p->local_as < 0xFFFF) ? p->local_as : AS_TRANS); + put_u16(buf+1, (p->public_as < 0xFFFF) ? p->public_as : AS_TRANS); put_u16(buf+3, p->cf->hold_time); put_u32(buf+5, p->local_id); - if (conn->start_state == BSS_CONNECT_NOCAP) - { - BGP_TRACE(D_PACKETS, "Skipping capabilities"); - buf[9] = 0; - return buf + 10; - } + if (p->cf->capabilities) + { + /* Prepare local_caps and write capabilities to buffer */ + byte *end = bgp_write_capabilities(conn, buf+12); + uint len = end - (buf+12); + + buf[9] = len + 2; /* Optional parameters length */ + buf[10] = 2; /* Option 2: Capability list */ + buf[11] = len; /* Option data length */ + + return end; + } + else + { + /* Prepare empty local_caps */ + conn->local_caps = mb_allocz(p->p.pool, sizeof(struct bgp_caps)); + + buf[9] = 0; /* No optional parameters */ + return buf + 10; + } + + return buf; +} + +static void +bgp_rx_open(struct bgp_conn *conn, byte *pkt, uint len) +{ + struct bgp_proto *p = conn->bgp; + struct bgp_conn *other; + u32 asn, hold, id; - /* Skipped 3 B for length field and Capabilities parameter header */ - cap = buf + 12; + /* Check state */ + if (conn->state != BS_OPENSENT) + { bgp_error(conn, 5, fsm_err_subcode[conn->state], NULL, 0); return; } + + /* Check message contents */ + if (len < 29 || len != 29 + (uint) pkt[28]) + { bgp_error(conn, 1, 2, pkt+16, 2); return; } -#ifndef IPV6 - if (p->cf->advertise_ipv4) - cap = bgp_put_cap_ipv4(p, cap); -#endif + if (pkt[19] != BGP_VERSION) + { u16 val = BGP_VERSION; bgp_error(conn, 2, 1, (byte *) &val, 2); return; } -#ifdef IPV6 - cap = bgp_put_cap_ipv6(p, cap); -#endif + asn = get_u16(pkt+20); + hold = get_u16(pkt+22); + id = get_u32(pkt+24); + BGP_TRACE(D_PACKETS, "Got OPEN(as=%d,hold=%d,id=%R)", asn, hold, id); - if (p->cf->enable_refresh) - cap = bgp_put_cap_rr(p, cap); + if (bgp_read_options(conn, pkt+29, pkt[28]) < 0) + return; - if (p->cf->gr_mode == BGP_GR_ABLE) - cap = bgp_put_cap_gr1(p, cap); - else if (p->cf->gr_mode == BGP_GR_AWARE) - cap = bgp_put_cap_gr2(p, cap); + if (hold > 0 && hold < 3) + { bgp_error(conn, 2, 6, pkt+22, 2); return; } - if (p->cf->enable_as4) - cap = bgp_put_cap_as4(p, cap); + /* RFC 6286 2.2 - router ID is nonzero and AS-wide unique */ + if (!id || (p->is_internal && id == p->local_id)) + { bgp_error(conn, 2, 3, pkt+24, -4); return; } - if (p->cf->add_path) - cap = bgp_put_cap_add_path(p, cap); + struct bgp_caps *caps = conn->remote_caps; - if (p->cf->enable_refresh) - cap = bgp_put_cap_err(p, cap); + if (caps->as4_support) + { + u32 as4 = caps->as4_number; - if (p->cf->enable_extended_messages) - cap = bgp_put_cap_ext_msg(p, cap); + if ((as4 != asn) && (asn != AS_TRANS)) + log(L_WARN "%s: Peer advertised inconsistent AS numbers", p->p.name); - cap_len = cap - buf - 12; - if (cap_len > 0) - { - buf[9] = cap_len + 2; /* Optional params len */ - buf[10] = 2; /* Option: Capability list */ - buf[11] = cap_len; /* Option length */ - return cap; - } + if (as4 != p->remote_as) + { as4 = htonl(as4); bgp_error(conn, 2, 2, (byte *) &as4, 4); return; } + } else + { + if (asn != p->remote_as) + { bgp_error(conn, 2, 2, pkt+20, 2); return; } + } + + /* Check the other connection */ + other = (conn == &p->outgoing_conn) ? &p->incoming_conn : &p->outgoing_conn; + switch (other->state) + { + case BS_CONNECT: + case BS_ACTIVE: + /* Stop outgoing connection attempts */ + bgp_conn_enter_idle_state(other); + break; + + case BS_IDLE: + case BS_OPENSENT: + case BS_CLOSE: + break; + + case BS_OPENCONFIRM: + /* + * Description of collision detection rules in RFC 4271 is confusing and + * contradictory, but it is essentially: + * + * 1. Router with higher ID is dominant + * 2. If both have the same ID, router with higher ASN is dominant [RFC6286] + * 3. When both connections are in OpenConfirm state, one initiated by + * the dominant router is kept. + * + * The first line in the expression below evaluates whether the neighbor + * is dominant, the second line whether the new connection was initiated + * by the neighbor. If both are true (or both are false), we keep the new + * connection, otherwise we keep the old one. + */ + if (((p->local_id < id) || ((p->local_id == id) && (p->public_as < p->remote_as))) + == (conn == &p->incoming_conn)) { - buf[9] = 0; /* No optional parameters */ - return buf + 10; + /* Should close the other connection */ + BGP_TRACE(D_EVENTS, "Connection collision, giving up the other connection"); + bgp_error(other, 6, 7, NULL, 0); + break; } + /* Fall thru */ + case BS_ESTABLISHED: + /* Should close this connection */ + BGP_TRACE(D_EVENTS, "Connection collision, giving up this connection"); + bgp_error(conn, 6, 7, NULL, 0); + return; + + default: + bug("bgp_rx_open: Unknown state"); + } + + /* Update our local variables */ + conn->hold_time = MIN(hold, p->cf->hold_time); + conn->keepalive_time = p->cf->keepalive_time ? : conn->hold_time / 3; + conn->as4_session = conn->local_caps->as4_support && caps->as4_support; + conn->ext_messages = conn->local_caps->ext_messages && caps->ext_messages; + p->remote_id = id; + + DBG("BGP: Hold timer set to %d, keepalive to %d, AS to %d, ID to %x, AS4 session to %d\n", + 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_conn_enter_openconfirm_state(conn); } -static uint -bgp_encode_prefixes(struct bgp_proto *p, byte *w, struct bgp_bucket *buck, uint remains) + +/* + * Next hop handling + */ + +#define REPORT(msg, args...) \ + ({ log(L_REMOTE "%s: " msg, s->proto->p.name, ## args); }) + +#define DISCARD(msg, args...) \ + ({ REPORT(msg, ## args); return; }) + +#define WITHDRAW(msg, args...) \ + ({ REPORT(msg, ## args); s->err_withdraw = 1; return; }) + +#define BAD_AFI "Unexpected AF <%u/%u> in UPDATE" +#define BAD_NEXT_HOP "Invalid NEXT_HOP attribute" +#define NO_NEXT_HOP "Missing NEXT_HOP attribute" +#define NO_LABEL_STACK "Missing MPLS stack" + + +static void +bgp_apply_next_hop(struct bgp_parse_state *s, rta *a, ip_addr gw, ip_addr ll) { - byte *start = w; - ip_addr a; - int bytes; + struct bgp_proto *p = s->proto; + struct bgp_channel *c = s->channel; - while (!EMPTY_LIST(buck->prefixes) && (remains >= (5+sizeof(ip_addr)))) - { - struct bgp_prefix *px = SKIP_BACK(struct bgp_prefix, bucket_node, HEAD(buck->prefixes)); - DBG("\tDequeued route %I/%d\n", px->n.prefix, px->n.pxlen); + if (c->cf->gw_mode == GW_DIRECT) + { + neighbor *nbr = NULL; - if (p->add_path_tx) - { - put_u32(w, px->path_id); - w += 4; - remains -= 4; - } + /* GW_DIRECT -> single_hop -> p->neigh != NULL */ + if (ipa_nonzero(gw)) + nbr = neigh_find2(&p->p, &gw, NULL, 0); + else if (ipa_nonzero(ll)) + nbr = neigh_find2(&p->p, &ll, p->neigh->iface, 0); - *w++ = px->n.pxlen; - bytes = (px->n.pxlen + 7) / 8; - a = px->n.prefix; - ipa_hton(a); - memcpy(w, &a, bytes); - w += bytes; - remains -= bytes + 1; - rem_node(&px->bucket_node); - bgp_free_prefix(p, px); - // fib_delete(&p->prefix_fib, px); - } - return w - start; + if (!nbr || (nbr->scope == SCOPE_HOST)) + WITHDRAW(BAD_NEXT_HOP); + + a->dest = RTD_UNICAST; + a->nh.gw = nbr->addr; + a->nh.iface = nbr->iface; + } + else /* GW_RECURSIVE */ + { + if (ipa_zero(gw)) + WITHDRAW(BAD_NEXT_HOP); + + rtable *tab = ipa_is_ip4(gw) ? c->igp_table_ip4 : c->igp_table_ip6; + s->hostentry = rt_get_hostentry(tab, gw, ll, c->c.table); + + if (!s->mpls) + rta_apply_hostentry(a, s->hostentry, NULL); + + /* With MPLS, hostentry is applied later in bgp_apply_mpls_labels() */ + } } static void -bgp_flush_prefixes(struct bgp_proto *p, struct bgp_bucket *buck) +bgp_apply_mpls_labels(struct bgp_parse_state *s, rta *a, u32 *labels, uint lnum) { - while (!EMPTY_LIST(buck->prefixes)) - { - struct bgp_prefix *px = SKIP_BACK(struct bgp_prefix, bucket_node, HEAD(buck->prefixes)); - log(L_ERR "%s: - route %I/%d skipped", p->p.name, px->n.prefix, px->n.pxlen); - rem_node(&px->bucket_node); - bgp_free_prefix(p, px); - // fib_delete(&p->prefix_fib, px); - } + if (lnum > MPLS_MAX_LABEL_STACK) + { + REPORT("Too many MPLS labels ($u)", lnum); + + a->dest = RTD_UNREACHABLE; + a->hostentry = NULL; + a->nh = (struct nexthop) { }; + return; + } + + /* Handle implicit NULL as empty MPLS stack */ + if ((lnum == 1) && (labels[0] == BGP_MPLS_NULL)) + lnum = 0; + + if (s->channel->cf->gw_mode == GW_DIRECT) + { + a->nh.labels = lnum; + memcpy(a->nh.label, labels, 4*lnum); + } + else /* GW_RECURSIVE */ + { + mpls_label_stack ms; + + ms.len = lnum; + memcpy(ms.stack, labels, 4*lnum); + rta_apply_hostentry(a, s->hostentry, &ms); + } } -#ifndef IPV6 /* IPv4 version */ -static byte * -bgp_create_update(struct bgp_conn *conn, byte *buf) +static inline int +bgp_use_next_hop(struct bgp_export_state *s, eattr *a) { - struct bgp_proto *p = conn->bgp; - struct bgp_bucket *buck; - int remains = bgp_max_packet_length(p) - BGP_HEADER_LENGTH - 4; - byte *w; - int wd_size = 0; - int r_size = 0; - int a_size = 0; - - w = buf+2; - if ((buck = p->withdraw_bucket) && !EMPTY_LIST(buck->prefixes)) - { - DBG("Withdrawn routes:\n"); - wd_size = bgp_encode_prefixes(p, w, buck, remains); - w += wd_size; - remains -= wd_size; - } - put_u16(buf, wd_size); + struct bgp_proto *p = s->proto; + ip_addr *nh = (void *) a->u.ptr->data; - if (!wd_size) - { - while ((buck = (struct bgp_bucket *) HEAD(p->bucket_queue))->send_node.next) - { - if (EMPTY_LIST(buck->prefixes)) - { - DBG("Deleting empty bucket %p\n", buck); - rem_node(&buck->send_node); - bgp_free_bucket(p, buck); - continue; - } - - DBG("Processing bucket %p\n", buck); - a_size = bgp_encode_attrs(p, w+2, buck->eattrs, remains - 1024); - - if (a_size < 0) - { - log(L_ERR "%s: Attribute list too long, skipping corresponding routes", p->p.name); - bgp_flush_prefixes(p, buck); - rem_node(&buck->send_node); - bgp_free_bucket(p, buck); - continue; - } - - put_u16(w, a_size); - w += a_size + 2; - r_size = bgp_encode_prefixes(p, w, buck, remains - a_size); - w += r_size; - break; - } - } - if (!a_size) /* Attributes not already encoded */ + if (s->channel->cf->next_hop_self) + return 0; + + if (s->channel->cf->next_hop_keep) + return 1; + + /* Keep it when explicitly set in export filter */ + if (a->type & EAF_FRESH) + return 1; + + /* Keep it when exported to internal peers */ + if (p->is_interior && ipa_nonzero(*nh)) + return 1; + + /* Keep it when forwarded between single-hop BGPs on the same iface */ + struct iface *ifa = (s->src && s->src->neigh) ? s->src->neigh->iface : NULL; + return p->neigh && (p->neigh->iface == ifa); +} + +static inline int +bgp_use_gateway(struct bgp_export_state *s) +{ + struct bgp_proto *p = s->proto; + rta *ra = s->route->attrs; + + if (s->channel->cf->next_hop_self) + return 0; + + /* We need one valid global gateway */ + if ((ra->dest != RTD_UNICAST) || ra->nh.next || ipa_zero(ra->nh.gw) || ipa_is_link_local(ra->nh.gw)) + return 0; + + /* Use it when exported to internal peers */ + if (p->is_interior) + return 1; + + /* Use it when forwarded to single-hop BGP peer on on the same iface */ + return p->neigh && (p->neigh->iface == ra->nh.iface); +} + +static void +bgp_update_next_hop_ip(struct bgp_export_state *s, eattr *a, ea_list **to) +{ + if (!a || !bgp_use_next_hop(s, a)) + { + if (bgp_use_gateway(s)) { - put_u16(w, 0); - w += 2; + rta *ra = s->route->attrs; + ip_addr nh[1] = { ra->nh.gw }; + bgp_set_attr_data(to, s->pool, BA_NEXT_HOP, 0, nh, 16); + + if (s->mpls) + { + u32 implicit_null = BGP_MPLS_NULL; + u32 *labels = ra->nh.labels ? ra->nh.label : &implicit_null; + uint lnum = ra->nh.labels ? ra->nh.labels : 1; + bgp_set_attr_data(to, s->pool, BA_MPLS_LABEL_STACK, 0, labels, lnum * 4); + } } - if (wd_size || r_size) + else { - BGP_TRACE_RL(&rl_snd_update, D_PACKETS, "Sending UPDATE"); - return w; + ip_addr nh[2] = { s->channel->next_hop_addr, s->channel->link_addr }; + bgp_set_attr_data(to, s->pool, BA_NEXT_HOP, 0, nh, ipa_nonzero(nh[1]) ? 32 : 16); + + /* TODO: Use local MPLS assigned label */ + if (s->mpls) + bgp_unset_attr(to, s->pool, BA_MPLS_LABEL_STACK); } + } + + /* Check if next hop is valid */ + a = bgp_find_attr(*to, BA_NEXT_HOP); + if (!a) + WITHDRAW(NO_NEXT_HOP); + + ip_addr *nh = (void *) a->u.ptr->data; + ip_addr peer = s->proto->cf->remote_ip; + uint len = a->u.ptr->length; + + /* Forbid zero next hop */ + if (ipa_zero(nh[0]) && ((len != 32) || ipa_zero(nh[1]))) + WITHDRAW(BAD_NEXT_HOP); + + /* Forbid next hop equal to neighbor IP */ + if (ipa_equal(peer, nh[0]) || ((len == 32) && ipa_equal(peer, nh[1]))) + WITHDRAW(BAD_NEXT_HOP); + + /* Forbid next hop with non-matching AF */ + if ((ipa_is_ip4(nh[0]) != bgp_channel_is_ipv4(s->channel)) && + !s->channel->ext_next_hop) + WITHDRAW(BAD_NEXT_HOP); + + /* Just check if MPLS stack */ + if (s->mpls && !bgp_find_attr(*to, BA_MPLS_LABEL_STACK)) + WITHDRAW(NO_LABEL_STACK); +} + +static uint +bgp_encode_next_hop_ip(struct bgp_write_state *s, eattr *a, byte *buf, uint size UNUSED) +{ + /* This function is used only for MP-BGP, see bgp_encode_next_hop() for IPv4 BGP */ + ip_addr *nh = (void *) a->u.ptr->data; + uint len = a->u.ptr->length; + + ASSERT((len == 16) || (len == 32)); + + /* + * Both IPv4 and IPv6 next hops can be used (with ext_next_hop enabled). This + * is specified in RFC 5549 for IPv4 and in RFC 4798 for IPv6. The difference + * is that IPv4 address is directly encoded with IPv4 NLRI, but as IPv4-mapped + * IPv6 address with IPv6 NLRI. + */ + + if (bgp_channel_is_ipv4(s->channel) && ipa_is_ip4(nh[0])) + { + put_ip4(buf, ipa_to_ip4(nh[0])); + return 4; + } + + put_ip6(buf, ipa_to_ip6(nh[0])); + + if (len == 32) + put_ip6(buf+16, ipa_to_ip6(nh[1])); + + return len; +} + +static void +bgp_decode_next_hop_ip(struct bgp_parse_state *s, byte *data, uint len, rta *a) +{ + struct bgp_channel *c = s->channel; + struct adata *ad = lp_alloc_adata(s->pool, 32); + ip_addr *nh = (void *) ad->data; + + if (len == 4) + { + nh[0] = ipa_from_ip4(get_ip4(data)); + nh[1] = IPA_NONE; + } + else if (len == 16) + { + nh[0] = ipa_from_ip6(get_ip6(data)); + nh[1] = IPA_NONE; + + if (ipa_is_link_local(nh[0])) + { nh[1] = nh[0]; nh[0] = IPA_NONE; } + } + else if (len == 32) + { + nh[0] = ipa_from_ip6(get_ip6(data)); + nh[1] = ipa_from_ip6(get_ip6(data+16)); + + if (ipa_is_ip4(nh[0]) || !ip6_is_link_local(nh[1])) + nh[1] = IPA_NONE; + } else - return NULL; + bgp_parse_error(s, 9); + + if (ipa_zero(nh[1])) + ad->length = 16; + + if ((bgp_channel_is_ipv4(c) != ipa_is_ip4(nh[0])) && !c->ext_next_hop) + WITHDRAW(BAD_NEXT_HOP); + + // XXXX validate next hop + + bgp_set_attr_ptr(&(a->eattrs), s->pool, BA_NEXT_HOP, 0, ad); + bgp_apply_next_hop(s, a, nh[0], nh[1]); } -static byte * -bgp_create_end_mark(struct bgp_conn *conn, byte *buf) +static uint +bgp_encode_next_hop_vpn(struct bgp_write_state *s, eattr *a, byte *buf, uint size UNUSED) { - struct bgp_proto *p = conn->bgp; - BGP_TRACE(D_PACKETS, "Sending END-OF-RIB"); + ip_addr *nh = (void *) a->u.ptr->data; + uint len = a->u.ptr->length; - put_u32(buf, 0); - return buf+4; + ASSERT((len == 16) || (len == 32)); + + /* + * Both IPv4 and IPv6 next hops can be used (with ext_next_hop enabled). This + * is specified in RFC 5549 for VPNv4 and in RFC 4659 for VPNv6. The difference + * is that IPv4 address is directly encoded with VPNv4 NLRI, but as IPv4-mapped + * IPv6 address with VPNv6 NLRI. + */ + + if (bgp_channel_is_ipv4(s->channel) && ipa_is_ip4(nh[0])) + { + put_u64(buf, 0); /* VPN RD is 0 */ + put_ip4(buf+8, ipa_to_ip4(nh[0])); + return 12; + } + + put_u64(buf, 0); /* VPN RD is 0 */ + put_ip6(buf+8, ipa_to_ip6(nh[0])); + + if (len == 16) + return 24; + + put_u64(buf+24, 0); /* VPN RD is 0 */ + put_ip6(buf+32, ipa_to_ip6(nh[1])); + + return 48; } -#else /* IPv6 version */ +static void +bgp_decode_next_hop_vpn(struct bgp_parse_state *s, byte *data, uint len, rta *a) +{ + struct bgp_channel *c = s->channel; + struct adata *ad = lp_alloc_adata(s->pool, 32); + ip_addr *nh = (void *) ad->data; -static inline int -same_iface(struct bgp_proto *p, ip_addr *ip) + if (len == 12) + { + nh[0] = ipa_from_ip4(get_ip4(data+8)); + nh[1] = IPA_NONE; + } + else if (len == 24) + { + nh[0] = ipa_from_ip6(get_ip6(data+8)); + nh[1] = IPA_NONE; + + if (ipa_is_link_local(nh[0])) + { nh[1] = nh[0]; nh[0] = IPA_NONE; } + } + else if (len == 48) + { + nh[0] = ipa_from_ip6(get_ip6(data+8)); + nh[1] = ipa_from_ip6(get_ip6(data+32)); + + if (ipa_is_ip4(nh[0]) || !ip6_is_link_local(nh[1])) + nh[1] = IPA_NONE; + } + else + bgp_parse_error(s, 9); + + if (ipa_zero(nh[1])) + ad->length = 16; + + /* XXXX which error */ + if ((get_u64(data) != 0) || ((len == 48) && (get_u64(data+24) != 0))) + bgp_parse_error(s, 9); + + if ((bgp_channel_is_ipv4(c) != ipa_is_ip4(nh[0])) && !c->ext_next_hop) + WITHDRAW(BAD_NEXT_HOP); + + // XXXX validate next hop + + bgp_set_attr_ptr(&(a->eattrs), s->pool, BA_NEXT_HOP, 0, ad); + bgp_apply_next_hop(s, a, nh[0], nh[1]); +} + + + +static uint +bgp_encode_next_hop_none(struct bgp_write_state *s UNUSED, eattr *a UNUSED, byte *buf UNUSED, uint size UNUSED) { - neighbor *n = neigh_find(&p->p, ip, 0); - return n && p->neigh && n->iface == p->neigh->iface; + return 0; } -static byte * -bgp_create_update(struct bgp_conn *conn, byte *buf) +static void +bgp_decode_next_hop_none(struct bgp_parse_state *s UNUSED, byte *data UNUSED, uint len UNUSED, rta *a UNUSED) { - struct bgp_proto *p = conn->bgp; - struct bgp_bucket *buck; - int size, second, rem_stored; - int remains = bgp_max_packet_length(p) - BGP_HEADER_LENGTH - 4; - byte *w, *w_stored, *tmp, *tstart; - ip_addr *ipp, ip, ip_ll; - ea_list *ea; - eattr *nh; + /* + * Although we expect no next hop and RFC 7606 7.11 states that attribute + * MP_REACH_NLRI with unexpected next hop length is considered malformed, + * FlowSpec RFC 5575 4 states that next hop shall be ignored on receipt. + */ + + return; +} - put_u16(buf, 0); - w = buf+4; +static void +bgp_update_next_hop_none(struct bgp_export_state *s, eattr *a, ea_list **to) +{ + /* NEXT_HOP shall not pass */ + if (a) + bgp_unset_attr(to, s->pool, BA_NEXT_HOP); +} + + +/* + * UPDATE + */ + +static void +bgp_rte_update(struct bgp_parse_state *s, net_addr *n, u32 path_id, rta *a0) +{ + if (path_id != s->last_id) + { + s->last_src = rt_get_source(&s->proto->p, path_id); + s->last_id = path_id; - if ((buck = p->withdraw_bucket) && !EMPTY_LIST(buck->prefixes)) + rta_free(s->cached_rta); + s->cached_rta = NULL; + } + + if (!a0) + { + /* Route withdraw */ + rte_update2(&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); + + e->pflags = 0; + e->u.bgp.suppressed = 0; + rte_update2(&s->channel->c, n, e, s->last_src); +} + +static void +bgp_encode_mpls_labels(struct bgp_write_state *s UNUSED, adata *mpls, byte **pos, uint *size, byte *pxlen) +{ + u32 dummy = 0; + u32 *labels = mpls ? (u32 *) mpls->data : &dummy; + uint lnum = mpls ? (mpls->length / 4) : 1; + + for (uint i = 0; i < lnum; i++) + { + put_u24(*pos, labels[i] << 4); + ADVANCE(*pos, *size, 3); + } + + /* Add bottom-of-stack flag */ + (*pos)[-1] |= BGP_MPLS_BOS; + + *pxlen += 24 * lnum; +} + +static void +bgp_decode_mpls_labels(struct bgp_parse_state *s, byte **pos, uint *len, uint *pxlen, rta *a) +{ + u32 labels[BGP_MPLS_MAX], label; + uint lnum = 0; + + do { + if (*pxlen < 24) + bgp_parse_error(s, 1); + + label = get_u24(*pos); + labels[lnum++] = label >> 4; + ADVANCE(*pos, *len, 3); + *pxlen -= 24; + + /* Withdraw: Magic label stack value 0x800000 according to RFC 3107, section 3, last paragraph */ + if (!a && !s->err_withdraw && (lnum == 1) && (label == BGP_MPLS_MAGIC)) + break; + } + while (!(label & BGP_MPLS_BOS)); + + if (!a) + return; + + /* Attach MPLS attribute unless we already have one */ + if (!s->mpls_labels) + { + s->mpls_labels = lp_alloc_adata(s->pool, 4*BGP_MPLS_MAX); + bgp_set_attr_ptr(&(a->eattrs), s->pool, BA_MPLS_LABEL_STACK, 0, s->mpls_labels); + } + + /* Overwrite data in the attribute */ + s->mpls_labels->length = 4*lnum; + memcpy(s->mpls_labels->data, labels, 4*lnum); + + /* Update next hop entry in rta */ + bgp_apply_mpls_labels(s, a, labels, lnum); + + /* Attributes were changed, invalidate cached entry */ + rta_free(s->cached_rta); + s->cached_rta = NULL; + + return; +} + +static uint +bgp_encode_nlri_ip4(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, uint size) +{ + byte *pos = buf; + + while (!EMPTY_LIST(buck->prefixes) && (size >= BGP_NLRI_MAX)) + { + struct bgp_prefix *px = HEAD(buck->prefixes); + struct net_addr_ip4 *net = (void *) px->net; + + /* Encode path ID */ + if (s->add_path) { - DBG("Withdrawn routes:\n"); - tmp = bgp_attach_attr_wa(&ea, bgp_linpool, BA_MP_UNREACH_NLRI, remains-8); - *tmp++ = 0; - *tmp++ = BGP_AF_IPV6; - *tmp++ = 1; - ea->attrs[0].u.ptr->length = 3 + bgp_encode_prefixes(p, tmp, buck, remains-11); - size = bgp_encode_attrs(p, w, ea, remains); - ASSERT(size >= 0); - w += size; - remains -= size; + put_u32(pos, px->path_id); + ADVANCE(pos, size, 4); } - else + + /* Encode prefix length */ + *pos = net->pxlen; + ADVANCE(pos, size, 1); + + /* Encode MPLS labels */ + if (s->mpls) + bgp_encode_mpls_labels(s, s->mpls_labels, &pos, &size, pos - 1); + + /* Encode prefix body */ + ip4_addr a = ip4_hton(net->prefix); + uint b = (net->pxlen + 7) / 8; + memcpy(pos, &a, b); + ADVANCE(pos, size, b); + + bgp_free_prefix(s->channel, px); + } + + return pos - buf; +} + +static void +bgp_decode_nlri_ip4(struct bgp_parse_state *s, byte *pos, uint len, rta *a) +{ + while (len) + { + net_addr_ip4 net; + u32 path_id = 0; + + /* Decode path ID */ + if (s->add_path) { - while ((buck = (struct bgp_bucket *) HEAD(p->bucket_queue))->send_node.next) - { - if (EMPTY_LIST(buck->prefixes)) - { - DBG("Deleting empty bucket %p\n", buck); - rem_node(&buck->send_node); - bgp_free_bucket(p, buck); - continue; - } - - DBG("Processing bucket %p\n", buck); - rem_stored = remains; - w_stored = w; - - size = bgp_encode_attrs(p, w, buck->eattrs, remains - 1024); - if (size < 0) - { - log(L_ERR "%s: Attribute list too long, skipping corresponding routes", p->p.name); - bgp_flush_prefixes(p, buck); - rem_node(&buck->send_node); - bgp_free_bucket(p, buck); - continue; - } - w += size; - remains -= size; - - /* We have two addresses here in NEXT_HOP eattr. Really. - Unless NEXT_HOP was modified by filter */ - nh = ea_find(buck->eattrs, EA_CODE(EAP_BGP, BA_NEXT_HOP)); - ASSERT(nh); - second = (nh->u.ptr->length == NEXT_HOP_LENGTH); - ipp = (ip_addr *) nh->u.ptr->data; - ip = ipp[0]; - ip_ll = IPA_NONE; - - if (ipa_equal(ip, p->source_addr)) - ip_ll = p->local_link; - else - { - /* If we send a route with 'third party' next hop destinated - * in the same interface, we should also send a link local - * next hop address. We use the received one (stored in the - * other part of BA_NEXT_HOP eattr). If we didn't received - * it (for example it is a static route), we can't use - * 'third party' next hop and we have to use local IP address - * as next hop. Sending original next hop address without - * link local address seems to be a natural way to solve that - * problem, but it is contrary to RFC 2545 and Quagga does not - * accept such routes. - * - * There are two cases, either we have global IP, or - * IPA_NONE if the neighbor is link-local. For IPA_NONE, - * we suppose it is on the same iface, see bgp_update_attrs(). - */ - - if (ipa_zero(ip) || same_iface(p, &ip)) - { - if (second && ipa_nonzero(ipp[1])) - ip_ll = ipp[1]; - else - { - switch (p->cf->missing_lladdr) - { - case MLL_SELF: - ip = p->source_addr; - ip_ll = p->local_link; - break; - case MLL_DROP: - log(L_ERR "%s: Missing link-local next hop address, skipping corresponding routes", p->p.name); - w = w_stored; - remains = rem_stored; - bgp_flush_prefixes(p, buck); - rem_node(&buck->send_node); - bgp_free_bucket(p, buck); - continue; - case MLL_IGNORE: - break; - } - } - } - } - - tstart = tmp = bgp_attach_attr_wa(&ea, bgp_linpool, BA_MP_REACH_NLRI, remains-8); - *tmp++ = 0; - *tmp++ = BGP_AF_IPV6; - *tmp++ = 1; - - if (ipa_is_link_local(ip)) - ip = IPA_NONE; - - if (ipa_nonzero(ip_ll)) - { - *tmp++ = 32; - ipa_hton(ip); - memcpy(tmp, &ip, 16); - ipa_hton(ip_ll); - memcpy(tmp+16, &ip_ll, 16); - tmp += 32; - } - else - { - *tmp++ = 16; - ipa_hton(ip); - memcpy(tmp, &ip, 16); - tmp += 16; - } - - *tmp++ = 0; /* No SNPA information */ - tmp += bgp_encode_prefixes(p, tmp, buck, remains - (8+3+32+1)); - ea->attrs[0].u.ptr->length = tmp - tstart; - size = bgp_encode_attrs(p, w, ea, remains); - ASSERT(size >= 0); - w += size; - break; - } + if (len < 5) + bgp_parse_error(s, 1); + + path_id = get_u32(pos); + ADVANCE(pos, len, 4); } - size = w - (buf+4); - put_u16(buf+2, size); - lp_flush(bgp_linpool); - if (size) + /* Decode prefix length */ + uint l = *pos; + ADVANCE(pos, len, 1); + + if (len < ((l + 7) / 8)) + bgp_parse_error(s, 1); + + /* Decode MPLS labels */ + if (s->mpls) + bgp_decode_mpls_labels(s, &pos, &len, &l, a); + + if (l > IP4_MAX_PREFIX_LENGTH) + bgp_parse_error(s, 10); + + /* Decode prefix body */ + ip4_addr addr = IP4_NONE; + uint b = (l + 7) / 8; + memcpy(&addr, pos, b); + ADVANCE(pos, len, b); + + net = NET_ADDR_IP4(ip4_ntoh(addr), l); + net_normalize_ip4(&net); + + // XXXX validate prefix + + bgp_rte_update(s, (net_addr *) &net, path_id, a); + } +} + + +static uint +bgp_encode_nlri_ip6(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, uint size) +{ + byte *pos = buf; + + while (!EMPTY_LIST(buck->prefixes) && (size >= BGP_NLRI_MAX)) + { + struct bgp_prefix *px = HEAD(buck->prefixes); + struct net_addr_ip6 *net = (void *) px->net; + + /* Encode path ID */ + if (s->add_path) { - BGP_TRACE_RL(&rl_snd_update, D_PACKETS, "Sending UPDATE"); - return w; + put_u32(pos, px->path_id); + ADVANCE(pos, size, 4); } - else - return NULL; + + /* Encode prefix length */ + *pos = net->pxlen; + ADVANCE(pos, size, 1); + + /* Encode MPLS labels */ + if (s->mpls) + bgp_encode_mpls_labels(s, s->mpls_labels, &pos, &size, pos - 1); + + /* Encode prefix body */ + ip6_addr a = ip6_hton(net->prefix); + uint b = (net->pxlen + 7) / 8; + memcpy(pos, &a, b); + ADVANCE(pos, size, b); + + bgp_free_prefix(s->channel, px); + } + + return pos - buf; } -static byte * -bgp_create_end_mark(struct bgp_conn *conn, byte *buf) +static void +bgp_decode_nlri_ip6(struct bgp_parse_state *s, byte *pos, uint len, rta *a) { - struct bgp_proto *p = conn->bgp; - BGP_TRACE(D_PACKETS, "Sending END-OF-RIB"); + while (len) + { + net_addr_ip6 net; + u32 path_id = 0; - put_u16(buf+0, 0); - put_u16(buf+2, 6); /* length 4-9 */ - buf += 4; + /* Decode path ID */ + if (s->add_path) + { + if (len < 5) + bgp_parse_error(s, 1); - /* Empty MP_UNREACH_NLRI atribute */ - *buf++ = BAF_OPTIONAL; - *buf++ = BA_MP_UNREACH_NLRI; - *buf++ = 3; /* Length 7-9 */ - *buf++ = 0; /* AFI */ - *buf++ = BGP_AF_IPV6; - *buf++ = 1; /* SAFI */ - return buf; -} + path_id = get_u32(pos); + ADVANCE(pos, len, 4); + } -#endif + /* Decode prefix length */ + uint l = *pos; + ADVANCE(pos, len, 1); -static inline byte * -bgp_create_route_refresh(struct bgp_conn *conn, byte *buf) -{ - struct bgp_proto *p = conn->bgp; - BGP_TRACE(D_PACKETS, "Sending ROUTE-REFRESH"); + if (len < ((l + 7) / 8)) + bgp_parse_error(s, 1); - /* Original original route refresh request, RFC 2918 */ - *buf++ = 0; - *buf++ = BGP_AF; - *buf++ = BGP_RR_REQUEST; - *buf++ = 1; /* SAFI */ - return buf; + /* Decode MPLS labels */ + if (s->mpls) + bgp_decode_mpls_labels(s, &pos, &len, &l, a); + + if (l > IP6_MAX_PREFIX_LENGTH) + bgp_parse_error(s, 10); + + /* Decode prefix body */ + ip6_addr addr = IP6_NONE; + uint b = (l + 7) / 8; + memcpy(&addr, pos, b); + ADVANCE(pos, len, b); + + net = NET_ADDR_IP6(ip6_ntoh(addr), l); + net_normalize_ip6(&net); + + // XXXX validate prefix + + bgp_rte_update(s, (net_addr *) &net, path_id, a); + } } -static inline byte * -bgp_create_begin_refresh(struct bgp_conn *conn, byte *buf) +static uint +bgp_encode_nlri_vpn4(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, uint size) { - struct bgp_proto *p = conn->bgp; - BGP_TRACE(D_PACKETS, "Sending BEGIN-OF-RR"); + byte *pos = buf; - /* Demarcation of beginning of route refresh (BoRR), RFC 7313 */ - *buf++ = 0; - *buf++ = BGP_AF; - *buf++ = BGP_RR_BEGIN; - *buf++ = 1; /* SAFI */ - return buf; + while (!EMPTY_LIST(buck->prefixes) && (size >= BGP_NLRI_MAX)) + { + struct bgp_prefix *px = HEAD(buck->prefixes); + struct net_addr_vpn4 *net = (void *) px->net; + + /* Encode path ID */ + if (s->add_path) + { + put_u32(pos, px->path_id); + ADVANCE(pos, size, 4); + } + + /* Encode prefix length */ + *pos = 64 + net->pxlen; + ADVANCE(pos, size, 1); + + /* Encode MPLS labels */ + if (s->mpls) + bgp_encode_mpls_labels(s, s->mpls_labels, &pos, &size, pos - 1); + + /* Encode route distinguisher */ + put_u64(pos, net->rd); + ADVANCE(pos, size, 8); + + /* Encode prefix body */ + ip4_addr a = ip4_hton(net->prefix); + uint b = (net->pxlen + 7) / 8; + memcpy(pos, &a, b); + ADVANCE(pos, size, b); + + bgp_free_prefix(s->channel, px); + } + + return pos - buf; } -static inline byte * -bgp_create_end_refresh(struct bgp_conn *conn, byte *buf) +static void +bgp_decode_nlri_vpn4(struct bgp_parse_state *s, byte *pos, uint len, rta *a) { - struct bgp_proto *p = conn->bgp; - BGP_TRACE(D_PACKETS, "Sending END-OF-RR"); + while (len) + { + net_addr_vpn4 net; + u32 path_id = 0; - /* Demarcation of ending of route refresh (EoRR), RFC 7313 */ - *buf++ = 0; - *buf++ = BGP_AF; - *buf++ = BGP_RR_END; - *buf++ = 1; /* SAFI */ - return buf; + /* Decode path ID */ + if (s->add_path) + { + if (len < 5) + bgp_parse_error(s, 1); + + path_id = get_u32(pos); + ADVANCE(pos, len, 4); + } + + /* Decode prefix length */ + uint l = *pos; + ADVANCE(pos, len, 1); + + if (len < ((l + 7) / 8)) + bgp_parse_error(s, 1); + + /* Decode MPLS labels */ + if (s->mpls) + bgp_decode_mpls_labels(s, &pos, &len, &l, a); + + /* Decode route distinguisher */ + if (l < 64) + bgp_parse_error(s, 1); + + u64 rd = get_u64(pos); + ADVANCE(pos, len, 8); + l -= 64; + + if (l > IP4_MAX_PREFIX_LENGTH) + bgp_parse_error(s, 10); + + /* Decode prefix body */ + ip4_addr addr = IP4_NONE; + uint b = (l + 7) / 8; + memcpy(&addr, pos, b); + ADVANCE(pos, len, b); + + net = NET_ADDR_VPN4(ip4_ntoh(addr), l, rd); + net_normalize_vpn4(&net); + + // XXXX validate prefix + + bgp_rte_update(s, (net_addr *) &net, path_id, a); + } } +static uint +bgp_encode_nlri_vpn6(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, uint size) +{ + byte *pos = buf; + + while (!EMPTY_LIST(buck->prefixes) && (size >= BGP_NLRI_MAX)) + { + struct bgp_prefix *px = HEAD(buck->prefixes); + struct net_addr_vpn6 *net = (void *) px->net; + + /* Encode path ID */ + if (s->add_path) + { + put_u32(pos, px->path_id); + ADVANCE(pos, size, 4); + } + + /* Encode prefix length */ + *pos = 64 + net->pxlen; + ADVANCE(pos, size, 1); + + /* Encode MPLS labels */ + bgp_encode_mpls_labels(s, s->mpls_labels, &pos, &size, pos - 1); + + /* Encode route distinguisher */ + put_u64(pos, net->rd); + ADVANCE(pos, size, 8); + + /* Encode prefix body */ + ip6_addr a = ip6_hton(net->prefix); + uint b = (net->pxlen + 7) / 8; + memcpy(pos, &a, b); + ADVANCE(pos, size, b); + + bgp_free_prefix(s->channel, px); + } + + return pos - buf; +} + static void -bgp_create_header(byte *buf, uint len, uint type) +bgp_decode_nlri_vpn6(struct bgp_parse_state *s, byte *pos, uint len, rta *a) { - memset(buf, 0xff, 16); /* Marker */ - put_u16(buf+16, len); - buf[18] = type; + while (len) + { + net_addr_vpn6 net; + u32 path_id = 0; + + /* Decode path ID */ + if (s->add_path) + { + if (len < 5) + bgp_parse_error(s, 1); + + path_id = get_u32(pos); + ADVANCE(pos, len, 4); + } + + /* Decode prefix length */ + uint l = *pos; + ADVANCE(pos, len, 1); + + if (len < ((l + 7) / 8)) + bgp_parse_error(s, 1); + + /* Decode MPLS labels */ + if (s->mpls) + bgp_decode_mpls_labels(s, &pos, &len, &l, a); + + /* Decode route distinguisher */ + if (l < 64) + bgp_parse_error(s, 1); + + u64 rd = get_u64(pos); + ADVANCE(pos, len, 8); + l -= 64; + + if (l > IP6_MAX_PREFIX_LENGTH) + bgp_parse_error(s, 10); + + /* Decode prefix body */ + ip6_addr addr = IP6_NONE; + uint b = (l + 7) / 8; + memcpy(&addr, pos, b); + ADVANCE(pos, len, b); + + net = NET_ADDR_VPN6(ip6_ntoh(addr), l, rd); + net_normalize_vpn6(&net); + + // XXXX validate prefix + + bgp_rte_update(s, (net_addr *) &net, path_id, a); + } } -/** - * bgp_fire_tx - transmit packets - * @conn: connection - * - * Whenever the transmit buffers of the underlying TCP connection - * are free and we have any packets queued for sending, the socket functions - * call bgp_fire_tx() which takes care of selecting the highest priority packet - * queued (Notification > Keepalive > Open > Update), assembling its header - * and body and sending it to the connection. - */ -static int -bgp_fire_tx(struct bgp_conn *conn) + +static uint +bgp_encode_nlri_flow4(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, uint size) { - struct bgp_proto *p = conn->bgp; - uint s = conn->packets_to_send; - sock *sk = conn->sk; - byte *buf, *pkt, *end; - int type; + byte *pos = buf; + + while (!EMPTY_LIST(buck->prefixes) && (size >= 4)) + { + struct bgp_prefix *px = HEAD(buck->prefixes); + struct net_addr_flow4 *net = (void *) px->net; + uint flen = net->length - sizeof(net_addr_flow4); - if (!sk) + /* Encode path ID */ + if (s->add_path) { - conn->packets_to_send = 0; - return 0; + put_u32(pos, px->path_id); + ADVANCE(pos, size, 4); } - buf = sk->tbuf; - pkt = buf + BGP_HEADER_LENGTH; - if (s & (1 << PKT_SCHEDULE_CLOSE)) + if (flen > size) + break; + + /* Copy whole flow data including length */ + memcpy(pos, net->data, flen); + ADVANCE(pos, size, flen); + + bgp_free_prefix(s->channel, px); + } + + return pos - buf; +} + +static void +bgp_decode_nlri_flow4(struct bgp_parse_state *s, byte *pos, uint len, rta *a) +{ + while (len) + { + u32 path_id = 0; + + /* Decode path ID */ + if (s->add_path) { - /* We can finally close connection and enter idle state */ - bgp_conn_enter_idle_state(conn); - return 0; + if (len < 4) + bgp_parse_error(s, 1); + + path_id = get_u32(pos); + ADVANCE(pos, len, 4); } - if (s & (1 << PKT_NOTIFICATION)) + + if (len < 2) + bgp_parse_error(s, 1); + + /* Decode flow length */ + uint hlen = flow_hdr_length(pos); + uint dlen = flow_read_length(pos); + uint flen = hlen + dlen; + byte *data = pos + hlen; + + if (len < flen) + bgp_parse_error(s, 1); + + /* Validate flow data */ + enum flow_validated_state r = flow4_validate(data, dlen); + if (r != FLOW_ST_VALID) { - s = 1 << PKT_SCHEDULE_CLOSE; - type = PKT_NOTIFICATION; - end = bgp_create_notification(conn, pkt); + log(L_REMOTE "%s: Invalid flow route: %s", s->proto->p.name, flow_validated_state_str(r)); + bgp_parse_error(s, 1); } - else if (s & (1 << PKT_KEEPALIVE)) + + if (data[0] != FLOW_TYPE_DST_PREFIX) { - s &= ~(1 << PKT_KEEPALIVE); - type = PKT_KEEPALIVE; - end = pkt; /* Keepalives carry no data */ - BGP_TRACE(D_PACKETS, "Sending KEEPALIVE"); - bgp_start_timer(conn->keepalive_timer, conn->keepalive_time); + log(L_REMOTE "%s: No dst prefix at first pos", s->proto->p.name); + bgp_parse_error(s, 1); } - else if (s & (1 << PKT_OPEN)) + + /* Decode dst prefix */ + ip4_addr px = IP4_NONE; + uint pxlen = data[1]; + + // FIXME: Use some generic function + memcpy(&px, data, BYTES(pxlen)); + px = ip4_and(px, ip4_mkmask(pxlen)); + + /* Prepare the flow */ + net_addr *n = alloca(sizeof(struct net_addr_flow4) + flen); + net_fill_flow4(n, px, pxlen, pos, flen); + ADVANCE(pos, len, flen); + + bgp_rte_update(s, n, path_id, a); + } +} + + +static uint +bgp_encode_nlri_flow6(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, uint size) +{ + byte *pos = buf; + + while (!EMPTY_LIST(buck->prefixes) && (size >= 4)) + { + struct bgp_prefix *px = HEAD(buck->prefixes); + struct net_addr_flow6 *net = (void *) px->net; + uint flen = net->length - sizeof(net_addr_flow6); + + /* Encode path ID */ + if (s->add_path) { - s &= ~(1 << PKT_OPEN); - type = PKT_OPEN; - end = bgp_create_open(conn, pkt); + put_u32(pos, px->path_id); + ADVANCE(pos, size, 4); } - else if (s & (1 << PKT_ROUTE_REFRESH)) + + if (flen > size) + break; + + /* Copy whole flow data including length */ + memcpy(pos, net->data, flen); + ADVANCE(pos, size, flen); + + bgp_free_prefix(s->channel, px); + } + + return pos - buf; +} + +static void +bgp_decode_nlri_flow6(struct bgp_parse_state *s, byte *pos, uint len, rta *a) +{ + while (len) + { + u32 path_id = 0; + + /* Decode path ID */ + if (s->add_path) { - s &= ~(1 << PKT_ROUTE_REFRESH); - type = PKT_ROUTE_REFRESH; - end = bgp_create_route_refresh(conn, pkt); + if (len < 4) + bgp_parse_error(s, 1); + + path_id = get_u32(pos); + ADVANCE(pos, len, 4); } - else if (s & (1 << PKT_BEGIN_REFRESH)) + + if (len < 2) + bgp_parse_error(s, 1); + + /* Decode flow length */ + uint hlen = flow_hdr_length(pos); + uint dlen = flow_read_length(pos); + uint flen = hlen + dlen; + byte *data = pos + hlen; + + if (len < flen) + bgp_parse_error(s, 1); + + /* Validate flow data */ + enum flow_validated_state r = flow6_validate(data, dlen); + if (r != FLOW_ST_VALID) { - s &= ~(1 << PKT_BEGIN_REFRESH); - type = PKT_ROUTE_REFRESH; /* BoRR is a subtype of RR */ - end = bgp_create_begin_refresh(conn, pkt); + log(L_REMOTE "%s: Invalid flow route: %s", s->proto->p.name, flow_validated_state_str(r)); + bgp_parse_error(s, 1); } - else if (s & (1 << PKT_UPDATE)) + + if (data[0] != FLOW_TYPE_DST_PREFIX) { - type = PKT_UPDATE; - end = bgp_create_update(conn, pkt); + log(L_REMOTE "%s: No dst prefix at first pos", s->proto->p.name); + bgp_parse_error(s, 1); + } - if (!end) - { - /* No update to send, perhaps we need to send End-of-RIB or EoRR */ + /* Decode dst prefix */ + ip6_addr px = IP6_NONE; + uint pxlen = data[1]; - conn->packets_to_send = 0; + // FIXME: Use some generic function + memcpy(&px, data, BYTES(pxlen)); + px = ip6_and(px, ip6_mkmask(pxlen)); - if (p->feed_state == BFS_LOADED) - { - type = PKT_UPDATE; - end = bgp_create_end_mark(conn, pkt); - } + /* Prepare the flow */ + net_addr *n = alloca(sizeof(struct net_addr_flow6) + flen); + net_fill_flow6(n, px, pxlen, pos, flen); + ADVANCE(pos, len, flen); - else if (p->feed_state == BFS_REFRESHED) - { - type = PKT_ROUTE_REFRESH; - end = bgp_create_end_refresh(conn, pkt); - } + bgp_rte_update(s, n, path_id, a); + } +} - else /* Really nothing to send */ - return 0; - p->feed_state = BFS_NONE; - } - } - else - return 0; +static const struct bgp_af_desc bgp_af_table[] = { + { + .afi = BGP_AF_IPV4, + .net = NET_IP4, + .name = "ipv4", + .encode_nlri = bgp_encode_nlri_ip4, + .decode_nlri = bgp_decode_nlri_ip4, + .encode_next_hop = bgp_encode_next_hop_ip, + .decode_next_hop = bgp_decode_next_hop_ip, + .update_next_hop = bgp_update_next_hop_ip, + }, + { + .afi = BGP_AF_IPV4_MC, + .net = NET_IP4, + .name = "ipv4-mc", + .encode_nlri = bgp_encode_nlri_ip4, + .decode_nlri = bgp_decode_nlri_ip4, + .encode_next_hop = bgp_encode_next_hop_ip, + .decode_next_hop = bgp_decode_next_hop_ip, + .update_next_hop = bgp_update_next_hop_ip, + }, + { + .afi = BGP_AF_IPV4_MPLS, + .net = NET_IP4, + .mpls = 1, + .name = "ipv4-mpls", + .encode_nlri = bgp_encode_nlri_ip4, + .decode_nlri = bgp_decode_nlri_ip4, + .encode_next_hop = bgp_encode_next_hop_ip, + .decode_next_hop = bgp_decode_next_hop_ip, + .update_next_hop = bgp_update_next_hop_ip, + }, + { + .afi = BGP_AF_IPV6, + .net = NET_IP6, + .name = "ipv6", + .encode_nlri = bgp_encode_nlri_ip6, + .decode_nlri = bgp_decode_nlri_ip6, + .encode_next_hop = bgp_encode_next_hop_ip, + .decode_next_hop = bgp_decode_next_hop_ip, + .update_next_hop = bgp_update_next_hop_ip, + }, + { + .afi = BGP_AF_IPV6_MC, + .net = NET_IP6, + .name = "ipv6-mc", + .encode_nlri = bgp_encode_nlri_ip6, + .decode_nlri = bgp_decode_nlri_ip6, + .encode_next_hop = bgp_encode_next_hop_ip, + .decode_next_hop = bgp_decode_next_hop_ip, + .update_next_hop = bgp_update_next_hop_ip, + }, + { + .afi = BGP_AF_IPV6_MPLS, + .net = NET_IP6, + .mpls = 1, + .name = "ipv6-mpls", + .encode_nlri = bgp_encode_nlri_ip6, + .decode_nlri = bgp_decode_nlri_ip6, + .encode_next_hop = bgp_encode_next_hop_ip, + .decode_next_hop = bgp_decode_next_hop_ip, + .update_next_hop = bgp_update_next_hop_ip, + }, + { + .afi = BGP_AF_VPN4_MPLS, + .net = NET_VPN4, + .mpls = 1, + .name = "vpn4-mpls", + .encode_nlri = bgp_encode_nlri_vpn4, + .decode_nlri = bgp_decode_nlri_vpn4, + .encode_next_hop = bgp_encode_next_hop_vpn, + .decode_next_hop = bgp_decode_next_hop_vpn, + .update_next_hop = bgp_update_next_hop_ip, + }, + { + .afi = BGP_AF_VPN6_MPLS, + .net = NET_VPN6, + .mpls = 1, + .name = "vpn6-mpls", + .encode_nlri = bgp_encode_nlri_vpn6, + .decode_nlri = bgp_decode_nlri_vpn6, + .encode_next_hop = bgp_encode_next_hop_vpn, + .decode_next_hop = bgp_decode_next_hop_vpn, + .update_next_hop = bgp_update_next_hop_ip, + }, + { + .afi = BGP_AF_VPN4_MC, + .net = NET_VPN4, + .name = "vpn4-mc", + .encode_nlri = bgp_encode_nlri_vpn4, + .decode_nlri = bgp_decode_nlri_vpn4, + .encode_next_hop = bgp_encode_next_hop_vpn, + .decode_next_hop = bgp_decode_next_hop_vpn, + .update_next_hop = bgp_update_next_hop_ip, + }, + { + .afi = BGP_AF_VPN6_MC, + .net = NET_VPN6, + .name = "vpn6-mc", + .encode_nlri = bgp_encode_nlri_vpn6, + .decode_nlri = bgp_decode_nlri_vpn6, + .encode_next_hop = bgp_encode_next_hop_vpn, + .decode_next_hop = bgp_decode_next_hop_vpn, + .update_next_hop = bgp_update_next_hop_ip, + }, + { + .afi = BGP_AF_FLOW4, + .net = NET_FLOW4, + .no_igp = 1, + .name = "flow4", + .encode_nlri = bgp_encode_nlri_flow4, + .decode_nlri = bgp_decode_nlri_flow4, + .encode_next_hop = bgp_encode_next_hop_none, + .decode_next_hop = bgp_decode_next_hop_none, + .update_next_hop = bgp_update_next_hop_none, + }, + { + .afi = BGP_AF_FLOW6, + .net = NET_FLOW6, + .no_igp = 1, + .name = "flow6", + .encode_nlri = bgp_encode_nlri_flow6, + .decode_nlri = bgp_decode_nlri_flow6, + .encode_next_hop = bgp_encode_next_hop_none, + .decode_next_hop = bgp_decode_next_hop_none, + .update_next_hop = bgp_update_next_hop_none, + }, +}; - conn->packets_to_send = s; - bgp_create_header(buf, end - buf, type); - return sk_send(sk, end - buf); +const struct bgp_af_desc * +bgp_get_af_desc(u32 afi) +{ + uint i; + for (i = 0; i < ARRAY_SIZE(bgp_af_table); i++) + if (bgp_af_table[i].afi == afi) + return &bgp_af_table[i]; + + return NULL; } -/** - * bgp_schedule_packet - schedule a packet for transmission - * @conn: connection - * @type: packet type - * - * Schedule a packet of type @type to be sent as soon as possible. - */ -void -bgp_schedule_packet(struct bgp_conn *conn, int type) +static inline uint +bgp_encode_nlri(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, byte *end) { - DBG("BGP: Scheduling packet type %d\n", type); - conn->packets_to_send |= 1 << type; - if (conn->sk && conn->sk->tpos == conn->sk->tbuf && !ev_active(conn->tx_ev)) - ev_schedule(conn->tx_ev); + return s->channel->desc->encode_nlri(s, buck, buf, end - buf); } -void -bgp_kick_tx(void *vconn) +static inline uint +bgp_encode_next_hop(struct bgp_write_state *s, eattr *nh, byte *buf) { - struct bgp_conn *conn = vconn; - - DBG("BGP: kicking TX\n"); - while (bgp_fire_tx(conn) > 0) - ; + return s->channel->desc->encode_next_hop(s, nh, buf, 255); } void -bgp_tx(sock *sk) +bgp_update_next_hop(struct bgp_export_state *s, eattr *a, ea_list **to) { - struct bgp_conn *conn = sk->data; - - DBG("BGP: TX hook\n"); - while (bgp_fire_tx(conn) > 0) - ; + s->channel->desc->update_next_hop(s, a, to); } -/* Capatibility negotiation as per RFC 2842 */ +#define MAX_ATTRS_LENGTH (end-buf+BGP_HEADER_LENGTH - 1024) -void -bgp_parse_capabilities(struct bgp_conn *conn, byte *opt, int len) +static byte * +bgp_create_ip_reach(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, byte *end) { - // struct bgp_proto *p = conn->bgp; - int i, cl; + /* + * 2 B Withdrawn Routes Length (zero) + * --- IPv4 Withdrawn Routes NLRI (unused) + * 2 B Total Path Attribute Length + * var Path Attributes + * var IPv4 Network Layer Reachability Information + */ + + int lr, la; + + la = bgp_encode_attrs(s, buck->eattrs, buf+4, buf + MAX_ATTRS_LENGTH); + if (la < 0) + { + /* Attribute list too long */ + bgp_withdraw_bucket(s->channel, buck); + return NULL; + } - while (len > 0) - { - if (len < 2 || len < 2 + opt[1]) - goto err; + put_u16(buf+0, 0); + put_u16(buf+2, la); - cl = opt[1]; + lr = bgp_encode_nlri(s, buck, buf+4+la, end); - switch (opt[0]) - { - case 2: /* Route refresh capability, RFC 2918 */ - if (cl != 0) - goto err; - conn->peer_refresh_support = 1; - break; + return buf+4+la+lr; +} - case 6: /* Extended message length capability, draft */ - if (cl != 0) - goto err; - conn->peer_ext_messages_support = 1; - break; +static byte * +bgp_create_mp_reach(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, byte *end) +{ + /* + * 2 B IPv4 Withdrawn Routes Length (zero) + * --- IPv4 Withdrawn Routes NLRI (unused) + * 2 B Total Path Attribute Length + * 1 B MP_REACH_NLRI hdr - Attribute Flags + * 1 B MP_REACH_NLRI hdr - Attribute Type Code + * 2 B MP_REACH_NLRI hdr - Length of Attribute Data + * 2 B MP_REACH_NLRI data - Address Family Identifier + * 1 B MP_REACH_NLRI data - Subsequent Address Family Identifier + * 1 B MP_REACH_NLRI data - Length of Next Hop Network Address + * var MP_REACH_NLRI data - Network Address of Next Hop + * 1 B MP_REACH_NLRI data - Reserved (zero) + * var MP_REACH_NLRI data - Network Layer Reachability Information + * var Rest of Path Attributes + * --- IPv4 Network Layer Reachability Information (unused) + */ + + int lh, lr, la; /* Lengths of next hop, NLRI and attributes */ + + /* Begin of MP_REACH_NLRI atribute */ + buf[4] = BAF_OPTIONAL | BAF_EXT_LEN; + buf[5] = BA_MP_REACH_NLRI; + put_u16(buf+6, 0); /* Will be fixed later */ + put_af3(buf+8, s->channel->afi); + byte *pos = buf+11; + + /* Encode attributes to temporary buffer */ + byte *abuf = alloca(MAX_ATTRS_LENGTH); + la = bgp_encode_attrs(s, buck->eattrs, abuf, abuf + MAX_ATTRS_LENGTH); + if (la < 0) + { + /* Attribute list too long */ + bgp_withdraw_bucket(s->channel, buck); + return NULL; + } - case 64: /* Graceful restart capability, RFC 4724 */ - if (cl % 4 != 2) - goto err; - conn->peer_gr_aware = 1; - conn->peer_gr_able = 0; - conn->peer_gr_time = get_u16(opt + 2) & 0x0fff; - conn->peer_gr_flags = opt[2] & 0xf0; - conn->peer_gr_aflags = 0; - for (i = 2; i < cl; i += 4) - if (opt[2+i+0] == 0 && opt[2+i+1] == BGP_AF && opt[2+i+2] == 1) /* Match AFI/SAFI */ - { - conn->peer_gr_able = 1; - conn->peer_gr_aflags = opt[2+i+3]; - } - break; + /* Encode the next hop */ + lh = bgp_encode_next_hop(s, s->mp_next_hop, pos+1); + *pos = lh; + pos += 1+lh; - case 65: /* AS4 capability, RFC 4893 */ - if (cl != 4) - goto err; - conn->peer_as4_support = 1; - if (conn->bgp->cf->enable_as4) - conn->advertised_as = get_u32(opt + 2); - break; + /* Reserved field */ + *pos++ = 0; - case 69: /* ADD-PATH capability, RFC 7911 */ - if (cl % 4) - goto err; - for (i = 0; i < cl; i += 4) - if (opt[2+i+0] == 0 && opt[2+i+1] == BGP_AF && opt[2+i+2] == 1) /* Match AFI/SAFI */ - conn->peer_add_path = opt[2+i+3]; - if (conn->peer_add_path > ADD_PATH_FULL) - goto err; - break; + /* Encode the NLRI */ + lr = bgp_encode_nlri(s, buck, pos, end - la); + pos += lr; - case 70: /* Enhanced route refresh capability, RFC 7313 */ - if (cl != 0) - goto err; - conn->peer_enhanced_refresh_support = 1; - break; + /* End of MP_REACH_NLRI atribute, update data length */ + put_u16(buf+6, pos-buf-8); - /* We can safely ignore all other capabilities */ - } - len -= 2 + cl; - opt += 2 + cl; - } - return; + /* Copy remaining attributes */ + memcpy(pos, abuf, la); + pos += la; - err: - bgp_error(conn, 2, 0, NULL, 0); - return; + /* Initial UPDATE fields */ + put_u16(buf+0, 0); + put_u16(buf+2, pos-buf-4); + + return pos; } -static int -bgp_parse_options(struct bgp_conn *conn, byte *opt, int len) +#undef MAX_ATTRS_LENGTH + +static byte * +bgp_create_ip_unreach(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, byte *end) { - struct bgp_proto *p = conn->bgp; - int ol; + /* + * 2 B Withdrawn Routes Length + * var IPv4 Withdrawn Routes NLRI + * 2 B Total Path Attribute Length (zero) + * --- Path Attributes (unused) + * --- IPv4 Network Layer Reachability Information (unused) + */ - while (len > 0) - { - if (len < 2 || len < 2 + opt[1]) - { bgp_error(conn, 2, 0, NULL, 0); return 0; } -#ifdef LOCAL_DEBUG - { - int i; - DBG("\tOption %02x:", opt[0]); - for(i=0; i<opt[1]; i++) - DBG(" %02x", opt[2+i]); - DBG("\n"); - } -#endif + uint len = bgp_encode_nlri(s, buck, buf+2, end); - ol = opt[1]; - switch (opt[0]) - { - case 2: - if (conn->start_state == BSS_CONNECT_NOCAP) - BGP_TRACE(D_PACKETS, "Ignoring received capabilities"); - else - bgp_parse_capabilities(conn, opt + 2, ol); - break; + put_u16(buf+0, len); + put_u16(buf+2+len, 0); - default: - /* - * BGP specs don't tell us to send which option - * we didn't recognize, but it's common practice - * to do so. Also, capability negotiation with - * Cisco routers doesn't work without that. - */ - bgp_error(conn, 2, 4, opt, ol); - return 0; - } - len -= 2 + ol; - opt += 2 + ol; - } - return 0; + return buf+4+len; } -static void -bgp_rx_open(struct bgp_conn *conn, byte *pkt, uint len) +static byte * +bgp_create_mp_unreach(struct bgp_write_state *s, struct bgp_bucket *buck, byte *buf, byte *end) { - struct bgp_conn *other; - struct bgp_proto *p = conn->bgp; - unsigned hold; - u16 base_as; - u32 id; + /* + * 2 B Withdrawn Routes Length (zero) + * --- IPv4 Withdrawn Routes NLRI (unused) + * 2 B Total Path Attribute Length + * 1 B MP_UNREACH_NLRI hdr - Attribute Flags + * 1 B MP_UNREACH_NLRI hdr - Attribute Type Code + * 2 B MP_UNREACH_NLRI hdr - Length of Attribute Data + * 2 B MP_UNREACH_NLRI data - Address Family Identifier + * 1 B MP_UNREACH_NLRI data - Subsequent Address Family Identifier + * var MP_UNREACH_NLRI data - Network Layer Reachability Information + * --- IPv4 Network Layer Reachability Information (unused) + */ + + uint len = bgp_encode_nlri(s, buck, buf+11, end); - /* Check state */ - if (conn->state != BS_OPENSENT) - { bgp_error(conn, 5, fsm_err_subcode[conn->state], NULL, 0); return; } + put_u16(buf+0, 0); + put_u16(buf+2, 7+len); - /* Check message contents */ - if (len < 29 || len != 29U + pkt[28]) - { bgp_error(conn, 1, 2, pkt+16, 2); return; } - if (pkt[19] != BGP_VERSION) - { bgp_error(conn, 2, 1, pkt+19, 1); return; } /* RFC 1771 says 16 bits, draft-09 tells to use 8 */ - conn->advertised_as = base_as = get_u16(pkt+20); - hold = get_u16(pkt+22); - id = get_u32(pkt+24); - BGP_TRACE(D_PACKETS, "Got OPEN(as=%d,hold=%d,id=%08x)", conn->advertised_as, hold, id); + /* Begin of MP_UNREACH_NLRI atribute */ + buf[4] = BAF_OPTIONAL | BAF_EXT_LEN; + buf[5] = BA_MP_UNREACH_NLRI; + put_u16(buf+6, 3+len); + put_af3(buf+8, s->channel->afi); - if (bgp_parse_options(conn, pkt+29, pkt[28])) - return; + return buf+11+len; +} - if (hold > 0 && hold < 3) - { bgp_error(conn, 2, 6, pkt+22, 2); return; } +static byte * +bgp_create_update(struct bgp_channel *c, byte *buf) +{ + struct bgp_proto *p = (void *) c->c.proto; + struct bgp_bucket *buck; + byte *end = buf + (bgp_max_packet_length(p->conn) - BGP_HEADER_LENGTH); + byte *res = NULL; + +again: ; + + /* Initialize write state */ + struct bgp_write_state s = { + .proto = p, + .channel = c, + .pool = bgp_linpool, + .as4_session = p->as4_session, + .add_path = c->add_path_tx, + .mpls = c->desc->mpls, + }; + + /* Try unreachable bucket */ + if ((buck = c->withdraw_bucket) && !EMPTY_LIST(buck->prefixes)) + { + res = (c->afi == BGP_AF_IPV4) && !c->ext_next_hop ? + bgp_create_ip_unreach(&s, buck, buf, end): + bgp_create_mp_unreach(&s, buck, buf, end); - /* RFC 6286 2.2 - router ID is nonzero and AS-wide unique */ - if (!id || (p->is_internal && id == p->local_id)) - { bgp_error(conn, 2, 3, pkt+24, -4); return; } + goto done; + } - if ((conn->advertised_as != base_as) && (base_as != AS_TRANS)) - log(L_WARN "%s: Peer advertised inconsistent AS numbers", p->p.name); + /* Try reachable buckets */ + if (!EMPTY_LIST(c->bucket_queue)) + { + buck = HEAD(c->bucket_queue); - if (conn->advertised_as != p->remote_as) + /* Cleanup empty buckets */ + if (EMPTY_LIST(buck->prefixes)) { - if (conn->peer_as4_support) - { - u32 val = htonl(conn->advertised_as); - bgp_error(conn, 2, 2, (byte *) &val, 4); - } - else - bgp_error(conn, 2, 2, pkt+20, 2); - - return; + bgp_free_bucket(c, buck); + goto again; } - /* Check the other connection */ - other = (conn == &p->outgoing_conn) ? &p->incoming_conn : &p->outgoing_conn; - switch (other->state) - { - case BS_CONNECT: - case BS_ACTIVE: - /* Stop outgoing connection attempts */ - bgp_conn_enter_idle_state(other); - break; + res = (c->afi == BGP_AF_IPV4) && !c->ext_next_hop ? + bgp_create_ip_reach(&s, buck, buf, end): + bgp_create_mp_reach(&s, buck, buf, end); - case BS_IDLE: - case BS_OPENSENT: - case BS_CLOSE: - break; + if (EMPTY_LIST(buck->prefixes)) + bgp_free_bucket(c, buck); + else + bgp_defer_bucket(c, buck); - case BS_OPENCONFIRM: - /* - * Description of collision detection rules in RFC 4271 is confusing and - * contradictory, but it is essentially: - * - * 1. Router with higher ID is dominant - * 2. If both have the same ID, router with higher ASN is dominant [RFC6286] - * 3. When both connections are in OpenConfirm state, one initiated by - * the dominant router is kept. - * - * The first line in the expression below evaluates whether the neighbor - * is dominant, the second line whether the new connection was initiated - * by the neighbor. If both are true (or both are false), we keep the new - * connection, otherwise we keep the old one. - */ - if (((p->local_id < id) || ((p->local_id == id) && (p->local_as < p->remote_as))) - == (conn == &p->incoming_conn)) - { - /* Should close the other connection */ - BGP_TRACE(D_EVENTS, "Connection collision, giving up the other connection"); - bgp_error(other, 6, 7, NULL, 0); - break; - } - /* Fall thru */ - case BS_ESTABLISHED: - /* Should close this connection */ - BGP_TRACE(D_EVENTS, "Connection collision, giving up this connection"); - bgp_error(conn, 6, 7, NULL, 0); - return; - default: - bug("bgp_rx_open: Unknown state"); - } + if (!res) + goto again; - /* Update our local variables */ - conn->hold_time = MIN(hold, p->cf->hold_time); - conn->keepalive_time = p->cf->keepalive_time ? : conn->hold_time / 3; - p->remote_id = id; - p->as4_session = p->cf->enable_as4 && conn->peer_as4_support; - p->add_path_rx = (p->cf->add_path & ADD_PATH_RX) && (conn->peer_add_path & ADD_PATH_TX); - p->add_path_tx = (p->cf->add_path & ADD_PATH_TX) && (conn->peer_add_path & ADD_PATH_RX); - p->gr_ready = p->cf->gr_mode && conn->peer_gr_able; - p->ext_messages = p->cf->enable_extended_messages && conn->peer_ext_messages_support; - - /* Update RA mode */ - if (p->add_path_tx) - p->p.accept_ra_types = RA_ANY; - else if (p->cf->secondary) - p->p.accept_ra_types = RA_ACCEPTED; - else - p->p.accept_ra_types = RA_OPTIMAL; + goto done; + } - DBG("BGP: Hold timer set to %d, keepalive to %d, AS to %d, ID to %x, AS4 session to %d\n", conn->hold_time, conn->keepalive_time, p->remote_as, p->remote_id, p->as4_session); + /* No more prefixes to send */ + return NULL; - bgp_schedule_packet(conn, PKT_KEEPALIVE); - bgp_start_timer(conn->hold_timer, conn->hold_time); - bgp_conn_enter_openconfirm_state(conn); +done: + BGP_TRACE_RL(&rl_snd_update, D_PACKETS, "Sending UPDATE"); + lp_flush(s.pool); + + return res; } +static byte * +bgp_create_ip_end_mark(struct bgp_channel *c UNUSED, byte *buf) +{ + /* Empty update packet */ + put_u32(buf, 0); -static inline void -bgp_rx_end_mark(struct bgp_proto *p) + return buf+4; +} + +static byte * +bgp_create_mp_end_mark(struct bgp_channel *c, byte *buf) { - BGP_TRACE(D_PACKETS, "Got END-OF-RIB"); + put_u16(buf+0, 0); + put_u16(buf+2, 6); /* length 4--9 */ - if (p->load_state == BFS_LOADING) - p->load_state = BFS_NONE; + /* Empty MP_UNREACH_NLRI atribute */ + buf[4] = BAF_OPTIONAL; + buf[5] = BA_MP_UNREACH_NLRI; + buf[6] = 3; /* Length 7--9 */ + put_af3(buf+7, c->afi); - if (p->p.gr_recovery) - proto_graceful_restart_unlock(&p->p); - - if (p->gr_active) - bgp_graceful_restart_done(p); -} - - -#define DECODE_PREFIX(pp, ll) do { \ - if (p->add_path_rx) \ - { \ - if (ll < 5) { err=1; goto done; } \ - path_id = get_u32(pp); \ - pp += 4; \ - ll -= 4; \ - } \ - int b = *pp++; \ - int q; \ - ll--; \ - if (b > BITS_PER_IP_ADDRESS) { err=10; goto done; } \ - q = (b+7) / 8; \ - if (ll < q) { err=1; goto done; } \ - memcpy(&prefix, pp, q); \ - pp += q; \ - ll -= q; \ - ipa_ntoh(prefix); \ - prefix = ipa_and(prefix, ipa_mkmask(b)); \ - pxlen = b; \ -} while (0) + return buf+10; +} +static byte * +bgp_create_end_mark(struct bgp_channel *c, byte *buf) +{ + struct bgp_proto *p = (void *) c->c.proto; + + BGP_TRACE(D_PACKETS, "Sending END-OF-RIB"); + + return (c->afi == BGP_AF_IPV4) ? + bgp_create_ip_end_mark(c, buf): + bgp_create_mp_end_mark(c, buf); +} static inline void -bgp_rte_update(struct bgp_proto *p, ip_addr prefix, int pxlen, - u32 path_id, u32 *last_id, struct rte_src **src, - rta *a0, rta **a) +bgp_rx_end_mark(struct bgp_parse_state *s, u32 afi) { - if (path_id != *last_id) - { - *src = rt_get_source(&p->p, path_id); - *last_id = path_id; + struct bgp_proto *p = s->proto; + struct bgp_channel *c = bgp_get_channel(p, afi); - if (*a) - { - rta_free(*a); - *a = NULL; - } - } + BGP_TRACE(D_PACKETS, "Got END-OF-RIB"); - /* Prepare cached route attributes */ - if (!*a) - { - a0->src = *src; + if (!c) + DISCARD(BAD_AFI, BGP_AFI(afi), BGP_SAFI(afi)); - /* Workaround for rta_lookup() breaking eattrs */ - ea_list *ea = a0->eattrs; - *a = rta_lookup(a0); - a0->eattrs = ea; - } + if (c->load_state == BFS_LOADING) + c->load_state = BFS_NONE; - net *n = net_get(p->p.table, prefix, pxlen); - rte *e = rte_get_temp(rta_clone(*a)); - e->net = n; - e->pflags = 0; - e->u.bgp.suppressed = 0; - rte_update2(p->p.main_ahook, n, e, *src); + if (p->p.gr_recovery) + channel_graceful_restart_unlock(&c->c); + + if (c->gr_active) + bgp_graceful_restart_done(c); } static inline void -bgp_rte_withdraw(struct bgp_proto *p, ip_addr prefix, int pxlen, - u32 path_id, u32 *last_id, struct rte_src **src) +bgp_decode_nlri(struct bgp_parse_state *s, u32 afi, byte *nlri, uint len, ea_list *ea, byte *nh, uint nh_len) { - if (path_id != *last_id) - { - *src = rt_find_source(&p->p, path_id); - *last_id = path_id; - } + struct bgp_channel *c = bgp_get_channel(s->proto, afi); + rta *a = NULL; - net *n = net_find(p->p.table, prefix, pxlen); - rte_update2( p->p.main_ahook, n, NULL, *src); -} + if (!c) + DISCARD(BAD_AFI, BGP_AFI(afi), BGP_SAFI(afi)); -static inline int -bgp_set_next_hop(struct bgp_proto *p, rta *a) -{ - struct eattr *nh = ea_find(a->eattrs, EA_CODE(EAP_BGP, BA_NEXT_HOP)); - ip_addr *nexthop = (ip_addr *) nh->u.ptr->data; + s->channel = c; + s->add_path = c->add_path_rx; + s->mpls = c->desc->mpls; -#ifdef IPV6 - int second = (nh->u.ptr->length == NEXT_HOP_LENGTH) && ipa_nonzero(nexthop[1]); + s->last_id = 0; + s->last_src = s->proto->p.main_source; - /* First address should not be link-local, but may be zero in direct mode */ - if (ipa_is_link_local(*nexthop)) - *nexthop = IPA_NONE; -#else - int second = 0; -#endif - - if (p->cf->gw_mode == GW_DIRECT) - { - neighbor *ng = NULL; + /* + * IPv4 BGP and MP-BGP may be used together in one update, therefore we do not + * add BA_NEXT_HOP in bgp_decode_attrs(), but we add it here independently for + * IPv4 BGP and MP-BGP. We undo the attribute (and possibly others attached by + * decode_next_hop hooks) by restoring a->eattrs afterwards. + */ - if (ipa_nonzero(*nexthop)) - ng = neigh_find(&p->p, nexthop, 0); - else if (second) /* GW_DIRECT -> single_hop -> p->neigh != NULL */ - ng = neigh_find2(&p->p, nexthop + 1, p->neigh->iface, 0); + if (ea) + { + a = allocz(RTA_MAX_SIZE); - /* Fallback */ - if (!ng) - ng = p->neigh; + a->source = RTS_BGP; + a->scope = SCOPE_UNIVERSE; + a->from = s->proto->cf->remote_ip; + a->eattrs = ea; - if (ng->scope == SCOPE_HOST) - return 0; + c->desc->decode_next_hop(s, nh, nh_len, a); - a->dest = RTD_ROUTER; - a->gw = ng->addr; - a->iface = ng->iface; - a->hostentry = NULL; - a->igp_metric = 0; - } - else /* GW_RECURSIVE */ - { - if (ipa_zero(*nexthop)) - return 0; + /* Handle withdraw during next hop decoding */ + if (s->err_withdraw) + a = NULL; + } - rta_set_recursive_next_hop(p->p.table, a, p->igp_table, nexthop, nexthop + second); - } + c->desc->decode_nlri(s, nlri, len, a); - return 1; + rta_free(s->cached_rta); + s->cached_rta = NULL; } -#ifndef IPV6 /* IPv4 version */ - static void -bgp_do_rx_update(struct bgp_conn *conn, - byte *withdrawn, int withdrawn_len, - byte *nlri, int nlri_len, - byte *attrs, int attr_len) +bgp_rx_update(struct bgp_conn *conn, byte *pkt, uint len) { struct bgp_proto *p = conn->bgp; - struct rte_src *src = p->p.main_source; - rta *a0, *a = NULL; - ip_addr prefix; - int pxlen, err = 0; - u32 path_id = 0; - u32 last_id = 0; + ea_list *ea = NULL; - /* Check for End-of-RIB marker */ - if (!withdrawn_len && !attr_len && !nlri_len) - { - bgp_rx_end_mark(p); - return; - } + BGP_TRACE_RL(&rl_rcv_update, D_PACKETS, "Got UPDATE"); - /* Withdraw routes */ - while (withdrawn_len) - { - DECODE_PREFIX(withdrawn, withdrawn_len); - DBG("Withdraw %I/%d\n", prefix, pxlen); + /* Workaround for some BGP implementations that skip initial KEEPALIVE */ + if (conn->state == BS_OPENCONFIRM) + bgp_conn_enter_established_state(conn); - bgp_rte_withdraw(p, prefix, pxlen, path_id, &last_id, &src); - } + if (conn->state != BS_ESTABLISHED) + { bgp_error(conn, 5, fsm_err_subcode[conn->state], NULL, 0); return; } - if (!attr_len && !nlri_len) /* shortcut */ - return; + bgp_start_timer(conn->hold_timer, conn->hold_time); - a0 = bgp_decode_attrs(conn, attrs, attr_len, bgp_linpool, nlri_len); + /* Initialize parse state */ + struct bgp_parse_state s = { + .proto = p, + .pool = bgp_linpool, + .as4_session = p->as4_session, + }; - if (conn->state != BS_ESTABLISHED) /* fatal error during decoding */ - return; + /* Parse error handler */ + if (setjmp(s.err_jmpbuf)) + { + bgp_error(conn, 3, s.err_subcode, NULL, 0); + goto done; + } - if (a0 && nlri_len && !bgp_set_next_hop(p, a0)) - a0 = NULL; + /* Check minimal length */ + if (len < 23) + { bgp_error(conn, 1, 2, pkt+16, 2); return; } - last_id = 0; - src = p->p.main_source; + /* Skip fixed header */ + uint pos = 19; - while (nlri_len) - { - DECODE_PREFIX(nlri, nlri_len); - DBG("Add %I/%d\n", prefix, pxlen); + /* + * UPDATE message format + * + * 2 B IPv4 Withdrawn Routes Length + * var IPv4 Withdrawn Routes NLRI + * 2 B Total Path Attribute Length + * var Path Attributes + * var IPv4 Reachable Routes NLRI + */ - if (a0) - bgp_rte_update(p, prefix, pxlen, path_id, &last_id, &src, a0, &a); - else /* Forced withdraw as a result of soft error */ - bgp_rte_withdraw(p, prefix, pxlen, path_id, &last_id, &src); - } + s.ip_unreach_len = get_u16(pkt + pos); + s.ip_unreach_nlri = pkt + pos + 2; + pos += 2 + s.ip_unreach_len; - done: - if (a) - rta_free(a); + if (pos + 2 > len) + bgp_parse_error(&s, 1); - if (err) - bgp_error(conn, 3, err, NULL, 0); + s.attr_len = get_u16(pkt + pos); + s.attrs = pkt + pos + 2; + pos += 2 + s.attr_len; + + if (pos > len) + bgp_parse_error(&s, 1); + s.ip_reach_len = len - pos; + s.ip_reach_nlri = pkt + pos; + + + if (s.attr_len) + ea = bgp_decode_attrs(&s, s.attrs, s.attr_len); + + /* Check for End-of-RIB marker */ + if (!s.attr_len && !s.ip_unreach_len && !s.ip_reach_len) + { bgp_rx_end_mark(&s, BGP_AF_IPV4); goto done; } + + /* Check for MP End-of-RIB marker */ + if ((s.attr_len < 8) && !s.ip_unreach_len && !s.ip_reach_len && + !s.mp_reach_len && !s.mp_unreach_len && s.mp_unreach_af) + { bgp_rx_end_mark(&s, s.mp_unreach_af); goto done; } + + if (s.ip_unreach_len) + bgp_decode_nlri(&s, BGP_AF_IPV4, s.ip_unreach_nlri, s.ip_unreach_len, NULL, NULL, 0); + + if (s.mp_unreach_len) + bgp_decode_nlri(&s, s.mp_unreach_af, s.mp_unreach_nlri, s.mp_unreach_len, NULL, NULL, 0); + + if (s.ip_reach_len) + bgp_decode_nlri(&s, BGP_AF_IPV4, s.ip_reach_nlri, s.ip_reach_len, + ea, s.ip_next_hop_data, s.ip_next_hop_len); + + if (s.mp_reach_len) + bgp_decode_nlri(&s, s.mp_reach_af, s.mp_reach_nlri, s.mp_reach_len, + ea, s.mp_next_hop_data, s.mp_next_hop_len); + +done: + rta_free(s.cached_rta); + lp_flush(s.pool); return; } -#else /* IPv6 version */ -#define DO_NLRI(name) \ - x = p->name##_start; \ - len = len0 = p->name##_len; \ - if (len) \ - { \ - if (len < 3) { err=9; goto done; } \ - af = get_u16(x); \ - x += 3; \ - len -= 3; \ - DBG("\tNLRI AF=%d sub=%d len=%d\n", af, x[-1], len);\ - } \ - else \ - af = 0; \ - if (af == BGP_AF_IPV6) +/* + * ROUTE-REFRESH + */ -static void -bgp_attach_next_hop(rta *a0, byte *x) +static inline byte * +bgp_create_route_refresh(struct bgp_channel *c, byte *buf) { - ip_addr *nh = (ip_addr *) bgp_attach_attr_wa(&a0->eattrs, bgp_linpool, BA_NEXT_HOP, NEXT_HOP_LENGTH); - memcpy(nh, x+1, 16); - ipa_ntoh(nh[0]); + struct bgp_proto *p = (void *) c->c.proto; - /* We store received link local address in the other part of BA_NEXT_HOP eattr. */ - if (*x == 32) - { - memcpy(nh+1, x+17, 16); - ipa_ntoh(nh[1]); - } - else - nh[1] = IPA_NONE; + BGP_TRACE(D_PACKETS, "Sending ROUTE-REFRESH"); + + /* Original route refresh request, RFC 2918 */ + put_af4(buf, c->afi); + buf[2] = BGP_RR_REQUEST; + + return buf+4; +} + +static inline byte * +bgp_create_begin_refresh(struct bgp_channel *c, byte *buf) +{ + struct bgp_proto *p = (void *) c->c.proto; + + BGP_TRACE(D_PACKETS, "Sending BEGIN-OF-RR"); + + /* Demarcation of beginning of route refresh (BoRR), RFC 7313 */ + put_af4(buf, c->afi); + buf[2] = BGP_RR_BEGIN; + + return buf+4; } +static inline byte * +bgp_create_end_refresh(struct bgp_channel *c, byte *buf) +{ + struct bgp_proto *p = (void *) c->c.proto; + + BGP_TRACE(D_PACKETS, "Sending END-OF-RR"); + + /* Demarcation of ending of route refresh (EoRR), RFC 7313 */ + put_af4(buf, c->afi); + buf[2] = BGP_RR_END; + + return buf+4; +} static void -bgp_do_rx_update(struct bgp_conn *conn, - byte *withdrawn UNUSED, int withdrawn_len, - byte *nlri UNUSED, int nlri_len, - byte *attrs, int attr_len) +bgp_rx_route_refresh(struct bgp_conn *conn, byte *pkt, uint len) { struct bgp_proto *p = conn->bgp; - struct rte_src *src = p->p.main_source; - byte *x; - int len, len0; - unsigned af; - rta *a0, *a = NULL; - ip_addr prefix; - int pxlen, err = 0; - u32 path_id = 0; - u32 last_id = 0; - - p->mp_reach_len = 0; - p->mp_unreach_len = 0; - a0 = bgp_decode_attrs(conn, attrs, attr_len, bgp_linpool, 0); - - if (conn->state != BS_ESTABLISHED) /* fatal error during decoding */ - return; - /* Check for End-of-RIB marker */ - if ((attr_len < 8) && !withdrawn_len && !nlri_len && !p->mp_reach_len && - (p->mp_unreach_len == 3) && (get_u16(p->mp_unreach_start) == BGP_AF_IPV6)) - { - bgp_rx_end_mark(p); - return; - } + if (conn->state != BS_ESTABLISHED) + { bgp_error(conn, 5, fsm_err_subcode[conn->state], NULL, 0); return; } - DO_NLRI(mp_unreach) - { - while (len) - { - DECODE_PREFIX(x, len); - DBG("Withdraw %I/%d\n", prefix, pxlen); - bgp_rte_withdraw(p, prefix, pxlen, path_id, &last_id, &src); - } - } + if (!conn->local_caps->route_refresh) + { bgp_error(conn, 1, 3, pkt+18, 1); return; } - DO_NLRI(mp_reach) - { - /* Create fake NEXT_HOP attribute */ - if (len < 1 || (*x != 16 && *x != 32) || len < *x + 2) - { err = 9; goto done; } + if (len < (BGP_HEADER_LENGTH + 4)) + { bgp_error(conn, 1, 2, pkt+16, 2); return; } - if (a0) - bgp_attach_next_hop(a0, x); + if (len > (BGP_HEADER_LENGTH + 4)) + { bgp_error(conn, 7, 1, pkt, MIN(len, 2048)); return; } - /* Also ignore one reserved byte */ - len -= *x + 2; - x += *x + 2; + struct bgp_channel *c = bgp_get_channel(p, get_af4(pkt+19)); + if (!c) + { + log(L_WARN "%s: Got ROUTE-REFRESH subtype %u for AF %u.%u, ignoring", + p->p.name, pkt[21], get_u16(pkt+19), pkt[22]); + return; + } - if (a0 && ! bgp_set_next_hop(p, a0)) - a0 = NULL; + /* RFC 7313 redefined reserved field as RR message subtype */ + uint subtype = p->enhanced_refresh ? pkt[21] : BGP_RR_REQUEST; - last_id = 0; - src = p->p.main_source; + switch (subtype) + { + case BGP_RR_REQUEST: + BGP_TRACE(D_PACKETS, "Got ROUTE-REFRESH"); + channel_request_feeding(&c->c); + break; - while (len) - { - DECODE_PREFIX(x, len); - DBG("Add %I/%d\n", prefix, pxlen); + case BGP_RR_BEGIN: + BGP_TRACE(D_PACKETS, "Got BEGIN-OF-RR"); + bgp_refresh_begin(c); + break; - if (a0) - bgp_rte_update(p, prefix, pxlen, path_id, &last_id, &src, a0, &a); - else /* Forced withdraw as a result of soft error */ - bgp_rte_withdraw(p, prefix, pxlen, path_id, &last_id, &src); - } - } + case BGP_RR_END: + BGP_TRACE(D_PACKETS, "Got END-OF-RR"); + bgp_refresh_end(c); + break; - done: - if (a) - rta_free(a); + default: + log(L_WARN "%s: Got ROUTE-REFRESH message with unknown subtype %u, ignoring", + p->p.name, subtype); + break; + } +} + +static inline struct bgp_channel * +bgp_get_channel_to_send(struct bgp_proto *p, struct bgp_conn *conn) +{ + uint i = conn->last_channel; - if (err) /* Use subcode 9, not err */ - bgp_error(conn, 3, 9, NULL, 0); + /* Try the last channel, but at most several times */ + if ((conn->channels_to_send & (1 << i)) && + (conn->last_channel_count < 16)) + goto found; - return; + /* Find channel with non-zero channels_to_send */ + do + { + i++; + if (i >= p->channel_count) + i = 0; + } + while (! (conn->channels_to_send & (1 << i))); + + /* Use that channel */ + conn->last_channel = i; + conn->last_channel_count = 0; + +found: + conn->last_channel_count++; + return p->channel_map[i]; } -#endif +static inline int +bgp_send(struct bgp_conn *conn, uint type, uint len) +{ + sock *sk = conn->sk; + byte *buf = sk->tbuf; -static void -bgp_rx_update(struct bgp_conn *conn, byte *pkt, uint len) + memset(buf, 0xff, 16); /* Marker */ + put_u16(buf+16, len); + buf[18] = type; + + return sk_send(sk, len); +} + +/** + * bgp_fire_tx - transmit packets + * @conn: connection + * + * Whenever the transmit buffers of the underlying TCP connection + * are free and we have any packets queued for sending, the socket functions + * call bgp_fire_tx() which takes care of selecting the highest priority packet + * queued (Notification > Keepalive > Open > Update), assembling its header + * and body and sending it to the connection. + */ +static int +bgp_fire_tx(struct bgp_conn *conn) { struct bgp_proto *p = conn->bgp; - byte *withdrawn, *attrs, *nlri; - uint withdrawn_len, attr_len, nlri_len; + struct bgp_channel *c; + byte *buf, *pkt, *end; + uint s; - BGP_TRACE_RL(&rl_rcv_update, D_PACKETS, "Got UPDATE"); + if (!conn->sk) + return 0; - /* Workaround for some BGP implementations that skip initial KEEPALIVE */ - if (conn->state == BS_OPENCONFIRM) - bgp_conn_enter_established_state(conn); + buf = conn->sk->tbuf; + pkt = buf + BGP_HEADER_LENGTH; + s = conn->packets_to_send; - 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); + if (s & (1 << PKT_SCHEDULE_CLOSE)) + { + /* We can finally close connection and enter idle state */ + bgp_conn_enter_idle_state(conn); + return 0; + } + if (s & (1 << PKT_NOTIFICATION)) + { + conn->packets_to_send = 1 << PKT_SCHEDULE_CLOSE; + end = bgp_create_notification(conn, pkt); + return bgp_send(conn, PKT_NOTIFICATION, end - buf); + } + else if (s & (1 << PKT_KEEPALIVE)) + { + conn->packets_to_send &= ~(1 << PKT_KEEPALIVE); + BGP_TRACE(D_PACKETS, "Sending KEEPALIVE"); + bgp_start_timer(conn->keepalive_timer, conn->keepalive_time); + return bgp_send(conn, PKT_KEEPALIVE, BGP_HEADER_LENGTH); + } + else if (s & (1 << PKT_OPEN)) + { + conn->packets_to_send &= ~(1 << PKT_OPEN); + end = bgp_create_open(conn, pkt); + return bgp_send(conn, PKT_OPEN, end - buf); + } + else while (conn->channels_to_send) + { + c = bgp_get_channel_to_send(p, conn); + s = c->packets_to_send; - /* Find parts of the packet and check sizes */ - if (len < 23) + if (s & (1 << PKT_ROUTE_REFRESH)) { - bgp_error(conn, 1, 2, pkt+16, 2); - return; + c->packets_to_send &= ~(1 << PKT_ROUTE_REFRESH); + end = bgp_create_route_refresh(c, pkt); + return bgp_send(conn, PKT_ROUTE_REFRESH, end - buf); } - withdrawn = pkt + 21; - withdrawn_len = get_u16(pkt + 19); - if (withdrawn_len + 23 > len) - goto malformed; - attrs = withdrawn + withdrawn_len + 2; - attr_len = get_u16(attrs - 2); - if (withdrawn_len + attr_len + 23 > len) - goto malformed; - nlri = attrs + attr_len; - nlri_len = len - withdrawn_len - attr_len - 23; - if (!attr_len && nlri_len) - goto malformed; - DBG("Sizes: withdrawn=%d, attrs=%d, NLRI=%d\n", withdrawn_len, attr_len, nlri_len); - - lp_flush(bgp_linpool); - - bgp_do_rx_update(conn, withdrawn, withdrawn_len, nlri, nlri_len, attrs, attr_len); - return; + else if (s & (1 << PKT_BEGIN_REFRESH)) + { + /* BoRR is a subtype of RR, but uses separate bit in packets_to_send */ + c->packets_to_send &= ~(1 << PKT_BEGIN_REFRESH); + end = bgp_create_begin_refresh(c, pkt); + return bgp_send(conn, PKT_ROUTE_REFRESH, end - buf); + } + else if (s & (1 << PKT_UPDATE)) + { + end = bgp_create_update(c, pkt); + if (end) + return bgp_send(conn, PKT_UPDATE, end - buf); + + /* No update to send, perhaps we need to send End-of-RIB or EoRR */ + c->packets_to_send = 0; + conn->channels_to_send &= ~(1 << c->index); + + if (c->feed_state == BFS_LOADED) + { + c->feed_state = BFS_NONE; + end = bgp_create_end_mark(c, pkt); + return bgp_send(conn, PKT_UPDATE, end - buf); + } + + else if (c->feed_state == BFS_REFRESHED) + { + c->feed_state = BFS_NONE; + end = bgp_create_end_refresh(c, pkt); + return bgp_send(conn, PKT_ROUTE_REFRESH, end - buf); + } + } + else if (s) + bug("Channel packets_to_send: %x", s); + + c->packets_to_send = 0; + conn->channels_to_send &= ~(1 << c->index); + } + + return 0; +} + +/** + * bgp_schedule_packet - schedule a packet for transmission + * @conn: connection + * @c: channel + * @type: packet type + * + * Schedule a packet of type @type to be sent as soon as possible. + */ +void +bgp_schedule_packet(struct bgp_conn *conn, struct bgp_channel *c, int type) +{ + ASSERT(conn->sk); + + DBG("BGP: Scheduling packet type %d\n", type); + + if (c) + { + if (! conn->channels_to_send) + { + conn->last_channel = c->index; + conn->last_channel_count = 0; + } + + c->packets_to_send |= 1 << type; + conn->channels_to_send |= 1 << c->index; + } + else + conn->packets_to_send |= 1 << type; + + if ((conn->sk->tpos == conn->sk->tbuf) && !ev_active(conn->tx_ev)) + ev_schedule(conn->tx_ev); +} + +void +bgp_kick_tx(void *vconn) +{ + struct bgp_conn *conn = vconn; -malformed: - bgp_error(conn, 3, 1, NULL, 0); + DBG("BGP: kicking TX\n"); + while (bgp_fire_tx(conn) > 0) + ; +} + +void +bgp_tx(sock *sk) +{ + struct bgp_conn *conn = sk->data; + + DBG("BGP: TX hook\n"); + while (bgp_fire_tx(conn) > 0) + ; } + static struct { byte major, minor; byte *msg; @@ -1480,26 +2665,25 @@ static struct { * which might be static string or given temporary buffer. */ const char * -bgp_error_dsc(unsigned code, unsigned subcode) +bgp_error_dsc(uint code, uint subcode) { static char buff[32]; - unsigned i; + uint i; + for (i=0; i < ARRAY_SIZE(bgp_msg_table); i++) if (bgp_msg_table[i].major == code && bgp_msg_table[i].minor == subcode) - { - return bgp_msg_table[i].msg; - } + return bgp_msg_table[i].msg; - bsprintf(buff, "Unknown error %d.%d", code, subcode); + bsprintf(buff, "Unknown error %u.%u", code, subcode); return buff; } void -bgp_log_error(struct bgp_proto *p, u8 class, char *msg, unsigned code, unsigned subcode, byte *data, unsigned len) +bgp_log_error(struct bgp_proto *p, u8 class, char *msg, uint code, uint subcode, byte *data, uint len) { const byte *name; byte *t, argbuf[36]; - unsigned i; + uint i; /* Don't report Cease messages generated by myself */ if (code == 6 && class == BE_BGP_TX) @@ -1515,7 +2699,7 @@ bgp_log_error(struct bgp_proto *p, u8 class, char *msg, unsigned code, unsigned if ((code == 2) && (subcode == 2) && ((len == 2) || (len == 4))) { /* Bad peer AS - we would like to print the AS */ - t += bsprintf(t, "%d", (len == 2) ? get_u16(data) : get_u32(data)); + t += bsprintf(t, "%u", (len == 2) ? get_u16(data) : get_u32(data)); goto done; } if (len > 16) @@ -1532,47 +2716,25 @@ static void bgp_rx_notification(struct bgp_conn *conn, byte *pkt, uint len) { struct bgp_proto *p = conn->bgp; + if (len < 21) - { - bgp_error(conn, 1, 2, pkt+16, 2); - return; - } + { bgp_error(conn, 1, 2, pkt+16, 2); return; } - unsigned code = pkt[19]; - unsigned subcode = pkt[20]; + uint code = pkt[19]; + uint subcode = pkt[20]; int err = (code != 6); bgp_log_error(p, BE_BGP_RX, "Received", code, subcode, pkt+21, len-21); bgp_store_error(p, conn, BE_BGP_RX, (code << 16) | subcode); -#ifndef IPV6 - if ((code == 2) && ((subcode == 4) || (subcode == 7)) - /* Error related to capability: - * 4 - Peer does not support capabilities at all. - * 7 - Peer request some capability. Strange unless it is IPv6 only peer. - */ - && (p->cf->capabilities == 2) - /* Capabilities are not explicitly enabled or disabled, therefore heuristic is used */ - && (conn->start_state == BSS_CONNECT) - /* Failed connection attempt have used capabilities */ - && (p->cf->remote_as <= 0xFFFF)) - /* Not possible with disabled capabilities */ - { - /* We try connect without capabilities */ - log(L_WARN "%s: Capability related error received, retry with capabilities disabled", p->p.name); - p->start_state = BSS_CONNECT_NOCAP; - err = 0; - } -#endif - bgp_conn_enter_close_state(conn); - bgp_schedule_packet(conn, PKT_SCHEDULE_CLOSE); + bgp_schedule_packet(conn, NULL, PKT_SCHEDULE_CLOSE); - if (err) - { - bgp_update_startup_delay(p); - bgp_stop(p, 0); - } + if (err) + { + bgp_update_startup_delay(p); + bgp_stop(p, 0); + } } static void @@ -1582,64 +2744,12 @@ bgp_rx_keepalive(struct bgp_conn *conn) BGP_TRACE(D_PACKETS, "Got KEEPALIVE"); bgp_start_timer(conn->hold_timer, conn->hold_time); - switch (conn->state) - { - case BS_OPENCONFIRM: - bgp_conn_enter_established_state(conn); - break; - case BS_ESTABLISHED: - break; - default: - bgp_error(conn, 5, fsm_err_subcode[conn->state], NULL, 0); - } -} -static void -bgp_rx_route_refresh(struct bgp_conn *conn, byte *pkt, uint len) -{ - struct bgp_proto *p = conn->bgp; + if (conn->state == BS_OPENCONFIRM) + { bgp_conn_enter_established_state(conn); return; } if (conn->state != BS_ESTABLISHED) - { bgp_error(conn, 5, fsm_err_subcode[conn->state], NULL, 0); return; } - - if (!p->cf->enable_refresh) - { bgp_error(conn, 1, 3, pkt+18, 1); return; } - - if (len < (BGP_HEADER_LENGTH + 4)) - { bgp_error(conn, 1, 2, pkt+16, 2); return; } - - if (len > (BGP_HEADER_LENGTH + 4)) - { bgp_error(conn, 7, 1, pkt, MIN(len, 2048)); return; } - - /* FIXME - we ignore AFI/SAFI values, as we support - just one value and even an error code for an invalid - request is not defined */ - - /* RFC 7313 redefined reserved field as RR message subtype */ - uint subtype = conn->peer_enhanced_refresh_support ? pkt[21] : BGP_RR_REQUEST; - - switch (subtype) - { - case BGP_RR_REQUEST: - BGP_TRACE(D_PACKETS, "Got ROUTE-REFRESH"); - proto_request_feeding(&p->p); - break; - - case BGP_RR_BEGIN: - BGP_TRACE(D_PACKETS, "Got BEGIN-OF-RR"); - bgp_refresh_begin(p); - break; - - case BGP_RR_END: - BGP_TRACE(D_PACKETS, "Got END-OF-RR"); - bgp_refresh_end(p); - break; - - default: - log(L_WARN "%s: Got ROUTE-REFRESH message with unknown subtype %u, ignoring", - p->p.name, subtype); - break; - } + bgp_error(conn, 5, fsm_err_subcode[conn->state], NULL, 0); } @@ -1653,7 +2763,7 @@ bgp_rx_route_refresh(struct bgp_conn *conn, byte *pkt, uint len) * packet handler according to the packet type. */ static void -bgp_rx_packet(struct bgp_conn *conn, byte *pkt, unsigned len) +bgp_rx_packet(struct bgp_conn *conn, byte *pkt, uint len) { byte type = pkt[18]; @@ -1663,14 +2773,14 @@ bgp_rx_packet(struct bgp_conn *conn, byte *pkt, unsigned len) mrt_dump_bgp_packet(conn, pkt, len); switch (type) - { - case PKT_OPEN: return bgp_rx_open(conn, pkt, len); - case PKT_UPDATE: return bgp_rx_update(conn, pkt, len); - case PKT_NOTIFICATION: return bgp_rx_notification(conn, pkt, len); - case PKT_KEEPALIVE: return bgp_rx_keepalive(conn); - case PKT_ROUTE_REFRESH: return bgp_rx_route_refresh(conn, pkt, len); - default: bgp_error(conn, 1, 3, pkt+18, 1); - } + { + case PKT_OPEN: return bgp_rx_open(conn, pkt, len); + case PKT_UPDATE: return bgp_rx_update(conn, pkt, len); + case PKT_NOTIFICATION: return bgp_rx_notification(conn, pkt, len); + case PKT_KEEPALIVE: return bgp_rx_keepalive(conn); + case PKT_ROUTE_REFRESH: return bgp_rx_route_refresh(conn, pkt, len); + default: bgp_error(conn, 1, 3, pkt+18, 1); + } } /** @@ -1687,10 +2797,9 @@ int bgp_rx(sock *sk, uint size) { struct bgp_conn *conn = sk->data; - struct bgp_proto *p = conn->bgp; byte *pkt_start = sk->rbuf; byte *end = pkt_start + size; - unsigned i, len; + uint i, len; DBG("BGP: RX hook: Got %d bytes\n", size); while (end >= pkt_start + BGP_HEADER_LENGTH) @@ -1704,7 +2813,7 @@ bgp_rx(sock *sk, uint size) break; } len = get_u16(pkt_start+16); - if (len < BGP_HEADER_LENGTH || len > bgp_max_packet_length(p)) + if ((len < BGP_HEADER_LENGTH) || (len > bgp_max_packet_length(conn))) { bgp_error(conn, 1, 2, pkt_start+16, 2); break; diff --git a/proto/ospf/Makefile b/proto/ospf/Makefile index f90222cf..39e74f71 100644 --- a/proto/ospf/Makefile +++ b/proto/ospf/Makefile @@ -1,5 +1,6 @@ -source=ospf.c topology.c packet.c hello.c neighbor.c iface.c dbdes.c lsreq.c lsupd.c lsack.c lsalib.c rt.c -root-rel=../../ -dir-name=proto/ospf +src := dbdes.c hello.c iface.c lsack.c lsalib.c lsreq.c lsupd.c neighbor.c ospf.c packet.c rt.c topology.c +obj := $(src-o-files) +$(all-daemon) +$(cf-local) -include ../../Rules +tests_objs := $(tests_objs) $(src-o-files)
\ No newline at end of file diff --git a/proto/ospf/config.Y b/proto/ospf/config.Y index 7b35b191..9cfc70a9 100644 --- a/proto/ospf/config.Y +++ b/proto/ospf/config.Y @@ -82,6 +82,10 @@ ospf_proto_finish(void) if (EMPTY_LIST(cf->area_list)) cf_error( "No configured areas in OSPF"); + /* Define default channel */ + if (EMPTY_LIST(this_proto->channels)) + channel_config_new(NULL, this_proto->net_type, this_proto); + int areano = 0; int backbone = 0; int nssa = 0; @@ -98,7 +102,7 @@ ospf_proto_finish(void) cf->abr = areano > 1; /* Route export or NSSA translation (RFC 3101 3.1) */ - cf->asbr = (this_proto->out_filter != FILTER_REJECT) || (nssa && cf->abr); + cf->asbr = (proto_cf_main_channel(this_proto)->out_filter != FILTER_REJECT) || (nssa && cf->abr); if (cf->abr && !backbone) { @@ -135,8 +139,8 @@ ospf_check_auth(void) CF_DECLS -CF_KEYWORDS(OSPF, AREA, OSPF_METRIC1, OSPF_METRIC2, OSPF_TAG, OSPF_ROUTER_ID) -CF_KEYWORDS(NEIGHBORS, RFC1583COMPAT, STUB, TICK, COST, COST2, RETRANSMIT) +CF_KEYWORDS(OSPF, V2, V3, OSPF_METRIC1, OSPF_METRIC2, OSPF_TAG, OSPF_ROUTER_ID) +CF_KEYWORDS(AREA, NEIGHBORS, RFC1583COMPAT, STUB, TICK, COST, COST2, RETRANSMIT) CF_KEYWORDS(HELLO, TRANSMIT, PRIORITY, DEAD, TYPE, BROADCAST, BCAST, DEFAULT) CF_KEYWORDS(NONBROADCAST, NBMA, POINTOPOINT, PTP, POINTOMULTIPOINT, PTMP) CF_KEYWORDS(NONE, SIMPLE, AUTHENTICATION, STRICT, CRYPTOGRAPHIC, TTL, SECURITY) @@ -146,23 +150,30 @@ CF_KEYWORDS(WAIT, DELAY, LSADB, ECMP, LIMIT, WEIGHT, NSSA, TRANSLATOR, STABILITY CF_KEYWORDS(GLOBAL, LSID, ROUTER, SELF, INSTANCE, REAL, NETMASK, TX, PRIORITY, LENGTH) CF_KEYWORDS(SECONDARY, MERGE, LSA, SUPPRESSION) -%type <t> opttext %type <ld> lsadb_args -%type <i> nbma_eligible +%type <i> ospf_variant nbma_eligible CF_GRAMMAR CF_ADDTO(proto, ospf_proto '}' { ospf_proto_finish(); } ) -ospf_proto_start: proto_start OSPF { - this_proto = proto_config_new(&proto_ospf, $1); - init_list(&OSPF_CFG->area_list); - init_list(&OSPF_CFG->vlink_list); - OSPF_CFG->tick = OSPF_DEFAULT_TICK; - OSPF_CFG->ospf2 = OSPF_IS_V2; - } +ospf_variant: + OSPF { $$ = 1; } + | OSPF V2 { $$ = 1; } + | OSPF V3 { $$ = 0; } ; +ospf_proto_start: proto_start ospf_variant +{ + this_proto = proto_config_new(&proto_ospf, $1); + this_proto->net_type = $2 ? NET_IP4 : NET_IP6; + + init_list(&OSPF_CFG->area_list); + init_list(&OSPF_CFG->vlink_list); + OSPF_CFG->tick = OSPF_DEFAULT_TICK; + OSPF_CFG->ospf2 = $2; +}; + ospf_proto: ospf_proto_start proto_name '{' | ospf_proto ospf_proto_item ';' @@ -170,6 +181,7 @@ ospf_proto: ospf_proto_item: proto_item + | proto_channel | RFC1583COMPAT bool { OSPF_CFG->rfc1583 = $2; } | STUB ROUTER bool { OSPF_CFG->stub_router = $3; } | ECMP bool { OSPF_CFG->ecmp = $2 ? OSPF_DEFAULT_ECMP_LIMIT : 0; } @@ -226,10 +238,10 @@ ospf_stubnet: ; ospf_stubnet_start: - prefix { + net_ip { this_stubnet = cfg_allocz(sizeof(struct ospf_stubnet_config)); add_tail(&this_area->stubnet_list, NODE this_stubnet); - this_stubnet->px = $1; + this_stubnet->prefix = $1; this_stubnet->cost = COST_D; } ; @@ -325,7 +337,6 @@ ospf_iface_item: | TTL SECURITY bool { OSPF_PATT->ttl_security = $3; } | TTL SECURITY TX ONLY { OSPF_PATT->ttl_security = 2; } | BFD bool { OSPF_PATT->bfd = $2; cf_check_bfd($2); } - | SECONDARY bool { OSPF_PATT->bsd_secondary = $2; } | password_list { ospf_check_auth(); } ; @@ -336,12 +347,11 @@ pref_list: pref_item: pref_base pref_opt ';' ; -pref_base: prefix +pref_base: net_ip { this_pref = cfg_allocz(sizeof(struct area_net_config)); add_tail(this_nets, NODE this_pref); - this_pref->px.addr = $1.addr; - this_pref->px.len = $1.len; + this_pref->prefix = $1; } ; @@ -415,11 +425,6 @@ ospf_iface: ospf_iface_start ospf_iface_patt_list ospf_iface_opt_list { ospf_iface_finish(); } ; -opttext: - TEXT - | /* empty */ { $$ = NULL; } - ; - CF_ADDTO(dynamic_attr, OSPF_METRIC1 { $$ = f_new_dynamic_attr(EAF_TYPE_INT | EAF_TEMP, T_INT, EA_OSPF_METRIC1); }) CF_ADDTO(dynamic_attr, OSPF_METRIC2 { $$ = f_new_dynamic_attr(EAF_TYPE_INT | EAF_TEMP, T_INT, EA_OSPF_METRIC2); }) CF_ADDTO(dynamic_attr, OSPF_TAG { $$ = f_new_dynamic_attr(EAF_TYPE_INT | EAF_TEMP, T_INT, EA_OSPF_TAG); }) diff --git a/proto/ospf/dbdes.c b/proto/ospf/dbdes.c index d6904343..195b03ad 100644 --- a/proto/ospf/dbdes.c +++ b/proto/ospf/dbdes.c @@ -39,7 +39,7 @@ struct ospf_dbdes3_packet static inline uint -ospf_dbdes_hdrlen(struct ospf_proto *p UNUSED4 UNUSED6) +ospf_dbdes_hdrlen(struct ospf_proto *p) { return ospf_is_v2(p) ? sizeof(struct ospf_dbdes2_packet) : sizeof(struct ospf_dbdes3_packet); diff --git a/proto/ospf/hello.c b/proto/ospf/hello.c index e00487dc..2c55155d 100644 --- a/proto/ospf/hello.c +++ b/proto/ospf/hello.c @@ -74,7 +74,7 @@ ospf_send_hello(struct ospf_iface *ifa, int kind, struct ospf_neighbor *dirn) ((ifa->type == OSPF_IT_PTP) && !ifa->ptp_netmask)) ps->netmask = 0; else - ps->netmask = htonl(u32_mkmask(ifa->addr->pxlen)); + ps->netmask = htonl(u32_mkmask(ifa->addr->prefix.pxlen)); ps->helloint = ntohs(ifa->helloint); ps->options = ifa->oa->options; @@ -198,7 +198,7 @@ ospf_receive_hello(struct ospf_packet *pkt, struct ospf_iface *ifa, /* RFC 2328 10.5 */ /* - * We may not yet havethe associate neighbor, so we use Router ID from the + * We may not yet have the associate neighbor, so we use Router ID from the * packet instead of one from the neighbor structure for log messages. */ u32 rcv_rid = ntohl(pkt->routerid); @@ -227,7 +227,7 @@ ospf_receive_hello(struct ospf_packet *pkt, struct ospf_iface *ifa, if ((ifa->type != OSPF_IT_VLINK) && (ifa->type != OSPF_IT_PTP) && - ((uint) pxlen != ifa->addr->pxlen)) + ((uint) pxlen != ifa->addr->prefix.pxlen)) DROP("prefix length mismatch", pxlen); neighbors = ps->neighbors; diff --git a/proto/ospf/iface.c b/proto/ospf/iface.c index 280fa4c1..675cf76d 100644 --- a/proto/ospf/iface.c +++ b/proto/ospf/iface.c @@ -55,7 +55,9 @@ ifa_tx_length(struct ospf_iface *ifa) static inline uint ifa_tx_hdrlen(struct ospf_iface *ifa) { - uint hlen = SIZE_OF_IP_HEADER; + struct ospf_proto *p = ifa->oa->po; + + uint hlen = ospf_is_v2(p) ? IP4_HEADER_LENGTH : IP6_HEADER_LENGTH; /* Relevant just for OSPFv2 */ if (ifa->autype == OSPF_AUTH_CRYPT) @@ -115,6 +117,7 @@ ospf_sk_open(struct ospf_iface *ifa) sock *sk = sk_new(ifa->pool); sk->type = SK_IP; + sk->subtype = ospf_is_v2(p) ? SK_IPV4 : SK_IPV6; sk->dport = OSPF_PROTO; sk->saddr = ifa->addr->ip; sk->iface = ifa->iface; @@ -199,6 +202,7 @@ ospf_open_vlink_sk(struct ospf_proto *p) { sock *sk = sk_new(p->p.pool); sk->type = SK_IP; + sk->subtype = ospf_is_v2(p) ? SK_IPV4 : SK_IPV6; sk->dport = OSPF_PROTO; /* FIXME: configurable tos/priority ? */ @@ -244,8 +248,8 @@ ospf_iface_down(struct ospf_iface *ifa) OSPF_TRACE(D_EVENTS, "Removing interface %s (peer %I) from area %R", ifa->ifname, ifa->addr->opposite, ifa->oa->areaid); else - OSPF_TRACE(D_EVENTS, "Removing interface %s (%I/%d) from area %R", - ifa->ifname, ifa->addr->prefix, ifa->addr->pxlen, ifa->oa->areaid); + OSPF_TRACE(D_EVENTS, "Removing interface %s (%N) from area %R", + ifa->ifname, &ifa->addr->prefix, ifa->oa->areaid); /* First of all kill all the related vlinks */ WALK_LIST(iff, p->iface_list) @@ -530,15 +534,6 @@ ospf_iface_stubby(struct ospf_iface_patt *ip, struct ifa *addr) if (addr->iface->flags & IF_LOOPBACK) return 1; - /* - * For compatibility reasons on BSD systems, we force OSPF - * interfaces with non-primary IP prefixes to be stub. - */ -#if defined(OSPFv2) && !defined(CONFIG_MC_PROPER_SRC) - if (!ip->bsd_secondary && !(addr->flags & IA_PRIMARY)) - return 1; -#endif - return ip->stub; } @@ -557,8 +552,8 @@ ospf_iface_new(struct ospf_area *oa, struct ifa *addr, struct ospf_iface_patt *i OSPF_TRACE(D_EVENTS, "Adding interface %s (peer %I) to area %R", iface->name, addr->opposite, oa->areaid); else - OSPF_TRACE(D_EVENTS, "Adding interface %s (%I/%d) to area %R", - iface->name, addr->prefix, addr->pxlen, oa->areaid); + 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"); ifa = mb_allocz(pool, sizeof(struct ospf_iface)); @@ -596,6 +591,7 @@ ospf_iface_new(struct ospf_area *oa, struct ifa *addr, struct ospf_iface_patt *i if (ip->ptp_netmask < 2) ifa->ptp_netmask = ip->ptp_netmask; + ifa->drip = ifa->bdrip = ospf_is_v2(p) ? IPA_NONE4 : IPA_NONE6; ifa->type = ospf_iface_classify(ip->type, addr); @@ -635,7 +631,7 @@ ospf_iface_new(struct ospf_area *oa, struct ifa *addr, struct ospf_iface_patt *i should be used). Because OSPFv3 iface is not subnet-specific, there is no need for ipa_in_net() check */ - if (ospf_is_v2(p) && !ipa_in_net(nb->ip, addr->prefix, addr->pxlen)) + if (ospf_is_v2(p) && !ipa_in_netX(nb->ip, &addr->prefix)) continue; if (ospf_is_v3(p) && !ipa_is_link_local(nb->ip)) @@ -648,7 +644,7 @@ ospf_iface_new(struct ospf_area *oa, struct ifa *addr, struct ospf_iface_patt *i add_tail(&oa->po->iface_list, NODE ifa); struct object_lock *lock = olock_new(pool); - lock->addr = ospf_is_v2(p) ? ifa->addr->prefix : IPA_NONE; + lock->addr = ospf_is_v2(p) ? ipa_from_ip4(net4_prefix(&ifa->addr->prefix)) : IPA_NONE; lock->type = OBJLOCK_IP; lock->port = OSPF_PROTO; lock->inst = ifa->instance_id; @@ -898,7 +894,7 @@ ospf_iface_reconfigure(struct ospf_iface *ifa, struct ospf_iface_patt *new) WALK_LIST(nb, new->nbma_list) { /* See related note in ospf_iface_new() */ - if (ospf_is_v2(p) && !ipa_in_net(nb->ip, ifa->addr->prefix, ifa->addr->pxlen)) + if (ospf_is_v2(p) && !ipa_in_netX(nb->ip, &ifa->addr->prefix)) continue; if (ospf_is_v3(p) && !ipa_is_link_local(nb->ip)) @@ -1085,6 +1081,9 @@ ospf_ifa_notify2(struct proto *P, uint flags, struct ifa *a) { struct ospf_proto *p = (struct ospf_proto *) P; + if (a->prefix.type != NET_IP4) + return; + if (a->flags & IA_SECONDARY) return; @@ -1114,6 +1113,9 @@ ospf_ifa_notify3(struct proto *P, uint flags, struct ifa *a) { struct ospf_proto *p = (struct ospf_proto *) P; + if (a->prefix.type != NET_IP6) + return; + if (a->flags & IA_SECONDARY) return; @@ -1166,6 +1168,9 @@ ospf_reconfigure_ifaces2(struct ospf_proto *p) WALK_LIST(a, iface->addrs) { + if (a->prefix.type != NET_IP4) + continue; + if (a->flags & IA_SECONDARY) continue; @@ -1184,8 +1189,8 @@ ospf_reconfigure_ifaces2(struct ospf_proto *p) continue; /* Hard restart */ - log(L_INFO "%s: Restarting interface %s (%I/%d) in area %R", - p->p.name, ifa->ifname, a->prefix, a->pxlen, s.oa->areaid); + log(L_INFO "%s: Restarting interface %s (%N) in area %R", + p->p.name, ifa->ifname, &a->prefix, s.oa->areaid); ospf_iface_shutdown(ifa); ospf_iface_remove(ifa); } @@ -1209,6 +1214,9 @@ ospf_reconfigure_ifaces3(struct ospf_proto *p) WALK_LIST(a, iface->addrs) { + if (a->prefix.type != NET_IP6) + continue; + if (a->flags & IA_SECONDARY) continue; @@ -1340,7 +1348,7 @@ ospf_iface_info(struct ospf_iface *ifa) else if (ifa->addr->flags & IA_PEER) cli_msg(-1015, "Interface %s (peer %I)", ifa->ifname, ifa->addr->opposite); else - cli_msg(-1015, "Interface %s (%I/%d)", ifa->ifname, ifa->addr->prefix, ifa->addr->pxlen); + cli_msg(-1015, "Interface %s (%N)", ifa->ifname, &ifa->addr->prefix); cli_msg(-1015, "\tType: %s%s", ospf_it[ifa->type], more); cli_msg(-1015, "\tArea: %R (%u)", ifa->oa->areaid, ifa->oa->areaid); diff --git a/proto/ospf/lsalib.c b/proto/ospf/lsalib.c index cb7b186a..b88a114d 100644 --- a/proto/ospf/lsalib.c +++ b/proto/ospf/lsalib.c @@ -280,21 +280,19 @@ lsa_walk_rt(struct ospf_lsa_rt_walk *rt) void -lsa_parse_sum_net(struct top_hash_entry *en, int ospf2, ip_addr *ip, int *pxlen, u8 *pxopts, u32 *metric) +lsa_parse_sum_net(struct top_hash_entry *en, int ospf2, net_addr *net, u8 *pxopts, u32 *metric) { if (ospf2) { struct ospf_lsa_sum2 *ls = en->lsa_body; - *ip = ipa_from_u32(en->lsa.id & ls->netmask); - *pxlen = u32_masklen(ls->netmask); + net_fill_ip4(net, ip4_from_u32(en->lsa.id & ls->netmask), u32_masklen(ls->netmask)); *pxopts = 0; *metric = ls->metric & LSA_METRIC_MASK; } else { struct ospf_lsa_sum3_net *ls = en->lsa_body; - u16 rest; - lsa_get_ipv6_prefix(ls->prefix, ip, pxlen, pxopts, &rest); + ospf_get_ipv6_prefix(ls->prefix, net, pxopts, NULL); *metric = ls->metric & LSA_METRIC_MASK; } } @@ -324,8 +322,9 @@ lsa_parse_ext(struct top_hash_entry *en, int ospf2, struct ospf_lsa_ext_local *r if (ospf2) { struct ospf_lsa_ext2 *ext = en->lsa_body; - rt->ip = ipa_from_u32(en->lsa.id & ext->netmask); - rt->pxlen = u32_masklen(ext->netmask); + net_fill_ip4(&rt->net, + ip4_from_u32(en->lsa.id & ext->netmask), + u32_masklen(ext->netmask)); rt->pxopts = 0; rt->metric = ext->metric & LSA_METRIC_MASK; rt->ebit = ext->metric & LSA_EXT2_EBIT; @@ -339,14 +338,13 @@ lsa_parse_ext(struct top_hash_entry *en, int ospf2, struct ospf_lsa_ext_local *r else { struct ospf_lsa_ext3 *ext = en->lsa_body; - u16 rest; - u32 *buf = lsa_get_ipv6_prefix(ext->rest, &rt->ip, &rt->pxlen, &rt->pxopts, &rest); + u32 *buf = ospf_get_ipv6_prefix(ext->rest, &rt->net, &rt->pxopts, NULL); rt->metric = ext->metric & LSA_METRIC_MASK; rt->ebit = ext->metric & LSA_EXT3_EBIT; rt->fbit = ext->metric & LSA_EXT3_FBIT; if (rt->fbit) - buf = lsa_get_ipv6_addr(buf, &rt->fwaddr); + buf = ospf_get_ipv6_addr(buf, &rt->fwaddr); else rt->fwaddr = IPA_NONE; @@ -452,7 +450,7 @@ lsa_validate_sum3_net(struct ospf_lsa_header *lsa, struct ospf_lsa_sum3_net *bod return 0; u8 pxl = pxlen(body->prefix); - if (pxl > MAX_PREFIX_LENGTH) + if (pxl > IP6_MAX_PREFIX_LENGTH) return 0; if (lsa->length != (HDRLEN + sizeof(struct ospf_lsa_sum3_net) + @@ -491,11 +489,11 @@ lsa_validate_ext3(struct ospf_lsa_header *lsa, struct ospf_lsa_ext3 *body) return 0; u8 pxl = pxlen(body->rest); - if (pxl > MAX_PREFIX_LENGTH) + if (pxl > IP6_MAX_PREFIX_LENGTH) return 0; int len = IPV6_PREFIX_SPACE(pxl); - if (body->metric & LSA_EXT3_FBIT) // forwardinf address + if (body->metric & LSA_EXT3_FBIT) // forwarding address len += 16; if (body->metric & LSA_EXT3_TBIT) // route tag len += 4; @@ -520,7 +518,7 @@ lsa_validate_pxlist(struct ospf_lsa_header *lsa, u32 pxcount, uint offset, u8 *p return 0; u8 pxl = pxlen((u32 *) (pbuf + offset)); - if (pxl > MAX_PREFIX_LENGTH) + if (pxl > IP6_MAX_PREFIX_LENGTH) return 0; offset += IPV6_PREFIX_SPACE(pxl); @@ -554,8 +552,8 @@ lsa_validate_prefix(struct ospf_lsa_header *lsa, struct ospf_lsa_prefix *body) /** * lsa_validate - check whether given LSA is valid * @lsa: LSA header - * @lsa_type: one of %LSA_T_xxx - * @ospf2: %true means OSPF version 2, %false means OSPF version 3 + * @lsa_type: internal LSA type (%LSA_T_xxx) + * @ospf2: %true for OSPFv2, %false for OSPFv3 * @body: pointer to LSA body * * Checks internal structure of given LSA body (minimal length, diff --git a/proto/ospf/lsalib.h b/proto/ospf/lsalib.h index 638b3525..c93f0295 100644 --- a/proto/ospf/lsalib.h +++ b/proto/ospf/lsalib.h @@ -41,7 +41,7 @@ void lsa_get_type_domain_(u32 itype, struct ospf_iface *ifa, u32 *otype, u32 *do static inline void lsa_get_type_domain(struct ospf_lsa_header *lsa, struct ospf_iface *ifa, u32 *otype, u32 *domain) { lsa_get_type_domain_(lsa->type_raw, ifa, otype, domain); } -static inline u32 lsa_get_etype(struct ospf_lsa_header *h, struct ospf_proto *p UNUSED4 UNUSED6) +static inline u32 lsa_get_etype(struct ospf_lsa_header *h, struct ospf_proto *p) { return ospf_is_v2(p) ? (h->type_raw & LSA_T_V2_MASK) : h->type_raw; } @@ -55,7 +55,7 @@ u16 lsa_verify_checksum(const void *lsa_n, int lsa_len); int lsa_comp(struct ospf_lsa_header *l1, struct ospf_lsa_header *l2); void lsa_walk_rt_init(struct ospf_proto *po, struct top_hash_entry *act, struct ospf_lsa_rt_walk *rt); int lsa_walk_rt(struct ospf_lsa_rt_walk *rt); -void lsa_parse_sum_net(struct top_hash_entry *en, int ospf2, ip_addr *ip, int *pxlen, u8 *pxopts, u32 *metric); +void lsa_parse_sum_net(struct top_hash_entry *en, int ospf2, net_addr *net, u8 *pxopts, u32 *metric); void lsa_parse_sum_rt(struct top_hash_entry *en, int ospf2, u32 *drid, u32 *metric, u32 *options); void lsa_parse_ext(struct top_hash_entry *en, int ospf2, struct ospf_lsa_ext_local *rt); int lsa_validate(struct ospf_lsa_header *lsa, u32 lsa_type, int ospf2, void *body); diff --git a/proto/ospf/neighbor.c b/proto/ospf/neighbor.c index 9fe3c028..b68ba6f4 100644 --- a/proto/ospf/neighbor.c +++ b/proto/ospf/neighbor.c @@ -359,7 +359,7 @@ can_do_adj(struct ospf_neighbor *n) } -static inline u32 neigh_get_id(struct ospf_proto *p UNUSED4 UNUSED6, struct ospf_neighbor *n) +static inline u32 neigh_get_id(struct ospf_proto *p, struct ospf_neighbor *n) { return ospf_is_v2(p) ? ipa_to_u32(n->ip) : n->rid; } static struct ospf_neighbor * @@ -507,13 +507,14 @@ ospf_dr_election(struct ospf_iface *ifa) u32 old_drid = ifa->drid; u32 old_bdrid = ifa->bdrid; + ip_addr none = ospf_is_v2(p) ? IPA_NONE4 : IPA_NONE6; ifa->drid = ndr ? ndr->rid : 0; - ifa->drip = ndr ? ndr->ip : IPA_NONE; + ifa->drip = ndr ? ndr->ip : none; ifa->dr_iface_id = ndr ? ndr->iface_id : 0; ifa->bdrid = nbdr ? nbdr->rid : 0; - ifa->bdrip = nbdr ? nbdr->ip : IPA_NONE; + ifa->bdrip = nbdr ? nbdr->ip : none; DBG("DR=%R, BDR=%R\n", ifa->drid, ifa->bdrid); diff --git a/proto/ospf/ospf.c b/proto/ospf/ospf.c index d5d5d354..daf76ff2 100644 --- a/proto/ospf/ospf.c +++ b/proto/ospf/ospf.c @@ -102,18 +102,11 @@ static int ospf_import_control(struct proto *P, rte **new, ea_list **attrs, struct linpool *pool); static struct ea_list *ospf_make_tmp_attrs(struct rte *rt, struct linpool *pool); static void ospf_store_tmp_attrs(struct rte *rt, struct ea_list *attrs); -static int ospf_reload_routes(struct proto *P); +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 void ospf_disp(timer *timer); -static void -ospf_area_initfib(struct fib_node *fn) -{ - struct area_net *an = (struct area_net *) fn; - an->hidden = 0; - an->active = 0; -} static void add_area_nets(struct ospf_area *oa, struct ospf_area_config *ac) @@ -122,18 +115,20 @@ add_area_nets(struct ospf_area *oa, struct ospf_area_config *ac) struct area_net_config *anc; struct area_net *an; - fib_init(&oa->net_fib, p->p.pool, sizeof(struct area_net), 0, ospf_area_initfib); - fib_init(&oa->enet_fib, p->p.pool, sizeof(struct area_net), 0, ospf_area_initfib); + fib_init(&oa->net_fib, p->p.pool, ospf_is_v2(p) ? NET_IP4 : NET_IP6, + sizeof(struct area_net), OFFSETOF(struct area_net, fn), 0, NULL); + fib_init(&oa->enet_fib, p->p.pool, ospf_is_v2(p) ? NET_IP4 : NET_IP6, + sizeof(struct area_net), OFFSETOF(struct area_net, fn), 0, NULL); WALK_LIST(anc, ac->net_list) { - an = (struct area_net *) fib_get(&oa->net_fib, &anc->px.addr, anc->px.len); + an = fib_get(&oa->net_fib, &anc->prefix); an->hidden = anc->hidden; } WALK_LIST(anc, ac->enet_list) { - an = (struct area_net *) fib_get(&oa->enet_fib, &anc->px.addr, anc->px.len); + an = fib_get(&oa->enet_fib, &anc->prefix); an->hidden = anc->hidden; an->tag = anc->tag; } @@ -154,7 +149,7 @@ ospf_area_add(struct ospf_proto *p, struct ospf_area_config *ac) oa->areaid = ac->areaid; oa->rt = NULL; oa->po = p; - fib_init(&oa->rtr, p->p.pool, sizeof(ort), 0, ospf_rt_initort); + fib_init(&oa->rtr, p->p.pool, NET_IP4, sizeof(ort), OFFSETOF(ort, fn), 0, NULL); add_area_nets(oa, ac); if (oa->areaid == 0) @@ -240,10 +235,13 @@ ospf_start(struct proto *P) p->lsab_size = 256; p->lsab_used = 0; p->lsab = mb_alloc(P->pool, p->lsab_size); - p->nhpool = lp_new(P->pool, 12*sizeof(struct mpnh)); + p->nhpool = lp_new(P->pool, 12*sizeof(struct nexthop)); init_list(&(p->iface_list)); init_list(&(p->area_list)); - fib_init(&p->rtf, P->pool, sizeof(ort), 0, ospf_rt_initort); + fib_init(&p->rtf, P->pool, p->ospf2 ? NET_IP4 : NET_IP6, + sizeof(ort), OFFSETOF(ort, fn), 0, NULL); + if (ospf_is_v3(p)) + idm_init(&p->idm, P->pool, 16); p->areano = 0; p->gr = ospf_top_new(p, P->pool); s_init_list(&(p->lsal)); @@ -299,15 +297,16 @@ ospf_dump(struct proto *P) } static struct proto * -ospf_init(struct proto_config *c) +ospf_init(struct proto_config *CF) { - struct ospf_config *oc = (struct ospf_config *) c; - struct proto *P = proto_new(c, sizeof(struct ospf_proto)); + struct ospf_config *cf = (struct ospf_config *) CF; + struct proto *P = proto_new(CF); + + P->main_channel = proto_add_channel(P, proto_cf_main_channel(CF)); - P->accept_ra_types = RA_OPTIMAL; P->rt_notify = ospf_rt_notify; P->if_notify = ospf_if_notify; - P->ifa_notify = oc->ospf2 ? ospf_ifa_notify2 : ospf_ifa_notify3; + P->ifa_notify = cf->ospf2 ? ospf_ifa_notify2 : ospf_ifa_notify3; P->import_control = ospf_import_control; P->reload_routes = ospf_reload_routes; P->make_tmp_attrs = ospf_make_tmp_attrs; @@ -391,17 +390,16 @@ ospf_schedule_rtcalc(struct ospf_proto *p) p->calcrt = 1; } -static int -ospf_reload_routes(struct proto *P) +static void +ospf_reload_routes(struct channel *C) { - struct ospf_proto *p = (struct ospf_proto *) P; + struct ospf_proto *p = (struct ospf_proto *) C->proto; - if (p->calcrt != 2) - OSPF_TRACE(D_EVENTS, "Scheduling routing table calculation with route reload"); + if (p->calcrt == 2) + return; + OSPF_TRACE(D_EVENTS, "Scheduling routing table calculation with route reload"); p->calcrt = 2; - - return 1; } @@ -506,9 +504,9 @@ ospf_shutdown(struct proto *P) ospf_iface_shutdown(ifa); /* Cleanup locked rta entries */ - FIB_WALK(&p->rtf, nftmp) + FIB_WALK(&p->rtf, ort, nf) { - rta_free(((ort *) nftmp)->old_rta); + rta_free(nf->old_rta); } FIB_WALK_END; @@ -639,17 +637,20 @@ ospf_area_reconfigure(struct ospf_area *oa, struct ospf_area_config *nac) * nonbroadcast network, cost of interface, etc. */ static int -ospf_reconfigure(struct proto *P, struct proto_config *c) +ospf_reconfigure(struct proto *P, struct proto_config *CF) { struct ospf_proto *p = (struct ospf_proto *) P; struct ospf_config *old = (struct ospf_config *) (P->cf); - struct ospf_config *new = (struct ospf_config *) c; + struct ospf_config *new = (struct ospf_config *) CF; struct ospf_area_config *nac; struct ospf_area *oa, *oax; struct ospf_iface *ifa, *ifx; struct ospf_iface_patt *ip; - if (proto_get_router_id(c) != p->router_id) + if (proto_get_router_id(CF) != p->router_id) + return 0; + + if (p->ospf2 != new->ospf2) return 0; if (p->rfc1583 != new->rfc1583) @@ -658,6 +659,9 @@ ospf_reconfigure(struct proto *P, struct proto_config *c) if (old->abr != new->abr) return 0; + if (!proto_configure_channel(P, &P->main_channel, proto_cf_main_channel(CF))) + return 0; + p->stub_router = new->stub_router; p->merge_external = new->merge_external; p->asbr = new->asbr; @@ -746,7 +750,6 @@ ospf_sh(struct proto *P) struct ospf_iface *ifa; struct ospf_neighbor *n; int ifano, nno, adjno, firstfib; - struct area_net *anet; if (p->p.proto_state != PS_UP) { @@ -795,29 +798,27 @@ ospf_sh(struct proto *P) cli_msg(-1014, "\t\tNumber of adjacent neighbors:\t%u", adjno); firstfib = 1; - FIB_WALK(&oa->net_fib, nftmp) + FIB_WALK(&oa->net_fib, struct area_net, anet) { - anet = (struct area_net *) nftmp; if(firstfib) { cli_msg(-1014, "\t\tArea networks:"); firstfib = 0; } - cli_msg(-1014, "\t\t\t%1I/%u\t%s\t%s", anet->fn.prefix, anet->fn.pxlen, + cli_msg(-1014, "\t\t\t%1N\t%s\t%s", anet->fn.addr, anet->hidden ? "Hidden" : "Advertise", anet->active ? "Active" : ""); } FIB_WALK_END; firstfib = 1; - FIB_WALK(&oa->enet_fib, nftmp) + FIB_WALK(&oa->enet_fib, struct area_net, anet) { - anet = (struct area_net *) nftmp; if(firstfib) { cli_msg(-1014, "\t\tArea external networks:"); firstfib = 0; } - cli_msg(-1014, "\t\t\t%1I/%u\t%s\t%s", anet->fn.prefix, anet->fn.pxlen, + cli_msg(-1014, "\t\t\t%1N\t%s\t%s", anet->fn.addr, anet->hidden ? "Hidden" : "Advertise", anet->active ? "Active" : ""); } FIB_WALK_END; @@ -1074,13 +1075,12 @@ show_lsa_network(struct top_hash_entry *he, int ospf2) static inline void show_lsa_sum_net(struct top_hash_entry *he, int ospf2) { - ip_addr ip; - int pxlen; + net_addr net; u8 pxopts; u32 metric; - lsa_parse_sum_net(he, ospf2, &ip, &pxlen, &pxopts, &metric); - cli_msg(-1016, "\t\txnetwork %I/%d metric %u", ip, pxlen, metric); + lsa_parse_sum_net(he, ospf2, &net, &pxopts, &metric); + cli_msg(-1016, "\t\txnetwork %N metric %u", &net, metric); } static inline void @@ -1099,7 +1099,7 @@ static inline void show_lsa_external(struct top_hash_entry *he, int ospf2) { struct ospf_lsa_ext_local rt; - char str_via[STD_ADDRESS_P_LENGTH + 8] = ""; + char str_via[IPA_MAX_TEXT_LENGTH + 8] = ""; char str_tag[16] = ""; if (he->lsa_type == LSA_T_EXT) @@ -1113,19 +1113,15 @@ show_lsa_external(struct top_hash_entry *he, int ospf2) if (rt.tag) bsprintf(str_tag, " tag %08x", rt.tag); - cli_msg(-1016, "\t\t%s %I/%d metric%s %u%s%s", + cli_msg(-1016, "\t\t%s %N metric%s %u%s%s", (he->lsa_type == LSA_T_NSSA) ? "nssa-ext" : "external", - rt.ip, rt.pxlen, rt.ebit ? "2" : "", rt.metric, str_via, str_tag); + &rt.net, rt.ebit ? "2" : "", rt.metric, str_via, str_tag); } static inline void show_lsa_prefix(struct top_hash_entry *he, struct top_hash_entry *cnode) { struct ospf_lsa_prefix *px = he->lsa_body; - ip_addr pxa; - int pxlen; - u8 pxopts; - u16 metric; u32 *buf; int i; @@ -1141,14 +1137,18 @@ show_lsa_prefix(struct top_hash_entry *he, struct top_hash_entry *cnode) buf = px->rest; for (i = 0; i < px->pxcount; i++) - { - buf = lsa_get_ipv6_prefix(buf, &pxa, &pxlen, &pxopts, &metric); + { + net_addr net; + u8 pxopts; + u16 metric; - if (px->ref_type == LSA_T_RT) - cli_msg(-1016, "\t\tstubnet %I/%d metric %u", pxa, pxlen, metric); - else - cli_msg(-1016, "\t\taddress %I/%d", pxa, pxlen); - } + buf = ospf_get_ipv6_prefix(buf, &net, &pxopts, &metric); + + if (px->ref_type == LSA_T_RT) + cli_msg(-1016, "\t\tstubnet %N metric %u", &net, metric); + else + cli_msg(-1016, "\t\taddress %N", &net); + } } void @@ -1468,6 +1468,8 @@ struct protocol proto_ospf = { .template = "ospf%d", .attr_class = EAP_OSPF, .preference = DEF_PREF_OSPF, + .channel_mask = NB_IP, + .proto_size = sizeof(struct ospf_proto), .config_size = sizeof(struct ospf_config), .init = ospf_init, .dump = ospf_dump, diff --git a/proto/ospf/ospf.h b/proto/ospf/ospf.h index 81c610d5..e3eae2b5 100644 --- a/proto/ospf/ospf.h +++ b/proto/ospf/ospf.h @@ -14,11 +14,11 @@ #include "nest/bird.h" #include "lib/checksum.h" -#include "lib/ip.h" +#include "lib/idm.h" #include "lib/lists.h" #include "lib/slists.h" #include "lib/socket.h" -#include "lib/timer.h" +#include "sysdep/unix/timer.h" #include "lib/resource.h" #include "nest/protocol.h" #include "nest/iface.h" @@ -37,14 +37,6 @@ #endif -#ifdef IPV6 -#define OSPF_IS_V2 0 -#else -#define OSPF_IS_V2 1 -#endif - -// FIXME: MAX_PREFIX_LENGTH - #define OSPF_TRACE(flags, msg, args...) \ do { if ((p->p.debug & flags) || OSPF_FORCE_DEBUG) \ log(L_TRACE "%s: " msg, p->p.name , ## args ); } while(0) @@ -87,7 +79,6 @@ #define OSPF_VLINK_ID_OFFSET 0x80000000 - struct ospf_config { struct proto_config c; @@ -125,24 +116,24 @@ struct ospf_area_config struct area_net_config { node n; - struct prefix px; + net_addr prefix; u32 tag; u8 hidden; }; struct area_net { - struct fib_node fn; u32 metric; /* With possible LSA_EXT3_EBIT for NSSA area nets */ u32 tag; u8 hidden; u8 active; + struct fib_node fn; }; struct ospf_stubnet_config { node n; - struct prefix px; + net_addr prefix; u32 cost; u8 hidden; u8 summary; @@ -189,7 +180,6 @@ struct ospf_iface_patt u8 ptp_netmask; /* bool + 2 for unspecified */ u8 ttl_security; /* bool + 2 for TX only */ u8 bfd; - u8 bsd_secondary; list *passwords; }; @@ -220,6 +210,7 @@ struct ospf_proto int areano; /* Number of area I belong to */ int padj; /* Number of neighbors in Exchange or Loading state */ struct fib rtf; /* Routing table */ + struct idm idm; /* OSPFv3 LSA ID map */ byte ospf2; /* OSPF v2 or v3 */ byte rfc1583; /* RFC1583 compatibility */ byte stub_router; /* Do not forward transit traffic */ @@ -682,8 +673,8 @@ struct ospf_lsa_ext3 struct ospf_lsa_ext_local { - ip_addr ip, fwaddr; - int pxlen; + net_addr net; + ip_addr fwaddr; u32 metric, ebit, fbit, tag, propagate; u8 pxopts; }; @@ -721,73 +712,80 @@ lsa_net_count(struct ospf_lsa_header *lsa) /* In ospf_area->rtr we store paths to routers, but we use RID (and not IP address) as index, so we need to encapsulate RID to IP address */ -#define ipa_from_rid(x) ipa_from_u32(x) -#define ipa_to_rid(x) ipa_to_u32(x) +#define net_from_rid(x) NET_ADDR_IP4(ip4_from_u32(x), IP4_MAX_PREFIX_LENGTH) +#define rid_from_net(x) ip4_to_u32(((net_addr_ip4 *) x)->prefix) #define IPV6_PREFIX_SPACE(x) ((((x) + 63) / 32) * 4) #define IPV6_PREFIX_WORDS(x) (((x) + 63) / 32) -/* FIXME: these four functions should be significantly redesigned w.r.t. integration, +/* FIXME: these functions should be significantly redesigned w.r.t. integration, also should be named as ospf3_* instead of *_ipv6_* */ +static inline int +ospf_valid_prefix(net_addr *n) +{ + /* In OSPFv2, prefix is stored as netmask; ip4_masklen() returns 255 for invalid one */ + return n->pxlen <= IP6_MAX_PREFIX_LENGTH; +} + static inline u32 * -lsa_get_ipv6_prefix(u32 *buf, ip_addr *addr, int *pxlen, u8 *pxopts, u16 *rest) +ospf_get_ipv6_prefix(u32 *buf, net_addr *N, u8 *pxopts, u16 *rest) { - u8 pxl = (*buf >> 24); - *pxopts = (*buf >> 16); - *rest = *buf; - *pxlen = pxl; + net_addr_ip6 *net = (void *) N; + u8 pxlen = (*buf >> 24); + *pxopts = (*buf >> 16) & 0xff; + if (rest) *rest = *buf & 0xffff; buf++; - *addr = IPA_NONE; + *net = NET_ADDR_IP6(IP6_NONE, pxlen); -#ifdef IPV6 - if (pxl > 0) - _I0(*addr) = *buf++; - if (pxl > 32) - _I1(*addr) = *buf++; - if (pxl > 64) - _I2(*addr) = *buf++; - if (pxl > 96) - _I3(*addr) = *buf++; + if (pxlen > 0) + _I0(net->prefix) = *buf++; + if (pxlen > 32) + _I1(net->prefix) = *buf++; + if (pxlen > 64) + _I2(net->prefix) = *buf++; + if (pxlen > 96) + _I3(net->prefix) = *buf++; /* Clean up remaining bits */ - if (pxl < 128) - addr->addr[pxl / 32] &= u32_mkmask(pxl % 32); -#endif + if (pxlen < 128) + net->prefix.addr[pxlen / 32] &= u32_mkmask(pxlen % 32); return buf; } static inline u32 * -lsa_get_ipv6_addr(u32 *buf, ip_addr *addr) +ospf_get_ipv6_addr(u32 *buf, ip_addr *addr) { - *addr = *(ip_addr *) buf; + *addr = ipa_from_ip6(*(ip6_addr *) buf); return buf + 4; } static inline u32 * -put_ipv6_prefix(u32 *buf, ip_addr addr UNUSED4, u8 pxlen UNUSED4, u8 pxopts UNUSED4, u16 lh UNUSED4) +ospf_put_ipv6_prefix(u32 *buf, net_addr *N, u8 pxopts, u16 rest) { -#ifdef IPV6 - *buf++ = ((pxlen << 24) | (pxopts << 16) | lh); + net_addr_ip6 *net = (void *) N; + u32 pxlen = net->pxlen; + + *buf++ = ((pxlen << 24) | (pxopts << 16) | rest); if (pxlen > 0) - *buf++ = _I0(addr); + *buf++ = _I0(net->prefix); if (pxlen > 32) - *buf++ = _I1(addr); + *buf++ = _I1(net->prefix); if (pxlen > 64) - *buf++ = _I2(addr); + *buf++ = _I2(net->prefix); if (pxlen > 96) - *buf++ = _I3(addr); -#endif + *buf++ = _I3(net->prefix); + return buf; } static inline u32 * -put_ipv6_addr(u32 *buf, ip_addr addr) +ospf_put_ipv6_addr(u32 *buf, ip_addr addr) { - *(ip_addr *) buf = addr; + *(ip6_addr *) buf = ipa_to_ip6(addr); return buf + 4; } @@ -831,17 +829,13 @@ static inline void ospf_notify_net_lsa(struct ospf_iface *ifa) static inline void ospf_notify_link_lsa(struct ospf_iface *ifa) { ifa->update_link_lsa = 1; } - -#define ospf_is_v2(X) OSPF_IS_V2 -#define ospf_is_v3(X) (!OSPF_IS_V2) -/* static inline int ospf_is_v2(struct ospf_proto *p) { return p->ospf2; } static inline int ospf_is_v3(struct ospf_proto *p) { return ! p->ospf2; } -*/ -static inline int ospf_get_version(struct ospf_proto *p UNUSED4 UNUSED6) + +static inline int ospf_get_version(struct ospf_proto *p) { return ospf_is_v2(p) ? 2 : 3; } struct ospf_area *ospf_find_area(struct ospf_proto *p, u32 aid); @@ -925,7 +919,7 @@ static inline void ospf_send_to_des(struct ospf_iface *ifa) #define SKIP(DSC) do { err_dsc = DSC; goto skip; } while(0) #endif -static inline uint ospf_pkt_hdrlen(struct ospf_proto *p UNUSED4 UNUSED6) +static inline uint ospf_pkt_hdrlen(struct ospf_proto *p) { return ospf_is_v2(p) ? (sizeof(struct ospf_packet) + sizeof(union ospf_auth)) : sizeof(struct ospf_packet); } static inline void * ospf_tx_buffer(struct ospf_iface *ifa) diff --git a/proto/ospf/packet.c b/proto/ospf/packet.c index 6b6a97a4..b84780d3 100644 --- a/proto/ospf/packet.c +++ b/proto/ospf/packet.c @@ -203,7 +203,7 @@ drop: /** * ospf_rx_hook * @sk: socket we received the packet. - * @len: size of the packet + * @len: length of the packet * * This is the entry point for messages from neighbors. Many checks (like * authentication, checksums, size) are done before the packet is passed to @@ -231,7 +231,7 @@ ospf_rx_hook(sock *sk, uint len) return 1; int src_local, dst_local, dst_mcast; - src_local = ipa_in_net(sk->faddr, ifa->addr->prefix, ifa->addr->pxlen); + src_local = ipa_in_netX(sk->faddr, &ifa->addr->prefix); dst_local = ipa_equal(sk->laddr, ifa->addr->ip); dst_mcast = ipa_equal(sk->laddr, ifa->all_routers) || ipa_equal(sk->laddr, ifa->des_routers); @@ -491,8 +491,8 @@ ospf_send_to_agt(struct ospf_iface *ifa, u8 state) void ospf_send_to_bdr(struct ospf_iface *ifa) { - if (ipa_nonzero(ifa->drip)) + if (ipa_nonzero2(ifa->drip)) ospf_send_to(ifa, ifa->drip); - if (ipa_nonzero(ifa->bdrip)) + if (ipa_nonzero2(ifa->bdrip)) ospf_send_to(ifa, ifa->bdrip); } diff --git a/proto/ospf/rt.c b/proto/ospf/rt.c index 368e3d05..df9eb75b 100644 --- a/proto/ospf/rt.c +++ b/proto/ospf/rt.c @@ -21,17 +21,8 @@ static inline void reset_ri(ort *ort) bzero(&ort->n, sizeof(orta)); } -void -ospf_rt_initort(struct fib_node *fn) -{ - ort *ri = (ort *) fn; - reset_ri(ri); - ri->old_rta = NULL; - ri->fn.flags = 0; -} - static inline int -nh_is_vlink(struct mpnh *nhs) +nh_is_vlink(struct nexthop *nhs) { return !nhs->iface; } @@ -42,20 +33,19 @@ unresolved_vlink(ort *ort) return ort->n.nhs && nh_is_vlink(ort->n.nhs); } -static inline struct mpnh * +static inline struct nexthop * new_nexthop(struct ospf_proto *p, ip_addr gw, struct iface *iface, byte weight) { - struct mpnh *nh = lp_alloc(p->nhpool, sizeof(struct mpnh)); + struct nexthop *nh = lp_allocz(p->nhpool, sizeof(struct nexthop)); nh->gw = gw; nh->iface = iface; - nh->next = NULL; nh->weight = weight; return nh; } /* Returns true if there are device nexthops in n */ static inline int -has_device_nexthops(const struct mpnh *n) +has_device_nexthops(const struct nexthop *n) { for (; n; n = n->next) if (ipa_zero(n->gw)) @@ -65,13 +55,13 @@ has_device_nexthops(const struct mpnh *n) } /* Replace device nexthops with nexthops to gw */ -static struct mpnh * -fix_device_nexthops(struct ospf_proto *p, const struct mpnh *n, ip_addr gw) +static struct nexthop * +fix_device_nexthops(struct ospf_proto *p, const struct nexthop *n, ip_addr gw) { - struct mpnh *root1 = NULL; - struct mpnh *root2 = NULL; - struct mpnh **nn1 = &root1; - struct mpnh **nn2 = &root2; + struct nexthop *root1 = NULL; + struct nexthop *root2 = NULL; + struct nexthop **nn1 = &root1; + struct nexthop **nn2 = &root2; if (!p->ecmp) return new_nexthop(p, gw, n->iface, n->weight); @@ -82,7 +72,7 @@ fix_device_nexthops(struct ospf_proto *p, const struct mpnh *n, ip_addr gw) for (; n; n = n->next) { - struct mpnh *nn = new_nexthop(p, ipa_zero(n->gw) ? gw : n->gw, n->iface, n->weight); + struct nexthop *nn = new_nexthop(p, ipa_zero(n->gw) ? gw : n->gw, n->iface, n->weight); if (ipa_zero(n->gw)) { @@ -96,7 +86,7 @@ fix_device_nexthops(struct ospf_proto *p, const struct mpnh *n, ip_addr gw) } } - return mpnh_merge(root1, root2, 1, 1, p->ecmp, p->nhpool); + return nexthop_merge(root1, root2, 1, 1, p->ecmp, p->nhpool); } @@ -292,7 +282,7 @@ ort_merge(struct ospf_proto *p, ort *o, const orta *new) if (old->nhs != new->nhs) { - old->nhs = mpnh_merge(old->nhs, new->nhs, old->nhs_reuse, new->nhs_reuse, + old->nhs = nexthop_merge(old->nhs, new->nhs, old->nhs_reuse, new->nhs_reuse, p->ecmp, p->nhpool); old->nhs_reuse = 1; } @@ -308,7 +298,7 @@ ort_merge_ext(struct ospf_proto *p, ort *o, const orta *new) if (old->nhs != new->nhs) { - old->nhs = mpnh_merge(old->nhs, new->nhs, old->nhs_reuse, new->nhs_reuse, + old->nhs = nexthop_merge(old->nhs, new->nhs, old->nhs_reuse, new->nhs_reuse, p->ecmp, p->nhpool); old->nhs_reuse = 1; } @@ -334,9 +324,9 @@ ort_merge_ext(struct ospf_proto *p, ort *o, const orta *new) static inline void -ri_install_net(struct ospf_proto *p, ip_addr prefix, int pxlen, const orta *new) +ri_install_net(struct ospf_proto *p, net_addr *net, const orta *new) { - ort *old = (ort *) fib_get(&p->rtf, &prefix, pxlen); + ort *old = fib_get(&p->rtf, net); int cmp = orta_compare(p, new, &old->n); if (cmp > 0) @@ -348,8 +338,8 @@ ri_install_net(struct ospf_proto *p, ip_addr prefix, int pxlen, const orta *new) static inline void ri_install_rt(struct ospf_area *oa, u32 rid, const orta *new) { - ip_addr addr = ipa_from_rid(rid); - ort *old = (ort *) fib_get(&oa->rtr, &addr, MAX_PREFIX_LENGTH); + net_addr_ip4 nrid = net_from_rid(rid); + ort *old = fib_get(&oa->rtr, (net_addr *) &nrid); int cmp = orta_compare(oa->po, new, &old->n); if (cmp > 0) @@ -359,17 +349,19 @@ ri_install_rt(struct ospf_area *oa, u32 rid, const orta *new) } static inline void -ri_install_asbr(struct ospf_proto *p, ip_addr *addr, const orta *new) +ri_install_asbr(struct ospf_proto *p, u32 rid, const orta *new) { - ort *old = (ort *) fib_get(&p->backbone->rtr, addr, MAX_PREFIX_LENGTH); + net_addr_ip4 nrid = net_from_rid(rid); + ort *old = fib_get(&p->backbone->rtr, (net_addr *) &nrid); + if (orta_compare_asbr(p, new, &old->n) > 0) ort_replace(old, new); } static inline void -ri_install_ext(struct ospf_proto *p, ip_addr prefix, int pxlen, const orta *new) +ri_install_ext(struct ospf_proto *p, net_addr *net, const orta *new) { - ort *old = (ort *) fib_get(&p->rtf, &prefix, pxlen); + ort *old = fib_get(&p->rtf, net); int cmp = orta_compare_ext(p, new, &old->n); if (cmp > 0) @@ -404,7 +396,7 @@ px_pos_to_ifa(struct ospf_area *oa, int pos) static void -add_network(struct ospf_area *oa, ip_addr px, int pxlen, int metric, struct top_hash_entry *en, int pos) +add_network(struct ospf_area *oa, net_addr *net, int metric, struct top_hash_entry *en, int pos) { struct ospf_proto *p = oa->po; @@ -419,7 +411,7 @@ add_network(struct ospf_area *oa, ip_addr px, int pxlen, int metric, struct top_ .nhs = en->nhs }; - if (pxlen < 0 || pxlen > MAX_PREFIX_LENGTH) + if (!ospf_valid_prefix(net)) { log(L_WARN "%s: Invalid prefix in LSA (Type: %04x, Id: %R, Rt: %R)", p->p.name, en->lsa_type, en->lsa.id, en->lsa.rt); @@ -440,7 +432,7 @@ add_network(struct ospf_area *oa, ip_addr px, int pxlen, int metric, struct top_ nf.nhs = ifa ? new_nexthop(p, IPA_NONE, ifa->iface, ifa->ecmp_weight) : NULL; } - ri_install_net(p, px, pxlen, &nf); + ri_install_net(p, net, &nf); } @@ -451,8 +443,7 @@ spfa_process_rt(struct ospf_proto *p, struct ospf_area *oa, struct top_hash_entr struct ospf_lsa_rt *rt = act->lsa_body; struct ospf_lsa_rt_walk rtl; struct top_hash_entry *tmp; - ip_addr prefix; - int pxlen, i; + int i; if (rt->options & OPT_RT_V) oa->trcap = 1; @@ -502,9 +493,10 @@ spfa_process_rt(struct ospf_proto *p, struct ospf_area *oa, struct top_hash_entr * the same result by handing them here because add_network() * will keep the best (not the first) found route. */ - prefix = ipa_from_u32(rtl.id & rtl.data); - pxlen = u32_masklen(rtl.data); - add_network(oa, prefix, pxlen, act->dist + rtl.metric, act, i); + net_addr_ip4 net = + NET_ADDR_IP4(ip4_from_u32(rtl.id & rtl.data), u32_masklen(rtl.data)); + + add_network(oa, (net_addr *) &net, act->dist + rtl.metric, act, i); break; case LSART_NET: @@ -526,14 +518,14 @@ spfa_process_net(struct ospf_proto *p, struct ospf_area *oa, struct top_hash_ent { struct ospf_lsa_net *ln = act->lsa_body; struct top_hash_entry *tmp; - ip_addr prefix; - int pxlen, i, cnt; + int i, cnt; if (ospf_is_v2(p)) { - prefix = ipa_from_u32(act->lsa.id & ln->optx); - pxlen = u32_masklen(ln->optx); - add_network(oa, prefix, pxlen, act->dist, act, -1); + net_addr_ip4 net = + NET_ADDR_IP4(ip4_from_u32(act->lsa.id & ln->optx), u32_masklen(ln->optx)); + + add_network(oa, (net_addr *) &net, act->dist, act, -1); } cnt = lsa_net_count(&act->lsa); @@ -549,10 +541,6 @@ spfa_process_prefixes(struct ospf_proto *p, struct ospf_area *oa) { struct top_hash_entry *en, *src; struct ospf_lsa_prefix *px; - ip_addr pxa; - int pxlen; - u8 pxopts; - u16 metric; u32 *buf; int i; @@ -587,18 +575,22 @@ spfa_process_prefixes(struct ospf_proto *p, struct ospf_area *oa) buf = px->rest; for (i = 0; i < px->pxcount; i++) - { - buf = lsa_get_ipv6_prefix(buf, &pxa, &pxlen, &pxopts, &metric); + { + net_addr_ip6 net; + u8 pxopts; + u16 metric; - if (pxopts & OPT_PX_NU) - continue; + buf = ospf_get_ipv6_prefix(buf, (net_addr *) &net, &pxopts, &metric); + + if (pxopts & OPT_PX_NU) + continue; - /* Store the first global address to use it later as a vlink endpoint */ - if ((pxopts & OPT_PX_LA) && ipa_zero(src->lb)) - src->lb = pxa; + /* Store the first global address to use it later as a vlink endpoint */ + if ((pxopts & OPT_PX_LA) && ipa_zero(src->lb)) + src->lb = ipa_from_ip6(net.prefix); - add_network(oa, pxa, pxlen, src->dist + metric, src, i); - } + add_network(oa, (net_addr *) &net, src->dist + metric, src, i); + } } } @@ -741,13 +733,12 @@ ospf_rt_sum(struct ospf_area *oa) { struct ospf_proto *p = oa->po; struct top_hash_entry *en; - ip_addr ip, abrip; + net_addr net; u32 dst_rid, metric, options; ort *abr; - int pxlen = -1, type = -1; + int type; u8 pxopts; - OSPF_TRACE(D_EVENTS, "Starting routing table calculation for inter-area (area %R)", oa->areaid); WALK_SLIST(en, p->lsal) @@ -770,18 +761,18 @@ ospf_rt_sum(struct ospf_area *oa) if (en->lsa_type == LSA_T_SUM_NET) { - lsa_parse_sum_net(en, ospf_is_v2(p), &ip, &pxlen, &pxopts, &metric); + lsa_parse_sum_net(en, ospf_is_v2(p), &net, &pxopts, &metric); - if (pxopts & OPT_PX_NU) - continue; - - if (pxlen < 0 || pxlen > MAX_PREFIX_LENGTH) + if (!ospf_valid_prefix(&net)) { log(L_WARN "%s: Invalid prefix in LSA (Type: %04x, Id: %R, Rt: %R)", p->p.name, en->lsa_type, en->lsa.id, en->lsa.rt); continue; } + if (pxopts & OPT_PX_NU) + continue; + options = 0; type = ORT_NET; } @@ -802,8 +793,8 @@ ospf_rt_sum(struct ospf_area *oa) continue; /* 16.2. (4) */ - abrip = ipa_from_rid(en->lsa.rt); - abr = (ort *) fib_find(&oa->rtr, &abrip, MAX_PREFIX_LENGTH); + net_addr_ip4 nrid = net_from_rid(en->lsa.rt); + abr = fib_find(&oa->rtr, (net_addr *) &nrid); if (!abr || !abr->n.type) continue; @@ -827,7 +818,7 @@ ospf_rt_sum(struct ospf_area *oa) }; if (type == ORT_NET) - ri_install_net(p, ip, pxlen, &nf); + ri_install_net(p, &net, &nf); else ri_install_rt(oa, dst_rid, &nf); } @@ -841,11 +832,7 @@ ospf_rt_sum_tr(struct ospf_area *oa) struct ospf_area *bb = p->backbone; struct top_hash_entry *en; ort *re, *abr; - ip_addr ip, abrip; - u32 dst_rid, metric, options; - int pxlen; - u8 pxopts; - + u32 metric; if (!bb) return; @@ -868,26 +855,31 @@ ospf_rt_sum_tr(struct ospf_area *oa) if (en->lsa_type == LSA_T_SUM_NET) { - lsa_parse_sum_net(en, ospf_is_v2(p), &ip, &pxlen, &pxopts, &metric); + net_addr net; + u8 pxopts; - if (pxopts & OPT_PX_NU) - continue; + lsa_parse_sum_net(en, ospf_is_v2(p), &net, &pxopts, &metric); - if (pxlen < 0 || pxlen > MAX_PREFIX_LENGTH) + if (!ospf_valid_prefix(&net)) { log(L_WARN "%s: Invalid prefix in LSA (Type: %04x, Id: %R, Rt: %R)", p->p.name, en->lsa_type, en->lsa.id, en->lsa.rt); continue; } - re = fib_find(&p->rtf, &ip, pxlen); + if (pxopts & OPT_PX_NU) + continue; + + re = fib_find(&p->rtf, &net); } else // en->lsa_type == LSA_T_SUM_RT { + u32 dst_rid, options; + lsa_parse_sum_rt(en, ospf_is_v2(p), &dst_rid, &metric, &options); - ip = ipa_from_rid(dst_rid); - re = fib_find(&bb->rtr, &ip, MAX_PREFIX_LENGTH); + net_addr_ip4 nrid = net_from_rid(dst_rid); + re = fib_find(&bb->rtr, (net_addr *) &nrid); } /* 16.3 (1b) */ @@ -905,8 +897,8 @@ ospf_rt_sum_tr(struct ospf_area *oa) continue; /* 16.3. (4) */ - abrip = ipa_from_rid(en->lsa.rt); - abr = fib_find(&oa->rtr, &abrip, MAX_PREFIX_LENGTH); + net_addr_ip4 nrid = net_from_rid(en->lsa.rt); + abr = fib_find(&oa->rtr, (net_addr *) &nrid); if (!abr || !abr->n.type) continue; @@ -997,7 +989,7 @@ decide_sum_lsa(struct ospf_area *oa, ort *nf, int dest) return 1; struct area_net *anet = (struct area_net *) - fib_route(&nf->n.oa->net_fib, nf->fn.prefix, nf->fn.pxlen); + fib_route(&nf->n.oa->net_fib, nf->fn.addr); /* Condensed area network found */ if (anet) @@ -1016,13 +1008,13 @@ check_sum_net_lsa(struct ospf_proto *p, ort *nf) if (nf->area_net) { /* It is a default route for stub areas, handled entirely in ospf_rt_abr() */ - if (nf->fn.pxlen == 0) + if (nf->fn.addr->pxlen == 0) return; /* Find that area network */ WALK_LIST(anet_oa, p->area_list) { - anet = (struct area_net *) fib_find(&anet_oa->net_fib, &nf->fn.prefix, nf->fn.pxlen); + anet = fib_find(&anet_oa->net_fib, nf->fn.addr); if (anet) break; } @@ -1041,14 +1033,16 @@ check_sum_net_lsa(struct ospf_proto *p, ort *nf) static inline void check_sum_rt_lsa(struct ospf_proto *p, ort *nf) { + u32 rid = rid_from_net(nf->fn.addr); + struct ospf_area *oa; WALK_LIST(oa, p->area_list) if (decide_sum_lsa(oa, nf, ORT_ROUTER)) - ospf_originate_sum_rt_lsa(p, oa, nf, nf->n.metric1, nf->n.options); + ospf_originate_sum_rt_lsa(p, oa, rid, nf->n.metric1, nf->n.options); } static inline int -decide_nssa_lsa(struct ospf_proto *p UNUSED4 UNUSED6, ort *nf, struct ospf_lsa_ext_local *rt) +decide_nssa_lsa(struct ospf_proto *p, ort *nf, struct ospf_lsa_ext_local *rt) { struct ospf_area *oa = nf->n.oa; struct top_hash_entry *en = nf->n.en; @@ -1057,7 +1051,7 @@ decide_nssa_lsa(struct ospf_proto *p UNUSED4 UNUSED6, ort *nf, struct ospf_lsa_e return 0; /* Condensed area network found */ - if (fib_route(&oa->enet_fib, nf->fn.prefix, nf->fn.pxlen)) + if (fib_route(&oa->enet_fib, nf->fn.addr)) return 0; if (!en || (en->lsa_type != LSA_T_NSSA)) @@ -1092,7 +1086,7 @@ check_nssa_lsa(struct ospf_proto *p, ort *nf) /* Find that area network */ WALK_LIST(oa, p->area_list) { - anet = (struct area_net *) fib_find(&oa->enet_fib, &nf->fn.prefix, nf->fn.pxlen); + anet = fib_find(&oa->enet_fib, nf->fn.addr); if (anet) break; } @@ -1162,24 +1156,20 @@ static void ospf_rt_abr1(struct ospf_proto *p) { struct area_net *anet; - ort *nf, *default_nf; + ort *default_nf; + net_addr default_net; /* RFC 2328 G.3 - incomplete resolution of virtual next hops - routers */ - FIB_WALK(&p->backbone->rtr, nftmp) + FIB_WALK(&p->backbone->rtr, ort, nf) { - nf = (ort *) nftmp; - if (nf->n.type && unresolved_vlink(nf)) reset_ri(nf); } FIB_WALK_END; - FIB_WALK(&p->rtf, nftmp) + FIB_WALK(&p->rtf, ort, nf) { - nf = (ort *) nftmp; - - /* RFC 2328 G.3 - incomplete resolution of virtual next hops - networks */ if (nf->n.type && unresolved_vlink(nf)) reset_ri(nf); @@ -1188,7 +1178,7 @@ ospf_rt_abr1(struct ospf_proto *p) /* Compute condensed area networks */ if (nf->n.type == RTS_OSPF) { - anet = (struct area_net *) fib_route(&nf->n.oa->net_fib, nf->fn.prefix, nf->fn.pxlen); + anet = (struct area_net *) fib_route(&nf->n.oa->net_fib, nf->fn.addr); if (anet) { if (!anet->active) @@ -1196,7 +1186,7 @@ ospf_rt_abr1(struct ospf_proto *p) anet->active = 1; /* Get a RT entry and mark it to know that it is an area network */ - ort *nfi = (ort *) fib_get(&p->rtf, &anet->fn.prefix, anet->fn.pxlen); + ort *nfi = fib_get(&p->rtf, anet->fn.addr); nfi->area_net = 1; /* 16.2. (3) */ @@ -1211,8 +1201,13 @@ ospf_rt_abr1(struct ospf_proto *p) } FIB_WALK_END; - ip_addr addr = IPA_NONE; - default_nf = (ort *) fib_get(&p->rtf, &addr, 0); + + if (ospf_is_v2(p)) + net_fill_ip4(&default_net, IP4_NONE, 0); + else + net_fill_ip6(&default_net, IP6_NONE, 0); + + default_nf = fib_get(&p->rtf, &default_net); default_nf->area_net = 1; struct ospf_area *oa; @@ -1239,11 +1234,10 @@ ospf_rt_abr1(struct ospf_proto *p) /* RFC 2328 16.4. (3) - precompute preferred ASBR entries */ if (oa_is_ext(oa)) { - FIB_WALK(&oa->rtr, nftmp) + FIB_WALK(&oa->rtr, ort, nf) { - nf = (ort *) nftmp; if (nf->n.options & ORTA_ASBR) - ri_install_asbr(p, &nf->fn.prefix, &nf->n); + ri_install_asbr(p, rid_from_net(nf->fn.addr), &nf->n); } FIB_WALK_END; } @@ -1251,9 +1245,9 @@ ospf_rt_abr1(struct ospf_proto *p) /* Originate or flush ASBR summary LSAs */ - FIB_WALK(&p->backbone->rtr, nftmp) + FIB_WALK(&p->backbone->rtr, ort, nf) { - check_sum_rt_lsa(p, (ort *) nftmp); + check_sum_rt_lsa(p, nf); } FIB_WALK_END; @@ -1280,8 +1274,6 @@ ospf_rt_abr2(struct ospf_proto *p) { struct ospf_area *oa; struct top_hash_entry *en; - ort *nf, *nf2; - /* RFC 3103 3.1 - type-7 translator election */ struct ospf_area *bb = p->backbone; @@ -1293,13 +1285,12 @@ ospf_rt_abr2(struct ospf_proto *p) if (oa->ac->translator) goto decided; - FIB_WALK(&oa->rtr, nftmp) + FIB_WALK(&oa->rtr, ort, nf) { - nf = (ort *) nftmp; if (!nf->n.type || !(nf->n.options & ORTA_ABR)) continue; - nf2 = fib_find(&bb->rtr, &nf->fn.prefix, MAX_PREFIX_LENGTH); + ort *nf2 = fib_find(&bb->rtr, nf->fn.addr); if (!nf2 || !nf2->n.type || !(nf2->n.options & ORTA_ABR)) continue; @@ -1339,13 +1330,11 @@ ospf_rt_abr2(struct ospf_proto *p) /* Compute condensed external networks */ - FIB_WALK(&p->rtf, nftmp) + FIB_WALK(&p->rtf, ort, nf) { - nf = (ort *) nftmp; if (rt_is_nssa(nf) && (nf->n.options & ORTA_PROP)) { - struct area_net *anet = (struct area_net *) - fib_route(&nf->n.oa->enet_fib, nf->fn.prefix, nf->fn.pxlen); + struct area_net *anet = fib_route(&nf->n.oa->enet_fib, nf->fn.addr); if (anet) { @@ -1354,7 +1343,7 @@ ospf_rt_abr2(struct ospf_proto *p) anet->active = 1; /* Get a RT entry and mark it to know that it is an area network */ - nf2 = (ort *) fib_get(&p->rtf, &anet->fn.prefix, anet->fn.pxlen); + ort *nf2 = fib_get(&p->rtf, anet->fn.addr); nf2->area_net = 1; } @@ -1369,10 +1358,8 @@ ospf_rt_abr2(struct ospf_proto *p) FIB_WALK_END; - FIB_WALK(&p->rtf, nftmp) + FIB_WALK(&p->rtf, ort, nf) { - nf = (ort *) nftmp; - check_sum_net_lsa(p, nf); check_nssa_lsa(p, nf); } @@ -1382,22 +1369,57 @@ ospf_rt_abr2(struct ospf_proto *p) /* Like fib_route(), but ignores dummy rt entries */ static void * -ospf_fib_route(struct fib *f, ip_addr a, int len) +ospf_fib_route_ip4(struct fib *f, ip4_addr a, int len) { - ip_addr a0; + net_addr_ip4 net = NET_ADDR_IP4(a, len); ort *nf; - while (len >= 0) +loop: + nf = fib_find(f, (net_addr *) &net); + if (nf && nf->n.type) + return nf; + + if (net.pxlen > 0) + { + net.pxlen--; + ip4_clrbit(&net.prefix, net.pxlen); + goto loop; + } + + return NULL; +} + +static void * +ospf_fib_route_ip6(struct fib *f, ip6_addr a, int len) +{ + net_addr_ip6 net = NET_ADDR_IP6(a, len); + ort *nf; + +loop: + nf = fib_find(f, (net_addr *) &net); + if (nf && nf->n.type) + return nf; + + if (net.pxlen > 0) { - a0 = ipa_and(a, ipa_mkmask(len)); - nf = fib_find(f, &a0, len); - if (nf && nf->n.type) - return nf; - len--; + net.pxlen--; + ip6_clrbit(&net.prefix, net.pxlen); + goto loop; } + return NULL; } +static void * +ospf_fib_route(struct fib *f, ip_addr a) +{ + if (f->addr_type == NET_IP4) + return ospf_fib_route_ip4(f, ipa_to_ip4(a), IP4_MAX_PREFIX_LENGTH); + else + return ospf_fib_route_ip6(f, ipa_to_ip6(a), IP6_MAX_PREFIX_LENGTH); +} + + /* RFC 2328 16.4. calculating external routes */ static void ospf_ext_spf(struct ospf_proto *p) @@ -1405,7 +1427,6 @@ ospf_ext_spf(struct ospf_proto *p) struct top_hash_entry *en; struct ospf_lsa_ext_local rt; ort *nf1, *nf2; - ip_addr rtid; u32 br_metric; struct ospf_area *atmp; @@ -1431,19 +1452,18 @@ ospf_ext_spf(struct ospf_proto *p) lsa_parse_ext(en, ospf_is_v2(p), &rt); - if (rt.metric == LSINFINITY) - continue; - - if (rt.pxopts & OPT_PX_NU) - continue; - - if (rt.pxlen < 0 || rt.pxlen > MAX_PREFIX_LENGTH) + if (!ospf_valid_prefix(&rt.net)) { log(L_WARN "%s: Invalid prefix in LSA (Type: %04x, Id: %R, Rt: %R)", p->p.name, en->lsa_type, en->lsa.id, en->lsa.rt); continue; } + if (rt.metric == LSINFINITY) + continue; + + if (rt.pxopts & OPT_PX_NU) + continue; /* 16.4. (3) */ /* If there are more areas, we already precomputed preferred ASBR @@ -1457,8 +1477,8 @@ ospf_ext_spf(struct ospf_proto *p) if (!atmp) continue; /* Should not happen */ - rtid = ipa_from_rid(en->lsa.rt); - nf1 = fib_find(&atmp->rtr, &rtid, MAX_PREFIX_LENGTH); + net_addr_ip4 nrid = net_from_rid(en->lsa.rt); + nf1 = fib_find(&atmp->rtr, (net_addr *) &nrid); if (!nf1 || !nf1->n.type) continue; /* No AS boundary router found */ @@ -1468,7 +1488,7 @@ ospf_ext_spf(struct ospf_proto *p) /* 16.4. (3) NSSA - special rule for default routes */ /* ABR should use default only if P-bit is set and summaries are active */ - if ((en->lsa_type == LSA_T_NSSA) && ipa_zero(rt.ip) && (rt.pxlen == 0) && + if ((en->lsa_type == LSA_T_NSSA) && (rt.net.pxlen == 0) && (p->areano > 1) && !(rt.propagate && atmp->ac->summary)) continue; @@ -1480,7 +1500,7 @@ ospf_ext_spf(struct ospf_proto *p) } else { - nf2 = ospf_fib_route(&p->rtf, rt.fwaddr, MAX_PREFIX_LENGTH); + nf2 = ospf_fib_route(&p->rtf, rt.fwaddr); if (!nf2) continue; @@ -1542,7 +1562,7 @@ ospf_ext_spf(struct ospf_proto *p) nfa.oa = atmp; /* undefined in RFC 2328 */ nfa.en = en; /* store LSA for later (NSSA processing) */ - ri_install_ext(p, rt.ip, rt.pxlen, &nfa); + ri_install_ext(p, &rt.net, &nfa); } } @@ -1552,13 +1572,10 @@ ospf_rt_reset(struct ospf_proto *p) { struct ospf_area *oa; struct top_hash_entry *en; - struct area_net *anet; - ort *ri; /* Reset old routing table */ - FIB_WALK(&p->rtf, nftmp) + FIB_WALK(&p->rtf, ort, ri) { - ri = (ort *) nftmp; ri->area_net = 0; ri->keep = 0; reset_ri(ri); @@ -1580,9 +1597,8 @@ ospf_rt_reset(struct ospf_proto *p) WALK_LIST(oa, p->area_list) { /* Reset ASBR routing tables */ - FIB_WALK(&oa->rtr, nftmp) + FIB_WALK(&oa->rtr, ort, ri) { - ri = (ort *) nftmp; reset_ri(ri); } FIB_WALK_END; @@ -1590,17 +1606,15 @@ ospf_rt_reset(struct ospf_proto *p) /* Reset condensed area networks */ if (p->areano > 1) { - FIB_WALK(&oa->net_fib, nftmp) + FIB_WALK(&oa->net_fib, struct area_net, anet) { - anet = (struct area_net *) nftmp; anet->active = 0; anet->metric = 0; } FIB_WALK_END; - FIB_WALK(&oa->enet_fib, nftmp) + FIB_WALK(&oa->enet_fib, struct area_net, anet) { - anet = (struct area_net *) nftmp; anet->active = 0; anet->metric = 0; } @@ -1659,18 +1673,18 @@ ospf_rt_spf(struct ospf_proto *p) static inline int -inherit_nexthops(struct mpnh *pn) +inherit_nexthops(struct nexthop *pn) { /* Proper nexthops (with defined GW) or dummy vlink nexthops (without iface) */ return pn && (ipa_nonzero(pn->gw) || !pn->iface); } -static struct mpnh * +static struct nexthop * calc_next_hop(struct ospf_area *oa, struct top_hash_entry *en, struct top_hash_entry *par, int pos) { struct ospf_proto *p = oa->po; - struct mpnh *pn = par->nhs; + struct nexthop *pn = par->nhs; struct ospf_iface *ifa; u32 rid = en->lsa.rt; @@ -1798,7 +1812,7 @@ add_cand(list * l, struct top_hash_entry *en, struct top_hash_entry *par, if (!link_back(oa, en, par)) return; - struct mpnh *nhs = calc_next_hop(oa, en, par, pos); + struct nexthop *nhs = calc_next_hop(oa, en, par, pos); if (!nhs) { log(L_WARN "%s: Cannot find next hop for LSA (Type: %04x, Id: %R, Rt: %R)", @@ -1836,7 +1850,7 @@ add_cand(list * l, struct top_hash_entry *en, struct top_hash_entry *par, /* Merge old and new */ int new_reuse = (par->nhs != nhs); - en->nhs = mpnh_merge(en->nhs, nhs, en->nhs_reuse, new_reuse, p->ecmp, p->nhpool); + en->nhs = nexthop_merge(en->nhs, nhs, en->nhs_reuse, new_reuse, p->ecmp, p->nhpool); en->nhs_reuse = 1; return; } @@ -1892,8 +1906,7 @@ ort_changed(ort *nf, rta *nr) (nf->n.metric1 != nf->old_metric1) || (nf->n.metric2 != nf->old_metric2) || (nf->n.tag != nf->old_tag) || (nf->n.rid != nf->old_rid) || (nr->source != or->source) || (nr->dest != or->dest) || - (nr->iface != or->iface) || !ipa_equal(nr->gw, or->gw) || - !mpnh_same(nr->nexthops, or->nexthops); + !nexthop_same(&(nr->nh), &(or->nh)); } static void @@ -1902,25 +1915,22 @@ rt_sync(struct ospf_proto *p) struct top_hash_entry *en; struct fib_iterator fit; struct fib *fib = &p->rtf; - ort *nf; struct ospf_area *oa; /* This is used for forced reload of routes */ int reload = (p->calcrt == 2); - OSPF_TRACE(D_EVENTS, "Starting routing table synchronisation"); + OSPF_TRACE(D_EVENTS, "Starting routing table synchronization"); DBG("Now syncing my rt table with nest's\n"); FIB_ITERATE_INIT(&fit, fib); again1: - FIB_ITERATE_START(fib, &fit, nftmp) + FIB_ITERATE_START(fib, &fit, ort, nf) { - nf = (ort *) nftmp; - /* Sanity check of next-hop addresses, failure should not happen */ if (nf->n.type) { - struct mpnh *nh; + struct nexthop *nh; for (nh = nf->n.nhs; nh; nh = nh->next) if (ipa_nonzero(nh->gw)) { @@ -1943,29 +1953,12 @@ again1: .src = p->p.main_source, .source = nf->n.type, .scope = SCOPE_UNIVERSE, - .cast = RTC_UNICAST + .dest = RTD_UNICAST, + .nh = *(nf->n.nhs), }; - if (nf->n.nhs->next) - { - a0.dest = RTD_MULTIPATH; - a0.nexthops = nf->n.nhs; - } - else if (ipa_nonzero(nf->n.nhs->gw)) - { - a0.dest = RTD_ROUTER; - a0.iface = nf->n.nhs->iface; - a0.gw = nf->n.nhs->gw; - } - else - { - a0.dest = RTD_DEVICE; - a0.iface = nf->n.nhs->iface; - } - if (reload || ort_changed(nf, &a0)) { - net *ne = net_get(p->p.table, nf->fn.prefix, nf->fn.pxlen); rta *a = rta_lookup(&a0); rte *e = rte_get_temp(a); @@ -1976,12 +1969,10 @@ again1: e->u.ospf.tag = nf->old_tag = nf->n.tag; e->u.ospf.router_id = nf->old_rid = nf->n.rid; e->pflags = 0; - e->net = ne; - e->pref = p->p.preference; - DBG("Mod rte type %d - %I/%d via %I on iface %s, met %d\n", - a0.source, nf->fn.prefix, nf->fn.pxlen, a0.gw, a0.iface ? a0.iface->name : "(none)", nf->n.metric1); - rte_update(&p->p, ne, e); + 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); } } else if (nf->old_rta) @@ -1990,19 +1981,21 @@ again1: rta_free(nf->old_rta); nf->old_rta = NULL; - net *ne = net_get(p->p.table, nf->fn.prefix, nf->fn.pxlen); - rte_update(&p->p, ne, NULL); + rte_update(&p->p, nf->fn.addr, NULL); } /* Remove unused rt entry, some special entries are persistent */ if (!nf->n.type && !nf->external_rte && !nf->area_net && !nf->keep) { - FIB_ITERATE_PUT(&fit, nftmp); - fib_delete(fib, nftmp); + if (nf->lsa_id) + idm_free(&p->idm, nf->lsa_id); + + FIB_ITERATE_PUT(&fit); + fib_delete(fib, nf); goto again1; } } - FIB_ITERATE_END(nftmp); + FIB_ITERATE_END; WALK_LIST(oa, p->area_list) @@ -2010,18 +2003,16 @@ again1: /* Cleanup ASBR hash tables */ FIB_ITERATE_INIT(&fit, &oa->rtr); again2: - FIB_ITERATE_START(&oa->rtr, &fit, nftmp) + FIB_ITERATE_START(&oa->rtr, &fit, ort, nf) { - nf = (ort *) nftmp; - if (!nf->n.type) { - FIB_ITERATE_PUT(&fit, nftmp); - fib_delete(&oa->rtr, nftmp); + FIB_ITERATE_PUT(&fit); + fib_delete(&oa->rtr, nf); goto again2; } } - FIB_ITERATE_END(nftmp); + FIB_ITERATE_END; } /* Cleanup stale LSAs */ diff --git a/proto/ospf/rt.h b/proto/ospf/rt.h index 73b28375..589d2bc5 100644 --- a/proto/ospf/rt.h +++ b/proto/ospf/rt.h @@ -53,7 +53,7 @@ typedef struct orta struct ospf_area *oa; struct ospf_area *voa; /* Used when route is replaced in ospf_rt_sum_tr(), NULL otherwise */ - struct mpnh *nhs; /* Next hops computed during SPF */ + struct nexthop *nhs; /* Next hops computed during SPF */ struct top_hash_entry *en; /* LSA responsible for this orta */ } orta; @@ -78,13 +78,15 @@ typedef struct ort * route was not in the last update, in that case other old_* values are not * valid. */ - struct fib_node fn; orta n; u32 old_metric1, old_metric2, old_tag, old_rid; rta *old_rta; + u32 lsa_id; u8 external_rte; u8 area_net; u8 keep; + + struct fib_node fn; } ort; diff --git a/proto/ospf/topology.c b/proto/ospf/topology.c index 341eff87..ce77f57a 100644 --- a/proto/ospf/topology.c +++ b/proto/ospf/topology.c @@ -283,8 +283,8 @@ ospf_originate_lsa(struct ospf_proto *p, struct ospf_new_lsa *lsa) if (en->nf != lsa->nf) { - log(L_ERR "%s: LSA ID collision for %I/%d", - p->p.name, lsa->nf->fn.prefix, lsa->nf->fn.pxlen); + log(L_ERR "%s: LSA ID collision for %N", + p->p.name, lsa->nf->fn.addr); en = NULL; goto drop; @@ -514,14 +514,14 @@ ospf_update_lsadb(struct ospf_proto *p) } -static inline u32 -ort_to_lsaid(struct ospf_proto *p UNUSED4 UNUSED6, ort *nf) +static u32 +ort_to_lsaid(struct ospf_proto *p, ort *nf) { /* * In OSPFv2, We have to map IP prefixes to u32 in such manner that resulting * u32 interpreted as IP address is a member of given prefix. Therefore, /32 - * prefix have to be mapped on itself. All received prefixes have to be - * mapped on different u32s. + * prefix has to be mapped on itself. All received prefixes have to be mapped + * on different u32s. * * We have an assumption that if there is nontrivial (non-/32) network prefix, * then there is not /32 prefix for the first and the last IP address of the @@ -542,17 +542,21 @@ ort_to_lsaid(struct ospf_proto *p UNUSED4 UNUSED6, ort *nf) * network appeared, we choose a different way. * * In OSPFv3, it is simpler. There is not a requirement for membership of the - * result in the input network, so we just use a hash-based unique ID of a - * routing table entry for a route that originated given LSA. For ext-LSA, it - * is an imported route in the nest's routing table (p->table). For summary-LSA, - * it is a 'source' route in the protocol internal routing table (p->rtf). + * result in the input network, so we just allocate a unique ID from ID map + * and store it in nf->lsa_id for further reference. */ if (ospf_is_v3(p)) - return nf->fn.uid; + { + if (!nf->lsa_id) + nf->lsa_id = idm_alloc(&p->idm); - u32 id = ipa_to_u32(nf->fn.prefix); - int pxlen = nf->fn.pxlen; + return nf->lsa_id; + } + + net_addr_ip4 *net = (void *) nf->fn.addr; + u32 id = ip4_to_u32(net->prefix); + int pxlen = net->pxlen; if ((pxlen == 0) || (pxlen == 32)) return id; @@ -628,12 +632,12 @@ configured_stubnet(struct ospf_area *oa, struct ifa *a) { if (sn->summary) { - if (ipa_in_net(a->prefix, sn->px.addr, sn->px.len) && (a->pxlen >= sn->px.len)) + if (net_in_netX(&a->prefix, &sn->prefix)) return 1; } else { - if (ipa_equal(a->prefix, sn->px.addr) && (a->pxlen == sn->px.len)) + if (net_equal(&a->prefix, &sn->prefix)) return 1; } } @@ -781,7 +785,8 @@ prepare_rt2_lsa_body(struct ospf_proto *p, struct ospf_area *oa) (ifa->type == OSPF_IT_PTMP)) add_rt2_lsa_link(p, LSART_STUB, ipa_to_u32(ifa->addr->ip), 0xffffffff, 0); else - add_rt2_lsa_link(p, LSART_STUB, ipa_to_u32(ifa->addr->prefix), u32_mkmask(ifa->addr->pxlen), ifa->cost); + add_rt2_lsa_link(p, LSART_STUB, ip4_to_u32(net4_prefix(&ifa->addr->prefix)), + u32_mkmask(net4_pxlen(&ifa->addr->prefix)), ifa->cost); i++; ifa->rt_pos_end = i; @@ -790,7 +795,8 @@ prepare_rt2_lsa_body(struct ospf_proto *p, struct ospf_area *oa) struct ospf_stubnet_config *sn; WALK_LIST(sn, oa->ac->stubnet_list) if (!sn->hidden) - add_rt2_lsa_link(p, LSART_STUB, ipa_to_u32(sn->px.addr), u32_mkmask(sn->px.len), sn->cost), i++; + add_rt2_lsa_link(p, LSART_STUB, ip4_to_u32(net4_prefix(&sn->prefix)), + u32_mkmask(net4_pxlen(&sn->prefix)), sn->cost), i++; struct ospf_lsa_rt *rt = p->lsab; /* Store number of links in lower half of options */ @@ -907,7 +913,7 @@ prepare_net2_lsa_body(struct ospf_proto *p, struct ospf_iface *ifa) ASSERT(p->lsab_used == 0); net = lsab_alloc(p, sizeof(struct ospf_lsa_net) + 4 * nodes); - net->optx = u32_mkmask(ifa->addr->pxlen); + net->optx = u32_mkmask(ifa->addr->prefix.pxlen); net->routers[0] = p->router_id; WALK_LIST(n, ifa->neigh_list) @@ -999,9 +1005,10 @@ prepare_sum3_net_lsa_body(struct ospf_proto *p, ort *nf, u32 metric) { struct ospf_lsa_sum3_net *sum; - sum = lsab_allocz(p, sizeof(struct ospf_lsa_sum3_net) + IPV6_PREFIX_SPACE(nf->fn.pxlen)); + sum = lsab_allocz(p, sizeof(struct ospf_lsa_sum3_net) + + IPV6_PREFIX_SPACE(nf->fn.addr->pxlen)); sum->metric = metric; - put_ipv6_prefix(sum->prefix, nf->fn.prefix, nf->fn.pxlen, 0, 0); + ospf_put_ipv6_prefix(sum->prefix, nf->fn.addr, 0, 0); } static inline void @@ -1028,7 +1035,7 @@ ospf_originate_sum_net_lsa(struct ospf_proto *p, struct ospf_area *oa, ort *nf, }; if (ospf_is_v2(p)) - prepare_sum2_lsa_body(p, nf->fn.pxlen, metric); + prepare_sum2_lsa_body(p, nf->fn.addr->pxlen, metric); else prepare_sum3_net_lsa_body(p, nf, metric); @@ -1036,20 +1043,20 @@ ospf_originate_sum_net_lsa(struct ospf_proto *p, struct ospf_area *oa, ort *nf, } void -ospf_originate_sum_rt_lsa(struct ospf_proto *p, struct ospf_area *oa, ort *nf, int metric, u32 options) +ospf_originate_sum_rt_lsa(struct ospf_proto *p, struct ospf_area *oa, u32 drid, int metric, u32 options) { struct ospf_new_lsa lsa = { .type = LSA_T_SUM_RT, .mode = LSA_M_RTCALC, .dom = oa->areaid, - .id = ipa_to_rid(nf->fn.prefix), /* Router ID of ASBR, irrelevant for OSPFv3 */ + .id = drid, /* Router ID of ASBR, irrelevant for OSPFv3 */ .opts = oa->options }; if (ospf_is_v2(p)) prepare_sum2_lsa_body(p, 0, metric); else - prepare_sum3_rt_lsa_body(p, lsa.id, metric, options & LSA_OPTIONS_MASK); + prepare_sum3_rt_lsa_body(p, drid, metric, options & LSA_OPTIONS_MASK); ospf_originate_lsa(p, &lsa); } @@ -1082,7 +1089,7 @@ prepare_ext3_lsa_body(struct ospf_proto *p, ort *nf, { struct ospf_lsa_ext3 *ext; int bsize = sizeof(struct ospf_lsa_ext3) - + IPV6_PREFIX_SPACE(nf->fn.pxlen) + + IPV6_PREFIX_SPACE(nf->fn.addr->pxlen) + (ipa_nonzero(fwaddr) ? 16 : 0) + (tag ? 4 : 0); @@ -1090,7 +1097,7 @@ prepare_ext3_lsa_body(struct ospf_proto *p, ort *nf, ext->metric = metric & LSA_METRIC_MASK; u32 *buf = ext->rest; - buf = put_ipv6_prefix(buf, nf->fn.prefix, nf->fn.pxlen, pbit ? OPT_PX_P : 0, 0); + buf = ospf_put_ipv6_prefix(buf, nf->fn.addr, pbit ? OPT_PX_P : 0, 0); if (ebit) ext->metric |= LSA_EXT3_EBIT; @@ -1098,7 +1105,7 @@ prepare_ext3_lsa_body(struct ospf_proto *p, ort *nf, if (ipa_nonzero(fwaddr)) { ext->metric |= LSA_EXT3_FBIT; - buf = put_ipv6_addr(buf, fwaddr); + buf = ospf_put_ipv6_addr(buf, fwaddr); } if (tag) @@ -1140,7 +1147,7 @@ ospf_originate_ext_lsa(struct ospf_proto *p, struct ospf_area *oa, ort *nf, u8 m }; if (ospf_is_v2(p)) - prepare_ext2_lsa_body(p, nf->fn.pxlen, metric, ebit, fwaddr, tag); + prepare_ext2_lsa_body(p, nf->fn.addr->pxlen, metric, ebit, fwaddr, tag); else prepare_ext3_lsa_body(p, nf, metric, ebit, fwaddr, tag, oa && pbit); @@ -1177,7 +1184,7 @@ use_gw_for_fwaddr(struct ospf_proto *p, ip_addr gw, struct iface *iface) WALK_LIST(ifa, p->iface_list) if ((ifa->iface == iface) && - (!ospf_is_v2(p) || ipa_in_net(gw, ifa->addr->prefix, ifa->addr->pxlen))) + (!ospf_is_v2(p) || ipa_in_netX(gw, &ifa->addr->prefix))) return 1; return 0; @@ -1215,7 +1222,8 @@ find_surrogate_fwaddr(struct ospf_proto *p, struct ospf_area *oa) { WALK_LIST(a, ifa->iface->addrs) { - if ((a->flags & IA_SECONDARY) || + if ((a->prefix.type != NET_IP6) || + (a->flags & IA_SECONDARY) || (a->flags & IA_PEER) || (a->scope <= SCOPE_LINK)) continue; @@ -1234,7 +1242,7 @@ find_surrogate_fwaddr(struct ospf_proto *p, struct ospf_area *oa) } void -ospf_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old UNUSED, ea_list *ea) +ospf_rt_notify(struct proto *P, struct channel *ch UNUSED, net *n, rte *new, rte *old UNUSED, ea_list *ea) { struct ospf_proto *p = (struct ospf_proto *) P; struct ospf_area *oa = NULL; /* non-NULL for NSSA-LSA */ @@ -1253,7 +1261,7 @@ ospf_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U if (!new) { - nf = (ort *) fib_find(&p->rtf, &n->n.prefix, n->n.pxlen); + nf = fib_find(&p->rtf, n->n.addr); if (!nf || !nf->external_rte) return; @@ -1280,8 +1288,8 @@ ospf_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U ip_addr fwd = IPA_NONE; - if ((a->dest == RTD_ROUTER) && use_gw_for_fwaddr(p, a->gw, a->iface)) - fwd = a->gw; + if ((a->dest == RTD_UNICAST) && use_gw_for_fwaddr(p, a->nh.gw, a->nh.iface)) + fwd = a->nh.gw; /* NSSA-LSA with P-bit set must have non-zero forwarding address */ if (oa && ipa_zero(fwd)) @@ -1290,13 +1298,13 @@ ospf_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U if (ipa_zero(fwd)) { - log(L_ERR "%s: Cannot find forwarding address for NSSA-LSA %I/%d", - p->p.name, n->n.prefix, n->n.pxlen); + log(L_ERR "%s: Cannot find forwarding address for NSSA-LSA %N", + p->p.name, n->n.addr); return; } } - nf = (ort *) fib_get(&p->rtf, &n->n.prefix, n->n.pxlen); + nf = fib_get(&p->rtf, n->n.addr); ospf_originate_ext_lsa(p, oa, nf, LSA_M_EXPORT, metric, ebit, fwd, tag, 1); nf->external_rte = 1; } @@ -1308,11 +1316,11 @@ ospf_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U */ static inline void -lsab_put_prefix(struct ospf_proto *p, ip_addr prefix, u32 pxlen, u32 cost) +lsab_put_prefix(struct ospf_proto *p, net_addr *net, u32 cost) { - void *buf = lsab_alloc(p, IPV6_PREFIX_SPACE(pxlen)); - u8 flags = (pxlen < MAX_PREFIX_LENGTH) ? 0 : OPT_PX_LA; - put_ipv6_prefix(buf, prefix, pxlen, flags, cost); + void *buf = lsab_alloc(p, IPV6_PREFIX_SPACE(net6_pxlen(net))); + u8 flags = (net6_pxlen(net) < IP6_MAX_PREFIX_LENGTH) ? 0 : OPT_PX_LA; + ospf_put_ipv6_prefix(buf, net, flags, cost); } static void @@ -1330,11 +1338,12 @@ prepare_link_lsa_body(struct ospf_proto *p, struct ospf_iface *ifa) struct ifa *a; WALK_LIST(a, ifa->iface->addrs) { - if ((a->flags & IA_SECONDARY) || - (a->scope < SCOPE_SITE)) + if ((a->prefix.type != NET_IP6) || + (a->flags & IA_SECONDARY) || + (a->scope <= SCOPE_LINK)) continue; - lsab_put_prefix(p, a->prefix, a->pxlen, 0); + lsab_put_prefix(p, &a->prefix, 0); i++; } @@ -1401,12 +1410,13 @@ prepare_prefix_rt_lsa_body(struct ospf_proto *p, struct ospf_area *oa) struct ifa *a; WALK_LIST(a, ifa->iface->addrs) { - if ((a->flags & IA_SECONDARY) || + if ((a->prefix.type != NET_IP6) || + (a->flags & IA_SECONDARY) || (a->flags & IA_PEER) || (a->scope <= SCOPE_LINK)) continue; - if (((a->pxlen < MAX_PREFIX_LENGTH) && net_lsa) || + if (((a->prefix.pxlen < IP6_MAX_PREFIX_LENGTH) && net_lsa) || configured_stubnet(oa, a)) continue; @@ -1414,11 +1424,12 @@ prepare_prefix_rt_lsa_body(struct ospf_proto *p, struct ospf_area *oa) (ifa->state == OSPF_IS_LOOP) || (ifa->type == OSPF_IT_PTMP)) { - lsab_put_prefix(p, a->ip, MAX_PREFIX_LENGTH, 0); + net_addr_ip6 net = NET_ADDR_IP6(a->ip, IP6_MAX_PREFIX_LENGTH); + lsab_put_prefix(p, (net_addr *) &net, 0); host_addr = 1; } else - lsab_put_prefix(p, a->prefix, a->pxlen, ifa->cost); + lsab_put_prefix(p, &a->prefix, ifa->cost); i++; } @@ -1429,8 +1440,8 @@ prepare_prefix_rt_lsa_body(struct ospf_proto *p, struct ospf_area *oa) WALK_LIST(sn, oa->ac->stubnet_list) if (!sn->hidden) { - lsab_put_prefix(p, sn->px.addr, sn->px.len, sn->cost); - if (sn->px.len == MAX_PREFIX_LENGTH) + lsab_put_prefix(p, &sn->prefix, sn->cost); + if (sn->prefix.pxlen == IP6_MAX_PREFIX_LENGTH) host_addr = 1; i++; } @@ -1447,11 +1458,14 @@ prepare_prefix_rt_lsa_body(struct ospf_proto *p, struct ospf_area *oa) struct ifa *a; WALK_LIST(a, ifa->iface->addrs) { - if ((a->flags & IA_SECONDARY) || (a->scope <= SCOPE_LINK)) + if ((a->prefix.type != NET_IP6) || + (a->flags & IA_SECONDARY) || + (a->scope <= SCOPE_LINK)) continue; /* Found some IP */ - lsab_put_prefix(p, a->ip, MAX_PREFIX_LENGTH, 0); + net_addr_ip6 net = NET_ADDR_IP6(a->ip, IP6_MAX_PREFIX_LENGTH); + lsab_put_prefix(p, (net_addr *) &net, 0); i++; goto done; } @@ -1748,7 +1762,7 @@ ospf_top_hash(struct top_graph *f, u32 domain, u32 lsaid, u32 rtrid, u32 type) * and request lists of OSPF neighbors. */ struct top_graph * -ospf_top_new(struct ospf_proto *p UNUSED4 UNUSED6, pool *pool) +ospf_top_new(struct ospf_proto *p, pool *pool) { struct top_graph *f; diff --git a/proto/ospf/topology.h b/proto/ospf/topology.h index 5652ced0..d1682c54 100644 --- a/proto/ospf/topology.h +++ b/proto/ospf/topology.h @@ -28,7 +28,7 @@ struct top_hash_entry u16 next_lsa_opts; /* For postponed LSA origination */ bird_clock_t inst_time; /* Time of installation into DB */ struct ort *nf; /* Reference fibnode for sum and ext LSAs, NULL for otherwise */ - struct mpnh *nhs; /* Computed nexthops - valid only in ospf_rt_spf() */ + struct nexthop *nhs; /* Computed nexthops - valid only in ospf_rt_spf() */ ip_addr lb; /* In OSPFv2, link back address. In OSPFv3, any global address in the area useful for vlinks */ u32 lb_id; /* Interface ID of link back iface (for bcast or NBMA networks) */ u32 dist; /* Distance from the root */ @@ -185,10 +185,10 @@ static inline void ospf_flush2_lsa(struct ospf_proto *p, struct top_hash_entry * { if (*en) { ospf_flush_lsa(p, *en); *en = NULL; } } void ospf_originate_sum_net_lsa(struct ospf_proto *p, struct ospf_area *oa, ort *nf, int metric); -void ospf_originate_sum_rt_lsa(struct ospf_proto *p, struct ospf_area *oa, ort *nf, int metric, u32 options); +void ospf_originate_sum_rt_lsa(struct ospf_proto *p, struct ospf_area *oa, u32 drid, int metric, u32 options); 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); -void ospf_rt_notify(struct proto *P, rtable *tbl, net *n, rte *new, rte *old, ea_list *attrs); +void ospf_rt_notify(struct proto *P, struct channel *ch, net *n, rte *new, rte *old, ea_list *attrs); 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/pipe/Makefile b/proto/pipe/Makefile index 77de5b88..5093da98 100644 --- a/proto/pipe/Makefile +++ b/proto/pipe/Makefile @@ -1,6 +1,6 @@ -source=pipe.c -root-rel=../../ -dir-name=proto/pipe - -include ../../Rules +src := pipe.c +obj := $(src-o-files) +$(all-daemon) +$(cf-local) +tests_objs := $(tests_objs) $(src-o-files)
\ No newline at end of file diff --git a/proto/pipe/config.Y b/proto/pipe/config.Y index 8daf2e7c..f51ee575 100644 --- a/proto/pipe/config.Y +++ b/proto/pipe/config.Y @@ -16,28 +16,25 @@ CF_DEFINES CF_DECLS -CF_KEYWORDS(PIPE, PEER, TABLE, MODE, OPAQUE, TRANSPARENT) +CF_KEYWORDS(PIPE, PEER, TABLE) CF_GRAMMAR -CF_ADDTO(proto, pipe_proto '}') +CF_ADDTO(proto, pipe_proto '}' { this_channel = NULL; } ) -pipe_proto_start: proto_start PIPE { - this_proto = proto_config_new(&proto_pipe, $1); - PIPE_CFG->mode = PIPE_TRANSPARENT; - } - ; +pipe_proto_start: proto_start PIPE +{ + this_proto = proto_config_new(&proto_pipe, $1); + this_channel = channel_config_new(NULL, 0, this_proto); + this_channel->in_filter = FILTER_ACCEPT; + this_channel->out_filter = FILTER_ACCEPT; +}; pipe_proto: pipe_proto_start proto_name '{' | pipe_proto proto_item ';' - | pipe_proto PEER TABLE SYM ';' { - if ($4->class != SYM_TABLE) - cf_error("Routing table name expected"); - PIPE_CFG->peer = $4->def; - } - | pipe_proto MODE OPAQUE ';' { PIPE_CFG->mode = PIPE_OPAQUE; } - | pipe_proto MODE TRANSPARENT ';' { PIPE_CFG->mode = PIPE_TRANSPARENT; } + | pipe_proto channel_item ';' + | pipe_proto PEER TABLE rtable ';' { PIPE_CFG->peer = $4; } ; CF_CODE diff --git a/proto/pipe/pipe.c b/proto/pipe/pipe.c index 6ef80322..310f3c01 100644 --- a/proto/pipe/pipe.c +++ b/proto/pipe/pipe.c @@ -44,54 +44,42 @@ #include "pipe.h" static void -pipe_rt_notify(struct proto *P, rtable *src_table, net *n, rte *new, rte *old, ea_list *attrs) +pipe_rt_notify(struct proto *P, struct channel *src_ch, net *n, rte *new, rte *old, ea_list *attrs) { - struct pipe_proto *p = (struct pipe_proto *) P; - struct announce_hook *ah = (src_table == P->table) ? p->peer_ahook : P->main_ahook; - rtable *dst_table = ah->table; + struct pipe_proto *p = (void *) P; + struct channel *dst = (src_ch == p->pri) ? p->sec : p->pri; struct rte_src *src; - net *nn; rte *e; - rta a; + rta *a; if (!new && !old) return; - if (dst_table->pipe_busy) + if (dst->table->pipe_busy) { - log(L_ERR "Pipe loop detected when sending %I/%d to table %s", - n->n.prefix, n->n.pxlen, dst_table->name); + log(L_ERR "Pipe loop detected when sending %N to table %s", + n->n.addr, dst->table->name); return; } - nn = net_get(dst_table, n->n.prefix, n->n.pxlen); if (new) { - memcpy(&a, new->attrs, sizeof(rta)); - - if (p->mode == PIPE_OPAQUE) - { - a.src = P->main_source; - a.source = RTS_PIPE; - } - - a.aflags = 0; - a.eattrs = attrs; - a.hostentry = NULL; - e = rte_get_temp(&a); - e->net = nn; + a = alloca(rta_size(new->attrs)); + memcpy(a, new->attrs, rta_size(new->attrs)); + + a->aflags = 0; + a->eattrs = attrs; + a->hostentry = NULL; + e = rte_get_temp(a); e->pflags = 0; - if (p->mode == PIPE_TRANSPARENT) - { - /* Copy protocol specific embedded attributes. */ - memcpy(&(e->u), &(new->u), sizeof(e->u)); - e->pref = new->pref; - e->pflags = new->pflags; - } + /* Copy protocol specific embedded attributes. */ + memcpy(&(e->u), &(new->u), sizeof(e->u)); + e->pref = new->pref; + e->pflags = new->pflags; - src = a.src; + src = a->src; } else { @@ -99,9 +87,9 @@ pipe_rt_notify(struct proto *P, rtable *src_table, net *n, rte *new, rte *old, e src = old->attrs->src; } - src_table->pipe_busy = 1; - rte_update2(ah, nn, e, src); - src_table->pipe_busy = 0; + src_ch->table->pipe_busy = 1; + rte_update2(dst, n->n.addr, e, src); + src_ch->table->pipe_busy = 0; } static int @@ -111,171 +99,117 @@ pipe_import_control(struct proto *P, rte **ee, ea_list **ea UNUSED, struct linpo if (pp == P) return -1; /* Avoid local loops automatically */ + return 0; } -static int -pipe_reload_routes(struct proto *P) +static void +pipe_reload_routes(struct channel *C) { - struct pipe_proto *p = (struct pipe_proto *) P; - - /* - * Because the pipe protocol feeds routes from both routing tables - * together, both directions are reloaded during refeed and 'reload - * out' command works like 'reload' command. For symmetry, we also - * request refeed when 'reload in' command is used. - */ - proto_request_feeding(P); + struct pipe_proto *p = (void *) C->proto; - proto_reset_limit(P->main_ahook->in_limit); - proto_reset_limit(p->peer_ahook->in_limit); - - return 1; + /* Route reload on one channel is just refeed on the other */ + channel_request_feeding((C == p->pri) ? p->sec : p->pri); } -static struct proto * -pipe_init(struct proto_config *C) -{ - struct pipe_config *c = (struct pipe_config *) C; - struct proto *P = proto_new(C, sizeof(struct pipe_proto)); - struct pipe_proto *p = (struct pipe_proto *) P; - p->mode = c->mode; - p->peer_table = c->peer->table; - P->accept_ra_types = (p->mode == PIPE_OPAQUE) ? RA_OPTIMAL : RA_ANY; - P->rt_notify = pipe_rt_notify; - P->import_control = pipe_import_control; - P->reload_routes = pipe_reload_routes; - - return P; -} - -static int -pipe_start(struct proto *P) +static void +pipe_postconfig(struct proto_config *CF) { - struct pipe_config *cf = (struct pipe_config *) P->cf; - struct pipe_proto *p = (struct pipe_proto *) P; + struct pipe_config *cf = (void *) CF; + struct channel_config *cc = proto_cf_main_channel(CF); - /* Lock both tables, unlock is handled in pipe_cleanup() */ - rt_lock_table(P->table); - rt_lock_table(p->peer_table); + if (!cc->table) + cf_error("Primary routing table not specified"); - /* Going directly to PS_UP - prepare for feeding, - connect the protocol to both routing tables */ + if (!cf->peer) + cf_error("Secondary routing table not specified"); - P->main_ahook = proto_add_announce_hook(P, P->table, &P->stats); - P->main_ahook->out_filter = cf->c.out_filter; - P->main_ahook->in_limit = cf->c.in_limit; - proto_reset_limit(P->main_ahook->in_limit); + if (cc->table == cf->peer) + cf_error("Primary table and peer table must be different"); - p->peer_ahook = proto_add_announce_hook(P, p->peer_table, &p->peer_stats); - p->peer_ahook->out_filter = cf->c.in_filter; - p->peer_ahook->in_limit = cf->c.out_limit; - proto_reset_limit(p->peer_ahook->in_limit); + if (cc->table->addr_type != cf->peer->addr_type) + cf_error("Primary table and peer table must have the same type"); - if (p->mode == PIPE_OPAQUE) - { - P->main_source = rt_get_source(P, 0); - rt_lock_source(P->main_source); - } + if (cc->rx_limit.action) + cf_error("Pipe protocol does not support receive limits"); - return PS_UP; + if (cc->in_keep_filtered) + cf_error("Pipe protocol prohibits keeping filtered routes"); } -static void -pipe_cleanup(struct proto *P) +static int +pipe_configure_channels(struct pipe_proto *p, struct pipe_config *cf) { - struct pipe_proto *p = (struct pipe_proto *) P; - - bzero(&P->stats, sizeof(struct proto_stats)); - bzero(&p->peer_stats, sizeof(struct proto_stats)); - - P->main_ahook = NULL; - p->peer_ahook = NULL; - - if (p->mode == PIPE_OPAQUE) - rt_unlock_source(P->main_source); - P->main_source = NULL; - - rt_unlock_table(P->table); - rt_unlock_table(p->peer_table); + struct channel_config *cc = proto_cf_main_channel(&cf->c); + + struct channel_config pri_cf = { + .name = "pri", + .channel = cc->channel, + .table = cc->table, + .out_filter = cc->out_filter, + .in_limit = cc->in_limit, + .ra_mode = RA_ANY + }; + + struct channel_config sec_cf = { + .name = "sec", + .channel = cc->channel, + .table = cf->peer, + .out_filter = cc->in_filter, + .in_limit = cc->out_limit, + .ra_mode = RA_ANY + }; + + return + proto_configure_channel(&p->p, &p->pri, &pri_cf) && + proto_configure_channel(&p->p, &p->sec, &sec_cf); } -static void -pipe_postconfig(struct proto_config *C) +static struct proto * +pipe_init(struct proto_config *CF) { - struct pipe_config *c = (struct pipe_config *) C; + struct proto *P = proto_new(CF); + struct pipe_proto *p = (void *) P; + struct pipe_config *cf = (void *) CF; - if (!c->peer) - cf_error("Name of peer routing table not specified"); - if (c->peer == C->table) - cf_error("Primary table and peer table must be different"); + P->rt_notify = pipe_rt_notify; + P->import_control = pipe_import_control; + P->reload_routes = pipe_reload_routes; - if (C->in_keep_filtered) - cf_error("Pipe protocol prohibits keeping filtered routes"); - if (C->rx_limit) - cf_error("Pipe protocol does not support receive limits"); -} + pipe_configure_channels(p, cf); -extern int proto_reconfig_type; + return P; +} static int -pipe_reconfigure(struct proto *P, struct proto_config *new) +pipe_reconfigure(struct proto *P, struct proto_config *CF) { - struct pipe_proto *p = (struct pipe_proto *)P; - struct proto_config *old = P->cf; - struct pipe_config *oc = (struct pipe_config *) old; - struct pipe_config *nc = (struct pipe_config *) new; - - if ((oc->peer->table != nc->peer->table) || (oc->mode != nc->mode)) - return 0; - - /* Update output filters in ahooks */ - if (P->main_ahook) - { - P->main_ahook->out_filter = new->out_filter; - P->main_ahook->in_limit = new->in_limit; - proto_verify_limits(P->main_ahook); - } - - if (p->peer_ahook) - { - p->peer_ahook->out_filter = new->in_filter; - p->peer_ahook->in_limit = new->out_limit; - proto_verify_limits(p->peer_ahook); - } - - if ((P->proto_state != PS_UP) || (proto_reconfig_type == RECONFIG_SOFT)) - return 1; - - if ((new->preference != old->preference) - || ! filter_same(new->in_filter, old->in_filter) - || ! filter_same(new->out_filter, old->out_filter)) - proto_request_feeding(P); + struct pipe_proto *p = (void *) P; + struct pipe_config *cf = (void *) CF; - return 1; + return pipe_configure_channels(p, cf); } static void -pipe_copy_config(struct proto_config *dest, struct proto_config *src) +pipe_copy_config(struct proto_config *dest UNUSED, struct proto_config *src UNUSED) { /* Just a shallow copy, not many items here */ - proto_copy_rest(dest, src, sizeof(struct pipe_config)); } static void pipe_get_status(struct proto *P, byte *buf) { - struct pipe_proto *p = (struct pipe_proto *) P; + struct pipe_proto *p = (void *) P; - bsprintf(buf, "%c> %s", (p->mode == PIPE_OPAQUE) ? '-' : '=', p->peer_table->name); + bsprintf(buf, "%s <=> %s", p->pri->table->name, p->sec->table->name); } static void pipe_show_stats(struct pipe_proto *p) { - struct proto_stats *s1 = &p->p.stats; - struct proto_stats *s2 = &p->peer_stats; + struct proto_stats *s1 = &p->pri->stats; + struct proto_stats *s2 = &p->sec->stats; /* * Pipe stats (as anything related to pipes) are a bit tricky. There @@ -318,17 +252,16 @@ pipe_show_stats(struct pipe_proto *p) static void pipe_show_proto_info(struct proto *P) { - struct pipe_proto *p = (struct pipe_proto *) P; - struct pipe_config *cf = (struct pipe_config *) P->cf; + struct pipe_proto *p = (void *) P; - // cli_msg(-1006, " Table: %s", P->table->name); - // cli_msg(-1006, " Peer table: %s", p->peer_table->name); - cli_msg(-1006, " Preference: %d", P->preference); - cli_msg(-1006, " Input filter: %s", filter_name(cf->c.in_filter)); - cli_msg(-1006, " Output filter: %s", filter_name(cf->c.out_filter)); + 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 filter: %s", filter_name(p->sec->out_filter)); + cli_msg(-1006, " Export filter: %s", filter_name(p->pri->out_filter)); - proto_show_limit(cf->c.in_limit, "Import limit:"); - proto_show_limit(cf->c.out_limit, "Export limit:"); + channel_show_limit(&p->pri->in_limit, "Import limit:"); + channel_show_limit(&p->sec->in_limit, "Export limit:"); if (P->proto_state != PS_DOWN) pipe_show_stats(p); @@ -338,13 +271,10 @@ pipe_show_proto_info(struct proto *P) struct protocol proto_pipe = { .name = "Pipe", .template = "pipe%d", - .multitable = 1, - .preference = DEF_PREF_PIPE, + .proto_size = sizeof(struct pipe_proto), .config_size = sizeof(struct pipe_config), .postconfig = pipe_postconfig, .init = pipe_init, - .start = pipe_start, - .cleanup = pipe_cleanup, .reconfigure = pipe_reconfigure, .copy_config = pipe_copy_config, .get_status = pipe_get_status, diff --git a/proto/pipe/pipe.h b/proto/pipe/pipe.h index 50b31698..038c6666 100644 --- a/proto/pipe/pipe.h +++ b/proto/pipe/pipe.h @@ -9,27 +9,15 @@ #ifndef _BIRD_PIPE_H_ #define _BIRD_PIPE_H_ -#define PIPE_OPAQUE 0 -#define PIPE_TRANSPARENT 1 - struct pipe_config { struct proto_config c; struct rtable_config *peer; /* Table we're connected to */ - int mode; /* PIPE_OPAQUE or PIPE_TRANSPARENT */ }; struct pipe_proto { struct proto p; - struct rtable *peer_table; - struct announce_hook *peer_ahook; /* Announce hook for direction peer->primary */ - struct proto_stats peer_stats; /* Statistics for the direction peer->primary */ - int mode; /* PIPE_OPAQUE or PIPE_TRANSPARENT */ + struct channel *pri; + struct channel *sec; }; - -extern struct protocol proto_pipe; - -static inline int proto_is_pipe(struct proto *p) -{ return p->proto == &proto_pipe; } - #endif diff --git a/proto/radv/Makefile b/proto/radv/Makefile index efc4d4af..05317eff 100644 --- a/proto/radv/Makefile +++ b/proto/radv/Makefile @@ -1,5 +1,6 @@ -source=radv.c packets.c -root-rel=../../ -dir-name=proto/radv +src := packets.c radv.c +obj := $(src-o-files) +$(all-daemon) +$(cf-local) -include ../../Rules +tests_objs := $(tests_objs) $(src-o-files)
\ No newline at end of file diff --git a/proto/radv/config.Y b/proto/radv/config.Y index da300667..7ba23205 100644 --- a/proto/radv/config.Y +++ b/proto/radv/config.Y @@ -41,6 +41,7 @@ CF_ADDTO(proto, radv_proto) radv_proto_start: proto_start RADV { this_proto = proto_config_new(&proto_radv, $1); + init_list(&RADV_CFG->patt_list); init_list(&RADV_CFG->pref_list); init_list(&RADV_CFG->rdnss_list); @@ -49,15 +50,12 @@ radv_proto_start: proto_start RADV radv_proto_item: proto_item + | proto_channel | INTERFACE radv_iface | PREFIX radv_prefix { add_tail(&RADV_CFG->pref_list, NODE this_radv_prefix); } | RDNSS { init_list(&radv_dns_list); } radv_rdnss { add_tail_list(&RADV_CFG->rdnss_list, &radv_dns_list); } | DNSSL { init_list(&radv_dns_list); } radv_dnssl { add_tail_list(&RADV_CFG->dnssl_list, &radv_dns_list); } - | TRIGGER prefix { - RADV_CFG->trigger_prefix = $2.addr; - RADV_CFG->trigger_pxlen = $2.len; - RADV_CFG->trigger_valid = 1; - } + | TRIGGER net_ip6 { RADV_CFG->trigger = $2; } ; radv_proto_opts: @@ -148,11 +146,10 @@ radv_iface: radv_iface_start iface_patt_list_nopx radv_iface_opt_list radv_iface_finish; -radv_prefix_start: prefix +radv_prefix_start: net_ip6 { this_radv_prefix = cfg_allocz(sizeof(struct radv_prefix_config)); - RADV_PREFIX->prefix = $1.addr; - RADV_PREFIX->pxlen = $1.len; + RADV_PREFIX->prefix = *(net_addr_ip6 *) &($1); RADV_PREFIX->onlink = 1; RADV_PREFIX->autonomous = 1; diff --git a/proto/radv/packets.c b/proto/radv/packets.c index 4bf960c3..f4352155 100644 --- a/proto/radv/packets.c +++ b/proto/radv/packets.c @@ -38,7 +38,7 @@ struct radv_opt_prefix u32 valid_lifetime; u32 preferred_lifetime; u32 reserved; - ip_addr prefix; + ip6_addr prefix; }; #define OPT_PX_ONLINK 0x80 @@ -58,7 +58,7 @@ struct radv_opt_rdnss u8 length; u16 reserved; u32 lifetime; - ip_addr servers[]; + ip6_addr servers[]; }; struct radv_opt_dnssl @@ -90,11 +90,11 @@ radv_prefix_match(struct radv_iface *ifa, struct ifa *a) return NULL; WALK_LIST(pc, ifa->cf->pref_list) - if ((a->pxlen >= pc->pxlen) && ipa_in_net(a->prefix, pc->prefix, pc->pxlen)) + if (net_in_netX(&a->prefix, (net_addr *) &pc->prefix)) return pc; WALK_LIST(pc, cf->pref_list) - if ((a->pxlen >= pc->pxlen) && ipa_in_net(a->prefix, pc->prefix, pc->pxlen)) + if (net_in_netX(&a->prefix, (net_addr *) &pc->prefix)) return pc; return &default_prefix; @@ -109,7 +109,7 @@ radv_prepare_rdnss(struct radv_iface *ifa, list *rdnss_list, char **buf, char *b { struct radv_rdnss_config *rcf_base = rcf; struct radv_opt_rdnss *op = (void *) *buf; - int max_i = (bufend - *buf - sizeof(struct radv_opt_rdnss)) / sizeof(ip_addr); + int max_i = (bufend - *buf - sizeof(struct radv_opt_rdnss)) / sizeof(ip6_addr); int i = 0; if (max_i < 1) @@ -123,20 +123,19 @@ radv_prepare_rdnss(struct radv_iface *ifa, list *rdnss_list, char **buf, char *b else op->lifetime = htonl(rcf->lifetime); - while(NODE_VALID(rcf) && + while(NODE_VALID(rcf) && (rcf->lifetime == rcf_base->lifetime) && (rcf->lifetime_mult == rcf_base->lifetime_mult)) { if (i >= max_i) goto too_much; - op->servers[i] = rcf->server; - ipa_hton(op->servers[i]); + op->servers[i] = ip6_hton(rcf->server); i++; rcf = NODE_NEXT(rcf); } - + op->length = 1+2*i; *buf += 8 * op->length; } @@ -273,6 +272,9 @@ radv_prepare_ra(struct radv_iface *ifa) struct ifa *addr; WALK_LIST(addr, ifa->iface->addrs) { + if (addr->prefix.type != NET_IP6) + continue; + struct radv_prefix_config *pc; pc = radv_prefix_match(ifa, addr); @@ -288,7 +290,7 @@ radv_prepare_ra(struct radv_iface *ifa) struct radv_opt_prefix *op = (void *) buf; op->type = OPT_PREFIX; op->length = 4; - op->pxlen = addr->pxlen; + op->pxlen = net6_pxlen(&addr->prefix); op->flags = (pc->onlink ? OPT_PX_ONLINK : 0) | (pc->autonomous ? OPT_PX_AUTONOMOUS : 0); op->valid_lifetime = (ra->active || !pc->valid_lifetime_sensitive) ? @@ -296,8 +298,7 @@ radv_prepare_ra(struct radv_iface *ifa) op->preferred_lifetime = (ra->active || !pc->preferred_lifetime_sensitive) ? htonl(pc->preferred_lifetime) : 0; op->reserved = 0; - op->prefix = addr->prefix; - ipa_hton(op->prefix); + op->prefix = ip6_hton(net6_prefix(&addr->prefix)); buf += sizeof(*op); } @@ -409,6 +410,7 @@ radv_sk_open(struct radv_iface *ifa) { sock *sk = sk_new(ifa->ra->p.pool); sk->type = SK_IP; + sk->subtype = SK_IPV6; sk->dport = ICMPV6_PROTO; sk->saddr = ifa->addr->ip; diff --git a/proto/radv/radv.c b/proto/radv/radv.c index 5c52217d..d7d975ab 100644 --- a/proto/radv/radv.c +++ b/proto/radv/radv.c @@ -143,7 +143,7 @@ find_lladdr(struct iface *iface) { struct ifa *a; WALK_LIST(a, iface->addrs) - if (a->scope == SCOPE_LINK) + if ((a->prefix.type == NET_IP6) && (a->scope == SCOPE_LINK)) return a; return NULL; @@ -256,11 +256,16 @@ radv_ifa_notify(struct proto *p, unsigned flags UNUSED, struct ifa *a) radv_iface_notify(ifa, RA_EV_CHANGE); } -static inline int radv_net_match_trigger(struct radv_config *cf, net *n) +static inline int +radv_trigger_valid(struct radv_config *cf) { - return cf->trigger_valid && - (n->n.pxlen == cf->trigger_pxlen) && - ipa_equal(n->n.prefix, cf->trigger_prefix); + return cf->trigger.type != 0; +} + +static inline int +radv_net_match_trigger(struct radv_config *cf, net *n) +{ + return radv_trigger_valid(cf) && net_equal(n->n.addr, &cf->trigger); } int @@ -276,7 +281,7 @@ radv_import_control(struct proto *p, rte **new, ea_list **attrs UNUSED, struct l } static void -radv_rt_notify(struct proto *p, rtable *tbl UNUSED, net *n, rte *new, rte *old UNUSED, ea_list *attrs UNUSED) +radv_rt_notify(struct proto *p, struct channel *ch UNUSED, net *n, rte *new, rte *old UNUSED, ea_list *attrs UNUSED) { struct proto_radv *ra = (struct proto_radv *) p; struct radv_config *cf = (struct radv_config *) (p->cf); @@ -303,23 +308,35 @@ radv_check_active(struct proto_radv *ra) { struct radv_config *cf = (struct radv_config *) (ra->p.cf); - if (! cf->trigger_valid) + if (!radv_trigger_valid(cf)) return 1; - return rt_examine(ra->p.table, cf->trigger_prefix, cf->trigger_pxlen, - &(ra->p), ra->p.cf->out_filter); + struct channel *c = ra->p.main_channel; + return rt_examine(c->table, &cf->trigger, &ra->p, c->out_filter); +} + +static void +radv_postconfig(struct proto_config *CF) +{ + // struct radv_config *cf = (void *) CF; + + /* Define default channel */ + if (EMPTY_LIST(CF->channels)) + channel_config_new(NULL, NET_IP6, CF); } static struct proto * -radv_init(struct proto_config *c) +radv_init(struct proto_config *CF) { - struct proto *p = proto_new(c, sizeof(struct proto_radv)); + struct proto *p = proto_new(CF); + + p->main_channel = proto_add_channel(p, proto_cf_main_channel(CF)); - p->accept_ra_types = RA_OPTIMAL; p->import_control = radv_import_control; p->rt_notify = radv_rt_notify; p->if_notify = radv_if_notify; p->ifa_notify = radv_ifa_notify; + return p; } @@ -330,7 +347,7 @@ radv_start(struct proto *p) struct radv_config *cf = (struct radv_config *) (p->cf); init_list(&(ra->iface_list)); - ra->active = !cf->trigger_valid; + ra->active = !radv_trigger_valid(cf); return PS_UP; } @@ -355,11 +372,11 @@ radv_shutdown(struct proto *p) } static int -radv_reconfigure(struct proto *p, struct proto_config *c) +radv_reconfigure(struct proto *p, struct proto_config *CF) { struct proto_radv *ra = (struct proto_radv *) p; // struct radv_config *old = (struct radv_config *) (p->cf); - struct radv_config *new = (struct radv_config *) c; + struct radv_config *new = (struct radv_config *) CF; /* * The question is why there is a reconfigure function for RAdv if @@ -369,7 +386,10 @@ radv_reconfigure(struct proto *p, struct proto_config *c) * causing nodes to temporary remove their default routes. */ - p->cf = c; /* radv_check_active() requires proper p->cf */ + if (!proto_configure_channel(p, &p->main_channel, proto_cf_main_channel(CF))) + return 0; + + p->cf = CF; /* radv_check_active() requires proper p->cf */ ra->active = radv_check_active(ra); struct iface *iface; @@ -426,7 +446,10 @@ radv_get_status(struct proto *p, byte *buf) struct protocol proto_radv = { .name = "RAdv", .template = "radv%d", + .channel_mask = NB_IP6, + .proto_size = sizeof(struct proto_radv), .config_size = sizeof(struct radv_config), + .postconfig = radv_postconfig, .init = radv_init, .start = radv_start, .shutdown = radv_shutdown, diff --git a/proto/radv/radv.h b/proto/radv/radv.h index e2bf07ba..735aa218 100644 --- a/proto/radv/radv.h +++ b/proto/radv/radv.h @@ -13,7 +13,7 @@ #include "lib/ip.h" #include "lib/lists.h" #include "lib/socket.h" -#include "lib/timer.h" +#include "sysdep/unix/timer.h" #include "lib/resource.h" #include "nest/protocol.h" #include "nest/iface.h" @@ -50,9 +50,7 @@ struct radv_config list rdnss_list; /* Global list of RDNSS configs (struct radv_rdnss_config) */ list dnssl_list; /* Global list of DNSSL configs (struct radv_dnssl_config) */ - ip_addr trigger_prefix; /* Prefix of a trigger route, if defined */ - u8 trigger_pxlen; /* Pxlen of a trigger route, if defined */ - u8 trigger_valid; /* Whether a trigger route is defined */ + net_addr trigger; /* Prefix of a trigger route, if defined */ }; struct radv_iface_config @@ -83,8 +81,7 @@ struct radv_iface_config struct radv_prefix_config { node n; - ip_addr prefix; - uint pxlen; + net_addr_ip6 prefix; u8 skip; /* Do not include this prefix to RA */ u8 onlink; /* Standard options from RFC 4261 */ @@ -100,7 +97,7 @@ struct radv_rdnss_config node n; u32 lifetime; /* Valid if lifetime_mult is 0 */ u16 lifetime_mult; /* Lifetime specified as multiple of max_ra_int */ - ip_addr server; /* IP address of recursive DNS server */ + ip6_addr server; /* IP address of recursive DNS server */ }; struct radv_dnssl_config diff --git a/proto/rip/Makefile b/proto/rip/Makefile index d2d3c987..7feabcd8 100644 --- a/proto/rip/Makefile +++ b/proto/rip/Makefile @@ -1,5 +1,6 @@ -source=rip.c packets.c -root-rel=../../ -dir-name=proto/rip +src := packets.c rip.c +obj := $(src-o-files) +$(all-daemon) +$(cf-local) -include ../../Rules +tests_objs := $(tests_objs) $(src-o-files)
\ No newline at end of file diff --git a/proto/rip/config.Y b/proto/rip/config.Y index 4ec45c7a..25c2ac7a 100644 --- a/proto/rip/config.Y +++ b/proto/rip/config.Y @@ -32,32 +32,38 @@ rip_check_auth(void) CF_DECLS -CF_KEYWORDS(RIP, ECMP, LIMIT, WEIGHT, INFINITY, METRIC, UPDATE, TIMEOUT, +CF_KEYWORDS(RIP, NG, ECMP, LIMIT, WEIGHT, INFINITY, METRIC, UPDATE, TIMEOUT, GARBAGE, PORT, ADDRESS, MODE, BROADCAST, MULTICAST, PASSIVE, VERSION, SPLIT, HORIZON, POISON, REVERSE, CHECK, ZERO, TIME, BFD, AUTHENTICATION, NONE, PLAINTEXT, CRYPTOGRAPHIC, MD5, TTL, SECURITY, RX, TX, BUFFER, LENGTH, PRIORITY, ONLY, LINK, RIP_METRIC, RIP_TAG) -%type <i> rip_auth +%type <i> rip_variant rip_auth CF_GRAMMAR CF_ADDTO(proto, rip_proto) -rip_proto_start: proto_start RIP +rip_variant: + RIP { $$ = 1; } + | RIP NG { $$ = 0; } + ; + +rip_proto_start: proto_start rip_variant { this_proto = proto_config_new(&proto_rip, $1); - init_list(&RIP_CFG->patt_list); + this_proto->net_type = $2 ? NET_IP4 : NET_IP6; - RIP_CFG->rip2 = RIP_IS_V2; + init_list(&RIP_CFG->patt_list); + RIP_CFG->rip2 = $2; RIP_CFG->infinity = RIP_DEFAULT_INFINITY; - RIP_CFG->min_timeout_time = 60; RIP_CFG->max_garbage_time = 60; }; rip_proto_item: proto_item + | proto_channel | ECMP bool { RIP_CFG->ecmp = $2 ? RIP_DEFAULT_ECMP_LIMIT : 0; } | ECMP bool LIMIT expr { RIP_CFG->ecmp = $2 ? $4 : 0; if ($4 < 0) cf_error("ECMP limit cannot be negative"); } | INFINITY expr { RIP_CFG->infinity = $2; } @@ -131,7 +137,7 @@ rip_iface_item: | MODE MULTICAST { RIP_IFACE->mode = RIP_IM_MULTICAST; } | MODE BROADCAST { RIP_IFACE->mode = RIP_IM_BROADCAST; if (rip_cfg_is_ng()) cf_error("Broadcast not supported in RIPng"); } | PASSIVE bool { RIP_IFACE->passive = $2; } - | ADDRESS ipa { RIP_IFACE->address = $2; } + | ADDRESS ipa { RIP_IFACE->address = $2; if (ipa_is_ip4($2) != rip_cfg_is_v2()) cf_error("IP address version mismatch"); } | PORT expr { RIP_IFACE->port = $2; if (($2<1) || ($2>65535)) cf_error("Invalid port number"); } | VERSION expr { RIP_IFACE->version = $2; if (rip_cfg_is_ng()) cf_error("Version not supported in RIPng"); diff --git a/proto/rip/packets.c b/proto/rip/packets.c index 468927e6..9dc492b7 100644 --- a/proto/rip/packets.c +++ b/proto/rip/packets.c @@ -9,6 +9,8 @@ * Can be freely distributed and used under the terms of the GNU GPL. */ +#undef LOCAL_DEBUG + #include "rip.h" #include "lib/mac.h" @@ -76,8 +78,7 @@ struct rip_auth_tail /* Internal representation of RTE block data */ struct rip_block { - ip_addr prefix; - int pxlen; + net_addr net; u32 metric; u16 tag; u16 no_af; @@ -106,30 +107,30 @@ static inline uint rip_pkt_hdrlen(struct rip_iface *ifa) { return sizeof(struct rip_packet) + (ifa->cf->auth_type ? RIP_BLOCK_LENGTH : 0); } static inline void -rip_put_block(struct rip_proto *p UNUSED4 UNUSED6, byte *pos, struct rip_block *rte) +rip_put_block(struct rip_proto *p, byte *pos, struct rip_block *rte) { if (rip_is_v2(p)) { struct rip_block_v2 *block = (void *) pos; block->family = rte->no_af ? 0 : htons(RIP_AF_IPV4); block->tag = htons(rte->tag); - block->network = ip4_hton(ipa_to_ip4(rte->prefix)); - block->netmask = ip4_hton(ip4_mkmask(rte->pxlen)); + block->network = ip4_hton(net4_prefix(&rte->net)); + block->netmask = ip4_hton(ip4_mkmask(net4_pxlen(&rte->net))); block->next_hop = ip4_hton(ipa_to_ip4(rte->next_hop)); block->metric = htonl(rte->metric); } else /* RIPng */ { struct rip_block_ng *block = (void *) pos; - block->prefix = ip6_hton(ipa_to_ip6(rte->prefix)); + block->prefix = ip6_hton(net6_prefix(&rte->net)); block->tag = htons(rte->tag); - block->pxlen = rte->pxlen; + block->pxlen = net6_pxlen(&rte->net); block->metric = rte->metric; } } static inline void -rip_put_next_hop(struct rip_proto *p UNUSED, byte *pos, struct rip_block *rte UNUSED4) +rip_put_next_hop(struct rip_proto *p UNUSED, byte *pos, struct rip_block *rte) { struct rip_block_ng *block = (void *) pos; block->prefix = ip6_hton(ipa_to_ip6(rte->next_hop)); @@ -139,7 +140,7 @@ rip_put_next_hop(struct rip_proto *p UNUSED, byte *pos, struct rip_block *rte UN } static inline int -rip_get_block(struct rip_proto *p UNUSED4 UNUSED6, byte *pos, struct rip_block *rte) +rip_get_block(struct rip_proto *p, byte *pos, struct rip_block *rte) { if (rip_is_v2(p)) { @@ -149,8 +150,8 @@ rip_get_block(struct rip_proto *p UNUSED4 UNUSED6, byte *pos, struct rip_block * if (block->family != (rte->no_af ? 0 : htons(RIP_AF_IPV4))) return 0; - rte->prefix = ipa_from_ip4(ip4_ntoh(block->network)); - rte->pxlen = ip4_masklen(ip4_ntoh(block->netmask)); + uint pxlen = ip4_masklen(ip4_ntoh(block->netmask)); + net_fill_ip4(&rte->net, ip4_ntoh(block->network), pxlen); rte->metric = ntohl(block->metric); rte->tag = ntohs(block->tag); rte->next_hop = ipa_from_ip4(ip4_ntoh(block->next_hop)); @@ -169,8 +170,8 @@ rip_get_block(struct rip_proto *p UNUSED4 UNUSED6, byte *pos, struct rip_block * return 0; } - rte->prefix = ipa_from_ip6(ip6_ntoh(block->prefix)); - rte->pxlen = block->pxlen; + uint pxlen = (block->pxlen <= IP6_MAX_PREFIX_LENGTH) ? block->pxlen : 255; + net_fill_ip6(&rte->net, ip6_ntoh(block->prefix), pxlen); rte->metric = block->metric; rte->tag = ntohs(block->tag); /* rte->next_hop is deliberately kept unmodified */; @@ -406,8 +407,9 @@ rip_receive_request(struct rip_proto *p, struct rip_iface *ifa, struct rip_packe if (!rip_get_block(p, pos, &b)) return; - /* Special case - zero prefix, infinity metric */ - if (ipa_nonzero(b.prefix) || b.pxlen || (b.metric != p->infinity)) + /* Special case - infinity metric, for RIPng also zero prefix */ + if ((b.metric != p->infinity) || + (rip_is_ng(p) && !net_zero_ip6((net_addr_ip6 *) &b.net))) return; /* We do nothing if TX is already active */ @@ -440,10 +442,8 @@ rip_send_response(struct rip_proto *p, struct rip_iface *ifa) pkt->unused = 0; pos += rip_pkt_hdrlen(ifa); - FIB_ITERATE_START(&p->rtable, &ifa->tx_fit, z) + FIB_ITERATE_START(&p->rtable, &ifa->tx_fit, struct rip_entry, en) { - struct rip_entry *en = (struct rip_entry *) z; - /* Dummy entries */ if (!en->valid) goto next_entry; @@ -460,28 +460,28 @@ rip_send_response(struct rip_proto *p, struct rip_iface *ifa) /* Not enough space for current entry */ if (pos > max) { - FIB_ITERATE_PUT(&ifa->tx_fit, z); + FIB_ITERATE_PUT(&ifa->tx_fit); goto break_loop; } struct rip_block rte = { - .prefix = en->n.prefix, - .pxlen = en->n.pxlen, .metric = en->metric, .tag = en->tag }; + net_copy(&rte.net, en->n.addr); + if (en->iface == ifa->iface) rte.next_hop = en->next_hop; if (rip_is_v2(p) && (ifa->cf->version == RIP_V1)) { /* Skipping subnets (i.e. not hosts, classful networks or default route) */ - if (ip4_masklen(ip4_class_mask(ipa_to_ip4(en->n.prefix))) != en->n.pxlen) + if (ip4_masklen(ip4_class_mask(net4_prefix(&rte.net))) != rte.net.pxlen) goto next_entry; rte.tag = 0; - rte.pxlen = 0; + rte.net.pxlen = 0; rte.next_hop = IPA_NONE; } @@ -497,7 +497,7 @@ rip_send_response(struct rip_proto *p, struct rip_iface *ifa) goto next_entry; } - // TRACE(D_PACKETS, " %I/%d -> %I metric %d", rte.prefix, rte.pxlen, rte.next_hop, rte.metric); + // TRACE(D_PACKETS, " %N -> %I metric %d", &rte.net, rte.next_hop, rte.metric); /* RIPng next hop entry */ if (rip_is_ng(p) && !ipa_equal(rte.next_hop, last_next_hop)) @@ -513,7 +513,7 @@ rip_send_response(struct rip_proto *p, struct rip_iface *ifa) next_entry: ; } - FIB_ITERATE_END(z); + FIB_ITERATE_END; ifa->tx_active = 0; /* Do not send empty packet */ @@ -542,7 +542,7 @@ break_loop: void rip_send_table(struct rip_proto *p, struct rip_iface *ifa, ip_addr addr, bird_clock_t changed) { - DBG("RIP: Opening TX session to %I on %s\n", dst, ifa->iface->name); + DBG("RIP: Opening TX session to %I on %s\n", addr, ifa->iface->name); rip_reset_tx_session(p, ifa); @@ -598,23 +598,25 @@ rip_receive_response(struct rip_proto *p, struct rip_iface *ifa, struct rip_pack if (!rip_get_block(p, pos, &rte)) continue; - int c = ipa_classify_net(rte.prefix); - if ((c < 0) || !(c & IADDR_HOST) || ((c & IADDR_SCOPE_MASK) <= SCOPE_LINK)) - SKIP("invalid prefix"); - if (rip_is_v2(p) && (pkt->version == RIP_V1)) { - if (ifa->cf->check_zero && (rte.tag || rte.pxlen || ipa_nonzero(rte.next_hop))) + if (ifa->cf->check_zero && (rte.tag || rte.net.pxlen || ipa_nonzero(rte.next_hop))) SKIP("RIPv1 reserved field is nonzero"); rte.tag = 0; - rte.pxlen = ip4_masklen(ip4_class_mask(ipa_to_ip4(rte.prefix))); + rte.net.pxlen = ip4_masklen(ip4_class_mask(net4_prefix(&rte.net))); rte.next_hop = IPA_NONE; } - if ((rte.pxlen < 0) || (rte.pxlen > MAX_PREFIX_LENGTH)) + if (rte.net.pxlen == 255) SKIP("invalid prefix length"); + net_normalize(&rte.net); + + int c = net_classify(&rte.net); + if ((c < 0) || !(c & IADDR_HOST) || ((c & IADDR_SCOPE_MASK) <= SCOPE_LINK)) + SKIP("invalid prefix"); + if (rte.metric > p->infinity) SKIP("invalid metric"); @@ -625,7 +627,7 @@ rip_receive_response(struct rip_proto *p, struct rip_iface *ifa, struct rip_pack rte.next_hop = IPA_NONE; } - // TRACE(D_PACKETS, " %I/%d -> %I metric %d", rte.prefix, rte.pxlen, rte.next_hop, rte.metric); + // TRACE(D_PACKETS, " %N -> %I metric %d", &rte.net.n, rte.next_hop, rte.metric); rte.metric += ifa->cf->metric; @@ -639,16 +641,16 @@ rip_receive_response(struct rip_proto *p, struct rip_iface *ifa, struct rip_pack .expires = now + ifa->cf->timeout_time }; - rip_update_rte(p, &rte.prefix, rte.pxlen, &new); + rip_update_rte(p, &rte.net, &new); } else - rip_withdraw_rte(p, &rte.prefix, rte.pxlen, from); + rip_withdraw_rte(p, &rte.net, from); continue; skip: - LOG_RTE("Ignoring route %I/%d received from %I - %s", - rte.prefix, rte.pxlen, from->nbr->addr, err_dsc); + LOG_RTE("Ignoring route %N received from %I - %s", + &rte.net, from->nbr->addr, err_dsc); } } @@ -736,6 +738,7 @@ rip_open_socket(struct rip_iface *ifa) sock *sk = sk_new(p->p.pool); sk->type = SK_UDP; + sk->subtype = rip_is_v2(p) ? SK_IPV4 : SK_IPV6; sk->sport = ifa->cf->port; sk->dport = ifa->cf->port; sk->iface = ifa->iface; diff --git a/proto/rip/rip.c b/proto/rip/rip.c index 7b380097..157093aa 100644 --- a/proto/rip/rip.c +++ b/proto/rip/rip.c @@ -92,15 +92,6 @@ static void rip_trigger_update(struct rip_proto *p); * RIP routes */ -static void -rip_init_entry(struct fib_node *fn) -{ - // struct rip_entry *en = (void) *fn; - - const uint offset = OFFSETOF(struct rip_entry, routes); - memset((byte *)fn + offset, 0, sizeof(struct rip_entry) - offset); -} - static struct rip_rte * rip_add_rte(struct rip_proto *p, struct rip_rte **rp, struct rip_rte *src) { @@ -152,27 +143,20 @@ rip_announce_rte(struct rip_proto *p, struct rip_entry *en) if (rt) { /* Update */ - net *n = net_get(p->p.table, en->n.prefix, en->n.pxlen); - rta a0 = { .src = p->p.main_source, .source = RTS_RIP, .scope = SCOPE_UNIVERSE, - .cast = RTC_UNICAST + .dest = RTD_UNICAST, }; u8 rt_metric = rt->metric; u16 rt_tag = rt->tag; - struct rip_rte *rt2 = rt->next; - - /* Find second valid rte */ - while (rt2 && !rip_valid_rte(rt2)) - rt2 = rt2->next; - if (p->ecmp && rt2) + if (p->ecmp) { /* ECMP route */ - struct mpnh *nhs = NULL; + struct nexthop *nhs = NULL; int num = 0; for (rt = en->routes; rt && (num < p->ecmp); rt = rt->next) @@ -180,54 +164,51 @@ rip_announce_rte(struct rip_proto *p, struct rip_entry *en) if (!rip_valid_rte(rt)) continue; - struct mpnh *nh = alloca(sizeof(struct mpnh)); + struct nexthop *nh = allocz(sizeof(struct nexthop)); + nh->gw = rt->next_hop; nh->iface = rt->from->nbr->iface; nh->weight = rt->from->ifa->cf->ecmp_weight; - mpnh_insert(&nhs, nh); + + nexthop_insert(&nhs, nh); num++; if (rt->tag != rt_tag) rt_tag = 0; } - a0.dest = RTD_MULTIPATH; - a0.nexthops = nhs; + a0.nh = *nhs; } else { /* Unipath route */ - a0.dest = RTD_ROUTER; - a0.gw = rt->next_hop; - a0.iface = rt->from->nbr->iface; a0.from = rt->from->nbr->addr; + a0.nh.gw = rt->next_hop; + a0.nh.iface = rt->from->nbr->iface; } rta *a = rta_lookup(&a0); rte *e = rte_get_temp(a); - e->u.rip.from = a0.iface; + e->u.rip.from = a0.nh.iface; e->u.rip.metric = rt_metric; e->u.rip.tag = rt_tag; - e->net = n; e->pflags = 0; - rte_update(&p->p, n, e); + rte_update(&p->p, en->n.addr, e); } else { /* Withdraw */ - net *n = net_find(p->p.table, en->n.prefix, en->n.pxlen); - rte_update(&p->p, n, NULL); + rte_update(&p->p, en->n.addr, NULL); } } /** * rip_update_rte - enter a route update to RIP routing table * @p: RIP instance - * @prefix: network prefix - * @pxlen: network prefix length + * @addr: network address * @new: a &rip_rte representing the new route * * The function is called by the RIP packet processing code whenever it receives @@ -237,9 +218,9 @@ rip_announce_rte(struct rip_proto *p, struct rip_entry *en) * rip_withdraw_rte() should be called instead of rip_update_rte(). */ void -rip_update_rte(struct rip_proto *p, ip_addr *prefix, int pxlen, struct rip_rte *new) +rip_update_rte(struct rip_proto *p, net_addr *n, struct rip_rte *new) { - struct rip_entry *en = fib_get(&p->rtable, prefix, pxlen); + struct rip_entry *en = fib_get(&p->rtable, n); struct rip_rte *rt, **rp; int changed = 0; @@ -279,8 +260,7 @@ rip_update_rte(struct rip_proto *p, ip_addr *prefix, int pxlen, struct rip_rte * /** * rip_withdraw_rte - enter a route withdraw to RIP routing table * @p: RIP instance - * @prefix: network prefix - * @pxlen: network prefix length + * @addr: network address * @from: a &rip_neighbor propagating the withdraw * * The function is called by the RIP packet processing code whenever it receives @@ -288,9 +268,9 @@ rip_update_rte(struct rip_proto *p, ip_addr *prefix, int pxlen, struct rip_rte * * removed. Eventually, the change is also propagated by rip_announce_rte(). */ void -rip_withdraw_rte(struct rip_proto *p, ip_addr *prefix, int pxlen, struct rip_neighbor *from) +rip_withdraw_rte(struct rip_proto *p, net_addr *n, struct rip_neighbor *from) { - struct rip_entry *en = fib_find(&p->rtable, prefix, pxlen); + struct rip_entry *en = fib_find(&p->rtable, n); struct rip_rte *rt, **rp; if (!en) @@ -317,7 +297,7 @@ rip_withdraw_rte(struct rip_proto *p, ip_addr *prefix, int pxlen, struct rip_nei * it into our data structures. */ static void -rip_rt_notify(struct proto *P, struct rtable *table UNUSED, struct network *net, struct rte *new, +rip_rt_notify(struct proto *P, struct channel *ch UNUSED, struct network *net, struct rte *new, struct rte *old UNUSED, struct ea_list *attrs) { struct rip_proto *p = (struct rip_proto *) P; @@ -332,15 +312,15 @@ rip_rt_notify(struct proto *P, struct rtable *table UNUSED, struct network *net, if (rt_metric > p->infinity) { - log(L_WARN "%s: Invalid rip_metric value %u for route %I/%d", - p->p.name, rt_metric, net->n.prefix, net->n.pxlen); + log(L_WARN "%s: Invalid rip_metric value %u for route %N", + p->p.name, rt_metric, net->n.addr); rt_metric = p->infinity; } if (rt_tag > 0xffff) { - log(L_WARN "%s: Invalid rip_tag value %u for route %I/%d", - p->p.name, rt_tag, net->n.prefix, net->n.pxlen); + log(L_WARN "%s: Invalid rip_tag value %u for route %N", + p->p.name, rt_tag, net->n.addr); rt_metric = p->infinity; rt_tag = 0; } @@ -352,7 +332,7 @@ rip_rt_notify(struct proto *P, struct rtable *table UNUSED, struct network *net, * collection. */ - en = fib_get(&p->rtable, &net->n.prefix, net->n.pxlen); + en = fib_get(&p->rtable, net->n.addr); old_metric = en->valid ? en->metric : -1; @@ -360,13 +340,13 @@ rip_rt_notify(struct proto *P, struct rtable *table UNUSED, struct network *net, en->metric = rt_metric; en->tag = rt_tag; en->from = (new->attrs->src->proto == P) ? new->u.rip.from : NULL; - en->iface = new->attrs->iface; - en->next_hop = new->attrs->gw; + en->iface = new->attrs->nh.iface; + en->next_hop = new->attrs->nh.gw; } else { /* Withdraw */ - en = fib_find(&p->rtable, &net->n.prefix, net->n.pxlen); + en = fib_find(&p->rtable, net->n.addr); if (!en || en->valid != RIP_ENTRY_VALID) return; @@ -830,9 +810,8 @@ rip_timer(timer *t) FIB_ITERATE_INIT(&fit, &p->rtable); loop: - FIB_ITERATE_START(&p->rtable, &fit, node) + FIB_ITERATE_START(&p->rtable, &fit, struct rip_entry, en) { - struct rip_entry *en = (struct rip_entry *) node; struct rip_rte *rt, **rp; int changed = 0; @@ -859,7 +838,7 @@ rip_timer(timer *t) * rip_rt_notify() -> p->rtable change, invalidating hidden variables. */ - FIB_ITERATE_PUT_NEXT(&fit, &p->rtable, node); + FIB_ITERATE_PUT_NEXT(&fit, &p->rtable); rip_announce_rte(p, en); goto loop; } @@ -871,7 +850,7 @@ rip_timer(timer *t) if (expires <= now) { - // TRACE(D_EVENTS, "entry is too old: %I/%d", en->n.prefix, en->n.pxlen); + // TRACE(D_EVENTS, "entry is too old: %N", en->n.addr); en->valid = 0; } else @@ -881,12 +860,12 @@ rip_timer(timer *t) /* Remove empty nodes */ if (!en->valid && !en->routes) { - FIB_ITERATE_PUT(&fit, node); - fib_delete(&p->rtable, node); + FIB_ITERATE_PUT(&fit); + fib_delete(&p->rtable, en); goto loop; } } - FIB_ITERATE_END(node); + FIB_ITERATE_END; p->rt_reload = 0; @@ -1035,19 +1014,17 @@ rip_import_control(struct proto *P UNUSED, struct rte **rt, struct ea_list **att return 0; } -static int -rip_reload_routes(struct proto *P) +static void +rip_reload_routes(struct channel *C) { - struct rip_proto *p = (struct rip_proto *) P; + struct rip_proto *p = (struct rip_proto *) C->proto; if (p->rt_reload) - return 1; + return; TRACE(D_EVENTS, "Scheduling route reload"); p->rt_reload = 1; rip_kick_timer(p); - - return 1; } static struct ea_list * @@ -1078,12 +1055,23 @@ rip_rte_same(struct rte *new, struct rte *old) } +static void +rip_postconfig(struct proto_config *CF) +{ + // struct rip_config *cf = (void *) CF; + + /* Define default channel */ + if (EMPTY_LIST(CF->channels)) + channel_config_new(NULL, CF->net_type, CF); +} + static struct proto * -rip_init(struct proto_config *cfg) +rip_init(struct proto_config *CF) { - struct proto *P = proto_new(cfg, sizeof(struct rip_proto)); + struct proto *P = proto_new(CF); + + P->main_channel = proto_add_channel(P, proto_cf_main_channel(CF)); - P->accept_ra_types = RA_OPTIMAL; P->if_notify = rip_if_notify; P->rt_notify = rip_rt_notify; P->neigh_notify = rip_neigh_notify; @@ -1104,10 +1092,12 @@ rip_start(struct proto *P) struct rip_config *cf = (void *) (P->cf); init_list(&p->iface_list); - fib_init(&p->rtable, P->pool, sizeof(struct rip_entry), 0, rip_init_entry); + fib_init(&p->rtable, P->pool, cf->rip2 ? NET_IP4 : NET_IP6, + sizeof(struct rip_entry), OFFSETOF(struct rip_entry, n), 0, NULL); p->rte_slab = sl_new(P->pool, sizeof(struct rip_rte)); p->timer = tm_new_set(P->pool, rip_timer, p, 0, 0); + p->rip2 = cf->rip2; p->ecmp = cf->ecmp; p->infinity = cf->infinity; p->triggered = 0; @@ -1121,18 +1111,24 @@ rip_start(struct proto *P) } static int -rip_reconfigure(struct proto *P, struct proto_config *c) +rip_reconfigure(struct proto *P, struct proto_config *CF) { struct rip_proto *p = (void *) P; - struct rip_config *new = (void *) c; + struct rip_config *new = (void *) CF; // struct rip_config *old = (void *) (P->cf); + if (new->rip2 != p->rip2) + return 0; + if (new->infinity != p->infinity) return 0; + if (!proto_configure_channel(P, &P->main_channel, proto_cf_main_channel(CF))) + return 0; + TRACE(D_EVENTS, "Reconfiguring"); - p->p.cf = c; + p->p.cf = CF; p->ecmp = new->ecmp; rip_reconfigure_ifaces(p, new); @@ -1250,11 +1246,10 @@ rip_dump(struct proto *P) int i; i = 0; - FIB_WALK(&p->rtable, e) + FIB_WALK(&p->rtable, struct rip_entry, en) { - struct rip_entry *en = (struct rip_entry *) e; - debug("RIP: entry #%d: %I/%d via %I dev %s valid %d metric %d age %d s\n", - i++, en->n.prefix, en->n.pxlen, en->next_hop, en->iface->name, + debug("RIP: entry #%d: %N via %I dev %s valid %d metric %d age %d s\n", + i++, en->n.addr, en->next_hop, en->iface->name, en->valid, en->metric, now - en->changed); } FIB_WALK_END; @@ -1274,7 +1269,10 @@ struct protocol proto_rip = { .template = "rip%d", .attr_class = EAP_RIP, .preference = DEF_PREF_RIP, + .channel_mask = NB_IP, + .proto_size = sizeof(struct rip_proto), .config_size = sizeof(struct rip_config), + .postconfig = rip_postconfig, .init = rip_init, .dump = rip_dump, .start = rip_start, diff --git a/proto/rip/rip.h b/proto/rip/rip.h index b24d9536..03dc9138 100644 --- a/proto/rip/rip.h +++ b/proto/rip/rip.h @@ -24,15 +24,9 @@ #include "lib/resource.h" #include "lib/socket.h" #include "lib/string.h" -#include "lib/timer.h" +#include "sysdep/unix/timer.h" -#ifdef IPV6 -#define RIP_IS_V2 0 -#else -#define RIP_IS_V2 1 -#endif - #define RIP_V1 1 #define RIP_V2 2 @@ -98,6 +92,7 @@ struct rip_proto slab *rte_slab; /* Slab for internal routes (struct rip_rte) */ timer *timer; /* Main protocol timer */ + u8 rip2; /* RIPv2 (IPv4) or RIPng (IPv6) */ u8 ecmp; /* Maximum number of nexthops in ECMP route, or 0 */ u8 infinity; /* Maximum metric value, representing infinity */ u8 triggered; /* Logical AND of interface want_triggered values */ @@ -149,7 +144,6 @@ struct rip_neighbor struct rip_entry { - struct fib_node n; struct rip_rte *routes; /* List of incoming routes */ u8 valid; /* Entry validity state (RIP_ENTRY_*) */ @@ -160,6 +154,8 @@ struct rip_entry ip_addr next_hop; /* Outgoing route next hop */ bird_clock_t changed; /* Last time when the outgoing route metric changed */ + + struct fib_node n; }; struct rip_rte @@ -189,16 +185,11 @@ struct rip_rte #define EA_RIP_METRIC EA_CODE(EAP_RIP, 0) #define EA_RIP_TAG EA_CODE(EAP_RIP, 1) -#define rip_is_v2(X) RIP_IS_V2 -#define rip_is_ng(X) (!RIP_IS_V2) - -/* static inline int rip_is_v2(struct rip_proto *p) { return p->rip2; } static inline int rip_is_ng(struct rip_proto *p) { return ! p->rip2; } -*/ static inline void rip_reset_tx_session(struct rip_proto *p, struct rip_iface *ifa) @@ -211,8 +202,8 @@ rip_reset_tx_session(struct rip_proto *p, struct rip_iface *ifa) } /* rip.c */ -void rip_update_rte(struct rip_proto *p, ip_addr *prefix, int pxlen, struct rip_rte *new); -void rip_withdraw_rte(struct rip_proto *p, ip_addr *prefix, int pxlen, struct rip_neighbor *from); +void rip_update_rte(struct rip_proto *p, net_addr *n, struct rip_rte *new); +void rip_withdraw_rte(struct rip_proto *p, net_addr *n, struct rip_neighbor *from); struct rip_neighbor * rip_get_neighbor(struct rip_proto *p, ip_addr *a, struct rip_iface *ifa); void rip_update_bfd(struct rip_proto *p, struct rip_neighbor *n); void rip_show_interfaces(struct proto *P, char *iff); diff --git a/proto/rpki/Doc b/proto/rpki/Doc new file mode 100644 index 00000000..d1d1bf55 --- /dev/null +++ b/proto/rpki/Doc @@ -0,0 +1,5 @@ +S rpki.c +S packets.c +S transport.c +S tcp_transport.c +S ssh_transport.c diff --git a/proto/rpki/Makefile b/proto/rpki/Makefile new file mode 100644 index 00000000..eb09b7df --- /dev/null +++ b/proto/rpki/Makefile @@ -0,0 +1,6 @@ +src := rpki.c packets.c tcp_transport.c ssh_transport.c transport.c +obj := $(src-o-files) +$(all-daemon) +$(cf-local) + +tests_objs := $(tests_objs) $(src-o-files)
\ No newline at end of file diff --git a/proto/rpki/config.Y b/proto/rpki/config.Y new file mode 100644 index 00000000..39fdfd01 --- /dev/null +++ b/proto/rpki/config.Y @@ -0,0 +1,144 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +CF_HDR + +#include "proto/rpki/rpki.h" + +CF_DEFINES + +#define RPKI_CFG ((struct rpki_config *) this_proto) +#define RPKI_TR_SSH_CFG ((struct rpki_tr_ssh_config *) RPKI_CFG->tr_config.spec) + +static void +rpki_check_unused_hostname(void) +{ + if (RPKI_CFG->hostname != NULL) + cf_error("Only one cache server per protocol allowed"); +} + +static void +rpki_check_unused_transport(void) +{ + if (RPKI_CFG->tr_config.spec != NULL) + cf_error("At the most one transport per protocol allowed"); +} + +CF_DECLS + +CF_KEYWORDS(RPKI, REMOTE, BIRD, PRIVATE, PUBLIC, KEY, TCP, SSH, TRANSPORT, USER, + RETRY, REFRESH, EXPIRE, KEEP) + +%type <i> rpki_keep_interval + +CF_GRAMMAR + +CF_ADDTO(proto, rpki_proto) + +rpki_proto_start: proto_start RPKI { + this_proto = proto_config_new(&proto_rpki, $1); + RPKI_CFG->retry_interval = RPKI_RETRY_INTERVAL; + RPKI_CFG->refresh_interval = RPKI_REFRESH_INTERVAL; + RPKI_CFG->expire_interval = RPKI_EXPIRE_INTERVAL; +}; + +rpki_proto: rpki_proto_start proto_name '{' rpki_proto_opts '}' { rpki_check_config(RPKI_CFG); }; + +rpki_proto_opts: + /* empty */ + | rpki_proto_opts rpki_proto_item ';' + ; + +rpki_proto_item: + proto_item + | proto_channel + | REMOTE rpki_cache_addr + | REMOTE rpki_cache_addr rpki_proto_item_port + | rpki_proto_item_port + | TRANSPORT rpki_transport + | REFRESH rpki_keep_interval expr { + if (rpki_check_refresh_interval($3)) + cf_error(rpki_check_refresh_interval($3)); + RPKI_CFG->refresh_interval = $3; + RPKI_CFG->keep_refresh_interval = $2; + } + | RETRY rpki_keep_interval expr { + if (rpki_check_retry_interval($3)) + cf_error(rpki_check_retry_interval($3)); + RPKI_CFG->retry_interval = $3; + RPKI_CFG->keep_retry_interval = $2; + } + | EXPIRE rpki_keep_interval expr { + if (rpki_check_expire_interval($3)) + cf_error(rpki_check_expire_interval($3)); + RPKI_CFG->expire_interval = $3; + RPKI_CFG->keep_expire_interval = $2; + } + ; + +rpki_keep_interval: + /* empty */ { $$ = 0; } + | KEEP { $$ = 1; } + ; + +rpki_proto_item_port: PORT expr { check_u16($2); RPKI_CFG->port = $2; }; + +rpki_cache_addr: + text { + rpki_check_unused_hostname(); + RPKI_CFG->hostname = $1; + } + | ipa { + rpki_check_unused_hostname(); + RPKI_CFG->ip = $1; + /* Ensure hostname is filled */ + char *hostname = cfg_allocz(sizeof(INET6_ADDRSTRLEN + 1)); + bsnprintf(hostname, INET6_ADDRSTRLEN+1, "%I", RPKI_CFG->ip); + RPKI_CFG->hostname = hostname; + } + ; + +rpki_transport: + TCP rpki_transport_tcp_init + | SSH rpki_transport_ssh_init '{' rpki_transport_ssh_opts '}' rpki_transport_ssh_check + ; + +rpki_transport_tcp_init: +{ + rpki_check_unused_transport(); + RPKI_CFG->tr_config.spec = cfg_allocz(sizeof(struct rpki_tr_tcp_config)); + RPKI_CFG->tr_config.type = RPKI_TR_TCP; +}; + +rpki_transport_ssh_init: +{ + rpki_check_unused_transport(); + RPKI_CFG->tr_config.spec = cfg_allocz(sizeof(struct rpki_tr_ssh_config)); + RPKI_CFG->tr_config.type = RPKI_TR_SSH; +}; + +rpki_transport_ssh_opts: + /* empty */ + | rpki_transport_ssh_opts rpki_transport_ssh_item ';' + ; + +rpki_transport_ssh_item: + BIRD PRIVATE KEY text { RPKI_TR_SSH_CFG->bird_private_key = $4; } + | REMOTE PUBLIC KEY text { RPKI_TR_SSH_CFG->cache_public_key = $4; } + | USER text { RPKI_TR_SSH_CFG->user = $2; } + ; + +rpki_transport_ssh_check: +{ + if (RPKI_TR_SSH_CFG->user == NULL) + cf_error("User must be set"); +}; + +CF_CODE + +CF_END diff --git a/proto/rpki/packets.c b/proto/rpki/packets.c new file mode 100644 index 00000000..60ca3936 --- /dev/null +++ b/proto/rpki/packets.c @@ -0,0 +1,1073 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * (c) 2015 Pavel Tvrdik <pawel.tvrdik@gmail.com> + * + * This file was a part of RTRlib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#undef LOCAL_DEBUG + +#include "rpki.h" +#include "transport.h" +#include "packets.h" + +#define RPKI_ADD_FLAG 0b00000001 + +enum rpki_transmit_type { + RPKI_RECV = 0, + RPKI_SEND = 1, +}; + +enum pdu_error_type { + CORRUPT_DATA = 0, + INTERNAL_ERROR = 1, + NO_DATA_AVAIL = 2, + INVALID_REQUEST = 3, + UNSUPPORTED_PROTOCOL_VER = 4, + UNSUPPORTED_PDU_TYPE = 5, + WITHDRAWAL_OF_UNKNOWN_RECORD = 6, + DUPLICATE_ANNOUNCEMENT = 7, + PDU_TOO_BIG = 32 +}; + +static const char *str_pdu_error_type[] = { + [CORRUPT_DATA] = "Corrupt-Data", + [INTERNAL_ERROR] = "Internal-Error", + [NO_DATA_AVAIL] = "No-Data-Available", + [INVALID_REQUEST] = "Invalid-Request", + [UNSUPPORTED_PROTOCOL_VER] = "Unsupported-Protocol-Version", + [UNSUPPORTED_PDU_TYPE] = "Unsupported-PDU-Type", + [WITHDRAWAL_OF_UNKNOWN_RECORD]= "Withdrawal-Of-Unknown-Record", + [DUPLICATE_ANNOUNCEMENT] = "Duplicate-Announcement", + [PDU_TOO_BIG] = "PDU-Too-Big", +}; + +enum pdu_type { + SERIAL_NOTIFY = 0, + SERIAL_QUERY = 1, + RESET_QUERY = 2, + CACHE_RESPONSE = 3, + IPV4_PREFIX = 4, + RESERVED = 5, + IPV6_PREFIX = 6, + END_OF_DATA = 7, + CACHE_RESET = 8, + ROUTER_KEY = 9, + ERROR = 10, + PDU_TYPE_MAX +}; + +static const char *str_pdu_type_[] = { + [SERIAL_NOTIFY] = "Serial Notify", + [SERIAL_QUERY] = "Serial Query", + [RESET_QUERY] = "Reset Query", + [CACHE_RESPONSE] = "Cache Response", + [IPV4_PREFIX] = "IPv4 Prefix", + [RESERVED] = "Reserved", + [IPV6_PREFIX] = "IPv6 Prefix", + [END_OF_DATA] = "End of Data", + [CACHE_RESET] = "Cache Reset", + [ROUTER_KEY] = "Router Key", + [ERROR] = "Error" +}; + +static const char *str_pdu_type(uint type) { + if (type < PDU_TYPE_MAX) + return str_pdu_type_[type]; + else + return "Undefined packet type"; +} + +/* + * 0 8 16 24 31 + * .-------------------------------------------. + * | Protocol | PDU | | + * | Version | Type | reserved = zero | + * | 0 or 1 | 0 - 10 | | + * +-------------------------------------------+ + * | | + * | Length >= 8 | + * | | + * `-------------------------------------------' */ +struct pdu_header { + u8 ver; + u8 type; + u16 reserved; + u32 len; +} PACKED; + +struct pdu_cache_response { + u8 ver; + u8 type; + u16 session_id; + u32 len; +} PACKED; + +struct pdu_serial_notify { + u8 ver; + u8 type; + u16 session_id; + u32 len; + u32 serial_num; +} PACKED; + +struct pdu_serial_query { + u8 ver; + u8 type; + u16 session_id; + u32 len; + u32 serial_num; +} PACKED; + +struct pdu_ipv4 { + u8 ver; + u8 type; + u16 reserved; + u32 len; + u8 flags; + u8 prefix_len; + u8 max_prefix_len; + u8 zero; + ip4_addr prefix; + u32 asn; +} PACKED; + +struct pdu_ipv6 { + u8 ver; + u8 type; + u16 reserved; + u32 len; + u8 flags; + u8 prefix_len; + u8 max_prefix_len; + u8 zero; + ip6_addr prefix; + u32 asn; +} PACKED; + +/* + * 0 8 16 24 31 + * .-------------------------------------------. + * | Protocol | PDU | | + * | Version | Type | Error Code | + * | 1 | 10 | | + * +-------------------------------------------+ + * | | + * | Length | + * | | + * +-------------------------------------------+ + * | | + * | Length of Encapsulated PDU | + * | | + * +-------------------------------------------+ + * | | + * ~ Copy of Erroneous PDU ~ + * | | + * +-------------------------------------------+ + * | | + * | Length of Error Text | + * | | + * +-------------------------------------------+ + * | | + * | Arbitrary Text | + * | of | + * ~ Error Diagnostic Message ~ + * | | + * `-------------------------------------------' */ +struct pdu_error { + u8 ver; + u8 type; + u16 error_code; + u32 len; + u32 len_enc_pdu; /* Length of Encapsulated PDU */ + byte rest[]; /* Copy of Erroneous PDU + * Length of Error Text + * Error Diagnostic Message */ +} PACKED; + +struct pdu_reset_query { + u8 ver; + u8 type; + u16 flags; + u32 len; +} PACKED; + +struct pdu_end_of_data_v0 { + u8 ver; + u8 type; + u16 session_id; + u32 len; + u32 serial_num; +} PACKED; + +struct pdu_end_of_data_v1 { + u8 ver; + u8 type; + u16 session_id; + u32 len; + u32 serial_num; + u32 refresh_interval; + u32 retry_interval; + u32 expire_interval; +} PACKED; + +static const size_t min_pdu_size[] = { + [SERIAL_NOTIFY] = sizeof(struct pdu_serial_notify), + [SERIAL_QUERY] = sizeof(struct pdu_serial_query), + [RESET_QUERY] = sizeof(struct pdu_reset_query), + [CACHE_RESPONSE] = sizeof(struct pdu_cache_response), + [IPV4_PREFIX] = sizeof(struct pdu_ipv4), + [RESERVED] = sizeof(struct pdu_header), + [IPV6_PREFIX] = sizeof(struct pdu_ipv6), + [END_OF_DATA] = sizeof(struct pdu_end_of_data_v0), + [CACHE_RESET] = sizeof(struct pdu_cache_response), + [ROUTER_KEY] = sizeof(struct pdu_header), /* FIXME */ + [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 void +rpki_pdu_to_network_byte_order(struct pdu_header *pdu) +{ + pdu->reserved = htons(pdu->reserved); + pdu->len = htonl(pdu->len); + + switch (pdu->type) + { + case SERIAL_QUERY: + { + /* Note that a session_id is converted using converting header->reserved */ + struct pdu_serial_query *sq_pdu = (void *) pdu; + sq_pdu->serial_num = htonl(sq_pdu->serial_num); + break; + } + + case ERROR: + { + struct pdu_error *err = (void *) pdu; + u32 *err_text_len = (u32 *)(err->rest + err->len_enc_pdu); + *err_text_len = htonl(*err_text_len); + err->len_enc_pdu = htonl(err->len_enc_pdu); + break; + } + + case RESET_QUERY: + break; + + default: + bug("PDU type %s should not be sent by us", str_pdu_type(pdu->type)); + } +} + +static void +rpki_pdu_to_host_byte_order(struct pdu_header *pdu) +{ + /* The Router Key PDU has two one-byte fields instead of one two-bytes field. */ + if (pdu->type != ROUTER_KEY) + pdu->reserved = ntohs(pdu->reserved); + + pdu->len = ntohl(pdu->len); + + switch (pdu->type) + { + case SERIAL_NOTIFY: + { + /* Note that a session_id is converted using converting header->reserved */ + struct pdu_serial_notify *sn_pdu = (void *) pdu; + sn_pdu->serial_num = ntohl(sn_pdu->serial_num); + break; + } + + case END_OF_DATA: + { + /* Note that a session_id is converted using converting header->reserved */ + struct pdu_end_of_data_v0 *eod0 = (void *) pdu; + eod0->serial_num = ntohl(eod0->serial_num); /* Same either for version 1 */ + + if (pdu->ver == RPKI_VERSION_1) + { + struct pdu_end_of_data_v1 *eod1 = (void *) pdu; + eod1->expire_interval = ntohl(eod1->expire_interval); + eod1->refresh_interval = ntohl(eod1->refresh_interval); + eod1->retry_interval = ntohl(eod1->retry_interval); + } + break; + } + + case IPV4_PREFIX: + { + struct pdu_ipv4 *ipv4 = (void *) pdu; + ipv4->prefix = ip4_ntoh(ipv4->prefix); + ipv4->asn = ntohl(ipv4->asn); + break; + } + + case IPV6_PREFIX: + { + struct pdu_ipv6 *ipv6 = (void *) pdu; + ipv6->prefix = ip6_ntoh(ipv6->prefix); + ipv6->asn = ntohl(ipv6->asn); + break; + } + + case ERROR: + { + /* Note that a error_code is converted using converting header->reserved */ + struct pdu_error *err = (void *) pdu; + err->len_enc_pdu = ntohl(err->len_enc_pdu); + u32 *err_text_len = (u32 *)(err->rest + err->len_enc_pdu); + *err_text_len = htonl(*err_text_len); + break; + } + + case ROUTER_KEY: + /* Router Key PDU is not supported yet */ + + case SERIAL_QUERY: + case RESET_QUERY: + /* Serial/Reset Query are sent only in direction router to cache. + * We don't care here. */ + + case CACHE_RESPONSE: + case CACHE_RESET: + /* Converted with pdu->reserved */ + break; + } +} + +/** + * rpki_convert_pdu_back_to_network_byte_order - convert host-byte order PDU back to network-byte order + * @out: allocated memory for writing a converted PDU of size @in->len + * @in: host-byte order PDU + * + * Assumed: |A == ntoh(ntoh(A))| + */ +static struct pdu_header * +rpki_pdu_back_to_network_byte_order(struct pdu_header *out, const struct pdu_header *in) +{ + memcpy(out, in, in->len); + rpki_pdu_to_host_byte_order(out); + return out; +} + +static void +rpki_log_packet(struct rpki_cache *cache, const struct pdu_header *pdu, const enum rpki_transmit_type action) +{ + if (!(cache->p->p.debug & D_PACKETS)) + return; + + const char *str_type = str_pdu_type(pdu->type); + char detail[256]; + +#define SAVE(fn) \ + do { \ + if (fn < 0) \ + { \ + bsnprintf(detail + sizeof(detail) - 16, 16, "... <too long>)"); \ + goto detail_finished; \ + } \ + } while(0) \ + + switch (pdu->type) + { + case SERIAL_NOTIFY: + case SERIAL_QUERY: + SAVE(bsnprintf(detail, sizeof(detail), "(session id: %u, serial number: %u)", pdu->reserved, ((struct pdu_serial_notify *) pdu)->serial_num)); + break; + + case END_OF_DATA: + { + const struct pdu_end_of_data_v1 *eod = (void *) pdu; + if (eod->ver == RPKI_VERSION_1) + SAVE(bsnprintf(detail, sizeof(detail), "(session id: %u, serial number: %u, refresh: %us, retry: %us, expire: %us)", eod->session_id, eod->serial_num, eod->refresh_interval, eod->retry_interval, eod->expire_interval)); + else + SAVE(bsnprintf(detail, sizeof(detail), "(session id: %u, serial number: %u)", eod->session_id, eod->serial_num)); + break; + } + + case CACHE_RESPONSE: + SAVE(bsnprintf(detail, sizeof(detail), "(session id: %u)", pdu->reserved)); + break; + + case IPV4_PREFIX: + { + const struct pdu_ipv4 *ipv4 = (void *) pdu; + SAVE(bsnprintf(detail, sizeof(detail), "(%I4/%u-%u AS%u)", ipv4->prefix, ipv4->prefix_len, ipv4->max_prefix_len, ipv4->asn)); + break; + } + + case IPV6_PREFIX: + { + const struct pdu_ipv6 *ipv6 = (void *) pdu; + SAVE(bsnprintf(detail, sizeof(detail), "(%I6/%u-%u AS%u)", ipv6->prefix, ipv6->prefix_len, ipv6->max_prefix_len, ipv6->asn)); + break; + } + + case ROUTER_KEY: + /* We don't support saving Router Key PDUs yet */ + SAVE(bsnprintf(detail, sizeof(detail), "(ignored)")); + break; + + case ERROR: + { + const struct pdu_error *err = (void *) pdu; + SAVE(bsnprintf(detail, sizeof(detail), "(%s", str_pdu_error_type[err->error_code])); + + /* Optional description of error */ + const u32 len_err_txt = *((u32 *) (err->rest + err->len_enc_pdu)); + if (len_err_txt > 0) + { + size_t expected_len = err->len_enc_pdu + len_err_txt + 16; + if (expected_len == err->len) + { + char txt[len_err_txt + 1]; + char *pdu_txt = (char *) err->rest + err->len_enc_pdu + 4; + bsnprintf(txt, sizeof(txt), "%s", pdu_txt); /* it's ensured that txt is ended with a null byte */ + SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), ": '%s'", txt)); + } + else + { + SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), ", malformed size")); + } + } + + /* Optional encapsulated erroneous packet */ + if (err->len_enc_pdu) + { + SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), ", %s packet:", str_pdu_type(((struct pdu_header *) err->rest)->type))); + if (err->rest + err->len_enc_pdu <= (byte *)err + err->len) + { + for (const byte *c = err->rest; c != err->rest + err->len_enc_pdu; c++) + SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), " %02X", *c)); + } + } + + SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), ")")); + break; + } + + default: + *detail = '\0'; + } +#undef SAVE + + detail_finished: + + if (action == RPKI_RECV) + { + CACHE_TRACE(D_PACKETS, cache, "Received %s packet %s", str_type, detail); + } + else + { + CACHE_TRACE(D_PACKETS, cache, "Sending %s packet %s", str_type, detail); + } + +#if defined(LOCAL_DEBUG) || defined(GLOBAL_DEBUG) + int seq = 0; + for(const byte *c = pdu; c != pdu + pdu->len; c++) + { + if ((seq % 4) == 0) + DBG("%2d: ", seq); + + DBG(" 0x%02X %-3u", *c, *c); + + if ((++seq % 4) == 0) + DBG("\n"); + } + if ((seq % 4) != 0) + DBG("\n"); +#endif +} + +static int +rpki_send_pdu(struct rpki_cache *cache, const void *pdu, const uint len) +{ + struct rpki_proto *p = cache->p; + sock *sk = cache->tr_sock->sk; + + rpki_log_packet(cache, pdu, RPKI_SEND); + + if (sk->tbuf != sk->tpos) + { + RPKI_WARN(p, "Old packet overwritten in TX buffer"); + } + + if (len > sk->tbsize) + { + RPKI_WARN(p, "%u bytes is too much for send", len); + ASSERT(0); + return RPKI_ERROR; + } + + memcpy(sk->tbuf, pdu, len); + rpki_pdu_to_network_byte_order((void *) sk->tbuf); + + if (!sk_send(sk, len)) + { + DBG("Cannot send just the whole data. It will be sent using a call of tx_hook()"); + } + + return RPKI_SUCCESS; +} + +/** + * rpki_check_receive_packet - make a basic validation of received RPKI PDU header + * @cache: cache connection instance + * @pdu: RPKI PDU in network byte order + * + * This function checks protocol version, PDU type, version and size. If all is all right then + * function returns |RPKI_SUCCESS| otherwise sends Error PDU and returns + * |RPKI_ERROR|. + */ +static int +rpki_check_receive_packet(struct rpki_cache *cache, const struct pdu_header *pdu) +{ + u32 pdu_len = ntohl(pdu->len); + + /* + * Minimal and maximal allowed PDU size is treated in rpki_rx_hook() function. + * @header.len corresponds to number of bytes of @pdu and + * it is in range from RPKI_PDU_HEADER_LEN to RPKI_PDU_MAX_LEN bytes. + */ + + /* Do not handle error PDUs here, leave this task to rpki_handle_error_pdu() */ + if (pdu->ver != cache->version && pdu->type != ERROR) + { + /* If this is the first PDU we have received */ + if (cache->request_session_id) + { + if (pdu->type == SERIAL_NOTIFY) + { + /* + * The router MUST ignore any Serial Notify PDUs it might receive from + * the cache during this initial start-up period, regardless of the + * Protocol Version field in the Serial Notify PDU. + * (https://tools.ietf.org/html/draft-ietf-sidr-rpki-rtr-rfc6810-bis-07#section-7) + */ + } + else if (cache->last_update == 0 + && pdu->ver <= RPKI_MAX_VERSION + && pdu->ver < cache->version) + { + CACHE_TRACE(D_EVENTS, cache, "Downgrade session to %s from %u to %u version", rpki_get_cache_ident(cache), cache->version, pdu->ver); + cache->version = pdu->ver; + } + else + { + /* If this is not the first PDU we have received, something is wrong with + * the server implementation -> Error */ + rpki_send_error_pdu(cache, UNSUPPORTED_PROTOCOL_VER, pdu_len, pdu, "PDU with unsupported Protocol version received"); + return RPKI_ERROR; + } + } + } + + if ((pdu->type >= PDU_TYPE_MAX) || (pdu->ver == RPKI_VERSION_0 && pdu->type == ROUTER_KEY)) + { + rpki_send_error_pdu(cache, UNSUPPORTED_PDU_TYPE, pdu_len, pdu, "Unsupported PDU type %u received", pdu->type); + return RPKI_ERROR; + } + + if (pdu_len < min_pdu_size[pdu->type]) + { + rpki_send_error_pdu(cache, CORRUPT_DATA, pdu_len, pdu, "Received %s packet with %d bytes, but expected at least %d bytes", str_pdu_type(pdu->type), pdu_len, min_pdu_size[pdu->type]); + return RPKI_ERROR; + } + + return RPKI_SUCCESS; +} + +static int +rpki_handle_error_pdu(struct rpki_cache *cache, const struct pdu_error *pdu) +{ + switch (pdu->error_code) + { + case CORRUPT_DATA: + case INTERNAL_ERROR: + case INVALID_REQUEST: + case UNSUPPORTED_PDU_TYPE: + rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL); + break; + + case NO_DATA_AVAIL: + rpki_cache_change_state(cache, RPKI_CS_ERROR_NO_DATA_AVAIL); + break; + + case UNSUPPORTED_PROTOCOL_VER: + CACHE_TRACE(D_PACKETS, cache, "Client uses unsupported protocol version"); + if (pdu->ver <= RPKI_MAX_VERSION && + pdu->ver < cache->version) + { + CACHE_TRACE(D_EVENTS, cache, "Downgrading from protocol version %d to version %d", cache->version, pdu->ver); + cache->version = pdu->ver; + rpki_cache_change_state(cache, RPKI_CS_FAST_RECONNECT); + } + else + { + CACHE_TRACE(D_PACKETS, cache, "Got UNSUPPORTED_PROTOCOL_VER error PDU with invalid values, " \ + "current version: %d, PDU version: %d", cache->version, pdu->ver); + rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL); + } + break; + + default: + CACHE_TRACE(D_PACKETS, cache, "Error unknown, server sent unsupported error code %u", pdu->error_code); + rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL); + break; + } + + return RPKI_SUCCESS; +} + +static void +rpki_handle_serial_notify_pdu(struct rpki_cache *cache, const struct pdu_serial_notify *pdu) +{ + /* The router MUST ignore any Serial Notify PDUs it might receive from + * the cache during this initial start-up period, regardless of the + * Protocol Version field in the Serial Notify PDU. + * (https://tools.ietf.org/html/draft-ietf-sidr-rpki-rtr-rfc6810-bis-07#section-7) + */ + if (cache->request_session_id) + { + CACHE_TRACE(D_PACKETS, cache, "Ignore a Serial Notify packet during initial start-up period"); + return; + } + + /* XXX Serial number should be compared using method RFC 1982 (3.2) */ + if (cache->serial_num != pdu->serial_num) + rpki_cache_change_state(cache, RPKI_CS_SYNC_START); +} + +static int +rpki_handle_cache_response_pdu(struct rpki_cache *cache, const struct pdu_cache_response *pdu) +{ + if (cache->request_session_id) + { + if (cache->last_update != 0) + { + /* + * 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; + } + cache->session_id = pdu->session_id; + cache->request_session_id = 0; + } + else + { + if (cache->session_id != pdu->session_id) + { + byte tmp[pdu->len]; + const struct pdu_header *hton_pdu = rpki_pdu_back_to_network_byte_order((void *) tmp, (const void *) pdu); + rpki_send_error_pdu(cache, CORRUPT_DATA, pdu->len, hton_pdu, "Wrong session_id %u in Cache Response PDU", pdu->session_id); + rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL); + return RPKI_ERROR; + } + } + + rpki_cache_change_state(cache, RPKI_CS_SYNC_RUNNING); + return RPKI_SUCCESS; +} + +/** + * rpki_prefix_pdu_2_net_addr - convert IPv4/IPv6 Prefix PDU into net_addr_union + * @pdu: host byte order IPv4/IPv6 Prefix PDU + * @n: allocated net_addr_union for save ROA + * + * This function reads ROA data from IPv4/IPv6 Prefix PDU and + * write them into net_addr_roa4 or net_addr_roa6 data structure. + */ +static net_addr_union * +rpki_prefix_pdu_2_net_addr(const struct pdu_header *pdu, net_addr_union *n) +{ + /* + * Note that sizeof(net_addr_roa6) > sizeof(net_addr) + * and thence we must use net_addr_union and not only net_addr + */ + + if (pdu->type == IPV4_PREFIX) + { + const struct pdu_ipv4 *ipv4 = (void *) pdu; + n->roa4.type = NET_ROA4; + n->roa4.length = sizeof(net_addr_roa4); + n->roa4.prefix = ipv4->prefix; + n->roa4.asn = ipv4->asn; + n->roa4.pxlen = ipv4->prefix_len; + n->roa4.max_pxlen = ipv4->max_prefix_len; + } + else + { + const struct pdu_ipv6 *ipv6 = (void *) pdu; + n->roa6.type = NET_ROA6; + n->roa6.length = sizeof(net_addr_roa6); + n->roa6.prefix = ipv6->prefix; + n->roa6.asn = ipv6->asn; + n->roa6.pxlen = ipv6->prefix_len; + n->roa6.max_pxlen = ipv6->max_prefix_len; + } + + return n; +} + +static int +rpki_handle_prefix_pdu(struct rpki_cache *cache, const struct pdu_header *pdu) +{ + const enum pdu_type type = pdu->type; + ASSERT(type == IPV4_PREFIX || type == IPV6_PREFIX); + + net_addr_union addr = {}; + rpki_prefix_pdu_2_net_addr(pdu, &addr); + + struct channel *channel = NULL; + + if (type == IPV4_PREFIX) + channel = cache->p->roa4_channel; + if (type == IPV6_PREFIX) + channel = cache->p->roa6_channel; + + if (!channel) + { + CACHE_TRACE(D_ROUTES, cache, "Skip %N, missing %s channel", &addr, (type == IPV4_PREFIX ? "roa4" : "roa6"), addr); + return RPKI_ERROR; + } + + cache->last_rx_prefix = now; + + /* A place for 'flags' is same for both data structures pdu_ipv4 or pdu_ipv6 */ + struct pdu_ipv4 *pfx = (void *) pdu; + if (pfx->flags & RPKI_ADD_FLAG) + rpki_table_add_roa(cache, channel, &addr); + else + rpki_table_remove_roa(cache, channel, &addr); + + return RPKI_SUCCESS; +} + +static uint +rpki_check_interval(struct rpki_cache *cache, const char *(check_fn)(uint), uint interval) +{ + if (check_fn(interval)) + { + RPKI_WARN(cache->p, "%s, received %u seconds", check_fn(interval), interval); + return 0; + } + return 1; +} + +static void +rpki_handle_end_of_data_pdu(struct rpki_cache *cache, const struct pdu_end_of_data_v1 *pdu) +{ + const struct rpki_config *cf = (void *) cache->p->p.cf; + + if (pdu->session_id != cache->session_id) + { + byte tmp[pdu->len]; + const struct pdu_header *hton_pdu = rpki_pdu_back_to_network_byte_order((void *) tmp, (const void *) pdu); + rpki_send_error_pdu(cache, CORRUPT_DATA, pdu->len, hton_pdu, "Received Session ID %u, but expected %u", pdu->session_id, cache->session_id); + rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL); + return; + } + + if (pdu->ver == RPKI_VERSION_1) + { + if (!cf->keep_refresh_interval && rpki_check_interval(cache, rpki_check_refresh_interval, pdu->refresh_interval)) + cache->refresh_interval = pdu->refresh_interval; + + if (!cf->keep_retry_interval && rpki_check_interval(cache, rpki_check_retry_interval, pdu->retry_interval)) + cache->retry_interval = pdu->retry_interval; + + if (!cf->keep_expire_interval && rpki_check_interval(cache, rpki_check_expire_interval, pdu->expire_interval)) + cache->expire_interval = pdu->expire_interval; + + CACHE_TRACE(D_EVENTS, cache, "New interval values: " + "refresh: %s%us, " + "retry: %s%us, " + "expire: %s%us", + (cf->keep_refresh_interval ? "keeps " : ""), cache->refresh_interval, + (cf->keep_retry_interval ? "keeps " : ""), cache->retry_interval, + (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); + } + + cache->last_update = now; + cache->serial_num = pdu->serial_num; + rpki_cache_change_state(cache, RPKI_CS_ESTABLISHED); +} + +/** + * rpki_rx_packet - process a received RPKI PDU + * @cache: RPKI connection instance + * @pdu: a RPKI PDU in network byte order + */ +static void +rpki_rx_packet(struct rpki_cache *cache, struct pdu_header *pdu) +{ + struct rpki_proto *p = cache->p; + + if (rpki_check_receive_packet(cache, pdu) == RPKI_ERROR) + { + rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL); + return; + } + + rpki_pdu_to_host_byte_order(pdu); + rpki_log_packet(cache, pdu, RPKI_RECV); + + switch (pdu->type) + { + case RESET_QUERY: + case SERIAL_QUERY: + RPKI_WARN(p, "Received a %s packet that is destined for cache server", str_pdu_type(pdu->type)); + break; + + case SERIAL_NOTIFY: + /* This is a signal to synchronize with the cache server just now */ + rpki_handle_serial_notify_pdu(cache, (void *) pdu); + break; + + case CACHE_RESPONSE: + rpki_handle_cache_response_pdu(cache, (void *) pdu); + break; + + case IPV4_PREFIX: + case IPV6_PREFIX: + rpki_handle_prefix_pdu(cache, pdu); + break; + + case END_OF_DATA: + rpki_handle_end_of_data_pdu(cache, (void *) pdu); + break; + + case CACHE_RESET: + /* Cache cannot provide an incremental update. */ + rpki_cache_change_state(cache, RPKI_CS_NO_INCR_UPDATE_AVAIL); + break; + + case ERROR: + rpki_handle_error_pdu(cache, (void *) pdu); + break; + + case ROUTER_KEY: + /* TODO: Implement Router Key PDU handling */ + break; + + default: + CACHE_TRACE(D_PACKETS, cache, "Received unsupported type (%u)", pdu->type); + }; +} + +int +rpki_rx_hook(struct birdsock *sk, uint size) +{ + struct rpki_cache *cache = sk->data; + struct rpki_proto *p = cache->p; + + byte *pkt_start = sk->rbuf; + byte *end = pkt_start + size; + + DBG("rx hook got %u bytes \n", size); + + while (end >= pkt_start + RPKI_PDU_HEADER_LEN) + { + struct pdu_header *pdu = (void *) pkt_start; + u32 pdu_size = ntohl(pdu->len); + + if (pdu_size < RPKI_PDU_HEADER_LEN || pdu_size > RPKI_PDU_MAX_LEN) + { + RPKI_WARN(p, "Received invalid packet length %u, purge the whole receiving buffer", pdu_size); + return 1; /* Purge recv buffer */ + } + + if (end < pkt_start + pdu_size) + break; + + rpki_rx_packet(cache, pdu); + + /* It is possible that bird socket was freed/closed */ + if (p->p.proto_state == PS_DOWN || sk != cache->tr_sock->sk) + return 0; + + pkt_start += pdu_size; + } + + if (pkt_start != sk->rbuf) + { + CACHE_DBG(cache, "Move %u bytes of a memory at the start of buffer", end - pkt_start); + memmove(sk->rbuf, pkt_start, end - pkt_start); + sk->rpos = sk->rbuf + (end - pkt_start); + } + + return 0; /* Not purge sk->rbuf */ +} + +void +rpki_err_hook(struct birdsock *sk, int error_num) +{ + struct rpki_cache *cache = sk->data; + + if (error_num) + { + /* sk->err may contains a SSH error description */ + if (sk->err) + CACHE_TRACE(D_EVENTS, cache, "Lost connection: %s", sk->err); + else + CACHE_TRACE(D_EVENTS, cache, "Lost connection: %M", error_num); + } + else + { + CACHE_TRACE(D_EVENTS, cache, "The other side closed a connection"); + } + + + rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT); +} + +static int +rpki_fire_tx(struct rpki_cache *cache) +{ + sock *sk = cache->tr_sock->sk; + + uint bytes_to_send = sk->tpos - sk->tbuf; + DBG("Sending %u bytes", bytes_to_send); + return sk_send(sk, bytes_to_send); +} + +void +rpki_tx_hook(sock *sk) +{ + struct rpki_cache *cache = sk->data; + + while (rpki_fire_tx(cache) > 0) + ; +} + +void +rpki_connected_hook(sock *sk) +{ + struct rpki_cache *cache = sk->data; + + CACHE_TRACE(D_EVENTS, cache, "Connected"); + proto_notify_state(&cache->p->p, PS_UP); + + sk->rx_hook = rpki_rx_hook; + sk->tx_hook = rpki_tx_hook; + + rpki_cache_change_state(cache, RPKI_CS_SYNC_START); +} + +/** + * rpki_send_error_pdu - send RPKI Error PDU + * @cache: RPKI connection instance + * @error_code: PDU Error type + * @err_pdu_len: length of @erroneous_pdu + * @erroneous_pdu: optional network byte-order PDU that invokes Error by us or NULL + * @fmt: optional description text of error or NULL + * @args: optional arguments for @fmt + * + * 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, ...) +{ + va_list args; + char msg[128]; + + /* Size including the terminating null byte ('\0') */ + int msg_len = 0; + + /* Don't send errors for erroneous error PDUs */ + if (err_pdu_len >= 2) + { + if (erroneous_pdu->type == ERROR) + return RPKI_SUCCESS; + } + + if (fmt) + { + va_start(args, fmt); + msg_len = bvsnprintf(msg, sizeof(msg), fmt, args) + 1; + } + + u32 pdu_size = 16 + err_pdu_len + msg_len; + byte pdu[pdu_size]; + memset(pdu, 0, sizeof(pdu)); + + struct pdu_error *e = (void *) pdu; + e->ver = cache->version; + e->type = ERROR; + e->error_code = error_code; + e->len = pdu_size; + + e->len_enc_pdu = err_pdu_len; + if (err_pdu_len > 0) + memcpy(e->rest, erroneous_pdu, err_pdu_len); + + *((u32 *)(e->rest + err_pdu_len)) = msg_len; + if (msg_len > 0) + memcpy(e->rest + err_pdu_len + 4, msg, msg_len); + + return rpki_send_pdu(cache, pdu, pdu_size); +} + +int +rpki_send_serial_query(struct rpki_cache *cache) +{ + struct pdu_serial_query pdu = { + .ver = cache->version, + .type = SERIAL_QUERY, + .session_id = cache->session_id, + .len = sizeof(pdu), + .serial_num = cache->serial_num + }; + + if (rpki_send_pdu(cache, &pdu, sizeof(pdu)) != RPKI_SUCCESS) + { + rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT); + return RPKI_ERROR; + } + + return RPKI_SUCCESS; +} + +int +rpki_send_reset_query(struct rpki_cache *cache) +{ + struct pdu_reset_query pdu = { + .ver = cache->version, + .type = RESET_QUERY, + .len = sizeof(pdu), + }; + + if (rpki_send_pdu(cache, &pdu, sizeof(pdu)) != RPKI_SUCCESS) + { + rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT); + return RPKI_ERROR; + } + + return RPKI_SUCCESS; +} diff --git a/proto/rpki/packets.h b/proto/rpki/packets.h new file mode 100644 index 00000000..d6f8a249 --- /dev/null +++ b/proto/rpki/packets.h @@ -0,0 +1,45 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * (c) 2015 Pavel Tvrdik <pawel.tvrdik@gmail.com> + * + * This file was a part of RTRlib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef _BIRD_RPKI_PACKETS_H_ +#define _BIRD_RPKI_PACKETS_H_ + +#include <arpa/inet.h> + +#define RPKI_PDU_HEADER_LEN 8 + +/* A Error PDU size is the biggest (has encapsulate PDU inside): + * +8 bytes (Header size) + * +4 bytes (Length of Encapsulated PDU) + * +32 bytes (Encapsulated PDU IPv6 32) + * +4 bytes (Length of inserted text) + * +800 bytes (UTF-8 text 400*2 bytes) + * ------------ + * = 848 bytes (Maximal expected PDU size) */ +#define RPKI_PDU_MAX_LEN 848 + +/* RX buffer size has a great impact to scheduler granularity */ +#define RPKI_RX_BUFFER_SIZE 4096 +#define RPKI_TX_BUFFER_SIZE RPKI_PDU_MAX_LEN + +/* Return values */ +enum rpki_rtvals { + RPKI_SUCCESS = 0, + RPKI_ERROR = -1 +}; + +int rpki_send_serial_query(struct rpki_cache *cache); +int rpki_send_reset_query(struct rpki_cache *cache); +int rpki_rx_hook(sock *sk, uint size); +void rpki_connected_hook(sock *sk); +void rpki_err_hook(sock *sk, int size); + +#endif diff --git a/proto/rpki/rpki.c b/proto/rpki/rpki.c new file mode 100644 index 00000000..349d2f70 --- /dev/null +++ b/proto/rpki/rpki.c @@ -0,0 +1,926 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * (c) 2015 Pavel Tvrdik <pawel.tvrdik@gmail.com> + * + * Using RTRlib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +/** + * DOC: RPKI To Router (RPKI-RTR) + * + * The RPKI-RTR protocol is implemented in several files: |rpki.c| containing + * the routes handling, protocol logic, timer events, cache connection, + * reconfiguration, configuration and protocol glue with BIRD core, |packets.c| + * containing the RPKI packets handling and finally all transports files: + * |transport.c|, |tcp_transport.c| and |ssh_transport.c|. + * + * The |transport.c| is a middle layer and interface for each specific + * transport. Transport is a way how to wrap a communication with a cache + * server. There is supported an unprotected TCP transport and an encrypted + * SSHv2 transport. The SSH transport requires LibSSH library. LibSSH is + * loading dynamically using |dlopen()| function. SSH support is integrated in + * |sysdep/unix/io.c|. Each transport must implement an initialization + * function, an open function and a socket identification function. That's all. + * + * This implementation is based on the RTRlib (http://rpki.realmv6.org/). The + * BIRD takes over files |packets.c|, |rtr.c| (inside |rpki.c|), |transport.c|, + * |tcp_transport.c| and |ssh_transport.c| from RTRlib. + * + * A RPKI-RTR connection is described by a structure &rpki_cache. The main + * logic is located in |rpki_cache_change_state()| function. There is a state + * machine. The standard starting state flow looks like |Down| ~> |Connecting| + * ~> |Sync-Start| ~> |Sync-Running| ~> |Established| and then the last three + * states are periodically repeated. + * + * |Connecting| state establishes the transport connection. The state from a + * call |rpki_cache_change_state(CONNECTING)| to a call |rpki_connected_hook()| + * + * |Sync-Start| state starts with sending |Reset Query| or |Serial Query| and + * then waits for |Cache Response|. The state from |rpki_connected_hook()| to + * |rpki_handle_cache_response_pdu()| + * + * During |Sync-Running| BIRD receives data with IPv4/IPv6 Prefixes from cache + * server. The state starts from |rpki_handle_cache_response_pdu()| and ends + * in |rpki_handle_end_of_data_pdu()|. + * + * |Established| state means that BIRD has synced all data with cache server. + * Schedules a refresh timer event that invokes |Sync-Start|. Schedules Expire + * timer event and stops a Retry timer event. + * + * |Transport Error| state means that we have some troubles with a network + * connection. We cannot connect to a cache server or we wait too long for some + * expected PDU for received - |Cache Response| or |End of Data|. It closes + * current connection and schedules a Retry timer event. + * + * |Fatal Protocol Error| is occurred e.g. by received a bad Session ID. We + * restart a protocol, so all ROAs are flushed immediately. + * + * The RPKI-RTR protocol (RFC 6810 bis) defines configurable refresh, retry and + * expire intervals. For maintaining a connection are used timer events that + * are scheduled by |rpki_schedule_next_refresh()|, + * |rpki_schedule_next_retry()| and |rpki_schedule_next_expire()| functions. + * + * A Refresh timer event performs a sync of |Established| connection. So it + * shifts state to |Sync-Start|. If at the beginning of second call of a + * refresh event is connection in |Sync-Start| state then we didn't receive a + * |Cache Response| from a cache server and we invoke |Transport Error| state. + * + * A Retry timer event attempts to connect cache server. It is activated after + * |Transport Error| state and terminated by reaching |Established| state. + * If cache connection is still connecting to the cache server at the beginning + * of an event call then the Retry timer event invokes |Transport Error| state. + * + * An Expire timer event checks expiration of ROAs. If a last successful sync + * was more ago than the expire interval then the Expire timer event invokes a + * protocol restart thereby removes all ROAs learned from that cache server and + * continue trying to connect to cache server. The Expire event is activated + * by initial successful loading of ROAs, receiving End of Data PDU. + * + * A reconfiguration of cache connection works well without restarting when we + * change only intervals values. + * + * Supported standards: + * - RFC 6810 - main RPKI-RTR standard + * - RFC 6810 bis - an explicit timing parameters and protocol version number negotiation + */ + +#include <stdlib.h> +#include <netdb.h> + +#undef LOCAL_DEBUG + +#include "rpki.h" +#include "lib/string.h" +#include "nest/cli.h" + +/* Return values for reconfiguration functions */ +#define NEED_RESTART 0 +#define SUCCESSFUL_RECONF 1 + +static int rpki_open_connection(struct rpki_cache *cache); +static void rpki_close_connection(struct rpki_cache *cache); +static void rpki_schedule_next_refresh(struct rpki_cache *cache); +static void rpki_schedule_next_retry(struct rpki_cache *cache); +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); + + +/* + * Routes handling + */ + +void +rpki_table_add_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr) +{ + struct rpki_proto *p = cache->p; + + rta a0 = { + .src = p->p.main_source, + .source = RTS_RPKI, + .scope = SCOPE_UNIVERSE, + .dest = RTD_NONE, + }; + + rta *a = rta_lookup(&a0); + rte *e = rte_get_temp(a); + + e->pflags = 0; + + rte_update2(channel, &pfxr->n, e, a0.src); +} + +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); +} + + +/* + * RPKI Protocol Logic + */ + +static const char *str_cache_states[] = { + [RPKI_CS_CONNECTING] = "Connecting", + [RPKI_CS_ESTABLISHED] = "Established", + [RPKI_CS_RESET] = "Reseting", + [RPKI_CS_SYNC_START] = "Sync-Start", + [RPKI_CS_SYNC_RUNNING] = "Sync-Running", + [RPKI_CS_FAST_RECONNECT] = "Fast-Reconnect", + [RPKI_CS_NO_INCR_UPDATE_AVAIL]= "No-Increment-Update-Available", + [RPKI_CS_ERROR_NO_DATA_AVAIL] = "Cache-Error-No-Data-Available", + [RPKI_CS_ERROR_FATAL] = "Fatal-Protocol-Error", + [RPKI_CS_ERROR_TRANSPORT] = "Transport-Error", + [RPKI_CS_SHUTDOWN] = "Down" +}; + +/** + * rpki_cache_state_to_str - give a text representation of cache state + * @state: A cache state + * + * The function converts logic cache state into string. + */ +const char * +rpki_cache_state_to_str(enum rpki_cache_state state) +{ + return str_cache_states[state]; +} + +/** + * rpki_start_cache - connect to a cache server + * @cache: RPKI connection instance + * + * This function is a high level method to kick up a connection to a cache server. + */ +static void +rpki_start_cache(struct rpki_cache *cache) +{ + rpki_cache_change_state(cache, RPKI_CS_CONNECTING); +} + +/** + * rpki_force_restart_proto - force shutdown and start protocol again + * @p: RPKI protocol instance + * + * This function calls shutdown and frees all protocol resources as well. + * After calling this function should be no operations with protocol data, + * they could be freed already. + */ +static void +rpki_force_restart_proto(struct rpki_proto *p) +{ + if (p->cache) + { + CACHE_DBG(p->cache, "Connection object destroying"); + } + + /* Sign as freed */ + p->cache = NULL; + + proto_notify_state(&p->p, PS_DOWN); +} + +/** + * rpki_cache_change_state - check and change cache state + * @cache: RPKI cache instance + * @new_state: suggested new state + * + * This function makes transitions between internal states. + * It represents the core of logic management of RPKI protocol. + * Cannot transit into the same state as cache is in already. + */ +void +rpki_cache_change_state(struct rpki_cache *cache, const enum rpki_cache_state new_state) +{ + const enum rpki_cache_state old_state = cache->state; + + if (old_state == new_state) + return; + + cache->state = new_state; + CACHE_TRACE(D_EVENTS, cache, "Changing from %s to %s state", rpki_cache_state_to_str(old_state), rpki_cache_state_to_str(new_state)); + + switch (new_state) + { + case RPKI_CS_CONNECTING: + { + sock *sk = cache->tr_sock->sk; + + if (sk == NULL || sk->fd < 0) + rpki_open_connection(cache); + else + rpki_cache_change_state(cache, RPKI_CS_SYNC_START); + + rpki_schedule_next_retry(cache); + break; + } + + case RPKI_CS_ESTABLISHED: + rpki_schedule_next_refresh(cache); + rpki_schedule_next_expire_check(cache); + rpki_stop_retry_timer_event(cache); + break; + + case RPKI_CS_RESET: + /* Resetting cache connection. */ + cache->request_session_id = 1; + cache->serial_num = 0; + rpki_cache_change_state(cache, RPKI_CS_SYNC_START); + break; + + case RPKI_CS_SYNC_START: + /* Requesting for receive ROAs from a cache server. */ + if (cache->request_session_id) + { + /* Send request for Session ID */ + if (rpki_send_reset_query(cache) != RPKI_SUCCESS) + rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT); + } + else + { + /* We have already a session_id. So send a Serial Query and start an incremental sync */ + if (rpki_send_serial_query(cache) != RPKI_SUCCESS) + rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT); + } + break; + + case RPKI_CS_SYNC_RUNNING: + /* The state between Cache Response and End of Data. Only waiting for + * receiving all IP Prefix PDUs and finally a End of Data PDU. */ + break; + + case RPKI_CS_NO_INCR_UPDATE_AVAIL: + /* Server was unable to answer the last Serial Query and sent Cache Reset. */ + rpki_cache_change_state(cache, RPKI_CS_RESET); + break; + + case RPKI_CS_ERROR_NO_DATA_AVAIL: + /* No validation records are available on the cache server. */ + rpki_cache_change_state(cache, RPKI_CS_RESET); + break; + + case RPKI_CS_ERROR_FATAL: + /* Fatal protocol error occurred. */ + rpki_force_restart_proto(cache->p); + break; + + case RPKI_CS_ERROR_TRANSPORT: + /* Error on the transport socket occurred. */ + rpki_close_connection(cache); + rpki_schedule_next_retry(cache); + rpki_stop_refresh_timer_event(cache); + break; + + case RPKI_CS_FAST_RECONNECT: + /* Reconnect without any waiting period */ + rpki_close_connection(cache); + rpki_cache_change_state(cache, RPKI_CS_CONNECTING); + break; + + case RPKI_CS_SHUTDOWN: + bug("This isn't never really called."); + break; + }; +} + + +/* + * RPKI Timer Events + */ + +static void +rpki_schedule_next_refresh(struct rpki_cache *cache) +{ + uint time_to_wait = cache->refresh_interval; + + CACHE_DBG(cache, "after %u seconds", time_to_wait); + tm_start(cache->refresh_timer, time_to_wait); +} + +static void +rpki_schedule_next_retry(struct rpki_cache *cache) +{ + uint time_to_wait = cache->retry_interval; + + CACHE_DBG(cache, "after %u seconds", time_to_wait); + tm_start(cache->retry_timer, time_to_wait); +} + +static void +rpki_schedule_next_expire_check(struct rpki_cache *cache) +{ + /* A minimum time to wait is 1 second */ + uint time_to_wait = MAX(((int)cache->expire_interval - (int)(now - cache->last_update)), 1); + + CACHE_DBG(cache, "after %u seconds", time_to_wait); + tm_start(cache->expire_timer, time_to_wait); +} + +static void +rpki_stop_refresh_timer_event(struct rpki_cache *cache) +{ + CACHE_DBG(cache, "Stop"); + tm_stop(cache->refresh_timer); +} + +static void +rpki_stop_retry_timer_event(struct rpki_cache *cache) +{ + CACHE_DBG(cache, "Stop"); + tm_stop(cache->retry_timer); +} + +static void UNUSED +rpki_stop_expire_timer_event(struct rpki_cache *cache) +{ + CACHE_DBG(cache, "Stop"); + tm_stop(cache->expire_timer); +} + +static int +rpki_do_we_recv_prefix_pdu_in_last_seconds(struct rpki_cache *cache) +{ + if (cache->last_rx_prefix == 0) + return 0; + + return ((now - cache->last_rx_prefix) <= 2); +} + +/** + * rpki_refresh_hook - control a scheduling of downloading data from cache server + * @tm: refresh timer with cache connection instance in data + * + * This function is periodically called during &ESTABLISHED or &SYNC* state + * cache connection. The first refresh schedule is invoked after receiving a + * |End of Data| PDU and has run by some &ERROR is occurred. + */ +static void +rpki_refresh_hook(struct timer *tm) +{ + struct rpki_cache *cache = tm->data; + + CACHE_DBG(cache, "%s", rpki_cache_state_to_str(cache->state)); + + switch (cache->state) + { + case RPKI_CS_ESTABLISHED: + rpki_cache_change_state(cache, RPKI_CS_SYNC_START); + break; + + case RPKI_CS_SYNC_START: + /* We sent Serial/Reset Query in last refresh hook call + * and didn't receive Cache Response yet. It is probably + * troubles with network. */ + case RPKI_CS_SYNC_RUNNING: + /* We sent Serial/Reset Query in last refresh hook call + * and we got Cache Response but didn't get End-Of-Data yet. + * It could be a trouble with network or only too long synchronization. */ + if (!rpki_do_we_recv_prefix_pdu_in_last_seconds(cache)) + { + CACHE_TRACE(D_EVENTS, cache, "Sync takes more time than refresh interval %us, resetting connection", cache->refresh_interval); + rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT); + } + break; + + default: + break; + } + + if (cache->state != RPKI_CS_SHUTDOWN && cache->state != RPKI_CS_ERROR_TRANSPORT) + rpki_schedule_next_refresh(cache); + else + rpki_stop_refresh_timer_event(cache); +} + +/** + * rpki_retry_hook - control a scheduling of retrying connection to cache server + * @tm: retry timer with cache connection instance in data + * + * This function is periodically called during &ERROR* state cache connection. + * The first retry schedule is invoked after any &ERROR* state occurred and + * ends by reaching of &ESTABLISHED state again. + */ +static void +rpki_retry_hook(struct timer *tm) +{ + struct rpki_cache *cache = tm->data; + + CACHE_DBG(cache, "%s", rpki_cache_state_to_str(cache->state)); + + switch (cache->state) + { + case RPKI_CS_ESTABLISHED: + case RPKI_CS_SHUTDOWN: + break; + + case RPKI_CS_CONNECTING: + case RPKI_CS_SYNC_START: + case RPKI_CS_SYNC_RUNNING: + if (!rpki_do_we_recv_prefix_pdu_in_last_seconds(cache)) + { + /* We tried to establish a connection in last retry hook call and haven't done + * yet. It looks like troubles with network. We are aggressive here. */ + CACHE_TRACE(D_EVENTS, cache, "Sync takes more time than retry interval %us, resetting connection.", cache->retry_interval); + rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT); + } + break; + + default: + rpki_cache_change_state(cache, RPKI_CS_CONNECTING); + break; + } + + if (cache->state != RPKI_CS_ESTABLISHED) + rpki_schedule_next_retry(cache); + else + rpki_stop_retry_timer_event(cache); +} + +/** + * rpki_expire_hook - control a expiration of ROA entries + * @tm: expire timer with cache connection instance in data + * + * This function is scheduled after received a |End of Data| PDU. + * A waiting interval is calculated dynamically by last update. + * If we reach an expiration time then we invoke a restarting + * of the protocol. + */ +static void +rpki_expire_hook(struct timer *tm) +{ + struct rpki_cache *cache = tm->data; + + if (cache->last_update == 0) + return; + + CACHE_DBG(cache, "%s", rpki_cache_state_to_str(cache->state)); + + if ((cache->last_update + cache->expire_interval) < now) + { + CACHE_TRACE(D_EVENTS, cache, "All ROAs expired"); + rpki_force_restart_proto(cache->p); + } + else + { + CACHE_DBG(cache, "Remains %d seconds to become ROAs obsolete", (int)cache->expire_interval - (int)(now - cache->last_update)); + rpki_schedule_next_expire_check(cache); + } +} + +/** + * rpki_check_refresh_interval - check validity of refresh interval value + * @seconds: suggested value + * + * This function validates value and should return |NULL|. + * If the check doesn't pass then returns error message. + */ +const char * +rpki_check_refresh_interval(uint seconds) +{ + if (seconds < 1) + return "Minimum allowed refresh interval is 1 second"; + if (seconds > 86400) + return "Maximum allowed refresh interval is 86400 seconds"; + return NULL; +} + +/** + * rpki_check_retry_interval - check validity of retry interval value + * @seconds: suggested value + * + * This function validates value and should return |NULL|. + * If the check doesn't pass then returns error message. + */ +const char * +rpki_check_retry_interval(uint seconds) +{ + if (seconds < 1) + return "Minimum allowed retry interval is 1 second"; + if (seconds > 7200) + return "Maximum allowed retry interval is 7200 seconds"; + return NULL; +} + +/** + * rpki_check_expire_interval - check validity of expire interval value + * @seconds: suggested value + * + * This function validates value and should return |NULL|. + * If the check doesn't pass then returns error message. + */ +const char * +rpki_check_expire_interval(uint seconds) +{ + if (seconds < 600) + return "Minimum allowed expire interval is 600 seconds"; + if (seconds > 172800) + return "Maximum allowed expire interval is 172800 seconds"; + return NULL; +} + + +/* + * RPKI Cache + */ + +static struct rpki_cache * +rpki_init_cache(struct rpki_proto *p, struct rpki_config *cf) +{ + pool *pool = rp_new(p->p.pool, cf->hostname); + + struct rpki_cache *cache = mb_allocz(pool, sizeof(struct rpki_cache)); + + cache->pool = pool; + cache->p = p; + + cache->state = RPKI_CS_SHUTDOWN; + cache->request_session_id = 1; + cache->version = RPKI_MAX_VERSION; + + cache->refresh_interval = cf->refresh_interval; + cache->retry_interval = cf->retry_interval; + cache->expire_interval = cf->expire_interval; + cache->refresh_timer = tm_new_set(pool, &rpki_refresh_hook, cache, 0, 0); + cache->retry_timer = tm_new_set(pool, &rpki_retry_hook, cache, 0, 0); + cache->expire_timer = tm_new_set(pool, &rpki_expire_hook, cache, 0, 0); + + cache->tr_sock = mb_allocz(pool, sizeof(struct rpki_tr_sock)); + cache->tr_sock->cache = cache; + + switch (cf->tr_config.type) + { + case RPKI_TR_TCP: rpki_tr_tcp_init(cache->tr_sock); break; + case RPKI_TR_SSH: rpki_tr_ssh_init(cache->tr_sock); break; + }; + + CACHE_DBG(cache, "Connection object created"); + + return cache; +} + +/** + * rpki_get_cache_ident - give a text representation of cache server name + * @cache: RPKI connection instance + * + * The function converts cache connection into string. + */ +const char * +rpki_get_cache_ident(struct rpki_cache *cache) +{ + return rpki_tr_ident(cache->tr_sock); +} + +static int +rpki_open_connection(struct rpki_cache *cache) +{ + CACHE_TRACE(D_EVENTS, cache, "Opening a connection"); + + if (rpki_tr_open(cache->tr_sock) == RPKI_TR_ERROR) + { + rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT); + return RPKI_TR_ERROR; + } + + return RPKI_TR_SUCCESS; +} + +static void +rpki_close_connection(struct rpki_cache *cache) +{ + CACHE_TRACE(D_EVENTS, cache, "Closing a connection"); + rpki_tr_close(cache->tr_sock); + proto_notify_state(&cache->p->p, PS_START); +} + +static int +rpki_shutdown(struct proto *P) +{ + struct rpki_proto *p = (void *) P; + + rpki_force_restart_proto(p); + + /* Protocol memory pool will be automatically freed */ + return PS_DOWN; +} + + +/* + * RPKI Reconfiguration + */ + +static int +rpki_try_fast_reconnect(struct rpki_cache *cache) +{ + if (cache->state == RPKI_CS_ESTABLISHED) + { + rpki_cache_change_state(cache, RPKI_CS_FAST_RECONNECT); + return SUCCESSFUL_RECONF; + } + + return NEED_RESTART; +} + +/** + * rpki_reconfigure_cache - a cache reconfiguration + * @p: RPKI protocol instance + * @cache: a cache connection + * @new: new RPKI configuration + * @old: old RPKI configuration + * + * This function reconfigures existing single cache server connection with new + * existing configuration. Generally, a change of time intervals could be + * reconfigured without restarting and all others changes requires a restart of + * protocol. Returns |NEED_TO_RESTART| or |SUCCESSFUL_RECONF|. + */ +static int +rpki_reconfigure_cache(struct rpki_proto *p UNUSED, struct rpki_cache *cache, struct rpki_config *new, struct rpki_config *old) +{ + u8 try_fast_reconnect = 0; + + if (strcmp(old->hostname, new->hostname) != 0) + { + CACHE_TRACE(D_EVENTS, cache, "Cache server address changed to %s", new->hostname); + return NEED_RESTART; + } + + if (old->port != new->port) + { + CACHE_TRACE(D_EVENTS, cache, "Cache server port changed to %u", new->port); + return NEED_RESTART; + } + + if (old->tr_config.type != new->tr_config.type) + { + CACHE_TRACE(D_EVENTS, cache, "Transport type changed"); + return NEED_RESTART; + } + else if (new->tr_config.type == RPKI_TR_SSH) + { + struct rpki_tr_ssh_config *ssh_old = (void *) old->tr_config.spec; + struct rpki_tr_ssh_config *ssh_new = (void *) new->tr_config.spec; + if ((strcmp(ssh_old->bird_private_key, ssh_new->bird_private_key) != 0) || + (strcmp(ssh_old->cache_public_key, ssh_new->cache_public_key) != 0) || + (strcmp(ssh_old->user, ssh_new->user) != 0)) + { + CACHE_TRACE(D_EVENTS, cache, "Settings of SSH transport configuration changed"); + try_fast_reconnect = 1; + } + } + +#define TEST_INTERVAL(name, Name) \ + if (cache->name##_interval != new->name##_interval || \ + old->keep_##name##_interval != new->keep_##name##_interval) \ + { \ + cache->name##_interval = new->name##_interval; \ + CACHE_TRACE(D_EVENTS, cache, #Name " interval changed to %u seconds %s", cache->name##_interval, (new->keep_##name##_interval ? "and keep it" : "")); \ + try_fast_reconnect = 1; \ + } + TEST_INTERVAL(refresh, Refresh); + TEST_INTERVAL(retry, Retry); + TEST_INTERVAL(expire, Expire); +#undef TEST_INTERVAL + + if (try_fast_reconnect) + return rpki_try_fast_reconnect(cache); + + return SUCCESSFUL_RECONF; +} + +/** + * rpki_reconfigure - a protocol reconfiguration hook + * @P: a protocol instance + * @CF: a new protocol configuration + * + * This function reconfigures whole protocol. + * It sets new protocol configuration into a protocol structure. + * Returns |NEED_TO_RESTART| or |SUCCESSFUL_RECONF|. + */ +static int +rpki_reconfigure(struct proto *P, struct proto_config *CF) +{ + struct rpki_proto *p = (void *) P; + struct rpki_config *new = (void *) CF; + struct rpki_config *old = (void *) p->p.cf; + struct rpki_cache *cache = p->cache; + + if (!proto_configure_channel(&p->p, &p->roa4_channel, proto_cf_find_channel(CF, NET_ROA4)) || + !proto_configure_channel(&p->p, &p->roa6_channel, proto_cf_find_channel(CF, NET_ROA6))) + return NEED_RESTART; + + if (rpki_reconfigure_cache(p, cache, new, old) != SUCCESSFUL_RECONF) + return NEED_RESTART; + + return SUCCESSFUL_RECONF; +} + + +/* + * RPKI Protocol Glue + */ + +static struct proto * +rpki_init(struct proto_config *CF) +{ + struct proto *P = proto_new(CF); + struct rpki_proto *p = (void *) P; + + proto_configure_channel(&p->p, &p->roa4_channel, proto_cf_find_channel(CF, NET_ROA4)); + proto_configure_channel(&p->p, &p->roa6_channel, proto_cf_find_channel(CF, NET_ROA6)); + + return P; +} + +static int +rpki_start(struct proto *P) +{ + struct rpki_proto *p = (void *) P; + struct rpki_config *cf = (void *) P->cf; + + p->cache = rpki_init_cache(p, cf); + rpki_start_cache(p->cache); + + return PS_START; +} + +static void +rpki_get_status(struct proto *P, byte *buf) +{ + struct rpki_proto *p = (struct rpki_proto *) P; + + if (P->proto_state == PS_DOWN) + { + *buf = 0; + return; + } + + if (p->cache) + bsprintf(buf, "%s", rpki_cache_state_to_str(p->cache->state)); + else + bsprintf(buf, "No cache server configured"); +} + +static void +rpki_show_proto_info_timer(const char *name, uint num, timer *t) +{ + if (t->expires) + cli_msg(-1006, " %-17s %us (remains %us)", name, num, tm_remains(t)); + else + cli_msg(-1006, " %-17s ---", name); +} + +static void +rpki_show_proto_info(struct proto *P) +{ + struct rpki_proto *p = (struct rpki_proto *) P; + struct rpki_config *cf = (void *) p->p.cf; + struct rpki_cache *cache = p->cache; + + if (P->proto_state == PS_DOWN) + return; + + if (cache) + { + const char *transport_name = "---"; + + switch (cf->tr_config.type) + { + case RPKI_TR_SSH: transport_name = "SSHv2"; break; + case RPKI_TR_TCP: transport_name = "Unprotected over TCP"; break; + }; + + cli_msg(-1006, " Cache server: %s", rpki_get_cache_ident(cache)); + cli_msg(-1006, " Status: %s", rpki_cache_state_to_str(cache->state)); + cli_msg(-1006, " Transport: %s", transport_name); + cli_msg(-1006, " Protocol version: %u", cache->version); + + if (cache->request_session_id) + cli_msg(-1006, " Session ID: ---"); + else + cli_msg(-1006, " Session ID: %u", cache->session_id); + + if (cache->last_update) + { + cli_msg(-1006, " Serial number: %u", cache->serial_num); + cli_msg(-1006, " Last update: before %us", now - cache->last_update); + } + else + { + cli_msg(-1006, " Serial number: ---"); + cli_msg(-1006, " Last update: ---"); + } + + rpki_show_proto_info_timer("Refresh interval:", cache->refresh_interval, cache->refresh_timer); + rpki_show_proto_info_timer("Retry interval:", cache->retry_interval, cache->retry_timer); + rpki_show_proto_info_timer("Expire interval:", cache->expire_interval, cache->expire_timer); + + if (p->roa4_channel) + channel_show_info(p->roa4_channel); + else + cli_msg(-1006, " No roa4 channel"); + + if (p->roa6_channel) + channel_show_info(p->roa6_channel); + else + cli_msg(-1006, " No roa6 channel"); + } +} + + +/* + * RPKI Protocol Configuration + */ + +/** + * rpki_check_config - check and complete configuration of RPKI protocol + * @cf: RPKI configuration + * + * This function is called at the end of parsing RPKI protocol configuration. + */ +void +rpki_check_config(struct rpki_config *cf) +{ + /* Do not check templates at all */ + if (cf->c.class == SYM_TEMPLATE) + return; + + if (ipa_zero(cf->ip) && cf->hostname == NULL) + cf_error("IP address or hostname of cache server must be set"); + + /* Set default transport type */ + if (cf->tr_config.spec == NULL) + { + cf->tr_config.spec = cfg_allocz(sizeof(struct rpki_tr_tcp_config)); + cf->tr_config.type = RPKI_TR_TCP; + } + + if (cf->port == 0) + { + /* Set default port numbers */ + switch (cf->tr_config.type) + { + case RPKI_TR_SSH: + cf->port = RPKI_SSH_PORT; + break; + default: + cf->port = RPKI_TCP_PORT; + } + } +} + +static void +rpki_postconfig(struct proto_config *CF) +{ + /* Define default channel */ + if (EMPTY_LIST(CF->channels)) + channel_config_new(NULL, CF->net_type, CF); +} + +static void +rpki_copy_config(struct proto_config *dest UNUSED, struct proto_config *src UNUSED) +{ + /* FIXME: Should copy transport */ +} + +struct protocol proto_rpki = { + .name = "RPKI", + .template = "rpki%d", + .preference = DEF_PREF_RPKI, + .proto_size = sizeof(struct rpki_proto), + .config_size = sizeof(struct rpki_config), + .init = rpki_init, + .start = rpki_start, + .postconfig = rpki_postconfig, + .channel_mask = (NB_ROA4 | NB_ROA6), + .show_proto_info = rpki_show_proto_info, + .shutdown = rpki_shutdown, + .copy_config = rpki_copy_config, + .reconfigure = rpki_reconfigure, + .get_status = rpki_get_status, +}; diff --git a/proto/rpki/rpki.h b/proto/rpki/rpki.h new file mode 100644 index 00000000..80ef83df --- /dev/null +++ b/proto/rpki/rpki.h @@ -0,0 +1,165 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * (c) 2015 Pavel Tvrdik <pawel.tvrdik@gmail.com> + * + * Using RTRlib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef _BIRD_RPKI_H_ +#define _BIRD_RPKI_H_ + +#include "nest/bird.h" +#include "nest/route.h" +#include "nest/protocol.h" +#include "lib/socket.h" +#include "lib/ip.h" + +#include "transport.h" +#include "packets.h" + +#define RPKI_TCP_PORT 323 +#define RPKI_SSH_PORT 22 +#define RPKI_RETRY_INTERVAL 600 +#define RPKI_REFRESH_INTERVAL 3600 +#define RPKI_EXPIRE_INTERVAL 7200 + +#define RPKI_VERSION_0 0 +#define RPKI_VERSION_1 1 +#define RPKI_MAX_VERSION RPKI_VERSION_1 + + +/* + * RPKI Cache + */ + +enum rpki_cache_state { + RPKI_CS_CONNECTING, /* Socket is establishing the transport connection. */ + RPKI_CS_ESTABLISHED, /* Connection is established, socket is waiting for a Serial Notify or expiration of the refresh_interval timer */ + RPKI_CS_RESET, /* Resetting RTR connection. */ + RPKI_CS_SYNC_START, /* Sending a Serial/Reset Query PDU and expecting a Cache Response PDU */ + RPKI_CS_SYNC_RUNNING, /* Receiving validation records from the RTR server. A state between Cache Response PDU and End of Data PDU */ + RPKI_CS_FAST_RECONNECT, /* Reconnect without any waiting period */ + RPKI_CS_NO_INCR_UPDATE_AVAIL, /* Server is unable to answer the last Serial Query and sent Cache Reset. */ + RPKI_CS_ERROR_NO_DATA_AVAIL, /* Server is unable to answer either a Serial Query or a Reset Query because it has no useful data available at this time. */ + RPKI_CS_ERROR_FATAL, /* Fatal protocol error occurred. */ + RPKI_CS_ERROR_TRANSPORT, /* Error on the transport socket occurred. */ + RPKI_CS_SHUTDOWN, /* RTR Socket is stopped. */ +}; + +struct rpki_cache { + pool *pool; /* Pool containing cache objects */ + struct rpki_proto *p; + + struct rpki_tr_sock *tr_sock; /* Transport specific socket */ + enum rpki_cache_state state; /* RPKI_CS_* */ + u32 session_id; + u8 request_session_id; /* 1: have to request new session id; 0: we have already received session id */ + u32 serial_num; /* Serial number denotes the logical version of data from cache server */ + u8 version; /* Protocol version */ + bird_clock_t last_update; /* Last successful synchronization with cache server */ + bird_clock_t last_rx_prefix; /* Last received prefix PDU */ + + /* Intervals can be changed by cache server on the fly */ + u32 refresh_interval; /* Actual refresh interval */ + u32 retry_interval; + u32 expire_interval; + timer *retry_timer; /* Retry timer event */ + timer *refresh_timer; /* Refresh timer event */ + timer *expire_timer; /* Expire timer event */ +}; + +const char *rpki_get_cache_ident(struct rpki_cache *cache); +const char *rpki_cache_state_to_str(enum rpki_cache_state state); + + +/* + * Routes handling + */ + +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); + + +/* + * RPKI Protocol Logic + */ + +void rpki_cache_change_state(struct rpki_cache *cache, const enum rpki_cache_state new_state); + + +/* + * RPKI Timer Events + */ + +const char *rpki_check_refresh_interval(uint seconds); +const char *rpki_check_retry_interval(uint seconds); +const char *rpki_check_expire_interval(uint seconds); + + +/* + * RPKI Protocol Configuration + */ + +struct rpki_proto { + struct proto p; + struct rpki_cache *cache; + + struct channel *roa4_channel; + struct channel *roa6_channel; + u8 refresh_channels; /* For non-incremental updates using rt_refresh_begin(), rt_refresh_end() */ +}; + +struct rpki_config { + struct proto_config c; + const char *hostname; /* Full domain name or stringified IP address of cache server */ + ip_addr ip; /* IP address of cache server or IPA_NONE */ + u16 port; /* Port number of cache server */ + struct rpki_tr_config tr_config; /* Specific transport configuration structure */ + u32 refresh_interval; /* Time interval (in seconds) for periodical downloading data from cache server */ + u32 retry_interval; /* Time interval (in seconds) for an unreachable server */ + u32 expire_interval; /* Maximal lifetime (in seconds) of ROAs without any successful refreshment */ + u8 keep_refresh_interval:1; /* Do not overwrite refresh interval by cache server update */ + u8 keep_retry_interval:1; /* Do not overwrite retry interval by cache server update */ + u8 keep_expire_interval:1; /* Do not overwrite expire interval by cache server update */ +}; + +void rpki_check_config(struct rpki_config *cf); + + +/* + * Logger + */ + +#define RPKI_LOG(log_level, rpki, msg, args...) \ + do { \ + log(log_level "%s: " msg, (rpki)->p.name , ## args); \ + } while(0) + +#if defined(LOCAL_DEBUG) || defined(GLOBAL_DEBUG) +#define CACHE_DBG(cache,msg,args...) \ + do { \ + RPKI_LOG(L_DEBUG, (cache)->p, "%s [%s] %s " msg, rpki_get_cache_ident(cache), rpki_cache_state_to_str((cache)->state), __func__, ## args); \ + } while(0) +#else +#define CACHE_DBG(cache,msg,args...) do { } while(0) +#endif + +#define RPKI_TRACE(level,rpki,msg,args...) \ + do { \ + if ((rpki)->p.debug & level) \ + RPKI_LOG(L_TRACE, rpki, msg, ## args); \ + } while(0) + +#define CACHE_TRACE(level,cache,msg,args...) \ + do { \ + if ((cache)->p->p.debug & level) \ + RPKI_LOG(L_TRACE, (cache)->p, msg, ## args); \ + } while(0) + +#define RPKI_WARN(p, msg, args...) RPKI_LOG(L_WARN, p, msg, ## args); + +#endif /* _BIRD_RPKI_H_ */ diff --git a/proto/rpki/ssh_transport.c b/proto/rpki/ssh_transport.c new file mode 100644 index 00000000..cd49ab90 --- /dev/null +++ b/proto/rpki/ssh_transport.c @@ -0,0 +1,75 @@ +/* + * BIRD -- An implementation of the SSH protocol for the RPKI transport + * + * (c) 2015 CZ.NIC + * (c) 2015 Pavel Tvrdik <pawel.tvrdik@gmail.com> + * + * This file was a part of RTRlib: http://rpki.realmv6.org/ + * This transport implementation uses libssh (http://www.libssh.org/) + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> + +#include "rpki.h" + +static int +rpki_tr_ssh_open(struct rpki_tr_sock *tr) +{ + struct rpki_cache *cache = tr->cache; + struct rpki_config *cf = (void *) cache->p->p.cf; + struct rpki_tr_ssh_config *ssh_cf = (void *) cf->tr_config.spec; + sock *sk = tr->sk; + + sk->type = SK_SSH_ACTIVE; + sk->ssh = mb_allocz(sk->pool, sizeof(struct ssh_sock)); + sk->ssh->username = ssh_cf->user; + sk->ssh->client_privkey_path = ssh_cf->bird_private_key; + sk->ssh->server_hostkey_path = ssh_cf->cache_public_key; + sk->ssh->subsystem = "rpki-rtr"; + sk->ssh->state = SK_SSH_CONNECT; + + if (sk_open(sk) != 0) + return RPKI_TR_ERROR; + + return RPKI_TR_SUCCESS; +} + +static const char * +rpki_tr_ssh_ident(struct rpki_tr_sock *tr) +{ + ASSERT(tr != NULL); + + struct rpki_cache *cache = tr->cache; + struct rpki_config *cf = (void *) cache->p->p.cf; + struct rpki_tr_ssh_config *ssh_cf = (void *) cf->tr_config.spec; + + if (tr->ident != NULL) + return tr->ident; + + const char *username = ssh_cf->user; + const char *host = cf->hostname; + u16 port = cf->port; + + size_t len = strlen(username) + 1 + strlen(host) + 1 + 5 + 1; /* <user> + '@' + <host> + ':' + <port> + '\0' */ + char *ident = mb_alloc(cache->pool, len); + bsnprintf(ident, len, "%s@%s:%u", username, host, port); + tr->ident = ident; + + return tr->ident; +} + +/** + * rpki_tr_ssh_init - initializes the RPKI transport structure for a SSH connection + * @tr: allocated RPKI transport structure + */ +void +rpki_tr_ssh_init(struct rpki_tr_sock *tr) +{ + tr->open_fp = &rpki_tr_ssh_open; + tr->ident_fp = &rpki_tr_ssh_ident; +} diff --git a/proto/rpki/tcp_transport.c b/proto/rpki/tcp_transport.c new file mode 100644 index 00000000..6c05964a --- /dev/null +++ b/proto/rpki/tcp_transport.c @@ -0,0 +1,78 @@ +/* + * BIRD -- An implementation of the TCP protocol for the RPKI protocol transport + * + * (c) 2015 CZ.NIC + * (c) 2015 Pavel Tvrdik <pawel.tvrdik@gmail.com> + * + * This file was a part of RTRlib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include <errno.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include "rpki.h" +#include "sysdep/unix/unix.h" + +static int +rpki_tr_tcp_open(struct rpki_tr_sock *tr) +{ + sock *sk = tr->sk; + + sk->type = SK_TCP_ACTIVE; + + if (sk_open(sk) != 0) + return RPKI_TR_ERROR; + + return RPKI_TR_SUCCESS; +} + +static const char * +rpki_tr_tcp_ident(struct rpki_tr_sock *tr) +{ + ASSERT(tr != NULL); + + struct rpki_cache *cache = tr->cache; + struct rpki_config *cf = (void *) cache->p->p.cf; + + if (tr->ident != NULL) + return tr->ident; + + const char *host = cf->hostname; + ip_addr ip = cf->ip; + u16 port = cf->port; + + size_t colon_and_port_len = 6; /* max ":65535" */ + size_t ident_len; + if (host) + ident_len = strlen(host) + colon_and_port_len + 1; + else + ident_len = IPA_MAX_TEXT_LENGTH + colon_and_port_len + 1; + + char *ident = mb_alloc(cache->pool, ident_len); + if (host) + bsnprintf(ident, ident_len, "%s:%u", host, port); + else + bsnprintf(ident, ident_len, "%I:%u", ip, port); + + tr->ident = ident; + return tr->ident; +} + +/** + * rpki_tr_tcp_init - initializes the RPKI transport structure for a TCP connection + * @tr: allocated RPKI transport structure + */ +void +rpki_tr_tcp_init(struct rpki_tr_sock *tr) +{ + tr->open_fp = &rpki_tr_tcp_open; + tr->ident_fp = &rpki_tr_tcp_ident; +} diff --git a/proto/rpki/transport.c b/proto/rpki/transport.c new file mode 100644 index 00000000..182667be --- /dev/null +++ b/proto/rpki/transport.c @@ -0,0 +1,135 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * (c) 2015 Pavel Tvrdik <pawel.tvrdik@gmail.com> + * + * This file was a part of RTRlib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include <sys/socket.h> +#include <netdb.h> + +#include "rpki.h" +#include "transport.h" +#include "sysdep/unix/unix.h" + +/** + * rpki_hostname_autoresolv - auto-resolve an IP address from a hostname + * @host: domain name of host, e.g. "rpki-validator.realmv6.org" + * + * This function resolves an IP address from a hostname. + * Returns &ip_addr structure with IP address or |IPA_NONE|. + */ +static ip_addr +rpki_hostname_autoresolv(const char *host) +{ + ip_addr addr = {}; + struct addrinfo *res; + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_ADDRCONFIG, + }; + + if (!host) + return IPA_NONE; + + int err_code = getaddrinfo(host, NULL, &hints, &res); + if (err_code != 0) + { + log(L_DEBUG "getaddrinfo failed: %s", gai_strerror(err_code)); + return IPA_NONE; + } + + sockaddr sa = { + .sa = *res->ai_addr, + }; + + uint unused; + sockaddr_read(&sa, res->ai_family, &addr, NULL, &unused); + + freeaddrinfo(res); + return addr; +} + +/** + * rpki_tr_open - prepare and open a socket connection + * @tr: initialized transport socket + * + * Prepare and open a socket connection specified by @tr that must be initialized before. + * This function ends with a calling the sk_open() function. + * Returns RPKI_TR_SUCCESS or RPKI_TR_ERROR. + */ +int +rpki_tr_open(struct rpki_tr_sock *tr) +{ + struct rpki_cache *cache = tr->cache; + struct rpki_config *cf = (void *) cache->p->p.cf; + + ASSERT(tr->sk == NULL); + tr->sk = sk_new(cache->pool); + sock *sk = tr->sk; + + /* sk->type -1 is invalid value, a correct value MUST be set in the specific transport layer in open_fp() hook */ + sk->type = -1; + + sk->tx_hook = rpki_connected_hook; + sk->err_hook = rpki_err_hook; + sk->data = cache; + sk->daddr = cf->ip; + sk->dport = cf->port; + sk->host = cf->hostname; + sk->rbsize = RPKI_RX_BUFFER_SIZE; + sk->tbsize = RPKI_TX_BUFFER_SIZE; + sk->tos = IP_PREC_INTERNET_CONTROL; + + if (ipa_zero2(sk->daddr) && sk->host) + { + sk->daddr = rpki_hostname_autoresolv(sk->host); + if (ipa_zero(sk->daddr)) + { + CACHE_TRACE(D_EVENTS, cache, "Cannot resolve the hostname '%s'", sk->host); + return RPKI_TR_ERROR; + } + } + + return tr->open_fp(tr); +} + +/** + * rpki_tr_close - close socket and prepare it for possible next open + * @tr: successfully opened transport socket + * + * Close socket and free resources. + */ +void +rpki_tr_close(struct rpki_tr_sock *tr) +{ + if (tr->ident) + { + mb_free((char *) tr->ident); + tr->ident = NULL; + } + + if (tr->sk) + { + rfree(tr->sk); + tr->sk = NULL; + } +} + +/** + * rpki_tr_ident - Returns a string identifier for the rpki transport socket + * @tr: successfully opened transport socket + * + * Returns a \0 terminated string identifier for the socket endpoint, e.g. "<host>:<port>". + * Memory is allocated inside @tr structure. + */ +inline const char * +rpki_tr_ident(struct rpki_tr_sock *tr) +{ + return tr->ident_fp(tr); +} diff --git a/proto/rpki/transport.h b/proto/rpki/transport.h new file mode 100644 index 00000000..f90b7e42 --- /dev/null +++ b/proto/rpki/transport.h @@ -0,0 +1,79 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * (c) 2015 Pavel Tvrdik <pawel.tvrdik@gmail.com> + * + * This file was a part of RTRlib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +/* + * The RPKI transport sockets implement the communication channel + * (e.g., SSH, TCP, TCP-AO) between an RPKI server and client. + * + * Before using the transport socket, a tr_socket must be + * initialized based on a protocol-dependent init function (e.g., + * rpki_tr_tcp_init()). + * + * The rpki_tr_* functions call the corresponding function pointers, which are + * passed in the rpki_tr_sock structure, and forward the remaining arguments. + */ + +#ifndef _BIRD_RPKI_TRANSPORT_H_ +#define _BIRD_RPKI_TRANSPORT_H_ + +#include <time.h> + +/* The return values for rpki_tr_ functions */ +enum rpki_tr_rtvals { + RPKI_TR_SUCCESS = 0, /* Operation was successful */ + RPKI_TR_ERROR = -1, /* Error occurred */ + RPKI_TR_WOULDBLOCK = -2, /* No data is available on the socket */ + RPKI_TR_INTR = -3, /* Call was interrupted from a signal */ + RPKI_TR_CLOSED = -4 /* Connection closed */ +}; + +/* A transport socket structure */ +struct rpki_tr_sock { + sock *sk; /* Standard BIRD socket */ + struct rpki_cache *cache; /* Cache server */ + int (*open_fp)(struct rpki_tr_sock *); /* Function that establishes the socket connection */ + const char *(*ident_fp)(struct rpki_tr_sock *); /* Function that returns an identifier for the socket endpoint */ + const char *ident; /* Internal. Use ident_fp() hook instead of this pointer */ +}; + +int rpki_tr_open(struct rpki_tr_sock *tr); +void rpki_tr_close(struct rpki_tr_sock *tr); +const char *rpki_tr_ident(struct rpki_tr_sock *tr); + +/* Types of supported transports */ +enum rpki_tr_type { + RPKI_TR_TCP, /* Unprotected transport over TCP */ + RPKI_TR_SSH, /* Protected transport by SSHv2 connection */ +}; + +/* Common configure structure for transports */ +struct rpki_tr_config { + enum rpki_tr_type type; /* RPKI_TR_TCP or RPKI_TR_SSH */ + const void *spec; /* Specific transport configuration, i.e. rpki_tr_tcp_config or rpki_tr_ssh_config */ +}; + +struct rpki_tr_tcp_config { + /* No internal configuration data */ +}; + +struct rpki_tr_ssh_config { + const char *bird_private_key; /* Filepath to the BIRD server private key */ + const char *cache_public_key; /* Filepath to the public key of cache server, can be file known_hosts */ + const char *user; /* Username for SSH connection */ +}; + +/* ssh_transport.c */ +void rpki_tr_ssh_init(struct rpki_tr_sock *tr); + +/* tcp_transport.c */ +void rpki_tr_tcp_init(struct rpki_tr_sock *tr); + +#endif /* _BIRD_RPKI_TRANSPORT_H_ */ diff --git a/proto/static/Makefile b/proto/static/Makefile index 61fadbea..e38f9b74 100644 --- a/proto/static/Makefile +++ b/proto/static/Makefile @@ -1,6 +1,6 @@ -source=static.c -root-rel=../../ -dir-name=proto/static - -include ../../Rules +src := static.c +obj := $(src-o-files) +$(all-daemon) +$(cf-local) +tests_objs := $(tests_objs) $(src-o-files)
\ No newline at end of file diff --git a/proto/static/config.Y b/proto/static/config.Y index 182721b3..66ae3c98 100644 --- a/proto/static/config.Y +++ b/proto/static/config.Y @@ -13,98 +13,116 @@ CF_HDR CF_DEFINES #define STATIC_CFG ((struct static_config *) this_proto) -static struct static_route *this_srt, *this_srt_nh, *last_srt_nh; +static struct static_route *this_srt, *this_snh; static struct f_inst **this_srt_last_cmd; +static struct static_route * +static_nexthop_new(void) +{ + struct static_route *nh = this_srt; + + if (this_snh) + { + /* Additional next hop */ + nh = cfg_allocz(sizeof(struct static_route)); + nh->net = this_srt->net; + this_snh->mp_next = nh; + } + + nh->dest = RTD_UNICAST; + nh->mp_head = this_srt; + return nh; +}; + static void static_route_finish(void) { - struct static_route *r; - - /* Update undefined use_bfd entries in multipath nexthops */ - if (this_srt->dest == RTD_MULTIPATH) - for (r = this_srt->mp_next; r; r = r->mp_next) - if (r->use_bfd < 0) - r->use_bfd = this_srt->use_bfd; + if (net_type_match(this_srt->net, NB_DEST) == !this_srt->dest) + cf_error("Unexpected or missing nexthop/type"); } CF_DECLS CF_KEYWORDS(STATIC, ROUTE, VIA, DROP, REJECT, PROHIBIT, PREFERENCE, CHECK, LINK) -CF_KEYWORDS(MULTIPATH, WEIGHT, RECURSIVE, IGP, TABLE, BLACKHOLE, UNREACHABLE, BFD) +CF_KEYWORDS(WEIGHT, RECURSIVE, IGP, TABLE, BLACKHOLE, UNREACHABLE, BFD, MPLS) CF_GRAMMAR CF_ADDTO(proto, static_proto '}') -static_proto_start: proto_start STATIC { - this_proto = proto_config_new(&proto_static, $1); - static_init_config((struct static_config *) this_proto); - } - ; +static_proto_start: proto_start STATIC +{ + this_proto = proto_config_new(&proto_static, $1); + init_list(&STATIC_CFG->routes); +}; static_proto: static_proto_start proto_name '{' | static_proto proto_item ';' + | static_proto proto_channel ';' { this_proto->net_type = $2->net_type; } | static_proto CHECK LINK bool ';' { STATIC_CFG->check_link = $4; } - | static_proto IGP TABLE rtable ';' { STATIC_CFG->igp_table = $4; } + | static_proto IGP TABLE rtable ';' { + if ($4->addr_type == NET_IP4) + STATIC_CFG->igp_table_ip4 = $4; + else if ($4->addr_type == NET_IP6) + STATIC_CFG->igp_table_ip6 = $4; + else + cf_error("Incompatible IGP table type"); + } | static_proto stat_route stat_route_opt_list ';' { static_route_finish(); } ; -stat_route0: ROUTE prefix { - this_srt = cfg_allocz(sizeof(struct static_route)); - add_tail(&STATIC_CFG->other_routes, &this_srt->n); - this_srt->net = $2.addr; - this_srt->masklen = $2.len; - this_srt_last_cmd = &(this_srt->cmds); +stat_nexthop: + VIA ipa ipa_scope { + this_snh = static_nexthop_new(); + this_snh->via = $2; + this_snh->iface = $3; + } + | VIA TEXT { + this_snh = static_nexthop_new(); + this_snh->via = IPA_NONE; + this_snh->iface = if_get_by_name($2); + } + | stat_nexthop MPLS label_stack { + this_snh->mls = $3; } - ; + | stat_nexthop WEIGHT expr { + this_snh->weight = $3 - 1; + if (($3<1) || ($3>256)) cf_error("Weight must be in range 1-256"); + } + | stat_nexthop BFD bool { + this_snh->use_bfd = $3; cf_check_bfd($3); + } +; -stat_multipath1: - VIA ipa ipa_scope { - last_srt_nh = this_srt_nh; - this_srt_nh = cfg_allocz(sizeof(struct static_route)); - this_srt_nh->dest = RTD_NONE; - this_srt_nh->via = $2; - this_srt_nh->via_if = $3; - this_srt_nh->if_name = (void *) this_srt; /* really */ - this_srt_nh->use_bfd = -1; /* undefined */ - } - | stat_multipath1 WEIGHT expr { - this_srt_nh->masklen = $3 - 1; /* really */ - if (($3<1) || ($3>256)) cf_error("Weight must be in range 1-256"); - } - | stat_multipath1 BFD bool { - this_srt_nh->use_bfd = $3; cf_check_bfd($3); - } - ; +stat_nexthops: + stat_nexthop + | stat_nexthops stat_nexthop +; -stat_multipath: - stat_multipath1 { this_srt->mp_next = this_srt_nh; } - | stat_multipath stat_multipath1 { last_srt_nh->mp_next = this_srt_nh; } +stat_route0: ROUTE net_any { + this_srt = cfg_allocz(sizeof(struct static_route)); + add_tail(&STATIC_CFG->routes, &this_srt->n); + this_srt->net = $2; + this_srt_last_cmd = &(this_srt->cmds); + this_srt->mp_next = NULL; + this_snh = NULL; + } ; stat_route: - stat_route0 VIA ipa ipa_scope { - this_srt->dest = RTD_ROUTER; + stat_route0 stat_nexthops + | stat_route0 RECURSIVE ipa { + this_srt->dest = RTDX_RECURSIVE; this_srt->via = $3; - this_srt->via_if = $4; - } - | stat_route0 VIA TEXT { - this_srt->dest = RTD_DEVICE; - this_srt->if_name = $3; - rem_node(&this_srt->n); - add_tail(&STATIC_CFG->iface_routes, &this_srt->n); } - | stat_route0 MULTIPATH stat_multipath { - this_srt->dest = RTD_MULTIPATH; - } - | stat_route0 RECURSIVE ipa { + | stat_route0 RECURSIVE ipa MPLS label_stack { this_srt->dest = RTDX_RECURSIVE; this_srt->via = $3; + this_srt->mls = $5; } - + | stat_route0 { this_srt->dest = RTD_NONE; } | stat_route0 DROP { this_srt->dest = RTD_BLACKHOLE; } | stat_route0 REJECT { this_srt->dest = RTD_UNREACHABLE; } | stat_route0 BLACKHOLE { this_srt->dest = RTD_BLACKHOLE; } @@ -114,7 +132,6 @@ stat_route: stat_route_item: cmd { *this_srt_last_cmd = $1; this_srt_last_cmd = &($1->next); } - | BFD bool ';' { this_srt->use_bfd = $2; cf_check_bfd($2); } ; stat_route_opts: diff --git a/proto/static/static.c b/proto/static/static.c index 849067b9..bb1501a5 100644 --- a/proto/static/static.c +++ b/proto/static/static.c @@ -9,33 +9,32 @@ /** * DOC: Static * - * The Static protocol is implemented in a straightforward way. It keeps - * two lists of static routes: one containing interface routes and one - * holding the remaining ones. Interface routes are inserted and removed according - * to interface events received from the core via the if_notify() hook. Routes - * pointing to a neighboring router use a sticky node in the neighbor cache - * to be notified about gaining or losing the neighbor. Special - * routes like black holes or rejects are inserted all the time. + * The Static protocol is implemented in a straightforward way. It keeps a list + * of static routes. Routes of dest RTD_UNICAST have associated sticky node in + * the neighbor cache to be notified about gaining or losing the neighbor and + * about interface-related events (e.g. link down). They may also have a BFD + * request if associated with a BFD session. When a route is notified, + * static_decide() is used to see whether the route activeness is changed. In + * such case, the route is marked as dirty and scheduled to be announced or + * withdrawn, which is done asynchronously from event hook. Routes of other + * types (e.g. black holes) are announced all the time. * - * Multipath routes are tricky. Because these routes depends on - * several neighbors we need to integrate that to the neighbor - * notification handling, we use dummy static_route nodes, one for - * each nexthop. Therefore, a multipath route consists of a master - * static_route node (of dest RTD_MULTIPATH), which specifies prefix - * and is used in most circumstances, and a list of dummy static_route - * nodes (of dest RTD_NONE), which stores info about nexthops and are - * connected to neighbor entries and neighbor notifications. Dummy - * nodes are chained using mp_next, they aren't in other_routes list, - * and abuse some fields (masklen, if_name) for other purposes. + * Multipath routes are a bit tricky. To represent additional next hops, dummy + * static_route nodes are used, which are chained using @mp_next field and link + * to the master node by @mp_head field. Each next hop has a separate neighbor + * entry and an activeness state, but the master node is used for most purposes. + * Note that most functions DO NOT accept dummy nodes as arguments. * * The only other thing worth mentioning is that when asked for reconfiguration, * Static not only compares the two configurations, but it also calculates - * difference between the lists of static routes and it just inserts the - * newly added routes and removes the obsolete ones. + * difference between the lists of static routes and it just inserts the newly + * added routes, removes the obsolete ones and reannounces changed ones. */ #undef LOCAL_DEBUG +#include <stdlib.h> + #include "nest/bird.h" #include "nest/iface.h" #include "nest/protocol.h" @@ -50,107 +49,116 @@ static linpool *static_lp; -static inline rtable * -p_igp_table(struct proto *p) -{ - struct static_config *cf = (void *) p->cf; - return cf->igp_table ? cf->igp_table->table : p->table; -} - static void -static_install(struct proto *p, struct static_route *r, struct iface *ifa) +static_announce_rte(struct static_proto *p, struct static_route *r) { - net *n; - rta a; - rte *e; + rta *a = allocz(RTA_MAX_SIZE); + a->src = p->p.main_source; + a->source = RTS_STATIC; + a->scope = SCOPE_UNIVERSE; + a->dest = r->dest; - if (r->installed > 0) - return; + if (r->dest == RTD_UNICAST) + { + struct static_route *r2; + struct nexthop *nhs = NULL; - DBG("Installing static route %I/%d, rtd=%d\n", r->net, r->masklen, r->dest); - bzero(&a, sizeof(a)); - a.src = p->main_source; - a.source = (r->dest == RTD_DEVICE) ? RTS_STATIC_DEVICE : RTS_STATIC; - a.scope = SCOPE_UNIVERSE; - a.cast = RTC_UNICAST; - a.dest = r->dest; - a.gw = r->via; - a.iface = ifa; - - if (r->dest == RTD_MULTIPATH) + for (r2 = r; r2; r2 = r2->mp_next) { - struct static_route *r2; - struct mpnh *nhs = NULL; - - for (r2 = r->mp_next; r2; r2 = r2->mp_next) - if (r2->installed) - { - struct mpnh *nh = alloca(sizeof(struct mpnh)); - nh->gw = r2->via; - nh->iface = r2->neigh->iface; - nh->weight = r2->masklen; /* really */ - mpnh_insert(&nhs, nh); - } - - /* There is at least one nexthop */ - if (!nhs->next) - { - /* Fallback to unipath route for exactly one nexthop */ - a.dest = RTD_ROUTER; - a.gw = nhs->gw; - a.iface = nhs->iface; - } - else - a.nexthops = nhs; + if (!r2->active) + continue; + + struct nexthop *nh = allocz(NEXTHOP_MAX_SIZE); + nh->gw = r2->via; + nh->iface = r2->neigh->iface; + nh->weight = r2->weight; + if (r2->mls) + { + nh->labels = r2->mls->len; + memcpy(nh->label, r2->mls->stack, r2->mls->len * sizeof(u32)); + } + + nexthop_insert(&nhs, nh); } + if (!nhs) + goto withdraw; + + nexthop_link(a, nhs); + } + if (r->dest == RTDX_RECURSIVE) - rta_set_recursive_next_hop(p->table, &a, p_igp_table(p), &r->via, &r->via); + { + 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); + } - /* We skip rta_lookup() here */ + /* Already announced */ + if (r->state == SRS_CLEAN) + return; - n = net_get(p->table, r->net, r->masklen); - e = rte_get_temp(&a); - e->net = n; + /* We skip rta_lookup() here */ + rte *e = rte_get_temp(a); e->pflags = 0; if (r->cmds) f_eval_rte(r->cmds, &e, static_lp); - rte_update(p, n, e); - r->installed = 1; + rte_update(&p->p, r->net, e); + r->state = SRS_CLEAN; if (r->cmds) lp_flush(static_lp); + + return; + +withdraw: + if (r->state == SRS_DOWN) + return; + + rte_update(&p->p, r->net, NULL); + r->state = SRS_DOWN; } static void -static_remove(struct proto *p, struct static_route *r) +static_mark_rte(struct static_proto *p, struct static_route *r) { - net *n; - - if (!r->installed) + if (r->state == SRS_DIRTY) return; - DBG("Removing static route %I/%d via %I\n", r->net, r->masklen, r->via); - n = net_find(p->table, r->net, r->masklen); - rte_update(p, n, NULL); - r->installed = 0; + r->state = SRS_DIRTY; + BUFFER_PUSH(p->marked) = r; + + if (!ev_active(p->event)) + ev_schedule(p->event); +} + +static void +static_announce_marked(void *P) +{ + struct static_proto *p = P; + + BUFFER_WALK(p->marked, r) + static_announce_rte(P, r); + + BUFFER_FLUSH(p->marked); } static void static_bfd_notify(struct bfd_request *req); static void -static_update_bfd(struct proto *p, struct static_route *r) +static_update_bfd(struct static_proto *p, struct static_route *r) { + /* The @r is a RTD_UNICAST next hop, may be a dummy node */ + struct neighbor *nb = r->neigh; int bfd_up = (nb->scope > 0) && r->use_bfd; if (bfd_up && !r->bfd_req) { // ip_addr local = ipa_nonzero(r->local) ? r->local : nb->ifa->ip; - r->bfd_req = bfd_request_session(p->pool, r->via, nb->ifa->ip, nb->iface, + r->bfd_req = bfd_request_session(p->p.pool, r->via, nb->ifa->ip, nb->iface, static_bfd_notify, r); } @@ -162,212 +170,171 @@ static_update_bfd(struct proto *p, struct static_route *r) } static int -static_decide(struct static_config *cf, struct static_route *r) +static_decide(struct static_proto *p, struct static_route *r) { - /* r->dest != RTD_MULTIPATH, but may be RTD_NONE (part of multipath route) - the route also have to be valid (r->neigh != NULL) */ + /* The @r is a RTD_UNICAST next hop, may be a dummy node */ + + struct static_config *cf = (void *) p->p.cf; + uint old_active = r->active; if (r->neigh->scope < 0) - return 0; + goto fail; if (cf->check_link && !(r->neigh->iface->flags & IF_LINK_UP)) - return 0; + goto fail; - if (r->bfd_req && r->bfd_req->state != BFD_STATE_UP) - return 0; + if (r->bfd_req && (r->bfd_req->state != BFD_STATE_UP)) + goto fail; - return 1; -} + r->active = 1; + return !old_active; +fail: + r->active = 0; + return old_active; +} static void -static_add(struct proto *p, struct static_config *cf, struct static_route *r) +static_add_rte(struct static_proto *p, struct static_route *r) { - DBG("static_add(%I/%d,%d)\n", r->net, r->masklen, r->dest); - switch (r->dest) - { - case RTD_ROUTER: - { - struct neighbor *n = neigh_find2(p, &r->via, r->via_if, NEF_STICKY); - if (n) - { - r->chain = n->data; - n->data = r; - r->neigh = n; - - static_update_bfd(p, r); - if (static_decide(cf, r)) - static_install(p, r, n->iface); - else - static_remove(p, r); - } - else - { - log(L_ERR "Static route destination %I is invalid. Ignoring.", r->via); - static_remove(p, r); - } - break; - } + if (r->dest == RTD_UNICAST) + { + struct static_route *r2; + struct neighbor *n; - case RTD_DEVICE: - break; + for (r2 = r; r2; r2 = r2->mp_next) + { + n = ipa_nonzero(r2->via) ? + neigh_find2(&p->p, &r2->via, r2->iface, NEF_STICKY) : + neigh_find_iface(&p->p, r2->iface); - case RTD_MULTIPATH: + if (!n) { - int count = 0; - struct static_route *r2; - - for (r2 = r->mp_next; r2; r2 = r2->mp_next) - { - struct neighbor *n = neigh_find2(p, &r2->via, r2->via_if, NEF_STICKY); - if (n) - { - r2->chain = n->data; - n->data = r2; - r2->neigh = n; - - static_update_bfd(p, r2); - r2->installed = static_decide(cf, r2); - count += r2->installed; - } - else - { - log(L_ERR "Static route destination %I is invalid. Ignoring.", r2->via); - r2->installed = 0; - } - } - - if (count) - static_install(p, r, NULL); - else - static_remove(p, r); - break; + log(L_WARN "Invalid next hop %I of static route %N", r2->via, r2->net); + continue; } - default: - static_install(p, r, NULL); + r2->neigh = n; + r2->chain = n->data; + n->data = r2; + + static_update_bfd(p, r2); + static_decide(p, r2); } + } + + static_announce_rte(p, r); } static void -static_rte_cleanup(struct proto *p UNUSED, struct static_route *r) +static_reset_rte(struct static_proto *p UNUSED, struct static_route *r) { struct static_route *r2; - if (r->bfd_req) + for (r2 = r; r2; r2 = r2->mp_next) { - rfree(r->bfd_req); - r->bfd_req = NULL; - } + r2->neigh = NULL; + r2->chain = NULL; - if (r->dest == RTD_MULTIPATH) - for (r2 = r->mp_next; r2; r2 = r2->mp_next) - if (r2->bfd_req) - { - rfree(r2->bfd_req); - r2->bfd_req = NULL; - } + r2->state = 0; + r2->active = 0; + + rfree(r2->bfd_req); + r2->bfd_req = NULL; + } } -static int -static_start(struct proto *p) +static void +static_remove_rte(struct static_proto *p, struct static_route *r) { - struct static_config *cf = (void *) p->cf; - struct static_route *r; + if (r->state) + rte_update(&p->p, r->net, NULL); - DBG("Static: take off!\n"); + static_reset_rte(p, r); +} - if (!static_lp) - static_lp = lp_new(&root_pool, 1008); - if (cf->igp_table) - rt_lock_table(cf->igp_table->table); +static inline int +static_same_dest(struct static_route *x, struct static_route *y) +{ + if (x->dest != y->dest) + return 0; + + switch (x->dest) + { + case RTD_UNICAST: + for (; x && y; x = x->mp_next, y = y->mp_next) + { + if (!ipa_equal(x->via, y->via) || + (x->iface != y->iface) || + (x->use_bfd != y->use_bfd) || + (x->weight != y->weight) || + (!x->mls != !y->mls) || + ((x->mls) && (y->mls) && (x->mls->len != y->mls->len))) + return 0; + + if (!x->mls) + continue; + + for (uint i = 0; i < x->mls->len; i++) + if (x->mls->stack[i] != y->mls->stack[i]) + return 0; + } + return !x && !y; - /* We have to go UP before routes could be installed */ - proto_notify_state(p, PS_UP); + case RTDX_RECURSIVE: + if (!ipa_equal(x->via, y->via) || + (!x->mls != !y->mls) || + ((x->mls) && (y->mls) && (x->mls->len != y->mls->len))) + return 0; - WALK_LIST(r, cf->other_routes) - static_add(p, cf, r); - return PS_UP; -} + if (!x->mls) + return 1; -static int -static_shutdown(struct proto *p) -{ - struct static_config *cf = (void *) p->cf; - struct static_route *r; + for (uint i = 0; i < x->mls->len; i++) + if (x->mls->stack[i] != y->mls->stack[i]) + return 0; - /* Just reset the flag, the routes will be flushed by the nest */ - WALK_LIST(r, cf->iface_routes) - r->installed = 0; - WALK_LIST(r, cf->other_routes) - { - static_rte_cleanup(p, r); - r->installed = 0; - } + return 1; - return PS_DOWN; + default: + return 1; + } } -static void -static_cleanup(struct proto *p) +static inline int +static_same_rte(struct static_route *or, struct static_route *nr) { - struct static_config *cf = (void *) p->cf; - - if (cf->igp_table) - rt_unlock_table(cf->igp_table->table); + /* Note that i_same() requires arguments in (new, old) order */ + return static_same_dest(or, nr) && i_same(nr->cmds, or->cmds); } static void -static_update_rte(struct proto *p, struct static_route *r) +static_reconfigure_rte(struct static_proto *p, struct static_route *or, struct static_route *nr) { - switch (r->dest) - { - case RTD_ROUTER: - if (static_decide((struct static_config *) p->cf, r)) - static_install(p, r, r->neigh->iface); - else - static_remove(p, r); - break; - - case RTD_NONE: /* a part of multipath route */ - { - int decision = static_decide((struct static_config *) p->cf, r); - if (decision == r->installed) - break; /* no change */ - r->installed = decision; - - struct static_route *r1, *r2; - int count = 0; - r1 = (void *) r->if_name; /* really */ - for (r2 = r1->mp_next; r2; r2 = r2->mp_next) - count += r2->installed; - - if (count) - { - /* Set of nexthops changed - force reinstall */ - r1->installed = 0; - static_install(p, r1, NULL); - } - else - static_remove(p, r1); + if ((or->state == SRS_CLEAN) && !static_same_rte(or, nr)) + nr->state = SRS_DIRTY; + else + nr->state = or->state; - break; - } - } + static_add_rte(p, nr); + static_reset_rte(p, or); } + static void static_neigh_notify(struct neighbor *n) { - struct proto *p = n->proto; + struct static_proto *p = (void *) n->proto; struct static_route *r; DBG("Static: neighbor notify for %I: iface %p\n", n->addr, n->iface); - for(r=n->data; r; r=r->chain) + for (r = n->data; r; r = r->chain) { static_update_bfd(p, r); - static_update_rte(p, r); + + if (static_decide(p, r)) + static_mark_rte(p, r->mp_head); } } @@ -375,241 +342,232 @@ static void static_bfd_notify(struct bfd_request *req) { struct static_route *r = req->data; - struct proto *p = r->neigh->proto; + struct static_proto *p = (void *) r->neigh->proto; // if (req->down) TRACE(D_EVENTS, "BFD session down for nbr %I on %s", XXXX); - static_update_rte(p, r); + if (static_decide(p, r)) + static_mark_rte(p, r->mp_head); } -static void -static_dump_rt(struct static_route *r) +static int +static_rte_mergable(rte *pri UNUSED, rte *sec UNUSED) { - debug("%-1I/%2d: ", r->net, r->masklen); - switch (r->dest) - { - case RTD_ROUTER: - debug("via %I\n", r->via); - break; - case RTD_DEVICE: - debug("dev %s\n", r->if_name); - break; - default: - debug("rtd %d\n", r->dest); - break; - } + return 1; } + static void -static_dump(struct proto *p) +static_postconfig(struct proto_config *CF) { - struct static_config *c = (void *) p->cf; + struct static_config *cf = (void *) CF; struct static_route *r; - debug("Independent static routes:\n"); - WALK_LIST(r, c->other_routes) - static_dump_rt(r); - debug("Device static routes:\n"); - WALK_LIST(r, c->iface_routes) - static_dump_rt(r); -} + if (EMPTY_LIST(CF->channels)) + cf_error("Channel not specified"); -static void -static_if_notify(struct proto *p, unsigned flags, struct iface *i) -{ - struct static_route *r; - struct static_config *c = (void *) p->cf; + struct channel_config *cc = proto_cf_main_channel(CF); - if (flags & IF_CHANGE_UP) - { - WALK_LIST(r, c->iface_routes) - if (!strcmp(r->if_name, i->name)) - static_install(p, r, i); - } - else if (flags & IF_CHANGE_DOWN) - { - WALK_LIST(r, c->iface_routes) - if (!strcmp(r->if_name, i->name)) - static_remove(p, r); - } -} + if (!cf->igp_table_ip4) + cf->igp_table_ip4 = (cc->table->addr_type == NET_IP4) ? + cc->table : cf->c.global->def_tables[NET_IP4]; -int -static_rte_mergable(rte *pri UNUSED, rte *sec UNUSED) -{ - return 1; -} + if (!cf->igp_table_ip6) + cf->igp_table_ip6 = (cc->table->addr_type == NET_IP6) ? + cc->table : cf->c.global->def_tables[NET_IP6]; -void -static_init_config(struct static_config *c) -{ - init_list(&c->iface_routes); - init_list(&c->other_routes); + WALK_LIST(r, cf->routes) + if (r->net && (r->net->type != CF->net_type)) + cf_error("Route %N incompatible with channel type", r->net); } static struct proto * -static_init(struct proto_config *c) +static_init(struct proto_config *CF) { - struct proto *p = proto_new(c, sizeof(struct proto)); + struct proto *P = proto_new(CF); + struct static_proto *p = (void *) P; + struct static_config *cf = (void *) CF; - p->neigh_notify = static_neigh_notify; - p->if_notify = static_if_notify; - p->rte_mergable = static_rte_mergable; + P->main_channel = proto_add_channel(P, proto_cf_main_channel(CF)); - return p; -} + P->neigh_notify = static_neigh_notify; + P->rte_mergable = static_rte_mergable; -static inline int -static_same_net(struct static_route *x, struct static_route *y) -{ - return ipa_equal(x->net, y->net) && (x->masklen == y->masklen); + if (cf->igp_table_ip4) + p->igp_table_ip4 = cf->igp_table_ip4->table; + + if (cf->igp_table_ip6) + p->igp_table_ip6 = cf->igp_table_ip6->table; + + return P; } -static inline int -static_same_dest(struct static_route *x, struct static_route *y) +static int +static_start(struct proto *P) { - if (x->dest != y->dest) - return 0; + struct static_proto *p = (void *) P; + struct static_config *cf = (void *) P->cf; + struct static_route *r; - switch (x->dest) - { - case RTD_ROUTER: - return ipa_equal(x->via, y->via) && (x->via_if == y->via_if); + if (!static_lp) + static_lp = lp_new(&root_pool, LP_GOOD_SIZE(1024)); - case RTD_DEVICE: - return !strcmp(x->if_name, y->if_name); + if (p->igp_table_ip4) + rt_lock_table(p->igp_table_ip4); - case RTD_MULTIPATH: - for (x = x->mp_next, y = y->mp_next; - x && y; - x = x->mp_next, y = y->mp_next) - if (!ipa_equal(x->via, y->via) || (x->via_if != y->via_if) || (x->use_bfd != y->use_bfd)) - return 0; - return !x && !y; + if (p->igp_table_ip6) + rt_lock_table(p->igp_table_ip6); - case RTDX_RECURSIVE: - return ipa_equal(x->via, y->via); + p->event = ev_new(p->p.pool); + p->event->hook = static_announce_marked; + p->event->data = p; - default: - return 1; - } + BUFFER_INIT(p->marked, p->p.pool, 4); + + /* We have to go UP before routes could be installed */ + proto_notify_state(P, PS_UP); + + WALK_LIST(r, cf->routes) + static_add_rte(p, r); + + return PS_UP; } -static inline int -static_same_rte(struct static_route *x, struct static_route *y) +static int +static_shutdown(struct proto *P) { - /* Note that i_same() requires arguments in (new, old) order */ - return static_same_dest(x, y) && i_same(y->cmds, x->cmds); -} + struct static_proto *p = (void *) P; + struct static_config *cf = (void *) P->cf; + struct static_route *r; + /* Just reset the flag, the routes will be flushed by the nest */ + WALK_LIST(r, cf->routes) + static_reset_rte(p, r); + + return PS_DOWN; +} static void -static_match(struct proto *p, struct static_route *r, struct static_config *n) +static_cleanup(struct proto *P) { - struct static_route *t; - - /* - * For given old route *r we find whether a route to the same - * network is also in the new route list. In that case, we keep the - * route and possibly update the route later if destination changed. - * Otherwise, we remove the route. - */ + struct static_proto *p = (void *) P; - if (r->neigh) - r->neigh->data = NULL; + if (p->igp_table_ip4) + rt_unlock_table(p->igp_table_ip4); - WALK_LIST(t, n->iface_routes) - if (static_same_net(r, t)) - goto found; + if (p->igp_table_ip6) + rt_unlock_table(p->igp_table_ip6); +} - WALK_LIST(t, n->other_routes) - if (static_same_net(r, t)) - goto found; +static void +static_dump_rte(struct static_route *r) +{ + debug("%-1N: ", r->net); + if (r->dest == RTD_UNICAST) + if (r->iface && ipa_zero(r->via)) + debug("dev %s\n", r->iface->name); + else + debug("via %I%J\n", r->via, r->iface); + else + debug("rtd %d\n", r->dest); +} - static_remove(p, r); - return; +static void +static_dump(struct proto *P) +{ + struct static_config *c = (void *) P->cf; + struct static_route *r; - found: - /* If destination is different, force reinstall */ - if ((r->installed > 0) && !static_same_rte(r, t)) - t->installed = -1; - else - t->installed = r->installed; + debug("Static routes:\n"); + WALK_LIST(r, c->routes) + static_dump_rte(r); } -static inline rtable * -cf_igp_table(struct static_config *cf) +#define IGP_TABLE(cf, sym) ((cf)->igp_table_##sym ? (cf)->igp_table_##sym ->table : NULL ) + +static inline int +static_cmp_rte(const void *X, const void *Y) { - return cf->igp_table ? cf->igp_table->table : NULL; + struct static_route *x = *(void **)X, *y = *(void **)Y; + return net_compare(x->net, y->net); } static int -static_reconfigure(struct proto *p, struct proto_config *new) +static_reconfigure(struct proto *P, struct proto_config *CF) { - struct static_config *o = (void *) p->cf; - struct static_config *n = (void *) new; - struct static_route *r; + struct static_proto *p = (void *) P; + struct static_config *o = (void *) P->cf; + struct static_config *n = (void *) CF; + struct static_route *r, *r2, *or, *nr; + + /* Check change in IGP tables */ + if ((IGP_TABLE(o, ip4) != IGP_TABLE(n, ip4)) || + (IGP_TABLE(o, ip6) != IGP_TABLE(n, ip6))) + return 0; - if (cf_igp_table(o) != cf_igp_table(n)) + if (!proto_configure_channel(P, &P->main_channel, proto_cf_main_channel(CF))) return 0; - /* Delete all obsolete routes and reset neighbor entries */ - WALK_LIST(r, o->iface_routes) - static_match(p, r, n); - WALK_LIST(r, o->other_routes) - static_match(p, r, n); + p->p.cf = CF; - /* Now add all new routes, those not changed will be ignored by static_install() */ - WALK_LIST(r, n->iface_routes) - { - struct iface *ifa; - if ((ifa = if_find_by_name(r->if_name)) && (ifa->flags & IF_UP)) - static_install(p, r, ifa); - } - WALK_LIST(r, n->other_routes) - static_add(p, n, r); + /* Reset route lists in neighbor entries */ + WALK_LIST(r, o->routes) + for (r2 = r; r2; r2 = r2->mp_next) + if (r2->neigh) + r2->neigh->data = NULL; - WALK_LIST(r, o->other_routes) - static_rte_cleanup(p, r); + /* Reconfigure initial matching sequence */ + for (or = HEAD(o->routes), nr = HEAD(n->routes); + NODE_VALID(or) && NODE_VALID(nr) && net_equal(or->net, nr->net); + or = NODE_NEXT(or), nr = NODE_NEXT(nr)) + static_reconfigure_rte(p, or, nr); - return 1; -} + if (!NODE_VALID(or) && !NODE_VALID(nr)) + return 1; -static void -static_copy_routes(list *dlst, list *slst) -{ - struct static_route *dr, *sr; + /* Reconfigure remaining routes, sort them to find matching pairs */ + struct static_route *or2, *nr2, **orbuf, **nrbuf; + uint ornum = 0, nrnum = 0, orpos = 0, nrpos = 0, i; - init_list(dlst); - WALK_LIST(sr, *slst) - { - /* copy one route */ - dr = cfg_alloc(sizeof(struct static_route)); - memcpy(dr, sr, sizeof(struct static_route)); - - /* This fn is supposed to be called on fresh src routes, which have 'live' - fields (like .chain, .neigh or .installed) zero, so no need to zero them */ - - /* We need to copy multipath chain, because there are backptrs in 'if_name' */ - if (dr->dest == RTD_MULTIPATH) - { - struct static_route *md, *ms, **mp_last; - - mp_last = &(dr->mp_next); - for (ms = sr->mp_next; ms; ms = ms->mp_next) - { - md = cfg_alloc(sizeof(struct static_route)); - memcpy(md, ms, sizeof(struct static_route)); - md->if_name = (void *) dr; /* really */ - - *mp_last = md; - mp_last = &(md->mp_next); - } - *mp_last = NULL; - } - - add_tail(dlst, (node *) dr); - } + for (or2 = or; NODE_VALID(or2); or2 = NODE_NEXT(or2)) + ornum++; + + for (nr2 = nr; NODE_VALID(nr2); nr2 = NODE_NEXT(nr2)) + nrnum++; + + orbuf = xmalloc(ornum * sizeof(void *)); + nrbuf = xmalloc(nrnum * sizeof(void *)); + + for (i = 0, or2 = or; i < ornum; i++, or2 = NODE_NEXT(or2)) + orbuf[i] = or2; + + for (i = 0, nr2 = nr; i < nrnum; i++, nr2 = NODE_NEXT(nr2)) + nrbuf[i] = nr2; + + qsort(orbuf, ornum, sizeof(struct static_route *), static_cmp_rte); + qsort(nrbuf, nrnum, sizeof(struct static_route *), static_cmp_rte); + + while ((orpos < ornum) && (nrpos < nrnum)) + { + int x = net_compare(orbuf[orpos]->net, nrbuf[nrpos]->net); + if (x < 0) + static_remove_rte(p, orbuf[orpos++]); + else if (x > 0) + static_add_rte(p, nrbuf[nrpos++]); + else + static_reconfigure_rte(p, orbuf[orpos++], nrbuf[nrpos++]); + } + + while (orpos < ornum) + static_remove_rte(p, orbuf[orpos++]); + + while (nrpos < nrnum) + static_add_rte(p, nrbuf[nrpos++]); + + xfree(orbuf); + xfree(nrbuf); + + return 1; } static void @@ -618,53 +576,64 @@ static_copy_config(struct proto_config *dest, struct proto_config *src) struct static_config *d = (struct static_config *) dest; struct static_config *s = (struct static_config *) src; - /* Shallow copy of everything */ - proto_copy_rest(dest, src, sizeof(struct static_config)); + struct static_route *srt, *snh; - /* Copy route lists */ - static_copy_routes(&d->iface_routes, &s->iface_routes); - static_copy_routes(&d->other_routes, &s->other_routes); -} + /* Copy route list */ + init_list(&d->routes); + WALK_LIST(srt, s->routes) + { + struct static_route *drt = NULL, *dnh = NULL, **dnp = &drt; + for (snh = srt; snh; snh = snh->mp_next) + { + dnh = cfg_alloc(sizeof(struct static_route)); + memcpy(dnh, snh, sizeof(struct static_route)); -struct protocol proto_static = { - .name = "Static", - .template = "static%d", - .preference = DEF_PREF_STATIC, - .config_size = sizeof(struct static_config), - .init = static_init, - .dump = static_dump, - .start = static_start, - .shutdown = static_shutdown, - .cleanup = static_cleanup, - .reconfigure = static_reconfigure, - .copy_config = static_copy_config -}; + if (!drt) + add_tail(&d->routes, &(dnh->n)); + + *dnp = dnh; + dnp = &(dnh->mp_next); + + if (snh->mp_head) + dnh->mp_head = drt; + } + } +} static void static_show_rt(struct static_route *r) { - byte via[STD_ADDRESS_P_LENGTH + 16]; - switch (r->dest) + { + case RTD_UNICAST: + { + struct static_route *r2; + + cli_msg(-1009, "%N", r->net); + for (r2 = r; r2; r2 = r2->mp_next) { - case RTD_ROUTER: bsprintf(via, "via %I%J", r->via, r->via_if); break; - case RTD_DEVICE: bsprintf(via, "dev %s", r->if_name); break; - case RTD_BLACKHOLE: bsprintf(via, "blackhole"); break; - case RTD_UNREACHABLE: bsprintf(via, "unreachable"); break; - case RTD_PROHIBIT: bsprintf(via, "prohibited"); break; - case RTD_MULTIPATH: bsprintf(via, "multipath"); break; - case RTDX_RECURSIVE: bsprintf(via, "recursive %I", r->via); break; - default: bsprintf(via, "???"); + if (r2->iface && ipa_zero(r2->via)) + cli_msg(-1009, "\tdev %s%s%s", r2->iface->name, + r2->bfd_req ? " (bfd)" : "", r2->active ? "" : " (dormant)"); + else + cli_msg(-1009, "\tvia %I%J%s%s", r2->via, r2->iface, + r2->bfd_req ? " (bfd)" : "", r2->active ? "" : " (dormant)"); } - cli_msg(-1009, "%I/%d %s%s%s", r->net, r->masklen, via, - r->bfd_req ? " (bfd)" : "", r->installed ? "" : " (dormant)"); + break; + } - struct static_route *r2; - if (r->dest == RTD_MULTIPATH) - for (r2 = r->mp_next; r2; r2 = r2->mp_next) - cli_msg(-1009, "\tvia %I%J weight %d%s%s", r2->via, r2->via_if, r2->masklen + 1, /* really */ - r2->bfd_req ? " (bfd)" : "", r2->installed ? "" : " (dormant)"); + case RTD_NONE: + case RTD_BLACKHOLE: + case RTD_UNREACHABLE: + case RTD_PROHIBIT: + cli_msg(-1009, "%N\t%s", r->net, rta_dest_names[r->dest]); + break; + + case RTDX_RECURSIVE: + cli_msg(-1009, "%N\trecursive %I", r->net, r->via); + break; + } } void @@ -673,9 +642,25 @@ static_show(struct proto *P) struct static_config *c = (void *) P->cf; struct static_route *r; - WALK_LIST(r, c->other_routes) - static_show_rt(r); - WALK_LIST(r, c->iface_routes) + WALK_LIST(r, c->routes) static_show_rt(r); cli_msg(0, ""); } + + +struct protocol proto_static = { + .name = "Static", + .template = "static%d", + .preference = DEF_PREF_STATIC, + .channel_mask = NB_ANY, + .proto_size = sizeof(struct static_proto), + .config_size = sizeof(struct static_config), + .postconfig = static_postconfig, + .init = static_init, + .dump = static_dump, + .start = static_start, + .shutdown = static_shutdown, + .cleanup = static_cleanup, + .reconfigure = static_reconfigure, + .copy_config = static_copy_config +}; diff --git a/proto/static/static.h b/proto/static/static.h index 6b047234..c84dfa98 100644 --- a/proto/static/static.h +++ b/proto/static/static.h @@ -11,41 +11,60 @@ #include "nest/route.h" #include "nest/bfd.h" +#include "lib/buffer.h" struct static_config { struct proto_config c; - list iface_routes; /* Routes to search on interface events */ - list other_routes; /* Routes hooked to neighbor cache and reject routes */ + list routes; /* List of static routes (struct static_route) */ int check_link; /* Whether iface link state is used */ - struct rtable_config *igp_table; /* Table used for recursive next hop lookups */ + struct rtable_config *igp_table_ip4; /* Table for recursive IPv4 next hop lookups */ + struct rtable_config *igp_table_ip6; /* Table for recursive IPv6 next hop lookups */ }; +struct static_proto { + struct proto p; -void static_init_config(struct static_config *); + struct event *event; /* Event for announcing updated routes */ + BUFFER(struct static_route *) marked; /* Routes marked for reannouncement */ + rtable *igp_table_ip4; /* Table for recursive IPv4 next hop lookups */ + rtable *igp_table_ip6; /* Table for recursive IPv6 next hop lookups */ +}; struct static_route { node n; - struct static_route *chain; /* Next for the same neighbor */ - ip_addr net; /* Network we route */ - int masklen; /* Mask length */ - int dest; /* Destination type (RTD_*) */ + net_addr *net; /* Network we route */ ip_addr via; /* Destination router */ - struct iface *via_if; /* Destination iface, for link-local vias */ - struct neighbor *neigh; - byte *if_name; /* Name for RTD_DEVICE routes */ - struct static_route *mp_next; /* Nexthops for RTD_MULTIPATH routes */ + struct iface *iface; /* Destination iface, for link-local vias or device routes */ + struct neighbor *neigh; /* Associated neighbor entry */ + struct static_route *chain; /* Next for the same neighbor */ + struct static_route *mp_head; /* First nexthop of this route */ + struct static_route *mp_next; /* Nexthops for multipath routes */ struct f_inst *cmds; /* List of commands for setting attributes */ - int installed; /* Installed in rt table, -1 for reinstall */ - int use_bfd; /* Configured to use BFD */ + byte dest; /* Destination type (RTD_*) */ + byte state; /* State of route announcement (SRS_*) */ + byte active; /* Next hop is active (nbr/iface/BFD available) */ + byte weight; /* Multipath next hop weight */ + byte use_bfd; /* Configured to use BFD */ struct bfd_request *bfd_req; /* BFD request, if BFD is used */ + mpls_label_stack *mls; /* MPLS label stack; may be NULL */ }; -/* Dummy nodes (parts of multipath route) abuses masklen field for weight - and if_name field for a ptr to the master (RTD_MULTIPATH) node. */ - +/* + * Note that data fields neigh, chain, state, active and bfd_req are runtime + * data, not real configuration data. Must be handled carefully. + * + * Regular (i.e. dest == RTD_UNICAST) routes use static_route structure for + * additional next hops (fields mp_head, mp_next). Note that 'state' is for + * whole route, while 'active' is for each next hop. Also note that fields + * mp_head, mp_next, active are zero for other kinds of routes. + */ #define RTDX_RECURSIVE 0x7f /* Phony dest value for recursive routes */ +#define SRS_DOWN 0 /* Route is not announced */ +#define SRS_CLEAN 1 /* Route is active and announced */ +#define SRS_DIRTY 2 /* Route changed since announcement */ + void static_show(struct proto *); #endif diff --git a/sysdep/bsd/Makefile b/sysdep/bsd/Makefile new file mode 100644 index 00000000..dfa32747 --- /dev/null +++ b/sysdep/bsd/Makefile @@ -0,0 +1,6 @@ +src := krt-sock.c +obj := $(src-o-files) +$(all-daemon) +$(conf-y-targets): $(s)krt-sock.Y + +tests_objs := $(tests_objs) $(src-o-files) diff --git a/sysdep/bsd/Modules b/sysdep/bsd/Modules deleted file mode 100644 index 39db88e9..00000000 --- a/sysdep/bsd/Modules +++ /dev/null @@ -1,5 +0,0 @@ -krt-sock.c -krt-sock.Y -krt-sys.h -sysio.h -setkey.h diff --git a/sysdep/bsd/krt-sock.c b/sysdep/bsd/krt-sock.c index 9c9df51d..df639816 100644 --- a/sysdep/bsd/krt-sock.c +++ b/sysdep/bsd/krt-sock.c @@ -28,9 +28,9 @@ #include "nest/route.h" #include "nest/protocol.h" #include "nest/iface.h" -#include "lib/timer.h" -#include "lib/unix.h" -#include "lib/krt.h" +#include "sysdep/unix/timer.h" +#include "sysdep/unix/unix.h" +#include "sysdep/unix/krt.h" #include "lib/string.h" #include "lib/socket.h" @@ -72,7 +72,6 @@ #endif - /* Dynamic max number of tables */ int krt_max_tables; @@ -136,7 +135,7 @@ extern int setfib(int fib); /* table_id -> krt_proto map */ #ifdef KRT_SHARED_SOCKET -static struct krt_proto *krt_table_map[KRT_MAX_TABLES]; +static struct krt_proto *krt_table_map[KRT_MAX_TABLES][2]; #endif @@ -147,10 +146,9 @@ krt_capable(rte *e) { rta *a = e->attrs; + /* XXXX device routes are broken */ return - a->cast == RTC_UNICAST && - (a->dest == RTD_ROUTER - || a->dest == RTD_DEVICE + ((a->dest == RTD_UNICAST && ipa_nonzero(a->nh.gw) && !a->nh.next) /* No multipath support */ #ifdef RTF_REJECT || a->dest == RTD_UNREACHABLE #endif @@ -191,12 +189,11 @@ krt_send_route(struct krt_proto *p, int cmd, rte *e) net *net = e->net; rta *a = e->attrs; static int msg_seq; - struct iface *j, *i = a->iface; + struct iface *j, *i = a->nh.iface; int l; struct ks_msg msg; char *body = (char *)msg.buf; sockaddr gate, mask, dst; - ip_addr gw; DBG("krt-sock: send %I/%d via %I\n", net->n.prefix, net->n.pxlen, a->gw); @@ -207,7 +204,8 @@ krt_send_route(struct krt_proto *p, int cmd, rte *e) msg.rtm.rtm_addrs = RTA_DST; msg.rtm.rtm_flags = RTF_UP | RTF_PROTO1; - if (net->n.pxlen == MAX_PREFIX_LENGTH) + /* XXXX */ + if (net_pxlen(net->n.addr) == net_max_prefix_length[net->n.addr->type]) msg.rtm.rtm_flags |= RTF_HOST; else msg.rtm.rtm_addrs |= RTA_NETMASK; @@ -225,14 +223,12 @@ krt_send_route(struct krt_proto *p, int cmd, rte *e) msg.rtm.rtm_flags |= RTF_BLACKHOLE; #endif - /* This is really very nasty, but I'm not able - * to add "(reject|blackhole)" route without - * gateway set + /* + * This is really very nasty, but I'm not able to add reject/blackhole route + * without gateway address. */ - if(!i) + if (!i) { - i = HEAD(iface_list); - WALK_LIST(j, iface_list) { if (j->flags & IF_LOOPBACK) @@ -241,52 +237,74 @@ krt_send_route(struct krt_proto *p, int cmd, rte *e) break; } } + + if (!i) + { + log(L_ERR "KRT: Cannot find loopback iface"); + return -1; + } } - gw = a->gw; + int af = AF_UNSPEC; -#ifdef IPV6 - /* Embed interface ID to link-local address */ - if (ipa_is_link_local(gw)) - _I0(gw) = 0xfe800000 | (i->index & 0x0000ffff); -#endif + switch (net->n.addr->type) { + case NET_IP4: + af = AF_INET; + break; + case NET_IP6: + af = AF_INET6; + break; + default: + log(L_ERR "KRT: Not sending route %N to kernel", net->n.addr); + return -1; + } - sockaddr_fill(&dst, BIRD_AF, net->n.prefix, NULL, 0); - sockaddr_fill(&mask, BIRD_AF, ipa_mkmask(net->n.pxlen), NULL, 0); - sockaddr_fill(&gate, BIRD_AF, gw, NULL, 0); + sockaddr_fill(&dst, af, net_prefix(net->n.addr), NULL, 0); + sockaddr_fill(&mask, af, net_pxmask(net->n.addr), NULL, 0); switch (a->dest) { - case RTD_ROUTER: + case RTD_UNICAST: + if (ipa_nonzero(a->nh.gw)) + { + ip_addr gw = a->nh.gw; + + /* Embed interface ID to link-local address */ + if (ipa_is_link_local(gw)) + _I0(gw) = 0xfe800000 | (i->index & 0x0000ffff); + + sockaddr_fill(&gate, af, gw, NULL, 0); msg.rtm.rtm_flags |= RTF_GATEWAY; msg.rtm.rtm_addrs |= RTA_GATEWAY; break; + } #ifdef RTF_REJECT - case RTD_UNREACHABLE: + case RTD_UNREACHABLE: #endif #ifdef RTF_BLACKHOLE - case RTD_BLACKHOLE: + case RTD_BLACKHOLE: #endif - case RTD_DEVICE: - if(i) - { + { + /* Fallback for all other valid cases */ + if (!i->addr) + { + log(L_ERR "KRT: interface %s has no IP addess", i->name); + return -1; + } + #ifdef RTF_CLONING - if (cmd == RTM_ADD && (i->flags & IF_MULTIACCESS) != IF_MULTIACCESS) /* PTP */ - msg.rtm.rtm_flags |= RTF_CLONING; + if (cmd == RTM_ADD && (i->flags & IF_MULTIACCESS) != IF_MULTIACCESS) /* PTP */ + msg.rtm.rtm_flags |= RTF_CLONING; #endif - if(!i->addr) { - log(L_ERR "KRT: interface %s has no IP addess", i->name); - return -1; - } + sockaddr_fill(&gate, ipa_is_ip4(i->addr->ip) ? AF_INET : AF_INET6, i->addr->ip, NULL, 0); + msg.rtm.rtm_addrs |= RTA_GATEWAY; + break; + } - sockaddr_fill(&gate, BIRD_AF, i->addr->ip, NULL, 0); - msg.rtm.rtm_addrs |= RTA_GATEWAY; - } - break; - default: - bug("krt-sock: unknown flags, but not filtered"); + default: + bug("krt-sock: unknown flags, but not filtered"); } msg.rtm.rtm_index = i->index; @@ -299,7 +317,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 %I/%d to kernel: %m", net->n.prefix, net->n.pxlen); + log(L_ERR "KRT: Error sending route %N to kernel: %m", net->n.addr); return -1; } @@ -331,10 +349,12 @@ 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; void *body = (char *)msg->buf; int new = (msg->rtm.rtm_type != RTM_DELETE); char *errmsg = "KRT: Invalid route received"; @@ -352,42 +372,64 @@ krt_read_route(struct ks_msg *msg, struct krt_proto *p, int scan) if (flags & RTF_LLINFO) SKIP("link-local\n"); -#ifdef KRT_SHARED_SOCKET - if (!scan) - { - int table_id = msg->rtm.rtm_tableid; - p = (table_id < KRT_MAX_TABLES) ? krt_table_map[table_id] : NULL; - - if (!p) - SKIP("unknown table id %d\n", table_id); - } -#endif - GETADDR(&dst, RTA_DST); GETADDR(&gate, RTA_GATEWAY); GETADDR(&mask, RTA_NETMASK); - if (dst.sa.sa_family != BIRD_AF) - SKIP("invalid DST"); + switch (dst.sa.sa_family) { + case AF_INET: + ipv6 = 0; + break; + case AF_INET6: + ipv6 = 1; + break; + default: + SKIP("invalid DST"); + } + + /* We do not test family for RTA_NETMASK, because BSD sends us + some strange values, but interpreting them as IPv4/IPv6 works */ + mask.sa.sa_family = dst.sa.sa_family; idst = ipa_from_sa(&dst); imask = ipa_from_sa(&mask); - igate = (gate.sa.sa_family == BIRD_AF) ? ipa_from_sa(&gate) : IPA_NONE; + igate = (gate.sa.sa_family == dst.sa.sa_family) ? ipa_from_sa(&gate) : IPA_NONE; - /* We do not test family for RTA_NETMASK, because BSD sends us - some strange values, but interpreting them as IPv4/IPv6 works */ +#ifdef KRT_SHARED_SOCKET + if (!scan) + { + int table_id = msg->rtm.rtm_tableid; + p = (table_id < KRT_MAX_TABLES) ? krt_table_map[table_id][ipv6] : NULL; + if (!p) + SKIP("unknown table id %d\n", table_id); + } +#endif + if ((!ipv6) && (p->p.main_channel->table->addr_type != NET_IP4)) + SKIP("reading only IPv4 routes"); + if ( ipv6 && (p->p.main_channel->table->addr_type != NET_IP6)) + SKIP("reading only IPv6 routes"); int c = ipa_classify_net(idst); if ((c < 0) || !(c & IADDR_HOST) || ((c & IADDR_SCOPE_MASK) <= SCOPE_LINK)) SKIP("strange class/scope\n"); - int pxlen = (flags & RTF_HOST) ? MAX_PREFIX_LENGTH : ipa_masklen(imask); + int pxlen; + if (ipv6) + pxlen = (flags & RTF_HOST) ? IP6_MAX_PREFIX_LENGTH : ip6_masklen(&ipa_to_ip6(imask)); + else + pxlen = (flags & RTF_HOST) ? IP4_MAX_PREFIX_LENGTH : ip4_masklen(ipa_to_ip4(imask)); + if (pxlen < 0) { log(L_ERR "%s (%I) - netmask %I", errmsg, idst, imask); return; } + if (ipv6) + net_fill_ip6(&ndst, ipa_to_ip6(idst), pxlen); + else + net_fill_ip4(&ndst, ipa_to_ip4(idst), pxlen); + if ((flags & RTF_GATEWAY) && ipa_zero(igate)) - { log(L_ERR "%s (%I/%d) - missing gateway", errmsg, idst, pxlen); return; } + { log(L_ERR "%s (%N) - missing gateway", errmsg, ndst); return; } u32 self_mask = RTF_PROTO1; u32 alien_mask = RTF_STATIC | RTF_PROTO1 | RTF_GATEWAY; @@ -426,13 +468,12 @@ krt_read_route(struct ks_msg *msg, struct krt_proto *p, int scan) else src = KRT_SRC_KERNEL; - net = net_get(p->p.table, idst, pxlen); + net = net_get(p->p.main_channel->table, &ndst); rta a = { .src = p->p.main_source, .source = RTS_INHERIT, .scope = SCOPE_UNIVERSE, - .cast = RTC_UNICAST }; /* reject/blackhole routes have also set RTF_GATEWAY, @@ -452,41 +493,37 @@ krt_read_route(struct ks_msg *msg, struct krt_proto *p, int scan) } #endif - a.iface = if_find_by_index(msg->rtm.rtm_index); - if (!a.iface) + a.nh.iface = if_find_by_index(msg->rtm.rtm_index); + if (!a.nh.iface) { - log(L_ERR "KRT: Received route %I/%d with unknown ifindex %u", - net->n.prefix, net->n.pxlen, msg->rtm.rtm_index); + log(L_ERR "KRT: Received route %N with unknown ifindex %u", + net->n.addr, msg->rtm.rtm_index); return; } + a.dest = RTD_UNICAST; if (flags & RTF_GATEWAY) { neighbor *ng; - a.dest = RTD_ROUTER; - a.gw = igate; + a.nh.gw = igate; -#ifdef IPV6 /* Clean up embedded interface ID returned in link-local address */ - if (ipa_is_link_local(a.gw)) - _I0(a.gw) = 0xfe800000; -#endif + if (ipa_is_link_local(a.nh.gw)) + _I0(a.nh.gw) = 0xfe800000; - ng = neigh_find2(&p->p, &a.gw, a.iface, 0); + ng = neigh_find2(&p->p, &a.nh.gw, a.nh.iface, 0); if (!ng || (ng->scope == SCOPE_HOST)) { /* Ignore routes with next-hop 127.0.0.1, host routes with such next-hop appear on OpenBSD for address aliases. */ - if (ipa_classify(a.gw) == (IADDR_HOST | SCOPE_HOST)) + if (ipa_classify(a.nh.gw) == (IADDR_HOST | SCOPE_HOST)) return; - log(L_ERR "KRT: Received route %I/%d with strange next-hop %I", - net->n.prefix, net->n.pxlen, a.gw); + log(L_ERR "KRT: Received route %N with strange next-hop %I", + net->n.addr, a.nh.gw); return; } } - else - a.dest = RTD_DEVICE; done: e = rte_get_temp(&a); @@ -643,22 +680,28 @@ krt_read_addr(struct ks_msg *msg, int scan) GETADDR (&null, RTA_AUTHOR); GETADDR (&brd, RTA_BRD); - /* Some other family address */ - if (addr.sa.sa_family != BIRD_AF) - return; + /* Is addr family IP4 or IP6? */ + int ipv6; + switch (addr.sa.sa_family) { + case AF_INET: ipv6 = 0; break; + case AF_INET6: ipv6 = 1; break; + default: return; + } + + /* We do not test family for RTA_NETMASK, because BSD sends us + some strange values, but interpreting them as IPv4/IPv6 works */ + mask.sa.sa_family = addr.sa.sa_family; iaddr = ipa_from_sa(&addr); imask = ipa_from_sa(&mask); ibrd = ipa_from_sa(&brd); - - if ((masklen = ipa_masklen(imask)) < 0) + if ((ipv6 ? (masklen = ip6_masklen(&ipa_to_ip6(imask))) : (masklen = ip4_masklen(ipa_to_ip4(imask)))) < 0) { - log(L_ERR "KIF: Invalid masklen %I for %s", imask, iface->name); + log(L_ERR "KIF: Invalid mask %I for %s", imask, iface->name); return; } -#ifdef IPV6 /* Clean up embedded interface ID returned in link-local address */ if (ipa_is_link_local(iaddr)) @@ -666,13 +709,11 @@ krt_read_addr(struct ks_msg *msg, int scan) if (ipa_is_link_local(ibrd)) _I0(ibrd) = 0xfe800000; -#endif bzero(&ifa, sizeof(ifa)); ifa.iface = iface; ifa.ip = iaddr; - ifa.pxlen = masklen; scope = ipa_classify(ifa.ip); if (scope < 0) @@ -682,17 +723,16 @@ krt_read_addr(struct ks_msg *msg, int scan) } ifa.scope = scope & IADDR_SCOPE_MASK; - if (masklen < BITS_PER_IP_ADDRESS) + if (masklen < (ipv6 ? IP6_MAX_PREFIX_LENGTH : IP4_MAX_PREFIX_LENGTH)) { - ifa.prefix = ipa_and(ifa.ip, ipa_mkmask(masklen)); + net_fill_ipa(&ifa.prefix, ifa.ip, masklen); + net_normalize(&ifa.prefix); - if (masklen == (BITS_PER_IP_ADDRESS - 1)) + if (masklen == ((ipv6 ? IP6_MAX_PREFIX_LENGTH : IP4_MAX_PREFIX_LENGTH) - 1)) ifa.opposite = ipa_opposite_m1(ifa.ip); -#ifndef IPV6 - if (masklen == (BITS_PER_IP_ADDRESS - 2)) + if ((!ipv6) && (masklen == IP4_MAX_PREFIX_LENGTH - 2)) ifa.opposite = ipa_opposite_m2(ifa.ip); -#endif if (iface->flags & IF_BROADCAST) ifa.brd = ibrd; @@ -702,12 +742,13 @@ krt_read_addr(struct ks_msg *msg, int scan) } else if (!(iface->flags & IF_MULTIACCESS) && ipa_nonzero(ibrd)) { - ifa.prefix = ifa.opposite = ibrd; + net_fill_ipa(&ifa.prefix, ibrd, (ipv6 ? IP6_MAX_PREFIX_LENGTH : IP4_MAX_PREFIX_LENGTH)); + ifa.opposite = ibrd; ifa.flags |= IA_PEER; } else { - ifa.prefix = ifa.ip; + net_fill_ipa(&ifa.prefix, ifa.ip, (ipv6 ? IP6_MAX_PREFIX_LENGTH : IP4_MAX_PREFIX_LENGTH)); ifa.flags |= IA_HOST; } @@ -804,7 +845,7 @@ krt_sysctl_scan(struct proto *p, int cmd, int table_id) mib[0] = CTL_NET; mib[1] = PF_ROUTE; mib[2] = 0; - mib[3] = BIRD_AF; + mib[3] = 0; // Set AF to 0 for all available families mib[4] = cmd; mib[5] = 0; mcnt = 6; @@ -948,6 +989,7 @@ krt_sock_open(pool *pool, void *data, int table_id UNUSED) return sk; } +static u32 krt_table_cf[(KRT_MAX_TABLES+31) / 32][2]; #ifdef KRT_SHARED_SOCKET @@ -979,7 +1021,17 @@ krt_sock_close_shared(void) int krt_sys_start(struct krt_proto *p) { - krt_table_map[KRT_CF->sys.table_id] = p; + int id = KRT_CF->sys.table_id; + + if (krt_table_cf[id/32][!!(p->af == AF_INET6)] & (1 << (id%32))) + { + log(L_ERR "%s: Multiple kernel syncers defined for table #%d", p->p.name, id); + return 0; + } + + krt_table_cf[id/32][!!(p->af == AF_INET6)] |= (1 << (id%32)); + + krt_table_map[KRT_CF->sys.table_id][!!(p->af == AF_INET6)] = p; krt_sock_open_shared(); p->sys.sk = krt_sock; @@ -990,10 +1042,12 @@ krt_sys_start(struct krt_proto *p) void krt_sys_shutdown(struct krt_proto *p) { + krt_table_cf[(KRT_CF->sys.table_id)/32][!!(p->af == AF_INET6)] &= ~(1 << ((KRT_CF->sys.table_id)%32)); + krt_sock_close_shared(); p->sys.sk = NULL; - krt_table_map[KRT_CF->sys.table_id] = NULL; + krt_table_map[KRT_CF->sys.table_id][!!(p->af == AF_INET6)] = NULL; krt_buffer_release(&p->p); } @@ -1003,6 +1057,16 @@ krt_sys_shutdown(struct krt_proto *p) int krt_sys_start(struct krt_proto *p) { + int id = KRT_CF->sys.table_id; + + if (krt_table_cf[id/32][!!(p->af == AF_INET6)] & (1 << (id%32))) + { + log(L_ERR "%s: Multiple kernel syncers defined for table #%d", p->p.name, id); + return 0; + } + + krt_table_cf[id/32][!!(p->af == AF_INET6)] |= (1 << (id%32)); + p->sys.sk = krt_sock_open(p->p.pool, p, KRT_CF->sys.table_id); return 1; } @@ -1010,6 +1074,8 @@ krt_sys_start(struct krt_proto *p) void krt_sys_shutdown(struct krt_proto *p) { + krt_table_cf[(KRT_CF->sys.table_id)/32][!!(p->af == AF_INET6)] &= ~(1 << ((KRT_CF->sys.table_id)%32)); + rfree(p->sys.sk); p->sys.sk = NULL; @@ -1021,8 +1087,6 @@ krt_sys_shutdown(struct krt_proto *p) /* KRT configuration callbacks */ -static u32 krt_table_cf[(KRT_MAX_TABLES+31) / 32]; - int krt_sys_reconfigure(struct krt_proto *p UNUSED, struct krt_config *n, struct krt_config *o) { @@ -1036,18 +1100,6 @@ krt_sys_preconfig(struct config *c UNUSED) bzero(&krt_table_cf, sizeof(krt_table_cf)); } -void -krt_sys_postconfig(struct krt_config *x) -{ - u32 *tbl = krt_table_cf; - int id = x->sys.table_id; - - if (tbl[id/32] & (1 << (id%32))) - cf_error("Multiple kernel syncers defined for table #%d", id); - - tbl[id/32] |= (1 << (id%32)); -} - void krt_sys_init_config(struct krt_config *c) { c->sys.table_id = 0; /* Default table */ @@ -1074,9 +1126,9 @@ kif_sys_shutdown(struct kif_proto *p) struct ifa * -kif_get_primary_ip(struct iface *i UNUSED6) +kif_get_primary_ip(struct iface *i UNUSED) { -#ifndef IPV6 +#if 0 static int fd = -1; if (fd < 0) diff --git a/sysdep/bsd/krt-sys.h b/sysdep/bsd/krt-sys.h index 353ffcec..ed667e80 100644 --- a/sysdep/bsd/krt-sys.h +++ b/sysdep/bsd/krt-sys.h @@ -44,8 +44,9 @@ struct krt_state { static inline void krt_sys_io_init(void) { } static inline void krt_sys_init(struct krt_proto *p UNUSED) { } +static inline void krt_sys_postconfig(struct krt_config *x UNUSED) { } -static inline int krt_sys_get_attr(eattr *a UNUSED, byte *buf UNUSED, int buflen UNUSED) { return 0; } +static inline int krt_sys_get_attr(eattr *a UNUSED, byte *buf UNUSED, int buflen UNUSED) { return GA_UNKNOWN; } #endif diff --git a/sysdep/bsd/setkey.h b/sysdep/bsd/setkey.h index b417faca..3bcd8623 100644 --- a/sysdep/bsd/setkey.h +++ b/sysdep/bsd/setkey.h @@ -11,7 +11,7 @@ #include <netipsec/ipsec.h> #include "nest/bird.h" -#include "lib/unix.h" +#include "sysdep/unix/unix.h" /* @@ -63,7 +63,7 @@ setkey_send(struct sadb_msg *msg, uint len) * operations to implement replace. */ static int -setkey_md5(sockaddr *src, sockaddr *dst, char *passwd, uint type) +setkey_md5(sockaddr *src, sockaddr *dst, uint pxlen, char *passwd, uint type) { uint passwd_len = passwd ? strlen(passwd) : 0; @@ -122,7 +122,7 @@ setkey_md5(sockaddr *src, sockaddr *dst, char *passwd, uint type) saddr->sadb_address_len = PFKEY_UNIT64(len); saddr->sadb_address_exttype = SADB_EXT_ADDRESS_SRC; saddr->sadb_address_proto = IPSEC_ULPROTO_ANY; - saddr->sadb_address_prefixlen = MAX_PREFIX_LENGTH; + saddr->sadb_address_prefixlen = pxlen; memcpy(pos + sizeof(struct sadb_address), &src->sa, src->sa.sa_len); pos += len; @@ -132,7 +132,7 @@ setkey_md5(sockaddr *src, sockaddr *dst, char *passwd, uint type) daddr->sadb_address_len = PFKEY_UNIT64(len); daddr->sadb_address_exttype = SADB_EXT_ADDRESS_DST; daddr->sadb_address_proto = IPSEC_ULPROTO_ANY; - daddr->sadb_address_prefixlen = MAX_PREFIX_LENGTH; + daddr->sadb_address_prefixlen = pxlen; memcpy(pos + sizeof(struct sadb_address), &dst->sa, dst->sa.sa_len); pos += len; @@ -152,18 +152,20 @@ sk_set_md5_in_sasp_db(sock *s, ip_addr local, ip_addr remote, struct iface *ifa, sockaddr_fill(&src, s->af, local, ifa, 0); sockaddr_fill(&dst, s->af, remote, ifa, 0); + uint pxlen = (s->af == AF_INET) ? IP4_MAX_PREFIX_LENGTH : IP6_MAX_PREFIX_LENGTH; + if (passwd && *passwd) { int len = strlen(passwd); if (len > TCP_KEYLEN_MAX) ERR_MSG("The password for TCP MD5 Signature is too long"); - if (setkey_md5(&src, &dst, passwd, SADB_ADD) < 0) + if (setkey_md5(&src, &dst, pxlen, passwd, SADB_ADD) < 0) ERR_MSG("Cannot add TCP-MD5 password into the IPsec SA/SP database"); } else { - if (setkey_md5(&src, &dst, NULL, SADB_DELETE) < 0) + if (setkey_md5(&src, &dst, pxlen, NULL, SADB_DELETE) < 0) ERR_MSG("Cannot delete TCP-MD5 password from the IPsec SA/SP database"); } return 0; diff --git a/sysdep/bsd/sysio.h b/sysdep/bsd/sysio.h index 9b10e6e8..0e895e20 100644 --- a/sysdep/bsd/sysio.h +++ b/sysdep/bsd/sysio.h @@ -201,7 +201,7 @@ sk_prepare_ip_header(sock *s, void *hdr, int dlen) #if defined(__FreeBSD__) #define USE_MD5SIG_SETKEY -#include "lib/setkey.h" +#include "sysdep/bsd/setkey.h" #endif int diff --git a/sysdep/cf/bsd-v6.h b/sysdep/cf/bsd-v6.h deleted file mode 100644 index 745dfba3..00000000 --- a/sysdep/cf/bsd-v6.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Configuration for *BSD based systems (tested on FreeBSD and NetBSD) - * - * (c) 2004 Ondrej Filip <feela@network.cz> - * - * Can be freely distributed and used under the terms of the GNU GPL. - */ - -#define IPV6 - -#define CONFIG_AUTO_ROUTES -#define CONFIG_SELF_CONSCIOUS -#define CONFIG_MULTIPLE_TABLES -#define CONFIG_SINGLE_ROUTE - -#define CONFIG_SKIP_MC_BIND -#define CONFIG_NO_IFACE_BIND - -/* -Link: sysdep/unix -Link: sysdep/bsd - */ diff --git a/sysdep/cf/bsd.h b/sysdep/cf/bsd.h index 51beb42b..22c54277 100644 --- a/sysdep/cf/bsd.h +++ b/sysdep/cf/bsd.h @@ -15,6 +15,9 @@ #define CONFIG_NO_IFACE_BIND #define CONFIG_USE_HDRINCL +#define CONFIG_INCLUDE_SYSIO_H "sysdep/bsd/sysio.h" +#define CONFIG_INCLUDE_KRTSYS_H "sysdep/bsd/krt-sys.h" + /* Link: sysdep/unix Link: sysdep/bsd diff --git a/sysdep/cf/linux-v6.h b/sysdep/cf/linux-v6.h deleted file mode 100644 index 09f60377..00000000 --- a/sysdep/cf/linux-v6.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Configuration for Linux based systems running IPv6 - * - * (c) 1998--1999 Martin Mares <mj@ucw.cz> - * - * Can be freely distributed and used under the terms of the GNU GPL. - */ - -#define IPV6 - -#define CONFIG_AUTO_ROUTES -#define CONFIG_SELF_CONSCIOUS -#define CONFIG_MULTIPLE_TABLES -#define CONFIG_ALL_TABLES_AT_ONCE - -#define CONFIG_RESTRICTED_PRIVILEGES - -/* -Link: sysdep/linux -Link: sysdep/unix - */ diff --git a/sysdep/cf/linux.h b/sysdep/cf/linux.h index 9e34f869..3a3a15da 100644 --- a/sysdep/cf/linux.h +++ b/sysdep/cf/linux.h @@ -14,7 +14,16 @@ #define CONFIG_MC_PROPER_SRC #define CONFIG_UNIX_DONTROUTE +#define CONFIG_INCLUDE_SYSIO_H "sysdep/linux/sysio.h" +#define CONFIG_INCLUDE_KRTSYS_H "sysdep/linux/krt-sys.h" + #define CONFIG_RESTRICTED_PRIVILEGES +#define CONFIG_INCLUDE_SYSPRIV_H "sysdep/linux/syspriv.h" + + +#ifndef AF_MPLS +#define AF_MPLS 28 +#endif /* Link: sysdep/linux diff --git a/sysdep/config.h b/sysdep/config.h index e529cd86..fb710d86 100644 --- a/sysdep/config.h +++ b/sysdep/config.h @@ -7,7 +7,7 @@ #define _BIRD_CONFIG_H_ /* BIRD version */ -#define BIRD_VERSION "1.6.3" +#define BIRD_VERSION "2.0.0-pre1" /* Include parameters determined by configure script */ #include "sysdep/autoconf.h" diff --git a/sysdep/linux/Makefile b/sysdep/linux/Makefile new file mode 100644 index 00000000..188ac8de --- /dev/null +++ b/sysdep/linux/Makefile @@ -0,0 +1,6 @@ +src := netlink.c +obj := $(src-o-files) +$(all-daemon) +$(conf-y-targets): $(s)netlink.Y + +tests_objs := $(tests_objs) $(src-o-files) diff --git a/sysdep/linux/Modules b/sysdep/linux/Modules deleted file mode 100644 index 940660b6..00000000 --- a/sysdep/linux/Modules +++ /dev/null @@ -1,5 +0,0 @@ -krt-sys.h -netlink.c -netlink.Y -sysio.h -syspriv.h diff --git a/sysdep/linux/lwtunnel.h b/sysdep/linux/lwtunnel.h new file mode 100644 index 00000000..7cea3cbf --- /dev/null +++ b/sysdep/linux/lwtunnel.h @@ -0,0 +1,45 @@ +#ifndef _LWTUNNEL_H_ +#define _LWTUNNEL_H_ + +#include <linux/types.h> + +enum lwtunnel_encap_types { + LWTUNNEL_ENCAP_NONE, + LWTUNNEL_ENCAP_MPLS, + LWTUNNEL_ENCAP_IP, + LWTUNNEL_ENCAP_ILA, + LWTUNNEL_ENCAP_IP6, + __LWTUNNEL_ENCAP_MAX, +}; + +#define LWTUNNEL_ENCAP_MAX (__LWTUNNEL_ENCAP_MAX - 1) + +enum lwtunnel_ip_t { + LWTUNNEL_IP_UNSPEC, + LWTUNNEL_IP_ID, + LWTUNNEL_IP_DST, + LWTUNNEL_IP_SRC, + LWTUNNEL_IP_TTL, + LWTUNNEL_IP_TOS, + LWTUNNEL_IP_FLAGS, + LWTUNNEL_IP_PAD, + __LWTUNNEL_IP_MAX, +}; + +#define LWTUNNEL_IP_MAX (__LWTUNNEL_IP_MAX - 1) + +enum lwtunnel_ip6_t { + LWTUNNEL_IP6_UNSPEC, + LWTUNNEL_IP6_ID, + LWTUNNEL_IP6_DST, + LWTUNNEL_IP6_SRC, + LWTUNNEL_IP6_HOPLIMIT, + LWTUNNEL_IP6_TC, + LWTUNNEL_IP6_FLAGS, + LWTUNNEL_IP6_PAD, + __LWTUNNEL_IP6_MAX, +}; + +#define LWTUNNEL_IP6_MAX (__LWTUNNEL_IP6_MAX - 1) + +#endif /* _LWTUNNEL_H_ */ diff --git a/sysdep/linux/netlink.c b/sysdep/linux/netlink.c index 22313f43..8f44b007 100644 --- a/sysdep/linux/netlink.c +++ b/sysdep/linux/netlink.c @@ -6,6 +6,7 @@ * Can be freely distributed and used under the terms of the GNU GPL. */ +#include <alloca.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> @@ -19,9 +20,10 @@ #include "nest/route.h" #include "nest/protocol.h" #include "nest/iface.h" -#include "lib/timer.h" -#include "lib/unix.h" -#include "lib/krt.h" +#include "lib/alloca.h" +#include "sysdep/unix/timer.h" +#include "sysdep/unix/unix.h" +#include "sysdep/unix/krt.h" #include "lib/socket.h" #include "lib/string.h" #include "lib/hash.h" @@ -29,6 +31,11 @@ #include <asm/types.h> #include <linux/if.h> +#ifdef HAVE_LWTUNNEL +#include <linux/lwtunnel.h> +#else +#include "sysdep/linux/lwtunnel.h" +#endif #include <linux/netlink.h> #include <linux/rtnetlink.h> @@ -49,13 +56,31 @@ #define RTA_TABLE 15 #endif +#ifndef RTA_VIA +#define RTA_VIA 18 +#endif -#ifdef IPV6 -#define krt_ecmp6(X) 1 -#else -#define krt_ecmp6(X) 0 +#ifndef HAVE_STRUCT_RTVIA +struct rtvia { + __kernel_sa_family_t rtvia_family; + __u8 rtvia_addr[0]; +}; +#endif + +#ifndef RTA_NEWDST +#define RTA_NEWDST 19 +#endif + +#ifndef RTA_ENCAP_TYPE +#define RTA_ENCAP_TYPE 21 +#endif + +#ifndef RTA_ENCAP +#define RTA_ENCAP 22 #endif +#define krt_ecmp6(p) ((p)->af == AF_INET6) + /* * Structure nl_parse_state keeps state of received route processing. Ideally, * we could just independently parse received Netlink messages and immediately @@ -302,29 +327,32 @@ static struct nl_want_attrs ifla_attr_want[BIRD_IFLA_MAX] = { #define BIRD_IFA_MAX (IFA_FLAGS+1) -#ifndef IPV6 static struct nl_want_attrs ifa_attr_want4[BIRD_IFA_MAX] = { [IFA_ADDRESS] = { 1, 1, sizeof(ip4_addr) }, [IFA_LOCAL] = { 1, 1, sizeof(ip4_addr) }, [IFA_BROADCAST] = { 1, 1, sizeof(ip4_addr) }, - [IFA_FLAGS] = { 1, 1, sizeof(u32) }, + [IFA_FLAGS] = { 1, 1, sizeof(u32) }, }; -#else + static struct nl_want_attrs ifa_attr_want6[BIRD_IFA_MAX] = { [IFA_ADDRESS] = { 1, 1, sizeof(ip6_addr) }, [IFA_LOCAL] = { 1, 1, sizeof(ip6_addr) }, [IFA_FLAGS] = { 1, 1, sizeof(u32) }, }; -#endif -#define BIRD_RTA_MAX (RTA_TABLE+1) +#define BIRD_RTA_MAX (RTA_ENCAP+1) -static struct nl_want_attrs mpnh_attr_want4[BIRD_RTA_MAX] = { +static struct nl_want_attrs nexthop_attr_want4[BIRD_RTA_MAX] = { [RTA_GATEWAY] = { 1, 1, sizeof(ip4_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 }, }; -#ifndef IPV6 static struct nl_want_attrs rtm_attr_want4[BIRD_RTA_MAX] = { [RTA_DST] = { 1, 1, sizeof(ip4_addr) }, [RTA_OIF] = { 1, 1, sizeof(u32) }, @@ -335,8 +363,10 @@ static struct nl_want_attrs rtm_attr_want4[BIRD_RTA_MAX] = { [RTA_MULTIPATH] = { 1, 0, 0 }, [RTA_FLOW] = { 1, 1, sizeof(u32) }, [RTA_TABLE] = { 1, 1, sizeof(u32) }, + [RTA_ENCAP_TYPE]= { 1, 1, sizeof(u16) }, + [RTA_ENCAP] = { 1, 0, 0 }, }; -#else + static struct nl_want_attrs rtm_attr_want6[BIRD_RTA_MAX] = { [RTA_DST] = { 1, 1, sizeof(ip6_addr) }, [RTA_IIF] = { 1, 1, sizeof(u32) }, @@ -347,8 +377,21 @@ static struct nl_want_attrs rtm_attr_want6[BIRD_RTA_MAX] = { [RTA_METRICS] = { 1, 0, 0 }, [RTA_FLOW] = { 1, 1, sizeof(u32) }, [RTA_TABLE] = { 1, 1, sizeof(u32) }, + [RTA_ENCAP_TYPE]= { 1, 1, sizeof(u16) }, + [RTA_ENCAP] = { 1, 0, 0 }, +}; + +static struct nl_want_attrs rtm_attr_want_mpls[BIRD_RTA_MAX] = { + [RTA_DST] = { 1, 1, sizeof(u32) }, + [RTA_IIF] = { 1, 1, sizeof(u32) }, + [RTA_OIF] = { 1, 1, sizeof(u32) }, + [RTA_PRIORITY] = { 1, 1, sizeof(u32) }, + [RTA_METRICS] = { 1, 0, 0 }, + [RTA_FLOW] = { 1, 1, sizeof(u32) }, + [RTA_TABLE] = { 1, 1, sizeof(u32) }, + [RTA_VIA] = { 1, 0, 0 }, + [RTA_NEWDST] = { 1, 0, 0 }, }; -#endif static int @@ -364,7 +407,7 @@ nl_parse_attrs(struct rtattr *a, struct nl_want_attrs *want, struct rtattr **k, if (want[a->rta_type].checksize && (RTA_PAYLOAD(a) != want[a->rta_type].size)) { - log(L_ERR "nl_parse_attrs: Malformed message received"); + log(L_ERR "nl_parse_attrs: Malformed attribute received"); return 0; } @@ -380,6 +423,9 @@ nl_parse_attrs(struct rtattr *a, struct nl_want_attrs *want, struct rtattr **k, return 1; } +static inline u16 rta_get_u16(struct rtattr *a) +{ return *(u16 *) RTA_DATA(a); } + static inline u32 rta_get_u32(struct rtattr *a) { return *(u32 *) RTA_DATA(a); } @@ -389,6 +435,32 @@ static inline ip4_addr rta_get_ip4(struct rtattr *a) static inline ip6_addr rta_get_ip6(struct rtattr *a) { return ip6_ntoh(*(ip6_addr *) RTA_DATA(a)); } +static inline ip_addr rta_get_ipa(struct rtattr *a) +{ + if (RTA_PAYLOAD(a) == sizeof(ip4_addr)) + return ipa_from_ip4(rta_get_ip4(a)); + else + return ipa_from_ip6(rta_get_ip6(a)); +} + +static inline ip_addr rta_get_via(struct rtattr *a) +{ + struct rtvia *v = RTA_DATA(a); + switch(v->rtvia_family) { + case AF_INET: return ipa_from_ip4(ip4_ntoh(*(ip4_addr *) v->rtvia_addr)); + case AF_INET6: return ipa_from_ip6(ip6_ntoh(*(ip6_addr *) v->rtvia_addr)); + } + return IPA_NONE; +} + +static u32 rta_mpls_stack[MPLS_MAX_LABEL_STACK]; +static inline int rta_get_mpls(struct rtattr *a, u32 *stack) +{ + if (RTA_PAYLOAD(a) % 4) + log(L_WARN "KRT: Strange length of received MPLS stack: %u", RTA_PAYLOAD(a)); + + return mpls_get(RTA_DATA(a), RTA_PAYLOAD(a) & ~0x3, stack); +} struct rtattr * nl_add_attr(struct nlmsghdr *h, uint bufsize, uint code, const void *data, uint dlen) @@ -410,29 +482,93 @@ nl_add_attr(struct nlmsghdr *h, uint bufsize, uint code, const void *data, uint return a; } +static inline struct rtattr * +nl_open_attr(struct nlmsghdr *h, uint bufsize, uint code) +{ + return nl_add_attr(h, bufsize, code, NULL, 0); +} + static inline void -nl_add_attr_u32(struct nlmsghdr *h, unsigned bufsize, int code, u32 data) +nl_close_attr(struct nlmsghdr *h, struct rtattr *a) +{ + a->rta_len = (void *)h + NLMSG_ALIGN(h->nlmsg_len) - (void *)a; +} + +static inline void +nl_add_attr_u16(struct nlmsghdr *h, uint bufsize, int code, u16 data) +{ + nl_add_attr(h, bufsize, code, &data, 2); +} + +static inline void +nl_add_attr_u32(struct nlmsghdr *h, uint bufsize, int code, u32 data) { nl_add_attr(h, bufsize, code, &data, 4); } static inline void -nl_add_attr_ipa(struct nlmsghdr *h, unsigned bufsize, int code, ip_addr ipa) +nl_add_attr_ip4(struct nlmsghdr *h, uint bufsize, int code, ip4_addr ip4) { - ipa_hton(ipa); - nl_add_attr(h, bufsize, code, &ipa, sizeof(ipa)); + ip4 = ip4_hton(ip4); + nl_add_attr(h, bufsize, code, &ip4, sizeof(ip4)); } -static inline struct rtattr * -nl_open_attr(struct nlmsghdr *h, uint bufsize, uint code) +static inline void +nl_add_attr_ip6(struct nlmsghdr *h, uint bufsize, int code, ip6_addr ip6) { - return nl_add_attr(h, bufsize, code, NULL, 0); + ip6 = ip6_hton(ip6); + nl_add_attr(h, bufsize, code, &ip6, sizeof(ip6)); } static inline void -nl_close_attr(struct nlmsghdr *h, struct rtattr *a) +nl_add_attr_ipa(struct nlmsghdr *h, uint bufsize, int code, ip_addr ipa) { - a->rta_len = (void *)h + NLMSG_ALIGN(h->nlmsg_len) - (void *)a; + if (ipa_is_ip4(ipa)) + nl_add_attr_ip4(h, bufsize, code, ipa_to_ip4(ipa)); + else + nl_add_attr_ip6(h, bufsize, code, ipa_to_ip6(ipa)); +} + +static inline void +nl_add_attr_mpls(struct nlmsghdr *h, uint bufsize, int code, int len, u32 *stack) +{ + char buf[len*4]; + mpls_put(buf, len, stack); + nl_add_attr(h, bufsize, code, buf, len*4); +} + +static inline void +nl_add_attr_mpls_encap(struct nlmsghdr *h, uint bufsize, int len, u32 *stack) +{ + nl_add_attr_u16(h, bufsize, RTA_ENCAP_TYPE, LWTUNNEL_ENCAP_MPLS); + + struct rtattr *nest = nl_open_attr(h, bufsize, RTA_ENCAP); + nl_add_attr_mpls(h, bufsize, RTA_DST, len, stack); + nl_close_attr(h, nest); +} + +static inline void +nl_add_attr_via(struct nlmsghdr *h, uint bufsize, ip_addr ipa) +{ + struct rtattr *nest = nl_open_attr(h, bufsize, RTA_VIA); + struct rtvia *via = RTA_DATA(nest); + + h->nlmsg_len += sizeof(*via); + + if (ipa_is_ip4(ipa)) + { + via->rtvia_family = AF_INET; + put_ip4(via->rtvia_addr, ipa_to_ip4(ipa)); + h->nlmsg_len += sizeof(ip4_addr); + } + else + { + via->rtvia_family = AF_INET6; + put_ip6(via->rtvia_addr, ipa_to_ip6(ipa)); + h->nlmsg_len += sizeof(ip6_addr); + } + + nl_close_attr(h, nest); } static inline struct rtnexthop * @@ -455,8 +591,24 @@ nl_close_nexthop(struct nlmsghdr *h, struct rtnexthop *nh) nh->rtnh_len = (void *)h + NLMSG_ALIGN(h->nlmsg_len) - (void *)nh; } +static inline void +nl_add_nexthop(struct nlmsghdr *h, uint bufsize, struct nexthop *nh, int af) +{ + if (nh->labels > 0) + if (af == AF_MPLS) + nl_add_attr_mpls(h, bufsize, RTA_NEWDST, nh->labels, nh->label); + else + nl_add_attr_mpls_encap(h, bufsize, nh->labels, nh->label); + + if (ipa_nonzero(nh->gw)) + if (af == AF_MPLS) + nl_add_attr_via(h, bufsize, nh->gw); + else + nl_add_attr_ipa(h, bufsize, RTA_GATEWAY, nh->gw); +} + static void -nl_add_multipath(struct nlmsghdr *h, unsigned bufsize, struct mpnh *nh) +nl_add_multipath(struct nlmsghdr *h, uint bufsize, struct nexthop *nh, int af) { struct rtattr *a = nl_open_attr(h, bufsize, RTA_MULTIPATH); @@ -468,7 +620,7 @@ nl_add_multipath(struct nlmsghdr *h, unsigned bufsize, struct mpnh *nh) rtnh->rtnh_hops = nh->weight; rtnh->rtnh_ifindex = nh->iface->index; - nl_add_attr_ipa(h, bufsize, RTA_GATEWAY, nh->gw); + nl_add_nexthop(h, bufsize, nh, af); nl_close_nexthop(h, rtnh); } @@ -476,17 +628,17 @@ nl_add_multipath(struct nlmsghdr *h, unsigned bufsize, struct mpnh *nh) nl_close_attr(h, a); } -static struct mpnh * +static struct nexthop * nl_parse_multipath(struct krt_proto *p, struct rtattr *ra) { /* Temporary buffer for multicast nexthops */ - static struct mpnh *nh_buffer; + static struct nexthop *nh_buffer; static int nh_buf_size; /* in number of structures */ static int nh_buf_used; struct rtattr *a[BIRD_RTA_MAX]; struct rtnexthop *nh = RTA_DATA(ra); - struct mpnh *rv, *first, **last; + struct nexthop *rv, *first, **last; unsigned len = RTA_PAYLOAD(ra); first = NULL; @@ -502,7 +654,7 @@ nl_parse_multipath(struct krt_proto *p, struct rtattr *ra) if (nh_buf_used == nh_buf_size) { nh_buf_size = nh_buf_size ? (nh_buf_size * 2) : 4; - nh_buffer = xrealloc(nh_buffer, nh_buf_size * sizeof(struct mpnh)); + nh_buffer = xrealloc(nh_buffer, nh_buf_size * NEXTHOP_MAX_SIZE); } *last = rv = nh_buffer + nh_buf_used++; rv->next = NULL; @@ -515,19 +667,34 @@ 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), mpnh_attr_want4, a, sizeof(a)); + nl_parse_attrs(RTNH_DATA(nh), nexthop_attr_want4, a, sizeof(a)); if (a[RTA_GATEWAY]) { - memcpy(&rv->gw, RTA_DATA(a[RTA_GATEWAY]), sizeof(ip_addr)); - ipa_ntoh(rv->gw); + rv->gw = rta_get_ipa(a[RTA_GATEWAY]); - neighbor *ng = neigh_find2(&p->p, &rv->gw, rv->iface, - (nh->rtnh_flags & RTNH_F_ONLINK) ? NEF_ONLINK : 0); - if (!ng || (ng->scope == SCOPE_HOST)) + neighbor *nbr; + nbr = neigh_find2(&p->p, &rv->gw, rv->iface, + (nh->rtnh_flags & RTNH_F_ONLINK) ? NEF_ONLINK : 0); + if (!nbr || (nbr->scope == SCOPE_HOST)) return NULL; } else - return NULL; + rv->gw = IPA_NONE; + + if (a[RTA_ENCAP_TYPE]) + { + if (rta_get_u16(a[RTA_ENCAP_TYPE]) != LWTUNNEL_ENCAP_MPLS) { + log(L_WARN "KRT: Unknown encapsulation method %d in multipath", rta_get_u16(a[RTA_ENCAP_TYPE])); + return NULL; + } + + struct rtattr *enca[BIRD_RTA_MAX]; + nl_attr_len = RTA_PAYLOAD(a[RTA_ENCAP]); + nl_parse_attrs(RTA_DATA(a[RTA_ENCAP]), encap_mpls_want, enca, sizeof(enca)); + rv->labels = rta_get_mpls(enca[RTA_DST], rv->label); + break; + } + len -= NLMSG_ALIGN(nh->rtnh_len); nh = RTNH_NEXT(nh); @@ -658,52 +825,131 @@ nl_parse_link(struct nlmsghdr *h, int scan) } static void -nl_parse_addr(struct nlmsghdr *h, int scan) +nl_parse_addr4(struct ifaddrmsg *i, int scan, int new) { - struct ifaddrmsg *i; struct rtattr *a[BIRD_IFA_MAX]; - int new = h->nlmsg_type == RTM_NEWADDR; - struct ifa ifa; struct iface *ifi; - int scope; u32 ifa_flags; + int scope; - if (!(i = nl_checkin(h, sizeof(*i)))) + if (!nl_parse_attrs(IFA_RTA(i), ifa_attr_want4, a, sizeof(a))) return; - switch (i->ifa_family) + if (!a[IFA_LOCAL]) { -#ifndef IPV6 - case AF_INET: - if (!nl_parse_attrs(IFA_RTA(i), ifa_attr_want4, a, sizeof(a))) - return; - if (!a[IFA_LOCAL]) - { - log(L_ERR "KIF: Malformed message received (missing IFA_LOCAL)"); - return; - } - break; -#else - case AF_INET6: - if (!nl_parse_attrs(IFA_RTA(i), ifa_attr_want6, a, sizeof(a))) - return; - break; -#endif - default: - return; + log(L_ERR "KIF: Malformed message received (missing IFA_LOCAL)"); + return; } - if (!a[IFA_ADDRESS]) { log(L_ERR "KIF: Malformed message received (missing IFA_ADDRESS)"); return; } + ifi = if_find_by_index(i->ifa_index); + if (!ifi) + { + log(L_ERR "KIF: Received address message for unknown interface %d", i->ifa_index); + return; + } + if (a[IFA_FLAGS]) ifa_flags = rta_get_u32(a[IFA_FLAGS]); else ifa_flags = i->ifa_flags; + struct ifa ifa; + bzero(&ifa, sizeof(ifa)); + ifa.iface = ifi; + if (ifa_flags & IFA_F_SECONDARY) + ifa.flags |= IA_SECONDARY; + + ifa.ip = rta_get_ipa(a[IFA_LOCAL]); + + if (i->ifa_prefixlen > IP4_MAX_PREFIX_LENGTH) + { + log(L_ERR "KIF: Invalid prefix length for interface %s: %d", ifi->name, i->ifa_prefixlen); + new = 0; + } + if (i->ifa_prefixlen == IP4_MAX_PREFIX_LENGTH) + { + ifa.brd = rta_get_ipa(a[IFA_ADDRESS]); + net_fill_ip4(&ifa.prefix, rta_get_ip4(a[IFA_ADDRESS]), i->ifa_prefixlen); + + /* It is either a host address or a peer address */ + if (ipa_equal(ifa.ip, ifa.brd)) + ifa.flags |= IA_HOST; + else + { + ifa.flags |= IA_PEER; + ifa.opposite = ifa.brd; + } + } + else + { + net_fill_ip4(&ifa.prefix, ipa_to_ip4(ifa.ip), i->ifa_prefixlen); + net_normalize(&ifa.prefix); + + if (i->ifa_prefixlen == IP4_MAX_PREFIX_LENGTH - 1) + ifa.opposite = ipa_opposite_m1(ifa.ip); + + if (i->ifa_prefixlen == IP4_MAX_PREFIX_LENGTH - 2) + ifa.opposite = ipa_opposite_m2(ifa.ip); + + if ((ifi->flags & IF_BROADCAST) && a[IFA_BROADCAST]) + { + ip4_addr xbrd = rta_get_ip4(a[IFA_BROADCAST]); + ip4_addr ybrd = ip4_or(ipa_to_ip4(ifa.ip), ip4_not(ip4_mkmask(i->ifa_prefixlen))); + + if (ip4_equal(xbrd, net4_prefix(&ifa.prefix)) || ip4_equal(xbrd, ybrd)) + ifa.brd = ipa_from_ip4(xbrd); + else if (ifi->flags & IF_TMP_DOWN) /* Complain only during the first scan */ + { + log(L_ERR "KIF: Invalid broadcast address %I4 for %s", xbrd, ifi->name); + ifa.brd = ipa_from_ip4(ybrd); + } + } + } + + scope = ipa_classify(ifa.ip); + if (scope < 0) + { + log(L_ERR "KIF: Invalid interface address %I for %s", ifa.ip, ifi->name); + return; + } + ifa.scope = scope & IADDR_SCOPE_MASK; + + DBG("KIF: IF%d(%s): %s IPA %I, flg %x, net %N, brd %I, opp %I\n", + ifi->index, ifi->name, + new ? "added" : "removed", + ifa.ip, ifa.flags, ifa.prefix, ifa.brd, ifa.opposite); + + if (new) + ifa_update(&ifa); + else + ifa_delete(&ifa); + + if (!scan) + if_end_partial_update(ifi); +} + +static void +nl_parse_addr6(struct ifaddrmsg *i, int scan, int new) +{ + struct rtattr *a[BIRD_IFA_MAX]; + struct iface *ifi; + u32 ifa_flags; + int scope; + + if (!nl_parse_attrs(IFA_RTA(i), ifa_attr_want6, a, sizeof(a))) + return; + + if (!a[IFA_ADDRESS]) + { + log(L_ERR "KIF: Malformed message received (missing IFA_ADDRESS)"); + return; + } + ifi = if_find_by_index(i->ifa_index); if (!ifi) { @@ -711,65 +957,50 @@ nl_parse_addr(struct nlmsghdr *h, int scan) return; } + if (a[IFA_FLAGS]) + ifa_flags = rta_get_u32(a[IFA_FLAGS]); + else + ifa_flags = i->ifa_flags; + + struct ifa ifa; bzero(&ifa, sizeof(ifa)); ifa.iface = ifi; if (ifa_flags & IFA_F_SECONDARY) ifa.flags |= IA_SECONDARY; -#ifdef IPV6 /* Ignore tentative addresses silently */ if (ifa_flags & IFA_F_TENTATIVE) return; -#endif /* IFA_LOCAL can be unset for IPv6 interfaces */ - memcpy(&ifa.ip, RTA_DATA(a[IFA_LOCAL] ? : a[IFA_ADDRESS]), sizeof(ifa.ip)); - ipa_ntoh(ifa.ip); - ifa.pxlen = i->ifa_prefixlen; - if (i->ifa_prefixlen > BITS_PER_IP_ADDRESS) + ifa.ip = rta_get_ipa(a[IFA_LOCAL] ? : a[IFA_ADDRESS]); + + if (i->ifa_prefixlen > IP6_MAX_PREFIX_LENGTH) { log(L_ERR "KIF: Invalid prefix length for interface %s: %d", ifi->name, i->ifa_prefixlen); new = 0; } - if (i->ifa_prefixlen == BITS_PER_IP_ADDRESS) + if (i->ifa_prefixlen == IP6_MAX_PREFIX_LENGTH) { - ip_addr addr; - memcpy(&addr, RTA_DATA(a[IFA_ADDRESS]), sizeof(addr)); - ipa_ntoh(addr); - ifa.prefix = ifa.brd = addr; + ifa.brd = rta_get_ipa(a[IFA_ADDRESS]); + net_fill_ip6(&ifa.prefix, rta_get_ip6(a[IFA_ADDRESS]), i->ifa_prefixlen); /* It is either a host address or a peer address */ - if (ipa_equal(ifa.ip, addr)) + if (ipa_equal(ifa.ip, ifa.brd)) ifa.flags |= IA_HOST; else { ifa.flags |= IA_PEER; - ifa.opposite = addr; + ifa.opposite = ifa.brd; } } else { - ip_addr netmask = ipa_mkmask(ifa.pxlen); - ifa.prefix = ipa_and(ifa.ip, netmask); - ifa.brd = ipa_or(ifa.ip, ipa_not(netmask)); - if (i->ifa_prefixlen == BITS_PER_IP_ADDRESS - 1) - ifa.opposite = ipa_opposite_m1(ifa.ip); - -#ifndef IPV6 - if (i->ifa_prefixlen == BITS_PER_IP_ADDRESS - 2) - ifa.opposite = ipa_opposite_m2(ifa.ip); + net_fill_ip6(&ifa.prefix, ipa_to_ip6(ifa.ip), i->ifa_prefixlen); + net_normalize(&ifa.prefix); - if ((ifi->flags & IF_BROADCAST) && a[IFA_BROADCAST]) - { - ip_addr xbrd; - memcpy(&xbrd, RTA_DATA(a[IFA_BROADCAST]), sizeof(xbrd)); - ipa_ntoh(xbrd); - if (ipa_equal(xbrd, ifa.prefix) || ipa_equal(xbrd, ifa.brd)) - ifa.brd = xbrd; - else if (ifi->flags & IF_TMP_DOWN) /* Complain only during the first scan */ - log(L_ERR "KIF: Invalid broadcast address %I for %s", xbrd, ifi->name); - } -#endif + if (i->ifa_prefixlen == IP6_MAX_PREFIX_LENGTH - 1) + ifa.opposite = ipa_opposite_m1(ifa.ip); } scope = ipa_classify(ifa.ip); @@ -780,10 +1011,10 @@ nl_parse_addr(struct nlmsghdr *h, int scan) } ifa.scope = scope & IADDR_SCOPE_MASK; - DBG("KIF: IF%d(%s): %s IPA %I, flg %x, net %I/%d, brd %I, opp %I\n", + DBG("KIF: IF%d(%s): %s IPA %I, flg %x, net %N, brd %I, opp %I\n", ifi->index, ifi->name, new ? "added" : "removed", - ifa.ip, ifa.flags, ifa.prefix, ifa.pxlen, ifa.brd, ifa.opposite); + ifa.ip, ifa.flags, ifa.prefix, ifa.brd, ifa.opposite); if (new) ifa_update(&ifa); @@ -794,6 +1025,26 @@ nl_parse_addr(struct nlmsghdr *h, int scan) if_end_partial_update(ifi); } +static void +nl_parse_addr(struct nlmsghdr *h, int scan) +{ + struct ifaddrmsg *i; + + if (!(i = nl_checkin(h, sizeof(*i)))) + return; + + int new = (h->nlmsg_type == RTM_NEWADDR); + + switch (i->ifa_family) + { + case AF_INET: + return nl_parse_addr4(i, scan, new); + + case AF_INET6: + return nl_parse_addr6(i, scan, new); + } +} + void kif_do_scan(struct kif_proto *p UNUSED) { @@ -808,7 +1059,14 @@ kif_do_scan(struct kif_proto *p UNUSED) else log(L_DEBUG "nl_scan_ifaces: Unknown packet received (type=%d)", h->nlmsg_type); - nl_request_dump(BIRD_AF, RTM_GETADDR); + nl_request_dump(AF_INET, RTM_GETADDR); + while (h = nl_get_scan()) + if (h->nlmsg_type == RTM_NEWADDR || h->nlmsg_type == RTM_DELADDR) + nl_parse_addr(h, 1); + else + log(L_DEBUG "nl_scan_ifaces: Unknown packet received (type=%d)", h->nlmsg_type); + + nl_request_dump(AF_INET6, RTM_GETADDR); while (h = nl_get_scan()) if (h->nlmsg_type == RTM_NEWADDR || h->nlmsg_type == RTM_DELADDR) nl_parse_addr(h, 1); @@ -830,10 +1088,10 @@ krt_table_id(struct krt_proto *p) static HASH(struct krt_proto) nl_table_map; -#define RTH_FN(k) u32_hash(k) -#define RTH_EQ(k1,k2) k1 == k2 -#define RTH_KEY(p) krt_table_id(p) -#define RTH_NEXT(p) p->sys.hash_next +#define RTH_KEY(p) p->af, krt_table_id(p) +#define RTH_NEXT(p) p->sys.hash_next +#define RTH_EQ(a1,i1,a2,i2) a1 == a2 && i1 == i2 +#define RTH_FN(a,i) a ^ u32_hash(i) #define RTH_REHASH rth_rehash #define RTH_PARAMS /8, *2, 2, 2, 6, 20 @@ -845,28 +1103,21 @@ krt_capable(rte *e) { rta *a = e->attrs; - if (a->cast != RTC_UNICAST) - return 0; - switch (a->dest) - { - case RTD_ROUTER: - case RTD_DEVICE: - if (a->iface == NULL) - return 0; + { + case RTD_UNICAST: case RTD_BLACKHOLE: case RTD_UNREACHABLE: case RTD_PROHIBIT: - case RTD_MULTIPATH: - break; + return 1; + default: return 0; - } - return 1; + } } static inline int -nh_bufsize(struct mpnh *nh) +nh_bufsize(struct nexthop *nh) { int rv = 0; for (; nh != NULL; nh = nh->next) @@ -875,32 +1126,42 @@ nh_bufsize(struct mpnh *nh) } static int -nl_send_route(struct krt_proto *p, rte *e, struct ea_list *eattrs, int op, int dest, ip_addr gw, struct iface *iface) +nl_send_route(struct krt_proto *p, rte *e, struct ea_list *eattrs, int op, int dest, struct nexthop *nh) { eattr *ea; net *net = e->net; rta *a = e->attrs; + int bufsize = 128 + KRT_METRICS_MAX*8 + nh_bufsize(&(a->nh)); u32 priority = 0; struct { struct nlmsghdr h; struct rtmsg r; - char buf[128 + KRT_METRICS_MAX*8 + nh_bufsize(a->nexthops)]; - } r; + char buf[0]; + } *r; - DBG("nl_send_route(%I/%d,op=%x)\n", net->n.prefix, net->n.pxlen, op); + int rsize = sizeof(*r) + bufsize; + r = alloca(rsize); - bzero(&r.h, sizeof(r.h)); - bzero(&r.r, sizeof(r.r)); - r.h.nlmsg_type = op ? RTM_NEWROUTE : RTM_DELROUTE; - r.h.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); - r.h.nlmsg_flags = op | NLM_F_REQUEST | NLM_F_ACK; + DBG("nl_send_route(%N,op=%x)\n", net->n.addr, op); - r.r.rtm_family = BIRD_AF; - r.r.rtm_dst_len = net->n.pxlen; - r.r.rtm_protocol = RTPROT_BIRD; - r.r.rtm_scope = RT_SCOPE_NOWHERE; - nl_add_attr_ipa(&r.h, sizeof(r), RTA_DST, net->n.prefix); + bzero(&r->h, sizeof(r->h)); + bzero(&r->r, sizeof(r->r)); + r->h.nlmsg_type = op ? RTM_NEWROUTE : RTM_DELROUTE; + r->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + 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_protocol = RTPROT_BIRD; + r->r.rtm_scope = RT_SCOPE_NOWHERE; + if (p->af == AF_MPLS) + { + u32 label = net_mpls(net->n.addr); + nl_add_attr_mpls(&r->h, rsize, RTA_DST, 1, &label); + } + else + nl_add_attr_ipa(&r->h, rsize, RTA_DST, net_prefix(net->n.addr)); /* * Strange behavior for RTM_DELROUTE: @@ -910,9 +1171,9 @@ nl_send_route(struct krt_proto *p, rte *e, struct ea_list *eattrs, int op, int d */ if (krt_table_id(p) < 256) - r.r.rtm_table = krt_table_id(p); + r->r.rtm_table = krt_table_id(p); else - nl_add_attr_u32(&r.h, sizeof(r), RTA_TABLE, krt_table_id(p)); + nl_add_attr_u32(&r->h, rsize, RTA_TABLE, krt_table_id(p)); if (a->source == RTS_DUMMY) priority = e->u.krt.metric; @@ -922,7 +1183,7 @@ nl_send_route(struct krt_proto *p, rte *e, struct ea_list *eattrs, int op, int d priority = ea->u.data; if (priority) - nl_add_attr_u32(&r.h, sizeof(r), RTA_PRIORITY, priority); + nl_add_attr_u32(&r->h, rsize, RTA_PRIORITY, priority); /* For route delete, we do not specify remaining route attributes */ if (op == NL_OP_DELETE) @@ -930,15 +1191,15 @@ nl_send_route(struct krt_proto *p, rte *e, struct ea_list *eattrs, int op, int d /* Default scope is LINK for device routes, UNIVERSE otherwise */ if (ea = ea_find(eattrs, EA_KRT_SCOPE)) - r.r.rtm_scope = ea->u.data; + r->r.rtm_scope = ea->u.data; else - r.r.rtm_scope = (dest == RTD_DEVICE) ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE; + r->r.rtm_scope = (dest == RTD_UNICAST && ipa_zero(nh->gw)) ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE; if (ea = ea_find(eattrs, EA_KRT_PREFSRC)) - nl_add_attr_ipa(&r.h, sizeof(r), RTA_PREFSRC, *(ip_addr *)ea->u.ptr->data); + nl_add_attr_ipa(&r->h, rsize, RTA_PREFSRC, *(ip_addr *)ea->u.ptr->data); if (ea = ea_find(eattrs, EA_KRT_REALM)) - nl_add_attr_u32(&r.h, sizeof(r), RTA_FLOW, ea->u.data); + nl_add_attr_u32(&r->h, rsize, RTA_FLOW, ea->u.data); u32 metrics[KRT_METRICS_MAX]; @@ -953,34 +1214,30 @@ nl_send_route(struct krt_proto *p, rte *e, struct ea_list *eattrs, int op, int d } if (metrics[0]) - nl_add_metrics(&r.h, sizeof(r), metrics, KRT_METRICS_MAX); + nl_add_metrics(&r->h, rsize, metrics, KRT_METRICS_MAX); dest: - /* a->iface != NULL checked in krt_capable() for router and device routes */ switch (dest) { - case RTD_ROUTER: - r.r.rtm_type = RTN_UNICAST; - nl_add_attr_u32(&r.h, sizeof(r), RTA_OIF, iface->index); - nl_add_attr_ipa(&r.h, sizeof(r), RTA_GATEWAY, gw); - break; - case RTD_DEVICE: - r.r.rtm_type = RTN_UNICAST; - nl_add_attr_u32(&r.h, sizeof(r), RTA_OIF, iface->index); + case RTD_UNICAST: + r->r.rtm_type = RTN_UNICAST; + if (nh->next && !krt_ecmp6(p)) + nl_add_multipath(&r->h, rsize, nh, p->af); + else + { + nl_add_attr_u32(&r->h, rsize, RTA_OIF, nh->iface->index); + nl_add_nexthop(&r->h, rsize, nh, p->af); + } break; case RTD_BLACKHOLE: - r.r.rtm_type = RTN_BLACKHOLE; + r->r.rtm_type = RTN_BLACKHOLE; break; case RTD_UNREACHABLE: - r.r.rtm_type = RTN_UNREACHABLE; + r->r.rtm_type = RTN_UNREACHABLE; break; case RTD_PROHIBIT: - r.r.rtm_type = RTN_PROHIBIT; - break; - case RTD_MULTIPATH: - r.r.rtm_type = RTN_UNICAST; - nl_add_multipath(&r.h, sizeof(r), a->nexthops); + r->r.rtm_type = RTN_PROHIBIT; break; case RTD_NONE: break; @@ -989,7 +1246,7 @@ dest: } /* Ignore missing for DELETE */ - return nl_exchange(&r.h, (op == NL_OP_DELETE)); + return nl_exchange(&r->h, (op == NL_OP_DELETE)); } static inline int @@ -998,21 +1255,21 @@ nl_add_rte(struct krt_proto *p, rte *e, struct ea_list *eattrs) rta *a = e->attrs; int err = 0; - if (krt_ecmp6(p) && (a->dest == RTD_MULTIPATH)) + if (krt_ecmp6(p) && a->nh.next) { - struct mpnh *nh = a->nexthops; + struct nexthop *nh = &(a->nh); - err = nl_send_route(p, e, eattrs, NL_OP_ADD, RTD_ROUTER, nh->gw, nh->iface); + err = nl_send_route(p, e, eattrs, NL_OP_ADD, RTD_UNICAST, nh); if (err < 0) return err; for (nh = nh->next; nh; nh = nh->next) - err += nl_send_route(p, e, eattrs, NL_OP_APPEND, RTD_ROUTER, nh->gw, nh->iface); + err += nl_send_route(p, e, eattrs, NL_OP_APPEND, RTD_UNICAST, nh); return err; } - return nl_send_route(p, e, eattrs, NL_OP_ADD, a->dest, a->gw, a->iface); + return nl_send_route(p, e, eattrs, NL_OP_ADD, a->dest, &(a->nh)); } static inline int @@ -1022,7 +1279,7 @@ nl_delete_rte(struct krt_proto *p, rte *e, struct ea_list *eattrs) /* For IPv6, we just repeatedly request DELETE until we get error */ do - err = nl_send_route(p, e, eattrs, NL_OP_DELETE, RTD_NONE, IPA_NONE, NULL); + err = nl_send_route(p, e, eattrs, NL_OP_DELETE, RTD_NONE, NULL); while (krt_ecmp6(p) && !err); return err; @@ -1057,10 +1314,10 @@ krt_replace_rte(struct krt_proto *p, net *n, rte *new, rte *old, struct ea_list } -static inline struct mpnh * -nl_alloc_mpnh(struct nl_parse_state *s, ip_addr gw, struct iface *iface, byte weight) +static inline struct nexthop * +nl_alloc_nexthop(struct nl_parse_state *s, ip_addr gw, struct iface *iface, byte weight) { - struct mpnh *nh = lp_alloc(s->pool, sizeof(struct mpnh)); + struct nexthop *nh = lp_alloc(s->pool, sizeof(struct nexthop)); nh->gw = gw; nh->iface = iface; @@ -1137,9 +1394,9 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h) struct rtattr *a[BIRD_RTA_MAX]; int new = h->nlmsg_type == RTM_NEWROUTE; - ip_addr dst = IPA_NONE; + net_addr dst; u32 oif = ~0; - u32 table; + u32 table_id; u32 priority = 0; u32 def_scope = RT_SCOPE_UNIVERSE; int src; @@ -1149,47 +1406,61 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h) switch (i->rtm_family) { -#ifndef IPV6 - case AF_INET: - if (!nl_parse_attrs(RTM_RTA(i), rtm_attr_want4, a, sizeof(a))) - return; - break; -#else - case AF_INET6: - if (!nl_parse_attrs(RTM_RTA(i), rtm_attr_want6, a, sizeof(a))) - return; - break; -#endif - default: + case AF_INET: + if (!nl_parse_attrs(RTM_RTA(i), rtm_attr_want4, a, sizeof(a))) return; - } - if (a[RTA_DST]) - { - memcpy(&dst, RTA_DATA(a[RTA_DST]), sizeof(dst)); - ipa_ntoh(dst); + if (a[RTA_DST]) + net_fill_ip4(&dst, rta_get_ip4(a[RTA_DST]), i->rtm_dst_len); + else + net_fill_ip4(&dst, IP4_NONE, 0); + break; + + case AF_INET6: + if (!nl_parse_attrs(RTM_RTA(i), rtm_attr_want6, a, sizeof(a))) + return; + + if (a[RTA_DST]) + net_fill_ip6(&dst, rta_get_ip6(a[RTA_DST]), i->rtm_dst_len); + else + net_fill_ip6(&dst, IP6_NONE, 0); + break; + + case AF_MPLS: + if (!nl_parse_attrs(RTM_RTA(i), rtm_attr_want_mpls, a, sizeof(a))) + return; + + if (!a[RTA_DST]) + SKIP("MPLS route without RTA_DST"); + + if (rta_get_mpls(a[RTA_DST], rta_mpls_stack) != 1) + SKIP("MPLS route with multi-label RTA_DST"); + + net_fill_mpls(&dst, rta_mpls_stack[0]); + break; + + default: + return; } if (a[RTA_OIF]) oif = rta_get_u32(a[RTA_OIF]); if (a[RTA_TABLE]) - table = rta_get_u32(a[RTA_TABLE]); + table_id = rta_get_u32(a[RTA_TABLE]); else - table = i->rtm_table; + table_id = i->rtm_table; - p = HASH_FIND(nl_table_map, RTH, table); /* Do we know this table? */ - DBG("KRT: Got %I/%d, type=%d, oif=%d, table=%d, prid=%d, proto=%s\n", dst, i->rtm_dst_len, i->rtm_type, oif, table, i->rtm_protocol, p ? p->p.name : "(none)"); + /* Do we know this table? */ + p = HASH_FIND(nl_table_map, RTH, i->rtm_family, table_id); if (!p) SKIP("unknown table %d\n", table); -#ifdef IPV6 if (a[RTA_IIF]) SKIP("IIF set\n"); -#else + if (i->rtm_tos != 0) /* We don't support TOS */ SKIP("TOS %02x\n", i->rtm_tos); -#endif if (s->scan && !new) SKIP("RTM_DELROUTE in scan\n"); @@ -1197,7 +1468,7 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h) if (a[RTA_PRIORITY]) priority = rta_get_u32(a[RTA_PRIORITY]); - int c = ipa_classify_net(dst); + int c = net_classify(&dst); if ((c < 0) || !(c & IADDR_HOST) || ((c & IADDR_SCOPE_MASK) <= SCOPE_LINK)) SKIP("strange class/scope\n"); @@ -1225,70 +1496,63 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h) src = KRT_SRC_ALIEN; } - net *net = net_get(p->p.table, dst, i->rtm_dst_len); + net *net = net_get(p->p.main_channel->table, &dst); if (s->net && !nl_mergable_route(s, net, p, priority, i->rtm_type)) nl_announce_route(s); - rta *ra = lp_allocz(s->pool, sizeof(rta)); + rta *ra = lp_allocz(s->pool, RTA_MAX_SIZE); ra->src = p->p.main_source; ra->source = RTS_INHERIT; ra->scope = SCOPE_UNIVERSE; - ra->cast = RTC_UNICAST; switch (i->rtm_type) { case RTN_UNICAST: + ra->dest = RTD_UNICAST; if (a[RTA_MULTIPATH] && (i->rtm_family == AF_INET)) { - ra->dest = RTD_MULTIPATH; - ra->nexthops = nl_parse_multipath(p, a[RTA_MULTIPATH]); - if (!ra->nexthops) + struct nexthop *nh = nl_parse_multipath(p, a[RTA_MULTIPATH]); + if (!nh) { - log(L_ERR "KRT: Received strange multipath route %I/%d", - net->n.prefix, net->n.pxlen); + log(L_ERR "KRT: Received strange multipath route %N", net->n.addr); return; } + ra->nh = *nh; break; } - ra->iface = if_find_by_index(oif); - if (!ra->iface) + ra->nh.iface = if_find_by_index(oif); + if (!ra->nh.iface) { - log(L_ERR "KRT: Received route %I/%d with unknown ifindex %u", - net->n.prefix, net->n.pxlen, oif); + log(L_ERR "KRT: Received route %N with unknown ifindex %u", net->n.addr, oif); return; } - if (a[RTA_GATEWAY]) + if ((i->rtm_family != AF_MPLS) && a[RTA_GATEWAY] || (i->rtm_family == AF_MPLS) && a[RTA_VIA]) { - neighbor *ng; - ra->dest = RTD_ROUTER; - memcpy(&ra->gw, RTA_DATA(a[RTA_GATEWAY]), sizeof(ra->gw)); - ipa_ntoh(ra->gw); + if (i->rtm_family == AF_MPLS) + ra->nh.gw = rta_get_via(a[RTA_VIA]); + else + ra->nh.gw = rta_get_ipa(a[RTA_GATEWAY]); -#ifdef IPV6 /* Silently skip strange 6to4 routes */ - if (ipa_in_net(ra->gw, IPA_NONE, 96)) + const net_addr_ip6 sit = NET_ADDR_IP6(IP6_NONE, 96); + if ((i->rtm_family == AF_INET6) && ipa_in_netX(ra->nh.gw, (net_addr *) &sit)) return; -#endif - ng = neigh_find2(&p->p, &ra->gw, ra->iface, - (i->rtm_flags & RTNH_F_ONLINK) ? NEF_ONLINK : 0); - if (!ng || (ng->scope == SCOPE_HOST)) + neighbor *nbr; + nbr = neigh_find2(&p->p, &(ra->nh.gw), ra->nh.iface, + (i->rtm_flags & RTNH_F_ONLINK) ? NEF_ONLINK : 0); + if (!nbr || (nbr->scope == SCOPE_HOST)) { - log(L_ERR "KRT: Received route %I/%d with strange next-hop %I", - net->n.prefix, net->n.pxlen, ra->gw); + log(L_ERR "KRT: Received route %N with strange next-hop %I", net->n.addr, + ra->nh.gw); return; } } - else - { - ra->dest = RTD_DEVICE; - def_scope = RT_SCOPE_LINK; - } break; case RTN_BLACKHOLE: @@ -1306,6 +1570,44 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h) return; } + int labels = 0; + if ((i->rtm_family == AF_MPLS) && a[RTA_NEWDST] && !ra->nh.next) + labels = rta_get_mpls(a[RTA_NEWDST], ra->nh.label); + + if (a[RTA_ENCAP] && a[RTA_ENCAP_TYPE] && !ra->nh.next) + { + switch (rta_get_u16(a[RTA_ENCAP_TYPE])) + { + case LWTUNNEL_ENCAP_MPLS: + { + struct rtattr *enca[BIRD_RTA_MAX]; + nl_attr_len = RTA_PAYLOAD(a[RTA_ENCAP]); + nl_parse_attrs(RTA_DATA(a[RTA_ENCAP]), encap_mpls_want, enca, sizeof(enca)); + labels = rta_get_mpls(enca[RTA_DST], ra->nh.label); + break; + } + default: + SKIP("unknown encapsulation method %d\n", rta_get_u16(a[RTA_ENCAP_TYPE])); + break; + } + } + + if (labels < 0) + { + log(L_WARN "KRT: Too long MPLS stack received, ignoring."); + ra->nh.labels = 0; + } + else + ra->nh.labels = labels; + + rte *e = rte_get_temp(ra); + e->net = net; + e->u.krt.src = src; + e->u.krt.proto = i->rtm_protocol; + e->u.krt.seen = 0; + e->u.krt.best = 0; + e->u.krt.metric = 0; + if (i->rtm_scope != def_scope) { ea_list *ea = lp_alloc(s->pool, sizeof(ea_list) + sizeof(eattr)); @@ -1319,11 +1621,12 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h) ea->attrs[0].u.data = i->rtm_scope; } + if (a[RTA_PRIORITY]) + e->u.krt.metric = rta_get_u32(a[RTA_PRIORITY]); + if (a[RTA_PREFSRC]) { - ip_addr ps; - memcpy(&ps, RTA_DATA(a[RTA_PREFSRC]), sizeof(ps)); - ipa_ntoh(ps); + ip_addr ps = rta_get_ipa(a[RTA_PREFSRC]); ea_list *ea = lp_alloc(s->pool, sizeof(ea_list) + sizeof(eattr)); ea->next = ra->eattrs; @@ -1359,8 +1662,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 %I/%d with strange RTA_METRICS attribute", - net->n.prefix, net->n.pxlen); + log(L_ERR "KRT: Received route %N with strange RTA_METRICS attribute", net->n.addr); return; } @@ -1404,15 +1706,20 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h) else { /* Merge next hops with the stored route */ - rta *a = s->attrs; + rta *oa = s->attrs; + + struct nexthop *nhs = &oa->nh; + nexthop_insert(&nhs, &ra->nh); - if (a->dest != RTD_MULTIPATH) + /* Perhaps new nexthop is inserted at the first position */ + if (nhs == &ra->nh) { - a->dest = RTD_MULTIPATH; - a->nexthops = nl_alloc_mpnh(s, a->gw, a->iface, 0); - } + /* Swap rtas */ + s->attrs = ra; - mpnh_insert(&a->nexthops, nl_alloc_mpnh(s, ra->gw, ra->iface, 0)); + /* Keep old eattrs */ + ra->eattrs = oa->eattrs; + } } } @@ -1422,15 +1729,31 @@ krt_do_scan(struct krt_proto *p UNUSED) /* CONFIG_ALL_TABLES_AT_ONCE => p is NUL struct nlmsghdr *h; struct nl_parse_state s; - nl_parse_begin(&s, 1, krt_ecmp6(p)); + nl_parse_begin(&s, 1, 0); + nl_request_dump(AF_INET, RTM_GETROUTE); + while (h = nl_get_scan()) + if (h->nlmsg_type == RTM_NEWROUTE || h->nlmsg_type == RTM_DELROUTE) + nl_parse_route(&s, h); + else + log(L_DEBUG "nl_scan_fire: Unknown packet received (type=%d)", h->nlmsg_type); + nl_parse_end(&s); - nl_request_dump(BIRD_AF, RTM_GETROUTE); + nl_parse_begin(&s, 1, 1); + nl_request_dump(AF_INET6, RTM_GETROUTE); while (h = nl_get_scan()) if (h->nlmsg_type == RTM_NEWROUTE || h->nlmsg_type == RTM_DELROUTE) nl_parse_route(&s, h); else log(L_DEBUG "nl_scan_fire: Unknown packet received (type=%d)", h->nlmsg_type); + nl_parse_end(&s); + nl_parse_begin(&s, 1, 1); + nl_request_dump(AF_MPLS, RTM_GETROUTE); + while (h = nl_get_scan()) + if (h->nlmsg_type == RTM_NEWROUTE || h->nlmsg_type == RTM_DELROUTE) + nl_parse_route(&s, h); + else + log(L_DEBUG "nl_scan_fire: Unknown packet received (type=%d)", h->nlmsg_type); nl_parse_end(&s); } @@ -1553,11 +1876,10 @@ nl_open_async(void) bzero(&sa, sizeof(sa)); sa.nl_family = AF_NETLINK; -#ifdef IPV6 - sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE; -#else - sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE; -#endif + sa.nl_groups = RTMGRP_LINK | + RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE | + RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE; + if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) { log(L_ERR "Unable to bind asynchronous rtnetlink socket: %m"); @@ -1584,14 +1906,14 @@ nl_open_async(void) void krt_sys_io_init(void) { - nl_linpool = lp_new(krt_pool, 4080); + nl_linpool = lp_new_default(krt_pool); HASH_INIT(nl_table_map, krt_pool, 6); } int krt_sys_start(struct krt_proto *p) { - struct krt_proto *old = HASH_FIND(nl_table_map, RTH, krt_table_id(p)); + struct krt_proto *old = HASH_FIND(nl_table_map, RTH, p->af, krt_table_id(p)); if (old) { @@ -1624,7 +1946,7 @@ void krt_sys_init_config(struct krt_config *cf) { cf->sys.table_id = RT_TABLE_MAIN; - cf->sys.metric = 0; + cf->sys.metric = 32; } void diff --git a/sysdep/unix/Makefile b/sysdep/unix/Makefile new file mode 100644 index 00000000..f592399c --- /dev/null +++ b/sysdep/unix/Makefile @@ -0,0 +1,8 @@ +src := io.c krt.c log.c main.c random.c +obj := $(src-o-files) +$(all-daemon) +$(cf-local) +$(conf-y-targets): $(s)krt.Y + +src := $(filter-out main.c, $(src)) +tests_objs := $(tests_objs) $(src-o-files) diff --git a/sysdep/unix/Modules b/sysdep/unix/Modules deleted file mode 100644 index 2c6514df..00000000 --- a/sysdep/unix/Modules +++ /dev/null @@ -1,12 +0,0 @@ -log.c -main.c -timer.h -io.c -unix.h -endian.h -config.Y -random.c - -krt.c -krt.h -krt.Y diff --git a/sysdep/unix/config.Y b/sysdep/unix/config.Y index d6ab8cab..ebadd454 100644 --- a/sysdep/unix/config.Y +++ b/sysdep/unix/config.Y @@ -8,7 +8,7 @@ CF_HDR -#include "lib/unix.h" +#include "sysdep/unix/unix.h" #include <stdio.h> CF_DECLS @@ -92,6 +92,7 @@ timeformat_which: | PROTOCOL { $$ = &new_config->tf_proto; } | BASE { $$ = &new_config->tf_base; } | LOG { $$ = &new_config->tf_log; } + ; timeformat_spec: timeformat_which TEXT { *$1 = (struct timeformat){$2, NULL, 0}; } diff --git a/sysdep/unix/io.c b/sysdep/unix/io.c index 561d4dea..d1246ea5 100644 --- a/sysdep/unix/io.c +++ b/sysdep/unix/io.c @@ -34,14 +34,14 @@ #include "nest/bird.h" #include "lib/lists.h" #include "lib/resource.h" -#include "lib/timer.h" +#include "sysdep/unix/timer.h" #include "lib/socket.h" #include "lib/event.h" #include "lib/string.h" #include "nest/iface.h" -#include "lib/unix.h" -#include "lib/sysio.h" +#include "sysdep/unix/unix.h" +#include CONFIG_INCLUDE_SYSIO_H /* Maximum number of calls of tx handler for one socket in one * poll iteration. Should be small enough to not monopolize CPU by @@ -1071,26 +1071,63 @@ sk_free_bufs(sock *s) } } +#ifdef HAVE_LIBSSH +static void +sk_ssh_free(sock *s) +{ + struct ssh_sock *ssh = s->ssh; + + if (s->ssh == NULL) + return; + + s->ssh = NULL; + + if (ssh->channel) + { + if (ssh_channel_is_open(ssh->channel)) + ssh_channel_close(ssh->channel); + ssh_channel_free(ssh->channel); + ssh->channel = NULL; + } + + if (ssh->session) + { + ssh_disconnect(ssh->session); + ssh_free(ssh->session); + ssh->session = NULL; + } +} +#endif + static void sk_free(resource *r) { sock *s = (sock *) r; sk_free_bufs(s); - if (s->fd >= 0) - { - close(s->fd); - /* FIXME: we should call sk_stop() for SKF_THREAD sockets */ - if (s->flags & SKF_THREAD) - return; +#ifdef HAVE_LIBSSH + if (s->type == SK_SSH || s->type == SK_SSH_ACTIVE) + sk_ssh_free(s); +#endif + + if (s->fd < 0) + return; + /* FIXME: we should call sk_stop() for SKF_THREAD sockets */ + if (!(s->flags & SKF_THREAD)) + { if (s == current_sock) current_sock = sk_next(s); if (s == stored_sock) stored_sock = sk_next(s); rem_node(&s->n); } + + if (s->type != SK_SSH && s->type != SK_SSH_ACTIVE) + close(s->fd); + + s->fd = -1; } void @@ -1141,7 +1178,7 @@ static void sk_dump(resource *r) { sock *s = (sock *) r; - static char *sk_type_names[] = { "TCP<", "TCP>", "TCP", "UDP", NULL, "IP", NULL, "MAGIC", "UNIX<", "UNIX", "DEL!" }; + static char *sk_type_names[] = { "TCP<", "TCP>", "TCP", "UDP", NULL, "IP", NULL, "MAGIC", "UNIX<", "UNIX", "SSH>", "SSH", "DEL!" }; debug("(%s, ud=%p, sa=%I, sp=%d, da=%I, dp=%d, tos=%d, ttl=%d, if=%s)\n", sk_type_names[s->type], @@ -1192,6 +1229,9 @@ sk_setup(sock *s) int y = 1; int fd = s->fd; + if (s->type == SK_SSH_ACTIVE) + return 0; + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) ERR("O_NONBLOCK"); @@ -1255,7 +1295,7 @@ sk_setup(sock *s) if (sk_is_ipv6(s)) { - if (s->flags & SKF_V6ONLY) + if ((s->type == SK_TCP_PASSIVE) || (s->type == SK_TCP_ACTIVE) || (s->type == SK_UDP)) if (setsockopt(fd, SOL_IPV6, IPV6_V6ONLY, &y, sizeof(y)) < 0) ERR("IPV6_V6ONLY"); @@ -1304,6 +1344,14 @@ sk_tcp_connected(sock *s) s->tx_hook(s); } +static void +sk_ssh_connected(sock *s) +{ + sk_alloc_bufs(s); + s->type = SK_SSH; + s->tx_hook(s); +} + static int sk_passive_connected(sock *s, int type) { @@ -1321,8 +1369,8 @@ sk_passive_connected(sock *s, int type) sock *t = sk_new(s->pool); t->type = type; - t->fd = fd; t->af = s->af; + t->fd = fd; t->ttl = s->ttl; t->tos = s->tos; t->rbsize = s->rbsize; @@ -1356,6 +1404,201 @@ sk_passive_connected(sock *s, int type) return 1; } +#ifdef HAVE_LIBSSH +/* + * Return SSH_OK or SSH_AGAIN or SSH_ERROR + */ +static int +sk_ssh_connect(sock *s) +{ + s->fd = ssh_get_fd(s->ssh->session); + + /* Big fall thru automata */ + switch (s->ssh->state) + { + case SK_SSH_CONNECT: + { + switch (ssh_connect(s->ssh->session)) + { + case SSH_AGAIN: + /* A quick look into libSSH shows that ssh_get_fd() should return non-(-1) + * after SSH_AGAIN is returned by ssh_connect(). This is however nowhere + * documented but our code relies on that. + */ + return SSH_AGAIN; + + case SSH_OK: + break; + + default: + return SSH_ERROR; + } + } + + case SK_SSH_SERVER_KNOWN: + { + s->ssh->state = SK_SSH_SERVER_KNOWN; + + if (s->ssh->server_hostkey_path) + { + int server_identity_is_ok = 1; + + /* Check server identity */ + switch (ssh_is_server_known(s->ssh->session)) + { +#define LOG_WARN_ABOUT_SSH_SERVER_VALIDATION(s,msg,args...) log(L_WARN "SSH Identity %s@%s:%u: " msg, (s)->ssh->username, (s)->host, (s)->dport, ## args); + case SSH_SERVER_KNOWN_OK: + /* The server is known and has not changed. */ + break; + + case SSH_SERVER_NOT_KNOWN: + LOG_WARN_ABOUT_SSH_SERVER_VALIDATION(s, "The server is unknown, its public key was not found in the known host file %s", s->ssh->server_hostkey_path); + break; + + case SSH_SERVER_KNOWN_CHANGED: + LOG_WARN_ABOUT_SSH_SERVER_VALIDATION(s, "The server key has changed. Either you are under attack or the administrator changed the key."); + server_identity_is_ok = 0; + break; + + case SSH_SERVER_FILE_NOT_FOUND: + LOG_WARN_ABOUT_SSH_SERVER_VALIDATION(s, "The known host file %s does not exist", s->ssh->server_hostkey_path); + server_identity_is_ok = 0; + break; + + case SSH_SERVER_ERROR: + LOG_WARN_ABOUT_SSH_SERVER_VALIDATION(s, "Some error happened"); + server_identity_is_ok = 0; + break; + + case SSH_SERVER_FOUND_OTHER: + LOG_WARN_ABOUT_SSH_SERVER_VALIDATION(s, "The server gave use a key of a type while we had an other type recorded. " \ + "It is a possible attack."); + server_identity_is_ok = 0; + break; + } + + if (!server_identity_is_ok) + return SSH_ERROR; + } + } + + case SK_SSH_USERAUTH: + { + s->ssh->state = SK_SSH_USERAUTH; + switch (ssh_userauth_publickey_auto(s->ssh->session, NULL, NULL)) + { + case SSH_AUTH_AGAIN: + return SSH_AGAIN; + + case SSH_AUTH_SUCCESS: + break; + + default: + return SSH_ERROR; + } + } + + case SK_SSH_CHANNEL: + { + s->ssh->state = SK_SSH_CHANNEL; + s->ssh->channel = ssh_channel_new(s->ssh->session); + if (s->ssh->channel == NULL) + return SSH_ERROR; + } + + case SK_SSH_SESSION: + { + s->ssh->state = SK_SSH_SESSION; + switch (ssh_channel_open_session(s->ssh->channel)) + { + case SSH_AGAIN: + return SSH_AGAIN; + + case SSH_OK: + break; + + default: + return SSH_ERROR; + } + } + + case SK_SSH_SUBSYSTEM: + { + s->ssh->state = SK_SSH_SUBSYSTEM; + if (s->ssh->subsystem) + { + switch (ssh_channel_request_subsystem(s->ssh->channel, s->ssh->subsystem)) + { + case SSH_AGAIN: + return SSH_AGAIN; + + case SSH_OK: + break; + + default: + return SSH_ERROR; + } + } + } + + case SK_SSH_ESTABLISHED: + s->ssh->state = SK_SSH_ESTABLISHED; + } + + return SSH_OK; +} + +/* + * Return file descriptor number if success + * Return -1 if failed + */ +static int +sk_open_ssh(sock *s) +{ + if (!s->ssh) + bug("sk_open() sock->ssh is not allocated"); + + ssh_session sess = ssh_new(); + if (sess == NULL) + ERR2("Cannot create a ssh session"); + s->ssh->session = sess; + + const int verbosity = SSH_LOG_NOLOG; + ssh_options_set(sess, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(sess, SSH_OPTIONS_HOST, s->host); + ssh_options_set(sess, SSH_OPTIONS_PORT, &(s->dport)); + /* TODO: Add SSH_OPTIONS_BINDADDR */ + ssh_options_set(sess, SSH_OPTIONS_USER, s->ssh->username); + + if (s->ssh->server_hostkey_path) + ssh_options_set(sess, SSH_OPTIONS_KNOWNHOSTS, s->ssh->server_hostkey_path); + + if (s->ssh->client_privkey_path) + ssh_options_set(sess, SSH_OPTIONS_IDENTITY, s->ssh->client_privkey_path); + + ssh_set_blocking(sess, 0); + + switch (sk_ssh_connect(s)) + { + case SSH_AGAIN: + break; + + case SSH_OK: + sk_ssh_connected(s); + break; + + case SSH_ERROR: + ERR2(ssh_get_error(sess)); + break; + } + + return ssh_get_fd(sess); + + err: + return -1; +} +#endif + /** * sk_open - open a socket * @s: socket @@ -1369,13 +1612,46 @@ sk_passive_connected(sock *s, int type) int sk_open(sock *s) { - int af = BIRD_AF; + int af = AF_UNSPEC; int fd = -1; int do_bind = 0; int bind_port = 0; ip_addr bind_addr = IPA_NONE; sockaddr sa; + if (s->type <= SK_IP) + { + /* + * For TCP/IP sockets, Address family (IPv4 or IPv6) can be specified either + * explicitly (SK_IPV4 or SK_IPV6) or implicitly (based on saddr, daddr). + * But the specifications have to be consistent. + */ + + switch (s->subtype) + { + case 0: + ASSERT(ipa_zero(s->saddr) || ipa_zero(s->daddr) || + (ipa_is_ip4(s->saddr) == ipa_is_ip4(s->daddr))); + af = (ipa_is_ip4(s->saddr) || ipa_is_ip4(s->daddr)) ? AF_INET : AF_INET6; + break; + + case SK_IPV4: + ASSERT(ipa_zero(s->saddr) || ipa_is_ip4(s->saddr)); + ASSERT(ipa_zero(s->daddr) || ipa_is_ip4(s->daddr)); + af = AF_INET; + break; + + case SK_IPV6: + ASSERT(ipa_zero(s->saddr) || !ipa_is_ip4(s->saddr)); + ASSERT(ipa_zero(s->daddr) || !ipa_is_ip4(s->daddr)); + af = AF_INET6; + break; + + default: + bug("Invalid subtype %d", s->subtype); + } + } + switch (s->type) { case SK_TCP_ACTIVE: @@ -1388,6 +1664,13 @@ sk_open(sock *s) do_bind = bind_port || ipa_nonzero(bind_addr); break; +#ifdef HAVE_LIBSSH + case SK_SSH_ACTIVE: + s->ttx = ""; /* Force s->ttx != s->tpos */ + fd = sk_open_ssh(s); + break; +#endif + case SK_UDP: fd = socket(af, SOCK_DGRAM, IPPROTO_UDP); bind_port = s->sport; @@ -1443,7 +1726,7 @@ sk_open(sock *s) if (sk_set_high_port(s) < 0) log(L_WARN "Socket error: %s%#m", s->err); - sockaddr_fill(&sa, af, bind_addr, s->iface, bind_port); + sockaddr_fill(&sa, s->af, bind_addr, s->iface, bind_port); if (bind(fd, &sa.sa, SA_LEN(sa)) < 0) ERR2("bind"); } @@ -1455,7 +1738,7 @@ sk_open(sock *s) switch (s->type) { case SK_TCP_ACTIVE: - sockaddr_fill(&sa, af, s->daddr, s->iface, s->dport); + sockaddr_fill(&sa, s->af, s->daddr, s->iface, s->dport); if (connect(fd, &sa.sa, SA_LEN(sa)) >= 0) sk_tcp_connected(s); else if (errno != EINTR && errno != EAGAIN && errno != EINPROGRESS && @@ -1468,6 +1751,7 @@ sk_open(sock *s) ERR2("listen"); break; + case SK_SSH_ACTIVE: case SK_MAGIC: break; @@ -1477,6 +1761,7 @@ sk_open(sock *s) if (!(s->flags & SKF_THREAD)) sk_insert(s); + return 0; err: @@ -1659,6 +1944,28 @@ sk_maybe_write(sock *s) reset_tx_buffer(s); return 1; +#ifdef HAVE_LIBSSH + case SK_SSH: + while (s->ttx != s->tpos) + { + e = ssh_channel_write(s->ssh->channel, s->ttx, s->tpos - s->ttx); + + if (e < 0) + { + s->err = ssh_get_error(s->ssh->session); + s->err_hook(s, ssh_get_error_code(s->ssh->session)); + + reset_tx_buffer(s); + /* EPIPE is just a connection close notification during TX */ + s->err_hook(s, (errno != EPIPE) ? errno : 0); + return -1; + } + s->ttx += e; + } + reset_tx_buffer(s); + return 1; +#endif + case SK_UDP: case SK_IP: { @@ -1683,6 +1990,7 @@ sk_maybe_write(sock *s) reset_tx_buffer(s); return 1; } + default: bug("sk_maybe_write: unknown socket type %d", s->type); } @@ -1762,6 +2070,64 @@ sk_send_full(sock *s, unsigned len, struct iface *ifa, } */ +static void +call_rx_hook(sock *s, int size) +{ + if (s->rx_hook(s, size)) + { + /* We need to be careful since the socket could have been deleted by the hook */ + if (current_sock == s) + s->rpos = s->rbuf; + } +} + +#ifdef HAVE_LIBSSH +static int +sk_read_ssh(sock *s) +{ + ssh_channel rchans[2] = { s->ssh->channel, NULL }; + struct timeval timev = { 1, 0 }; + + if (ssh_channel_select(rchans, NULL, NULL, &timev) == SSH_EINTR) + return 1; /* Try again */ + + if (ssh_channel_is_eof(s->ssh->channel) != 0) + { + /* The remote side is closing the connection */ + s->err_hook(s, 0); + return 0; + } + + if (rchans[0] == NULL) + return 0; /* No data is available on the socket */ + + const uint used_bytes = s->rpos - s->rbuf; + const int read_bytes = ssh_channel_read_nonblocking(s->ssh->channel, s->rpos, s->rbsize - used_bytes, 0); + if (read_bytes > 0) + { + /* Received data */ + s->rpos += read_bytes; + call_rx_hook(s, used_bytes + read_bytes); + return 1; + } + else if (read_bytes == 0) + { + if (ssh_channel_is_eof(s->ssh->channel) != 0) + { + /* The remote side is closing the connection */ + s->err_hook(s, 0); + } + } + else + { + s->err = ssh_get_error(s->ssh->session); + s->err_hook(s, ssh_get_error_code(s->ssh->session)); + } + + return 0; /* No data is available on the socket */ +} +#endif + /* sk_read() and sk_write() are called from BFD's event loop */ int @@ -1795,17 +2161,17 @@ sk_read(sock *s, int revents) else { s->rpos += c; - if (s->rx_hook(s, s->rpos - s->rbuf)) - { - /* We need to be careful since the socket could have been deleted by the hook */ - if (current_sock == s) - s->rpos = s->rbuf; - } + call_rx_hook(s, s->rpos - s->rbuf); return 1; } return 0; } +#ifdef HAVE_LIBSSH + case SK_SSH: + return sk_read_ssh(s); +#endif + case SK_MAGIC: return s->rx_hook(s, 0); @@ -1844,6 +2210,27 @@ sk_write(sock *s) return 0; } +#ifdef HAVE_LIBSSH + case SK_SSH_ACTIVE: + { + switch (sk_ssh_connect(s)) + { + case SSH_OK: + sk_ssh_connected(s); + break; + + case SSH_AGAIN: + return 1; + + case SSH_ERROR: + s->err = ssh_get_error(s->ssh->session); + s->err_hook(s, ssh_get_error_code(s->ssh->session)); + break; + } + return 0; + } +#endif + default: if (s->ttx != s->tpos && sk_maybe_write(s) > 0) { @@ -1855,6 +2242,12 @@ sk_write(sock *s) } } +int sk_is_ipv4(sock *s) +{ return s->af == AF_INET; } + +int sk_is_ipv6(sock *s) +{ return s->af == AF_INET6; } + void sk_err(sock *s, int revents) { diff --git a/sysdep/unix/krt.Y b/sysdep/unix/krt.Y index 6fe39fa9..b261c91e 100644 --- a/sysdep/unix/krt.Y +++ b/sysdep/unix/krt.Y @@ -8,13 +8,23 @@ CF_HDR -#include "lib/krt.h" +#include "sysdep/unix/krt.h" CF_DEFINES #define THIS_KRT ((struct krt_config *) this_proto) #define THIS_KIF ((struct kif_config *) this_proto) +static void +krt_set_merge_paths(struct channel_config *cc, uint merge, uint limit) +{ + if ((limit <= 0) || (limit > 255)) + cf_error("Merge paths limit must be in range 1-255"); + + cc->ra_mode = merge ? RA_MERGED : RA_OPTIMAL; + cc->merge_limit = limit; +} + CF_DECLS CF_KEYWORDS(KERNEL, PERSIST, SCAN, TIME, LEARN, DEVICE, ROUTES, GRACEFUL, RESTART, KRT_SOURCE, KRT_METRIC, MERGE, PATHS) @@ -27,11 +37,12 @@ CF_GRAMMAR CF_ADDTO(proto, kern_proto '}') -kern_proto_start: proto_start KERNEL { this_proto = krt_init_config($1); } +kern_proto_start: proto_start KERNEL { + this_proto = krt_init_config($1); +} ; CF_ADDTO(kern_proto, kern_proto_start proto_name '{') -CF_ADDTO(kern_proto, kern_proto proto_item ';') CF_ADDTO(kern_proto, kern_proto kern_item ';') kern_mp_limit: @@ -40,7 +51,9 @@ kern_mp_limit: ; kern_item: - PERSIST bool { THIS_KRT->persist = $2; } + proto_item + | proto_channel { this_proto->net_type = $1->net_type; } + | PERSIST bool { THIS_KRT->persist = $2; } | SCAN TIME expr { /* Scan time of 0 means scan on startup only */ THIS_KRT->scan_time = $3; @@ -55,7 +68,7 @@ kern_item: | DEVICE ROUTES bool { THIS_KRT->devroutes = $3; } | GRACEFUL RESTART bool { THIS_KRT->graceful_restart = $3; } | MERGE PATHS bool kern_mp_limit { - THIS_KRT->merge_paths = $3 ? $4 : 0; + krt_set_merge_paths(this_channel, $3, $4); #ifndef KRT_ALLOW_MERGE_PATHS if ($3) cf_error("Path merging not supported on this platform"); @@ -71,19 +84,18 @@ kif_proto_start: proto_start DEVICE { this_proto = kif_init_config($1); } ; CF_ADDTO(kif_proto, kif_proto_start proto_name '{') -CF_ADDTO(kif_proto, kif_proto proto_item ';') CF_ADDTO(kif_proto, kif_proto kif_item ';') kif_item: - SCAN TIME expr { + proto_item + | SCAN TIME expr { /* Scan time of 0 means scan on startup only */ THIS_KIF->scan_time = $3; } - | PRIMARY text_or_none prefix_or_ipa { + | PRIMARY opttext net_or_ipa { struct kif_primary_item *kpi = cfg_alloc(sizeof (struct kif_primary_item)); kpi->pattern = $2; - kpi->prefix = $3.addr; - kpi->pxlen = $3.len; + kpi->addr = $3; add_tail(&THIS_KIF->primary, &kpi->n); } ; diff --git a/sysdep/unix/krt.c b/sysdep/unix/krt.c index 07a55c0d..f0241777 100644 --- a/sysdep/unix/krt.c +++ b/sysdep/unix/krt.c @@ -56,7 +56,7 @@ #include "nest/route.h" #include "nest/protocol.h" #include "filter/filter.h" -#include "lib/timer.h" +#include "sysdep/unix/timer.h" #include "conf/conf.h" #include "lib/string.h" @@ -75,7 +75,7 @@ void krt_io_init(void) { krt_pool = rp_new(&root_pool, "Kernel Syncer"); - krt_filter_lp = lp_new(krt_pool, 4080); + krt_filter_lp = lp_new_default(krt_pool); init_list(&krt_proto_list); krt_sys_io_init(); } @@ -131,14 +131,14 @@ prefer_addr(struct ifa *a, struct ifa *b) } static inline struct ifa * -find_preferred_ifa(struct iface *i, ip_addr prefix, ip_addr mask) +find_preferred_ifa(struct iface *i, const net_addr *n) { struct ifa *a, *b = NULL; WALK_LIST(a, i->addrs) { if (!(a->flags & IA_SECONDARY) && - ipa_equal(ipa_and(a->ip, mask), prefix) && + (!n || ipa_in_netX(a->ip, n)) && (!b || prefer_addr(a, b))) b = a; } @@ -156,21 +156,21 @@ kif_choose_primary(struct iface *i) WALK_LIST(it, cf->primary) { if (!it->pattern || patmatch(it->pattern, i->name)) - if (a = find_preferred_ifa(i, it->prefix, ipa_mkmask(it->pxlen))) + if (a = find_preferred_ifa(i, &it->addr)) return a; } if (a = kif_get_primary_ip(i)) return a; - return find_preferred_ifa(i, IPA_NONE, IPA_NONE); + return find_preferred_ifa(i, NULL); } static struct proto * kif_init(struct proto_config *c) { - struct kif_proto *p = proto_new(c, sizeof(struct kif_proto)); + struct kif_proto *p = proto_new(c); kif_sys_init(p); return &p->p; @@ -266,9 +266,6 @@ kif_copy_config(struct proto_config *dest, struct proto_config *src) struct kif_config *d = (struct kif_config *) dest; struct kif_config *s = (struct kif_config *) src; - /* Shallow copy of everything (just scan_time currently) */ - proto_copy_rest(dest, src, sizeof(struct kif_config)); - /* Copy primary addr list */ cfg_copy_list(&d->primary, &s->primary, sizeof(struct kif_primary_item)); @@ -280,7 +277,7 @@ kif_copy_config(struct proto_config *dest, struct proto_config *src) struct protocol proto_unix_iface = { .name = "Device", .template = "device%d", - .preference = DEF_PREF_DIRECT, + .proto_size = sizeof(struct kif_proto), .config_size = sizeof(struct kif_config), .preconfig = kif_preconfig, .init = kif_init, @@ -298,14 +295,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: %I/%d: %s", p->p.name, e->net->n.prefix, e->net->n.pxlen, msg); + log(L_TRACE "%s: %N: %s", p->p.name, e->net->n.addr, 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: %I/%d: %s", p->p.name, e->net->n.prefix, e->net->n.pxlen, msg); + log_rl(f, L_TRACE "%s: %N: %s", p->p.name, e->net->n.addr, msg); } /* @@ -348,19 +345,15 @@ 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); - net *nn = net_get(p->p.table, n->n.prefix, n->n.pxlen); - ee->net = nn; ee->pflags = 0; - ee->pref = p->p.preference; ee->u.krt = e->u.krt; - rte_update(&p->p, nn, ee); + rte_update(&p->p, n->n.addr, ee); } static void krt_learn_announce_delete(struct krt_proto *p, net *n) { - n = net_find(p->p.table, n->n.prefix, n->n.pxlen); - rte_update(&p->p, n, NULL); + rte_update(&p->p, n->n.addr, NULL); } /* Called when alien route is discovered during scan */ @@ -368,7 +361,7 @@ static void krt_learn_scan(struct krt_proto *p, rte *e) { net *n0 = e->net; - net *n = net_get(&p->krt_table, n0->n.prefix, n0->n.pxlen); + net *n = net_get(&p->krt_table, n0->n.addr); rte *m, **mm; e->attrs = rta_lookup(e->attrs); @@ -412,9 +405,8 @@ krt_learn_prune(struct krt_proto *p) FIB_ITERATE_INIT(&fit, fib); again: - FIB_ITERATE_START(fib, &fit, f) + FIB_ITERATE_START(fib, &fit, net, n) { - net *n = (net *) f; rte *e, **ee, *best, **pbest, *old_best; /* @@ -455,8 +447,8 @@ again: if (old_best) krt_learn_announce_delete(p, n); - FIB_ITERATE_PUT(&fit, f); - fib_delete(fib, f); + FIB_ITERATE_PUT(&fit); + fib_delete(fib, n); goto again; } @@ -473,7 +465,7 @@ again: else DBG("%I/%d: uptodate (metric=%d)\n", n->n.prefix, n->n.pxlen, best->u.krt.metric); } - FIB_ITERATE_END(f); + FIB_ITERATE_END; p->reload = 0; } @@ -482,7 +474,7 @@ static void krt_learn_async(struct krt_proto *p, rte *e, int new) { net *n0 = e->net; - net *n = net_get(&p->krt_table, n0->n.prefix, n0->n.pxlen); + net *n = net_get(&p->krt_table, n0->n.addr); rte *g, **gg, *best, **bestp, *old_best; e->attrs = rta_lookup(e->attrs); @@ -588,12 +580,11 @@ krt_dump_attrs(rte *e) static void krt_flush_routes(struct krt_proto *p) { - struct rtable *t = p->p.table; + struct rtable *t = p->p.main_channel->table; KRT_TRACE(p, D_EVENTS, "Flushing kernel routes"); - FIB_WALK(&t->fib, f) + FIB_WALK(&t->fib, net, n) { - net *n = (net *) f; rte *e = n->routes; if (rte_is_valid(e) && (n->n.flags & KRF_INSTALLED)) { @@ -608,12 +599,12 @@ krt_flush_routes(struct krt_proto *p) static struct rte * krt_export_net(struct krt_proto *p, net *net, rte **rt_free, ea_list **tmpa) { - struct announce_hook *ah = p->p.main_ahook; - struct filter *filter = ah->out_filter; + struct channel *c = p->p.main_channel; + struct filter *filter = c->out_filter; rte *rt; - if (p->p.accept_ra_types == RA_MERGED) - return rt_export_merged(ah, net, rt_free, tmpa, krt_filter_lp, 1); + if (c->ra_mode == RA_MERGED) + return rt_export_merged(c, net, rt_free, tmpa, krt_filter_lp, 1); rt = net->routes; *rt_free = NULL; @@ -654,17 +645,11 @@ krt_same_dest(rte *k, rte *e) if (ka->dest != ea->dest) return 0; - switch (ka->dest) - { - case RTD_ROUTER: - return ipa_equal(ka->gw, ea->gw); - case RTD_DEVICE: - return !strcmp(ka->iface->name, ea->iface->name); - case RTD_MULTIPATH: - return mpnh_same(ka->nexthops, ea->nexthops); - default: - return 1; - } + + if (ka->dest == RTD_UNICAST) + return nexthop_same(&(ka->nh), &(ea->nh)); + + return 1; } /* @@ -760,13 +745,12 @@ krt_got_route(struct krt_proto *p, rte *e) static void krt_prune(struct krt_proto *p) { - struct rtable *t = p->p.table; + struct rtable *t = p->p.main_channel->table; KRT_TRACE(p, D_EVENTS, "Pruning table %s", t->name); - FIB_WALK(&t->fib, f) + FIB_WALK(&t->fib, net, n) { - net *n = (net *) f; - int verdict = f->flags & KRF_VERDICT_MASK; + int verdict = n->n.flags & KRF_VERDICT_MASK; rte *new, *old, *rt_free = NULL; ea_list *tmpa = NULL; @@ -795,7 +779,7 @@ krt_prune(struct krt_proto *p) switch (verdict) { case KRF_CREATE: - if (new && (f->flags & KRF_INSTALLED)) + if (new && (n->n.flags & KRF_INSTALLED)) { krt_trace_in(p, new, "reinstalling"); krt_replace_rte(p, n, new, NULL, tmpa); @@ -822,7 +806,7 @@ krt_prune(struct krt_proto *p) if (rt_free) rte_free(rt_free); lp_flush(krt_filter_lp); - f->flags &= ~KRF_VERDICT_MASK; + n->n.flags &= ~KRF_VERDICT_MASK; } FIB_WALK_END; @@ -1000,7 +984,7 @@ krt_store_tmp_attrs(rte *rt, struct ea_list *attrs) static int krt_import_control(struct proto *P, rte **new, ea_list **attrs UNUSED, struct linpool *pool UNUSED) { - struct krt_proto *p = (struct krt_proto *) P; + // struct krt_proto *p = (struct krt_proto *) P; rte *e = *new; if (e->attrs->src->proto == P) @@ -1021,11 +1005,6 @@ krt_import_control(struct proto *P, rte **new, ea_list **attrs UNUSED, struct li return -1; } - if (!KRT_CF->devroutes && - (e->attrs->dest == RTD_DEVICE) && - (e->attrs->source != RTS_STATIC_DEVICE)) - return -1; - if (!krt_capable(e)) return -1; @@ -1033,7 +1012,7 @@ krt_import_control(struct proto *P, rte **new, ea_list **attrs UNUSED, struct li } static void -krt_rt_notify(struct proto *P, struct rtable *table UNUSED, net *net, +krt_rt_notify(struct proto *P, struct channel *ch UNUSED, net *net, rte *new, rte *old, struct ea_list *eattrs) { struct krt_proto *p = (struct krt_proto *) P; @@ -1067,10 +1046,10 @@ krt_if_notify(struct proto *P, uint flags, struct iface *iface UNUSED) krt_scan_timer_kick(p); } -static int -krt_reload_routes(struct proto *P) +static void +krt_reload_routes(struct channel *C) { - struct krt_proto *p = (struct krt_proto *) P; + struct krt_proto *p = (void *) C->proto; /* Although we keep learned routes in krt_table, we rather schedule a scan */ @@ -1079,14 +1058,12 @@ krt_reload_routes(struct proto *P) p->reload = 1; krt_scan_timer_kick(p); } - - return 1; } static void -krt_feed_end(struct proto *P) +krt_feed_end(struct channel *C) { - struct krt_proto *p = (struct krt_proto *) P; + struct krt_proto *p = (void *) C->proto; p->ready = 1; krt_scan_timer_kick(p); @@ -1107,14 +1084,42 @@ krt_rte_same(rte *a, rte *b) struct krt_config *krt_cf; +static void +krt_preconfig(struct protocol *P UNUSED, struct config *c) +{ + krt_cf = NULL; + krt_sys_preconfig(c); +} + +static void +krt_postconfig(struct proto_config *CF) +{ + struct krt_config *cf = (void *) CF; + + if (EMPTY_LIST(CF->channels)) + cf_error("Channel not specified"); + +#ifdef CONFIG_ALL_TABLES_AT_ONCE + if (krt_cf->scan_time != cf->scan_time) + cf_error("All kernel syncers must use the same table scan interval"); +#endif + + struct rtable_config *tab = proto_cf_main_channel(CF)->table; + if (tab->krt_attached) + cf_error("Kernel syncer (%s) already attached to table %s", tab->krt_attached->name, tab->name); + tab->krt_attached = CF; + + krt_sys_postconfig(cf); +} + static struct proto * -krt_init(struct proto_config *C) +krt_init(struct proto_config *CF) { - struct krt_proto *p = proto_new(C, sizeof(struct krt_proto)); - struct krt_config *c = (struct krt_config *) C; + struct krt_proto *p = proto_new(CF); + // struct krt_config *cf = (void *) CF; + + p->p.main_channel = proto_add_channel(&p->p, proto_cf_main_channel(CF)); - p->p.accept_ra_types = c->merge_paths ? RA_MERGED : RA_OPTIMAL; - p->p.merge_limit = c->merge_paths; p->p.import_control = krt_import_control; p->p.rt_notify = krt_rt_notify; p->p.if_notify = krt_if_notify; @@ -1133,6 +1138,16 @@ krt_start(struct proto *P) { struct krt_proto *p = (struct krt_proto *) P; + switch (p->p.net_type) + { + case NET_IP4: p->af = AF_INET; break; + case NET_IP6: p->af = AF_INET6; break; +#ifdef AF_MPLS + case NET_MPLS: p->af = AF_MPLS; break; +#endif + default: log(L_ERR "KRT: Tried to start with strange net type: %d", p->p.net_type); return PS_START; break; + } + add_tail(&krt_proto_list, &p->krt_node); #ifdef KRT_ALLOW_LEARN @@ -1147,8 +1162,8 @@ krt_start(struct proto *P) krt_scan_timer_start(p); - if (P->gr_recovery && KRT_CF->graceful_restart) - P->gr_wait = 1; + if (p->p.gr_recovery && KRT_CF->graceful_restart) + p->p.main_channel->gr_wait = 1; return PS_UP; } @@ -1177,40 +1192,19 @@ krt_shutdown(struct proto *P) } static int -krt_reconfigure(struct proto *p, struct proto_config *new) +krt_reconfigure(struct proto *p, struct proto_config *CF) { - struct krt_config *o = (struct krt_config *) p->cf; - struct krt_config *n = (struct krt_config *) new; + struct krt_config *o = (void *) p->cf; + struct krt_config *n = (void *) CF; + + if (!proto_configure_channel(p, &p->main_channel, proto_cf_main_channel(CF))) + return 0; if (!krt_sys_reconfigure((struct krt_proto *) p, n, o)) return 0; /* persist, graceful restart need not be the same */ - return o->scan_time == n->scan_time && o->learn == n->learn && - o->devroutes == n->devroutes && o->merge_paths == n->merge_paths; -} - -static void -krt_preconfig(struct protocol *P UNUSED, struct config *c) -{ - krt_cf = NULL; - krt_sys_preconfig(c); -} - -static void -krt_postconfig(struct proto_config *C) -{ - struct krt_config *c = (struct krt_config *) C; - -#ifdef CONFIG_ALL_TABLES_AT_ONCE - if (krt_cf->scan_time != c->scan_time) - cf_error("All kernel syncers must use the same table scan interval"); -#endif - - if (C->table->krt_attached) - cf_error("Kernel syncer (%s) already attached to table %s", C->table->krt_attached->name, C->table->name); - C->table->krt_attached = C; - krt_sys_postconfig(c); + return o->scan_time == n->scan_time && o->learn == n->learn && o->devroutes == n->devroutes; } struct proto_config * @@ -1234,9 +1228,6 @@ krt_copy_config(struct proto_config *dest, struct proto_config *src) struct krt_config *d = (struct krt_config *) dest; struct krt_config *s = (struct krt_config *) src; - /* Shallow copy of everything */ - proto_copy_rest(dest, src, sizeof(struct krt_config)); - /* Fix sysdep parts */ krt_sys_copy_config(d, s); } @@ -1265,6 +1256,8 @@ struct protocol proto_unix_kernel = { .template = "kernel%d", .attr_class = EAP_KRT, .preference = DEF_PREF_INHERITED, + .channel_mask = NB_IP | NB_MPLS, + .proto_size = sizeof(struct krt_proto), .config_size = sizeof(struct krt_config), .preconfig = krt_preconfig, .postconfig = krt_postconfig, diff --git a/sysdep/unix/krt.h b/sysdep/unix/krt.h index d4a8717e..cb404de3 100644 --- a/sysdep/unix/krt.h +++ b/sysdep/unix/krt.h @@ -15,7 +15,8 @@ struct krt_proto; struct kif_config; struct kif_proto; -#include "lib/krt-sys.h" +#include "sysdep/config.h" +#include CONFIG_INCLUDE_KRTSYS_H /* Flags stored in net->n.flags, rest are in nest/route.h */ @@ -49,7 +50,6 @@ struct krt_config { int learn; /* Learn routes from other sources */ int devroutes; /* Allow export of device routes */ int graceful_restart; /* Regard graceful restart recovery */ - int merge_paths; /* Exported routes are merged for ECMP */ }; struct krt_proto { @@ -65,6 +65,7 @@ struct krt_proto { #endif node krt_node; /* Node in krt_proto_list */ + byte af; /* Kernel address family (AF_*) */ byte ready; /* Initial feed has been finished */ byte initialized; /* First scan has been finished */ byte reload; /* Next scan is doing reload */ @@ -96,8 +97,7 @@ extern struct protocol proto_unix_iface; struct kif_primary_item { node n; byte *pattern; - ip_addr prefix; - int pxlen; + net_addr addr; }; struct kif_config { @@ -112,7 +112,7 @@ struct kif_proto { struct kif_state sys; /* Sysdep state */ }; -struct kif_proto *kif_proto; +extern struct kif_proto *kif_proto; #define KIF_CF ((struct kif_config *)p->p.cf) diff --git a/sysdep/unix/log.c b/sysdep/unix/log.c index 88a7188c..06d3b09e 100644 --- a/sysdep/unix/log.c +++ b/sysdep/unix/log.c @@ -27,7 +27,7 @@ #include "nest/mrtdump.h" #include "lib/string.h" #include "lib/lists.h" -#include "lib/unix.h" +#include "sysdep/unix/unix.h" static FILE *dbgf; static list *current_log_list; diff --git a/sysdep/unix/main.c b/sysdep/unix/main.c index 8aa19fce..c1b92b7e 100644 --- a/sysdep/unix/main.c +++ b/sysdep/unix/main.c @@ -71,7 +71,7 @@ async_dump(void) */ #ifdef CONFIG_RESTRICTED_PRIVILEGES -#include "lib/syspriv.h" +#include CONFIG_INCLUDE_SYSPRIV_H #else static inline void @@ -571,6 +571,10 @@ sysdep_shutdown_done(void) * Signals */ +volatile int async_config_flag; +volatile int async_dump_flag; +volatile int async_shutdown_flag; + static void handle_sighup(int sig UNUSED) { @@ -820,7 +824,7 @@ main(int argc, char **argv) io_init(); rt_init(); if_init(); - roa_init(); +// roa_init(); config_init(); uid_t use_uid = get_uid(use_user); diff --git a/sysdep/unix/unix.h b/sysdep/unix/unix.h index 3ef2e3ef..dcaab729 100644 --- a/sysdep/unix/unix.h +++ b/sysdep/unix/unix.h @@ -47,14 +47,6 @@ typedef struct sockaddr_bird { } sockaddr; -#ifdef IPV6 -#define BIRD_AF AF_INET6 -#define ipa_from_sa(x) ipa_from_sa6(x) -#else -#define BIRD_AF AF_INET -#define ipa_from_sa(x) ipa_from_sa4(x) -#endif - /* This is sloppy hack, it should be detected by configure script */ /* Linux systems have it defined so this is definition for BSD systems */ @@ -63,29 +55,33 @@ typedef struct sockaddr_bird { #endif -static inline ip_addr ipa_from_in4(struct in_addr a UNUSED6) +static inline ip_addr ipa_from_in4(struct in_addr a) { return ipa_from_u32(ntohl(a.s_addr)); } -static inline ip_addr ipa_from_in6(struct in6_addr a UNUSED4) +static inline ip_addr ipa_from_in6(struct in6_addr a) { return ipa_build6(ntohl(a.s6_addr32[0]), ntohl(a.s6_addr32[1]), ntohl(a.s6_addr32[2]), ntohl(a.s6_addr32[3])); } -static inline ip_addr ipa_from_sa4(sockaddr *sa UNUSED6) +static inline ip_addr ipa_from_sa4(sockaddr *sa) { return ipa_from_in4(((struct sockaddr_in *) sa)->sin_addr); } -static inline ip_addr ipa_from_sa6(sockaddr *sa UNUSED4) +static inline ip_addr ipa_from_sa6(sockaddr *sa) { return ipa_from_in6(((struct sockaddr_in6 *) sa)->sin6_addr); } +static inline ip_addr ipa_from_sa(sockaddr *sa) +{ + switch (sa->sa.sa_family) + { + case AF_INET: return ipa_from_sa4(sa); + case AF_INET6: return ipa_from_sa6(sa); + default: return IPA_NONE; + } +} + static inline struct in_addr ipa_to_in4(ip_addr a) { return (struct in_addr) { htonl(ipa_to_u32(a)) }; } -#ifdef IPV6 static inline struct in6_addr ipa_to_in6(ip_addr a) { return (struct in6_addr) { .s6_addr32 = { htonl(_I0(a)), htonl(_I1(a)), htonl(_I2(a)), htonl(_I3(a)) } }; } -#else -/* Temporary dummy */ -static inline struct in6_addr ipa_to_in6(ip_addr a UNUSED) -{ return (struct in6_addr) { .s6_addr32 = { 0, 0, 0, 0 } }; } -#endif void sockaddr_fill(sockaddr *sa, int af, ip_addr a, struct iface *ifa, uint port); int sockaddr_read(sockaddr *sa, int af, ip_addr *a, struct iface **ifa, uint *port); @@ -95,9 +91,9 @@ int sockaddr_read(sockaddr *sa, int af, ip_addr *a, struct iface **ifa, uint *po #define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) + strlen ((ptr)->sun_path)) #endif -volatile int async_config_flag; -volatile int async_dump_flag; -volatile int async_shutdown_flag; +extern volatile int async_config_flag; +extern volatile int async_dump_flag; +extern volatile int async_shutdown_flag; void io_init(void); void io_loop(void); @@ -106,7 +102,6 @@ int sk_open_unix(struct birdsock *s, char *name); void *tracked_fopen(struct pool *, char *name, char *mode); void test_old_bird(char *path); - /* krt.c bits */ void krt_io_init(void); diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 00000000..2cee9234 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,3 @@ +src := birdtest.c bt-utils.c +obj := $(src-o-files) +tests_objs := $(tests_objs) $(src-o-files) diff --git a/test/birdtest.c b/test/birdtest.c new file mode 100644 index 00000000..a4312e9b --- /dev/null +++ b/test/birdtest.c @@ -0,0 +1,502 @@ +/* + * BIRD -- Unit Test Framework (BIRD Test) + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <unistd.h> + +#include <sys/ioctl.h> +#include <sys/resource.h> +#include <sys/wait.h> + +#include "test/birdtest.h" +#include "lib/string.h" + +#ifdef HAVE_EXECINFO_H +#include <execinfo.h> +#endif + +#define BACKTRACE_MAX_LINES 100 + +#define sprintf_concat(s, format, ...) \ + snprintf(s + strlen(s), sizeof(s) - strlen(s), format, ##__VA_ARGS__) + +static const char *request; +static int list_tests; +static int do_core; +static int no_fork; +static int no_timeout; +static int is_terminal; /* Whether stdout is a live terminal or pipe redirect */ + +uint bt_verbose; +const char *bt_filename; +const char *bt_test_id; + +int bt_result; /* Overall program run result */ +int bt_suite_result; /* One suit result */ +char bt_out_fmt_buf[1024]; /* Temporary memory buffer for output of testing function */ + +long int +bt_random(void) +{ + /* Seeded in bt_init() */ + long int rand_low, rand_high; + + rand_low = random(); + rand_high = random(); + return (rand_low & 0xffff) | ((rand_high & 0xffff) << 16); +} + +void +bt_init(int argc, char *argv[]) +{ + int c; + + srandom(BT_RANDOM_SEED); + + bt_verbose = 0; + bt_filename = argv[0]; + bt_result = 1; + bt_test_id = NULL; + is_terminal = isatty(fileno(stdout)); + + while ((c = getopt(argc, argv, "lcftv")) >= 0) + switch (c) + { + case 'l': + list_tests = 1; + break; + + case 'c': + do_core = 1; + break; + + case 'f': + no_fork = 1; + break; + + case 't': + no_timeout = 1; + break; + + case 'v': + bt_verbose++; + break; + + default: + goto usage; + } + + /* Optional requested test_id */ + if ((optind + 1) == argc) + request = argv[optind++]; + + if (optind != argc) + goto usage; + + if (do_core) + { + struct rlimit rl = {RLIM_INFINITY, RLIM_INFINITY}; + int rv = setrlimit(RLIMIT_CORE, &rl); + bt_syscall(rv < 0, "setrlimit RLIMIT_CORE"); + } + + return; + + usage: + printf("Usage: %s [-l] [-c] [-f] [-t] [-vvv] [<test_suit_name>]\n", argv[0]); + printf("Options: \n"); + printf(" -l List all test suite names and descriptions \n"); + printf(" -c Force unlimit core dumps (needs root privileges) \n"); + printf(" -f No forking \n"); + printf(" -t No timeout limit \n"); + printf(" -v More verbosity, maximum is 3 -vvv \n"); + exit(3); +} + +static void +bt_dump_backtrace(void) +{ +#ifdef HAVE_EXECINFO_H + void *buf[BACKTRACE_MAX_LINES]; + char **pp_backtrace; + int lines, j; + + if (!bt_verbose) + return; + + lines = backtrace(buf, BACKTRACE_MAX_LINES); + bt_log("backtrace() returned %d addresses", lines); + + pp_backtrace = backtrace_symbols(buf, lines); + if (pp_backtrace == NULL) + { + perror("backtrace_symbols"); + exit(EXIT_FAILURE); + } + + for (j = 0; j < lines; j++) + bt_log("%s", pp_backtrace[j]); + + free(pp_backtrace); +#endif /* HAVE_EXECINFO_H */ +} + +static +int bt_run_test_fn(int (*fn)(const void *), const void *fn_arg, int timeout) +{ + int result; + alarm(timeout); + + if (fn_arg) + result = fn(fn_arg); + else + result = ((int (*)(void))fn)(); + + if (!bt_suite_result) + result = 0; + + return result; +} + +static uint +get_num_terminal_cols(void) +{ + struct winsize w = {}; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + uint cols = w.ws_col; + return (cols > 0 ? cols : 80); +} + +/** + * bt_log_result - pretty print of test result + * @result: 1 or 0 + * @fmt: a description message (could be long, over more lines) + * @argptr: variable argument list + * + * This function is used for pretty printing of test results on all verbose + * levels. + */ +static void +bt_log_result(int result, const char *fmt, va_list argptr) +{ + static char msg_buf[BT_BUFFER_SIZE]; + char *pos; + + snprintf(msg_buf, sizeof(msg_buf), "%s%s%s%s", + bt_filename, + bt_test_id ? ": " : "", + bt_test_id ? bt_test_id : "", + (fmt && strlen(fmt) > 0) ? ": " : ""); + pos = msg_buf + strlen(msg_buf); + + vsnprintf(pos, sizeof(msg_buf) - (pos - msg_buf), fmt, argptr); + + int chrs = 0; + for (uint i = 0; i < strlen(msg_buf); i += get_num_terminal_cols()) + { + if (i) + printf("\n"); + char *stop = msg_buf + i + get_num_terminal_cols(); + char backup = *stop; + *stop = 0; + chrs = printf("%s", msg_buf + i); + *stop = backup; + } + + int offset = get_num_terminal_cols() - chrs - BT_PROMPT_OK_FAIL_STRLEN; + if (offset < 0) + { + printf("\n"); + offset = get_num_terminal_cols() - BT_PROMPT_OK_FAIL_STRLEN; + } + + for (int i = 0; i < offset; i++) + putchar(' '); + + const char *result_str = is_terminal ? BT_PROMPT_OK : BT_PROMPT_OK_NO_COLOR; + if (!result) + result_str = is_terminal ? BT_PROMPT_FAIL : BT_PROMPT_FAIL_NO_COLOR; + + printf("%s\n", result_str); +} + +/** + * bt_log_overall_result - pretty print of suite case result + * @result: 1 or 0 + * @fmt: a description message (could be long, over more lines) + * ...: variable argument list + * + * This function is used for pretty printing of test suite case result. + */ +static void +bt_log_overall_result(int result, const char *fmt, ...) +{ + va_list argptr; + va_start(argptr, fmt); + bt_log_result(result, fmt, argptr); + va_end(argptr); +} + +/** + * bt_log_suite_result - pretty print of suite case result + * @result: 1 or 0 + * @fmt: a description message (could be long, over more lines) + * ...: variable argument list + * + * This function is used for pretty printing of test suite case result. + */ +void +bt_log_suite_result(int result, const char *fmt, ...) +{ + if(bt_verbose >= BT_VERBOSE_SUITE || !result) + { + va_list argptr; + va_start(argptr, fmt); + bt_log_result(result, fmt, argptr); + va_end(argptr); + } +} + +/** + * bt_log_suite_case_result - pretty print of suite result + * @result: 1 or 0 + * @fmt: a description message (could be long, over more lines) + * ...: variable argument list + * + * This function is used for pretty printing of test suite result. + */ +void +bt_log_suite_case_result(int result, const char *fmt, ...) +{ + if(bt_verbose >= BT_VERBOSE_SUITE_CASE) + { + va_list argptr; + va_start(argptr, fmt); + bt_log_result(result, fmt, argptr); + va_end(argptr); + } +} + +int +bt_test_suite_base(int (*fn)(const void *), const char *id, const void *fn_arg, int forked, int timeout, const char *dsc, ...) +{ + if (list_tests) + { + printf("%28s - ", id); + va_list args; + va_start(args, dsc); + vprintf(dsc, args); + va_end(args); + printf("\n"); + return 1; + } + + if (no_fork) + forked = 0; + + if (no_timeout) + timeout = 0; + + if (request && strcmp(id, request)) + return 1; + + bt_suite_result = 1; + bt_test_id = id; + + if (bt_verbose >= BT_VERBOSE_ABSOLUTELY_ALL) + bt_log("Starting"); + + if (!forked) + { + bt_suite_result = bt_run_test_fn(fn, fn_arg, timeout); + } + else + { + pid_t pid = fork(); + bt_syscall(pid < 0, "fork"); + + if (pid == 0) + { + /* child of fork */ + _exit(bt_run_test_fn(fn, fn_arg, timeout)); + } + + int s; + int rv = waitpid(pid, &s, 0); + bt_syscall(rv < 0, "waitpid"); + + if (WIFEXITED(s)) + { + /* Normal exit */ + bt_suite_result = WEXITSTATUS(s); + } + else if (WIFSIGNALED(s)) + { + /* Stopped by signal */ + bt_suite_result = 0; + + int sn = WTERMSIG(s); + if (sn == SIGALRM) + { + bt_log("Timeout expired"); + } + else if (sn == SIGSEGV) + { + bt_log("Segmentation fault"); + bt_dump_backtrace(); + } + else if (sn != SIGABRT) + bt_log("Signal %d received", sn); + } + + if (WCOREDUMP(s) && bt_verbose) + bt_log("Core dumped"); + } + + if (!bt_suite_result) + bt_result = 0; + + bt_log_suite_result(bt_suite_result, NULL); + bt_test_id = NULL; + + return bt_suite_result; +} + +int +bt_exit_value(void) +{ + if (!list_tests || (list_tests && !bt_result)) + bt_log_overall_result(bt_result, ""); + return bt_result ? EXIT_SUCCESS : EXIT_FAILURE; +} + +/** + * bt_assert_batch__ - test a batch of inputs/outputs tests + * @opts: includes all necessary data + * + * Should be called using macro bt_assert_batch(). + * Returns 1 or 0. + */ +int +bt_assert_batch__(struct bt_batch *opts) +{ + int i; + for (i = 0; i < opts->ndata; i++) + { + int bt_suit_case_result = opts->test_fn(opts->out_buf, opts->data[i].in, opts->data[i].out); + + if (bt_suit_case_result == 0) + bt_suite_result = 0; + + char b[BT_BUFFER_SIZE]; + snprintf(b, sizeof(b), "%s(", opts->test_fn_name); + + opts->in_fmt(b+strlen(b), sizeof(b)-strlen(b), opts->data[i].in); + sprintf_concat(b, ") gives "); + opts->out_fmt(b+strlen(b), sizeof(b)-strlen(b), opts->out_buf); + + if (bt_suit_case_result == 0) + { + sprintf_concat(b, ", but expecting is "); + opts->out_fmt(b+strlen(b), sizeof(b)-strlen(b), opts->data[i].out); + } + + bt_log_suite_case_result(bt_suit_case_result, "%s", b); + } + + return bt_suite_result; +} + +/** + * bt_fmt_str - formating string into output buffer + * @buf: buffer for write + * @size: empty size in @buf + * @data: null-byte terminated string + * + * This function can be used with bt_assert_batch() function. + * Input @data should be const char * string. + */ +void +bt_fmt_str(char *buf, size_t size, const void *data) +{ + const byte *s = data; + + snprintf(buf, size, "\""); + while (*s) + { + snprintf(buf+strlen(buf), size-strlen(buf), bt_is_char(*s) ? "%c" : "\\%03u", *s); + s++; + } + snprintf(buf+strlen(buf), size-strlen(buf), "\""); +} + +/** + * bt_fmt_unsigned - formating unsigned int into output buffer + * @buf: buffer for write + * @size: empty size in @buf + * @data: unsigned number + * + * This function can be used with bt_assert_batch() function. + */ +void +bt_fmt_unsigned(char *buf, size_t size, const void *data) +{ + const uint *n = data; + snprintf(buf, size, "0x%x (%u)", *n, *n); +} + +/** + * bt_fmt_ipa - formating ip_addr into output buffer + * @buf: buffer for write + * @size: empty size in @buf + * @data: should be struct ip_addr * + * + * This function can be used with bt_assert_batch() function. + */ +void +bt_fmt_ipa(char *buf, size_t size, const void *data) +{ + const ip_addr *ip = data; + bsnprintf(buf, size, "%I", *ip); +} + +int +bt_is_char(byte c) +{ + return (c >= (byte) 32 && c <= (byte) 126); +} + +/* + * Mock-ups of all necessary public functions in main.c + */ + +char *bird_name; +void async_config(void) {} +void async_dump(void) {} +void async_shutdown(void) {} +void cmd_check_config(char *name UNUSED) {} +void cmd_reconfig(char *name UNUSED, int type UNUSED, int timeout UNUSED) {} +void cmd_reconfig_confirm(void) {} +void cmd_reconfig_undo(void) {} +void cmd_shutdown(void) {} +void cmd_reconfig_undo_notify(void) {} + +#include "nest/bird.h" +#include "lib/net.h" +#include "conf/conf.h" +void sysdep_preconfig(struct config *c UNUSED) {} +int sysdep_commit(struct config *new UNUSED, struct config *old UNUSED) { return 0; } +void sysdep_shutdown_done(void) {} + +#include "nest/cli.h" +int cli_get_command(cli *c UNUSED) { return 0; } +void cli_write_trigger(cli *c UNUSED) {} +cli *cmd_reconfig_stored_cli; diff --git a/test/birdtest.h b/test/birdtest.h new file mode 100644 index 00000000..4443bfc1 --- /dev/null +++ b/test/birdtest.h @@ -0,0 +1,179 @@ +/* + * BIRD -- Unit Test Framework (BIRD Test) + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef _BIRDTEST_H_ +#define _BIRDTEST_H_ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> + +#include "nest/bird.h" + + +extern int bt_result; +extern int bt_suite_result; +extern char bt_out_fmt_buf[1024]; + +extern uint bt_verbose; +#define BT_VERBOSE_NO 0 +#define BT_VERBOSE_SUITE 1 +#define BT_VERBOSE_SUITE_CASE 2 +#define BT_VERBOSE_ABSOLUTELY_ALL 3 + +extern const char *bt_filename; +extern const char *bt_test_id; + +void bt_init(int argc, char *argv[]); +int bt_exit_value(void); +int bt_test_suite_base(int (*test_fn)(const void *), const char *test_id, const void *test_fn_argument, int forked, int timeout, const char *dsc, ...); +long int bt_random(void); + +void bt_log_suite_result(int result, const char *fmt, ...); +void bt_log_suite_case_result(int result, const char *fmt, ...); + +#define BT_TIMEOUT 5 /* Default timeout in seconds */ +#define BT_FORKING 1 /* Forking is enabled in default */ + +#define BT_RANDOM_SEED 982451653 + +#define BT_BUFFER_SIZE 10000 + +#define BT_PROMPT_GREEN "\e[1;32m" +#define BT_PROMPT_RED "\e[1;31m" +#define BT_PROMPT_NORMAL "\e[0m" +#define BT_PROMPT_OK " [" BT_PROMPT_GREEN " OK " BT_PROMPT_NORMAL "] " +#define BT_PROMPT_OK_NO_COLOR " [" " OK " "] " +#define BT_PROMPT_FAIL " [" BT_PROMPT_RED "FAIL" BT_PROMPT_NORMAL "] " +#define BT_PROMPT_FAIL_NO_COLOR " [" "FAIL" "] " +#define BT_PROMPT_OK_FAIL_STRLEN 8 /* strlen ' [FAIL] ' */ + +#define bt_test_suite(fn, dsc, ...) \ + bt_test_suite_extra(fn, BT_FORKING, BT_TIMEOUT, dsc, ##__VA_ARGS__) + +#define bt_test_suite_extra(fn, f, t, dsc, ...) \ + bt_test_suite_base((int (*)(const void *))fn, #fn, NULL, f, t, dsc, ##__VA_ARGS__) + +#define bt_test_suite_arg(fn, arg, dsc, ...) \ + bt_test_suite_arg_extra(fn, arg, BT_FORKING, BT_TIMEOUT, dsc, ##__VA_ARGS__) + +#define bt_test_suite_arg_extra(fn, arg, f, t, dsc, ...) \ + bt_test_suite_base(fn, #fn, arg, f, t, dsc, ##__VA_ARGS__) + +#define bt_abort() \ + bt_abort_msg("Aborted at %s:%d", __FILE__, __LINE__) + +#define bt_abort_msg(format, ...) \ + do \ + { \ + bt_log(format, ##__VA_ARGS__); \ + abort(); \ + } while (0) + +#define bt_log(format, ...) \ + do \ + { \ + if (bt_test_id) \ + printf("%s: %s: " format "\n", bt_filename, bt_test_id, ##__VA_ARGS__); \ + else \ + printf("%s: " format "\n", bt_filename, ##__VA_ARGS__); \ + } while(0) + +#define bt_debug(format, ...) \ + do \ + { \ + if (bt_verbose >= BT_VERBOSE_ABSOLUTELY_ALL) \ + printf(format, ##__VA_ARGS__); \ + } while (0) + +#define bt_assert(test) \ + bt_assert_msg(test, "Assertion (%s) at %s:%d", #test, __FILE__, __LINE__) + +#define bt_assert_msg(test, format, ...) \ + do \ + { \ + int bt_suit_case_result = 1; \ + if ((test) == 0) \ + { \ + bt_result = 0; \ + bt_suite_result = 0; \ + bt_suit_case_result = 0; \ + } \ + bt_log_suite_case_result(bt_suit_case_result, format, ##__VA_ARGS__); \ + } while (0) + +#define bt_syscall(test, format, ...) \ + do \ + { \ + if (test) \ + { \ + bt_log(format ": %s", ##__VA_ARGS__, strerror(errno)); \ + exit(3); \ + } \ + } while (0) + +#define bt_sprintf_concat(s, format, ...) \ + snprintf(s + strlen(s), sizeof(s) - strlen(s), format, ##__VA_ARGS__) + +struct bt_pair { + const void *in; + const void *out; +}; + +/* Data structure used by bt_assert_batch() function */ +struct bt_batch { + /* in_fmt / out_fmt - formating data + * @buf: buffer for write stringified @data + * @size: empty size in @buf + * @data: data for stringify + * + * There are some build-in functions, see bt_fmt_* functions */ + void (*in_fmt)(char *buf, size_t size, const void *data); + void (*out_fmt)(char *buf, size_t size, const void *data); + + /* Temporary output buffer */ + void *out_buf; + + /* test_fn - testing function + * @out: output data from tested function + * @in: data for input + * @expected_out: expected data from tested function + * + * Input arguments should not be stringified using in_fmt() or out_fmt() + * function already. This function should return only 0 or 1 */ + int (*test_fn)(void *out, const void *in, const void *expected_out); + + /* Name of testing function @test_fn */ + const char *test_fn_name; + + /* Number of items in data */ + int ndata; + + /* Array of input and expected output pairs */ + struct bt_pair *data; +}; + +void bt_fmt_str(char *buf, size_t size, const void *data); +void bt_fmt_unsigned(char *buf, size_t size, const void *data); +void bt_fmt_ipa(char *buf, size_t size, const void *data); +int bt_assert_batch__(struct bt_batch *opts); +int bt_is_char(byte c); + +#define bt_assert_batch(data__, fn__, in_fmt__, out_fmt__) \ + bt_assert_batch__(& (struct bt_batch) { \ + .data = data__, \ + .ndata = ARRAY_SIZE(data__), \ + .test_fn = fn__, \ + .test_fn_name = #fn__, \ + .in_fmt = in_fmt__, \ + .out_fmt = out_fmt__, \ + .out_buf = bt_out_fmt_buf, /* Global memory for this usage */ \ + }) + +#endif /* _BIRDTEST_H_ */ diff --git a/test/bt-utils.c b/test/bt-utils.c new file mode 100644 index 00000000..aaeb77b8 --- /dev/null +++ b/test/bt-utils.c @@ -0,0 +1,223 @@ +/* + * BIRD Test -- Utils for testing parsing configuration file + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> + +#include "test/birdtest.h" +#include "test/bt-utils.h" + +#include "nest/bird.h" +#include "nest/route.h" +#include "nest/protocol.h" + +#include "sysdep/unix/unix.h" +#include "sysdep/unix/krt.h" + +#include "nest/iface.h" +#include "nest/locks.h" + +#include "filter/filter.h" + +#define BETWEEN(a, b, c) (((a) >= (b)) && ((a) <= (c))) + +static const byte *bt_config_parse_pos; +static uint bt_config_parse_remain_len; + +/* This is cf_read_hook for hard-coded text configuration */ +static int +cf_static_read(byte *dest, uint max_len, int fd UNUSED) +{ + if (max_len > bt_config_parse_remain_len) + max_len = bt_config_parse_remain_len; + memcpy(dest, bt_config_parse_pos, max_len); + bt_config_parse_pos += max_len; + bt_config_parse_remain_len -= max_len; + return max_len; +} + +/* This is cf_read_hook for reading configuration files, + * function is copied from main.c, cf_read() */ +static int +cf_file_read(byte *dest, uint max_len, int fd) +{ + int l = read(fd, dest, max_len); + if (l < 0) + cf_error("Read error"); + return l; +} + +void +bt_bird_init(void) +{ + if(bt_verbose) + log_init_debug(""); + log_switch(bt_verbose != 0, NULL, NULL); + + resource_init(); + olock_init(); + io_init(); + rt_init(); + if_init(); + config_init(); + + protos_build(); + proto_build(&proto_unix_kernel); + proto_build(&proto_unix_iface); +} + +void bt_bird_cleanup(void) +{ + for (int i = 0; i < EAP_MAX; i++) + attr_class_to_protocol[i] = NULL; + + config = new_config = NULL; +} + +static char * +bt_load_file(const char *filename, int quiet) +{ + FILE *f = fopen(filename, "rb"); + if (!quiet) + bt_assert_msg(f != NULL, "Open %s", filename); + + if (f == NULL) + return NULL; + + fseek(f, 0, SEEK_END); + long file_size_ = ftell(f); + fseek(f, 0, SEEK_SET); + + if (file_size_ < 0) + return NULL; + + size_t file_size = file_size_; + size_t read_size = 0; + + char *file_body = mb_allocz(&root_pool, file_size+1); + + /* XXX: copied from cf-lex.c */ + errno=0; + while ((read_size += fread(file_body+read_size, 1, file_size-read_size, f)) != file_size && ferror(f)) + { + bt_debug("iteration \n"); + if(errno != EINTR) + { + bt_abort_msg("errno: %d", errno); + break; + } + errno=0; + clearerr(f); + } + fclose(f); + + if (!quiet) + bt_assert_msg(read_size == file_size, "Read %s", filename); + + return file_body; +} + +static void +bt_show_cfg_error(const struct config *cfg) +{ + int lino = 0; + int lino_delta = 5; + int lino_err = cfg->err_lino; + + const char *str = bt_load_file(cfg->err_file_name, 1); + + while (str && *str) + { + lino++; + if (BETWEEN(lino, lino_err - lino_delta, lino_err + lino_delta)) + bt_debug("%4u%s", lino, (lino_err == lino ? " >> " : " ")); + do + { + if (BETWEEN(lino, lino_err - lino_delta, lino_err + lino_delta)) + bt_debug("%c", *str); + } while (*str && *(str++) != '\n'); + } + bt_debug("\n"); +} + +static struct config * +bt_config_parse__(struct config *cfg) +{ + bt_assert_msg(config_parse(cfg) == 1, "Parse %s", cfg->file_name); + + if (cfg->err_msg) + { + bt_debug("Parse error %s, line %d: %s\n", cfg->err_file_name, cfg->err_lino, cfg->err_msg); + bt_show_cfg_error(cfg); + return NULL; + } + + config_commit(cfg, RECONFIG_HARD, 0); + new_config = cfg; + + return cfg; +} + +struct config * +bt_config_parse(const char *cfg_str) +{ + struct config *cfg = config_alloc("configuration"); + + bt_config_parse_pos = cfg_str; + bt_config_parse_remain_len = strlen(cfg_str); + cf_read_hook = cf_static_read; + + return bt_config_parse__(cfg); +} + +struct config * +bt_config_file_parse(const char *filepath) +{ + struct config *cfg = config_alloc(filepath); + + cfg->file_fd = open(filepath, O_RDONLY); + bt_assert_msg(cfg->file_fd > 0, "Open %s", filepath); + if (cfg->file_fd < 0) + return NULL; + + cf_read_hook = cf_file_read; + + return bt_config_parse__(cfg); +} + +/* + * Returns @base raised to the power of @power. + */ +uint +bt_naive_pow(uint base, uint power) +{ + uint result = 1; + uint i; + for (i = 0; i < power; i++) + result *= base; + return result; +} + +/** + * bytes_to_hex - transform data into hexadecimal representation + * @buf: preallocated string buffer + * @in_data: data for transformation + * @size: the length of @in_data + * + * This function transforms @in_data of length @size into hexadecimal + * representation and writes it into @buf. + */ +void +bt_bytes_to_hex(char *buf, const byte *in_data, size_t size) +{ + size_t i; + for(i = 0; i < size; i++) + sprintf(buf + i*2, "%02x", in_data[i]); +} + diff --git a/test/bt-utils.h b/test/bt-utils.h new file mode 100644 index 00000000..13d267cc --- /dev/null +++ b/test/bt-utils.h @@ -0,0 +1,35 @@ +/* + * BIRD Test -- Utils for testing parsing configuration file + * + * (c) 2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef _BIRDTEST_UTILS_H_ +#define _BIRDTEST_UTILS_H_ + +#include "sysdep/config.h" + +#define PRIip4 "%d.%d.%d.%d" +#define ARGip4(x) (_I(x) >> 24) & 0xff, (_I(x) >> 16) & 0xff, (_I(x) >> 8) & 0xff, _I(x) & 0xff + +#define PRIip6 "%04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X" +#define ARGip6_HIGH(x,i) (((x).addr[(i)] >> 16) & 0xffff) +#define ARGip6_LOW(x,i) ((x).addr[(i)] & 0xffff) +#define ARGip6_BOTH(x,i) ARGip6_HIGH(x,i), ARGip6_LOW(x,i) +#define ARGip6(x) ARGip6_BOTH((x), 0), ARGip6_BOTH((x), 1), ARGip6_BOTH((x), 2), ARGip6_BOTH((x), 3) + +#define BT_CONFIG_PARSE_ROUTER_ID "router id 1.1.1.1; \n" +#define BT_CONFIG_PARSE_STATIC_PROTO "protocol static { ipv4; } \n" +#define BT_CONFIG_SIMPLE BT_CONFIG_PARSE_ROUTER_ID BT_CONFIG_PARSE_STATIC_PROTO + +uint bt_naive_pow(uint base, uint power); +void bt_bytes_to_hex(char *buf, const byte *in_data, size_t size); + +void bt_bird_init(void); +void bt_bird_cleanup(void); +struct config *bt_config_parse(const char *cfg); +struct config *bt_config_file_parse(const char *filepath); + +#endif /* _BIRDTEST_UTILS_H_ */ diff --git a/tools/Makefile-top.in b/tools/Makefile-top.in deleted file mode 100644 index fa02b5e6..00000000 --- a/tools/Makefile-top.in +++ /dev/null @@ -1,20 +0,0 @@ -# Makefile for in place build of BIRD -# (c) 1999--2000 Martin Mares <mj@ucw.cz> - -objdir=@objdir@ - -all depend tags install install-docs: - $(MAKE) -C $(objdir) $@ - -docs userdocs progdocs: - $(MAKE) -C doc $@ - -clean: - $(MAKE) -C $(objdir) clean - find . -name "*~" -or -name "*.[oa]" -or -name "\#*\#" -or -name TAGS -or -name core -or -name depend -or -name ".#*" | xargs rm -f - -distclean: clean - $(MAKE) -C doc distclean - rm -rf $(objdir) autom4te.cache - rm -f config.* configure sysdep/autoconf.h.in sysdep/paths.h Makefile - diff --git a/tools/Makefile.in b/tools/Makefile.in deleted file mode 100644 index 01bb7a7c..00000000 --- a/tools/Makefile.in +++ /dev/null @@ -1,97 +0,0 @@ -# Makefile for the BIRD Internet Routing Daemon -# (c) 1999--2000 Martin Mares <mj@ucw.cz> - -include Rules - -.PHONY: all daemon birdc birdcl subdir depend clean distclean tags docs userdocs progdocs - -all: sysdep/paths.h .dep-stamp subdir daemon birdcl @CLIENT@ - -daemon: $(exedir)/bird - -birdc: $(exedir)/birdc - -birdcl: $(exedir)/birdcl - -bird-dep := $(addsuffix /all.o, $(static-dirs)) conf/all.o lib/birdlib.a - -$(bird-dep): sysdep/paths.h .dep-stamp subdir - -birdc-dep := client/birdc.o client/all.o lib/birdlib.a - -$(birdc-dep): sysdep/paths.h .dep-stamp subdir - -birdcl-dep := client/birdcl.o client/all.o lib/birdlib.a - -$(birdcl-dep): sysdep/paths.h .dep-stamp subdir - - -export client := @CLIENT@ - -depend: sysdep/paths.h .dir-stamp - set -e ; for a in $(dynamic-dirs) ; do $(MAKE) -C $$a $@ ; done - set -e ; for a in $(static-dirs) $(client-dirs) ; do $(MAKE) -C $$a -f $(srcdir_abs)/$$a/Makefile $@ ; done - -subdir: sysdep/paths.h .dir-stamp .dep-stamp - set -e ; for a in $(dynamic-dirs) ; do $(MAKE) -C $$a $@ ; done - set -e ; for a in $(static-dirs) $(client-dirs) ; do $(MAKE) -C $$a -f $(srcdir_abs)/$$a/Makefile $@ ; done - -$(exedir)/bird: $(bird-dep) - @echo LD $(LDFLAGS) -o $@ $^ $(LIBS) - @$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) - -$(exedir)/birdc: $(birdc-dep) - @echo LD $(LDFLAGS) -o $@ $^ $(LIBS) $(CLIENT_LIBS) - @$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) $(CLIENT_LIBS) - -$(exedir)/birdcl: $(birdcl-dep) - @echo LD $(LDFLAGS) -o $@ $^ $(LIBS) - @$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) - -.dir-stamp: sysdep/paths.h - mkdir -p $(static-dirs) $(client-dirs) $(doc-dirs) - touch .dir-stamp - -.dep-stamp: - $(MAKE) depend - touch .dep-stamp - -docs: userdocs progdocs - -userdocs progdocs: .dir-stamp - $(MAKE) -C doc -f $(srcdir_abs)/doc/Makefile $@ - -sysdep/paths.h: - echo >sysdep/paths.h "/* Generated by Makefile, don't edit manually! */" - echo >>sysdep/paths.h "#define PATH_CONFIG_FILE \"@CONFIG_FILE@\"" - echo >>sysdep/paths.h "#define PATH_CONTROL_SOCKET \"@CONTROL_SOCKET@\"" - if test -n "@iproutedir@" ; then echo >>sysdep/paths.h "#define PATH_IPROUTE_DIR \"@iproutedir@\"" ; fi - -tags: - cd $(srcdir) ; etags -lc `find $(static-dirs) $(addprefix $(objdir)/,$(dynamic-dirs)) $(client-dirs) -name *.[chY]` - -install: all - $(INSTALL) -d $(DESTDIR)/$(sbindir) $(DESTDIR)/$(sysconfdir) $(DESTDIR)/@runtimedir@ - $(INSTALL_PROGRAM) $(exedir)/bird $(DESTDIR)/$(sbindir)/bird@SUFFIX@ - $(INSTALL_PROGRAM) $(exedir)/birdcl $(DESTDIR)/$(sbindir)/birdcl@SUFFIX@ - if test -n "@CLIENT@" ; then \ - $(INSTALL_PROGRAM) $(exedir)/birdc $(DESTDIR)/$(sbindir)/birdc@SUFFIX@ ; \ - fi - if ! test -f $(DESTDIR)/@CONFIG_FILE@ ; then \ - $(INSTALL_DATA) $(srcdir)/doc/bird.conf.example $(DESTDIR)/@CONFIG_FILE@ ; \ - else \ - echo "Not overwriting old bird@SUFFIX@.conf" ; \ - fi - -install-docs: - $(INSTALL) -d $(DESTDIR)/$(docdir) - $(INSTALL_DATA) $(srcdir)/doc/{bird,prog}{,-*}.html $(DESTDIR)/$(docdir)/ - -clean: - find . -name "*.[oa]" -o -name core -o -name depend -o -name "*.html" | xargs rm -f - rm -f conf/cf-lex.c conf/cf-parse.* conf/commands.h conf/keywords.h - rm -f $(exedir)/bird $(exedir)/birdcl $(exedir)/birdc $(exedir)/bird.ctl $(exedir)/bird6.ctl .dep-stamp - -distclean: clean - rm -f config.* configure sysdep/autoconf.h sysdep/paths.h Makefile Rules - rm -rf .dir-stamp $(clean-dirs) diff --git a/tools/Rules.in b/tools/Rules.in deleted file mode 100644 index f00c85d1..00000000 --- a/tools/Rules.in +++ /dev/null @@ -1,91 +0,0 @@ -# Makefile fragments for the BIRD Internet Routing Daemon -# (c) 1999--2000 Martin Mares <mj@ucw.cz> - -srcdir=@srcdir_rel_mf@ -srcdir_abs := $(shell cd $(srcdir) ; pwd) -objdir=@objdir@ -exedir=@exedir@ - -protocols=@protocols@ -static-dirs := nest filter $(addprefix proto/,$(protocols)) -static-dir-paths := $(addprefix $(srcdir)/,$(static-dirs)) -dynamic-dirs := lib conf -dynamic-dir-paths := $(dynamic-dirs) -client-dirs := client -client-dir-paths := $(client-dirs) -doc-dirs := doc -doc-dir-paths := $(doc-dirs) - -all-dirs:=$(static-dirs) $(dynamic-dirs) $(client-dirs) $(doc-dirs) -clean-dirs:=$(all-dirs) proto sysdep - -CPPFLAGS=-I$(root-rel) -I$(srcdir) @CPPFLAGS@ -CFLAGS=$(CPPFLAGS) @CFLAGS@ -LDFLAGS=@LDFLAGS@ -LIBS=@LIBS@ -CLIENT_LIBS=@CLIENT_LIBS@ -CC=@CC@ -M4=@M4@ -BISON=@BISON@ -FLEX=@FLEX@ -RANLIB=@RANLIB@ -INSTALL=@INSTALL@ -INSTALL_PROGRAM=@INSTALL_PROGRAM@ -INSTALL_DATA=@INSTALL_DATA@ - -prefix=@prefix@ -exec_prefix=@exec_prefix@ -bindir=@bindir@ -sbindir=@sbindir@ -sysconfdir=@sysconfdir@ -localstatedir=@localstatedir@ -docdir=@prefix@/doc - -ifdef source - -objs := $(subst .c,.o,$(source)) - -ifdef dir-name -src-path := $(srcdir)/$(dir-name)/ -endif - -all: - cd $(root-rel) && make - -ifdef lib-dest - -subdir: $(lib-dest) - -$(lib-dest): $(objs) - rm -f $@ - ar rcs $@ $^ - $(RANLIB) $@ - -else - -subdir: all.o - -all.o: $(objs) -# $(LD) -r -o $@ $^ -# Changed to $(CC) because $(LD) has problems with crosscompiling - @echo LD -r -o $@ $^ - @$(CC) -nostdlib -r -o $@ $^ - -endif - -%.o: $(src-path)%.c - @echo CC -o $@ -c $< - @$(CC) $(CFLAGS) -o $@ -c $< - -ifndef source-dep -source-dep := $(source) -endif - -depend: - $(CC) $(CPPFLAGS) -MM $(addprefix $(src-path),$(source-dep)) >depend - -ifneq ($(wildcard depend),) -include depend -endif - -endif diff --git a/tools/gendist b/tools/gendist index d9b34941..09d5d055 100755 --- a/tools/gendist +++ b/tools/gendist @@ -8,10 +8,10 @@ set -e AC=`if [ -x /usr/bin/autoconf2.50 ] ; then echo autoconf2.50 ; else echo autoconf ; fi` $AC ./configure +make docs make distclean $AC rm -rf autom4te*cache -( cd doc ; make docs ; make clean ) VERSION=`sed <sysdep/config.h '/BIRD_VERSION/!d;s/^.*"\(.*\)"$/\1/'` REL=bird-$VERSION DREL=bird-doc-$VERSION @@ -23,9 +23,10 @@ cp -a . $T/$REL echo Generating ChangeLog git log >$T/$REL/ChangeLog mv $T/$REL/doc/*.ps $T/$DREL/doc +mv $T/$REL/doc/*.pdf $T/$DREL/doc rm -f $T/$REL/bird.conf* rm -rf $T/$REL/.git/ -rm -rf `find $T/$REL -name CVS -o -name tmp` $T/$REL/{misc,rfc,doc/slides} +rm -rf `find $T/$REL -name CVS -o -name tmp` $T/$REL/{misc,rfc,doc/slides,doc/slt2001,doc/old,doc/*.out} ( cd $T ; tar czvvf $REL.tar.gz $REL ) ( cd $T ; tar czvvf $DREL.tar.gz $DREL ) rm -rf $T/$REL $T/$DREL diff --git a/tools/mergedirs b/tools/mergedirs deleted file mode 100755 index fb48c6c7..00000000 --- a/tools/mergedirs +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/sh - -if [ -z "$4" ] ; then - echo "Usage: mergedirs <src-dir> <src-dir-rel> <obj-dir> <list-of-dirs>" - exit 1 - fi -cpp=${CPP:-cc -E} -SRCDIR=$1 -shift -SRCREL=$1 -case $SRCDIR in - /*) ;; - *) SRCREL="../$SRCREL" ;; - esac -shift -OBJDIR=$1 -LIBDIR=$OBJDIR/lib -CONFDIR=$OBJDIR/conf -shift - -echo "Merging system-dependent modules ($@)" -MODULES=`for a in $@ ; do - cat $SRCDIR/sysdep/config.h $SRCDIR/$a/Modules | - $cpp -U unix -D MACROS_ONLY -I $OBJDIR - | - sed "/^[ ]*\$/d;/^#/d;s@\\(.*\\)@\\1 $a/\\1@" - done | - sort -k1,1 -u | - cut -d ' ' -f 2` -rm -rf $LIBDIR $CONFDIR -mkdir -p $LIBDIR $CONFDIR -for a in $MODULES ; do - b=`basename $a` - case $b in - *.h) ln -s $SRCREL/$a $LIBDIR/$b - ;; - *.c) OBJ=`echo $b | sed 's/\.c$/\.o/'` - OBJS="$OBJS $OBJ" - SRCS="$SRCS \\ - $b" - ln -s $SRCREL/$a $LIBDIR/$b - ;; - *.Y) CONFS="$CONFS\$(srcdir)/$a " - ln -s $SRCREL/$a $CONFDIR/$b - ;; - *) echo "$b: Unknown file type" - exit 1 - ;; - esac - done - -cat >$LIBDIR/Makefile <<EOF -source=$SRCS -lib-dest=birdlib.a -root-rel=../ - -include ../Rules -EOF - -sed <$SRCDIR/conf/Makefile >$CONFDIR/Makefile "s|@CONFS@|$CONFS|" -CONFS=`cd $SRCDIR ; ls conf/*.[chl]` -for a in $CONFS ; do - ln -s $SRCREL/$a $CONFDIR/ -done diff --git a/tools/progdoc b/tools/progdoc index ef44d3aa..fc4024bf 100755 --- a/tools/progdoc +++ b/tools/progdoc @@ -1,17 +1,18 @@ #!/usr/bin/perl $srcdir = $ARGV[0]; +$out = $ARGV[1]; -open(OUT, ">prog.sgml") || die "Cannot create output file"; -include("doc/prog-head.sgml"); -process(""); -include("doc/prog-foot.sgml"); +open(OUT, ">", $out) || die "Cannot create output file"; +process($srcdir); close OUT; +gen_deps(); exit 0; sub include { my $f = shift @_; - open(IN, "$srcdir/$f") || die "Unable to find $f"; + open(IN, "$f") || die "Unable to find $f"; + push(@deps, "$f"); while (<IN>) { print OUT; } @@ -21,7 +22,8 @@ sub include { sub process { my $dir = shift @_; print "$dir/Doc\n"; - open(IN, "$srcdir/$dir/Doc") || die "Unable to read $dir/Doc"; + open(IN, "$dir/Doc") || die "Unable to read $dir/Doc"; + push(@deps, "$dir/Doc"); my @docfile = <IN>; close IN; foreach $_ (@docfile) { @@ -36,7 +38,10 @@ sub process { print OUT "<chapt>$arg\n"; } elsif ($cmd eq "S") { print " $arg\n"; - open(DOC, "cd $srcdir/$dir ; $srcdir/doc/kernel-doc -bird $arg |") || die "Unable to start kernel-doc"; + my @files = map("$dir/$_", split(' ', $arg)); + my $fargs = join(' ', @files); + open(DOC, "$srcdir/doc/kernel-doc -bird $fargs |") || die "Unable to start kernel-doc"; + push(@deps, @files); while (<DOC>) { print OUT; } close DOC; } elsif ($cmd eq "D") { @@ -45,3 +50,17 @@ sub process { } else { die "Unknown command: $cmd"; } } } + +sub gen_deps { + open(DEP, ">", "$out.d"); + print DEP "$out:"; + foreach $f (@deps) { + print DEP " \\\n $f"; + } + print DEP "\n\n"; + + foreach $f (@deps) { + print DEP "$f:\n\n"; + } + close DEP; +} |