1bd4aed0eSJiong Wang // SPDX-License-Identifier: GPL-2.0 2bd4aed0eSJiong Wang #include <limits.h> 3bd4aed0eSJiong Wang #include <stddef.h> 4bd4aed0eSJiong Wang #include <stdbool.h> 5bd4aed0eSJiong Wang #include <string.h> 6bd4aed0eSJiong Wang #include <linux/pkt_cls.h> 7bd4aed0eSJiong Wang #include <linux/bpf.h> 8bd4aed0eSJiong Wang #include <linux/in.h> 9bd4aed0eSJiong Wang #include <linux/if_ether.h> 10bd4aed0eSJiong Wang #include <linux/icmp.h> 11bd4aed0eSJiong Wang #include <linux/ip.h> 12bd4aed0eSJiong Wang #include <linux/ipv6.h> 13bd4aed0eSJiong Wang #include <linux/tcp.h> 14bd4aed0eSJiong Wang #include <linux/udp.h> 15bd4aed0eSJiong Wang #include <linux/if_packet.h> 16bd4aed0eSJiong Wang #include <sys/socket.h> 17bd4aed0eSJiong Wang #include <linux/if_tunnel.h> 18bd4aed0eSJiong Wang #include <linux/mpls.h> 19bd4aed0eSJiong Wang #include "bpf_helpers.h" 20bd4aed0eSJiong Wang #include "bpf_endian.h" 21bd4aed0eSJiong Wang 22bd4aed0eSJiong Wang int _version SEC("version") = 1; 23bd4aed0eSJiong Wang #define PROG(F) SEC(#F) int bpf_func_##F 24bd4aed0eSJiong Wang 25bd4aed0eSJiong Wang /* These are the identifiers of the BPF programs that will be used in tail 26bd4aed0eSJiong Wang * calls. Name is limited to 16 characters, with the terminating character and 27bd4aed0eSJiong Wang * bpf_func_ above, we have only 6 to work with, anything after will be cropped. 28bd4aed0eSJiong Wang */ 29bd4aed0eSJiong Wang enum { 30bd4aed0eSJiong Wang IP, 31bd4aed0eSJiong Wang IPV6, 32bd4aed0eSJiong Wang IPV6OP, /* Destination/Hop-by-Hop Options IPv6 Extension header */ 33bd4aed0eSJiong Wang IPV6FR, /* Fragmentation IPv6 Extension Header */ 34bd4aed0eSJiong Wang MPLS, 35bd4aed0eSJiong Wang VLAN, 36bd4aed0eSJiong Wang }; 37bd4aed0eSJiong Wang 38bd4aed0eSJiong Wang #define IP_MF 0x2000 39bd4aed0eSJiong Wang #define IP_OFFSET 0x1FFF 40bd4aed0eSJiong Wang #define IP6_MF 0x0001 41bd4aed0eSJiong Wang #define IP6_OFFSET 0xFFF8 42bd4aed0eSJiong Wang 43bd4aed0eSJiong Wang struct vlan_hdr { 44bd4aed0eSJiong Wang __be16 h_vlan_TCI; 45bd4aed0eSJiong Wang __be16 h_vlan_encapsulated_proto; 46bd4aed0eSJiong Wang }; 47bd4aed0eSJiong Wang 48bd4aed0eSJiong Wang struct gre_hdr { 49bd4aed0eSJiong Wang __be16 flags; 50bd4aed0eSJiong Wang __be16 proto; 51bd4aed0eSJiong Wang }; 52bd4aed0eSJiong Wang 53bd4aed0eSJiong Wang struct frag_hdr { 54bd4aed0eSJiong Wang __u8 nexthdr; 55bd4aed0eSJiong Wang __u8 reserved; 56bd4aed0eSJiong Wang __be16 frag_off; 57bd4aed0eSJiong Wang __be32 identification; 58bd4aed0eSJiong Wang }; 59bd4aed0eSJiong Wang 60bd4aed0eSJiong Wang struct bpf_map_def SEC("maps") jmp_table = { 61bd4aed0eSJiong Wang .type = BPF_MAP_TYPE_PROG_ARRAY, 62bd4aed0eSJiong Wang .key_size = sizeof(__u32), 63bd4aed0eSJiong Wang .value_size = sizeof(__u32), 64bd4aed0eSJiong Wang .max_entries = 8 65bd4aed0eSJiong Wang }; 66bd4aed0eSJiong Wang 67bd4aed0eSJiong Wang static __always_inline void *bpf_flow_dissect_get_header(struct __sk_buff *skb, 68bd4aed0eSJiong Wang __u16 hdr_size, 69bd4aed0eSJiong Wang void *buffer) 70bd4aed0eSJiong Wang { 71bd4aed0eSJiong Wang void *data_end = (void *)(long)skb->data_end; 72bd4aed0eSJiong Wang void *data = (void *)(long)skb->data; 73bd4aed0eSJiong Wang __u16 thoff = skb->flow_keys->thoff; 74bd4aed0eSJiong Wang __u8 *hdr; 75bd4aed0eSJiong Wang 76bd4aed0eSJiong Wang /* Verifies this variable offset does not overflow */ 77bd4aed0eSJiong Wang if (thoff > (USHRT_MAX - hdr_size)) 78bd4aed0eSJiong Wang return NULL; 79bd4aed0eSJiong Wang 80bd4aed0eSJiong Wang hdr = data + thoff; 81bd4aed0eSJiong Wang if (hdr + hdr_size <= data_end) 82bd4aed0eSJiong Wang return hdr; 83bd4aed0eSJiong Wang 84bd4aed0eSJiong Wang if (bpf_skb_load_bytes(skb, thoff, buffer, hdr_size)) 85bd4aed0eSJiong Wang return NULL; 86bd4aed0eSJiong Wang 87bd4aed0eSJiong Wang return buffer; 88bd4aed0eSJiong Wang } 89bd4aed0eSJiong Wang 90bd4aed0eSJiong Wang /* Dispatches on ETHERTYPE */ 91bd4aed0eSJiong Wang static __always_inline int parse_eth_proto(struct __sk_buff *skb, __be16 proto) 92bd4aed0eSJiong Wang { 93bd4aed0eSJiong Wang struct bpf_flow_keys *keys = skb->flow_keys; 94bd4aed0eSJiong Wang 95bd4aed0eSJiong Wang switch (proto) { 96bd4aed0eSJiong Wang case bpf_htons(ETH_P_IP): 97bd4aed0eSJiong Wang bpf_tail_call(skb, &jmp_table, IP); 98bd4aed0eSJiong Wang break; 99bd4aed0eSJiong Wang case bpf_htons(ETH_P_IPV6): 100bd4aed0eSJiong Wang bpf_tail_call(skb, &jmp_table, IPV6); 101bd4aed0eSJiong Wang break; 102bd4aed0eSJiong Wang case bpf_htons(ETH_P_MPLS_MC): 103bd4aed0eSJiong Wang case bpf_htons(ETH_P_MPLS_UC): 104bd4aed0eSJiong Wang bpf_tail_call(skb, &jmp_table, MPLS); 105bd4aed0eSJiong Wang break; 106bd4aed0eSJiong Wang case bpf_htons(ETH_P_8021Q): 107bd4aed0eSJiong Wang case bpf_htons(ETH_P_8021AD): 108bd4aed0eSJiong Wang bpf_tail_call(skb, &jmp_table, VLAN); 109bd4aed0eSJiong Wang break; 110bd4aed0eSJiong Wang default: 111bd4aed0eSJiong Wang /* Protocol not supported */ 112bd4aed0eSJiong Wang return BPF_DROP; 113bd4aed0eSJiong Wang } 114bd4aed0eSJiong Wang 115bd4aed0eSJiong Wang return BPF_DROP; 116bd4aed0eSJiong Wang } 117bd4aed0eSJiong Wang 118bd4aed0eSJiong Wang SEC("flow_dissector") 119bd4aed0eSJiong Wang int _dissect(struct __sk_buff *skb) 120bd4aed0eSJiong Wang { 121822fe617SStanislav Fomichev struct bpf_flow_keys *keys = skb->flow_keys; 122822fe617SStanislav Fomichev 123822fe617SStanislav Fomichev return parse_eth_proto(skb, keys->n_proto); 124bd4aed0eSJiong Wang } 125bd4aed0eSJiong Wang 126bd4aed0eSJiong Wang /* Parses on IPPROTO_* */ 127bd4aed0eSJiong Wang static __always_inline int parse_ip_proto(struct __sk_buff *skb, __u8 proto) 128bd4aed0eSJiong Wang { 129bd4aed0eSJiong Wang struct bpf_flow_keys *keys = skb->flow_keys; 130bd4aed0eSJiong Wang void *data_end = (void *)(long)skb->data_end; 131bd4aed0eSJiong Wang struct icmphdr *icmp, _icmp; 132bd4aed0eSJiong Wang struct gre_hdr *gre, _gre; 133bd4aed0eSJiong Wang struct ethhdr *eth, _eth; 134bd4aed0eSJiong Wang struct tcphdr *tcp, _tcp; 135bd4aed0eSJiong Wang struct udphdr *udp, _udp; 136bd4aed0eSJiong Wang 137bd4aed0eSJiong Wang keys->ip_proto = proto; 138bd4aed0eSJiong Wang switch (proto) { 139bd4aed0eSJiong Wang case IPPROTO_ICMP: 140bd4aed0eSJiong Wang icmp = bpf_flow_dissect_get_header(skb, sizeof(*icmp), &_icmp); 141bd4aed0eSJiong Wang if (!icmp) 142bd4aed0eSJiong Wang return BPF_DROP; 143bd4aed0eSJiong Wang return BPF_OK; 144bd4aed0eSJiong Wang case IPPROTO_IPIP: 145bd4aed0eSJiong Wang keys->is_encap = true; 146bd4aed0eSJiong Wang return parse_eth_proto(skb, bpf_htons(ETH_P_IP)); 147bd4aed0eSJiong Wang case IPPROTO_IPV6: 148bd4aed0eSJiong Wang keys->is_encap = true; 149bd4aed0eSJiong Wang return parse_eth_proto(skb, bpf_htons(ETH_P_IPV6)); 150bd4aed0eSJiong Wang case IPPROTO_GRE: 151bd4aed0eSJiong Wang gre = bpf_flow_dissect_get_header(skb, sizeof(*gre), &_gre); 152bd4aed0eSJiong Wang if (!gre) 153bd4aed0eSJiong Wang return BPF_DROP; 154bd4aed0eSJiong Wang 155bd4aed0eSJiong Wang if (bpf_htons(gre->flags & GRE_VERSION)) 156bd4aed0eSJiong Wang /* Only inspect standard GRE packets with version 0 */ 157bd4aed0eSJiong Wang return BPF_OK; 158bd4aed0eSJiong Wang 159bd4aed0eSJiong Wang keys->thoff += sizeof(*gre); /* Step over GRE Flags and Proto */ 160bd4aed0eSJiong Wang if (GRE_IS_CSUM(gre->flags)) 161bd4aed0eSJiong Wang keys->thoff += 4; /* Step over chksum and Padding */ 162bd4aed0eSJiong Wang if (GRE_IS_KEY(gre->flags)) 163bd4aed0eSJiong Wang keys->thoff += 4; /* Step over key */ 164bd4aed0eSJiong Wang if (GRE_IS_SEQ(gre->flags)) 165bd4aed0eSJiong Wang keys->thoff += 4; /* Step over sequence number */ 166bd4aed0eSJiong Wang 167bd4aed0eSJiong Wang keys->is_encap = true; 168bd4aed0eSJiong Wang 169bd4aed0eSJiong Wang if (gre->proto == bpf_htons(ETH_P_TEB)) { 170bd4aed0eSJiong Wang eth = bpf_flow_dissect_get_header(skb, sizeof(*eth), 171bd4aed0eSJiong Wang &_eth); 172bd4aed0eSJiong Wang if (!eth) 173bd4aed0eSJiong Wang return BPF_DROP; 174bd4aed0eSJiong Wang 175bd4aed0eSJiong Wang keys->thoff += sizeof(*eth); 176bd4aed0eSJiong Wang 177bd4aed0eSJiong Wang return parse_eth_proto(skb, eth->h_proto); 178bd4aed0eSJiong Wang } else { 179bd4aed0eSJiong Wang return parse_eth_proto(skb, gre->proto); 180bd4aed0eSJiong Wang } 181bd4aed0eSJiong Wang case IPPROTO_TCP: 182bd4aed0eSJiong Wang tcp = bpf_flow_dissect_get_header(skb, sizeof(*tcp), &_tcp); 183bd4aed0eSJiong Wang if (!tcp) 184bd4aed0eSJiong Wang return BPF_DROP; 185bd4aed0eSJiong Wang 186bd4aed0eSJiong Wang if (tcp->doff < 5) 187bd4aed0eSJiong Wang return BPF_DROP; 188bd4aed0eSJiong Wang 189bd4aed0eSJiong Wang if ((__u8 *)tcp + (tcp->doff << 2) > data_end) 190bd4aed0eSJiong Wang return BPF_DROP; 191bd4aed0eSJiong Wang 192bd4aed0eSJiong Wang keys->sport = tcp->source; 193bd4aed0eSJiong Wang keys->dport = tcp->dest; 194bd4aed0eSJiong Wang return BPF_OK; 195bd4aed0eSJiong Wang case IPPROTO_UDP: 196bd4aed0eSJiong Wang case IPPROTO_UDPLITE: 197bd4aed0eSJiong Wang udp = bpf_flow_dissect_get_header(skb, sizeof(*udp), &_udp); 198bd4aed0eSJiong Wang if (!udp) 199bd4aed0eSJiong Wang return BPF_DROP; 200bd4aed0eSJiong Wang 201bd4aed0eSJiong Wang keys->sport = udp->source; 202bd4aed0eSJiong Wang keys->dport = udp->dest; 203bd4aed0eSJiong Wang return BPF_OK; 204bd4aed0eSJiong Wang default: 205bd4aed0eSJiong Wang return BPF_DROP; 206bd4aed0eSJiong Wang } 207bd4aed0eSJiong Wang 208bd4aed0eSJiong Wang return BPF_DROP; 209bd4aed0eSJiong Wang } 210bd4aed0eSJiong Wang 211bd4aed0eSJiong Wang static __always_inline int parse_ipv6_proto(struct __sk_buff *skb, __u8 nexthdr) 212bd4aed0eSJiong Wang { 213bd4aed0eSJiong Wang struct bpf_flow_keys *keys = skb->flow_keys; 214bd4aed0eSJiong Wang 215bd4aed0eSJiong Wang keys->ip_proto = nexthdr; 216bd4aed0eSJiong Wang switch (nexthdr) { 217bd4aed0eSJiong Wang case IPPROTO_HOPOPTS: 218bd4aed0eSJiong Wang case IPPROTO_DSTOPTS: 219bd4aed0eSJiong Wang bpf_tail_call(skb, &jmp_table, IPV6OP); 220bd4aed0eSJiong Wang break; 221bd4aed0eSJiong Wang case IPPROTO_FRAGMENT: 222bd4aed0eSJiong Wang bpf_tail_call(skb, &jmp_table, IPV6FR); 223bd4aed0eSJiong Wang break; 224bd4aed0eSJiong Wang default: 225bd4aed0eSJiong Wang return parse_ip_proto(skb, nexthdr); 226bd4aed0eSJiong Wang } 227bd4aed0eSJiong Wang 228bd4aed0eSJiong Wang return BPF_DROP; 229bd4aed0eSJiong Wang } 230bd4aed0eSJiong Wang 231bd4aed0eSJiong Wang PROG(IP)(struct __sk_buff *skb) 232bd4aed0eSJiong Wang { 233bd4aed0eSJiong Wang void *data_end = (void *)(long)skb->data_end; 234bd4aed0eSJiong Wang struct bpf_flow_keys *keys = skb->flow_keys; 235bd4aed0eSJiong Wang void *data = (void *)(long)skb->data; 236bd4aed0eSJiong Wang struct iphdr *iph, _iph; 237bd4aed0eSJiong Wang bool done = false; 238bd4aed0eSJiong Wang 239bd4aed0eSJiong Wang iph = bpf_flow_dissect_get_header(skb, sizeof(*iph), &_iph); 240bd4aed0eSJiong Wang if (!iph) 241bd4aed0eSJiong Wang return BPF_DROP; 242bd4aed0eSJiong Wang 243bd4aed0eSJiong Wang /* IP header cannot be smaller than 20 bytes */ 244bd4aed0eSJiong Wang if (iph->ihl < 5) 245bd4aed0eSJiong Wang return BPF_DROP; 246bd4aed0eSJiong Wang 247bd4aed0eSJiong Wang keys->addr_proto = ETH_P_IP; 248bd4aed0eSJiong Wang keys->ipv4_src = iph->saddr; 249bd4aed0eSJiong Wang keys->ipv4_dst = iph->daddr; 250bd4aed0eSJiong Wang 251bd4aed0eSJiong Wang keys->thoff += iph->ihl << 2; 252bd4aed0eSJiong Wang if (data + keys->thoff > data_end) 253bd4aed0eSJiong Wang return BPF_DROP; 254bd4aed0eSJiong Wang 255bd4aed0eSJiong Wang if (iph->frag_off & bpf_htons(IP_MF | IP_OFFSET)) { 256bd4aed0eSJiong Wang keys->is_frag = true; 257bd4aed0eSJiong Wang if (iph->frag_off & bpf_htons(IP_OFFSET)) 258bd4aed0eSJiong Wang /* From second fragment on, packets do not have headers 259bd4aed0eSJiong Wang * we can parse. 260bd4aed0eSJiong Wang */ 261bd4aed0eSJiong Wang done = true; 262bd4aed0eSJiong Wang else 263bd4aed0eSJiong Wang keys->is_first_frag = true; 264bd4aed0eSJiong Wang } 265bd4aed0eSJiong Wang 266bd4aed0eSJiong Wang if (done) 267bd4aed0eSJiong Wang return BPF_OK; 268bd4aed0eSJiong Wang 269bd4aed0eSJiong Wang return parse_ip_proto(skb, iph->protocol); 270bd4aed0eSJiong Wang } 271bd4aed0eSJiong Wang 272bd4aed0eSJiong Wang PROG(IPV6)(struct __sk_buff *skb) 273bd4aed0eSJiong Wang { 274bd4aed0eSJiong Wang struct bpf_flow_keys *keys = skb->flow_keys; 275bd4aed0eSJiong Wang struct ipv6hdr *ip6h, _ip6h; 276bd4aed0eSJiong Wang 277bd4aed0eSJiong Wang ip6h = bpf_flow_dissect_get_header(skb, sizeof(*ip6h), &_ip6h); 278bd4aed0eSJiong Wang if (!ip6h) 279bd4aed0eSJiong Wang return BPF_DROP; 280bd4aed0eSJiong Wang 281bd4aed0eSJiong Wang keys->addr_proto = ETH_P_IPV6; 282bd4aed0eSJiong Wang memcpy(&keys->ipv6_src, &ip6h->saddr, 2*sizeof(ip6h->saddr)); 283bd4aed0eSJiong Wang 284bd4aed0eSJiong Wang keys->thoff += sizeof(struct ipv6hdr); 285bd4aed0eSJiong Wang 286bd4aed0eSJiong Wang return parse_ipv6_proto(skb, ip6h->nexthdr); 287bd4aed0eSJiong Wang } 288bd4aed0eSJiong Wang 289bd4aed0eSJiong Wang PROG(IPV6OP)(struct __sk_buff *skb) 290bd4aed0eSJiong Wang { 291bd4aed0eSJiong Wang struct ipv6_opt_hdr *ip6h, _ip6h; 292bd4aed0eSJiong Wang 293bd4aed0eSJiong Wang ip6h = bpf_flow_dissect_get_header(skb, sizeof(*ip6h), &_ip6h); 294bd4aed0eSJiong Wang if (!ip6h) 295bd4aed0eSJiong Wang return BPF_DROP; 296bd4aed0eSJiong Wang 297bd4aed0eSJiong Wang /* hlen is in 8-octets and does not include the first 8 bytes 298bd4aed0eSJiong Wang * of the header 299bd4aed0eSJiong Wang */ 300bd4aed0eSJiong Wang skb->flow_keys->thoff += (1 + ip6h->hdrlen) << 3; 301bd4aed0eSJiong Wang 302bd4aed0eSJiong Wang return parse_ipv6_proto(skb, ip6h->nexthdr); 303bd4aed0eSJiong Wang } 304bd4aed0eSJiong Wang 305bd4aed0eSJiong Wang PROG(IPV6FR)(struct __sk_buff *skb) 306bd4aed0eSJiong Wang { 307bd4aed0eSJiong Wang struct bpf_flow_keys *keys = skb->flow_keys; 308bd4aed0eSJiong Wang struct frag_hdr *fragh, _fragh; 309bd4aed0eSJiong Wang 310bd4aed0eSJiong Wang fragh = bpf_flow_dissect_get_header(skb, sizeof(*fragh), &_fragh); 311bd4aed0eSJiong Wang if (!fragh) 312bd4aed0eSJiong Wang return BPF_DROP; 313bd4aed0eSJiong Wang 314bd4aed0eSJiong Wang keys->thoff += sizeof(*fragh); 315bd4aed0eSJiong Wang keys->is_frag = true; 316bd4aed0eSJiong Wang if (!(fragh->frag_off & bpf_htons(IP6_OFFSET))) 317bd4aed0eSJiong Wang keys->is_first_frag = true; 318bd4aed0eSJiong Wang 319bd4aed0eSJiong Wang return parse_ipv6_proto(skb, fragh->nexthdr); 320bd4aed0eSJiong Wang } 321bd4aed0eSJiong Wang 322bd4aed0eSJiong Wang PROG(MPLS)(struct __sk_buff *skb) 323bd4aed0eSJiong Wang { 324bd4aed0eSJiong Wang struct mpls_label *mpls, _mpls; 325bd4aed0eSJiong Wang 326bd4aed0eSJiong Wang mpls = bpf_flow_dissect_get_header(skb, sizeof(*mpls), &_mpls); 327bd4aed0eSJiong Wang if (!mpls) 328bd4aed0eSJiong Wang return BPF_DROP; 329bd4aed0eSJiong Wang 330bd4aed0eSJiong Wang return BPF_OK; 331bd4aed0eSJiong Wang } 332bd4aed0eSJiong Wang 333bd4aed0eSJiong Wang PROG(VLAN)(struct __sk_buff *skb) 334bd4aed0eSJiong Wang { 335bd4aed0eSJiong Wang struct bpf_flow_keys *keys = skb->flow_keys; 336bd4aed0eSJiong Wang struct vlan_hdr *vlan, _vlan; 337bd4aed0eSJiong Wang 338bd4aed0eSJiong Wang /* Account for double-tagging */ 3392c3af7d9SStanislav Fomichev if (keys->n_proto == bpf_htons(ETH_P_8021AD)) { 340bd4aed0eSJiong Wang vlan = bpf_flow_dissect_get_header(skb, sizeof(*vlan), &_vlan); 341bd4aed0eSJiong Wang if (!vlan) 342bd4aed0eSJiong Wang return BPF_DROP; 343bd4aed0eSJiong Wang 344bd4aed0eSJiong Wang if (vlan->h_vlan_encapsulated_proto != bpf_htons(ETH_P_8021Q)) 345bd4aed0eSJiong Wang return BPF_DROP; 346bd4aed0eSJiong Wang 3472c3af7d9SStanislav Fomichev keys->nhoff += sizeof(*vlan); 348bd4aed0eSJiong Wang keys->thoff += sizeof(*vlan); 349bd4aed0eSJiong Wang } 350bd4aed0eSJiong Wang 351bd4aed0eSJiong Wang vlan = bpf_flow_dissect_get_header(skb, sizeof(*vlan), &_vlan); 352bd4aed0eSJiong Wang if (!vlan) 353bd4aed0eSJiong Wang return BPF_DROP; 354bd4aed0eSJiong Wang 3552c3af7d9SStanislav Fomichev keys->nhoff += sizeof(*vlan); 356bd4aed0eSJiong Wang keys->thoff += sizeof(*vlan); 357bd4aed0eSJiong Wang /* Only allow 8021AD + 8021Q double tagging and no triple tagging.*/ 358bd4aed0eSJiong Wang if (vlan->h_vlan_encapsulated_proto == bpf_htons(ETH_P_8021AD) || 359bd4aed0eSJiong Wang vlan->h_vlan_encapsulated_proto == bpf_htons(ETH_P_8021Q)) 360bd4aed0eSJiong Wang return BPF_DROP; 361bd4aed0eSJiong Wang 362822fe617SStanislav Fomichev keys->n_proto = vlan->h_vlan_encapsulated_proto; 363bd4aed0eSJiong Wang return parse_eth_proto(skb, vlan->h_vlan_encapsulated_proto); 364bd4aed0eSJiong Wang } 365bd4aed0eSJiong Wang 366bd4aed0eSJiong Wang char __license[] SEC("license") = "GPL"; 367