Commit a5082f0c authored by Patrick McHardy's avatar Patrick McHardy

Merge coreworks.de:/home/kaber/src/nf/nf-post-2.6.9

into coreworks.de:/home/kaber/src/nf/nf-2.6
parents df1f9e22 0e8b7c1c
......@@ -355,13 +355,15 @@ struct ip6t_match
/* Return true or false: return FALSE and set *hotdrop = 1 to
force immediate packet drop. */
/* Arguments changed since 2.6.9, as this must now handle
non-linear skb, using skb_header_pointer and
skb_ip_make_writable. */
int (*match)(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *hdr,
u_int16_t datalen,
unsigned int protoff,
int *hotdrop);
/* Called when user tries to insert an entry of this type. */
......@@ -386,11 +388,13 @@ struct ip6t_target
const char name[IP6T_FUNCTION_MAXNAMELEN];
/* Returns verdict. */
/* Returns verdict. Argument order changed since 2.6.9, as this
must now handle non-linear skbs, using skb_copy_bits and
skb_ip_make_writable. */
unsigned int (*target)(struct sk_buff **pskb,
unsigned int hooknum,
const struct net_device *in,
const struct net_device *out,
unsigned int hooknum,
const void *targinfo,
void *userdata);
......
......@@ -351,16 +351,14 @@ __ip_conntrack_find(const struct ip_conntrack_tuple *tuple,
{
struct ip_conntrack_tuple_hash *h;
unsigned int hash = hash_conntrack(tuple);
/* use per_cpu() to avoid multiple calls to smp_processor_id() */
unsigned int cpu = smp_processor_id();
MUST_BE_READ_LOCKED(&ip_conntrack_lock);
list_for_each_entry(h, &ip_conntrack_hash[hash], list) {
if (conntrack_tuple_cmp(h, tuple, ignored_conntrack)) {
per_cpu(ip_conntrack_stat, cpu).found++;
CONNTRACK_STAT_INC(found);
return h;
}
per_cpu(ip_conntrack_stat, cpu).searched++;
CONNTRACK_STAT_INC(searched);
}
return NULL;
......
......@@ -81,8 +81,8 @@ masquerade_target(struct sk_buff **pskb,
enum ip_conntrack_info ctinfo;
const struct ip_nat_multi_range *mr;
struct ip_nat_multi_range newrange;
u_int32_t newsrc;
struct rtable *rt;
u_int32_t newsrc;
IP_NF_ASSERT(hooknum == NF_IP_POST_ROUTING);
......@@ -96,36 +96,13 @@ masquerade_target(struct sk_buff **pskb,
|| ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY));
mr = targinfo;
{
struct flowi fl = { .nl_u = { .ip4_u =
{ .daddr = (*pskb)->nh.iph->daddr,
.tos = (RT_TOS((*pskb)->nh.iph->tos) |
RTO_CONN),
#ifdef CONFIG_IP_ROUTE_FWMARK
.fwmark = (*pskb)->nfmark
#endif
} } };
if (ip_route_output_key(&rt, &fl) != 0) {
/* Funky routing can do this. */
if (net_ratelimit())
printk("MASQUERADE:"
" No route: Rusty's brain broke!\n");
return NF_DROP;
}
if (rt->u.dst.dev != out) {
if (net_ratelimit())
printk("MASQUERADE:"
" Route sent us somewhere else.\n");
ip_rt_put(rt);
return NF_DROP;
}
rt = (struct rtable *)(*pskb)->dst;
newsrc = inet_select_addr(out, rt->rt_gateway, RT_SCOPE_UNIVERSE);
if (!newsrc) {
printk("MASQUERADE: %s ate my IP address\n", out->name);
return NF_DROP;
}
newsrc = rt->rt_src;
DEBUGP("newsrc = %u.%u.%u.%u\n", NIPQUAD(newsrc));
ip_rt_put(rt);
WRITE_LOCK(&masq_lock);
ct->nat.masq_index = out->ifindex;
WRITE_UNLOCK(&masq_lock);
......@@ -157,6 +134,18 @@ device_cmp(const struct ip_conntrack *i, void *_ina)
return ret;
}
static inline int
connect_unassure(const struct ip_conntrack *i, void *_ina)
{
struct in_ifaddr *ina = _ina;
/* We reset the ASSURED bit on all connections, so they will
* get reaped under memory pressure. */
if (i->nat.masq_index == ina->ifa_dev->dev->ifindex)
clear_bit(IPS_ASSURED_BIT, (unsigned long *)&i->status);
return 0;
}
static int masq_inet_event(struct notifier_block *this,
unsigned long event,
void *ptr)
......@@ -166,6 +155,8 @@ static int masq_inet_event(struct notifier_block *this,
* entries. */
if (event == NETDEV_UP)
ip_ct_selective_cleanup(device_cmp, ptr);
else if (event == NETDEV_DOWN)
ip_ct_selective_cleanup(connect_unassure, ptr);
return NOTIFY_DONE;
}
......
This diff is collapsed.
This diff is collapsed.
......@@ -20,9 +20,9 @@ MODULE_AUTHOR("Netfilter Core Team <coreteam@netfilter.org>");
static unsigned int
target(struct sk_buff **pskb,
unsigned int hooknum,
const struct net_device *in,
const struct net_device *out,
unsigned int hooknum,
const void *targinfo,
void *userinfo)
{
......
......@@ -31,12 +31,12 @@ MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
static inline int
spi_match(u_int32_t min, u_int32_t max, u_int32_t spi, int invert)
{
int r=0;
DEBUGP("ah spi_match:%c 0x%x <= 0x%x <= 0x%x",invert? '!':' ',
min,spi,max);
r=(spi >= min && spi <= max) ^ invert;
DEBUGP(" result %s\n",r? "PASS\n" : "FAILED\n");
return r;
int r=0;
DEBUGP("ah spi_match:%c 0x%x <= 0x%x <= 0x%x",invert? '!':' ',
min,spi,max);
r = (spi >= min && spi <= max) ^ invert;
DEBUGP(" result %s\n",r? "PASS\n" : "FAILED\n");
return r;
}
static int
......@@ -45,125 +45,124 @@ match(const struct sk_buff *skb,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *protohdr,
u_int16_t datalen,
unsigned int protoff,
int *hotdrop)
{
struct ip_auth_hdr *ah = NULL;
const struct ip6t_ah *ahinfo = matchinfo;
unsigned int temp;
int len;
u8 nexthdr;
unsigned int ptr;
unsigned int hdrlen = 0;
/*DEBUGP("IPv6 AH entered\n");*/
/* if (opt->auth == 0) return 0;
* It does not filled on output */
/* type of the 1st exthdr */
nexthdr = skb->nh.ipv6h->nexthdr;
/* pointer to the 1st exthdr */
ptr = sizeof(struct ipv6hdr);
/* available length */
len = skb->len - ptr;
temp = 0;
while (ip6t_ext_hdr(nexthdr)) {
struct ipv6_opt_hdr *hdr;
DEBUGP("ipv6_ah header iteration \n");
/* Is there enough space for the next ext header? */
if (len < (int)sizeof(struct ipv6_opt_hdr))
return 0;
/* No more exthdr -> evaluate */
if (nexthdr == NEXTHDR_NONE) {
break;
}
/* ESP -> evaluate */
if (nexthdr == NEXTHDR_ESP) {
break;
}
hdr=(struct ipv6_opt_hdr *)skb->data+ptr;
/* Calculate the header length */
if (nexthdr == NEXTHDR_FRAGMENT) {
hdrlen = 8;
} else if (nexthdr == NEXTHDR_AUTH)
hdrlen = (hdr->hdrlen+2)<<2;
else
hdrlen = ipv6_optlen(hdr);
/* AH -> evaluate */
if (nexthdr == NEXTHDR_AUTH) {
temp |= MASK_AH;
break;
}
/* set the flag */
switch (nexthdr){
case NEXTHDR_HOP:
case NEXTHDR_ROUTING:
case NEXTHDR_FRAGMENT:
case NEXTHDR_AUTH:
case NEXTHDR_DEST:
break;
default:
DEBUGP("ipv6_ah match: unknown nextheader %u\n",nexthdr);
return 0;
break;
}
nexthdr = hdr->nexthdr;
len -= hdrlen;
ptr += hdrlen;
if ( ptr > skb->len ) {
struct ip_auth_hdr *ah = NULL, _ah;
const struct ip6t_ah *ahinfo = matchinfo;
unsigned int temp;
int len;
u8 nexthdr;
unsigned int ptr;
unsigned int hdrlen = 0;
/*DEBUGP("IPv6 AH entered\n");*/
/* if (opt->auth == 0) return 0;
* It does not filled on output */
/* type of the 1st exthdr */
nexthdr = skb->nh.ipv6h->nexthdr;
/* pointer to the 1st exthdr */
ptr = sizeof(struct ipv6hdr);
/* available length */
len = skb->len - ptr;
temp = 0;
while (ip6t_ext_hdr(nexthdr)) {
struct ipv6_opt_hdr _hdr, *hp;
DEBUGP("ipv6_ah header iteration \n");
/* Is there enough space for the next ext header? */
if (len < sizeof(struct ipv6_opt_hdr))
return 0;
/* No more exthdr -> evaluate */
if (nexthdr == NEXTHDR_NONE)
break;
/* ESP -> evaluate */
if (nexthdr == NEXTHDR_ESP)
break;
hp = skb_header_pointer(skb, ptr, sizeof(_hdr), &_hdr);
BUG_ON(hp == NULL);
/* Calculate the header length */
if (nexthdr == NEXTHDR_FRAGMENT)
hdrlen = 8;
else if (nexthdr == NEXTHDR_AUTH)
hdrlen = (hp->hdrlen+2)<<2;
else
hdrlen = ipv6_optlen(hp);
/* AH -> evaluate */
if (nexthdr == NEXTHDR_AUTH) {
temp |= MASK_AH;
break;
}
/* set the flag */
switch (nexthdr) {
case NEXTHDR_HOP:
case NEXTHDR_ROUTING:
case NEXTHDR_FRAGMENT:
case NEXTHDR_AUTH:
case NEXTHDR_DEST:
break;
default:
DEBUGP("ipv6_ah match: unknown nextheader %u\n",nexthdr);
return 0;
}
nexthdr = hp->nexthdr;
len -= hdrlen;
ptr += hdrlen;
if (ptr > skb->len) {
DEBUGP("ipv6_ah: new pointer too large! \n");
break;
}
}
/* AH header not found */
if ( temp != MASK_AH ) return 0;
if (len < (int)sizeof(struct ip_auth_hdr)){
*hotdrop = 1;
return 0;
}
ah = (struct ip_auth_hdr *) (skb->data + ptr);
DEBUGP("IPv6 AH LEN %u %u ", hdrlen, ah->hdrlen);
DEBUGP("RES %04X ", ah->reserved);
DEBUGP("SPI %u %08X\n", ntohl(ah->spi), ntohl(ah->spi));
DEBUGP("IPv6 AH spi %02X ",
(spi_match(ahinfo->spis[0], ahinfo->spis[1],
ntohl(ah->spi),
!!(ahinfo->invflags & IP6T_AH_INV_SPI))));
DEBUGP("len %02X %04X %02X ",
ahinfo->hdrlen, hdrlen,
(!ahinfo->hdrlen ||
(ahinfo->hdrlen == hdrlen) ^
!!(ahinfo->invflags & IP6T_AH_INV_LEN)));
DEBUGP("res %02X %04X %02X\n",
ahinfo->hdrres, ah->reserved,
!(ahinfo->hdrres && ah->reserved));
return (ah != NULL)
&&
(spi_match(ahinfo->spis[0], ahinfo->spis[1],
ntohl(ah->spi),
!!(ahinfo->invflags & IP6T_AH_INV_SPI)))
&&
(!ahinfo->hdrlen ||
(ahinfo->hdrlen == hdrlen) ^
!!(ahinfo->invflags & IP6T_AH_INV_LEN))
&&
!(ahinfo->hdrres && ah->reserved);
}
/* AH header not found */
if (temp != MASK_AH)
return 0;
if (len < sizeof(struct ip_auth_hdr)){
*hotdrop = 1;
return 0;
}
ah = skb_header_pointer(skb, ptr, sizeof(_ah), &_ah);
BUG_ON(ah == NULL);
DEBUGP("IPv6 AH LEN %u %u ", hdrlen, ah->hdrlen);
DEBUGP("RES %04X ", ah->reserved);
DEBUGP("SPI %u %08X\n", ntohl(ah->spi), ntohl(ah->spi));
DEBUGP("IPv6 AH spi %02X ",
(spi_match(ahinfo->spis[0], ahinfo->spis[1],
ntohl(ah->spi),
!!(ahinfo->invflags & IP6T_AH_INV_SPI))));
DEBUGP("len %02X %04X %02X ",
ahinfo->hdrlen, hdrlen,
(!ahinfo->hdrlen ||
(ahinfo->hdrlen == hdrlen) ^
!!(ahinfo->invflags & IP6T_AH_INV_LEN)));
DEBUGP("res %02X %04X %02X\n",
ahinfo->hdrres, ah->reserved,
!(ahinfo->hdrres && ah->reserved));
return (ah != NULL)
&&
(spi_match(ahinfo->spis[0], ahinfo->spis[1],
ntohl(ah->spi),
!!(ahinfo->invflags & IP6T_AH_INV_SPI)))
&&
(!ahinfo->hdrlen ||
(ahinfo->hdrlen == hdrlen) ^
!!(ahinfo->invflags & IP6T_AH_INV_LEN))
&&
!(ahinfo->hdrres && ah->reserved);
}
/* Called when user tries to insert an entry of this type. */
......@@ -174,20 +173,18 @@ checkentry(const char *tablename,
unsigned int matchinfosize,
unsigned int hook_mask)
{
const struct ip6t_ah *ahinfo = matchinfo;
if (matchinfosize != IP6T_ALIGN(sizeof(struct ip6t_ah))) {
DEBUGP("ip6t_ah: matchsize %u != %u\n",
matchinfosize, IP6T_ALIGN(sizeof(struct ip6t_ah)));
return 0;
}
if (ahinfo->invflags & ~IP6T_AH_INV_MASK) {
DEBUGP("ip6t_ah: unknown flags %X\n",
ahinfo->invflags);
return 0;
}
return 1;
const struct ip6t_ah *ahinfo = matchinfo;
if (matchinfosize != IP6T_ALIGN(sizeof(struct ip6t_ah))) {
DEBUGP("ip6t_ah: matchsize %u != %u\n",
matchinfosize, IP6T_ALIGN(sizeof(struct ip6t_ah)));
return 0;
}
if (ahinfo->invflags & ~IP6T_AH_INV_MASK) {
DEBUGP("ip6t_ah: unknown flags %X\n", ahinfo->invflags);
return 0;
}
return 1;
}
static struct ip6t_match ah_match = {
......@@ -199,12 +196,12 @@ static struct ip6t_match ah_match = {
static int __init init(void)
{
return ip6t_register_match(&ah_match);
return ip6t_register_match(&ah_match);
}
static void __exit cleanup(void)
{
ip6t_unregister_match(&ah_match);
ip6t_unregister_match(&ah_match);
}
module_init(init);
......
......@@ -60,8 +60,7 @@ match(const struct sk_buff *skb,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *protohdr,
u_int16_t datalen,
unsigned int protoff,
int *hotdrop)
{
struct ipv6_opt_hdr *optsh = NULL;
......
......@@ -32,8 +32,8 @@ static inline int
spi_match(u_int32_t min, u_int32_t max, u_int32_t spi, int invert)
{
int r=0;
DEBUGP("esp spi_match:%c 0x%x <= 0x%x <= 0x%x",invert? '!':' ',
min,spi,max);
DEBUGP("esp spi_match:%c 0x%x <= 0x%x <= 0x%x",invert? '!':' ',
min,spi,max);
r=(spi >= min && spi <= max) ^ invert;
DEBUGP(" result %s\n",r? "PASS\n" : "FAILED\n");
return r;
......@@ -45,11 +45,10 @@ match(const struct sk_buff *skb,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *protohdr,
u_int16_t datalen,
unsigned int protoff,
int *hotdrop)
{
struct ip_esp_hdr *esp = NULL;
struct ip_esp_hdr _esp, *eh = NULL;
const struct ip6t_esp *espinfo = matchinfo;
unsigned int temp;
int len;
......@@ -67,73 +66,74 @@ match(const struct sk_buff *skb,
len = skb->len - ptr;
temp = 0;
while (ip6t_ext_hdr(nexthdr)) {
struct ipv6_opt_hdr *hdr;
int hdrlen;
while (ip6t_ext_hdr(nexthdr)) {
struct ipv6_opt_hdr _hdr, *hp;
int hdrlen;
DEBUGP("ipv6_esp header iteration \n");
/* Is there enough space for the next ext header? */
if (len < (int)sizeof(struct ipv6_opt_hdr))
return 0;
if (len < sizeof(struct ipv6_opt_hdr))
return 0;
/* No more exthdr -> evaluate */
if (nexthdr == NEXTHDR_NONE) {
if (nexthdr == NEXTHDR_NONE)
break;
}
/* ESP -> evaluate */
if (nexthdr == NEXTHDR_ESP) {
if (nexthdr == NEXTHDR_ESP) {
temp |= MASK_ESP;
break;
}
hdr=(struct ipv6_opt_hdr *)skb->data+ptr;
hp = skb_header_pointer(skb, ptr, sizeof(_hdr), &_hdr);
BUG_ON(hp == NULL);
/* Calculate the header length */
if (nexthdr == NEXTHDR_FRAGMENT) {
hdrlen = 8;
} else if (nexthdr == NEXTHDR_AUTH)
hdrlen = (hdr->hdrlen+2)<<2;
else
hdrlen = ipv6_optlen(hdr);
if (nexthdr == NEXTHDR_FRAGMENT)
hdrlen = 8;
else if (nexthdr == NEXTHDR_AUTH)
hdrlen = (hp->hdrlen+2)<<2;
else
hdrlen = ipv6_optlen(hp);
/* set the flag */
switch (nexthdr){
case NEXTHDR_HOP:
case NEXTHDR_ROUTING:
case NEXTHDR_FRAGMENT:
case NEXTHDR_AUTH:
case NEXTHDR_DEST:
break;
default:
DEBUGP("ipv6_esp match: unknown nextheader %u\n",nexthdr);
return 0;
break;
switch (nexthdr) {
case NEXTHDR_HOP:
case NEXTHDR_ROUTING:
case NEXTHDR_FRAGMENT:
case NEXTHDR_AUTH:
case NEXTHDR_DEST:
break;
default:
DEBUGP("ipv6_esp match: unknown nextheader %u\n",nexthdr);
return 0;
}
nexthdr = hdr->nexthdr;
len -= hdrlen;
ptr += hdrlen;
if ( ptr > skb->len ) {
nexthdr = hp->nexthdr;
len -= hdrlen;
ptr += hdrlen;
if (ptr > skb->len) {
DEBUGP("ipv6_esp: new pointer too large! \n");
break;
}
}
}
/* ESP header not found */
if ( temp != MASK_ESP ) return 0;
if (temp != MASK_ESP)
return 0;
if (len < (int)sizeof(struct ip_esp_hdr)){
*hotdrop = 1;
return 0;
}
if (len < sizeof(struct ip_esp_hdr)) {
*hotdrop = 1;
return 0;
}
esp = (struct ip_esp_hdr *) (skb->data + ptr);
eh = skb_header_pointer(skb, ptr, sizeof(_esp), &_esp);
BUG_ON(eh == NULL);
DEBUGP("IPv6 ESP SPI %u %08X\n", ntohl(esp->spi), ntohl(esp->spi));
DEBUGP("IPv6 ESP SPI %u %08X\n", ntohl(eh->spi), ntohl(eh->spi));
return (esp != NULL)
return (eh != NULL)
&& spi_match(espinfo->spis[0], espinfo->spis[1],
ntohl(esp->spi),
ntohl(eh->spi),
!!(espinfo->invflags & IP6T_ESP_INV_SPI));
}
......@@ -157,7 +157,6 @@ checkentry(const char *tablename,
espinfo->invflags);
return 0;
}
return 1;
}
......
......@@ -24,8 +24,7 @@ match(const struct sk_buff *skb,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *hdr,
u_int16_t datalen,
unsigned int protoff,
int *hotdrop)
{
......
......@@ -45,8 +45,7 @@ match(const struct sk_buff *skb,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *protohdr,
u_int16_t datalen,
unsigned int protoff,
int *hotdrop)
{
struct frag_hdr *frag = NULL;
......
......@@ -59,8 +59,7 @@ match(const struct sk_buff *skb,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *protohdr,
u_int16_t datalen,
unsigned int protoff,
int *hotdrop)
{
struct ipv6_opt_hdr *optsh = NULL;
......
......@@ -20,7 +20,7 @@ MODULE_LICENSE("GPL");
static int match(const struct sk_buff *skb, const struct net_device *in,
const struct net_device *out, const void *matchinfo,
int offset, const void *hdr, u_int16_t datalen,
int offset, unsigned int protoff,
int *hotdrop)
{
const struct ip6t_hl_info *info = matchinfo;
......
......@@ -31,8 +31,7 @@ ipv6header_match(const struct sk_buff *skb,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *protohdr,
u_int16_t datalen,
unsigned int protoff,
int *hotdrop)
{
const struct ip6t_ipv6header_info *info = matchinfo;
......
......@@ -23,8 +23,7 @@ match(const struct sk_buff *skb,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *hdr,
u_int16_t datalen,
unsigned int protoff,
int *hotdrop)
{
const struct ip6t_length_info *info = matchinfo;
......
......@@ -57,8 +57,7 @@ ip6t_limit_match(const struct sk_buff *skb,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *hdr,
u_int16_t datalen,
unsigned int protoff,
int *hotdrop)
{
struct ip6t_rateinfo *r = ((struct ip6t_rateinfo *)matchinfo)->master;
......
......@@ -25,8 +25,7 @@ match(const struct sk_buff *skb,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *hdr,
u_int16_t datalen,
unsigned int protoff,
int *hotdrop)
{
const struct ip6t_mac_info *info = matchinfo;
......
......@@ -24,8 +24,7 @@ match(const struct sk_buff *skb,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *hdr,
u_int16_t datalen,
unsigned int protoff,
int *hotdrop)
{
const struct ip6t_mark_info *info = matchinfo;
......
......@@ -53,15 +53,14 @@ match(const struct sk_buff *skb,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *hdr,
u_int16_t datalen,
unsigned int protoff,
int *hotdrop)
{
const struct udphdr *udp = hdr;
const struct udphdr *udp = (const struct udphdr *)(skb->data + protoff);
const struct ip6t_multiport *multiinfo = matchinfo;
/* Must be big enough to read ports. */
if (offset == 0 && datalen < sizeof(struct udphdr)) {
if (offset == 0 && skb->len - protoff < sizeof(struct udphdr)) {
/* We've been asked to examine this packet, and we
can't. Hence, no choice but to drop. */
duprintf("ip6t_multiport:"
......
......@@ -92,8 +92,7 @@ match(const struct sk_buff *skb,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *hdr,
u_int16_t datalen,
unsigned int protoff,
int *hotdrop)
{
const struct ip6t_owner_info *info = matchinfo;
......
......@@ -26,8 +26,7 @@ match(const struct sk_buff *skb,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *hdr,
u_int16_t datalen,
unsigned int protoff,
int *hotdrop)
{
int i;
......
......@@ -47,8 +47,7 @@ match(const struct sk_buff *skb,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *protohdr,
u_int16_t datalen,
unsigned int protoff,
int *hotdrop)
{
struct ipv6_rt_hdr *route = NULL;
......
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