summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--usr.sbin/bgpctl/Makefile4
-rw-r--r--usr.sbin/bgpctl/bgpctl.c11
-rw-r--r--usr.sbin/bgpctl/irr_asset.c234
-rw-r--r--usr.sbin/bgpctl/irr_output.c183
-rw-r--r--usr.sbin/bgpctl/irr_parser.c395
-rw-r--r--usr.sbin/bgpctl/irr_prefix.c102
-rw-r--r--usr.sbin/bgpctl/irrfilter.c52
-rw-r--r--usr.sbin/bgpctl/irrfilter.h96
-rw-r--r--usr.sbin/bgpctl/parser.c16
-rw-r--r--usr.sbin/bgpctl/parser.h5
-rw-r--r--usr.sbin/bgpctl/whois.c149
11 files changed, 1241 insertions, 6 deletions
diff --git a/usr.sbin/bgpctl/Makefile b/usr.sbin/bgpctl/Makefile
index 0ed3be67547..71ace1ba13f 100644
--- a/usr.sbin/bgpctl/Makefile
+++ b/usr.sbin/bgpctl/Makefile
@@ -1,9 +1,11 @@
-# $OpenBSD: Makefile,v 1.8 2006/11/26 11:31:12 deraadt Exp $
+# $OpenBSD: Makefile,v 1.9 2007/03/03 11:45:30 henning Exp $
.PATH: ${.CURDIR}/../bgpd
PROG= bgpctl
SRCS= bgpctl.c parser.c buffer.c imsg.c util.c
+SRCS+= irrfilter.c whois.c irr_asset.c irr_prefix.c irr_output.c
+SRCS+= irr_parser.c
CFLAGS+= -Wall
CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
CFLAGS+= -Wmissing-declarations
diff --git a/usr.sbin/bgpctl/bgpctl.c b/usr.sbin/bgpctl/bgpctl.c
index adfb300fc51..768c4e680d8 100644
--- a/usr.sbin/bgpctl/bgpctl.c
+++ b/usr.sbin/bgpctl/bgpctl.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: bgpctl.c,v 1.115 2007/02/22 08:38:19 henning Exp $ */
+/* $OpenBSD: bgpctl.c,v 1.116 2007/03/03 11:45:30 henning Exp $ */
/*
* Copyright (c) 2003 Henning Brauer <henning@openbsd.org>
@@ -36,6 +36,7 @@
#include "rde.h"
#include "log.h"
#include "parser.h"
+#include "irrfilter.h"
enum neighbor_views {
NV_DEFAULT,
@@ -102,10 +103,11 @@ main(int argc, char *argv[])
struct parse_result *res;
struct ctl_neighbor neighbor;
struct ctl_show_rib_request ribreq;
- char *sockname;
+ char *sockname, *outdir;
enum imsg_type type;
sockname = SOCKET_NAME;
+ outdir = getcwd(NULL, 0);
while ((ch = getopt(argc, argv, "ns:")) != -1) {
switch (ch) {
case 'n':
@@ -126,6 +128,9 @@ main(int argc, char *argv[])
if ((res = parse(argc, argv)) == NULL)
exit(1);
+ if (res->action == IRRFILTER)
+ irr_main(res->as.as, res->flags, outdir);
+
memcpy(&neighbor.addr, &res->peeraddr, sizeof(neighbor.addr));
strlcpy(neighbor.descr, res->peerdesc, sizeof(neighbor.descr));
@@ -147,6 +152,7 @@ main(int argc, char *argv[])
switch (res->action) {
case NONE:
+ case IRRFILTER:
usage();
/* not reached */
case SHOW:
@@ -352,6 +358,7 @@ main(int argc, char *argv[])
case NETWORK_ADD:
case NETWORK_REMOVE:
case NETWORK_FLUSH:
+ case IRRFILTER:
break;
}
imsg_free(&imsg);
diff --git a/usr.sbin/bgpctl/irr_asset.c b/usr.sbin/bgpctl/irr_asset.c
new file mode 100644
index 00000000000..0cf54557a49
--- /dev/null
+++ b/usr.sbin/bgpctl/irr_asset.c
@@ -0,0 +1,234 @@
+/* $OpenBSD: irr_asset.c,v 1.1 2007/03/03 11:45:30 henning Exp $ */
+
+/*
+ * Copyright (c) 2007 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "irrfilter.h"
+
+int as_set_compare(struct as_set *, struct as_set *);
+struct as_set *as_set_find(char *);
+
+RB_HEAD(as_set_h, as_set) as_set_h;
+RB_PROTOTYPE(as_set_h, as_set, entry, as_set_compare)
+RB_GENERATE(as_set_h, as_set, entry, as_set_compare)
+
+struct as_set *curass;
+
+struct as_set *asset_get(char *);
+void asset_resolve(struct as_set *);
+int asset_merge(struct as_set *, struct as_set *);
+int asset_add_as(struct as_set *, char *);
+int asset_add_asset(struct as_set *, char *);
+
+struct as_set *
+asset_expand(char *name)
+{
+ struct as_set *ass;
+
+ ass = asset_get(name);
+ asset_resolve(ass);
+
+ return (ass);
+}
+
+struct as_set *
+asset_get(char *name)
+{
+ struct as_set *ass, *mas;
+ u_int i;
+ int r;
+
+
+ /*
+ * the caching prevents the endless recursion.
+ * MUST have the RB_INSERT before calling self again.
+ */
+
+ /* cached? then things are easy */
+ if ((ass = as_set_find(name)) != NULL)
+ return ass;
+
+ if ((ass = calloc(1, sizeof(*ass))) == NULL)
+ err(1, "expand_as_set calloc");
+ if ((ass->name = strdup(name)) == NULL)
+ err(1, "expand_as_set strdup");
+ RB_INSERT(as_set_h, &as_set_h, ass);
+
+ curass = ass;
+ if ((r = whois(name, QTYPE_ASSET)) == -1)
+ errx(1, "whois error, asset_get %s", name);
+ curass = NULL;
+
+ /*
+ * if there are no members, this is an aut-num.
+ * if thsi was specified directly in the policy,
+ * make a dummy as-set with the the AS as name
+ * and its only member */
+ if (ass->n_members == 0)
+ asset_add_as(ass, name);
+
+ for (i = 0; i < ass->n_members; i++) {
+ mas = asset_get(ass->members[i]);
+ if (mas->n_members == 0)
+ asset_add_as(ass, ass->members[i]);
+ else
+ asset_add_asset(ass, ass->members[i]);
+ }
+
+ return (ass);
+}
+
+void
+asset_resolve(struct as_set *ass)
+{
+ struct as_set *mas;
+ u_int i;
+
+ /*
+ * traverse all as_set members and fold their
+ * members as into this as_set.
+ * ass->n_as_set is a moving target, it grows
+ * as member as-sets' member as-sets are beeing
+ * added.
+ * remove processed member as-sets (all!) only
+ * after we are done, they're needed for dupe
+ * detection
+ */
+
+ for (i = 0; i < ass->n_as_set; i++) {
+ if ((mas = as_set_find(ass->as_set[i])) == NULL)
+ errx(1, "asset_get %s: %s unresolved?!?",
+ ass->name, ass->as_set[i]);
+ if (asset_merge(ass, mas) == -1)
+ errx(1, "asset_merge failed");
+ }
+
+ for (i = 0; i < ass->n_as_set; i++) {
+ free(ass->as_set[i]);
+ ass->as_set[i] = NULL;
+ }
+ free(ass->as_set);
+ ass->as_set = NULL;
+ ass->n_as_set = 0;
+}
+
+int
+asset_merge(struct as_set *ass, struct as_set *mas)
+{
+ u_int i, j;
+
+ /* merge ASes from the member into the parent */
+ for (i = 0; i < mas->n_as; i++) {
+ for (j = 0; j < ass->n_as && strcmp(ass->as[j],
+ mas->as[i]); j++)
+ ; /* nothing */
+ if (j == ass->n_as)
+ if (asset_add_as(ass, mas->as[i]) == -1)
+ return (-1);
+ }
+
+ /* merge as-set members from the member into the parent */
+ for (i = 0; i < mas->n_as_set; i++) {
+ if (!strcmp(ass->name, mas->as_set[i])) /* skip self! */
+ continue;
+ for (j = 0; j < ass->n_as_set && strcmp(ass->as_set[j],
+ mas->as_set[i]); j++)
+ ; /* nothing */
+ if (j == ass->n_as_set)
+ if (asset_add_asset(ass, mas->as_set[i]) == -1)
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+asset_addmember(char *s)
+{
+ void *p;
+
+ if ((p = realloc(curass->members,
+ (curass->n_members + 1) * sizeof(char *))) == NULL)
+ err(1, "asset_addmember strdup");
+ curass->members = p;
+ curass->n_members++;
+
+ if ((curass->members[curass->n_members - 1] =
+ strdup(s)) == NULL)
+ err(1, "asset_addmember strdup");
+
+ return (0);
+}
+
+int
+asset_add_as(struct as_set *ass, char *s)
+{
+ void *p;
+
+ if ((p = realloc(ass->as,
+ (ass->n_as + 1) * sizeof(char *))) == NULL)
+ err(1, "asset_add_as strdup");
+ ass->as = p;
+ ass->n_as++;
+
+ if ((ass->as[ass->n_as - 1] =
+ strdup(s)) == NULL)
+ err(1, "asset_add_as strdup");
+
+ return (0);
+}
+
+int
+asset_add_asset(struct as_set *ass, char *s)
+{
+ void *p;
+
+ if ((p = realloc(ass->as_set,
+ (ass->n_as_set + 1) * sizeof(char *))) == NULL)
+ err(1, "asset_add_asset strdup");
+ ass->as_set = p;
+ ass->n_as_set++;
+
+ if ((ass->as_set[ass->n_as_set - 1] =
+ strdup(s)) == NULL)
+ err(1, "asset_add_asset strdup");
+
+ return (0);
+}
+
+/* RB helpers */
+int
+as_set_compare(struct as_set *a, struct as_set *b)
+{
+ return (strcmp(a->name, b->name));
+}
+
+struct as_set *
+as_set_find(char *name)
+{
+ struct as_set s;
+
+ s.name = name;
+ return (RB_FIND(as_set_h, &as_set_h, &s));
+}
diff --git a/usr.sbin/bgpctl/irr_output.c b/usr.sbin/bgpctl/irr_output.c
new file mode 100644
index 00000000000..b0503ca2d4a
--- /dev/null
+++ b/usr.sbin/bgpctl/irr_output.c
@@ -0,0 +1,183 @@
+/* $OpenBSD: irr_output.c,v 1.1 2007/03/03 11:45:30 henning Exp $ */
+
+/*
+ * Copyright (c) 2007 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "irrfilter.h"
+
+int process_policies(struct policy_head *, char *);
+void policy_prettyprint(FILE *, struct policy_item *);
+void policy_torule(FILE *, struct policy_item *);
+char *action_torule(char *);
+void print_rule(FILE *, enum pdir, char *, char *, char *);
+
+int
+write_filters(char *outpath)
+{
+ struct router *r;
+ int ret = 0;
+
+ while ((r = TAILQ_FIRST(&router_head)) != NULL) {
+ TAILQ_REMOVE(&router_head, r, entry);
+ printf("\nRouter %s\n", r->address);
+
+ if (process_policies(&r->policy_h,
+ r->address) == -1)
+ ret = -1;
+
+ free(r->address);
+ free(r);
+ }
+
+ return (ret);
+}
+
+int
+process_policies(struct policy_head *head, char *router)
+{
+ struct policy_item *pi;
+
+ while ((pi = TAILQ_FIRST(head)) != NULL) {
+ TAILQ_REMOVE(head, pi, entry);
+
+ policy_prettyprint(stdout, pi);
+ policy_torule(stdout, pi);
+
+ free(pi->peer_as);
+ free(pi->peer_addr);
+ free(pi->action);
+ free(pi->filter);
+ free(pi);
+ }
+
+ return (0);
+}
+
+void
+policy_prettyprint(FILE *fh, struct policy_item *pi)
+{
+ if (pi->dir == IMPORT)
+ fprintf(fh, "# import: from ");
+ else
+ fprintf(fh, "# export: to ");
+ fprintf(fh, "%s ", pi->peer_as);
+ if (pi->peer_addr)
+ fprintf(fh, "%s ", pi->peer_addr);
+ if (pi->action)
+ fprintf(fh, "action %s ", pi->action);
+ fprintf(fh, "%s %s\n", pi->dir == IMPORT ? "accept" : "announce",
+ pi->filter);
+}
+
+void
+policy_torule(FILE *fh, struct policy_item *pi)
+{
+ struct as_set *ass;
+ struct prefix_set *pfxs;
+ u_int i, j;
+
+ if (pi->filter == NULL || !strcasecmp(pi->filter, "any"))
+ print_rule(fh, pi->dir, pi->peer_addr, pi->action, NULL);
+ else {
+ ass = asset_expand(pi->filter);
+
+ for (i = 0; i < ass->n_as; i++) {
+ pfxs = prefixset_get(ass->as[i]);
+ printf("# prefixes from %s\n", ass->as[i]);
+ for (j = 0; j < pfxs->prefixcnt; j++)
+ print_rule(fh, pi->dir, pi->peer_addr,
+ pi->action, pfxs->prefix[j]);
+ }
+ }
+}
+
+/* XXX should really be parsed earlier! */
+char *
+action_torule(char *s)
+{
+ int cnt = 0;
+ char *key, *val, *pre, *buf, *tmp;
+ static char abuf[8192];
+ char ebuf[2048];
+
+ if ((tmp = strdup(s)) == NULL)
+ err(1, "foo");
+ abuf[0] = '\0';
+ buf = abuf;
+ while ((val = strsep(&tmp, ";")) != NULL && *val) {
+ key = strsep(&val, "=");
+ if (key == NULL || val == NULL)
+ err(1, "format error in action spec\n");
+
+ EATWS(key);
+ EATWS(val);
+
+ if (cnt++ == 0)
+ pre = " set {";
+ else
+ pre = ",";
+
+ if (!strcmp(key, "pref"))
+ snprintf(ebuf, sizeof(ebuf),
+ "%s localpref=%s", pre, val);
+ else if (!strcmp(key, "med"))
+ snprintf(ebuf, sizeof(ebuf),
+ "%s med=%s", pre, val);
+ else
+ warnx("unknown action key \"%s\"", key);
+
+ strlcat(abuf, ebuf, sizeof(abuf));
+ }
+ if (cnt > 0)
+ strlcat(abuf, " }", sizeof(abuf));
+
+ free(tmp);
+ return (abuf);
+}
+
+void
+print_rule(FILE *fh, enum pdir pdir, char *peerspec, char *actspec,
+ char *prefix)
+{
+ char *fmt = "allow quick %s %s%s%s%s\n";
+ char *peer = "any";
+ char *action = "";
+ char *dir;
+
+ if (pdir == IMPORT)
+ dir = "from";
+ else
+ dir = "to";
+
+ if (peerspec)
+ peer = peerspec;
+
+ if (actspec)
+ action = action_torule(actspec);
+
+ if (prefix == NULL)
+ fprintf(fh, fmt, dir, peer, action, "", "");
+ else
+ fprintf(fh, fmt, dir, peer, action, " prefix ", prefix);
+}
diff --git a/usr.sbin/bgpctl/irr_parser.c b/usr.sbin/bgpctl/irr_parser.c
new file mode 100644
index 00000000000..8d9e34a9e45
--- /dev/null
+++ b/usr.sbin/bgpctl/irr_parser.c
@@ -0,0 +1,395 @@
+/* $OpenBSD: irr_parser.c,v 1.1 2007/03/03 11:45:30 henning Exp $ */
+
+/*
+ * Copyright (c) 2007 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "irrfilter.h"
+
+#define PARSEBUF_INCREMENT 4096
+
+int lineno;
+char *parsebuf = NULL;
+size_t parsebuflen = 0;
+
+void grow_parsebuf(void);
+char *irr_getln(FILE *f);
+int parse_policy(char *, char *);
+int policy_additem(char *, struct policy_item *);
+int parse_asset(char *, char *);
+int parse_route(char *, char *);
+
+int
+parse_response(FILE *f, enum qtype qtype)
+{
+ char *key, *val;
+ int cnt, n;
+
+ lineno = 1;
+ cnt = 0;
+ while ((val = irr_getln(f)) != NULL) {
+ if (!strncmp(val, "%ERROR:101:", 11)) /* no entries found */
+ return (0);
+
+ if (val[0] == '%') {
+ warnx("message from whois server: %s", val);
+ return (-1);
+ }
+
+ key = strsep(&val, ":");
+ if (val == NULL) {
+ warnx("%u: %s", lineno, key);
+ warnx("no \":\" found!");
+ return (-1);
+ }
+ while (ISWS(*val))
+ val++;
+
+ switch (qtype) {
+ case QTYPE_OWNAS:
+ if ((n = parse_policy(key, val)) == -1)
+ return (-1);
+ break;
+ case QTYPE_ASSET:
+ if ((n = parse_asset(key, val)) == -1)
+ return (-1);
+ break;
+ case QTYPE_ROUTE:
+ if ((n = parse_route(key, val)) == -1)
+ return (-1);
+ break;
+ default:
+ err(1, "king bula suffers from dementia");
+ }
+ cnt += n;
+ }
+
+ return (cnt);
+}
+
+void
+grow_parsebuf(void)
+{
+ char *p;
+ size_t newlen;
+
+ newlen = parsebuflen + PARSEBUF_INCREMENT;
+ if ((p = realloc(parsebuf, newlen)) == NULL)
+ err(1, "grow_parsebuf realloc");
+ parsebuf = p;
+ parsebuflen = newlen;
+
+ if (0)
+ fprintf(stderr, "parsebuf now %lu bytes\n", (ulong)parsebuflen);
+}
+
+char *
+irr_getln(FILE *f)
+{
+ int c, next, last;
+ char *p;
+
+ if (parsebuf == NULL)
+ grow_parsebuf();
+ p = parsebuf;
+ last = -1;
+
+ do {
+ c = getc(f);
+
+ if (p == parsebuf) { /* beginning of new line */
+ if (c == '%') {
+ next = getc(f);
+ switch (next) {
+ case ' ': /* comment. skip over */
+ while ((c = getc(f)) != '\n' &&
+ c != EOF)
+ ; /* nothing */
+ break;
+ case '\n':
+ case EOF:
+ c = next;
+ break;
+ default:
+ ungetc(next, f);
+ break;
+ }
+ }
+ }
+
+ if (c == '#') /* skip until \n */
+ while ((c = getc(f)) != '\n' && c != EOF)
+ ; /* nothing */
+
+ if (c == '\n') {
+ lineno++;
+ next = getc(f);
+ if (next == '+') /* continuation, skip the + */
+ c = getc(f);
+ else if (ISWS(next)) /* continuation */
+ c = next;
+ else
+ ungetc(next, f);
+ }
+
+
+ if (c == '\n' || c == EOF) {
+ if (c == EOF)
+ if (ferror(f))
+ err(1, "ferror");
+ if (p > parsebuf) {
+ *p = '\0';
+ return (parsebuf);
+ }
+ } else {
+ if (!(ISWS(c) && ISWS(last))) {
+ if (p + 1 >= parsebuf + parsebuflen - 1) {
+ size_t offset;
+
+ offset = p - parsebuf;
+ grow_parsebuf();
+ p = parsebuf + offset;
+ }
+ if (ISWS(c)) /* equal opportunity whitespace */
+ *p++ = ' ';
+ else
+ *p++ = (char)c;
+ }
+ last = c;
+ }
+ } while (c != EOF);
+
+ return (NULL);
+}
+
+/*
+ * parse the policy from an aut-num object
+ */
+
+enum policy_parser_st {
+ PO_NONE,
+ PO_PEER_KEY,
+ PO_PEER_AS,
+ PO_PEER_ADDR,
+ PO_RTR_KEY,
+ PO_RTR_ADDR,
+ PO_ACTION_KEY,
+ PO_ACTION_SPEC,
+ PO_FILTER_KEY,
+ PO_FILTER_SPEC
+};
+
+int
+parse_policy(char *key, char *val)
+{
+ struct policy_item *pi;
+ enum pdir dir;
+ enum policy_parser_st st = PO_NONE, nextst;
+ char *tok, *router = NULL, *p;
+
+ if (!strcmp(key, "import"))
+ dir = IMPORT;
+ else if (!strcmp(key, "export"))
+ dir = EXPORT;
+ else /* ignore! */
+ return (0);
+
+ if (dir == EXPORT && (irrflags & F_IMPORTONLY))
+ return (0);
+
+ if ((pi = calloc(1, sizeof(*pi))) == NULL)
+ err(1, "parse_policy calloc");
+ pi->dir = dir;
+
+ while ((tok = strsep(&val, " ")) != NULL) {
+ nextst = PO_NONE;
+ if (dir == IMPORT) {
+ if (!strcmp(tok, "from"))
+ nextst = PO_PEER_KEY;
+ else if (!strcmp(tok, "at"))
+ nextst = PO_RTR_KEY;
+ else if (!strcmp(tok, "action"))
+ nextst = PO_ACTION_KEY;
+ else if (!strcmp(tok, "accept"))
+ nextst = PO_FILTER_KEY;
+ } else if (dir == EXPORT) {
+ if (!strcmp(tok, "to"))
+ nextst = PO_PEER_KEY;
+ else if (!strcmp(tok, "at"))
+ nextst = PO_RTR_KEY;
+ else if (!strcmp(tok, "announce"))
+ nextst = PO_FILTER_KEY;
+ }
+
+ if (nextst == PO_FILTER_KEY) /* rest is filter spec */
+ if ((pi->filter = strdup(val)) == NULL)
+ err(1, NULL);
+
+ if (nextst == PO_ACTION_KEY) {
+ /* action list. ends after last ; */
+ p = strrchr(val, ';');
+ if (p == NULL || !ISWS(*++p))
+ errx(1, "syntax error in action spec");
+ *p = '\0';
+ if ((pi->action = strdup(val)) == NULL)
+ err(1, NULL);
+ val = ++p;
+ while (ISWS(*p))
+ p++;
+ }
+
+ switch (st) {
+ case PO_NONE:
+ if (nextst != PO_PEER_KEY)
+ goto ppoerr;
+ st = nextst;
+ break;
+ case PO_PEER_KEY:
+ if (pi->peer_as == NULL) {
+ if (nextst != PO_NONE)
+ goto ppoerr;
+ if ((pi->peer_as = strdup(tok)) == NULL)
+ err(1, NULL);
+ } else {
+ switch (nextst) {
+ case PO_NONE:
+ if (!strcasecmp(tok, "and") ||
+ !strcasecmp(tok, "or") ||
+ !strcasecmp(tok, "not"))
+ fprintf(stderr, "compound peering "
+ "statements are not supported");
+ else /* peer address */
+ if ((pi->peer_addr = strdup(tok)) == NULL)
+ err(1, NULL);
+ break;
+ case PO_RTR_KEY:
+ case PO_ACTION_KEY:
+ case PO_FILTER_KEY:
+ st = nextst;
+ break;
+ default:
+ goto ppoerr;
+ }
+ }
+ break;
+ case PO_PEER_AS:
+ case PO_PEER_ADDR:
+ err(1, "state error");
+ break;
+ case PO_RTR_KEY:
+ if (nextst != PO_NONE)
+ goto ppoerr;
+ /* rtr address */
+ if ((router = strdup(tok)) == NULL)
+ err(1, NULL);
+ st = PO_RTR_ADDR;
+ break;
+ case PO_RTR_ADDR:
+ if (nextst != PO_ACTION_KEY &&
+ nextst != PO_FILTER_KEY)
+ goto ppoerr;
+ st = nextst;
+ break;
+ case PO_ACTION_KEY:
+ /* already handled, next must be FILTER_KEY */
+ if (nextst != PO_FILTER_KEY)
+ goto ppoerr;
+ st = nextst;
+ break;
+ case PO_FILTER_KEY:
+ /* already handled */
+ break;
+ case PO_ACTION_SPEC:
+ case PO_FILTER_SPEC:
+ err(1, "state error");
+ break;
+ }
+ }
+
+ if (st != PO_FILTER_KEY)
+ err(1, "state error");
+
+ if (policy_additem(router, pi) == -1)
+ return (-1);
+
+ return (1);
+
+ppoerr:
+ free(pi);
+ fprintf(stderr, "%u: parse error\n", lineno);
+ return (-1);
+}
+
+int
+policy_additem(char *router, struct policy_item *pi)
+{
+ struct router *r;
+
+ for (r = TAILQ_FIRST(&router_head); r != NULL &&
+ strcmp(r->address, router); r = TAILQ_NEXT(r, entry))
+ ; /* nothing */
+
+ if (r == NULL) {
+ if ((r = calloc(1, sizeof(*r))) == NULL ||
+ (r->address = strdup(router)) == NULL)
+ err(1, NULL);
+ TAILQ_INIT(&r->policy_h);
+ TAILQ_INSERT_TAIL(&router_head, r, entry);
+ }
+
+ TAILQ_INSERT_TAIL(&r->policy_h, pi, entry);
+
+ return (0);
+}
+
+/*
+ * parse as-set: get members
+ */
+
+int
+parse_asset(char *key, char *val)
+{
+ char *tok;
+
+ if (strcmp(key, "members")) /* ignore everything else */
+ return (0);
+
+ while ((tok = strsep(&val, ",")) != NULL) {
+ EATWS(tok);
+ asset_addmember(tok);
+ }
+
+ return (1);
+}
+
+/*
+ * parse route obj: just get the prefix
+ */
+int
+parse_route(char *key, char *val)
+{
+ if (strcmp(key, "route")) /* ignore everything else */
+ return (0);
+
+ return(prefixset_addmember(val));
+}
diff --git a/usr.sbin/bgpctl/irr_prefix.c b/usr.sbin/bgpctl/irr_prefix.c
new file mode 100644
index 00000000000..d694ae09cf7
--- /dev/null
+++ b/usr.sbin/bgpctl/irr_prefix.c
@@ -0,0 +1,102 @@
+/* $OpenBSD: irr_prefix.c,v 1.1 2007/03/03 11:45:30 henning Exp $ */
+
+/*
+ * Copyright (c) 2007 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "irrfilter.h"
+
+int prefix_set_compare(struct prefix_set *, struct prefix_set *);
+struct prefix_set
+ *prefix_set_find(char *);
+
+RB_HEAD(prefix_set_h, prefix_set) prefix_set_h;
+RB_PROTOTYPE(prefix_set_h, prefix_set, entry, prefix_set_compare)
+RB_GENERATE(prefix_set_h, prefix_set, entry, prefix_set_compare)
+
+struct prefix_set *curpfxs = NULL;
+
+struct prefix_set *
+prefixset_get(char *as)
+{
+ struct prefix_set *pfxs;
+ int r;
+
+ if ((pfxs = prefix_set_find(as)) != NULL)
+ return (pfxs);
+
+ /* nothing found, resolve and store */
+ if ((pfxs = calloc(1, sizeof(*pfxs))) == NULL)
+ err(1, "get_prefixset calloc");
+ if ((pfxs->as = strdup(as)) == NULL)
+ err(1, "get_prefixset strdup");
+ RB_INSERT(prefix_set_h, &prefix_set_h, pfxs);
+
+ curpfxs = pfxs;
+ if ((r = whois(as, QTYPE_ROUTE)) == -1)
+ errx(1, "whois error, prefixset_get %s", as);
+ curpfxs = NULL;
+
+ return (pfxs);
+}
+
+int
+prefixset_addmember(char *s)
+{
+ void *p;
+ u_int i;
+
+ /* yes, there are dupes... e. g. from multiple sources */
+ for (i = 0; i < curpfxs->prefixcnt; i++)
+ if (!strcmp(curpfxs->prefix[i], s))
+ return (0);
+
+ if ((p = realloc(curpfxs->prefix,
+ (curpfxs->prefixcnt + 1) * sizeof(char *))) == NULL)
+ err(1, "prefixset_addmember strdup");
+ curpfxs->prefix = p;
+ curpfxs->prefixcnt++;
+
+ if ((curpfxs->prefix[curpfxs->prefixcnt - 1] =
+ strdup(s)) == NULL)
+ err(1, "prefixset_addmember strdup");
+
+ return (1);
+}
+
+
+/* RB helpers */
+int
+prefix_set_compare(struct prefix_set *a, struct prefix_set *b)
+{
+ return (strcmp(a->as, b->as));
+}
+
+struct prefix_set *
+prefix_set_find(char *as)
+{
+ struct prefix_set s;
+
+ s.as = as;
+ return (RB_FIND(prefix_set_h, &prefix_set_h, &s));
+}
diff --git a/usr.sbin/bgpctl/irrfilter.c b/usr.sbin/bgpctl/irrfilter.c
new file mode 100644
index 00000000000..203411d2d7c
--- /dev/null
+++ b/usr.sbin/bgpctl/irrfilter.c
@@ -0,0 +1,52 @@
+/* $OpenBSD: irrfilter.c,v 1.1 2007/03/03 11:45:30 henning Exp $ */
+
+/*
+ * Copyright (c) 2007 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "irrfilter.h"
+
+__dead void
+irr_main(u_int32_t AS, int flags, char *outdir)
+{
+ char *query;
+ int r;
+
+ fprintf(stderr, "irrfilter for: %u\n", AS);
+
+ irrflags = flags;
+ TAILQ_INIT(&router_head);
+
+ /* send query for own AS, parse policy */
+ if (asprintf(&query, "AS%u", AS) == -1)
+ err(1, "parse_policy asprintf");
+ if ((r = whois(query, QTYPE_OWNAS)) == -1)
+ exit (1);
+ if (r == 0)
+ errx(1, "aut-num object %s not found", query);
+ free(query);
+
+ write_filters(outdir);
+
+ exit (0);
+}
diff --git a/usr.sbin/bgpctl/irrfilter.h b/usr.sbin/bgpctl/irrfilter.h
new file mode 100644
index 00000000000..1cd55f7dce5
--- /dev/null
+++ b/usr.sbin/bgpctl/irrfilter.h
@@ -0,0 +1,96 @@
+/* $OpenBSD: irrfilter.h,v 1.1 2007/03/03 11:45:30 henning Exp $ */
+
+/*
+ * Copyright (c) 2007 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+#include <sys/tree.h>
+
+#define F_IMPORTONLY 0x01 /* skip export: items */
+
+int irrflags;
+
+enum pdir {
+ PDIR_NONE,
+ IMPORT,
+ EXPORT
+};
+
+struct policy_item {
+ TAILQ_ENTRY(policy_item) entry;
+ enum pdir dir;
+ char *peer_as;
+ char *peer_addr;
+ char *action;
+ char *filter;
+};
+
+TAILQ_HEAD(policy_head, policy_item);
+
+struct router {
+ TAILQ_ENTRY(router) entry;
+ char *address;
+ struct policy_head policy_h;
+};
+
+TAILQ_HEAD(router_head, router) router_head;
+
+/* keep qtype and qtype_objs in whois.c in sync! */
+enum qtype {
+ QTYPE_NONE,
+ QTYPE_OWNAS,
+ QTYPE_ASSET,
+ QTYPE_ROUTE
+};
+
+struct as_set {
+ RB_ENTRY(as_set) entry;
+ char *name;
+ char **members; /* direct members */
+ char **as_set; /* members as-set */
+ char **as; /* members aut-num */
+ u_int n_members;
+ u_int n_as_set;
+ u_int n_as;
+};
+
+struct prefix_set {
+ RB_ENTRY(prefix_set) entry;
+ char *as;
+ char **prefix;
+ u_int prefixcnt;
+};
+
+/* eat trailing and leading whitespace */
+#define ISWS(x) (x == ' ' || x == '\t')
+#define EATWS(s) \
+ do { \
+ char *ps; \
+ while (ISWS(*s)) \
+ s++; \
+ ps = s + strlen(s) - 1; \
+ while (ps && ps >= s && ISWS(*ps)) \
+ *ps-- = '\0'; \
+ } while (0);
+
+__dead void irr_main(u_int32_t, int, char *);
+int whois(const char *, enum qtype);
+int parse_response(FILE *, enum qtype);
+int write_filters(char *);
+struct as_set *asset_expand(char *);
+int asset_addmember(char *);
+struct prefix_set *prefixset_get(char *);
+int prefixset_addmember(char *);
diff --git a/usr.sbin/bgpctl/parser.c b/usr.sbin/bgpctl/parser.c
index caec5342bc0..1a066909c2e 100644
--- a/usr.sbin/bgpctl/parser.c
+++ b/usr.sbin/bgpctl/parser.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: parser.c,v 1.38 2007/02/22 08:38:19 henning Exp $ */
+/* $OpenBSD: parser.c,v 1.39 2007/03/03 11:45:30 henning Exp $ */
/*
* Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -28,6 +28,7 @@
#include <string.h>
#include "parser.h"
+#include "irrfilter.h"
enum token_type {
NOTOKEN,
@@ -84,6 +85,8 @@ static const struct token t_pftable[];
static const struct token t_prepnbr[];
static const struct token t_prepself[];
static const struct token t_weight[];
+static const struct token t_irrfilter[];
+static const struct token t_irrfilter_opts[];
static const struct token t_main[] = {
{ KEYWORD, "reload", RELOAD, NULL},
@@ -91,6 +94,7 @@ static const struct token t_main[] = {
{ KEYWORD, "fib", FIB, t_fib},
{ KEYWORD, "neighbor", NEIGHBOR, t_neighbor},
{ KEYWORD, "network", NONE, t_network},
+ { KEYWORD, "irrfilter", IRRFILTER, t_irrfilter},
{ ENDTOKEN, "", NONE, NULL}
};
@@ -271,6 +275,16 @@ static const struct token t_weight[] = {
{ ENDTOKEN, "", NONE, NULL}
};
+static const struct token t_irrfilter[] = {
+ { ASNUM, "", NONE, t_irrfilter_opts},
+ { ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_irrfilter_opts[] = {
+ { NOTOKEN, "", NONE, NULL},
+ { FLAG, "importonly", F_IMPORTONLY, t_irrfilter_opts},
+ { ENDTOKEN, "", NONE, NULL}
+};
static struct parse_result res;
diff --git a/usr.sbin/bgpctl/parser.h b/usr.sbin/bgpctl/parser.h
index a2732c8d9b4..fa92508744b 100644
--- a/usr.sbin/bgpctl/parser.h
+++ b/usr.sbin/bgpctl/parser.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: parser.h,v 1.14 2006/08/23 08:21:11 claudio Exp $ */
+/* $OpenBSD: parser.h,v 1.15 2007/03/03 11:45:30 henning Exp $ */
/*
* Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -44,7 +44,8 @@ enum actions {
NETWORK_ADD,
NETWORK_REMOVE,
NETWORK_FLUSH,
- NETWORK_SHOW
+ NETWORK_SHOW,
+ IRRFILTER
};
struct parse_result {
diff --git a/usr.sbin/bgpctl/whois.c b/usr.sbin/bgpctl/whois.c
new file mode 100644
index 00000000000..cecbf53e092
--- /dev/null
+++ b/usr.sbin/bgpctl/whois.c
@@ -0,0 +1,149 @@
+/* $OpenBSD: whois.c,v 1.1 2007/03/03 11:45:30 henning Exp $ */
+
+/*
+ * Copyright (c) 2007 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Copyright (c) 1980, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "irrfilter.h"
+
+#define WHOIS_STDOPTS "-r -a"
+
+char *qtype_opts[] = {
+ "",
+ "-T aut-num",
+ "-K -T as-set",
+ "-K -T route -i origin"
+};
+
+char *server = "whois.ripe.net";
+char *port = "whois";
+
+int
+whois(const char *query, enum qtype qtype)
+{
+ FILE *sfw, *sfr;
+ int s, r = -1, error = 0, attempt, ret;
+ struct addrinfo hints, *res, *ai;
+ const char *reason = NULL;
+ char *fmt;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = 0;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ error = getaddrinfo(server, port, &hints, &res);
+ if (error) {
+ if (error == EAI_SERVICE)
+ warnx("%s: bad port", port);
+ else
+ warnx("%s: %s", server, gai_strerror(error));
+ return (1);
+ }
+
+ for (s = -1, ai = res; ai != NULL; ai = ai->ai_next) {
+ attempt = 0;
+ do {
+ attempt++;
+ if (s != -1)
+ close (s);
+ s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (s == -1) {
+ error = errno;
+ reason = "socket";
+ } else
+ r = connect(s, ai->ai_addr, ai->ai_addrlen);
+ } while (r == -1 && errno == ETIMEDOUT && attempt <= 3);
+
+ if (r == -1) {
+ error = errno;
+ reason = "connect";
+ close(s);
+ s = -1;
+ continue;
+ }
+ if (s != -1)
+ break; /*okay*/
+ }
+ freeaddrinfo(res);
+
+ if (s == -1) {
+ if (reason) {
+ errno = error;
+ warn("%s: %s", server, reason);
+ } else
+ warn("unknown error in connection attempt");
+ return (1);
+ }
+
+ sfr = fdopen(s, "r");
+ sfw = fdopen(s, "w");
+ if (sfr == NULL || sfw == NULL)
+ err(1, "fdopen");
+ fmt = "%s %s %s\r\n";
+ fprintf(sfw, fmt, WHOIS_STDOPTS, qtype_opts[qtype], query);
+ fflush(sfw);
+
+ if ((ret = parse_response(sfr, qtype)) == -1)
+ warnx("parse error, query=\"%s %s\"", qtype_opts[qtype], query);
+
+ fclose(sfw);
+ fclose(sfr);
+ close(s);
+ return (ret);
+}