├── .gitignore ├── LICENSE.md ├── Makefile ├── README.md ├── mcast-proxy.conf.md ├── mcast-proxy.md └── usr.sbin └── mcast-proxy ├── Makefile ├── kroute.c ├── log.c ├── log.h ├── mcast-proxy.8 ├── mcast-proxy.c ├── mcast-proxy.conf.5 ├── mcast-proxy.h ├── mrt.c ├── parse.y └── util.c /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.core 3 | *.o 4 | obj 5 | CVS 6 | *.core 7 | *.manlint 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | mcast-proxy is free software under OpenBSD's ISC-style license. 5 | 6 | * httpd is based on OpenBSD relayd 7 | * Most of the code has been written by Rafael Zalamena 8 | * With input and help from Reyk Floeter 9 | * Please refer to the individual source files for other copyright holders! 10 | 11 | > Copyright (c) 2017 Rafael Zalamena 12 | > 13 | > Permission to use, copy, modify, and distribute this software for any 14 | > purpose with or without fee is hereby granted, provided that the above 15 | > copyright notice and this permission notice appear in all copies. 16 | > 17 | > THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 18 | > WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 19 | > MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 20 | > ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 21 | > WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 22 | > ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 23 | > OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SUBDIR= usr.sbin/mcast-proxy 2 | MAKE_FLAGS= BINDIR=/usr/sbin 3 | 4 | .include 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mcast-proxy 2 | =========== 3 | 4 | About 5 | ----- 6 | 7 | This is a new daemon for OpenBSD that fills in a gap in the multicast 8 | protocol support for network edges. More specifically we're talking 9 | about a multicast proxy. 10 | 11 | The mcast-proxy is a less featured multicast routing daemon that is 12 | mostly used on equipments that face client networks (end users). It is 13 | mainly used when you don't need a full multicast routing daemon (like 14 | [dvmrpd(8)](http://man.openbsd.org/dvmrpd.8), 15 | [mrouted(8)](http://man.openbsd.org/mrouted.8) or pim), 16 | but you want to use your networks resources efficiently. 17 | This implementation has the following features: 18 | 19 | * Support IPv4 (IGMPv1/v2) multicast proxy 20 | * Support IPv6 (MLDv1) multicast proxy 21 | * Privilege dropping (runs as user) 22 | * chroot jailing 23 | 24 | The development of this daemon brought improvements to the IPv6 25 | multicast stack, like: 26 | 27 | * Initial MP support 28 | Now IPv6 multicast routing code uses the art routing table to store 29 | the multicast routes. This also means you can see your multicast 30 | routes in [route(8)](http://man.openbsd.org/route.8). 31 | * Support multiple rdomains 32 | The interfaces mif (multicast interface) are now domain specific, so 33 | you can have mif ids duplicated on different rdomains. 34 | * Fixed a few problems in MLD code that prevented some client/server 35 | functionality 36 | 37 | Notes 38 | ----- 39 | 40 | * The daemon is not yet pledge()d as there is no support for 41 | MRT(6)_* setsockopt() calls. 42 | * IPv6 multicast proxy requires an OpenBSD -current, because of 43 | the recent kernel changes and [netstat(1)](http://man.openbsd.org/netstat.1). 44 | 45 | Running mcast-proxy 46 | ------------------- 47 | 48 | To run multicast routing protocols in your machines you have to configure 49 | the following settings: 50 | 51 | * Allow multicast routing: 52 | 53 | rcctl enable multicast 54 | 55 | * Allow IGMP packets (IPv4 only): 56 | To allow IP options you have to configure your PF traffic pass rule to 57 | accept IP options. Example: change 'pass' to 'pass allow-opts'. 58 | 59 | * Add a multicast route (if the default doesn't exist or is not correct) 60 | 61 | route add 224/8 192.168.0.1 62 | route add ff00::/8 fe80::fce1:baff:fed0:2001%vio1 63 | 64 | * In case you are using the default route for multicast you might need 65 | to specify an alternate multicast source. By default mcast-proxy only 66 | accepts multicast traffic from the same network of your interface. 67 | For example em0 has IPv6 address: 2001:db8::100, 68 | but the multicast traffic comes from 2001:db9::10. The same applies for IPv4. 69 | The `mcast-proxy.conf`: 70 | 71 | interface em0 { 72 | source 2001:db9::/64 73 | upstream 74 | } 75 | 76 | Design 77 | ------ 78 | 79 | The daemon code is split in the following file hierarchy: 80 | 81 | * [mcast-proxy.c](usr.sbin/mcast-proxy/mcast-proxy.c): all IGMP/MLD related packet parsing 82 | * [mrt.c](usr.sbin/mcast-proxy/mrt.c): the multicast routing table on userland 83 | * [kroute.c](usr.sbin/mcast-proxy/kroute.c): all kernel interactions 84 | * [util.c](usr.sbin/mcast-proxy/util.c): misc functions that did not fit the other files 85 | 86 | Further Reading 87 | --------------- 88 | 89 | * [mcast-proxy.8](mcast-proxy.md) 90 | * [mcast-proxy.conf.5](mcast-proxy.conf.md) 91 | * [License](LICENSE.md) 92 | -------------------------------------------------------------------------------- /mcast-proxy.conf.md: -------------------------------------------------------------------------------- 1 | MCAST-PROXY.CONF(5) - File Formats Manual 2 | 3 | # NAME 4 | 5 | **mcast-proxy.conf** - Multicast Proxy configuration file 6 | 7 | # DESCRIPTION 8 | 9 | The 10 | mcast-proxy(8) 11 | daemon implements IGMP/MLD proxy for multicast routing. 12 | 13 | # SECTIONS 14 | 15 | The 16 | **mcast-proxy.conf** 17 | config file is divided into three main sections. 18 | 19 | **Macros** 20 | 21 | > User-defined variables may be defined and used later, simplifying the 22 | > configuration file. 23 | 24 | **Global Configuration** 25 | 26 | > Global settings for 27 | > mcast-proxy(8). 28 | > Allows the configuration of globally supported Internet Protocols 29 | > versions: IPv4 and/or IPv6. 30 | 31 | **Interfaces Configuration** 32 | 33 | > Interface-specific parameters. 34 | 35 | Argument names not beginning with a letter, digit, or underscore 36 | must be quoted. 37 | 38 | Additional configuration files can be included with the 39 | **include** 40 | keyword, for example: 41 | 42 | include "/etc/mcast-proxy.sub.conf" 43 | 44 | # MACROS 45 | 46 | Macros can be defined that will later be expanded in context. 47 | Macro names must start with a letter, digit, or underscore, 48 | and may contain any of those characters. 49 | Macro names may not be reserved words (for example, 50 | **upstreamif**, 51 | **interface**, 52 | or 53 | **default-threshold**). 54 | Macros are not expanded inside quotes. 55 | 56 | For example: 57 | 58 | upstreamif="em0" 59 | default_threshold="1" 60 | interface $upstreamif { 61 | threshold $default_threshold 62 | upstream 63 | } 64 | 65 | # GLOBAL CONFIGURATION 66 | 67 | Here are the settings that can be set globally: 68 | 69 | **ipv4** (**yes**|**no**) 70 | 71 | > Determines if the mcast-proxy will be enabled for IPv4. 72 | > This setting is enabled by default. 73 | 74 | **ipv6** (**yes**|**no**) 75 | 76 | > Determines if MLD-proxy will be enabled for IPv6. 77 | > This setting is disabled by default. 78 | 79 | # INTERFACES CONFIGURATION 80 | 81 | This section will describe the interface multicast configuration 82 | options. 83 | An interface is specified by its name. 84 | 85 | interface em0 { 86 | ... 87 | } 88 | 89 | Interface-specific parameters are listed below. 90 | 91 | **ipv4** (**yes**|**no**) 92 | 93 | > Enables or disables IPv4 support in this interface. 94 | > The default value is inherited from the global configuration. 95 | 96 | **ipv6** (**yes**|**no**) 97 | 98 | > Enables or disables IPv6 support in this interface. 99 | > The default value is inherited from the global configuration. 100 | 101 | **threshold** *number* 102 | 103 | > Specify the minimum TTL required in the incoming packets to be 104 | > forwarded (IPv4 only). The default value is 1. 105 | 106 | **source** *network*/*prefix* 107 | 108 | > Specify an alternate network to receive multicast from. 109 | > By default only multicast traffic coming from the same network of the 110 | > interface will be allowed. 111 | 112 | (**disabled**|**downstream**|**upstream**) 113 | 114 | > Configure the interface role in the multicast proxying setup. 115 | > *disabled* 116 | > will disable the interface participation, 117 | > *downstream* 118 | > mark client facing interfaces and 119 | > *upstream* 120 | > mark the interface which will receive the multicast traffic. 121 | 122 | > By default all interfaces are 123 | > *disabled*. 124 | 125 | # FILES 126 | 127 | */etc/mcast-proxy.conf* 128 | 129 | > mcast-proxy(8) 130 | > configuration file 131 | 132 | # SEE ALSO 133 | 134 | mcast-proxy(8), 135 | rc.conf.local(8) 136 | 137 | # HISTORY 138 | 139 | The 140 | **mcast-proxy.conf** 141 | file format first appeared in 142 | OpenBSD 6.2. 143 | 144 | # AUTHORS 145 | 146 | The 147 | mcast-proxy(8) 148 | program was written by 149 | Rafael Zalamena <[rzalamena@openbsd.org](mailto:rzalamena@openbsd.org)>. 150 | 151 | OpenBSD 6.1 - June 28, 2017 152 | -------------------------------------------------------------------------------- /mcast-proxy.md: -------------------------------------------------------------------------------- 1 | MCAST-PROXY(8) - System Manager's Manual 2 | 3 | # NAME 4 | 5 | **mcast-proxy** - Multicast Proxy 6 | 7 | # SYNOPSIS 8 | 9 | **mcast-proxy** 10 | \[**-dnv**] 11 | \[**-D** *macro*=*value*] 12 | \[**-f** *file*] 13 | 14 | # DESCRIPTION 15 | 16 | **mcast-proxy** 17 | is a multicast proxy implementation for the Internet Group Management 18 | Protocol (IGMP) and Multicast Listener Discovery (MLD) protocols. 19 | It is used on networks that face the client to control the multicast 20 | traffic based on the interest of the local network and to reduce the 21 | load by filtering unneeded multicast traffic. 22 | 23 | The options are as follows: 24 | 25 | **-D** *macro*=*value* 26 | 27 | > Define 28 | > *macro* 29 | > to be set to 30 | > *value* 31 | > on the command line. 32 | > Overrides the definition of 33 | > *macro* 34 | > in the configuration file. 35 | 36 | **-d** 37 | 38 | > Do not daemonize. 39 | > If this option is specified, 40 | > **mcast-proxy** 41 | > will run in the foreground and log to 42 | > *stderr*. 43 | 44 | **-f** *file* 45 | 46 | > Specify an alternative configuration file. 47 | 48 | **-n** 49 | 50 | > Only check the configuration file for validity. 51 | 52 | **-v** 53 | 54 | > Produce more verbose output. 55 | 56 | # FILES 57 | 58 | */etc/mcast-proxy.conf* 59 | 60 | > Default 61 | > **mcast-proxy** 62 | > configuration file. 63 | 64 | # SEE ALSO 65 | 66 | multicast(4), 67 | mcast-proxy.conf(5) 68 | 69 | # STANDARDS 70 | 71 | S. Deering, 72 | *Host Extensions for IP Multicasting*, 73 | RFC 1112, 74 | August 1989. 75 | 76 | W. Fenner, 77 | *Internet Group Management Protocol, Version 2*, 78 | RFC 2236, 79 | November 1997. 80 | 81 | M. Christensen, 82 | Thrane & Thrane, and 83 | K. Kimball, and 84 | F. Solensky, 85 | *Considerations for Internet Group Management Protocol (IGMP) and Multicast Listener Discovery (MLD) Snooping Switches*, 86 | RFC 4541, 87 | May 2006. 88 | 89 | B. Fenner, 90 | H. He, and 91 | B. Haberman, and 92 | H. Sandick, 93 | *Internet Group Management Protocol (IGMP) / Multicast Listener Discovery (MLD)-Based Multicast Forwarding ("IGMP/MLD Proxying")*, 94 | RFC 4605, 95 | August 2006. 96 | 97 | # HISTORY 98 | 99 | The 100 | **mcast-proxy** 101 | program first appeared in 102 | OpenBSD 6.2. 103 | 104 | # AUTHORS 105 | 106 | **mcast-proxy** 107 | was written by 108 | Rafael Zalamena <[rzalamena@openbsd.org](mailto:rzalamena@openbsd.org)> 109 | 110 | OpenBSD 6.1 - June 28, 2017 111 | -------------------------------------------------------------------------------- /usr.sbin/mcast-proxy/Makefile: -------------------------------------------------------------------------------- 1 | # $OpenBSD$ 2 | 3 | PROG= mcast-proxy 4 | SRCS= mcast-proxy.c kroute.c log.c mrt.c parse.y util.c 5 | MAN= mcast-proxy.8 mcast-proxy.conf.5 6 | 7 | CFLAGS+= -I${.CURDIR} 8 | CFLAGS+= -Wall -Wextra -Wshadow 9 | CFLAGS+= -Wmissing-prototypes -Wmissing-declarations 10 | CFLAGS+= -Wstrict-prototypes -Wpointer-arith -Wsign-compare 11 | DPADD= ${LIBEVENT} 12 | LDADD= -levent 13 | 14 | .include 15 | -------------------------------------------------------------------------------- /usr.sbin/mcast-proxy/kroute.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD:$ */ 2 | 3 | /* 4 | * Copyright (c) 2017 Rafael Zalamena 5 | * 6 | * Permission to use, copy, modify, and/or distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "mcast-proxy.h" 35 | 36 | #define MAX_RTSOCK_BUF (128 * 1024) 37 | 38 | int bad_addr_v4(struct in_addr); 39 | int bad_addr_v6(struct in6_addr *); 40 | int iacmp(struct intf_addr *, struct intf_addr *); 41 | 42 | int vif4_nextvidx(void); 43 | int vif6_nextvidx(void); 44 | 45 | void if_announce(struct if_announcemsghdr *); 46 | void if_update(unsigned short, int, struct if_data *, 47 | struct sockaddr_dl *sdl); 48 | void if_newaddr(unsigned short, struct sockaddr *, struct sockaddr *); 49 | void if_deladdr(unsigned short, struct sockaddr *, struct sockaddr *); 50 | void get_rtaddrs(int, struct sockaddr *, struct sockaddr **); 51 | void rtmsg_process(const uint8_t *, size_t); 52 | 53 | struct in6_addr in6_allrouters = IN6ADDR_LINKLOCAL_ALLROUTERS_INIT; 54 | 55 | int vindex; 56 | int vindex6; 57 | int rtsd_rcvbuf; 58 | 59 | void 60 | assert_mcastforward(void) 61 | { 62 | int mforward = 0; 63 | size_t mforwardlen = sizeof(mforward); 64 | int mib[4]; 65 | 66 | if (!ic.ic_ipv4) 67 | goto skip_v4mforwarding; 68 | 69 | mib[0] = CTL_NET; 70 | mib[1] = PF_INET; 71 | mib[2] = IPPROTO_IP; 72 | mib[3] = IPCTL_MFORWARDING; 73 | if (sysctl(mib, nitems(mib), &mforward, &mforwardlen, NULL, 0) == -1) 74 | fatal("sysctl IPv4 IPCTL_MFORWARDING"); 75 | 76 | if (!mforward) 77 | fatalx("%s: IPv4 multicast forwarding is disabled", 78 | __func__); 79 | 80 | skip_v4mforwarding: 81 | if (!ic.ic_ipv6) 82 | return; 83 | 84 | mib[0] = CTL_NET; 85 | mib[1] = PF_INET6; 86 | mib[2] = IPPROTO_IPV6; 87 | mib[3] = IPV6CTL_MFORWARDING; 88 | if (sysctl(mib, nitems(mib), &mforward, &mforwardlen, NULL, 0) == -1) 89 | fatal("sysctl IPv6 IPCTL_MFORWARDING"); 90 | 91 | if (!mforward) 92 | fatalx("%s: IPv6 multicast forwarding is disabled", 93 | __func__); 94 | } 95 | 96 | int 97 | open_igmp_socket(void) 98 | { 99 | int sd, v; 100 | uint8_t ttl = 1, loop = 0; 101 | 102 | sd = socket(AF_INET, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_IGMP); 103 | if (sd == -1) { 104 | log_warn("%s: socket", __func__); 105 | return -1; 106 | } 107 | 108 | /* Initialize the multicast routing socket. */ 109 | v = 1; 110 | if (setsockopt(sd, IPPROTO_IP, MRT_INIT, &v, sizeof(v)) == -1) { 111 | log_warn("%s: setsockopt MRT_INIT", __func__); 112 | close(sd); 113 | return -1; 114 | } 115 | 116 | /* Include IP header on packets. */ 117 | v = 1; 118 | if (setsockopt(sd, IPPROTO_IP, IP_HDRINCL, &v, sizeof(v)) == -1) { 119 | log_warn("%s: setsockopt IP_HDRINCL", __func__); 120 | close(sd); 121 | return -1; 122 | } 123 | 124 | /* Use TTL of 1 to send multicast packets. */ 125 | if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, 126 | sizeof(ttl)) == -1) { 127 | log_warn("%s: setsockopt IP_MULTICAST_TTL", __func__); 128 | close(sd); 129 | return -1; 130 | } 131 | 132 | /* Don't send multicast packets to loopback. */ 133 | if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, 134 | sizeof(loop)) == -1) { 135 | log_warn("%s: setsockopt IP_MULTICAST_LOOP", __func__); 136 | close(sd); 137 | return -1; 138 | } 139 | 140 | return sd; 141 | } 142 | 143 | int 144 | close_igmp_socket(int sd) 145 | { 146 | if (sd == -1) 147 | return 0; 148 | 149 | if (setsockopt(sd, IPPROTO_IP, MRT_DONE, NULL, 0) == -1) { 150 | log_warn("%s: setsockopt MRT_DONE", __func__); 151 | return -1; 152 | } 153 | 154 | if (close(sd) == -1) { 155 | log_warn("%s: close", __func__); 156 | return -1; 157 | } 158 | 159 | return 0; 160 | } 161 | 162 | int 163 | open_mld_socket(void) 164 | { 165 | int sd, v; 166 | unsigned int ttl = 1; 167 | 168 | sd = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_ICMPV6); 169 | if (sd == -1) { 170 | log_warn("%s: socket", __func__); 171 | return -1; 172 | } 173 | 174 | /* Initialize the multicast routing socket. */ 175 | v = 1; 176 | if (setsockopt(sd, IPPROTO_IPV6, MRT6_INIT, &v, sizeof(v)) == -1) { 177 | log_warn("%s: setsockopt MRT6_INIT", __func__); 178 | close(sd); 179 | return -1; 180 | } 181 | 182 | /* Include IP header on packets. */ 183 | v = 1; 184 | if (setsockopt(sd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &v, 185 | sizeof(v)) == -1) { 186 | log_warn("%s: setsockopt IPV6_RECVPKTINFO", __func__); 187 | close(sd); 188 | return -1; 189 | } 190 | 191 | /* Use TTL of 1 to send multicast packets. */ 192 | if (setsockopt(sd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, 193 | sizeof(ttl)) == -1) { 194 | log_warn("%s: setsockopt IPV6_MULTICAST_HOPS", __func__); 195 | close(sd); 196 | return -1; 197 | } 198 | 199 | return sd; 200 | } 201 | 202 | int 203 | close_mld_socket(int sd) 204 | { 205 | if (sd == -1) 206 | return 0; 207 | 208 | if (setsockopt(sd, IPPROTO_IPV6, MRT6_DONE, NULL, 0) == -1) { 209 | log_warn("%s: setsockopt MRT6_DONE", __func__); 210 | return -1; 211 | } 212 | 213 | if (close(sd) == -1) { 214 | log_warn("%s: close", __func__); 215 | return -1; 216 | } 217 | 218 | return 0; 219 | } 220 | 221 | int 222 | igmp_setif(struct intf_data *id) 223 | { 224 | struct intf_addr *ia; 225 | struct in_addr any; 226 | 227 | if (id == NULL) { 228 | memset(&any, 0, sizeof(any)); 229 | if (setsockopt(igmpsd, IPPROTO_IP, IP_MULTICAST_IF, 230 | &any, sizeof(any)) == -1) { 231 | log_warn("%s: setsockopt IP_MULTICAST_IF default", 232 | __func__); 233 | return -1; 234 | } 235 | return 0; 236 | } 237 | 238 | ia = intf_primaryv4(id); 239 | if (ia == NULL) 240 | return -1; 241 | 242 | if (setsockopt(igmpsd, IPPROTO_IP, IP_MULTICAST_IF, 243 | &ia->ia_addr.v4, sizeof(ia->ia_addr.v4)) == -1) { 244 | log_warn("%s: setsockopt IP_MULTICAST_IF %s", 245 | __func__, id->id_name); 246 | return -1; 247 | } 248 | 249 | return 0; 250 | } 251 | 252 | int 253 | vif_register(struct intf_data *id) 254 | { 255 | int error = 0; 256 | 257 | if (id->id_vindex == INVALID_VINDEX) 258 | error |= vif4_register(id); 259 | if (id->id_vindex6 == INVALID_VINDEX) 260 | error |= vif6_register(id); 261 | 262 | return error; 263 | } 264 | 265 | int 266 | vif_unregister(struct intf_data *id) 267 | { 268 | int error = 0; 269 | 270 | if (id->id_vindex != INVALID_VINDEX) 271 | error |= vif4_unregister(id); 272 | if (id->id_vindex != INVALID_VINDEX) 273 | error |= vif6_unregister(id); 274 | 275 | return error; 276 | } 277 | 278 | int 279 | vif4_nextvidx(void) 280 | { 281 | struct intf_data *id; 282 | int vidx; 283 | 284 | for (vidx = 0; vidx < MAXMIFS; vidx++) { 285 | SLIST_FOREACH(id, &iflist, id_entry) { 286 | if (vidx == id->id_vindex) 287 | break; 288 | } 289 | if (id != NULL) 290 | continue; 291 | 292 | return vidx; 293 | } 294 | 295 | return -1; 296 | } 297 | 298 | int 299 | vif4_register(struct intf_data *id) 300 | { 301 | struct intf_addr *ia; 302 | struct vifctl vifc; 303 | int vidx; 304 | 305 | /* Don't allow registration if not selected. */ 306 | if (!id->id_mv4) 307 | return 0; 308 | 309 | /* Already registered. */ 310 | if (id->id_vindex != INVALID_VINDEX) 311 | return 0; 312 | 313 | ia = intf_primaryv4(id); 314 | if (ia == NULL) 315 | return -1; 316 | 317 | memset(&vifc, 0, sizeof(vifc)); 318 | vifc.vifc_flags = 0; 319 | vifc.vifc_threshold = id->id_ttl; 320 | vifc.vifc_rate_limit = 0; 321 | vifc.vifc_lcl_addr = ia->ia_addr.v4; 322 | vifc.vifc_rmt_addr.s_addr = INADDR_ANY; 323 | 324 | vidx = vif4_nextvidx(); 325 | if (vidx == -1) { 326 | log_warnx("%s: no more virtual interfaces available", 327 | __func__); 328 | return -1; 329 | } 330 | 331 | vifc.vifc_vifi = id->id_vindex = vidx; 332 | log_debug("%s: %s (vindex %d) threshold %d rate %d address %s", 333 | __func__, id->id_name, id->id_vindex, id->id_ttl, 0, 334 | addr4tostr(&ia->ia_addr.v4)); 335 | 336 | if (setsockopt(igmpsd, IPPROTO_IP, MRT_ADD_VIF, &vifc, 337 | sizeof(vifc)) == -1) { 338 | id->id_vindex = INVALID_VINDEX; 339 | log_warn("%s: setsockopt MRT_ADD_VIF", __func__); 340 | return -1; 341 | } 342 | 343 | return 0; 344 | } 345 | 346 | int 347 | vif4_unregister(struct intf_data *id) 348 | { 349 | struct intf_addr *ia; 350 | struct vifctl vifc; 351 | 352 | /* Don't allow registration if not selected. */ 353 | if (!id->id_mv4) 354 | return 0; 355 | 356 | /* Already unregistered. */ 357 | if (id->id_vindex == INVALID_VINDEX) 358 | return 0; 359 | 360 | ia = intf_primaryv4(id); 361 | if (ia == NULL) 362 | return -1; 363 | 364 | memset(&vifc, 0, sizeof(vifc)); 365 | vifc.vifc_flags = 0; 366 | vifc.vifc_vifi = id->id_vindex; 367 | vifc.vifc_threshold = id->id_ttl; 368 | vifc.vifc_rate_limit = 0; 369 | vifc.vifc_lcl_addr = ia->ia_addr.v4; 370 | vifc.vifc_rmt_addr.s_addr = INADDR_ANY; 371 | 372 | log_debug("%s: %s (%d) threshold %d rate %d address %s", 373 | __func__, id->id_name, id->id_vindex, id->id_ttl, 0, 374 | addr4tostr(&ia->ia_addr.v4)); 375 | 376 | if (setsockopt(igmpsd, IPPROTO_IP, MRT_DEL_VIF, &vifc, 377 | sizeof(vifc)) == -1) { 378 | log_warn("%s: setsockopt MRT_DEL_VIF", __func__); 379 | return -1; 380 | } 381 | 382 | id->id_vindex = INVALID_VINDEX; 383 | 384 | return 0; 385 | } 386 | 387 | int 388 | vif6_nextvidx(void) 389 | { 390 | struct intf_data *id; 391 | int vidx; 392 | 393 | for (vidx = 0; vidx < MAXMIFS; vidx++) { 394 | SLIST_FOREACH(id, &iflist, id_entry) { 395 | if (vidx == id->id_vindex6) 396 | break; 397 | } 398 | if (id != NULL) 399 | continue; 400 | 401 | return vidx; 402 | } 403 | 404 | return -1; 405 | } 406 | 407 | int 408 | vif6_register(struct intf_data *id) 409 | { 410 | struct mif6ctl mif6c; 411 | int vidx; 412 | 413 | /* Don't allow registration if not selected. */ 414 | if (!id->id_mv6) 415 | return 0; 416 | 417 | /* Already registered. */ 418 | if (id->id_vindex6 != INVALID_VINDEX) 419 | return 0; 420 | 421 | memset(&mif6c, 0, sizeof(mif6c)); 422 | mif6c.mif6c_pifi = id->id_index; 423 | 424 | vidx = vif6_nextvidx(); 425 | if (vidx == -1) { 426 | log_warnx("%s: no more virtual interfaces available", 427 | __func__); 428 | return -1; 429 | } 430 | 431 | id->id_vindex6 = mif6c.mif6c_mifi = vidx; 432 | log_debug("%s: %s (vindex %d) rate %d", 433 | __func__, id->id_name, id->id_vindex6, 0); 434 | 435 | if (setsockopt(mldsd, IPPROTO_IPV6, MRT6_ADD_MIF, &mif6c, 436 | sizeof(mif6c)) == -1) { 437 | id->id_vindex6 = INVALID_VINDEX; 438 | log_warn("%s: setsockopt MRT6_ADD_MIF", __func__); 439 | return -1; 440 | } 441 | 442 | return 0; 443 | } 444 | 445 | int 446 | vif6_unregister(struct intf_data *id) 447 | { 448 | struct mif6ctl mif6c; 449 | 450 | /* Don't allow registration if not selected. */ 451 | if (!id->id_mv6) 452 | return 0; 453 | 454 | /* Already unregistered. */ 455 | if (id->id_vindex6 == INVALID_VINDEX) 456 | return 0; 457 | 458 | memset(&mif6c, 0, sizeof(mif6c)); 459 | mif6c.mif6c_pifi = id->id_index; 460 | 461 | log_debug("%s: %s (vindex %d) rate %d", 462 | __func__, id->id_name, id->id_vindex6, 0); 463 | 464 | if (setsockopt(mldsd, IPPROTO_IPV6, MRT6_DEL_MIF, &mif6c, 465 | sizeof(mif6c)) == -1) { 466 | log_warn("%s: setsockopt MRT6_DEL_MIF", __func__); 467 | return -1; 468 | } 469 | 470 | id->id_vindex6 = INVALID_VINDEX; 471 | 472 | return 0; 473 | } 474 | 475 | int 476 | mcast_join(struct intf_data *id, struct sockaddr_storage *ss) 477 | { 478 | int error = 0; 479 | 480 | if (ss == NULL) { 481 | error |= mcast4_join(id, NULL); 482 | error |= mcast6_join(id, NULL); 483 | } else { 484 | switch (ss->ss_family) { 485 | case AF_INET: 486 | error = mcast4_join(id, &sstosin(ss)->sin_addr); 487 | break; 488 | case AF_INET6: 489 | error = mcast6_join(id, &sstosin6(ss)->sin6_addr); 490 | break; 491 | 492 | default: 493 | log_debug("%s: invalid protocol %d", 494 | __func__, ss->ss_family); 495 | error = -1; 496 | } 497 | } 498 | 499 | return error; 500 | } 501 | 502 | int 503 | mcast_leave(struct intf_data *id, struct sockaddr_storage *ss) 504 | { 505 | int error = 0; 506 | 507 | if (ss == NULL) { 508 | error |= mcast4_leave(id, NULL); 509 | error |= mcast6_leave(id, NULL); 510 | } else { 511 | switch (ss->ss_family) { 512 | case AF_INET: 513 | error = mcast4_leave(id, &sstosin(ss)->sin_addr); 514 | break; 515 | case AF_INET6: 516 | error = mcast6_leave(id, &sstosin6(ss)->sin6_addr); 517 | break; 518 | 519 | default: 520 | log_debug("%s: invalid protocol %d", 521 | __func__, ss->ss_family); 522 | error = -1; 523 | } 524 | } 525 | 526 | return error; 527 | } 528 | 529 | int 530 | mcast4_join(struct intf_data *id, struct in_addr *in) 531 | { 532 | struct intf_addr *ia; 533 | struct ip_mreq imr; 534 | 535 | /* IPv4 is disabled in this interface. */ 536 | if (!id->id_mv4) 537 | return 0; 538 | 539 | ia = intf_primaryv4(id); 540 | if (ia == NULL) 541 | return -1; 542 | 543 | if (in == NULL) 544 | log_debug("%s: %s (%d) address %s group all_routers", 545 | __func__, id->id_name, id->id_vindex, 546 | addr4tostr(&ia->ia_addr.v4)); 547 | else 548 | log_debug("%s: %s (%d) address %s group %s", 549 | __func__, id->id_name, id->id_vindex, 550 | addr4tostr(&ia->ia_addr.v4), addr4tostr(in)); 551 | 552 | imr.imr_multiaddr.s_addr = (in == NULL) ? 553 | htonl(INADDR_ALLROUTERS_GROUP) : in->s_addr; 554 | imr.imr_interface = ia->ia_addr.v4; 555 | if (setsockopt(igmpsd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, 556 | sizeof(imr)) == -1) { 557 | log_debug("%s: setsockopt IP_ADD_MEMBERSHIP: %s", 558 | __func__, strerror(errno)); 559 | return -1; 560 | } 561 | 562 | return 0; 563 | } 564 | 565 | int 566 | mcast4_leave(struct intf_data *id, struct in_addr *in) 567 | { 568 | struct intf_addr *ia; 569 | struct ip_mreq imr; 570 | 571 | /* IPv4 is disabled in this interface. */ 572 | if (!id->id_mv4) 573 | return 0; 574 | 575 | ia = intf_primaryv4(id); 576 | if (ia == NULL) 577 | return -1; 578 | 579 | if (in == NULL) 580 | log_debug("%s: %s (%d) address %s group all_routers", 581 | __func__, id->id_name, id->id_vindex, 582 | addr4tostr(&ia->ia_addr.v4)); 583 | else 584 | log_debug("%s: %s (%d) address %s group %s", 585 | __func__, id->id_name, id->id_vindex, 586 | addr4tostr(&ia->ia_addr.v4), addr4tostr(in)); 587 | 588 | imr.imr_multiaddr.s_addr = (in == NULL) ? 589 | htonl(INADDR_ALLROUTERS_GROUP) : in->s_addr; 590 | imr.imr_interface = ia->ia_addr.v4; 591 | if (setsockopt(igmpsd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr, 592 | sizeof(imr)) == -1) { 593 | log_debug("%s: setsockopt IP_DROP_MEMBERSHIP: %s", 594 | __func__, strerror(errno)); 595 | return -1; 596 | } 597 | 598 | return 0; 599 | } 600 | 601 | int 602 | mcast6_join(struct intf_data *id, struct in6_addr *in6) 603 | { 604 | struct ipv6_mreq ipv6mr; 605 | 606 | /* IPv6 is disabled in this interface. */ 607 | if (!id->id_mv6) 608 | return 0; 609 | 610 | if (in6 == NULL) 611 | log_debug("%s: %s (%d) group all_routers", 612 | __func__, id->id_name, id->id_vindex6); 613 | else 614 | log_debug("%s: %s (%d) group %s", 615 | __func__, id->id_name, id->id_vindex6, addr6tostr(in6)); 616 | 617 | ipv6mr.ipv6mr_multiaddr = (in6 == NULL) ? in6_allrouters : *in6; 618 | ipv6mr.ipv6mr_interface = id->id_index; 619 | if (setsockopt(mldsd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr, 620 | sizeof(ipv6mr)) == -1) { 621 | log_debug("%s: setsockopt IPV6_JOIN_GROUP: %s", 622 | __func__, strerror(errno)); 623 | return -1; 624 | } 625 | 626 | return 0; 627 | } 628 | 629 | int 630 | mcast6_leave(struct intf_data *id, struct in6_addr *in6) 631 | { 632 | struct ipv6_mreq ipv6mr; 633 | 634 | /* IPv6 is disabled in this interface. */ 635 | if (!id->id_mv6) 636 | return 0; 637 | 638 | if (in6 == NULL) 639 | log_debug("%s: %s (%d) group all_routers", 640 | __func__, id->id_name, id->id_vindex6); 641 | else 642 | log_debug("%s: %s (%d) group %s", 643 | __func__, id->id_name, id->id_vindex6, addr6tostr(in6)); 644 | 645 | ipv6mr.ipv6mr_multiaddr = (in6 == NULL) ? in6_allrouters : *in6; 646 | ipv6mr.ipv6mr_interface = id->id_index; 647 | if (setsockopt(mldsd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &ipv6mr, 648 | sizeof(ipv6mr)) == -1) { 649 | log_warn("%s: setsockopt IPV6_LEAVE_GROUP: %s", 650 | __func__, strerror(errno)); 651 | return -1; 652 | } 653 | 654 | return 0; 655 | } 656 | 657 | int 658 | mcast_addroute(unsigned short pvidx, union uaddr *origin, 659 | union uaddr *group, struct molist *molist) 660 | { 661 | struct intf_data *id; 662 | struct multicast_origin *mo; 663 | struct mfcctl mfcc; 664 | unsigned short vidx; 665 | 666 | memset(&mfcc, 0, sizeof(mfcc)); 667 | mfcc.mfcc_origin = origin->v4; 668 | mfcc.mfcc_mcastgrp = group->v4; 669 | mfcc.mfcc_parent = pvidx; 670 | LIST_FOREACH(mo, molist, mo_entry) { 671 | id = mo->mo_id; 672 | 673 | /* Don't set upstream interface TTL. */ 674 | if (id == upstreamif) 675 | continue; 676 | 677 | vidx = id->id_vindex; 678 | if (vidx > MAXVIFS) 679 | continue; 680 | 681 | mfcc.mfcc_ttls[vidx] = id->id_ttl; 682 | } 683 | 684 | log_debug("%s: add route origin %s group %s parent %d", 685 | __func__, addr4tostr(&origin->v4), addr4tostr(&group->v4), 686 | pvidx); 687 | 688 | LIST_FOREACH(mo, molist, mo_entry) { 689 | id = mo->mo_id; 690 | vidx = id->id_vindex; 691 | if (vidx > MAXVIFS) 692 | continue; 693 | 694 | if (mfcc.mfcc_ttls[vidx]) 695 | log_debug(" vif %s (%d) ttl %d", 696 | id->id_name, vidx, mfcc.mfcc_ttls[vidx]); 697 | else 698 | log_debug(" vif %s (%d) disabled", 699 | id->id_name, vidx); 700 | } 701 | 702 | 703 | if (setsockopt(igmpsd, IPPROTO_IP, MRT_ADD_MFC, &mfcc, 704 | sizeof(mfcc)) == -1) { 705 | log_warn("%s: setsockopt MRT_ADD_MFC", __func__); 706 | return -1; 707 | } 708 | 709 | return 0; 710 | } 711 | 712 | int 713 | mcast_addroute6(unsigned short pvidx, union uaddr *origin, 714 | union uaddr *group, struct molist *molist) 715 | { 716 | struct intf_data *id; 717 | struct multicast_origin *mo; 718 | struct mf6cctl mf6cc; 719 | unsigned short vidx; 720 | 721 | memset(&mf6cc, 0, sizeof(mf6cc)); 722 | mf6cc.mf6cc_parent = pvidx; 723 | mf6cc.mf6cc_origin.sin6_family = AF_INET6; 724 | mf6cc.mf6cc_origin.sin6_addr = origin->v6; 725 | mf6cc.mf6cc_origin.sin6_len = sizeof(mf6cc.mf6cc_origin); 726 | mf6cc.mf6cc_mcastgrp.sin6_family = AF_INET6; 727 | mf6cc.mf6cc_mcastgrp.sin6_addr = group->v6; 728 | mf6cc.mf6cc_mcastgrp.sin6_len = sizeof(mf6cc.mf6cc_mcastgrp); 729 | LIST_FOREACH(mo, molist, mo_entry) { 730 | id = mo->mo_id; 731 | 732 | /* Don't set upstream interface. */ 733 | if (id == upstreamif) 734 | continue; 735 | 736 | vidx = id->id_vindex6; 737 | if (vidx > MAXMIFS) 738 | continue; 739 | 740 | IF_SET(vidx, &mf6cc.mf6cc_ifset); 741 | } 742 | 743 | log_debug("%s: add route origin %s group %s parent %d", 744 | __func__, addr6tostr(&origin->v6), addr6tostr(&group->v6), 745 | pvidx); 746 | 747 | LIST_FOREACH(mo, molist, mo_entry) { 748 | id = mo->mo_id; 749 | vidx = id->id_vindex6; 750 | if (vidx > MAXMIFS) 751 | continue; 752 | 753 | if (IF_ISSET(vidx, &mf6cc.mf6cc_ifset)) 754 | log_debug(" mif %s (%d)", 755 | id->id_name, vidx); 756 | else 757 | log_debug(" mif %s (%d) disabled", 758 | id->id_name, vidx); 759 | } 760 | 761 | 762 | if (setsockopt(mldsd, IPPROTO_IPV6, MRT6_ADD_MFC, &mf6cc, 763 | sizeof(mf6cc)) == -1) { 764 | log_warn("%s: setsockopt MRT6_ADD_MFC", __func__); 765 | return -1; 766 | } 767 | 768 | return 0; 769 | } 770 | 771 | int 772 | mcast_delroute(unsigned short pvidx, union uaddr *origin, 773 | union uaddr *group) 774 | { 775 | struct mfcctl mfcc; 776 | 777 | memset(&mfcc, 0, sizeof(mfcc)); 778 | mfcc.mfcc_origin = origin->v4; 779 | mfcc.mfcc_mcastgrp = group->v4; 780 | mfcc.mfcc_parent = pvidx; 781 | 782 | log_debug("%s: del route origin %s group %s parent %d", 783 | __func__, addr4tostr(&origin->v4), addr4tostr(&group->v4), 784 | pvidx); 785 | 786 | if (setsockopt(igmpsd, IPPROTO_IP, MRT_DEL_MFC, &mfcc, 787 | sizeof(mfcc)) == -1) { 788 | log_warn("%s: setsockopt MRT_DEL_MFC", __func__); 789 | return -1; 790 | } 791 | 792 | return 0; 793 | } 794 | 795 | int 796 | mcast_delroute6(unsigned short pvidx, union uaddr *origin, 797 | union uaddr *group) 798 | { 799 | struct mf6cctl mf6cc; 800 | 801 | memset(&mf6cc, 0, sizeof(mf6cc)); 802 | mf6cc.mf6cc_parent = pvidx; 803 | mf6cc.mf6cc_origin.sin6_addr = origin->v6; 804 | mf6cc.mf6cc_mcastgrp.sin6_addr = group->v6; 805 | 806 | log_debug("%s: del route origin %s group %s parent %d", 807 | __func__, addr6tostr(&origin->v6), addr6tostr(&group->v6), 808 | pvidx); 809 | 810 | if (setsockopt(mldsd, IPPROTO_IPV6, MRT6_DEL_MFC, &mf6cc, 811 | sizeof(mf6cc)) == -1) { 812 | log_warn("%s: setsockopt MRT_DEL6_MFC", __func__); 813 | return -1; 814 | } 815 | 816 | return 0; 817 | } 818 | 819 | void 820 | intf_dispatch(int sd, __unused short ev, __unused void *arg) 821 | { 822 | ssize_t n; 823 | static uint8_t *buf; 824 | 825 | if (buf == NULL) { 826 | buf = malloc(rtsd_rcvbuf); 827 | if (buf == NULL) 828 | fatal("%s: malloc", __func__); 829 | } 830 | 831 | n = read(sd, buf, rtsd_rcvbuf); 832 | if (n == -1) { 833 | if (errno == EAGAIN || errno == EWOULDBLOCK || 834 | errno == EINTR) 835 | return; 836 | 837 | log_warn("%s: read", __func__); 838 | return; 839 | } 840 | if (n == 0) 841 | fatalx("%s: routing socket closed", __func__); 842 | 843 | rtmsg_process(buf, n); 844 | } 845 | 846 | int 847 | intf_init(void) 848 | { 849 | size_t len; 850 | int mib[6]; 851 | uint8_t *buf; 852 | int sd, opt, rcvbuf, defrcvbuf; 853 | socklen_t optlen; 854 | 855 | mib[0] = CTL_NET; 856 | mib[1] = PF_ROUTE; 857 | mib[2] = 0; 858 | mib[3] = 0; /* wildcard */ 859 | mib[4] = NET_RT_IFLIST; 860 | mib[5] = 0; 861 | 862 | if (sysctl(mib, 6, NULL, &len, NULL, 0) == -1) 863 | fatal("%s: sysctl", __func__); 864 | if ((buf = malloc(len)) == NULL) 865 | fatal("%s: malloc", __func__); 866 | if (sysctl(mib, 6, buf, &len, NULL, 0) == -1) { 867 | free(buf); 868 | fatal("%s: sysctl", __func__); 869 | } 870 | 871 | rtmsg_process(buf, len); 872 | free(buf); 873 | 874 | sd = socket(AF_ROUTE, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); 875 | if (sd == -1) 876 | fatal("%s: socket", __func__); 877 | 878 | opt = 0; 879 | if (setsockopt(sd, SOL_SOCKET, SO_USELOOPBACK, 880 | &opt, sizeof(opt)) == -1) 881 | fatal("%s: setsockopt SO_USELOOPBACK", __func__); 882 | 883 | /* Increase the receive buffer. */ 884 | rcvbuf = MAX_RTSOCK_BUF; 885 | optlen = sizeof(rcvbuf); 886 | if (getsockopt(sd, SOL_SOCKET, SO_RCVBUF, 887 | &defrcvbuf, &optlen) == -1) 888 | log_warn("%s: getsockopt SO_RCVBUF", __func__); 889 | else 890 | for (; rcvbuf > defrcvbuf && 891 | setsockopt(sd, SOL_SOCKET, SO_RCVBUF, 892 | &rcvbuf, sizeof(rcvbuf)) == -1 && errno == ENOBUFS; 893 | rcvbuf /= 2) 894 | continue; 895 | 896 | rtsd_rcvbuf = rcvbuf; 897 | 898 | return (sd); 899 | } 900 | 901 | void 902 | if_announce(struct if_announcemsghdr *ifan) 903 | { 904 | struct intf_data *id; 905 | 906 | if (ifan->ifan_what == IFAN_DEPARTURE) { 907 | log_debug("%s departure: %s", __func__, ifan->ifan_name); 908 | 909 | id = intf_lookupbyname(ifan->ifan_name); 910 | if (id == NULL) 911 | return; 912 | 913 | id->id_enabled = 0; 914 | id->id_vindex = INVALID_VINDEX; 915 | id->id_vindex6 = INVALID_VINDEX; 916 | return; 917 | } else 918 | log_debug("%s arrival: %s", __func__, ifan->ifan_name); 919 | 920 | id = intf_lookupbyname(ifan->ifan_name); 921 | if (id == NULL) { 922 | id = id_insert(ifan->ifan_index); 923 | if (id == NULL) 924 | return; 925 | } 926 | 927 | id->id_index = ifan->ifan_index; 928 | strlcpy(id->id_name, ifan->ifan_name, sizeof(id->id_name)); 929 | } 930 | 931 | void 932 | if_update(unsigned short ifindex, int flags, struct if_data *ifd, 933 | struct sockaddr_dl *sdl) 934 | { 935 | struct intf_data *id; 936 | size_t sdllen = 0; 937 | char ifname[IFNAMSIZ]; 938 | 939 | /* Don't install loopback interfaces. */ 940 | if ((flags & IFF_LOOPBACK) == IFF_LOOPBACK) 941 | return; 942 | /* Don't install non multicast interfaces. */ 943 | if ((flags & IFF_MULTICAST) != IFF_MULTICAST) 944 | return; 945 | 946 | /* Check for sdl and copy interface name. */ 947 | if (sdl == NULL || sdl->sdl_family != AF_LINK) 948 | goto insert_interface; 949 | 950 | sdllen = (sdl->sdl_nlen >= sizeof(id->id_name)) ? 951 | (sizeof(id->id_name) - 1) : sdl->sdl_nlen; 952 | 953 | memcpy(ifname, sdl->sdl_data, sdllen); 954 | ifname[sdllen] = 0; 955 | 956 | log_debug("%s: if %s (%d)", __func__, ifname, ifindex); 957 | 958 | id = intf_lookupbyname(ifname); 959 | if (id == NULL) { 960 | insert_interface: 961 | id = id_insert(ifindex); 962 | if (id == NULL) 963 | return; 964 | } 965 | 966 | id->id_enabled = (flags & IFF_UP) && 967 | LINK_STATE_IS_UP(ifd->ifi_link_state); 968 | id->id_index = ifindex; 969 | id->id_flags = flags; 970 | id->id_rdomain = ifd->ifi_rdomain; 971 | if (sdllen > 0) 972 | strlcpy(id->id_name, ifname, sizeof(id->id_name)); 973 | } 974 | 975 | int 976 | bad_addr_v4(struct in_addr addr) 977 | { 978 | uint32_t a = ntohl(addr.s_addr); 979 | 980 | if (((a >> IN_CLASSA_NSHIFT) == 0) || 981 | ((a >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) || 982 | IN_MULTICAST(a) || IN_BADCLASS(a)) 983 | return (1); 984 | 985 | return (0); 986 | } 987 | 988 | int 989 | bad_addr_v6(struct in6_addr *addr) 990 | { 991 | if (IN6_IS_ADDR_UNSPECIFIED(addr) || 992 | IN6_IS_ADDR_LOOPBACK(addr) || 993 | IN6_IS_ADDR_MULTICAST(addr) || 994 | IN6_IS_ADDR_SITELOCAL(addr) || 995 | IN6_IS_ADDR_V4MAPPED(addr) || 996 | IN6_IS_ADDR_V4COMPAT(addr)) 997 | return (1); 998 | 999 | return (0); 1000 | } 1001 | 1002 | void 1003 | if_newaddr(unsigned short ifindex, struct sockaddr *ifa, struct sockaddr *mask) 1004 | { 1005 | struct intf_data *id; 1006 | struct intf_addr *ia; 1007 | struct sockaddr_in *ifa4, *mask4; 1008 | struct sockaddr_in6 *ifa6, *mask6; 1009 | int newaddr; 1010 | 1011 | if (ifa == NULL) 1012 | return; 1013 | 1014 | id = intf_lookupbyindex(ifindex); 1015 | if (id == NULL) { 1016 | log_debug("%s: corresponding if %d not found", 1017 | __func__, ifindex); 1018 | return; 1019 | } 1020 | 1021 | switch (ifa->sa_family) { 1022 | case AF_INET: 1023 | ifa4 = (struct sockaddr_in *) ifa; 1024 | mask4 = (struct sockaddr_in *) mask; 1025 | 1026 | /* filter out unwanted addresses */ 1027 | if (bad_addr_v4(ifa4->sin_addr)) 1028 | return; 1029 | 1030 | ia = calloc(1, sizeof(*ia)); 1031 | if (ia == NULL) 1032 | fatal("%s: calloc", __func__); 1033 | 1034 | ia->ia_addr.v4 = ifa4->sin_addr; 1035 | if (mask4) 1036 | ia->ia_prefixlen = 1037 | mask2prefixlen(mask4->sin_addr.s_addr); 1038 | 1039 | log_debug("%s: if %s (%d): %s (prefixlen %d)", 1040 | __func__, id->id_name, id->id_index, 1041 | addr4tostr(&ifa4->sin_addr), ia->ia_prefixlen); 1042 | break; 1043 | case AF_INET6: 1044 | ifa6 = (struct sockaddr_in6 *) ifa; 1045 | mask6 = (struct sockaddr_in6 *) mask; 1046 | 1047 | /* We only care about link-local and global-scope. */ 1048 | if (bad_addr_v6(&ifa6->sin6_addr)) 1049 | return; 1050 | 1051 | ia = calloc(1, sizeof(*ia)); 1052 | if (ia == NULL) 1053 | fatal("%s: calloc", __func__); 1054 | 1055 | ia->ia_addr.v6 = ifa6->sin6_addr; 1056 | if (mask6) 1057 | ia->ia_prefixlen = mask2prefixlen6(mask6); 1058 | 1059 | log_debug("%s: if %s (%d): %s (prefixlen %d)", 1060 | __func__, id->id_name, id->id_index, 1061 | addr6tostr(&ifa6->sin6_addr), ia->ia_prefixlen); 1062 | break; 1063 | default: 1064 | return; 1065 | } 1066 | 1067 | newaddr = (intf_primaryv4(id) == NULL); 1068 | 1069 | ia->ia_af = ifa->sa_family; 1070 | ia_inserttail(&id->id_ialist, ia); 1071 | 1072 | /* 1073 | * Register interface if it is a new primary address in a 1074 | * enabled interface. 1075 | */ 1076 | if (newaddr && id->id_dir != IDIR_DISABLE) { 1077 | vif_register(id); 1078 | if (id->id_dir == IDIR_DOWNSTREAM) 1079 | mcast_join(id, NULL); 1080 | } 1081 | } 1082 | 1083 | int 1084 | iacmp(struct intf_addr *ia, struct intf_addr *ian) 1085 | { 1086 | if (ia->ia_af > ian->ia_af) 1087 | return -1; 1088 | 1089 | return memcmp(&ia->ia_addr, &ian->ia_addr, (ia->ia_af == AF_INET) ? 1090 | sizeof(ia->ia_addr.v4) : sizeof(ia->ia_addr.v6)); 1091 | } 1092 | 1093 | void 1094 | if_deladdr(unsigned short ifindex, struct sockaddr *ifa, struct sockaddr *mask) 1095 | { 1096 | struct intf_data *id; 1097 | struct intf_addr iac, *ia; 1098 | struct sockaddr_in *ifa4, *mask4; 1099 | struct sockaddr_in6 *ifa6, *mask6; 1100 | int regagain = 0; 1101 | 1102 | if (ifa == NULL) 1103 | return; 1104 | 1105 | id = intf_lookupbyindex(ifindex); 1106 | if (id == NULL) { 1107 | log_debug("%s: corresponding if %d not found", 1108 | __func__, ifindex); 1109 | return; 1110 | } 1111 | 1112 | memset(&iac, 0, sizeof(iac)); 1113 | iac.ia_af = ifa->sa_family; 1114 | switch (ifa->sa_family) { 1115 | case AF_INET: 1116 | ifa4 = (struct sockaddr_in *) ifa; 1117 | mask4 = (struct sockaddr_in *) mask; 1118 | 1119 | /* filter out unwanted addresses */ 1120 | if (bad_addr_v4(ifa4->sin_addr)) 1121 | return; 1122 | 1123 | iac.ia_addr.v4 = ifa4->sin_addr; 1124 | if (mask4) 1125 | iac.ia_prefixlen = 1126 | mask2prefixlen(mask4->sin_addr.s_addr); 1127 | 1128 | log_debug("%s: if %s (%d): %s (prefixlen %d)", 1129 | __func__, id->id_name, id->id_index, 1130 | addr4tostr(&ifa4->sin_addr), iac.ia_prefixlen); 1131 | break; 1132 | case AF_INET6: 1133 | ifa6 = (struct sockaddr_in6 *) ifa; 1134 | mask6 = (struct sockaddr_in6 *) mask; 1135 | 1136 | /* We only care about link-local and global-scope. */ 1137 | if (bad_addr_v6(&ifa6->sin6_addr)) 1138 | return; 1139 | 1140 | iac.ia_addr.v6 = ifa6->sin6_addr; 1141 | if (mask6) 1142 | iac.ia_prefixlen = mask2prefixlen6(mask6); 1143 | 1144 | log_debug("%s: if %s (%d): %s (prefixlen %d)", 1145 | __func__, id->id_name, id->id_index, 1146 | addr6tostr(&ifa6->sin6_addr), iac.ia_prefixlen); 1147 | break; 1148 | default: 1149 | return; 1150 | } 1151 | 1152 | SLIST_FOREACH(ia, &id->id_ialist, ia_entry) { 1153 | if (ia->ia_af != iac.ia_af || 1154 | ia->ia_prefixlen != iac.ia_prefixlen || 1155 | iacmp(ia, &iac)) 1156 | continue; 1157 | 1158 | /* 1159 | * Unregister the interface if this is a primary 1160 | * address, then check for new primary address. 1161 | */ 1162 | if (ia->ia_af == AF_INET && ia == intf_primaryv4(id)) { 1163 | vif4_unregister(id); 1164 | if (intf_primaryv4(id) != NULL) 1165 | regagain = 1; 1166 | } 1167 | 1168 | SLIST_REMOVE(&id->id_ialist, ia, intf_addr, ia_entry); 1169 | free(ia); 1170 | 1171 | /* Re-register if there is a new primary address. */ 1172 | if (regagain) 1173 | vif4_register(id); 1174 | return; 1175 | } 1176 | } 1177 | 1178 | #define ROUNDUP(a) \ 1179 | (((a) & (sizeof(long) - 1)) ? (1 + ((a) | (sizeof(long) - 1))) : (a)) 1180 | 1181 | void 1182 | get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info) 1183 | { 1184 | int i; 1185 | 1186 | for (i = 0; i < RTAX_MAX; i++) { 1187 | if (addrs & (1 << i)) { 1188 | rti_info[i] = sa; 1189 | sa = (struct sockaddr *)((char *)(sa) + 1190 | ROUNDUP(sa->sa_len)); 1191 | } else 1192 | rti_info[i] = NULL; 1193 | } 1194 | } 1195 | 1196 | void 1197 | rtmsg_process(const uint8_t *buf, size_t len) 1198 | { 1199 | struct rt_msghdr *rtm; 1200 | struct if_msghdr ifm; 1201 | struct ifa_msghdr *ifam; 1202 | struct sockaddr *sa, *rti_info[RTAX_MAX]; 1203 | size_t offset; 1204 | const uint8_t *next; 1205 | 1206 | for (offset = 0; offset < len; offset += rtm->rtm_msglen) { 1207 | next = buf + offset; 1208 | rtm = (struct rt_msghdr *)next; 1209 | if (len < offset + sizeof(unsigned short) || 1210 | len < offset + rtm->rtm_msglen) 1211 | fatalx("%s: partial RTM in buffer", __func__); 1212 | if (rtm->rtm_version != RTM_VERSION) 1213 | continue; 1214 | 1215 | sa = (struct sockaddr *)(next + rtm->rtm_hdrlen); 1216 | get_rtaddrs(rtm->rtm_addrs, sa, rti_info); 1217 | 1218 | switch (rtm->rtm_type) { 1219 | case RTM_IFINFO: 1220 | memcpy(&ifm, next, sizeof(ifm)); 1221 | if_update(ifm.ifm_index, ifm.ifm_flags, &ifm.ifm_data, 1222 | (struct sockaddr_dl *)rti_info[RTAX_IFP]); 1223 | break; 1224 | case RTM_NEWADDR: 1225 | ifam = (struct ifa_msghdr *)rtm; 1226 | if ((ifam->ifam_addrs & (RTA_NETMASK | RTA_IFA | 1227 | RTA_BRD)) == 0) 1228 | break; 1229 | 1230 | if_newaddr(ifam->ifam_index, 1231 | (struct sockaddr *)rti_info[RTAX_IFA], 1232 | (struct sockaddr *)rti_info[RTAX_NETMASK]); 1233 | break; 1234 | case RTM_DELADDR: 1235 | ifam = (struct ifa_msghdr *)rtm; 1236 | if ((ifam->ifam_addrs & (RTA_NETMASK | RTA_IFA | 1237 | RTA_BRD)) == 0) 1238 | break; 1239 | 1240 | if_deladdr(ifam->ifam_index, 1241 | (struct sockaddr *)rti_info[RTAX_IFA], 1242 | (struct sockaddr *)rti_info[RTAX_NETMASK]); 1243 | break; 1244 | case RTM_IFANNOUNCE: 1245 | if_announce((struct if_announcemsghdr *)next); 1246 | break; 1247 | default: 1248 | break; 1249 | } 1250 | } 1251 | } 1252 | -------------------------------------------------------------------------------- /usr.sbin/mcast-proxy/log.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD:$ */ 2 | 3 | /* 4 | * Copyright (c) 2003, 2004 Henning Brauer 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | static int debug; 28 | static int verbose; 29 | const char *log_procname; 30 | 31 | void log_init(int, int); 32 | void log_procinit(const char *); 33 | void log_setverbose(int); 34 | int log_getverbose(void); 35 | void log_warn(const char *, ...) 36 | __attribute__((__format__ (printf, 1, 2))); 37 | void log_warnx(const char *, ...) 38 | __attribute__((__format__ (printf, 1, 2))); 39 | void log_info(const char *, ...) 40 | __attribute__((__format__ (printf, 1, 2))); 41 | void log_debug(const char *, ...) 42 | __attribute__((__format__ (printf, 1, 2))); 43 | void logit(int, const char *, ...) 44 | __attribute__((__format__ (printf, 2, 3))); 45 | void vlog(int, const char *, va_list) 46 | __attribute__((__format__ (printf, 2, 0))); 47 | __dead void fatal(const char *, ...) 48 | __attribute__((__format__ (printf, 1, 2))); 49 | __dead void fatalx(const char *, ...) 50 | __attribute__((__format__ (printf, 1, 2))); 51 | 52 | void 53 | log_init(int n_debug, int facility) 54 | { 55 | extern char *__progname; 56 | 57 | debug = n_debug; 58 | verbose = n_debug; 59 | log_procinit(__progname); 60 | 61 | if (!debug) 62 | openlog(__progname, LOG_PID | LOG_NDELAY, facility); 63 | 64 | tzset(); 65 | } 66 | 67 | void 68 | log_procinit(const char *procname) 69 | { 70 | if (procname != NULL) 71 | log_procname = procname; 72 | } 73 | 74 | void 75 | log_setverbose(int v) 76 | { 77 | verbose = v; 78 | } 79 | 80 | int 81 | log_getverbose(void) 82 | { 83 | return (verbose); 84 | } 85 | 86 | void 87 | logit(int pri, const char *fmt, ...) 88 | { 89 | va_list ap; 90 | 91 | va_start(ap, fmt); 92 | vlog(pri, fmt, ap); 93 | va_end(ap); 94 | } 95 | 96 | void 97 | vlog(int pri, const char *fmt, va_list ap) 98 | { 99 | char *nfmt; 100 | int saved_errno = errno; 101 | 102 | if (debug) { 103 | /* best effort in out of mem situations */ 104 | if (asprintf(&nfmt, "%s\n", fmt) == -1) { 105 | vfprintf(stderr, fmt, ap); 106 | fprintf(stderr, "\n"); 107 | } else { 108 | vfprintf(stderr, nfmt, ap); 109 | free(nfmt); 110 | } 111 | fflush(stderr); 112 | } else 113 | vsyslog(pri, fmt, ap); 114 | 115 | errno = saved_errno; 116 | } 117 | 118 | void 119 | log_warn(const char *emsg, ...) 120 | { 121 | char *nfmt; 122 | va_list ap; 123 | int saved_errno = errno; 124 | 125 | /* best effort to even work in out of memory situations */ 126 | if (emsg == NULL) 127 | logit(LOG_ERR, "%s", strerror(saved_errno)); 128 | else { 129 | va_start(ap, emsg); 130 | 131 | if (asprintf(&nfmt, "%s: %s", emsg, 132 | strerror(saved_errno)) == -1) { 133 | /* we tried it... */ 134 | vlog(LOG_ERR, emsg, ap); 135 | logit(LOG_ERR, "%s", strerror(saved_errno)); 136 | } else { 137 | vlog(LOG_ERR, nfmt, ap); 138 | free(nfmt); 139 | } 140 | va_end(ap); 141 | } 142 | 143 | errno = saved_errno; 144 | } 145 | 146 | void 147 | log_warnx(const char *emsg, ...) 148 | { 149 | va_list ap; 150 | 151 | va_start(ap, emsg); 152 | vlog(LOG_ERR, emsg, ap); 153 | va_end(ap); 154 | } 155 | 156 | void 157 | log_info(const char *emsg, ...) 158 | { 159 | va_list ap; 160 | 161 | va_start(ap, emsg); 162 | vlog(LOG_INFO, emsg, ap); 163 | va_end(ap); 164 | } 165 | 166 | void 167 | log_debug(const char *emsg, ...) 168 | { 169 | va_list ap; 170 | 171 | if (verbose > 1) { 172 | va_start(ap, emsg); 173 | vlog(LOG_DEBUG, emsg, ap); 174 | va_end(ap); 175 | } 176 | } 177 | 178 | static void 179 | vfatalc(int code, const char *emsg, va_list ap) 180 | { 181 | static char s[BUFSIZ]; 182 | const char *sep; 183 | 184 | if (emsg != NULL) { 185 | (void)vsnprintf(s, sizeof(s), emsg, ap); 186 | sep = ": "; 187 | } else { 188 | s[0] = '\0'; 189 | sep = ""; 190 | } 191 | if (code) 192 | logit(LOG_CRIT, "%s: %s%s%s", 193 | log_procname, s, sep, strerror(code)); 194 | else 195 | logit(LOG_CRIT, "%s%s%s", log_procname, sep, s); 196 | } 197 | 198 | void 199 | fatal(const char *emsg, ...) 200 | { 201 | va_list ap; 202 | 203 | va_start(ap, emsg); 204 | vfatalc(errno, emsg, ap); 205 | va_end(ap); 206 | exit(1); 207 | } 208 | 209 | void 210 | fatalx(const char *emsg, ...) 211 | { 212 | va_list ap; 213 | 214 | va_start(ap, emsg); 215 | vfatalc(0, emsg, ap); 216 | va_end(ap); 217 | exit(1); 218 | } 219 | -------------------------------------------------------------------------------- /usr.sbin/mcast-proxy/log.h: -------------------------------------------------------------------------------- 1 | /* $OpenBSD:$ */ 2 | 3 | /* 4 | * Copyright (c) 2003, 2004 Henning Brauer 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #ifndef LOG_H 20 | #define LOG_H 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | void log_init(int, int); 28 | void log_procinit(const char *); 29 | void log_setverbose(int); 30 | int log_getverbose(void); 31 | void log_warn(const char *, ...) 32 | __attribute__((__format__ (printf, 1, 2))); 33 | void log_warnx(const char *, ...) 34 | __attribute__((__format__ (printf, 1, 2))); 35 | void log_info(const char *, ...) 36 | __attribute__((__format__ (printf, 1, 2))); 37 | void log_debug(const char *, ...) 38 | __attribute__((__format__ (printf, 1, 2))); 39 | void logit(int, const char *, ...) 40 | __attribute__((__format__ (printf, 2, 3))); 41 | void vlog(int, const char *, va_list) 42 | __attribute__((__format__ (printf, 2, 0))); 43 | __dead void fatal(const char *, ...) 44 | __attribute__((__format__ (printf, 1, 2))); 45 | __dead void fatalx(const char *, ...) 46 | __attribute__((__format__ (printf, 1, 2))); 47 | 48 | #endif /* LOG_H */ 49 | -------------------------------------------------------------------------------- /usr.sbin/mcast-proxy/mcast-proxy.8: -------------------------------------------------------------------------------- 1 | .\" $OpenBSD:$ 2 | .\" 3 | .\" Copyright (c) 2017 Rafael Zalamena 4 | .\" 5 | .\" Permission to use, copy, modify, and/or distribute this software for any 6 | .\" purpose with or without fee is hereby granted, provided that the above 7 | .\" copyright notice and this permission notice appear in all copies. 8 | .\" 9 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | .\" 17 | .Dd $Mdocdate$ 18 | .Dt MCAST-PROXY 8 19 | .Os 20 | .Sh NAME 21 | .Nm mcast-proxy 22 | .Nd Multicast Proxy 23 | .Sh SYNOPSIS 24 | .Nm 25 | .Op Fl dnv 26 | .Op Fl D Ar macro Ns = Ns Ar value 27 | .Op Fl f Ar file 28 | .Sh DESCRIPTION 29 | .Nm 30 | is a multicast proxy implementation for the Internet Group Management 31 | Protocol (IGMP) and Multicast Listener Discovery (MLD) protocols. 32 | It is used on networks that face the client to control the multicast 33 | traffic based on the interest of the local network and to reduce the 34 | load by filtering unneeded multicast traffic. 35 | .Pp 36 | The options are as follows: 37 | .Bl -tag -width Ds 38 | .It Fl D Ar macro Ns = Ns Ar value 39 | Define 40 | .Ar macro 41 | to be set to 42 | .Ar value 43 | on the command line. 44 | Overrides the definition of 45 | .Ar macro 46 | in the configuration file. 47 | .It Fl d 48 | Do not daemonize. 49 | If this option is specified, 50 | .Nm 51 | will run in the foreground and log to 52 | .Em stderr . 53 | .It Fl f Ar file 54 | Specify an alternative configuration file. 55 | .It Fl n 56 | Only check the configuration file for validity. 57 | .It Fl v 58 | Produce more verbose output. 59 | .El 60 | .Sh FILES 61 | .Bl -tag -width "/etc/mcast-proxy.confXX" 62 | .It Pa /etc/mcast-proxy.conf 63 | Default 64 | .Nm 65 | configuration file. 66 | .El 67 | .Sh SEE ALSO 68 | .Xr multicast 4 , 69 | .Xr mcast-proxy.conf 5 70 | .Sh STANDARDS 71 | .Rs 72 | .%A S. Deering 73 | .%D August 1989 74 | .%R RFC 1112 75 | .%T Host Extensions for IP Multicasting 76 | .Re 77 | .Pp 78 | .Rs 79 | .%A W. Fenner 80 | .%D November 1997 81 | .%R RFC 2236 82 | .%T Internet Group Management Protocol, Version 2 83 | .Re 84 | .Pp 85 | .Rs 86 | .%A M. Christensen 87 | .%A Thrane & Thrane 88 | .%A K. Kimball 89 | .%A F. Solensky 90 | .%D May 2006 91 | .%R RFC 4541 92 | .%T Considerations for Internet Group Management Protocol (IGMP) and Multicast Listener Discovery (MLD) Snooping Switches 93 | .Re 94 | .Pp 95 | .Rs 96 | .%A B. Fenner 97 | .%A H. He 98 | .%A B. Haberman 99 | .%A H. Sandick 100 | .%D August 2006 101 | .%R RFC 4605 102 | .%T Internet Group Management Protocol (IGMP) / Multicast Listener Discovery (MLD)-Based Multicast Forwarding ("IGMP/MLD Proxying") 103 | .Re 104 | .Sh HISTORY 105 | The 106 | .Nm 107 | program first appeared in 108 | .Ox 6.2 . 109 | .Sh AUTHORS 110 | .An -nosplit 111 | .Nm 112 | was written by 113 | .An Rafael Zalamena Aq Mt rzalamena@openbsd.org 114 | -------------------------------------------------------------------------------- /usr.sbin/mcast-proxy/mcast-proxy.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD:$ */ 2 | 3 | /* 4 | * Copyright (c) 2017 Rafael Zalamena 5 | * 6 | * Permission to use, copy, modify, and/or distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "mcast-proxy.h" 37 | 38 | __dead void usage(void); 39 | __dead void daemon_shutdown(void); 40 | void sighandler(int, short, void *); 41 | void config_setdefaults(void); 42 | 43 | int mcast_mquery4(struct intf_data *, struct in_addr *, struct in_addr *); 44 | int mcast_mquery6(struct intf_data *, struct in6_addr *, struct in6_addr *); 45 | int build_packet(uint8_t *, size_t *, struct intf_data *, struct in_addr *, 46 | struct in_addr *, uint8_t, uint8_t); 47 | int build_packet6(uint8_t *, size_t *, struct intf_data *, 48 | struct in6_addr *, uint8_t, uint8_t); 49 | int kernel_parse(uint8_t *, size_t); 50 | int kernel_parsev6(uint8_t *, size_t); 51 | struct igmp *igmp_parse(uint8_t *, size_t *, struct sockaddr_storage *); 52 | const char *igmptypetostr(uint16_t); 53 | void intf_setup(void); 54 | void send_generalmquery(int, short, void *); 55 | void igmp_recv(int, short, void *); 56 | const char *mldtypetostr(uint16_t); 57 | int mld_parse(struct intf_data *, struct sockaddr_storage *, uint8_t *, 58 | size_t); 59 | void mld_recv(int, short, void *); 60 | 61 | struct iflist iflist; 62 | struct intf_data *upstreamif; 63 | int igmpsd = -1; 64 | int mldsd = -1; 65 | 66 | const char *config_file = "/etc/mcast-proxy.conf"; 67 | struct igmpproxy_conf ic; 68 | 69 | int 70 | main(int argc, char *argv[]) 71 | { 72 | struct passwd *pw; 73 | int verbose = 0, daemonize = 1, noaction = 0; 74 | int ch, intfsd; 75 | struct timeval qtv; 76 | struct event igmpev, mldev, intfev, qtimerev; 77 | struct event hupev, termev, intev; 78 | 79 | config_setdefaults(); 80 | 81 | /* Load all system interfaces and get their information. */ 82 | intfsd = intf_init(); 83 | 84 | /* Initiate with verbose logging. */ 85 | log_init(1, LOG_DAEMON); 86 | log_setverbose(1); 87 | 88 | while ((ch = getopt(argc, argv, "f:D:dnv")) != -1) { 89 | switch (ch) { 90 | case 'D': 91 | if (cmdline_symset(optarg) < 0) 92 | log_warnx("could not parse macro definition %s", 93 | optarg); 94 | break; 95 | case 'd': 96 | daemonize = 0; 97 | break; 98 | case 'f': 99 | config_file = optarg; 100 | break; 101 | case 'n': 102 | noaction = 1; 103 | break; 104 | case 'v': 105 | verbose = 2; 106 | break; 107 | default: 108 | usage(); 109 | break; 110 | } 111 | } 112 | 113 | if (parse_config(config_file) == -1) 114 | fatalx("configuration failed"); 115 | 116 | if (noaction) 117 | exit(0); 118 | 119 | /* Assert that we can run multicast forwarding. */ 120 | assert_mcastforward(); 121 | 122 | /* Create the IGMP socket. */ 123 | if (ic.ic_ipv4) 124 | igmpsd = open_igmp_socket(); 125 | if (ic.ic_ipv6) 126 | mldsd = open_mld_socket(); 127 | 128 | /* Drop privileges. */ 129 | pw = getpwnam(MCAST_PROXY_USER); 130 | if (pw == NULL) 131 | fatal("getpwnam"); 132 | 133 | if (chroot(pw->pw_dir) == -1) 134 | fatal("chroot"); 135 | if (chdir("/") == -1) 136 | fatal("chdir"); 137 | 138 | if (setgroups(1, &pw->pw_gid) || 139 | setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 140 | setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 141 | fatal("privilege drop"); 142 | 143 | /* Use the configured logging verbosity. */ 144 | log_init(!daemonize, LOG_DAEMON); 145 | log_setverbose(verbose); 146 | 147 | if (daemonize) 148 | daemon(0, 0); 149 | 150 | log_info("startup"); 151 | 152 | /* Initialize libevent. */ 153 | event_init(); 154 | 155 | /* Install signal handlers. */ 156 | signal_set(&hupev, SIGHUP, sighandler, NULL); 157 | signal_set(&intev, SIGINT, sighandler, NULL); 158 | signal_set(&termev, SIGTERM, sighandler, NULL); 159 | signal_add(&hupev, NULL); 160 | signal_add(&intev, NULL); 161 | signal_add(&termev, NULL); 162 | signal(SIGPIPE, SIG_IGN); 163 | 164 | event_set(&igmpev, igmpsd, EV_READ | EV_PERSIST, 165 | igmp_recv, NULL); 166 | event_add(&igmpev, NULL); 167 | event_set(&mldev, mldsd, EV_READ | EV_PERSIST, 168 | mld_recv, NULL); 169 | event_add(&mldev, NULL); 170 | event_set(&intfev, intfsd, EV_READ | EV_PERSIST, 171 | intf_dispatch, NULL); 172 | event_add(&intfev, NULL); 173 | 174 | qtv.tv_sec = IGMP_STARTUP_QUERY_INTERVAL; 175 | qtv.tv_usec = 0; 176 | evtimer_set(&qtimerev, send_generalmquery, &qtimerev); 177 | evtimer_add(&qtimerev, &qtv); 178 | 179 | /* Initialize interfaces IGMP reception. */ 180 | intf_setup(); 181 | 182 | #if 0 183 | if (pledge("stdio inet", NULL) != 0) 184 | fatal("pledge"); 185 | #endif 186 | 187 | /* Send the startup query. */ 188 | send_generalmquery(0, 0, &qtimerev); 189 | 190 | event_dispatch(); 191 | 192 | daemon_shutdown(); 193 | 194 | return 0; 195 | } 196 | 197 | __dead void 198 | usage(void) 199 | { 200 | extern const char *__progname; 201 | 202 | fprintf(stderr, "%s: [-dnv] [-D macro=value] [-f config]\n", 203 | __progname); 204 | 205 | exit(1); 206 | } 207 | 208 | __dead void 209 | daemon_shutdown(void) 210 | { 211 | struct intf_data *id; 212 | int error = 0; 213 | 214 | /* Clean up routes to make sure no interface references exist. */ 215 | mrt_cleanup(); 216 | upstreamif = NULL; 217 | 218 | /* Remove all interfaces. */ 219 | while (!SLIST_EMPTY(&iflist)) { 220 | id = SLIST_FIRST(&iflist); 221 | id_free(id); 222 | } 223 | 224 | /* Close multicast sockets. */ 225 | error |= close_igmp_socket(igmpsd); 226 | error |= close_mld_socket(mldsd); 227 | igmpsd = -1; 228 | mldsd = -1; 229 | 230 | exit(error != 0); 231 | } 232 | 233 | void 234 | sighandler(int sig, __unused short ev, __unused void *arg) 235 | { 236 | switch (sig) { 237 | case SIGHUP: 238 | /* FALLTHROUGH */ 239 | case SIGTERM: 240 | case SIGINT: 241 | log_info("received signal %d", sig); 242 | daemon_shutdown(); 243 | break; 244 | } 245 | } 246 | 247 | void 248 | config_setdefaults(void) 249 | { 250 | ic.ic_ipv4 = 1; 251 | ic.ic_ipv6 = 0; 252 | } 253 | 254 | const char * 255 | igmptypetostr(uint16_t type) 256 | { 257 | switch (type) { 258 | case IGMP_HOST_MEMBERSHIP_QUERY: 259 | return "MEMBERSHIP_QUERY"; 260 | case IGMP_v1_HOST_MEMBERSHIP_REPORT: 261 | return "MEMBERSHIP_REPORT_V1"; 262 | case IGMP_v2_HOST_MEMBERSHIP_REPORT: 263 | return "MEMBERSHIP_REPORT_V2"; 264 | case IGMP_HOST_LEAVE_MESSAGE: 265 | return "LEAVE"; 266 | 267 | default: 268 | return "unknown"; 269 | } 270 | } 271 | 272 | int 273 | build_packet(uint8_t *p, size_t *plen, struct intf_data *id, 274 | struct in_addr *dst, struct in_addr *grp, uint8_t type, uint8_t code) 275 | { 276 | struct ip *ip = (struct ip *)p; 277 | struct intf_addr *ia; 278 | struct igmp *igmp; 279 | uint8_t hlen; 280 | 281 | *plen = 0; 282 | 283 | if ((ia = intf_primaryv4(id)) == NULL) { 284 | log_debug("%s doesn't have an address", id->id_name); 285 | return -1; 286 | } 287 | 288 | memset(ip, 0, sizeof(*ip)); 289 | hlen = sizeof(*ip) >> 2; 290 | ip->ip_hl = hlen; 291 | ip->ip_v = IPVERSION; 292 | ip->ip_tos = IPTOS_PREC_INTERNETCONTROL; 293 | ip->ip_ttl = IPDEFTTL; 294 | ip->ip_p = IPPROTO_IGMP; 295 | ip->ip_src = ia->ia_addr.v4; 296 | ip->ip_dst = *dst; 297 | *plen = hlen << 2; 298 | 299 | igmp = (struct igmp *)(p + sizeof(*ip)); 300 | igmp->igmp_type = type; 301 | igmp->igmp_code = code; 302 | igmp->igmp_cksum = 0; 303 | igmp->igmp_group = *grp; 304 | *plen += sizeof(*igmp); 305 | 306 | /* Calculate the IP checksum. */ 307 | ip->ip_len = htons(*plen); 308 | ip->ip_sum = wrapsum(checksum((uint8_t *)ip, hlen, 0)); 309 | 310 | /* Calculate the IGMP checksum. */ 311 | igmp->igmp_cksum = wrapsum(checksum((uint8_t *)igmp, 312 | sizeof(*igmp), 0)); 313 | 314 | return 0; 315 | } 316 | 317 | int 318 | mcast_mquery4(struct intf_data *id, struct in_addr *dst, struct in_addr *grp) 319 | { 320 | struct intf_addr *ia; 321 | size_t blen; 322 | ssize_t bsent; 323 | struct sockaddr_storage to; 324 | uint8_t b[2048]; 325 | 326 | if ((ia = intf_primaryv4(id)) == NULL) { 327 | log_debug("%s doesn't have an address", id->id_name); 328 | return -1; 329 | } 330 | 331 | blen = sizeof(b); 332 | if (build_packet(b, &blen, id, dst, grp, 333 | IGMP_HOST_MEMBERSHIP_QUERY, IGMP_QUERY_INTERVAL) == -1) { 334 | log_debug("%s: packet build failed", __func__); 335 | return -1; 336 | } 337 | 338 | igmp_setif(id); 339 | 340 | to.ss_family = AF_INET; 341 | to.ss_len = sizeof(struct sockaddr_in); 342 | sstosin(&to)->sin_addr = *dst; 343 | if ((bsent = sendto(igmpsd, b, blen, 0, (struct sockaddr *)&to, 344 | to.ss_len)) == -1) { 345 | log_warn("send IGMP %s (via %s) to %s", 346 | addr4tostr(&ia->ia_addr.v4), id->id_name, addrtostr(&to)); 347 | return -1; 348 | } 349 | 350 | igmp_setif(NULL); 351 | 352 | log_debug("%s (%s) -> %s IGMP MEMBERSHIP_QUERY %ld bytes", 353 | addr4tostr(&ia->ia_addr.v4), id->id_name, addrtostr(&to), 354 | bsent); 355 | 356 | return 0; 357 | } 358 | 359 | int 360 | build_packet6(uint8_t *p, size_t *plen, struct intf_data *id, 361 | struct in6_addr *grp, uint8_t type, uint8_t code) 362 | { 363 | struct intf_addr *ia; 364 | struct mld_hdr *mld; 365 | 366 | *plen = 0; 367 | 368 | if ((ia = intf_ipv6linklayer(id)) == NULL) { 369 | log_debug("%s doesn't have an address", id->id_name); 370 | return -1; 371 | } 372 | 373 | mld = (struct mld_hdr *)p; 374 | mld->mld_type = type; 375 | mld->mld_code = code; 376 | mld->mld_cksum = 0; 377 | mld->mld_maxdelay = 0; 378 | mld->mld_reserved = 0; 379 | mld->mld_addr = *grp; 380 | *plen += sizeof(*mld); 381 | 382 | return 0; 383 | } 384 | 385 | int 386 | mcast_mquery6(struct intf_data *id, struct in6_addr *dst, 387 | struct in6_addr *grp) 388 | { 389 | struct intf_addr *ia; 390 | struct cmsghdr *cmsg; 391 | struct in6_pktinfo *ipi6; 392 | size_t blen; 393 | ssize_t bsent; 394 | struct msghdr msg; 395 | struct sockaddr_storage to; 396 | struct iovec iov[1]; 397 | uint8_t b[2048]; 398 | uint8_t cmsgbuf[ 399 | CMSG_SPACE(sizeof(struct in6_pktinfo)) 400 | ]; 401 | 402 | if ((ia = intf_ipv6linklayer(id)) == NULL) { 403 | log_debug("%s doesn't have an address", id->id_name); 404 | return -1; 405 | } 406 | 407 | blen = sizeof(b); 408 | if (build_packet6(b, &blen, id, grp, MLD_LISTENER_QUERY, 0) == -1) { 409 | log_debug("%s: packet build failed", __func__); 410 | return -1; 411 | } 412 | 413 | to.ss_family = AF_INET6; 414 | to.ss_len = sizeof(struct sockaddr_in6); 415 | sstosin6(&to)->sin6_addr = *dst; 416 | 417 | /* Populate msghdr. */ 418 | memset(&msg, 0, sizeof(msg)); 419 | iov[0].iov_base = b; 420 | iov[0].iov_len = blen; 421 | msg.msg_iov = iov; 422 | msg.msg_iovlen = 1; 423 | msg.msg_name = &to; 424 | msg.msg_namelen = sizeof(struct sockaddr_in6); 425 | 426 | /* Populate msghdr parameters. */ 427 | memset(cmsgbuf, 0, sizeof(cmsgbuf)); 428 | msg.msg_control = cmsgbuf; 429 | msg.msg_controllen = sizeof(cmsgbuf); 430 | 431 | /* Use the IPV6_PKTINFO to select the interface. */ 432 | cmsg = (struct cmsghdr *)CMSG_FIRSTHDR(&msg); 433 | cmsg->cmsg_len = CMSG_LEN(sizeof(*ipi6)); 434 | cmsg->cmsg_level = IPPROTO_IPV6; 435 | cmsg->cmsg_type = IPV6_PKTINFO; 436 | 437 | /* Set output interface */ 438 | ipi6 = (struct in6_pktinfo *)CMSG_DATA(cmsg); 439 | ipi6->ipi6_ifindex = id->id_index; 440 | 441 | if ((bsent = sendmsg(mldsd, &msg, 0)) == -1) { 442 | log_warn("MLD %s (via %s) to %s", 443 | addr6tostr(&ia->ia_addr.v6), id->id_name, addrtostr(&to)); 444 | return -1; 445 | } 446 | 447 | log_debug("%s (%s) -> %s MLD MEMBERSHIP_QUERY %ld bytes", 448 | addr6tostr(&ia->ia_addr.v6), id->id_name, addrtostr(&to), 449 | bsent); 450 | 451 | return 0; 452 | } 453 | 454 | int 455 | kernel_parse(uint8_t *p, size_t plen) 456 | { 457 | struct intf_addr *ia; 458 | struct intf_data *id; 459 | struct ip *ip = (struct ip *)p; 460 | 461 | /* Sanity check: do we have enough data to work with? */ 462 | if (plen < sizeof(*ip)) { 463 | log_debug("%s: insufficient packet size", __func__); 464 | return 0; 465 | } 466 | 467 | /* Validate upstream interface current state. */ 468 | if (upstreamif == NULL) { 469 | log_debug("%s: no upstream interface", __func__); 470 | return 0; 471 | } 472 | if ((ia = intf_primaryv4(upstreamif)) == NULL) { 473 | log_debug("%s: no upstream interface address", __func__); 474 | return 0; 475 | } 476 | 477 | if (ip->ip_src.s_addr == INADDR_ANY || 478 | ip->ip_dst.s_addr == INADDR_ANY) { 479 | log_debug("%s: invalid packet addresses", __func__); 480 | return 0; 481 | } 482 | 483 | /* We only handle kernel messages here. */ 484 | if (ip->ip_p != IPPROTO_IP) 485 | return -1; 486 | 487 | id = intf_lookupbyaddr4(ip->ip_src.s_addr); 488 | if (id == NULL || !id->id_enabled) { 489 | log_debug("%s: no interface matches origin", __func__); 490 | return 0; 491 | } 492 | 493 | mrt_insert4(MV_IGMPV3, id, &ip->ip_src, &ip->ip_dst); 494 | 495 | return 0; 496 | } 497 | 498 | struct igmp * 499 | igmp_parse(uint8_t *p, size_t *plen, struct sockaddr_storage *src) 500 | { 501 | struct ip *ip = (struct ip *)p; 502 | size_t hlen, ptotal; 503 | uint16_t cksum; 504 | 505 | if (ip->ip_p != IPPROTO_IGMP) { 506 | log_debug("%s: expected IGMP message, got %d", 507 | __func__, ip->ip_p); 508 | return NULL; 509 | } 510 | 511 | /* IP header validations. */ 512 | if (ip->ip_v != IPVERSION) { 513 | log_debug("%s: wrong IP version", __func__); 514 | return 0; 515 | } 516 | hlen = ip->ip_hl << 2; 517 | if (hlen < sizeof(*ip)) { 518 | log_debug("%s: wrong IP header length", __func__); 519 | return 0; 520 | } 521 | if ((ip->ip_off & htons(IP_OFFMASK)) != 0) { 522 | log_debug("%s: fragmented packet", __func__); 523 | return 0; 524 | } 525 | if (ip->ip_ttl == 0) { 526 | log_debug("%s: invalid TTL", __func__); 527 | return 0; 528 | } 529 | 530 | hlen = ip->ip_hl << 2; 531 | 532 | ptotal = ntohs(ip->ip_len); 533 | if (*plen != ptotal) { 534 | log_debug("%s: IP header length different than packet " 535 | "(%ld vs %ld)", __func__, ptotal, *plen); 536 | return 0; 537 | } 538 | 539 | cksum = wrapsum(checksum((uint8_t *)ip, hlen, 0)); 540 | if (cksum != 0) { 541 | log_debug("%s: IP checksum is invalid", __func__); 542 | return NULL; 543 | } 544 | 545 | cksum = wrapsum(checksum((uint8_t *)ip, *plen, 0)); 546 | if (cksum != 0) { 547 | log_debug("%s: IGMP invalid checksum", __func__); 548 | return NULL; 549 | } 550 | 551 | log_debug("IGMP (IPv%d) %s -> %s %ld bytes", 552 | ip->ip_v, addr4tostr(&ip->ip_src), addr4tostr(&ip->ip_dst), 553 | *plen); 554 | 555 | /* Return the source address and update the remaining size. */ 556 | memset(src, 0, sizeof(*src)); 557 | src->ss_family = AF_INET; 558 | src->ss_len = sizeof(struct sockaddr_in); 559 | sstosin(src)->sin_addr = ip->ip_src; 560 | 561 | *plen -= hlen; 562 | 563 | return ((struct igmp *)(p + hlen)); 564 | } 565 | 566 | void 567 | igmp_recv(int fd, __unused short ev, __unused void *arg) 568 | { 569 | struct igmp *igmp; 570 | struct intf_data *id; 571 | ssize_t rlen; 572 | struct sockaddr_storage src; 573 | uint8_t p[2048]; 574 | 575 | if ((rlen = recv(fd, p, sizeof(p), 0)) == -1) { 576 | log_warn("%s: recv", __func__); 577 | return; 578 | } 579 | /* Check for kernel messages and do IP header validations. */ 580 | if (kernel_parse(p, rlen) == 0 || 581 | (igmp = igmp_parse(p, &rlen, &src)) == NULL) 582 | return; 583 | 584 | /* Handle the IGMP packet. */ 585 | if ((size_t)rlen < sizeof(*igmp)) { 586 | log_debug("%s: IGMP packet too short", __func__); 587 | return; 588 | } 589 | 590 | log_debug(" %s: code %d group %s", 591 | igmptypetostr(igmp->igmp_type), igmp->igmp_code, 592 | addr4tostr(&igmp->igmp_group)); 593 | 594 | /* Sanity check: group is always multicast address. */ 595 | if (!IN_MULTICAST(ntohl(igmp->igmp_group.s_addr))) { 596 | log_debug("%s: group is not a multicast address", 597 | __func__); 598 | return; 599 | } 600 | 601 | /* Determine from which interface this packet came from. */ 602 | id = intf_lookupbyaddr4(sstosin(&src)->sin_addr.s_addr); 603 | if (id == NULL || !id->id_enabled) { 604 | log_debug("%s: no interface matches origin", __func__); 605 | return; 606 | } 607 | 608 | /* Don't receive commands from upstream interface. */ 609 | if (id == upstreamif) { 610 | log_debug("%s: ignoring host command on upstream interface", 611 | __func__); 612 | return; 613 | } 614 | 615 | switch (igmp->igmp_type) { 616 | case IGMP_HOST_MEMBERSHIP_QUERY: 617 | break; 618 | case IGMP_v1_HOST_MEMBERSHIP_REPORT: 619 | mrt_insert4(MV_IGMPV1, id, &sstosin(&src)->sin_addr, 620 | &igmp->igmp_group); 621 | break; 622 | case IGMP_v2_HOST_MEMBERSHIP_REPORT: 623 | mrt_insert4(MV_IGMPV2, id, &sstosin(&src)->sin_addr, 624 | &igmp->igmp_group); 625 | break; 626 | case IGMP_HOST_LEAVE_MESSAGE: 627 | mrt_remove4(id, &sstosin(&src)->sin_addr, &igmp->igmp_group); 628 | break; 629 | } 630 | } 631 | 632 | const char * 633 | mldtypetostr(uint16_t type) 634 | { 635 | switch (type) { 636 | case MLD_LISTENER_QUERY: 637 | return "LISTENER_QUERY"; 638 | case MLD_LISTENER_REPORT: 639 | return "LISTENER_REPORT"; 640 | case MLD_LISTENER_DONE: 641 | return "LISTENER_DONE"; 642 | 643 | default: 644 | return "unknown"; 645 | } 646 | } 647 | 648 | int 649 | kernel_parsev6(uint8_t *p, size_t plen) 650 | { 651 | struct ip6_hdr *ip6 = (struct ip6_hdr *)p; 652 | struct intf_data *id; 653 | 654 | /* Sanity checks: 655 | * - packet size (ipv6 header) 656 | * - multicast destination 657 | */ 658 | if (plen < sizeof(*ip6)) { 659 | log_debug("%s: packet too small for IPv6 header", __func__); 660 | return -1; 661 | } 662 | if (!IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { 663 | log_debug("%s: not a multicast packet", __func__); 664 | return -1; 665 | } 666 | 667 | id = intf_lookupbyaddr6(&ip6->ip6_src); 668 | if (id == NULL || !id->id_enabled) { 669 | log_debug("%s: no input interface for %s", 670 | __func__, addr6tostr(&ip6->ip6_src)); 671 | return -1; 672 | } 673 | 674 | log_debug("IPv6 %s (%s) -> %s", 675 | addr6tostr(&ip6->ip6_src), id->id_name, 676 | addr6tostr(&ip6->ip6_dst)); 677 | 678 | mrt_insert6(MV_IGMPV3, id, &ip6->ip6_src, &ip6->ip6_dst); 679 | 680 | return 0; 681 | } 682 | 683 | int 684 | mld_parse(struct intf_data *id, struct sockaddr_storage *src, 685 | uint8_t *p, size_t plen) 686 | { 687 | struct mld_hdr *mld = (struct mld_hdr *)p; 688 | 689 | if (plen < sizeof(*mld)) { 690 | log_debug("%s: packet too small", __func__); 691 | return -1; 692 | } 693 | 694 | log_debug("MLD %s %s -> %s", mldtypetostr(mld->mld_type), 695 | addrtostr(src), addr6tostr(&mld->mld_addr)); 696 | 697 | switch (mld->mld_type) { 698 | case MLD_LISTENER_QUERY: 699 | break; 700 | case MLD_LISTENER_REPORT: 701 | mrt_insert6(MV_IGMPV2, id, &sstosin6(src)->sin6_addr, 702 | &mld->mld_addr); 703 | break; 704 | case MLD_LISTENER_DONE: 705 | mrt_remove6(id, &sstosin6(src)->sin6_addr, &mld->mld_addr); 706 | break; 707 | 708 | default: 709 | log_debug("%s: invalid MLD type %d", 710 | __func__, mld->mld_type); 711 | break; 712 | } 713 | 714 | return 0; 715 | } 716 | 717 | void 718 | mld_recv(int sd, __unused short ev, __unused void *arg) 719 | { 720 | struct in6_pktinfo *ipi6 = NULL; 721 | struct intf_data *id; 722 | struct cmsghdr *cmsg; 723 | ssize_t rlen; 724 | struct msghdr msg; 725 | struct iovec iov[1]; 726 | struct sockaddr_storage ss; 727 | uint8_t iovbuf[2048]; 728 | uint8_t cmsgbuf[ 729 | CMSG_SPACE(sizeof(*ipi6)) 730 | ]; 731 | 732 | iov[0].iov_base = iovbuf; 733 | iov[0].iov_len = sizeof(iovbuf); 734 | 735 | memset(&msg, 0, sizeof(msg)); 736 | msg.msg_iov = iov; 737 | msg.msg_iovlen = 1; 738 | msg.msg_control = cmsgbuf; 739 | msg.msg_controllen = sizeof(cmsgbuf); 740 | msg.msg_name = &ss; 741 | msg.msg_namelen = sizeof(ss); 742 | if ((rlen = recvmsg(sd, &msg, 0)) == -1) { 743 | log_warn("%s: recvmsg", __func__); 744 | return; 745 | } 746 | 747 | /* Sanity check: is this IPv6? */ 748 | if (ss.ss_family != AF_INET6) { 749 | log_debug("%s: received non IPv6 packet", __func__); 750 | return; 751 | } 752 | 753 | /* Find out input interface. */ 754 | for (cmsg = (struct cmsghdr *)CMSG_FIRSTHDR(&msg); cmsg; 755 | cmsg = (struct cmsghdr *)CMSG_NXTHDR(&msg, cmsg)) { 756 | if (cmsg->cmsg_level != IPPROTO_IPV6) 757 | continue; 758 | 759 | switch (cmsg->cmsg_type) { 760 | case IPV6_PKTINFO: 761 | ipi6 = (struct in6_pktinfo *)CMSG_DATA(cmsg); 762 | break; 763 | } 764 | } 765 | /* Kernel messages from the routing socket don't have PKTINFO. */ 766 | if (ipi6 == NULL) { 767 | kernel_parsev6(iovbuf, rlen); 768 | return; 769 | } 770 | 771 | /* Deal with packets coming from the network. */ 772 | id = intf_lookupbyindex(ipi6->ipi6_ifindex); 773 | if (id == NULL || !id->id_enabled) { 774 | log_debug("%s: no input interface for %s", 775 | __func__, addrtostr(&ss)); 776 | return; 777 | } 778 | 779 | /* Don't receive commands from upstream interface. */ 780 | if (id == upstreamif) { 781 | log_debug("%s: ignoring host on upstream interface", 782 | __func__); 783 | return; 784 | } 785 | 786 | mld_parse(id, &ss, iovbuf, rlen); 787 | } 788 | 789 | void 790 | send_generalmquery(__unused int sd, short ev, void *arg) 791 | { 792 | struct event *qtimerev = (struct event *)arg; 793 | struct intf_data *id; 794 | struct timeval qtv = { IGMP_QUERY_INTERVAL, 0 }; 795 | struct in_addr allhostsgrp, zerogrp; 796 | struct in6_addr allhostsgrp6 = 797 | IN6ADDR_LINKLOCAL_ALLNODES_INIT; 798 | struct in6_addr zerogrp6 = IN6ADDR_ANY_INIT; 799 | 800 | allhostsgrp.s_addr = htonl(INADDR_ALLHOSTS_GROUP); 801 | zerogrp.s_addr = 0; 802 | 803 | SLIST_FOREACH(id, &iflist, id_entry) { 804 | /* Only join downstream interfaces. */ 805 | if (id->id_dir != IDIR_DOWNSTREAM) 806 | continue; 807 | 808 | if (id->id_mv4) 809 | mcast_mquery4(id, &allhostsgrp, &zerogrp); 810 | if (id->id_mv6) 811 | mcast_mquery6(id, &allhostsgrp6, &zerogrp6); 812 | } 813 | 814 | /* Only start timers if not called manually. */ 815 | if ((ev & EV_TIMEOUT) == EV_TIMEOUT) { 816 | evtimer_add(qtimerev, &qtv); 817 | mrt_querytimeradd(); 818 | } 819 | } 820 | 821 | void 822 | intf_setup(void) 823 | { 824 | struct intf_data *id; 825 | 826 | SLIST_FOREACH(id, &iflist, id_entry) { 827 | /* Disable IPv4 multicast if disabled globally. */ 828 | if (ic.ic_ipv4 == 0) 829 | id->id_mv4 = 0; 830 | /* Disable IPv6 multicast if disabled globally. */ 831 | if (ic.ic_ipv6 == 0) 832 | id->id_mv6 = 0; 833 | 834 | if (id->id_dir == IDIR_DISABLE) 835 | continue; 836 | 837 | /* Register all enabled interfaces. */ 838 | vif_register(id); 839 | 840 | if (id->id_dir != IDIR_DOWNSTREAM) 841 | continue; 842 | 843 | /* Only join downstream interfaces. */ 844 | mcast_join(id, NULL); 845 | } 846 | } 847 | -------------------------------------------------------------------------------- /usr.sbin/mcast-proxy/mcast-proxy.conf.5: -------------------------------------------------------------------------------- 1 | .\" $OpenBSD:$ 2 | .\" 3 | .\" Copyright (c) 2017 Rafael Zalamena 4 | .\" 5 | .\" Permission to use, copy, modify, and distribute this software for any 6 | .\" purpose with or without fee is hereby granted, provided that the above 7 | .\" copyright notice and this permission notice appear in all copies. 8 | .\" 9 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | .\" 17 | .Dd $Mdocdate$ 18 | .Dt MCAST-PROXY.CONF 5 19 | .Os 20 | .Sh NAME 21 | .Nm mcast-proxy.conf 22 | .Nd Multicast Proxy configuration file 23 | .Sh DESCRIPTION 24 | The 25 | .Xr mcast-proxy 8 26 | daemon implements IGMP/MLD proxy for multicast routing. 27 | .Sh SECTIONS 28 | The 29 | .Nm 30 | config file is divided into three main sections. 31 | .Bl -tag -width xxxx 32 | .It Sy Macros 33 | User-defined variables may be defined and used later, simplifying the 34 | configuration file. 35 | .It Sy Global Configuration 36 | Global settings for 37 | .Xr mcast-proxy 8 . 38 | Allows the configuration of globally supported Internet Protocols 39 | versions: IPv4 and/or IPv6. 40 | .It Sy Interfaces Configuration 41 | Interface-specific parameters. 42 | .El 43 | .Pp 44 | Argument names not beginning with a letter, digit, or underscore 45 | must be quoted. 46 | .Pp 47 | Additional configuration files can be included with the 48 | .Ic include 49 | keyword, for example: 50 | .Bd -literal -offset indent 51 | include "/etc/mcast-proxy.sub.conf" 52 | .Ed 53 | .Sh MACROS 54 | Macros can be defined that will later be expanded in context. 55 | Macro names must start with a letter, digit, or underscore, 56 | and may contain any of those characters. 57 | Macro names may not be reserved words (for example, 58 | .Ic upstreamif , 59 | .Ic interface , 60 | or 61 | .Ic default-threshold ) . 62 | Macros are not expanded inside quotes. 63 | .Pp 64 | For example: 65 | .Bd -literal -offset indent 66 | upstreamif="em0" 67 | default_threshold="1" 68 | interface $upstreamif { 69 | threshold $default_threshold 70 | upstream 71 | } 72 | .Ed 73 | .Sh GLOBAL CONFIGURATION 74 | Here are the settings that can be set globally: 75 | .Bl -tag -width Ds 76 | .It Ic ipv4 Pq Ic yes Ns | Ns Ic no 77 | Determines if the mcast-proxy will be enabled for IPv4. 78 | This setting is enabled by default. 79 | .It Ic ipv6 Pq Ic yes Ns | Ns Ic no 80 | Determines if MLD-proxy will be enabled for IPv6. 81 | This setting is disabled by default. 82 | .El 83 | .Sh INTERFACES CONFIGURATION 84 | This section will describe the interface multicast configuration 85 | options. 86 | An interface is specified by its name. 87 | .Bd -literal -offset indent 88 | interface em0 { 89 | ... 90 | } 91 | .Ed 92 | .Pp 93 | Interface-specific parameters are listed below. 94 | .Bl -tag -width Ds 95 | .It Ic ipv4 Pq Ic yes Ns | Ns Ic no 96 | Enables or disables IPv4 support in this interface. 97 | The default value is inherited from the global configuration. 98 | .It Ic ipv6 Pq Ic yes Ns | Ns Ic no 99 | Enables or disables IPv6 support in this interface. 100 | The default value is inherited from the global configuration. 101 | .It Ic threshold Ar number 102 | Specify the minimum TTL required in the incoming packets to be 103 | forwarded (IPv4 only). The default value is 1. 104 | .It Ic source Ar network Ns / Ns Ar prefix 105 | Specify an alternate network to receive multicast from. 106 | By default only multicast traffic coming from the same network of the 107 | interface will be allowed. 108 | .It Pq Ic disabled Ns | Ns Ic downstream Ns | Ns Ic upstream 109 | Configure the interface role in the multicast proxying setup. 110 | .Ar disabled 111 | will disable the interface participation, 112 | .Ar downstream 113 | mark client facing interfaces and 114 | .Ar upstream 115 | mark the interface which will receive the multicast traffic. 116 | .Pp 117 | By default all interfaces are 118 | .Ar disabled . 119 | .El 120 | .Sh FILES 121 | .Bl -tag -width "/etc/mcast-proxy.conf" -compact 122 | .It Pa /etc/mcast-proxy.conf 123 | .Xr mcast-proxy 8 124 | configuration file 125 | .El 126 | .Sh SEE ALSO 127 | .Xr mcast-proxy 8 , 128 | .Xr rc.conf.local 8 129 | .Sh HISTORY 130 | The 131 | .Nm 132 | file format first appeared in 133 | .Ox 6.2 . 134 | .Sh AUTHORS 135 | The 136 | .Xr mcast-proxy 8 137 | program was written by 138 | .An Rafael Zalamena Aq Mt rzalamena@openbsd.org . 139 | -------------------------------------------------------------------------------- /usr.sbin/mcast-proxy/mcast-proxy.h: -------------------------------------------------------------------------------- 1 | /* $OpenBSD:$ */ 2 | 3 | /* 4 | * Copyright (c) 2017 Rafael Zalamena 5 | * 6 | * Permission to use, copy, modify, and/or distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #ifndef MCAST_PROXY_H 20 | #define MCAST_PROXY_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | #include "log.h" 33 | 34 | #define MCAST_PROXY_USER "_mcastproxy" 35 | 36 | /* RFC 2236 section 8: value definitions. */ 37 | #define IGMP_QUERY_INTERVAL 125 /* 125 seconds. */ 38 | #define IGMP_RESPONSE_INTERVAL 10 /* 10 seconds. */ 39 | #define IGMP_ROBUSTNESS_DEFVALUE 2 40 | #define IGMP_STARTUP_QUERY_INTERVAL (IGMP_QUERY_INTERVAL * 0.25) 41 | /* 42 | * RFC 2236 Section 8.4: Group membership interval. 43 | * Group membership interval is composed by the following formula: 44 | * (Robustness * Query_Interval) + Query_Response_Interval. 45 | */ 46 | #define IGMP_GROUP_MEMBERSHIP_INTERVAL(r, q) \ 47 | (((r) * (q)) + IGMP_RESPONSE_INTERVAL) 48 | 49 | /* Signalize invalid virtual/multicast interface index. */ 50 | #define INVALID_VINDEX ((uint16_t)-1) 51 | 52 | /* Interface direction configuration values. */ 53 | enum intf_direction { 54 | IDIR_DISABLE = 0, 55 | IDIR_DOWNSTREAM, 56 | IDIR_UPSTREAM, 57 | }; 58 | 59 | enum mr_version { 60 | MV_UNKNOWN, 61 | MV_IGMPV1, 62 | MV_IGMPV2, /* or MLDv1. */ 63 | MV_IGMPV3, /* or MLDv2. */ 64 | }; 65 | 66 | union uaddr { 67 | struct in_addr v4; 68 | struct in6_addr v6; 69 | }; 70 | 71 | struct intf_addr { 72 | SLIST_ENTRY(intf_addr) ia_entry; 73 | int ia_af; 74 | union uaddr ia_addr; 75 | uint8_t ia_prefixlen; 76 | }; 77 | SLIST_HEAD(ialist, intf_addr); 78 | 79 | struct intf_data { 80 | SLIST_ENTRY(intf_data) id_entry; 81 | 82 | /* Interface status. */ 83 | int id_enabled; 84 | /* Interface name. */ 85 | char id_name[IFNAMSIZ]; 86 | /* Interface index. */ 87 | unsigned int id_index; 88 | /* Interface rdomain. */ 89 | unsigned int id_rdomain; 90 | /* Interface flags. */ 91 | unsigned int id_flags; 92 | /* Interface IPv4 list. */ 93 | struct ialist id_ialist; 94 | /* Interface alternative networks. */ 95 | struct ialist id_altnetlist; 96 | 97 | /* Multicast configurations. */ 98 | 99 | /* Virtual interface index. */ 100 | uint16_t id_vindex; 101 | /* Virtual IPv6 interface index. */ 102 | uint16_t id_vindex6; 103 | /* Interface direction configuration. */ 104 | enum intf_direction id_dir; 105 | /* Acceptable TTL threshold. */ 106 | uint8_t id_ttl; 107 | /* Use IPv4 multicast. */ 108 | int id_mv4; 109 | /* Use IPv6 multicast. */ 110 | int id_mv6; 111 | }; 112 | SLIST_HEAD(iflist, intf_data); 113 | 114 | struct multicast_origin { 115 | LIST_ENTRY(multicast_origin) mo_entry; 116 | int mo_alive; 117 | int mo_af; 118 | struct intf_data *mo_id; 119 | union uaddr mo_addr; 120 | }; 121 | LIST_HEAD(molist, multicast_origin); 122 | 123 | struct igmpproxy_conf { 124 | int ic_ipv4; 125 | int ic_ipv6; 126 | }; 127 | 128 | /* igmp-proxy.c */ 129 | extern struct intf_data *upstreamif; 130 | extern struct iflist iflist; 131 | extern int igmpsd; 132 | extern int mldsd; 133 | extern struct igmpproxy_conf ic; 134 | 135 | /* kroute.c */ 136 | void assert_mcastforward(void); 137 | int intf_init(void); 138 | int igmp_setif(struct intf_data *); 139 | int vif_register(struct intf_data *); 140 | int vif_unregister(struct intf_data *); 141 | int vif4_register(struct intf_data *); 142 | int vif4_unregister(struct intf_data *); 143 | int vif6_register(struct intf_data *); 144 | int vif6_unregister(struct intf_data *); 145 | void intf_dispatch(int, short, void *); 146 | void intf_load(void); 147 | int open_igmp_socket(void); 148 | int close_igmp_socket(int); 149 | int open_mld_socket(void); 150 | int close_mld_socket(int); 151 | int mcast_join(struct intf_data *, struct sockaddr_storage *); 152 | int mcast_leave(struct intf_data *, struct sockaddr_storage *); 153 | int mcast4_join(struct intf_data *, struct in_addr *); 154 | int mcast4_leave(struct intf_data *, struct in_addr *); 155 | int mcast6_join(struct intf_data *, struct in6_addr *); 156 | int mcast6_leave(struct intf_data *, struct in6_addr *); 157 | int mcast_addroute(unsigned short, union uaddr *, union uaddr *, 158 | struct molist *); 159 | int mcast_addroute6(unsigned short, union uaddr *, union uaddr *, 160 | struct molist *); 161 | int mcast_delroute(unsigned short, union uaddr *, union uaddr *); 162 | int mcast_delroute6(unsigned short, union uaddr *, union uaddr *); 163 | 164 | /* util.c */ 165 | const char *addrtostr(struct sockaddr_storage *); 166 | const char *addr4tostr(struct in_addr *); 167 | const char *addr6tostr(struct in6_addr *); 168 | int id_matchaddr4(struct intf_data *, uint32_t); 169 | int id_matchaddr6(struct intf_data *, struct in6_addr *); 170 | uint16_t checksum(uint8_t *, uint16_t, uint32_t); 171 | uint16_t wrapsum(uint16_t); 172 | struct intf_data *id_insert(unsigned short); 173 | struct intf_data *id_new(void); 174 | void id_free(struct intf_data *); 175 | void ia_inserttail(struct ialist *, struct intf_addr *); 176 | struct intf_data *intf_lookupbyname(const char *); 177 | struct intf_data *intf_lookupbyaddr4(uint32_t); 178 | struct intf_data *intf_lookupbyaddr6(struct in6_addr *); 179 | struct intf_data *intf_lookupbyindex(unsigned short); 180 | struct intf_addr *intf_primaryv4(struct intf_data *); 181 | struct intf_addr *intf_ipv6linklayer(struct intf_data *); 182 | uint8_t mask2prefixlen(in_addr_t); 183 | uint8_t mask2prefixlen6(struct sockaddr_in6 *); 184 | in_addr_t prefixlen2mask(uint8_t); 185 | void applymask(int, union uaddr *, const union uaddr *, int); 186 | 187 | /* mrt.c */ 188 | void mrt_querytimeradd(void); 189 | struct multicast_route *mrt_insert4(enum mr_version, struct intf_data *, 190 | struct in_addr *, struct in_addr *); 191 | struct multicast_route *mrt_insert6(enum mr_version, struct intf_data *, 192 | struct in6_addr *, struct in6_addr *); 193 | void mrt_remove4(struct intf_data *, struct in_addr *, struct in_addr *); 194 | void mrt_remove6(struct intf_data *, struct in6_addr *, struct in6_addr *); 195 | void mrt_cleanup(void); 196 | 197 | /* parse.y */ 198 | int cmdline_symset(const char *); 199 | int parse_config(const char *); 200 | 201 | /* Helpers */ 202 | static inline struct sockaddr_in * 203 | sstosin(struct sockaddr_storage *ss) 204 | { 205 | return (struct sockaddr_in *)ss; 206 | } 207 | 208 | static inline struct sockaddr_in6 * 209 | sstosin6(struct sockaddr_storage *ss) 210 | { 211 | return (struct sockaddr_in6 *)ss; 212 | } 213 | 214 | #endif /* MCAST_PROXY_H */ 215 | -------------------------------------------------------------------------------- /usr.sbin/mcast-proxy/mrt.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD:$ */ 2 | 3 | /* 4 | * Copyright (c) 2017 Rafael Zalamena 5 | * 6 | * Permission to use, copy, modify, and/or distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | #include "mcast-proxy.h" 25 | 26 | enum mr_state { 27 | MS_NOTJOINED, 28 | MS_JOINED, 29 | }; 30 | 31 | struct multicast_route { 32 | RB_ENTRY(multicast_route) mr_entry; 33 | 34 | enum mr_state mr_state; 35 | enum mr_version mr_version; 36 | int mr_af; 37 | union uaddr mr_group; 38 | struct event mr_timer; 39 | /* Version timer. */ 40 | struct event mr_vtimer; 41 | /* Lowest version recorded during the version timer. */ 42 | enum mr_version mr_lowestversion; 43 | struct intf_data *mr_upstream; 44 | 45 | /* Origin list. */ 46 | struct molist mr_molist; 47 | }; 48 | RB_HEAD(mrtree, multicast_route) mrtree = RB_INITIALIZER(&mrtree); 49 | 50 | struct multicast_origin *mo_lookup(struct molist *, struct intf_data *, 51 | union uaddr *); 52 | struct multicast_origin *mrt_addorigin(struct multicast_route *, 53 | struct intf_data *,union uaddr *); 54 | void _mrt_delorigin(struct multicast_route *, struct multicast_origin *); 55 | void mrt_delorigin(struct multicast_route *, struct intf_data *, 56 | union uaddr *); 57 | 58 | void mrt_timeradd(struct event *); 59 | void mrt_timer(int, short, void *); 60 | void mrt_vtimeradd(struct multicast_route *); 61 | void mrt_vtimer(int, short, void *); 62 | struct multicast_route *mrt_new(void); 63 | void mrt_free(struct multicast_route *); 64 | struct multicast_route *mrt_find4(struct in_addr *); 65 | struct multicast_route *mrt_find6(struct in6_addr *); 66 | int mrcmp(struct multicast_route *, struct multicast_route *); 67 | RB_PROTOTYPE(mrtree, multicast_route, mr_entry, mrcmp); 68 | void mrt_nextstate(struct multicast_route *); 69 | 70 | struct multicast_origin * 71 | mo_lookup(struct molist *molist, struct intf_data *id, union uaddr *addr) 72 | { 73 | struct multicast_origin *mo; 74 | size_t addrsize; 75 | 76 | LIST_FOREACH(mo, molist, mo_entry) { 77 | addrsize = (mo->mo_af == AF_INET) ? 78 | sizeof(addr->v4) : sizeof(addr->v6); 79 | if (id != NULL && id != mo->mo_id) 80 | continue; 81 | if (memcmp(addr, &mo->mo_addr, addrsize) != 0) 82 | continue; 83 | 84 | return mo; 85 | } 86 | 87 | return NULL; 88 | } 89 | 90 | struct multicast_origin * 91 | mrt_addorigin(struct multicast_route *mr, struct intf_data *id, 92 | union uaddr *addr) 93 | { 94 | struct multicast_origin *mo; 95 | 96 | mo = mo_lookup(&mr->mr_molist, id, addr); 97 | if (mo != NULL) { 98 | /* Update the kernel routes in case they have expired. */ 99 | if (mr->mr_upstream != NULL) { 100 | if (mo->mo_af == AF_INET) 101 | mcast_addroute(mr->mr_upstream->id_vindex, 102 | addr, &mr->mr_group, &mr->mr_molist); 103 | else 104 | mcast_addroute6(mr->mr_upstream->id_vindex6, 105 | addr, &mr->mr_group, &mr->mr_molist); 106 | } 107 | mo->mo_alive = 1; 108 | return mo; 109 | } 110 | 111 | mo = calloc(1, sizeof(*mo)); 112 | if (mo == NULL) { 113 | log_warn("%s: calloc", __func__); 114 | return NULL; 115 | } 116 | 117 | LIST_INSERT_HEAD(&mr->mr_molist, mo, mo_entry); 118 | 119 | mo->mo_alive = 1; 120 | mo->mo_id = id; 121 | mo->mo_af = mr->mr_af; 122 | mo->mo_addr = *addr; 123 | if (id == upstreamif || mr->mr_upstream) { 124 | if (mr->mr_upstream == NULL) 125 | mr->mr_upstream = upstreamif; 126 | 127 | if (mo->mo_af == AF_INET) 128 | mcast_addroute(mr->mr_upstream->id_vindex, addr, 129 | &mr->mr_group, &mr->mr_molist); 130 | else 131 | mcast_addroute6(mr->mr_upstream->id_vindex6, addr, 132 | &mr->mr_group, &mr->mr_molist); 133 | } 134 | 135 | return mo; 136 | } 137 | 138 | void 139 | _mrt_delorigin(struct multicast_route *mr, struct multicast_origin *mo) 140 | { 141 | LIST_REMOVE(mo, mo_entry); 142 | 143 | if (mr->mr_upstream != NULL) { 144 | /* 145 | * If this was the last item of the group list we can 146 | * uninstall the whole group, otherwise update the 147 | * installed routes with the current origins. 148 | */ 149 | if (LIST_EMPTY(&mr->mr_molist)) { 150 | if (mo->mo_af == AF_INET) 151 | mcast_delroute(mr->mr_upstream->id_vindex, 152 | &mo->mo_addr, &mr->mr_group); 153 | else 154 | mcast_delroute6(mr->mr_upstream->id_vindex6, 155 | &mo->mo_addr, &mr->mr_group); 156 | } else { 157 | if (mo->mo_af == AF_INET) 158 | mcast_addroute(mr->mr_upstream->id_vindex, 159 | &mo->mo_addr, &mr->mr_group, 160 | &mr->mr_molist); 161 | else 162 | mcast_addroute6(mr->mr_upstream->id_vindex6, 163 | &mo->mo_addr, &mr->mr_group, 164 | &mr->mr_molist); 165 | } 166 | } 167 | 168 | free(mo); 169 | } 170 | 171 | void 172 | mrt_delorigin(struct multicast_route *mr, struct intf_data *id, 173 | union uaddr *addr) 174 | { 175 | struct multicast_origin *mo; 176 | 177 | mo = mo_lookup(&mr->mr_molist, id, addr); 178 | if (mo == NULL) 179 | return; 180 | 181 | _mrt_delorigin(mr, mo); 182 | } 183 | 184 | void 185 | mrt_timeradd(struct event *ev) 186 | { 187 | unsigned long total = IGMP_GROUP_MEMBERSHIP_INTERVAL( 188 | IGMP_ROBUSTNESS_DEFVALUE, IGMP_RESPONSE_INTERVAL); 189 | struct timeval tv; 190 | 191 | if (evtimer_pending(ev, &tv)) 192 | evtimer_del(ev); 193 | 194 | tv.tv_sec = total; 195 | tv.tv_usec = 0; 196 | evtimer_add(ev, &tv); 197 | } 198 | 199 | void 200 | mrt_querytimeradd(void) 201 | { 202 | struct multicast_route *mr; 203 | 204 | /* Activate all group expire timers. */ 205 | RB_FOREACH(mr, mrtree, &mrtree) { 206 | mrt_timeradd(&mr->mr_timer); 207 | } 208 | } 209 | 210 | void 211 | mrt_vtimeradd(struct multicast_route *mr) 212 | { 213 | mrt_timeradd(&mr->mr_vtimer); 214 | } 215 | 216 | void 217 | mrt_timer(__unused int sd, __unused short ev, void *arg) 218 | { 219 | struct multicast_route *mr = arg; 220 | struct multicast_origin *mo, *mon; 221 | 222 | if (mr->mr_af == AF_INET) 223 | log_debug("%s: group %s timer expired", 224 | __func__, addr4tostr(&mr->mr_group.v4)); 225 | else 226 | log_debug("%s: group %s timer expired", 227 | __func__, addr6tostr(&mr->mr_group.v6)); 228 | 229 | /* Remove origins that did not respond. */ 230 | LIST_FOREACH_SAFE(mo, &mr->mr_molist, mo_entry, mon) { 231 | if (mo->mo_alive) { 232 | /* Mark as dead until next update. */ 233 | mo->mo_alive = 0; 234 | continue; 235 | } 236 | 237 | _mrt_delorigin(mr, mo); 238 | } 239 | 240 | mrt_nextstate(mr); 241 | 242 | /* Remove the group if there is no more origins. */ 243 | if (LIST_EMPTY(&mr->mr_molist)) 244 | mrt_free(mr); 245 | } 246 | 247 | void 248 | mrt_vtimer(__unused int sd, __unused short ev, void *arg) 249 | { 250 | struct multicast_route *mr = arg; 251 | 252 | if (mr->mr_af == AF_INET) 253 | log_debug("%s: group %s version timer expired", 254 | __func__, addr4tostr(&mr->mr_group.v4)); 255 | else 256 | log_debug("%s: group %s version timer expired", 257 | __func__, addr6tostr(&mr->mr_group.v6)); 258 | 259 | mrt_vtimeradd(mr); 260 | 261 | /* 262 | * Apply the RFC 2236 section 5 and RFC 4541 section 2.1.1 sub 263 | * item 1: the IGMPv2 is the most compatible version of the 264 | * protocol. 265 | * 266 | * This is the default fallback version. 267 | */ 268 | if (mr->mr_version == MV_IGMPV2) 269 | return; 270 | 271 | /* 272 | * If we are on a 'special' version, reset the lowest value and 273 | * expect another report with a version different than v2. If no 274 | * new reports with different version comes in, assume that 275 | * there are no more to enter a compatibility mode. 276 | */ 277 | mr->mr_version = mr->mr_lowestversion; 278 | mr->mr_lowestversion = MV_IGMPV2; 279 | } 280 | 281 | struct multicast_route * 282 | mrt_new(void) 283 | { 284 | struct multicast_route *mr; 285 | 286 | mr = calloc(1, sizeof(*mr)); 287 | if (mr == NULL) { 288 | log_warn("%s: calloc", __func__); 289 | return NULL; 290 | } 291 | 292 | mr->mr_state = MS_NOTJOINED; 293 | mr->mr_version = MV_IGMPV3; 294 | mr->mr_lowestversion = MV_IGMPV3; 295 | LIST_INIT(&mr->mr_molist); 296 | 297 | evtimer_set(&mr->mr_timer, mrt_timer, mr); 298 | evtimer_set(&mr->mr_vtimer, mrt_vtimer, mr); 299 | mrt_timeradd(&mr->mr_timer); 300 | mrt_timeradd(&mr->mr_vtimer); 301 | 302 | return mr; 303 | } 304 | 305 | void 306 | mrt_free(struct multicast_route *mr) 307 | { 308 | struct multicast_origin *mo; 309 | struct timeval tv; 310 | struct sockaddr_storage ss; 311 | 312 | if (evtimer_pending(&mr->mr_timer, &tv)) 313 | evtimer_del(&mr->mr_timer); 314 | 315 | if (evtimer_pending(&mr->mr_vtimer, &tv)) 316 | evtimer_del(&mr->mr_vtimer); 317 | 318 | while (!LIST_EMPTY(&mr->mr_molist)) { 319 | mo = LIST_FIRST(&mr->mr_molist); 320 | LIST_REMOVE(mo, mo_entry); 321 | _mrt_delorigin(mr, mo); 322 | } 323 | 324 | ss.ss_family = mr->mr_af; 325 | if (ss.ss_family == AF_INET) 326 | sstosin(&ss)->sin_addr = mr->mr_group.v4; 327 | else 328 | sstosin6(&ss)->sin6_addr = mr->mr_group.v6; 329 | 330 | log_debug("%s: remove group %s", __func__, addrtostr(&ss)); 331 | 332 | RB_REMOVE(mrtree, &mrtree, mr); 333 | 334 | free(mr); 335 | } 336 | 337 | void 338 | mrt_cleanup(void) 339 | { 340 | struct multicast_route *mr; 341 | 342 | while (!RB_EMPTY(&mrtree)) { 343 | mr = RB_ROOT(&mrtree); 344 | mrt_free(mr); 345 | } 346 | } 347 | 348 | struct multicast_route * 349 | mrt_find4(struct in_addr *in) 350 | { 351 | struct multicast_route key; 352 | 353 | memset(&key, 0, sizeof(key)); 354 | key.mr_af = AF_INET; 355 | key.mr_group.v4 = *in; 356 | return RB_FIND(mrtree, &mrtree, &key); 357 | } 358 | 359 | struct multicast_route * 360 | mrt_insert4(enum mr_version mv, struct intf_data *id, 361 | struct in_addr *origin, struct in_addr *group) 362 | { 363 | struct multicast_route *mr, *mrn; 364 | union uaddr uorigin; 365 | 366 | /* Sanity check: only use multicast groups. */ 367 | if (!IN_MULTICAST(ntohl(group->s_addr))) { 368 | log_debug("%s(%s, %s): not multicast group", 369 | __func__, id->id_name, addr4tostr(group)); 370 | return NULL; 371 | } 372 | 373 | /* Try to find it, if it exists just add the new origin. */ 374 | mr = mrt_find4(group); 375 | if (mr != NULL) 376 | goto add_origin; 377 | 378 | /* Otherwise create one and insert. */ 379 | mr = mrt_new(); 380 | if (mr == NULL) 381 | return NULL; 382 | 383 | mr->mr_af = AF_INET; 384 | mr->mr_group.v4 = *group; 385 | mrn = RB_INSERT(mrtree, &mrtree, mr); 386 | if (mrn != NULL) { 387 | mrt_free(mr); 388 | mr = mrn; 389 | } 390 | 391 | add_origin: 392 | /* 393 | * Always use the lowest version immediately, otherwise wait the 394 | * query timeout before switching. See mrt_vtimer() for more 395 | * details. 396 | */ 397 | if (mr->mr_version > mv) 398 | mr->mr_version = mv; 399 | if (mr->mr_lowestversion > mv) 400 | mr->mr_lowestversion = mv; 401 | 402 | uorigin.v4 = *origin; 403 | mrt_addorigin(mr, id, &uorigin); 404 | 405 | mrt_nextstate(mr); 406 | 407 | return mr; 408 | } 409 | 410 | void 411 | mrt_remove4(struct intf_data *id, struct in_addr *origin, 412 | struct in_addr *group) 413 | { 414 | struct multicast_route *mr; 415 | union uaddr uorigin; 416 | 417 | mr = mrt_find4(group); 418 | if (mr == NULL) 419 | return; 420 | 421 | /* IGMPv1 compatibility mode does not accept fast-leave. */ 422 | if (mr->mr_version == MV_IGMPV1) 423 | return; 424 | 425 | uorigin.v4 = *origin; 426 | mrt_delorigin(mr, id, &uorigin); 427 | mrt_nextstate(mr); 428 | if (LIST_EMPTY(&mr->mr_molist)) 429 | mrt_free(mr); 430 | } 431 | 432 | struct multicast_route * 433 | mrt_find6(struct in6_addr *in6) 434 | { 435 | struct multicast_route key; 436 | 437 | memset(&key, 0, sizeof(key)); 438 | key.mr_af = AF_INET6; 439 | key.mr_group.v6 = *in6; 440 | return RB_FIND(mrtree, &mrtree, &key); 441 | } 442 | 443 | struct multicast_route * 444 | mrt_insert6(enum mr_version mv, struct intf_data *id, 445 | struct in6_addr *origin, struct in6_addr *group) 446 | { 447 | struct multicast_route *mr, *mrn; 448 | union uaddr uorigin; 449 | 450 | /* Sanity check: only use multicast groups. */ 451 | if (!IN6_IS_ADDR_MULTICAST(group)) { 452 | log_debug("%s(%s, %s): not multicast group", 453 | __func__, id->id_name, addr6tostr(group)); 454 | return NULL; 455 | } 456 | 457 | /* Try to find it, if it exists just add the new origin. */ 458 | mr = mrt_find6(group); 459 | if (mr != NULL) 460 | goto add_origin; 461 | 462 | /* Otherwise create one and insert. */ 463 | mr = mrt_new(); 464 | if (mr == NULL) 465 | return NULL; 466 | 467 | mr->mr_af = AF_INET6; 468 | mr->mr_group.v6 = *group; 469 | mrn = RB_INSERT(mrtree, &mrtree, mr); 470 | if (mrn != NULL) { 471 | mrt_free(mr); 472 | mr = mrn; 473 | } 474 | 475 | add_origin: 476 | /* 477 | * Always use the lowest version immediately, otherwise wait the 478 | * query timeout before switching. See mrt_vtimer() for more 479 | * details. 480 | */ 481 | if (mr->mr_version > mv) 482 | mr->mr_version = mv; 483 | if (mr->mr_lowestversion > mv) 484 | mr->mr_lowestversion = mv; 485 | 486 | uorigin.v6 = *origin; 487 | mrt_addorigin(mr, id, &uorigin); 488 | 489 | mrt_nextstate(mr); 490 | 491 | return mr; 492 | } 493 | 494 | void 495 | mrt_remove6(struct intf_data *id, struct in6_addr *origin, 496 | struct in6_addr *group) 497 | { 498 | struct multicast_route *mr; 499 | union uaddr uorigin; 500 | 501 | mr = mrt_find6(group); 502 | if (mr == NULL) 503 | return; 504 | 505 | uorigin.v6 = *origin; 506 | mrt_delorigin(mr, id, &uorigin); 507 | mrt_nextstate(mr); 508 | if (LIST_EMPTY(&mr->mr_molist)) 509 | mrt_free(mr); 510 | } 511 | 512 | void 513 | mrt_nextstate(struct multicast_route *mr) 514 | { 515 | struct sockaddr_storage ss; 516 | 517 | if (upstreamif == NULL) { 518 | log_debug("%s: no upstream interface", __func__); 519 | return; 520 | } 521 | 522 | ss.ss_family = mr->mr_af; 523 | switch (ss.ss_family) { 524 | case AF_INET: 525 | sstosin(&ss)->sin_addr = mr->mr_group.v4; 526 | break; 527 | case AF_INET6: 528 | sstosin6(&ss)->sin6_addr = mr->mr_group.v6; 529 | break; 530 | default: 531 | fatalx("%s: unknown family %d", 532 | __func__, ss.ss_family); 533 | } 534 | 535 | switch (mr->mr_state) { 536 | case MS_NOTJOINED: 537 | /* Don't join if there is no interest. */ 538 | if (LIST_EMPTY(&mr->mr_molist)) 539 | return; 540 | 541 | mcast_join(upstreamif, &ss); 542 | mr->mr_state = MS_JOINED; 543 | break; 544 | 545 | case MS_JOINED: 546 | /* Don't leave if there is still peers. */ 547 | if (!LIST_EMPTY(&mr->mr_molist)) 548 | return; 549 | 550 | mcast_leave(upstreamif, &ss); 551 | mr->mr_state = MS_NOTJOINED; 552 | break; 553 | 554 | default: 555 | log_debug("%s: invalid state %d", 556 | __func__, mr->mr_state); 557 | break; 558 | } 559 | } 560 | 561 | RB_GENERATE(mrtree, multicast_route, mr_entry, mrcmp); 562 | 563 | int 564 | mrcmp(struct multicast_route *mr1, struct multicast_route *mr2) 565 | { 566 | size_t addrsize; 567 | 568 | if (mr1->mr_af > mr2->mr_af) 569 | return 1; 570 | else if (mr1->mr_af < mr2->mr_af) 571 | return -1; 572 | 573 | addrsize = (mr1->mr_af == AF_INET) ? 574 | sizeof(mr1->mr_group.v4) : sizeof(mr1->mr_group.v6); 575 | 576 | return memcmp(&mr1->mr_group, &mr2->mr_group, addrsize); 577 | } 578 | -------------------------------------------------------------------------------- /usr.sbin/mcast-proxy/parse.y: -------------------------------------------------------------------------------- 1 | /* $OpenBSD:$ */ 2 | 3 | /* 4 | * Copyright (c) 2017 Rafael Zalamena 5 | * Copyright (c) 2015 Renato Westphal 6 | * Copyright (c) 2004, 2005 Esben Norby 7 | * Copyright (c) 2004 Ryan McBride 8 | * Copyright (c) 2002, 2003, 2004 Henning Brauer 9 | * Copyright (c) 2001 Markus Friedl. All rights reserved. 10 | * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. 11 | * Copyright (c) 2001 Theo de Raadt. All rights reserved. 12 | * 13 | * Permission to use, copy, modify, and distribute this software for any 14 | * purpose with or without fee is hereby granted, provided that the above 15 | * copyright notice and this permission notice appear in all copies. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 18 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 19 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 20 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 21 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 22 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 23 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 24 | */ 25 | 26 | %{ 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include "mcast-proxy.h" 41 | 42 | struct file { 43 | TAILQ_ENTRY(file) entry; 44 | FILE *stream; 45 | char *name; 46 | int lineno; 47 | int errors; 48 | }; 49 | TAILQ_HEAD(files, file); 50 | 51 | struct sym { 52 | TAILQ_ENTRY(sym) entry; 53 | int used; 54 | int persist; 55 | char *nam; 56 | char *val; 57 | }; 58 | TAILQ_HEAD(symhead, sym); 59 | 60 | typedef struct { 61 | union { 62 | int64_t number; 63 | char *string; 64 | } v; 65 | int lineno; 66 | } YYSTYPE; 67 | 68 | #define MAXPUSHBACK 128 69 | 70 | static int yyerror(const char *, ...) 71 | __attribute__((__format__ (printf, 1, 2))) 72 | __attribute__((__nonnull__ (1))); 73 | static int kw_cmp(const void *, const void *); 74 | static int lookup(char *); 75 | static int lgetc(int); 76 | static int lungetc(int); 77 | static int findeol(void); 78 | static int yylex(void); 79 | static int check_file_secrecy(int, const char *); 80 | static struct file *pushfile(const char *, int); 81 | static int popfile(void); 82 | static int yyparse(void); 83 | static int symset(const char *, const char *, int); 84 | static char *symget(const char *); 85 | 86 | static struct file *file, *topfile; 87 | static struct files files = TAILQ_HEAD_INITIALIZER(files); 88 | static struct symhead symhead = TAILQ_HEAD_INITIALIZER(symhead); 89 | static int errors; 90 | 91 | static unsigned char *parsebuf; 92 | static int parseindex; 93 | static unsigned char pushback_buffer[MAXPUSHBACK]; 94 | static int pushback_index; 95 | 96 | struct intf_data *cid; 97 | 98 | %} 99 | 100 | %token IPV4 IPV6 INTERFACE DISABLE DOWNSTREAM SOURCE UPSTREAM THRESHOLD 101 | %token INCLUDE YES NO 102 | %token ERROR 103 | %token STRING 104 | %token NUMBER 105 | %type yesno 106 | %type string 107 | 108 | %% 109 | 110 | grammar : /* empty */ 111 | | grammar '\n' 112 | | grammar conf_opt '\n' 113 | | grammar include '\n' 114 | | grammar varset '\n' 115 | | grammar error '\n' { file->errors++; } 116 | ; 117 | 118 | conf_opt : INTERFACE STRING { 119 | cid = intf_lookupbyname($2); 120 | if (cid == NULL) { 121 | cid = id_new(); 122 | if (cid == NULL) 123 | fatal("%s:%d: calloc", 124 | file->name, yylval.lineno); 125 | if (strlcpy(cid->id_name, $2, 126 | sizeof(cid->id_name)) >= sizeof(cid->id_name)) 127 | fatalx("%s:%d: interface name too long", 128 | file->name, yylval.lineno); 129 | } 130 | 131 | cid->id_mv4 = ic.ic_ipv4; 132 | cid->id_mv6 = ic.ic_ipv6; 133 | } intf_block 134 | | global_ip 135 | ; 136 | 137 | global_ip : IPV4 yesno { ic.ic_ipv4 = $2; } 138 | | IPV6 yesno { ic.ic_ipv6 = $2; } 139 | ; 140 | 141 | intf_block : '{' optnl intf_opts '}' 142 | | '{' optnl '}' 143 | ; 144 | 145 | intf_opts : intf_opt nl intf_opts 146 | | intf_opt optnl 147 | ; 148 | 149 | intf_opt : THRESHOLD NUMBER { 150 | if ($2 < 1 || $2 > 255) 151 | fatalx("%s:%d: invalid threshold value: %llu", 152 | file->name, yylval.lineno, $2); 153 | 154 | cid->id_ttl = $2; 155 | } 156 | | SOURCE STRING { 157 | struct intf_addr *ia; 158 | char *prefixp; 159 | const char *errp; 160 | 161 | prefixp = strchr($2, '/'); 162 | if (prefixp == NULL) 163 | fatalx("%s:%d: failed to find prefix", 164 | file->name, yylval.lineno); 165 | 166 | *prefixp = 0; 167 | prefixp++; 168 | if (*prefixp == 0) 169 | fatalx("%s:%d: empty prefix", 170 | file->name, yylval.lineno); 171 | 172 | ia = calloc(1, sizeof(*ia)); 173 | if (ia == NULL) 174 | fatal("%s:%d: calloc", 175 | file->name, yylval.lineno); 176 | 177 | if (inet_pton(AF_INET, $2, &ia->ia_addr) != 1) { 178 | if (inet_pton(AF_INET6, $2, &ia->ia_addr) != 1) { 179 | fatalx("%s:%d: invalid address '%s'", 180 | file->name, yylval.lineno, $2); 181 | } else 182 | ia->ia_af = AF_INET6; 183 | } else 184 | ia->ia_af = AF_INET; 185 | 186 | ia->ia_prefixlen = strtonum(prefixp, 0, 128, &errp); 187 | if (errp != NULL) 188 | fatalx("%s:%d: invalid prefix length: %s", 189 | file->name, yylval.lineno, errp); 190 | if (ia->ia_af == AF_INET && ia->ia_prefixlen > 32) 191 | fatalx("%s:%d: invalid prefix length", 192 | file->name, yylval.lineno); 193 | else if (ia->ia_af == AF_INET6 && ia->ia_prefixlen > 128) 194 | fatalx("%s:%d: invalid prefix length", 195 | file->name, yylval.lineno); 196 | 197 | SLIST_INSERT_HEAD(&cid->id_altnetlist, ia, ia_entry); 198 | } 199 | | UPSTREAM { 200 | if (upstreamif != NULL) 201 | fatalx("%s:%d: it is not possible to have " 202 | "multiple upstream interfaces.", 203 | file->name, yylval.lineno); 204 | 205 | upstreamif = cid; 206 | cid->id_dir = IDIR_UPSTREAM; 207 | } 208 | | DOWNSTREAM { cid->id_dir = IDIR_DOWNSTREAM; } 209 | | DISABLE { cid->id_dir = IDIR_DISABLE; } 210 | | IPV4 yesno { cid->id_mv4 = $2; } 211 | | IPV6 yesno { cid->id_mv6 = $2; } 212 | ; 213 | 214 | include : INCLUDE STRING { 215 | struct file *nfile; 216 | 217 | if ((nfile = pushfile($2, 1)) == NULL) { 218 | yyerror("failed to include file %s", $2); 219 | free($2); 220 | YYERROR; 221 | } 222 | free($2); 223 | 224 | file = nfile; 225 | lungetc('\n'); 226 | } 227 | ; 228 | 229 | varset : STRING '=' string { 230 | const char *s = $1; 231 | while (*s++) { 232 | if (isspace((unsigned char)*s)) { 233 | yyerror("macro name cannot contain " 234 | "whitespace"); 235 | YYERROR; 236 | } 237 | } 238 | if (symset($1, $3, 0) == -1) 239 | fatal("cannot store variable"); 240 | free($1); 241 | free($3); 242 | } 243 | ; 244 | 245 | string : string STRING { 246 | if (asprintf(&$$, "%s %s", $1, $2) == -1) { 247 | free($1); 248 | free($2); 249 | yyerror("string: asprintf"); 250 | YYERROR; 251 | } 252 | free($1); 253 | free($2); 254 | } 255 | | STRING 256 | ; 257 | 258 | optnl : '\n' optnl 259 | | 260 | ; 261 | 262 | nl : '\n' optnl 263 | ; 264 | 265 | yesno : YES { $$ = 1; } 266 | | NO { $$ = 0; } 267 | ; 268 | 269 | %% 270 | 271 | struct keywords { 272 | const char *k_name; 273 | int k_val; 274 | }; 275 | 276 | static int 277 | yyerror(const char *fmt, ...) 278 | { 279 | va_list ap; 280 | char *msg; 281 | 282 | file->errors++; 283 | va_start(ap, fmt); 284 | if (vasprintf(&msg, fmt, ap) == -1) 285 | fatalx("yyerror vasprintf"); 286 | va_end(ap); 287 | logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); 288 | free(msg); 289 | return (0); 290 | } 291 | 292 | static int 293 | kw_cmp(const void *k, const void *e) 294 | { 295 | return (strcmp(k, ((const struct keywords *)e)->k_name)); 296 | } 297 | 298 | static int 299 | lookup(char *s) 300 | { 301 | /* this has to be sorted always */ 302 | static const struct keywords keywords[] = { 303 | {"disabled", DISABLE}, 304 | {"downstream", DOWNSTREAM}, 305 | {"include", INCLUDE}, 306 | {"interface", INTERFACE}, 307 | {"ipv4", IPV4}, 308 | {"ipv6", IPV6}, 309 | {"no", NO}, 310 | {"source", SOURCE}, 311 | {"threshold", THRESHOLD}, 312 | {"upstream", UPSTREAM}, 313 | {"yes", YES}, 314 | }; 315 | const struct keywords *p; 316 | 317 | p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), 318 | sizeof(keywords[0]), kw_cmp); 319 | 320 | if (p) 321 | return (p->k_val); 322 | else 323 | return (STRING); 324 | } 325 | 326 | static int 327 | lgetc(int quotec) 328 | { 329 | int c, next; 330 | 331 | if (parsebuf) { 332 | /* Read character from the parsebuffer instead of input. */ 333 | if (parseindex >= 0) { 334 | c = parsebuf[parseindex++]; 335 | if (c != '\0') 336 | return (c); 337 | parsebuf = NULL; 338 | } else 339 | parseindex++; 340 | } 341 | 342 | if (pushback_index) 343 | return (pushback_buffer[--pushback_index]); 344 | 345 | if (quotec) { 346 | if ((c = getc(file->stream)) == EOF) { 347 | yyerror("reached end of file while parsing " 348 | "quoted string"); 349 | if (file == topfile || popfile() == EOF) 350 | return (EOF); 351 | return (quotec); 352 | } 353 | return (c); 354 | } 355 | 356 | while ((c = getc(file->stream)) == '\\') { 357 | next = getc(file->stream); 358 | if (next != '\n') { 359 | c = next; 360 | break; 361 | } 362 | yylval.lineno = file->lineno; 363 | file->lineno++; 364 | } 365 | 366 | while (c == EOF) { 367 | if (file == topfile || popfile() == EOF) 368 | return (EOF); 369 | c = getc(file->stream); 370 | } 371 | return (c); 372 | } 373 | 374 | static int 375 | lungetc(int c) 376 | { 377 | if (c == EOF) 378 | return (EOF); 379 | if (parsebuf) { 380 | parseindex--; 381 | if (parseindex >= 0) 382 | return (c); 383 | } 384 | if (pushback_index < MAXPUSHBACK-1) 385 | return (pushback_buffer[pushback_index++] = c); 386 | else 387 | return (EOF); 388 | } 389 | 390 | static int 391 | findeol(void) 392 | { 393 | int c; 394 | 395 | parsebuf = NULL; 396 | 397 | /* skip to either EOF or the first real EOL */ 398 | while (1) { 399 | if (pushback_index) 400 | c = pushback_buffer[--pushback_index]; 401 | else 402 | c = lgetc(0); 403 | if (c == '\n') { 404 | file->lineno++; 405 | break; 406 | } 407 | if (c == EOF) 408 | break; 409 | } 410 | return (ERROR); 411 | } 412 | 413 | static int 414 | yylex(void) 415 | { 416 | unsigned char buf[8096]; 417 | unsigned char *p, *val; 418 | int quotec, next, c; 419 | int token; 420 | 421 | top: 422 | p = buf; 423 | while ((c = lgetc(0)) == ' ' || c == '\t') 424 | ; /* nothing */ 425 | 426 | yylval.lineno = file->lineno; 427 | if (c == '#') 428 | while ((c = lgetc(0)) != '\n' && c != EOF) 429 | ; /* nothing */ 430 | if (c == '$' && parsebuf == NULL) { 431 | while (1) { 432 | if ((c = lgetc(0)) == EOF) 433 | return (0); 434 | 435 | if (p + 1 >= buf + sizeof(buf) - 1) { 436 | yyerror("string too long"); 437 | return (findeol()); 438 | } 439 | if (isalnum(c) || c == '_') { 440 | *p++ = c; 441 | continue; 442 | } 443 | *p = '\0'; 444 | lungetc(c); 445 | break; 446 | } 447 | val = symget(buf); 448 | if (val == NULL) { 449 | yyerror("macro '%s' not defined", buf); 450 | return (findeol()); 451 | } 452 | parsebuf = val; 453 | parseindex = 0; 454 | goto top; 455 | } 456 | 457 | switch (c) { 458 | case '\'': 459 | case '"': 460 | quotec = c; 461 | while (1) { 462 | if ((c = lgetc(quotec)) == EOF) 463 | return (0); 464 | if (c == '\n') { 465 | file->lineno++; 466 | continue; 467 | } else if (c == '\\') { 468 | if ((next = lgetc(quotec)) == EOF) 469 | return (0); 470 | if (next == quotec || c == ' ' || c == '\t') 471 | c = next; 472 | else if (next == '\n') { 473 | file->lineno++; 474 | continue; 475 | } else 476 | lungetc(next); 477 | } else if (c == quotec) { 478 | *p = '\0'; 479 | break; 480 | } else if (c == '\0') { 481 | yyerror("syntax error"); 482 | return (findeol()); 483 | } 484 | if (p + 1 >= buf + sizeof(buf) - 1) { 485 | yyerror("string too long"); 486 | return (findeol()); 487 | } 488 | *p++ = c; 489 | } 490 | yylval.v.string = strdup(buf); 491 | if (yylval.v.string == NULL) 492 | err(1, "yylex: strdup"); 493 | return (STRING); 494 | } 495 | 496 | #define allowed_to_end_number(x) \ 497 | (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') 498 | 499 | if (c == '-' || isdigit(c)) { 500 | do { 501 | *p++ = c; 502 | if ((unsigned)(p-buf) >= sizeof(buf)) { 503 | yyerror("string too long"); 504 | return (findeol()); 505 | } 506 | } while ((c = lgetc(0)) != EOF && isdigit(c)); 507 | lungetc(c); 508 | if (p == buf + 1 && buf[0] == '-') 509 | goto nodigits; 510 | if (c == EOF || allowed_to_end_number(c)) { 511 | const char *errstr = NULL; 512 | 513 | *p = '\0'; 514 | yylval.v.number = strtonum(buf, LLONG_MIN, 515 | LLONG_MAX, &errstr); 516 | if (errstr) { 517 | yyerror("\"%s\" invalid number: %s", 518 | buf, errstr); 519 | return (findeol()); 520 | } 521 | return (NUMBER); 522 | } else { 523 | nodigits: 524 | while (p > buf + 1) 525 | lungetc(*--p); 526 | c = *--p; 527 | if (c == '-') 528 | return (c); 529 | } 530 | } 531 | 532 | #define allowed_in_string(x) \ 533 | (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ 534 | x != '{' && x != '}' && \ 535 | x != '!' && x != '=' && x != '#' && \ 536 | x != ',')) 537 | 538 | if (isalnum(c) || c == ':' || c == '_') { 539 | do { 540 | *p++ = c; 541 | if ((unsigned)(p-buf) >= sizeof(buf)) { 542 | yyerror("string too long"); 543 | return (findeol()); 544 | } 545 | } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); 546 | lungetc(c); 547 | *p = '\0'; 548 | if ((token = lookup(buf)) == STRING) 549 | if ((yylval.v.string = strdup(buf)) == NULL) 550 | err(1, "yylex: strdup"); 551 | return (token); 552 | } 553 | if (c == '\n') { 554 | yylval.lineno = file->lineno; 555 | file->lineno++; 556 | } 557 | if (c == EOF) 558 | return (0); 559 | return (c); 560 | } 561 | 562 | static int 563 | check_file_secrecy(int fd, const char *fname) 564 | { 565 | struct stat st; 566 | 567 | if (fstat(fd, &st)) { 568 | log_warn("cannot stat %s", fname); 569 | return (-1); 570 | } 571 | if (st.st_uid != 0 && st.st_uid != getuid()) { 572 | log_warnx("%s: owner not root or current user", fname); 573 | return (-1); 574 | } 575 | if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { 576 | log_warnx("%s: group writable or world read/writable", fname); 577 | return (-1); 578 | } 579 | return (0); 580 | } 581 | 582 | static struct file * 583 | pushfile(const char *name, int secret) 584 | { 585 | struct file *nfile; 586 | 587 | if ((nfile = calloc(1, sizeof(struct file))) == NULL) { 588 | log_warn("calloc"); 589 | return (NULL); 590 | } 591 | if ((nfile->name = strdup(name)) == NULL) { 592 | log_warn("strdup"); 593 | free(nfile); 594 | return (NULL); 595 | } 596 | if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { 597 | log_warn("%s", nfile->name); 598 | free(nfile->name); 599 | free(nfile); 600 | return (NULL); 601 | } else if (secret && 602 | check_file_secrecy(fileno(nfile->stream), nfile->name)) { 603 | fclose(nfile->stream); 604 | free(nfile->name); 605 | free(nfile); 606 | return (NULL); 607 | } 608 | nfile->lineno = 1; 609 | TAILQ_INSERT_TAIL(&files, nfile, entry); 610 | return (nfile); 611 | } 612 | 613 | static int 614 | popfile(void) 615 | { 616 | struct file *prev; 617 | 618 | if ((prev = TAILQ_PREV(file, files, entry)) != NULL) 619 | prev->errors += file->errors; 620 | 621 | TAILQ_REMOVE(&files, file, entry); 622 | fclose(file->stream); 623 | free(file->name); 624 | free(file); 625 | file = prev; 626 | return (file ? 0 : EOF); 627 | } 628 | 629 | static int 630 | symset(const char *nam, const char *val, int persist) 631 | { 632 | struct sym *sym; 633 | 634 | TAILQ_FOREACH(sym, &symhead, entry) { 635 | if (strcmp(nam, sym->nam) == 0) 636 | break; 637 | } 638 | 639 | if (sym != NULL) { 640 | if (sym->persist == 1) 641 | return (0); 642 | else { 643 | free(sym->nam); 644 | free(sym->val); 645 | TAILQ_REMOVE(&symhead, sym, entry); 646 | free(sym); 647 | } 648 | } 649 | if ((sym = calloc(1, sizeof(*sym))) == NULL) 650 | return (-1); 651 | 652 | sym->nam = strdup(nam); 653 | if (sym->nam == NULL) { 654 | free(sym); 655 | return (-1); 656 | } 657 | sym->val = strdup(val); 658 | if (sym->val == NULL) { 659 | free(sym->nam); 660 | free(sym); 661 | return (-1); 662 | } 663 | sym->used = 0; 664 | sym->persist = persist; 665 | TAILQ_INSERT_TAIL(&symhead, sym, entry); 666 | return (0); 667 | } 668 | 669 | int 670 | cmdline_symset(const char *s) 671 | { 672 | char *sym, *val; 673 | int ret; 674 | size_t len; 675 | 676 | if ((val = strrchr(s, '=')) == NULL) 677 | return (-1); 678 | 679 | len = strlen(s) - strlen(val) + 1; 680 | if ((sym = malloc(len)) == NULL) 681 | errx(1, "cmdline_symset: malloc"); 682 | 683 | strlcpy(sym, s, len); 684 | 685 | ret = symset(sym, val + 1, 1); 686 | free(sym); 687 | 688 | return (ret); 689 | } 690 | 691 | static char * 692 | symget(const char *nam) 693 | { 694 | struct sym *sym; 695 | 696 | TAILQ_FOREACH(sym, &symhead, entry) { 697 | if (strcmp(nam, sym->nam) == 0) { 698 | sym->used = 1; 699 | return (sym->val); 700 | } 701 | } 702 | return (NULL); 703 | } 704 | 705 | int 706 | parse_config(const char *filename) 707 | { 708 | if ((file = pushfile(filename, 0)) == NULL) 709 | return -1; 710 | 711 | topfile = file; 712 | 713 | yyparse(); 714 | errors = file->errors; 715 | popfile(); 716 | if (errors) 717 | return -1; 718 | 719 | return 0; 720 | } 721 | -------------------------------------------------------------------------------- /usr.sbin/mcast-proxy/util.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD:$ */ 2 | 3 | /* 4 | * Copyright (c) 2017 Rafael Zalamena 5 | * 6 | * Permission to use, copy, modify, and/or distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | #include "mcast-proxy.h" 25 | 26 | const char * 27 | addrtostr(struct sockaddr_storage *ss) 28 | { 29 | struct sockaddr_in *sin; 30 | struct sockaddr_in6 *sin6; 31 | static char buf[4][128]; 32 | static unsigned int bufpos = 0; 33 | 34 | bufpos = (bufpos + 1) % 4; 35 | 36 | switch (ss->ss_family) { 37 | case AF_INET: 38 | sin = sstosin(ss); 39 | inet_ntop(AF_INET, &sin->sin_addr, buf[bufpos], 40 | sizeof(buf[bufpos])); 41 | return buf[bufpos]; 42 | case AF_INET6: 43 | sin6 = sstosin6(ss); 44 | buf[bufpos][0] = '['; 45 | inet_ntop(AF_INET6, &sin6->sin6_addr, &buf[bufpos][1], 46 | sizeof(buf[bufpos])); 47 | strlcat(buf[bufpos], "]", sizeof(buf[bufpos])); 48 | return buf[bufpos]; 49 | 50 | default: 51 | return "unknown"; 52 | } 53 | } 54 | 55 | const char * 56 | addr4tostr(struct in_addr *addr) 57 | { 58 | struct sockaddr_storage ss; 59 | 60 | memset(&ss, 0, sizeof(ss)); 61 | ss.ss_family = AF_INET; 62 | ss.ss_len = sizeof(struct sockaddr_in); 63 | sstosin(&ss)->sin_addr = *addr; 64 | 65 | return addrtostr(&ss); 66 | } 67 | 68 | const char * 69 | addr6tostr(struct in6_addr *addr) 70 | { 71 | struct sockaddr_storage ss; 72 | 73 | memset(&ss, 0, sizeof(ss)); 74 | ss.ss_family = AF_INET6; 75 | ss.ss_len = sizeof(struct sockaddr_in6); 76 | memcpy(&sstosin6(&ss)->sin6_addr, addr, sizeof(*addr)); 77 | 78 | return addrtostr(&ss); 79 | } 80 | 81 | int 82 | id_matchaddr4(struct intf_data *id, uint32_t addr) 83 | { 84 | struct intf_addr *ia; 85 | union uaddr addrorg, addrtgt, naddr; 86 | 87 | naddr.v4.s_addr = addr; 88 | 89 | /* Check for address in interface address list. */ 90 | SLIST_FOREACH(ia, &id->id_ialist, ia_entry) { 91 | if (ia->ia_af != AF_INET) 92 | continue; 93 | 94 | applymask(AF_INET, &addrtgt, &naddr, ia->ia_prefixlen); 95 | applymask(AF_INET, &addrorg, &ia->ia_addr, ia->ia_prefixlen); 96 | if (memcmp(&addrorg, &addrtgt, sizeof(addrorg.v4)) == 0) 97 | return 1; 98 | } 99 | 100 | /* Check for address in the subnet address list. */ 101 | SLIST_FOREACH(ia, &id->id_altnetlist, ia_entry) { 102 | if (ia->ia_af != AF_INET) 103 | continue; 104 | 105 | applymask(AF_INET, &addrtgt, &naddr, ia->ia_prefixlen); 106 | applymask(AF_INET, &addrorg, &ia->ia_addr, ia->ia_prefixlen); 107 | if (memcmp(&addrorg, &addrtgt, sizeof(addrorg.v4)) == 0) 108 | return 1; 109 | } 110 | 111 | return 0; 112 | } 113 | 114 | int 115 | id_matchaddr6(struct intf_data *id, struct in6_addr *addr) 116 | { 117 | struct intf_addr *ia; 118 | union uaddr addrorg, addrtgt, naddr; 119 | 120 | naddr.v6 = *addr; 121 | 122 | /* Check for address in interface address list. */ 123 | SLIST_FOREACH(ia, &id->id_ialist, ia_entry) { 124 | if (ia->ia_af != AF_INET6) 125 | continue; 126 | 127 | applymask(AF_INET6, &addrtgt, &naddr, ia->ia_prefixlen); 128 | applymask(AF_INET6, &addrorg, &ia->ia_addr, ia->ia_prefixlen); 129 | if (memcmp(&addrorg, &addrtgt, sizeof(addrorg.v6)) == 0) 130 | return 1; 131 | } 132 | 133 | /* Check for address in the subnet address list. */ 134 | SLIST_FOREACH(ia, &id->id_altnetlist, ia_entry) { 135 | if (ia->ia_af != AF_INET6) 136 | continue; 137 | 138 | applymask(AF_INET6, &addrtgt, &naddr, ia->ia_prefixlen); 139 | applymask(AF_INET6, &addrorg, &ia->ia_addr, ia->ia_prefixlen); 140 | if (memcmp(&addrorg, &addrtgt, sizeof(addrorg.v6)) == 0) 141 | return 1; 142 | } 143 | 144 | return 0; 145 | } 146 | 147 | struct intf_data * 148 | intf_lookupbyname(const char *ifname) 149 | { 150 | struct intf_data *id; 151 | 152 | SLIST_FOREACH(id, &iflist, id_entry) { 153 | if (strcmp(id->id_name, ifname) == 0) 154 | return id; 155 | } 156 | 157 | return NULL; 158 | } 159 | 160 | struct intf_data * 161 | intf_lookupbyindex(unsigned short index) 162 | { 163 | struct intf_data *id; 164 | 165 | SLIST_FOREACH(id, &iflist, id_entry) { 166 | if (id->id_index == index) 167 | return id; 168 | } 169 | 170 | return NULL; 171 | } 172 | 173 | struct intf_data * 174 | intf_lookupbyaddr4(uint32_t addr) 175 | { 176 | struct intf_data *id; 177 | 178 | SLIST_FOREACH(id, &iflist, id_entry) { 179 | if (id_matchaddr4(id, addr)) 180 | return id; 181 | } 182 | 183 | return NULL; 184 | } 185 | 186 | struct intf_data * 187 | intf_lookupbyaddr6(struct in6_addr *addr) 188 | { 189 | struct intf_data *id; 190 | 191 | SLIST_FOREACH(id, &iflist, id_entry) { 192 | if (id_matchaddr6(id, addr)) 193 | return id; 194 | } 195 | 196 | return NULL; 197 | } 198 | 199 | struct intf_addr * 200 | intf_primaryv4(struct intf_data *id) 201 | { 202 | struct intf_addr *ia; 203 | 204 | SLIST_FOREACH(ia, &id->id_ialist, ia_entry) { 205 | if (ia->ia_af != AF_INET) 206 | continue; 207 | 208 | return ia; 209 | } 210 | 211 | return NULL; 212 | } 213 | 214 | struct intf_addr * 215 | intf_ipv6linklayer(struct intf_data *id) 216 | { 217 | struct intf_addr *ia; 218 | 219 | SLIST_FOREACH(ia, &id->id_ialist, ia_entry) { 220 | if (ia->ia_af != AF_INET6) 221 | continue; 222 | if (!IN6_IS_ADDR_LINKLOCAL(&ia->ia_addr.v6)) 223 | continue; 224 | 225 | return ia; 226 | } 227 | 228 | return NULL; 229 | } 230 | 231 | void 232 | ia_inserttail(struct ialist *ial, struct intf_addr *ia) 233 | { 234 | struct intf_addr *ian; 235 | 236 | SLIST_FOREACH(ian, ial, ia_entry) { 237 | if (SLIST_NEXT(ian, ia_entry) == NULL) 238 | break; 239 | } 240 | if (ian != NULL) 241 | SLIST_INSERT_AFTER(ian, ia, ia_entry); 242 | else 243 | SLIST_INSERT_HEAD(ial, ia, ia_entry); 244 | } 245 | 246 | struct intf_data * 247 | id_new(void) 248 | { 249 | struct intf_data *id; 250 | 251 | id = calloc(1, sizeof(*id)); 252 | if (id == NULL) { 253 | log_warn("%s: calloc", __func__); 254 | return NULL; 255 | } 256 | 257 | /* Default minimum TTL threshold. */ 258 | id->id_ttl = 1; 259 | 260 | id->id_index = (unsigned short)-1; 261 | id->id_vindex = INVALID_VINDEX; 262 | id->id_vindex6 = INVALID_VINDEX; 263 | SLIST_INSERT_HEAD(&iflist, id, id_entry); 264 | 265 | return id; 266 | } 267 | 268 | struct intf_data * 269 | id_insert(unsigned short index) 270 | { 271 | struct intf_data *id; 272 | 273 | id = intf_lookupbyindex(index); 274 | if (id != NULL) 275 | return id; 276 | 277 | id = id_new(); 278 | if (id == NULL) 279 | return NULL; 280 | 281 | id->id_index = index; 282 | 283 | return id; 284 | } 285 | 286 | void 287 | id_free(struct intf_data *id) 288 | { 289 | struct intf_addr *ia; 290 | 291 | if (id == NULL) 292 | return; 293 | 294 | while (!SLIST_EMPTY(&id->id_ialist)) { 295 | ia = SLIST_FIRST(&id->id_ialist); 296 | SLIST_REMOVE(&id->id_ialist, ia, intf_addr, ia_entry); 297 | free(ia); 298 | } 299 | while (!SLIST_EMPTY(&id->id_altnetlist)) { 300 | ia = SLIST_FIRST(&id->id_altnetlist); 301 | SLIST_REMOVE(&id->id_altnetlist, ia, intf_addr, ia_entry); 302 | free(ia); 303 | } 304 | 305 | SLIST_REMOVE(&iflist, id, intf_data, id_entry); 306 | free(id); 307 | } 308 | 309 | uint8_t 310 | mask2prefixlen(in_addr_t ina) 311 | { 312 | if (ina == 0) 313 | return (0); 314 | else 315 | return (33 - ffs(ntohl(ina))); 316 | } 317 | 318 | uint8_t 319 | mask2prefixlen6(struct sockaddr_in6 *sa_in6) 320 | { 321 | uint8_t l = 0, *ap, *ep; 322 | 323 | /* 324 | * sin6_len is the size of the sockaddr so substract the offset of 325 | * the possibly truncated sin6_addr struct. 326 | */ 327 | ap = (uint8_t *)&sa_in6->sin6_addr; 328 | ep = (uint8_t *)sa_in6 + sa_in6->sin6_len; 329 | for (; ap < ep; ap++) { 330 | /* this "beauty" is adopted from sbin/route/show.c ... */ 331 | switch (*ap) { 332 | case 0xff: 333 | l += 8; 334 | break; 335 | case 0xfe: 336 | l += 7; 337 | return (l); 338 | case 0xfc: 339 | l += 6; 340 | return (l); 341 | case 0xf8: 342 | l += 5; 343 | return (l); 344 | case 0xf0: 345 | l += 4; 346 | return (l); 347 | case 0xe0: 348 | l += 3; 349 | return (l); 350 | case 0xc0: 351 | l += 2; 352 | return (l); 353 | case 0x80: 354 | l += 1; 355 | return (l); 356 | case 0x00: 357 | return (l); 358 | default: 359 | fatalx("%s: non contiguous inet6 netmask", __func__); 360 | } 361 | } 362 | 363 | return (l); 364 | } 365 | 366 | in_addr_t 367 | prefixlen2mask(uint8_t prefixlen) 368 | { 369 | if (prefixlen == 0) 370 | return (0); 371 | 372 | return (htonl(0xffffffff << (32 - prefixlen))); 373 | } 374 | 375 | void 376 | applymask(int af, union uaddr *dest, const union uaddr *src, 377 | int prefixlen) 378 | { 379 | struct in6_addr mask; 380 | int i; 381 | 382 | switch (af) { 383 | case AF_INET: 384 | dest->v4.s_addr = src->v4.s_addr & prefixlen2mask(prefixlen); 385 | break; 386 | case AF_INET6: 387 | memset(&mask, 0, sizeof(mask)); 388 | for (i = 0; i < prefixlen / 8; i++) 389 | mask.s6_addr[i] = 0xff; 390 | i = prefixlen % 8; 391 | if (i) 392 | mask.s6_addr[prefixlen / 8] = 0xff00 >> i; 393 | 394 | for (i = 0; i < 16; i++) 395 | dest->v6.s6_addr[i] = src->v6.s6_addr[i] & 396 | mask.s6_addr[i]; 397 | break; 398 | default: 399 | fatalx("%s: unknown address family", __func__); 400 | } 401 | } 402 | 403 | /* Packet assembly code, originally contributed by Archie Cobbs. */ 404 | 405 | /* 406 | * Copyright (c) 1995, 1996, 1999 The Internet Software Consortium. 407 | * All rights reserved. 408 | * 409 | * Redistribution and use in source and binary forms, with or without 410 | * modification, are permitted provided that the following conditions 411 | * are met: 412 | * 413 | * 1. Redistributions of source code must retain the above copyright 414 | * notice, this list of conditions and the following disclaimer. 415 | * 2. Redistributions in binary form must reproduce the above copyright 416 | * notice, this list of conditions and the following disclaimer in the 417 | * documentation and/or other materials provided with the distribution. 418 | * 3. Neither the name of The Internet Software Consortium nor the names 419 | * of its contributors may be used to endorse or promote products derived 420 | * from this software without specific prior written permission. 421 | * 422 | * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND 423 | * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 424 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 425 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 426 | * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR 427 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 428 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 429 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 430 | * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 431 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 432 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 433 | * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 434 | * SUCH DAMAGE. 435 | * 436 | * This software has been written for the Internet Software Consortium 437 | * by Ted Lemon in cooperation with Vixie 438 | * Enterprises. To learn more about the Internet Software Consortium, 439 | * see ``http://www.vix.com/isc''. To learn more about Vixie 440 | * Enterprises, see ``http://www.vix.com''. 441 | */ 442 | 443 | uint16_t 444 | checksum(uint8_t *buf, uint16_t nbytes, uint32_t sum) 445 | { 446 | unsigned int i; 447 | 448 | /* Checksum all the pairs of bytes first... */ 449 | for (i = 0; i < (nbytes & ~1U); i += 2) { 450 | sum += (u_int16_t)ntohs(*((u_int16_t *)(buf + i))); 451 | if (sum > 0xFFFF) 452 | sum -= 0xFFFF; 453 | } 454 | 455 | /* 456 | * If there's a single byte left over, checksum it, too. 457 | * Network byte order is big-endian, so the remaining byte is 458 | * the high byte. 459 | */ 460 | if (i < nbytes) { 461 | sum += buf[i] << 8; 462 | if (sum > 0xFFFF) 463 | sum -= 0xFFFF; 464 | } 465 | 466 | return sum; 467 | } 468 | 469 | uint16_t 470 | wrapsum(uint16_t sum) 471 | { 472 | sum = ~sum & 0xFFFF; 473 | return htons(sum); 474 | } 475 | --------------------------------------------------------------------------------