James Kuszmaul | 871d071 | 2021-01-17 11:30:43 -0800 | [diff] [blame^] | 1 | /** |
| 2 | * @file pcp/msg.c PCP messages |
| 3 | * |
| 4 | * Copyright (C) 2010 - 2016 Creytiv.com |
| 5 | */ |
| 6 | #include <re_types.h> |
| 7 | #include <re_fmt.h> |
| 8 | #include <re_mem.h> |
| 9 | #include <re_mbuf.h> |
| 10 | #include <re_list.h> |
| 11 | #include <re_sa.h> |
| 12 | #include <re_pcp.h> |
| 13 | #include "pcp.h" |
| 14 | |
| 15 | |
| 16 | static int pcp_map_decode(struct pcp_map *map, struct mbuf *mb) |
| 17 | { |
| 18 | uint16_t port; |
| 19 | int err; |
| 20 | |
| 21 | if (!map || !mb) |
| 22 | return EINVAL; |
| 23 | |
| 24 | if (mbuf_get_left(mb) < PCP_MAP_SZ) |
| 25 | return EBADMSG; |
| 26 | |
| 27 | (void)mbuf_read_mem(mb, map->nonce, sizeof(map->nonce)); |
| 28 | map->proto = mbuf_read_u8(mb); mbuf_advance(mb, 3); |
| 29 | map->int_port = ntohs(mbuf_read_u16(mb)); |
| 30 | |
| 31 | port = ntohs(mbuf_read_u16(mb)); |
| 32 | err = pcp_ipaddr_decode(mb, &map->ext_addr); |
| 33 | sa_set_port(&map->ext_addr, port); |
| 34 | |
| 35 | return err; |
| 36 | } |
| 37 | |
| 38 | |
| 39 | static int pcp_peer_decode(struct pcp_peer *peer, struct mbuf *mb) |
| 40 | { |
| 41 | uint16_t port; |
| 42 | int err = 0; |
| 43 | |
| 44 | if (!peer || !mb) |
| 45 | return EINVAL; |
| 46 | |
| 47 | if (mbuf_get_left(mb) < PCP_PEER_SZ) |
| 48 | return EBADMSG; |
| 49 | |
| 50 | /* note: the MAP and PEER opcodes are quite similar */ |
| 51 | err = pcp_map_decode(&peer->map, mb); |
| 52 | if (err) |
| 53 | return err; |
| 54 | |
| 55 | port = ntohs(mbuf_read_u16(mb)); mbuf_advance(mb, 2); |
| 56 | err |= pcp_ipaddr_decode(mb, &peer->remote_addr); |
| 57 | sa_set_port(&peer->remote_addr, port); |
| 58 | |
| 59 | return err; |
| 60 | } |
| 61 | |
| 62 | |
| 63 | static void destructor(void *arg) |
| 64 | { |
| 65 | struct pcp_msg *msg = arg; |
| 66 | |
| 67 | list_flush(&msg->optionl); |
| 68 | } |
| 69 | |
| 70 | |
| 71 | static int pcp_header_encode_request(struct mbuf *mb, enum pcp_opcode opcode, |
| 72 | uint32_t req_lifetime, const struct sa *int_addr) |
| 73 | { |
| 74 | int err = 0; |
| 75 | |
| 76 | if (!mb || !int_addr) |
| 77 | return EINVAL; |
| 78 | |
| 79 | err |= mbuf_write_u8(mb, PCP_VERSION); |
| 80 | err |= mbuf_write_u8(mb, opcode); |
| 81 | err |= mbuf_write_u16(mb, 0x0000); |
| 82 | err |= mbuf_write_u32(mb, htonl(req_lifetime)); |
| 83 | err |= pcp_ipaddr_encode(mb, int_addr); |
| 84 | |
| 85 | return err; |
| 86 | } |
| 87 | |
| 88 | |
| 89 | static int pcp_header_decode(struct pcp_hdr *hdr, struct mbuf *mb) |
| 90 | { |
| 91 | uint8_t b; |
| 92 | |
| 93 | if (!hdr || !mb) |
| 94 | return EINVAL; |
| 95 | |
| 96 | if (mbuf_get_left(mb) < PCP_HDR_SZ) |
| 97 | return EBADMSG; |
| 98 | |
| 99 | hdr->version = mbuf_read_u8(mb); |
| 100 | |
| 101 | if (hdr->version != PCP_VERSION) { |
| 102 | (void)re_fprintf(stderr, "pcp: unknown version %u\n", |
| 103 | hdr->version); |
| 104 | return EPROTO; |
| 105 | } |
| 106 | |
| 107 | b = mbuf_read_u8(mb); |
| 108 | hdr->resp = b>>7; |
| 109 | hdr->opcode = b & 0x7f; |
| 110 | |
| 111 | (void)mbuf_read_u8(mb); |
| 112 | b = mbuf_read_u8(mb); |
| 113 | |
| 114 | if (hdr->resp) |
| 115 | hdr->result = b; |
| 116 | |
| 117 | hdr->lifetime = ntohl(mbuf_read_u32(mb)); |
| 118 | |
| 119 | if (hdr->resp) { |
| 120 | hdr->epoch = ntohl(mbuf_read_u32(mb)); |
| 121 | mbuf_advance(mb, 12); |
| 122 | } |
| 123 | else { /* Request */ |
| 124 | (void)pcp_ipaddr_decode(mb, &hdr->cli_addr); |
| 125 | } |
| 126 | |
| 127 | return 0; |
| 128 | } |
| 129 | |
| 130 | |
| 131 | int pcp_msg_req_vencode(struct mbuf *mb, enum pcp_opcode opcode, |
| 132 | uint32_t lifetime, const struct sa *cli_addr, |
| 133 | const void *payload, uint32_t optionc, va_list ap) |
| 134 | { |
| 135 | uint32_t i; |
| 136 | int err; |
| 137 | |
| 138 | if (!mb || !cli_addr) |
| 139 | return EINVAL; |
| 140 | |
| 141 | err = pcp_header_encode_request(mb, opcode, lifetime, cli_addr); |
| 142 | if (err) |
| 143 | return err; |
| 144 | |
| 145 | if (payload) { |
| 146 | err = pcp_payload_encode(mb, opcode, payload); |
| 147 | if (err) |
| 148 | return err; |
| 149 | } |
| 150 | |
| 151 | /* encode options */ |
| 152 | for (i=0; i<optionc; i++) { |
| 153 | |
| 154 | enum pcp_option_code code = va_arg(ap, int); |
| 155 | const void *v = va_arg(ap, const void *); |
| 156 | |
| 157 | if (!v) |
| 158 | continue; |
| 159 | |
| 160 | err |= pcp_option_encode(mb, code, v); |
| 161 | } |
| 162 | |
| 163 | return err; |
| 164 | } |
| 165 | |
| 166 | |
| 167 | int pcp_msg_req_encode(struct mbuf *mb, enum pcp_opcode opcode, |
| 168 | uint32_t lifetime, const struct sa *cli_addr, |
| 169 | const void *payload, uint32_t optionc, ...) |
| 170 | { |
| 171 | va_list ap; |
| 172 | int err; |
| 173 | |
| 174 | va_start(ap, optionc); |
| 175 | err = pcp_msg_req_vencode(mb, opcode, lifetime, cli_addr, |
| 176 | payload, optionc, ap); |
| 177 | va_end(ap); |
| 178 | |
| 179 | return err; |
| 180 | } |
| 181 | |
| 182 | |
| 183 | int pcp_msg_decode(struct pcp_msg **msgp, struct mbuf *mb) |
| 184 | { |
| 185 | struct pcp_msg *msg; |
| 186 | size_t len, pos; |
| 187 | int err; |
| 188 | |
| 189 | if (!msgp || !mb) |
| 190 | return EINVAL; |
| 191 | |
| 192 | len = mbuf_get_left(mb); |
| 193 | if (len < PCP_MIN_PACKET || len > PCP_MAX_PACKET || len&3) |
| 194 | return EBADMSG; |
| 195 | |
| 196 | msg = mem_zalloc(sizeof(*msg), destructor); |
| 197 | if (!msg) |
| 198 | return ENOMEM; |
| 199 | |
| 200 | pos = mb->pos; |
| 201 | err = pcp_header_decode(&msg->hdr, mb); |
| 202 | if (err) |
| 203 | goto out; |
| 204 | |
| 205 | switch (msg->hdr.opcode) { |
| 206 | |
| 207 | case PCP_MAP: |
| 208 | err = pcp_map_decode(&msg->pld.map, mb); |
| 209 | break; |
| 210 | |
| 211 | case PCP_PEER: |
| 212 | err = pcp_peer_decode(&msg->pld.peer, mb); |
| 213 | break; |
| 214 | |
| 215 | default: |
| 216 | break; |
| 217 | } |
| 218 | if (err) |
| 219 | goto out; |
| 220 | |
| 221 | /* Decode PCP Options */ |
| 222 | while (mbuf_get_left(mb) >= 4) { |
| 223 | |
| 224 | struct pcp_option *opt; |
| 225 | |
| 226 | err = pcp_option_decode(&opt, mb); |
| 227 | if (err) |
| 228 | goto out; |
| 229 | |
| 230 | list_append(&msg->optionl, &opt->le, opt); |
| 231 | } |
| 232 | |
| 233 | out: |
| 234 | if (err) { |
| 235 | mb->pos = pos; |
| 236 | mem_deref(msg); |
| 237 | } |
| 238 | else |
| 239 | *msgp = msg; |
| 240 | |
| 241 | return err; |
| 242 | } |
| 243 | |
| 244 | |
| 245 | struct pcp_option *pcp_msg_option(const struct pcp_msg *msg, |
| 246 | enum pcp_option_code code) |
| 247 | { |
| 248 | struct le *le = msg ? list_head(&msg->optionl) : NULL; |
| 249 | |
| 250 | while (le) { |
| 251 | struct pcp_option *opt = le->data; |
| 252 | |
| 253 | le = le->next; |
| 254 | |
| 255 | if (opt->code == code) |
| 256 | return opt; |
| 257 | } |
| 258 | |
| 259 | return NULL; |
| 260 | } |
| 261 | |
| 262 | |
| 263 | struct pcp_option *pcp_msg_option_apply(const struct pcp_msg *msg, |
| 264 | pcp_option_h *h, void *arg) |
| 265 | { |
| 266 | struct le *le = msg ? list_head(&msg->optionl) : NULL; |
| 267 | |
| 268 | while (le) { |
| 269 | struct pcp_option *opt = le->data; |
| 270 | |
| 271 | le = le->next; |
| 272 | |
| 273 | if (h && h(opt, arg)) |
| 274 | return opt; |
| 275 | } |
| 276 | |
| 277 | return NULL; |
| 278 | } |
| 279 | |
| 280 | |
| 281 | static bool option_print(const struct pcp_option *opt, void *arg) |
| 282 | { |
| 283 | return 0 != pcp_option_print(arg, opt); |
| 284 | } |
| 285 | |
| 286 | |
| 287 | int pcp_msg_printhdr(struct re_printf *pf, const struct pcp_msg *msg) |
| 288 | { |
| 289 | int err; |
| 290 | |
| 291 | if (!msg) |
| 292 | return 0; |
| 293 | |
| 294 | err = re_hprintf(pf, "%s %s %usec", |
| 295 | msg->hdr.resp ? "Response" : "Request", |
| 296 | pcp_opcode_name(msg->hdr.opcode), |
| 297 | msg->hdr.lifetime); |
| 298 | |
| 299 | if (msg->hdr.resp) { |
| 300 | err |= re_hprintf(pf, " result=%s, epoch_time=%u sec", |
| 301 | pcp_result_name(msg->hdr.result), |
| 302 | msg->hdr.epoch); |
| 303 | } |
| 304 | else { |
| 305 | err |= re_hprintf(pf, " client_addr=%j", &msg->hdr.cli_addr); |
| 306 | } |
| 307 | |
| 308 | return err; |
| 309 | } |
| 310 | |
| 311 | |
| 312 | static int pcp_map_print(struct re_printf *pf, const struct pcp_map *map) |
| 313 | { |
| 314 | if (!map) |
| 315 | return 0; |
| 316 | |
| 317 | return re_hprintf(pf, |
| 318 | " nonce = %w\n protocol = %s\n" |
| 319 | " int_port = %u\n ext_addr = %J\n", |
| 320 | map->nonce, sizeof(map->nonce), |
| 321 | pcp_proto_name(map->proto), |
| 322 | map->int_port, |
| 323 | &map->ext_addr); |
| 324 | } |
| 325 | |
| 326 | |
| 327 | int pcp_msg_print(struct re_printf *pf, const struct pcp_msg *msg) |
| 328 | { |
| 329 | int err; |
| 330 | |
| 331 | if (!msg) |
| 332 | return 0; |
| 333 | |
| 334 | err = pcp_msg_printhdr(pf, msg); |
| 335 | err |= re_hprintf(pf, "\n"); |
| 336 | |
| 337 | switch (msg->hdr.opcode) { |
| 338 | |
| 339 | case PCP_MAP: |
| 340 | err |= pcp_map_print(pf, &msg->pld.map); |
| 341 | break; |
| 342 | |
| 343 | case PCP_PEER: |
| 344 | err |= pcp_map_print(pf, &msg->pld.peer.map); |
| 345 | err |= re_hprintf(pf, " remote_peer = %J\n", |
| 346 | &msg->pld.peer.remote_addr); |
| 347 | break; |
| 348 | } |
| 349 | |
| 350 | if (err) |
| 351 | return err; |
| 352 | |
| 353 | if (pcp_msg_option_apply(msg, option_print, pf)) |
| 354 | return ENOMEM; |
| 355 | |
| 356 | return 0; |
| 357 | } |
| 358 | |
| 359 | |
| 360 | /** |
| 361 | * Get the payload from a PCP message |
| 362 | * |
| 363 | * @param msg PCP message |
| 364 | * |
| 365 | * @return either "struct pcp_map" or "struct pcp_peer" |
| 366 | */ |
| 367 | const void *pcp_msg_payload(const struct pcp_msg *msg) |
| 368 | { |
| 369 | if (!msg) |
| 370 | return NULL; |
| 371 | |
| 372 | switch (msg->hdr.opcode) { |
| 373 | |
| 374 | case PCP_MAP: return &msg->pld.map; |
| 375 | case PCP_PEER: return &msg->pld.peer; |
| 376 | default: return NULL; |
| 377 | } |
| 378 | } |