Commit 080774a2 authored by Harald Welte's avatar Harald Welte Committed by David S. Miller

[NETFILTER]: Add ctnetlink subsystem

Add ctnetlink subsystem for userspace-access to ip_conntrack table.
This allows reading and updating of existing entries, as well as
creating new ones (and new expect's) via nfnetlink.

Please note the 'strange' byte order: nfattr (tag+length) are in host
byte order, while the payload is always guaranteed to be in network
byte order.  This allows a simple userspace process to encapsulate netlink
messages into arch-independent udp packets by just processing/swapping the
headers and not knowing anything about the actual payload.
Signed-off-by: default avatarHarald Welte <laforge@netfilter.org>
Signed-off-by: default avatarPatrick McHardy <kaber@trash.net>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 6f1cf165
...@@ -56,7 +56,7 @@ struct nfgenmsg { ...@@ -56,7 +56,7 @@ struct nfgenmsg {
u_int16_t res_id; /* resource id */ u_int16_t res_id; /* resource id */
} __attribute__ ((packed)); } __attribute__ ((packed));
#define NFNETLINK_V1 1 #define NFNETLINK_V0 0
#define NFM_NFA(n) ((struct nfattr *)(((char *)(n)) \ #define NFM_NFA(n) ((struct nfattr *)(((char *)(n)) \
+ NLMSG_ALIGN(sizeof(struct nfgenmsg)))) + NLMSG_ALIGN(sizeof(struct nfgenmsg))))
...@@ -81,6 +81,7 @@ enum nfnl_subsys_id { ...@@ -81,6 +81,7 @@ enum nfnl_subsys_id {
#ifdef __KERNEL__ #ifdef __KERNEL__
#include <linux/netlink.h>
#include <linux/capability.h> #include <linux/capability.h>
struct nfnl_callback struct nfnl_callback
......
#ifndef _IPCONNTRACK_NETLINK_H
#define _IPCONNTRACK_NETLINK_H
#include <linux/netfilter/nfnetlink.h>
enum cntl_msg_types {
IPCTNL_MSG_CT_NEW,
IPCTNL_MSG_CT_GET,
IPCTNL_MSG_CT_DELETE,
IPCTNL_MSG_CT_GET_CTRZERO,
IPCTNL_MSG_MAX
};
enum ctnl_exp_msg_types {
IPCTNL_MSG_EXP_NEW,
IPCTNL_MSG_EXP_GET,
IPCTNL_MSG_EXP_DELETE,
IPCTNL_MSG_EXP_MAX
};
enum ctattr_type {
CTA_UNSPEC,
CTA_TUPLE_ORIG,
CTA_TUPLE_REPLY,
CTA_STATUS,
CTA_PROTOINFO,
CTA_HELP,
CTA_NAT,
CTA_TIMEOUT,
CTA_MARK,
CTA_COUNTERS_ORIG,
CTA_COUNTERS_REPLY,
CTA_USE,
CTA_EXPECT,
CTA_ID,
__CTA_MAX
};
#define CTA_MAX (__CTA_MAX - 1)
enum ctattr_tuple {
CTA_TUPLE_UNSPEC,
CTA_TUPLE_IP,
CTA_TUPLE_PROTO,
__CTA_TUPLE_MAX
};
#define CTA_TUPLE_MAX (__CTA_TUPLE_MAX - 1)
enum ctattr_ip {
CTA_IP_UNSPEC,
CTA_IP_V4_SRC,
CTA_IP_V4_DST,
CTA_IP_V6_SRC,
CTA_IP_V6_DST,
__CTA_IP_MAX
};
#define CTA_IP_MAX (__CTA_IP_MAX - 1)
enum ctattr_l4proto {
CTA_PROTO_UNSPEC,
CTA_PROTO_NUM,
CTA_PROTO_SRC_PORT,
CTA_PROTO_DST_PORT,
CTA_PROTO_ICMP_ID,
CTA_PROTO_ICMP_TYPE,
CTA_PROTO_ICMP_CODE,
__CTA_PROTO_MAX
};
#define CTA_PROTO_MAX (__CTA_PROTO_MAX - 1)
enum ctattr_protoinfo {
CTA_PROTOINFO_UNSPEC,
CTA_PROTOINFO_TCP_STATE,
__CTA_PROTOINFO_MAX
};
#define CTA_PROTOINFO_MAX (__CTA_PROTOINFO_MAX - 1)
enum ctattr_counters {
CTA_COUNTERS_UNSPEC,
CTA_COUNTERS_PACKETS,
CTA_COUNTERS_BYTES,
__CTA_COUNTERS_MAX
};
#define CTA_COUNTERS_MAX (__CTA_COUNTERS_MAX - 1)
enum ctattr_nat {
CTA_NAT_UNSPEC,
CTA_NAT_MINIP,
CTA_NAT_MAXIP,
CTA_NAT_PROTO,
__CTA_NAT_MAX
};
#define CTA_NAT_MAX (__CTA_NAT_MAX - 1)
enum ctattr_protonat {
CTA_PROTONAT_UNSPEC,
CTA_PROTONAT_PORT_MIN,
CTA_PROTONAT_PORT_MAX,
__CTA_PROTONAT_MAX
};
#define CTA_PROTONAT_MAX (__CTA_PROTONAT_MAX - 1)
enum ctattr_expect {
CTA_EXPECT_UNSPEC,
CTA_EXPECT_TUPLE,
CTA_EXPECT_MASK,
CTA_EXPECT_TIMEOUT,
CTA_EXPECT_ID,
__CTA_EXPECT_MAX
};
#define CTA_EXPECT_MAX (__CTA_EXPECT_MAX - 1)
enum ctattr_help {
CTA_HELP_UNSPEC,
CTA_HELP_NAME,
__CTA_HELP_MAX
};
#define CTA_HELP_MAX (__CTA_HELP_MAX - 1)
#define CTA_HELP_MAXNAMESIZE 32
#endif /* _IPCONNTRACK_NETLINK_H */
...@@ -209,6 +209,9 @@ struct ip_conntrack ...@@ -209,6 +209,9 @@ struct ip_conntrack
/* Current number of expected connections */ /* Current number of expected connections */
unsigned int expecting; unsigned int expecting;
/* Unique ID that identifies this conntrack*/
unsigned int id;
/* Helper, if any. */ /* Helper, if any. */
struct ip_conntrack_helper *helper; struct ip_conntrack_helper *helper;
...@@ -257,6 +260,9 @@ struct ip_conntrack_expect ...@@ -257,6 +260,9 @@ struct ip_conntrack_expect
/* Usage count. */ /* Usage count. */
atomic_t use; atomic_t use;
/* Unique ID */
unsigned int id;
#ifdef CONFIG_IP_NF_NAT_NEEDED #ifdef CONFIG_IP_NF_NAT_NEEDED
/* This is the original per-proto part, used to map the /* This is the original per-proto part, used to map the
* expected connection the way the recipient expects. */ * expected connection the way the recipient expects. */
...@@ -296,7 +302,12 @@ ip_conntrack_get(const struct sk_buff *skb, enum ip_conntrack_info *ctinfo) ...@@ -296,7 +302,12 @@ ip_conntrack_get(const struct sk_buff *skb, enum ip_conntrack_info *ctinfo)
} }
/* decrement reference count on a conntrack */ /* decrement reference count on a conntrack */
extern void ip_conntrack_put(struct ip_conntrack *ct); static inline void
ip_conntrack_put(struct ip_conntrack *ct)
{
IP_NF_ASSERT(ct);
nf_conntrack_put(&ct->ct_general);
}
/* call to create an explicit dependency on ip_conntrack. */ /* call to create an explicit dependency on ip_conntrack. */
extern void need_ip_conntrack(void); extern void need_ip_conntrack(void);
...@@ -331,6 +342,39 @@ extern void ...@@ -331,6 +342,39 @@ extern void
ip_ct_iterate_cleanup(int (*iter)(struct ip_conntrack *i, void *data), ip_ct_iterate_cleanup(int (*iter)(struct ip_conntrack *i, void *data),
void *data); void *data);
extern struct ip_conntrack_helper *
__ip_conntrack_helper_find_byname(const char *);
extern struct ip_conntrack_helper *
ip_conntrack_helper_find_get(const struct ip_conntrack_tuple *tuple);
extern void ip_conntrack_helper_put(struct ip_conntrack_helper *helper);
extern struct ip_conntrack_protocol *
__ip_conntrack_proto_find(u_int8_t protocol);
extern struct ip_conntrack_protocol *
ip_conntrack_proto_find_get(u_int8_t protocol);
extern void ip_conntrack_proto_put(struct ip_conntrack_protocol *proto);
extern void ip_ct_remove_expectations(struct ip_conntrack *ct);
extern struct ip_conntrack *ip_conntrack_alloc(struct ip_conntrack_tuple *,
struct ip_conntrack_tuple *);
extern void ip_conntrack_free(struct ip_conntrack *ct);
extern void ip_conntrack_hash_insert(struct ip_conntrack *ct);
extern struct ip_conntrack_expect *
__ip_conntrack_expect_find(const struct ip_conntrack_tuple *tuple);
extern struct ip_conntrack_expect *
ip_conntrack_expect_find_get(const struct ip_conntrack_tuple *tuple);
extern struct ip_conntrack_tuple_hash *
__ip_conntrack_find(const struct ip_conntrack_tuple *tuple,
const struct ip_conntrack *ignored_conntrack);
extern void ip_conntrack_flush(void);
/* It's confirmed if it is, or has been in the hash table. */ /* It's confirmed if it is, or has been in the hash table. */
static inline int is_confirmed(struct ip_conntrack *ct) static inline int is_confirmed(struct ip_conntrack *ct)
{ {
......
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
#define _IP_CONNTRACK_CORE_H #define _IP_CONNTRACK_CORE_H
#include <linux/netfilter.h> #include <linux/netfilter.h>
#define MAX_IP_CT_PROTO 256
extern struct ip_conntrack_protocol *ip_ct_protos[MAX_IP_CT_PROTO];
/* This header is used to share core functionality between the /* This header is used to share core functionality between the
standalone connection tracking module, and the compatibility layer's use standalone connection tracking module, and the compatibility layer's use
of connection tracking. */ of connection tracking. */
...@@ -53,6 +56,8 @@ struct ip_conntrack_ecache; ...@@ -53,6 +56,8 @@ struct ip_conntrack_ecache;
extern void __ip_ct_deliver_cached_events(struct ip_conntrack_ecache *ec); extern void __ip_ct_deliver_cached_events(struct ip_conntrack_ecache *ec);
#endif #endif
extern void __ip_ct_expect_unlink_destroy(struct ip_conntrack_expect *exp);
extern struct list_head *ip_conntrack_hash; extern struct list_head *ip_conntrack_hash;
extern struct list_head ip_conntrack_expect_list; extern struct list_head ip_conntrack_expect_list;
extern rwlock_t ip_conntrack_lock; extern rwlock_t ip_conntrack_lock;
......
...@@ -24,6 +24,8 @@ struct ip_conntrack_helper ...@@ -24,6 +24,8 @@ struct ip_conntrack_helper
int (*help)(struct sk_buff **pskb, int (*help)(struct sk_buff **pskb,
struct ip_conntrack *ct, struct ip_conntrack *ct,
enum ip_conntrack_info conntrackinfo); enum ip_conntrack_info conntrackinfo);
int (*to_nfattr)(struct sk_buff *skb, const struct ip_conntrack *ct);
}; };
extern int ip_conntrack_helper_register(struct ip_conntrack_helper *); extern int ip_conntrack_helper_register(struct ip_conntrack_helper *);
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
#ifndef _IP_CONNTRACK_PROTOCOL_H #ifndef _IP_CONNTRACK_PROTOCOL_H
#define _IP_CONNTRACK_PROTOCOL_H #define _IP_CONNTRACK_PROTOCOL_H
#include <linux/netfilter_ipv4/ip_conntrack.h> #include <linux/netfilter_ipv4/ip_conntrack.h>
#include <linux/netfilter/nfnetlink_conntrack.h>
struct seq_file; struct seq_file;
...@@ -47,22 +48,22 @@ struct ip_conntrack_protocol ...@@ -47,22 +48,22 @@ struct ip_conntrack_protocol
int (*error)(struct sk_buff *skb, enum ip_conntrack_info *ctinfo, int (*error)(struct sk_buff *skb, enum ip_conntrack_info *ctinfo,
unsigned int hooknum); unsigned int hooknum);
/* convert protoinfo to nfnetink attributes */
int (*to_nfattr)(struct sk_buff *skb, struct nfattr *nfa,
const struct ip_conntrack *ct);
int (*tuple_to_nfattr)(struct sk_buff *skb,
const struct ip_conntrack_tuple *t);
int (*nfattr_to_tuple)(struct nfattr *tb[],
struct ip_conntrack_tuple *t);
/* Module (if any) which this is connected to. */ /* Module (if any) which this is connected to. */
struct module *me; struct module *me;
}; };
#define MAX_IP_CT_PROTO 256
extern struct ip_conntrack_protocol *ip_ct_protos[MAX_IP_CT_PROTO];
/* Protocol registration. */ /* Protocol registration. */
extern int ip_conntrack_protocol_register(struct ip_conntrack_protocol *proto); extern int ip_conntrack_protocol_register(struct ip_conntrack_protocol *proto);
extern void ip_conntrack_protocol_unregister(struct ip_conntrack_protocol *proto); extern void ip_conntrack_protocol_unregister(struct ip_conntrack_protocol *proto);
static inline struct ip_conntrack_protocol *ip_ct_find_proto(u_int8_t protocol)
{
return ip_ct_protos[protocol];
}
/* Existing built-in protocols */ /* Existing built-in protocols */
extern struct ip_conntrack_protocol ip_conntrack_protocol_tcp; extern struct ip_conntrack_protocol ip_conntrack_protocol_tcp;
extern struct ip_conntrack_protocol ip_conntrack_protocol_udp; extern struct ip_conntrack_protocol ip_conntrack_protocol_udp;
...@@ -73,6 +74,11 @@ extern int ip_conntrack_protocol_tcp_init(void); ...@@ -73,6 +74,11 @@ extern int ip_conntrack_protocol_tcp_init(void);
/* Log invalid packets */ /* Log invalid packets */
extern unsigned int ip_ct_log_invalid; extern unsigned int ip_ct_log_invalid;
extern int ip_ct_port_tuple_to_nfattr(struct sk_buff *,
const struct ip_conntrack_tuple *);
extern int ip_ct_port_nfattr_to_tuple(struct nfattr *tb[],
struct ip_conntrack_tuple *);
#ifdef CONFIG_SYSCTL #ifdef CONFIG_SYSCTL
#ifdef DEBUG_INVALID_PACKETS #ifdef DEBUG_INVALID_PACKETS
#define LOG_INVALID(proto) \ #define LOG_INVALID(proto) \
......
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
#include <linux/init.h> #include <linux/init.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/netfilter_ipv4/ip_nat.h>
#include <linux/netfilter/nfnetlink_conntrack.h>
struct iphdr; struct iphdr;
struct ip_nat_range; struct ip_nat_range;
...@@ -15,6 +18,8 @@ struct ip_nat_protocol ...@@ -15,6 +18,8 @@ struct ip_nat_protocol
/* Protocol number. */ /* Protocol number. */
unsigned int protonum; unsigned int protonum;
struct module *me;
/* Translate a packet to the target according to manip type. /* Translate a packet to the target according to manip type.
Return true if succeeded. */ Return true if succeeded. */
int (*manip_pkt)(struct sk_buff **pskb, int (*manip_pkt)(struct sk_buff **pskb,
...@@ -43,19 +48,20 @@ struct ip_nat_protocol ...@@ -43,19 +48,20 @@ struct ip_nat_protocol
unsigned int (*print_range)(char *buffer, unsigned int (*print_range)(char *buffer,
const struct ip_nat_range *range); const struct ip_nat_range *range);
};
#define MAX_IP_NAT_PROTO 256 int (*range_to_nfattr)(struct sk_buff *skb,
extern struct ip_nat_protocol *ip_nat_protos[MAX_IP_NAT_PROTO]; const struct ip_nat_range *range);
int (*nfattr_to_range)(struct nfattr *tb[],
struct ip_nat_range *range);
};
/* Protocol registration. */ /* Protocol registration. */
extern int ip_nat_protocol_register(struct ip_nat_protocol *proto); extern int ip_nat_protocol_register(struct ip_nat_protocol *proto);
extern void ip_nat_protocol_unregister(struct ip_nat_protocol *proto); extern void ip_nat_protocol_unregister(struct ip_nat_protocol *proto);
static inline struct ip_nat_protocol *ip_nat_find_proto(u_int8_t protocol) extern struct ip_nat_protocol *ip_nat_proto_find_get(u_int8_t protocol);
{ extern void ip_nat_proto_put(struct ip_nat_protocol *proto);
return ip_nat_protos[protocol];
}
/* Built-in protocols. */ /* Built-in protocols. */
extern struct ip_nat_protocol ip_nat_protocol_tcp; extern struct ip_nat_protocol ip_nat_protocol_tcp;
...@@ -67,4 +73,9 @@ extern int init_protocols(void) __init; ...@@ -67,4 +73,9 @@ extern int init_protocols(void) __init;
extern void cleanup_protocols(void); extern void cleanup_protocols(void);
extern struct ip_nat_protocol *find_nat_proto(u_int16_t protonum); extern struct ip_nat_protocol *find_nat_proto(u_int16_t protonum);
extern int ip_nat_port_range_to_nfattr(struct sk_buff *skb,
const struct ip_nat_range *range);
extern int ip_nat_port_nfattr_to_range(struct nfattr *tb[],
struct ip_nat_range *range);
#endif /*_IP_NAT_PROTO_H*/ #endif /*_IP_NAT_PROTO_H*/
...@@ -702,5 +702,12 @@ config IP_NF_ARP_MANGLE ...@@ -702,5 +702,12 @@ config IP_NF_ARP_MANGLE
Allows altering the ARP packet payload: source and destination Allows altering the ARP packet payload: source and destination
hardware and network addresses. hardware and network addresses.
config IP_NF_CONNTRACK_NETLINK
tristate 'Connection tracking netlink interface'
depends on IP_NF_CONNTRACK && NETFILTER_NETLINK
help
This option enables support for a netlink-based userspace interface
endmenu endmenu
...@@ -9,6 +9,10 @@ iptable_nat-objs := ip_nat_standalone.o ip_nat_rule.o ip_nat_core.o ip_nat_helpe ...@@ -9,6 +9,10 @@ iptable_nat-objs := ip_nat_standalone.o ip_nat_rule.o ip_nat_core.o ip_nat_helpe
# connection tracking # connection tracking
obj-$(CONFIG_IP_NF_CONNTRACK) += ip_conntrack.o obj-$(CONFIG_IP_NF_CONNTRACK) += ip_conntrack.o
# conntrack netlink interface
obj-$(CONFIG_IP_NF_CONNTRACK_NETLINK) += ip_conntrack_netlink.o
# SCTP protocol connection tracking # SCTP protocol connection tracking
obj-$(CONFIG_IP_NF_CT_PROTO_SCTP) += ip_conntrack_proto_sctp.o obj-$(CONFIG_IP_NF_CT_PROTO_SCTP) += ip_conntrack_proto_sctp.o
......
This diff is collapsed.
This diff is collapsed.
...@@ -109,16 +109,17 @@ static int icmp_packet(struct ip_conntrack *ct, ...@@ -109,16 +109,17 @@ static int icmp_packet(struct ip_conntrack *ct,
return NF_ACCEPT; return NF_ACCEPT;
} }
static u_int8_t valid_new[] = {
[ICMP_ECHO] = 1,
[ICMP_TIMESTAMP] = 1,
[ICMP_INFO_REQUEST] = 1,
[ICMP_ADDRESS] = 1
};
/* Called when a new connection for this protocol found. */ /* Called when a new connection for this protocol found. */
static int icmp_new(struct ip_conntrack *conntrack, static int icmp_new(struct ip_conntrack *conntrack,
const struct sk_buff *skb) const struct sk_buff *skb)
{ {
static u_int8_t valid_new[]
= { [ICMP_ECHO] = 1,
[ICMP_TIMESTAMP] = 1,
[ICMP_INFO_REQUEST] = 1,
[ICMP_ADDRESS] = 1 };
if (conntrack->tuplehash[0].tuple.dst.u.icmp.type >= sizeof(valid_new) if (conntrack->tuplehash[0].tuple.dst.u.icmp.type >= sizeof(valid_new)
|| !valid_new[conntrack->tuplehash[0].tuple.dst.u.icmp.type]) { || !valid_new[conntrack->tuplehash[0].tuple.dst.u.icmp.type]) {
/* Can't create a new ICMP `conn' with this. */ /* Can't create a new ICMP `conn' with this. */
...@@ -159,11 +160,12 @@ icmp_error_message(struct sk_buff *skb, ...@@ -159,11 +160,12 @@ icmp_error_message(struct sk_buff *skb,
return NF_ACCEPT; return NF_ACCEPT;
} }
innerproto = ip_ct_find_proto(inside->ip.protocol); innerproto = ip_conntrack_proto_find_get(inside->ip.protocol);
dataoff = skb->nh.iph->ihl*4 + sizeof(inside->icmp) + inside->ip.ihl*4; dataoff = skb->nh.iph->ihl*4 + sizeof(inside->icmp) + inside->ip.ihl*4;
/* Are they talking about one of our connections? */ /* Are they talking about one of our connections? */
if (!ip_ct_get_tuple(&inside->ip, skb, dataoff, &origtuple, innerproto)) { if (!ip_ct_get_tuple(&inside->ip, skb, dataoff, &origtuple, innerproto)) {
DEBUGP("icmp_error: ! get_tuple p=%u", inside->ip.protocol); DEBUGP("icmp_error: ! get_tuple p=%u", inside->ip.protocol);
ip_conntrack_proto_put(innerproto);
return NF_ACCEPT; return NF_ACCEPT;
} }
...@@ -171,8 +173,10 @@ icmp_error_message(struct sk_buff *skb, ...@@ -171,8 +173,10 @@ icmp_error_message(struct sk_buff *skb,
been preserved inside the ICMP. */ been preserved inside the ICMP. */
if (!ip_ct_invert_tuple(&innertuple, &origtuple, innerproto)) { if (!ip_ct_invert_tuple(&innertuple, &origtuple, innerproto)) {
DEBUGP("icmp_error_track: Can't invert tuple\n"); DEBUGP("icmp_error_track: Can't invert tuple\n");
ip_conntrack_proto_put(innerproto);
return NF_ACCEPT; return NF_ACCEPT;
} }
ip_conntrack_proto_put(innerproto);
*ctinfo = IP_CT_RELATED; *ctinfo = IP_CT_RELATED;
...@@ -266,6 +270,47 @@ icmp_error(struct sk_buff *skb, enum ip_conntrack_info *ctinfo, ...@@ -266,6 +270,47 @@ icmp_error(struct sk_buff *skb, enum ip_conntrack_info *ctinfo,
return icmp_error_message(skb, ctinfo, hooknum); return icmp_error_message(skb, ctinfo, hooknum);
} }
#if defined(CONFIG_IP_NF_CONNTRACK_NETLINK) || \
defined(CONFIG_IP_NF_CONNTRACK_NETLINK_MODULE)
static int icmp_tuple_to_nfattr(struct sk_buff *skb,
const struct ip_conntrack_tuple *t)
{
NFA_PUT(skb, CTA_PROTO_ICMP_ID, sizeof(u_int16_t),
&t->src.u.icmp.id);
NFA_PUT(skb, CTA_PROTO_ICMP_TYPE, sizeof(u_int8_t),
&t->dst.u.icmp.type);
NFA_PUT(skb, CTA_PROTO_ICMP_CODE, sizeof(u_int8_t),
&t->dst.u.icmp.code);
if (t->dst.u.icmp.type >= sizeof(valid_new)
|| !valid_new[t->dst.u.icmp.type])
return -EINVAL;
return 0;
nfattr_failure:
return -1;
}
static int icmp_nfattr_to_tuple(struct nfattr *tb[],
struct ip_conntrack_tuple *tuple)
{
if (!tb[CTA_PROTO_ICMP_TYPE-1]
|| !tb[CTA_PROTO_ICMP_CODE-1]
|| !tb[CTA_PROTO_ICMP_ID-1])
return -1;
tuple->dst.u.icmp.type =
*(u_int8_t *)NFA_DATA(tb[CTA_PROTO_ICMP_TYPE-1]);
tuple->dst.u.icmp.code =
*(u_int8_t *)NFA_DATA(tb[CTA_PROTO_ICMP_CODE-1]);
tuple->src.u.icmp.id =
*(u_int8_t *)NFA_DATA(tb[CTA_PROTO_ICMP_ID-1]);
return 0;
}
#endif
struct ip_conntrack_protocol ip_conntrack_protocol_icmp = struct ip_conntrack_protocol ip_conntrack_protocol_icmp =
{ {
.proto = IPPROTO_ICMP, .proto = IPPROTO_ICMP,
...@@ -277,4 +322,9 @@ struct ip_conntrack_protocol ip_conntrack_protocol_icmp = ...@@ -277,4 +322,9 @@ struct ip_conntrack_protocol ip_conntrack_protocol_icmp =
.packet = icmp_packet, .packet = icmp_packet,
.new = icmp_new, .new = icmp_new,
.error = icmp_error, .error = icmp_error,
#if defined(CONFIG_IP_NF_CONNTRACK_NETLINK) || \
defined(CONFIG_IP_NF_CONNTRACK_NETLINK_MODULE)
.tuple_to_nfattr = icmp_tuple_to_nfattr,
.nfattr_to_tuple = icmp_nfattr_to_tuple,
#endif
}; };
...@@ -505,7 +505,12 @@ static struct ip_conntrack_protocol ip_conntrack_protocol_sctp = { ...@@ -505,7 +505,12 @@ static struct ip_conntrack_protocol ip_conntrack_protocol_sctp = {
.packet = sctp_packet, .packet = sctp_packet,
.new = sctp_new, .new = sctp_new,
.destroy = NULL, .destroy = NULL,
.me = THIS_MODULE .me = THIS_MODULE,
#if defined(CONFIG_IP_NF_CONNTRACK_NETLINK) || \
defined(CONFIG_IP_NF_CONNTRACK_NETLINK_MODULE)
.tuple_to_nfattr = ip_ct_port_tuple_to_nfattr,
.nfattr_to_tuple = ip_ct_port_nfattr_to_tuple,
#endif
}; };
#ifdef CONFIG_SYSCTL #ifdef CONFIG_SYSCTL
......
...@@ -336,6 +336,23 @@ static int tcp_print_conntrack(struct seq_file *s, ...@@ -336,6 +336,23 @@ static int tcp_print_conntrack(struct seq_file *s,
return seq_printf(s, "%s ", tcp_conntrack_names[state]); return seq_printf(s, "%s ", tcp_conntrack_names[state]);
} }
#if defined(CONFIG_IP_NF_CONNTRACK_NETLINK) || \
defined(CONFIG_IP_NF_CONNTRACK_NETLINK_MODULE)
static int tcp_to_nfattr(struct sk_buff *skb, struct nfattr *nfa,
const struct ip_conntrack *ct)
{
read_lock_bh(&tcp_lock);
NFA_PUT(skb, CTA_PROTOINFO_TCP_STATE, sizeof(u_int8_t),
&ct->proto.tcp.state);
read_unlock_bh(&tcp_lock);
return 0;
nfattr_failure:
return -1;
}
#endif
static unsigned int get_conntrack_index(const struct tcphdr *tcph) static unsigned int get_conntrack_index(const struct tcphdr *tcph)
{ {
if (tcph->rst) return TCP_RST_SET; if (tcph->rst) return TCP_RST_SET;
...@@ -1100,4 +1117,10 @@ struct ip_conntrack_protocol ip_conntrack_protocol_tcp = ...@@ -1100,4 +1117,10 @@ struct ip_conntrack_protocol ip_conntrack_protocol_tcp =
.packet = tcp_packet, .packet = tcp_packet,
.new = tcp_new, .new = tcp_new,
.error = tcp_error, .error = tcp_error,
#if defined(CONFIG_IP_NF_CONNTRACK_NETLINK) || \
defined(CONFIG_IP_NF_CONNTRACK_NETLINK_MODULE)
.to_nfattr = tcp_to_nfattr,
.tuple_to_nfattr = ip_ct_port_tuple_to_nfattr,
.nfattr_to_tuple = ip_ct_port_nfattr_to_tuple,
#endif
}; };
...@@ -145,4 +145,9 @@ struct ip_conntrack_protocol ip_conntrack_protocol_udp = ...@@ -145,4 +145,9 @@ struct ip_conntrack_protocol ip_conntrack_protocol_udp =
.packet = udp_packet, .packet = udp_packet,
.new = udp_new, .new = udp_new,
.error = udp_error, .error = udp_error,
#if defined(CONFIG_IP_NF_CONNTRACK_NETLINK) || \
defined(CONFIG_IP_NF_CONNTRACK_NETLINK_MODULE)
.tuple_to_nfattr = ip_ct_port_tuple_to_nfattr,
.nfattr_to_tuple = ip_ct_port_nfattr_to_tuple,
#endif
}; };
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
*/ */
/* (C) 1999-2001 Paul `Rusty' Russell /* (C) 1999-2001 Paul `Rusty' Russell
* (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org> * (C) 2002-2005 Netfilter Core Team <coreteam@netfilter.org>
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as * it under the terms of the GNU General Public License version 2 as
...@@ -147,8 +147,7 @@ static int ct_seq_show(struct seq_file *s, void *v) ...@@ -147,8 +147,7 @@ static int ct_seq_show(struct seq_file *s, void *v)
if (DIRECTION(hash)) if (DIRECTION(hash))
return 0; return 0;
proto = ip_ct_find_proto(conntrack->tuplehash[IP_CT_DIR_ORIGINAL] proto = __ip_conntrack_proto_find(conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum);
.tuple.dst.protonum);
IP_NF_ASSERT(proto); IP_NF_ASSERT(proto);
if (seq_printf(s, "%-8s %u %ld ", if (seq_printf(s, "%-8s %u %ld ",
...@@ -283,7 +282,7 @@ static int exp_seq_show(struct seq_file *s, void *v) ...@@ -283,7 +282,7 @@ static int exp_seq_show(struct seq_file *s, void *v)
seq_printf(s, "proto=%u ", expect->tuple.dst.protonum); seq_printf(s, "proto=%u ", expect->tuple.dst.protonum);
print_tuple(s, &expect->tuple, print_tuple(s, &expect->tuple,
ip_ct_find_proto(expect->tuple.dst.protonum)); __ip_conntrack_proto_find(expect->tuple.dst.protonum));
return seq_putc(s, '\n'); return seq_putc(s, '\n');
} }
...@@ -992,12 +991,16 @@ EXPORT_SYMBOL(ip_conntrack_helper_register); ...@@ -992,12 +991,16 @@ EXPORT_SYMBOL(ip_conntrack_helper_register);
EXPORT_SYMBOL(ip_conntrack_helper_unregister); EXPORT_SYMBOL(ip_conntrack_helper_unregister);
EXPORT_SYMBOL(ip_ct_iterate_cleanup); EXPORT_SYMBOL(ip_ct_iterate_cleanup);
EXPORT_SYMBOL(ip_ct_refresh_acct); EXPORT_SYMBOL(ip_ct_refresh_acct);
EXPORT_SYMBOL(ip_ct_protos);
EXPORT_SYMBOL(ip_ct_find_proto);
EXPORT_SYMBOL(ip_conntrack_expect_alloc); EXPORT_SYMBOL(ip_conntrack_expect_alloc);
EXPORT_SYMBOL(ip_conntrack_expect_put); EXPORT_SYMBOL(ip_conntrack_expect_put);
EXPORT_SYMBOL_GPL(ip_conntrack_expect_find_get);
EXPORT_SYMBOL(ip_conntrack_expect_related); EXPORT_SYMBOL(ip_conntrack_expect_related);
EXPORT_SYMBOL(ip_conntrack_unexpect_related); EXPORT_SYMBOL(ip_conntrack_unexpect_related);
EXPORT_SYMBOL_GPL(ip_conntrack_expect_list);
EXPORT_SYMBOL_GPL(__ip_conntrack_expect_find);
EXPORT_SYMBOL_GPL(__ip_ct_expect_unlink_destroy);
EXPORT_SYMBOL(ip_conntrack_tuple_taken); EXPORT_SYMBOL(ip_conntrack_tuple_taken);
EXPORT_SYMBOL(ip_ct_gather_frags); EXPORT_SYMBOL(ip_ct_gather_frags);
EXPORT_SYMBOL(ip_conntrack_htable_size); EXPORT_SYMBOL(ip_conntrack_htable_size);
...@@ -1005,7 +1008,28 @@ EXPORT_SYMBOL(ip_conntrack_lock); ...@@ -1005,7 +1008,28 @@ EXPORT_SYMBOL(ip_conntrack_lock);
EXPORT_SYMBOL(ip_conntrack_hash); EXPORT_SYMBOL(ip_conntrack_hash);
EXPORT_SYMBOL(ip_conntrack_untracked); EXPORT_SYMBOL(ip_conntrack_untracked);
EXPORT_SYMBOL_GPL(ip_conntrack_find_get); EXPORT_SYMBOL_GPL(ip_conntrack_find_get);
EXPORT_SYMBOL_GPL(ip_conntrack_put);
#ifdef CONFIG_IP_NF_NAT_NEEDED #ifdef CONFIG_IP_NF_NAT_NEEDED
EXPORT_SYMBOL(ip_conntrack_tcp_update); EXPORT_SYMBOL(ip_conntrack_tcp_update);
#endif #endif
EXPORT_SYMBOL_GPL(ip_conntrack_flush);
EXPORT_SYMBOL_GPL(__ip_conntrack_find);
EXPORT_SYMBOL_GPL(ip_conntrack_alloc);
EXPORT_SYMBOL_GPL(ip_conntrack_free);
EXPORT_SYMBOL_GPL(ip_conntrack_hash_insert);
EXPORT_SYMBOL_GPL(ip_ct_remove_expectations);
EXPORT_SYMBOL_GPL(ip_conntrack_helper_find_get);
EXPORT_SYMBOL_GPL(ip_conntrack_helper_put);
EXPORT_SYMBOL_GPL(__ip_conntrack_helper_find_byname);
EXPORT_SYMBOL_GPL(ip_conntrack_proto_find_get);
EXPORT_SYMBOL_GPL(ip_conntrack_proto_put);
EXPORT_SYMBOL_GPL(__ip_conntrack_proto_find);
#if defined(CONFIG_IP_NF_CONNTRACK_NETLINK) || \
defined(CONFIG_IP_NF_CONNTRACK_NETLINK_MODULE)
EXPORT_SYMBOL_GPL(ip_ct_port_tuple_to_nfattr);
EXPORT_SYMBOL_GPL(ip_ct_port_nfattr_to_tuple);
#endif
...@@ -47,8 +47,39 @@ DEFINE_RWLOCK(ip_nat_lock); ...@@ -47,8 +47,39 @@ DEFINE_RWLOCK(ip_nat_lock);
static unsigned int ip_nat_htable_size; static unsigned int ip_nat_htable_size;
static struct list_head *bysource; static struct list_head *bysource;
#define MAX_IP_NAT_PROTO 256
struct ip_nat_protocol *ip_nat_protos[MAX_IP_NAT_PROTO]; struct ip_nat_protocol *ip_nat_protos[MAX_IP_NAT_PROTO];
static inline struct ip_nat_protocol *
__ip_nat_proto_find(u_int8_t protonum)
{
return ip_nat_protos[protonum];
}
struct ip_nat_protocol *
ip_nat_proto_find_get(u_int8_t protonum)
{
struct ip_nat_protocol *p;
/* we need to disable preemption to make sure 'p' doesn't get
* removed until we've grabbed the reference */
preempt_disable();
p = __ip_nat_proto_find(protonum);
if (p) {
if (!try_module_get(p->me))
p = &ip_nat_unknown_protocol;
}
preempt_enable();
return p;
}
void
ip_nat_proto_put(struct ip_nat_protocol *p)
{
module_put(p->me);
}
/* We keep an extra hash for each conntrack, for fast searching. */ /* We keep an extra hash for each conntrack, for fast searching. */
static inline unsigned int static inline unsigned int
...@@ -103,7 +134,8 @@ static int ...@@ -103,7 +134,8 @@ static int
in_range(const struct ip_conntrack_tuple *tuple, in_range(const struct ip_conntrack_tuple *tuple,
const struct ip_nat_range *range) const struct ip_nat_range *range)
{ {
struct ip_nat_protocol *proto = ip_nat_find_proto(tuple->dst.protonum); struct ip_nat_protocol *proto =
__ip_nat_proto_find(tuple->dst.protonum);
/* If we are supposed to map IPs, then we must be in the /* If we are supposed to map IPs, then we must be in the
range specified, otherwise let this drag us onto a new src IP. */ range specified, otherwise let this drag us onto a new src IP. */
...@@ -216,8 +248,7 @@ get_unique_tuple(struct ip_conntrack_tuple *tuple, ...@@ -216,8 +248,7 @@ get_unique_tuple(struct ip_conntrack_tuple *tuple,
struct ip_conntrack *conntrack, struct ip_conntrack *conntrack,
enum ip_nat_manip_type maniptype) enum ip_nat_manip_type maniptype)
{ {
struct ip_nat_protocol *proto struct ip_nat_protocol *proto;
= ip_nat_find_proto(orig_tuple->dst.protonum);
/* 1) If this srcip/proto/src-proto-part is currently mapped, /* 1) If this srcip/proto/src-proto-part is currently mapped,
and that same mapping gives a unique tuple within the given and that same mapping gives a unique tuple within the given
...@@ -242,14 +273,20 @@ get_unique_tuple(struct ip_conntrack_tuple *tuple, ...@@ -242,14 +273,20 @@ get_unique_tuple(struct ip_conntrack_tuple *tuple,
/* 3) The per-protocol part of the manip is made to map into /* 3) The per-protocol part of the manip is made to map into
the range to make a unique tuple. */ the range to make a unique tuple. */
proto = ip_nat_proto_find_get(orig_tuple->dst.protonum);
/* Only bother mapping if it's not already in range and unique */ /* Only bother mapping if it's not already in range and unique */
if ((!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED) if ((!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED)
|| proto->in_range(tuple, maniptype, &range->min, &range->max)) || proto->in_range(tuple, maniptype, &range->min, &range->max))
&& !ip_nat_used_tuple(tuple, conntrack)) && !ip_nat_used_tuple(tuple, conntrack)) {
ip_nat_proto_put(proto);
return; return;
}
/* Last change: get protocol to try to obtain unique tuple. */ /* Last change: get protocol to try to obtain unique tuple. */
proto->unique_tuple(tuple, range, maniptype, conntrack); proto->unique_tuple(tuple, range, maniptype, conntrack);
ip_nat_proto_put(proto);
} }
unsigned int unsigned int
...@@ -320,6 +357,7 @@ manip_pkt(u_int16_t proto, ...@@ -320,6 +357,7 @@ manip_pkt(u_int16_t proto,
enum ip_nat_manip_type maniptype) enum ip_nat_manip_type maniptype)
{ {
struct iphdr *iph; struct iphdr *iph;
struct ip_nat_protocol *p;
if (!skb_ip_make_writable(pskb, iphdroff + sizeof(*iph))) if (!skb_ip_make_writable(pskb, iphdroff + sizeof(*iph)))
return 0; return 0;
...@@ -327,9 +365,12 @@ manip_pkt(u_int16_t proto, ...@@ -327,9 +365,12 @@ manip_pkt(u_int16_t proto,
iph = (void *)(*pskb)->data + iphdroff; iph = (void *)(*pskb)->data + iphdroff;
/* Manipulate protcol part. */ /* Manipulate protcol part. */
if (!ip_nat_find_proto(proto)->manip_pkt(pskb, iphdroff, p = ip_nat_proto_find_get(proto);
target, maniptype)) if (!p->manip_pkt(pskb, iphdroff, target, maniptype)) {
ip_nat_proto_put(p);
return 0; return 0;
}
ip_nat_proto_put(p);
iph = (void *)(*pskb)->data + iphdroff; iph = (void *)(*pskb)->data + iphdroff;
...@@ -425,7 +466,8 @@ int icmp_reply_translation(struct sk_buff **pskb, ...@@ -425,7 +466,8 @@ int icmp_reply_translation(struct sk_buff **pskb,
if (!ip_ct_get_tuple(&inside->ip, *pskb, (*pskb)->nh.iph->ihl*4 + if (!ip_ct_get_tuple(&inside->ip, *pskb, (*pskb)->nh.iph->ihl*4 +
sizeof(struct icmphdr) + inside->ip.ihl*4, sizeof(struct icmphdr) + inside->ip.ihl*4,
&inner, ip_ct_find_proto(inside->ip.protocol))) &inner,
__ip_conntrack_proto_find(inside->ip.protocol)))
return 0; return 0;
/* Change inner back to look like incoming packet. We do the /* Change inner back to look like incoming packet. We do the
...@@ -495,6 +537,49 @@ void ip_nat_protocol_unregister(struct ip_nat_protocol *proto) ...@@ -495,6 +537,49 @@ void ip_nat_protocol_unregister(struct ip_nat_protocol *proto)
synchronize_net(); synchronize_net();
} }
#if defined(CONFIG_IP_NF_CONNTRACK_NETLINK) || \
defined(CONFIG_IP_NF_CONNTRACK_NETLINK_MODULE)
int
ip_nat_port_range_to_nfattr(struct sk_buff *skb,
const struct ip_nat_range *range)
{
NFA_PUT(skb, CTA_PROTONAT_PORT_MIN, sizeof(u_int16_t),
&range->min.tcp.port);
NFA_PUT(skb, CTA_PROTONAT_PORT_MAX, sizeof(u_int16_t),
&range->max.tcp.port);
return 0;
nfattr_failure:
return -1;
}
int
ip_nat_port_nfattr_to_range(struct nfattr *tb[], struct ip_nat_range *range)
{
int ret = 0;
/* we have to return whether we actually parsed something or not */
if (tb[CTA_PROTONAT_PORT_MIN-1]) {
ret = 1;
range->min.tcp.port =
*(u_int16_t *)NFA_DATA(tb[CTA_PROTONAT_PORT_MIN-1]);
}
if (!tb[CTA_PROTONAT_PORT_MAX-1]) {
if (ret)
range->max.tcp.port = range->min.tcp.port;
} else {
ret = 1;
range->max.tcp.port =
*(u_int16_t *)NFA_DATA(tb[CTA_PROTONAT_PORT_MAX-1]);
}
return ret;
}
#endif
int __init ip_nat_init(void) int __init ip_nat_init(void)
{ {
size_t i; size_t i;
......
...@@ -107,10 +107,15 @@ icmp_print_range(char *buffer, const struct ip_nat_range *range) ...@@ -107,10 +107,15 @@ icmp_print_range(char *buffer, const struct ip_nat_range *range)
} }
struct ip_nat_protocol ip_nat_protocol_icmp struct ip_nat_protocol ip_nat_protocol_icmp
= { "ICMP", IPPROTO_ICMP, = { "ICMP", IPPROTO_ICMP, THIS_MODULE,
icmp_manip_pkt, icmp_manip_pkt,
icmp_in_range, icmp_in_range,
icmp_unique_tuple, icmp_unique_tuple,
icmp_print, icmp_print,
icmp_print_range icmp_print_range,
#if defined(CONFIG_IP_NF_CONNTRACK_NETLINK) || \
defined(CONFIG_IP_NF_CONNTRACK_NETLINK_MODULE)
ip_nat_port_range_to_nfattr,
ip_nat_port_nfattr_to_range,
#endif
}; };
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include <linux/ip.h> #include <linux/ip.h>
#include <linux/tcp.h> #include <linux/tcp.h>
#include <linux/if.h> #include <linux/if.h>
#include <linux/netfilter/nfnetlink_conntrack.h>
#include <linux/netfilter_ipv4/ip_nat.h> #include <linux/netfilter_ipv4/ip_nat.h>
#include <linux/netfilter_ipv4/ip_nat_rule.h> #include <linux/netfilter_ipv4/ip_nat_rule.h>
#include <linux/netfilter_ipv4/ip_nat_protocol.h> #include <linux/netfilter_ipv4/ip_nat_protocol.h>
...@@ -170,10 +171,15 @@ tcp_print_range(char *buffer, const struct ip_nat_range *range) ...@@ -170,10 +171,15 @@ tcp_print_range(char *buffer, const struct ip_nat_range *range)
} }
struct ip_nat_protocol ip_nat_protocol_tcp struct ip_nat_protocol ip_nat_protocol_tcp
= { "TCP", IPPROTO_TCP, = { "TCP", IPPROTO_TCP, THIS_MODULE,
tcp_manip_pkt, tcp_manip_pkt,
tcp_in_range, tcp_in_range,
tcp_unique_tuple, tcp_unique_tuple,
tcp_print, tcp_print,
tcp_print_range tcp_print_range,
#if defined(CONFIG_IP_NF_CONNTRACK_NETLINK) || \
defined(CONFIG_IP_NF_CONNTRACK_NETLINK_MODULE)
ip_nat_port_range_to_nfattr,
ip_nat_port_nfattr_to_range,
#endif
}; };
...@@ -157,10 +157,15 @@ udp_print_range(char *buffer, const struct ip_nat_range *range) ...@@ -157,10 +157,15 @@ udp_print_range(char *buffer, const struct ip_nat_range *range)
} }
struct ip_nat_protocol ip_nat_protocol_udp struct ip_nat_protocol ip_nat_protocol_udp
= { "UDP", IPPROTO_UDP, = { "UDP", IPPROTO_UDP, THIS_MODULE,
udp_manip_pkt, udp_manip_pkt,
udp_in_range, udp_in_range,
udp_unique_tuple, udp_unique_tuple,
udp_print, udp_print,
udp_print_range udp_print_range,
#if defined(CONFIG_IP_NF_CONNTRACK_NETLINK) || \
defined(CONFIG_IP_NF_CONNTRACK_NETLINK_MODULE)
ip_nat_port_range_to_nfattr,
ip_nat_port_nfattr_to_range,
#endif
}; };
...@@ -61,7 +61,7 @@ unknown_print_range(char *buffer, const struct ip_nat_range *range) ...@@ -61,7 +61,7 @@ unknown_print_range(char *buffer, const struct ip_nat_range *range)
} }
struct ip_nat_protocol ip_nat_unknown_protocol = { struct ip_nat_protocol ip_nat_unknown_protocol = {
"unknown", 0, "unknown", 0, THIS_MODULE,
unknown_manip_pkt, unknown_manip_pkt,
unknown_in_range, unknown_in_range,
unknown_unique_tuple, unknown_unique_tuple,
......
...@@ -394,6 +394,8 @@ module_exit(fini); ...@@ -394,6 +394,8 @@ module_exit(fini);
EXPORT_SYMBOL(ip_nat_setup_info); EXPORT_SYMBOL(ip_nat_setup_info);
EXPORT_SYMBOL(ip_nat_protocol_register); EXPORT_SYMBOL(ip_nat_protocol_register);
EXPORT_SYMBOL(ip_nat_protocol_unregister); EXPORT_SYMBOL(ip_nat_protocol_unregister);
EXPORT_SYMBOL_GPL(ip_nat_proto_find_get);
EXPORT_SYMBOL_GPL(ip_nat_proto_put);
EXPORT_SYMBOL(ip_nat_cheat_check); EXPORT_SYMBOL(ip_nat_cheat_check);
EXPORT_SYMBOL(ip_nat_mangle_tcp_packet); EXPORT_SYMBOL(ip_nat_mangle_tcp_packet);
EXPORT_SYMBOL(ip_nat_mangle_udp_packet); EXPORT_SYMBOL(ip_nat_mangle_udp_packet);
......
...@@ -121,6 +121,7 @@ void __nfa_fill(struct sk_buff *skb, int attrtype, int attrlen, ...@@ -121,6 +121,7 @@ void __nfa_fill(struct sk_buff *skb, int attrtype, int attrlen,
nfa->nfa_type = attrtype; nfa->nfa_type = attrtype;
nfa->nfa_len = size; nfa->nfa_len = size;
memcpy(NFA_DATA(nfa), data, attrlen); memcpy(NFA_DATA(nfa), data, attrlen);
memset(NFA_DATA(nfa) + attrlen, 0, NFA_ALIGN(size) - size);
} }
int nfattr_parse(struct nfattr *tb[], int maxattr, struct nfattr *nfa, int len) int nfattr_parse(struct nfattr *tb[], int maxattr, struct nfattr *nfa, int len)
......
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