]> git.proxmox.com Git - mirror_frr.git/blobdiff - pimd/pim_vxlan.c
*: s/TRUE/true/, s/FALSE/false/
[mirror_frr.git] / pimd / pim_vxlan.c
index 24b71bf3d838b1155b656b528db9c083d54444aa..cc2e6627d57e279368e2d817d1c826dff7bedfed 100644 (file)
 /* pim-vxlan global info */
 struct pim_vxlan vxlan_info, *pim_vxlan_p = &vxlan_info;
 
+static void pim_vxlan_work_timer_setup(bool start);
+static void pim_vxlan_set_peerlink_rif(struct pim_instance *pim,
+                       struct interface *ifp);
+
+/*************************** vxlan work list **********************************
+ * A work list is maintained for staggered generation of pim null register
+ * messages for vxlan SG entries that are in a reg_join state.
+ *
+ * A max of 500 NULL registers are generated at one shot. If paused reg
+ * generation continues on the next second and so on till all register
+ * messages have been sent out. And the process is restarted every 60s.
+ *
+ * purpose of this null register generation is to setup the SPT and maintain
+ * independent of the presence of overlay BUM traffic.
+ ****************************************************************************/
+static void pim_vxlan_do_reg_work(void)
+{
+       struct listnode *listnode;
+       int work_cnt = 0;
+       struct pim_vxlan_sg *vxlan_sg;
+       static int sec_count;
+
+       ++sec_count;
+
+       if (sec_count > PIM_VXLAN_NULL_REG_INTERVAL) {
+               sec_count = 0;
+               listnode = vxlan_info.next_work ?
+                                       vxlan_info.next_work :
+                                       vxlan_info.work_list->head;
+               if (PIM_DEBUG_VXLAN && listnode)
+                       zlog_debug("vxlan SG work %s",
+                               vxlan_info.next_work ? "continues" : "starts");
+       } else {
+               listnode = vxlan_info.next_work;
+       }
+
+       for (; listnode; listnode = listnode->next) {
+               vxlan_sg = (struct pim_vxlan_sg *)listnode->data;
+               if (vxlan_sg->up && (vxlan_sg->up->reg_state == PIM_REG_JOIN)) {
+                       if (PIM_DEBUG_VXLAN)
+                               zlog_debug("vxlan SG %s periodic NULL register",
+                                               vxlan_sg->sg_str);
+                       pim_null_register_send(vxlan_sg->up);
+                       ++work_cnt;
+               }
+
+               if (work_cnt > vxlan_info.max_work_cnt) {
+                       vxlan_info.next_work = listnode->next;
+                       if (PIM_DEBUG_VXLAN)
+                               zlog_debug("vxlan SG %d work items proc and pause",
+                                       work_cnt);
+                       return;
+               }
+       }
+
+       if (work_cnt) {
+               if (PIM_DEBUG_VXLAN)
+                       zlog_debug("vxlan SG %d work items proc", work_cnt);
+       }
+       vxlan_info.next_work = NULL;
+}
+
+/* Staggered work related info is initialized when the first work comes
+ * along
+ */
+static void pim_vxlan_init_work(void)
+{
+       if (vxlan_info.flags & PIM_VXLANF_WORK_INITED)
+               return;
+
+       vxlan_info.max_work_cnt = PIM_VXLAN_WORK_MAX;
+       vxlan_info.flags |= PIM_VXLANF_WORK_INITED;
+       vxlan_info.work_list = list_new();
+       pim_vxlan_work_timer_setup(true/* start */);
+}
+
+static void pim_vxlan_add_work(struct pim_vxlan_sg *vxlan_sg)
+{
+       if (vxlan_sg->flags & PIM_VXLAN_SGF_DEL_IN_PROG) {
+               if (PIM_DEBUG_VXLAN)
+                       zlog_debug("vxlan SG %s skip work list; del-in-prog",
+                                       vxlan_sg->sg_str);
+               return;
+       }
+
+       pim_vxlan_init_work();
+
+       /* already a part of the work list */
+       if (vxlan_sg->work_node)
+               return;
+
+       if (PIM_DEBUG_VXLAN)
+               zlog_debug("vxlan SG %s work list add",
+                               vxlan_sg->sg_str);
+       vxlan_sg->work_node = listnode_add(vxlan_info.work_list, vxlan_sg);
+       /* XXX: adjust max_work_cnt if needed */
+}
+
+static void pim_vxlan_del_work(struct pim_vxlan_sg *vxlan_sg)
+{
+       if (!vxlan_sg->work_node)
+               return;
+
+       if (PIM_DEBUG_VXLAN)
+               zlog_debug("vxlan SG %s work list del",
+                               vxlan_sg->sg_str);
+
+       if (vxlan_sg->work_node == vxlan_info.next_work)
+               vxlan_info.next_work = vxlan_sg->work_node->next;
+
+       list_delete_node(vxlan_info.work_list, vxlan_sg->work_node);
+       vxlan_sg->work_node = NULL;
+}
+
+void pim_vxlan_update_sg_reg_state(struct pim_instance *pim,
+               struct pim_upstream *up, bool reg_join)
+{
+       struct pim_vxlan_sg *vxlan_sg;
+
+       vxlan_sg = pim_vxlan_sg_find(pim, &up->sg);
+       if (!vxlan_sg)
+               return;
+
+       /* add the vxlan sg entry to a work list for periodic reg joins.
+        * the entry will stay in the list as long as the register state is
+        * PIM_REG_JOIN
+        */
+       if (reg_join)
+               pim_vxlan_add_work(vxlan_sg);
+       else
+               pim_vxlan_del_work(vxlan_sg);
+}
+
+static int pim_vxlan_work_timer_cb(struct thread *t)
+{
+       pim_vxlan_do_reg_work();
+       pim_vxlan_work_timer_setup(true /* start */);
+       return 0;
+}
+
+/* global 1second timer used for periodic processing */
+static void pim_vxlan_work_timer_setup(bool start)
+{
+       THREAD_OFF(vxlan_info.work_timer);
+       if (start)
+               thread_add_timer(router->master, pim_vxlan_work_timer_cb, NULL,
+                       PIM_VXLAN_WORK_TIME, &vxlan_info.work_timer);
+}
+
 /**************************** vxlan origination mroutes ***********************
  * For every (local-vtep-ip, bum-mcast-grp) registered by evpn an origination
  * mroute is setup by pimd. The purpose of this mroute is to forward vxlan
@@ -321,6 +470,158 @@ static void pim_vxlan_orig_mr_del(struct pim_vxlan_sg *vxlan_sg)
        pim_vxlan_orig_mr_up_del(vxlan_sg);
 }
 
+static void pim_vxlan_orig_mr_iif_update(struct hash_backet *backet, void *arg)
+{
+       struct interface *ifp = (struct interface *)arg;
+       struct pim_vxlan_sg *vxlan_sg = (struct pim_vxlan_sg *)backet->data;
+       struct interface *old_iif = vxlan_sg->iif;
+
+       if (!pim_vxlan_is_orig_mroute(vxlan_sg))
+               return;
+
+       if (PIM_DEBUG_VXLAN)
+               zlog_debug("vxlan SG %s iif changed from %s to %s",
+                               vxlan_sg->sg_str,
+                               old_iif ? old_iif->name : "-",
+                               ifp ? ifp->name : "-");
+
+       if (pim_vxlan_orig_mr_add_is_ok(vxlan_sg)) {
+               if (vxlan_sg->up) {
+                       /* upstream exists but iif changed */
+                       pim_vxlan_orig_mr_up_iif_update(vxlan_sg);
+               } else {
+                       /* install mroute */
+                       pim_vxlan_orig_mr_install(vxlan_sg);
+               }
+       } else {
+               pim_vxlan_orig_mr_del(vxlan_sg);
+       }
+}
+
+/**************************** vxlan termination mroutes ***********************
+ * For every bum-mcast-grp registered by evpn a *G termination
+ * mroute is setup by pimd. The purpose of this mroute is to pull down vxlan
+ * packets with the bum-mcast-grp dip from the underlay and terminate the
+ * tunnel. This is done by including the vxlan termination device (ipmr-lo) in
+ * its OIL. The vxlan de-capsulated packets are subject to subsequent overlay
+ * bridging.
+ *
+ * Sample mroute:
+ * (0.0.0.0, 239.1.1.100)     Iif: uplink-1      Oifs: ipmr-lo, uplink-1
+ *****************************************************************************/
+struct pim_interface *pim_vxlan_get_term_ifp(struct pim_instance *pim)
+{
+       return pim->vxlan.term_if ?
+               (struct pim_interface *)pim->vxlan.term_if->info : NULL;
+}
+
+static void pim_vxlan_term_mr_oif_add(struct pim_vxlan_sg *vxlan_sg)
+{
+       if (vxlan_sg->flags & PIM_VXLAN_SGF_OIF_INSTALLED)
+               return;
+
+       if (PIM_DEBUG_VXLAN)
+               zlog_debug("vxlan SG %s term-oif %s add",
+                       vxlan_sg->sg_str, vxlan_sg->term_oif->name);
+
+       if (pim_ifchannel_local_membership_add(vxlan_sg->term_oif,
+                               &vxlan_sg->sg)) {
+               vxlan_sg->flags |= PIM_VXLAN_SGF_OIF_INSTALLED;
+       } else {
+               zlog_warn("vxlan SG %s term-oif %s add failed",
+                       vxlan_sg->sg_str, vxlan_sg->term_oif->name);
+       }
+}
+
+static void pim_vxlan_term_mr_oif_del(struct pim_vxlan_sg *vxlan_sg)
+{
+       if (!(vxlan_sg->flags & PIM_VXLAN_SGF_OIF_INSTALLED))
+               return;
+
+       if (PIM_DEBUG_VXLAN)
+               zlog_debug("vxlan SG %s oif %s del",
+                       vxlan_sg->sg_str, vxlan_sg->term_oif->name);
+
+       vxlan_sg->flags &= ~PIM_VXLAN_SGF_OIF_INSTALLED;
+       pim_ifchannel_local_membership_del(vxlan_sg->term_oif, &vxlan_sg->sg);
+}
+
+static void pim_vxlan_term_mr_up_add(struct pim_vxlan_sg *vxlan_sg)
+{
+       struct pim_upstream *up;
+       int flags = 0;
+
+       if (vxlan_sg->up) {
+               /* nothing to do */
+               return;
+       }
+
+       if (PIM_DEBUG_VXLAN)
+               zlog_debug("vxlan SG %s term mroute-up add",
+                       vxlan_sg->sg_str);
+
+       PIM_UPSTREAM_FLAG_SET_SRC_VXLAN_TERM(flags);
+       /* enable MLAG designated-forwarder election on termination mroutes */
+       PIM_UPSTREAM_FLAG_SET_MLAG_VXLAN(flags);
+
+       up = pim_upstream_add(vxlan_sg->pim, &vxlan_sg->sg,
+                       NULL /* iif */, flags,
+                       __PRETTY_FUNCTION__, NULL);
+       vxlan_sg->up = up;
+
+       if (!up) {
+               zlog_warn("vxlan SG %s term mroute-up add failed",
+                       vxlan_sg->sg_str);
+       }
+}
+
+static void pim_vxlan_term_mr_up_del(struct pim_vxlan_sg *vxlan_sg)
+{
+       struct pim_upstream *up = vxlan_sg->up;
+
+       if (!up)
+               return;
+
+       if (PIM_DEBUG_VXLAN)
+               zlog_debug("vxlan SG %s term mroute-up del",
+                       vxlan_sg->sg_str);
+       vxlan_sg->up = NULL;
+       if (up->flags & PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM) {
+               /* clear out all the vxlan related flags */
+               up->flags &= ~(PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM |
+                       PIM_UPSTREAM_FLAG_MASK_MLAG_VXLAN);
+
+               pim_upstream_del(vxlan_sg->pim, up,
+                               __PRETTY_FUNCTION__);
+       }
+}
+
+static void pim_vxlan_term_mr_add(struct pim_vxlan_sg *vxlan_sg)
+{
+       if (PIM_DEBUG_VXLAN)
+               zlog_debug("vxlan SG %s term mroute add", vxlan_sg->sg_str);
+
+       vxlan_sg->term_oif = vxlan_sg->pim->vxlan.term_if;
+       if (!vxlan_sg->term_oif)
+               /* defer termination mroute till we have a termination device */
+               return;
+
+       pim_vxlan_term_mr_up_add(vxlan_sg);
+       /* set up local membership for the term-oif */
+       pim_vxlan_term_mr_oif_add(vxlan_sg);
+}
+
+static void pim_vxlan_term_mr_del(struct pim_vxlan_sg *vxlan_sg)
+{
+       if (PIM_DEBUG_VXLAN)
+               zlog_debug("vxlan SG %s term mroute del", vxlan_sg->sg_str);
+
+       /* remove local membership associated with the term oif */
+       pim_vxlan_term_mr_oif_del(vxlan_sg);
+       /* remove references to the upstream entry */
+       pim_vxlan_term_mr_up_del(vxlan_sg);
+}
+
 /************************** vxlan SG cache management ************************/
 static unsigned int pim_vxlan_sg_hash_key_make(void *p)
 {
@@ -380,6 +681,8 @@ struct pim_vxlan_sg *pim_vxlan_sg_add(struct pim_instance *pim,
 
        if (pim_vxlan_is_orig_mroute(vxlan_sg))
                pim_vxlan_orig_mr_add(vxlan_sg);
+       else
+               pim_vxlan_term_mr_add(vxlan_sg);
 
        return vxlan_sg;
 }
@@ -394,8 +697,12 @@ void pim_vxlan_sg_del(struct pim_instance *pim, struct prefix_sg *sg)
 
        vxlan_sg->flags |= PIM_VXLAN_SGF_DEL_IN_PROG;
 
+       pim_vxlan_del_work(vxlan_sg);
+
        if (pim_vxlan_is_orig_mroute(vxlan_sg))
                pim_vxlan_orig_mr_del(vxlan_sg);
+       else
+               pim_vxlan_term_mr_del(vxlan_sg);
 
        hash_release(vxlan_sg->pim->vxlan.sg_hash, vxlan_sg);
 
@@ -405,6 +712,299 @@ void pim_vxlan_sg_del(struct pim_instance *pim, struct prefix_sg *sg)
        XFREE(MTYPE_PIM_VXLAN_SG, vxlan_sg);
 }
 
