IFLA_BRPORT_GROUP_FWD_MASK,
        IFLA_BRPORT_NEIGH_SUPPRESS,
        IFLA_BRPORT_ISOLATED,
+       IFLA_BRPORT_BACKUP_PORT,
        __IFLA_BRPORT_MAX
 };
 #define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1)
 
 void br_forward(const struct net_bridge_port *to,
                struct sk_buff *skb, bool local_rcv, bool local_orig)
 {
-       if (to && should_deliver(to, skb)) {
+       if (unlikely(!to))
+               goto out;
+
+       /* redirect to backup link if the destination port is down */
+       if (rcu_access_pointer(to->backup_port) && !netif_carrier_ok(to->dev)) {
+               struct net_bridge_port *backup_port;
+
+               backup_port = rcu_dereference(to->backup_port);
+               if (unlikely(!backup_port))
+                       goto out;
+               to = backup_port;
+       }
+
+       if (should_deliver(to, skb)) {
                if (local_rcv)
                        deliver_clone(to, skb, local_orig);
                else
                return;
        }
 
+out:
        if (!local_rcv)
                kfree_skb(skb);
 }
 
        }
 }
 
+int nbp_backup_change(struct net_bridge_port *p,
+                     struct net_device *backup_dev)
+{
+       struct net_bridge_port *old_backup = rtnl_dereference(p->backup_port);
+       struct net_bridge_port *backup_p = NULL;
+
+       ASSERT_RTNL();
+
+       if (backup_dev) {
+               if (!br_port_exists(backup_dev))
+                       return -ENOENT;
+
+               backup_p = br_port_get_rtnl(backup_dev);
+               if (backup_p->br != p->br)
+                       return -EINVAL;
+       }
+
+       if (p == backup_p)
+               return -EINVAL;
+
+       if (old_backup == backup_p)
+               return 0;
+
+       /* if the backup link is already set, clear it */
+       if (old_backup)
+               old_backup->backup_redirected_cnt--;
+
+       if (backup_p)
+               backup_p->backup_redirected_cnt++;
+       rcu_assign_pointer(p->backup_port, backup_p);
+
+       return 0;
+}
+
+static void nbp_backup_clear(struct net_bridge_port *p)
+{
+       nbp_backup_change(p, NULL);
+       if (p->backup_redirected_cnt) {
+               struct net_bridge_port *cur_p;
+
+               list_for_each_entry(cur_p, &p->br->port_list, list) {
+                       struct net_bridge_port *backup_p;
+
+                       backup_p = rtnl_dereference(cur_p->backup_port);
+                       if (backup_p == p)
+                               nbp_backup_change(cur_p, NULL);
+               }
+       }
+
+       WARN_ON(rcu_access_pointer(p->backup_port) || p->backup_redirected_cnt);
+}
+
 static void nbp_update_port_count(struct net_bridge *br)
 {
        struct net_bridge_port *p;
        nbp_vlan_flush(p);
        br_fdb_delete_by_port(br, p, 0, 1);
        switchdev_deferred_process();
+       nbp_backup_clear(p);
 
        nbp_update_port_count(br);
 
 
                + nla_total_size(1) /* IFLA_OPERSTATE */
                + nla_total_size(br_port_info_size()) /* IFLA_PROTINFO */
                + nla_total_size(br_get_link_af_size_filtered(dev,
-                                filter_mask)); /* IFLA_AF_SPEC */
+                                filter_mask)) /* IFLA_AF_SPEC */
+               + nla_total_size(4); /* IFLA_BRPORT_BACKUP_PORT */
 }
 
 static int br_port_fill_attrs(struct sk_buff *skb,
                              const struct net_bridge_port *p)
 {
        u8 mode = !!(p->flags & BR_HAIRPIN_MODE);
+       struct net_bridge_port *backup_p;
        u64 timerval;
 
        if (nla_put_u8(skb, IFLA_BRPORT_STATE, p->state) ||
                return -EMSGSIZE;
 #endif
 
+       /* we might be called only with br->lock */
+       rcu_read_lock();
+       backup_p = rcu_dereference(p->backup_port);
+       if (backup_p)
+               nla_put_u32(skb, IFLA_BRPORT_BACKUP_PORT,
+                           backup_p->dev->ifindex);
+       rcu_read_unlock();
+
        return 0;
 }
 
        [IFLA_BRPORT_GROUP_FWD_MASK] = { .type = NLA_U16 },
        [IFLA_BRPORT_NEIGH_SUPPRESS] = { .type = NLA_U8 },
        [IFLA_BRPORT_ISOLATED]  = { .type = NLA_U8 },
+       [IFLA_BRPORT_BACKUP_PORT] = { .type = NLA_U32 },
 };
 
 /* Change the state of the port and notify spanning tree */
        if (err)
                return err;
 
