| /* |
| * Copyright 2017-present Open Networking Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.onosproject.artemis.impl; |
| |
| import com.fasterxml.jackson.databind.JsonNode; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Sets; |
| import com.google.common.collect.Streams; |
| import org.json.JSONArray; |
| import org.json.JSONException; |
| import org.onlab.packet.IpAddress; |
| import org.onlab.packet.IpPrefix; |
| import org.onosproject.core.ApplicationId; |
| import org.onosproject.net.config.Config; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| |
| /** |
| * Artemis Configuration Class. |
| */ |
| public class ArtemisConfig extends Config<ApplicationId> { |
| private static final String PREFIXES = "prefixes"; |
| /* */ |
| private static final String PREFIX = "prefix"; |
| private static final String PATHS = "paths"; |
| private static final String MOAS = "moas"; |
| /* */ |
| private static final String ORIGIN = "origin"; |
| private static final String NEIGHBOR = "neighbor"; |
| private static final String ASN = "asn"; |
| /* */ |
| private static final String MONITORS = "monitors"; |
| /* */ |
| private static final String RIPE = "ripe"; |
| private static final String EXABGP = "exabgp"; |
| /* */ |
| private static final String MOAS_LEGIT = "legit"; |
| private static final String TUNNEL_POINTS = "tunnelPoints"; |
| private static final String TUNNEL_OVSDB_IP = "ovsdb_ip"; |
| private static final String TUNNEL_LOCAL_IP = "local_ip"; |
| private static final String TUNNEL_OVS_PORT = "ovs_port"; |
| |
| private final Logger log = LoggerFactory.getLogger(getClass()); |
| |
| Set<IpPrefix> prefixesToMonitor() { |
| JsonNode prefixesNode = object.path(PREFIXES); |
| if (!prefixesNode.isMissingNode()) { |
| return Streams.stream(prefixesNode) |
| .map(prefix -> IpPrefix.valueOf(prefix.get(PREFIX).asText())) |
| .collect(Collectors.toSet()); |
| } |
| return null; |
| } |
| |
| /** |
| * Gets the set of monitored prefixes with the details (prefix, paths and MOAS). |
| * |
| * @return artemis class prefixes |
| */ |
| Set<ArtemisPrefixes> monitoredPrefixes() { |
| Set<ArtemisPrefixes> prefixes = Sets.newHashSet(); |
| |
| JsonNode prefixesNode = object.path(PREFIXES); |
| if (prefixesNode.isMissingNode()) { |
| log.warn("prefixes field is null!"); |
| return prefixes; |
| } |
| |
| prefixesNode.forEach(jsonNode -> { |
| IpPrefix prefix = IpPrefix.valueOf(jsonNode.get(PREFIX).asText()); |
| |
| JsonNode moasNode = jsonNode.get(MOAS); |
| Set<IpAddress> moasIps = Streams.stream(moasNode) |
| .map(asn -> IpAddress.valueOf(asn.asText())) |
| .collect(Collectors.toSet()); |
| |
| JsonNode pathsNode = jsonNode.get(PATHS); |
| Map<Integer, Map<Integer, Set<Integer>>> paths = Maps.newHashMap(); |
| pathsNode.forEach(path -> addPath(paths, path)); |
| |
| prefixes.add(new ArtemisPrefixes(prefix, moasIps, paths)); |
| }); |
| |
| return prefixes; |
| } |
| |
| /** |
| * Appends an ASN path on the ASN paths list of the Artemis application. |
| * |
| * @param paths active ASN paths list |
| * @param path ASN path to be added |
| */ |
| private void addPath(Map<Integer, Map<Integer, Set<Integer>>> paths, JsonNode path) { |
| Integer origin = path.path(ORIGIN).asInt(); |
| |
| JsonNode firstNeighborNode = path.path(NEIGHBOR); |
| // Check if neighbor exists in the configuration |
| if (!firstNeighborNode.isMissingNode()) { |
| firstNeighborNode.forEach(firstNeighbor -> { |
| Integer firstNeighborAsn = firstNeighbor.get(ASN).asInt(); |
| |
| JsonNode secondNeighborNode = firstNeighbor.path(NEIGHBOR); |
| // check if second neighbor exists in configuration |
| if (!secondNeighborNode.isMissingNode()) { |
| secondNeighborNode.forEach(secondNeighbor -> { |
| Integer secondNeighborAsn = secondNeighbor.asInt(); |
| |
| if (paths.containsKey(origin)) { |
| // paths already contain origin ASN. |
| Map<Integer, Set<Integer>> integerSetMap = paths.get(origin); |
| if (integerSetMap.containsKey(firstNeighborAsn)) { |
| integerSetMap.get(firstNeighborAsn).add(secondNeighborAsn); |
| } else { |
| paths.get(origin).put(firstNeighborAsn, Sets.newHashSet(secondNeighborAsn)); |
| } |
| } else { |
| // origin ASN does not exist in Map. |
| Map<Integer, Set<Integer>> first2second = Maps.newHashMap(); |
| first2second.put(firstNeighborAsn, Sets.newHashSet(secondNeighborAsn)); |
| paths.put(origin, first2second); |
| } |
| }); |
| // else append to paths without second neighbor |
| } else { |
| if (!paths.containsKey(origin)) { |
| Map<Integer, Set<Integer>> first2second = Maps.newHashMap(); |
| first2second.put(firstNeighborAsn, Sets.newHashSet()); |
| paths.put(origin, first2second); |
| } else { |
| // paths already contain origin ASN. |
| Map<Integer, Set<Integer>> integerSetMap = paths.get(origin); |
| if (!integerSetMap.containsKey(firstNeighborAsn)) { |
| paths.get(origin).put(firstNeighborAsn, Sets.newHashSet()); |
| } |
| } |
| } |
| }); |
| // else append to paths only the origin |
| } else { |
| if (!paths.containsKey(origin)) { |
| paths.put(origin, Maps.newHashMap()); |
| } |
| } |
| } |
| |
| // /** |
| // * Helper function to print the loaded ASN paths. |
| // * |
| // * @param paths ASN paths to print |
| // */ |
| // private void printPaths(Map<Integer, Map<Integer, Set<Integer>>> paths) { |
| // log.warn("------------------------------------"); |
| // paths.forEach((k, v) -> v.forEach((l, n) -> { |
| // n.forEach(p -> log.warn("Origin: " + k + ", 1st: " + l + ", 2nd: " + p)); |
| // })); |
| // } |
| |
| /** |
| * Gets the active route collectors. |
| * |
| * @return map with type as a key and host as a value. |
| */ |
| Map<String, Set<String>> activeMonitors() { |
| Map<String, Set<String>> monitors = Maps.newHashMap(); |
| |
| JsonNode monitorsNode = object.path(MONITORS); |
| |
| if (!monitorsNode.isMissingNode()) { |
| JsonNode ripeNode = monitorsNode.path(RIPE); |
| if (!ripeNode.isMissingNode()) { |
| Set<String> hosts = Sets.newHashSet(); |
| ripeNode.forEach(host -> hosts.add(host.asText())); |
| monitors.put(RIPE, hosts); |
| } |
| |
| JsonNode exabgpNode = monitorsNode.path(EXABGP); |
| if (!exabgpNode.isMissingNode()) { |
| Set<String> hosts = Sets.newHashSet(); |
| exabgpNode.forEach(host -> hosts.add(host.asText())); |
| monitors.put(EXABGP, hosts); |
| } |
| } |
| |
| return monitors; |
| } |
| |
| /** |
| * Get the information about MOAS. Including remote MOAS server IPs, OVSDB ID and local tunnel IP. |
| * |
| * @return MOAS information |
| */ |
| MoasInfo moasInfo() { |
| MoasInfo moasInfo = new MoasInfo(); |
| |
| JsonNode moasNode = object.path(MOAS); |
| |
| if (!moasNode.isMissingNode()) { |
| JsonNode legitIpsNode = moasNode.path(MOAS_LEGIT); |
| if (!legitIpsNode.isMissingNode()) { |
| if (legitIpsNode.isArray()) { |
| moasInfo.setMoasAddresses( |
| Streams.stream(legitIpsNode) |
| .map(ipAddress -> IpAddress.valueOf(ipAddress.asText())) |
| .collect(Collectors.toSet()) |
| ); |
| } else { |
| log.warn("Legit MOAS field need to be a list"); |
| } |
| } else { |
| log.warn("No IPs for legit MOAS specified in configuration"); |
| } |
| |
| JsonNode tunnelPointsNode = moasNode.path(TUNNEL_POINTS); |
| if (!tunnelPointsNode.isMissingNode()) { |
| if (tunnelPointsNode.isArray()) { |
| tunnelPointsNode.forEach( |
| tunnelPoint -> { |
| JsonNode idNode = tunnelPoint.path(TUNNEL_OVSDB_IP), |
| localNode = tunnelPoint.path(TUNNEL_LOCAL_IP), |
| ovsNode = tunnelPoint.path(TUNNEL_OVS_PORT); |
| |
| if (!idNode.isMissingNode() && !localNode.isMissingNode()) { |
| moasInfo.addTunnelPoint( |
| new MoasInfo.TunnelPoint( |
| IpAddress.valueOf(idNode.asText()), |
| IpAddress.valueOf(localNode.asText()), |
| ovsNode.asText() |
| ) |
| ); |
| } else { |
| log.warn("Tunnel point need to have an ID and a Local IP"); |
| } |
| } |
| ); |
| } else { |
| log.warn("Tunnel points field need to be a list"); |
| } |
| } |
| } else { |
| log.warn("No tunnel points specified in configuration"); |
| } |
| |
| return moasInfo; |
| } |
| |
| /** |
| * Information holder for MOAS. |
| */ |
| public static class MoasInfo { |
| private Set<IpAddress> moasAddresses; |
| private Set<TunnelPoint> tunnelPoints; |
| |
| public MoasInfo() { |
| moasAddresses = Sets.newConcurrentHashSet(); |
| tunnelPoints = Sets.newConcurrentHashSet(); |
| } |
| |
| public Set<IpAddress> getMoasAddresses() { |
| return moasAddresses; |
| } |
| |
| public void setMoasAddresses(Set<IpAddress> moasAddresses) { |
| this.moasAddresses = moasAddresses; |
| } |
| |
| public Set<TunnelPoint> getTunnelPoints() { |
| return tunnelPoints; |
| } |
| |
| public void setTunnelPoints(Set<TunnelPoint> tunnelPoints) { |
| this.tunnelPoints = tunnelPoints; |
| } |
| |
| public TunnelPoint getTunnelPoint() { |
| return tunnelPoints.iterator().next(); |
| } |
| |
| public void addTunnelPoint(TunnelPoint tunnelPoint) { |
| this.tunnelPoints.add(tunnelPoint); |
| } |
| |
| @Override |
| public String toString() { |
| return "MoasInfo{" + |
| "moasAddresses=" + moasAddresses + |
| ", tunnelPoints=" + tunnelPoints + |
| '}'; |
| } |
| |
| public static class TunnelPoint { |
| private IpAddress ovsdbIp; |
| private IpAddress localIP; |
| private String ovsPort; |
| |
| public TunnelPoint(IpAddress ovsdbIp, IpAddress localIP, String ovsPort) { |
| this.ovsdbIp = ovsdbIp; |
| this.localIP = localIP; |
| this.ovsPort = ovsPort; |
| } |
| |
| public IpAddress getOvsdbIp() { |
| return ovsdbIp; |
| } |
| |
| public void setOvsdbIp(IpAddress ovsdbIp) { |
| this.ovsdbIp = ovsdbIp; |
| } |
| |
| public IpAddress getLocalIp() { |
| return localIP; |
| } |
| |
| public void setLocalIp(IpAddress localIP) { |
| this.localIP = localIP; |
| } |
| |
| public String getOvsPort() { |
| return ovsPort; |
| } |
| |
| public void setOvsPort(String ovsPort) { |
| this.ovsPort = ovsPort; |
| } |
| |
| @Override |
| public String toString() { |
| return "TunnelPoint{" + |
| "ovsdbIp='" + ovsdbIp + '\'' + |
| ", localIP=" + localIP + |
| ", ovsPort='" + ovsPort + '\'' + |
| '}'; |
| } |
| } |
| } |
| |
| /** |
| * Configuration for a specific prefix. |
| */ |
| public class ArtemisPrefixes { |
| private IpPrefix prefix; |
| private Set<IpAddress> moas; |
| private Map<Integer, Map<Integer, Set<Integer>>> paths; |
| |
| ArtemisPrefixes(IpPrefix prefix, Set<IpAddress> moas, Map<Integer, Map<Integer, Set<Integer>>> paths) { |
| this.prefix = checkNotNull(prefix); |
| this.moas = checkNotNull(moas); |
| this.paths = checkNotNull(paths); |
| } |
| |
| protected IpPrefix prefix() { |
| return prefix; |
| } |
| |
| protected Set<IpAddress> moas() { |
| return moas; |
| } |
| |
| protected Map<Integer, Map<Integer, Set<Integer>>> paths() { |
| return paths; |
| } |
| |
| /** |
| * Given a path we check if the origin is a friendly MOAS or our ASN. |
| * If the origin ASN is not ours the we have a hijack of type 0. Next, in case that the first neighbor is |
| * not a legit neighbor from our configuration we detect a hijack of type 1 and lastly, if the second |
| * neighbor is not a legit neighbor we detect a type 2 hijack. |
| * |
| * @param path as-path that announces our prefix and found from monitors |
| * @return <code>0</code> no bgp hijack detected |
| * <code>50</code> friendly anycaster announcing our prefix |
| * <code>100+i</code> BGP hijack type i (0 <= i <=2) |
| */ |
| int checkPath(JSONArray path) { |
| // TODO add MOAS check |
| ArrayList<Integer> asnPath = new ArrayList<>(); |
| for (int i = 0; i < path.length(); i++) { |
| try { |
| asnPath.add(path.getInt(i)); |
| } catch (JSONException e) { |
| log.warn("checkPath", e); |
| } |
| } |
| // reverse the list to get path starting from origin |
| Collections.reverse(asnPath); |
| |
| if (asnPath.size() > 0 && !paths.containsKey(asnPath.get(0))) { |
| return 100; |
| } else if (asnPath.size() > 1 && !paths.get(asnPath.get(0)).containsKey(asnPath.get(1))) { |
| return 101; |
| } else if (asnPath.size() > 2 && !paths.get(asnPath.get(0)).get(asnPath.get(1)).contains(asnPath.get(2))) { |
| return 102; |
| } |
| return 0; |
| } |
| |
| @Override |
| public String toString() { |
| return "ArtemisPrefixes{" + |
| "prefix=" + prefix + |
| ", moas=" + moas + |
| ", paths=" + paths + |
| '}'; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hashCode(prefix); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj instanceof PrefixHandler) { |
| final PrefixHandler that = (PrefixHandler) obj; |
| return Objects.equals(this.prefix, that.getPrefix()); |
| } |
| return false; |
| } |
| } |
| |
| } |