+/******************************* MLAG handling *******************************/
+/* The peerlink sub-interface is added as an OIF to the origination-mroute.
+ * This is done to send a copy of the multicast-vxlan encapsulated traffic
+ * to the MLAG peer which may mroute it over the underlay if there are any
+ * interested receivers.
+ */
+static void pim_vxlan_sg_peerlink_update(struct hash_backet *backet, void *arg)
+{
+       struct interface *new_oif = (struct interface *)arg;
+       struct pim_vxlan_sg *vxlan_sg = (struct pim_vxlan_sg *)backet->data;
+
+       if (!pim_vxlan_is_orig_mroute(vxlan_sg))
+               return;
+
+       if (vxlan_sg->orig_oif == new_oif)
+               return;
+
+       pim_vxlan_orig_mr_oif_del(vxlan_sg);
+
+       vxlan_sg->orig_oif = new_oif;
+       pim_vxlan_orig_mr_oif_add(vxlan_sg);
+}
+
+/* In the case of anycast VTEPs the VTEP-PIP must be used as the
+ * register source.
+ */
+bool pim_vxlan_get_register_src(struct pim_instance *pim,
+               struct pim_upstream *up, struct in_addr *src_p)
+{
+       if (!(vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED))
+               return true;
+
+       /* if address is not available suppress the pim-register */
+       if (vxlan_mlag.reg_addr.s_addr == INADDR_ANY)
+               return false;
+
+       *src_p = vxlan_mlag.reg_addr;
+       return true;
+}
+
+void pim_vxlan_mlag_update(bool enable, bool peer_state, uint32_t role,
+                               struct interface *peerlink_rif,
+                               struct in_addr *reg_addr)
+{
+       struct pim_instance *pim;
+       struct interface *old_oif;
+       struct interface *new_oif;
+       char addr_buf[INET_ADDRSTRLEN];
+       struct pim_interface *pim_ifp = NULL;
+
+       if (PIM_DEBUG_VXLAN) {
+               inet_ntop(AF_INET, reg_addr,
+                               addr_buf, INET_ADDRSTRLEN);
+               zlog_debug("vxlan MLAG update %s state %s role %d rif %s addr %s",
+                               enable ? "enable" : "disable",
+                               peer_state ? "up" : "down",
+                               role,
+                               peerlink_rif ? peerlink_rif->name : "-",
+                               addr_buf);
+       }
+
+       /* XXX: for now vxlan termination is only possible in the default VRF
+        * when that changes this will need to change to iterate all VRFs
+        */
+       pim = pim_get_pim_instance(VRF_DEFAULT);
+
+       old_oif = pim_vxlan_orig_mr_oif_get(pim);
+
+       if (enable)
+               vxlan_mlag.flags |= PIM_VXLAN_MLAGF_ENABLED;
+       else
+               vxlan_mlag.flags &= ~PIM_VXLAN_MLAGF_ENABLED;
+
+       if (vxlan_mlag.peerlink_rif != peerlink_rif)
+               vxlan_mlag.peerlink_rif = peerlink_rif;
+
+       vxlan_mlag.reg_addr = *reg_addr;
+       vxlan_mlag.peer_state = peer_state;
+       vxlan_mlag.role = role;
+
+       /* process changes */
+       if (vxlan_mlag.peerlink_rif)
+               pim_ifp = (struct pim_interface *)vxlan_mlag.peerlink_rif->info;
+       if ((vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED) &&
+                       pim_ifp && (pim_ifp->mroute_vif_index > 0))
+               pim_vxlan_set_peerlink_rif(pim, peerlink_rif);
+       else
+               pim_vxlan_set_peerlink_rif(pim, NULL);
+
+       new_oif = pim_vxlan_orig_mr_oif_get(pim);
+       if (old_oif != new_oif)
+               hash_iterate(pim->vxlan.sg_hash, pim_vxlan_sg_peerlink_update,
+                       new_oif);
+}
+
+/****************************** misc callbacks *******************************/
+void pim_vxlan_config_write(struct vty *vty, char *spaces, int *writes)
+{
+       char addr_buf[INET_ADDRSTRLEN];
+
+       if ((vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED) &&
+                       vxlan_mlag.peerlink_rif) {
+
+               inet_ntop(AF_INET, &vxlan_mlag.reg_addr,
+                               addr_buf, sizeof(addr_buf));
+               vty_out(vty,
+                       "%sip pim mlag %s role %s state %s addr %s\n",
+                       spaces,
+                       vxlan_mlag.peerlink_rif->name,
+                       (vxlan_mlag.role == PIM_VXLAN_MLAG_ROLE_PRIMARY) ?
+                               "primary":"secondary",
+                       vxlan_mlag.peer_state ? "up" : "down",
+                       addr_buf);
+               *writes += 1;
+       }
+}
+
+static void pim_vxlan_set_default_iif(struct pim_instance *pim,
+                               struct interface *ifp)
+{
+       struct interface *old_iif;
+
+       if (pim->vxlan.default_iif == ifp)
+               return;
+
+       old_iif = pim->vxlan.default_iif;
+       if (PIM_DEBUG_VXLAN)
+               zlog_debug("%s: vxlan default iif changed from %s to %s",
+                       __PRETTY_FUNCTION__,
+                       old_iif ? old_iif->name : "-",
+                       ifp ? ifp->name : "-");
+
+       old_iif = pim_vxlan_orig_mr_iif_get(pim);
+       pim->vxlan.default_iif = ifp;
+       ifp = pim_vxlan_orig_mr_iif_get(pim);
+       if (old_iif == ifp)
+               return;
+
+       if (PIM_DEBUG_VXLAN)
+               zlog_debug("%s: vxlan orig iif changed from %s to %s",
+                       __PRETTY_FUNCTION__, old_iif ? old_iif->name : "-",
+                       ifp ? ifp->name : "-");
+
+       /* add/del upstream entries for the existing vxlan SG when the
+        * interface becomes available
+        */
+       if (pim->vxlan.sg_hash)
+               hash_iterate(pim->vxlan.sg_hash,
+                               pim_vxlan_orig_mr_iif_update, ifp);
+}
+
+static void pim_vxlan_set_peerlink_rif(struct pim_instance *pim,
+                       struct interface *ifp)
+{
+       struct interface *old_iif;
+
+       if (pim->vxlan.peerlink_rif == ifp)
+               return;
+
+       old_iif = pim->vxlan.peerlink_rif;
+       if (PIM_DEBUG_VXLAN)
+               zlog_debug("%s: vxlan peerlink_rif changed from %s to %s",
+                       __PRETTY_FUNCTION__, old_iif ? old_iif->name : "-",
+                       ifp ? ifp->name : "-");
+
+       old_iif = pim_vxlan_orig_mr_iif_get(pim);
+       pim->vxlan.peerlink_rif = ifp;
+       ifp = pim_vxlan_orig_mr_iif_get(pim);
+       if (old_iif == ifp)
+               return;
+
+       if (PIM_DEBUG_VXLAN)
+               zlog_debug("%s: vxlan orig iif changed from %s to %s",
+                       __PRETTY_FUNCTION__, old_iif ? old_iif->name : "-",
+                       ifp ? ifp->name : "-");
+
+       /* add/del upstream entries for the existing vxlan SG when the
+        * interface becomes available
+        */
+       if (pim->vxlan.sg_hash)
+               hash_iterate(pim->vxlan.sg_hash,
+                               pim_vxlan_orig_mr_iif_update, ifp);
+}
+
+void pim_vxlan_add_vif(struct interface *ifp)
+{
+       struct pim_interface *pim_ifp = ifp->info;
+       struct pim_instance *pim = pim_ifp->pim;
+
+       if (pim->vrf_id != VRF_DEFAULT)
+               return;
+
+       if (if_is_loopback_or_vrf(ifp))
+               pim_vxlan_set_default_iif(pim, ifp);
+
+       if (vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED &&
+                       (ifp == vxlan_mlag.peerlink_rif))
+               pim_vxlan_set_peerlink_rif(pim, ifp);
+}
+
+void pim_vxlan_del_vif(struct interface *ifp)
+{
+       struct pim_interface *pim_ifp = ifp->info;
+       struct pim_instance *pim = pim_ifp->pim;
+
+       if (pim->vrf_id != VRF_DEFAULT)
+               return;
+
+       if (pim->vxlan.default_iif == ifp)
+               pim_vxlan_set_default_iif(pim, NULL);
+
+       if (pim->vxlan.peerlink_rif == ifp)
+               pim_vxlan_set_peerlink_rif(pim, NULL);
+}
+
+static void pim_vxlan_term_mr_oif_update(struct hash_backet *backet, void *arg)
+{
+       struct interface *ifp = (struct interface *)arg;
+       struct pim_vxlan_sg *vxlan_sg = (struct pim_vxlan_sg *)backet->data;
+
+       if (pim_vxlan_is_orig_mroute(vxlan_sg))
+               return;
+
+       if (vxlan_sg->term_oif == ifp)
+               return;
+
+       if (PIM_DEBUG_VXLAN)
+               zlog_debug("vxlan SG %s term oif changed from %s to %s",
+                       vxlan_sg->sg_str,
+                       vxlan_sg->term_oif ? vxlan_sg->term_oif->name : "-",
+                       ifp ? ifp->name : "-");
+
+       pim_vxlan_term_mr_del(vxlan_sg);
+       vxlan_sg->term_oif = ifp;
+       pim_vxlan_term_mr_add(vxlan_sg);
+}
+
+void pim_vxlan_add_term_dev(struct pim_instance *pim,
+               struct interface *ifp)
+{
+       struct pim_interface *pim_ifp;
+
+       if (pim->vxlan.term_if == ifp)
+               return;
+
+       if (PIM_DEBUG_VXLAN)
+               zlog_debug("vxlan term oif changed from %s to %s",
+                       pim->vxlan.term_if ? pim->vxlan.term_if->name : "-",
+                       ifp->name);
+
+       /* enable pim on the term ifp */
+       pim_ifp = (struct pim_interface *)ifp->info;
+       if (pim_ifp) {
+               PIM_IF_DO_PIM(pim_ifp->options);
+       } else {
+               pim_ifp = pim_if_new(ifp, false /*igmp*/, true /*pim*/,
+                               false /*pimreg*/, true /*vxlan_term*/);
+               /* ensure that pimreg existss before using the newly created
+                * vxlan termination device
+                */
+               pim_if_create_pimreg(pim);
+       }
+
+       pim->vxlan.term_if = ifp;
+
+       if (pim->vxlan.sg_hash)
+               hash_iterate(pim_ifp->pim->vxlan.sg_hash,
+                               pim_vxlan_term_mr_oif_update, ifp);
+}
+
+void pim_vxlan_del_term_dev(struct pim_instance *pim)
+{
+       struct interface *ifp = pim->vxlan.term_if;
+       struct pim_interface *pim_ifp;
+
+       if (PIM_DEBUG_VXLAN)
+               zlog_debug("vxlan term oif changed from %s to -", ifp->name);
+
+       pim->vxlan.term_if = NULL;
+
+       if (pim->vxlan.sg_hash)
+               hash_iterate(pim->vxlan.sg_hash,
+                               pim_vxlan_term_mr_oif_update, NULL);
+
+       pim_ifp = (struct pim_interface *)ifp->info;
+       if (pim_ifp) {
+               PIM_IF_DONT_PIM(pim_ifp->options);
+               if (!PIM_IF_TEST_IGMP(pim_ifp->options))
+                       pim_if_delete(ifp);
+       }
+
+}
+
 void pim_vxlan_init(struct pim_instance *pim)
 {
        char hash_name[64];