/* * Copyright 2015 Steven Barth * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include #include #include "querier.h" #include "client.h" #include "mrib.h" #include "proxy.h" struct proxy { struct list_head head; int ifindex; struct mrib_user mrib; struct querier querier; enum proxy_flags flags; }; struct proxy_downlink { struct querier_user_iface iface; struct mrib_user mrib; struct client client; enum proxy_flags flags; }; static struct list_head proxies = LIST_HEAD_INIT(proxies); // Remove and cleanup a downlink static void proxy_remove_downlink(struct proxy_downlink *downlink) { mrib_detach_user(&downlink->mrib); querier_detach(&downlink->iface); client_deinit(&downlink->client); free(downlink); } // Match scope of a multicast-group against proxy scope-filter static bool proxy_match_scope(enum proxy_flags flags, const struct in6_addr *addr) { unsigned scope = 0; if (IN6_IS_ADDR_V4MAPPED(addr)) { if (addr->s6_addr[12] == 239 && addr->s6_addr[13] == 255) scope = PROXY_REALMLOCAL; else if (addr->s6_addr[12] == 239 && (addr->s6_addr[13] & 0xfc) == 192) scope = PROXY_ORGLOCAL; else if (addr->s6_addr[12] == 224 && addr->s6_addr[13] == 0 && addr->s6_addr[14] == 0) scope = 2; else scope = PROXY_GLOBAL; } else { scope = addr->s6_addr[1] & 0xf; } return scope >= (flags & _PROXY_SCOPEMASK); } // Test and set multicast route (called by mrib on detection of new source) static void proxy_mrib(struct mrib_user *mrib, const struct in6_addr *group, const struct in6_addr *source, mrib_filter *filter) { struct proxy *proxy = container_of(mrib, struct proxy, mrib); if (!proxy_match_scope(proxy->flags, group)) return; omgp_time_t now = omgp_time(); struct querier_user *user; list_for_each_entry(user, &proxy->querier.ifaces, head) { if (groups_includes_group(user->groups, group, source, now)) { struct querier_user_iface *iface = container_of(user, struct querier_user_iface, user); struct proxy_downlink *downlink = container_of(iface, struct proxy_downlink, iface); mrib_filter_add(filter, &downlink->mrib); } } } // Update proxy state (called from querier on change of combined group-state) static void proxy_trigger(struct querier_user_iface *user, const struct in6_addr *group, bool include, const struct in6_addr *sources, size_t len) { struct proxy_downlink *iface = container_of(user, struct proxy_downlink, iface); if (proxy_match_scope(iface->flags, group)) client_set(&iface->client, group, include, sources, len); } // Remove proxy with given name static int proxy_unset(struct proxy *proxyp) { bool found = false; struct proxy *proxy, *n; list_for_each_entry_safe(proxy, n, &proxies, head) { if ((proxyp && proxy == proxyp) || (!proxyp && (proxy->flags & _PROXY_UNUSED))) { mrib_detach_user(&proxy->mrib); struct querier_user *user, *n; list_for_each_entry_safe(user, n, &proxy->querier.ifaces, head) { struct querier_user_iface *i = container_of(user, struct querier_user_iface, user); proxy_remove_downlink(container_of(i, struct proxy_downlink, iface)); } querier_deinit(&proxy->querier); list_del(&proxy->head); free(proxy); found = true; } } return (found) ? 0 : -ENOENT; } // Add / update proxy int proxy_set(int uplink, const int downlinks[], size_t downlinks_cnt, enum proxy_flags flags) { struct proxy *proxy = NULL, *p; list_for_each_entry(p, &proxies, head) if (p->ifindex == uplink) proxy = p; if (proxy && (downlinks_cnt == 0 || ((proxy->flags & _PROXY_SCOPEMASK) != (flags & _PROXY_SCOPEMASK)))) { proxy_unset(proxy); proxy = NULL; } if (downlinks_cnt <= 0) return 0; if (!proxy) { if (!(proxy = calloc(1, sizeof(*proxy)))) return -ENOMEM; if ((flags & _PROXY_SCOPEMASK) == 0) flags |= PROXY_GLOBAL; proxy->flags = flags; proxy->ifindex = uplink; querier_init(&proxy->querier); list_add(&proxy->head, &proxies); if (mrib_attach_user(&proxy->mrib, uplink, proxy_mrib)) goto err; } struct querier_user *user, *n; list_for_each_entry_safe(user, n, &proxy->querier.ifaces, head) { struct querier_user_iface *iface = container_of(user, struct querier_user_iface, user); size_t i; for (i = 0; i < downlinks_cnt && downlinks[i] == iface->iface->ifindex; ++i); if (i == downlinks_cnt) proxy_remove_downlink(container_of(iface, struct proxy_downlink, iface)); } for (size_t i = 0; i < downlinks_cnt; ++i) { bool found = false; struct querier_user *user; list_for_each_entry(user, &proxy->querier.ifaces, head) { struct querier_user_iface *iface = container_of(user, struct querier_user_iface, user); if (iface->iface->ifindex == downlinks[i]) { found = true; break; } } if (found) continue; struct proxy_downlink *downlink = calloc(1, sizeof(*downlink)); if (!downlink) goto err; if (client_init(&downlink->client, uplink)) goto downlink_err3; if (mrib_attach_user(&downlink->mrib, downlinks[i], NULL)) goto downlink_err2; if (querier_attach(&downlink->iface, &proxy->querier, downlinks[i], proxy_trigger)) goto downlink_err1; downlink->flags = proxy->flags; continue; downlink_err1: mrib_detach_user(&downlink->mrib); downlink_err2: client_deinit(&downlink->client); downlink_err3: free(downlink); goto err; } return 0; err: proxy_unset(proxy); return -errno; } // Mark all flushable proxies as unused void proxy_update(bool all) { struct proxy *proxy; list_for_each_entry(proxy, &proxies, head) if (all || (proxy->flags & PROXY_FLUSHABLE)) proxy->flags |= _PROXY_UNUSED; } // Flush all unused proxies void proxy_flush(void) { proxy_unset(NULL); }