Kunihiro Ishiguro
Committed by Gerrit Code Review

WIP: proxy NDP for IPv6.

ONOS-924

Need to implement test cases.

Change-Id: I86b0cb8c3c8d75f7276778e04c1a7166c9bb2f59
......@@ -37,6 +37,7 @@
<module>ifwd</module>
<module>mobility</module>
<module>proxyarp</module>
<module>proxyndp</module>
<module>config</module>
<module>sdnip</module>
<module>calendar</module>
......
......@@ -91,7 +91,7 @@ public class ProxyArp {
}
//handle the arp packet.
proxyArpService.handleArp(context);
proxyArpService.handlePacket(context);
}
}
}
......
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2014 Open Networking Laboratory
~
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.onosproject</groupId>
<artifactId>onos-apps</artifactId>
<version>1.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-app-proxyndp</artifactId>
<packaging>bundle</packaging>
<description>ONOS simple proxy neighbor discovery for IPv6 module</description>
</project>
/*
* Copyright 2014 Open Networking Laboratory
*
* 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.proxyndp;
import static org.slf4j.LoggerFactory.getLogger;
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.onlab.packet.Ethernet;
import org.onlab.packet.IPv6;
import org.onlab.packet.ICMP6;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.packet.PacketPriority;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.onosproject.net.proxyarp.ProxyArpService;
import org.slf4j.Logger;
/**
* Sample reactive proxy ndp application.
*/
@Component(immediate = true)
public class ProxyNdp {
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PacketService packetService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ProxyArpService proxyArpService;
private ProxyNdpProcessor processor = new ProxyNdpProcessor();
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
private ApplicationId appId;
@Activate
public void activate() {
appId = coreService.registerApplication("org.onosproject.proxyndp");
packetService.addProcessor(processor, PacketProcessor.ADVISOR_MAX + 1);
TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder();
selectorBuilder.matchEthType(Ethernet.TYPE_IPV6);
selectorBuilder.matchIPProtocol(IPv6.PROTOCOL_ICMP6);
selectorBuilder.matchIcmpv6Type(ICMP6.NEIGHBOR_SOLICITATION);
packetService.requestPackets(selectorBuilder.build(),
PacketPriority.CONTROL, appId);
selectorBuilder = DefaultTrafficSelector.builder();
selectorBuilder.matchEthType(Ethernet.TYPE_IPV6);
selectorBuilder.matchIPProtocol(IPv6.PROTOCOL_ICMP6);
selectorBuilder.matchIcmpv6Type(ICMP6.NEIGHBOR_ADVERTISEMENT);
packetService.requestPackets(selectorBuilder.build(),
PacketPriority.CONTROL, appId);
log.info("Started with Application ID {}", appId.id());
}
@Deactivate
public void deactivate() {
packetService.removeProcessor(processor);
processor = null;
log.info("Stopped");
}
/**
* Packet processor responsible for forwarding packets along their paths.
*/
private class ProxyNdpProcessor implements PacketProcessor {
@Override
public void process(PacketContext context) {
// Stop processing if the packet has been handled, since we
// can't do any more to it.
if (context.isHandled()) {
return;
}
// Handle the neighbor discovery packet.
proxyArpService.handlePacket(context);
}
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* 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.
*/
/**
* Proxy Ndp application that handles IPv6 neighbor resolution for you.
*/
package org.onosproject.proxyndp;
......@@ -16,7 +16,7 @@
package org.onosproject.net.proxyarp;
import org.onlab.packet.Ethernet;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.IpAddress;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.packet.PacketContext;
......@@ -27,37 +27,39 @@ import org.onosproject.net.packet.PacketContext;
public interface ProxyArpService {
/**
* Returns whether this particular IPv4 address is known to the system.
* Returns whether this particular IP address is known to the system.
*
* @param addr an IPv4 address
* @param addr an IP address
* @return true if know, false otherwise
*/
boolean isKnown(Ip4Address addr);
boolean isKnown(IpAddress addr);
/**
* Sends a reply for a given request. If the host is not known then the arp
* will be flooded at all edge ports.
* Sends a reply for a given request. If the host is not known then the
* arp or neighbor solicitation will be flooded at all edge ports.
*
* @param eth an arp request
* @param eth an arp or neighbor solicitation request
* @param inPort the port the request was received on
*/
void reply(Ethernet eth, ConnectPoint inPort);
/**
* Forwards an ARP request to its destination. Floods at the edge the ARP request if the
* destination is not known.
* Forwards an ARP or neighbor solicitation request to its destination.
* Floods at the edg the request if the destination is not known.
*
* @param eth an ethernet frame containing an ARP request.
* @param eth an ethernet frame containing an ARP or neighbor solicitation
* request.
* @param inPort the port the request was received on
*/
void forward(Ethernet eth, ConnectPoint inPort);
/**
* Handles a arp packet.
* Replies to arp requests and forwards request to the right place.
* Handles a arp or neighbor solicitation packet.
* Replies to arp or neighbor solicitation requests and forwards request
* to the right place.
* @param context the packet context to handle
* @return true if handled, false otherwise.
*/
boolean handleArp(PacketContext context);
boolean handlePacket(PacketContext context);
}
......
......@@ -26,10 +26,15 @@ import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.packet.ARP;
import org.onlab.packet.Ethernet;
import org.onlab.packet.ICMP6;
import org.onlab.packet.IPv6;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.Ip6Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onlab.packet.ndp.NeighborAdvertisement;
import org.onlab.packet.ndp.NeighborSolicitation;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Device;
import org.onosproject.net.Host;
......@@ -72,7 +77,7 @@ public class ProxyArpManager implements ProxyArpService {
private final Logger log = getLogger(getClass());
private static final String MAC_ADDR_NULL = "Mac address cannot be null.";
private static final String REQUEST_NULL = "Arp request cannot be null.";
private static final String REQUEST_NULL = "ARP or NDP request cannot be null.";
private static final String REQUEST_NOT_ARP = "Ethernet frame does not contain ARP request.";
private static final String NOT_ARP_REQUEST = "ARP is not a request.";
private static final String NOT_ARP_REPLY = "ARP is not a reply.";
......@@ -115,7 +120,7 @@ public class ProxyArpManager implements ProxyArpService {
}
@Override
public boolean isKnown(Ip4Address addr) {
public boolean isKnown(IpAddress addr) {
checkNotNull(addr, MAC_ADDR_NULL);
Set<Host> hosts = hostService.getHostsByIp(addr);
return !hosts.isEmpty();
......@@ -124,8 +129,15 @@ public class ProxyArpManager implements ProxyArpService {
@Override
public void reply(Ethernet eth, ConnectPoint inPort) {
checkNotNull(eth, REQUEST_NULL);
checkArgument(eth.getEtherType() == Ethernet.TYPE_ARP,
REQUEST_NOT_ARP);
if (eth.getEtherType() == Ethernet.TYPE_ARP) {
replyArp(eth, inPort);
} else if (eth.getEtherType() == Ethernet.TYPE_IPV6) {
replyNdp(eth, inPort);
}
}
private void replyArp(Ethernet eth, ConnectPoint inPort) {
ARP arp = (ARP) eth.getPayload();
checkArgument(arp.getOpCode() == ARP.OP_REQUEST, NOT_ARP_REQUEST);
checkNotNull(inPort);
......@@ -209,6 +221,92 @@ public class ProxyArpManager implements ProxyArpService {
}
}
private void replyNdp(Ethernet eth, ConnectPoint inPort) {
IPv6 ipv6 = (IPv6) eth.getPayload();
ICMP6 icmpv6 = (ICMP6) ipv6.getPayload();
NeighborSolicitation nsol = (NeighborSolicitation) icmpv6.getPayload();
VlanId vlan = VlanId.vlanId(eth.getVlanID());
// If the request came from outside the network, only reply if it was
// for one of our external addresses.
if (isOutsidePort(inPort)) {
Ip6Address target =
Ip6Address.valueOf(nsol.getTargetAddress());
Set<PortAddresses> addressSet =
hostService.getAddressBindingsForPort(inPort);
for (PortAddresses addresses : addressSet) {
for (InterfaceIpAddress ia : addresses.ipAddresses()) {
if (ia.ipAddress().equals(target)) {
Ethernet ndpReply =
buildNdpReply(target, addresses.mac(), eth);
sendTo(ndpReply, inPort);
}
}
}
return;
} else {
// If the source address matches one of our external addresses
// it could be a request from an internal host to an external
// address. Forward it over to the correct ports.
Ip6Address source =
Ip6Address.valueOf(nsol.getTargetAddress());
Set<PortAddresses> sourceAddresses = findPortsInSubnet(source);
boolean matched = false;
for (PortAddresses pa : sourceAddresses) {
for (InterfaceIpAddress ia : pa.ipAddresses()) {
if (ia.ipAddress().equals(source) &&
pa.vlan().equals(vlan)) {
matched = true;
sendTo(eth, pa.connectPoint());
}
}
}
if (matched) {
return;
}
}
// Continue with normal proxy ARP case
Set<Host> hosts = hostService.getHostsByIp(
Ip6Address.valueOf(nsol.getTargetAddress()));
Host dst = null;
Host src = hostService.getHost(HostId.hostId(eth.getSourceMAC(),
VlanId.vlanId(eth.getVlanID())));
for (Host host : hosts) {
if (host.vlan().equals(vlan)) {
dst = host;
break;
}
}
if (src == null || dst == null) {
flood(eth, inPort);
return;
}
//
// TODO find the correct IP address.
// Right now we use the first IPv4 address that is found.
//
for (IpAddress ipAddress : dst.ipAddresses()) {
Ip6Address ip6Address = ipAddress.getIp6Address();
if (ip6Address != null) {
Ethernet arpReply = buildNdpReply(ip6Address, dst.mac(), eth);
// TODO: check send status with host service.
sendTo(arpReply, src.location());
break;
}
}
}
/**
* Outputs the given packet out the given port.
*
......@@ -236,7 +334,7 @@ public class ProxyArpManager implements ProxyArpService {
* @param target the target address to find a matching port for
* @return a set of PortAddresses describing ports in the subnet
*/
private Set<PortAddresses> findPortsInSubnet(Ip4Address target) {
private Set<PortAddresses> findPortsInSubnet(IpAddress target) {
Set<PortAddresses> result = new HashSet<PortAddresses>();
for (PortAddresses addresses : hostService.getAddressBindings()) {
for (InterfaceIpAddress ia : addresses.ipAddresses()) {
......@@ -266,10 +364,6 @@ public class ProxyArpManager implements ProxyArpService {
@Override
public void forward(Ethernet eth, ConnectPoint inPort) {
checkNotNull(eth, REQUEST_NULL);
checkArgument(eth.getEtherType() == Ethernet.TYPE_ARP,
REQUEST_NOT_ARP);
ARP arp = (ARP) eth.getPayload();
checkArgument(arp.getOpCode() == ARP.OP_REPLY, NOT_ARP_REPLY);
Host h = hostService.getHost(HostId.hostId(eth.getDestinationMAC(),
VlanId.vlanId(eth.getVlanID())));
......@@ -286,21 +380,52 @@ public class ProxyArpManager implements ProxyArpService {
}
@Override
public boolean handleArp(PacketContext context) {
public boolean handlePacket(PacketContext context) {
InboundPacket pkt = context.inPacket();
Ethernet ethPkt = pkt.parsed();
if (ethPkt != null && ethPkt.getEtherType() == Ethernet.TYPE_ARP) {
if (ethPkt == null) {
return false;
}
if (ethPkt.getEtherType() == Ethernet.TYPE_ARP) {
return handleArp(context, ethPkt);
} else if (ethPkt.getEtherType() == Ethernet.TYPE_IPV6) {
return handleNdp(context, ethPkt);
}
return false;
}
private boolean handleArp(PacketContext context, Ethernet ethPkt) {
ARP arp = (ARP) ethPkt.getPayload();
if (arp.getOpCode() == ARP.OP_REPLY) {
forward(ethPkt, context.inPacket().receivedFrom());
} else if (arp.getOpCode() == ARP.OP_REQUEST) {
reply(ethPkt, context.inPacket().receivedFrom());
} else {
return false;
}
context.block();
return true;
}
private boolean handleNdp(PacketContext context, Ethernet ethPkt) {
IPv6 ipv6 = (IPv6) ethPkt.getPayload();
if (ipv6.getNextHeader() != IPv6.PROTOCOL_ICMP6) {
return false;
}
ICMP6 icmpv6 = (ICMP6) ipv6.getPayload();
if (icmpv6.getIcmpType() == ICMP6.NEIGHBOR_ADVERTISEMENT) {
forward(ethPkt, context.inPacket().receivedFrom());
} else if (icmpv6.getIcmpType() == ICMP6.NEIGHBOR_SOLICITATION) {
reply(ethPkt, context.inPacket().receivedFrom());
} else {
return false;
}
context.block();
return true;
}
/**
* Flood the arp request at all edges in the network.
......@@ -398,6 +523,44 @@ public class ProxyArpManager implements ProxyArpService {
return eth;
}
/**
* Builds an Neighbor Discovery reply based on a request.
*
* @param srcIp the IP address to use as the reply source
* @param srcMac the MAC address to use as the reply source
* @param request the Neighbor Solicitation request we got
* @return an Ethernet frame containing the Neighbor Advertisement reply
*/
private Ethernet buildNdpReply(Ip6Address srcIp, MacAddress srcMac,
Ethernet request) {
Ethernet eth = new Ethernet();
eth.setDestinationMACAddress(request.getSourceMAC());
eth.setSourceMACAddress(srcMac);
eth.setEtherType(Ethernet.TYPE_IPV6);
eth.setVlanID(request.getVlanID());
IPv6 requestIp = (IPv6) request.getPayload();
IPv6 ipv6 = new IPv6();
ipv6.setSourceAddress(srcIp.toOctets());
ipv6.setDestinationAddress(requestIp.getSourceAddress());
ipv6.setHopLimit((byte) 255);
eth.setPayload(ipv6);
ICMP6 icmp6 = new ICMP6();
icmp6.setIcmpType(ICMP6.NEIGHBOR_ADVERTISEMENT);
icmp6.setIcmpCode((byte) 0);
ipv6.setPayload(icmp6);
NeighborAdvertisement nadv = new NeighborAdvertisement();
nadv.setTargetAddress(srcMac.toBytes());
nadv.setSolicitedFlag((byte) 1);
nadv.setOverrideFlag((byte) 1);
icmp6.setPayload(nadv);
return eth;
}
public class InternalLinkListener implements LinkListener {
@Override
......
......@@ -184,6 +184,12 @@
<bundle>mvn:org.onosproject/onos-app-proxyarp/@ONOS-VERSION</bundle>
</feature>
<feature name="onos-app-proxyndp" version="@FEATURE-VERSION"
description="ONOS sample proxyndp application">
<feature>onos-api</feature>
<bundle>mvn:org.onosproject/onos-app-proxyndp/@ONOS-VERSION</bundle>
</feature>
<feature name="onos-app-foo" version="@FEATURE-VERSION"
description="ONOS sample playground application">
<feature>onos-api</feature>
......