Commit 9f32605d authored by Kristian Lyngstøl's avatar Kristian Lyngstøl

Add a DNS director

The DNS director allows Varnish to pick backend based on the Host header
provided by the client, and how it resolves in DNS. A suffix can be added
to make it "internal" (see vcl(7)).

There's still some quirks that I want to work out, but this seems fairly
commit-ready and non-intrusive.



git-svn-id: http://www.varnish-cache.org/svn/trunk/varnish-cache@5062 d4fa192b-c00b-0410-8231-f00ffab90ce4
parent 050abf19
...@@ -21,6 +21,7 @@ varnishd_SOURCES = \ ...@@ -21,6 +21,7 @@ varnishd_SOURCES = \
cache_center.c \ cache_center.c \
cache_cli.c \ cache_cli.c \
cache_dir_random.c \ cache_dir_random.c \
cache_dir_dns.c \
cache_dir_round_robin.c \ cache_dir_round_robin.c \
cache_esi.c \ cache_esi.c \
cache_expire.c \ cache_expire.c \
......
...@@ -536,6 +536,24 @@ struct vdi_simple { ...@@ -536,6 +536,24 @@ struct vdi_simple {
struct vsc_vbe *stats; struct vsc_vbe *stats;
}; };
/* Returns the backend if and only if the this is a simple director.
* XXX: Needs a better name and possibly needs a better general approach.
* XXX: This is mainly used by the DNS director to fetch the actual backend
* XXX: so it can compare DNS lookups with the actual IP.
*/
struct backend *
vdi_get_backend_if_simple(const struct director *d)
{
CHECK_OBJ_NOTNULL(d, DIRECTOR_MAGIC);
struct vdi_simple *vs, *vs2;
vs2 = d->priv;
if (vs2->magic != VDI_SIMPLE_MAGIC)
return NULL;
CAST_OBJ_NOTNULL(vs, d->priv, VDI_SIMPLE_MAGIC);
return vs->backend;
}
static struct vbe_conn * static struct vbe_conn *
vdi_simple_getfd(const struct director *d, struct sess *sp) vdi_simple_getfd(const struct director *d, struct sess *sp)
{ {
......
...@@ -272,6 +272,8 @@ VRT_init_dir(struct cli *cli, struct director **dir, const char *name, ...@@ -272,6 +272,8 @@ VRT_init_dir(struct cli *cli, struct director **dir, const char *name,
VRT_init_dir_hash(cli, dir, idx, priv); VRT_init_dir_hash(cli, dir, idx, priv);
else if (!strcmp(name, "random")) else if (!strcmp(name, "random"))
VRT_init_dir_random(cli, dir, idx, priv); VRT_init_dir_random(cli, dir, idx, priv);
else if (!strcmp(name, "dns"))
VRT_init_dir_dns(cli, dir, idx, priv);
else if (!strcmp(name, "round-robin")) else if (!strcmp(name, "round-robin"))
VRT_init_dir_round_robin(cli, dir, idx, priv); VRT_init_dir_round_robin(cli, dir, idx, priv);
else if (!strcmp(name, "client")) else if (!strcmp(name, "client"))
......
/*-
* Copyright (c) 2009 Redpill Linpro AS
* Copyright (c) 2010 Varnish Software AS
* All rights reserved.
*
* Author: Kristian Lyngstol <kristian@redpill-linpro.com>
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 "config.h"
#include "svnid.h"
SVNID("$Id$")
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <unistd.h>
#include <stdio.h>
#include "cache.h"
#include "cache_backend.h"
#include "vrt.h"
/*--------------------------------------------------------------------*/
/* FIXME: Should eventually be a configurable variable. */
#define VDI_DNS_MAX_CACHE 1024
#define VDI_DNS_GROUP_MAX_BACKENDS 1024
/* DNS Cache entry
*/
struct vdi_dns_hostgroup {
unsigned magic;
#define VDI_DNSDIR_MAGIC 0x1bacab21
char *hostname;
struct director *hosts[VDI_DNS_GROUP_MAX_BACKENDS];
unsigned nhosts;
unsigned next_host; /* Next to use...*/
double ttl;
VTAILQ_ENTRY(vdi_dns_hostgroup) list;
};
struct vdi_dns {
unsigned magic;
#define VDI_DNS_MAGIC 0x1337a178
struct director dir;
struct director **hosts;
unsigned nhosts;
VTAILQ_HEAD(_cachelist,vdi_dns_hostgroup) cachelist;
unsigned ncachelist;
pthread_rwlock_t rwlock;
const char *suffix;
double ttl;
const unsigned max_cache_size;
};
/* Compare an IPv4 backend to a IPv4 addr/len */
static int
vdi_dns_comp_addrinfo4(struct backend *bp,
const struct sockaddr_in *addr,
const socklen_t len)
{
uint32_t u, p;
struct sockaddr_in *bps = (struct sockaddr_in *) bp->ipv4;
if (bp->ipv4len != len || len <= 0)
return 0;
u = addr->sin_addr.s_addr;
p = bps->sin_addr.s_addr;
return u == p;
}
/* Compare an IPv6 backend to a IPv6 addr/len */
static int
vdi_dns_comp_addrinfo6(struct backend *bp,
struct sockaddr_in6 *addr,
const socklen_t len)
{
uint8_t *u, *p;
int i;
struct sockaddr_in6 *bps = (struct sockaddr_in6 *) bp->ipv6;
if (bp->ipv6len != len || len <= 0)
return 0;
u = addr->sin6_addr.s6_addr;
p = bps->sin6_addr.s6_addr;
for (i=0; i < 16; i++) {
if (u[i] != p[i])
return 0;
}
return 1;
}
struct backend *
vdi_get_backend_if_simple(const struct director *d);
/* Check if a backends socket is the same as addr */
static int
vdi_dns_comp_addrinfo(struct director *dir,
struct sockaddr *addr,
const socklen_t len)
{
struct backend *bp;
bp = vdi_get_backend_if_simple(dir);
if (addr->sa_family == PF_INET && bp->ipv4) {
return (vdi_dns_comp_addrinfo4(bp, (struct sockaddr_in *)
addr, len));
} else if (addr->sa_family == PF_INET6 && bp->ipv6) {
return (vdi_dns_comp_addrinfo6(bp, (struct sockaddr_in6 *)
addr, len));
}
return 0;
}
/* Pick a host from an existing hostgroup.
* Balance on round-robin if multiple backends are available and only pick
* healthy ones.
*/
static struct director *
vdi_dns_pick_host(const struct sess *sp, struct vdi_dns_hostgroup *group) {
int initial, i, nhosts, current;
if (group->nhosts == 0)
return (NULL); // In case of error.
if (group->next_host >= group->nhosts)
group->next_host = 0;
/* Pick a healthy backend */
initial = group->next_host;
nhosts = group->nhosts;
for (i=0; i < nhosts; i++) {
if (i + initial >= nhosts)
current = i + initial - nhosts;
else
current = i + initial;
if (VBE_Healthy_sp(sp, group->hosts[current])) {
group->next_host = current+1;
return group->hosts[current];
}
}
return NULL;
}
/* Remove an item from the dns cache.
* If *group is NULL, the head is popped.
* Remember locking.
*/
static void
vdi_dns_pop_cache(struct vdi_dns *vs,
struct vdi_dns_hostgroup *group)
{
if (group == NULL)
group = VTAILQ_LAST( &vs->cachelist, _cachelist );
assert(group != NULL);
free(group->hostname);
VTAILQ_REMOVE(&vs->cachelist, group, list);
FREE_OBJ(group);
vs->ncachelist--;
}
/* Dummy in case someone feels like optimizing it? meh...
*/
static inline int
vdi_dns_groupmatch(const struct vdi_dns_hostgroup *group, const char *hostname)
{
return !strcmp(group->hostname, hostname);
}
/* Search the cache for 'hostname' and put a backend-pointer as necessary,
* return true for cache hit. This could still be a NULL backend if we did
* a lookup earlier and didn't find a host (ie: cache failed too)
*
* if rwlock is true, the first timed out object found (if any) is popped
* and freed.
*/
static int
vdi_dns_cache_has(const struct sess *sp,
struct vdi_dns *vs,
const char *hostname,
struct director **backend,
int rwlock)
{
struct director *ret;
struct vdi_dns_hostgroup *hostgr;
struct vdi_dns_hostgroup *hostgr2;
VTAILQ_FOREACH_SAFE(hostgr, &vs->cachelist, list, hostgr2) {
CHECK_OBJ_NOTNULL(hostgr, VDI_DNSDIR_MAGIC);
if (hostgr->ttl <= sp->t_req) {
if (rwlock)
vdi_dns_pop_cache(vs, hostgr);
return 0;
}
if (vdi_dns_groupmatch(hostgr, hostname)) {
ret = (vdi_dns_pick_host(sp, hostgr));
*backend = ret;
if (*backend != NULL)
CHECK_OBJ_NOTNULL(*backend, DIRECTOR_MAGIC);
return 1;
}
}
return 0;
}
/* Add a newly cached item to the dns cache list.
* (Sorry for the list_add/_add confusion...)
*/
static void
vdi_dns_cache_list_add(const struct sess *sp,
struct vdi_dns *vs,
struct vdi_dns_hostgroup *new)
{
if (vs->ncachelist >= VDI_DNS_MAX_CACHE) {
VSC_main->dir_dns_cache_full++;
vdi_dns_pop_cache(vs, NULL);
}
CHECK_OBJ_NOTNULL(new, VDI_DNSDIR_MAGIC);
assert(new->hostname != 0);
new->ttl = sp->t_req + vs->ttl;
VTAILQ_INSERT_HEAD(&vs->cachelist, new, list);
vs->ncachelist++;
}
/* Add an item to the dns cache.
* XXX: Might want to factor the getaddrinfo() out of the lock and do the
* cache_has() afterwards to do multiple dns lookups in parallel...
*/
static int
vdi_dns_cache_add(const struct sess *sp,
struct vdi_dns *vs,
const char *hostname,
struct director **backend)
{
int error, i, host = 0;
struct addrinfo *res0, *res, hint;
struct vdi_dns_hostgroup *new;
/* Due to possible race while upgrading the lock, we have to
* recheck if the result is already looked up. The overhead for
* this is insignificant unless dns isn't cached properly (all
* unique names or something equally troublesome).
*/
if (vdi_dns_cache_has(sp, vs, hostname, backend, 1))
return 1;
memset(&hint, 0, sizeof hint);
hint.ai_family = PF_UNSPEC;
hint.ai_socktype = SOCK_STREAM;
ALLOC_OBJ(new, VDI_DNSDIR_MAGIC);
new->hostname = calloc(sizeof(char), strlen(hostname)+1);
assert(new->hostname != NULL);
strcpy(new->hostname, hostname);
error = getaddrinfo(hostname, "80", &hint, &res0);
VSC_main->dir_dns_lookups++;
if (error) {
vdi_dns_cache_list_add(sp, vs, new);
VSC_main->dir_dns_failed++;
return 0;
}
for (res = res0; res; res = res->ai_next) {
if (res->ai_family != PF_INET &&
res->ai_family != PF_INET6)
continue;
for (i = 0; i < vs->nhosts; i++) {
if (vdi_dns_comp_addrinfo(vs->hosts[i],
res->ai_addr, res->ai_addrlen)) {
new->hosts[host] = vs->hosts[i];
CHECK_OBJ_NOTNULL(new->hosts[host], DIRECTOR_MAGIC);
host++;
}
}
}
freeaddrinfo(res0);
new->nhosts = host;
vdi_dns_cache_list_add(sp, vs, new);
*backend = vdi_dns_pick_host(sp, new);
return 1;
}
/* Walk through the cached lookups looking for the relevant host, add one
* if it isn't already cached.
*
* Returns a backend or NULL.
*/
static struct director *
vdi_dns_walk_cache(const struct sess *sp,
struct vdi_dns *vs,
const char *hostname)
{
struct director *backend = NULL;
int ret;
AZ(pthread_rwlock_rdlock(&vs->rwlock));
ret = vdi_dns_cache_has(sp, vs, hostname, &backend, 0);
pthread_rwlock_unlock(&vs->rwlock);
if (!ret) {
AZ(pthread_rwlock_wrlock(&vs->rwlock));
ret = vdi_dns_cache_add(sp, vs, hostname, &backend);
pthread_rwlock_unlock(&vs->rwlock);
} else
VSC_main->dir_dns_hit++;
/* Bank backend == cached a failure, so to speak */
if (backend != NULL)
CHECK_OBJ_NOTNULL(backend, DIRECTOR_MAGIC);
return backend;
}
/* Parses the Host:-header and heads out to find a backend.
*/
static struct director *
vdi_dns_find_backend(const struct sess *sp, struct vdi_dns *vs)
{
struct director *ret;
struct http *hp;
char *p;
char hostname[NI_MAXHOST];
int i;
/* bereq is only present after recv et. al, otherwise use req (ie:
* use req for health checks in vcl_recv and such).
*/
if (sp->wrk->bereq)
hp = sp->wrk->bereq;
else
hp = sp->http;
CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);
if (http_GetHdr(hp, H_Host, &p) == 0)
return (NULL);
/* We need a working copy since it's going to be modified */
strncpy(hostname, p, sizeof(hostname));
/* remove port-portion of the Host-header, if present. */
for (i = 0; i < strlen(hostname); i++) {
if (hostname[i] == ':') {
hostname[i] = '\0';
break;
}
}
if (vs->suffix)
strncat(hostname, vs->suffix, sizeof(hostname) - strlen(hostname));
ret = vdi_dns_walk_cache(sp, vs, hostname);
return ret;
}
static struct vbe_conn *
vdi_dns_getfd(const struct director *director, struct sess *sp)
{
int i;
struct vdi_dns *vs;
struct director *dir;
struct vbe_conn *vbe;
CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
CHECK_OBJ_NOTNULL(director, DIRECTOR_MAGIC);
CAST_OBJ_NOTNULL(vs, director->priv, VDI_DNS_MAGIC);
dir = vdi_dns_find_backend(sp, vs);
if (!dir || !VBE_Healthy_sp(sp, dir))
return (NULL);
vbe = VBE_GetFd(dir, sp);
return (vbe);
}
static unsigned
vdi_dns_healthy(double now, const struct director *dir, uintptr_t target)
{
return 1;
/*
struct vdi_dns *vs;
struct director *dir;
int i;
CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
CHECK_OBJ_NOTNULL(sp->director, DIRECTOR_MAGIC);
CAST_OBJ_NOTNULL(vs, sp->director->priv, VDI_DNS_MAGIC);
dir = vdi_dns_find_backend(sp, vs);
if (dir)
return 1;
return 0;
*/
}
/*lint -e{818} not const-able */
static void
vdi_dns_fini(struct director *d)
{
int i;
struct vdi_dns *vs;
struct director **vh;
CHECK_OBJ_NOTNULL(d, DIRECTOR_MAGIC);
CAST_OBJ_NOTNULL(vs, d->priv, VDI_DNS_MAGIC);
vh = vs->hosts;
free(vs->hosts);
free(vs->dir.vcl_name);
vs->dir.magic = 0;
/* FIXME: Free the cache */
pthread_rwlock_destroy(&vs->rwlock);
FREE_OBJ(vs);
}
void
VRT_init_dir_dns(struct cli *cli, struct director **bp, int idx,
const void *priv)
{
const struct vrt_dir_dns *t;
struct vdi_dns *vs;
const struct vrt_dir_dns_entry *te;
int i;
ASSERT_CLI();
(void)cli;
t = priv;
ALLOC_OBJ(vs, VDI_DNS_MAGIC);
XXXAN(vs);
vs->hosts = calloc(sizeof(struct director *), t->nmember);
XXXAN(vs->hosts);
vs->dir.magic = DIRECTOR_MAGIC;
vs->dir.priv = vs;
vs->dir.name = "dns";
REPLACE(vs->dir.vcl_name, t->name);
vs->dir.getfd = vdi_dns_getfd;
vs->dir.fini = vdi_dns_fini;
vs->dir.healthy = vdi_dns_healthy;
vs->suffix = t->suffix;
vs->ttl = t->ttl;
te = t->members;
for (i = 0; i < t->nmember; i++, te++)
vs->hosts[i] = bp[te->host];
vs->nhosts = t->nmember;
vs->ttl = t->ttl;
VTAILQ_INIT(&vs->cachelist);
pthread_rwlock_init(&vs->rwlock, NULL);
bp[idx] = &vs->dir;
}
...@@ -141,6 +141,34 @@ The round-robin director ...@@ -141,6 +141,34 @@ The round-robin director
The round-robin does not take any options. The round-robin does not take any options.
The DNS director
~~~~~~~~~~~~~~~~
The DNS director can use backends in three different ways. Either like the
random or round-robin director or using .list::
director directorname dns {
.list = {
.host_header = "www.example.com";
.port = "80";
.connection_timeout = 0.4;
"192.168.15.0"/24;
"192.168.16.128"/25;
}
.ttl = 5m;
.suffix = "internal.example.net";
}
This will specify 384 backends, all using port 80 and a connection timeout
of 0.4s. Options must come before the list of IPs in the .list statement.
The .ttl defines the cache duration of the DNS lookups.
The above example will append "internal.example.net" to the incoming Host
header supplied by the client, before looking it up. All settings are
optional.
Backend probes Backend probes
-------------- --------------
......
...@@ -153,6 +153,12 @@ MAC_STAT(accept_fail, uint64_t, 0, 'a', "Accept failures") ...@@ -153,6 +153,12 @@ MAC_STAT(accept_fail, uint64_t, 0, 'a', "Accept failures")
MAC_STAT(client_drop_late, uint64_t, 0, 'a', "Connection dropped late") MAC_STAT(client_drop_late, uint64_t, 0, 'a', "Connection dropped late")
MAC_STAT(uptime, uint64_t, 0, 'a', "Client uptime") MAC_STAT(uptime, uint64_t, 0, 'a', "Client uptime")
MAC_STAT(dir_dns_lookups, uint64_t, 0, 'a', "DNS director lookups")
MAC_STAT(dir_dns_failed, uint64_t, 0, 'a', "DNS director failed lookups")
MAC_STAT(dir_dns_hit, uint64_t, 0, 'a', "DNS director cached lookups hit")
MAC_STAT(dir_dns_cache_full, uint64_t, 0, 'a', "DNS director full dnscache")
MAC_STAT(critbit_cooler, uint64_t, 0, 'i', "Objhdr's on cool list") MAC_STAT(critbit_cooler, uint64_t, 0, 'i', "Objhdr's on cool list")
#ifdef __MAC_STAT #ifdef __MAC_STAT
......
...@@ -107,6 +107,21 @@ struct vrt_dir_round_robin { ...@@ -107,6 +107,21 @@ struct vrt_dir_round_robin {
const struct vrt_dir_round_robin_entry *members; const struct vrt_dir_round_robin_entry *members;
}; };
/*
* A director with dns-based selection
*/
struct vrt_dir_dns_entry {
int host;
};
struct vrt_dir_dns {
const char *name;
const char *suffix;
const double ttl;
unsigned nmember;
const struct vrt_dir_dns_entry *members;
};
/* /*
* other stuff. * other stuff.
......
...@@ -154,6 +154,12 @@ VSC_F_MAIN(accept_fail, uint64_t, 0, 'a', "Accept failures") ...@@ -154,6 +154,12 @@ VSC_F_MAIN(accept_fail, uint64_t, 0, 'a', "Accept failures")
VSC_F_MAIN(client_drop_late, uint64_t, 0, 'a', "Connection dropped late") VSC_F_MAIN(client_drop_late, uint64_t, 0, 'a', "Connection dropped late")
VSC_F_MAIN(uptime, uint64_t, 0, 'a', "Client uptime") VSC_F_MAIN(uptime, uint64_t, 0, 'a', "Client uptime")
VSC_F_MAIN(dir_dns_lookups, uint64_t, 0, 'a', "DNS director lookups")
VSC_F_MAIN(dir_dns_failed, uint64_t, 0, 'a', "DNS director failed lookups")
VSC_F_MAIN(dir_dns_hit, uint64_t, 0, 'a', "DNS director cached lookups hit")
VSC_F_MAIN(dir_dns_cache_full, uint64_t, 0, 'a', "DNS director full dnscache")
VSC_F_MAIN(critbit_cooler, uint64_t, 0, 'i', "Objhdr's on cool list") VSC_F_MAIN(critbit_cooler, uint64_t, 0, 'i', "Objhdr's on cool list")
#ifdef __VSC_F_MAIN #ifdef __VSC_F_MAIN
......
...@@ -19,6 +19,7 @@ libvcl_la_SOURCES = \ ...@@ -19,6 +19,7 @@ libvcl_la_SOURCES = \
vcc_compile.c \ vcc_compile.c \
vcc_dir_random.c \ vcc_dir_random.c \
vcc_dir_round_robin.c \ vcc_dir_round_robin.c \
vcc_dir_dns.c \
vcc_expr.c \ vcc_expr.c \
vcc_parse.c \ vcc_parse.c \
$(builddir)/vcc_fixed_token.c \ $(builddir)/vcc_fixed_token.c \
......
...@@ -96,7 +96,7 @@ CheckHostPort(const char *host, const char *port) ...@@ -96,7 +96,7 @@ CheckHostPort(const char *host, const char *port)
* and put it in an official sockaddr when we load the VCL. * and put it in an official sockaddr when we load the VCL.
*/ */
static void void
Emit_Sockaddr(struct vcc *tl, const struct token *t_host, Emit_Sockaddr(struct vcc *tl, const struct token *t_host,
const char *port) const char *port)
{ {
...@@ -189,7 +189,7 @@ Emit_Sockaddr(struct vcc *tl, const struct token *t_host, ...@@ -189,7 +189,7 @@ Emit_Sockaddr(struct vcc *tl, const struct token *t_host,
* in that context. * in that context.
*/ */
static void void
vcc_EmitBeIdent(const struct vcc *tl, struct vsb *v, vcc_EmitBeIdent(const struct vcc *tl, struct vsb *v,
int serial, const struct token *first, const struct token *last) int serial, const struct token *first, const struct token *last)
{ {
...@@ -696,6 +696,7 @@ static const struct dirlist { ...@@ -696,6 +696,7 @@ static const struct dirlist {
{ "random", vcc_ParseRandomDirector }, { "random", vcc_ParseRandomDirector },
{ "client", vcc_ParseRandomDirector }, { "client", vcc_ParseRandomDirector },
{ "round-robin", vcc_ParseRoundRobinDirector }, { "round-robin", vcc_ParseRoundRobinDirector },
{ "dns", vcc_ParseDnsDirector },
{ NULL, NULL } { NULL, NULL }
}; };
......
...@@ -222,6 +222,9 @@ unsigned vcc_UintVal(struct vcc *tl); ...@@ -222,6 +222,9 @@ unsigned vcc_UintVal(struct vcc *tl);
double vcc_DoubleVal(struct vcc *tl); double vcc_DoubleVal(struct vcc *tl);
void vcc_Expr(struct vcc *tl, enum var_type fmt); void vcc_Expr(struct vcc *tl, enum var_type fmt);
/* vcc_dir_dns.c */
parsedirector_f vcc_ParseDnsDirector;
/* vcc_obj.c */ /* vcc_obj.c */
extern const struct var vcc_vars[]; extern const struct var vcc_vars[];
......
/*-
* Copyright (c) 2009 Redpill Linpro AS
* Copyright (c) 2010 Varnish Software AS
* All rights reserved.
*
* Author: Kristian Lyngstol <kristian@bohemians.org>
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 "config.h"
#include "svnid.h"
SVNID("$Id$")
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <limits.h>
#include "vsb.h"
#include "vcc_priv.h"
#include "vcc_compile.h"
#include "libvarnish.h"
/*--------------------------------------------------------------------
* Parse directors
*/
void
vcc_EmitBeIdent(const struct vcc *tl, struct vsb *v,
int serial, const struct token *first, const struct token *last);
void
Emit_Sockaddr(struct vcc *tl, const struct token *t_host,
const char *port);
struct vcc_dir_backend_defaults {
char *port;
char *hostheader;
double connect_timeout;
double first_byte_timeout;
double between_bytes_timeout;
unsigned max_connections;
unsigned saint;
} b_defaults;
void vcc_dir_initialize_defaults(void)
{
b_defaults.port = NULL;
b_defaults.hostheader = NULL;
b_defaults.connect_timeout = -1.0;
b_defaults.first_byte_timeout = -1.0;
b_defaults.between_bytes_timeout = -1.0;
b_defaults.max_connections = UINT_MAX;
b_defaults.saint = UINT_MAX;
}
struct token *dns_first;
void
print_backend(struct vcc *tl,
uint32_t serial,
uint8_t *ip)
{
char vgcname[BUFSIZ];
char strip[16];
struct token tmptok;
struct vsb *vsb;
sprintf(strip, "%d.%d.%d.%d",ip[3],ip[2],ip[1],ip[0]);
tmptok.dec = strip;
sprintf(vgcname,"%.*s_%u",PF(tl->t_dir),serial);
vsb = vsb_newauto();
AN(vsb);
tl->fb = vsb;
Fc(tl, 0, "\t{ .host = VGC_backend_%s },\n",vgcname);
Fh(tl, 1, "\n#define VGC_backend_%s %u\n", vgcname, serial);
Fb(tl, 0, "\nstatic const struct vrt_backend vgc_dir_priv_%s = {\n", vgcname);
Fb(tl, 0, "\t.vcl_name = \"%.*s", PF(tl->t_dir));
if (serial >= 0)
Fb(tl, 0, "[%d]", serial);
Fb(tl, 0, "\",\n");
Emit_Sockaddr(tl, &tmptok, b_defaults.port);
vcc_EmitBeIdent(tl, tl->fb, serial, dns_first , tl->t);
Fb(tl, 0, "\t.hosthdr = \"");
if (b_defaults.hostheader != NULL)
Fb(tl,0, b_defaults.hostheader);
else
Fb(tl,0, strip);
Fb(tl, 0, "\",\n");
Fb(tl, 0, "\t.saintmode_threshold = %d,\n",b_defaults.saint);
#define FB_TIMEOUT(type) do { \
if (b_defaults.type != -1.0) \
Fb(tl, 0, "\t.%s = %g,\n",#type,b_defaults.type); \
} while (0)
FB_TIMEOUT(connect_timeout);
FB_TIMEOUT(first_byte_timeout);
FB_TIMEOUT(between_bytes_timeout);
Fb(tl, 0, "};\n");
tl->fb = NULL;
vsb_finish(vsb);
Fh(tl, 0, "%s", vsb_data(vsb));
vsb_delete(vsb);
Fi(tl, 0, "\tVRT_init_dir(cli, VCL_conf.director, \"simple\",\n"
"\t VGC_backend_%s, &vgc_dir_priv_%s);\n", vgcname, vgcname);
Ff(tl, 0, "\tVRT_fini_dir(cli, VGCDIR(%s));\n", vgcname);
tl->ndirector++;
}
/*
* Output backends for all IPs in the range supplied by
* "a[0].a[1].a[2].a[3]/inmask".
*
* XXX:
* This assumes that a uint32_t can be safely accessed as an array of 4
* uint8_ts.
*/
void
vcc_dir_dns_makebackend(struct vcc *tl,
uint32_t *serial,
unsigned char a[],
int inmask)
{
uint32_t ip4=0;
uint32_t ip4end;
uint32_t mask = UINT32_MAX << (32-inmask);
ip4 |= a[0] << 24;
ip4 |= a[1] << 16;
ip4 |= a[2] << 8;
ip4 |= a[3] ;
ip4end = ip4 | ~mask;
assert (ip4 == (ip4 & mask));
/* printf("uip4: \t0x%.8X\na: \t0x", ip4,ip4);
for (int i=0;i<4;i++) printf("%.2X",a[i]);
printf("\nmask:\t0x%.8X\nend:\t0x%.8X\n", mask, ip4end);
*/
while (ip4 <= ip4end) {
uint8_t *b;
b=(uint8_t *)&ip4;
(*serial)++;
print_backend(tl, *serial, b);
ip4++;
}
}
void
vcc_dir_dns_parse_backend_options(struct vcc *tl)
{
struct fld_spec *fs;
struct token *t_field;
double t;
unsigned u;
vcc_dir_initialize_defaults();
fs = vcc_FldSpec(tl,
"?port",
"?host_header",
"?connect_timeout",
"?first_byte_timeout",
"?between_bytes_timeout",
"?max_connections",
"?saintmode_threshold",
NULL);
while (tl->t->tok != CSTR) {
vcc_IsField(tl, &t_field, fs);
ERRCHK(tl);
if (vcc_IdIs(t_field, "port")) {
ExpectErr(tl, CSTR);
assert(tl->t->dec != NULL);
b_defaults.port = strdup(tl->t->dec);
assert(b_defaults.port);
vcc_NextToken(tl);
SkipToken(tl, ';');
} else if (vcc_IdIs(t_field, "host_header")) {
ExpectErr(tl, CSTR);
assert(tl->t->dec != NULL);
b_defaults.hostheader = strdup(tl->t->dec);
assert(b_defaults.hostheader);
vcc_NextToken(tl);
SkipToken(tl, ';');
} else if (vcc_IdIs(t_field, "connect_timeout")) {
vcc_TimeVal(tl, &t);
ERRCHK(tl);
b_defaults.connect_timeout = t;
SkipToken(tl, ';');
} else if (vcc_IdIs(t_field, "first_byte_timeout")) {
vcc_TimeVal(tl, &t);
ERRCHK(tl);
b_defaults.first_byte_timeout = t;
SkipToken(tl, ';');
} else if (vcc_IdIs(t_field, "between_bytes_timeout")) {
vcc_TimeVal(tl, &t);
ERRCHK(tl);
b_defaults.between_bytes_timeout = t;
SkipToken(tl, ';');
} else if (vcc_IdIs(t_field, "max_connections")) {
u = vcc_UintVal(tl);
ERRCHK(tl);
SkipToken(tl, ';');
b_defaults.max_connections = u;
} else if (vcc_IdIs(t_field, "saintmode_threshold")) {
u = vcc_UintVal(tl);
/* UINT_MAX == magic number to mark as unset, so
* not allowed here.
*/
if (u == UINT_MAX) {
vsb_printf(tl->sb,
"Value outside allowed range: ");
vcc_ErrToken(tl, tl->t);
vsb_printf(tl->sb, " at\n");
vcc_ErrWhere(tl, tl->t);
}
ERRCHK(tl);
b_defaults.saint = u;
SkipToken(tl, ';');
} else {
ErrInternal(tl);
return;
}
}
}
/* Parse a list of backends with optional /mask notation, then print out
* all relevant backends.
*/
void
vcc_dir_dns_parse_list(struct vcc *tl, int *serial)
{
unsigned char a[4],mask;
int ret, nitem;
ERRCHK(tl);
SkipToken(tl, '{');
if (tl->t->tok != CSTR)
vcc_dir_dns_parse_backend_options(tl);
while (tl->t->tok == CSTR) {
mask = 32;
ret = sscanf(tl->t->dec, "%d.%d.%d.%d",&a[0],&a[1],&a[2],&a[3],&a[4]);
assert(ret == 4);
vcc_NextToken(tl);
if (tl->t->tok == '/') {
vcc_NextToken(tl);
mask = vcc_UintVal(tl);
ERRCHK(tl);
}
vcc_dir_dns_makebackend(tl,serial,a,mask);
SkipToken(tl,';');
}
ExpectErr(tl, '}');
}
void
vcc_ParseDnsDirector(struct vcc *tl)
{
struct token *t_field, *t_be, *t_suffix = NULL;
double ttl = 60.0;
int nbh, nelem = 0;
struct fld_spec *fs;
const char *first;
char *p;
dns_first = tl->t;
tl->fb = tl->fc;
fs = vcc_FldSpec(tl, "!backend", "?ttl", "?suffix","?list", NULL);
Fc(tl, 0, "\nstatic const struct vrt_dir_dns_entry "
"vddnse_%.*s[] = {\n", PF(tl->t_dir));
for (; tl->t->tok != '}'; ) { /* List of members */
if (tl->t->tok == '{') {
nelem++;
first = "";
t_be = tl->t;
vcc_ResetFldSpec(fs);
nbh = -1;
ExpectErr(tl, '{');
vcc_NextToken(tl);
Fc(tl, 0, "\t{");
while (tl->t->tok != '}') { /* Member fields */
vcc_IsField(tl, &t_field, fs);
ERRCHK(tl);
if (vcc_IdIs(t_field, "backend")) {
vcc_ParseBackendHost(tl, nelem, &p);
ERRCHK(tl);
AN(p);
Fc(tl, 0, "%s .host = VGC_backend_%s", first, p);
} else {
ErrInternal(tl);
}
first = ", ";
}
vcc_FieldsOk(tl, fs);
if (tl->err) {
vsb_printf(tl->sb,
"\nIn member host specification starting at:\n");
vcc_ErrWhere(tl, t_be);
return;
}
Fc(tl, 0, " },\n");
} else {
vcc_IsField(tl, &t_field, fs);
ERRCHK(tl);
if (vcc_IdIs(t_field, "suffix")) {
ExpectErr(tl, CSTR);
t_suffix = tl->t;
vcc_NextToken(tl);
ExpectErr(tl, ';');
} else if (vcc_IdIs(t_field, "ttl")) {
vcc_RTimeVal(tl, &ttl);
ExpectErr(tl, ';');
} else if (vcc_IdIs(t_field, "list")) {
vcc_dir_dns_parse_list(tl,&nelem);
}
}
vcc_NextToken(tl);
}
Fc(tl, 0, "};\n");
Fc(tl, 0,
"\nstatic const struct vrt_dir_dns vgc_dir_priv_%.*s = {\n",
PF(tl->t_dir));
Fc(tl, 0, "\t.name = \"%.*s\",\n", PF(tl->t_dir));
Fc(tl, 0, "\t.nmember = %d,\n", nelem);
Fc(tl, 0, "\t.members = vddnse_%.*s,\n", PF(tl->t_dir));
Fc(tl, 0, "\t.suffix = ");
if (t_suffix)
Fc(tl, 0, "%.*s", PF(t_suffix));
else
Fc(tl, 0, "\"\"");
Fc(tl, 0, ",\n");
Fc(tl, 0, "\t.ttl = %f", ttl);
Fc(tl, 0, ",\n");
Fc(tl, 0, "};\n");
Ff(tl, 0, "\tVRT_fini_dir(cli, VGCDIR(_%.*s));\n", PF(tl->t_dir));
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment