Jonathan Hart
Committed by Gerrit Code Review

ProxyArpManager: Factor out common logic for handling ARP and NDP packets.

Change-Id: I0709f9cda2506f01dbc912b8c3e12443ec217fed
......@@ -82,7 +82,7 @@ public class ProxyArp {
packetService.addProcessor(processor, PacketProcessor.director(1));
readComponentConfiguration(context);
requestPackests();
requestPackets();
log.info("Started with Application ID {}", appId.id());
}
......@@ -99,13 +99,13 @@ public class ProxyArp {
@Modified
public void modified(ComponentContext context) {
readComponentConfiguration(context);
requestPackests();
requestPackets();
}
/**
* Request packet in via PacketService.
*/
private void requestPackests() {
private void requestPackets() {
TrafficSelector.Builder selectorBuilder =
DefaultTrafficSelector.builder();
selectorBuilder.matchEthType(TYPE_ARP);
......
......@@ -21,7 +21,7 @@ import org.onosproject.net.ConnectPoint;
import org.onosproject.net.packet.PacketContext;
/**
* Service for processing arp requests on behalf of applications.
* Service for processing ARP or NDP requests on behalf of applications.
*/
// TODO: move to the peer host package
public interface ProxyArpService {
......
......@@ -33,8 +33,6 @@ import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Host;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.instructions.Instruction;
import org.onosproject.net.flow.instructions.Instructions;
import org.onosproject.net.host.HostProvider;
import org.onosproject.net.host.InterfaceIpAddress;
import org.onosproject.net.packet.DefaultOutboundPacket;
......@@ -43,9 +41,7 @@ import org.onosproject.net.packet.PacketService;
import org.onosproject.net.provider.ProviderId;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
......@@ -149,7 +145,7 @@ public class HostMonitor implements TimerTask {
Set<Host> hosts = hostManager.getHostsByIp(ip);
if (hosts.isEmpty()) {
sendArpNdpRequest(ip);
sendRequest(ip);
} else {
for (Host host : hosts) {
HostProvider provider = hostProviders.get(host.providerId());
......@@ -166,12 +162,11 @@ public class HostMonitor implements TimerTask {
}
/**
* Sends an ARP or Neighbor Discovery Protocol request for the given IP
* address.
* Sends an ARP or NDP request for the given IP address.
*
* @param targetIp IP address to send the request for
*/
private void sendArpNdpRequest(IpAddress targetIp) {
private void sendRequest(IpAddress targetIp) {
Interface intf = interfaceService.getMatchingInterface(targetIp);
if (intf == null) {
......@@ -180,31 +175,26 @@ public class HostMonitor implements TimerTask {
for (InterfaceIpAddress ia : intf.ipAddresses()) {
if (ia.subnetAddress().contains(targetIp)) {
sendArpNdpProbe(intf.connectPoint(), targetIp, ia.ipAddress(),
sendProbe(intf.connectPoint(), targetIp, ia.ipAddress(),
intf.mac(), intf.vlan());
}
}
}
private void sendArpNdpProbe(ConnectPoint connectPoint,
IpAddress targetIp,
IpAddress sourceIp, MacAddress sourceMac,
VlanId vlan) {
private void sendProbe(ConnectPoint connectPoint,
IpAddress targetIp,
IpAddress sourceIp, MacAddress sourceMac,
VlanId vlan) {
Ethernet probePacket = null;
if (targetIp.isIp4()) {
// IPv4: Use ARP
probePacket = buildArpRequest(targetIp, sourceIp, sourceMac,
vlan);
probePacket = buildArpRequest(targetIp, sourceIp, sourceMac, vlan);
} else {
// IPv6: Use Neighbor Discovery
probePacket = buildNdpRequest(targetIp, sourceIp, sourceMac,
vlan);
probePacket = buildNdpRequest(targetIp, sourceIp, sourceMac, vlan);
}
List<Instruction> instructions = new ArrayList<>();
instructions.add(Instructions.createOutput(connectPoint.port()));
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(connectPoint.port())
.build();
......@@ -273,7 +263,7 @@ public class HostMonitor implements TimerTask {
icmp6.setIcmpType(ICMP6.NEIGHBOR_SOLICITATION);
icmp6.setIcmpCode((byte) 0);
// Create the Neighbor Solication packet
// Create the Neighbor Solicitation packet
NeighborSolicitation ns = new NeighborSolicitation();
ns.setTargetAddress(targetIp.toOctets());
ns.addOption(NeighborDiscoveryOptions.TYPE_SOURCE_LL_ADDRESS,
......
......@@ -69,11 +69,9 @@ 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 MAC_ADDR_NULL = "MAC address 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.";
private static final String MSG_NOT_REQUEST = "Message is not an ARP or NDP request";
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected EdgePortService edgeService;
......@@ -96,6 +94,14 @@ public class ProxyArpManager implements ProxyArpService {
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected InterfaceService interfaceService;
private enum Protocol {
ARP, NDP
}
private enum MessageType {
REQUEST, REPLY
}
@Activate
public void activate() {
store.setDelegate(this::sendTo);
......@@ -123,46 +129,48 @@ public class ProxyArpManager implements ProxyArpService {
checkNotNull(eth, REQUEST_NULL);
if (eth.getEtherType() == Ethernet.TYPE_ARP) {
replyArp(eth, inPort);
} else if (eth.getEtherType() == Ethernet.TYPE_IPV6) {
replyNdp(eth, inPort);
MessageContext context = createContext(eth, inPort);
if (context != null) {
replyInternal(context);
}
}
private void replyArp(Ethernet eth, ConnectPoint inPort) {
ARP arp = (ARP) eth.getPayload();
checkArgument(arp.getOpCode() == ARP.OP_REQUEST, NOT_ARP_REQUEST);
checkNotNull(inPort);
Ip4Address targetAddress = Ip4Address.valueOf(arp.getTargetProtocolAddress());
VlanId vlan = vlanId(eth.getVlanID());
/**
* Handles a request message.
*
* If the MAC address of the target is known, we can reply directly to the
* requestor. Otherwise, we forward the request out other ports in an
* attempt to find the correct host.
*
* @param context request message context to process
*/
private void replyInternal(MessageContext context) {
checkNotNull(context);
checkArgument(context.type() == MessageType.REQUEST, MSG_NOT_REQUEST);
if (hasIpAddress(inPort)) {
if (hasIpAddress(context.inPort())) {
// If the request came from outside the network, only reply if it was
// for one of our external addresses.
interfaceService.getInterfacesByPort(inPort)
interfaceService.getInterfacesByPort(context.inPort())
.stream()
.filter(intf -> intf.ipAddresses()
.stream()
.anyMatch(ia -> ia.ipAddress().equals(targetAddress)))
.forEach(intf -> buildAndSendArp(targetAddress, intf.mac(), eth, inPort));
.anyMatch(ia -> ia.ipAddress().equals(context.target())))
.forEach(intf -> buildAndSendReply(context, intf.mac()));
// Stop here and don't proxy ARPs if the port has an IP address
return;
}
// See if we have the target host in the host store
Set<Host> hosts = hostService.getHostsByIp(targetAddress);
Set<Host> hosts = hostService.getHostsByIp(context.target());
Host dst = null;
Host src = hostService.getHost(hostId(eth.getSourceMAC(),
vlanId(eth.getVlanID())));
Host src = hostService.getHost(hostId(context.srcMac(), context.vlan()));
for (Host host : hosts) {
if (host.vlan().equals(vlan)) {
if (host.vlan().equals(context.vlan())) {
dst = host;
break;
}
......@@ -170,22 +178,19 @@ public class ProxyArpManager implements ProxyArpService {
if (src != null && dst != null) {
// We know the target host so we can respond
buildAndSendArp(targetAddress, dst.mac(), eth, inPort);
buildAndSendReply(context, dst.mac());
return;
}
// 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 port.
Ip4Address source =
Ip4Address.valueOf(arp.getSenderProtocolAddress());
boolean matched = false;
Set<Interface> interfaces = interfaceService.getInterfacesByIp(source);
Set<Interface> interfaces = interfaceService.getInterfacesByIp(context.sender());
for (Interface intf : interfaces) {
if (intf.vlan().equals(vlan)) {
if (intf.vlan().equals(context.vlan())) {
matched = true;
sendTo(eth, intf.connectPoint());
sendTo(context.packet(), intf.connectPoint());
break;
}
}
......@@ -196,89 +201,33 @@ public class ProxyArpManager implements ProxyArpService {
// The request couldn't be resolved.
// Flood the request on all ports except the incoming port.
flood(eth, inPort);
flood(context.packet(), context.inPort());
}
private void replyNdp(Ethernet eth, ConnectPoint inPort) {
IPv6 ipv6 = (IPv6) eth.getPayload();
ICMP6 icmpv6 = (ICMP6) ipv6.getPayload();
NeighborSolicitation nsol = (NeighborSolicitation) icmpv6.getPayload();
Ip6Address targetAddress = Ip6Address.valueOf(nsol.getTargetAddress());
VlanId vlan = vlanId(eth.getVlanID());
// If the request came from outside the network, only reply if it was
// for one of our external addresses.
if (hasIpAddress(inPort)) {
interfaceService.getInterfacesByPort(inPort)
.stream()
.filter(intf -> intf.ipAddresses()
.stream()
.anyMatch(ia -> ia.ipAddress().equals(targetAddress)))
.forEach(intf -> buildAndSendNdp(targetAddress, intf.mac(), eth, inPort));
return;
}
// Continue with normal proxy ARP case
Set<Host> hosts = hostService.getHostsByIp(targetAddress);
Host dst = null;
Host src = hostService.getHost(hostId(eth.getSourceMAC(),
vlanId(eth.getVlanID())));
for (Host host : hosts) {
if (host.vlan().equals(vlan)) {
dst = host;
break;
}
}
if (src != null && dst != null) {
// We know the target host so we can respond
buildAndSendNdp(targetAddress, dst.mac(), eth, inPort);
return;
}
// 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 port.
Ip6Address source =
Ip6Address.valueOf(ipv6.getSourceAddress());
boolean matched = false;
Set<Interface> interfaces = interfaceService.getInterfacesByIp(source);
for (Interface intf : interfaces) {
if (intf.vlan().equals(vlan)) {
matched = true;
sendTo(eth, intf.connectPoint());
break;
}
}
if (matched) {
return;
/**
* Builds and sends a reply message given a request context and the resolved
* MAC address to answer with.
*
* @param context message context of request
* @param targetMac MAC address to be given in the response
*/
private void buildAndSendReply(MessageContext context, MacAddress targetMac) {
switch (context.protocol()) {
case ARP:
sendTo(ARP.buildArpReply((Ip4Address) context.target(),
targetMac, context.packet()), context.inPort());
break;
case NDP:
sendTo(buildNdpReply((Ip6Address) context.target(), targetMac,
context.packet()), context.inPort());
break;
default:
break;
}
// The request couldn't be resolved.
// Flood the request on all ports except the incoming ports.
flood(eth, inPort);
}
//TODO checkpoint
private void buildAndSendArp(Ip4Address srcIp, MacAddress srcMac,
Ethernet request, ConnectPoint port) {
sendTo(ARP.buildArpReply(srcIp, srcMac, request), port);
}
private void buildAndSendNdp(Ip6Address srcIp, MacAddress srcMac,
Ethernet request, ConnectPoint port) {
sendTo(buildNdpReply(srcIp, srcMac, request), port);
}
/**
* Outputs the given packet out the given port.
* Outputs a packet out a specific port.
*
* @param packet the packet to send
* @param outPort the port to send it out
......@@ -287,6 +236,12 @@ public class ProxyArpManager implements ProxyArpService {
sendTo(outPort, ByteBuffer.wrap(packet.serialize()));
}
/**
* Outputs a packet out a specific port.
*
* @param outPort port to send it out
* @param packet packet to send
*/
private void sendTo(ConnectPoint outPort, ByteBuffer packet) {
if (!edgeService.isEdgePoint(outPort)) {
// Sanity check to make sure we don't send the packet out an
......@@ -298,7 +253,7 @@ public class ProxyArpManager implements ProxyArpService {
TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
builder.setOutput(outPort.port());
packetService.emit(new DefaultOutboundPacket(outPort.deviceId(),
builder.build(), packet));
builder.build(), packet));
}
/**
......@@ -323,7 +278,7 @@ public class ProxyArpManager implements ProxyArpService {
checkNotNull(eth, REQUEST_NULL);
Host h = hostService.getHost(hostId(eth.getDestinationMAC(),
vlanId(eth.getVlanID())));
vlanId(eth.getVlanID())));
if (h == null) {
flood(eth, inPort);
......@@ -344,42 +299,24 @@ public class ProxyArpManager implements ProxyArpService {
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();
MessageContext msgContext = createContext(ethPkt, pkt.receivedFrom());
if (arp.getOpCode() == ARP.OP_REPLY) {
forward(ethPkt, context.inPacket().receivedFrom());
} else if (arp.getOpCode() == ARP.OP_REQUEST) {
reply(ethPkt, context.inPacket().receivedFrom());
} else {
if (msgContext == null) {
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 {
switch (msgContext.type()) {
case REPLY:
forward(msgContext.packet(), msgContext.inPort());
break;
case REQUEST:
replyInternal(msgContext);
break;
default:
return false;
}
context.block();
return true;
}
......@@ -444,4 +381,148 @@ public class ProxyArpManager implements ProxyArpService {
eth.setPayload(ipv6);
return eth;
}
/**
* Attempts to create a MessageContext for the given Ethernet frame. If the
* frame is a valid ARP or NDP request or response, a context will be
* created.
*
* @param eth input Ethernet frame
* @param inPort in port
* @return MessageContext if the packet was ARP or NDP, otherwise null
*/
private MessageContext createContext(Ethernet eth, ConnectPoint inPort) {
if (eth.getEtherType() == Ethernet.TYPE_ARP) {
return createArpContext(eth, inPort);
} else if (eth.getEtherType() == Ethernet.TYPE_IPV6) {
return createNdpContext(eth, inPort);
}
return null;
}
/**
* Extracts context information from ARP packets.
*
* @param eth input Ethernet frame that is thought to be ARP
* @param inPort in port
* @return MessageContext object if the packet was a valid ARP packet,
* otherwise null
*/
private MessageContext createArpContext(Ethernet eth, ConnectPoint inPort) {
if (eth.getEtherType() != Ethernet.TYPE_ARP) {
return null;
}
ARP arp = (ARP) eth.getPayload();
IpAddress target = Ip4Address.valueOf(arp.getTargetProtocolAddress());
IpAddress sender = Ip4Address.valueOf(arp.getSenderProtocolAddress());
MessageType type;
if (arp.getOpCode() == ARP.OP_REQUEST) {
type = MessageType.REQUEST;
} else if (arp.getOpCode() == ARP.OP_REPLY) {
type = MessageType.REPLY;
} else {
return null;
}
return new MessageContext(eth, inPort, Protocol.ARP, type, target, sender);
}
/**
* Extracts context information from NDP packets.
*
* @param eth input Ethernet frame that is thought to be NDP
* @param inPort in port
* @return MessageContext object if the packet was a valid NDP packet,
* otherwise null
*/
private MessageContext createNdpContext(Ethernet eth, ConnectPoint inPort) {
if (eth.getEtherType() != Ethernet.TYPE_IPV6) {
return null;
}
IPv6 ipv6 = (IPv6) eth.getPayload();
if (ipv6.getNextHeader() != IPv6.PROTOCOL_ICMP6) {
return null;
}
ICMP6 icmpv6 = (ICMP6) ipv6.getPayload();
IpAddress sender = Ip6Address.valueOf(ipv6.getSourceAddress());
IpAddress target = null;
MessageType type;
if (icmpv6.getIcmpType() == ICMP6.NEIGHBOR_SOLICITATION) {
type = MessageType.REQUEST;
NeighborSolicitation nsol = (NeighborSolicitation) icmpv6.getPayload();
target = Ip6Address.valueOf(nsol.getTargetAddress());
} else if (icmpv6.getIcmpType() == ICMP6.NEIGHBOR_ADVERTISEMENT) {
type = MessageType.REPLY;
} else {
return null;
}
return new MessageContext(eth, inPort, Protocol.NDP, type, target, sender);
}
/**
* Provides context information for a particular ARP or NDP message, with
* a unified interface to access data regardless of protocol.
*/
private class MessageContext {
private Protocol protocol;
private MessageType type;
private IpAddress target;
private IpAddress sender;
private Ethernet eth;
private ConnectPoint inPort;
public MessageContext(Ethernet eth, ConnectPoint inPort,
Protocol protocol, MessageType type,
IpAddress target, IpAddress sender) {
this.eth = eth;
this.inPort = inPort;
this.protocol = protocol;
this.type = type;
this.target = target;
this.sender = sender;
}
public ConnectPoint inPort() {
return inPort;
}
public Ethernet packet() {
return eth;
}
public Protocol protocol() {
return protocol;
}
public MessageType type() {
return type;
}
public VlanId vlan() {
return VlanId.vlanId(eth.getVlanID());
}
public MacAddress srcMac() {
return MacAddress.valueOf(eth.getSourceMACAddress());
}
public IpAddress target() {
return target;
}
public IpAddress sender() {
return sender;
}
}
}
......