diff options
Diffstat (limited to 'proto/radv/packets.c')
-rw-r--r-- | proto/radv/packets.c | 181 |
1 files changed, 177 insertions, 4 deletions
diff --git a/proto/radv/packets.c b/proto/radv/packets.c index ac59ce94..6fdfcaa3 100644 --- a/proto/radv/packets.c +++ b/proto/radv/packets.c @@ -24,8 +24,10 @@ struct radv_ra_packet #define OPT_RA_MANAGED 0x80 #define OPT_RA_OTHER_CFG 0x40 -#define OPT_PREFIX 3 -#define OPT_MTU 5 +#define OPT_PREFIX 3 +#define OPT_MTU 5 +#define OPT_RDNSS 25 +#define OPT_DNSSL 31 struct radv_opt_prefix { @@ -50,6 +52,25 @@ struct radv_opt_mtu u32 mtu; }; +struct radv_opt_rdnss +{ + u8 type; + u8 length; + u16 reserved; + u32 lifetime; + ip_addr servers[]; +}; + +struct radv_opt_dnssl +{ + u8 type; + u8 length; + u16 reserved; + u32 lifetime; + char domain[]; +}; + + static struct radv_prefix_config default_prefix = { .onlink = 1, .autonomous = 1, @@ -57,6 +78,7 @@ static struct radv_prefix_config default_prefix = { .preferred_lifetime = DEFAULT_PREFERRED_LIFETIME }; + static struct radv_prefix_config * radv_prefix_match(struct radv_iface *ifa, struct ifa *a) { @@ -78,10 +100,146 @@ radv_prefix_match(struct radv_iface *ifa, struct ifa *a) return &default_prefix; } +static int +radv_prepare_rdnss(struct radv_iface *ifa, list *rdnss_list, char **buf, char *bufend) +{ + struct radv_rdnss_config *rcf = HEAD(*rdnss_list); + + while(NODE_VALID(rcf)) + { + 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 i = 0; + + if (max_i < 1) + goto too_much; + + op->type = OPT_RDNSS; + op->reserved = 0; + + if (rcf->lifetime_mult) + op->lifetime = htonl(rcf->lifetime_mult * ifa->cf->max_ra_int); + else + op->lifetime = htonl(rcf->lifetime); + + 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]); + i++; + + rcf = NODE_NEXT(rcf); + } + + op->length = 1+2*i; + *buf += 8 * op->length; + } + + return 0; + + too_much: + log(L_WARN "%s: Too many RA options on interface %s", + ifa->ra->p.name, ifa->iface->name); + return -1; +} + +int +radv_process_domain(struct radv_dnssl_config *cf) +{ + /* Format of domain in search list is <size> <label> <size> <label> ... 0 */ + + char *dom = cf->domain; + char *dom_end = dom; /* Just to */ + u8 *dlen_save = &cf->dlen_first; + int len; + + while (dom_end) + { + dom_end = strchr(dom, '.'); + len = dom_end ? (dom_end - dom) : strlen(dom); + + if (len < 1 || len > 63) + return -1; + + *dlen_save = len; + dlen_save = (u8 *) dom_end; + + dom += len + 1; + } + + len = dom - cf->domain; + if (len > 254) + return -1; + + cf->dlen_all = len; + + return 0; +} + +static int +radv_prepare_dnssl(struct radv_iface *ifa, list *dnssl_list, char **buf, char *bufend) +{ + struct radv_dnssl_config *dcf = HEAD(*dnssl_list); + + while(NODE_VALID(dcf)) + { + struct radv_dnssl_config *dcf_base = dcf; + struct radv_opt_dnssl *op = (void *) *buf; + int bsize = bufend - *buf - sizeof(struct radv_opt_dnssl); + int bpos = 0; + + if (bsize < 0) + goto too_much; + + bsize = bsize & ~7; /* Round down to multiples of 8 */ + + op->type = OPT_DNSSL; + op->reserved = 0; + + if (dcf->lifetime_mult) + op->lifetime = htonl(dcf->lifetime_mult * ifa->cf->max_ra_int); + else + op->lifetime = htonl(dcf->lifetime); + + while(NODE_VALID(dcf) && + (dcf->lifetime == dcf_base->lifetime) && + (dcf->lifetime_mult == dcf_base->lifetime_mult)) + { + if (bpos + dcf->dlen_all + 1 > bsize) + goto too_much; + + op->domain[bpos++] = dcf->dlen_first; + memcpy(op->domain + bpos, dcf->domain, dcf->dlen_all); + bpos += dcf->dlen_all; + + dcf = NODE_NEXT(dcf); + } + + int blen = (bpos + 7) / 8; + bzero(op->domain + bpos, 8 * blen - bpos); + op->length = 1 + blen; + *buf += 8 * op->length; + } + + return 0; + + too_much: + log(L_WARN "%s: Too many RA options on interface %s", + ifa->ra->p.name, ifa->iface->name); + return -1; +} + static void radv_prepare_ra(struct radv_iface *ifa) { struct proto_radv *ra = ifa->ra; + struct radv_config *cf = (struct radv_config *) (ra->p.cf); char *buf = ifa->sk->tbuf; char *bufstart = buf; @@ -121,7 +279,7 @@ radv_prepare_ra(struct radv_iface *ifa) if (buf + sizeof(struct radv_opt_prefix) > bufend) { log(L_WARN "%s: Too many prefixes on interface %s", ra->p.name, ifa->iface->name); - break; + goto done; } struct radv_opt_prefix *op = (void *) buf; @@ -137,7 +295,22 @@ radv_prepare_ra(struct radv_iface *ifa) ipa_hton(op->prefix); buf += sizeof(*op); } - + + if (! ifa->cf->rdnss_local) + if (radv_prepare_rdnss(ifa, &cf->rdnss_list, &buf, bufend) < 0) + goto done; + + if (radv_prepare_rdnss(ifa, &ifa->cf->rdnss_list, &buf, bufend) < 0) + goto done; + + if (! ifa->cf->dnssl_local) + if (radv_prepare_dnssl(ifa, &cf->dnssl_list, &buf, bufend) < 0) + goto done; + + if (radv_prepare_dnssl(ifa, &ifa->cf->dnssl_list, &buf, bufend) < 0) + goto done; + + done: ifa->plen = buf - bufstart; } |