1 #include <linux/errno.h>
3 #include <linux/kernel.h>
4 #include <linux/module.h>
5 #include <linux/skbuff.h>
6 #include <linux/socket.h>
7 #include <linux/types.h>
8 #include <net/checksum.h>
10 #include <net/ip6_fib.h>
11 #include <net/lwtunnel.h>
12 #include <net/protocol.h>
13 #include <uapi/linux/ila.h>
16 static __wsum
get_csum_diff(struct ipv6hdr
*ip6h
, struct ila_params
*p
)
18 struct ila_addr
*iaddr
= ila_a2i(&ip6h
->daddr
);
20 if (p
->locator_match
.v64
)
23 return compute_csum_diff8((__be32
*)&iaddr
->loc
,
24 (__be32
*)&p
->locator
);
27 static void ila_csum_do_neutral(struct ila_addr
*iaddr
,
30 __sum16
*adjust
= (__force __sum16
*)&iaddr
->ident
.v16
[3];
33 /* Check if checksum adjust value has been cached */
34 if (p
->locator_match
.v64
) {
37 diff
= compute_csum_diff8((__be32
*)&p
->locator
,
41 fval
= (__force __wsum
)(ila_csum_neutral_set(iaddr
->ident
) ?
42 CSUM_NEUTRAL_FLAG
: ~CSUM_NEUTRAL_FLAG
);
44 diff
= csum_add(diff
, fval
);
46 *adjust
= ~csum_fold(csum_add(diff
, csum_unfold(*adjust
)));
48 /* Flip the csum-neutral bit. Either we are doing a SIR->ILA
49 * translation with ILA_CSUM_NEUTRAL_MAP as the csum_method
50 * and the C-bit is not set, or we are doing an ILA-SIR
51 * tranlsation and the C-bit is set.
53 iaddr
->ident
.csum_neutral
^= 1;
56 static void ila_csum_adjust_transport(struct sk_buff
*skb
,
60 struct ipv6hdr
*ip6h
= ipv6_hdr(skb
);
61 struct ila_addr
*iaddr
= ila_a2i(&ip6h
->daddr
);
62 size_t nhoff
= sizeof(struct ipv6hdr
);
64 switch (ip6h
->nexthdr
) {
66 if (likely(pskb_may_pull(skb
, nhoff
+ sizeof(struct tcphdr
)))) {
67 struct tcphdr
*th
= (struct tcphdr
*)
68 (skb_network_header(skb
) + nhoff
);
70 diff
= get_csum_diff(ip6h
, p
);
71 inet_proto_csum_replace_by_diff(&th
->check
, skb
,
76 if (likely(pskb_may_pull(skb
, nhoff
+ sizeof(struct udphdr
)))) {
77 struct udphdr
*uh
= (struct udphdr
*)
78 (skb_network_header(skb
) + nhoff
);
80 if (uh
->check
|| skb
->ip_summed
== CHECKSUM_PARTIAL
) {
81 diff
= get_csum_diff(ip6h
, p
);
82 inet_proto_csum_replace_by_diff(&uh
->check
, skb
,
85 uh
->check
= CSUM_MANGLED_0
;
90 if (likely(pskb_may_pull(skb
,
91 nhoff
+ sizeof(struct icmp6hdr
)))) {
92 struct icmp6hdr
*ih
= (struct icmp6hdr
*)
93 (skb_network_header(skb
) + nhoff
);
95 diff
= get_csum_diff(ip6h
, p
);
96 inet_proto_csum_replace_by_diff(&ih
->icmp6_cksum
, skb
,
102 /* Now change destination address */
103 iaddr
->loc
= p
->locator
;
106 void ila_update_ipv6_locator(struct sk_buff
*skb
, struct ila_params
*p
,
107 bool set_csum_neutral
)
109 struct ipv6hdr
*ip6h
= ipv6_hdr(skb
);
110 struct ila_addr
*iaddr
= ila_a2i(&ip6h
->daddr
);
112 /* First deal with the transport checksum */
113 if (ila_csum_neutral_set(iaddr
->ident
)) {
114 /* C-bit is set in the locator indicating that this
115 * is a locator being translated to a SIR address.
116 * Perform (receiver) checksum-neutral translation.
118 if (!set_csum_neutral
)
119 ila_csum_do_neutral(iaddr
, p
);
121 switch (p
->csum_mode
) {
122 case ILA_CSUM_ADJUST_TRANSPORT
:
123 ila_csum_adjust_transport(skb
, p
);
125 case ILA_CSUM_NEUTRAL_MAP
:
126 ila_csum_do_neutral(iaddr
, p
);
128 case ILA_CSUM_NO_ACTION
:
133 /* Now change destination address */
134 iaddr
->loc
= p
->locator
;
137 void ila_init_saved_csum(struct ila_params
*p
)
139 if (!p
->locator_match
.v64
)
142 p
->csum_diff
= compute_csum_diff8(
143 (__be32
*)&p
->locator
,
144 (__be32
*)&p
->locator_match
);
147 static int __init
ila_init(void)
151 ret
= ila_lwt_init();
156 ret
= ila_xlat_init();
167 static void __exit
ila_fini(void)
173 module_init(ila_init
);
174 module_exit(ila_fini
);
175 MODULE_ALIAS_RTNL_LWT(ILA
);
176 MODULE_AUTHOR("Tom Herbert <tom@herbertland.com>");
177 MODULE_LICENSE("GPL");