/* When a static FDB entry is added, the mac address from the entry is * added to the bridge private HW address list and all required ports * are then updated with the new information. * Called under RTNL.
*/ staticvoid fdb_add_hw_addr(struct net_bridge *br, constunsignedchar *addr)
{ int err; struct net_bridge_port *p;
ASSERT_RTNL();
list_for_each_entry(p, &br->port_list, list) { if (!br_promisc_port(p)) {
err = dev_uc_add(p->dev, addr); if (err) goto undo;
}
}
/* When a static FDB entry is deleted, the HW address from that entry is * also removed from the bridge private HW address list and updates all * the ports with needed information. * Called under RTNL.
*/ staticvoid fdb_del_hw_addr(struct net_bridge *br, constunsignedchar *addr)
{ struct net_bridge_port *p;
ASSERT_RTNL();
list_for_each_entry(p, &br->port_list, list) { if (!br_promisc_port(p))
dev_uc_del(p->dev, addr);
}
}
/* Delete a local entry if no other port had the same address. * * This function should only be called on entries with BR_FDB_LOCAL set, * so even with BR_FDB_ADDED_BY_USER cleared we never need to increase * the accounting for dynamically learned entries again.
*/ staticvoid fdb_delete_local(struct net_bridge *br, conststruct net_bridge_port *p, struct net_bridge_fdb_entry *f)
{ constunsignedchar *addr = f->key.addr.addr; struct net_bridge_vlan_group *vg; conststruct net_bridge_vlan *v; struct net_bridge_port *op;
u16 vid = f->key.vlan_id;
/* Maybe another port has same hw addr? */
list_for_each_entry(op, &br->port_list, list) {
vg = nbp_vlan_group(op); if (op != p && ether_addr_equal(op->dev->dev_addr, addr) &&
(!vid || br_vlan_find(vg, vid))) {
f->dst = op;
clear_bit(BR_FDB_ADDED_BY_USER, &f->flags); return;
}
}
vg = br_vlan_group(br);
v = br_vlan_find(vg, vid); /* Maybe bridge device has same hw addr? */ if (p && ether_addr_equal(br->dev->dev_addr, addr) &&
(!vid || (v && br_vlan_should_use(v)))) {
f->dst = NULL;
clear_bit(BR_FDB_ADDED_BY_USER, &f->flags); return;
}
fdb = br_fdb_find(br, addr, vid); if (fdb) { /* it is okay to have multiple ports with same * address, just use the first one.
*/ if (test_bit(BR_FDB_LOCAL, &fdb->flags)) return 0;
br_warn(br, "adding interface %s with same address as a received packet (addr:%pM, vlan:%u)\n",
source ? source->dev->name : br->dev->name, addr, vid);
fdb_delete(br, fdb, true);
}
spin_lock_bh(&br->hash_lock);
vg = nbp_vlan_group(p);
hlist_for_each_entry(f, &br->fdb_list, fdb_node) { if (f->dst == p && test_bit(BR_FDB_LOCAL, &f->flags) &&
!test_bit(BR_FDB_ADDED_BY_USER, &f->flags)) { /* delete old one */
fdb_delete_local(br, p, f);
/* if this port has no vlan information * configured, we can safely be done at * this point.
*/ if (!vg || !vg->num_vlans) goto insert;
}
}
insert: /* insert new address, may fail if invalid address or dup. */
fdb_add_local(br, p, newaddr, 0);
if (!vg || !vg->num_vlans) goto done;
/* Now add entries for every VLAN configured on the port. * This function runs under RTNL so the bitmap will not change * from under us.
*/
list_for_each_entry(v, &vg->vlan_list, vlist)
fdb_add_local(br, p, newaddr, v->vid);
/* If old entry was unassociated with any port, then delete it. */
f = br_fdb_find(br, br->dev->dev_addr, 0); if (f && test_bit(BR_FDB_LOCAL, &f->flags) &&
!f->dst && !test_bit(BR_FDB_ADDED_BY_USER, &f->flags))
fdb_delete_local(br, NULL, f);
fdb_add_local(br, NULL, newaddr, 0);
vg = br_vlan_group(br); if (!vg || !vg->num_vlans) goto out; /* Now remove and add entries for every VLAN configured on the * bridge. This function runs under RTNL so the bitmap will not * change from under us.
*/
list_for_each_entry(v, &vg->vlan_list, vlist) { if (!br_vlan_should_use(v)) continue;
f = br_fdb_find(br, br->dev->dev_addr, v->vid); if (f && test_bit(BR_FDB_LOCAL, &f->flags) &&
!f->dst && !test_bit(BR_FDB_ADDED_BY_USER, &f->flags))
fdb_delete_local(br, NULL, f);
fdb_add_local(br, NULL, newaddr, v->vid);
}
out:
spin_unlock_bh(&br->hash_lock);
}
/* this part is tricky, in order to avoid blocking learning and * consequently forwarding, we rely on rcu to delete objects with * delayed freeing allowing us to continue traversing
*/
rcu_read_lock();
hlist_for_each_entry_rcu(f, &br->fdb_list, fdb_node) { unsignedlong this_timer = f->updated + delay;
if (test_bit(BR_FDB_STATIC, &f->flags) ||
test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &f->flags)) { if (test_bit(BR_FDB_NOTIFY, &f->flags)) { if (time_after(this_timer, now))
work_delay = min(work_delay,
this_timer - now); elseif (!test_and_set_bit(BR_FDB_NOTIFY_INACTIVE,
&f->flags))
fdb_notify(br, f, RTM_NEWNEIGH, false);
} continue;
}
dev = __dev_get_by_index(dev_net(br->dev), ifindex); if (!dev) {
NL_SET_ERR_MSG_MOD(extack, "Unknown flush device ifindex"); return -ENODEV;
} if (!netif_is_bridge_master(dev) && !netif_is_bridge_port(dev)) {
NL_SET_ERR_MSG_MOD(extack, "Flush device is not a bridge or bridge port"); return -EINVAL;
} if (netif_is_bridge_master(dev) && dev != br->dev) {
NL_SET_ERR_MSG_MOD(extack, "Flush bridge device does not match target bridge device"); return -EINVAL;
} if (netif_is_bridge_port(dev)) { struct net_bridge_port *p = br_port_get_rtnl(dev);
if (p->br != br) {
NL_SET_ERR_MSG_MOD(extack, "Port belongs to a different bridge device"); return -EINVAL;
}
}
if (netif_is_bridge_master(dev)) {
br = netdev_priv(dev);
} else {
p = br_port_get_rtnl(dev); if (!p) {
NL_SET_ERR_MSG_MOD(extack, "Device is not a bridge port"); return -EINVAL;
}
br = p->br;
}
if (tb[NDA_VLAN])
desc.vlan_id = nla_get_u16(tb[NDA_VLAN]);
if (ndm_flags & ~FDB_FLUSH_ALLOWED_NDM_FLAGS) {
NL_SET_ERR_MSG(extack, "Unsupported fdb flush ndm flag bits set"); return -EINVAL;
} if (ndm->ndm_state & ~FDB_FLUSH_ALLOWED_NDM_STATES) {
NL_SET_ERR_MSG(extack, "Unsupported fdb flush ndm state bits set"); return -EINVAL;
}
/* Flush all entries referring to a specific port. * if do_all is set also flush static entries * if vid is set delete all entries that match the vlan_id
*/ void br_fdb_delete_by_port(struct net_bridge *br, conststruct net_bridge_port *p,
u16 vid, int do_all)
{ struct net_bridge_fdb_entry *f; struct hlist_node *tmp;
#if IS_ENABLED(CONFIG_ATM_LANE) /* Interface used by ATM LANE hook to test
* if an addr is on some other bridge port */ int br_fdb_test_addr(struct net_device *dev, unsignedchar *addr)
{ struct net_bridge_fdb_entry *fdb; struct net_bridge_port *port; int ret;
rcu_read_lock();
port = br_port_get_rcu(dev); if (!port)
ret = 0; else { conststruct net_bridge_port *dst = NULL;
fdb = br_fdb_find_rcu(port->br, addr, 0); if (fdb)
dst = READ_ONCE(fdb->dst);
ret = dst && dst->dev != dev &&
dst->state == BR_STATE_FORWARDING;
}
rcu_read_unlock();
return ret;
} #endif/* CONFIG_ATM_LANE */
/* * Fill buffer with forwarding table records in * the API format.
*/ int br_fdb_fillbuf(struct net_bridge *br, void *buf, unsignedlong maxnum, unsignedlong skip)
{ struct net_bridge_fdb_entry *f; struct __fdb_entry *fe = buf; int num = 0;
/* Add entry for local address of interface */ int br_fdb_add_local(struct net_bridge *br, struct net_bridge_port *source, constunsignedchar *addr, u16 vid)
{ int ret;
/* some users want to always flood. */ if (hold_time(br) == 0) return;
fdb = fdb_find_rcu(&br->fdb_hash_tbl, addr, vid); if (likely(fdb)) { /* attempt to update an entry for a local interface */ if (unlikely(test_bit(BR_FDB_LOCAL, &fdb->flags))) { if (net_ratelimit())
br_warn(br, "received packet on %s with own address as source address (addr:%pM, vlan:%u)\n",
source->dev->name, addr, vid);
} else { unsignedlong now = jiffies; bool fdb_modified = false;
/* fastpath: update of existing entry */ if (unlikely(source != READ_ONCE(fdb->dst) &&
!test_bit(BR_FDB_STICKY, &fdb->flags))) {
br_switchdev_fdb_notify(br, fdb, RTM_DELNEIGH);
WRITE_ONCE(fdb->dst, source);
fdb_modified = true; /* Take over HW learned entry */ if (unlikely(test_bit(BR_FDB_ADDED_BY_EXT_LEARN,
&fdb->flags)))
clear_bit(BR_FDB_ADDED_BY_EXT_LEARN,
&fdb->flags); /* Clear locked flag when roaming to an * unlocked port.
*/ if (unlikely(test_bit(BR_FDB_LOCKED, &fdb->flags)))
clear_bit(BR_FDB_LOCKED, &fdb->flags);
}
if (unlikely(test_bit(BR_FDB_ADDED_BY_USER, &flags))) {
set_bit(BR_FDB_ADDED_BY_USER, &fdb->flags); if (test_and_clear_bit(BR_FDB_DYNAMIC_LEARNED,
&fdb->flags))
atomic_dec(&br->fdb_n_learned);
} if (unlikely(fdb_modified)) {
trace_br_fdb_update(br, source, addr, vid, flags);
fdb_notify(br, fdb, RTM_NEWNEIGH, true);
}
}
} else {
spin_lock(&br->hash_lock);
fdb = fdb_create(br, source, addr, vid, flags); if (fdb) {
trace_br_fdb_update(br, source, addr, vid, flags);
fdb_notify(br, fdb, RTM_NEWNEIGH, true);
} /* else we lose race and someone else inserts * it first, don't bother updating
*/
spin_unlock(&br->hash_lock);
}
}
/* Dump information about entries, in response to GETNEIGH */ int br_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb, struct net_device *dev, struct net_device *filter_dev, int *idx)
{ struct ndo_fdb_dump_context *ctx = (void *)cb->ctx; struct net_bridge *br = netdev_priv(dev); struct net_bridge_fdb_entry *f; int err = 0;
if (!netif_is_bridge_master(dev)) return err;
if (!filter_dev) {
err = ndo_dflt_fdb_dump(skb, cb, dev, NULL, idx); if (err < 0) return err;
}
rcu_read_lock();
hlist_for_each_entry_rcu(f, &br->fdb_list, fdb_node) { if (*idx < ctx->fdb_idx) goto skip; if (filter_dev && (!f->dst || f->dst->dev != filter_dev)) { if (filter_dev != dev) goto skip; /* !f->dst is a special case for bridge * It means the MAC belongs to the bridge * Therefore need a little more filtering * we only want to dump the !f->dst case
*/ if (f->dst) goto skip;
} if (!filter_dev && f->dst) goto skip;
/* returns true if the fdb is modified */ staticbool fdb_handle_notify(struct net_bridge_fdb_entry *fdb, u8 notify)
{ bool modified = false;
/* allow to mark an entry as inactive, usually done on creation */ if ((notify & FDB_NOTIFY_INACTIVE_BIT) &&
!test_and_set_bit(BR_FDB_NOTIFY_INACTIVE, &fdb->flags))
modified = true;
/* If the port cannot learn allow only local and static entries */ if (source && !(state & NUD_PERMANENT) && !(state & NUD_NOARP) &&
!(source->state == BR_STATE_LEARNING ||
source->state == BR_STATE_FORWARDING)) return -EPERM;
if (!source && !(state & NUD_PERMANENT)) {
pr_info("bridge: RTM_NEWNEIGH %s without NUD_PERMANENT\n",
br->dev->name); return -EINVAL;
}
if (is_sticky && (state & NUD_PERMANENT)) return -EINVAL;
if (nfea_tb[NFEA_ACTIVITY_NOTIFY]) {
notify = nla_get_u8(nfea_tb[NFEA_ACTIVITY_NOTIFY]); if ((notify & ~BR_FDB_NOTIFY_SETTABLE_BITS) ||
(notify & BR_FDB_NOTIFY_SETTABLE_BITS) == FDB_NOTIFY_INACTIVE_BIT) return -EINVAL;
}
fdb = br_fdb_find(br, addr, vid); if (fdb == NULL) { if (!(flags & NLM_F_CREATE)) return -ENOENT;
if (ndm->ndm_flags & NTF_USE) { if (!p) {
pr_info("bridge: RTM_NEWNEIGH %s with NTF_USE is not supported\n",
br->dev->name); return -EINVAL;
} if (!nbp_state_should_learn(p)) return 0;
if (!(ndm->ndm_state & (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE))) {
pr_info("bridge: RTM_NEWNEIGH with invalid state %#x\n", ndm->ndm_state); return -EINVAL;
}
if (is_zero_ether_addr(addr)) {
pr_info("bridge: RTM_NEWNEIGH with invalid ether address\n"); return -EINVAL;
}
if (netif_is_bridge_master(dev)) {
br = netdev_priv(dev);
vg = br_vlan_group(br);
} else {
p = br_port_get_rtnl(dev); if (!p) {
pr_info("bridge: RTM_NEWNEIGH %s not a bridge port\n",
dev->name); return -EINVAL;
}
br = p->br;
vg = nbp_vlan_group(p);
}
if (tb[NDA_FLAGS_EXT])
ext_flags = nla_get_u32(tb[NDA_FLAGS_EXT]);
if (ext_flags & NTF_EXT_LOCKED) {
NL_SET_ERR_MSG_MOD(extack, "Cannot add FDB entry with \"locked\" flag set"); return -EINVAL;
}
if (vid) {
v = br_vlan_find(vg, vid); if (!v || !br_vlan_should_use(v)) {
pr_info("bridge: RTM_NEWNEIGH with unconfigured vlan %d on %s\n", vid, dev->name); return -EINVAL;
}
/* VID was specified, so use it. */
err = __br_fdb_add(ndm, br, p, addr, nlh_flags, vid, nfea_tb,
notified, extack);
} else {
err = __br_fdb_add(ndm, br, p, addr, nlh_flags, 0, nfea_tb,
notified, extack); if (err || !vg || !vg->num_vlans) goto out;
/* We have vlans configured on this port and user didn't * specify a VLAN. To be nice, add/update entry for every * vlan on this port.
*/
list_for_each_entry(v, &vg->vlan_list, vlist) { if (!br_vlan_should_use(v)) continue;
err = __br_fdb_add(ndm, br, p, addr, nlh_flags, v->vid,
nfea_tb, notified, extack); if (err) goto out;
}
}
int br_fdb_sync_static(struct net_bridge *br, struct net_bridge_port *p)
{ struct net_bridge_fdb_entry *f, *tmp; int err = 0;
ASSERT_RTNL();
/* the key here is that static entries change only under rtnl */
rcu_read_lock();
hlist_for_each_entry_rcu(f, &br->fdb_list, fdb_node) { /* We only care for static entries */ if (!test_bit(BR_FDB_STATIC, &f->flags)) continue;
err = dev_uc_add(p->dev, f->key.addr.addr); if (err) goto rollback;
}
done:
rcu_read_unlock();
return err;
rollback:
hlist_for_each_entry_rcu(tmp, &br->fdb_list, fdb_node) { /* We only care for static entries */ if (!test_bit(BR_FDB_STATIC, &tmp->flags)) continue; if (tmp == f) break;
dev_uc_del(p->dev, tmp->key.addr.addr);
}
rcu_read_lock();
hlist_for_each_entry_rcu(f, &br->fdb_list, fdb_node) { /* We only care for static entries */ if (!test_bit(BR_FDB_STATIC, &f->flags)) continue;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.