diff --git a/apps/artemis/BUCK b/apps/artemis/BUCK old mode 100644 new mode 100755 index 82c703ac87..e06097d27e --- a/apps/artemis/BUCK +++ b/apps/artemis/BUCK @@ -1,24 +1,36 @@ COMPILE_DEPS = [ '//lib:CORE_DEPS', - '//lib:NETTY', '//lib:JACKSON', + '//lib:NETTY', + '//lib:netty', + '//lib:netty-transport', '//lib:org.apache.karaf.shell.console', '//cli:onos-cli', '//apps/routing-api:onos-apps-routing-api', '//apps/routing/common:onos-apps-routing-common', + '//protocols/ovsdb/api:onos-protocols-ovsdb-api', + '//apps/intentsync:onos-apps-intentsync', + '//apps/route-service/api:onos-apps-route-service-api', + '//protocols/ovsdb/rfc:onos-protocols-ovsdb-rfc', '//lib:okhttp', '//lib:okio', ':commons-net', ':io.socket-client', ':json', - ':engine.io-client', - '//lib:netty', + ':engine.io-client' ] BUNDLES = [ '//apps/artemis:onos-apps-artemis', '//apps/routing-api:onos-apps-routing-api', '//apps/routing/common:onos-apps-routing-common', + '//protocols/ovsdb/api:onos-protocols-ovsdb-api', + '//apps/route-service/api:onos-apps-route-service-api', + '//protocols/ovsdb/rfc:onos-protocols-ovsdb-rfc' +] + +TEST_DEPS = [ + '//lib:TEST_ADAPTERS' ] EXCLUDED_BUNDLES = [ @@ -30,8 +42,9 @@ EXCLUDED_BUNDLES = [ ':engine.io-client' ] -osgi_jar ( +osgi_jar_with_tests ( deps = COMPILE_DEPS, + test_deps = TEST_DEPS, ) onos_app ( @@ -42,7 +55,12 @@ onos_app ( description = 'Artemis', included_bundles = BUNDLES, excluded_bundles = EXCLUDED_BUNDLES, - required_apps = [ 'org.onosproject.sdnip' ], + required_apps = [ + 'org.onosproject.sdnip', + 'org.onosproject.openflow', + 'org.onosproject.ovsdb', + 'org.onosproject.drivers.ovsdb' + ], ) remote_jar ( @@ -79,4 +97,4 @@ remote_jar ( sha1 = '854b49396e1e9f9bb0ab025062ddb49c4ed65ca1', maven_coords = 'io.socket:engine.io-client:jar:NON-OSGI:0.8.3', visibility = [ 'PUBLIC' ], -) +) \ No newline at end of file diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisDeaggregator.java b/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisDeaggregator.java new file mode 100644 index 0000000000..227f68d3bc --- /dev/null +++ b/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisDeaggregator.java @@ -0,0 +1,33 @@ +/* + * 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; + +/** + * Interface for Deaggregator Service of Artemis. + * + * When a prefix hijacking is detected, ARTEMIS automatically launches its mitigation service (deaggregator). + * Since in Internet routing the most specific prefix is always preferred, ARTEMIS modifies the BGP configuration of + * the routers so that they announce deaggregated sub-prefixes of the hijacked prefix (that are most preferred from any + * AS). After BGP converges, the hijacking attack is mitigated and traffic flows normally back to the ARTEMIS-protected + * AS (the one that runs ARTEMIS). Therefore, ARTEMIS assumes write permissions to the routers of the network, in order + * to be able to modify their BGP configuration and mitigate the attack. The purpose of this service is to receive all + * hijack events from the detector service and proceed on writing all the new prefixes to be announced by the BGP + * Speakers. + */ +public interface ArtemisDeaggregator { + //TODO: give the ability of other services to announce prefixes to BGP Speakers through this interface +} diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisDetector.java b/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisDetector.java new file mode 100644 index 0000000000..650d32d288 --- /dev/null +++ b/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisDetector.java @@ -0,0 +1,27 @@ +/* + * 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; + +/** + * Interface for Detector Service of Artemis. + * + * The detection service combines the information received through the events generated from the monitor service and + * the configuration file that includes all the legit BGP paths. The purpose of this interface is to identify given + * a BGP update message if there is a BGP hijack or not. + */ +public interface ArtemisDetector { + //TODO: give the ability to other services to check the legitimacy of a BGP Update message +} diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisEventListener.java b/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisEventListener.java new file mode 100644 index 0000000000..ced3d56212 --- /dev/null +++ b/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisEventListener.java @@ -0,0 +1,25 @@ +/* + * 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; + +import org.onosproject.artemis.impl.ArtemisEvent; +import org.onosproject.event.EventListener; + +/** + * Entity capable of receiving artemis-related events. + */ +public interface ArtemisEventListener extends EventListener { +} diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisMoasAgent.java b/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisMoasAgent.java new file mode 100644 index 0000000000..b25c7147a2 --- /dev/null +++ b/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisMoasAgent.java @@ -0,0 +1,40 @@ +/* + * 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; + +import io.netty.channel.ChannelHandlerContext; +import org.onlab.packet.IpAddress; + +/** + * MOAS agent that handles remote connections. + */ +public interface ArtemisMoasAgent { + + /** + * Keep a connection active if MOAS client is legit. + * + * @param ipAddress remote IP address + * @param ctx channel context + */ + void addMoas(IpAddress ipAddress, ChannelHandlerContext ctx); + + /** + * Remove MOAS. + * + * @param ipAddress remote IP address + */ + void removeMoas(IpAddress ipAddress); +} diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisMonitor.java b/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisMonitor.java new file mode 100755 index 0000000000..2d82145b5f --- /dev/null +++ b/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisMonitor.java @@ -0,0 +1,28 @@ +/* + * 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; + +/** + * Interface for Monitor Service of Artemis. + * + * The monitoring service runs continuously and provides control plane information from the AS itself, the streaming + * services can be RIPE RIS, BGPstream, BGPmon and Periscope, which return almost real-time BGP updates for a given + * list of prefixes and ASNs. The purpose of this interface is to provide store and provide this BGO information to the + * consumers (e.g. Artemis Detector Service). + */ +public interface ArtemisMonitor { + //TODO: give access to BGP Update messages to other services through this service +} diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisPacketProcessor.java b/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisPacketProcessor.java new file mode 100644 index 0000000000..d4cae92449 --- /dev/null +++ b/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisPacketProcessor.java @@ -0,0 +1,41 @@ +/* + * 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; + +import io.netty.channel.ChannelHandlerContext; +import org.json.JSONObject; +import org.onosproject.artemis.impl.objects.ArtemisMessage; + +/** + * Packet processor for artemis messages. + */ +public interface ArtemisPacketProcessor { + + /** + * Process a packet received from a MOAS client/server. + * + * @param msg artemis message + * @param ctx channel context + */ + void processMoasPacket(ArtemisMessage msg, ChannelHandlerContext ctx); + + /** + * Process a BGP Update packet received from a monitor. + * + * @param msg BGP Update message + */ + void processMonitorPacket(JSONObject msg); +} diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisService.java b/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisService.java new file mode 100644 index 0000000000..37206b04f1 --- /dev/null +++ b/apps/artemis/src/main/java/org/onosproject/artemis/ArtemisService.java @@ -0,0 +1,36 @@ +/* + * 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; + +import org.onosproject.artemis.impl.ArtemisConfig; +import org.onosproject.artemis.impl.ArtemisEvent; +import org.onosproject.event.ListenerService; + +import java.util.Optional; + +/** + * The main service/orchestrator of Artemis. + */ +public interface ArtemisService extends ListenerService { + + /** + * Get the current configuration. + * + * @return config + */ + Optional getConfig(); + +} diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisService.java b/apps/artemis/src/main/java/org/onosproject/artemis/BgpSpeakers.java similarity index 65% rename from apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisService.java rename to apps/artemis/src/main/java/org/onosproject/artemis/BgpSpeakers.java index 910906cd2a..9b333bcc7f 100644 --- a/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisService.java +++ b/apps/artemis/src/main/java/org/onosproject/artemis/BgpSpeakers.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-present Open Networking Foundation + * 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. @@ -13,18 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.onosproject.artemis.impl; +package org.onosproject.artemis; /** - * Artemis Service. + * Interface for all the types of BGP Speakers. */ -public interface ArtemisService { +public interface BgpSpeakers { /** - * Set logger to print incoming packets or not. + * Announces the two new subprefixes on the BGP Speaker. * - * @param value true to print incoming BGP messages + * @param prefixes list of two prefixes */ - void setLogger(boolean value); - + void announceSubPrefixes(String[] prefixes); } diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/Monitor.java b/apps/artemis/src/main/java/org/onosproject/artemis/Monitors.java similarity index 62% rename from apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/Monitor.java rename to apps/artemis/src/main/java/org/onosproject/artemis/Monitors.java index 853f1a079b..3a5bf22e70 100644 --- a/apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/Monitor.java +++ b/apps/artemis/src/main/java/org/onosproject/artemis/Monitors.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-present Open Networking Foundation + * 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. @@ -13,90 +13,91 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.onosproject.artemis.impl.monitors; +package org.onosproject.artemis; import org.onlab.packet.IpPrefix; +import java.util.Arrays; +import java.util.Optional; + /** - * Abstract class for Monitors. + * Interface for Monitors. */ -public abstract class Monitor { - /** - * Match enum type with monitor type inside configuration to map them. - */ - public enum Types { - RIPE { - @Override - public String toString() { - return "ripe"; - } - }, - EXABGP { - @Override - public String toString() { - return "exabgp"; - } - } - } - - IpPrefix prefix; - Monitor(IpPrefix prefix) { - this.prefix = prefix; - } - +public interface Monitors { /** * Get prefix of the specific monitor. * * @return prefix */ - public IpPrefix getPrefix() { - return prefix; - } + IpPrefix getPrefix(); /** * Set prefix for monitor. * * @param prefix prefix */ - public void setPrefix(IpPrefix prefix) { - this.prefix = prefix; - } + void setPrefix(IpPrefix prefix); /** * Start monitor to begin capturing incoming BGP packets. */ - public abstract void startMonitor(); + void startMonitor(); /** * Stop monitor from capturing incoming BGP packets. */ - public abstract void stopMonitor(); - - /** - * Get type of monitor. - * - * @return enum type - */ - public abstract Types getType(); + void stopMonitor(); /** * Check if monitor is running. * * @return true if running */ - public abstract boolean isRunning(); + boolean isRunning(); /** * Get host alias e.g. IP address, name. * * @return host alias */ - public abstract String getHost(); + String getHost(); /** * Set alias of host. * * @param host alias */ - public abstract void setHost(String host); + void setHost(String host); + + /** + * Match enum type with monitor type inside configuration to map them. + */ + enum Types { + RIPE("ripe") { + @Override + public String toString() { + return "ripe"; + } + }, + EXABGP("exabgp") { + @Override + public String toString() { + return "exabgp"; + } + }; + + private String name; + + Types(String name) { + this.name = name; + } + + public static Types getEnum(String name) { + Optional any = Arrays.stream(Types.values()).filter(typeStr -> typeStr.name.equals(name)).findAny(); + if (any.isPresent()) { + return any.get(); + } + throw new IllegalArgumentException("No enum defined for string: " + name); + } + } } diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/cli/LogOptionsCommand.java b/apps/artemis/src/main/java/org/onosproject/artemis/cli/LogOptionsCommand.java deleted file mode 100644 index 334b4ab517..0000000000 --- a/apps/artemis/src/main/java/org/onosproject/artemis/cli/LogOptionsCommand.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015 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.cli; - -import org.apache.karaf.shell.commands.Command; -import org.apache.karaf.shell.commands.Option; -import org.onosproject.artemis.impl.ArtemisService; -import org.onosproject.cli.AbstractShellCommand; - -/** - * CLI to enable or disable BGP Update message logging. - */ -@Command(scope = "artemis", name = "log-messages", - description = "Show RIS messages in logger.") -public class LogOptionsCommand extends AbstractShellCommand { - - @Option(name = "--enable", aliases = "-e", description = "Enable RIS message logging", - required = false, multiValued = false) - private boolean enable = false; - - @Option(name = "--disable", aliases = "-d", description = "Disable RIS message logging", - required = false, multiValued = false) - private boolean disable = false; - - @Override - protected void execute() { - ArtemisService artemisService = get(ArtemisService.class); - if (enable) { - artemisService.setLogger(true); - } else if (disable) { - artemisService.setLogger(false); - } - } -} \ No newline at end of file diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisConfig.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisConfig.java index 54285a8275..b21c5f61f3 100644 --- a/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisConfig.java +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-present Open Networking Foundation + * 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. @@ -18,8 +18,10 @@ 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; @@ -31,14 +33,14 @@ 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. */ -class ArtemisConfig extends Config { - +public class ArtemisConfig extends Config { private static final String PREFIXES = "prefixes"; /* */ private static final String PREFIX = "prefix"; @@ -49,18 +51,29 @@ class ArtemisConfig extends Config { 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 FREQUENCY = "frequency"; - + 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 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). * @@ -69,8 +82,8 @@ class ArtemisConfig extends Config { Set monitoredPrefixes() { Set prefixes = Sets.newHashSet(); - JsonNode prefixesNode = object.get(PREFIXES); - if (prefixesNode == null) { + JsonNode prefixesNode = object.path(PREFIXES); + if (prefixesNode.isMissingNode()) { log.warn("prefixes field is null!"); return prefixes; } @@ -78,33 +91,16 @@ class ArtemisConfig extends Config { prefixesNode.forEach(jsonNode -> { IpPrefix prefix = IpPrefix.valueOf(jsonNode.get(PREFIX).asText()); - Set moasNumbers = Sets.newHashSet(); JsonNode moasNode = jsonNode.get(MOAS); - moasNode.forEach(asn -> - moasNumbers.add(asn.asInt()) - ); + Set moasIps = Streams.stream(moasNode) + .map(asn -> IpAddress.valueOf(asn.asText())) + .collect(Collectors.toSet()); - /* - "paths" : [{ - "origin" : 65004, - "neighbor" : [{ - "asn" : 65002, - "neighbor": [{ - "asn" : 65001, - }] - }] - }] - */ - - Map>> paths = Maps.newHashMap(); JsonNode pathsNode = jsonNode.get(PATHS); - pathsNode.forEach(path -> { - addPath(paths, path); - }); + Map>> paths = Maps.newHashMap(); + pathsNode.forEach(path -> addPath(paths, path)); - // printPaths(paths); - - prefixes.add(new ArtemisPrefixes(prefix, moasNumbers, paths)); + prefixes.add(new ArtemisPrefixes(prefix, moasIps, paths)); }); return prefixes; @@ -146,7 +142,7 @@ class ArtemisConfig extends Config { paths.put(origin, first2second); } }); - // else append to paths without second neighbor + // else append to paths without second neighbor } else { if (!paths.containsKey(origin)) { Map> first2second = Maps.newHashMap(); @@ -161,7 +157,7 @@ class ArtemisConfig extends Config { } } }); - // else append to paths only the origin + // else append to paths only the origin } else { if (!paths.containsKey(origin)) { paths.put(origin, Maps.newHashMap()); @@ -169,34 +165,17 @@ class ArtemisConfig extends Config { } } - /** - * Helper function to print the loaded ASN paths. - * - * @param paths ASN paths to print - */ - private void printPaths(Map>> paths) { - log.warn("------------------------------------"); - paths.forEach((k, v) -> v.forEach((l, n) -> { - n.forEach(p -> log.warn("Origin: " + k + ", 1st: " + l + ", 2nd: " + p)); - })); - } - - /** - * Gets the frequency of the detection module in milliseconds. - * - * @return frequency (ms) - */ - int detectionFrequency() { - JsonNode thresholdNode = object.get(FREQUENCY); - int threshold = 0; - - if (thresholdNode == null) { - log.warn("threshold field is null!"); - return threshold; - } - - return thresholdNode.asInt(); - } +// /** +// * Helper function to print the loaded ASN paths. +// * +// * @param paths ASN paths to print +// */ +// private void printPaths(Map>> 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. @@ -206,36 +185,185 @@ class ArtemisConfig extends Config { Map> activeMonitors() { Map> monitors = Maps.newHashMap(); - JsonNode monitorsNode = object.get(MONITORS); + JsonNode monitorsNode = object.path(MONITORS); - JsonNode ripeNode = monitorsNode.path(RIPE); - if (!ripeNode.isMissingNode()) { - Set hosts = Sets.newHashSet(); - ripeNode.forEach(host -> hosts.add(host.asText())); - monitors.put(RIPE, hosts); - } + if (!monitorsNode.isMissingNode()) { + JsonNode ripeNode = monitorsNode.path(RIPE); + if (!ripeNode.isMissingNode()) { + Set hosts = Sets.newHashSet(); + ripeNode.forEach(host -> hosts.add(host.asText())); + monitors.put(RIPE, hosts); + } - JsonNode exabgpNode = monitorsNode.path(EXABGP); - if (!exabgpNode.isMissingNode()) { - Set hosts = Sets.newHashSet(); - exabgpNode.forEach(host -> hosts.add(host.asText())); - monitors.put(EXABGP, hosts); + JsonNode exabgpNode = monitorsNode.path(EXABGP); + if (!exabgpNode.isMissingNode()) { + Set 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 moasAddresses; + private Set tunnelPoints; + + public MoasInfo() { + moasAddresses = Sets.newConcurrentHashSet(); + tunnelPoints = Sets.newConcurrentHashSet(); + } + + public Set getMoasAddresses() { + return moasAddresses; + } + + public void setMoasAddresses(Set moasAddresses) { + this.moasAddresses = moasAddresses; + } + + public Set getTunnelPoints() { + return tunnelPoints; + } + + public void setTunnelPoints(Set 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. */ - static class ArtemisPrefixes { + public class ArtemisPrefixes { private IpPrefix prefix; - private Set moas; + private Set moas; private Map>> paths; - private final Logger log = LoggerFactory.getLogger(getClass()); - - ArtemisPrefixes(IpPrefix prefix, Set moas, Map>> paths) { + ArtemisPrefixes(IpPrefix prefix, Set moas, Map>> paths) { this.prefix = checkNotNull(prefix); this.moas = checkNotNull(moas); this.paths = checkNotNull(paths); @@ -245,7 +373,7 @@ class ArtemisConfig extends Config { return prefix; } - protected Set moas() { + protected Set moas() { return moas; } @@ -261,8 +389,8 @@ class ArtemisConfig extends Config { * * @param path as-path that announces our prefix and found from monitors * @return 0 no bgp hijack detected - * 50 friendly anycaster announcing our prefix - * 100+i BGP hijack type i (0 <= i <=2) + * 50 friendly anycaster announcing our prefix + * 100+i BGP hijack type i (0 <= i <=2) */ int checkPath(JSONArray path) { // TODO add MOAS check @@ -287,6 +415,15 @@ class ArtemisConfig extends Config { return 0; } + @Override + public String toString() { + return "ArtemisPrefixes{" + + "prefix=" + prefix + + ", moas=" + moas + + ", paths=" + paths + + '}'; + } + @Override public int hashCode() { return Objects.hashCode(prefix); diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisDeaggregatorImpl.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisDeaggregatorImpl.java new file mode 100644 index 0000000000..5660644a7b --- /dev/null +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisDeaggregatorImpl.java @@ -0,0 +1,473 @@ +/* + * 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.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.CharsetUtil; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.json.JSONObject; +import org.onlab.packet.IpAddress; +import org.onlab.packet.IpPrefix; +import org.onlab.packet.TpPort; +import org.onosproject.artemis.ArtemisDeaggregator; +import org.onosproject.artemis.ArtemisEventListener; +import org.onosproject.artemis.ArtemisMoasAgent; +import org.onosproject.artemis.ArtemisPacketProcessor; +import org.onosproject.artemis.ArtemisService; +import org.onosproject.artemis.BgpSpeakers; +import org.onosproject.artemis.impl.bgpspeakers.QuaggaBgpSpeakers; +import org.onosproject.artemis.impl.moas.MoasClientController; +import org.onosproject.artemis.impl.moas.MoasServerController; +import org.onosproject.artemis.impl.objects.ArtemisMessage; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flowobjective.DefaultForwardingObjective; +import org.onosproject.net.flowobjective.FlowObjectiveService; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.intf.InterfaceService; +import org.onosproject.ovsdb.controller.OvsdbBridge; +import org.onosproject.ovsdb.controller.OvsdbClientService; +import org.onosproject.ovsdb.controller.OvsdbController; +import org.onosproject.ovsdb.controller.OvsdbInterface; +import org.onosproject.routing.bgp.BgpInfoService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static org.onlab.packet.Ethernet.TYPE_IPV4; + +@Component(immediate = true) +@Service +public class ArtemisDeaggregatorImpl implements ArtemisDeaggregator { + + private final Logger log = LoggerFactory.getLogger(getClass()); + private static final int PRIORITY = 1000; + + /* Services */ + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private BgpInfoService bgpInfoService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private ArtemisService artemisService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private OvsdbController ovsdbController; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private InterfaceService interfaceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private FlowObjectiveService flowObjectiveService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private FlowRuleService flowRuleService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private CoreService coreService; + + /* Variables */ + private Set bgpSpeakers = Sets.newHashSet(); + private MoasServerController moasServer; + + private Port tunnelPort = null; + private ApplicationId appId; + + private IpAddress remoteTunnelIp = null; + private IpPrefix remotePrefix = null; + private boolean rulesInstalled; + + /* Agent */ + private InternalMoasAgent moasAgent = new InternalMoasAgent(); + private InternalPacketProcessor packetProcessor = new InternalPacketProcessor(); + private InternalDeviceListener deviceListener = new InternalDeviceListener(); + + private Set moasClientControllers = Sets.newConcurrentHashSet(); + + private final ArtemisEventListener artemisEventListener = this::handleArtemisEvent; + + @Activate + protected void activate() { + rulesInstalled = false; + + // FIXME: add other type of BGP Speakers when Dynamic Configuration is available + bgpSpeakers.add(new QuaggaBgpSpeakers(bgpInfoService)); + + moasServer = new MoasServerController(); + moasServer.start(moasAgent, packetProcessor); + + deviceService.addListener(deviceListener); + + appId = coreService.getAppId("org.onosproject.artemis"); + + // enable OVSDB for the switches that we will install the GRE tunnel + artemisService.getConfig().ifPresent(config -> config.moasInfo().getTunnelPoints() + .forEach(tunnelPoint -> ovsdbController.connect(tunnelPoint.getOvsdbIp(), TpPort.tpPort(6640))) + ); + + artemisService.addListener(artemisEventListener); + + log.info("Artemis Deaggregator Service Started"); + + /* + log.info("interfaces {}", interfaceService.getInterfaces()); + + [{ + "name": "", + "connectPoint": "of:000000000000000a/2", + "ipAddresses": "[1.1.1.1/30]", + "macAddress": "00:00:00:00:00:01" + }, + { + "name": "", + "connectPoint": "of:000000000000000a/3", + "ipAddresses": "[10.0.0.1/8]", + "macAddress": "00:00:00:00:00:01" + }] + */ + } + + @Deactivate + protected void deactivate() { + moasServer.stop(); + + moasClientControllers.forEach(MoasClientController::stop); + moasClientControllers.clear(); + + flowRuleService.removeFlowRulesById(appId); + deviceService.removeListener(deviceListener); + + remoteTunnelIp = null; + remotePrefix = null; + tunnelPort = null; + + artemisService.removeListener(artemisEventListener); + + log.info("Artemis Deaggregator Service Stopped"); + } + + /** + * Create a GRE tunnel interface pointing to remote MOAS. + * + * @param remoteIp remote ip on GRE tunnel + */ + private void createTunnelInterface(IpAddress remoteIp) { + ovsdbController.getNodeIds().forEach(nodeId -> artemisService.getConfig().flatMap(config -> + config.moasInfo().getTunnelPoints() + .stream() + .filter(tunnelPoint -> tunnelPoint.getOvsdbIp().toString().equals(nodeId.getIpAddress())) + .findFirst() + ).ifPresent(tunnelPoint -> { + OvsdbClientService ovsdbClient = ovsdbController.getOvsdbClient(nodeId); + ovsdbClient.dropInterface("gre-int"); + Map options = Maps.newHashMap(); + options.put("remote_ip", remoteIp.toString()); + OvsdbInterface ovsdbInterface = OvsdbInterface.builder() + .name("gre-int") + .options(options) + .type(OvsdbInterface.Type.GRE) + .build(); + OvsdbBridge mainBridge = ovsdbClient.getBridges().iterator().next(); + ovsdbClient.createInterface(mainBridge.name(), ovsdbInterface); + log.info("Tunnel setup at {} - {}", nodeId, tunnelPoint); + })); + } + + /** + * Install rules. + */ + private void installRules() { + log.info("Remote Data {} - {} - {}", tunnelPort, remoteTunnelIp, remotePrefix); + // FIXME: currently works only for a simple pair of client-server + if (!rulesInstalled && tunnelPort != null && remoteTunnelIp != null) { + if (remotePrefix != null) { + installServerRules(); + } else { + installClientRules(); + } + rulesInstalled = true; + } + } + + /** + * Rules to be installed on MOAS Client. + */ + private void installClientRules() { + log.info("installClientRules"); + artemisService.getConfig().ifPresent(config -> { + // selector + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchEthType(TYPE_IPV4) + .matchIPSrc(remoteTunnelIp.toIpPrefix()) + .matchIPDst(config.moasInfo().getTunnelPoint().getLocalIp().toIpPrefix()) + .build(); + // treatment + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(PortNumber.LOCAL) + .build(); + // forwarding objective builder + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .withSelector(selector) + .withTreatment(treatment) + .withPriority(PRIORITY) + .withFlag(ForwardingObjective.Flag.VERSATILE) + .fromApp(appId) + .add(); + // send flow objective to specified switch + flowObjectiveService.forward(DeviceId.deviceId(tunnelPort.element().id().toString()), + forwardingObjective); + + log.info("Installing flow rule = {}", forwardingObjective); + }); + } + + /** + * Rules to be isntalled on MOAS Server. + */ + private void installServerRules() { + log.info("installServerRules"); + artemisService.getConfig().ifPresent(config -> { + // selector + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchEthType(TYPE_IPV4) + .matchIPDst(remotePrefix) + .build(); + // treatment + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(tunnelPort.number()) + .build(); + // forwarding objective builder + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .withSelector(selector) + .withTreatment(treatment) + .withPriority(PRIORITY) + .withFlag(ForwardingObjective.Flag.VERSATILE) + .fromApp(appId) + .add(); + // send flow objective to specified switch + flowObjectiveService.forward(DeviceId.deviceId(tunnelPort.element().id().toString()), + forwardingObjective); + + log.info("Installing flow rule = {}", forwardingObjective); + + // selector + selector = DefaultTrafficSelector.builder() + .matchEthType(TYPE_IPV4) + .matchIPSrc(config.moasInfo().getTunnelPoint().getLocalIp().toIpPrefix()) + .matchIPDst(remoteTunnelIp.toIpPrefix()) + .build(); + // treatment + treatment = DefaultTrafficTreatment.builder() + // FIXME: find a better way + .setOutput(PortNumber.portNumber(2)) + .build(); + // forwarding objective builder + forwardingObjective = DefaultForwardingObjective.builder() + .withSelector(selector) + .withTreatment(treatment) + .withPriority(PRIORITY) + .withFlag(ForwardingObjective.Flag.VERSATILE) + .fromApp(appId) + .add(); + // send flow objective to specified switch + flowObjectiveService.forward(DeviceId.deviceId(tunnelPort.element().id().toString()), + forwardingObjective); + + log.info("Installing flow rule = {}", forwardingObjective); + }); + } + + /** + * Handles a artemis event. + * + * @param event the artemis event + */ + protected void handleArtemisEvent(ArtemisEvent event) { + if (event.type().equals(ArtemisEvent.Type.HIJACK_ADDED)) { + IpPrefix receivedPrefix = (IpPrefix) event.subject(); + + log.info("Deaggregator received a prefix " + receivedPrefix.toString()); + + // can only de-aggregate /23 subnets and higher + int cidr = receivedPrefix.prefixLength(); + if (receivedPrefix.prefixLength() < 24) { + byte[] octets = receivedPrefix.address().toOctets(); + int byteGroup = (cidr + 1) / 8, + bitPos = 8 - (cidr + 1) % 8; + + octets[byteGroup] = (byte) (octets[byteGroup] & ~(1 << bitPos)); + String low = IpPrefix.valueOf(IpAddress.Version.INET, octets, cidr + 1).toString(); + octets[byteGroup] = (byte) (octets[byteGroup] | (1 << bitPos)); + String high = IpPrefix.valueOf(IpAddress.Version.INET, octets, cidr + 1).toString(); + + String[] prefixes = {low, high}; + bgpSpeakers.forEach(bgpSpeakers -> bgpSpeakers.announceSubPrefixes(prefixes)); + } else { + log.warn("Initiating MOAS"); + + artemisService.getConfig().ifPresent(config -> config.monitoredPrefixes().forEach(artemisPrefixes -> { + log.info("checking if {} > {}", artemisPrefixes.prefix(), receivedPrefix); + if (artemisPrefixes.prefix().contains(receivedPrefix)) { + artemisPrefixes.moas().forEach(moasAddress -> { + log.info("Creating a client for {}", moasAddress); + MoasClientController client = new MoasClientController( + packetProcessor, + moasAddress, + config.moasInfo().getTunnelPoints().iterator().next() + .getLocalIp(), + receivedPrefix); + log.info("Running client"); + client.run(); + moasClientControllers.add(client); + } + ); + } + } + )); + } + + } + } + + private class InternalPacketProcessor implements ArtemisPacketProcessor { + @Override + public void processMoasPacket(ArtemisMessage msg, ChannelHandlerContext ctx) { + log.info("Received {}", msg); + switch (msg.getType()) { + case INITIATE_FROM_CLIENT: { + artemisService.getConfig().ifPresent(config -> { + // SERVER SIDE CODE + createTunnelInterface(IpAddress.valueOf(msg.getLocalIp())); + + ArtemisMessage message = new ArtemisMessage(); + message.setType(ArtemisMessage.Type.INITIATE_FROM_SERVER); + message.setLocalIp( + config.moasInfo().getTunnelPoints() + .iterator() + .next() + .getLocalIp() + .toString()); + + ObjectMapper mapper = new ObjectMapper(); + try { + String jsonInString = mapper.writeValueAsString(message); + ByteBuf buffer = Unpooled.copiedBuffer(jsonInString, CharsetUtil.UTF_8); + ctx.writeAndFlush(buffer); + } catch (JsonProcessingException e) { + e.printStackTrace(); + log.warn(ExceptionUtils.getFullStackTrace(e)); + } + + remoteTunnelIp = IpAddress.valueOf(msg.getLocalIp()); + remotePrefix = IpPrefix.valueOf(msg.getLocalPrefix()); + }); + break; + } + case INITIATE_FROM_SERVER: { + // CLIENT SIDE CODE + createTunnelInterface(IpAddress.valueOf(msg.getLocalIp())); + + remoteTunnelIp = IpAddress.valueOf(msg.getLocalIp()); + + break; + } + default: + } + + installRules(); + } + + @Override + public void processMonitorPacket(JSONObject msg) { + + } + } + + private class InternalMoasAgent implements ArtemisMoasAgent { + + @Override + public void addMoas(IpAddress ipAddress, ChannelHandlerContext ctx) { + Optional config = artemisService.getConfig(); + if (config.isPresent() && config.get().moasInfo().getMoasAddresses().contains(ipAddress)) { + log.info("Received Moas request from legit IP address"); + } else { + log.info("Received Moas request from unknown IP address; ignoring.."); + ctx.close(); + } + } + + @Override + public void removeMoas(IpAddress ipAddress) { + + } + } + + private class InternalDeviceListener implements DeviceListener { + + /* + EVENT + DefaultDevice{id=of:000000000000000a, type=SWITCH, manufacturer=Nicira, Inc., hwVersion=Open vSwitch, + swVersion=2.8.0, serialNumber=None, driver=ovs} + DefaultPort{element=of:000000000000000a, number=5, isEnabled=true, type=COPPER, portSpeed=0, annotations= + {portMac=96:13:4c:12:ca:8a, portName=gre-int}} + */ + @Override + public void event(DeviceEvent event) { + switch (event.type()) { + case PORT_UPDATED: + case PORT_ADDED: { + log.info("event {}", event); + // FIXME: currently only one tunnel is supported + if (event.port().annotations().keys().contains("portName") && + event.port().annotations().value("portName").equals("gre-int")) { + tunnelPort = event.port(); + + installRules(); + } + } + default: + } + } + } +} diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisDetectorImpl.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisDetectorImpl.java new file mode 100644 index 0000000000..09a8d2c102 --- /dev/null +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisDetectorImpl.java @@ -0,0 +1,105 @@ +/* + * 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 org.apache.commons.lang.exception.ExceptionUtils; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.onlab.packet.IpPrefix; +import org.onosproject.artemis.ArtemisDetector; +import org.onosproject.artemis.ArtemisEventListener; +import org.onosproject.artemis.ArtemisService; +import org.onosproject.core.CoreService; +import org.onosproject.event.EventDeliveryService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component(immediate = true) +@Service +public class ArtemisDetectorImpl implements ArtemisDetector { + private final Logger log = LoggerFactory.getLogger(getClass()); + + /* Services */ + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private ArtemisService artemisService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected EventDeliveryService eventDispatcher; + + private final ArtemisEventListener artemisEventListener = this::handleArtemisEvent; + + @Activate + protected void activate() { + artemisService.addListener(artemisEventListener); + log.info("Artemis Detector Service Started"); + } + + @Deactivate + protected void deactivate() { + artemisService.removeListener(artemisEventListener); + log.info("Artemis Detector Service Stopped"); + } + + /** + * Handles a artemis event. + * + * @param event the artemis event + */ + void handleArtemisEvent(ArtemisEvent event) { + // If an instance was deactivated, check whether we need to roll back the upgrade. + if (event.type().equals(ArtemisEvent.Type.BGPUPDATE_ADDED)) { + JSONObject take = (JSONObject) event.subject(); + + log.info("Received information about monitored prefix " + take.toString()); + artemisService.getConfig().ifPresent(config -> + config.monitoredPrefixes().forEach(artemisPrefix -> { + try { + IpPrefix prefix = artemisPrefix.prefix(), receivedPrefix; + + receivedPrefix = IpPrefix.valueOf(take.getString("prefix")); + + if (prefix.contains(receivedPrefix)) { + JSONArray path = take.getJSONArray("path"); + + int state = artemisPrefix.checkPath(path); + if (state >= 100) { + log.info("BGP Hijack detected; pushing prefix for hijack Deaggregation"); + eventDispatcher.post(new ArtemisEvent(ArtemisEvent.Type.HIJACK_ADDED, + receivedPrefix)); + } else { + log.info("BGP Update is legit"); + } + } + } catch (JSONException e) { + log.error(ExceptionUtils.getFullStackTrace(e)); + } + }) + ); + } + } + +} diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisEvent.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisEvent.java new file mode 100644 index 0000000000..38deac7a61 --- /dev/null +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisEvent.java @@ -0,0 +1,93 @@ +/* + * 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.google.common.base.MoreObjects; +import org.onosproject.event.AbstractEvent; + +import java.util.Objects; + +/** + * Artemis event. + */ +public class ArtemisEvent extends AbstractEvent { + + /** + * Creates an event of a given type and for the specified state and the + * current time. + * + * @param type upgrade event type + * @param subject upgrade state + */ + protected ArtemisEvent(Type type, Object subject) { + super(type, subject); + } + + /** + * Creates an event of a given type and for the specified state and time. + * + * @param type upgrade event type + * @param subject upgrade state + * @param time occurrence time + */ + protected ArtemisEvent(Type type, Object subject, long time) { + super(type, subject, time); + } + + @Override + public int hashCode() { + return Objects.hash(type(), subject(), time()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof ArtemisEvent) { + final ArtemisEvent other = (ArtemisEvent) obj; + return Objects.equals(this.type(), other.type()) && + Objects.equals(this.subject(), other.subject()) && + Objects.equals(this.time(), other.time()); + } + return false; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this.getClass()) + .add("type", type()) + .add("subject", subject()) + .add("time", time()) + .toString(); + } + + /** + * Type of artemis-related events. + */ + public enum Type { + + /** + * Indicates that a hijack was detected. + */ + HIJACK_ADDED, + + /** + * Indicates that a bgp update message was received. + */ + BGPUPDATE_ADDED, + } +} diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisManager.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisManager.java index 8d10d2e2e3..c714b05a23 100644 --- a/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisManager.java +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-present Open Networking Foundation + * 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. @@ -13,41 +13,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.onosproject.artemis.impl; -import com.google.common.collect.Sets; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.Service; +import org.onosproject.artemis.ArtemisEventListener; +import org.onosproject.artemis.ArtemisService; import org.onosproject.core.ApplicationId; import org.onosproject.core.CoreService; +import org.onosproject.event.AbstractListenerManager; import org.onosproject.net.config.ConfigFactory; import org.onosproject.net.config.NetworkConfigEvent; import org.onosproject.net.config.NetworkConfigListener; import org.onosproject.net.config.NetworkConfigRegistry; import org.onosproject.net.config.NetworkConfigService; import org.onosproject.net.config.basics.SubjectFactories; -import org.onosproject.routing.bgp.BgpInfoService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Map; import java.util.Optional; -import java.util.Set; -import java.util.Timer; -/** - * Artemis Component. - */ @Component(immediate = true) @Service -public class ArtemisManager implements ArtemisService { +public class ArtemisManager + extends AbstractListenerManager + implements ArtemisService { + private static final String ARTEMIS_APP_ID = "org.onosproject.artemis"; private static final Class CONFIG_CLASS = ArtemisConfig.class; + private final Logger log = LoggerFactory.getLogger(getClass()); + private final InternalNetworkConfigListener configListener = + new InternalNetworkConfigListener(); + /* Services */ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) private NetworkConfigRegistry registry; @@ -57,21 +60,12 @@ public class ArtemisManager implements ArtemisService { @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) private CoreService coreService; - @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) - private BgpInfoService bgpInfoService; - - private final Logger log = LoggerFactory.getLogger(getClass()); - + /* Variables */ private ApplicationId appId; - public static boolean logging = false; + private ArtemisConfig artemisConfig; - private Set prefixHandlers = Sets.newHashSet(); - private Deaggregator deaggr; - private Timer timer; - - private final InternalNetworkConfigListener configListener = - new InternalNetworkConfigListener(); + /* Config */ private ConfigFactory artemisConfigFactory = new ConfigFactory( SubjectFactories.APP_SUBJECT_FACTORY, ArtemisConfig.class, "artemis") { @@ -86,72 +80,25 @@ public class ArtemisManager implements ArtemisService { appId = coreService.registerApplication(ARTEMIS_APP_ID); configService.addListener(configListener); registry.registerConfigFactory(artemisConfigFactory); - log.info("Artemis Started"); + + eventDispatcher.addSink(ArtemisEvent.class, listenerRegistry); + + log.info("Artemis Service Started"); } @Deactivate protected void deactivate() { configService.removeListener(configListener); registry.unregisterConfigFactory(artemisConfigFactory); - prefixHandlers.forEach(PrefixHandler::stopPrefixMonitors); - log.info("Artemis Stopped"); - } - /** - * Helper function to start and stop monitors on configuration changes. - */ - private void setUpConfiguration() { - ArtemisConfig config = configService.getConfig(appId, CONFIG_CLASS); + eventDispatcher.removeSink(ArtemisEvent.class); - if (config == null) { - log.warn("No artemis config available!"); - return; - } - - final Set prefixes = config.monitoredPrefixes(); - final Integer frequency = config.detectionFrequency(); - final Map> monitors = config.activeMonitors(); - - Set toRemove = Sets.newHashSet(prefixHandlers); - - for (ArtemisConfig.ArtemisPrefixes curr : prefixes) { - final Optional handler = prefixHandlers - .stream() - .filter(prefixHandler -> prefixHandler.getPrefix().equals(curr.prefix())) - .findFirst(); - - if (handler.isPresent()) { - PrefixHandler oldHandler = handler.get(); - oldHandler.changeMonitors(monitors); - - // remove the ones we are going to keep from toRemove list - toRemove.remove(oldHandler); - } else { - // Add new handler - PrefixHandler newHandler = new PrefixHandler(curr.prefix(), monitors); - newHandler.startPrefixMonitors(); - prefixHandlers.add(newHandler); - } - } - - // stop and remove old monitors that do not exist on new configuration - toRemove.forEach(PrefixHandler::stopPrefixMonitors); - prefixHandlers.removeAll(toRemove); - - // new timer task with updated bgp speakers - deaggr = new Deaggregator(bgpInfoService); - deaggr.setPrefixes(prefixes); - - if (timer != null) { - timer.cancel(); - } - timer = new Timer(); - timer.scheduleAtFixedRate(deaggr, frequency, frequency); + log.info("Artemis Service Stopped"); } @Override - public void setLogger(boolean value) { - logging = value; + public Optional getConfig() { + return Optional.ofNullable(artemisConfig); } private class InternalNetworkConfigListener implements NetworkConfigListener { @@ -160,19 +107,27 @@ public class ArtemisManager implements ArtemisService { public void event(NetworkConfigEvent event) { switch (event.type()) { case CONFIG_REGISTERED: + case CONFIG_UNREGISTERED: { break; - case CONFIG_UNREGISTERED: - break; - case CONFIG_ADDED: - case CONFIG_UPDATED: - case CONFIG_REMOVED: + } + case CONFIG_REMOVED: { if (event.configClass() == CONFIG_CLASS) { - setUpConfiguration(); + artemisConfig = null; } break; + } + case CONFIG_UPDATED: + case CONFIG_ADDED: { + if (event.configClass() == CONFIG_CLASS) { + event.config().ifPresent(config -> artemisConfig = (ArtemisConfig) config); + } + break; + } default: break; } } + } + } diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisMonitorImpl.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisMonitorImpl.java new file mode 100755 index 0000000000..1f4d757ef6 --- /dev/null +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/ArtemisMonitorImpl.java @@ -0,0 +1,131 @@ +/* + * 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.google.common.collect.Sets; +import io.netty.channel.ChannelHandlerContext; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.json.JSONObject; +import org.onlab.packet.IpPrefix; +import org.onosproject.artemis.ArtemisMonitor; +import org.onosproject.artemis.ArtemisPacketProcessor; +import org.onosproject.artemis.impl.objects.ArtemisMessage; +import org.onosproject.event.EventDeliveryService; +import org.onosproject.net.config.NetworkConfigEvent; +import org.onosproject.net.config.NetworkConfigListener; +import org.onosproject.net.config.NetworkConfigService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Component(immediate = true) +@Service +public class ArtemisMonitorImpl implements ArtemisMonitor { + private final Logger log = LoggerFactory.getLogger(getClass()); + private static final Class CONFIG_CLASS = ArtemisConfig.class; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected EventDeliveryService eventDispatcher; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private NetworkConfigService configService; + + /* Variables */ + private Set prefixHandlers = Sets.newHashSet(); + private InternalPacketProcessor packetProcessor = new InternalPacketProcessor(); + + private final InternalNetworkConfigListener configListener = + new InternalNetworkConfigListener(); + + @Activate + protected void activate() { + configService.addListener(configListener); + log.info("Artemis Monitor Service Started"); + } + + @Deactivate + protected void deactivate() { + configService.removeListener(configListener); + prefixHandlers.forEach(PrefixHandler::stopPrefixMonitors); + prefixHandlers.clear(); + + log.info("Artemis Monitor Service Stopped"); + } + + private class InternalPacketProcessor implements ArtemisPacketProcessor { + + @Override + public void processMoasPacket(ArtemisMessage msg, ChannelHandlerContext ctx) { + + } + + @Override + public void processMonitorPacket(JSONObject msg) { + // TODO: in future maybe store the BGP Update message and propagate it to the cluster instead of Events + eventDispatcher.post(new ArtemisEvent(ArtemisEvent.Type.BGPUPDATE_ADDED, msg)); + } + } + + private class InternalNetworkConfigListener implements NetworkConfigListener { + + @Override + public void event(NetworkConfigEvent event) { + switch (event.type()) { + case CONFIG_REGISTERED: + case CONFIG_UNREGISTERED: { + break; + } + case CONFIG_REMOVED: { + if (event.configClass() == CONFIG_CLASS) { + prefixHandlers.forEach(PrefixHandler::stopPrefixMonitors); + prefixHandlers.clear(); + } + break; + } + case CONFIG_UPDATED: + case CONFIG_ADDED: { + if (event.configClass() == CONFIG_CLASS) { + event.config().ifPresent(config -> { + ArtemisConfig artemisConfig = (ArtemisConfig) config; + Set ipPrefixes = artemisConfig.prefixesToMonitor(); + Map> monitors = artemisConfig.activeMonitors(); + + prefixHandlers.forEach(PrefixHandler::stopPrefixMonitors); + prefixHandlers.clear(); + prefixHandlers = ipPrefixes.stream() + .map(prefix -> new PrefixHandler(prefix, monitors, packetProcessor)) + .collect(Collectors.toSet()); + + prefixHandlers.forEach(PrefixHandler::startPrefixMonitors); + }); + } + break; + } + default: + break; + } + } + + } +} diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/DataHandler.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/DataHandler.java deleted file mode 100644 index e4eb2cf636..0000000000 --- a/apps/artemis/src/main/java/org/onosproject/artemis/impl/DataHandler.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2016-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 org.json.JSONObject; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicReference; - -/** - * Helper class that handles BGP Update messages. - */ -public final class DataHandler { - private static final File DATA_FILE = new File("data.json"); - private static final File HIJACKS_FILE = new File("hijack.json"); - - private final AtomicReference> data = new AtomicReference>(); - - private static DataHandler instance = new DataHandler(); - - private DataHandler() { - data.set(new ArrayList<>()); - } - - /** - * Singleton for data handler class. - * - * @return instance of class - */ - public static synchronized DataHandler getInstance() { - if (instance == null) { - instance = new DataHandler(); - } - return instance; - } - - /** - * Atomic append a BGP update message to a list. - * - * @param obj BGP update message - */ - public synchronized void appendData(JSONObject obj) { - data.get().add(obj); - } - - /** - * Atomic read and clear a list of BGP updates. - * - * @return list of messages that received in 'threshold' period - */ - synchronized ArrayList getData() { - ArrayList tmp = (ArrayList) data.get().clone(); - data.get().clear(); - return tmp; - } - - /** - * A serializer to write incoming BGP updates and hijack attempts to json files. - */ - public static class Serializer { - private static RandomAccessFile fwData, fwHijack; - private static long lengthData, lengthHijack; - - static { - try { - if (DATA_FILE.exists()) { - fwData = new RandomAccessFile(DATA_FILE, "rw"); - } else { - fwData = new RandomAccessFile(DATA_FILE, "rw"); - fwData.writeBytes("[\n]"); - } - lengthData = fwData.length() - 1; - - if (HIJACKS_FILE.exists()) { - fwHijack = new RandomAccessFile(HIJACKS_FILE, "rw"); - } else { - fwHijack = new RandomAccessFile(HIJACKS_FILE, "rw"); - fwHijack.writeBytes("[\n]"); - } - lengthHijack = fwHijack.length() - 1; - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * Writes BGP update to json file. - * - * @param data BGP update - */ - public static synchronized void writeData(Object data) { - try { - String entry = data.toString() + ",\n]"; - fwData.seek(lengthData); - fwData.writeBytes(entry); - lengthData += entry.length() - 1; - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * Writes detected BGP hijack to json file. - * - * @param data BGP update of hijack - */ - static synchronized void writeHijack(Object data) { - try { - String entry = data.toString() + ",\n]"; - fwHijack.seek(lengthHijack); - fwHijack.writeBytes(entry); - lengthHijack += entry.length() - 1; - } catch (IOException e) { - e.printStackTrace(); - } - } - } -} diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/Deaggregator.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/Deaggregator.java deleted file mode 100644 index b78ade163b..0000000000 --- a/apps/artemis/src/main/java/org/onosproject/artemis/impl/Deaggregator.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2016-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.google.common.collect.Sets; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.onlab.packet.IpAddress; -import org.onlab.packet.IpPrefix; -import org.onosproject.artemis.impl.bgpspeakers.BgpSpeakers; -import org.onosproject.artemis.impl.bgpspeakers.QuaggaBgpSpeakers; -import org.onosproject.routing.bgp.BgpInfoService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.Set; - -/** - * Timertask class which detects and mitigates BGP hijacks. - */ -class Deaggregator extends java.util.TimerTask { - - private final Logger log = LoggerFactory.getLogger(getClass()); - private Set prefixes = Sets.newHashSet(); - private Set bgpSpeakers = Sets.newHashSet(); - - Deaggregator(BgpInfoService bgpInfoService) { - super(); - // deaggregator must know the type of the connected BGP speakers and the BGP info. - // for this example we only have one Quagga BGP speaker. - bgpSpeakers.add(new QuaggaBgpSpeakers(bgpInfoService)); - } - - @Override - public void run() { - ArrayList messagesArray = DataHandler.getInstance().getData(); -// log.info("Messages size: " + messagesArray.size()); - - // Example of BGP Update message: - // { - // "path":[65001, 65002, 65004], (origin being last) - // "prefix":"12.0.0.0/8", - // } - - prefixes.forEach(prefix -> { - IpPrefix monitoredPrefix = prefix.prefix(); - - // for each update message in memory check for hijack - for (JSONObject tmp : messagesArray) { - IpPrefix receivedPrefix = null; - try { - receivedPrefix = IpPrefix.valueOf(tmp.getString("prefix")); - } catch (JSONException e) { - log.warn("JSONException: " + e.getMessage()); - e.printStackTrace(); - } - if (receivedPrefix == null) { - continue; - } - - // check if the announced network address is inside our subnet - if (monitoredPrefix.contains(receivedPrefix)) { - JSONArray path = null; - try { - path = tmp.getJSONArray("path"); - } catch (JSONException e) { - log.warn("JSONException: " + e.getMessage()); - e.printStackTrace(); - } - if (path == null) { - continue; - } - - int state = prefix.checkPath(path); - if (state >= 100) { - log.warn("BGP Hijack detected of type " + - (state - 100) + "\n" + tmp.toString()); - DataHandler.Serializer.writeHijack(tmp); - // can only de-aggregate /23 subnets and higher - int cidr = receivedPrefix.prefixLength(); - if (receivedPrefix.prefixLength() < 24) { - byte[] octets = receivedPrefix.address().toOctets(); - int byteGroup = (cidr + 1) / 8, - bitPos = 8 - (cidr + 1) % 8; - - octets[byteGroup] = (byte) (octets[byteGroup] & ~(1 << bitPos)); - String low = IpPrefix.valueOf(IpAddress.Version.INET, octets, cidr + 1).toString(); - octets[byteGroup] = (byte) (octets[byteGroup] | (1 << bitPos)); - String high = IpPrefix.valueOf(IpAddress.Version.INET, octets, cidr + 1).toString(); - - String[] prefixes = {low, high}; - bgpSpeakers.forEach(bgpSpeakers -> bgpSpeakers.announceSubPrefixes(prefixes)); - } else { - log.warn("Cannot announce smaller prefix than /24"); - } - } - } - } - }); - } - - public Set getPrefixes() { - return prefixes; - } - - public void setPrefixes(Set prefixes) { - this.prefixes = prefixes; - } -} \ No newline at end of file diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/PrefixHandler.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/PrefixHandler.java index 57553b5596..6fa407e334 100644 --- a/apps/artemis/src/main/java/org/onosproject/artemis/impl/PrefixHandler.java +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/PrefixHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-present Open Networking Foundation + * 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. @@ -15,12 +15,12 @@ */ package org.onosproject.artemis.impl; -import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.onlab.packet.IpPrefix; -import org.onosproject.artemis.impl.monitors.ExaBgpMonitor; -import org.onosproject.artemis.impl.monitors.Monitor; -import org.onosproject.artemis.impl.monitors.RipeMonitor; +import org.onosproject.artemis.ArtemisPacketProcessor; +import org.onosproject.artemis.Monitors; +import org.onosproject.artemis.impl.monitors.ExaBgpMonitors; +import org.onosproject.artemis.impl.monitors.RipeMonitors; import java.util.Map; import java.util.Objects; @@ -33,22 +33,23 @@ import java.util.Set; class PrefixHandler { private IpPrefix prefix; - private Set prefixMonitors = Sets.newHashSet(); + private Set prefixMonitors = Sets.newHashSet(); /** * Constructor that takes a CIDR-notation string and a list of monitors. * - * @param prefix A CIDR-notation string, e.g. "192.168.0.1/24" - * @param monitors A map of strings to a set of string for monitors, e.g. "ripe", ["host1","host2",..] + * @param prefix A CIDR-notation string, e.g. "192.168.0.1/24" + * @param monitors A map of strings to a set of string for monitors, e.g. "ripe", ["host1","host2",..] + * @param packetProcessor Packet processor */ - PrefixHandler(IpPrefix prefix, Map> monitors) { + PrefixHandler(IpPrefix prefix, Map> monitors, ArtemisPacketProcessor packetProcessor) { this.prefix = prefix; monitors.forEach((type, values) -> { - if (type.equals(Monitor.Types.RIPE.toString())) { - values.forEach(host -> prefixMonitors.add(new RipeMonitor(prefix, host))); - } else if (type.equals(Monitor.Types.EXABGP.toString())) { - values.forEach(host -> prefixMonitors.add(new ExaBgpMonitor(prefix, host))); + if (Monitors.Types.getEnum(type).equals(Monitors.Types.RIPE)) { + values.forEach(host -> prefixMonitors.add(new RipeMonitors(prefix, host, packetProcessor))); + } else if (Monitors.Types.getEnum(type).equals(Monitors.Types.EXABGP)) { + values.forEach(host -> prefixMonitors.add(new ExaBgpMonitors(prefix, host, packetProcessor))); } }); } @@ -57,14 +58,14 @@ class PrefixHandler { * Start all monitors for this prefix. */ void startPrefixMonitors() { - prefixMonitors.forEach(Monitor::startMonitor); + prefixMonitors.forEach(Monitors::startMonitor); } /** * Stop all monitors for this prefix. */ void stopPrefixMonitors() { - prefixMonitors.forEach(Monitor::stopMonitor); + prefixMonitors.forEach(Monitors::stopMonitor); } /** @@ -76,61 +77,20 @@ class PrefixHandler { return prefix; } - /** - * Changes the monitors based on the new list given. - * - * @param newMonitors monitors to be added - */ - void changeMonitors(Map> newMonitors) { - Set newTypes = newMonitors.keySet(); - Set monToRemove = Sets.newHashSet(); - Map> monToAdd = Maps.newHashMap(newMonitors); - - prefixMonitors.forEach(monitor -> { - String oldType = monitor.getType().toString(); - if (newTypes.contains(oldType)) { - Set newHosts = newMonitors.get(oldType); - String oldHost = monitor.getHost(); - if (newHosts.contains(oldHost)) { - monToAdd.remove(oldHost, oldHost); - } else { - monToRemove.add(monitor); - } - } else { - monToRemove.add(monitor); - } - }); - - monToRemove.forEach(Monitor::stopMonitor); - prefixMonitors.removeAll(monToRemove); - - //TODO - monToAdd.forEach((type, values) -> { - if (type.equals(Monitor.Types.RIPE.toString())) { - values.forEach(host -> prefixMonitors.add(new RipeMonitor(prefix, host))); - } else if (type.equals(Monitor.Types.EXABGP.toString())) { - values.forEach(host -> prefixMonitors.add(new ExaBgpMonitor(prefix, host))); - } - }); - - startPrefixMonitors(); + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PrefixHandler that = (PrefixHandler) o; + return Objects.equals(prefix, that.prefix); } @Override public int hashCode() { - return Objects.hashCode(prefix); + return Objects.hash(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.prefix); - } - return false; - } - } diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/bgpspeakers/BgpSpeakers.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/bgpspeakers/BgpSpeakers.java deleted file mode 100644 index a3642ed1f2..0000000000 --- a/apps/artemis/src/main/java/org/onosproject/artemis/impl/bgpspeakers/BgpSpeakers.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2016-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.bgpspeakers; - -import org.onosproject.routing.bgp.BgpInfoService; -import org.onosproject.routing.bgp.BgpSession; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collection; - -/** - * Abstract class for all the types of BGP Speakers. - */ -public abstract class BgpSpeakers { - - final Logger log = LoggerFactory.getLogger(getClass()); - Collection bgpSessions; - - BgpSpeakers(BgpInfoService bgpInfoService) { - this.bgpSessions = bgpInfoService.getBgpSessions(); - } - - /** - * Abstract function which announces the two new subprefixes on the BGP Speaker. - * @param prefixes list of two prefixes - */ - public abstract void announceSubPrefixes(String[] prefixes); -} diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/bgpspeakers/QuaggaBgpSpeakers.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/bgpspeakers/QuaggaBgpSpeakers.java index 462a033a70..8c95896647 100644 --- a/apps/artemis/src/main/java/org/onosproject/artemis/impl/bgpspeakers/QuaggaBgpSpeakers.java +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/bgpspeakers/QuaggaBgpSpeakers.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-present Open Networking Foundation + * 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. @@ -15,8 +15,12 @@ */ package org.onosproject.artemis.impl.bgpspeakers; +import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.net.telnet.TelnetClient; +import org.onosproject.artemis.BgpSpeakers; import org.onosproject.routing.bgp.BgpInfoService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.InputStream; import java.io.PrintStream; @@ -25,20 +29,23 @@ import java.util.Arrays; /** * Quagga interface to connect and announce prefixes. */ -public class QuaggaBgpSpeakers extends BgpSpeakers { +public class QuaggaBgpSpeakers implements BgpSpeakers { + // TODO: move this to configuration private static final String PASSWORD = "sdnip"; - + private final Logger log = LoggerFactory.getLogger(getClass()); private TelnetClient telnet = new TelnetClient(); private InputStream in; private PrintStream out; + private BgpInfoService bgpInfoService; public QuaggaBgpSpeakers(BgpInfoService bgpInfoService) { - super(bgpInfoService); + this.bgpInfoService = bgpInfoService; } @Override public void announceSubPrefixes(String[] prefixes) { - bgpSessions.forEach((session) -> { + log.info("Announcing subprefixes: {}", (Object[]) prefixes); + bgpInfoService.getBgpSessions().forEach((session) -> { String peerIp = session.remoteInfo().ip4Address().toString(), localAs = String.valueOf(session.remoteInfo().as4Number()); assert peerIp != null; @@ -59,7 +66,7 @@ public class QuaggaBgpSpeakers extends BgpSpeakers { log.info("Announced " + prefixes[0] + " and " + prefixes[1] + " at " + peerIp); } catch (Exception e) { - log.warn(e.getMessage()); + log.error(ExceptionUtils.getFullStackTrace(e)); } }); } @@ -85,7 +92,7 @@ public class QuaggaBgpSpeakers extends BgpSpeakers { ch = (char) in.read(); } } catch (Exception e) { - log.warn(e.getMessage()); + log.error(ExceptionUtils.getFullStackTrace(e)); } return null; } @@ -100,7 +107,7 @@ public class QuaggaBgpSpeakers extends BgpSpeakers { out.println(value); out.flush(); } catch (Exception e) { - log.warn(e.getMessage()); + log.error(ExceptionUtils.getFullStackTrace(e)); } } @@ -108,7 +115,7 @@ public class QuaggaBgpSpeakers extends BgpSpeakers { * Configure terminal and announce prefix inside the Quagga router. * * @param prefixes prefixes to announce - * @param localAs ASN of BGP Speaker + * @param localAs ASN of BGP Speaker */ private void announcePrefix(String[] prefixes, String localAs) { write("en"); @@ -131,7 +138,7 @@ public class QuaggaBgpSpeakers extends BgpSpeakers { try { telnet.disconnect(); } catch (Exception e) { - log.warn(e.getMessage()); + log.error(ExceptionUtils.getFullStackTrace(e)); } } } diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/bgpspeakers/package-info.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/bgpspeakers/package-info.java index 2dfcb49aed..4eb19fde1f 100644 --- a/apps/artemis/src/main/java/org/onosproject/artemis/impl/bgpspeakers/package-info.java +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/bgpspeakers/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-present Open Networking Foundation + * 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. diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/moas/MoasClientController.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/moas/MoasClientController.java new file mode 100644 index 0000000000..fccda3e587 --- /dev/null +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/moas/MoasClientController.java @@ -0,0 +1,107 @@ +/* + * 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.moas; + +import com.google.common.annotations.Beta; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.onlab.packet.IpAddress; +import org.onlab.packet.IpPrefix; +import org.onosproject.artemis.ArtemisPacketProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * MOAS Client Controller. + */ +@Beta +public class MoasClientController { + private final Logger log = LoggerFactory.getLogger(getClass()); + + private IpAddress host; + private EventLoopGroup workerGroup; + private MoasClientHandler ach; + private ChannelFuture channel; + private IpAddress localIp; + private IpPrefix localPrefix; + private ArtemisPacketProcessor packetProcessor; + + public MoasClientController(ArtemisPacketProcessor packetProcessor, + IpAddress host, IpAddress localIp, IpPrefix localPrefix) { + this.host = host; + this.ach = null; + this.localIp = localIp; + this.localPrefix = localPrefix; + this.packetProcessor = packetProcessor; + } + + /** + * Run the MOAS client. + */ + public void run() { + try { + final Bootstrap bootstrap = createBootstrap(); + + ach = new MoasClientHandler(localIp, localPrefix, packetProcessor); + + bootstrap.handler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) throws Exception { + ch.pipeline().addLast(ach); + } + }); + + channel = bootstrap.connect(host.toInetAddress(), 32323).sync(); + } catch (Exception e) { + log.warn(ExceptionUtils.getFullStackTrace(e)); + } + } + + /** + * Bootstrap netty socket. + * + * @return bootstrap + * @throws Exception exception + */ + private Bootstrap createBootstrap() throws Exception { + try { + workerGroup = new NioEventLoopGroup(); + return new Bootstrap() + .group(workerGroup) + .channel(NioSocketChannel.class) + .option(ChannelOption.SO_KEEPALIVE, true); + } catch (Exception e) { + throw new Exception(e); + } + } + + /** + * Stop the MOAS client. + */ + public void stop() { + channel.channel().close(); + workerGroup.shutdownGracefully(); + } + +} diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/moas/MoasClientHandler.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/moas/MoasClientHandler.java new file mode 100644 index 0000000000..58954d98e3 --- /dev/null +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/moas/MoasClientHandler.java @@ -0,0 +1,95 @@ +/* + * 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.moas; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.annotations.Beta; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.util.CharsetUtil; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.onlab.packet.IpAddress; +import org.onlab.packet.IpPrefix; +import org.onosproject.artemis.ArtemisPacketProcessor; +import org.onosproject.artemis.impl.objects.ArtemisMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +/** + * MOAS Client channel handler. + */ +@Sharable +@Beta +public class MoasClientHandler extends ChannelInboundHandlerAdapter { + + private static final Logger log = + LoggerFactory.getLogger(MoasClientHandler.class); + + private IpAddress localIp; + private IpPrefix localPrefix; + private ArtemisPacketProcessor packetProcessor; + + MoasClientHandler(IpAddress localIp, IpPrefix localPrefix, ArtemisPacketProcessor packetProcessor) { + this.localIp = localIp; + this.packetProcessor = packetProcessor; + this.localPrefix = localPrefix; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + log.info("Connected to server {}", ctx.channel().remoteAddress()); + + ArtemisMessage message = new ArtemisMessage(); + message.setType(ArtemisMessage.Type.INITIATE_FROM_CLIENT); + message.setLocalIp(localIp.toString()); + message.setLocalPrefix(localPrefix.toString()); + + ObjectMapper mapper = new ObjectMapper(); + try { + String jsonInString = mapper.writeValueAsString(message); + ByteBuf buffer = Unpooled.copiedBuffer(jsonInString, CharsetUtil.UTF_8); + ctx.writeAndFlush(buffer); + } catch (JsonProcessingException e) { + e.printStackTrace(); + log.warn(ExceptionUtils.getFullStackTrace(e)); + } + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws IOException { + ByteBuf in = (ByteBuf) msg; + String strMsg = in.toString(io.netty.util.CharsetUtil.US_ASCII); + + ObjectMapper mapper = new ObjectMapper(); + ArtemisMessage actObj = mapper.readValue(strMsg, ArtemisMessage.class); + + packetProcessor.processMoasPacket(actObj, ctx); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + log.error(ExceptionUtils.getFullStackTrace(cause)); + cause.printStackTrace(); + ctx.close(); + } + +} diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/moas/MoasServerController.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/moas/MoasServerController.java new file mode 100644 index 0000000000..c27a1348f8 --- /dev/null +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/moas/MoasServerController.java @@ -0,0 +1,129 @@ +/* + * 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.moas; + +import com.google.common.annotations.Beta; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.onosproject.artemis.ArtemisMoasAgent; +import org.onosproject.artemis.ArtemisPacketProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * MOAS Server Controller. + */ +@Beta +public class MoasServerController { + private final Logger log = LoggerFactory.getLogger(getClass()); + + protected ArtemisMoasAgent deviceAgent; + protected ArtemisPacketProcessor packetAgent; + + private EventLoopGroup bossGroup; + private EventLoopGroup workerGroup; + private ChannelFuture channel; + private int port = 32323; + + private boolean isRunning = false; + + /** + * Run the MOAS Servcer. + */ + private void run() { + final MoasServerController ctrl = this; + try { + final ServerBootstrap bootstrap = createServerBootStrap(); + + bootstrap.childHandler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) throws Exception { + ch.pipeline().addLast( + new MoasServerHandler(ctrl) + ); + } + }); + + channel = bootstrap.bind(port).sync(); + isRunning = true; + } catch (Exception e) { + log.warn(ExceptionUtils.getFullStackTrace(e)); + } + } + + /** + * Create netty server bootstrap. + * + * @return bootstrap + * @throws Exception exception + */ + private ServerBootstrap createServerBootStrap() throws Exception { + try { + bossGroup = new NioEventLoopGroup(); + workerGroup = new NioEventLoopGroup(); + + return new ServerBootstrap() + .group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .option(ChannelOption.SO_REUSEADDR, true) + .childOption(ChannelOption.SO_KEEPALIVE, true) + .childOption(ChannelOption.TCP_NODELAY, true); + } catch (Exception e) { + throw new Exception(e); + } + } + + /** + * Start Server Controller and initialize agents. + * + * @param deviceAgent device agent + * @param packetAgent packet agen + */ + public void start(ArtemisMoasAgent deviceAgent, ArtemisPacketProcessor packetAgent) { + if (isRunning) { + stop(); + this.deviceAgent = deviceAgent; + this.packetAgent = packetAgent; + run(); + } else { + this.deviceAgent = deviceAgent; + this.packetAgent = packetAgent; + run(); + } + isRunning = true; + } + + /** + * Stop Server Controller. + */ + public void stop() { + if (isRunning) { + channel.channel().close(); + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + isRunning = false; + } + } +} diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/moas/MoasServerHandler.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/moas/MoasServerHandler.java new file mode 100644 index 0000000000..d140416b0c --- /dev/null +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/moas/MoasServerHandler.java @@ -0,0 +1,85 @@ +/* + * 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.moas; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.annotations.Beta; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.onlab.packet.IpAddress; +import org.onosproject.artemis.impl.objects.ArtemisMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +/** + * MOAS Server channel handler. + */ +@Sharable +@Beta +public class MoasServerHandler extends ChannelInboundHandlerAdapter { + + private static final Logger log = + LoggerFactory.getLogger(MoasServerHandler.class); + + private MoasServerController controller; + + MoasServerHandler(MoasServerController controller) { + this.controller = controller; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + final SocketAddress address = ctx.channel().remoteAddress(); + if (!(address instanceof InetSocketAddress)) { + log.warn("Invalid client connection. MOAS is identified based on IP"); + ctx.close(); + return; + } + + final InetSocketAddress inetAddress = (InetSocketAddress) address; + final String host = inetAddress.getHostString(); + log.info("New client connected to the Server: {}", host); + + controller.deviceAgent.addMoas(IpAddress.valueOf(host), ctx); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws IOException { + ByteBuf in = (ByteBuf) msg; + String strMsg = in.toString(io.netty.util.CharsetUtil.US_ASCII); + + ObjectMapper mapper = new ObjectMapper(); + ArtemisMessage actObj = mapper.readValue(strMsg, ArtemisMessage.class); + + controller.packetAgent.processMoasPacket(actObj, ctx); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + log.error(ExceptionUtils.getFullStackTrace(cause)); + cause.printStackTrace(); + ctx.close(); + } + +} diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/moas/package-info.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/moas/package-info.java new file mode 100644 index 0000000000..dfe902f4b7 --- /dev/null +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/moas/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * MOAS related package. + */ +package org.onosproject.artemis.impl.moas; \ No newline at end of file diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/ExaBgpMonitor.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/ExaBgpMonitors.java similarity index 79% rename from apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/ExaBgpMonitor.java rename to apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/ExaBgpMonitors.java index 44019d506d..5fb9be4b71 100644 --- a/apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/ExaBgpMonitor.java +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/ExaBgpMonitors.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-present Open Networking Foundation + * 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. @@ -20,8 +20,8 @@ import io.socket.client.Socket; import org.json.JSONException; import org.json.JSONObject; import org.onlab.packet.IpPrefix; -import org.onosproject.artemis.impl.ArtemisManager; -import org.onosproject.artemis.impl.DataHandler; +import org.onosproject.artemis.ArtemisPacketProcessor; +import org.onosproject.artemis.Monitors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,17 +29,19 @@ import java.net.URISyntaxException; import java.util.Objects; /** - * Implementation of ExaBGP Route Collector Monitor. + * Implementation of ExaBGP Route Collector Monitors. */ -public class ExaBgpMonitor extends Monitor { +public class ExaBgpMonitors implements Monitors { + private final Logger log = LoggerFactory.getLogger(getClass()); private String host; private Socket socket; + private IpPrefix prefix; + private ArtemisPacketProcessor packetProcessor; - private final Logger log = LoggerFactory.getLogger(getClass()); - - public ExaBgpMonitor(IpPrefix prefix, String host) { - super(prefix); + public ExaBgpMonitors(IpPrefix prefix, String host, ArtemisPacketProcessor packetProcessor) { this.host = host; + this.prefix = prefix; + this.packetProcessor = packetProcessor; } /** @@ -56,22 +58,20 @@ public class ExaBgpMonitor extends Monitor { } } + /** + * ExaBGP message received on the socket.io. + * + * @param args exabgp message + */ private void onExaMessage(Object[] args) { JSONObject message = (JSONObject) args[0]; try { if (message.getString("type").equals("A")) { - // Write BGP message to a json database - DataHandler.Serializer.writeData(args[0]); - - if (ArtemisManager.logging) { - log.info(message.toString()); - } - // Example of BGP Update message: // { // "path":[65001], - // "peer":"1.1.1.1", + // "peer":"1.1.1.s1", // "prefix":"12.0.0.0/8", // "host":"exabgp", <-- Can put IP here // "type":"A", @@ -85,13 +85,23 @@ public class ExaBgpMonitor extends Monitor { message.remove("timestamp"); // Append synchronized message to message list in memory. - DataHandler.getInstance().appendData(message); + packetProcessor.processMonitorPacket(message); } } catch (JSONException e) { e.printStackTrace(); } } + @Override + public IpPrefix getPrefix() { + return prefix; + } + + @Override + public void setPrefix(IpPrefix prefix) { + this.prefix = prefix; + } + @Override public void startMonitor() { if (!isRunning()) { @@ -119,11 +129,6 @@ public class ExaBgpMonitor extends Monitor { } } - @Override - public Types getType() { - return Types.EXABGP; - } - @Override public boolean isRunning() { return this.socket != null; @@ -149,8 +154,8 @@ public class ExaBgpMonitor extends Monitor { if (this == obj) { return true; } - if (obj instanceof ExaBgpMonitor) { - final ExaBgpMonitor that = (ExaBgpMonitor) obj; + if (obj instanceof ExaBgpMonitors) { + final ExaBgpMonitors that = (ExaBgpMonitors) obj; return Objects.equals(this.prefix, that.prefix) && Objects.equals(this.host, that.host); } diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/RipeMonitor.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/RipeMonitors.java similarity index 82% rename from apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/RipeMonitor.java rename to apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/RipeMonitors.java index 811eeced29..e848875fa6 100644 --- a/apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/RipeMonitor.java +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/RipeMonitors.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-present Open Networking Foundation + * 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. @@ -17,11 +17,12 @@ package org.onosproject.artemis.impl.monitors; import io.socket.client.IO; import io.socket.client.Socket; +import org.apache.commons.lang.exception.ExceptionUtils; import org.json.JSONException; import org.json.JSONObject; import org.onlab.packet.IpPrefix; -import org.onosproject.artemis.impl.ArtemisManager; -import org.onosproject.artemis.impl.DataHandler; +import org.onosproject.artemis.ArtemisPacketProcessor; +import org.onosproject.artemis.Monitors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,17 +30,19 @@ import java.net.URISyntaxException; import java.util.Objects; /** - * Implementation of RIPE Route Collector Monitor. + * Implementation of RIPE Route Collector Monitors. */ -public class RipeMonitor extends Monitor { +public class RipeMonitors implements Monitors { + private final Logger log = LoggerFactory.getLogger(getClass()); private String host; private Socket socket; + private IpPrefix prefix; + private ArtemisPacketProcessor packetProcessor; - private final Logger log = LoggerFactory.getLogger(getClass()); - - public RipeMonitor(IpPrefix prefix, String host) { - super(prefix); + public RipeMonitors(IpPrefix prefix, String host, ArtemisPacketProcessor packetProcessor) { + this.prefix = prefix; this.host = host; + this.packetProcessor = packetProcessor; } /** @@ -64,6 +67,16 @@ public class RipeMonitor extends Monitor { } } + @Override + public IpPrefix getPrefix() { + return prefix; + } + + @Override + public void setPrefix(IpPrefix prefix) { + this.prefix = prefix; + } + /** * socket.io onRisMessage event handler. * This event is custom made that triggers when it receives an BGP update/withdraw for our prefix. @@ -74,13 +87,6 @@ public class RipeMonitor extends Monitor { try { JSONObject message = (JSONObject) args[0]; if (message.getString("type").equals("A")) { - // Write BGP message to a json database - DataHandler.Serializer.writeData(args[0]); - - if (ArtemisManager.logging) { - log.info(message.toString()); - } - // Example of BGP Update message: // { // "timestamp":1488044022.97, @@ -101,9 +107,10 @@ public class RipeMonitor extends Monitor { message.remove("host"); // Append synchronized message to message list in memory. - DataHandler.getInstance().appendData(message); + packetProcessor.processMonitorPacket(message); } } catch (JSONException e) { + log.error(ExceptionUtils.getFullStackTrace(e)); e.printStackTrace(); } socket.emit("ping"); @@ -122,6 +129,7 @@ public class RipeMonitor extends Monitor { this.socket.on(Socket.EVENT_PONG, args -> socket.emit("ping")); this.socket.on("ris_message", this::onRisMessage); } catch (URISyntaxException e) { + log.error(ExceptionUtils.getFullStackTrace(e)); e.printStackTrace(); } @@ -140,11 +148,6 @@ public class RipeMonitor extends Monitor { } } - @Override - public Types getType() { - return Types.RIPE; - } - @Override public boolean isRunning() { return this.socket != null; @@ -170,8 +173,8 @@ public class RipeMonitor extends Monitor { if (this == obj) { return true; } - if (obj instanceof RipeMonitor) { - final RipeMonitor that = (RipeMonitor) obj; + if (obj instanceof RipeMonitors) { + final RipeMonitors that = (RipeMonitors) obj; return Objects.equals(this.prefix, that.prefix) && Objects.equals(this.host, that.host); } diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/package-info.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/package-info.java index 88048745b9..d47aa3668a 100644 --- a/apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/package-info.java +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/monitors/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-present Open Networking Foundation + * 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. diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/objects/ArtemisMessage.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/objects/ArtemisMessage.java new file mode 100644 index 0000000000..78d4e442fa --- /dev/null +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/objects/ArtemisMessage.java @@ -0,0 +1,80 @@ +/* + * 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.objects; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import java.io.Serializable; + +/** + * Messages that are exchanged between the two MOAS entities. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "type", "localIp", "localPrefix" +}) +public class ArtemisMessage implements Serializable { + + @JsonProperty("type") + private Type type; + + @JsonProperty("localIp") + private String localIp; + + @JsonProperty("localPrefix") + private String localPrefix; + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public String getLocalIp() { + return localIp; + } + + public void setLocalIp(String localIp) { + this.localIp = localIp; + } + + public String getLocalPrefix() { + return localPrefix; + } + + public void setLocalPrefix(String localPrefix) { + this.localPrefix = localPrefix; + } + + @Override + public String toString() { + return "ArtemisMessage{" + + "type=" + type + + ", localIp=" + localIp + + ", localPrefix=" + localPrefix + + '}'; + } + + public enum Type { + INITIATE_FROM_CLIENT, + INITIATE_FROM_SERVER + } +} diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/objects/package-info.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/objects/package-info.java new file mode 100644 index 0000000000..65b1b221b5 --- /dev/null +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/objects/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Custom objects. + */ +package org.onosproject.artemis.impl.objects; \ No newline at end of file diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/impl/package-info.java b/apps/artemis/src/main/java/org/onosproject/artemis/impl/package-info.java index 13f5fc3d37..633afc5a4b 100644 --- a/apps/artemis/src/main/java/org/onosproject/artemis/impl/package-info.java +++ b/apps/artemis/src/main/java/org/onosproject/artemis/impl/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-present Open Networking Foundation + * 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. @@ -15,6 +15,6 @@ */ /** - * Artemis component. + * Implementation classes. */ package org.onosproject.artemis.impl; \ No newline at end of file diff --git a/apps/artemis/src/main/java/org/onosproject/artemis/cli/package-info.java b/apps/artemis/src/main/java/org/onosproject/artemis/package-info.java old mode 100644 new mode 100755 similarity index 83% rename from apps/artemis/src/main/java/org/onosproject/artemis/cli/package-info.java rename to apps/artemis/src/main/java/org/onosproject/artemis/package-info.java index b2d0dc46c4..07d5aab06e --- a/apps/artemis/src/main/java/org/onosproject/artemis/cli/package-info.java +++ b/apps/artemis/src/main/java/org/onosproject/artemis/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-present Open Networking Foundation + * 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. @@ -15,6 +15,6 @@ */ /** - * Artemis CLI commands. + * Artemis. */ -package org.onosproject.artemis.cli; \ No newline at end of file +package org.onosproject.artemis; \ No newline at end of file diff --git a/apps/artemis/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/artemis/src/main/resources/OSGI-INF/blueprint/shell-config.xml old mode 100644 new mode 100755 index adf71cabcd..af1af5abc5 --- a/apps/artemis/src/main/resources/OSGI-INF/blueprint/shell-config.xml +++ b/apps/artemis/src/main/resources/OSGI-INF/blueprint/shell-config.xml @@ -16,9 +16,6 @@ - - - diff --git a/tools/tutorials/artemis/topo.py b/tools/tutorials/artemis/artemis-topo.py similarity index 100% rename from tools/tutorials/artemis/topo.py rename to tools/tutorials/artemis/artemis-topo.py diff --git a/tools/tutorials/artemis/configs/network-cfg.json b/tools/tutorials/artemis/configs/network-cfg.json index 8b5d56fe28..35380ce74e 100644 --- a/tools/tutorials/artemis/configs/network-cfg.json +++ b/tools/tutorials/artemis/configs/network-cfg.json @@ -1,5 +1,75 @@ { - "ports" : { + "apps": { + "org.onosproject.artemis": { + "artemis": { + "moas": { }, + "monitors": { + "exabgp": [ + "192.168.1.2:5000" + ], + "ripe": [] + }, + "prefixes": [ + { + "moas": [ ], + "paths": [ + { + "neighbor": [ + { + "asn": 65002, + "neighbor": [ + 65001 + ] + } + ], + "origin": 65004 + } + ], + "prefix": "40.0.0.0/8" + } + ] + } + }, + "org.onosproject.reactive.routing": { + "reactiveRouting": { + "ip4LocalPrefixes": [ + { + "ipPrefix" : "40.0.0.0/24", + "type" : "PUBLIC", + "gatewayIp" : "40.0.0.1" + }, + { + "ipPrefix" : "150.1.3.0/30", + "type" : "PRIVATE", + "gatewayIp" : "150.1.3.2" + } + ], + "ip6LocalPrefixes": [], + "virtualGatewayMacAddress": "e2:f5:32:16:9a:46" + } + }, + "org.onosproject.router": { + "bgp": { + "bgpSpeakers" : [ + { + "name" : "speaker1", + "connectPoint" : "of:00002a45d713e141/4", + "peers" : [ + "150.1.3.1" + ] + } + ] + } + } + }, + "devices": { + "ovsdb:192.168.0.2": { + "basic": { + "driver": "ovs" + } + } + }, + "ports": { "of:00002a45d713e141/2" : { "interfaces" : [ { @@ -18,67 +88,5 @@ } ] } - }, - "apps" : { - "org.onosproject.router" : { - "bgp" : { - "bgpSpeakers" : [ - { - "name" : "speaker1", - "connectPoint" : "of:00002a45d713e141/4", - "peers" : [ - "150.1.3.1" - ] - } - ] - } - }, - "org.onosproject.reactive.routing" : { - "reactiveRouting" : { - "ip4LocalPrefixes" : [ - { - "ipPrefix" : "40.0.0.0/24", - "type" : "PUBLIC", - "gatewayIp" : "40.0.0.1" - }, - { - "ipPrefix" : "150.1.3.0/30", - "type" : "PRIVATE", - "gatewayIp" : "150.1.3.2" - } - ], - "ip6LocalPrefixes" : [ - ], - "virtualGatewayMacAddress" : "e2:f5:32:16:9a:46" - } - }, - "org.onosproject.artemis" : { - "artemis" : { - "prefixes" : [ - { - "prefix" : "40.0.0.0/8", - "paths" : [ - { - "origin" : 65004, - "neighbor" : [ - { - "asn" : 65002, - "neighbor": [ - 65001 - ] - } - ] - } - ], - "moas" : [ ] - } - ], - "frequency" : 3000, - "monitors" : { - "ripe" : [ ], - "exabgp": [ "192.168.1.2:5000" ] - } - } - } } -} +} \ No newline at end of file