/* Copyright (C) 2021-2022 MaxLinear, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . SPDX-License-Identifier: GPL-2.0-or-later */ #include "br_private.h" void *(*br_compat_get_bridge_hook)(struct net_device *dev); EXPORT_SYMBOL(br_compat_get_bridge_hook); static struct net_bridge *br_compat_get_bridge(struct net_device *dev) { struct net_bridge *bridge = NULL; if (br_compat_get_bridge_hook) bridge = (struct net_bridge *)br_compat_get_bridge_hook(dev); return bridge; } int br_compat_get_mtu_set_by_user(struct net_device *dev) { struct net_bridge *bridge; bridge = br_compat_get_bridge(dev); if (bridge) return br_opt_get(bridge, BROPT_MTU_SET_BY_USER); return -EFAULT; } EXPORT_SYMBOL(br_compat_get_mtu_set_by_user); int br_compat_set_mtu_set_by_user(struct net_device *dev, int val) { struct net_bridge *bridge; bridge = br_compat_get_bridge(dev); if (bridge) { br_opt_toggle(bridge, BROPT_MTU_SET_BY_USER, !!val); } return 0; } EXPORT_SYMBOL(br_compat_set_mtu_set_by_user); int br_compat_multicast_add_port(void *port) { return br_multicast_add_port((struct net_bridge_port *)port); } EXPORT_SYMBOL(br_compat_multicast_add_port); void br_compat_multicast_del_port(void *port) { struct net_bridge_port *p = (struct net_bridge_port *)port; br_multicast_disable_port(p); br_multicast_del_port(p); list_del_rcu(&p->list); dev_put(p->dev); kfree(p); } EXPORT_SYMBOL(br_compat_multicast_del_port); void br_compat_multicast_enable_port(void *port) { local_bh_disable(); br_multicast_enable_port((struct net_bridge_port *)port); local_bh_enable(); } EXPORT_SYMBOL(br_compat_multicast_enable_port); void br_compat_multicast_disable_port(void *port) { local_bh_disable(); br_multicast_disable_port((struct net_bridge_port *)port); local_bh_enable(); } EXPORT_SYMBOL(br_compat_multicast_disable_port); void br_compat_multicast_init(void *br) { br_multicast_init((struct net_bridge *)br); } EXPORT_SYMBOL(br_compat_multicast_init); void br_compat_multicast_open(void *br_data) { struct net_bridge *br = NULL; br = (struct net_bridge *) br_data; br_multicast_open(br); if (br_opt_get(br, BROPT_MULTICAST_ENABLED)) br_multicast_join_snoopers(br); } EXPORT_SYMBOL(br_compat_multicast_open); void br_compat_multicast_stop(void *br_data) { struct net_bridge *br = NULL; br = (struct net_bridge *) br_data; br_multicast_stop(br); if (br_opt_get(br, BROPT_MULTICAST_ENABLED)) br_multicast_leave_snoopers(br); } EXPORT_SYMBOL(br_compat_multicast_stop); void br_compat_multicast_dev_del(void *br_data) { struct net_bridge *br = NULL; br = (struct net_bridge *) br_data; br_multicast_dev_del(br); br_multicast_uninit_stats(br); br_mdb_hash_fini(br); kfree(br); } EXPORT_SYMBOL(br_compat_multicast_dev_del); int br_compat_multicast_toggle(void *br, unsigned long val) { return br_multicast_toggle(br, val, NULL); } EXPORT_SYMBOL(br_compat_multicast_toggle); int br_compat_multicast_set_querier(void *br, unsigned long val) { return br_multicast_set_querier(&((struct net_bridge *)br)->multicast_ctx, val); } EXPORT_SYMBOL(br_compat_multicast_set_querier); int br_compat_multicast_set_hash_max(void *br, unsigned long val) { // return br_multicast_set_hash_max((struct net_bridge *)br, val); return 0; } EXPORT_SYMBOL(br_compat_multicast_set_hash_max); int br_compat_multicast_set_igmp_version(void *br, unsigned long val) { return br_multicast_set_igmp_version(&((struct net_bridge *)br)->multicast_ctx, val); } EXPORT_SYMBOL(br_compat_multicast_set_igmp_version); int br_compat_multicast_init_stats(void *br_data) { struct net_bridge *br = NULL; int err; if (br_data == NULL) return -ENXIO; br = (struct net_bridge *) br_data; err = br_mdb_hash_init(br); if (err) { return err; } err = br_multicast_init_stats(br); if (err) { br_mdb_hash_fini(br); } return err; } EXPORT_SYMBOL(br_compat_multicast_init_stats); void br_compat_multicast_set_query_use_ifaddr(void *br, unsigned long val) { struct net_bridge *bridge = (struct net_bridge *)br; br_opt_toggle(bridge, BROPT_MULTICAST_QUERY_USE_IFADDR, !!val); } EXPORT_SYMBOL(br_compat_multicast_set_query_use_ifaddr); void br_compat_multicast_set_last_member_cnt(void *br, unsigned long val) { struct net_bridge *bridge = (struct net_bridge *)br; bridge->multicast_ctx.multicast_last_member_count = val; } EXPORT_SYMBOL(br_compat_multicast_set_last_member_cnt); void br_compat_multicast_set_last_member_intvl(void *br, unsigned long val) { struct net_bridge *bridge = (struct net_bridge *)br; bridge->multicast_ctx.multicast_last_member_interval = clock_t_to_jiffies(val); } EXPORT_SYMBOL(br_compat_multicast_set_last_member_intvl); void br_compat_multicast_set_membership_intvl(void *br, unsigned long val) { struct net_bridge *bridge = (struct net_bridge *)br; bridge->multicast_ctx.multicast_membership_interval = clock_t_to_jiffies(val); } EXPORT_SYMBOL(br_compat_multicast_set_membership_intvl); void br_compat_multicast_set_query_intvl(void *br, unsigned long val) { struct net_bridge *bridge = (struct net_bridge *)br; bridge->multicast_ctx.multicast_query_interval = clock_t_to_jiffies(val); } EXPORT_SYMBOL(br_compat_multicast_set_query_intvl); void br_compat_multicast_set_query_response_intvl(void *br, unsigned long val) { struct net_bridge *bridge = (struct net_bridge *)br; bridge->multicast_ctx.multicast_query_response_interval = clock_t_to_jiffies(val); } EXPORT_SYMBOL(br_compat_multicast_set_query_response_intvl); void br_compat_multicast_get_querier(void *br, unsigned long *val) { struct net_bridge *bridge = (struct net_bridge *)br; *val = bridge->multicast_ctx.multicast_querier; } EXPORT_SYMBOL(br_compat_multicast_get_querier); void br_compat_multicast_get_igmp_version(void *br, unsigned long *val) { struct net_bridge *bridge = (struct net_bridge *)br; *val = bridge->multicast_ctx.multicast_igmp_version; } EXPORT_SYMBOL(br_compat_multicast_get_igmp_version); void br_compat_multicast_get_query_use_ifaddr(void *br, unsigned long *val) { struct net_bridge *bridge = (struct net_bridge *)br; *val = br_opt_get(bridge, BROPT_MULTICAST_QUERY_USE_IFADDR); } EXPORT_SYMBOL(br_compat_multicast_get_query_use_ifaddr); void br_compat_multicast_get_last_member_cnt(void *br, unsigned long *val) { struct net_bridge *bridge = (struct net_bridge *)br; *val = bridge->multicast_ctx.multicast_last_member_count; } EXPORT_SYMBOL(br_compat_multicast_get_last_member_cnt); void br_compat_multicast_get_last_member_intvl(void *br, unsigned long *val) { struct net_bridge *bridge = (struct net_bridge *)br; *val = jiffies_to_clock_t(bridge->multicast_ctx.multicast_last_member_interval); } EXPORT_SYMBOL(br_compat_multicast_get_last_member_intvl); void br_compat_multicast_get_membership_intvl(void *br, unsigned long *val) { struct net_bridge *bridge = (struct net_bridge *)br; *val = jiffies_to_clock_t(bridge->multicast_ctx.multicast_membership_interval); } EXPORT_SYMBOL(br_compat_multicast_get_membership_intvl); void br_compat_multicast_get_query_intvl(void *br, unsigned long *val) { struct net_bridge *bridge = (struct net_bridge *)br; *val = jiffies_to_clock_t(bridge->multicast_ctx.multicast_query_interval); } EXPORT_SYMBOL(br_compat_multicast_get_query_intvl); void br_compat_multicast_get_query_response_intvl(void *br, unsigned long *val) { struct net_bridge *bridge = (struct net_bridge *)br; *val = jiffies_to_clock_t(bridge->multicast_ctx.multicast_query_response_interval); } EXPORT_SYMBOL(br_compat_multicast_get_query_response_intvl); int br_compat_multicast_fill_info(void *br, struct sk_buff *skb, const struct net_device *br_dev) { #ifdef CONFIG_BRIDGE_IGMP_SNOOPING struct net_bridge *bridge = (struct net_bridge *)br; if (nla_put_u8(skb, IFLA_BR_MCAST_QUERIER, bridge->multicast_ctx.multicast_querier) || nla_put_u8(skb, IFLA_BR_MCAST_SNOOPING, br_opt_get(bridge, BROPT_MULTICAST_ENABLED)) || nla_put_u8(skb, IFLA_BR_MCAST_IGMP_VERSION, bridge->multicast_ctx.multicast_igmp_version) || nla_put_u8(skb, IFLA_BR_MCAST_QUERY_USE_IFADDR, br_opt_get(bridge, BROPT_MULTICAST_QUERY_USE_IFADDR)) || nla_put_u32(skb, IFLA_BR_MCAST_LAST_MEMBER_CNT, bridge->multicast_ctx.multicast_last_member_count)) return -EMSGSIZE; #if IS_ENABLED(CONFIG_IPV6) if (nla_put_u8(skb, IFLA_BR_MCAST_MLD_VERSION, bridge->multicast_ctx.multicast_mld_version)) return -EMSGSIZE; #endif if (nla_put_u64_64bit(skb, IFLA_BR_MCAST_LAST_MEMBER_INTVL, jiffies_to_clock_t(bridge->multicast_ctx.multicast_last_member_interval), IFLA_BR_PAD)) return -EMSGSIZE; if (nla_put_u64_64bit(skb, IFLA_BR_MCAST_MEMBERSHIP_INTVL, jiffies_to_clock_t(bridge->multicast_ctx.multicast_membership_interval), IFLA_BR_PAD)) return -EMSGSIZE; if (nla_put_u64_64bit(skb, IFLA_BR_MCAST_QUERY_INTVL, jiffies_to_clock_t(bridge->multicast_ctx.multicast_query_interval), IFLA_BR_PAD)) return -EMSGSIZE; if (nla_put_u64_64bit(skb, IFLA_BR_MCAST_QUERY_RESPONSE_INTVL, jiffies_to_clock_t(bridge->multicast_ctx.multicast_query_response_interval), IFLA_BR_PAD)) return -EMSGSIZE; #endif return 0; } EXPORT_SYMBOL(br_compat_multicast_fill_info); int br_compat_multicast_fill_slave_info(void *p, struct sk_buff *skb, const struct net_device *br_dev, const struct net_device *dev) { struct net_bridge_port *port = (struct net_bridge_port *)p; if (nla_put_u8(skb, IFLA_BRPORT_FAST_LEAVE, !!(port->flags & BR_MULTICAST_FAST_LEAVE))) return -EMSGSIZE; if (nla_put_u8(skb, IFLA_BRPORT_MODE, !!(port->flags & BR_HAIRPIN_MODE))) return -EMSGSIZE; return 0; } EXPORT_SYMBOL(br_compat_multicast_fill_slave_info); #if IS_ENABLED(CONFIG_IPV6) int br_compat_multicast_set_mld_version(void *br, unsigned long val) { int res; local_bh_disable(); res = br_multicast_set_mld_version(&((struct net_bridge *)br)->multicast_ctx, val); local_bh_enable(); return res; } EXPORT_SYMBOL(br_compat_multicast_set_mld_version); void br_compat_multicast_get_mld_version(void *br, unsigned long *val) { struct net_bridge *bridge = (struct net_bridge *)br; *val = bridge->multicast_ctx.multicast_mld_version; } EXPORT_SYMBOL(br_compat_multicast_get_mld_version); #endif int br_compat_set_port_flag(void *brcompat_data, unsigned long flag, unsigned long mask) { struct net_bridge_port *p; if (!brcompat_data) return -EINVAL; p = (struct net_bridge_port *)brcompat_data; if (flag) p->flags |= mask; else p->flags &= ~mask; return 0; } EXPORT_SYMBOL(br_compat_set_port_flag); int br_compat_get_port_flag(void *brcompat_data, unsigned long *flag, unsigned long mask) { struct net_bridge_port *p; if (!brcompat_data || !flag) return -EINVAL; p = (struct net_bridge_port *)brcompat_data; *flag = !!(p->flags & mask); return 0; } EXPORT_SYMBOL(br_compat_get_port_flag); int br_compat_bridge_create(struct net_device *dev, void **br_data) { int err = 0; struct net_bridge *br = NULL; if (!br_data || !dev) { err = -EINVAL; goto err; } br = kzalloc(sizeof(struct net_bridge), GFP_KERNEL); if (!br) { err = -ENOMEM; goto err; } *br_data = br; br->dev = dev; spin_lock_init(&br->lock); INIT_LIST_HEAD(&br->port_list); spin_lock_init(&br->hash_lock); br->bridge_id.prio[0] = 0x80; br->bridge_id.prio[1] = 0x00; br->stp_enabled = BR_NO_STP; br->group_fwd_mask = BR_GROUPFWD_DEFAULT; br->group_fwd_mask_required = BR_GROUPFWD_DEFAULT; br->designated_root = br->bridge_id; br->bridge_max_age = br->max_age = 20 * HZ; br->bridge_hello_time = br->hello_time = 2 * HZ; br->bridge_forward_delay = br->forward_delay = 15 * HZ; br->ageing_time = BR_DEFAULT_AGEING_TIME; br_multicast_init(br); #ifdef CONFIG_TI_L2_SELECTIVE_PACKET_HANDLING br->selective_packet_handler = NULL; #endif /* CONFIG_TI_L2_SELECTIVE_PACKET_HANDLING */ #ifdef CONFIG_LTQ_MCAST_SNOOPING br->this_br_snooping_enabled = false; #endif /* CONFIG_LTQ_MCAST_SNOOPING */ return err; err: if (br) kfree(br); return err; } EXPORT_SYMBOL(br_compat_bridge_create); int br_compat_bridge_port_create(void* br_data, struct net_device *dev, void** br_port) { struct net_bridge *br = NULL; struct net_bridge_port *port = NULL; int err = 0; if (!br_data || !br_port || !dev) { err = -EINVAL; goto err; } br = (struct net_bridge *) br_data; port = kzalloc(sizeof(struct net_bridge_port), GFP_KERNEL); if (!port) { err = -ENOMEM; goto err; } *(struct net_bridge_port **)br_port = port; port->br = br; dev_hold(dev); port->dev = dev; port->priority = 0x8000 >> BR_PORT_BITS; port->state = BR_STATE_FORWARDING; err = br_multicast_add_port(port); local_bh_disable(); br_multicast_enable_port(port); local_bh_enable(); list_add_rcu(&port->list, &br->port_list); err: if (err) { dev_put(dev); kfree(port); *(struct net_bridge_port **)br_port = NULL; } return err; } EXPORT_SYMBOL(br_compat_bridge_port_create); int br_compat_multicast_add_group(void *br_ptr, void* port_ptr, struct br_ip *group, const unsigned char *src) { bool igmpv2; int err=0; struct net_bridge *br = NULL; struct net_bridge_port *port = NULL; if ((br_ptr == NULL) && (port_ptr == NULL)){ err = -ENODEV; goto err; } if (br_ptr){ br = (struct net_bridge *)br_ptr; } if (port_ptr) { port = (struct net_bridge_port *) port_ptr; br = port->br; } if (!br) { err = -EINVAL; goto err; } igmpv2 = br->multicast_ctx.multicast_igmp_version == 2; local_bh_disable(); err = br_multicast_add_group(&br->multicast_ctx, &port->multicast_ctx, group, src, igmpv2 ? MCAST_EXCLUDE : MCAST_INCLUDE, igmpv2); local_bh_enable(); err: return err; } EXPORT_SYMBOL(br_compat_multicast_add_group); int br_compat_multicast_leave_group(void *br_ptr, void* port_ptr, struct br_ip *group, const unsigned char *src) { int err=0; struct net_bridge *br = NULL; struct net_bridge_port *port = NULL; struct bridge_mcast_own_query *own_query; if ((br_ptr == NULL) && (port_ptr == NULL)){ err = -ENODEV; goto err; } if (br_ptr) br = (struct net_bridge *)br_ptr; if (port_ptr) { port = (struct net_bridge_port *) port_ptr; br = port->br; } if (br == NULL) { err = -EINVAL; goto err; } own_query = port ? &port->multicast_ctx.ip4_own_query : &br->multicast_ctx.ip4_own_query; local_bh_disable(); br_multicast_leave_group(&br->multicast_ctx, &port->multicast_ctx, group, &br->multicast_ctx.ip4_other_query, own_query, src); local_bh_enable(); err: return err; } EXPORT_SYMBOL(br_compat_multicast_leave_group);