| /* NHRP interface |
| * Copyright (c) 2014-2015 Timo Teräs |
| * |
| * This file is free software: you may copy, redistribute 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. |
| */ |
| |
| #include <net/if_arp.h> |
| #include "zebra.h" |
| #include "linklist.h" |
| #include "memory.h" |
| #include "thread.h" |
| |
| #include "nhrpd.h" |
| #include "os.h" |
| #include "netlink.h" |
| |
| static int nhrp_if_new_hook(struct interface *ifp) |
| { |
| struct nhrp_interface *nifp; |
| afi_t afi; |
| |
| nifp = XCALLOC(MTYPE_NHRP_IF, sizeof(struct nhrp_interface)); |
| if (!nifp) return 0; |
| |
| ifp->info = nifp; |
| nifp->ifp = ifp; |
| |
| notifier_init(&nifp->notifier_list); |
| for (afi = 0; afi < AFI_MAX; afi++) { |
| struct nhrp_afi_data *ad = &nifp->afi[afi]; |
| ad->holdtime = NHRPD_DEFAULT_HOLDTIME; |
| list_init(&ad->nhslist_head); |
| } |
| |
| return 0; |
| } |
| |
| static int nhrp_if_delete_hook(struct interface *ifp) |
| { |
| XFREE(MTYPE_NHRP_IF, ifp->info); |
| return 0; |
| } |
| |
| void nhrp_interface_init(void) |
| { |
| if_add_hook(IF_NEW_HOOK, nhrp_if_new_hook); |
| if_add_hook(IF_DELETE_HOOK, nhrp_if_delete_hook); |
| } |
| |
| void nhrp_interface_update_mtu(struct interface *ifp, afi_t afi) |
| { |
| struct nhrp_interface *nifp = ifp->info; |
| struct nhrp_afi_data *if_ad = &nifp->afi[afi]; |
| unsigned short new_mtu; |
| |
| if (if_ad->configured_mtu < 0) |
| new_mtu = nifp->nbmaifp ? nifp->nbmaifp->mtu : 0; |
| else |
| new_mtu = if_ad->configured_mtu; |
| if (new_mtu >= 1500) |
| new_mtu = 0; |
| |
| if (new_mtu != if_ad->mtu) { |
| debugf(NHRP_DEBUG_IF, "%s: MTU changed to %d", ifp->name, new_mtu); |
| if_ad->mtu = new_mtu; |
| notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_MTU_CHANGED); |
| } |
| } |
| |
| static void nhrp_interface_update_source(struct interface *ifp) |
| { |
| struct nhrp_interface *nifp = ifp->info; |
| |
| if (!nifp->source || !nifp->nbmaifp || |
| nifp->linkidx == nifp->nbmaifp->ifindex) |
| return; |
| |
| nifp->linkidx = nifp->nbmaifp->ifindex; |
| debugf(NHRP_DEBUG_IF, "%s: bound device index changed to %d", ifp->name, nifp->linkidx); |
| netlink_gre_set_link(ifp->ifindex, nifp->linkidx); |
| } |
| |
| static void nhrp_interface_interface_notifier(struct notifier_block *n, unsigned long cmd) |
| { |
| struct nhrp_interface *nifp = container_of(n, struct nhrp_interface, nbmanifp_notifier); |
| struct interface *nbmaifp = nifp->nbmaifp; |
| struct nhrp_interface *nbmanifp = nbmaifp->info; |
| char buf[SU_ADDRSTRLEN]; |
| |
| switch (cmd) { |
| case NOTIFY_INTERFACE_CHANGED: |
| nhrp_interface_update_mtu(nifp->ifp, AFI_IP); |
| nhrp_interface_update_source(nifp->ifp); |
| break; |
| case NOTIFY_INTERFACE_ADDRESS_CHANGED: |
| nifp->nbma = nbmanifp->afi[AFI_IP].addr; |
| nhrp_interface_update(nifp->ifp); |
| notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_NBMA_CHANGED); |
| debugf(NHRP_DEBUG_IF, "%s: NBMA change: address %s", |
| nifp->ifp->name, |
| sockunion2str(&nifp->nbma, buf, sizeof buf)); |
| break; |
| } |
| } |
| |
| static void nhrp_interface_update_nbma(struct interface *ifp) |
| { |
| struct nhrp_interface *nifp = ifp->info, *nbmanifp = NULL; |
| struct interface *nbmaifp = NULL; |
| union sockunion nbma; |
| |
| sockunion_family(&nbma) = AF_UNSPEC; |
| |
| if (nifp->source) |
| nbmaifp = if_lookup_by_name(nifp->source); |
| |
| switch (ifp->ll_type) { |
| case ZEBRA_LLT_IPGRE: { |
| struct in_addr saddr = {0}; |
| netlink_gre_get_info(ifp->ifindex, &nifp->grekey, &nifp->linkidx, &saddr); |
| debugf(NHRP_DEBUG_IF, "%s: GRE: %x %x %x", ifp->name, nifp->grekey, nifp->linkidx, saddr.s_addr); |
| if (saddr.s_addr) |
| sockunion_set(&nbma, AF_INET, (u_char *) &saddr.s_addr, sizeof(saddr.s_addr)); |
| else if (!nbmaifp && nifp->linkidx != IFINDEX_INTERNAL) |
| nbmaifp = if_lookup_by_index(nifp->linkidx); |
| } |
| break; |
| default: |
| break; |
| } |
| |
| if (nbmaifp) |
| nbmanifp = nbmaifp->info; |
| |
| if (nbmaifp != nifp->nbmaifp) { |
| if (nifp->nbmaifp) |
| notifier_del(&nifp->nbmanifp_notifier); |
| nifp->nbmaifp = nbmaifp; |
| if (nbmaifp) { |
| notifier_add(&nifp->nbmanifp_notifier, &nbmanifp->notifier_list, nhrp_interface_interface_notifier); |
| debugf(NHRP_DEBUG_IF, "%s: bound to %s", ifp->name, nbmaifp->name); |
| } |
| } |
| |
| if (nbmaifp) { |
| if (sockunion_family(&nbma) == AF_UNSPEC) |
| nbma = nbmanifp->afi[AFI_IP].addr; |
| nhrp_interface_update_mtu(ifp, AFI_IP); |
| nhrp_interface_update_source(ifp); |
| } |
| |
| if (!sockunion_same(&nbma, &nifp->nbma)) { |
| nifp->nbma = nbma; |
| nhrp_interface_update(nifp->ifp); |
| debugf(NHRP_DEBUG_IF, "%s: NBMA address changed", ifp->name); |
| notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_NBMA_CHANGED); |
| } |
| |
| nhrp_interface_update(ifp); |
| } |
| |
| static void nhrp_interface_update_address(struct interface *ifp, afi_t afi, int force) |
| { |
| const int family = afi2family(afi); |
| struct nhrp_interface *nifp = ifp->info; |
| struct nhrp_afi_data *if_ad = &nifp->afi[afi]; |
| struct nhrp_cache *nc; |
| struct connected *c, *best; |
| struct listnode *cnode; |
| union sockunion addr; |
| char buf[PREFIX_STRLEN]; |
| |
| /* Select new best match preferring primary address */ |
| best = NULL; |
| for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) { |
| if (PREFIX_FAMILY(c->address) != family) |
| continue; |
| if (best == NULL) { |
| best = c; |
| continue; |
| } |
| if ((best->flags & ZEBRA_IFA_SECONDARY) && !(c->flags & ZEBRA_IFA_SECONDARY)) { |
| best = c; |
| continue; |
| } |
| if (!(best->flags & ZEBRA_IFA_SECONDARY) && (c->flags & ZEBRA_IFA_SECONDARY)) |
| continue; |
| if (best->address->prefixlen > c->address->prefixlen) { |
| best = c; |
| continue; |
| } |
| if (best->address->prefixlen < c->address->prefixlen) |
| continue; |
| } |
| |
| /* On NHRP interfaces a host prefix is required */ |
| if (best && if_ad->configured && best->address->prefixlen != 8 * prefix_blen(best->address)) { |
| zlog_notice("%s: %s is not a host prefix", ifp->name, |
| prefix2str(best->address, buf, sizeof buf)); |
| best = NULL; |
| } |
| |
| /* Update address if it changed */ |
| if (best) |
| prefix2sockunion(best->address, &addr); |
| else |
| memset(&addr, 0, sizeof(addr)); |
| |
| if (!force && sockunion_same(&if_ad->addr, &addr)) |
| return; |
| |
| if (sockunion_family(&if_ad->addr) != AF_UNSPEC) { |
| nc = nhrp_cache_get(ifp, &if_ad->addr, 0); |
| if (nc) nhrp_cache_update_binding(nc, NHRP_CACHE_LOCAL, -1, NULL, 0, NULL); |
| } |
| |
| debugf(NHRP_DEBUG_KERNEL, "%s: IPv%d address changed to %s", |
| ifp->name, afi == AFI_IP ? 4 : 6, |
| best ? prefix2str(best->address, buf, sizeof buf) : "(none)"); |
| if_ad->addr = addr; |
| |
| if (if_ad->configured && sockunion_family(&if_ad->addr) != AF_UNSPEC) { |
| nc = nhrp_cache_get(ifp, &addr, 1); |
| if (nc) nhrp_cache_update_binding(nc, NHRP_CACHE_LOCAL, 0, NULL, 0, NULL); |
| } |
| |
| notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_ADDRESS_CHANGED); |
| } |
| |
| void nhrp_interface_update(struct interface *ifp) |
| { |
| struct nhrp_interface *nifp = ifp->info; |
| struct nhrp_afi_data *if_ad; |
| afi_t afi; |
| int enabled = 0; |
| |
| notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_CHANGED); |
| |
| for (afi = 0; afi < AFI_MAX; afi++) { |
| if_ad = &nifp->afi[afi]; |
| |
| if (sockunion_family(&nifp->nbma) == AF_UNSPEC || |
| ifp->ifindex == IFINDEX_INTERNAL || !if_is_up(ifp) || |
| !if_ad->network_id) { |
| if (if_ad->configured) { |
| if_ad->configured = 0; |
| nhrp_interface_update_address(ifp, afi, 1); |
| } |
| continue; |
| } |
| |
| if (!if_ad->configured) { |
| os_configure_dmvpn(ifp->ifindex, ifp->name, afi2family(afi)); |
| if_ad->configured = 1; |
| nhrp_interface_update_address(ifp, afi, 1); |
| } |
| |
| enabled = 1; |
| } |
| |
| if (enabled != nifp->enabled) { |
| nifp->enabled = enabled; |
| notifier_call(&nifp->notifier_list, enabled ? NOTIFY_INTERFACE_UP : NOTIFY_INTERFACE_DOWN); |
| } |
| } |
| |
| int nhrp_interface_add(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id) |
| { |
| struct interface *ifp; |
| |
| /* read and add the interface in the iflist. */ |
| ifp = zebra_interface_add_read(client->ibuf, vrf_id); |
| if (ifp == NULL) |
| return 0; |
| |
| debugf(NHRP_DEBUG_IF, "if-add: %s, ifindex: %u, hw_type: %d %s", |
| ifp->name, ifp->ifindex, |
| ifp->ll_type, if_link_type_str(ifp->ll_type)); |
| |
| nhrp_interface_update_nbma(ifp); |
| |
| return 0; |
| } |
| |
| int nhrp_interface_delete(int cmd, struct zclient *client, |
| zebra_size_t length, vrf_id_t vrf_id) |
| { |
| struct interface *ifp; |
| struct stream *s; |
| |
| s = client->ibuf; |
| ifp = zebra_interface_state_read(s, vrf_id); |
| if (ifp == NULL) |
| return 0; |
| |
| debugf(NHRP_DEBUG_IF, "if-delete: %s", ifp->name); |
| ifp->ifindex = IFINDEX_INTERNAL; |
| nhrp_interface_update(ifp); |
| /* if_delete(ifp); */ |
| return 0; |
| } |
| |
| int nhrp_interface_up(int cmd, struct zclient *client, |
| zebra_size_t length, vrf_id_t vrf_id) |
| { |
| struct interface *ifp; |
| |
| ifp = zebra_interface_state_read(client->ibuf, vrf_id); |
| if (ifp == NULL) |
| return 0; |
| |
| debugf(NHRP_DEBUG_IF, "if-up: %s", ifp->name); |
| nhrp_interface_update_nbma(ifp); |
| |
| return 0; |
| } |
| |
| int nhrp_interface_down(int cmd, struct zclient *client, |
| zebra_size_t length, vrf_id_t vrf_id) |
| { |
| struct interface *ifp; |
| |
| ifp = zebra_interface_state_read(client->ibuf, vrf_id); |
| if (ifp == NULL) |
| return 0; |
| |
| debugf(NHRP_DEBUG_IF, "if-down: %s", ifp->name); |
| nhrp_interface_update(ifp); |
| return 0; |
| } |
| |
| int nhrp_interface_address_add(int cmd, struct zclient *client, |
| zebra_size_t length, vrf_id_t vrf_id) |
| { |
| struct connected *ifc; |
| char buf[PREFIX_STRLEN]; |
| |
| ifc = zebra_interface_address_read(cmd, client->ibuf, vrf_id); |
| if (ifc == NULL) |
| return 0; |
| |
| debugf(NHRP_DEBUG_IF, "if-addr-add: %s: %s", |
| ifc->ifp->name, |
| prefix2str(ifc->address, buf, sizeof buf)); |
| |
| nhrp_interface_update_address(ifc->ifp, family2afi(PREFIX_FAMILY(ifc->address)), 0); |
| |
| return 0; |
| } |
| |
| int nhrp_interface_address_delete(int cmd, struct zclient *client, |
| zebra_size_t length, vrf_id_t vrf_id) |
| { |
| struct connected *ifc; |
| char buf[PREFIX_STRLEN]; |
| |
| ifc = zebra_interface_address_read(cmd, client->ibuf, vrf_id); |
| if (ifc == NULL) |
| return 0; |
| |
| debugf(NHRP_DEBUG_IF, "if-addr-del: %s: %s", |
| ifc->ifp->name, |
| prefix2str(ifc->address, buf, sizeof buf)); |
| |
| nhrp_interface_update_address(ifc->ifp, family2afi(PREFIX_FAMILY(ifc->address)), 0); |
| connected_free(ifc); |
| |
| return 0; |
| } |
| |
| void nhrp_interface_notify_add(struct interface *ifp, struct notifier_block *n, notifier_fn_t fn) |
| { |
| struct nhrp_interface *nifp = ifp->info; |
| notifier_add(n, &nifp->notifier_list, fn); |
| } |
| |
| void nhrp_interface_notify_del(struct interface *ifp, struct notifier_block *n) |
| { |
| notifier_del(n); |
| } |
| |
| void nhrp_interface_set_protection(struct interface *ifp, const char *profile, const char *fallback_profile) |
| { |
| struct nhrp_interface *nifp = ifp->info; |
| |
| if (nifp->ipsec_profile) free(nifp->ipsec_profile); |
| nifp->ipsec_profile = profile ? strdup(profile) : NULL; |
| |
| if (nifp->ipsec_fallback_profile) free(nifp->ipsec_fallback_profile); |
| nifp->ipsec_fallback_profile = fallback_profile ? strdup(fallback_profile) : NULL; |
| } |
| |
| void nhrp_interface_set_source(struct interface *ifp, const char *ifname) |
| { |
| struct nhrp_interface *nifp = ifp->info; |
| |
| if (nifp->source) free(nifp->source); |
| nifp->source = ifname ? strdup(ifname) : NULL; |
| |
| nhrp_interface_update_nbma(ifp); |
| } |