Unverified Commit 15a8e6c0 authored by Geoff Simmons's avatar Geoff Simmons Committed by Nils Goroll

Add the .authority field to backend definitions.

If the .via field is also set, then the value of .authority is set
as the authority TLV in the PROXY header. This gives the "true"
backend (usually the ssl-onloader) the opportunity to set the SNI
(HostName field) from the TLV value, for the TLS handshake with the
remote backend.

This mandates that PROXYv2 is always used with a via backend (since
only version 2 supports TLVs).

If the value of .authority is the empty string, then the TLV is not
sent. If .authority is not set for the backend, then fall back to
.host_header, which itself may have been a fallback to .host. Note
that if neither .authority nor .host_header is set, and .host is
set to an IP address, then the IP address is forwarded as the SNI
value, which is not permitted for HostName (RFC4366 ch 3.1). So
users are advised to set either .authority or .host_header, or set
.authority="", when .via is set.

Usage note with haproxy:

To enable sending SNI when haproxy is used as a TLS onloader, ``sni
fc_pp_authority`` needs to be used with the backend configuration.

Full usage example with haproxy 2.2:

listen sslon
	mode	tcp
	maxconn	1000
	bind	/shared/varnish_haproxy/haproxy_sslon accept-proxy mode 777
	stick-table type ip size 100
	stick	on dst
	server	s00 0.0.0.0:443 ssl ca-file /etc/ssl/certs/ca-bundle.crt alpn http/1.1 sni fc_pp_authority
	server	s01 0.0.0.0:443 ssl ca-file /etc/ssl/certs/ca-bundle.crt alpn http/1.1 sni fc_pp_authority
	# ...

