├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md └── src ├── client.c ├── client.h ├── groups.c ├── groups.h ├── igmp.c ├── mld.c ├── mrib.c ├── mrib.h ├── omcproxy.c ├── omcproxy.h ├── proxy.c ├── proxy.h ├── querier.c └── querier.h /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .cproject 3 | CMakeCache.txt 4 | CMakeFiles 5 | CMakeScripts 6 | cmake_install.cmake 7 | Makefile 8 | omcproxy 9 | CTestTestfile.cmake 10 | Testing 11 | coverage.info 12 | test_ifgroup 13 | test_rib 14 | coverage 15 | .settings 16 | install_manifest.txt 17 | *~ 18 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libubox"] 2 | path = libubox 3 | url = http://git.openwrt.org/project/libubox.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.8) 2 | 3 | project(omcproxy C) 4 | 5 | set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") 6 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -std=c99") 7 | 8 | add_definitions(-D_GNU_SOURCE -Wall -Wno-gnu) 9 | 10 | if(${L_LEVEL}) 11 | add_definitions(-DL_LEVEL=${L_LEVEL}) 12 | endif(${L_LEVEL}) 13 | 14 | FIND_PATH(ubox_include_dir libubox/list.h) 15 | INCLUDE_DIRECTORIES(${ubox_include_dir}) 16 | 17 | if(WITH_LIBUBOX) 18 | add_definitions(-Wextra) 19 | set(PLATFORM_LINK ${PLATFORM_LINK} ubox) 20 | else (WITH_LIBUBOX) 21 | add_definitions(-Dtypeof=__typeof) 22 | include_directories(BEFORE .) 23 | set(PLATFORM_SOURCE ${PLATFORM_SOURCE} libubox/uloop.c libubox/avl.c libubox/blobmsg.c libubox/blob.c) 24 | endif(WITH_LIBUBOX) 25 | 26 | add_executable(omcproxy src/client.c src/mrib.c src/querier.c src/groups.c src/igmp.c src/mld.c src/proxy.c src/omcproxy.c ${PLATFORM_SOURCE}) 27 | target_link_libraries(omcproxy ${PLATFORM_LINK}) 28 | 29 | install(TARGETS omcproxy DESTINATION sbin/) 30 | 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # omcproxy - Embedded IGMPv3 and MLDv2 proxy 2 | 3 | omcproxy is an IGMPv3 and MLDv2 multicast proxy for use in embedded Linux 4 | devices like routers. It is small in size and can be compiled to <40 KB. 5 | 6 | It is partly based on code of https://github.com/Oryon/pimbd 7 | 8 | ## Specifications and Features 9 | 10 | 1. Source-Specific Multicast Querier 11 | - MLDv2 querier (based on RFC 3810) 12 | - IGMPv3 querier (based on RFC 3376) 13 | 14 | 2. Multicast Proxying (based on RFC 4605) 15 | - Kernel-space multicast routing 16 | - Multiple instances support 17 | - Address-scope specific proxying 18 | 19 | 20 | ## Compiling 21 | 22 | omcproxy uses libubox as submodule, be sure to clone this git repository 23 | with --recursive or run: "git submodule init; git submodule update" 24 | after cloning. If you are already using libubox as a shared library 25 | just pass -DWITH_LIBUBOX=1 to cmake. 26 | 27 | omcproxy uses cmake: 28 | - To prepare a Makefile use: "cmake ." 29 | - To build / install use: "make" / "make install" afterwards. 30 | - To build DEB or RPM packages use: "make package" afterwards. 31 | -------------------------------------------------------------------------------- /src/client.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Steven Barth 3 | * 4 | * Copyright 2015 Deutsche Telekom AG 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "client.h" 32 | 33 | 34 | // Add / update / remove a client entry for a multicast group 35 | int client_set(struct client *client, const struct in6_addr *group, 36 | bool include, const struct in6_addr sources[], size_t cnt) 37 | { 38 | int family = (IN6_IS_ADDR_V4MAPPED(group)) ? AF_INET : AF_INET6; 39 | int sol = (family == AF_INET) ? SOL_IP : SOL_IPV6; 40 | char addrbuf[INET6_ADDRSTRLEN]; 41 | size_t len = sizeof(struct group_filter) + cnt * sizeof(struct sockaddr_storage); 42 | struct { 43 | struct group_filter f; 44 | struct sockaddr_storage s[]; 45 | } *filter = alloca(len); 46 | struct sockaddr_in *in_addr = (struct sockaddr_in*)&filter->f.gf_group; 47 | struct sockaddr_in6 *in6_addr = (struct sockaddr_in6*)&filter->f.gf_group; 48 | 49 | inet_ntop(AF_INET6, group, addrbuf, sizeof(addrbuf)); 50 | L_DEBUG("%s: %s on %d => %s (+%d sources)", __FUNCTION__, addrbuf, 51 | client->ifindex, (include) ? "include" : "exclude", (int)cnt); 52 | 53 | // Construct MSFILTER for outgoing IGMP / MLD 54 | memset(filter, 0, len); 55 | filter->f.gf_interface = client->ifindex; 56 | filter->f.gf_fmode = include ? MCAST_INCLUDE : MCAST_EXCLUDE; 57 | filter->f.gf_group.ss_family = family; 58 | filter->f.gf_numsrc = cnt; 59 | 60 | if (family == AF_INET) 61 | client_unmap(&in_addr->sin_addr, group); 62 | else 63 | in6_addr->sin6_addr = *group; 64 | 65 | for (size_t i = 0; i < cnt; ++i) { 66 | filter->f.gf_slist[i].ss_family = family; 67 | 68 | in_addr = (struct sockaddr_in*)&filter->f.gf_slist[i]; 69 | in6_addr = (struct sockaddr_in6*)&filter->f.gf_slist[i]; 70 | 71 | if (family == AF_INET) 72 | client_unmap(&in_addr->sin_addr, &sources[i]); 73 | else 74 | in6_addr->sin6_addr = sources[i]; 75 | } 76 | 77 | int fd = (family == AF_INET) ? client->igmp_fd : client->mld_fd; 78 | setsockopt(fd, sol, MCAST_LEAVE_GROUP, filter, sizeof(struct group_req)); 79 | if (!include || cnt > 0) { 80 | if (setsockopt(fd, sol, MCAST_JOIN_GROUP, filter, sizeof(struct group_req)) 81 | && family == AF_INET && errno == ENOBUFS) { 82 | L_WARN("proxy: kernel denied joining multicast group. check igmp_max_memberships?"); 83 | return -errno; 84 | } 85 | 86 | if (setsockopt(fd, sol, MCAST_MSFILTER, filter, len)) 87 | return -errno; 88 | } 89 | return 0; 90 | } 91 | 92 | // Initialize client-instance 93 | int client_init(struct client *client, int ifindex) 94 | { 95 | client->igmp_fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); 96 | if (client->igmp_fd < 0) 97 | return -errno; 98 | 99 | client->mld_fd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0); 100 | if (client->mld_fd < 0) 101 | return -errno; 102 | 103 | client->ifindex = ifindex; 104 | return 0; 105 | } 106 | 107 | // Cleanup client-instance 108 | void client_deinit(struct client *client) 109 | { 110 | if (client->ifindex) { 111 | close(client->igmp_fd); 112 | close(client->mld_fd); 113 | client->igmp_fd = -1; 114 | client->mld_fd = -1; 115 | client->ifindex = 0; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/client.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Steven Barth 3 | * 4 | * Copyright 2015 Deutsche Telekom AG 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | #include 22 | #include 23 | #include 24 | #include "omcproxy.h" 25 | 26 | #define PROXY_MAX_SOURCES 1000 27 | 28 | 29 | struct client { 30 | int igmp_fd; 31 | int mld_fd; 32 | int ifindex; 33 | }; 34 | 35 | // Register a new interface to proxy 36 | int client_init(struct client *client, int ifindex); 37 | 38 | // Deregister a new interface from proxy 39 | void client_deinit(struct client *client); 40 | 41 | // Set / update / delete a multicast proxy entry 42 | int client_set(struct client *client, const struct in6_addr *group, bool include, 43 | const struct in6_addr sources[], size_t cnt); 44 | 45 | // Unmap IPv4 address 46 | static inline void client_unmap(struct in_addr *addr4, const struct in6_addr *addr6) 47 | { 48 | addr4->s_addr = addr6->s6_addr32[3]; 49 | } 50 | -------------------------------------------------------------------------------- /src/groups.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Steven Barth 3 | * 4 | * Copyright 2015 Deutsche Telekom AG 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include "groups.h" 24 | 25 | 26 | // Group comparator for AVL-tree 27 | static int compare_groups(const void *k1, const void *k2, __unused void *ptr) 28 | { 29 | return memcmp(k1, k2, sizeof(struct in6_addr)); 30 | } 31 | 32 | // Remove a source-definition for a group 33 | static void querier_remove_source(struct group *group, struct group_source *source) 34 | { 35 | --group->source_count; 36 | list_del(&source->head); 37 | free(source); 38 | } 39 | 40 | // Clear all sources of a certain group 41 | static void querier_clear_sources(struct group *group) 42 | { 43 | struct group_source *s, *n; 44 | list_for_each_entry_safe(s, n, &group->sources, head) 45 | querier_remove_source(group, s); 46 | } 47 | 48 | // Remove a group and all associated sources from the group state 49 | static void querier_remove_group(struct groups *groups, struct group *group, omgp_time_t now) 50 | { 51 | querier_clear_sources(group); 52 | group->exclude_until = 0; 53 | 54 | if (groups->cb_update) 55 | groups->cb_update(groups, group, now); 56 | 57 | avl_delete(&groups->groups, &group->node); 58 | free(group); 59 | } 60 | 61 | // Expire a group and / or its associated sources depending on the current time 62 | static omgp_time_t expire_group(struct groups *groups, struct group *group, 63 | omgp_time_t now, omgp_time_t next_event) 64 | { 65 | struct groups_config *cfg = IN6_IS_ADDR_V4MAPPED(&group->addr) ? &groups->cfg_v4 : &groups->cfg_v6; 66 | omgp_time_t llqi = now + cfg->last_listener_query_interval; 67 | omgp_time_t llqt = now + (cfg->last_listener_query_interval * cfg->last_listener_query_count); 68 | 69 | // Handle group and source-specific query retransmission 70 | struct list_head suppressed = LIST_HEAD_INIT(suppressed); 71 | struct list_head unsuppressed = LIST_HEAD_INIT(unsuppressed); 72 | struct group_source *s, *s2; 73 | 74 | if (group->next_source_transmit > 0 && group->next_source_transmit <= now) { 75 | group->next_source_transmit = 0; 76 | 77 | list_for_each_entry_safe(s, s2, &group->sources, head) { 78 | if (s->retransmit > 0) { 79 | list_move_tail(&s->head, (s->include_until > llqt) ? &suppressed : &unsuppressed); 80 | --s->retransmit; 81 | } 82 | 83 | if (s->retransmit > 0) 84 | group->next_source_transmit = llqi; 85 | } 86 | } 87 | 88 | if (group->next_source_transmit > 0 && group->next_source_transmit < next_event) 89 | next_event = group->next_source_transmit; 90 | 91 | // Handle group-specific query retransmission 92 | if (group->retransmit > 0 && group->next_generic_transmit <= now) { 93 | group->next_generic_transmit = 0; 94 | 95 | if (groups->cb_query) 96 | groups->cb_query(groups, &group->addr, NULL, group->exclude_until > llqt); 97 | 98 | --group->retransmit; 99 | 100 | if (group->retransmit > 0) 101 | group->next_generic_transmit = llqi; 102 | 103 | // Skip suppresed source-specific query (RFC 3810 7.6.3.2) 104 | list_splice_init(&suppressed, &group->sources); 105 | } 106 | 107 | if (group->next_generic_transmit > 0 && group->next_generic_transmit < next_event) 108 | next_event = group->next_generic_transmit; 109 | 110 | if (!list_empty(&suppressed)) { 111 | if (groups->cb_query) 112 | groups->cb_query(groups, &group->addr, &suppressed, true); 113 | 114 | list_splice(&suppressed, &group->sources); 115 | } 116 | 117 | if (!list_empty(&unsuppressed)) { 118 | if (groups->cb_query) 119 | groups->cb_query(groups, &group->addr, &unsuppressed, false); 120 | 121 | list_splice(&unsuppressed, &group->sources); 122 | } 123 | 124 | // Handle source and group expiry 125 | bool changed = false; 126 | if (group->exclude_until > 0) { 127 | if (group_is_included(group, now)) { 128 | // Leaving exclude mode 129 | group->exclude_until = 0; 130 | changed = true; 131 | } else if (group->exclude_until < next_event) { 132 | next_event = group->exclude_until; 133 | } 134 | } 135 | 136 | list_for_each_entry_safe(s, s2, &group->sources, head) { 137 | if (s->include_until > 0) { 138 | if (!source_is_included(s, now)) { 139 | s->include_until = 0; 140 | changed = true; 141 | } else if (s->include_until < next_event) { 142 | next_event = s->include_until; 143 | } 144 | } 145 | 146 | if (group->exclude_until == 0 && s->include_until == 0) 147 | querier_remove_source(group, s); 148 | } 149 | 150 | if (group->exclude_until == 0 && group->source_count == 0) 151 | querier_remove_group(groups, group, now); 152 | else if (changed && groups->cb_update) 153 | groups->cb_update(groups, group, now); 154 | 155 | return next_event; 156 | } 157 | 158 | // Rearm the global groups-timer if the next event is before timer expiration 159 | static void rearm_timer(struct groups *groups, int msecs) 160 | { 161 | int64_t remain = uloop_timeout_remaining64(&groups->timer); 162 | if (remain < 0 || remain >= msecs) 163 | uloop_timeout_set(&groups->timer, msecs); 164 | } 165 | 166 | // Expire all groups of a group-state (called by timer as callback) 167 | static void expire_groups(struct uloop_timeout *t) 168 | { 169 | struct groups *groups = container_of(t, struct groups, timer); 170 | omgp_time_t now = omgp_time(); 171 | omgp_time_t next_event = now + 3600 * OMGP_TIME_PER_SECOND; 172 | 173 | struct group *group, *n; 174 | avl_for_each_element_safe(&groups->groups, group, node, n) 175 | next_event = expire_group(groups, group, now, next_event); 176 | 177 | rearm_timer(groups, (next_event > now) ? next_event - now : 0); 178 | } 179 | 180 | // Initialize a group-state 181 | void groups_init(struct groups *groups) 182 | { 183 | avl_init(&groups->groups, compare_groups, false, NULL); 184 | groups->timer.cb = expire_groups; 185 | 186 | groups_update_config(groups, false, OMGP_TIME_PER_SECOND * 10, 187 | 125 * OMGP_TIME_PER_SECOND, 2); 188 | groups_update_config(groups, true, OMGP_TIME_PER_SECOND * 10, 189 | 125 * OMGP_TIME_PER_SECOND, 2); 190 | } 191 | 192 | // Cleanup a group-state 193 | void groups_deinit(struct groups *groups) 194 | { 195 | omgp_time_t now = omgp_time(); 196 | struct group *group, *safe; 197 | avl_for_each_element_safe(&groups->groups, group, node, safe) 198 | querier_remove_group(groups, group, now); 199 | uloop_timeout_cancel(&groups->timer); 200 | } 201 | 202 | // Get group-object for a given group, create if requested 203 | static struct group* groups_get_group(struct groups *groups, 204 | const struct in6_addr *addr, bool *created) 205 | { 206 | struct group *group = avl_find_element(&groups->groups, addr, group, node); 207 | if (!group && created && (group = calloc(1, sizeof(*group)))) { 208 | group->addr = *addr; 209 | group->node.key = &group->addr; 210 | avl_insert(&groups->groups, &group->node); 211 | 212 | INIT_LIST_HEAD(&group->sources); 213 | *created = true; 214 | } else if (created) { 215 | *created = false; 216 | } 217 | return group; 218 | } 219 | 220 | // Get source-object for a given source, create if requested 221 | static struct group_source* groups_get_source(struct groups *groups, 222 | struct group *group, const struct in6_addr *addr, bool *created) 223 | { 224 | struct group_source *c, *source = NULL; 225 | group_for_each_source(c, group) 226 | if (IN6_ARE_ADDR_EQUAL(&c->addr, addr)) 227 | source = c; 228 | 229 | if (!source && created && group->source_count < groups->source_limit && 230 | (source = calloc(1, sizeof(*source)))) { 231 | source->addr = *addr; 232 | list_add_tail(&source->head, &group->sources); 233 | ++group->source_count; 234 | *created = true; 235 | } else if (created) { 236 | *created = false; 237 | } 238 | 239 | return source; 240 | } 241 | 242 | // Update the IGMP/MLD timers of a group-state 243 | void groups_update_config(struct groups *groups, bool v6, 244 | omgp_time_t query_response_interval, omgp_time_t query_interval, int robustness) 245 | { 246 | struct groups_config *cfg = v6 ? &groups->cfg_v6 : &groups->cfg_v4; 247 | cfg->query_response_interval = query_response_interval; 248 | cfg->query_interval = query_interval; 249 | cfg->robustness = robustness; 250 | cfg->last_listener_query_count = cfg->robustness; 251 | cfg->last_listener_query_interval = 1 * OMGP_TIME_PER_SECOND; 252 | } 253 | 254 | // Update timers for a given group (called when receiving queries from other queriers) 255 | void groups_update_timers(struct groups *groups, 256 | const struct in6_addr *groupaddr, 257 | const struct in6_addr *addrs, size_t len) 258 | { 259 | char addrbuf[INET6_ADDRSTRLEN]; 260 | inet_ntop(AF_INET6, groupaddr, addrbuf, sizeof(addrbuf)); 261 | struct group *group = groups_get_group(groups, groupaddr, NULL); 262 | if (!group) { 263 | L_WARN("%s: failed to update timer: no such group %s", __FUNCTION__, addrbuf); 264 | return; 265 | } 266 | 267 | struct groups_config *cfg = IN6_IS_ADDR_V4MAPPED(&group->addr) ? &groups->cfg_v4 : &groups->cfg_v6; 268 | omgp_time_t now = omgp_time(); 269 | omgp_time_t llqt = now + (cfg->last_listener_query_count * cfg->last_listener_query_interval); 270 | 271 | if (len == 0) { 272 | if (group->exclude_until > llqt) 273 | group->exclude_until = llqt; 274 | } else { 275 | for (size_t i = 0; i < len; ++i) { 276 | struct group_source *source = groups_get_source(groups, group, &addrs[i], NULL); 277 | if (!source) { 278 | L_WARN("%s: failed to update timer: unknown sources for group %s", __FUNCTION__, addrbuf); 279 | continue; 280 | } 281 | 282 | if (source->include_until > llqt) 283 | source->include_until = llqt; 284 | } 285 | } 286 | 287 | rearm_timer(groups, llqt - now); 288 | } 289 | 290 | // Update state of a given group (on reception of node's IGMP/MLD packets) 291 | void groups_update_state(struct groups *groups, 292 | const struct in6_addr *groupaddr, 293 | const struct in6_addr *addrs, size_t len, 294 | enum groups_update update) 295 | { 296 | bool created = false, changed = false; 297 | char addrbuf[INET6_ADDRSTRLEN]; 298 | inet_ntop(AF_INET6, groupaddr, addrbuf, sizeof(addrbuf)); 299 | L_DEBUG("%s: %s (+%d sources) => %d", __FUNCTION__, addrbuf, (int)len, update); 300 | 301 | struct group *group = groups_get_group(groups, groupaddr, &created); 302 | if (!group) { 303 | L_ERR("querier_state: failed to allocate group for %s", addrbuf); 304 | return; 305 | } 306 | 307 | if (created) 308 | changed = true; 309 | 310 | omgp_time_t now = omgp_time(); 311 | omgp_time_t next_event = OMGP_TIME_MAX; 312 | struct groups_config *cfg = IN6_IS_ADDR_V4MAPPED(&group->addr) ? &groups->cfg_v4 : &groups->cfg_v6; 313 | 314 | // Backwards compatibility modes 315 | if (group->compat_v2_until > now || group->compat_v1_until > now) { 316 | if (update == UPDATE_BLOCK) 317 | return; 318 | 319 | if (group->compat_v1_until > now && (update == UPDATE_DONE || update == UPDATE_TO_IN)) 320 | return; 321 | 322 | if (update == UPDATE_TO_EX) 323 | len = 0; 324 | } 325 | 326 | if (update == UPDATE_REPORT || update == UPDATE_REPORT_V1 || update == UPDATE_DONE) { 327 | omgp_time_t compat_until = now + cfg->query_response_interval + 328 | (cfg->robustness * cfg->query_interval); 329 | 330 | if (update == UPDATE_REPORT_V1) 331 | group->compat_v1_until = compat_until; 332 | else if (update == UPDATE_REPORT) 333 | group->compat_v2_until = compat_until; 334 | 335 | update = (update == UPDATE_DONE) ? UPDATE_TO_IN : UPDATE_IS_EXCLUDE; 336 | len = 0; 337 | } 338 | 339 | bool include = group->exclude_until <= now; 340 | bool is_include = update == UPDATE_IS_INCLUDE || update == UPDATE_TO_IN || update == UPDATE_ALLOW; 341 | 342 | int llqc = cfg->last_listener_query_count; 343 | omgp_time_t mali = now + (cfg->robustness * cfg->query_interval) + cfg->query_response_interval; 344 | omgp_time_t llqt = now + (cfg->last_listener_query_interval * llqc); 345 | 346 | // RFC 3810 7.4 347 | struct list_head saved = LIST_HEAD_INIT(saved); 348 | struct list_head queried = LIST_HEAD_INIT(queried); 349 | for (size_t i = 0; i < len; ++i) { 350 | bool *create = (include && update == UPDATE_BLOCK) ? NULL : &created; 351 | struct group_source *source = groups_get_source(groups, group, &addrs[i], create); 352 | 353 | if (include && update == UPDATE_BLOCK) { 354 | if (source) 355 | list_move_tail(&source->head, &queried); 356 | } else { 357 | bool query = false; 358 | if (!source) { 359 | groups_update_state(groups, groupaddr, NULL, 0, false); 360 | L_WARN("querier: failed to allocate source for %s, fallback to ASM", addrbuf); 361 | return; 362 | } 363 | 364 | if (created) 365 | changed = true; 366 | else if (include && update == UPDATE_TO_EX) 367 | query = true; 368 | 369 | if (source->include_until <= now && update == UPDATE_SET_IN) { 370 | source->include_until = mali; 371 | changed = true; 372 | } else if (source->include_until > now && update == UPDATE_SET_EX) { 373 | source->include_until = now; 374 | changed = true; 375 | } 376 | 377 | if (!include && (update == UPDATE_BLOCK || update == UPDATE_TO_EX) && 378 | (created || source->include_until > now)) 379 | query = true; 380 | 381 | if ((is_include || (!include && created))) { 382 | if (source->include_until <= now) 383 | changed = true; 384 | 385 | source->include_until = (is_include || update == UPDATE_IS_EXCLUDE) 386 | ? mali : group->exclude_until; 387 | 388 | if (next_event > mali) 389 | next_event = mali; 390 | } 391 | 392 | if (query) 393 | list_move_tail(&source->head, &queried); 394 | else if (update == UPDATE_IS_EXCLUDE || update == UPDATE_TO_EX || 395 | update == UPDATE_SET_EX || update == UPDATE_SET_IN) 396 | list_move_tail(&source->head, &saved); 397 | } 398 | } 399 | 400 | if (update == UPDATE_IS_EXCLUDE || update == UPDATE_TO_EX || update == UPDATE_SET_EX) { 401 | if (include || !list_empty(&group->sources)) 402 | changed = true; 403 | 404 | querier_clear_sources(group); 405 | list_splice(&saved, &group->sources); 406 | group->exclude_until = mali; 407 | 408 | if (next_event > mali) 409 | next_event = mali; 410 | } 411 | 412 | if (update == UPDATE_SET_IN) { 413 | if (!include || !list_empty(&group->sources)) { 414 | changed = true; 415 | next_event = now; 416 | } 417 | 418 | querier_clear_sources(group); 419 | list_splice(&saved, &group->sources); 420 | group->exclude_until = now; 421 | } 422 | 423 | // Prepare queries 424 | if (update == UPDATE_TO_IN) { 425 | struct group_source *source, *n; 426 | list_for_each_entry_safe(source, n, &group->sources, head) { 427 | if (source->include_until <= now) 428 | continue; 429 | 430 | size_t i; 431 | for (i = 0; i < len && !IN6_ARE_ADDR_EQUAL(&source->addr, &addrs[i]); ++i); 432 | if (i == len) 433 | list_move_tail(&source->head, &queried); 434 | } 435 | } 436 | 437 | if (!list_empty(&queried)) { 438 | struct group_source *source; 439 | list_for_each_entry(source, &queried, head) { 440 | if (source->include_until > llqt) 441 | source->include_until = llqt; 442 | 443 | group->next_source_transmit = now; 444 | source->retransmit = llqc; 445 | } 446 | 447 | next_event = now; 448 | list_splice(&queried, &group->sources); 449 | } 450 | 451 | if (!include && update == UPDATE_TO_IN) { 452 | if (group->exclude_until > llqt) 453 | group->exclude_until = llqt; 454 | 455 | group->next_generic_transmit = now; 456 | group->retransmit = llqc; 457 | next_event = now; 458 | } 459 | 460 | if (changed && groups->cb_update) 461 | groups->cb_update(groups, group, now); 462 | 463 | if (group_is_included(group, now) && group->source_count == 0) 464 | next_event = now; 465 | 466 | if (next_event < OMGP_TIME_MAX) 467 | rearm_timer(groups, next_event - now); 468 | 469 | if (changed) 470 | L_DEBUG("%s: %s => %s (+%d sources)", __FUNCTION__, addrbuf, 471 | (group_is_included(group, now)) ? "included" : "excluded", 472 | (int)group->source_count); 473 | 474 | } 475 | 476 | // Get group object of a given group 477 | const struct group* groups_get(struct groups *groups, const struct in6_addr *addr) 478 | { 479 | return groups_get_group(groups, addr, NULL); 480 | } 481 | 482 | // Test if a group (and source) is requested in the current group state 483 | // (i.e. for deciding if it should be routed / forwarded) 484 | bool groups_includes_group(struct groups *groups, const struct in6_addr *addr, 485 | const struct in6_addr *src, omgp_time_t time) 486 | { 487 | struct group *group = groups_get_group(groups, addr, NULL); 488 | if (group) { 489 | if (!src && (!group_is_included(group, time) || group->source_count > 0)) 490 | return true; 491 | 492 | struct group_source *source = groups_get_source(groups, group, src, NULL); 493 | if ((!group_is_included(group, time) && (!source || source_is_included(source, time))) || 494 | (group_is_included(group, time) && source && source_is_included(source, time))) 495 | return true; 496 | } 497 | return false; 498 | } 499 | -------------------------------------------------------------------------------- /src/groups.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Steven Barth 3 | * 4 | * Copyright 2015 Deutsche Telekom AG 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "omcproxy.h" 27 | 28 | struct group { 29 | struct avl_node node; 30 | struct in6_addr addr; 31 | struct list_head sources; 32 | size_t source_count; 33 | omgp_time_t exclude_until; 34 | omgp_time_t compat_v2_until; 35 | omgp_time_t compat_v1_until; 36 | omgp_time_t next_generic_transmit; 37 | omgp_time_t next_source_transmit; 38 | int retransmit; 39 | }; 40 | 41 | struct group_source { 42 | struct list_head head; 43 | struct in6_addr addr; 44 | omgp_time_t include_until; 45 | int retransmit; 46 | }; 47 | 48 | struct groups_config { 49 | omgp_time_t query_response_interval; 50 | omgp_time_t query_interval; 51 | omgp_time_t last_listener_query_interval; 52 | int robustness; 53 | int last_listener_query_count; 54 | }; 55 | 56 | struct groups { 57 | struct groups_config cfg_v4; 58 | struct groups_config cfg_v6; 59 | struct avl_tree groups; 60 | struct uloop_timeout timer; 61 | size_t source_limit; 62 | size_t group_limit; 63 | void (*cb_query)(struct groups *g, const struct in6_addr *addr, 64 | const struct list_head *sources, bool suppress); 65 | void (*cb_update)(struct groups *g, struct group *group, omgp_time_t now); 66 | }; 67 | 68 | 69 | void groups_init(struct groups *groups); 70 | void groups_deinit(struct groups *groups); 71 | 72 | 73 | enum groups_update { 74 | UPDATE_UNSPEC, 75 | UPDATE_IS_INCLUDE = 1, 76 | UPDATE_IS_EXCLUDE = 2, 77 | UPDATE_TO_IN = 3, 78 | UPDATE_TO_EX = 4, 79 | UPDATE_ALLOW = 5, 80 | UPDATE_BLOCK = 6, 81 | UPDATE_REPORT = 7, 82 | UPDATE_REPORT_V1 = 8, 83 | UPDATE_DONE = 9, 84 | UPDATE_SET_IN = 0x11, 85 | UPDATE_SET_EX = 0x12, 86 | }; 87 | 88 | void groups_update_config(struct groups *groups, bool v6, 89 | omgp_time_t query_response_interval, omgp_time_t query_interval, int robustness); 90 | 91 | void groups_update_timers(struct groups *groups, 92 | const struct in6_addr *groupaddr, 93 | const struct in6_addr *addrs, size_t len); 94 | 95 | void groups_update_state(struct groups *groups, 96 | const struct in6_addr *groupaddr, 97 | const struct in6_addr *addrs, size_t len, 98 | enum groups_update update); 99 | 100 | void groups_synthesize_events(struct groups *groups); 101 | 102 | // Groups user query API 103 | 104 | static inline bool group_is_included(const struct group *group, omgp_time_t time) 105 | { 106 | return group->exclude_until <= time; 107 | } 108 | 109 | static inline bool source_is_included(const struct group_source *source, omgp_time_t time) 110 | { 111 | return source->include_until > time; 112 | } 113 | 114 | #define groups_for_each_group(group, groupsp) \ 115 | avl_for_each_element(&(groupsp)->groups, group, node) 116 | 117 | #define group_for_each_source(source, group) \ 118 | list_for_each_entry(source, &(group)->sources, head) 119 | 120 | #define group_for_each_active_source(source, group, time) \ 121 | list_for_each_entry(source, &group->sources, head) \ 122 | if (source_is_included(source, time) == group_is_included(group, time)) 123 | 124 | const struct group* groups_get(struct groups *groups, const struct in6_addr *addr); 125 | bool groups_includes_group(struct groups *groups, const struct in6_addr *addr, 126 | const struct in6_addr *src, omgp_time_t time); 127 | -------------------------------------------------------------------------------- /src/igmp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Steven Barth 3 | * 4 | * Copyright 2015 Deutsche Telekom AG 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "querier.h" 30 | 31 | // Test if multicast-group is valid and relevant 32 | static inline bool igmp_is_valid_group(in_addr_t group) 33 | { 34 | return IN_MULTICAST(be32_to_cpu(group)); 35 | } 36 | 37 | // Handle an IGMP-record from an IGMP-packet (called by igmp_receive) 38 | static ssize_t igmp_handle_record(struct groups *groups, const uint8_t *data, size_t len) 39 | { 40 | struct igmpv3_grec *r = (struct igmpv3_grec*)data; 41 | 42 | if (len < sizeof(*r)) 43 | return -1; 44 | 45 | size_t nsrc = ntohs(r->grec_nsrcs); 46 | size_t read = sizeof(*r) + nsrc * sizeof(struct in_addr) + r->grec_auxwords * 4; 47 | 48 | if (len < read) 49 | return -1; 50 | 51 | if (r->grec_type >= UPDATE_IS_INCLUDE && r->grec_type <= UPDATE_BLOCK && 52 | igmp_is_valid_group(r->grec_mca)) { 53 | struct in6_addr addr, sources[nsrc]; 54 | querier_map(&addr, r->grec_mca); 55 | 56 | for (size_t i = 0; i < nsrc; ++i) 57 | querier_map(&sources[i], r->grec_src[i]); 58 | 59 | groups_update_state(groups, &addr, sources, nsrc, r->grec_type); 60 | } 61 | 62 | return read; 63 | } 64 | 65 | // Receive and parse an IGMP-packet (called by uloop as callback) 66 | void igmp_handle(struct mrib_querier *mrib, const struct igmphdr *igmp, size_t len, 67 | const struct sockaddr_in *from) 68 | { 69 | struct querier_iface *q = container_of(mrib, struct querier_iface, mrib); 70 | omgp_time_t now = omgp_time(); 71 | char addrbuf[INET_ADDRSTRLEN]; 72 | struct in6_addr group; 73 | 74 | querier_map(&group, igmp->group); 75 | inet_ntop(AF_INET, &from->sin_addr, addrbuf, sizeof(addrbuf)); 76 | 77 | if (igmp->type == IGMP_HOST_MEMBERSHIP_QUERY) { 78 | struct igmpv3_query *query = (struct igmpv3_query*)igmp; 79 | 80 | if (len != sizeof(*igmp) && ((size_t)len < sizeof(*query) || 81 | (size_t)len < sizeof(*query) + ntohs(query->nsrcs) * sizeof(struct in_addr))) 82 | return; 83 | 84 | if (query->group && !igmp_is_valid_group(query->group)) 85 | return; 86 | 87 | // Setup query target address 88 | struct in_addr addr; 89 | if (mrib_igmp_source(mrib, &addr)) 90 | return; 91 | 92 | bool suppress = false; 93 | size_t nsrc = 0; 94 | int robustness = 2; 95 | omgp_time_t mrd = 10000; 96 | omgp_time_t query_interval = 125000; 97 | 98 | if (igmp->code) 99 | mrd = 100 * ((len == sizeof(*igmp)) ? igmp->code : querier_qqi(igmp->code)); 100 | 101 | if ((size_t)len > sizeof(*igmp)) { 102 | if (query->qrv) 103 | robustness = query->qrv; 104 | 105 | if (query->qqic) 106 | query_interval = querier_qqi(query->qqic) * 1000; 107 | 108 | suppress = query->suppress; 109 | nsrc = ntohs(query->nsrcs); 110 | } 111 | 112 | if (!suppress && query->group) { 113 | struct in6_addr sources[nsrc]; 114 | for (size_t i = 0; i < nsrc; ++i) 115 | querier_map(&sources[i], query->srcs[i]); 116 | 117 | groups_update_timers(&q->groups, &group, sources, nsrc); 118 | } 119 | 120 | int election = memcmp(&from->sin_addr, &addr, sizeof(from->sin_addr)); 121 | L_INFO("%s: detected other querier %s with priority %d on %d", 122 | __FUNCTION__, addrbuf, election, q->ifindex); 123 | 124 | // TODO: we ignore IGMPv1/v2 queriers for now, since a lot of them are dumb switches 125 | 126 | if (election < 0 && !query->group && len > sizeof(*igmp)) { 127 | groups_update_config(&q->groups, false, mrd, query_interval, robustness); 128 | 129 | q->igmp_other_querier = true; 130 | q->igmp_next_query = now + (q->groups.cfg_v4.query_response_interval / 2) + 131 | (q->groups.cfg_v4.robustness * q->groups.cfg_v4.query_interval); 132 | } 133 | } else if (igmp->type == IGMPV3_HOST_MEMBERSHIP_REPORT) { 134 | struct igmpv3_report *report = (struct igmpv3_report*)igmp; 135 | 136 | if ((size_t)len <= sizeof(*report)) 137 | return; 138 | 139 | uint8_t *ibuf = (uint8_t*)igmp; 140 | size_t count = ntohs(report->ngrec); 141 | size_t offset = sizeof(*report); 142 | 143 | while (count > 0 && offset < len) { 144 | ssize_t read = igmp_handle_record(&q->groups, &ibuf[offset], len - offset); 145 | if (read < 0) 146 | break; 147 | 148 | offset += read; 149 | --count; 150 | } 151 | } else if (igmp->type == IGMPV2_HOST_MEMBERSHIP_REPORT || 152 | igmp->type == IGMP_HOST_LEAVE_MESSAGE || 153 | igmp->type == IGMP_HOST_MEMBERSHIP_REPORT) { 154 | 155 | if (len != sizeof(*igmp) || !igmp_is_valid_group(igmp->group)) 156 | return; 157 | 158 | groups_update_state(&q->groups, &group, NULL, 0, 159 | (igmp->type == IGMPV2_HOST_MEMBERSHIP_REPORT) ? UPDATE_REPORT : 160 | (igmp->type == IGMP_HOST_MEMBERSHIP_REPORT) ? UPDATE_REPORT_V1 : UPDATE_DONE); 161 | } 162 | 163 | uloop_timeout_set(&q->timeout, 0); 164 | } 165 | 166 | // Send generic / group-specific / group-and-source specific IGMP-query 167 | int igmp_send_query(struct querier_iface *q, 168 | const struct in6_addr *group, 169 | const struct list_head *sources, 170 | bool suppress) 171 | { 172 | uint8_t qqic = querier_qqic(((group) ? q->groups.cfg_v4.last_listener_query_interval : 173 | q->groups.cfg_v4.query_response_interval) / 100); 174 | struct { 175 | struct igmpv3_query q; 176 | struct in_addr srcs[QUERIER_MAX_SOURCE]; 177 | } query = {.q = { 178 | .type = IGMP_HOST_MEMBERSHIP_QUERY, 179 | .code = qqic, 180 | .qrv = q->groups.cfg_v4.robustness, 181 | .suppress = suppress, 182 | .qqic = querier_qqic(q->groups.cfg_v4.query_interval / 1000), 183 | }}; 184 | 185 | struct group_source *s; 186 | size_t cnt = 0; 187 | if (sources) { 188 | list_for_each_entry(s, sources, head) { 189 | if (cnt >= QUERIER_MAX_SOURCE) { 190 | L_WARN("%s: maximum source count (%d) exceeded", 191 | __FUNCTION__, QUERIER_MAX_SOURCE); 192 | break; 193 | } 194 | 195 | query.q.srcs[cnt] = querier_unmap(&s->addr); 196 | } 197 | } 198 | query.q.nsrcs = htons(cnt); 199 | 200 | struct sockaddr_in dest = { .sin_family = AF_INET, .sin_addr = {htonl(0xe0000001U)}}; 201 | if (group) { 202 | query.q.group = querier_unmap(group); 203 | dest.sin_addr.s_addr = query.q.group; 204 | } 205 | 206 | return mrib_send_igmp(&q->mrib, &query.q, 207 | sizeof(query.q) + cnt * sizeof(query.srcs[0]), &dest); 208 | } 209 | 210 | -------------------------------------------------------------------------------- /src/mld.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Steven Barth 3 | * 4 | * Copyright 2015 Deutsche Telekom AG 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "mrib.h" 35 | #include "querier.h" 36 | 37 | struct mld_query { 38 | struct mld_hdr mld; 39 | uint8_t s_qrv; 40 | uint8_t qqic; 41 | uint16_t nsrc; 42 | struct in6_addr addrs[0]; 43 | }; 44 | 45 | // Test whether group address is valid and interesting 46 | static inline bool mld_is_valid_group(const struct in6_addr *addr) 47 | { 48 | return IN6_IS_ADDR_MULTICAST(addr); 49 | } 50 | 51 | // Handle Multicast Address Record from MLD-Packets (called by mld_receive) 52 | static ssize_t mld_handle_record(struct groups *groups, const uint8_t *data, size_t len) 53 | { 54 | struct mld_record { 55 | uint8_t type; 56 | uint8_t aux; 57 | uint16_t nsrc; 58 | struct in6_addr addr; 59 | struct in6_addr sources[]; 60 | } *r = (struct mld_record*)data; 61 | 62 | if (len < sizeof(*r)) 63 | return -1; 64 | 65 | size_t nsrc = ntohs(r->nsrc); 66 | size_t read = sizeof(*r) + nsrc * sizeof(struct in6_addr) + r->aux; 67 | if (len < read) 68 | return -1; 69 | 70 | if (r->type >= UPDATE_IS_INCLUDE && r->type <= UPDATE_BLOCK && mld_is_valid_group(&r->addr)) 71 | groups_update_state(groups, &r->addr, r->sources, nsrc, r->type); 72 | 73 | return read; 74 | } 75 | 76 | // Receive an MLD-Packet from a node (called by uloop as callback) 77 | void mld_handle(struct mrib_querier *mrib, const struct mld_hdr *hdr, size_t len, 78 | const struct sockaddr_in6 *from) 79 | { 80 | char addrbuf[INET_ADDRSTRLEN]; 81 | omgp_time_t now = omgp_time(); 82 | inet_ntop(AF_INET6, &hdr->mld_addr, addrbuf, sizeof(addrbuf)); 83 | 84 | struct querier_iface *q = container_of(mrib, struct querier_iface, mrib); 85 | if (hdr->mld_icmp6_hdr.icmp6_type == ICMPV6_MGM_QUERY) { 86 | struct mld_query *query = (struct mld_query*)hdr; 87 | 88 | if (len != 24 && ((size_t)len < sizeof(*query) || 89 | (size_t)len < sizeof(*query) + ntohs(query->nsrc) * sizeof(struct in6_addr))) 90 | return; 91 | 92 | if (!IN6_IS_ADDR_UNSPECIFIED(&query->mld.mld_addr) && 93 | !mld_is_valid_group(&query->mld.mld_addr)) 94 | return; 95 | 96 | // Detect local source address 97 | struct in6_addr addr; 98 | if (mrib_mld_source(mrib, &addr)) 99 | return; 100 | 101 | bool suppress = false; 102 | size_t nsrc = 0; 103 | int robustness = 2; 104 | omgp_time_t mrd = 10000; 105 | omgp_time_t query_interval = 125000; 106 | 107 | if (query->mld.mld_icmp6_hdr.icmp6_dataun.icmp6_un_data16[0]) 108 | mrd = (len == 24) ? ntohs(query->mld.mld_icmp6_hdr.icmp6_dataun.icmp6_un_data16[0]) : 109 | querier_mrd(query->mld.mld_icmp6_hdr.icmp6_dataun.icmp6_un_data16[0]); 110 | 111 | if (len > 24) { 112 | if (query->s_qrv & 0x7) 113 | robustness = query->s_qrv & 0x7; 114 | 115 | if (query->qqic) 116 | query_interval = querier_qqi(query->qqic) * 1000; 117 | } 118 | 119 | if (!suppress && !IN6_IS_ADDR_UNSPECIFIED(&query->mld.mld_addr)) 120 | groups_update_timers(&q->groups, &query->mld.mld_addr, query->addrs, nsrc); 121 | 122 | int election = memcmp(&from->sin6_addr, &addr, sizeof(from->sin6_addr)); 123 | L_INFO("%s: detected other querier %s with priority %d on %d", 124 | __FUNCTION__, addrbuf, election, q->ifindex); 125 | 126 | // TODO: we ignore MLDv1 queriers for now, since a lot of them are dumb switches 127 | 128 | if (election < 0 && IN6_IS_ADDR_UNSPECIFIED(&query->mld.mld_addr) && len > 24) { 129 | groups_update_config(&q->groups, true, mrd, query_interval, robustness); 130 | 131 | q->mld_other_querier = true; 132 | q->mld_next_query = now + (q->groups.cfg_v6.query_response_interval / 2) + 133 | (q->groups.cfg_v6.robustness * q->groups.cfg_v6.query_interval); 134 | } 135 | } else if (hdr->mld_icmp6_hdr.icmp6_type == ICMPV6_MLD2_REPORT) { 136 | struct icmp6_hdr *mld_report = (struct icmp6_hdr *)hdr; 137 | if ((size_t)len <= sizeof(*mld_report)) 138 | return; 139 | 140 | uint8_t *buf = (uint8_t*)hdr; 141 | size_t count = ntohs(mld_report->icmp6_dataun.icmp6_un_data16[1]); 142 | ssize_t offset = sizeof(*mld_report); 143 | 144 | while (count > 0 && offset < (ssize_t)len) { 145 | ssize_t read = mld_handle_record(&q->groups, &buf[offset], len - offset); 146 | if (read < 0) 147 | break; 148 | 149 | offset += read; 150 | --count; 151 | } 152 | } else if (hdr->mld_icmp6_hdr.icmp6_type == MLD_LISTENER_REPORT || 153 | hdr->mld_icmp6_hdr.icmp6_type == MLD_LISTENER_REDUCTION) { 154 | if (len != 24 || !mld_is_valid_group(&hdr->mld_addr)) 155 | return; 156 | 157 | groups_update_state(&q->groups, &hdr->mld_addr, NULL, 0, 158 | (hdr->mld_icmp6_hdr.icmp6_type == MLD_LISTENER_REPORT) ? UPDATE_REPORT : UPDATE_DONE); 159 | } 160 | uloop_timeout_set(&q->timeout, 0); 161 | } 162 | 163 | 164 | // Send generic / group-specific / group-and-source-specific queries 165 | ssize_t mld_send_query(struct querier_iface *q, const struct in6_addr *group, 166 | const struct list_head *sources, bool suppress) 167 | { 168 | uint16_t mrc = querier_mrc((group) ? q->groups.cfg_v6.last_listener_query_interval : 169 | q->groups.cfg_v6.query_response_interval); 170 | struct { 171 | struct mld_query q; 172 | struct in6_addr addrs[QUERIER_MAX_SOURCE]; 173 | } query = {.q = { 174 | .mld = {.mld_icmp6_hdr = {MLD_LISTENER_QUERY, 0, 0, {.icmp6_un_data16 = {mrc, 0}}}}, 175 | .s_qrv = (q->groups.cfg_v6.robustness & 0x7) | (suppress ? QUERIER_SUPPRESS : 0), 176 | .qqic = querier_qqic(q->groups.cfg_v6.query_interval / 1000), 177 | }}; 178 | 179 | struct group_source *s; 180 | size_t cnt = 0; 181 | if (sources) { 182 | list_for_each_entry(s, sources, head) { 183 | if (cnt >= QUERIER_MAX_SOURCE) 184 | break; // TODO: log source overflow 185 | 186 | query.addrs[cnt++] = s->addr; 187 | } 188 | } 189 | query.q.nsrc = htons(cnt); 190 | 191 | struct sockaddr_in6 dest = {AF_INET6, 0, 0, IPV6_ALL_NODES_INIT, q->ifindex}; 192 | 193 | if (group) 194 | query.q.mld.mld_addr = dest.sin6_addr = *group; 195 | 196 | return mrib_send_mld(&q->mrib, &query.q.mld, 197 | sizeof(query.q) + cnt * sizeof(query.addrs[0]), &dest); 198 | } 199 | -------------------------------------------------------------------------------- /src/mrib.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Steven Barth 3 | * 4 | * Copyright 2015 Deutsche Telekom AG 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | #include 37 | 38 | #include 39 | 40 | #include "omcproxy.h" 41 | #include "mrib.h" 42 | 43 | struct mrib_route { 44 | struct list_head head; 45 | struct in6_addr group; 46 | struct in6_addr source; 47 | omgp_time_t valid_until; 48 | }; 49 | 50 | struct mrib_iface { 51 | int ifindex; 52 | struct list_head users; 53 | struct list_head routes; 54 | struct list_head queriers; 55 | struct uloop_timeout timer; 56 | }; 57 | 58 | /* we can't use cpu_to_be32 outside a function */ 59 | #if __BYTE_ORDER == __BIG_ENDIAN 60 | static uint32_t ipv4_rtr_alert = 0x94040000; 61 | #else 62 | static uint32_t ipv4_rtr_alert = 0x00000494; 63 | #endif 64 | 65 | static struct { 66 | struct ip6_hbh hdr; 67 | struct ip6_opt_router rt; 68 | uint8_t pad[2]; 69 | } ipv6_rtr_alert = { 70 | .hdr = {0, 0}, 71 | .rt = {IP6OPT_ROUTER_ALERT, 2, {0, IP6_ALERT_MLD}}, 72 | .pad = {0, 0} 73 | }; 74 | 75 | static struct mrib_iface mifs[MAXMIFS] = {}; 76 | static struct uloop_fd mrt_fd = { .fd = -1 }; 77 | static struct uloop_fd mrt6_fd = { .fd = -1 }; 78 | 79 | 80 | // Unmap IPv4 address from IPv6 81 | static inline void mrib_unmap(struct in_addr *addr4, const struct in6_addr *addr6) 82 | { 83 | addr4->s_addr = addr6->s6_addr32[3]; 84 | } 85 | 86 | // Add / delete multicast route 87 | static int mrib_set(const struct in6_addr *group, const struct in6_addr *source, 88 | struct mrib_iface *iface, mrib_filter dest, bool del) 89 | { 90 | int status = 0; 91 | size_t mifid = iface - mifs; 92 | if (IN6_IS_ADDR_V4MAPPED(group)) { 93 | struct mfcctl ctl = { .mfcc_parent = mifid }; 94 | mrib_unmap(&ctl.mfcc_origin, source); 95 | mrib_unmap(&ctl.mfcc_mcastgrp, group); 96 | 97 | if(!del) 98 | for (size_t i = 0; i < MAXMIFS; ++i) 99 | if (dest & (1 << i)) 100 | ctl.mfcc_ttls[i] = 1; 101 | 102 | if (setsockopt(mrt_fd.fd, IPPROTO_IP, 103 | (del) ? MRT_DEL_MFC : MRT_ADD_MFC, 104 | &ctl, sizeof(ctl))) 105 | status = -errno; 106 | } else { 107 | struct mf6cctl ctl = { 108 | .mf6cc_origin = {AF_INET6, 0, 0, *source, 0}, 109 | .mf6cc_mcastgrp = {AF_INET6, 0, 0, *group, 0}, 110 | .mf6cc_parent = mifid, 111 | }; 112 | 113 | if(!del) 114 | for (size_t i = 0; i < MAXMIFS; ++i) 115 | if (dest & (1 << i)) 116 | IF_SET(i, &ctl.mf6cc_ifset); 117 | 118 | if (setsockopt(mrt6_fd.fd, IPPROTO_IPV6, 119 | (del) ? MRT6_DEL_MFC : MRT6_ADD_MFC, 120 | &ctl, sizeof(ctl))) 121 | status = -errno; 122 | } 123 | 124 | char groupbuf[INET6_ADDRSTRLEN], sourcebuf[INET6_ADDRSTRLEN]; 125 | inet_ntop(AF_INET6, group, groupbuf, sizeof(groupbuf)); 126 | inet_ntop(AF_INET6, source, sourcebuf, sizeof(sourcebuf)); 127 | if(del) { 128 | L_DEBUG("%s: deleting MFC-entry for %s from %s%%%d: %s", 129 | __FUNCTION__, groupbuf, sourcebuf, iface->ifindex, strerror(-status)); 130 | } else { 131 | int ifbuf_len = 0; 132 | char ifbuf[256] = {0}; 133 | for (size_t i = 0; i < MAXMIFS; ++i) 134 | if (dest & (1 << i)) 135 | ifbuf_len += snprintf(&ifbuf[ifbuf_len], sizeof(ifbuf) - ifbuf_len, " %d", mifs[i].ifindex); 136 | 137 | 138 | L_DEBUG("%s: setting MFC-entry for %s from %s%%%d to%s: %s", 139 | __FUNCTION__, groupbuf, sourcebuf, iface->ifindex, ifbuf, strerror(-status)); 140 | } 141 | 142 | return status; 143 | } 144 | 145 | 146 | // We have no way of knowing when a source disappears, so we delete multicast routes from time to time 147 | static void mrib_clean(struct uloop_timeout *t) 148 | { 149 | struct mrib_iface *iface = container_of(t, struct mrib_iface, timer); 150 | omgp_time_t now = omgp_time(); 151 | uloop_timeout_cancel(t); 152 | 153 | struct mrib_route *c, *n; 154 | list_for_each_entry_safe(c, n, &iface->routes, head) { 155 | if (c->valid_until <= now || (list_empty(&iface->users) && list_empty(&iface->queriers))) { 156 | mrib_set(&c->group, &c->source, iface, 0, 1); 157 | list_del(&c->head); 158 | free(c); 159 | } else { 160 | uloop_timeout_set(t, c->valid_until - now); 161 | break; 162 | } 163 | } 164 | } 165 | 166 | 167 | // Find MIFID by ifindex 168 | static size_t mrib_find(int ifindex) 169 | { 170 | size_t i = 0; 171 | while (i < MAXMIFS && mifs[i].ifindex != ifindex) 172 | ++i; 173 | return i; 174 | } 175 | 176 | // Notify all users of a new multicast source 177 | static void mrib_notify_newsource(struct mrib_iface *iface, 178 | const struct in6_addr *group, const struct in6_addr *source) 179 | { 180 | mrib_filter filter = 0; 181 | struct mrib_user *user; 182 | list_for_each_entry(user, &iface->users, head) 183 | if (user->cb_newsource) 184 | user->cb_newsource(user, group, source, &filter); 185 | 186 | char groupbuf[INET6_ADDRSTRLEN], sourcebuf[INET6_ADDRSTRLEN]; 187 | inet_ntop(AF_INET6, group, groupbuf, sizeof(groupbuf)); 188 | inet_ntop(AF_INET6, source, sourcebuf, sizeof(sourcebuf)); 189 | L_DEBUG("%s: detected new multicast source %s for %s on %d", 190 | __FUNCTION__, sourcebuf, groupbuf, iface->ifindex); 191 | 192 | struct mrib_route *route = malloc(sizeof(*route)); 193 | if (route) { 194 | route->group = *group; 195 | route->source = *source; 196 | route->valid_until = omgp_time() + MRIB_DEFAULT_LIFETIME * OMGP_TIME_PER_SECOND; 197 | 198 | if (list_empty(&iface->routes)) 199 | uloop_timeout_set(&iface->timer, MRIB_DEFAULT_LIFETIME * OMGP_TIME_PER_SECOND); 200 | 201 | list_add_tail(&route->head, &iface->routes); 202 | mrib_set(group, source, iface, filter, 0); 203 | } 204 | } 205 | 206 | // Calculate IGMP-checksum 207 | static uint16_t igmp_checksum(const uint16_t *buf, size_t len) 208 | { 209 | int32_t sum = 0; 210 | 211 | while (len > 1) { 212 | sum += *buf++; 213 | sum = (sum + (sum >> 16)) & 0xffff; 214 | len -= 2; 215 | } 216 | 217 | if (len == 1) { 218 | sum += *((uint8_t*)buf); 219 | sum += (sum + (sum >> 16)) & 0xffff; 220 | } 221 | 222 | return ~sum; 223 | } 224 | 225 | // Receive and handle MRT event 226 | static void mrib_receive_mrt(struct uloop_fd *fd, __unused unsigned flags) 227 | { 228 | uint8_t buf[9216], cbuf[CMSG_SPACE(sizeof(struct in_pktinfo))]; 229 | char addrbuf[INET_ADDRSTRLEN]; 230 | struct sockaddr_in from; 231 | 232 | while (true) { 233 | struct iovec iov = {buf, sizeof(buf)}; 234 | struct msghdr hdr = { 235 | .msg_name = (void*)&from, 236 | .msg_namelen = sizeof(from), 237 | .msg_iov = &iov, 238 | .msg_iovlen = 1, 239 | .msg_control = cbuf, 240 | .msg_controllen = sizeof(cbuf) 241 | }; 242 | 243 | ssize_t len = recvmsg(fd->fd, &hdr, MSG_DONTWAIT); 244 | if (len < 0 && errno == EAGAIN) 245 | break; 246 | 247 | struct iphdr *iph = iov.iov_base; 248 | if (len < (ssize_t)sizeof(*iph)) 249 | continue; 250 | 251 | if (iph->protocol == 0) { 252 | // Pseudo IP/IGMP-packet from kernel MC-API 253 | struct igmpmsg *msg = iov.iov_base; 254 | struct mrib_iface *iface = NULL; 255 | if (msg->im_vif < MAXMIFS) 256 | iface = &mifs[msg->im_vif]; 257 | 258 | if (!iface) { 259 | L_WARN("MRT kernel-message for unknown MIF %i", msg->im_vif); 260 | continue; 261 | } 262 | 263 | if (msg->im_msgtype != IGMPMSG_NOCACHE) { 264 | L_WARN("Unknown MRT kernel-message %i on interface %d", 265 | msg->im_msgtype, iface->ifindex); 266 | continue; 267 | } 268 | 269 | struct in6_addr dst = IN6ADDR_ANY_INIT; 270 | struct in6_addr src = IN6ADDR_ANY_INIT; 271 | dst.s6_addr32[2] = cpu_to_be32(0xffff); 272 | dst.s6_addr32[3] = msg->im_dst.s_addr; 273 | src.s6_addr32[2] = cpu_to_be32(0xffff); 274 | src.s6_addr32[3] = msg->im_src.s_addr; 275 | 276 | mrib_notify_newsource(iface, &dst, &src); 277 | } else { 278 | // IGMP packet 279 | if ((len -= iph->ihl * 4) < 0) 280 | continue; 281 | 282 | int ifindex = 0; 283 | for (struct cmsghdr *ch = CMSG_FIRSTHDR(&hdr); ch != NULL; ch = CMSG_NXTHDR(&hdr, ch)) { 284 | if (ch->cmsg_level == IPPROTO_IP && ch->cmsg_type == IP_PKTINFO) { 285 | struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(ch); 286 | ifindex = info->ipi_ifindex; 287 | } 288 | } 289 | 290 | if (ifindex == 0) 291 | continue; 292 | 293 | inet_ntop(AF_INET, &from.sin_addr, addrbuf, sizeof(addrbuf)); 294 | struct igmphdr *igmp = (struct igmphdr*)&buf[iph->ihl * 4]; 295 | 296 | uint16_t checksum = igmp->csum; 297 | igmp->csum = 0; 298 | 299 | if (iph->ttl != 1 || len < (ssize_t)sizeof(*igmp) || 300 | checksum != igmp_checksum((uint16_t*)igmp, len)) { 301 | L_WARN("%s: ignoring invalid IGMP-message of type %x from %s on %d", 302 | __FUNCTION__, igmp->type, addrbuf, ifindex); 303 | continue; 304 | } 305 | 306 | uint32_t *opts = (uint32_t*)&iph[1]; 307 | bool alert = (void*)&opts[1] <= (void*)igmp && *opts == ipv4_rtr_alert; 308 | if (!alert && (igmp->type != IGMP_HOST_MEMBERSHIP_QUERY || 309 | (size_t)len > sizeof(*igmp) || igmp->code > 0)) { 310 | L_WARN("%s: ignoring invalid IGMP-message of type %x from %s on %d", 311 | __FUNCTION__, igmp->type, addrbuf, ifindex); 312 | continue; 313 | } 314 | 315 | ssize_t mifid = mrib_find(ifindex); 316 | if (mifid < MAXMIFS) { 317 | struct mrib_querier *q; 318 | list_for_each_entry(q, &mifs[mifid].queriers, head) 319 | if (q->cb_igmp) 320 | q->cb_igmp(q, igmp, len, &from); 321 | } 322 | } 323 | } 324 | } 325 | 326 | // Receive and handle MRT6 event 327 | static void mrib_receive_mrt6(struct uloop_fd *fd, __unused unsigned flags) 328 | { 329 | uint8_t buf[9216], cbuf[128]; 330 | char addrbuf[INET6_ADDRSTRLEN]; 331 | struct sockaddr_in6 from; 332 | 333 | while (true) { 334 | struct iovec iov = {buf, sizeof(buf)}; 335 | struct msghdr hdr = { 336 | .msg_name = (void*)&from, 337 | .msg_namelen = sizeof(from), 338 | .msg_iov = &iov, 339 | .msg_iovlen = 1, 340 | .msg_control = cbuf, 341 | .msg_controllen = sizeof(cbuf) 342 | }; 343 | 344 | ssize_t len = recvmsg(fd->fd, &hdr, MSG_DONTWAIT); 345 | if (len < 0 && errno == EAGAIN) 346 | break; 347 | 348 | struct mld_hdr *mld = iov.iov_base; 349 | if (len < (ssize_t)sizeof(*mld)) 350 | continue; 351 | 352 | if (mld->mld_icmp6_hdr.icmp6_type == 0) { 353 | // Pseudo ICMPv6/MLD-packet from kernel MC-API 354 | struct mrt6msg *msg = iov.iov_base; 355 | struct mrib_iface *iface = NULL; 356 | if (msg->im6_mif < MAXMIFS) 357 | iface = &mifs[msg->im6_mif]; 358 | 359 | if (!iface) { 360 | L_WARN("MRT6 kernel-message for unknown MIF %i", msg->im6_mif); 361 | continue; 362 | } 363 | 364 | if (msg->im6_msgtype != MRT6MSG_NOCACHE) { 365 | L_WARN("Unknown MRT6 kernel-message %i on interface %d", 366 | msg->im6_msgtype, iface->ifindex); 367 | continue; 368 | } 369 | 370 | mrib_notify_newsource(iface, &msg->im6_dst, &msg->im6_src); 371 | } else { 372 | int hlim = 0, ifindex = from.sin6_scope_id; 373 | bool alert = false; 374 | for (struct cmsghdr *ch = CMSG_FIRSTHDR(&hdr); ch != NULL; ch = CMSG_NXTHDR(&hdr, ch)) { 375 | if (ch->cmsg_level == IPPROTO_IPV6 && ch->cmsg_type == IPV6_HOPLIMIT) 376 | memcpy(&hlim, CMSG_DATA(ch), sizeof(hlim)); 377 | else if (ch->cmsg_level == IPPROTO_IPV6 && ch->cmsg_type == IPV6_HOPOPTS && 378 | ch->cmsg_len >= CMSG_LEN(sizeof(ipv6_rtr_alert)) && 379 | memmem(CMSG_DATA(ch), ch->cmsg_len - CMSG_LEN(0), 380 | &ipv6_rtr_alert.rt, sizeof(ipv6_rtr_alert.rt))) 381 | alert = true; // FIXME: memmem is wrong 382 | } 383 | inet_ntop(AF_INET6, &from.sin6_addr, addrbuf, sizeof(addrbuf)); 384 | 385 | if (!IN6_IS_ADDR_LINKLOCAL(&from.sin6_addr) || hlim != 1 || len < 24 || !alert) { 386 | L_WARN("mld: ignoring invalid MLD-message of type %d from %s on %d", 387 | mld->mld_icmp6_hdr.icmp6_type, addrbuf, ifindex); 388 | continue; 389 | } 390 | 391 | ssize_t mifid = mrib_find(from.sin6_scope_id); 392 | if (mifid < MAXMIFS) { 393 | struct mrib_querier *q; 394 | list_for_each_entry(q, &mifs[mifid].queriers, head) 395 | if (q->cb_mld) 396 | q->cb_mld(q, mld, len, &from); 397 | } 398 | } 399 | } 400 | } 401 | 402 | // Send an IGMP-packet 403 | int mrib_send_igmp(struct mrib_querier *q, struct igmpv3_query *igmp, size_t len, 404 | const struct sockaddr_in *dest) 405 | { 406 | uint8_t cbuf[CMSG_SPACE(sizeof(struct in_pktinfo))] = {0}; 407 | struct iovec iov = {igmp, len}; 408 | struct msghdr msg = { 409 | .msg_name = (void*)dest, 410 | .msg_namelen = sizeof(*dest), 411 | .msg_iov = &iov, 412 | .msg_iovlen = 1, 413 | .msg_control = cbuf, 414 | .msg_controllen = sizeof(cbuf) 415 | }; 416 | 417 | igmp->csum = 0; 418 | igmp->csum = igmp_checksum((uint16_t*)igmp, len); 419 | 420 | // Set control data (define destination interface) 421 | struct cmsghdr *chdr = CMSG_FIRSTHDR(&msg); 422 | chdr->cmsg_level = IPPROTO_IP; 423 | chdr->cmsg_type = IP_PKTINFO; 424 | chdr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); 425 | 426 | struct in_pktinfo *pktinfo = (struct in_pktinfo*)CMSG_DATA(chdr); 427 | pktinfo->ipi_addr.s_addr = 0; 428 | pktinfo->ipi_ifindex = q->iface->ifindex; 429 | if (mrib_igmp_source(q, &pktinfo->ipi_spec_dst)) 430 | return -errno; 431 | 432 | ssize_t s = sendmsg(mrt_fd.fd, &msg, MSG_DONTWAIT); 433 | return (s < 0) ? -errno : (s < (ssize_t)len) ? -EMSGSIZE : 0; 434 | } 435 | 436 | // Send an IGMP-packet 437 | int mrib_send_mld(struct mrib_querier *q, struct mld_hdr *mld, size_t len, 438 | const struct sockaddr_in6 *dest) 439 | { 440 | uint8_t cbuf[CMSG_SPACE(sizeof(struct in6_pktinfo))] = {0}; 441 | struct iovec iov = {mld, len}; 442 | struct msghdr msg = { 443 | .msg_name = (void*)dest, 444 | .msg_namelen = sizeof(*dest), 445 | .msg_iov = &iov, 446 | .msg_iovlen = 1, 447 | .msg_control = cbuf, 448 | .msg_controllen = sizeof(cbuf) 449 | }; 450 | 451 | // Set control data (define destination interface) 452 | struct cmsghdr *chdr = CMSG_FIRSTHDR(&msg); 453 | chdr->cmsg_level = IPPROTO_IPV6; 454 | chdr->cmsg_type = IPV6_PKTINFO; 455 | chdr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); 456 | 457 | struct in6_pktinfo *pktinfo = (struct in6_pktinfo*)CMSG_DATA(chdr); 458 | pktinfo->ipi6_ifindex = q->iface->ifindex; 459 | if (mrib_mld_source(q, &pktinfo->ipi6_addr)) 460 | return -errno; 461 | 462 | ssize_t s = sendmsg(mrt6_fd.fd, &msg, MSG_DONTWAIT); 463 | return (s < 0) ? -errno : (s < (ssize_t)len) ? -EMSGSIZE : 0; 464 | } 465 | 466 | // Initialize MRIB 467 | static int mrib_init(void) 468 | { 469 | int fd; 470 | int val; 471 | 472 | if ((fd = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP)) < 0) 473 | goto err; 474 | 475 | val = 1; 476 | if (setsockopt(fd, IPPROTO_IP, MRT_INIT, &val, sizeof(val))) 477 | goto err; 478 | 479 | if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val, sizeof(val))) 480 | goto err; 481 | 482 | // Configure IP header fields 483 | if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &val, sizeof(val))) 484 | goto err; 485 | 486 | val = 0xc0; 487 | if (setsockopt(fd, IPPROTO_IP, IP_TOS, &val, sizeof(val))) 488 | goto err; 489 | 490 | val = 0; 491 | if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &val, sizeof(val))) 492 | goto err; 493 | 494 | // Set router-alert option 495 | if (setsockopt(fd, IPPROTO_IP, IP_OPTIONS, &ipv4_rtr_alert, sizeof(ipv4_rtr_alert))) 496 | goto err; 497 | 498 | mrt_fd.fd = fd; 499 | 500 | 501 | if ((fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) 502 | goto err; 503 | 504 | // We need to know the source interface and hop-opts 505 | val = 1; 506 | if (setsockopt(fd, IPPROTO_IPV6, MRT6_INIT, &val, sizeof(val))) 507 | goto err; 508 | 509 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPOPTS, &val, sizeof(val))) 510 | goto err; 511 | 512 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &val, sizeof(val))) 513 | goto err; 514 | 515 | // MLD has hoplimit 1 516 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val))) 517 | goto err; 518 | 519 | val = 0; 520 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, sizeof(val))) 521 | goto err; 522 | 523 | // Let the kernel compute our checksums 524 | val = 2; 525 | if (setsockopt(fd, IPPROTO_RAW, IPV6_CHECKSUM, &val, sizeof(val))) 526 | goto err; 527 | 528 | // Set hop-by-hop router alert on outgoing 529 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_HOPOPTS, &ipv6_rtr_alert, sizeof(ipv6_rtr_alert))) 530 | goto err; 531 | 532 | // Set ICMP6 filter 533 | struct icmp6_filter flt; 534 | ICMP6_FILTER_SETBLOCKALL(&flt); 535 | ICMP6_FILTER_SETPASS(ICMPV6_MGM_QUERY, &flt); 536 | ICMP6_FILTER_SETPASS(ICMPV6_MGM_REPORT, &flt); 537 | ICMP6_FILTER_SETPASS(ICMPV6_MGM_REDUCTION, &flt); 538 | ICMP6_FILTER_SETPASS(ICMPV6_MLD2_REPORT, &flt); 539 | if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &flt, sizeof(flt))) 540 | goto err; 541 | 542 | mrt6_fd.fd = fd; 543 | 544 | mrt_fd.cb = mrib_receive_mrt; 545 | mrt6_fd.cb = mrib_receive_mrt6; 546 | 547 | uloop_fd_add(&mrt_fd, ULOOP_READ | ULOOP_EDGE_TRIGGER); 548 | uloop_fd_add(&mrt6_fd, ULOOP_READ | ULOOP_EDGE_TRIGGER); 549 | 550 | fd = -1; 551 | errno = 0; 552 | 553 | err: 554 | if (fd >= 0) 555 | close(fd); 556 | return -errno; 557 | } 558 | 559 | // Create new interface entry 560 | static struct mrib_iface* mrib_get_iface(int ifindex) 561 | { 562 | if (mrt_fd.fd < 0 && mrib_init() < 0) 563 | return NULL; 564 | 565 | size_t mifid = mrib_find(ifindex); 566 | if (mifid < MAXMIFS) 567 | return &mifs[mifid]; 568 | 569 | errno = EBUSY; 570 | if ((mifid = mrib_find(0)) >= MAXMIFS) 571 | return NULL; 572 | 573 | struct mrib_iface *iface = &mifs[mifid]; 574 | 575 | struct vifctl ctl = {mifid, VIFF_USE_IFINDEX, 1, 0, { .vifc_lcl_ifindex = ifindex }, {INADDR_ANY}}; 576 | if (setsockopt(mrt_fd.fd, IPPROTO_IP, MRT_ADD_VIF, &ctl, sizeof(ctl))) 577 | return NULL; 578 | 579 | struct mif6ctl ctl6 = {mifid, 0, 1, ifindex, 0}; 580 | if (setsockopt(mrt6_fd.fd, IPPROTO_IPV6, MRT6_ADD_MIF, &ctl6, sizeof(ctl6))) 581 | return NULL; 582 | 583 | struct ip_mreqn mreq = {{INADDR_ALLIGMPV3RTRS_GROUP}, {INADDR_ANY}, ifindex}; 584 | setsockopt(mrt_fd.fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); 585 | 586 | mreq.imr_multiaddr.s_addr = cpu_to_be32(INADDR_ALLRTRS_GROUP); 587 | setsockopt(mrt_fd.fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); 588 | 589 | struct ipv6_mreq mreq6 = {MLD2_ALL_MCR_INIT, ifindex}; 590 | setsockopt(mrt6_fd.fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq6, sizeof(mreq6)); 591 | 592 | mreq6.ipv6mr_multiaddr.s6_addr[15] = 0x02; 593 | setsockopt(mrt6_fd.fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq6, sizeof(mreq6)); 594 | 595 | iface->timer.cb = mrib_clean; 596 | iface->ifindex = ifindex; 597 | INIT_LIST_HEAD(&iface->routes); 598 | INIT_LIST_HEAD(&iface->users); 599 | INIT_LIST_HEAD(&iface->queriers); 600 | return iface; 601 | } 602 | 603 | // Remove interfaces if it has no more users 604 | static void mrib_clean_iface(struct mrib_iface *iface) 605 | { 606 | if (list_empty(&iface->users) && list_empty(&iface->queriers)) { 607 | iface->ifindex = 0; 608 | mrib_clean(&iface->timer); 609 | 610 | size_t mifid = iface - mifs; 611 | struct vifctl ctl = {mifid, VIFF_USE_IFINDEX, 1, 0, 612 | { .vifc_lcl_ifindex = iface->ifindex }, {INADDR_ANY}}; 613 | setsockopt(mrt_fd.fd, IPPROTO_IP, MRT_DEL_VIF, &ctl, sizeof(ctl)); 614 | 615 | struct mif6ctl ctl6 = {mifid, 0, 1, iface->ifindex, 0}; 616 | setsockopt(mrt6_fd.fd, IPPROTO_IPV6, MRT6_DEL_MIF, &ctl6, sizeof(ctl6)); 617 | 618 | struct ip_mreqn mreq = {{INADDR_ALLIGMPV3RTRS_GROUP}, {INADDR_ANY}, iface->ifindex}; 619 | setsockopt(mrt_fd.fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); 620 | 621 | mreq.imr_multiaddr.s_addr = cpu_to_be32(INADDR_ALLRTRS_GROUP); 622 | setsockopt(mrt_fd.fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); 623 | 624 | struct ipv6_mreq mreq6 = {MLD2_ALL_MCR_INIT, iface->ifindex}; 625 | setsockopt(mrt6_fd.fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq6, sizeof(mreq6)); 626 | 627 | mreq6.ipv6mr_multiaddr.s6_addr[15] = 0x02; 628 | setsockopt(mrt6_fd.fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq6, sizeof(mreq6)); 629 | } 630 | } 631 | 632 | // Register a new interface to mrib 633 | int mrib_attach_user(struct mrib_user *user, int ifindex, mrib_cb *cb_newsource) 634 | { 635 | struct mrib_iface *iface = mrib_get_iface(ifindex); 636 | if (!iface) 637 | return -errno; 638 | 639 | if (user->iface == iface) 640 | return -EALREADY; 641 | 642 | list_add(&user->head, &iface->users); 643 | user->iface = iface; 644 | user->cb_newsource = cb_newsource; 645 | return 0; 646 | } 647 | 648 | // Deregister an interface from mrib 649 | void mrib_detach_user(struct mrib_user *user) 650 | { 651 | struct mrib_iface *iface = user->iface; 652 | if (!iface) 653 | return; 654 | 655 | user->iface = NULL; 656 | list_del(&user->head); 657 | mrib_clean_iface(iface); 658 | } 659 | 660 | // Register a querier to mrib 661 | int mrib_attach_querier(struct mrib_querier *querier, int ifindex, mrib_igmp_cb *cb_igmp, mrib_mld_cb *cb_mld) 662 | { 663 | struct mrib_iface *iface = mrib_get_iface(ifindex); 664 | if (!iface) 665 | return -errno; 666 | 667 | list_add(&querier->head, &iface->queriers); 668 | querier->iface = iface; 669 | querier->cb_igmp = cb_igmp; 670 | querier->cb_mld = cb_mld; 671 | return 0; 672 | } 673 | 674 | // Deregister a querier from mrib 675 | void mrib_detach_querier(struct mrib_querier *querier) 676 | { 677 | struct mrib_iface *iface = querier->iface; 678 | if (!iface) 679 | return; 680 | 681 | querier->iface = NULL; 682 | list_del(&querier->head); 683 | mrib_clean_iface(iface); 684 | } 685 | 686 | static uint8_t prefix_contains(const struct in6_addr *p, uint8_t plen, const struct in6_addr *addr) 687 | { 688 | int blen = plen >> 3; 689 | if(blen && memcmp(p, addr, blen)) 690 | return 0; 691 | 692 | int rem = plen & 0x07; 693 | if(rem && ((p->s6_addr[blen] ^ addr->s6_addr[blen]) >> (8 - rem))) 694 | return 0; 695 | 696 | return 1; 697 | } 698 | 699 | // Flush state for a multicast route 700 | int mrib_flush(struct mrib_user *user, const struct in6_addr *group, uint8_t group_plen, const struct in6_addr *source) 701 | { 702 | struct mrib_iface *iface = user->iface; 703 | if (!iface) 704 | return -ENOENT; 705 | 706 | bool found = false; 707 | struct mrib_route *route, *n; 708 | list_for_each_entry_safe(route, n, &iface->routes, head) { 709 | if (prefix_contains(group, group_plen, &route->group) && 710 | (!source || IN6_ARE_ADDR_EQUAL(&route->source, source))) { 711 | route->valid_until = 0; 712 | list_del(&route->head); 713 | list_add(&route->head, &iface->routes); 714 | found = true; 715 | } 716 | } 717 | 718 | if (found) 719 | mrib_clean(&iface->timer); 720 | 721 | return (found) ? 0 : -ENOENT; 722 | } 723 | 724 | // Add an interface to the filter 725 | int mrib_filter_add(mrib_filter *filter, struct mrib_user *user) 726 | { 727 | struct mrib_iface *iface = user->iface; 728 | if (!iface) 729 | return -ENOENT; 730 | 731 | *filter |= 1 << (iface - mifs); 732 | return 0; 733 | } 734 | 735 | // Get MLD source address 736 | int mrib_mld_source(struct mrib_querier *q, struct in6_addr *source) 737 | { 738 | struct sockaddr_in6 addr = {AF_INET6, 0, 0, MLD2_ALL_MCR_INIT, q->iface->ifindex}; 739 | socklen_t alen = sizeof(addr); 740 | int sock = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_ICMPV6); 741 | int ret = 0; 742 | 743 | if (sock < 0 || connect(sock, (struct sockaddr*)&addr, sizeof(addr))) 744 | ret = -errno; 745 | 746 | if (ret || getsockname(sock, (struct sockaddr*)&addr, &alen)) { 747 | L_WARN("%s: failed to detect local source address on %d", __FUNCTION__, q->iface->ifindex); 748 | ret = -errno; 749 | } 750 | 751 | close(sock); 752 | 753 | if (ret == 0) 754 | *source = addr.sin6_addr; 755 | 756 | return ret; 757 | } 758 | 759 | 760 | // Get IGMP source address 761 | int mrib_igmp_source(struct mrib_querier *q, struct in_addr *source) 762 | { 763 | struct sockaddr_in addr = {AF_INET, 0, {cpu_to_be32(INADDR_ALLHOSTS_GROUP)}, {0}}; 764 | socklen_t alen = sizeof(addr); 765 | struct ifreq ifr = {.ifr_name = ""}; 766 | int sock = socket(AF_INET, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_IGMP); 767 | int ret = 0; 768 | 769 | ifr.ifr_ifindex = q->iface->ifindex; 770 | 771 | if (sock < 0 || ioctl(sock, SIOCGIFNAME, &ifr) || 772 | setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifr.ifr_name, strlen(ifr.ifr_name))) 773 | ret = -errno; 774 | 775 | 776 | if (ret || connect(sock, (struct sockaddr*)&addr, sizeof(addr))) 777 | ret = -errno; 778 | 779 | if (ret || getsockname(sock, (struct sockaddr*)&addr, &alen)) { 780 | L_WARN("%s: failed to detect local source address on %d", __FUNCTION__, q->iface->ifindex); 781 | ret = -errno; 782 | } 783 | 784 | close(sock); 785 | 786 | if (ret == 0) 787 | *source = addr.sin_addr; 788 | 789 | return ret; 790 | } 791 | -------------------------------------------------------------------------------- /src/mrib.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Steven Barth 3 | * 4 | * Copyright 2015 Deutsche Telekom AG 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #define icmp6_filter icmpv6_filter 29 | #include 30 | #include 31 | #undef icmp6_filter 32 | 33 | #define MRIB_DEFAULT_LIFETIME 125 34 | 35 | #define IPV6_ALL_NODES_INIT { { { 0xff,0x02,0,0,0,0,0,0,0,0,0,0,0,0,0,0x1 } } } 36 | #define INADDR_ALLIGMPV3RTRS_GROUP cpu_to_be32(0xe0000016U) 37 | 38 | typedef uint32_t mrib_filter; 39 | struct mrib_iface; 40 | struct mrib_user; 41 | struct mrib_querier; 42 | 43 | typedef void(mrib_cb)(struct mrib_user *user, const struct in6_addr *group, 44 | const struct in6_addr *source, mrib_filter *filter); 45 | 46 | typedef void(mrib_igmp_cb)(struct mrib_querier *mrib, const struct igmphdr *igmp, size_t len, 47 | const struct sockaddr_in *from); 48 | 49 | typedef void(mrib_mld_cb)(struct mrib_querier *mrib, const struct mld_hdr *mld, size_t len, 50 | const struct sockaddr_in6 *from); 51 | 52 | struct mrib_user { 53 | struct list_head head; 54 | struct mrib_iface *iface; 55 | mrib_cb *cb_newsource; 56 | }; 57 | 58 | struct mrib_querier { 59 | struct list_head head; 60 | struct mrib_iface *iface; 61 | mrib_igmp_cb *cb_igmp; 62 | mrib_mld_cb *cb_mld; 63 | }; 64 | 65 | // Register a new user to mrib 66 | int mrib_attach_user(struct mrib_user *user, int ifindex, mrib_cb *cb_newsource); 67 | 68 | // Deregister a user from mrib 69 | void mrib_detach_user(struct mrib_user *user); 70 | 71 | // Register a querier to mrib 72 | int mrib_attach_querier(struct mrib_querier *querier, int ifindex, mrib_igmp_cb *cb_igmp, mrib_mld_cb *cb_mld); 73 | 74 | // Deregister a querier from mrib 75 | void mrib_detach_querier(struct mrib_querier *querier); 76 | 77 | // Flush state for a multicast route 78 | int mrib_flush(struct mrib_user *user, const struct in6_addr *group, uint8_t group_plen, const struct in6_addr *source); 79 | 80 | // Add interface to filter 81 | int mrib_filter_add(mrib_filter *filter, struct mrib_user *user); 82 | 83 | // Send IGMP-packet 84 | int mrib_send_igmp(struct mrib_querier *querier, struct igmpv3_query *igmp, size_t len, 85 | const struct sockaddr_in *dest); 86 | 87 | // Send MLD-packet 88 | int mrib_send_mld(struct mrib_querier *querier, struct mld_hdr *mld, size_t len, 89 | const struct sockaddr_in6 *dest); 90 | 91 | // Get source address 92 | int mrib_mld_source(struct mrib_querier *q, struct in6_addr *source); 93 | int mrib_igmp_source(struct mrib_querier *q, struct in_addr *source); 94 | -------------------------------------------------------------------------------- /src/omcproxy.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Steven Barth 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | #include "omcproxy.h" 30 | #include "proxy.h" 31 | 32 | int log_level = LOG_WARNING; 33 | 34 | 35 | enum { 36 | PROXY_ATTR_SOURCE, 37 | PROXY_ATTR_SCOPE, 38 | PROXY_ATTR_DEST, 39 | PROXY_ATTR_MAX, 40 | }; 41 | 42 | static struct blobmsg_policy proxy_policy[PROXY_ATTR_MAX] = { 43 | [PROXY_ATTR_SOURCE] = { .name = "source", .type = BLOBMSG_TYPE_STRING }, 44 | [PROXY_ATTR_SCOPE] = { .name = "scope", .type = BLOBMSG_TYPE_STRING }, 45 | [PROXY_ATTR_DEST] = { .name = "dest", .type = BLOBMSG_TYPE_ARRAY }, 46 | }; 47 | 48 | static int handle_proxy_set(void *data, size_t len) 49 | { 50 | struct blob_attr *tb[PROXY_ATTR_MAX], *c; 51 | blobmsg_parse(proxy_policy, PROXY_ATTR_MAX, tb, data, len); 52 | 53 | const char *name = ((c = tb[PROXY_ATTR_SOURCE])) ? blobmsg_get_string(c) : NULL; 54 | int uplink = 0; 55 | int downlinks[32] = {0}; 56 | size_t downlinks_cnt = 0; 57 | enum proxy_flags flags = 0; 58 | 59 | if (!name) 60 | return -EINVAL; 61 | 62 | if (!(uplink = if_nametoindex(name))) { 63 | L_WARN("%s(%s): %s", __FUNCTION__, name, strerror(errno)); 64 | return -errno; 65 | } 66 | 67 | if ((c = tb[PROXY_ATTR_SCOPE])) { 68 | const char *scope = blobmsg_get_string(c); 69 | if (!strcmp(scope, "global")) 70 | flags = PROXY_GLOBAL; 71 | else if (!strcmp(scope, "organization")) 72 | flags = PROXY_ORGLOCAL; 73 | else if (!strcmp(scope, "site")) 74 | flags = PROXY_SITELOCAL; 75 | else if (!strcmp(scope, "admin")) 76 | flags = PROXY_ADMINLOCAL; 77 | else if (!strcmp(scope, "realm")) 78 | flags = PROXY_REALMLOCAL; 79 | 80 | if (!flags) { 81 | L_WARN("%s(%s): invalid scope (%s)", __FUNCTION__, name, scope); 82 | return -EINVAL; 83 | } 84 | } 85 | 86 | if ((c = tb[PROXY_ATTR_DEST])) { 87 | struct blob_attr *d; 88 | unsigned rem; 89 | blobmsg_for_each_attr(d, c, rem) { 90 | if (downlinks_cnt >= 32) { 91 | L_WARN("%s(%s): maximum number of destinations exceeded", __FUNCTION__, name); 92 | return -EINVAL; 93 | } 94 | 95 | const char *n = blobmsg_type(d) == BLOBMSG_TYPE_STRING ? blobmsg_get_string(d) : ""; 96 | if (!(downlinks[downlinks_cnt++] = if_nametoindex(n))) { 97 | L_WARN("%s(%s): %s (%s)", __FUNCTION__, name, strerror(errno), blobmsg_get_string(d)); 98 | return -errno; 99 | } 100 | } 101 | } 102 | 103 | return proxy_set(uplink, downlinks, downlinks_cnt, flags); 104 | } 105 | 106 | static void handle_signal(__unused int signal) 107 | { 108 | uloop_end(); 109 | } 110 | 111 | static void usage(const char *arg) { 112 | fprintf(stderr, "Usage: %s [options] [] [...]\n" 113 | "\nProxy examples:\n" 114 | "eth1,eth2\n" 115 | "eth1,eth2,eth3,scope=organization\n" 116 | "\nProxy options (each option may only occur once):\n" 117 | " interfaces to proxy (first is uplink)\n" 118 | " scope= minimum multicast scope to proxy\n" 119 | " [global,organization,site,admin,realm] (default: global)\n" 120 | "\nOptions:\n" 121 | " -v verbose logging\n" 122 | " -h show this help\n", 123 | arg); 124 | } 125 | 126 | int main(int argc, char **argv) { 127 | signal(SIGINT, handle_signal); 128 | signal(SIGTERM, handle_signal); 129 | signal(SIGHUP, SIG_IGN); 130 | signal(SIGPIPE, SIG_IGN); 131 | openlog("omcproxy", LOG_PERROR, LOG_DAEMON); 132 | 133 | if (getuid()) { 134 | L_ERR("must be run as root!"); 135 | return 2; 136 | } 137 | 138 | uloop_init(); 139 | bool start = true; 140 | 141 | for (ssize_t i = 1; i < argc; ++i) { 142 | const char *source = NULL; 143 | const char *scope = NULL; 144 | struct blob_buf b = {NULL, NULL, 0, NULL}; 145 | 146 | if (!strcmp(argv[i], "-h")) { 147 | usage(argv[0]); 148 | return 1; 149 | } else if (!strncmp(argv[i], "-v", 2)) { 150 | if ((log_level = atoi(&argv[i][2])) <= 0) 151 | log_level = 7; 152 | continue; 153 | } 154 | 155 | 156 | blob_buf_init(&b, 0); 157 | 158 | void *k = blobmsg_open_array(&b, "dest"); 159 | for (char *c = strtok(argv[i], ","); c; c = strtok(NULL, ",")) { 160 | if (!strncmp(c, "scope=", 6)) { 161 | scope = &c[6]; 162 | } else if (!source) { 163 | source = c; 164 | } else { 165 | blobmsg_add_string(&b, NULL, c); 166 | } 167 | } 168 | blobmsg_close_array(&b, k); 169 | 170 | if (source) 171 | blobmsg_add_string(&b, "source", source); 172 | 173 | if (scope) 174 | blobmsg_add_string(&b, "scope", scope); 175 | 176 | if (handle_proxy_set(blob_data(b.head), blob_len(b.head))) { 177 | fprintf(stderr, "failed to setup proxy: %s\n", argv[i]); 178 | start = false; 179 | } 180 | 181 | blob_buf_free(&b); 182 | } 183 | 184 | if (argc < 2) { 185 | usage(argv[0]); 186 | start = false; 187 | } 188 | 189 | if (start) 190 | uloop_run(); 191 | 192 | proxy_update(true); 193 | proxy_flush(); 194 | 195 | uloop_done(); 196 | return 0; 197 | } 198 | -------------------------------------------------------------------------------- /src/omcproxy.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Steven Barth 3 | * 4 | * Copyright 2015 Deutsche Telekom AG 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #ifndef OMGPROXY_H_ 21 | #define OMGPROXY_H_ 22 | 23 | #define OMGPROXY_DEFAULT_L_LEVEL 7 24 | 25 | #ifndef L_LEVEL 26 | #define L_LEVEL OMGPROXY_DEFAULT_L_LEVEL 27 | #endif /* !L_LEVEL */ 28 | 29 | #ifndef L_PREFIX 30 | #define L_PREFIX "" 31 | #endif /* !L_PREFIX */ 32 | 33 | #ifdef __APPLE__ 34 | 35 | #define __APPLE_USE_RFC_3542 36 | #define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP 37 | #define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP 38 | 39 | #include 40 | #ifdef LIST_HEAD 41 | #undef LIST_HEAD 42 | #endif /* LIST_HEAD */ 43 | 44 | #endif /* __APPLE__ */ 45 | 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | 53 | #define STR_EXPAND(tok) #tok 54 | #define STR(tok) STR_EXPAND(tok) 55 | 56 | typedef int64_t omgp_time_t; 57 | #define OMGP_TIME_MAX INT64_MAX 58 | #define OMGP_TIME_PER_SECOND INT64_C(1000) 59 | 60 | static inline omgp_time_t omgp_time(void) { 61 | struct timespec ts; 62 | clock_gettime(CLOCK_MONOTONIC, &ts); 63 | return ((omgp_time_t)ts.tv_sec * OMGP_TIME_PER_SECOND) + 64 | ((omgp_time_t)ts.tv_nsec / (1000000000 / OMGP_TIME_PER_SECOND)); 65 | } 66 | 67 | extern int log_level; 68 | 69 | // Logging macros 70 | 71 | #define L_INTERNAL(level, ...) \ 72 | do { \ 73 | if (log_level >= level) \ 74 | syslog(level, L_PREFIX __VA_ARGS__); \ 75 | } while(0) 76 | 77 | #if L_LEVEL >= LOG_ERR 78 | #define L_ERR(...) L_INTERNAL(LOG_ERR, __VA_ARGS__) 79 | #else 80 | #define L_ERR(...) do {} while(0) 81 | #endif 82 | 83 | #if L_LEVEL >= LOG_WARNING 84 | #define L_WARN(...) L_INTERNAL(LOG_WARNING, __VA_ARGS__) 85 | #else 86 | #define L_WARN(...) do {} while(0) 87 | #endif 88 | 89 | #if L_LEVEL >= LOG_NOTICE 90 | #define L_NOTICE(...) L_INTERNAL(LOG_NOTICE, __VA_ARGS__) 91 | #else 92 | #define L_NOTICE(...) do {} while(0) 93 | #endif 94 | 95 | #if L_LEVEL >= LOG_INFO 96 | #define L_INFO(...) L_INTERNAL(LOG_INFO, __VA_ARGS__) 97 | #else 98 | #define L_INFO(...) do {} while(0) 99 | #endif 100 | 101 | #if L_LEVEL >= LOG_DEBUG 102 | #define L_DEBUG(...) L_INTERNAL(LOG_DEBUG, __VA_ARGS__) 103 | #else 104 | #define L_DEBUG(...) do {} while(0) 105 | #endif 106 | 107 | 108 | // Some C99 compatibility 109 | #ifndef typeof 110 | #define typeof __typeof 111 | #endif 112 | 113 | #ifndef container_of 114 | #define container_of(ptr, type, member) ( \ 115 | (type *)( (char *)ptr - offsetof(type,member) )) 116 | #endif 117 | 118 | #ifndef __unused 119 | #define __unused __attribute__((unused)) 120 | #endif 121 | 122 | #endif /* PIMBD_H_ */ 123 | -------------------------------------------------------------------------------- /src/proxy.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Steven Barth 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | #include 19 | #include 20 | 21 | #include "querier.h" 22 | #include "client.h" 23 | #include "mrib.h" 24 | #include "proxy.h" 25 | 26 | struct proxy { 27 | struct list_head head; 28 | int ifindex; 29 | struct mrib_user mrib; 30 | struct querier querier; 31 | enum proxy_flags flags; 32 | }; 33 | 34 | struct proxy_downlink { 35 | struct querier_user_iface iface; 36 | struct mrib_user mrib; 37 | struct client client; 38 | enum proxy_flags flags; 39 | }; 40 | 41 | static struct list_head proxies = LIST_HEAD_INIT(proxies); 42 | 43 | // Remove and cleanup a downlink 44 | static void proxy_remove_downlink(struct proxy_downlink *downlink) 45 | { 46 | mrib_detach_user(&downlink->mrib); 47 | querier_detach(&downlink->iface); 48 | client_deinit(&downlink->client); 49 | free(downlink); 50 | } 51 | 52 | // Match scope of a multicast-group against proxy scope-filter 53 | static bool proxy_match_scope(enum proxy_flags flags, const struct in6_addr *addr) 54 | { 55 | unsigned scope = 0; 56 | if (IN6_IS_ADDR_V4MAPPED(addr)) { 57 | if (addr->s6_addr[12] == 239 && addr->s6_addr[13] == 255) 58 | scope = PROXY_REALMLOCAL; 59 | else if (addr->s6_addr[12] == 239 && (addr->s6_addr[13] & 0xfc) == 192) 60 | scope = PROXY_ORGLOCAL; 61 | else if (addr->s6_addr[12] == 224 && addr->s6_addr[13] == 0 && addr->s6_addr[14] == 0) 62 | scope = 2; 63 | else 64 | scope = PROXY_GLOBAL; 65 | } else { 66 | scope = addr->s6_addr[1] & 0xf; 67 | } 68 | return scope >= (flags & _PROXY_SCOPEMASK); 69 | } 70 | 71 | // Test and set multicast route (called by mrib on detection of new source) 72 | static void proxy_mrib(struct mrib_user *mrib, const struct in6_addr *group, 73 | const struct in6_addr *source, mrib_filter *filter) 74 | { 75 | struct proxy *proxy = container_of(mrib, struct proxy, mrib); 76 | if (!proxy_match_scope(proxy->flags, group)) 77 | return; 78 | 79 | omgp_time_t now = omgp_time(); 80 | struct querier_user *user; 81 | list_for_each_entry(user, &proxy->querier.ifaces, head) { 82 | if (groups_includes_group(user->groups, group, source, now)) { 83 | struct querier_user_iface *iface = container_of(user, struct querier_user_iface, user); 84 | struct proxy_downlink *downlink = container_of(iface, struct proxy_downlink, iface); 85 | mrib_filter_add(filter, &downlink->mrib); 86 | } 87 | } 88 | } 89 | 90 | // Update proxy state (called from querier on change of combined group-state) 91 | static void proxy_trigger(struct querier_user_iface *user, const struct in6_addr *group, 92 | bool include, const struct in6_addr *sources, size_t len) 93 | { 94 | struct proxy_downlink *iface = container_of(user, struct proxy_downlink, iface); 95 | if (proxy_match_scope(iface->flags, group)) 96 | client_set(&iface->client, group, include, sources, len); 97 | } 98 | 99 | // Remove proxy with given name 100 | static int proxy_unset(struct proxy *proxyp) 101 | { 102 | bool found = false; 103 | struct proxy *proxy, *n; 104 | list_for_each_entry_safe(proxy, n, &proxies, head) { 105 | if ((proxyp && proxy == proxyp) || 106 | (!proxyp && (proxy->flags & _PROXY_UNUSED))) { 107 | mrib_detach_user(&proxy->mrib); 108 | 109 | struct querier_user *user, *n; 110 | list_for_each_entry_safe(user, n, &proxy->querier.ifaces, head) { 111 | struct querier_user_iface *i = container_of(user, struct querier_user_iface, user); 112 | proxy_remove_downlink(container_of(i, struct proxy_downlink, iface)); 113 | } 114 | 115 | querier_deinit(&proxy->querier); 116 | list_del(&proxy->head); 117 | free(proxy); 118 | found = true; 119 | } 120 | } 121 | return (found) ? 0 : -ENOENT; 122 | } 123 | 124 | // Add / update proxy 125 | int proxy_set(int uplink, const int downlinks[], size_t downlinks_cnt, enum proxy_flags flags) 126 | { 127 | struct proxy *proxy = NULL, *p; 128 | list_for_each_entry(p, &proxies, head) 129 | if (p->ifindex == uplink) 130 | proxy = p; 131 | 132 | if (proxy && (downlinks_cnt == 0 || 133 | ((proxy->flags & _PROXY_SCOPEMASK) != (flags & _PROXY_SCOPEMASK)))) { 134 | proxy_unset(proxy); 135 | proxy = NULL; 136 | } 137 | 138 | if (downlinks_cnt <= 0) 139 | return 0; 140 | 141 | if (!proxy) { 142 | if (!(proxy = calloc(1, sizeof(*proxy)))) 143 | return -ENOMEM; 144 | 145 | if ((flags & _PROXY_SCOPEMASK) == 0) 146 | flags |= PROXY_GLOBAL; 147 | 148 | proxy->flags = flags; 149 | proxy->ifindex = uplink; 150 | querier_init(&proxy->querier); 151 | list_add(&proxy->head, &proxies); 152 | if (mrib_attach_user(&proxy->mrib, uplink, proxy_mrib)) 153 | goto err; 154 | } 155 | 156 | struct querier_user *user, *n; 157 | list_for_each_entry_safe(user, n, &proxy->querier.ifaces, head) { 158 | struct querier_user_iface *iface = container_of(user, struct querier_user_iface, user); 159 | 160 | size_t i; 161 | for (i = 0; i < downlinks_cnt && downlinks[i] == iface->iface->ifindex; ++i); 162 | if (i == downlinks_cnt) 163 | proxy_remove_downlink(container_of(iface, struct proxy_downlink, iface)); 164 | } 165 | 166 | for (size_t i = 0; i < downlinks_cnt; ++i) { 167 | bool found = false; 168 | struct querier_user *user; 169 | list_for_each_entry(user, &proxy->querier.ifaces, head) { 170 | struct querier_user_iface *iface = container_of(user, struct querier_user_iface, user); 171 | if (iface->iface->ifindex == downlinks[i]) { 172 | found = true; 173 | break; 174 | } 175 | } 176 | 177 | if (found) 178 | continue; 179 | 180 | struct proxy_downlink *downlink = calloc(1, sizeof(*downlink)); 181 | if (!downlink) 182 | goto err; 183 | 184 | if (client_init(&downlink->client, uplink)) 185 | goto downlink_err3; 186 | 187 | if (mrib_attach_user(&downlink->mrib, downlinks[i], NULL)) 188 | goto downlink_err2; 189 | 190 | if (querier_attach(&downlink->iface, &proxy->querier, downlinks[i], proxy_trigger)) 191 | goto downlink_err1; 192 | 193 | downlink->flags = proxy->flags; 194 | continue; 195 | 196 | downlink_err1: 197 | mrib_detach_user(&downlink->mrib); 198 | downlink_err2: 199 | client_deinit(&downlink->client); 200 | downlink_err3: 201 | free(downlink); 202 | goto err; 203 | } 204 | 205 | return 0; 206 | 207 | err: 208 | proxy_unset(proxy); 209 | return -errno; 210 | } 211 | 212 | // Mark all flushable proxies as unused 213 | void proxy_update(bool all) 214 | { 215 | struct proxy *proxy; 216 | list_for_each_entry(proxy, &proxies, head) 217 | if (all || (proxy->flags & PROXY_FLUSHABLE)) 218 | proxy->flags |= _PROXY_UNUSED; 219 | } 220 | 221 | 222 | // Flush all unused proxies 223 | void proxy_flush(void) 224 | { 225 | proxy_unset(NULL); 226 | } 227 | -------------------------------------------------------------------------------- /src/proxy.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Steven Barth 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | enum proxy_flags { 25 | // minimum scope to proxy (use only one, includes higher scopes) 26 | PROXY_REALMLOCAL = 3, 27 | PROXY_ADMINLOCAL = 4, 28 | PROXY_SITELOCAL = 5, 29 | PROXY_ORGLOCAL = 8, 30 | PROXY_GLOBAL = 0xe, 31 | 32 | // proxy may be flushed (from static config source) 33 | PROXY_FLUSHABLE = 1 << 4, 34 | 35 | // internal values 36 | _PROXY_UNUSED = 1 << 5, 37 | _PROXY_SCOPEMASK = 0xf, 38 | }; 39 | 40 | 41 | int proxy_set(int uplink, const int downlinks[], size_t downlinks_cnt, enum proxy_flags flags); 42 | 43 | 44 | void proxy_update(bool all); 45 | void proxy_flush(void); 46 | -------------------------------------------------------------------------------- /src/querier.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Steven Barth 3 | * 4 | * Copyright 2015 Deutsche Telekom AG 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "querier.h" 29 | 30 | static struct list_head ifaces = LIST_HEAD_INIT(ifaces); 31 | 32 | 33 | // Handle querier update event from a querier-interface 34 | static void querier_announce_iface(struct querier_user_iface *user, omgp_time_t now, const struct group *group, bool enabled) 35 | { 36 | bool include = true; 37 | size_t cnt = 0; 38 | struct in6_addr sources[group->source_count]; 39 | 40 | if (enabled) { 41 | struct group_source *source; 42 | group_for_each_active_source(source, group, now) 43 | sources[cnt++] = source->addr; 44 | 45 | include = group_is_included(group, now); 46 | } 47 | 48 | if (user->user_cb) 49 | user->user_cb(user, &group->addr, include, sources, cnt); 50 | } 51 | 52 | // Handle changes from a querier for a given group (called by a group-state as callback) 53 | static void querier_announce_change(struct groups *groups, struct group *group, omgp_time_t now) 54 | { 55 | struct querier_iface *iface = container_of(groups, struct querier_iface, groups); 56 | 57 | // Only recognize changes to non-link-local groups 58 | struct querier_user_iface *user; 59 | list_for_each_entry(user, &iface->users, head) 60 | querier_announce_iface(user, now, group, true); 61 | } 62 | 63 | // Send query for a group + sources (called by a group-state as callback) 64 | static void querier_send_query(struct groups *groups, const struct in6_addr *group, 65 | const struct list_head *sources, bool suppress) 66 | { 67 | struct querier_iface *iface = container_of(groups, struct querier_iface, groups); 68 | char addrbuf[INET6_ADDRSTRLEN] = "::"; 69 | inet_ntop(AF_INET6, group, addrbuf, sizeof(addrbuf)); 70 | 71 | L_DEBUG("%s: sending %s-specific query for %s on %d (S: %d)", __FUNCTION__, 72 | (!sources) ? "group" : "source", addrbuf, iface->ifindex, suppress); 73 | 74 | bool v4 = IN6_IS_ADDR_V4MAPPED(group); 75 | if (v4 && !iface->igmp_other_querier) 76 | igmp_send_query(iface, group, sources, suppress); 77 | else if (!v4 && !iface->mld_other_querier) 78 | mld_send_query(iface, group, sources, suppress); 79 | } 80 | 81 | // Expire interface timers and send queries (called by timer as callback) 82 | static void querier_iface_timer(struct uloop_timeout *timeout) 83 | { 84 | struct querier_iface *iface = container_of(timeout, struct querier_iface, timeout); 85 | omgp_time_t now = omgp_time(); 86 | omgp_time_t next_event = now + 3600 * OMGP_TIME_PER_SECOND; 87 | 88 | if (iface->igmp_next_query <= now) { 89 | // If the other querier is gone, reset interface config 90 | if (iface->igmp_other_querier) { 91 | iface->groups.cfg_v4 = iface->cfg; 92 | iface->igmp_other_querier = false; 93 | } 94 | 95 | igmp_send_query(iface, NULL, NULL, false); 96 | L_DEBUG("%s: sending generic IGMP-query on %d (S: 0)", __FUNCTION__, iface->ifindex); 97 | 98 | if (iface->igmp_startup_tries > 0) 99 | --iface->igmp_startup_tries; 100 | 101 | iface->igmp_next_query = now + ((iface->igmp_startup_tries > 0) ? 102 | (iface->groups.cfg_v4.query_interval / 4) : 103 | iface->groups.cfg_v4.query_interval); 104 | } 105 | 106 | if (iface->igmp_next_query < next_event) 107 | next_event = iface->igmp_next_query; 108 | 109 | if (iface->mld_next_query <= now) { 110 | // If the other querier is gone, reset interface config 111 | if (iface->mld_other_querier) { 112 | iface->groups.cfg_v6 = iface->cfg; 113 | iface->mld_other_querier = false; 114 | } 115 | 116 | mld_send_query(iface, NULL, NULL, false); 117 | L_DEBUG("%s: sending generic MLD-query on %d (S: 0)", __FUNCTION__, iface->ifindex); 118 | 119 | if (iface->mld_startup_tries > 0) 120 | --iface->mld_startup_tries; 121 | 122 | iface->mld_next_query = now + ((iface->mld_startup_tries > 0) ? 123 | (iface->groups.cfg_v6.query_interval / 4) : 124 | iface->groups.cfg_v6.query_interval); 125 | } 126 | 127 | if (iface->mld_next_query < next_event) 128 | next_event = iface->mld_next_query; 129 | 130 | uloop_timeout_set(&iface->timeout, (next_event > now) ? next_event - now : 0); 131 | } 132 | 133 | 134 | // Calculate QQI from QQIC 135 | int querier_qqi(uint8_t qqic) 136 | { 137 | return (qqic & 0x80) ? (((qqic & 0xf) | 0x10) << (((qqic >> 4) & 0x7) + 3)) : qqic; 138 | } 139 | 140 | // Calculate MRD from MRC 141 | int querier_mrd(uint16_t mrc) 142 | { 143 | mrc = ntohs(mrc); 144 | return (mrc & 0x8000) ? (((mrc & 0xfff) | 0x1000) << (((mrc >> 12) & 0x7) + 3)) : mrc; 145 | } 146 | 147 | // Calculate QQIC from QQI 148 | uint8_t querier_qqic(int qqi) 149 | { 150 | if (qqi >= 128) { 151 | int exp = 3; 152 | 153 | while ((qqi >> exp) > 0x1f && exp <= 10) 154 | ++exp; 155 | 156 | if (exp > 10) 157 | qqi = 0xff; 158 | else 159 | qqi = 0x80 | ((exp - 3) << 4) | ((qqi >> exp) & 0xf); 160 | } 161 | return qqi; 162 | } 163 | 164 | // Calculate MRC from MRD 165 | uint16_t querier_mrc(int mrd) 166 | { 167 | if (mrd >= 32768) { 168 | int exp = 3; 169 | 170 | while ((mrd >> exp) > 0x1fff && exp <= 10) 171 | ++exp; 172 | 173 | if (exp > 10) 174 | mrd = 0xffff; 175 | else 176 | mrd = 0x8000 | ((exp - 3) << 12) | ((mrd >> exp) & 0xfff); 177 | } 178 | return htons(mrd); 179 | } 180 | 181 | // Attach an interface to a querier-instance 182 | int querier_attach(struct querier_user_iface *user, 183 | struct querier *querier, int ifindex, querier_iface_cb *cb) 184 | { 185 | struct querier_iface *c, *iface = NULL; 186 | list_for_each_entry(c, &ifaces, head) { 187 | if (c->ifindex == ifindex) { 188 | iface = c; 189 | break; 190 | } 191 | } 192 | 193 | omgp_time_t now = omgp_time(); 194 | int res = 0; 195 | if (!iface) { 196 | if (!(iface = calloc(1, sizeof(*iface)))) { 197 | res = -errno; 198 | goto out; 199 | } 200 | 201 | list_add(&iface->head, &ifaces); 202 | INIT_LIST_HEAD(&iface->users); 203 | 204 | iface->ifindex = ifindex; 205 | iface->timeout.cb = querier_iface_timer; 206 | uloop_timeout_set(&iface->timeout, 0); 207 | 208 | groups_init(&iface->groups); 209 | iface->groups.source_limit = QUERIER_MAX_SOURCE; 210 | iface->groups.group_limit = QUERIER_MAX_GROUPS; 211 | iface->groups.cb_update = querier_announce_change; 212 | iface->groups.cb_query = querier_send_query; 213 | iface->cfg = iface->groups.cfg_v6; 214 | iface->igmp_startup_tries = iface->groups.cfg_v4.robustness; 215 | iface->mld_startup_tries = iface->groups.cfg_v6.robustness; 216 | 217 | if ((res = mrib_attach_querier(&iface->mrib, ifindex, igmp_handle, mld_handle))) 218 | goto out; 219 | } 220 | 221 | out: 222 | if (iface) { 223 | list_add(&user->head, &iface->users); 224 | user->iface = iface; 225 | 226 | list_add(&user->user.head, &querier->ifaces); 227 | user->user_cb = cb; 228 | user->user.querier = querier; 229 | user->user.groups = &iface->groups; 230 | 231 | struct group *group; 232 | groups_for_each_group(group, &iface->groups) 233 | querier_announce_iface(user, now, group, true); 234 | } 235 | 236 | if (res) 237 | querier_detach(user); 238 | return res; 239 | } 240 | 241 | // Detach an interface from a querier-instance 242 | void querier_detach(struct querier_user_iface *user) 243 | { 244 | struct querier_iface *iface = user->iface; 245 | list_del(&user->user.head); 246 | list_del(&user->head); 247 | 248 | omgp_time_t now = omgp_time(); 249 | struct group *group; 250 | groups_for_each_group(group, &iface->groups) 251 | querier_announce_iface(user, now, group, false); 252 | 253 | if (list_empty(&iface->users)) { 254 | uloop_timeout_cancel(&iface->timeout); 255 | groups_deinit(&iface->groups); 256 | mrib_detach_querier(&iface->mrib); 257 | list_del(&iface->head); 258 | free(iface); 259 | } 260 | } 261 | 262 | // Initialize querier-instance 263 | int querier_init(struct querier *querier) 264 | { 265 | memset(querier, 0, sizeof(*querier)); 266 | INIT_LIST_HEAD(&querier->ifaces); 267 | return 0; 268 | } 269 | 270 | // Cleanup querier-instance 271 | void querier_deinit(struct querier *querier) 272 | { 273 | struct querier_user *user, *n; 274 | list_for_each_entry_safe(user, n, &querier->ifaces, head) 275 | querier_detach(container_of(user, struct querier_user_iface, user)); 276 | } 277 | -------------------------------------------------------------------------------- /src/querier.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Steven Barth 3 | * 4 | * Copyright 2015 Deutsche Telekom AG 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | #pragma once 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "mrib.h" 30 | #include "groups.h" 31 | 32 | struct querier_iface { 33 | struct list_head head; 34 | struct list_head users; 35 | struct uloop_timeout timeout; 36 | struct groups_config cfg; 37 | 38 | struct uloop_fd igmp_fd; 39 | omgp_time_t igmp_next_query; 40 | bool igmp_other_querier; 41 | int igmp_startup_tries; 42 | 43 | struct uloop_fd mld_fd; 44 | omgp_time_t mld_next_query; 45 | bool mld_other_querier; 46 | int mld_startup_tries; 47 | 48 | struct mrib_querier mrib; 49 | struct groups groups; 50 | int ifindex; 51 | }; 52 | 53 | struct querier; 54 | struct querier_user; 55 | struct querier_user_iface; 56 | 57 | typedef void (querier_iface_cb)(struct querier_user_iface *user, const struct in6_addr *group, 58 | bool include, const struct in6_addr *sources, size_t len); 59 | 60 | struct querier_user { 61 | struct list_head head; 62 | struct groups *groups; 63 | struct querier *querier; 64 | }; 65 | 66 | struct querier_user_iface { 67 | struct list_head head; 68 | struct querier_user user; 69 | struct querier_iface *iface; 70 | querier_iface_cb *user_cb; 71 | }; 72 | 73 | 74 | /* External API */ 75 | int querier_init(struct querier *querier); 76 | void querier_deinit(struct querier *querier); 77 | 78 | int querier_attach(struct querier_user_iface *user, struct querier *querier, 79 | int ifindex, querier_iface_cb *cb); 80 | void querier_detach(struct querier_user_iface *user); 81 | 82 | 83 | /* Internal API */ 84 | 85 | struct querier { 86 | struct list_head ifaces; 87 | }; 88 | 89 | #define QUERIER_MAX_SOURCE 75 90 | #define QUERIER_MAX_GROUPS 256 91 | #define QUERIER_SUPPRESS (1 << 3) 92 | 93 | static inline in_addr_t querier_unmap(const struct in6_addr *addr6) 94 | { 95 | return addr6->s6_addr32[3]; 96 | } 97 | 98 | static inline void querier_map(struct in6_addr *addr6, in_addr_t addr4) 99 | { 100 | addr6->s6_addr32[0] = 0; 101 | addr6->s6_addr32[1] = 0; 102 | addr6->s6_addr32[2] = cpu_to_be32(0xffff); 103 | addr6->s6_addr32[3] = addr4; 104 | } 105 | 106 | void querier_announce(struct querier_user *user, omgp_time_t now, const struct group *group, bool enabled); 107 | void querier_synthesize_events(struct querier *querier); 108 | 109 | int querier_qqi(uint8_t qqic); 110 | int querier_mrd(uint16_t mrc); 111 | uint8_t querier_qqic(int qi); 112 | uint16_t querier_mrc(int mrd); 113 | 114 | 115 | void igmp_handle(struct mrib_querier *mrib, const struct igmphdr *igmp, size_t len, 116 | const struct sockaddr_in *from); 117 | int igmp_send_query(struct querier_iface *q, 118 | const struct in6_addr *group, 119 | const struct list_head *sources, 120 | bool suppress); 121 | 122 | 123 | void mld_handle(struct mrib_querier *mrib, const struct mld_hdr *hdr, size_t len, 124 | const struct sockaddr_in6 *from); 125 | ssize_t mld_send_query(struct querier_iface *q, 126 | const struct in6_addr *group, 127 | const struct list_head *sources, 128 | bool suppress); 129 | 130 | --------------------------------------------------------------------------------