|  | // SPDX-License-Identifier: GPL-2.0-or-later | 
|  | /* | 
|  | *	6LoWPAN IPv6 UDP compression according to RFC6282 | 
|  | * | 
|  | *	Authors: | 
|  | *	Alexander Aring	<aar@pengutronix.de> | 
|  | * | 
|  | *	Original written by: | 
|  | *	Alexander Smirnov <alex.bluesman.smirnov@gmail.com> | 
|  | *	Jon Smirl <jonsmirl@gmail.com> | 
|  | */ | 
|  |  | 
|  | #include "nhc.h" | 
|  |  | 
|  | #define LOWPAN_NHC_UDP_MASK		0xF8 | 
|  | #define LOWPAN_NHC_UDP_ID		0xF0 | 
|  |  | 
|  | #define LOWPAN_NHC_UDP_4BIT_PORT	0xF0B0 | 
|  | #define LOWPAN_NHC_UDP_4BIT_MASK	0xFFF0 | 
|  | #define LOWPAN_NHC_UDP_8BIT_PORT	0xF000 | 
|  | #define LOWPAN_NHC_UDP_8BIT_MASK	0xFF00 | 
|  |  | 
|  | /* values for port compression, _with checksum_ ie bit 5 set to 0 */ | 
|  |  | 
|  | /* all inline */ | 
|  | #define LOWPAN_NHC_UDP_CS_P_00	0xF0 | 
|  | /* source 16bit inline, dest = 0xF0 + 8 bit inline */ | 
|  | #define LOWPAN_NHC_UDP_CS_P_01	0xF1 | 
|  | /* source = 0xF0 + 8bit inline, dest = 16 bit inline */ | 
|  | #define LOWPAN_NHC_UDP_CS_P_10	0xF2 | 
|  | /* source & dest = 0xF0B + 4bit inline */ | 
|  | #define LOWPAN_NHC_UDP_CS_P_11	0xF3 | 
|  | /* checksum elided */ | 
|  | #define LOWPAN_NHC_UDP_CS_C	0x04 | 
|  |  | 
|  | static int udp_uncompress(struct sk_buff *skb, size_t needed) | 
|  | { | 
|  | u8 tmp = 0, val = 0; | 
|  | struct udphdr uh; | 
|  | bool fail; | 
|  | int err; | 
|  |  | 
|  | fail = lowpan_fetch_skb(skb, &tmp, sizeof(tmp)); | 
|  |  | 
|  | pr_debug("UDP header uncompression\n"); | 
|  | switch (tmp & LOWPAN_NHC_UDP_CS_P_11) { | 
|  | case LOWPAN_NHC_UDP_CS_P_00: | 
|  | fail |= lowpan_fetch_skb(skb, &uh.source, sizeof(uh.source)); | 
|  | fail |= lowpan_fetch_skb(skb, &uh.dest, sizeof(uh.dest)); | 
|  | break; | 
|  | case LOWPAN_NHC_UDP_CS_P_01: | 
|  | fail |= lowpan_fetch_skb(skb, &uh.source, sizeof(uh.source)); | 
|  | fail |= lowpan_fetch_skb(skb, &val, sizeof(val)); | 
|  | uh.dest = htons(val + LOWPAN_NHC_UDP_8BIT_PORT); | 
|  | break; | 
|  | case LOWPAN_NHC_UDP_CS_P_10: | 
|  | fail |= lowpan_fetch_skb(skb, &val, sizeof(val)); | 
|  | uh.source = htons(val + LOWPAN_NHC_UDP_8BIT_PORT); | 
|  | fail |= lowpan_fetch_skb(skb, &uh.dest, sizeof(uh.dest)); | 
|  | break; | 
|  | case LOWPAN_NHC_UDP_CS_P_11: | 
|  | fail |= lowpan_fetch_skb(skb, &val, sizeof(val)); | 
|  | uh.source = htons(LOWPAN_NHC_UDP_4BIT_PORT + (val >> 4)); | 
|  | uh.dest = htons(LOWPAN_NHC_UDP_4BIT_PORT + (val & 0x0f)); | 
|  | break; | 
|  | default: | 
|  | BUG(); | 
|  | } | 
|  |  | 
|  | pr_debug("uncompressed UDP ports: src = %d, dst = %d\n", | 
|  | ntohs(uh.source), ntohs(uh.dest)); | 
|  |  | 
|  | /* checksum */ | 
|  | if (tmp & LOWPAN_NHC_UDP_CS_C) { | 
|  | pr_debug_ratelimited("checksum elided currently not supported\n"); | 
|  | fail = true; | 
|  | } else { | 
|  | fail |= lowpan_fetch_skb(skb, &uh.check, sizeof(uh.check)); | 
|  | } | 
|  |  | 
|  | if (fail) | 
|  | return -EINVAL; | 
|  |  | 
|  | /* UDP length needs to be inferred from the lower layers | 
|  | * here, we obtain the hint from the remaining size of the | 
|  | * frame | 
|  | */ | 
|  | switch (lowpan_dev(skb->dev)->lltype) { | 
|  | case LOWPAN_LLTYPE_IEEE802154: | 
|  | if (lowpan_802154_cb(skb)->d_size) | 
|  | uh.len = htons(lowpan_802154_cb(skb)->d_size - | 
|  | sizeof(struct ipv6hdr)); | 
|  | else | 
|  | uh.len = htons(skb->len + sizeof(struct udphdr)); | 
|  | break; | 
|  | default: | 
|  | uh.len = htons(skb->len + sizeof(struct udphdr)); | 
|  | break; | 
|  | } | 
|  | pr_debug("uncompressed UDP length: src = %d", ntohs(uh.len)); | 
|  |  | 
|  | /* replace the compressed UDP head by the uncompressed UDP | 
|  | * header | 
|  | */ | 
|  | err = skb_cow(skb, needed); | 
|  | if (unlikely(err)) | 
|  | return err; | 
|  |  | 
|  | skb_push(skb, sizeof(struct udphdr)); | 
|  | skb_copy_to_linear_data(skb, &uh, sizeof(struct udphdr)); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int udp_compress(struct sk_buff *skb, u8 **hc_ptr) | 
|  | { | 
|  | const struct udphdr *uh = udp_hdr(skb); | 
|  | u8 tmp; | 
|  |  | 
|  | if (((ntohs(uh->source) & LOWPAN_NHC_UDP_4BIT_MASK) == | 
|  | LOWPAN_NHC_UDP_4BIT_PORT) && | 
|  | ((ntohs(uh->dest) & LOWPAN_NHC_UDP_4BIT_MASK) == | 
|  | LOWPAN_NHC_UDP_4BIT_PORT)) { | 
|  | pr_debug("UDP header: both ports compression to 4 bits\n"); | 
|  | /* compression value */ | 
|  | tmp = LOWPAN_NHC_UDP_CS_P_11; | 
|  | lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp)); | 
|  | /* source and destination port */ | 
|  | tmp = ntohs(uh->dest) - LOWPAN_NHC_UDP_4BIT_PORT + | 
|  | ((ntohs(uh->source) - LOWPAN_NHC_UDP_4BIT_PORT) << 4); | 
|  | lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp)); | 
|  | } else if ((ntohs(uh->dest) & LOWPAN_NHC_UDP_8BIT_MASK) == | 
|  | LOWPAN_NHC_UDP_8BIT_PORT) { | 
|  | pr_debug("UDP header: remove 8 bits of dest\n"); | 
|  | /* compression value */ | 
|  | tmp = LOWPAN_NHC_UDP_CS_P_01; | 
|  | lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp)); | 
|  | /* source port */ | 
|  | lowpan_push_hc_data(hc_ptr, &uh->source, sizeof(uh->source)); | 
|  | /* destination port */ | 
|  | tmp = ntohs(uh->dest) - LOWPAN_NHC_UDP_8BIT_PORT; | 
|  | lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp)); | 
|  | } else if ((ntohs(uh->source) & LOWPAN_NHC_UDP_8BIT_MASK) == | 
|  | LOWPAN_NHC_UDP_8BIT_PORT) { | 
|  | pr_debug("UDP header: remove 8 bits of source\n"); | 
|  | /* compression value */ | 
|  | tmp = LOWPAN_NHC_UDP_CS_P_10; | 
|  | lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp)); | 
|  | /* source port */ | 
|  | tmp = ntohs(uh->source) - LOWPAN_NHC_UDP_8BIT_PORT; | 
|  | lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp)); | 
|  | /* destination port */ | 
|  | lowpan_push_hc_data(hc_ptr, &uh->dest, sizeof(uh->dest)); | 
|  | } else { | 
|  | pr_debug("UDP header: can't compress\n"); | 
|  | /* compression value */ | 
|  | tmp = LOWPAN_NHC_UDP_CS_P_00; | 
|  | lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp)); | 
|  | /* source port */ | 
|  | lowpan_push_hc_data(hc_ptr, &uh->source, sizeof(uh->source)); | 
|  | /* destination port */ | 
|  | lowpan_push_hc_data(hc_ptr, &uh->dest, sizeof(uh->dest)); | 
|  | } | 
|  |  | 
|  | /* checksum is always inline */ | 
|  | lowpan_push_hc_data(hc_ptr, &uh->check, sizeof(uh->check)); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | LOWPAN_NHC(nhc_udp, "RFC6282 UDP", NEXTHDR_UDP, sizeof(struct udphdr), | 
|  | LOWPAN_NHC_UDP_ID, LOWPAN_NHC_UDP_MASK, udp_uncompress, udp_compress); | 
|  |  | 
|  | module_lowpan_nhc(nhc_udp); | 
|  | MODULE_DESCRIPTION("6LoWPAN next header RFC6282 UDP compression"); | 
|  | MODULE_LICENSE("GPL"); |