Neels Hofmeyr | 3243c7c | 2018-09-30 05:01:20 +0200 | [diff] [blame] | 1 | /* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ |
| 2 | /* IuUP Core Network side protocol, minimal implementation */ |
| 3 | |
| 4 | /* |
| 5 | * (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> |
| 6 | * All Rights Reserved |
| 7 | * |
| 8 | * Author: Neels Hofmeyr <neels@hofmeyr.de> |
| 9 | * |
| 10 | * This program is free software; you can redistribute it and/or modify |
| 11 | * it under the terms of the GNU Affero General Public License as published by |
| 12 | * the Free Software Foundation; either version 3 of the License, or |
| 13 | * (at your option) any later version. |
| 14 | * |
| 15 | * This program is distributed in the hope that it will be useful, |
| 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 18 | * GNU Affero General Public License for more details. |
| 19 | * |
| 20 | * You should have received a copy of the GNU Affero General Public License |
| 21 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 22 | * |
| 23 | */ |
| 24 | |
| 25 | #include <errno.h> |
| 26 | #include <osmocom/mgcp/iuup_protocol.h> |
| 27 | #include <osmocom/mgcp/debug.h> |
| 28 | #include <osmocom/netif/rtp.h> |
| 29 | |
| 30 | /* Calculating two bytes of CRC is ok to do by a loop */ |
| 31 | static uint8_t header_crc6(const uint8_t *hdr) |
| 32 | { |
| 33 | int bit; |
| 34 | /* Polynomial: D^6 + D^5 + D^3 + D^2 + D^1 + 1 |
| 35 | * that's 1101111 or 0x6f; |
| 36 | * align its lowest bit with a uint16_t's highest bit: */ |
| 37 | uint32_t polynomial = 0x6f << 15; // 00110111 10000000 00000000 |
| 38 | uint32_t remainder = ( ((uint32_t)hdr[0]) << 8 | hdr[1] ) << 6; |
| 39 | |
| 40 | for (bit = 15; bit >= 0; bit--) |
| 41 | { |
| 42 | if (remainder & (0x40 << bit)) |
| 43 | remainder ^= polynomial; |
| 44 | polynomial >>= 1; |
| 45 | } |
| 46 | |
| 47 | return remainder; |
| 48 | } |
| 49 | |
| 50 | /* |
| 51 | * Charles Michael Heard's CRC-10 code, from |
| 52 | * |
| 53 | * http://web.archive.org/web/20061005231950/http://cell-relay.indiana.edu/cell-relay/publications/software/CRC/crc10.html |
| 54 | * |
| 55 | * with the CRC table initialized with values computed by |
| 56 | * his "gen_byte_crc10_table()" routine, rather than by calling that |
| 57 | * routine at run time, and with various data type cleanups. |
| 58 | */ |
| 59 | static const uint16_t byte_crc10_table[256] = { |
| 60 | 0x0000, 0x0233, 0x0255, 0x0066, 0x0299, 0x00aa, 0x00cc, 0x02ff, |
| 61 | 0x0301, 0x0132, 0x0154, 0x0367, 0x0198, 0x03ab, 0x03cd, 0x01fe, |
| 62 | 0x0031, 0x0202, 0x0264, 0x0057, 0x02a8, 0x009b, 0x00fd, 0x02ce, |
| 63 | 0x0330, 0x0103, 0x0165, 0x0356, 0x01a9, 0x039a, 0x03fc, 0x01cf, |
| 64 | 0x0062, 0x0251, 0x0237, 0x0004, 0x02fb, 0x00c8, 0x00ae, 0x029d, |
| 65 | 0x0363, 0x0150, 0x0136, 0x0305, 0x01fa, 0x03c9, 0x03af, 0x019c, |
| 66 | 0x0053, 0x0260, 0x0206, 0x0035, 0x02ca, 0x00f9, 0x009f, 0x02ac, |
| 67 | 0x0352, 0x0161, 0x0107, 0x0334, 0x01cb, 0x03f8, 0x039e, 0x01ad, |
| 68 | 0x00c4, 0x02f7, 0x0291, 0x00a2, 0x025d, 0x006e, 0x0008, 0x023b, |
| 69 | 0x03c5, 0x01f6, 0x0190, 0x03a3, 0x015c, 0x036f, 0x0309, 0x013a, |
| 70 | 0x00f5, 0x02c6, 0x02a0, 0x0093, 0x026c, 0x005f, 0x0039, 0x020a, |
| 71 | 0x03f4, 0x01c7, 0x01a1, 0x0392, 0x016d, 0x035e, 0x0338, 0x010b, |
| 72 | 0x00a6, 0x0295, 0x02f3, 0x00c0, 0x023f, 0x000c, 0x006a, 0x0259, |
| 73 | 0x03a7, 0x0194, 0x01f2, 0x03c1, 0x013e, 0x030d, 0x036b, 0x0158, |
| 74 | 0x0097, 0x02a4, 0x02c2, 0x00f1, 0x020e, 0x003d, 0x005b, 0x0268, |
| 75 | 0x0396, 0x01a5, 0x01c3, 0x03f0, 0x010f, 0x033c, 0x035a, 0x0169, |
| 76 | 0x0188, 0x03bb, 0x03dd, 0x01ee, 0x0311, 0x0122, 0x0144, 0x0377, |
| 77 | 0x0289, 0x00ba, 0x00dc, 0x02ef, 0x0010, 0x0223, 0x0245, 0x0076, |
| 78 | 0x01b9, 0x038a, 0x03ec, 0x01df, 0x0320, 0x0113, 0x0175, 0x0346, |
| 79 | 0x02b8, 0x008b, 0x00ed, 0x02de, 0x0021, 0x0212, 0x0274, 0x0047, |
| 80 | 0x01ea, 0x03d9, 0x03bf, 0x018c, 0x0373, 0x0140, 0x0126, 0x0315, |
| 81 | 0x02eb, 0x00d8, 0x00be, 0x028d, 0x0072, 0x0241, 0x0227, 0x0014, |
| 82 | 0x01db, 0x03e8, 0x038e, 0x01bd, 0x0342, 0x0171, 0x0117, 0x0324, |
| 83 | 0x02da, 0x00e9, 0x008f, 0x02bc, 0x0043, 0x0270, 0x0216, 0x0025, |
| 84 | 0x014c, 0x037f, 0x0319, 0x012a, 0x03d5, 0x01e6, 0x0180, 0x03b3, |
| 85 | 0x024d, 0x007e, 0x0018, 0x022b, 0x00d4, 0x02e7, 0x0281, 0x00b2, |
| 86 | 0x017d, 0x034e, 0x0328, 0x011b, 0x03e4, 0x01d7, 0x01b1, 0x0382, |
| 87 | 0x027c, 0x004f, 0x0029, 0x021a, 0x00e5, 0x02d6, 0x02b0, 0x0083, |
| 88 | 0x012e, 0x031d, 0x037b, 0x0148, 0x03b7, 0x0184, 0x01e2, 0x03d1, |
| 89 | 0x022f, 0x001c, 0x007a, 0x0249, 0x00b6, 0x0285, 0x02e3, 0x00d0, |
| 90 | 0x011f, 0x032c, 0x034a, 0x0179, 0x0386, 0x01b5, 0x01d3, 0x03e0, |
| 91 | 0x021e, 0x002d, 0x004b, 0x0278, 0x0087, 0x02b4, 0x02d2, 0x00e1 |
| 92 | }; |
| 93 | |
| 94 | static uint16_t crc10(uint16_t crc10_accum, const uint8_t *payload, unsigned int payload_len) |
| 95 | { |
| 96 | int i; |
| 97 | |
| 98 | for (i = 0; i < payload_len; i++) { |
| 99 | crc10_accum = ((crc10_accum << 8) & 0x300) |
| 100 | ^ byte_crc10_table[(crc10_accum >> 2) & 0xff] |
| 101 | ^ payload[i]; |
| 102 | } |
| 103 | return crc10_accum; |
| 104 | } |
| 105 | |
| 106 | /* When a payload of a multiple of bytes has run through, we need to still feed 10 bits of zeros into the |
| 107 | * CRC10 to get the payload's checksum result that we can send to a peer. That can't be done with above |
| 108 | * table, because it acts as if full 16 bits are fed. This stops after 10 bits. */ |
| 109 | static uint16_t crc10_remainder(uint16_t crc10_accum) |
| 110 | { |
| 111 | int bit; |
| 112 | /* Polynomial: D^10 + D^9 + D^5 + D^4 + D^1 + 1 |
| 113 | * that's 11000110011 or 0x633; |
| 114 | * align its lowest bit with a 10bit value's highest bit: */ |
| 115 | uint32_t polynomial = 0x633 << 9; // 1100 01100110 00000000 |
| 116 | uint32_t remainder = ((uint32_t)crc10_accum) << 10; |
| 117 | |
| 118 | /* Run on 10 bits */ |
| 119 | for (bit = 9; bit >= 0; bit--) |
| 120 | { |
| 121 | if (remainder & ((1 << 10) << bit)) |
| 122 | remainder ^= polynomial; |
| 123 | polynomial >>= 1; |
| 124 | } |
| 125 | |
| 126 | return remainder & 0x3ff; |
| 127 | } |
| 128 | |
| 129 | static uint16_t payload_crc10(const uint8_t *payload, unsigned int payload_len) |
| 130 | { |
| 131 | uint16_t crc10_accum = crc10(0, payload, payload_len); |
| 132 | return crc10_remainder(crc10_accum); |
| 133 | } |
| 134 | |
| 135 | /* Given an IuUP PDU data block, write the correct header and payload CRC checksums at the right places. |
| 136 | */ |
| 137 | void osmo_iuup_set_checksums(uint8_t *iuup_header_and_payload, unsigned int header_and_payload_len) |
| 138 | { |
| 139 | /* For both data and ctrl, the checksums and payload are at the same offset */ |
| 140 | struct osmo_iuup_hdr_data *hdr = (void*)iuup_header_and_payload; |
| 141 | uint16_t crc; |
| 142 | unsigned int payload_len; |
| 143 | |
| 144 | hdr->header_crc = header_crc6(iuup_header_and_payload); |
| 145 | |
| 146 | payload_len = iuup_header_and_payload + header_and_payload_len - hdr->payload; |
| 147 | crc = payload_crc10(hdr->payload, payload_len); |
| 148 | hdr->payload_crc_hi = (crc >> 8) & 0x3; |
| 149 | hdr->payload_crc_lo = crc & 0xff; |
| 150 | |
| 151 | } |
| 152 | |
| 153 | /* Validate minimum message sizes, IuUP PDU type, header- and payload checksums. If it is a Control |
| 154 | * Procedure PDU, return the header position in is_ctrl, if it is a Data PDU, return the header position |
Neels Hofmeyr | 85f9401 | 2018-10-08 03:36:45 +0200 | [diff] [blame] | 155 | * in is_data. If log_errors is true, log on DIUUP with the given log label for context. Return NULL in |
Neels Hofmeyr | 3243c7c | 2018-09-30 05:01:20 +0200 | [diff] [blame] | 156 | * both is_ctrl and is_data, and return a negative error code if the PDU could not be identified as a |
| 157 | * valid RTP PDU containing an IuUP part. */ |
| 158 | int osmo_iuup_classify(bool log_errors, |
| 159 | const char *log_label, |
| 160 | struct msgb *pdu, |
| 161 | struct osmo_iuup_hdr_ctrl **is_ctrl, |
| 162 | struct osmo_iuup_hdr_data **is_data) |
| 163 | { |
| 164 | struct rtp_hdr *rtp = (void*)pdu->data; |
| 165 | struct osmo_iuup_hdr_ctrl *hdr = (void*)rtp->data; |
| 166 | unsigned int payload_len; |
| 167 | uint16_t crc_calculated; |
| 168 | uint16_t crc_from_peer; |
| 169 | |
| 170 | #define ERR(fmt, args...) do { \ |
| 171 | if (log_errors) \ |
Neels Hofmeyr | 85f9401 | 2018-10-08 03:36:45 +0200 | [diff] [blame] | 172 | LOGP(DIUUP, LOGL_ERROR, "(%s) " fmt, log_label? : "-", ## args); \ |
Neels Hofmeyr | 3243c7c | 2018-09-30 05:01:20 +0200 | [diff] [blame] | 173 | return -EINVAL; \ |
| 174 | } while (0) |
| 175 | |
| 176 | if (is_ctrl) |
| 177 | *is_ctrl = NULL; |
| 178 | if (is_data) |
| 179 | *is_data = NULL; |
| 180 | |
| 181 | /* We need at least a header of 4 bytes. The osmo_iuup_hdr_ctrl already includes a byte of |
| 182 | * payload, so use osmo_iuup_hdr_data to check the minimum here. */ |
| 183 | if (pdu->len < (sizeof(*rtp) + sizeof(struct osmo_iuup_hdr_data))) |
| 184 | ERR("IuUP PDU too short: %u\n", pdu->len); |
| 185 | |
| 186 | /* Let's not validate checksums if the header type isn't sane */ |
| 187 | switch (hdr->pdu_type) { |
| 188 | case OSMO_IUUP_PDU_DATA_WITH_CRC: |
| 189 | /* If the caller isn't interested in data PDUs, cut short here. */ |
| 190 | if (!is_data) |
| 191 | return 0; |
| 192 | break; |
| 193 | case OSMO_IUUP_PDU_CONTROL_PROCEDURE: |
| 194 | /* If the caller isn't interested in control PDUs, cut short here. */ |
| 195 | if (!is_ctrl) |
| 196 | return 0; |
| 197 | if (pdu->len < (sizeof(*rtp) + sizeof(struct osmo_iuup_hdr_ctrl))) |
| 198 | ERR("IuUP control PDU too short: %u\n", pdu->len); |
| 199 | break; |
| 200 | default: |
| 201 | ERR("IuUP with invalid type: %u\n", hdr->pdu_type); |
| 202 | } |
| 203 | |
| 204 | /* For both data and ctrl, the checksums and payload are at the same offset */ |
| 205 | |
| 206 | crc_calculated = header_crc6((uint8_t*)hdr); |
| 207 | if (crc_calculated != hdr->header_crc) |
| 208 | ERR("IuUP PDU with invalid header CRC (peer sent 0x%x, calculated 0x%x)\n", |
| 209 | hdr->header_crc, crc_calculated); |
| 210 | |
| 211 | payload_len = pdu->tail - hdr->payload; |
| 212 | crc_calculated = payload_crc10(hdr->payload, payload_len); |
| 213 | crc_from_peer = (((uint16_t)hdr->payload_crc_hi) << 8) | hdr->payload_crc_lo; |
| 214 | if (crc_from_peer != crc_calculated) |
| 215 | ERR("IuUP PDU with invalid payload CRC (peer sent 0x%x, calculated 0x%x)\n", |
| 216 | crc_from_peer, crc_calculated); |
| 217 | |
| 218 | switch (hdr->pdu_type) { |
| 219 | case OSMO_IUUP_PDU_DATA_WITH_CRC: |
| 220 | if (is_data) |
| 221 | *is_data = (void*)hdr; |
| 222 | return 0; |
| 223 | case OSMO_IUUP_PDU_CONTROL_PROCEDURE: |
| 224 | if (is_ctrl) |
| 225 | *is_ctrl = hdr; |
| 226 | return 0; |
| 227 | default: |
| 228 | ERR("IuUP with invalid type: %u\n", hdr->pdu_type); |
| 229 | } |
| 230 | #undef ERR |
| 231 | } |
| 232 | |
| 233 | /* Return true if this RTP packet contains an IuUP Initialization header (detect IuUP peer). */ |
| 234 | bool osmo_iuup_is_init(struct msgb *pdu) |
| 235 | { |
| 236 | struct osmo_iuup_hdr_ctrl *is_ctrl; |
| 237 | osmo_iuup_classify(false, NULL, pdu, &is_ctrl, NULL); |
| 238 | return is_ctrl |
| 239 | && is_ctrl->procedure == OSMO_IUUP_PROC_INITIALIZATION |
| 240 | && is_ctrl->ack_nack == OSMO_IUUP_ACKNACK_PROCEDURE; |
| 241 | } |
| 242 | |
Neels Hofmeyr | 85f9401 | 2018-10-08 03:36:45 +0200 | [diff] [blame] | 243 | /* Append an IuUP Initialization ACK message */ |
Neels Hofmeyr | 3243c7c | 2018-09-30 05:01:20 +0200 | [diff] [blame] | 244 | void osmo_iuup_make_init_ack(struct msgb *ack) |
| 245 | { |
| 246 | /* Send Initialization Ack PDU back to the sender */ |
| 247 | struct osmo_iuup_hdr_ctrl *hdr; |
| 248 | OSMO_ASSERT(ack); |
| 249 | |
| 250 | hdr = (void*)msgb_put(ack, sizeof(*hdr)); |
| 251 | |
| 252 | *hdr = (struct osmo_iuup_hdr_ctrl){ |
| 253 | .pdu_type = OSMO_IUUP_PDU_CONTROL_PROCEDURE, |
| 254 | .ack_nack = OSMO_IUUP_ACKNACK_ACK, |
| 255 | .procedure = OSMO_IUUP_PROC_INITIALIZATION, |
| 256 | }; |
| 257 | |
| 258 | osmo_iuup_set_checksums((uint8_t*)hdr, sizeof(*hdr)); |
| 259 | } |
Neels Hofmeyr | 85f9401 | 2018-10-08 03:36:45 +0200 | [diff] [blame] | 260 | |
| 261 | const struct value_string osmo_iuup_error_cause_names[] = { |
| 262 | { 0, "CRC error of frame header" }, |
| 263 | { 1, "CRC error of frame payload" }, |
| 264 | { 2, "Unexpected frame number" }, |
| 265 | { 3, "Frame loss" }, |
| 266 | { 4, "PDU type unknown" }, |
| 267 | { 5, "Unknown procedure" }, |
| 268 | { 6, "Unknown reserved value" }, |
| 269 | { 7, "Unknown field" }, |
| 270 | { 8, "Frame too short" }, |
| 271 | { 9, "Missing fields" }, |
| 272 | { 16, "Unexpected PDU type" }, |
| 273 | { 17, "spare" }, |
| 274 | { 18, "Unexpected procedure" }, |
| 275 | { 19, "Unexpected RFCI" }, |
| 276 | { 20, "Unexpected value" }, |
| 277 | { 42, "Initialisation failure" }, |
| 278 | { 43, "Initialisation failure (network error, timer expiry)" }, |
| 279 | { 44, "Initialisation failure (Iu UP function error, repeated NACK)" }, |
| 280 | { 45, "Rate control failure" }, |
| 281 | { 46, "Error event failure" }, |
| 282 | { 47, "Time Alignment not supported" }, |
| 283 | { 48, "Requested Time Alignment not possible" }, |
| 284 | { 49, "Iu UP Mode version not supported" }, |
| 285 | {} |
| 286 | }; |