+       if (tb[IFLA_BRPORT_BACKUP_PORT]) {
+               struct net_device *backup_dev = NULL;
+               u32 backup_ifindex;
+
+               backup_ifindex = nla_get_u32(tb[IFLA_BRPORT_BACKUP_PORT]);
+               if (backup_ifindex) {
+                       backup_dev = __dev_get_by_index(dev_net(p->dev),
+                                                       backup_ifindex);
+                       if (!backup_dev)
+                               return -ENOENT;
+               }
+
+               err = nbp_backup_change(p, backup_dev);
+               if (err)
+                       return err;
+       }
+
        br_port_flags_change(p, old_flags ^ p->flags);
        return 0;
 }
 
 #ifdef CONFIG_BRIDGE_VLAN_FILTERING
        struct net_bridge_vlan_group    __rcu *vlgrp;
 #endif
+       struct net_bridge_port          __rcu *backup_port;
 
        /* STP */
        u8                              priority;
        int                             offload_fwd_mark;
 #endif
        u16                             group_fwd_mask;
+       u16                             backup_redirected_cnt;
 };
 
 #define kobj_to_brport(obj)    container_of(obj, struct net_bridge_port, kobj)
                                        netdev_features_t features);
 void br_port_flags_change(struct net_bridge_port *port, unsigned long mask);
 void br_manage_promisc(struct net_bridge *br);
+int nbp_backup_change(struct net_bridge_port *p, struct net_device *backup_dev);
 
 /* br_input.c */
 int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb);
 
 static BRPORT_ATTR(group_fwd_mask, 0644, show_group_fwd_mask,
                   store_group_fwd_mask);
 
+static ssize_t show_backup_port(struct net_bridge_port *p, char *buf)
+{
+       struct net_bridge_port *backup_p;
+       int ret = 0;
+
+       rcu_read_lock();
+       backup_p = rcu_dereference(p->backup_port);
+       if (backup_p)
+               ret = sprintf(buf, "%s\n", backup_p->dev->name);
+       rcu_read_unlock();
+
+       return ret;
+}
+
+static int store_backup_port(struct net_bridge_port *p, char *buf)
+{
+       struct net_device *backup_dev = NULL;
+       char *nl = strchr(buf, '\n');
+
+       if (nl)
+               *nl = '\0';
+
+       if (strlen(buf) > 0) {
+               backup_dev = __dev_get_by_name(dev_net(p->dev), buf);
+               if (!backup_dev)
+                       return -ENOENT;
+       }
+
+       return nbp_backup_change(p, backup_dev);
+}
+static BRPORT_ATTR_RAW(backup_port, 0644, show_backup_port, store_backup_port);
+
 BRPORT_ATTR_FLAG(hairpin_mode, BR_HAIRPIN_MODE);
 BRPORT_ATTR_FLAG(bpdu_guard, BR_BPDU_GUARD);
 BRPORT_ATTR_FLAG(root_block, BR_ROOT_BLOCK);
        &brport_attr_group_fwd_mask,
        &brport_attr_neigh_suppress,
        &brport_attr_isolated,
+       &brport_attr_backup_port,
        NULL
 };