Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
openanolis
cloud-kernel
提交
58a317f1
cloud-kernel
项目概览
openanolis
/
cloud-kernel
1 年多 前同步成功
通知
160
Star
36
Fork
7
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
10
列表
看板
标记
里程碑
合并请求
2
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
cloud-kernel
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
10
Issue
10
列表
看板
标记
里程碑
合并请求
2
合并请求
2
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
提交
58a317f1
编写于
8月 26, 2012
作者:
P
Patrick McHardy
提交者:
Pablo Neira Ayuso
8月 30, 2012
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
netfilter: ipv6: add IPv6 NAT support
Signed-off-by:
N
Patrick McHardy
<
kaber@trash.net
>
上级
2cf545e8
变更
13
隐藏空白更改
内联
并排
Showing
13 changed file
with
764 addition
and
2 deletion
+764
-2
include/linux/netfilter/nfnetlink_conntrack.h
include/linux/netfilter/nfnetlink_conntrack.h
+2
-0
include/net/netfilter/nf_nat_l3proto.h
include/net/netfilter/nf_nat_l3proto.h
+5
-0
include/net/netfilter/nf_nat_l4proto.h
include/net/netfilter/nf_nat_l4proto.h
+1
-0
include/net/netns/ipv6.h
include/net/netns/ipv6.h
+1
-0
net/core/secure_seq.c
net/core/secure_seq.c
+1
-0
net/ipv6/netfilter/Kconfig
net/ipv6/netfilter/Kconfig
+12
-0
net/ipv6/netfilter/Makefile
net/ipv6/netfilter/Makefile
+4
-0
net/ipv6/netfilter/ip6table_nat.c
net/ipv6/netfilter/ip6table_nat.c
+321
-0
net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
+35
-2
net/ipv6/netfilter/nf_nat_l3proto_ipv6.c
net/ipv6/netfilter/nf_nat_l3proto_ipv6.c
+287
-0
net/ipv6/netfilter/nf_nat_proto_icmpv6.c
net/ipv6/netfilter/nf_nat_proto_icmpv6.c
+90
-0
net/netfilter/nf_nat_core.c
net/netfilter/nf_nat_core.c
+2
-0
net/netfilter/xt_nat.c
net/netfilter/xt_nat.c
+3
-0
未找到文件。
include/linux/netfilter/nfnetlink_conntrack.h
浏览文件 @
58a317f1
...
@@ -147,6 +147,8 @@ enum ctattr_nat {
...
@@ -147,6 +147,8 @@ enum ctattr_nat {
CTA_NAT_V4_MAXIP
,
CTA_NAT_V4_MAXIP
,
#define CTA_NAT_MAXIP CTA_NAT_V4_MAXIP
#define CTA_NAT_MAXIP CTA_NAT_V4_MAXIP
CTA_NAT_PROTO
,
CTA_NAT_PROTO
,
CTA_NAT_V6_MINIP
,
CTA_NAT_V6_MAXIP
,
__CTA_NAT_MAX
__CTA_NAT_MAX
};
};
#define CTA_NAT_MAX (__CTA_NAT_MAX - 1)
#define CTA_NAT_MAX (__CTA_NAT_MAX - 1)
...
...
include/net/netfilter/nf_nat_l3proto.h
浏览文件 @
58a317f1
...
@@ -43,5 +43,10 @@ extern int nf_nat_icmp_reply_translation(struct sk_buff *skb,
...
@@ -43,5 +43,10 @@ extern int nf_nat_icmp_reply_translation(struct sk_buff *skb,
struct
nf_conn
*
ct
,
struct
nf_conn
*
ct
,
enum
ip_conntrack_info
ctinfo
,
enum
ip_conntrack_info
ctinfo
,
unsigned
int
hooknum
);
unsigned
int
hooknum
);
extern
int
nf_nat_icmpv6_reply_translation
(
struct
sk_buff
*
skb
,
struct
nf_conn
*
ct
,
enum
ip_conntrack_info
ctinfo
,
unsigned
int
hooknum
,
unsigned
int
hdrlen
);
#endif
/* _NF_NAT_L3PROTO_H */
#endif
/* _NF_NAT_L3PROTO_H */
include/net/netfilter/nf_nat_l4proto.h
浏览文件 @
58a317f1
...
@@ -51,6 +51,7 @@ extern const struct nf_nat_l4proto *__nf_nat_l4proto_find(u8 l3proto, u8 l4proto
...
@@ -51,6 +51,7 @@ extern const struct nf_nat_l4proto *__nf_nat_l4proto_find(u8 l3proto, u8 l4proto
extern
const
struct
nf_nat_l4proto
nf_nat_l4proto_tcp
;
extern
const
struct
nf_nat_l4proto
nf_nat_l4proto_tcp
;
extern
const
struct
nf_nat_l4proto
nf_nat_l4proto_udp
;
extern
const
struct
nf_nat_l4proto
nf_nat_l4proto_udp
;
extern
const
struct
nf_nat_l4proto
nf_nat_l4proto_icmp
;
extern
const
struct
nf_nat_l4proto
nf_nat_l4proto_icmp
;
extern
const
struct
nf_nat_l4proto
nf_nat_l4proto_icmpv6
;
extern
const
struct
nf_nat_l4proto
nf_nat_l4proto_unknown
;
extern
const
struct
nf_nat_l4proto
nf_nat_l4proto_unknown
;
extern
bool
nf_nat_l4proto_in_range
(
const
struct
nf_conntrack_tuple
*
tuple
,
extern
bool
nf_nat_l4proto_in_range
(
const
struct
nf_conntrack_tuple
*
tuple
,
...
...
include/net/netns/ipv6.h
浏览文件 @
58a317f1
...
@@ -42,6 +42,7 @@ struct netns_ipv6 {
...
@@ -42,6 +42,7 @@ struct netns_ipv6 {
#ifdef CONFIG_SECURITY
#ifdef CONFIG_SECURITY
struct
xt_table
*
ip6table_security
;
struct
xt_table
*
ip6table_security
;
#endif
#endif
struct
xt_table
*
ip6table_nat
;
#endif
#endif
struct
rt6_info
*
ip6_null_entry
;
struct
rt6_info
*
ip6_null_entry
;
struct
rt6_statistics
*
rt6_stats
;
struct
rt6_statistics
*
rt6_stats
;
...
...
net/core/secure_seq.c
浏览文件 @
58a317f1
...
@@ -76,6 +76,7 @@ u32 secure_ipv6_port_ephemeral(const __be32 *saddr, const __be32 *daddr,
...
@@ -76,6 +76,7 @@ u32 secure_ipv6_port_ephemeral(const __be32 *saddr, const __be32 *daddr,
return
hash
[
0
];
return
hash
[
0
];
}
}
EXPORT_SYMBOL
(
secure_ipv6_port_ephemeral
);
#endif
#endif
#ifdef CONFIG_INET
#ifdef CONFIG_INET
...
...
net/ipv6/netfilter/Kconfig
浏览文件 @
58a317f1
...
@@ -25,6 +25,18 @@ config NF_CONNTRACK_IPV6
...
@@ -25,6 +25,18 @@ config NF_CONNTRACK_IPV6
To compile it as a module, choose M here. If unsure, say N.
To compile it as a module, choose M here. If unsure, say N.
config NF_NAT_IPV6
tristate "IPv6 NAT"
depends on NF_CONNTRACK_IPV6
depends on NETFILTER_ADVANCED
select NF_NAT
help
The IPv6 NAT option allows masquerading, port forwarding and other
forms of full Network Address Port Translation. It is controlled by
the `nat' table in ip6tables, see the man page for ip6tables(8).
To compile it as a module, choose M here. If unsure, say N.
config IP6_NF_IPTABLES
config IP6_NF_IPTABLES
tristate "IP6 tables support (required for filtering)"
tristate "IP6 tables support (required for filtering)"
depends on INET && IPV6
depends on INET && IPV6
...
...
net/ipv6/netfilter/Makefile
浏览文件 @
58a317f1
...
@@ -8,6 +8,7 @@ obj-$(CONFIG_IP6_NF_FILTER) += ip6table_filter.o
...
@@ -8,6 +8,7 @@ obj-$(CONFIG_IP6_NF_FILTER) += ip6table_filter.o
obj-$(CONFIG_IP6_NF_MANGLE)
+=
ip6table_mangle.o
obj-$(CONFIG_IP6_NF_MANGLE)
+=
ip6table_mangle.o
obj-$(CONFIG_IP6_NF_RAW)
+=
ip6table_raw.o
obj-$(CONFIG_IP6_NF_RAW)
+=
ip6table_raw.o
obj-$(CONFIG_IP6_NF_SECURITY)
+=
ip6table_security.o
obj-$(CONFIG_IP6_NF_SECURITY)
+=
ip6table_security.o
obj-$(CONFIG_NF_NAT_IPV6)
+=
ip6table_nat.o
# objects for l3 independent conntrack
# objects for l3 independent conntrack
nf_conntrack_ipv6-y
:=
nf_conntrack_l3proto_ipv6.o nf_conntrack_proto_icmpv6.o
nf_conntrack_ipv6-y
:=
nf_conntrack_l3proto_ipv6.o nf_conntrack_proto_icmpv6.o
...
@@ -15,6 +16,9 @@ nf_conntrack_ipv6-y := nf_conntrack_l3proto_ipv6.o nf_conntrack_proto_icmpv6.o
...
@@ -15,6 +16,9 @@ nf_conntrack_ipv6-y := nf_conntrack_l3proto_ipv6.o nf_conntrack_proto_icmpv6.o
# l3 independent conntrack
# l3 independent conntrack
obj-$(CONFIG_NF_CONNTRACK_IPV6)
+=
nf_conntrack_ipv6.o nf_defrag_ipv6.o
obj-$(CONFIG_NF_CONNTRACK_IPV6)
+=
nf_conntrack_ipv6.o nf_defrag_ipv6.o
nf_nat_ipv6-y
:=
nf_nat_l3proto_ipv6.o nf_nat_proto_icmpv6.o
obj-$(CONFIG_NF_NAT_IPV6)
+=
nf_nat_ipv6.o
# defrag
# defrag
nf_defrag_ipv6-y
:=
nf_defrag_ipv6_hooks.o nf_conntrack_reasm.o
nf_defrag_ipv6-y
:=
nf_defrag_ipv6_hooks.o nf_conntrack_reasm.o
obj-$(CONFIG_NF_DEFRAG_IPV6)
+=
nf_defrag_ipv6.o
obj-$(CONFIG_NF_DEFRAG_IPV6)
+=
nf_defrag_ipv6.o
...
...
net/ipv6/netfilter/ip6table_nat.c
0 → 100644
浏览文件 @
58a317f1
/*
* Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
*
* 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
* published by the Free Software Foundation.
*
* Based on Rusty Russell's IPv4 NAT code. Development of IPv6 NAT
* funded by Astaro.
*/
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv6.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <linux/ipv6.h>
#include <net/ipv6.h>
#include <net/netfilter/nf_nat.h>
#include <net/netfilter/nf_nat_core.h>
#include <net/netfilter/nf_nat_l3proto.h>
static
const
struct
xt_table
nf_nat_ipv6_table
=
{
.
name
=
"nat"
,
.
valid_hooks
=
(
1
<<
NF_INET_PRE_ROUTING
)
|
(
1
<<
NF_INET_POST_ROUTING
)
|
(
1
<<
NF_INET_LOCAL_OUT
)
|
(
1
<<
NF_INET_LOCAL_IN
),
.
me
=
THIS_MODULE
,
.
af
=
NFPROTO_IPV6
,
};
static
unsigned
int
alloc_null_binding
(
struct
nf_conn
*
ct
,
unsigned
int
hooknum
)
{
/* Force range to this IP; let proto decide mapping for
* per-proto parts (hence not IP_NAT_RANGE_PROTO_SPECIFIED).
*/
struct
nf_nat_range
range
;
range
.
flags
=
0
;
pr_debug
(
"Allocating NULL binding for %p (%pI6)
\n
"
,
ct
,
HOOK2MANIP
(
hooknum
)
==
NF_NAT_MANIP_SRC
?
&
ct
->
tuplehash
[
IP_CT_DIR_REPLY
].
tuple
.
dst
.
u3
.
ip6
:
&
ct
->
tuplehash
[
IP_CT_DIR_REPLY
].
tuple
.
src
.
u3
.
ip6
);
return
nf_nat_setup_info
(
ct
,
&
range
,
HOOK2MANIP
(
hooknum
));
}
static
unsigned
int
nf_nat_rule_find
(
struct
sk_buff
*
skb
,
unsigned
int
hooknum
,
const
struct
net_device
*
in
,
const
struct
net_device
*
out
,
struct
nf_conn
*
ct
)
{
struct
net
*
net
=
nf_ct_net
(
ct
);
unsigned
int
ret
;
ret
=
ip6t_do_table
(
skb
,
hooknum
,
in
,
out
,
net
->
ipv6
.
ip6table_nat
);
if
(
ret
==
NF_ACCEPT
)
{
if
(
!
nf_nat_initialized
(
ct
,
HOOK2MANIP
(
hooknum
)))
ret
=
alloc_null_binding
(
ct
,
hooknum
);
}
return
ret
;
}
static
unsigned
int
nf_nat_ipv6_fn
(
unsigned
int
hooknum
,
struct
sk_buff
*
skb
,
const
struct
net_device
*
in
,
const
struct
net_device
*
out
,
int
(
*
okfn
)(
struct
sk_buff
*
))
{
struct
nf_conn
*
ct
;
enum
ip_conntrack_info
ctinfo
;
struct
nf_conn_nat
*
nat
;
enum
nf_nat_manip_type
maniptype
=
HOOK2MANIP
(
hooknum
);
__be16
frag_off
;
int
hdrlen
;
u8
nexthdr
;
ct
=
nf_ct_get
(
skb
,
&
ctinfo
);
/* Can't track? It's not due to stress, or conntrack would
* have dropped it. Hence it's the user's responsibilty to
* packet filter it out, or implement conntrack/NAT for that
* protocol. 8) --RR
*/
if
(
!
ct
)
return
NF_ACCEPT
;
/* Don't try to NAT if this packet is not conntracked */
if
(
nf_ct_is_untracked
(
ct
))
return
NF_ACCEPT
;
nat
=
nfct_nat
(
ct
);
if
(
!
nat
)
{
/* NAT module was loaded late. */
if
(
nf_ct_is_confirmed
(
ct
))
return
NF_ACCEPT
;
nat
=
nf_ct_ext_add
(
ct
,
NF_CT_EXT_NAT
,
GFP_ATOMIC
);
if
(
nat
==
NULL
)
{
pr_debug
(
"failed to add NAT extension
\n
"
);
return
NF_ACCEPT
;
}
}
switch
(
ctinfo
)
{
case
IP_CT_RELATED
:
case
IP_CT_RELATED_REPLY
:
nexthdr
=
ipv6_hdr
(
skb
)
->
nexthdr
;
hdrlen
=
ipv6_skip_exthdr
(
skb
,
sizeof
(
struct
ipv6hdr
),
&
nexthdr
,
&
frag_off
);
if
(
hdrlen
>=
0
&&
nexthdr
==
IPPROTO_ICMPV6
)
{
if
(
!
nf_nat_icmpv6_reply_translation
(
skb
,
ct
,
ctinfo
,
hooknum
,
hdrlen
))
return
NF_DROP
;
else
return
NF_ACCEPT
;
}
/* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */
case
IP_CT_NEW
:
/* Seen it before? This can happen for loopback, retrans,
* or local packets.
*/
if
(
!
nf_nat_initialized
(
ct
,
maniptype
))
{
unsigned
int
ret
;
ret
=
nf_nat_rule_find
(
skb
,
hooknum
,
in
,
out
,
ct
);
if
(
ret
!=
NF_ACCEPT
)
return
ret
;
}
else
pr_debug
(
"Already setup manip %s for ct %p
\n
"
,
maniptype
==
NF_NAT_MANIP_SRC
?
"SRC"
:
"DST"
,
ct
);
break
;
default:
/* ESTABLISHED */
NF_CT_ASSERT
(
ctinfo
==
IP_CT_ESTABLISHED
||
ctinfo
==
IP_CT_ESTABLISHED_REPLY
);
}
return
nf_nat_packet
(
ct
,
ctinfo
,
hooknum
,
skb
);
}
static
unsigned
int
nf_nat_ipv6_in
(
unsigned
int
hooknum
,
struct
sk_buff
*
skb
,
const
struct
net_device
*
in
,
const
struct
net_device
*
out
,
int
(
*
okfn
)(
struct
sk_buff
*
))
{
unsigned
int
ret
;
struct
in6_addr
daddr
=
ipv6_hdr
(
skb
)
->
daddr
;
ret
=
nf_nat_ipv6_fn
(
hooknum
,
skb
,
in
,
out
,
okfn
);
if
(
ret
!=
NF_DROP
&&
ret
!=
NF_STOLEN
&&
ipv6_addr_cmp
(
&
daddr
,
&
ipv6_hdr
(
skb
)
->
daddr
))
skb_dst_drop
(
skb
);
return
ret
;
}
static
unsigned
int
nf_nat_ipv6_out
(
unsigned
int
hooknum
,
struct
sk_buff
*
skb
,
const
struct
net_device
*
in
,
const
struct
net_device
*
out
,
int
(
*
okfn
)(
struct
sk_buff
*
))
{
#ifdef CONFIG_XFRM
const
struct
nf_conn
*
ct
;
enum
ip_conntrack_info
ctinfo
;
#endif
unsigned
int
ret
;
/* root is playing with raw sockets. */
if
(
skb
->
len
<
sizeof
(
struct
ipv6hdr
))
return
NF_ACCEPT
;
ret
=
nf_nat_ipv6_fn
(
hooknum
,
skb
,
in
,
out
,
okfn
);
#ifdef CONFIG_XFRM
if
(
ret
!=
NF_DROP
&&
ret
!=
NF_STOLEN
&&
!
(
IP6CB
(
skb
)
->
flags
&
IP6SKB_XFRM_TRANSFORMED
)
&&
(
ct
=
nf_ct_get
(
skb
,
&
ctinfo
))
!=
NULL
)
{
enum
ip_conntrack_dir
dir
=
CTINFO2DIR
(
ctinfo
);
if
(
!
nf_inet_addr_cmp
(
&
ct
->
tuplehash
[
dir
].
tuple
.
src
.
u3
,
&
ct
->
tuplehash
[
!
dir
].
tuple
.
dst
.
u3
)
||
(
ct
->
tuplehash
[
dir
].
tuple
.
src
.
u
.
all
!=
ct
->
tuplehash
[
!
dir
].
tuple
.
dst
.
u
.
all
))
if
(
nf_xfrm_me_harder
(
skb
,
AF_INET6
)
<
0
)
ret
=
NF_DROP
;
}
#endif
return
ret
;
}
static
unsigned
int
nf_nat_ipv6_local_fn
(
unsigned
int
hooknum
,
struct
sk_buff
*
skb
,
const
struct
net_device
*
in
,
const
struct
net_device
*
out
,
int
(
*
okfn
)(
struct
sk_buff
*
))
{
const
struct
nf_conn
*
ct
;
enum
ip_conntrack_info
ctinfo
;
unsigned
int
ret
;
/* root is playing with raw sockets. */
if
(
skb
->
len
<
sizeof
(
struct
ipv6hdr
))
return
NF_ACCEPT
;
ret
=
nf_nat_ipv6_fn
(
hooknum
,
skb
,
in
,
out
,
okfn
);
if
(
ret
!=
NF_DROP
&&
ret
!=
NF_STOLEN
&&
(
ct
=
nf_ct_get
(
skb
,
&
ctinfo
))
!=
NULL
)
{
enum
ip_conntrack_dir
dir
=
CTINFO2DIR
(
ctinfo
);
if
(
!
nf_inet_addr_cmp
(
&
ct
->
tuplehash
[
dir
].
tuple
.
dst
.
u3
,
&
ct
->
tuplehash
[
!
dir
].
tuple
.
src
.
u3
))
{
if
(
ip6_route_me_harder
(
skb
))
ret
=
NF_DROP
;
}
#ifdef CONFIG_XFRM
else
if
(
!
(
IP6CB
(
skb
)
->
flags
&
IP6SKB_XFRM_TRANSFORMED
)
&&
ct
->
tuplehash
[
dir
].
tuple
.
dst
.
u
.
all
!=
ct
->
tuplehash
[
!
dir
].
tuple
.
src
.
u
.
all
)
if
(
nf_xfrm_me_harder
(
skb
,
AF_INET6
))
ret
=
NF_DROP
;
#endif
}
return
ret
;
}
static
struct
nf_hook_ops
nf_nat_ipv6_ops
[]
__read_mostly
=
{
/* Before packet filtering, change destination */
{
.
hook
=
nf_nat_ipv6_in
,
.
owner
=
THIS_MODULE
,
.
pf
=
NFPROTO_IPV6
,
.
hooknum
=
NF_INET_PRE_ROUTING
,
.
priority
=
NF_IP6_PRI_NAT_DST
,
},
/* After packet filtering, change source */
{
.
hook
=
nf_nat_ipv6_out
,
.
owner
=
THIS_MODULE
,
.
pf
=
NFPROTO_IPV6
,
.
hooknum
=
NF_INET_POST_ROUTING
,
.
priority
=
NF_IP6_PRI_NAT_SRC
,
},
/* Before packet filtering, change destination */
{
.
hook
=
nf_nat_ipv6_local_fn
,
.
owner
=
THIS_MODULE
,
.
pf
=
NFPROTO_IPV6
,
.
hooknum
=
NF_INET_LOCAL_OUT
,
.
priority
=
NF_IP6_PRI_NAT_DST
,
},
/* After packet filtering, change source */
{
.
hook
=
nf_nat_ipv6_fn
,
.
owner
=
THIS_MODULE
,
.
pf
=
NFPROTO_IPV6
,
.
hooknum
=
NF_INET_LOCAL_IN
,
.
priority
=
NF_IP6_PRI_NAT_SRC
,
},
};
static
int
__net_init
ip6table_nat_net_init
(
struct
net
*
net
)
{
struct
ip6t_replace
*
repl
;
repl
=
ip6t_alloc_initial_table
(
&
nf_nat_ipv6_table
);
if
(
repl
==
NULL
)
return
-
ENOMEM
;
net
->
ipv6
.
ip6table_nat
=
ip6t_register_table
(
net
,
&
nf_nat_ipv6_table
,
repl
);
kfree
(
repl
);
if
(
IS_ERR
(
net
->
ipv6
.
ip6table_nat
))
return
PTR_ERR
(
net
->
ipv6
.
ip6table_nat
);
return
0
;
}
static
void
__net_exit
ip6table_nat_net_exit
(
struct
net
*
net
)
{
ip6t_unregister_table
(
net
,
net
->
ipv6
.
ip6table_nat
);
}
static
struct
pernet_operations
ip6table_nat_net_ops
=
{
.
init
=
ip6table_nat_net_init
,
.
exit
=
ip6table_nat_net_exit
,
};
static
int
__init
ip6table_nat_init
(
void
)
{
int
err
;
err
=
register_pernet_subsys
(
&
ip6table_nat_net_ops
);
if
(
err
<
0
)
goto
err1
;
err
=
nf_register_hooks
(
nf_nat_ipv6_ops
,
ARRAY_SIZE
(
nf_nat_ipv6_ops
));
if
(
err
<
0
)
goto
err2
;
return
0
;
err2:
unregister_pernet_subsys
(
&
ip6table_nat_net_ops
);
err1:
return
err
;
}
static
void
__exit
ip6table_nat_exit
(
void
)
{
nf_unregister_hooks
(
nf_nat_ipv6_ops
,
ARRAY_SIZE
(
nf_nat_ipv6_ops
));
unregister_pernet_subsys
(
&
ip6table_nat_net_ops
);
}
module_init
(
ip6table_nat_init
);
module_exit
(
ip6table_nat_exit
);
MODULE_LICENSE
(
"GPL"
);
net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
浏览文件 @
58a317f1
...
@@ -28,6 +28,7 @@
...
@@ -28,6 +28,7 @@
#include <net/netfilter/nf_conntrack_core.h>
#include <net/netfilter/nf_conntrack_core.h>
#include <net/netfilter/nf_conntrack_zones.h>
#include <net/netfilter/nf_conntrack_zones.h>
#include <net/netfilter/ipv6/nf_conntrack_ipv6.h>
#include <net/netfilter/ipv6/nf_conntrack_ipv6.h>
#include <net/netfilter/nf_nat_helper.h>
#include <net/netfilter/ipv6/nf_defrag_ipv6.h>
#include <net/netfilter/ipv6/nf_defrag_ipv6.h>
#include <net/netfilter/nf_log.h>
#include <net/netfilter/nf_log.h>
...
@@ -142,6 +143,36 @@ static unsigned int ipv6_confirm(unsigned int hooknum,
...
@@ -142,6 +143,36 @@ static unsigned int ipv6_confirm(unsigned int hooknum,
const
struct
net_device
*
out
,
const
struct
net_device
*
out
,
int
(
*
okfn
)(
struct
sk_buff
*
))
int
(
*
okfn
)(
struct
sk_buff
*
))
{
{
struct
nf_conn
*
ct
;
enum
ip_conntrack_info
ctinfo
;
unsigned
char
pnum
=
ipv6_hdr
(
skb
)
->
nexthdr
;
int
protoff
;
__be16
frag_off
;
ct
=
nf_ct_get
(
skb
,
&
ctinfo
);
if
(
!
ct
||
ctinfo
==
IP_CT_RELATED_REPLY
)
goto
out
;
protoff
=
ipv6_skip_exthdr
(
skb
,
sizeof
(
struct
ipv6hdr
),
&
pnum
,
&
frag_off
);
if
(
protoff
<
0
||
(
frag_off
&
htons
(
~
0x7
))
!=
0
)
{
pr_debug
(
"proto header not found
\n
"
);
goto
out
;
}
/* adjust seqs for loopback traffic only in outgoing direction */
if
(
test_bit
(
IPS_SEQ_ADJUST_BIT
,
&
ct
->
status
)
&&
!
nf_is_loopback_packet
(
skb
))
{
typeof
(
nf_nat_seq_adjust_hook
)
seq_adjust
;
seq_adjust
=
rcu_dereference
(
nf_nat_seq_adjust_hook
);
if
(
!
seq_adjust
||
!
seq_adjust
(
skb
,
ct
,
ctinfo
,
protoff
))
{
NF_CT_STAT_INC_ATOMIC
(
nf_ct_net
(
ct
),
drop
);
return
NF_DROP
;
}
}
out:
/* We've seen it coming out the other side: confirm it */
/* We've seen it coming out the other side: confirm it */
return
nf_conntrack_confirm
(
skb
);
return
nf_conntrack_confirm
(
skb
);
}
}
...
@@ -170,12 +201,14 @@ static unsigned int __ipv6_conntrack_in(struct net *net,
...
@@ -170,12 +201,14 @@ static unsigned int __ipv6_conntrack_in(struct net *net,
}
}
/* Conntrack helpers need the entire reassembled packet in the
/* Conntrack helpers need the entire reassembled packet in the
* POST_ROUTING hook.
* POST_ROUTING hook. In case of unconfirmed connections NAT
* might reassign a helper, so the entire packet is also
* required.
*/
*/
ct
=
nf_ct_get
(
reasm
,
&
ctinfo
);
ct
=
nf_ct_get
(
reasm
,
&
ctinfo
);
if
(
ct
!=
NULL
&&
!
nf_ct_is_untracked
(
ct
))
{
if
(
ct
!=
NULL
&&
!
nf_ct_is_untracked
(
ct
))
{
help
=
nfct_help
(
ct
);
help
=
nfct_help
(
ct
);
if
(
help
&&
help
->
helper
)
{
if
(
(
help
&&
help
->
helper
)
||
!
nf_ct_is_confirmed
(
ct
)
)
{
nf_conntrack_get_reasm
(
skb
);
nf_conntrack_get_reasm
(
skb
);
NF_HOOK_THRESH
(
NFPROTO_IPV6
,
hooknum
,
reasm
,
NF_HOOK_THRESH
(
NFPROTO_IPV6
,
hooknum
,
reasm
,
(
struct
net_device
*
)
in
,
(
struct
net_device
*
)
in
,
...
...
net/ipv6/netfilter/nf_nat_l3proto_ipv6.c
0 → 100644
浏览文件 @
58a317f1
/*
* Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
*
* 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
* published by the Free Software Foundation.
*
* Development of IPv6 NAT funded by Astaro.
*/
#include <linux/types.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/ipv6.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv6.h>
#include <net/secure_seq.h>
#include <net/checksum.h>
#include <net/ip6_route.h>
#include <net/ipv6.h>
#include <net/netfilter/nf_conntrack_core.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_nat_core.h>
#include <net/netfilter/nf_nat_l3proto.h>
#include <net/netfilter/nf_nat_l4proto.h>
static
const
struct
nf_nat_l3proto
nf_nat_l3proto_ipv6
;
#ifdef CONFIG_XFRM
static
void
nf_nat_ipv6_decode_session
(
struct
sk_buff
*
skb
,
const
struct
nf_conn
*
ct
,
enum
ip_conntrack_dir
dir
,
unsigned
long
statusbit
,
struct
flowi
*
fl
)
{
const
struct
nf_conntrack_tuple
*
t
=
&
ct
->
tuplehash
[
dir
].
tuple
;
struct
flowi6
*
fl6
=
&
fl
->
u
.
ip6
;
if
(
ct
->
status
&
statusbit
)
{
fl6
->
daddr
=
t
->
dst
.
u3
.
in6
;
if
(
t
->
dst
.
protonum
==
IPPROTO_TCP
||
t
->
dst
.
protonum
==
IPPROTO_UDP
||
t
->
dst
.
protonum
==
IPPROTO_UDPLITE
||
t
->
dst
.
protonum
==
IPPROTO_DCCP
||
t
->
dst
.
protonum
==
IPPROTO_SCTP
)
fl6
->
fl6_dport
=
t
->
dst
.
u
.
all
;
}
statusbit
^=
IPS_NAT_MASK
;
if
(
ct
->
status
&
statusbit
)
{
fl6
->
saddr
=
t
->
src
.
u3
.
in6
;
if
(
t
->
dst
.
protonum
==
IPPROTO_TCP
||
t
->
dst
.
protonum
==
IPPROTO_UDP
||
t
->
dst
.
protonum
==
IPPROTO_UDPLITE
||
t
->
dst
.
protonum
==
IPPROTO_DCCP
||
t
->
dst
.
protonum
==
IPPROTO_SCTP
)
fl6
->
fl6_sport
=
t
->
src
.
u
.
all
;
}
}
#endif
static
bool
nf_nat_ipv6_in_range
(
const
struct
nf_conntrack_tuple
*
t
,
const
struct
nf_nat_range
*
range
)
{
return
ipv6_addr_cmp
(
&
t
->
src
.
u3
.
in6
,
&
range
->
min_addr
.
in6
)
>=
0
&&
ipv6_addr_cmp
(
&
t
->
src
.
u3
.
in6
,
&
range
->
max_addr
.
in6
)
<=
0
;
}
static
u32
nf_nat_ipv6_secure_port
(
const
struct
nf_conntrack_tuple
*
t
,
__be16
dport
)
{
return
secure_ipv6_port_ephemeral
(
t
->
src
.
u3
.
ip6
,
t
->
dst
.
u3
.
ip6
,
dport
);
}
static
bool
nf_nat_ipv6_manip_pkt
(
struct
sk_buff
*
skb
,
unsigned
int
iphdroff
,
const
struct
nf_nat_l4proto
*
l4proto
,
const
struct
nf_conntrack_tuple
*
target
,
enum
nf_nat_manip_type
maniptype
)
{
struct
ipv6hdr
*
ipv6h
;
__be16
frag_off
;
int
hdroff
;
u8
nexthdr
;
if
(
!
skb_make_writable
(
skb
,
iphdroff
+
sizeof
(
*
ipv6h
)))
return
false
;
ipv6h
=
(
void
*
)
skb
->
data
+
iphdroff
;
nexthdr
=
ipv6h
->
nexthdr
;
hdroff
=
ipv6_skip_exthdr
(
skb
,
iphdroff
+
sizeof
(
*
ipv6h
),
&
nexthdr
,
&
frag_off
);
if
(
hdroff
<
0
)
goto
manip_addr
;
if
((
frag_off
&
htons
(
~
0x7
))
==
0
&&
!
l4proto
->
manip_pkt
(
skb
,
&
nf_nat_l3proto_ipv6
,
iphdroff
,
hdroff
,
target
,
maniptype
))
return
false
;
manip_addr:
if
(
maniptype
==
NF_NAT_MANIP_SRC
)
ipv6h
->
saddr
=
target
->
src
.
u3
.
in6
;
else
ipv6h
->
daddr
=
target
->
dst
.
u3
.
in6
;
return
true
;
}
static
void
nf_nat_ipv6_csum_update
(
struct
sk_buff
*
skb
,
unsigned
int
iphdroff
,
__sum16
*
check
,
const
struct
nf_conntrack_tuple
*
t
,
enum
nf_nat_manip_type
maniptype
)
{
const
struct
ipv6hdr
*
ipv6h
=
(
struct
ipv6hdr
*
)(
skb
->
data
+
iphdroff
);
const
struct
in6_addr
*
oldip
,
*
newip
;
if
(
maniptype
==
NF_NAT_MANIP_SRC
)
{
oldip
=
&
ipv6h
->
saddr
;
newip
=
&
t
->
src
.
u3
.
in6
;
}
else
{
oldip
=
&
ipv6h
->
daddr
;
newip
=
&
t
->
dst
.
u3
.
in6
;
}
inet_proto_csum_replace16
(
check
,
skb
,
oldip
->
s6_addr32
,
newip
->
s6_addr32
,
1
);
}
static
void
nf_nat_ipv6_csum_recalc
(
struct
sk_buff
*
skb
,
u8
proto
,
void
*
data
,
__sum16
*
check
,
int
datalen
,
int
oldlen
)
{
const
struct
ipv6hdr
*
ipv6h
=
ipv6_hdr
(
skb
);
struct
rt6_info
*
rt
=
(
struct
rt6_info
*
)
skb_dst
(
skb
);
if
(
skb
->
ip_summed
!=
CHECKSUM_PARTIAL
)
{
if
(
!
(
rt
->
rt6i_flags
&
RTF_LOCAL
)
&&
(
!
skb
->
dev
||
skb
->
dev
->
features
&
NETIF_F_V6_CSUM
))
{
skb
->
ip_summed
=
CHECKSUM_PARTIAL
;
skb
->
csum_start
=
skb_headroom
(
skb
)
+
skb_network_offset
(
skb
)
+
(
data
-
(
void
*
)
skb
->
data
);
skb
->
csum_offset
=
(
void
*
)
check
-
data
;
*
check
=
~
csum_ipv6_magic
(
&
ipv6h
->
saddr
,
&
ipv6h
->
daddr
,
datalen
,
proto
,
0
);
}
else
{
*
check
=
0
;
*
check
=
csum_ipv6_magic
(
&
ipv6h
->
saddr
,
&
ipv6h
->
daddr
,
datalen
,
proto
,
csum_partial
(
data
,
datalen
,
0
));
if
(
proto
==
IPPROTO_UDP
&&
!*
check
)
*
check
=
CSUM_MANGLED_0
;
}
}
else
inet_proto_csum_replace2
(
check
,
skb
,
htons
(
oldlen
),
htons
(
datalen
),
1
);
}
static
int
nf_nat_ipv6_nlattr_to_range
(
struct
nlattr
*
tb
[],
struct
nf_nat_range
*
range
)
{
if
(
tb
[
CTA_NAT_V6_MINIP
])
{
nla_memcpy
(
&
range
->
min_addr
.
ip6
,
tb
[
CTA_NAT_V6_MINIP
],
sizeof
(
struct
in6_addr
));
range
->
flags
|=
NF_NAT_RANGE_MAP_IPS
;
}
if
(
tb
[
CTA_NAT_V6_MAXIP
])
nla_memcpy
(
&
range
->
max_addr
.
ip6
,
tb
[
CTA_NAT_V6_MAXIP
],
sizeof
(
struct
in6_addr
));
else
range
->
max_addr
=
range
->
min_addr
;
return
0
;
}
static
const
struct
nf_nat_l3proto
nf_nat_l3proto_ipv6
=
{
.
l3proto
=
NFPROTO_IPV6
,
.
secure_port
=
nf_nat_ipv6_secure_port
,
.
in_range
=
nf_nat_ipv6_in_range
,
.
manip_pkt
=
nf_nat_ipv6_manip_pkt
,
.
csum_update
=
nf_nat_ipv6_csum_update
,
.
csum_recalc
=
nf_nat_ipv6_csum_recalc
,
.
nlattr_to_range
=
nf_nat_ipv6_nlattr_to_range
,
#ifdef CONFIG_XFRM
.
decode_session
=
nf_nat_ipv6_decode_session
,
#endif
};
int
nf_nat_icmpv6_reply_translation
(
struct
sk_buff
*
skb
,
struct
nf_conn
*
ct
,
enum
ip_conntrack_info
ctinfo
,
unsigned
int
hooknum
,
unsigned
int
hdrlen
)
{
struct
{
struct
icmp6hdr
icmp6
;
struct
ipv6hdr
ip6
;
}
*
inside
;
enum
ip_conntrack_dir
dir
=
CTINFO2DIR
(
ctinfo
);
enum
nf_nat_manip_type
manip
=
HOOK2MANIP
(
hooknum
);
const
struct
nf_nat_l4proto
*
l4proto
;
struct
nf_conntrack_tuple
target
;
unsigned
long
statusbit
;
NF_CT_ASSERT
(
ctinfo
==
IP_CT_RELATED
||
ctinfo
==
IP_CT_RELATED_REPLY
);
if
(
!
skb_make_writable
(
skb
,
hdrlen
+
sizeof
(
*
inside
)))
return
0
;
if
(
nf_ip6_checksum
(
skb
,
hooknum
,
hdrlen
,
IPPROTO_ICMPV6
))
return
0
;
inside
=
(
void
*
)
skb
->
data
+
hdrlen
;
if
(
inside
->
icmp6
.
icmp6_type
==
NDISC_REDIRECT
)
{
if
((
ct
->
status
&
IPS_NAT_DONE_MASK
)
!=
IPS_NAT_DONE_MASK
)
return
0
;
if
(
ct
->
status
&
IPS_NAT_MASK
)
return
0
;
}
if
(
manip
==
NF_NAT_MANIP_SRC
)
statusbit
=
IPS_SRC_NAT
;
else
statusbit
=
IPS_DST_NAT
;
/* Invert if this is reply direction */
if
(
dir
==
IP_CT_DIR_REPLY
)
statusbit
^=
IPS_NAT_MASK
;
if
(
!
(
ct
->
status
&
statusbit
))
return
1
;
l4proto
=
__nf_nat_l4proto_find
(
NFPROTO_IPV6
,
inside
->
ip6
.
nexthdr
);
if
(
!
nf_nat_ipv6_manip_pkt
(
skb
,
hdrlen
+
sizeof
(
inside
->
icmp6
),
l4proto
,
&
ct
->
tuplehash
[
!
dir
].
tuple
,
!
manip
))
return
0
;
if
(
skb
->
ip_summed
!=
CHECKSUM_PARTIAL
)
{
struct
ipv6hdr
*
ipv6h
=
ipv6_hdr
(
skb
);
inside
=
(
void
*
)
skb
->
data
+
hdrlen
;
inside
->
icmp6
.
icmp6_cksum
=
0
;
inside
->
icmp6
.
icmp6_cksum
=
csum_ipv6_magic
(
&
ipv6h
->
saddr
,
&
ipv6h
->
daddr
,
skb
->
len
-
hdrlen
,
IPPROTO_ICMPV6
,
csum_partial
(
&
inside
->
icmp6
,
skb
->
len
-
hdrlen
,
0
));
}
nf_ct_invert_tuplepr
(
&
target
,
&
ct
->
tuplehash
[
!
dir
].
tuple
);
l4proto
=
__nf_nat_l4proto_find
(
NFPROTO_IPV6
,
IPPROTO_ICMPV6
);
if
(
!
nf_nat_ipv6_manip_pkt
(
skb
,
0
,
l4proto
,
&
target
,
manip
))
return
0
;
return
1
;
}
EXPORT_SYMBOL_GPL
(
nf_nat_icmpv6_reply_translation
);
static
int
__init
nf_nat_l3proto_ipv6_init
(
void
)
{
int
err
;
err
=
nf_nat_l4proto_register
(
NFPROTO_IPV6
,
&
nf_nat_l4proto_icmpv6
);
if
(
err
<
0
)
goto
err1
;
err
=
nf_nat_l3proto_register
(
&
nf_nat_l3proto_ipv6
);
if
(
err
<
0
)
goto
err2
;
return
err
;
err2:
nf_nat_l4proto_unregister
(
NFPROTO_IPV6
,
&
nf_nat_l4proto_icmpv6
);
err1:
return
err
;
}
static
void
__exit
nf_nat_l3proto_ipv6_exit
(
void
)
{
nf_nat_l3proto_unregister
(
&
nf_nat_l3proto_ipv6
);
nf_nat_l4proto_unregister
(
NFPROTO_IPV6
,
&
nf_nat_l4proto_icmpv6
);
}
MODULE_LICENSE
(
"GPL"
);
MODULE_ALIAS
(
"nf-nat-"
__stringify
(
AF_INET6
));
module_init
(
nf_nat_l3proto_ipv6_init
);
module_exit
(
nf_nat_l3proto_ipv6_exit
);
net/ipv6/netfilter/nf_nat_proto_icmpv6.c
0 → 100644
浏览文件 @
58a317f1
/*
* Copyright (c) 2011 Patrick Mchardy <kaber@trash.net>
*
* 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
* published by the Free Software Foundation.
*
* Based on Rusty Russell's IPv4 ICMP NAT code. Development of IPv6
* NAT funded by Astaro.
*/
#include <linux/types.h>
#include <linux/init.h>
#include <linux/icmpv6.h>
#include <linux/netfilter.h>
#include <net/netfilter/nf_nat.h>
#include <net/netfilter/nf_nat_core.h>
#include <net/netfilter/nf_nat_l3proto.h>
#include <net/netfilter/nf_nat_l4proto.h>
static
bool
icmpv6_in_range
(
const
struct
nf_conntrack_tuple
*
tuple
,
enum
nf_nat_manip_type
maniptype
,
const
union
nf_conntrack_man_proto
*
min
,
const
union
nf_conntrack_man_proto
*
max
)
{
return
ntohs
(
tuple
->
src
.
u
.
icmp
.
id
)
>=
ntohs
(
min
->
icmp
.
id
)
&&
ntohs
(
tuple
->
src
.
u
.
icmp
.
id
)
<=
ntohs
(
max
->
icmp
.
id
);
}
static
void
icmpv6_unique_tuple
(
const
struct
nf_nat_l3proto
*
l3proto
,
struct
nf_conntrack_tuple
*
tuple
,
const
struct
nf_nat_range
*
range
,
enum
nf_nat_manip_type
maniptype
,
const
struct
nf_conn
*
ct
)
{
static
u16
id
;
unsigned
int
range_size
;
unsigned
int
i
;
range_size
=
ntohs
(
range
->
max_proto
.
icmp
.
id
)
-
ntohs
(
range
->
min_proto
.
icmp
.
id
)
+
1
;
if
(
!
(
range
->
flags
&
NF_NAT_RANGE_PROTO_SPECIFIED
))
range_size
=
0xffff
;
for
(
i
=
0
;
;
++
id
)
{
tuple
->
src
.
u
.
icmp
.
id
=
htons
(
ntohs
(
range
->
min_proto
.
icmp
.
id
)
+
(
id
%
range_size
));
if
(
++
i
==
range_size
||
!
nf_nat_used_tuple
(
tuple
,
ct
))
return
;
}
}
static
bool
icmpv6_manip_pkt
(
struct
sk_buff
*
skb
,
const
struct
nf_nat_l3proto
*
l3proto
,
unsigned
int
iphdroff
,
unsigned
int
hdroff
,
const
struct
nf_conntrack_tuple
*
tuple
,
enum
nf_nat_manip_type
maniptype
)
{
struct
icmp6hdr
*
hdr
;
if
(
!
skb_make_writable
(
skb
,
hdroff
+
sizeof
(
*
hdr
)))
return
false
;
hdr
=
(
struct
icmp6hdr
*
)(
skb
->
data
+
hdroff
);
l3proto
->
csum_update
(
skb
,
iphdroff
,
&
hdr
->
icmp6_cksum
,
tuple
,
maniptype
);
if
(
hdr
->
icmp6_code
==
ICMPV6_ECHO_REQUEST
||
hdr
->
icmp6_code
==
ICMPV6_ECHO_REPLY
)
{
inet_proto_csum_replace2
(
&
hdr
->
icmp6_cksum
,
skb
,
hdr
->
icmp6_identifier
,
tuple
->
src
.
u
.
icmp
.
id
,
0
);
hdr
->
icmp6_identifier
=
tuple
->
src
.
u
.
icmp
.
id
;
}
return
true
;
}
const
struct
nf_nat_l4proto
nf_nat_l4proto_icmpv6
=
{
.
l4proto
=
IPPROTO_ICMPV6
,
.
manip_pkt
=
icmpv6_manip_pkt
,
.
in_range
=
icmpv6_in_range
,
.
unique_tuple
=
icmpv6_unique_tuple
,
#if defined(CONFIG_NF_CT_NETLINK) || defined(CONFIG_NF_CT_NETLINK_MODULE)
.
nlattr_to_range
=
nf_nat_l4proto_nlattr_to_range
,
#endif
};
net/netfilter/nf_nat_core.c
浏览文件 @
58a317f1
...
@@ -696,6 +696,8 @@ static int nfnetlink_parse_nat_proto(struct nlattr *attr,
...
@@ -696,6 +696,8 @@ static int nfnetlink_parse_nat_proto(struct nlattr *attr,
static
const
struct
nla_policy
nat_nla_policy
[
CTA_NAT_MAX
+
1
]
=
{
static
const
struct
nla_policy
nat_nla_policy
[
CTA_NAT_MAX
+
1
]
=
{
[
CTA_NAT_V4_MINIP
]
=
{
.
type
=
NLA_U32
},
[
CTA_NAT_V4_MINIP
]
=
{
.
type
=
NLA_U32
},
[
CTA_NAT_V4_MAXIP
]
=
{
.
type
=
NLA_U32
},
[
CTA_NAT_V4_MAXIP
]
=
{
.
type
=
NLA_U32
},
[
CTA_NAT_V6_MINIP
]
=
{
.
len
=
sizeof
(
struct
in6_addr
)
},
[
CTA_NAT_V6_MAXIP
]
=
{
.
len
=
sizeof
(
struct
in6_addr
)
},
[
CTA_NAT_PROTO
]
=
{
.
type
=
NLA_NESTED
},
[
CTA_NAT_PROTO
]
=
{
.
type
=
NLA_NESTED
},
};
};
...
...
net/netfilter/xt_nat.c
浏览文件 @
58a317f1
...
@@ -163,5 +163,8 @@ module_init(xt_nat_init);
...
@@ -163,5 +163,8 @@ module_init(xt_nat_init);
module_exit
(
xt_nat_exit
);
module_exit
(
xt_nat_exit
);
MODULE_LICENSE
(
"GPL"
);
MODULE_LICENSE
(
"GPL"
);
MODULE_AUTHOR
(
"Patrick McHardy <kaber@trash.net>"
);
MODULE_ALIAS
(
"ipt_SNAT"
);
MODULE_ALIAS
(
"ipt_SNAT"
);
MODULE_ALIAS
(
"ipt_DNAT"
);
MODULE_ALIAS
(
"ipt_DNAT"
);
MODULE_ALIAS
(
"ip6t_SNAT"
);
MODULE_ALIAS
(
"ip6t_DNAT"
);
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录