1 // SPDX-License-Identifier: GPL-2.0
2 // Copyright (c) 2017 Facebook
6 #include <linux/pkt_cls.h>
9 #include <linux/if_ether.h>
11 #include <linux/ipv6.h>
12 #include <linux/icmp.h>
13 #include <linux/icmpv6.h>
14 #include <linux/tcp.h>
15 #include <linux/udp.h>
16 #include "bpf_helpers.h"
17 #include "bpf_endian.h"
19 static __u32
rol32(__u32 word
, unsigned int shift
)
21 return (word
<< shift
) | (word
>> ((-shift
) & 31));
24 /* copy paste of jhash from kernel sources to make sure llvm
25 * can compile it into valid sequence of bpf instructions
27 #define __jhash_mix(a, b, c) \
29 a -= c; a ^= rol32(c, 4); c += b; \
30 b -= a; b ^= rol32(a, 6); a += c; \
31 c -= b; c ^= rol32(b, 8); b += a; \
32 a -= c; a ^= rol32(c, 16); c += b; \
33 b -= a; b ^= rol32(a, 19); a += c; \
34 c -= b; c ^= rol32(b, 4); b += a; \
37 #define __jhash_final(a, b, c) \
39 c ^= b; c -= rol32(b, 14); \
40 a ^= c; a -= rol32(c, 11); \
41 b ^= a; b -= rol32(a, 25); \
42 c ^= b; c -= rol32(b, 16); \
43 a ^= c; a -= rol32(c, 4); \
44 b ^= a; b -= rol32(a, 14); \
45 c ^= b; c -= rol32(b, 24); \
48 #define JHASH_INITVAL 0xdeadbeef
50 typedef unsigned int u32
;
52 static __attribute__ ((noinline
))
53 u32
jhash(const void *key
, u32 length
, u32 initval
)
56 const unsigned char *k
= key
;
58 a
= b
= c
= JHASH_INITVAL
+ length
+ initval
;
69 case 12: c
+= (u32
)k
[11]<<24;
70 case 11: c
+= (u32
)k
[10]<<16;
71 case 10: c
+= (u32
)k
[9]<<8;
73 case 8: b
+= (u32
)k
[7]<<24;
74 case 7: b
+= (u32
)k
[6]<<16;
75 case 6: b
+= (u32
)k
[5]<<8;
77 case 4: a
+= (u32
)k
[3]<<24;
78 case 3: a
+= (u32
)k
[2]<<16;
79 case 2: a
+= (u32
)k
[1]<<8;
81 __jhash_final(a
, b
, c
);
82 case 0: /* Nothing left to add */
89 static __attribute__ ((noinline
))
90 u32
__jhash_nwords(u32 a
, u32 b
, u32 c
, u32 initval
)
95 __jhash_final(a
, b
, c
);
99 static __attribute__ ((noinline
))
100 u32
jhash_2words(u32 a
, u32 b
, u32 initval
)
102 return __jhash_nwords(a
, b
, 0, initval
+ JHASH_INITVAL
+ (2 << 2));
121 struct packet_description
{
122 struct flow_key flow
;
134 struct vip_definition
{
149 struct real_pos_lru
{
154 struct real_definition
{
168 __uint(type
, BPF_MAP_TYPE_HASH
);
169 __uint(max_entries
, 512);
170 __type(key
, struct vip_definition
);
171 __type(value
, struct vip_meta
);
172 } vip_map
SEC(".maps");
175 __uint(type
, BPF_MAP_TYPE_LRU_HASH
);
176 __uint(max_entries
, 300);
177 __uint(map_flags
, 1U << 1);
178 __type(key
, struct flow_key
);
179 __type(value
, struct real_pos_lru
);
180 } lru_cache
SEC(".maps");
183 __uint(type
, BPF_MAP_TYPE_ARRAY
);
184 __uint(max_entries
, 12 * 655);
186 __type(value
, __u32
);
187 } ch_rings
SEC(".maps");
190 __uint(type
, BPF_MAP_TYPE_ARRAY
);
191 __uint(max_entries
, 40);
193 __type(value
, struct real_definition
);
194 } reals
SEC(".maps");
197 __uint(type
, BPF_MAP_TYPE_PERCPU_ARRAY
);
198 __uint(max_entries
, 515);
200 __type(value
, struct lb_stats
);
201 } stats
SEC(".maps");
204 __uint(type
, BPF_MAP_TYPE_ARRAY
);
205 __uint(max_entries
, 16);
207 __type(value
, struct ctl_value
);
208 } ctl_array
SEC(".maps");
211 unsigned char eth_dest
[6];
212 unsigned char eth_source
[6];
213 unsigned short eth_proto
;
216 static inline __u64
calc_offset(bool is_ipv6
, bool is_icmp
)
218 __u64 off
= sizeof(struct eth_hdr
);
220 off
+= sizeof(struct ipv6hdr
);
222 off
+= sizeof(struct icmp6hdr
) + sizeof(struct ipv6hdr
);
224 off
+= sizeof(struct iphdr
);
226 off
+= sizeof(struct icmphdr
) + sizeof(struct iphdr
);
231 static __attribute__ ((noinline
))
232 bool parse_udp(void *data
, void *data_end
,
233 bool is_ipv6
, struct packet_description
*pckt
)
236 bool is_icmp
= !((pckt
->flags
& (1 << 0)) == 0);
237 __u64 off
= calc_offset(is_ipv6
, is_icmp
);
241 if (udp
+ 1 > data_end
)
244 pckt
->flow
.port16
[0] = udp
->source
;
245 pckt
->flow
.port16
[1] = udp
->dest
;
247 pckt
->flow
.port16
[0] = udp
->dest
;
248 pckt
->flow
.port16
[1] = udp
->source
;
253 static __attribute__ ((noinline
))
254 bool parse_tcp(void *data
, void *data_end
,
255 bool is_ipv6
, struct packet_description
*pckt
)
258 bool is_icmp
= !((pckt
->flags
& (1 << 0)) == 0);
259 __u64 off
= calc_offset(is_ipv6
, is_icmp
);
263 if (tcp
+ 1 > data_end
)
266 pckt
->flags
|= (1 << 1);
268 pckt
->flow
.port16
[0] = tcp
->source
;
269 pckt
->flow
.port16
[1] = tcp
->dest
;
271 pckt
->flow
.port16
[0] = tcp
->dest
;
272 pckt
->flow
.port16
[1] = tcp
->source
;
277 static __attribute__ ((noinline
))
278 bool encap_v6(struct xdp_md
*xdp
, struct ctl_value
*cval
,
279 struct packet_description
*pckt
,
280 struct real_definition
*dst
, __u32 pkt_bytes
)
282 struct eth_hdr
*new_eth
;
283 struct eth_hdr
*old_eth
;
284 struct ipv6hdr
*ip6h
;
289 if (bpf_xdp_adjust_head(xdp
, 0 - (int)sizeof(struct ipv6hdr
)))
291 data
= (void *)(long)xdp
->data
;
292 data_end
= (void *)(long)xdp
->data_end
;
294 ip6h
= data
+ sizeof(struct eth_hdr
);
295 old_eth
= data
+ sizeof(struct ipv6hdr
);
296 if (new_eth
+ 1 > data_end
||
297 old_eth
+ 1 > data_end
|| ip6h
+ 1 > data_end
)
299 memcpy(new_eth
->eth_dest
, cval
->mac
, 6);
300 memcpy(new_eth
->eth_source
, old_eth
->eth_dest
, 6);
301 new_eth
->eth_proto
= 56710;
304 memset(ip6h
->flow_lbl
, 0, sizeof(ip6h
->flow_lbl
));
306 ip6h
->nexthdr
= IPPROTO_IPV6
;
307 ip_suffix
= pckt
->flow
.srcv6
[3] ^ pckt
->flow
.port16
[0];
309 bpf_htons(pkt_bytes
+ sizeof(struct ipv6hdr
));
312 ip6h
->saddr
.in6_u
.u6_addr32
[0] = 1;
313 ip6h
->saddr
.in6_u
.u6_addr32
[1] = 2;
314 ip6h
->saddr
.in6_u
.u6_addr32
[2] = 3;
315 ip6h
->saddr
.in6_u
.u6_addr32
[3] = ip_suffix
;
316 memcpy(ip6h
->daddr
.in6_u
.u6_addr32
, dst
->dstv6
, 16);
320 static __attribute__ ((noinline
))
321 bool encap_v4(struct xdp_md
*xdp
, struct ctl_value
*cval
,
322 struct packet_description
*pckt
,
323 struct real_definition
*dst
, __u32 pkt_bytes
)
326 __u32 ip_suffix
= bpf_ntohs(pckt
->flow
.port16
[0]);
327 struct eth_hdr
*new_eth
;
328 struct eth_hdr
*old_eth
;
336 ip_suffix
^= pckt
->flow
.src
;
337 if (bpf_xdp_adjust_head(xdp
, 0 - (int)sizeof(struct iphdr
)))
339 data
= (void *)(long)xdp
->data
;
340 data_end
= (void *)(long)xdp
->data_end
;
342 iph
= data
+ sizeof(struct eth_hdr
);
343 old_eth
= data
+ sizeof(struct iphdr
);
344 if (new_eth
+ 1 > data_end
||
345 old_eth
+ 1 > data_end
|| iph
+ 1 > data_end
)
347 memcpy(new_eth
->eth_dest
, cval
->mac
, 6);
348 memcpy(new_eth
->eth_source
, old_eth
->eth_dest
, 6);
349 new_eth
->eth_proto
= 8;
353 iph
->protocol
= IPPROTO_IPIP
;
356 iph
->tot_len
= bpf_htons(pkt_bytes
+ sizeof(struct iphdr
));
357 /* don't update iph->daddr, since it will overwrite old eth_proto
358 * and multiple iterations of bpf_prog_run() will fail
361 iph
->saddr
= ((0xFFFF0000 & ip_suffix
) | 4268) ^ dst
->dst
;
364 next_iph_u16
= (__u16
*) iph
;
365 #pragma clang loop unroll(full)
366 for (int i
= 0; i
< sizeof(struct iphdr
) >> 1; i
++)
367 csum
+= *next_iph_u16
++;
368 iph
->check
= ~((csum
& 0xffff) + (csum
>> 16));
369 if (bpf_xdp_adjust_head(xdp
, (int)sizeof(struct iphdr
)))
374 static __attribute__ ((noinline
))
375 bool decap_v6(struct xdp_md
*xdp
, void **data
, void **data_end
, bool inner_v4
)
377 struct eth_hdr
*new_eth
;
378 struct eth_hdr
*old_eth
;
381 new_eth
= *data
+ sizeof(struct ipv6hdr
);
382 memcpy(new_eth
->eth_source
, old_eth
->eth_source
, 6);
383 memcpy(new_eth
->eth_dest
, old_eth
->eth_dest
, 6);
385 new_eth
->eth_proto
= 8;
387 new_eth
->eth_proto
= 56710;
388 if (bpf_xdp_adjust_head(xdp
, (int)sizeof(struct ipv6hdr
)))
390 *data
= (void *)(long)xdp
->data
;
391 *data_end
= (void *)(long)xdp
->data_end
;
395 static __attribute__ ((noinline
))
396 bool decap_v4(struct xdp_md
*xdp
, void **data
, void **data_end
)
398 struct eth_hdr
*new_eth
;
399 struct eth_hdr
*old_eth
;
402 new_eth
= *data
+ sizeof(struct iphdr
);
403 memcpy(new_eth
->eth_source
, old_eth
->eth_source
, 6);
404 memcpy(new_eth
->eth_dest
, old_eth
->eth_dest
, 6);
405 new_eth
->eth_proto
= 8;
406 if (bpf_xdp_adjust_head(xdp
, (int)sizeof(struct iphdr
)))
408 *data
= (void *)(long)xdp
->data
;
409 *data_end
= (void *)(long)xdp
->data_end
;
413 static __attribute__ ((noinline
))
414 int swap_mac_and_send(void *data
, void *data_end
)
416 unsigned char tmp_mac
[6];
420 memcpy(tmp_mac
, eth
->eth_source
, 6);
421 memcpy(eth
->eth_source
, eth
->eth_dest
, 6);
422 memcpy(eth
->eth_dest
, tmp_mac
, 6);
426 static __attribute__ ((noinline
))
427 int send_icmp_reply(void *data
, void *data_end
)
429 struct icmphdr
*icmp_hdr
;
437 if (data
+ sizeof(struct eth_hdr
)
438 + sizeof(struct iphdr
) + sizeof(struct icmphdr
) > data_end
)
440 off
+= sizeof(struct eth_hdr
);
442 off
+= sizeof(struct iphdr
);
443 icmp_hdr
= data
+ off
;
445 icmp_hdr
->checksum
+= 0x0007;
447 tmp_addr
= iph
->daddr
;
448 iph
->daddr
= iph
->saddr
;
449 iph
->saddr
= tmp_addr
;
451 next_iph_u16
= (__u16
*) iph
;
452 #pragma clang loop unroll(full)
453 for (int i
= 0; i
< sizeof(struct iphdr
) >> 1; i
++)
454 csum
+= *next_iph_u16
++;
455 iph
->check
= ~((csum
& 0xffff) + (csum
>> 16));
456 return swap_mac_and_send(data
, data_end
);
459 static __attribute__ ((noinline
))
460 int send_icmp6_reply(void *data
, void *data_end
)
462 struct icmp6hdr
*icmp_hdr
;
463 struct ipv6hdr
*ip6h
;
467 if (data
+ sizeof(struct eth_hdr
)
468 + sizeof(struct ipv6hdr
) + sizeof(struct icmp6hdr
) > data_end
)
470 off
+= sizeof(struct eth_hdr
);
472 off
+= sizeof(struct ipv6hdr
);
473 icmp_hdr
= data
+ off
;
474 icmp_hdr
->icmp6_type
= 129;
475 icmp_hdr
->icmp6_cksum
-= 0x0001;
477 memcpy(tmp_addr
, ip6h
->saddr
.in6_u
.u6_addr32
, 16);
478 memcpy(ip6h
->saddr
.in6_u
.u6_addr32
, ip6h
->daddr
.in6_u
.u6_addr32
, 16);
479 memcpy(ip6h
->daddr
.in6_u
.u6_addr32
, tmp_addr
, 16);
480 return swap_mac_and_send(data
, data_end
);
483 static __attribute__ ((noinline
))
484 int parse_icmpv6(void *data
, void *data_end
, __u64 off
,
485 struct packet_description
*pckt
)
487 struct icmp6hdr
*icmp_hdr
;
488 struct ipv6hdr
*ip6h
;
490 icmp_hdr
= data
+ off
;
491 if (icmp_hdr
+ 1 > data_end
)
493 if (icmp_hdr
->icmp6_type
== 128)
494 return send_icmp6_reply(data
, data_end
);
495 if (icmp_hdr
->icmp6_type
!= 3)
497 off
+= sizeof(struct icmp6hdr
);
499 if (ip6h
+ 1 > data_end
)
501 pckt
->flow
.proto
= ip6h
->nexthdr
;
502 pckt
->flags
|= (1 << 0);
503 memcpy(pckt
->flow
.srcv6
, ip6h
->daddr
.in6_u
.u6_addr32
, 16);
504 memcpy(pckt
->flow
.dstv6
, ip6h
->saddr
.in6_u
.u6_addr32
, 16);
508 static __attribute__ ((noinline
))
509 int parse_icmp(void *data
, void *data_end
, __u64 off
,
510 struct packet_description
*pckt
)
512 struct icmphdr
*icmp_hdr
;
515 icmp_hdr
= data
+ off
;
516 if (icmp_hdr
+ 1 > data_end
)
518 if (icmp_hdr
->type
== 8)
519 return send_icmp_reply(data
, data_end
);
520 if ((icmp_hdr
->type
!= 3) || (icmp_hdr
->code
!= 4))
522 off
+= sizeof(struct icmphdr
);
524 if (iph
+ 1 > data_end
)
528 pckt
->flow
.proto
= iph
->protocol
;
529 pckt
->flags
|= (1 << 0);
530 pckt
->flow
.src
= iph
->daddr
;
531 pckt
->flow
.dst
= iph
->saddr
;
535 static __attribute__ ((noinline
))
536 __u32
get_packet_hash(struct packet_description
*pckt
,
540 return jhash_2words(jhash(pckt
->flow
.srcv6
, 16, 12),
541 pckt
->flow
.ports
, 24);
543 return jhash_2words(pckt
->flow
.src
, pckt
->flow
.ports
,
547 __attribute__ ((noinline
))
548 static bool get_packet_dst(struct real_definition
**real
,
549 struct packet_description
*pckt
,
550 struct vip_meta
*vip_info
,
551 bool is_ipv6
, void *lru_map
)
553 struct real_pos_lru new_dst_lru
= { };
554 bool hash_16bytes
= is_ipv6
;
555 __u32
*real_pos
, hash
, key
;
558 if (vip_info
->flags
& (1 << 2))
560 if (vip_info
->flags
& (1 << 3)) {
561 pckt
->flow
.port16
[0] = pckt
->flow
.port16
[1];
562 memset(pckt
->flow
.srcv6
, 0, 16);
564 hash
= get_packet_hash(pckt
, hash_16bytes
);
565 if (hash
!= 0x358459b7 /* jhash of ipv4 packet */ &&
566 hash
!= 0x2f4bc6bb /* jhash of ipv6 packet */)
568 key
= 2 * vip_info
->vip_num
+ hash
% 2;
569 real_pos
= bpf_map_lookup_elem(&ch_rings
, &key
);
573 *real
= bpf_map_lookup_elem(&reals
, &key
);
576 if (!(vip_info
->flags
& (1 << 1))) {
577 __u32 conn_rate_key
= 512 + 2;
578 struct lb_stats
*conn_rate_stats
=
579 bpf_map_lookup_elem(&stats
, &conn_rate_key
);
581 if (!conn_rate_stats
)
583 cur_time
= bpf_ktime_get_ns();
584 if ((cur_time
- conn_rate_stats
->v2
) >> 32 > 0xffFFFF) {
585 conn_rate_stats
->v1
= 1;
586 conn_rate_stats
->v2
= cur_time
;
588 conn_rate_stats
->v1
+= 1;
589 if (conn_rate_stats
->v1
>= 1)
592 if (pckt
->flow
.proto
== IPPROTO_UDP
)
593 new_dst_lru
.atime
= cur_time
;
594 new_dst_lru
.pos
= key
;
595 bpf_map_update_elem(lru_map
, &pckt
->flow
, &new_dst_lru
, 0);
600 __attribute__ ((noinline
))
601 static void connection_table_lookup(struct real_definition
**real
,
602 struct packet_description
*pckt
,
606 struct real_pos_lru
*dst_lru
;
610 dst_lru
= bpf_map_lookup_elem(lru_map
, &pckt
->flow
);
613 if (pckt
->flow
.proto
== IPPROTO_UDP
) {
614 cur_time
= bpf_ktime_get_ns();
615 if (cur_time
- dst_lru
->atime
> 300000)
617 dst_lru
->atime
= cur_time
;
620 *real
= bpf_map_lookup_elem(&reals
, &key
);
623 /* don't believe your eyes!
624 * below function has 6 arguments whereas bpf and llvm allow maximum of 5
625 * but since it's _static_ llvm can optimize one argument away
627 __attribute__ ((noinline
))
628 static int process_l3_headers_v6(struct packet_description
*pckt
,
629 __u8
*protocol
, __u64 off
,
630 __u16
*pkt_bytes
, void *data
,
633 struct ipv6hdr
*ip6h
;
638 if (ip6h
+ 1 > data_end
)
640 iph_len
= sizeof(struct ipv6hdr
);
641 *protocol
= ip6h
->nexthdr
;
642 pckt
->flow
.proto
= *protocol
;
643 *pkt_bytes
= bpf_ntohs(ip6h
->payload_len
);
645 if (*protocol
== 45) {
647 } else if (*protocol
== 59) {
648 action
= parse_icmpv6(data
, data_end
, off
, pckt
);
652 memcpy(pckt
->flow
.srcv6
, ip6h
->saddr
.in6_u
.u6_addr32
, 16);
653 memcpy(pckt
->flow
.dstv6
, ip6h
->daddr
.in6_u
.u6_addr32
, 16);
658 __attribute__ ((noinline
))
659 static int process_l3_headers_v4(struct packet_description
*pckt
,
660 __u8
*protocol
, __u64 off
,
661 __u16
*pkt_bytes
, void *data
,
669 if (iph
+ 1 > data_end
)
673 *protocol
= iph
->protocol
;
674 pckt
->flow
.proto
= *protocol
;
675 *pkt_bytes
= bpf_ntohs(iph
->tot_len
);
677 if (iph
->frag_off
& 65343)
679 if (*protocol
== IPPROTO_ICMP
) {
680 action
= parse_icmp(data
, data_end
, off
, pckt
);
684 pckt
->flow
.src
= iph
->saddr
;
685 pckt
->flow
.dst
= iph
->daddr
;
690 __attribute__ ((noinline
))
691 static int process_packet(void *data
, __u64 off
, void *data_end
,
692 bool is_ipv6
, struct xdp_md
*xdp
)
695 struct real_definition
*dst
= NULL
;
696 struct packet_description pckt
= { };
697 struct vip_definition vip
= { };
698 struct lb_stats
*data_stats
;
699 struct eth_hdr
*eth
= data
;
700 void *lru_map
= &lru_cache
;
701 struct vip_meta
*vip_info
;
702 __u32 lru_stats_key
= 513;
703 __u32 mac_addr_pos
= 0;
704 __u32 stats_key
= 512;
705 struct ctl_value
*cval
;
713 action
= process_l3_headers_v6(&pckt
, &protocol
, off
,
714 &pkt_bytes
, data
, data_end
);
716 action
= process_l3_headers_v4(&pckt
, &protocol
, off
,
717 &pkt_bytes
, data
, data_end
);
720 protocol
= pckt
.flow
.proto
;
721 if (protocol
== IPPROTO_TCP
) {
722 if (!parse_tcp(data
, data_end
, is_ipv6
, &pckt
))
724 } else if (protocol
== IPPROTO_UDP
) {
725 if (!parse_udp(data
, data_end
, is_ipv6
, &pckt
))
732 memcpy(vip
.vipv6
, pckt
.flow
.dstv6
, 16);
734 vip
.vip
= pckt
.flow
.dst
;
735 vip
.port
= pckt
.flow
.port16
[1];
736 vip
.proto
= pckt
.flow
.proto
;
737 vip_info
= bpf_map_lookup_elem(&vip_map
, &vip
);
740 vip_info
= bpf_map_lookup_elem(&vip_map
, &vip
);
743 if (!(vip_info
->flags
& (1 << 4)))
744 pckt
.flow
.port16
[1] = 0;
746 if (data_end
- data
> 1400)
748 data_stats
= bpf_map_lookup_elem(&stats
, &stats_key
);
753 if (vip_info
->flags
& (1 << 0))
754 pckt
.flow
.port16
[0] = 0;
755 if (!(pckt
.flags
& (1 << 1)) && !(vip_info
->flags
& (1 << 1)))
756 connection_table_lookup(&dst
, &pckt
, lru_map
);
759 if (pckt
.flow
.proto
== IPPROTO_TCP
) {
760 struct lb_stats
*lru_stats
=
761 bpf_map_lookup_elem(&stats
, &lru_stats_key
);
765 if (pckt
.flags
& (1 << 1))
770 if (!get_packet_dst(&dst
, &pckt
, vip_info
, is_ipv6
, lru_map
))
775 cval
= bpf_map_lookup_elem(&ctl_array
, &mac_addr_pos
);
778 if (dst
->flags
& (1 << 0)) {
779 if (!encap_v6(xdp
, cval
, &pckt
, dst
, pkt_bytes
))
782 if (!encap_v4(xdp
, cval
, &pckt
, dst
, pkt_bytes
))
785 vip_num
= vip_info
->vip_num
;
786 data_stats
= bpf_map_lookup_elem(&stats
, &vip_num
);
790 data_stats
->v2
+= pkt_bytes
;
792 data
= (void *)(long)xdp
->data
;
793 data_end
= (void *)(long)xdp
->data_end
;
794 if (data
+ 4 > data_end
)
796 *(u32
*)data
= dst
->dst
;
800 __attribute__ ((section("xdp-test"), used
))
801 int balancer_ingress(struct xdp_md
*ctx
)
803 void *data
= (void *)(long)ctx
->data
;
804 void *data_end
= (void *)(long)ctx
->data_end
;
805 struct eth_hdr
*eth
= data
;
809 nh_off
= sizeof(struct eth_hdr
);
810 if (data
+ nh_off
> data_end
)
812 eth_proto
= bpf_ntohs(eth
->eth_proto
);
813 if (eth_proto
== ETH_P_IP
)
814 return process_packet(data
, nh_off
, data_end
, 0, ctx
);
815 else if (eth_proto
== ETH_P_IPV6
)
816 return process_packet(data
, nh_off
, data_end
, 1, ctx
);
821 char _license
[] __attribute__ ((section("license"), used
)) = "GPL";
822 int _version
__attribute__ ((section("version"), used
)) = 1;