接続トレースICMP
icmpプロトコルは、tcpに比べてudpの読み取り特性を有し、ネットワーク層と伝送層の間にあり、伝送層のソースポートがない.したがって、接続トレースを作成するには、特別な処理が必要です.また、ICMPはエラーメッセージに属し、すべてのicmpメッセージがペアで現れるわけではなく、これらの違いはicmpの処理とtcp,udpの処理の違いをもたらした.
ICMPの概要
icmpメッセージは以下の種類から構成されています.
接続トレースの実装
メッセージタイプ
最大18種類のicmpメッセージがあり、各icmpメッセージにはいくつかのサブクラスがある可能性があります.次の4つのicmpメッセージだけがペアで表示されます.
この4組のメッセージのみがペアになっているため、接続トレースはこの4組のメッセージのみを接続トレースします.
五元グループ
icmpメッセージにはソースポートがありませんが、tupleを埋めるには何を採用しますか?
次のコードから分かるように、接続トレースは1つの__を使用します.be 16 idはtupleのソースportの代わりに使用されます.ここでidはicmpメッセージの識別子です.この4つのメッセージはすべてあります.pingメッセージは一般的にpingプログラムのpidを埋め込むので、同じデバイスが2つの異なるpingプログラムのpingを起動すると、同じipが2つのセッションを生成します.
次のコードから、接続トレースはu_を使用していることがわかります.int8_t type, code;tupleの目的portの代わりに:
次に、icmpはどのようにtupleの反転tupleを求めますか?項目tcp、udpはソースのポートを同じに交換しますか?
以上から分かるように、icmpで反転tupleを求める場合、typeを対応するtypeに置き換えるだけです(ここではIPアドレスには触れません)
非ペアicmpメッセージ処理
icmpは、ソースホストのエラー情報を通知するために使用されるエラーメッセージが多い.その発生は往々にしてある装置が送信したメッセージが伝送中にエラーが発生し、伝送経路で装置またはターゲットホスト装置がエラーを検出し、ICMPエラーメッセージを生成してソースホストに通知する.エラーメッセージは、icmpメッセージヘッダの後に、そのicmpメッセージをもたらす元のメッセージヘッダ情報を追加します.したがって、icmpエラーメッセージは接続の付属であり、接続追跡はエラーメッセージをサブ接続と見なしている(CTは実際に作成されず、メイン接続に依存し、そのメッセージの状態をIP_CT_RELATEDまたはIP_CT_RELATED_REPLYとする).
接続追跡処理には、次のエラーメッセージがあります.
接続トレースは、これらのエラーメッセージに対して、ターゲットホストに正しく渡す必要があります.処理の主な原因はNATであり,詳細は後述する.
ICMPプロトコル制御ブロック
接続追跡はまずエラーチェックを行い、error関数を実行し、icmpにとってicmp_error関数
icmp_pkt_to_tuple
icmp_invert_tuple
icmp_new
非エラーメッセージの要求方向メッセージはこの関数によって処理され、主に合法性検査、icmp_error関数はすでに処理されていますが、ここでは余計です.
icmp_packet
非エラーメッセージの応答方向メッセージは、主にタイムアウト更新とメッセージ統計を行う関数で処理されます.
icmp_get_timeouts
icmp接続追跡タイムアウト時間取得は、一般的に30秒です.
ICMP対NATのサポート
非エラーメッセージによるnatのサポート
icmpメッセージはnatのサポートに対して実際にはネットワーク層のサポートが多く、icmpメッセージ自体にとって1つの識別子しか変更できないが、識別子を変更するシーンは少ない.コードについて簡単に分析します.
icmp nat制御ブロック
icmp_in_range
icmpの識別子が指定された範囲にあるかどうかを判断します.
icmp_unique_tuple
五元グループが一意になるようにフラグを割り当てます.
icmp_manip_pkt
選択した識別子を元の識別子に置き換え、検証コードを更新します.
エラーメッセージNATへのサポート、よく言われるICMP ALG
エラーメッセージの内層メッセージは、エラーが発生したメッセージから来ている.ホストがNATを介してメッセージを送信すると、そのメッセージヘッダが変更される.すなわち,このメッセージに誤りが検出された機器が見たメッセージはNAT経由のメッセージであるため,NATは内層メッセージを元のメッセージに戻してソースホストに転送する必要がある.
ICMPの概要
icmpメッセージは以下の種類から構成されています.
接続トレースの実装
メッセージタイプ
最大18種類のicmpメッセージがあり、各icmpメッセージにはいくつかのサブクラスがある可能性があります.次の4つのicmpメッセージだけがペアで表示されます.
static const u_int8_t valid_new[] = {
[ICMP_ECHO] = 1,
[ICMP_TIMESTAMP] = 1,
[ICMP_INFO_REQUEST] = 1,
[ICMP_ADDRESS] = 1
};
//
/* Add 1; spaces filled with 0. 1, ICMP_ECHO 0, 0 , 1, CT 1。 icmp_invert_tuple。
*/
static const u_int8_t invmap[] = {
[ICMP_ECHO] = ICMP_ECHOREPLY + 1,
[ICMP_ECHOREPLY] = ICMP_ECHO + 1,
[ICMP_TIMESTAMP] = ICMP_TIMESTAMPREPLY + 1,
[ICMP_TIMESTAMPREPLY] = ICMP_TIMESTAMP + 1,
[ICMP_INFO_REQUEST] = ICMP_INFO_REPLY + 1,
[ICMP_INFO_REPLY] = ICMP_INFO_REQUEST + 1,
[ICMP_ADDRESS] = ICMP_ADDRESSREPLY + 1,
[ICMP_ADDRESSREPLY] = ICMP_ADDRESS + 1
};
この4組のメッセージのみがペアになっているため、接続トレースはこの4組のメッセージのみを接続トレースします.
五元グループ
icmpメッセージにはソースポートがありませんが、tupleを埋めるには何を採用しますか?
次のコードから分かるように、接続トレースは1つの__を使用します.be 16 idはtupleのソースportの代わりに使用されます.ここでidはicmpメッセージの識別子です.この4つのメッセージはすべてあります.pingメッセージは一般的にpingプログラムのpidを埋め込むので、同じデバイスが2つの異なるpingプログラムのpingを起動すると、同じipが2つのセッションを生成します.
/* The protocol-specific manipulable parts of the tuple: always in
* network order
*/
union nf_conntrack_man_proto {
/* Add other protocols here. */
__be16 all;
struct {
__be16 port;
} tcp;
struct {
__be16 port;
} udp;
struct {
__be16 id;
} icmp;
struct {
__be16 port;
} dccp;
struct {
__be16 port;
} sctp;
struct {
__be16 key; /* GRE key is 32bit, PPtP only uses 16bit */
} gre;
};
次のコードから、接続トレースはu_を使用していることがわかります.int8_t type, code;tupleの目的portの代わりに:
/* This contains the information to distinguish a connection. */
struct nf_conntrack_tuple {
struct nf_conntrack_man src;
/* These are the parts of the tuple which are fixed. */
struct {
union nf_inet_addr u3;
union {
/* Add other protocols here. */
__be16 all;
struct {
__be16 port;
} tcp;
struct {
__be16 port;
} udp;
struct {
u_int8_t type, code;
} icmp;
struct {
__be16 port;
} dccp;
struct {
__be16 port;
} sctp;
struct {
__be16 key;
} gre;
} u;
/* The protocol. */
u_int8_t protonum;
/* The direction (for tuplehash) */
u_int8_t dir;
} dst;
};
次に、icmpはどのようにtupleの反転tupleを求めますか?項目tcp、udpはソースのポートを同じに交換しますか?
/* */
static bool icmp_invert_tuple(struct nf_conntrack_tuple *tuple,
const struct nf_conntrack_tuple *orig)
{
if (orig->dst.u.icmp.type >= sizeof(invmap) ||// ,
!invmap[orig->dst.u.icmp.type])// icmp , 0 , 。
return false;
//id id , type,code
tuple->src.u.icmp.id = orig->src.u.icmp.id;
// type type。
tuple->dst.u.icmp.type = invmap[orig->dst.u.icmp.type] - 1;// 1, invmap 1
//code 。 code 0。 。
tuple->dst.u.icmp.code = orig->dst.u.icmp.code;
return true;
}
以上から分かるように、icmpで反転tupleを求める場合、typeを対応するtypeに置き換えるだけです(ここではIPアドレスには触れません)
非ペアicmpメッセージ処理
icmpは、ソースホストのエラー情報を通知するために使用されるエラーメッセージが多い.その発生は往々にしてある装置が送信したメッセージが伝送中にエラーが発生し、伝送経路で装置またはターゲットホスト装置がエラーを検出し、ICMPエラーメッセージを生成してソースホストに通知する.エラーメッセージは、icmpメッセージヘッダの後に、そのicmpメッセージをもたらす元のメッセージヘッダ情報を追加します.したがって、icmpエラーメッセージは接続の付属であり、接続追跡はエラーメッセージをサブ接続と見なしている(CTは実際に作成されず、メイン接続に依存し、そのメッセージの状態をIP_CT_RELATEDまたはIP_CT_RELATED_REPLYとする).
接続追跡処理には、次のエラーメッセージがあります.
ICMP_DEST_UNREACH //
ICMP_SOURCE_QUENCH // ,
ICMP_TIME_EXCEEDED //TTL ,
ICMP_PARAMETERPROB // ,
ICMP_REDIRECT // , , ( )
接続トレースは、これらのエラーメッセージに対して、ターゲットホストに正しく渡す必要があります.処理の主な原因はNATであり,詳細は後述する.
ICMPプロトコル制御ブロック
const struct nf_conntrack_l4proto nf_conntrack_l4proto_icmp =
{
.l3proto = PF_INET,
.l4proto = IPPROTO_ICMP,
.pkt_to_tuple = icmp_pkt_to_tuple,
.invert_tuple = icmp_invert_tuple,
.packet = icmp_packet,
.get_timeouts = icmp_get_timeouts,
.new = icmp_new,
.error = icmp_error,
.destroy = NULL,
.me = NULL,
#if IS_ENABLED(CONFIG_NF_CT_NETLINK)
.tuple_to_nlattr = icmp_tuple_to_nlattr,
.nlattr_tuple_size = icmp_nlattr_tuple_size,
.nlattr_to_tuple = icmp_nlattr_to_tuple,
.nla_policy = icmp_nla_policy,
#endif
#if IS_ENABLED(CONFIG_NF_CT_NETLINK_TIMEOUT)
.ctnl_timeout = {
.nlattr_to_obj = icmp_timeout_nlattr_to_obj,
.obj_to_nlattr = icmp_timeout_obj_to_nlattr,
.nlattr_max = CTA_TIMEOUT_ICMP_MAX,
.obj_size = sizeof(unsigned int),
.nla_policy = icmp_timeout_nla_policy,
},
#endif /* CONFIG_NF_CT_NETLINK_TIMEOUT */
.init_net = icmp_init_net,
.get_net_proto = icmp_get_net_proto,
};
接続追跡はまずエラーチェックを行い、error関数を実行し、icmpにとってicmp_error関数
/* Small and modified version of icmp_rcv */
/* ,tmpl NULL */
static int
icmp_error(struct net *net, struct nf_conn *tmpl,
struct sk_buff *skb, unsigned int dataoff,
u8 pf, unsigned int hooknum)
{
const struct icmphdr *icmph;
struct icmphdr _ih;
/* Not enough header? icmp */
icmph = skb_header_pointer(skb, ip_hdrlen(skb), sizeof(_ih), &_ih);
if (icmph == NULL) {
icmp_error_log(skb, net, pf, "short packet");
return -NF_ACCEPT;
}
/* See ip_conntrack_proto_tcp.c */
/* */
if (net->ct.sysctl_checksum && hooknum == NF_INET_PRE_ROUTING &&//
nf_ip_checksum(skb, hooknum, dataoff, 0)) {
icmp_error_log(skb, net, pf, "bad hw icmp checksum");
return -NF_ACCEPT;
}
/*
* 18 is the highest 'known' ICMP type. Anything else is a mystery
*
* RFC 1122: 3.2.2 Unknown ICMP messages types MUST be silently
* discarded.
* 。
*/
if (icmph->type > NR_ICMP_TYPES) {
icmp_error_log(skb, net, pf, "invalid icmp type");
return -NF_ACCEPT;
}
/* Need to track icmp error message? */
/* */
if (icmph->type != ICMP_DEST_UNREACH &&
icmph->type != ICMP_SOURCE_QUENCH &&
icmph->type != ICMP_TIME_EXCEEDED &&
icmph->type != ICMP_PARAMETERPROB &&
icmph->type != ICMP_REDIRECT)
return NF_ACCEPT;
// icmp
return icmp_error_message(net, tmpl, skb, hooknum);
}
/* Returns conntrack if it dealt with ICMP, and filled in skb fields */
/* icmp , 。
** , RELATE
*/
static int
icmp_error_message(struct net *net, struct nf_conn *tmpl, struct sk_buff *skb,
unsigned int hooknum)
{
struct nf_conntrack_tuple innertuple, origtuple;
const struct nf_conntrack_l4proto *innerproto;
const struct nf_conntrack_tuple_hash *h;
const struct nf_conntrack_zone *zone;
enum ip_conntrack_info ctinfo;
struct nf_conntrack_zone tmp;
WARN_ON(skb_nfct(skb));
zone = nf_ct_zone_tmpl(tmpl, skb, &tmp);
/* Are they talking about one of our connections? */
/* origtuple */
if (!nf_ct_get_tuplepr(skb,
skb_network_offset(skb) + ip_hdrlen(skb)
+ sizeof(struct icmphdr),
PF_INET, net, &origtuple)) {
pr_debug("icmp_error_message: failed to get tuple
");
return -NF_ACCEPT;
}
/* rcu_read_lock()ed by nf_hook_thresh */
/* */
innerproto = __nf_ct_l4proto_find(PF_INET, origtuple.dst.protonum);
/* Ordinarily, we'd expect the inverted tupleproto, but it's
been preserved inside the ICMP.
**
*/
if (!nf_ct_invert_tuple(&innertuple, &origtuple,
&nf_conntrack_l3proto_ipv4, innerproto)) {
pr_debug("icmp_error_message: no match
");
return -NF_ACCEPT;
}
// , 。
ctinfo = IP_CT_RELATED;
// CT。 ?
// icmp , 。
h = nf_conntrack_find_get(net, zone, &innertuple);
if (!h) {
pr_debug("icmp_error_message: no match
");
return -NF_ACCEPT;
}
// ,
if (NF_CT_DIRECTION(h) == IP_CT_DIR_REPLY)
ctinfo += IP_CT_IS_REPLY;
/* Update skb to refer to this connection */
/* CT , IP_CT_RELATED or IP_CT_RELATED_REPLY*/
nf_ct_set(skb, nf_ct_tuplehash_to_ctrack(h), ctinfo);
/* */
return NF_ACCEPT;
}
// NF_ACCEPT , CT, 。 nf_conntrack_in 。
icmp_pkt_to_tuple
/* icmp , */
static bool icmp_pkt_to_tuple(const struct sk_buff *skb, unsigned int dataoff,
struct net *net, struct nf_conntrack_tuple *tuple)
{
const struct icmphdr *hp;
struct icmphdr _hdr;
hp = skb_header_pointer(skb, dataoff, sizeof(_hdr), &_hdr);
if (hp == NULL)
return false;
tuple->dst.u.icmp.type = hp->type;/* */
tuple->src.u.icmp.id = hp->un.echo.id;/* id ,ping id */
tuple->dst.u.icmp.code = hp->code;/* , 0 */
return true;
}
icmp_invert_tuple
/* */
static bool icmp_invert_tuple(struct nf_conntrack_tuple *tuple,
const struct nf_conntrack_tuple *orig)
{
if (orig->dst.u.icmp.type >= sizeof(invmap) ||// ,
!invmap[orig->dst.u.icmp.type])// icmp , 0 , 。
return false;
//id id , type,code
tuple->src.u.icmp.id = orig->src.u.icmp.id;
// type type。
tuple->dst.u.icmp.type = invmap[orig->dst.u.icmp.type] - 1;// 1, invmap 1
//code 。 code 0。 。
tuple->dst.u.icmp.code = orig->dst.u.icmp.code;
return true;
}
icmp_new
非エラーメッセージの要求方向メッセージはこの関数によって処理され、主に合法性検査、icmp_error関数はすでに処理されていますが、ここでは余計です.
/* Called when a new connection for this protocol found. */
static bool icmp_new(struct nf_conn *ct, const struct sk_buff *skb,
unsigned int dataoff, unsigned int *timeouts)
{
static const u_int8_t valid_new[] = {/* 18 , icmp */
[ICMP_ECHO] = 1,
[ICMP_TIMESTAMP] = 1,
[ICMP_INFO_REQUEST] = 1,
[ICMP_ADDRESS] = 1
};
if (ct->tuplehash[0].tuple.dst.u.icmp.type >= sizeof(valid_new) ||
!valid_new[ct->tuplehash[0].tuple.dst.u.icmp.type]) {
/* Can't create a new ICMP `conn' with this. */
pr_debug("icmp: can't create new conn with type %u
",
ct->tuplehash[0].tuple.dst.u.icmp.type);
nf_ct_dump_tuple_ip(&ct->tuplehash[0].tuple);
return false;
}
return true;
}
icmp_packet
非エラーメッセージの応答方向メッセージは、主にタイムアウト更新とメッセージ統計を行う関数で処理されます.
/* Returns verdict for packet, or -1 for invalid. */
/* icmp , icmp */
static int icmp_packet(struct nf_conn *ct,
const struct sk_buff *skb,
unsigned int dataoff,
enum ip_conntrack_info ctinfo,
unsigned int *timeout)
{
/* Do not immediately delete the connection after the first
successful reply to avoid excessive conntrackd traffic
and also to handle correctly ICMP echo reply duplicates. */
nf_ct_refresh_acct(ct, ctinfo, skb, *timeout);
return NF_ACCEPT;
}
icmp_get_timeouts
icmp接続追跡タイムアウト時間取得は、一般的に30秒です.
/* icmp */
static unsigned int *icmp_get_timeouts(struct net *net)
{
return &icmp_pernet(net)->timeout;
}
static int icmp_init_net(struct net *net, u_int16_t proto)
{
struct nf_icmp_net *in = icmp_pernet(net);
struct nf_proto_net *pn = &in->pn;
in->timeout = nf_ct_icmp_timeout;
return icmp_kmemdup_sysctl_table(pn, in);
}
/* icmp 30 */
static const unsigned int nf_ct_icmp_timeout = 30*HZ;
ICMP対NATのサポート
非エラーメッセージによるnatのサポート
icmpメッセージはnatのサポートに対して実際にはネットワーク層のサポートが多く、icmpメッセージ自体にとって1つの識別子しか変更できないが、識別子を変更するシーンは少ない.コードについて簡単に分析します.
icmp nat制御ブロック
const struct nf_nat_l4proto nf_nat_l4proto_icmp = {
.l4proto = IPPROTO_ICMP,
.manip_pkt = icmp_manip_pkt,
.in_range = icmp_in_range,
.unique_tuple = icmp_unique_tuple,
#if IS_ENABLED(CONFIG_NF_CT_NETLINK)
.nlattr_to_range = nf_nat_l4proto_nlattr_to_range,
#endif
};
icmp_in_range
icmpの識別子が指定された範囲にあるかどうかを判断します.
static bool
icmp_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);
}
icmp_unique_tuple
五元グループが一意になるようにフラグを割り当てます.
static void
icmp_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 u_int16_t id;
unsigned int range_size;
unsigned int i;
range_size = ntohs(range->max_proto.icmp.id) -
ntohs(range->min_proto.icmp.id) + 1;
/* If no range specified... , 0xffff */
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;
}
return;
}
icmp_manip_pkt
選択した識別子を元の識別子に置き換え、検証コードを更新します.
static bool
icmp_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 icmphdr *hdr;
if (!skb_make_writable(skb, hdroff + sizeof(*hdr)))
return false;
hdr = (struct icmphdr *)(skb->data + hdroff);
inet_proto_csum_replace2(&hdr->checksum, skb,
hdr->un.echo.id, tuple->src.u.icmp.id, false);
hdr->un.echo.id = tuple->src.u.icmp.id;
return true;
}
エラーメッセージNATへのサポート、よく言われるICMP ALG
エラーメッセージの内層メッセージは、エラーが発生したメッセージから来ている.ホストがNATを介してメッセージを送信すると、そのメッセージヘッダが変更される.すなわち,このメッセージに誤りが検出された機器が見たメッセージはNAT経由のメッセージであるため,NATは内層メッセージを元のメッセージに戻してソースホストに転送する必要がある.
unsigned int
nf_nat_ipv4_fn(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state,
unsigned int (*do_chain)(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state,
struct nf_conn *ct))
{
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
struct nf_conn_nat *nat;
/* maniptype == SRC for postrouting. */
enum nf_nat_manip_type maniptype = HOOK2MANIP(state->hook);
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;
nat = nfct_nat(ct);
switch (ctinfo) {
case IP_CT_RELATED:// icmp ,
case IP_CT_RELATED_REPLY:
//icmp , icmp 。
// ct。 icmp
// 。
if (ip_hdr(skb)->protocol == IPPROTO_ICMP) {
// nat, nat, 。
if (!nf_nat_icmp_reply_translation(skb, ct, ctinfo,
state->hook))
return NF_DROP;
else
return NF_ACCEPT;
}
...
}
int nf_nat_icmp_reply_translation(struct sk_buff *skb,
struct nf_conn *ct,
enum ip_conntrack_info ctinfo,
unsigned int hooknum)
{
struct {
struct icmphdr icmp;
struct iphdr ip;
} *inside;
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
enum nf_nat_manip_type manip = HOOK2MANIP(hooknum);
unsigned int hdrlen = ip_hdrlen(skb);
const struct nf_nat_l4proto *l4proto;
struct nf_conntrack_tuple target;
unsigned long statusbit;
WARN_ON(ctinfo != IP_CT_RELATED && ctinfo != IP_CT_RELATED_REPLY);
if (!skb_make_writable(skb, hdrlen + sizeof(*inside)))
return 0;
if (nf_ip_checksum(skb, hooknum, hdrlen, 0))
return 0;
// icmp
inside = (void *)skb->data + hdrlen;
if (inside->icmp.type == ICMP_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;
// nat , 。
if (!(ct->status & statusbit))
return 1;
//
l4proto = __nf_nat_l4proto_find(NFPROTO_IPV4, inside->ip.protocol);
// nat 。
if (!nf_nat_ipv4_manip_pkt(skb, hdrlen + sizeof(inside->icmp),
l4proto, &ct->tuplehash[!dir].tuple, !manip))
return 0;
// icmp
if (skb->ip_summed != CHECKSUM_PARTIAL) {
/* Reloading "inside" here since manip_pkt may reallocate */
inside = (void *)skb->data + hdrlen;
inside->icmp.checksum = 0;
inside->icmp.checksum =
csum_fold(skb_checksum(skb, hdrlen,
skb->len - hdrlen, 0));
}
/* Change outer to look like the reply to an incoming packet */
// nat
nf_ct_invert_tuplepr(&target, &ct->tuplehash[!dir].tuple);
l4proto = __nf_nat_l4proto_find(NFPROTO_IPV4, 0);
if (!nf_nat_ipv4_manip_pkt(skb, 0, l4proto, &target, manip))
return 0;
return 1;
}