Hi! On Wed, Sep 17, 2008 at 05:45:23PM +0200, Mikael Jansson wrote: > I use relayd with redirects to loadbalance between two webservers > one redirect is used for http requests and the other for https. > the redirects looks like the following: > > redirect web_http { > listen on $ext_ip1 port http > sticky-address > forward to <webservers> port http check script "/usr/local/sbin/chksrvs" > } > > redirect web_https { > listen on $ext_ip1 port https > sticky-address > forward to <webservers> port https check script "/usr/local/sbin/chksrvs" > } > > The redirects works fine separately and sticks to the same machine, > but when the user navigates from http to https the requests sometimes > move over to the other machine. I need the same source-ip to always > stay on the same server regardless of which destination port (http or https) > is being used. Any suggestions on how to achive this would be greatly > appreciated. >
it does not work without a patch. the problem is that the pf Source Tracking table includes a reference to the rule but your example above will load two different rules into pf - one matching http and another one matching https. the trick is to combine both statements into one rule. we don't support "port tables" in pf yet (which whould be very helpful in many cases) but there is support for a port range. so the "hack" is to allow port ranges in the relayd redirection block redirect web { listen on $ext_ip1 port 80:443 sticky-address forward to <webservers> port http check script "/usr/local/sbin/chksrvs" } note that this will match any traffic in the 80 - 443 port range, make sure that you add additional pf rules to filter any other ports except 80 and 443. but it works with Source Tracking and should allow your clients to move between http and https on the same server. another limitation is that it only runs checks on one of the ports. reyk Index: parse.y =================================================================== RCS file: /cvs/src/usr.sbin/relayd/parse.y,v retrieving revision 1.122 diff -u -p -I$OpenBSD.*$ -r1.122 parse.y --- parse.y 22 Jul 2008 23:17:37 -0000 1.122 +++ parse.y 17 Sep 2008 19:21:53 -0000 @@ -30,6 +30,7 @@ #include <sys/queue.h> #include <net/if.h> +#include <net/pfvar.h> #include <netinet/in.h> #include <arpa/inet.h> #include <arpa/nameser.h> @@ -100,11 +101,12 @@ static in_port_t tableport = 0; struct address *host_v4(const char *); struct address *host_v6(const char *); int host_dns(const char *, struct addresslist *, - int, in_port_t, const char *); + int, struct portrange *, const char *); int host(const char *, struct addresslist *, - int, in_port_t, const char *); + int, struct portrange *, const char *); struct table *table_inherit(struct table *); +int getservice(char *); typedef struct { union { @@ -113,6 +115,7 @@ typedef struct { struct host *host; struct timeval tv; struct table *table; + struct portrange port; struct { enum digest_type type; char *digest; @@ -134,8 +137,9 @@ typedef struct { %token <v.string> STRING %token <v.number> NUMBER %type <v.string> interface hostname table -%type <v.number> port http_type loglevel sslcache optssl mark parent +%type <v.number> http_type loglevel sslcache optssl mark parent %type <v.number> proto_type dstmode retry log flag direction forwardmode +%type <v.port> port %type <v.host> host %type <v.tv> timeout %type <v.digest> digest @@ -231,15 +235,29 @@ eflags : STYLE STRING ; port : PORT STRING { - struct servent *servent; + char *a, *b; + int p[2]; - servent = getservbyname($2, "tcp"); - if (servent == NULL) { - yyerror("port %s is invalid", $2); + p[0] = p[1] = 0; + + a = $2; + b = strchr($2, ':'); + if (b == NULL) + $$.op = PF_OP_EQ; + else { + *b++ = '\0'; + if ((p[1] = getservice(b)) == -1) { + free($2); + YYERROR; + } + $$.op = PF_OP_RRG; + } + if ((p[0] = getservice(a)) == -1) { free($2); YYERROR; } - $$ = servent->s_port; + $$.val[0] = p[0]; + $$.val[1] = p[1]; free($2); } | PORT NUMBER { @@ -247,7 +265,8 @@ port : PORT STRING { yyerror("invalid port: %d", $2); YYERROR; } - $$ = htons($2); + $$.val[0] = htons($2); + $$.op = PF_OP_EQ; } ; @@ -424,7 +443,7 @@ rdroptsl : forwardmode TO tablespec inte } | LISTEN ON STRING port interface { if (host($3, &rdr->virts, - SRV_MAX_VIRTS, $4, $5) <= 0) { + SRV_MAX_VIRTS, &$4, $5) <= 0) { yyerror("invalid virtual ip: %s", $3); free($3); free($5); @@ -433,7 +452,7 @@ rdroptsl : forwardmode TO tablespec inte free($3); free($5); if (rdr->conf.port == 0) - rdr->conf.port = $4; + rdr->conf.port = $4.val[0]; tableport = rdr->conf.port; } | DISABLE { rdr->conf.flags |= F_DISABLE; } @@ -539,6 +558,8 @@ tablespec : table { struct table *tb; if (table->conf.port == 0) table->conf.port = tableport; + else + table->conf.flags |= F_PORT; if ((tb = table_inherit(table)) == NULL) YYERROR; $$ = tb; @@ -550,7 +571,13 @@ tableopts_l : tableopts tableopts_l ; tableopts : CHECK tablecheck - | port { table->conf.port = $1; } + | port { + if ($1.op != PF_OP_EQ) { + yyerror("invalid port"); + YYERROR; + } + table->conf.port = $1.val[0]; + } | TIMEOUT timeout { bcopy(&$2, &table->conf.timeout, sizeof(struct timeval)); @@ -1117,11 +1144,17 @@ relayoptsl : LISTEN ON STRING port optss if (rlay->rl_conf.ss.ss_family != AF_UNSPEC) { yyerror("relay %s listener already specified", rlay->rl_conf.name); + free($3); + YYERROR; + } + if ($4.op != PF_OP_EQ) { + yyerror("invalid port"); + free($3); YYERROR; } TAILQ_INIT(&al); - if (host($3, &al, 1, $4, NULL) <= 0) { + if (host($3, &al, 1, &$4, NULL) <= 0) { yyerror("invalid listen ip: %s", $3); free($3); YYERROR; @@ -1129,12 +1162,12 @@ relayoptsl : LISTEN ON STRING port optss free($3); h = TAILQ_FIRST(&al); bcopy(&h->ss, &rlay->rl_conf.ss, sizeof(rlay->rl_conf.ss)); - rlay->rl_conf.port = h->port; + rlay->rl_conf.port = h->port.val[0]; if ($5) { rlay->rl_conf.flags |= F_SSL; conf->sc_flags |= F_SSL; } - tableport = h->port; + tableport = h->port.val[0]; } | forwardmode TO forwardspec interface dstaf { rlay->rl_conf.fwdmode = $1; @@ -1207,9 +1240,14 @@ forwardspec : tablespec { free($1); YYERROR; } + if ($2.op != PF_OP_EQ) { + yyerror("invalid port"); + free($1); + YYERROR; + } TAILQ_INIT(&al); - if (host($1, &al, 1, $2, NULL) <= 0) { + if (host($1, &al, 1, &$2, NULL) <= 0) { yyerror("invalid listen ip: %s", $1); free($1); YYERROR; @@ -1218,7 +1256,7 @@ forwardspec : tablespec { h = TAILQ_FIRST(&al); bcopy(&h->ss, &rlay->rl_conf.dstss, sizeof(rlay->rl_conf.dstss)); - rlay->rl_conf.dstport = h->port; + rlay->rl_conf.dstport = h->port.val[0]; rlay->rl_conf.dstretry = $3; } | NAT LOOKUP retry { @@ -1268,7 +1306,7 @@ host : STRING retry parent { fatal("out of memory"); TAILQ_INIT(&al); - if (host($1, &al, 1, 0, NULL) <= 0) { + if (host($1, &al, 1, NULL, NULL) <= 0) { yyerror("invalid host %s", $2); free($1); free($$); @@ -2041,7 +2079,7 @@ host_v6(const char *s) int host_dns(const char *s, struct addresslist *al, int max, - in_port_t port, const char *ifname) + struct portrange *port, const char *ifname) { struct addrinfo hints, *res0, *res; int error, cnt = 0; @@ -2068,7 +2106,8 @@ host_dns(const char *s, struct addressli if ((h = calloc(1, sizeof(*h))) == NULL) fatal(NULL); - h->port = port; + if (port != NULL) + bcopy(port, &h->port, sizeof(h->port)); if (ifname != NULL) { if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >= sizeof(h->ifname)) @@ -2102,7 +2141,7 @@ host_dns(const char *s, struct addressli int host(const char *s, struct addresslist *al, int max, - in_port_t port, const char *ifname) + struct portrange *port, const char *ifname) { struct address *h; @@ -2113,7 +2152,8 @@ host(const char *s, struct addresslist * h = host_v6(s); if (h != NULL) { - h->port = port; + if (port != NULL) + bcopy(port, &h->port, sizeof(h->port)); if (ifname != NULL) { if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >= sizeof(h->ifname)) { @@ -2199,4 +2239,26 @@ table_inherit(struct table *tb) TAILQ_INSERT_TAIL(conf->sc_tables, tb, entry); return (tb); +} + +int +getservice(char *n) +{ + struct servent *s; + const char *errstr; + long long llval; + + llval = strtonum(n, 0, UINT16_MAX, &errstr); + if (errstr) { + s = getservbyname(n, "tcp"); + if (s == NULL) + s = getservbyname(n, "udp"); + if (s == NULL) { + yyerror("unknown port %s", n); + return (-1); + } + return (s->s_port); + } + + return (htons((u_short)llval)); } Index: pfe_filter.c =================================================================== RCS file: /cvs/src/usr.sbin/relayd/pfe_filter.c,v retrieving revision 1.32 diff -u -p -I$OpenBSD.*$ -r1.32 pfe_filter.c --- pfe_filter.c 16 Jul 2008 14:38:33 -0000 1.32 +++ pfe_filter.c 17 Sep 2008 19:21:53 -0000 @@ -385,8 +385,9 @@ sync_ruleset(struct relayd *env, struct rio.rule.proto = IPPROTO_TCP; rio.rule.src.addr.type = PF_ADDR_ADDRMASK; rio.rule.dst.addr.type = PF_ADDR_ADDRMASK; - rio.rule.dst.port_op = PF_OP_EQ; - rio.rule.dst.port[0] = address->port; + rio.rule.dst.port_op = address->port.op; + rio.rule.dst.port[0] = address->port.val[0]; + rio.rule.dst.port[1] = address->port.val[1]; rio.rule.rtableid = -1; /* stay in the main routing table */ if (strlen(rdr->conf.tag)) @@ -422,8 +423,12 @@ sync_ruleset(struct relayd *env, struct if (ioctl(env->sc_pf->dev, DIOCADDADDR, &pio) == -1) fatal("sync_ruleset: cannot add address to pool"); - rio.rule.rpool.proxy_port[0] = ntohs(rdr->table->conf.port); - rio.rule.rpool.port_op = PF_OP_EQ; + if (address->port.op == PF_OP_EQ || + rdr->table->conf.flags & F_PORT) { + rio.rule.rpool.proxy_port[0] = + ntohs(rdr->table->conf.port); + rio.rule.rpool.port_op = PF_OP_EQ; + } rio.rule.rpool.opts = PF_POOL_ROUNDROBIN; if (rdr->conf.flags & F_STICKY) rio.rule.rpool.opts |= PF_POOL_STICKYADDR; Index: relayd.conf.5 =================================================================== RCS file: /cvs/src/usr.sbin/relayd/relayd.conf.5,v retrieving revision 1.93 diff -u -p -I$OpenBSD.*$ -r1.93 relayd.conf.5 --- relayd.conf.5 8 Aug 2008 22:49:33 -0000 1.93 +++ relayd.conf.5 17 Sep 2008 19:21:54 -0000 @@ -414,7 +414,7 @@ Specify the tables of target hosts to be section above for information about table options. If the .Ic port -option is not specified, the port from the +option is not specified, the first port from the .Ic listen on directive will be used. This directive can be specified twice \(en the second entry will be used @@ -432,6 +432,11 @@ to listen on. .Xr pf 4 will redirect incoming connections for the specified target to the hosts in the main or backup table. +The +.Ar port +argument can optionally specify a port range instead of a single port, +the format is +.Ar min-port Ic : Ar max-port . The rdr rule can be optionally restricted to a given interface name. .It Xo .Ic route to Index: relayd.h =================================================================== RCS file: /cvs/src/usr.sbin/relayd/relayd.h,v retrieving revision 1.110 diff -u -p -I$OpenBSD.*$ -r1.110 relayd.h --- relayd.h 11 Aug 2008 08:07:14 -0000 1.110 +++ relayd.h 17 Sep 2008 19:21:54 -0000 @@ -309,9 +309,14 @@ struct ctl_stats { u_int32_t last_day; }; +struct portrange { + in_port_t val[2]; + u_int8_t op; +}; + struct address { struct sockaddr_storage ss; - in_port_t port; + struct portrange port; char ifname[IFNAMSIZ]; TAILQ_ENTRY(address) entry; }; @@ -337,6 +342,7 @@ TAILQ_HEAD(addresslist, address); #define F_RETURN 0x00020000 #define F_TRAP 0x00040000 #define F_NEEDPF 0x00080000 +#define F_PORT 0x00100000 enum forwardmode { FWD_NORMAL = 0,