| 1 | /* |
| 2 | * OpenVPN -- An application to securely tunnel IP networks |
| 3 | * over a single TCP/UDP port, with support for SSL/TLS-based |
| 4 | * session authentication and key exchange, |
| 5 | * packet encryption, packet authentication, and |
| 6 | * packet compression. |
| 7 | * |
| 8 | * Copyright (C) 2002-2005 OpenVPN Solutions LLC <info@openvpn.net> |
| 9 | * |
| 10 | * This program is free software; you can redistribute it and/or modify |
| 11 | * it under the terms of the GNU General Public License version 2 |
| 12 | * as published by the Free Software Foundation. |
| 13 | * |
| 14 | * This program is distributed in the hope that it will be useful, |
| 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 17 | * GNU General Public License for more details. |
| 18 | * |
| 19 | * You should have received a copy of the GNU General Public License |
| 20 | * along with this program (see the file COPYING included with this |
| 21 | * distribution); if not, write to the Free Software Foundation, Inc., |
| 22 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 23 | */ |
| 24 | |
| 25 | #ifdef WIN32 |
| 26 | #include "config-win32.h" |
| 27 | #else |
| 28 | #include "config.h" |
| 29 | #endif |
| 30 | |
| 31 | #include "syshead.h" |
| 32 | |
| 33 | #include "mcast.h" |
| 34 | #include "proto.h" |
| 35 | #include "list.h" |
| 36 | #include "mroute.h" |
| 37 | |
| 38 | struct mcast_recipient_list |
| 39 | { |
| 40 | struct mroute_addr rcpt; |
| 41 | struct multi_instance *mi; |
| 42 | struct mcast_recipient_list *next; |
| 43 | }; |
| 44 | |
| 45 | struct mcast_timeout_list |
| 46 | { |
| 47 | struct mroute_addr * rcpt; |
| 48 | uint32_t group; |
| 49 | struct timeval timeout; |
| 50 | struct mcast_timeout_list * next; |
| 51 | }; |
| 52 | |
| 53 | /** mcast hashing functions */ |
| 54 | uint32_t mcast_addr_hash(const void *key, uint32_t iv) |
| 55 | { |
| 56 | return (*(const int32_t *)key) % iv; |
| 57 | } |
| 58 | |
| 59 | bool mcast_addr_compare(const void *key1, const void *key2) |
| 60 | { |
| 61 | return (*(const int32_t *)key1) == (*(const int32_t *)key2); |
| 62 | } |
| 63 | |
| 64 | /** initialization-stuff */ |
| 65 | void mcast_init(struct multi_context *m) |
| 66 | { |
| 67 | m->mcast_group_map = hash_init(4096, mcast_addr_hash, mcast_addr_compare); |
| 68 | msg (D_MCAST_LOW, "MCAST: initialized multicast maps"); |
| 69 | } |
| 70 | |
| 71 | /** debug_output_functions */ |
| 72 | void mcast_print_group_list(const struct multi_context *m, struct gc_arena *gc, uint32_t group) |
| 73 | { |
| 74 | struct mcast_recipient_list * current_list; |
| 75 | |
| 76 | msg (D_MCAST_DEBUG, "MCAST: current recipients for group %s", print_in_addr_t (group, IA_EMPTY_IF_UNDEF, gc)); |
| 77 | |
| 78 | for (current_list = (struct mcast_recipient_list *)hash_lookup(m->mcast_group_map, &group); current_list; current_list = current_list->next) |
| 79 | msg (D_MCAST_DEBUG, "MCAST: %s through %s", mroute_addr_print(¤t_list->rcpt, gc), multi_instance_string(current_list->mi, false, gc)); |
| 80 | } |
| 81 | |
| 82 | void mcast_print_timeout_list(const struct multi_instance *mi, struct gc_arena *gc) |
| 83 | { |
| 84 | struct mcast_timeout_list * current_list; |
| 85 | |
| 86 | msg (D_MCAST_DEBUG, "MCAST: current timeouts for multi-instance. (now = %s)", tv_string_abs(&((struct timeval){now,0}), gc)); |
| 87 | |
| 88 | for (current_list = mi->mcast_timeouts; current_list; current_list = current_list->next) |
| 89 | msg (D_MCAST_DEBUG, "MCAST: (%s, %s) -> %s", mroute_addr_print(current_list->rcpt, gc), print_in_addr_t (current_list->group, IA_EMPTY_IF_UNDEF, gc), tv_string_abs(¤t_list->timeout, gc)); |
| 90 | } |
| 91 | |
| 92 | /** functions to manipulate mcast-receiver-lists */ |
| 93 | inline struct mcast_recipient_list * mcast__create_recv_list_item(struct multi_instance * mi, const struct mroute_addr * rcpt) |
| 94 | { |
| 95 | struct mcast_recipient_list *list; |
| 96 | ALLOC_OBJ(list, struct mcast_recipient_list); |
| 97 | list->next = NULL; |
| 98 | list->mi = mi; |
| 99 | memcpy(&list->rcpt, rcpt, sizeof(struct mroute_addr)); |
| 100 | |
| 101 | return list; |
| 102 | } |
| 103 | |
| 104 | struct mcast_timeout_list * mcast__create_timeout_list_item(const uint32_t group, struct mroute_addr * rcpt) |
| 105 | { |
| 106 | struct mcast_timeout_list * timeout; |
| 107 | ALLOC_OBJ(timeout, struct mcast_timeout_list); |
| 108 | timeout->group = group; |
| 109 | timeout->rcpt = rcpt; |
| 110 | timeout->timeout = (struct timeval){now + MCAST_TIMEOUT_INTERVAL,0}; |
| 111 | return timeout; |
| 112 | } |
| 113 | |
| 114 | void mcast__update_time_for_recipient(struct multi_instance * mi, const uint32_t group, struct mroute_addr * rcpt) |
| 115 | { |
| 116 | struct mcast_timeout_list **current_list; |
| 117 | struct mcast_timeout_list * tmp_list; |
| 118 | struct mcast_timeout_list * new_list_item = mcast__create_timeout_list_item(group, rcpt); |
| 119 | |
| 120 | mutex_lock(mi->mutex); |
| 121 | |
| 122 | current_list = &mi->mcast_timeouts; // Start with a pointer to the pointer to the first item. |
| 123 | |
| 124 | while ((*current_list) && tv_ge(&new_list_item->timeout, &(*current_list)->timeout)) // Continue while there exist a next item, and the next item is scheduled for removal later than the current. */ |
| 125 | { |
| 126 | if (mroute_addr_equal((*current_list)->rcpt, rcpt) && ((*current_list)->group == group)) // If old item exist, remove it. |
| 127 | { |
| 128 | tmp_list = (*current_list)->next; |
| 129 | free(*current_list); |
| 130 | *current_list = tmp_list; |
| 131 | } |
| 132 | else |
| 133 | current_list = &(*current_list)->next; // Bring up the next item in list |
| 134 | } |
| 135 | // We should now be positioned at the right spot in the list, just insert the new item. |
| 136 | new_list_item->next = (*current_list); |
| 137 | *current_list = new_list_item; |
| 138 | |
| 139 | mutex_unlock(mi->mutex); |
| 140 | } |
| 141 | |
| 142 | void mcast__clean_times_for_recipient(struct multi_instance * mi, const uint32_t group, struct mroute_addr * rcpt) |
| 143 | { |
| 144 | struct mcast_timeout_list **current_list; |
| 145 | struct mcast_timeout_list * tmp_list; |
| 146 | |
| 147 | mutex_lock(mi->mutex); |
| 148 | |
| 149 | current_list = &mi->mcast_timeouts; // Start with a pointer to the pointer to the first item. |
| 150 | |
| 151 | while (*current_list) // Continue while there exist a next item |
| 152 | { |
| 153 | if (mroute_addr_equal((*current_list)->rcpt, rcpt) && (*current_list)->group == group) // If old item exist, remove it. |
| 154 | { |
| 155 | tmp_list = (*current_list)->next; |
| 156 | free(*current_list); |
| 157 | *current_list = tmp_list; |
| 158 | } |
| 159 | else |
| 160 | current_list = &(*current_list)->next; // Bring up the next item in list |
| 161 | } |
| 162 | mutex_unlock(mi->mutex); |
| 163 | } |
| 164 | |
| 165 | void mcast_add_rcpt(struct multi_context *m, const uint32_t group, const struct mroute_addr *rcpt, struct multi_instance * mi) |
| 166 | { |
| 167 | struct gc_arena gc = gc_new (); |
| 168 | struct mcast_recipient_list **list_item_ptr; |
| 169 | |
| 170 | mutex_lock(m->mutex); |
| 171 | |
| 172 | list_item_ptr = (struct mcast_recipient_list **)hash_lookup_ptr(m->mcast_group_map, &group); |
| 173 | |
| 174 | if (list_item_ptr) // Recipient list for this group already exist |
| 175 | { |
| 176 | while ((*list_item_ptr) && !mroute_addr_equal(rcpt, &(*list_item_ptr)->rcpt)) |
| 177 | list_item_ptr = &(*list_item_ptr)->next; |
| 178 | |
| 179 | if (*list_item_ptr) |
| 180 | { |
| 181 | /* TODO: Add timers and updates. */ |
| 182 | msg (D_MCAST_LOW, "MCAST: refreshed %s in group-list %s", mroute_addr_print(rcpt, &gc), print_in_addr_t (group, IA_EMPTY_IF_UNDEF, &gc)); |
| 183 | } |
| 184 | else |
| 185 | { |
| 186 | *list_item_ptr = mcast__create_recv_list_item(mi, rcpt); |
| 187 | msg (D_MCAST_LOW, "MCAST: added %s to group-list %s", mroute_addr_print(rcpt, &gc), print_in_addr_t (group, IA_EMPTY_IF_UNDEF, &gc)); |
| 188 | } |
| 189 | } |
| 190 | else // Create new mcast_recipient_list |
| 191 | { |
| 192 | struct mcast_recipient_list * new_list_item = mcast__create_recv_list_item(mi, rcpt);; |
| 193 | uint32_t *group_cpy; |
| 194 | ALLOC_OBJ(group_cpy, uint32_t) |
| 195 | *group_cpy = group; |
| 196 | list_item_ptr = &new_list_item; |
| 197 | hash_add(m->mcast_group_map, group_cpy, new_list_item, false); |
| 198 | msg (D_MCAST_LOW, "MCAST: created group-list %s for %s", print_in_addr_t (group, IA_EMPTY_IF_UNDEF, &gc), mroute_addr_print(rcpt, &gc)); |
| 199 | } |
| 200 | mcast__update_time_for_recipient(mi, group, &(*list_item_ptr)->rcpt); |
| 201 | mcast_print_group_list(m, &gc, group); |
| 202 | |
| 203 | mutex_unlock(m->mutex); |
| 204 | gc_free(&gc); |
| 205 | } |
| 206 | |
| 207 | void mcast_remove_rcpt(struct multi_context *m, const uint32_t group, const struct mroute_addr *rcpt, struct multi_instance * mi, bool clean_timeouts) |
| 208 | { |
| 209 | struct gc_arena gc = gc_new(); |
| 210 | struct mcast_recipient_list ** list_item_ptr; |
| 211 | struct mcast_recipient_list * next_item; |
| 212 | struct mroute_addr rcpt_copy = *rcpt; |
| 213 | |
| 214 | mutex_lock(m->mutex); |
| 215 | |
| 216 | list_item_ptr = (struct mcast_recipient_list **)hash_lookup_ptr(m->mcast_group_map, &group); |
| 217 | |
| 218 | while (list_item_ptr && (*list_item_ptr) && !mroute_addr_equal(rcpt, &(*list_item_ptr)->rcpt)) |
| 219 | list_item_ptr = &(*list_item_ptr)->next; |
| 220 | |
| 221 | if (list_item_ptr && *list_item_ptr) // We found something |
| 222 | { |
| 223 | if (clean_timeouts) |
| 224 | mcast__clean_times_for_recipient(mi, group, &(*list_item_ptr)->rcpt); |
| 225 | |
| 226 | next_item = (*list_item_ptr)->next; |
| 227 | free(*list_item_ptr); |
| 228 | *list_item_ptr = next_item; |
| 229 | } |
| 230 | |
| 231 | msg (D_MCAST_LOW, "MCAST: removed %s from group %s", mroute_addr_print(&rcpt_copy, &gc), print_in_addr_t (group, IA_EMPTY_IF_UNDEF, &gc)); |
| 232 | |
| 233 | mcast_print_group_list(m, &gc, group); |
| 234 | |
| 235 | mutex_unlock(m->mutex); |
| 236 | |
| 237 | gc_free(&gc); |
| 238 | } |
| 239 | |
| 240 | void mcast_clean_old_groups(struct multi_context *m, struct multi_instance *mi, bool drop_all) |
| 241 | { |
| 242 | struct mcast_timeout_list **current_list; |
| 243 | struct mcast_timeout_list * tmp_list; |
| 244 | struct timeval timeval_now = (struct timeval){now, 0}; |
| 245 | struct gc_arena gc = gc_new(); |
| 246 | |
| 247 | update_time(); |
| 248 | |
| 249 | mutex_lock(mi->mutex); |
| 250 | |
| 251 | current_list = &mi->mcast_timeouts; // Start with a pointer to the pointer to the first item. |
| 252 | |
| 253 | msg (D_MCAST_DEBUG, "Cleaning old groups for multi_instance"); |
| 254 | |
| 255 | while ((*current_list) && (tv_ge(&timeval_now, &(*current_list)->timeout) || drop_all)) // Continue while there exist a next item, and either the next item is scheduled for removal before now, or we're due to clean out all joined groups*/ |
| 256 | { |
| 257 | msg (D_MCAST_LOW, "MCAST: detected stale recipient %s in group %s", mroute_addr_print((*current_list)->rcpt, &gc), print_in_addr_t ((*current_list)->group, IA_EMPTY_IF_UNDEF, &gc)); |
| 258 | mcast_remove_rcpt(m, (*current_list)->group, (*current_list)->rcpt, mi, false); // If old item exist, remove it. |
| 259 | |
| 260 | tmp_list = (*current_list)->next; |
| 261 | free(*current_list); |
| 262 | *current_list = tmp_list; |
| 263 | } |
| 264 | mcast_print_timeout_list(mi, &gc); |
| 265 | mutex_unlock(mi->mutex); |
| 266 | gc_free(&gc); |
| 267 | } |
| 268 | |
| 269 | /** functions to parse possible IGMP-headers */ |
| 270 | void mcast_igmp_snoop(struct multi_context *m, struct multi_instance * mi, const struct openvpn_igmpv3hdr *igmp, const void * buf_end, const struct mroute_addr *src_addr) |
| 271 | { |
| 272 | uint16_t i; |
| 273 | struct openvpn_igmpv3_record_hdr * record; |
| 274 | |
| 275 | update_time(); |
| 276 | |
| 277 | if (mi) |
| 278 | mcast_clean_old_groups(m, mi, false); |
| 279 | |
| 280 | switch (igmp->type) |
| 281 | { |
| 282 | case OPENVPN_IGMP_QUERY: |
| 283 | msg(D_MCAST_DEBUG, "MCAST: saw IGMP query message"); |
| 284 | break; |
| 285 | case OPENVPN_IGMP_REPORT_V2: |
| 286 | mcast_add_rcpt(m, ntohl(igmp->data.igmpv2_group_addr), src_addr, mi); |
| 287 | break; |
| 288 | case OPENVPN_IGMP_LEAVE_V2: |
| 289 | mcast_remove_rcpt(m, ntohl(igmp->data.igmpv2_group_addr), src_addr, mi, true); |
| 290 | break; |
| 291 | case OPENVPN_IGMP_REPORT_V3: |
| 292 | record = (struct openvpn_igmpv3_record_hdr*)((void *)igmp + sizeof(struct openvpn_igmpv3hdr)); |
| 293 | for (i = 0; (i < ntohs(igmp->data.igmpv3_num_records)) && ((((void *)record) + sizeof(struct openvpn_igmpv3_record_hdr)) <= buf_end); i++) |
| 294 | { |
| 295 | switch (record->type) |
| 296 | { |
| 297 | case OPENVPN_IGMPV3_FILTER_CHANGE_TO_INCLUDE: |
| 298 | mcast_remove_rcpt(m, ntohl(record->group_address), src_addr, mi, true); |
| 299 | break; |
| 300 | case OPENVPN_IGMPV3_FILTER_CHANGE_TO_EXCLUDE: |
| 301 | mcast_add_rcpt(m, ntohl(record->group_address), src_addr, mi); |
| 302 | break; |
| 303 | } |
| 304 | record = ((void *)record) + sizeof(struct openvpn_igmpv3hdr) + 4*(record->aux_len + record->num_sources); |
| 305 | } |
| 306 | break; |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | /** functions to quickly filter out IGMP-packets **/ |
| 311 | const struct openvpn_iphdr * mcast_pkt_is_ip(const struct buffer *buf) |
| 312 | { |
| 313 | if (BLEN(buf) >= sizeof(struct openvpn_ethhdr) + sizeof(struct openvpn_iphdr)) |
| 314 | { |
| 315 | if (ntohs(((struct openvpn_ethhdr *)BPTR(buf))->proto) == OPENVPN_ETH_P_IPV4) |
| 316 | return (const struct openvpn_iphdr *)(BPTR(buf) + sizeof(struct openvpn_ethhdr)); |
| 317 | } |
| 318 | return NULL; |
| 319 | } |
| 320 | |
| 321 | const struct openvpn_igmpv3hdr* mcast_pkt_is_igmp(const struct buffer *buf) |
| 322 | { |
| 323 | const struct openvpn_iphdr *ip; |
| 324 | if (ip = mcast_pkt_is_ip(buf)) |
| 325 | { |
| 326 | if ((ip->protocol == OPENVPN_IPPROTO_IGMP) && (BLEN(buf) >= (sizeof(struct openvpn_ethhdr) + ntohs(ip->tot_len)))) |
| 327 | return (const struct openvpn_igmpv3hdr *)openvpn_ip_payload(ip); |
| 328 | } |
| 329 | return NULL; |
| 330 | } |
| 331 | |
| 332 | /** functions hash multi_instances */ |
| 333 | uint32_t multi_instance_hash(const void *key, uint32_t iv) |
| 334 | { |
| 335 | return (uint32_t)key % iv; |
| 336 | } |
| 337 | |
| 338 | bool multi_instance_compare(const void *key1, const void *key2) |
| 339 | { |
| 340 | return key1 == key2; |
| 341 | } |
| 342 | |
| 343 | /** functions to actually use the learnt mcast-forward-lists */ |
| 344 | void mcast_send(struct multi_context *m, |
| 345 | const struct buffer *buf, |
| 346 | struct multi_instance *omit) |
| 347 | { |
| 348 | struct multi_instance *mi; |
| 349 | struct mbuf_buffer *mb; |
| 350 | struct hash *output_instances; |
| 351 | const struct openvpn_iphdr *ip; |
| 352 | uint32_t group_address; |
| 353 | struct mcast_recipient_list * rcpt_list; |
| 354 | struct gc_arena gc = gc_new (); |
| 355 | |
| 356 | if (ip = mcast_pkt_is_ip(buf)) |
| 357 | { |
| 358 | group_address = ntohl(ip->daddr); |
| 359 | |
| 360 | msg (D_MULTI_DEBUG, "MCAST: sending packet to group %s", print_in_addr_t (group_address, IA_EMPTY_IF_UNDEF, &gc)); |
| 361 | |
| 362 | mutex_lock(m->mutex); |
| 363 | |
| 364 | rcpt_list = (struct mcast_recipient_list *)hash_lookup(m->mcast_group_map, &group_address); |
| 365 | if (rcpt_list) |
| 366 | { |
| 367 | perf_push (PERF_MULTI_MCAST); |
| 368 | #ifdef MULTI_DEBUG_EVENT_LOOP |
| 369 | printf ("MCAST len=%d\n", BLEN (buf)); |
| 370 | #endif |
| 371 | mb = mbuf_alloc_buf (buf); |
| 372 | output_instances = hash_init(16, multi_instance_hash, multi_instance_compare); |
| 373 | |
| 374 | |
| 375 | while (rcpt_list) |
| 376 | { |
| 377 | mi = rcpt_list->mi; |
| 378 | if (mi != omit && !mi->halt && hash_add(output_instances, mi, mi, false)) // Should send here |
| 379 | multi_add_mbuf (m, mi, mb); |
| 380 | rcpt_list = rcpt_list->next; |
| 381 | } |
| 382 | |
| 383 | hash_free(output_instances); |
| 384 | mbuf_free_buf (mb); |
| 385 | perf_pop (); |
| 386 | |
| 387 | mutex_unlock(m->mutex); |
| 388 | } |
| 389 | } |
| 390 | |
| 391 | gc_free(&gc); |
| 392 | } |
| 393 | |
| 394 | void mcast_disconnect(struct multi_context *m, struct multi_instance *mi) |
| 395 | { |
| 396 | struct gc_arena gc = gc_new (); |
| 397 | |
| 398 | // Free dangling multicast-groups |
| 399 | mcast_clean_old_groups(m, mi, true); |
| 400 | |
| 401 | msg (D_MCAST_DEBUG, "MCAST: Disconnect: %s has left the building.", multi_instance_string(mi, false, &gc)); |
| 402 | |
| 403 | gc_free(&gc); |
| 404 | } |