A higher number of servers improves TLS session caching.
parent da539902
......@@ -576,8 +576,8 @@ via_resolve(VRT_CTX, const struct vrt_endpoint *vep, VCL_BACKEND via)
{
const struct backend *viabe = NULL;
AN(vep);
AN(via);
CHECK_OBJ_NOTNULL(vep, VRT_ENDPOINT_MAGIC);
CHECK_OBJ_NOTNULL(via, DIRECTOR_MAGIC);
if (vep->uds_path) {
VRT_fail(ctx, "Via is only supported for IP addresses");
......@@ -586,9 +586,16 @@ via_resolve(VRT_CTX, const struct vrt_endpoint *vep, VCL_BACKEND via)
via = VRT_DirectorResolve(ctx, via);
if (via != NULL &&
(via->vdir->methods == vbe_methods ||
via->vdir->methods == vbe_methods_noprobe))
if (via == NULL) {
VRT_fail(ctx, "Via resolution failed");
return (NULL);
}
CHECK_OBJ(via, DIRECTOR_MAGIC);
CHECK_OBJ_NOTNULL(via->vdir, VCLDIR_MAGIC);
if (via->vdir->methods == vbe_methods ||
via->vdir->methods == vbe_methods_noprobe)
CAST_OBJ_NOTNULL(viabe, via->priv, BACKEND_MAGIC);
if (viabe == NULL)
......@@ -601,18 +608,19 @@ via_resolve(VRT_CTX, const struct vrt_endpoint *vep, VCL_BACKEND via)
* construct a new endpoint identical to vep with sa in a proxy header
*/
static struct vrt_endpoint *
via_endpoint(const struct vrt_endpoint *vep, const struct suckaddr *sa)
via_endpoint(const struct vrt_endpoint *vep, const struct suckaddr *sa,
const char *auth)
{
struct vsb *preamble;
struct vrt_blob blob[1];
struct vrt_endpoint *nvep, *ret;
const struct suckaddr *client_bogo;
AN(vep);
CHECK_OBJ_NOTNULL(vep, VRT_ENDPOINT_MAGIC);
AN(sa);
nvep = VRT_Endpoint_Clone(vep);
AN(nvep);
CHECK_OBJ_NOTNULL(nvep, VRT_ENDPOINT_MAGIC);
if (VSA_Get_Proto(sa) == AF_INET6)
client_bogo = bogo_ip6;
......@@ -621,11 +629,12 @@ via_endpoint(const struct vrt_endpoint *vep, const struct suckaddr *sa)
preamble = VSB_new_auto();
AN(preamble);
VPX_Format_Proxy(preamble, 2, client_bogo, sa, NULL);
VPX_Format_Proxy(preamble, 2, client_bogo, sa, auth);
blob->blob = VSB_data(preamble);
blob->len = VSB_len(preamble);
nvep->preamble = blob;
ret = VRT_Endpoint_Clone(nvep);
CHECK_OBJ_NOTNULL(ret, VRT_ENDPOINT_MAGIC);
VSB_destroy(&preamble);
free(nvep);
......@@ -658,7 +667,7 @@ VRT_new_backend_clustered(VRT_CTX, struct vsmw_cluster *vc,
assert(vep->ipv4== NULL && vep->ipv6== NULL);
}
if (via) {
if (via != NULL) {
viabe = via_resolve(ctx, vep, via);
if (viabe == NULL)
return (NULL);
......@@ -701,7 +710,8 @@ VRT_new_backend_clustered(VRT_CTX, struct vsmw_cluster *vc,
VRT_VSC_Hide(be->vsc_seg);
if (viabe)
vep = be->endpoint = via_endpoint(viabe->endpoint, sa);
vep = be->endpoint = via_endpoint(viabe->endpoint, sa,
be->authority);
else
vep = be->endpoint = VRT_Endpoint_Clone(vep);
......
varnishtest "Test vcl defined via backends"
server s1 {
rxreq
txresp
rxreq
txresp
loop 5 {
rxreq
txresp
}
} -start
# the use case for via-proxy is to have a(n ha)proxy make a(n ssl)
......@@ -16,6 +16,7 @@ server s1 {
varnish v2 -proto PROXY -vcl {
import debug;
import std;
import proxy;
backend dummy { .host = "${bad_backend}"; }
......@@ -28,14 +29,13 @@ varnish v2 -proto PROXY -vcl {
set req.backend_hint = s1.backend();
return (pass);
}
sub vcl_deliver {
set resp.http.Authority = proxy.authority();
}
} -start
varnish v1 -vcl {
import debug;
import vtc;
backend dummy { .host = "${bad_backend}"; }
backend v2 { .host = "${v2_addr}"; .port = "${v2_port}"; }
backend s1 { .via = v2; .host = "${s1_addr}"; .port = "${s1_port}"; }
......@@ -48,9 +48,79 @@ client c1 {
txreq -url /1
rxresp
expect resp.status == 200
expect resp.http.Authority == "127.0.0.1"
txreq -url /2
rxresp
expect resp.status == 200
expect resp.http.Authority == "127.0.0.1"
} -run
varnish v1 -vcl {
backend v2 { .host = "${v2_addr}"; .port = "${v2_port}"; }
backend s1 {
.via = v2;
.host = "${s1_addr}";
.port = "${s1_port}";
.authority = "authority.com";
}
sub vcl_recv {
set req.backend_hint = s1;
}
}
client c1 {
txreq -url /3
rxresp
expect resp.status == 200
expect resp.http.Authority == "authority.com"
} -run
varnish v1 -vcl {
backend v2 { .host = "${v2_addr}"; .port = "${v2_port}"; }
backend s1 {
.via = v2;
.host = "${s1_addr}";
.port = "${s1_port}";
.host_header = "host.com";
}
sub vcl_recv {
set req.backend_hint = s1;
}
}
client c1 {
txreq -url /4
rxresp
expect resp.status == 200
expect resp.http.Authority == "host.com"
} -run
# Setting .authority = "" disables sending the TLV.
varnish v1 -vcl {
backend v2 { .host = "${v2_addr}"; .port = "${v2_port}"; }
backend s1 {
.via = v2;
.host = "${s1_addr}";
.port = "${s1_port}";
.authority = "";
}
sub vcl_recv {
set req.backend_hint = s1;
}
}
client c1 {
txreq -url /5
rxresp
expect resp.status == 200
# vmod_proxy returns the empty string if the TLV is absent.
expect resp.http.Authority == ""
} -run
varnish v1 -errvcl "Cannot set both .via and .path" {
......
varnishtest "TLV authority over via backends used as SNI for haproxy backend/1"
# This test is skipped unless haproxy is available. It fails unless
# that binary implements the fc_pp_authority fetch, to return the TLV
# Authority value sent in a PROXYv2 header.
# In this version of the test, we set port 0 in the server config of
# the "ssl-onloading" haproxy, and set the destination port in the
# backend config for Varnish. The onloader sets its destination port
# from the address forwarded via PROXY, which in turn is set from the
# Varnish backend config. See c00101.vtc for another config method.
feature ignore_unknown_macro
feature cmd {haproxy --version 2>&1 | grep -q 'HA-*Proxy version'}
server s1 {
rxreq
txresp -hdr "Foo: bar"
} -start
haproxy h1 -conf {
listen feh1
mode http
bind "fd@${feh1}" ssl verify none crt ${testdir}/common.pem
server s1 ${s1_addr}:${s1_port}
http-response set-header X-SNI %[ssl_fc_sni]
} -start
# Note the use of port 0 for server s0.
haproxy h2 -conf {
listen clear-to-ssl
bind unix@"${tmpdir}/h2.sock" accept-proxy
server s0 0.0.0.0:0 ssl verify none sni fc_pp_authority
} -start
varnish v1 -vcl {
backend h2 { .path = "${tmpdir}/h2.sock"; }
# The ssl-onloader uses the port number set here.
backend h1 {
.via = h2;
.host = "${h1_feh1_addr}";
.port = "${h1_feh1_port}";
.authority = "authority.com";
}
sub vcl_recv {
set req.backend_hint = h1;
}
} -start
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.Foo == "bar"
expect resp.http.X-SNI == "authority.com"
} -run
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAnb0BDF7FsqzslakNg7u/n/JQkq6nheuKwvyTqECfpc9y7uSB
e/vrEFqBaDSLQagJxuZdL5geFeVtRbdAoB97N1/LZa6vecjjgGSP0Aag/gS/ocnM
RIyvlVWWT9MrD46OG3qZY1ORU1ltrVL0NKttJP8xME7j3bTwIDElx/hNI0n7L+yS
kAe2xb/7CbZRfoOhjTVAcGv4aSLVc/Hi8k6VkIzdOEtH6TcghXmuGcuqvLNH9Buo
syngKTcQ8zg6J+e64aVvC+e7vi94uil9Qu+JHm0pkDzAZ2WluNsuXlrJToPirWyj
6/YdN6xgSI1hbZkBmUPAebgYuxBt6huvfyQd3wIDAQABAoIBABojc8UE/2W4WgwC
04Z82ig7Ezb7Ui9S9M+S4zUCYHItijIkE4DkIfO3y7Hk4x6iJdyb191HK9UdC5p9
32upS9XFPgM/izx3GZvxDhO+xXbSep7ovbyuQ3pPkHTx3TTavpm3GyvmcTKKoy4R
jP4dWhzDXPdQW1ol3ZS4EDau4rlyClY6oi1mq9aBEX3MqVjB/nO7s2AbdgclAgP2
OZMhTzWYR1k5tYySHCXh3ggGMCikyvHU0+SsGyrstYzP1VYi/n3f0VgqW/5ZjG8x
6SHpe04unErPF3HuSun2ZMCFdBxaTFZ8FENb8evrSXe3nQOc9W21RQdRRrNNUbjl
JYI4veECgYEA0ATYKMS1VCUYRZoQ49b5GTg7avUYqfW4bEo4fSfBue8NrnKR3Wu8
PPBiCTuIYq1vSF+60B7Vu+hW0A8OuQ2UuMxLpYcQ7lKfNad/+yAfoWWafIqCqNU9
at0QMdbW6A69d6jZt7OrXtleBsphCnN58jTz4ch4PIa2Oyq46NUXCvUCgYEAwh8t
G6BOHOs3yRNI2s9Y9EEfwoil2uIKrZhqiL3AwdIpu5uNIMuPnbaEpXvRX6jv/qtL
321i8vZLc31aM7zfxQ6B4ReQFJfYC80FJsWvcLwT9hB9mTJpLS4sIu5tzQc87O6w
RtjFMom+5ns5hfPB4Eccy0EtbQWVY4nCzUeO6QMCgYBSvqqRRPXwG7VU8lznlHqP
upuABzChYrnScY+Y0TixUlL54l79Wb6N6vzEOWceAWkzu8iewrU4QspNhr/PgoR3
IeSxWlG0yy7Dc/ZnmTabx8O06I/iwrfkizzG5nOj6UEamRLJjPGNEB/jyZriQl7u
pnugg1K4mMliLbNSAnlhBQKBgQCmYepbv260Qrex1KGhSg9Ia3k5V74weYYFfJnz
UhChD+1NK+ourcsOtp3C6PlwMHBjq5aAjlU9QfUxq8NgjQaO8/xGXdfUjsFSfAtq
TA4vZkUFpuTAJgEYBHc4CXx7OzTxLzRPxQRgaMgC7KNFOMR34vu/CsJQq3R7uFwL
bsYC2QKBgQCtEmg1uDZVdByX9zyUMuRxz5Tq/vDcp+A5lJj2mha1+bUMaKX2+lxQ
vPxY55Vaw/ukWkJirRrpGv6IytBn0dLAFSlKZworZGBaxsm8OGTFJ5Oe9+kZTjI9
hvjpClOA1otbmj2F2uZAbuIjxQGDNUkLoifN5yDYCC8JPujHuHmULw==
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIGeTCCBGGgAwIBAgIBAjANBgkqhkiG9w0BAQsFADB+MQswCQYDVQQGEwJGUjEW
MBQGA1UECBMNSWxlLWRlLUZyYW5jZTEOMAwGA1UEBxMFUGFyaXMxEDAOBgNVBAoT
B296b24uaW8xFTATBgNVBAMTDE96b24gVGVzdCBDQTEeMBwGCSqGSIb3DQEJARYP
c3VwcG9ydEBvem9uLmlvMB4XDTE2MDExNzIzMDIzOFoXDTE4MDExNjIzMDIzOFow
gb4xCzAJBgNVBAYTAkZSMRYwFAYDVQQIEw1JbGUtZGUtRnJhbmNlMRowGAYDVQQH
ExFOZXVpbGx5LXN1ci1TZWluZTEYMBYGA1UEChMPVE9BRCBDb25zdWx0aW5nMRcw
FQYDVQQLEw5lUGFyYXBoZXIgVGVhbTEWMBQGA1UEAxMNd3d3LnRlc3QxLmNvbTEw
MC4GCSqGSIb3DQEJARYhYXJuYXVsdC5taWNoZWxAdG9hZC1jb25zdWx0aW5nLmZy
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnb0BDF7FsqzslakNg7u/
n/JQkq6nheuKwvyTqECfpc9y7uSBe/vrEFqBaDSLQagJxuZdL5geFeVtRbdAoB97
N1/LZa6vecjjgGSP0Aag/gS/ocnMRIyvlVWWT9MrD46OG3qZY1ORU1ltrVL0NKtt
JP8xME7j3bTwIDElx/hNI0n7L+ySkAe2xb/7CbZRfoOhjTVAcGv4aSLVc/Hi8k6V
kIzdOEtH6TcghXmuGcuqvLNH9BuosyngKTcQ8zg6J+e64aVvC+e7vi94uil9Qu+J
Hm0pkDzAZ2WluNsuXlrJToPirWyj6/YdN6xgSI1hbZkBmUPAebgYuxBt6huvfyQd
3wIDAQABo4IBvzCCAbswCwYDVR0PBAQDAgOoMBMGA1UdJQQMMAoGCCsGAQUFBwMB
MB0GA1UdDgQWBBTIihFNVNgOseQnsWEcAQxAbIKE4TCBsgYDVR0jBIGqMIGngBRv
G9At9gzk2MW5Z7JVey1LtPIZ8KGBg6SBgDB+MQswCQYDVQQGEwJGUjEWMBQGA1UE
CBMNSWxlLWRlLUZyYW5jZTEOMAwGA1UEBxMFUGFyaXMxEDAOBgNVBAoTB296b24u
aW8xFTATBgNVBAMTDE96b24gVGVzdCBDQTEeMBwGCSqGSIb3DQEJARYPc3VwcG9y
dEBvem9uLmlvggkA15FtIaGcrk8wDAYDVR0TAQH/BAIwADAaBgNVHREEEzARgg9j
b21tb25OYW1lOmNvcHkwCQYDVR0SBAIwADBIBgNVHR8EQTA/MD2gO6A5hjdodHRw
Oi8vb3BlbnNzbGNhLnRvYWQtY29uc3VsdGluZy5jb20vb3BlbnZwbi9MYXRlc3Qu
Y3JsMBEGCWCGSAGG+EIBAQQEAwIGQDAxBglghkgBhvhCAQ0EJBYiVE9BRC1Db25z
dWx0aW5nIHNlcnZlciBjZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQsFAAOCAgEAewDa
9BukGNJMex8gsXmmdaczTr8yh9Uvw4NJcZS38I+26o//2g+d6i7wxcQg8hIm62Hj
0TblGU3+RsJo4uzcWxxA5YUYlVszbHNBRpQengEE5pjwHvoXVMNES6Bt8xP04+Vj
0qVnA8gUaDMk9lN5anK7tF/mbHOIJwHJZYCa2t3y95dIOVEXFwOIzzbSbaprjkLN
w0BgR5paJz7NZWNqo4sZHUUz94uH2bPEd01SqHO0dJwEVxadgxuPnD05I9gqGpGX
Zf3Rn7EQylvUtX9mpPaulQPXc3emefewLUSSAdnZrVikZK2J/B4lSi9FpUwl4iQH
pZoE0QLQHtB1SBKacnOAddGSTLSdFvpzjErjjWSpMukF0vutmrP86GG3xtshWVhI
u+yLfDJVm/pXfaeDtWMXpxIT/U1i0avpk5MZtFMRC0MTaxEWBTnnJm+/yiaAXQYg
E1ZIP0mkZkiUojIawTR7JTjHGhIraP9UVPNceVy0DLfETHEou3vhwBn7PFOz7piJ
wjp3A47DStJD4fapaX6B1fqM+n34CMD9ZAiJFgQEIQfObAWC9hyr4m+pqkp1Qfuw
vsAP/ZoS1CBirJfm3i+Gshh+VeH+TAmO/NBBYCfzBdgkNz4tJCkOc7CUT/NQTR/L
N2OskR/Fkge149RJi7hHvE3gk/mtGtNmHJPuQ+s=
-----END CERTIFICATE-----
......@@ -2,18 +2,30 @@ varnishtest "Test dynamic backends"
server s1 {
rxreq
expect req.url == "/"
expect req.http.Probe == "p1"
expect req.http.Authority == <undef>
txresp
close
accept
rxreq
expect req.url == "/1"
expect req.http.Probe == <undef>
expect req.http.Authority == <undef>
txresp
close
accept
rxreq
expect req.url == "/"
expect req.http.Probe == "p2"
expect req.http.Authority == "127.0.0.1"
txresp
close
accept
rxreq
expect req.url == "/2"
expect req.http.Probe == <undef>
expect req.http.Authority == "127.0.0.1"
txresp
} -start
......@@ -26,6 +38,7 @@ server s1 {
varnish v2 -proto PROXY -vcl {
import debug;
import std;
import proxy;
backend dummy { .host = "${bad_backend}"; }
......@@ -36,8 +49,13 @@ varnish v2 -proto PROXY -vcl {
sub vcl_recv {
s1.refresh(server.ip, std.port(server.ip));
set req.backend_hint = s1.backend();
set req.http.Authority = proxy.authority();
return (pass);
}
sub vcl_deliver {
set resp.http.Authority = req.http.Authority;
}
} -start
#
......@@ -53,10 +71,26 @@ varnish v1 -vcl {
backend dummy { .host = "${bad_backend}"; }
probe pr {
probe p1 {
.threshold = 8;
.initial = 8;
.interval = 1m;
.request =
"GET / HTTP/1.1"
"Host: ${s1_addr}"
"Probe: p1"
"Connection: close";
}
probe p2 {
.threshold = 8;
.initial = 8;
.interval = 1m;
.request =
"GET / HTTP/1.1"
"Host: ${s1_addr}"
"Probe: p2"
"Connection: close";
}
backend v2 { .host = "${v2_addr}"; .port = "${v2_port}"; }
......@@ -67,10 +101,10 @@ varnish v1 -vcl {
sub vcl_recv {
if (req.url == "/1") {
s1.refresh("${s1_addr}", "${s1_port}", pr);
s1.refresh("${s1_addr}", "${s1_port}", p1);
vtc.sleep(1s);
} else if (req.url == "/2") {
s1.refresh("${s1_addr}", "${s1_port}", pr,
s1.refresh("${s1_addr}", "${s1_port}", p2,
via=v2);
vtc.sleep(1s);
}
......@@ -84,7 +118,9 @@ client c1 {
txreq -url /1
rxresp
expect resp.status == 200
expect resp.http.Authority == <undef>
txreq -url /2
rxresp
expect resp.status == 200
expect resp.http.Authority == "127.0.0.1"
} -run
......@@ -63,6 +63,7 @@
* exp_close added to struct vrt_backend_probe
* VRT_new_backend() signature changed
* VRT_new_backend_clustered() signature changed
* authority field added to struct vrt_backend
* 16.0 (2022-09-15)
* VMOD C-prototypes moved into JSON
* VRT_AddVDP() deprecated
......@@ -561,6 +562,7 @@ struct vrt_endpoint {
#define VRT_BACKEND_FIELDS(rigid) \
rigid char *vcl_name; \
rigid char *hosthdr; \
rigid char *authority; \
vtim_dur connect_timeout; \
vtim_dur first_byte_timeout; \
vtim_dur between_bytes_timeout; \
......@@ -571,6 +573,7 @@ struct vrt_endpoint {
do { \
DA(vcl_name); \
DA(hosthdr); \
DA(authority); \
DN(connect_timeout); \
DN(first_byte_timeout); \
DN(between_bytes_timeout); \
......
......@@ -372,6 +372,7 @@ vcc_ParseHostDef(struct vcc *tl, const struct token *t_be, const char *vgcname)
const struct token *t_port = NULL;
const struct token *t_path = NULL;
const struct token *t_hosthdr = NULL;
const struct token *t_authority = NULL;
const struct token *t_did = NULL;
const struct token *t_preamble = NULL;
struct symbol *pb;
......@@ -420,6 +421,7 @@ vcc_ParseHostDef(struct vcc *tl, const struct token *t_be, const char *vgcname)
"?proxy_header",
"?preamble",
"?via",
"?authority",
NULL);
tl->fb = VSB_new_auto();
......@@ -547,6 +549,12 @@ vcc_ParseHostDef(struct vcc *tl, const struct token *t_be, const char *vgcname)
AN(via);
AN(via->rname);
SkipToken(tl, ';');
} else if (vcc_IdIs(t_field, "authority")) {
ExpectErr(tl, CSTR);
assert(tl->t->dec != NULL);
t_authority = tl->t;
vcc_NextToken(tl);
SkipToken(tl, ';');
} else {
ErrInternal(tl);
VSB_destroy(&tl->fb);
......@@ -609,6 +617,28 @@ vcc_ParseHostDef(struct vcc *tl, const struct token *t_be, const char *vgcname)
Fb(tl, 0, "\"0.0.0.0\"");
Fb(tl, 0, ",\n");
/*
* Emit the authority field, falling back to hosthdr, then host.
*
* When authority is "", sending the TLV is disabled.
*
* Falling back to host may result in an IP address in authority,
* which is an illegal SNI HostName (RFC 4366 ch. 3.1). But we
* document the potential error, rather than try to find out
* whether or not Emit_Sockaddr() had to look up a name.
*/
if (via != NULL) {
AN(t_host);
Fb(tl, 0, "\t.authority = ");
if (t_authority != NULL)
EncToken(tl->fb, t_authority);
else if (t_hosthdr != NULL)
EncToken(tl->fb, t_hosthdr);
else
EncToken(tl->fb, t_host);
Fb(tl, 0, ",\n");
}
/* Close the struct */
Fb(tl, 0, "};\n");
......
......@@ -79,6 +79,7 @@ dyn_dir_init(VRT_CTX, struct xyzzy_debug_dyn *dyn,
vrt.endpoint = &vep;
vrt.vcl_name = dyn->vcl_name;
vrt.hosthdr = addr;
vrt.authority = addr;
vrt.probe = probe;
sa = VSS_ResolveOne(NULL, addr, port, AF_UNSPEC, SOCK_STREAM, 0);
......
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