[CORD-1434][CORD-1112] DHCP relay manager
Change-Id: I2e4d8fc8e85ed66b33ac517660ee72a1c0183597
diff --git a/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java b/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java
index 7190aea..b762e79 100644
--- a/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java
+++ b/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java
@@ -28,7 +28,6 @@
import org.apache.felix.scr.annotations.Service;
import org.onlab.packet.ARP;
import org.onlab.packet.DHCP;
-import org.onlab.packet.dhcp.DhcpOption;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IPv4;
import org.onlab.packet.Ip4Address;
@@ -37,6 +36,7 @@
import org.onlab.packet.TpPort;
import org.onlab.packet.UDP;
import org.onlab.packet.VlanId;
+import org.onlab.packet.dhcp.DhcpOption;
import org.onlab.util.SharedScheduledExecutors;
import org.onlab.util.Tools;
import org.onosproject.cfg.ComponentConfigService;
@@ -77,6 +77,7 @@
import java.util.Date;
import java.util.Dictionary;
import java.util.HashSet;
+
import java.util.List;
import java.util.Map;
import java.util.Objects;
diff --git a/apps/dhcprelay/BUCK b/apps/dhcprelay/BUCK
index e420f72..4a2b65c 100644
--- a/apps/dhcprelay/BUCK
+++ b/apps/dhcprelay/BUCK
@@ -6,6 +6,8 @@
TEST_DEPS = [
'//lib:TEST_ADAPTERS',
+ '//core/api:onos-api-tests',
+ '//incubator/api:onos-incubator-api-tests',
]
osgi_jar_with_tests (
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelay.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelay.java
deleted file mode 100644
index 778f778..0000000
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelay.java
+++ /dev/null
@@ -1,626 +0,0 @@
-/*
- * Copyright 2016-present 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.dhcprelay;
-
-import java.nio.ByteBuffer;
-import java.util.Dictionary;
-import java.util.Objects;
-import java.util.Set;
-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.Modified;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.ReferenceCardinality;
-import org.onlab.packet.ARP;
-import org.onlab.packet.DHCP;
-import org.onlab.packet.dhcp.DhcpOption;
-import org.onlab.packet.Ethernet;
-import org.onlab.packet.IPv4;
-import org.onlab.packet.Ip4Address;
-import org.onlab.packet.IpAddress;
-import org.onlab.packet.MacAddress;
-import org.onlab.packet.TpPort;
-import org.onlab.packet.UDP;
-import org.onlab.packet.VlanId;
-import org.onlab.util.Tools;
-import org.onosproject.core.ApplicationId;
-import org.onosproject.core.CoreService;
-import org.onosproject.incubator.net.intf.Interface;
-import org.onosproject.incubator.net.intf.InterfaceService;
-import org.onosproject.net.ConnectPoint;
-import org.onosproject.net.Host;
-import org.onosproject.net.HostId;
-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.flow.DefaultTrafficSelector;
-import org.onosproject.net.flow.DefaultTrafficTreatment;
-import org.onosproject.net.flow.TrafficSelector;
-import org.onosproject.net.flow.TrafficTreatment;
-import org.onosproject.net.host.HostEvent;
-import org.onosproject.net.host.HostListener;
-import org.onosproject.net.host.HostService;
-import org.onosproject.net.host.InterfaceIpAddress;
-import org.onosproject.net.packet.DefaultOutboundPacket;
-import org.onosproject.net.packet.OutboundPacket;
-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.osgi.service.component.ComponentContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.collect.ImmutableSet;
-
-import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
-import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
-import static org.onlab.packet.MacAddress.valueOf;
-/**
- * DHCP Relay Agent Application Component.
- */
-@Component(immediate = true)
-public class DhcpRelay {
-
- public static final String DHCP_RELAY_APP = "org.onosproject.dhcp-relay";
- private final Logger log = LoggerFactory.getLogger(getClass());
- private final InternalConfigListener cfgListener = new InternalConfigListener();
- private static MacAddress myMAC = valueOf("4f:4f:4f:4f:4f:4f");
-
- private final Set<ConfigFactory> factories = ImmutableSet.of(
- new ConfigFactory<ApplicationId, DhcpRelayConfig>(APP_SUBJECT_FACTORY,
- DhcpRelayConfig.class,
- "dhcprelay") {
- @Override
- public DhcpRelayConfig createConfig() {
- return new DhcpRelayConfig();
- }
- }
- );
-
- @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
- protected NetworkConfigRegistry cfgService;
-
- @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
- protected CoreService coreService;
-
- @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
- protected PacketService packetService;
-
- @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
- protected HostService hostService;
-
- @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
- protected InterfaceService interfaceService;
-
- @Property(name = "arpEnabled", boolValue = true,
- label = "Enable Address resolution protocol")
- protected boolean arpEnabled = true;
-
- private DhcpRelayPacketProcessor dhcpRelayPacketProcessor = new DhcpRelayPacketProcessor();
- private InternalHostListener hostListener = new InternalHostListener();
-
- private Ip4Address dhcpServerIp = null;
- // dhcp server may be connected directly to the SDN network or
- // via an external gateway. When connected directly, the dhcpConnectPoint, dhcpConnectMac,
- // and dhcpConnectVlan refer to the server. When connected via the gateway, they refer
- // to the gateway.
- private ConnectPoint dhcpServerConnectPoint = null;
- private MacAddress dhcpConnectMac = null;
- private VlanId dhcpConnectVlan = null;
- private Ip4Address dhcpGatewayIp = null;
- private ApplicationId appId;
-
- @Activate
- protected void activate(ComponentContext context) {
- //start the dhcp relay agent
- appId = coreService.registerApplication(DHCP_RELAY_APP);
-
- cfgService.addListener(cfgListener);
- factories.forEach(cfgService::registerConfigFactory);
- //update the dhcp server configuration.
- updateConfig();
- //add the packet services.
- packetService.addProcessor(dhcpRelayPacketProcessor, PacketProcessor.director(0));
- hostService.addListener(hostListener);
- requestDhcpPackets();
- modified(context);
-
- log.info("DHCP-RELAY Started");
- }
-
- @Deactivate
- protected void deactivate() {
- cfgService.removeListener(cfgListener);
- factories.forEach(cfgService::unregisterConfigFactory);
- packetService.removeProcessor(dhcpRelayPacketProcessor);
- hostService.removeListener(hostListener);
- cancelDhcpPackets();
- cancelArpPackets();
- if (dhcpGatewayIp != null) {
- hostService.stopMonitoringIp(dhcpGatewayIp);
- } else {
- hostService.stopMonitoringIp(dhcpServerIp);
- }
-
- log.info("DHCP-RELAY Stopped");
- }
-
- @Modified
- protected void modified(ComponentContext context) {
- Dictionary<?, ?> properties = context.getProperties();
- Boolean flag;
-
- flag = Tools.isPropertyEnabled(properties, "arpEnabled");
- if (flag != null) {
- arpEnabled = flag;
- log.info("Address resolution protocol is {}",
- arpEnabled ? "enabled" : "disabled");
- }
-
- if (arpEnabled) {
- requestArpPackets();
- } else {
- cancelArpPackets();
- }
- }
-
- /**
- * Checks if this app has been configured.
- *
- * @return true if all information we need have been initialized
- */
- private boolean configured() {
- return dhcpServerConnectPoint != null && dhcpServerIp != null;
- }
-
- private void updateConfig() {
- DhcpRelayConfig cfg = cfgService.getConfig(appId, DhcpRelayConfig.class);
- if (cfg == null) {
- log.warn("Dhcp Server info not available");
- return;
- }
-
- dhcpServerConnectPoint = cfg.getDhcpServerConnectPoint();
- Ip4Address oldDhcpServerIp = dhcpServerIp;
- Ip4Address oldDhcpGatewayIp = dhcpGatewayIp;
- dhcpServerIp = cfg.getDhcpServerIp();
- dhcpGatewayIp = cfg.getDhcpGatewayIp();
- dhcpConnectMac = null; // reset for updated config
- dhcpConnectVlan = null; // reset for updated config
- log.info("DHCP server connect point: " + dhcpServerConnectPoint);
- log.info("DHCP server ipaddress " + dhcpServerIp);
-
- IpAddress ipToProbe = dhcpGatewayIp != null ? dhcpGatewayIp : dhcpServerIp;
- String hostToProbe = dhcpGatewayIp != null ? "gateway" : "DHCP server";
-
- Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
- if (hosts.isEmpty()) {
- log.info("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
- if (oldDhcpGatewayIp != null) {
- hostService.stopMonitoringIp(oldDhcpGatewayIp);
- }
- if (oldDhcpServerIp != null) {
- hostService.stopMonitoringIp(oldDhcpServerIp);
- }
- hostService.startMonitoringIp(ipToProbe);
- } else {
- // Probe target is known; There should be only 1 host with this ip
- hostUpdated(hosts.iterator().next());
- }
- }
-
- private void hostRemoved(Host host) {
- if (host.ipAddresses().contains(dhcpServerIp)) {
- log.warn("DHCP server {} removed", dhcpServerIp);
- dhcpConnectMac = null;
- dhcpConnectVlan = null;
- }
- if (dhcpGatewayIp != null && host.ipAddresses().contains(dhcpGatewayIp)) {
- log.warn("DHCP gateway {} removed", dhcpGatewayIp);
- dhcpConnectMac = null;
- dhcpConnectVlan = null;
- }
- }
-
- private void hostUpdated(Host host) {
- if (dhcpGatewayIp != null) {
- if (host.ipAddresses().contains(dhcpGatewayIp)) {
- dhcpConnectMac = host.mac();
- dhcpConnectVlan = host.vlan();
- log.info("DHCP gateway {} resolved to Mac/Vlan:{}/{}", dhcpGatewayIp,
- dhcpConnectMac, dhcpConnectVlan);
- }
- return;
- }
- if (host.ipAddresses().contains(dhcpServerIp)) {
- dhcpConnectMac = host.mac();
- dhcpConnectVlan = host.vlan();
- log.info("DHCP server {} resolved to Mac/Vlan:{}/{}", dhcpServerIp,
- dhcpConnectMac, dhcpConnectVlan);
- }
- }
-
- /**
- * Request DHCP packet in via PacketService.
- */
- private void requestDhcpPackets() {
- TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
- .matchEthType(Ethernet.TYPE_IPV4)
- .matchIPProtocol(IPv4.PROTOCOL_UDP)
- .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
- packetService.requestPackets(selectorServer.build(), PacketPriority.CONTROL, appId);
-
- TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
- .matchEthType(Ethernet.TYPE_IPV4)
- .matchIPProtocol(IPv4.PROTOCOL_UDP)
- .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
- packetService.requestPackets(selectorClient.build(), PacketPriority.CONTROL, appId);
- }
-
- /**
- * Cancel requested DHCP packets in via packet service.
- */
- private void cancelDhcpPackets() {
- TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
- .matchEthType(Ethernet.TYPE_IPV4)
- .matchIPProtocol(IPv4.PROTOCOL_UDP)
- .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
- packetService.cancelPackets(selectorServer.build(), PacketPriority.CONTROL, appId);
-
- TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
- .matchEthType(Ethernet.TYPE_IPV4)
- .matchIPProtocol(IPv4.PROTOCOL_UDP)
- .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
- packetService.cancelPackets(selectorClient.build(), PacketPriority.CONTROL, appId);
- }
-
- /**
- * Request ARP packet in via PacketService.
- */
- private void requestArpPackets() {
- TrafficSelector.Builder selectorArpServer = DefaultTrafficSelector.builder()
- .matchEthType(Ethernet.TYPE_ARP);
- packetService.requestPackets(selectorArpServer.build(), PacketPriority.CONTROL, appId);
- }
-
- /**
- * Cancel requested ARP packets in via packet service.
- */
- private void cancelArpPackets() {
- TrafficSelector.Builder selectorArpServer = DefaultTrafficSelector.builder()
- .matchEthType(Ethernet.TYPE_ARP);
- packetService.cancelPackets(selectorArpServer.build(), PacketPriority.CONTROL, appId);
- }
-
- private class DhcpRelayPacketProcessor implements PacketProcessor {
-
- @Override
- public void process(PacketContext context) {
- if (!configured()) {
- log.warn("Missing DHCP relay server config. Abort packet processing");
- return;
- }
-
- // process the packet and get the payload
- Ethernet packet = context.inPacket().parsed();
- if (packet == null) {
- return;
- }
-
- if (packet.getEtherType() == Ethernet.TYPE_IPV4) {
- IPv4 ipv4Packet = (IPv4) packet.getPayload();
-
- if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
- UDP udpPacket = (UDP) ipv4Packet.getPayload();
- DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
- if (udpPacket.getSourcePort() == UDP.DHCP_CLIENT_PORT ||
- udpPacket.getSourcePort() == UDP.DHCP_SERVER_PORT) {
- //This packet is dhcp.
- processDhcpPacket(context, dhcpPayload);
- }
- }
- } else if (packet.getEtherType() == Ethernet.TYPE_ARP && arpEnabled) {
- ARP arpPacket = (ARP) packet.getPayload();
- Set<Interface> serverInterfaces = interfaceService.
- getInterfacesByPort(context.inPacket().receivedFrom());
- //ignore the packets if dhcp server interface is not configured on onos.
- if (serverInterfaces.isEmpty()) {
- log.warn("server virtual interface not configured");
- return;
- }
- if ((arpPacket.getOpCode() == ARP.OP_REQUEST) &&
- checkArpRequestFrmDhcpServ(serverInterfaces, arpPacket)) {
- processArpPacket(context, packet);
- }
- }
- }
-
- //method to check the arp request is from dhcp server for default-gateway.
- private boolean checkArpRequestFrmDhcpServ(Set<Interface> serverInterfaces, ARP arpPacket) {
- if (Objects.equals(serverInterfaces.iterator().next().ipAddressesList().get(0).
- ipAddress().getIp4Address(),
- Ip4Address.valueOf(arpPacket.getTargetProtocolAddress()))) {
- return true;
- }
- return false;
- }
-
- //forward the packet to ConnectPoint where the DHCP server is attached.
- private void forwardPacket(Ethernet packet) {
- //send Packetout to dhcp server connectpoint.
- if (dhcpServerConnectPoint != null) {
- TrafficTreatment t = DefaultTrafficTreatment.builder()
- .setOutput(dhcpServerConnectPoint.port()).build();
- OutboundPacket o = new DefaultOutboundPacket(
- dhcpServerConnectPoint.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
- if (log.isTraceEnabled()) {
- log.trace("Relaying packet to dhcp server {}", packet);
- }
- packetService.emit(o);
- }
- }
-
- /**
- * Processes the ARP Payload and initiates a reply to the client.
- *
- * @param context context of the incoming message
- * @param packet the ethernet payload
- */
- private void processArpPacket(PacketContext context, Ethernet packet) {
- ARP arpPacket = (ARP) packet.getPayload();
-
- ARP arpReply = (ARP) arpPacket.clone();
- arpReply.setOpCode(ARP.OP_REPLY);
-
- arpReply.setTargetProtocolAddress(arpPacket.getSenderProtocolAddress());
- arpReply.setTargetHardwareAddress(arpPacket.getSenderHardwareAddress());
- arpReply.setSenderProtocolAddress(arpPacket.getTargetProtocolAddress());
- arpReply.setSenderHardwareAddress(myMAC.toBytes());
-
- // Ethernet Frame.
- Ethernet ethReply = new Ethernet();
- ethReply.setSourceMACAddress(myMAC);
- ethReply.setDestinationMACAddress(packet.getSourceMAC());
- ethReply.setEtherType(Ethernet.TYPE_ARP);
- ethReply.setVlanID(packet.getVlanID());
-
- ethReply.setPayload(arpReply);
- forwardPacket(ethReply);
- }
-
- //process the dhcp packet before sending to server
- private void processDhcpPacket(PacketContext context, DHCP dhcpPayload) {
- ConnectPoint inPort = context.inPacket().receivedFrom();
- Set<Interface> clientServerInterfaces = interfaceService.getInterfacesByPort(inPort);
- //ignore the packets if dhcp client interface is not configured on onos.
- if (clientServerInterfaces.isEmpty()) {
- log.warn("Virtual interface is not configured on {}", inPort);
- return;
- }
-
- if (dhcpPayload == null) {
- return;
- }
-
- Ethernet packet = context.inPacket().parsed();
- DHCP.MsgType incomingPacketType = null;
- for (DhcpOption option : dhcpPayload.getOptions()) {
- if (option.getCode() == OptionCode_MessageType.getValue()) {
- byte[] data = option.getData();
- incomingPacketType = DHCP.MsgType.getType(data[0]);
- }
- }
-
- switch (incomingPacketType) {
- case DHCPDISCOVER:
- // add the gatewayip as virtual interface ip for server to understand
- // the lease to be assigned and forward the packet to dhcp server.
- Ethernet ethernetPacketDiscover =
- processDhcpPacketFromClient(context, packet, clientServerInterfaces);
- if (ethernetPacketDiscover != null) {
- forwardPacket(ethernetPacketDiscover);
- }
- break;
- case DHCPOFFER:
- //reply to dhcp client.
- Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
- if (ethernetPacketOffer != null) {
- sendReply(ethernetPacketOffer, dhcpPayload);
- }
- break;
- case DHCPREQUEST:
- // add the gatewayip as virtual interface ip for server to understand
- // the lease to be assigned and forward the packet to dhcp server.
- Ethernet ethernetPacketRequest =
- processDhcpPacketFromClient(context, packet, clientServerInterfaces);
- if (ethernetPacketRequest != null) {
- forwardPacket(ethernetPacketRequest);
- }
- break;
- case DHCPACK:
- //reply to dhcp client.
- Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
- if (ethernetPacketAck != null) {
- sendReply(ethernetPacketAck, dhcpPayload);
- }
- break;
- default:
- break;
- }
- }
-
- //build the DHCP discover/request packet with gatewayip(unicast packet)
- private Ethernet processDhcpPacketFromClient(PacketContext context,
- Ethernet ethernetPacket, Set<Interface> clientInterfaces) {
- Ip4Address relayAgentIp = getRelayAgentIPv4Address(clientInterfaces);
- MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
- if (relayAgentIp == null || relayAgentMac == null) {
- log.warn("Missing DHCP relay agent interface Ipv4 addr config for "
- + "packet from client on port: {}. Aborting packet processing",
- clientInterfaces.iterator().next().connectPoint());
- return null;
- }
- if (dhcpConnectMac == null) {
- log.warn("DHCP {} not yet resolved .. Aborting DHCP "
- + "packet processing from client on port: {}",
- (dhcpGatewayIp == null) ? "server IP " + dhcpServerIp
- : "gateway IP " + dhcpGatewayIp,
- clientInterfaces.iterator().next().connectPoint());
- return null;
- }
- // get dhcp header.
- Ethernet etherReply = (Ethernet) ethernetPacket.clone();
- etherReply.setSourceMACAddress(relayAgentMac);
- etherReply.setDestinationMACAddress(dhcpConnectMac);
- etherReply.setVlanID(dhcpConnectVlan.toShort());
- IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
- ipv4Packet.setSourceAddress(relayAgentIp.toInt());
- ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
- UDP udpPacket = (UDP) ipv4Packet.getPayload();
- DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
- dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
- udpPacket.setPayload(dhcpPacket);
- ipv4Packet.setPayload(udpPacket);
- etherReply.setPayload(ipv4Packet);
- return etherReply;
- }
-
- // Returns the first v4 interface ip out of a set of interfaces or null.
- // Checks all interfaces, and ignores v6 interface ips
- private Ip4Address getRelayAgentIPv4Address(Set<Interface> intfs) {
- for (Interface intf : intfs) {
- for (InterfaceIpAddress ip : intf.ipAddressesList()) {
- Ip4Address relayAgentIp = ip.ipAddress().getIp4Address();
- if (relayAgentIp != null) {
- return relayAgentIp;
- }
- }
- }
- return null;
- }
-
- //build the DHCP offer/ack with proper client port.
- private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
- // get dhcp header.
- Ethernet etherReply = (Ethernet) ethernetPacket.clone();
- IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
- UDP udpPacket = (UDP) ipv4Packet.getPayload();
- DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
-
- // determine the vlanId of the client host - note that this vlan id
- // could be different from the vlan in the packet from the server
- MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
- Set<Host> hosts = hostService.getHostsByMac(dstMac);
- if (hosts == null || hosts.isEmpty()) {
- log.warn("Cannot determine host for DHCP client: {}. Aborting "
- + "relay for dhcp packet from server {}",
- dhcpPayload.getClientHardwareAddress(), ethernetPacket);
- return null;
- } else if (hosts.size() > 1) {
- // XXX redo to send reply to all hosts found
- log.warn("Multiple hosts found for mac:{}. Picking one "
- + "host out of {}", dstMac, hosts);
- }
- Host host = hosts.iterator().next();
- etherReply.setDestinationMACAddress(dstMac);
- etherReply.setVlanID(host.vlan().toShort());
- // we leave the srcMac from the original packet
-
- // figure out the relay agent IP corresponding to the original request
- Ip4Address relayAgentIP = getRelayAgentIPv4Address(
- interfaceService.getInterfacesByPort(host.location()));
- if (relayAgentIP == null) {
- log.warn("Cannot determine relay agent interface Ipv4 addr for host {}. "
- + "Aborting relay for dhcp packet from server {}",
- host, ethernetPacket);
- return null;
- }
- // SRC_IP: relay agent IP
- // DST_IP: offered IP
- ipv4Packet.setSourceAddress(relayAgentIP.toInt());
- ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
-
- udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
- udpPacket.setPayload(dhcpPayload);
- ipv4Packet.setPayload(udpPacket);
- etherReply.setPayload(ipv4Packet);
- return etherReply;
- }
-
- //send the response to the requester host.
- private void sendReply(Ethernet ethPacket, DHCP dhcpPayload) {
- MacAddress descMac = valueOf(dhcpPayload.getClientHardwareAddress());
- Host host = hostService.getHost(HostId.hostId(descMac,
- VlanId.vlanId(ethPacket.getVlanID())));
-
- // Send packet out to requester if the host information is available
- if (host != null) {
- TrafficTreatment t = DefaultTrafficTreatment.builder()
- .setOutput(host.location().port()).build();
- OutboundPacket o = new DefaultOutboundPacket(
- host.location().deviceId(), t, ByteBuffer.wrap(ethPacket.serialize()));
- if (log.isTraceEnabled()) {
- log.trace("Relaying packet to dhcp client {}", ethPacket);
- }
- packetService.emit(o);
- }
- }
- }
-
- /**
- * Listener for network config events.
- */
- private class InternalConfigListener implements NetworkConfigListener {
-
- @Override
- public void event(NetworkConfigEvent event) {
-
- if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
- event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
- event.configClass().equals(DhcpRelayConfig.class)) {
- updateConfig();
- log.info("Reconfigured");
- }
- }
- }
-
- /**
- * Internal listener for host events.
- */
- private class InternalHostListener implements HostListener {
- @Override
- public void event(HostEvent event) {
- switch (event.type()) {
- case HOST_ADDED:
- case HOST_UPDATED:
- hostUpdated(event.subject());
- break;
- case HOST_REMOVED:
- hostRemoved(event.subject());
- break;
- case HOST_MOVED:
- // XXX todo -- moving dhcp server
- break;
- default:
- break;
- }
- }
- }
-}
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java
new file mode 100644
index 0000000..7a0aec8
--- /dev/null
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java
@@ -0,0 +1,975 @@
+/*
+ * Copyright 2016-present 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.dhcprelay;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+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.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.ARP;
+import org.onlab.packet.DHCP;
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.IPacket;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.dhcp.DhcpOption;
+import org.onlab.packet.dhcp.CircuitId;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.UDP;
+import org.onlab.packet.VlanId;
+import org.onlab.packet.dhcp.DhcpRelayAgentOption;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.dhcprelay.store.DhcpRecord;
+import org.onosproject.dhcprelay.store.DhcpRelayStore;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.incubator.net.routing.Route;
+import org.onosproject.incubator.net.routing.RouteStore;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+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.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.HostStore;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+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.provider.ProviderId;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableSet;
+
+import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_CircuitID;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.packet.dhcp.DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID;
+import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
+import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
+import static org.onlab.packet.MacAddress.valueOf;
+/**
+ * DHCP Relay Agent Application Component.
+ */
+@Component(immediate = true)
+@Service
+public class DhcpRelayManager implements DhcpRelayService {
+ public static final String DHCP_RELAY_APP = "org.onosproject.dhcp-relay";
+ protected static final ProviderId PROVIDER_ID = new ProviderId("dhcp", DHCP_RELAY_APP);
+ public static final String HOST_LOCATION_PROVIDER =
+ "org.onosproject.provider.host.impl.HostLocationProvider";
+ private final Logger log = LoggerFactory.getLogger(getClass());
+ private final InternalConfigListener cfgListener = new InternalConfigListener();
+
+ private final Set<ConfigFactory> factories = ImmutableSet.of(
+ new ConfigFactory<ApplicationId, DhcpRelayConfig>(APP_SUBJECT_FACTORY,
+ DhcpRelayConfig.class,
+ "dhcprelay") {
+ @Override
+ public DhcpRelayConfig createConfig() {
+ return new DhcpRelayConfig();
+ }
+ }
+ );
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NetworkConfigRegistry cfgService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostStore hostStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected RouteStore routeStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InterfaceService interfaceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DhcpRelayStore dhcpRelayStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ComponentConfigService compCfgService;
+
+ @Property(name = "arpEnabled", boolValue = true,
+ label = "Enable Address resolution protocol")
+ protected boolean arpEnabled = true;
+
+ private DhcpRelayPacketProcessor dhcpRelayPacketProcessor = new DhcpRelayPacketProcessor();
+ private InternalHostListener hostListener = new InternalHostListener();
+ private Ip4Address dhcpServerIp = null;
+ // dhcp server may be connected directly to the SDN network or
+ // via an external gateway. When connected directly, the dhcpConnectPoint, dhcpConnectMac,
+ // and dhcpConnectVlan refer to the server. When connected via the gateway, they refer
+ // to the gateway.
+ private ConnectPoint dhcpServerConnectPoint = null;
+ private MacAddress dhcpConnectMac = null;
+ private VlanId dhcpConnectVlan = null;
+ private Ip4Address dhcpGatewayIp = null;
+ private ApplicationId appId;
+
+ @Activate
+ protected void activate(ComponentContext context) {
+ //start the dhcp relay agent
+ appId = coreService.registerApplication(DHCP_RELAY_APP);
+
+ cfgService.addListener(cfgListener);
+ factories.forEach(cfgService::registerConfigFactory);
+ //update the dhcp server configuration.
+ updateConfig();
+ //add the packet services.
+ packetService.addProcessor(dhcpRelayPacketProcessor, PacketProcessor.director(0));
+ hostService.addListener(hostListener);
+ requestDhcpPackets();
+ modified(context);
+
+ // disable dhcp from host location provider
+ compCfgService.preSetProperty(HOST_LOCATION_PROVIDER,
+ "useDhcp", Boolean.FALSE.toString());
+ compCfgService.registerProperties(getClass());
+ log.info("DHCP-RELAY Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ cfgService.removeListener(cfgListener);
+ factories.forEach(cfgService::unregisterConfigFactory);
+ packetService.removeProcessor(dhcpRelayPacketProcessor);
+ hostService.removeListener(hostListener);
+ cancelDhcpPackets();
+ cancelArpPackets();
+ if (dhcpGatewayIp != null) {
+ hostService.stopMonitoringIp(dhcpGatewayIp);
+ } else {
+ hostService.stopMonitoringIp(dhcpServerIp);
+ }
+ compCfgService.unregisterProperties(getClass(), true);
+ log.info("DHCP-RELAY Stopped");
+ }
+
+ @Modified
+ protected void modified(ComponentContext context) {
+ Dictionary<?, ?> properties = context.getProperties();
+ Boolean flag;
+
+ flag = Tools.isPropertyEnabled(properties, "arpEnabled");
+ if (flag != null) {
+ arpEnabled = flag;
+ log.info("Address resolution protocol is {}",
+ arpEnabled ? "enabled" : "disabled");
+ }
+
+ if (arpEnabled) {
+ requestArpPackets();
+ } else {
+ cancelArpPackets();
+ }
+ }
+
+ /**
+ * Checks if this app has been configured.
+ *
+ * @return true if all information we need have been initialized
+ */
+ private boolean configured() {
+ return dhcpServerConnectPoint != null && dhcpServerIp != null;
+ }
+
+ private void updateConfig() {
+ DhcpRelayConfig cfg = cfgService.getConfig(appId, DhcpRelayConfig.class);
+ if (cfg == null) {
+ log.warn("Dhcp Server info not available");
+ return;
+ }
+
+ dhcpServerConnectPoint = cfg.getDhcpServerConnectPoint();
+ Ip4Address oldDhcpServerIp = dhcpServerIp;
+ Ip4Address oldDhcpGatewayIp = dhcpGatewayIp;
+ dhcpServerIp = cfg.getDhcpServerIp();
+ dhcpGatewayIp = cfg.getDhcpGatewayIp();
+ dhcpConnectMac = null; // reset for updated config
+ dhcpConnectVlan = null; // reset for updated config
+ log.info("DHCP server connect point: " + dhcpServerConnectPoint);
+ log.info("DHCP server ipaddress " + dhcpServerIp);
+
+ IpAddress ipToProbe = dhcpGatewayIp != null ? dhcpGatewayIp : dhcpServerIp;
+ String hostToProbe = dhcpGatewayIp != null ? "gateway" : "DHCP server";
+
+ Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
+ if (hosts.isEmpty()) {
+ log.info("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
+ if (oldDhcpGatewayIp != null) {
+ hostService.stopMonitoringIp(oldDhcpGatewayIp);
+ }
+ if (oldDhcpServerIp != null) {
+ hostService.stopMonitoringIp(oldDhcpServerIp);
+ }
+ hostService.startMonitoringIp(ipToProbe);
+ } else {
+ // Probe target is known; There should be only 1 host with this ip
+ hostUpdated(hosts.iterator().next());
+ }
+ }
+
+ private void hostRemoved(Host host) {
+ if (host.ipAddresses().contains(dhcpServerIp)) {
+ log.warn("DHCP server {} removed", dhcpServerIp);
+ dhcpConnectMac = null;
+ dhcpConnectVlan = null;
+ }
+ if (dhcpGatewayIp != null && host.ipAddresses().contains(dhcpGatewayIp)) {
+ log.warn("DHCP gateway {} removed", dhcpGatewayIp);
+ dhcpConnectMac = null;
+ dhcpConnectVlan = null;
+ }
+ }
+
+ private void hostUpdated(Host host) {
+ if (dhcpGatewayIp != null && host.ipAddresses().contains(dhcpGatewayIp)) {
+ dhcpConnectMac = host.mac();
+ dhcpConnectVlan = host.vlan();
+ log.info("DHCP gateway {} resolved to Mac/Vlan:{}/{}", dhcpGatewayIp,
+ dhcpConnectMac, dhcpConnectVlan);
+ } else if (host.ipAddresses().contains(dhcpServerIp)) {
+ dhcpConnectMac = host.mac();
+ dhcpConnectVlan = host.vlan();
+ log.info("DHCP server {} resolved to Mac/Vlan:{}/{}", dhcpServerIp,
+ dhcpConnectMac, dhcpConnectVlan);
+ }
+ }
+
+ /**
+ * Request DHCP packet in via PacketService.
+ */
+ private void requestDhcpPackets() {
+ TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPProtocol(IPv4.PROTOCOL_UDP)
+ .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
+ packetService.requestPackets(selectorServer.build(), PacketPriority.CONTROL, appId);
+
+ TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPProtocol(IPv4.PROTOCOL_UDP)
+ .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
+ packetService.requestPackets(selectorClient.build(), PacketPriority.CONTROL, appId);
+ }
+
+ /**
+ * Cancel requested DHCP packets in via packet service.
+ */
+ private void cancelDhcpPackets() {
+ TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPProtocol(IPv4.PROTOCOL_UDP)
+ .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
+ packetService.cancelPackets(selectorServer.build(), PacketPriority.CONTROL, appId);
+
+ TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPProtocol(IPv4.PROTOCOL_UDP)
+ .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
+ packetService.cancelPackets(selectorClient.build(), PacketPriority.CONTROL, appId);
+ }
+
+ /**
+ * Request ARP packet in via PacketService.
+ */
+ private void requestArpPackets() {
+ TrafficSelector.Builder selectorArpServer = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_ARP);
+ packetService.requestPackets(selectorArpServer.build(), PacketPriority.CONTROL, appId);
+ }
+
+ /**
+ * Cancel requested ARP packets in via packet service.
+ */
+ private void cancelArpPackets() {
+ TrafficSelector.Builder selectorArpServer = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_ARP);
+ packetService.cancelPackets(selectorArpServer.build(), PacketPriority.CONTROL, appId);
+ }
+
+ @Override
+ public Optional<DhcpRecord> getDhcpRecord(HostId hostId) {
+ return dhcpRelayStore.getDhcpRecord(hostId);
+ }
+
+ @Override
+ public Collection<DhcpRecord> getDhcpRecords() {
+ return dhcpRelayStore.getDhcpRecords();
+ }
+
+ /**
+ * Gets output interface of a dhcp packet.
+ * If option 82 exists in the dhcp packet and the option was sent by
+ * ONOS (gateway address exists in ONOS interfaces), use the connect
+ * point and vlan id from circuit id; otherwise, find host by destination
+ * address and use vlan id from sender (dhcp server).
+ *
+ * @param ethPacket the ethernet packet
+ * @param dhcpPayload the dhcp packet
+ * @return an interface represent the output port and vlan; empty value
+ * if the host or circuit id not found
+ */
+ private Optional<Interface> getOutputInterface(Ethernet ethPacket, DHCP dhcpPayload) {
+ VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
+ IpAddress gatewayIpAddress = Ip4Address.valueOf(dhcpPayload.getGatewayIPAddress());
+ Set<Interface> gatewayInterfaces = interfaceService.getInterfacesByIp(gatewayIpAddress);
+ DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
+
+ // Sent by ONOS, and contains circuit id
+ if (!gatewayInterfaces.isEmpty() && option != null) {
+ DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
+ try {
+ CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
+ ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
+ VlanId vlanId = circuitId.vlanId();
+ return Optional.of(new Interface(null, connectPoint, null, null, vlanId));
+ } catch (IllegalArgumentException ex) {
+ // invalid circuit format, didn't sent by ONOS
+ log.debug("Invalid circuit {}, use information from dhcp payload",
+ circuitIdSubOption.getData());
+ }
+ }
+
+ // Use Vlan Id from DHCP server if DHCP relay circuit id was not
+ // sent by ONOS or circuit Id can't be parsed
+ MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
+ Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
+ return dhcpRecord
+ .map(DhcpRecord::locations)
+ .orElse(Collections.emptySet())
+ .stream()
+ .reduce((hl1, hl2) -> {
+ if (hl1 == null || hl2 == null) {
+ return hl1 == null ? hl2 : hl1;
+ }
+ return hl1.time() > hl2.time() ? hl1 : hl2;
+ })
+ .map(lastLocation -> new Interface(null, lastLocation, null, null, originalPacketVlanId));
+ }
+
+ /**
+ * Send the response DHCP to the requester host.
+ *
+ * @param ethPacket the packet
+ * @param dhcpPayload the DHCP data
+ */
+ private void handleDhcpOffer(Ethernet ethPacket, DHCP dhcpPayload) {
+ Optional<Interface> outInterface = getOutputInterface(ethPacket, dhcpPayload);
+ outInterface.ifPresent(theInterface -> {
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(theInterface.connectPoint().port())
+ .build();
+ OutboundPacket o = new DefaultOutboundPacket(
+ theInterface.connectPoint().deviceId(),
+ treatment,
+ ByteBuffer.wrap(ethPacket.serialize()));
+ if (log.isTraceEnabled()) {
+ log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
+ ethPacket,
+ theInterface.connectPoint(),
+ theInterface.vlan());
+ }
+ packetService.emit(o);
+ });
+ }
+
+ /**
+ * Send the DHCP ack to the requester host.
+ *
+ * @param ethernetPacketAck the packet
+ * @param dhcpPayload the DHCP data
+ */
+ private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
+ Optional<Interface> outInterface = getOutputInterface(ethernetPacketAck, dhcpPayload);
+ if (!outInterface.isPresent()) {
+ log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
+ return;
+ }
+
+ Interface outIface = outInterface.get();
+ HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
+ MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
+ VlanId vlanId = outIface.vlan();
+ HostId hostId = HostId.hostId(macAddress, vlanId);
+ Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
+
+ if (directlyConnected(dhcpPayload)) {
+ // Add to host store if it connect to network directly
+ Set<IpAddress> ips = Sets.newHashSet(ip);
+ HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
+ hostLocation, ips);
+
+ // Replace the ip when dhcp server give the host new ip address
+ hostStore.createOrUpdateHost(PROVIDER_ID, hostId, desc, true);
+ } else {
+ // Add to route store if it does not connect to network directly
+ // Get gateway host IP according to host mac address
+ DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
+
+ if (record == null) {
+ log.warn("Can't find DHCP record of host {}", hostId);
+ return;
+ }
+
+ MacAddress gwMac = record.nextHop().orElse(null);
+ if (gwMac == null) {
+ log.warn("Can't find gateway mac address from record {}", record);
+ return;
+ }
+
+ HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
+ Host gwHost = hostService.getHost(gwHostId);
+
+ if (gwHost == null) {
+ log.warn("Can't find gateway host {}", gwHostId);
+ return;
+ }
+
+ Ip4Address nextHopIp = gwHost.ipAddresses()
+ .stream()
+ .filter(IpAddress::isIp4)
+ .map(IpAddress::getIp4Address)
+ .findFirst()
+ .orElse(null);
+
+ if (nextHopIp == null) {
+ log.warn("Can't find IP address of gateway {}", gwHost);
+ return;
+ }
+
+ Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
+ routeStore.updateRoute(route);
+ }
+ handleDhcpOffer(ethernetPacketAck, dhcpPayload);
+ }
+
+ /**
+ * forward the packet to ConnectPoint where the DHCP server is attached.
+ *
+ * @param packet the packet
+ */
+ private void handleDhcpDiscoverAndRequest(Ethernet packet) {
+ // send packet to dhcp server connect point.
+ if (dhcpServerConnectPoint != null) {
+ TrafficTreatment t = DefaultTrafficTreatment.builder()
+ .setOutput(dhcpServerConnectPoint.port()).build();
+ OutboundPacket o = new DefaultOutboundPacket(
+ dhcpServerConnectPoint.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
+ if (log.isTraceEnabled()) {
+ log.trace("Relaying packet to dhcp server {}", packet);
+ }
+ packetService.emit(o);
+ } else {
+ log.warn("Can't find DHCP server connect point, abort.");
+ }
+ }
+
+ /**
+ * Gets DHCP data from a packet.
+ *
+ * @param packet the packet
+ * @return the DHCP data; empty if it is not a DHCP packet
+ */
+ private Optional<DHCP> findDhcp(Ethernet packet) {
+ return Stream.of(packet)
+ .filter(Objects::nonNull)
+ .map(Ethernet::getPayload)
+ .filter(p -> p instanceof IPv4)
+ .map(IPacket::getPayload)
+ .filter(Objects::nonNull)
+ .filter(p -> p instanceof UDP)
+ .map(IPacket::getPayload)
+ .filter(Objects::nonNull)
+ .filter(p -> p instanceof DHCP)
+ .map(p -> (DHCP) p)
+ .findFirst();
+ }
+
+ /**
+ * Gets DHCPv6 data from a packet.
+ *
+ * @param packet the packet
+ * @return the DHCPv6 data; empty if it is not a DHCPv6 packet
+ */
+ private Optional<DHCP6> findDhcp6(Ethernet packet) {
+ return Stream.of(packet)
+ .filter(Objects::nonNull)
+ .map(Ethernet::getPayload)
+ .filter(p -> p instanceof IPv6)
+ .map(IPacket::getPayload)
+ .filter(Objects::nonNull)
+ .filter(p -> p instanceof UDP)
+ .map(IPacket::getPayload)
+ .filter(Objects::nonNull)
+ .filter(p -> p instanceof DHCP6)
+ .map(p -> (DHCP6) p)
+ .findFirst();
+ }
+
+ /**
+ * Check if the host is directly connected to the network or not.
+ *
+ * @param dhcpPayload the dhcp payload
+ * @return true if the host is directly connected to the network; false otherwise
+ */
+ private boolean directlyConnected(DHCP dhcpPayload) {
+ DhcpOption relayAgentOption = dhcpPayload.getOption(OptionCode_CircuitID);
+
+ // Doesn't contains relay option
+ if (relayAgentOption == null) {
+ return true;
+ }
+
+ IpAddress gatewayIp = IpAddress.valueOf(dhcpPayload.getGatewayIPAddress());
+ Set<Interface> gatewayInterfaces = interfaceService.getInterfacesByIp(gatewayIp);
+
+ // Contains relay option, and added by ONOS
+ if (!gatewayInterfaces.isEmpty()) {
+ return true;
+ }
+
+ // Relay option added by other relay agent
+ return false;
+ }
+
+ private class DhcpRelayPacketProcessor implements PacketProcessor {
+
+ @Override
+ public void process(PacketContext context) {
+ if (!configured()) {
+ log.warn("Missing DHCP relay server config. Abort packet processing");
+ return;
+ }
+
+ // process the packet and get the payload
+ Ethernet packet = context.inPacket().parsed();
+ if (packet == null) {
+ return;
+ }
+
+ findDhcp(packet).ifPresent(dhcpPayload -> {
+ processDhcpPacket(context, dhcpPayload);
+ });
+
+ findDhcp6(packet).ifPresent(dhcp6Payload -> {
+ // TODO: handle DHCPv6 packet
+ log.warn("DHCPv6 unsupported.");
+ });
+
+ if (packet.getEtherType() == Ethernet.TYPE_ARP && arpEnabled) {
+ ARP arpPacket = (ARP) packet.getPayload();
+ VlanId vlanId = VlanId.vlanId(packet.getVlanID());
+ Set<Interface> interfaces = interfaceService.
+ getInterfacesByPort(context.inPacket().receivedFrom());
+ //ignore the packets if dhcp server interface is not configured on onos.
+ if (interfaces.isEmpty()) {
+ log.warn("server virtual interface not configured");
+ return;
+ }
+ if ((arpPacket.getOpCode() != ARP.OP_REQUEST)) {
+ // handle request only
+ return;
+ }
+ MacAddress interfaceMac = interfaces.stream()
+ .filter(iface -> iface.vlan().equals(vlanId))
+ .map(Interface::mac)
+ .filter(mac -> !mac.equals(MacAddress.NONE))
+ .findFirst()
+ .orElse(null);
+
+ if (interfaceMac == null) {
+ // can't find interface mac address
+ return;
+ }
+ processArpPacket(context, packet, interfaceMac);
+ }
+ }
+
+ /**
+ * Processes the ARP Payload and initiates a reply to the client.
+ *
+ * @param context the packet context
+ * @param packet the ethernet payload
+ * @param replyMac mac address to be replied
+ */
+ private void processArpPacket(PacketContext context, Ethernet packet, MacAddress replyMac) {
+ ARP arpPacket = (ARP) packet.getPayload();
+ ARP arpReply = (ARP) arpPacket.clone();
+ arpReply.setOpCode(ARP.OP_REPLY);
+
+ arpReply.setTargetProtocolAddress(arpPacket.getSenderProtocolAddress());
+ arpReply.setTargetHardwareAddress(arpPacket.getSenderHardwareAddress());
+ arpReply.setSenderProtocolAddress(arpPacket.getTargetProtocolAddress());
+ arpReply.setSenderHardwareAddress(replyMac.toBytes());
+
+ // Ethernet Frame.
+ Ethernet ethReply = new Ethernet();
+ ethReply.setSourceMACAddress(replyMac.toBytes());
+ ethReply.setDestinationMACAddress(packet.getSourceMAC());
+ ethReply.setEtherType(Ethernet.TYPE_ARP);
+ ethReply.setVlanID(packet.getVlanID());
+ ethReply.setPayload(arpReply);
+
+ ConnectPoint targetPort = context.inPacket().receivedFrom();
+ TrafficTreatment t = DefaultTrafficTreatment.builder()
+ .setOutput(targetPort.port()).build();
+ OutboundPacket o = new DefaultOutboundPacket(
+ targetPort.deviceId(), t, ByteBuffer.wrap(ethReply.serialize()));
+ if (log.isTraceEnabled()) {
+ log.trace("Relaying ARP packet {} to {}", packet, targetPort);
+ }
+ packetService.emit(o);
+ }
+
+ // process the dhcp packet before sending to server
+ private void processDhcpPacket(PacketContext context, DHCP dhcpPayload) {
+ ConnectPoint inPort = context.inPacket().receivedFrom();
+ Set<Interface> clientServerInterfaces = interfaceService.getInterfacesByPort(inPort);
+ // ignore the packets if dhcp client interface is not configured on onos.
+ if (clientServerInterfaces.isEmpty()) {
+ log.warn("Virtual interface is not configured on {}", inPort);
+ return;
+ }
+ checkNotNull(dhcpPayload, "Can't find DHCP payload");
+ Ethernet packet = context.inPacket().parsed();
+ DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
+ .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
+ .map(DhcpOption::getData)
+ .map(data -> DHCP.MsgType.getType(data[0]))
+ .findFirst()
+ .orElse(null);
+ checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
+ switch (incomingPacketType) {
+ case DHCPDISCOVER:
+ // add the gatewayip as virtual interface ip for server to understand
+ // the lease to be assigned and forward the packet to dhcp server.
+ Ethernet ethernetPacketDiscover =
+ processDhcpPacketFromClient(context, packet, clientServerInterfaces);
+
+ if (ethernetPacketDiscover != null) {
+ writeRequestDhcpRecord(inPort, packet, dhcpPayload);
+ handleDhcpDiscoverAndRequest(ethernetPacketDiscover);
+ }
+ break;
+ case DHCPOFFER:
+ //reply to dhcp client.
+ Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
+ if (ethernetPacketOffer != null) {
+ writeResponseDhcpRecord(ethernetPacketOffer, dhcpPayload);
+ handleDhcpOffer(ethernetPacketOffer, dhcpPayload);
+ }
+ break;
+ case DHCPREQUEST:
+ // add the gateway ip as virtual interface ip for server to understand
+ // the lease to be assigned and forward the packet to dhcp server.
+ Ethernet ethernetPacketRequest =
+ processDhcpPacketFromClient(context, packet, clientServerInterfaces);
+ if (ethernetPacketRequest != null) {
+ writeRequestDhcpRecord(inPort, packet, dhcpPayload);
+ handleDhcpDiscoverAndRequest(ethernetPacketRequest);
+ }
+ break;
+ case DHCPACK:
+ // reply to dhcp client.
+ Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
+ if (ethernetPacketAck != null) {
+ writeResponseDhcpRecord(ethernetPacketAck, dhcpPayload);
+ handleDhcpAck(ethernetPacketAck, dhcpPayload);
+ }
+ break;
+ case DHCPRELEASE:
+ // TODO: release the ip address from client
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void writeRequestDhcpRecord(ConnectPoint location,
+ Ethernet ethernet,
+ DHCP dhcpPayload) {
+ VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
+ MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
+ HostId hostId = HostId.hostId(macAddress, vlanId);
+ DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
+ if (record == null) {
+ record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
+ } else {
+ record = record.clone();
+ }
+ record.addLocation(new HostLocation(location, System.currentTimeMillis()));
+ record.ip4Status(dhcpPayload.getPacketType());
+ record.setDirectlyConnected(directlyConnected(dhcpPayload));
+ if (!directlyConnected(dhcpPayload)) {
+ // Update gateway mac address if the host is not directly connected
+ record.nextHop(ethernet.getSourceMAC());
+ }
+ record.updateLastSeen();
+ dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
+ }
+
+ private void writeResponseDhcpRecord(Ethernet ethernet,
+ DHCP dhcpPayload) {
+ Optional<Interface> outInterface = getOutputInterface(ethernet, dhcpPayload);
+ outInterface.ifPresent(outIface -> {
+ ConnectPoint location = outIface.connectPoint();
+ VlanId vlanId = outIface.vlan();
+ MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
+ HostId hostId = HostId.hostId(macAddress, vlanId);
+ DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
+ if (record == null) {
+ record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
+ } else {
+ record = record.clone();
+ }
+ record.addLocation(new HostLocation(location, System.currentTimeMillis()));
+ if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
+ record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
+ }
+ record.ip4Status(dhcpPayload.getPacketType());
+ record.setDirectlyConnected(directlyConnected(dhcpPayload));
+ record.updateLastSeen();
+ dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
+ });
+ }
+
+ //build the DHCP discover/request packet with gatewayip(unicast packet)
+ private Ethernet processDhcpPacketFromClient(PacketContext context,
+ Ethernet ethernetPacket, Set<Interface> clientInterfaces) {
+ Ip4Address relayAgentIp = getRelayAgentIPv4Address(clientInterfaces);
+ MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
+ if (relayAgentIp == null || relayAgentMac == null) {
+ log.warn("Missing DHCP relay agent interface Ipv4 addr config for "
+ + "packet from client on port: {}. Aborting packet processing",
+ clientInterfaces.iterator().next().connectPoint());
+ return null;
+ }
+ if (dhcpConnectMac == null) {
+ log.warn("DHCP {} not yet resolved .. Aborting DHCP "
+ + "packet processing from client on port: {}",
+ (dhcpGatewayIp == null) ? "server IP " + dhcpServerIp
+ : "gateway IP " + dhcpGatewayIp,
+ clientInterfaces.iterator().next().connectPoint());
+ return null;
+ }
+ // get dhcp header.
+ Ethernet etherReply = (Ethernet) ethernetPacket.clone();
+ etherReply.setSourceMACAddress(relayAgentMac);
+ etherReply.setDestinationMACAddress(dhcpConnectMac);
+ etherReply.setVlanID(dhcpConnectVlan.toShort());
+ IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
+ ipv4Packet.setSourceAddress(relayAgentIp.toInt());
+ ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
+ UDP udpPacket = (UDP) ipv4Packet.getPayload();
+ DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
+
+ // If there is no relay agent option(option 82), add one to DHCP payload
+ boolean containsRelayAgentOption = dhcpPacket.getOptions().stream()
+ .map(DhcpOption::getCode)
+ .anyMatch(code -> code == OptionCode_CircuitID.getValue());
+
+ if (!containsRelayAgentOption) {
+ ConnectPoint inPort = context.inPacket().receivedFrom();
+ VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
+ // add connected in port and vlan
+ CircuitId cid = new CircuitId(inPort.toString(), vlanId);
+ byte[] circuitId = cid.serialize();
+
+ if (circuitId != null) {
+ DhcpOption circuitIdSubOpt = new DhcpOption();
+ circuitIdSubOpt
+ .setCode(CIRCUIT_ID.getValue())
+ .setLength((byte) circuitId.length)
+ .setData(circuitId);
+
+ DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
+ newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
+ newRelayAgentOpt.addSubOption(circuitIdSubOpt);
+
+ // push new circuit id to last
+ List<DhcpOption> options = dhcpPacket.getOptions();
+ options.add(newRelayAgentOpt);
+
+ // make sure option 255(End) is the last option
+ options.sort((opt1, opt2) -> opt2.getCode() - opt1.getCode());
+ dhcpPacket.setOptions(options);
+
+ dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
+ } else {
+ log.warn("Can't generate circuit id for port {}, vlan {}", inPort, vlanId);
+ }
+ } else {
+ // TODO: if it contains a relay agent information, sets gateway ip
+ // according to the information it provided
+ }
+
+ udpPacket.setPayload(dhcpPacket);
+ udpPacket.setSourcePort(UDP.DHCP_CLIENT_PORT);
+ udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
+ ipv4Packet.setPayload(udpPacket);
+ etherReply.setPayload(ipv4Packet);
+ return etherReply;
+ }
+
+ // Returns the first v4 interface ip out of a set of interfaces or null.
+ // Checks all interfaces, and ignores v6 interface ips
+ private Ip4Address getRelayAgentIPv4Address(Set<Interface> intfs) {
+ for (Interface intf : intfs) {
+ for (InterfaceIpAddress ip : intf.ipAddressesList()) {
+ Ip4Address relayAgentIp = ip.ipAddress().getIp4Address();
+ if (relayAgentIp != null) {
+ return relayAgentIp;
+ }
+ }
+ }
+ return null;
+ }
+
+ //build the DHCP offer/ack with proper client port.
+ private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
+ // get dhcp header.
+ Ethernet etherReply = (Ethernet) ethernetPacket.clone();
+ IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
+ UDP udpPacket = (UDP) ipv4Packet.getPayload();
+ DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
+
+ // determine the vlanId of the client host - note that this vlan id
+ // could be different from the vlan in the packet from the server
+ Interface outInterface = getOutputInterface(ethernetPacket, dhcpPayload).orElse(null);
+
+ if (outInterface == null) {
+ log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
+ return null;
+ }
+
+ etherReply.setVlanID(outInterface.vlan().toShort());
+ // we leave the srcMac from the original packet
+
+ // figure out the relay agent IP corresponding to the original request
+ Ip4Address relayAgentIP = getRelayAgentIPv4Address(
+ interfaceService.getInterfacesByPort(outInterface.connectPoint()));
+ if (relayAgentIP == null) {
+ log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
+ + "Aborting relay for dhcp packet from server {}",
+ etherReply.getDestinationMAC(), outInterface.vlan(),
+ ethernetPacket);
+ return null;
+ }
+ // SRC_IP: relay agent IP
+ // DST_IP: offered IP
+ ipv4Packet.setSourceAddress(relayAgentIP.toInt());
+ ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
+ udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
+ if (directlyConnected(dhcpPayload)) {
+ udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
+ } else {
+ // forward to another dhcp relay
+ udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
+ }
+
+ udpPacket.setPayload(dhcpPayload);
+ ipv4Packet.setPayload(udpPacket);
+ etherReply.setPayload(ipv4Packet);
+ return etherReply;
+ }
+ }
+
+ /**
+ * Listener for network config events.
+ */
+ private class InternalConfigListener implements NetworkConfigListener {
+ @Override
+ public void event(NetworkConfigEvent event) {
+ if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
+ event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
+ event.configClass().equals(DhcpRelayConfig.class)) {
+ updateConfig();
+ log.info("Reconfigured");
+ }
+ }
+ }
+
+ /**
+ * Internal listener for host events.
+ */
+ private class InternalHostListener implements HostListener {
+ @Override
+ public void event(HostEvent event) {
+ switch (event.type()) {
+ case HOST_ADDED:
+ case HOST_UPDATED:
+ hostUpdated(event.subject());
+ break;
+ case HOST_REMOVED:
+ hostRemoved(event.subject());
+ break;
+ case HOST_MOVED:
+ // XXX todo -- moving dhcp server
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayService.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayService.java
new file mode 100644
index 0000000..7ced882
--- /dev/null
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayService.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017-present 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.dhcprelay;
+
+import org.onosproject.dhcprelay.store.DhcpRecord;
+import org.onosproject.net.HostId;
+
+import java.util.Collection;
+import java.util.Optional;
+
+public interface DhcpRelayService {
+ /**
+ * Gets DHCP record for specific host id (mac + vlan).
+ *
+ * @param hostId the id of host
+ * @return the DHCP record of the host
+ */
+ Optional<DhcpRecord> getDhcpRecord(HostId hostId);
+
+ /**
+ * Gets all DHCP records from store.
+ *
+ * @return all DHCP records from store
+ */
+ Collection<DhcpRecord> getDhcpRecords();
+}
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayStoreEvent.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayStoreEvent.java
index bca3df3..ca1112e 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayStoreEvent.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayStoreEvent.java
@@ -45,7 +45,7 @@
* @param type the type of event
* @param subject the DHCP record of this event
*/
- protected DhcpRelayStoreEvent(Type type, DhcpRecord subject) {
+ public DhcpRelayStoreEvent(Type type, DhcpRecord subject) {
super(type, subject);
}
}
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DistributedDhcpRelayStore.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DistributedDhcpRelayStore.java
index b5f2bf8..e2225f1 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DistributedDhcpRelayStore.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DistributedDhcpRelayStore.java
@@ -21,6 +21,7 @@
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.onlab.packet.DHCP;
import org.onlab.packet.DHCP6;
import org.onlab.util.KryoNamespace;
@@ -43,7 +44,8 @@
/**
* Distributed DHCP relay store.
*/
-@Component
+@Component(immediate = true)
+@Service
public class DistributedDhcpRelayStore implements DhcpRelayStore {
private static final KryoNamespace APP_KRYO = KryoNamespace.newBuilder()
.register(KryoNamespaces.API)
diff --git a/apps/dhcprelay/src/main/resources/dhcp-relay.json b/apps/dhcprelay/src/main/resources/dhcp-relay.json
index 616c4ab..be8b062 100644
--- a/apps/dhcprelay/src/main/resources/dhcp-relay.json
+++ b/apps/dhcprelay/src/main/resources/dhcp-relay.json
@@ -4,7 +4,7 @@
"dhcprelay" : {
"dhcpserverConnectPoint": "of:0000000000000002/2",
"serverip": "172.168.10.2",
- "servermac": "d2:70:98:90:8c:44"
+ "gatewayip": "192.168.10.254"
}
}
}
diff --git a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
new file mode 100644
index 0000000..fe2cbff
--- /dev/null
+++ b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
@@ -0,0 +1,648 @@
+/*
+ * Copyright 2017-present 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.dhcprelay;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.ARP;
+import org.onlab.packet.DHCP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.UDP;
+import org.onlab.packet.VlanId;
+import org.onlab.packet.dhcp.CircuitId;
+import org.onlab.packet.dhcp.DhcpOption;
+import org.onlab.packet.dhcp.DhcpRelayAgentOption;
+import org.onosproject.TestApplicationId;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.dhcprelay.store.DhcpRecord;
+import org.onosproject.dhcprelay.store.DhcpRelayStore;
+import org.onosproject.dhcprelay.store.DhcpRelayStoreEvent;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceServiceAdapter;
+import org.onosproject.incubator.net.routing.Route;
+import org.onosproject.incubator.net.routing.RouteStoreAdapter;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.HostStoreAdapter;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.packet.DefaultInboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketContextAdapter;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketServiceAdapter;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.StoreDelegate;
+import org.osgi.service.component.ComponentContext;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+public class DhcpRelayManagerTest {
+ // Ip address for interfaces
+ private static final InterfaceIpAddress INTERFACE_IP = InterfaceIpAddress.valueOf("10.0.3.254/32");
+ private static final List<InterfaceIpAddress> INTERFACE_IPS = ImmutableList.of(INTERFACE_IP);
+
+ // DHCP client (will send without option 82)
+ private static final Ip4Address IP_FOR_CLIENT = Ip4Address.valueOf("10.0.0.1");
+ private static final MacAddress CLIENT_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+ private static final VlanId CLIENT_VLAN = VlanId.vlanId("100");
+ private static final ConnectPoint CLIENT_CP = ConnectPoint.deviceConnectPoint("of:0000000000000001/1");
+ private static final MacAddress CLIENT_IFACE_MAC = MacAddress.valueOf("00:00:00:00:11:01");
+ private static final Interface CLIENT_INTERFACE = new Interface("C1",
+ CLIENT_CP,
+ INTERFACE_IPS,
+ CLIENT_IFACE_MAC,
+ CLIENT_VLAN);
+
+ // DHCP client 2 (will send with option 82, so the vlan should equals to vlan from server)
+ private static final MacAddress CLIENT2_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+ private static final VlanId CLIENT2_VLAN = VlanId.NONE;
+ private static final ConnectPoint CLIENT2_CP = ConnectPoint.deviceConnectPoint("of:0000000000000001/2");
+ private static final MacAddress CLIENT2_IFACE_MAC = MacAddress.valueOf("00:00:00:00:11:01");
+ private static final Interface CLIENT2_INTERFACE = new Interface("C2",
+ CLIENT2_CP,
+ INTERFACE_IPS,
+ CLIENT2_IFACE_MAC,
+ CLIENT2_VLAN);
+
+ // Outer relay information
+ private static final Ip4Address OUTER_RELAY_IP = Ip4Address.valueOf("10.0.5.253");
+ private static final Set<IpAddress> OUTER_RELAY_IPS = ImmutableSet.of(OUTER_RELAY_IP);
+ private static final MacAddress OUTER_RELAY_MAC = MacAddress.valueOf("00:01:02:03:04:05");
+ private static final VlanId OUTER_RELAY_VLAN = VlanId.NONE;
+ private static final ConnectPoint OUTER_RELAY_CP = ConnectPoint.deviceConnectPoint("of:0000000000000001/2");
+ private static final HostLocation OUTER_REPLAY_HL = new HostLocation(OUTER_RELAY_CP, 0);
+ private static final HostId OUTER_RELAY_HOST_ID = HostId.hostId(OUTER_RELAY_MAC, OUTER_RELAY_VLAN);
+ private static final Host OUTER_RELAY_HOST = new DefaultHost(DhcpRelayManager.PROVIDER_ID,
+ OUTER_RELAY_HOST_ID,
+ OUTER_RELAY_MAC,
+ OUTER_RELAY_VLAN,
+ OUTER_REPLAY_HL,
+ OUTER_RELAY_IPS);
+
+ // DHCP Server
+ private static final MacAddress SERVER_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+ private static final VlanId SERVER_VLAN = VlanId.NONE;
+ private static final ConnectPoint SERVER_CONNECT_POINT =
+ ConnectPoint.deviceConnectPoint("of:0000000000000001/5");
+ private static final HostLocation SERVER_LOCATION =
+ new HostLocation(SERVER_CONNECT_POINT, 0);
+ private static final Ip4Address SERVER_IP = Ip4Address.valueOf("10.0.3.253");
+ private static final Set<IpAddress> DHCP_SERVER_IPS = ImmutableSet.of(SERVER_IP);
+ private static final HostId SERVER_HOST_ID = HostId.hostId(SERVER_MAC, SERVER_VLAN);
+ private static final Host SERVER_HOST = new DefaultHost(DhcpRelayManager.PROVIDER_ID,
+ SERVER_HOST_ID,
+ SERVER_MAC,
+ SERVER_VLAN,
+ SERVER_LOCATION,
+ DHCP_SERVER_IPS);
+ private static final MacAddress SERVER_IFACE_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+ private static final Interface SERVER_INTERFACE = new Interface("SERVER",
+ SERVER_CONNECT_POINT,
+ INTERFACE_IPS,
+ SERVER_IFACE_MAC,
+ SERVER_VLAN);
+
+ // Components
+ private static final ApplicationId APP_ID = TestApplicationId.create(DhcpRelayManager.DHCP_RELAY_APP);
+ private static final DhcpRelayConfig CONFIG = new MockDhcpRelayConfig();
+ private static final Set<Interface> INTERFACES = ImmutableSet.of(
+ CLIENT_INTERFACE,
+ CLIENT2_INTERFACE,
+ SERVER_INTERFACE
+ );
+
+ private DhcpRelayManager manager;
+ private MockPacketService packetService;
+ private MockHostStore mockHostStore;
+ private MockRouteStore mockRouteStore;
+ private MockDhcpRelayStore mockDhcpRelayStore;
+
+ @Before
+ public void setup() {
+ manager = new DhcpRelayManager();
+ manager.cfgService = createNiceMock(NetworkConfigRegistry.class);
+
+ expect(manager.cfgService.getConfig(anyObject(), anyObject()))
+ .andReturn(CONFIG)
+ .anyTimes();
+
+ manager.coreService = createNiceMock(CoreService.class);
+ expect(manager.coreService.registerApplication(anyString()))
+ .andReturn(APP_ID).anyTimes();
+
+ manager.hostService = createNiceMock(HostService.class);
+ expect(manager.hostService.getHostsByIp(anyObject())).andReturn(ImmutableSet.of(SERVER_HOST));
+ expect(manager.hostService.getHost(OUTER_RELAY_HOST_ID)).andReturn(OUTER_RELAY_HOST);
+
+ packetService = new MockPacketService();
+ manager.packetService = packetService;
+ manager.compCfgService = createNiceMock(ComponentConfigService.class);
+
+ mockHostStore = new MockHostStore();
+ mockRouteStore = new MockRouteStore();
+ mockDhcpRelayStore = new MockDhcpRelayStore();
+
+ manager.hostStore = mockHostStore;
+ manager.routeStore = mockRouteStore;
+ manager.dhcpRelayStore = mockDhcpRelayStore;
+
+ manager.interfaceService = new MockInterfaceService();
+
+ // properties
+ Dictionary<String, Object> dictionary = createNiceMock(Dictionary.class);
+ expect(dictionary.get("arpEnabled")).andReturn(true).anyTimes();
+ ComponentContext context = createNiceMock(ComponentContext.class);
+ expect(context.getProperties()).andReturn(dictionary).anyTimes();
+
+ EasyMock.replay(manager.cfgService, manager.coreService, manager.hostService,
+ manager.compCfgService, dictionary, context);
+ manager.activate(context);
+ }
+
+ @After
+ public void tearDown() {
+ manager.deactivate();
+ }
+
+ /**
+ * Relay a DHCP packet without option 82.
+ * Should add new host to host store after dhcp ack.
+ */
+ @Test
+ public void relayDhcpWithoutAgentInfo() {
+ // send request
+ packetService.processPacket(new TestDhcpRequestPacketContext(CLIENT_MAC,
+ CLIENT_VLAN,
+ CLIENT_CP,
+ INTERFACE_IP.ipAddress().getIp4Address(),
+ false));
+
+ Set<Host> hosts = ImmutableSet.copyOf(mockHostStore.getHosts());
+ assertEquals(0, hosts.size());
+ assertEquals(0, mockRouteStore.routes.size());
+
+ // send ack
+ packetService.processPacket(new TestDhcpAckPacketContext(CLIENT_CP, CLIENT_MAC,
+ CLIENT_VLAN, INTERFACE_IP.ipAddress().getIp4Address(),
+ false));
+ hosts = ImmutableSet.copyOf(mockHostStore.getHosts());
+ assertEquals(1, hosts.size());
+ assertEquals(0, mockRouteStore.routes.size());
+
+ Host host = hosts.iterator().next();
+ assertEquals(CLIENT_MAC, host.mac());
+ assertEquals(CLIENT_VLAN, host.vlan());
+ assertEquals(CLIENT_CP.deviceId(), host.location().elementId());
+ assertEquals(CLIENT_CP.port(), host.location().port());
+ assertEquals(1, host.ipAddresses().size());
+ assertEquals(IP_FOR_CLIENT, host.ipAddresses().iterator().next());
+ assertEquals(HostId.hostId(CLIENT_MAC, CLIENT_VLAN), host.id());
+ }
+
+ /**
+ * Relay a DHCP packet with option 82 (Indirectly connected host).
+ */
+ @Test
+ public void relayDhcpWithAgentInfo() {
+ // Assume outer dhcp relay agent exists in store already
+ // send request
+ packetService.processPacket(new TestDhcpRequestPacketContext(CLIENT2_MAC,
+ CLIENT2_VLAN,
+ CLIENT2_CP,
+ INTERFACE_IP.ipAddress().getIp4Address(),
+ true));
+
+ Set<Host> hosts = ImmutableSet.copyOf(mockHostStore.getHosts());
+ assertEquals(0, hosts.size());
+ assertEquals(0, mockRouteStore.routes.size());
+
+ // send ack
+ packetService.processPacket(new TestDhcpAckPacketContext(CLIENT2_CP,
+ CLIENT2_MAC,
+ CLIENT2_VLAN,
+ INTERFACE_IP.ipAddress().getIp4Address(),
+ true));
+
+ hosts = ImmutableSet.copyOf(mockHostStore.getHosts());
+ assertEquals(0, hosts.size());
+ assertEquals(1, mockRouteStore.routes.size());
+
+ Route route = mockRouteStore.routes.get(0);
+ assertEquals(OUTER_RELAY_IP, route.nextHop());
+ assertEquals(IP_FOR_CLIENT.toIpPrefix(), route.prefix());
+ assertEquals(Route.Source.STATIC, route.source());
+ }
+
+ @Test
+ public void testArpRequest() throws Exception {
+ packetService.processPacket(new TestArpRequestPacketContext(CLIENT_INTERFACE));
+ OutboundPacket outboundPacket = packetService.emitedPacket;
+ byte[] outPacketData = outboundPacket.data().array();
+ Ethernet eth = Ethernet.deserializer().deserialize(outPacketData, 0, outPacketData.length);
+
+ assertEquals(eth.getEtherType(), Ethernet.TYPE_ARP);
+ ARP arp = (ARP) eth.getPayload();
+ assertArrayEquals(arp.getSenderHardwareAddress(), CLIENT_INTERFACE.mac().toBytes());
+ }
+
+ private static class MockDhcpRelayConfig extends DhcpRelayConfig {
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public ConnectPoint getDhcpServerConnectPoint() {
+ return SERVER_CONNECT_POINT;
+ }
+
+ public Ip4Address getDhcpServerIp() {
+ return SERVER_IP;
+ }
+
+ public Ip4Address getDhcpGatewayIp() {
+ return null;
+ }
+ }
+
+ private class MockHostStore extends HostStoreAdapter {
+
+ private final Map<HostId, HostDescription> hosts = Maps.newHashMap();
+
+ @Override
+ public HostEvent createOrUpdateHost(ProviderId providerId, HostId hostId,
+ HostDescription hostDescription,
+ boolean replaceIps) {
+ hosts.put(hostId, hostDescription);
+
+ // not necessary to return host event in this test.
+ return null;
+ }
+
+ public HostDescription hostDesc(HostId hostId) {
+ return hosts.get(hostId);
+ }
+
+ @Override
+ public Iterable<Host> getHosts() {
+ return hosts.values().stream()
+ .map(hd -> new DefaultHost(DhcpRelayManager.PROVIDER_ID,
+ HostId.hostId(hd.hwAddress(), hd.vlan()),
+ hd.hwAddress(),
+ hd.vlan(), hd.locations(),
+ hd.ipAddress(), false))
+ .collect(Collectors.toList());
+ }
+ }
+
+ private class MockRouteStore extends RouteStoreAdapter {
+ private List<Route> routes = Lists.newArrayList();
+
+ @Override
+ public void updateRoute(Route route) {
+ routes.add(route);
+ }
+ }
+
+ private class MockInterfaceService extends InterfaceServiceAdapter {
+
+ @Override
+ public Set<Interface> getInterfaces() {
+ return INTERFACES;
+ }
+
+ @Override
+ public Set<Interface> getInterfacesByIp(IpAddress ip) {
+ return INTERFACES.stream()
+ .filter(iface -> {
+ return iface.ipAddressesList().stream()
+ .anyMatch(ifaceIp -> ifaceIp.ipAddress().equals(ip));
+ })
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<Interface> getInterfacesByPort(ConnectPoint port) {
+ return INTERFACES.stream()
+ .filter(iface -> iface.connectPoint().equals(port))
+ .collect(Collectors.toSet());
+ }
+ }
+
+ private class MockDhcpRelayStore implements DhcpRelayStore {
+ StoreDelegate<DhcpRelayStoreEvent> delegate;
+ private Map<HostId, DhcpRecord> records = Maps.newHashMap();
+
+ @Override
+ public void updateDhcpRecord(HostId hostId, DhcpRecord dhcpRecord) {
+ records.put(hostId, dhcpRecord);
+ DhcpRelayStoreEvent event = new DhcpRelayStoreEvent(DhcpRelayStoreEvent.Type.UPDATED,
+ dhcpRecord);
+ if (delegate != null) {
+ delegate.notify(event);
+ }
+ }
+
+ @Override
+ public Optional<DhcpRecord> getDhcpRecord(HostId hostId) {
+ return Optional.ofNullable(records.get(hostId));
+ }
+
+ @Override
+ public Collection<DhcpRecord> getDhcpRecords() {
+ return records.values();
+ }
+
+ @Override
+ public Optional<DhcpRecord> removeDhcpRecord(HostId hostId) {
+ DhcpRecord dhcpRecord = records.remove(hostId);
+ if (dhcpRecord != null) {
+ DhcpRelayStoreEvent event = new DhcpRelayStoreEvent(DhcpRelayStoreEvent.Type.REMOVED,
+ dhcpRecord);
+ if (delegate != null) {
+ delegate.notify(event);
+ }
+ }
+ return Optional.ofNullable(dhcpRecord);
+ }
+
+ @Override
+ public void setDelegate(StoreDelegate<DhcpRelayStoreEvent> delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void unsetDelegate(StoreDelegate<DhcpRelayStoreEvent> delegate) {
+ this.delegate = null;
+ }
+
+ @Override
+ public boolean hasDelegate() {
+ return this.delegate != null;
+ }
+ }
+
+ private class MockPacketService extends PacketServiceAdapter {
+ Set<PacketProcessor> packetProcessors = Sets.newHashSet();
+ OutboundPacket emitedPacket;
+
+ @Override
+ public void addProcessor(PacketProcessor processor, int priority) {
+ packetProcessors.add(processor);
+ }
+
+ public void processPacket(PacketContext packetContext) {
+ packetProcessors.forEach(p -> p.process(packetContext));
+ }
+
+ @Override
+ public void emit(OutboundPacket packet) {
+ this.emitedPacket = packet;
+ }
+ }
+
+
+
+ /**
+ * Generates DHCP REQUEST packet.
+ */
+ private class TestDhcpRequestPacketContext extends PacketContextAdapter {
+
+
+ private InboundPacket inPacket;
+
+ public TestDhcpRequestPacketContext(MacAddress clientMac, VlanId vlanId,
+ ConnectPoint clientCp,
+ Ip4Address clientGwAddr,
+ boolean withNonOnosRelayInfo) {
+ super(0, null, null, false);
+ byte[] dhcpMsgType = new byte[1];
+ dhcpMsgType[0] = (byte) DHCP.MsgType.DHCPREQUEST.getValue();
+
+ DhcpOption dhcpOption = new DhcpOption();
+ dhcpOption.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
+ dhcpOption.setData(dhcpMsgType);
+ dhcpOption.setLength((byte) 1);
+ DhcpOption endOption = new DhcpOption();
+ endOption.setCode(DHCP.DHCPOptionCode.OptionCode_END.getValue());
+
+ DHCP dhcp = new DHCP();
+ dhcp.setHardwareType(DHCP.HWTYPE_ETHERNET);
+ dhcp.setHardwareAddressLength((byte) 6);
+ dhcp.setClientHardwareAddress(clientMac.toBytes());
+ if (withNonOnosRelayInfo) {
+ DhcpRelayAgentOption relayOption = new DhcpRelayAgentOption();
+ DhcpOption circuitIdOption = new DhcpOption();
+ CircuitId circuitId = new CircuitId("Custom option", VlanId.NONE);
+ byte[] cid = circuitId.serialize();
+ circuitIdOption.setCode(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
+ circuitIdOption.setLength((byte) cid.length);
+ circuitIdOption.setData(cid);
+ relayOption.setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue());
+ relayOption.addSubOption(circuitIdOption);
+ dhcp.setOptions(ImmutableList.of(dhcpOption, relayOption, endOption));
+ dhcp.setGatewayIPAddress(OUTER_RELAY_IP.getIp4Address().toInt());
+ } else {
+ dhcp.setOptions(ImmutableList.of(dhcpOption, endOption));
+ }
+
+
+ UDP udp = new UDP();
+ udp.setPayload(dhcp);
+ udp.setSourcePort(UDP.DHCP_CLIENT_PORT);
+ udp.setDestinationPort(UDP.DHCP_SERVER_PORT);
+
+ IPv4 ipv4 = new IPv4();
+ ipv4.setPayload(udp);
+ ipv4.setDestinationAddress(SERVER_IP.toInt());
+ ipv4.setSourceAddress(clientGwAddr.toInt());
+
+ Ethernet eth = new Ethernet();
+ if (withNonOnosRelayInfo) {
+ eth.setEtherType(Ethernet.TYPE_IPV4)
+ .setVlanID(vlanId.toShort())
+ .setSourceMACAddress(OUTER_RELAY_MAC)
+ .setDestinationMACAddress(MacAddress.BROADCAST)
+ .setPayload(ipv4);
+ } else {
+ eth.setEtherType(Ethernet.TYPE_IPV4)
+ .setVlanID(vlanId.toShort())
+ .setSourceMACAddress(clientMac)
+ .setDestinationMACAddress(MacAddress.BROADCAST)
+ .setPayload(ipv4);
+ }
+
+ this.inPacket = new DefaultInboundPacket(clientCp, eth,
+ ByteBuffer.wrap(eth.serialize()));
+ }
+
+ @Override
+ public InboundPacket inPacket() {
+ return this.inPacket;
+ }
+ }
+
+ /**
+ * Generates DHCP ACK packet.
+ */
+ private class TestDhcpAckPacketContext extends PacketContextAdapter {
+ private InboundPacket inPacket;
+
+ public TestDhcpAckPacketContext(ConnectPoint clientCp, MacAddress clientMac,
+ VlanId clientVlan, Ip4Address clientGwAddr,
+ boolean withNonOnosRelayInfo) {
+ super(0, null, null, false);
+
+ byte[] dhcpMsgType = new byte[1];
+ dhcpMsgType[0] = (byte) DHCP.MsgType.DHCPACK.getValue();
+
+ DhcpOption dhcpOption = new DhcpOption();
+ dhcpOption.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
+ dhcpOption.setData(dhcpMsgType);
+ dhcpOption.setLength((byte) 1);
+
+ DhcpOption endOption = new DhcpOption();
+ endOption.setCode(DHCP.DHCPOptionCode.OptionCode_END.getValue());
+
+ DHCP dhcp = new DHCP();
+ if (withNonOnosRelayInfo) {
+ DhcpRelayAgentOption relayOption = new DhcpRelayAgentOption();
+ DhcpOption circuitIdOption = new DhcpOption();
+ CircuitId circuitId = new CircuitId("Custom cid", VlanId.NONE);
+ byte[] cid = circuitId.serialize();
+ circuitIdOption.setCode(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
+ circuitIdOption.setLength((byte) cid.length);
+ circuitIdOption.setData(cid);
+ relayOption.setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue());
+ relayOption.addSubOption(circuitIdOption);
+ dhcp.setOptions(ImmutableList.of(dhcpOption, relayOption, endOption));
+ dhcp.setGatewayIPAddress(OUTER_RELAY_IP.getIp4Address().toInt());
+ } else {
+ CircuitId cid = new CircuitId(clientCp.toString(), clientVlan);
+ byte[] circuitId = cid.serialize();
+ DhcpOption circuitIdSubOption = new DhcpOption();
+ circuitIdSubOption.setCode(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
+ circuitIdSubOption.setData(circuitId);
+ circuitIdSubOption.setLength((byte) circuitId.length);
+
+ DhcpRelayAgentOption relayInfoOption = new DhcpRelayAgentOption();
+ relayInfoOption.setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue());
+ relayInfoOption.addSubOption(circuitIdSubOption);
+ dhcp.setOptions(ImmutableList.of(dhcpOption, relayInfoOption, endOption));
+ dhcp.setGatewayIPAddress(clientGwAddr.toInt());
+ }
+ dhcp.setHardwareType(DHCP.HWTYPE_ETHERNET);
+ dhcp.setHardwareAddressLength((byte) 6);
+ dhcp.setClientHardwareAddress(clientMac.toBytes());
+ dhcp.setYourIPAddress(IP_FOR_CLIENT.toInt());
+
+ UDP udp = new UDP();
+ udp.setPayload(dhcp);
+ udp.setSourcePort(UDP.DHCP_SERVER_PORT);
+ udp.setDestinationPort(UDP.DHCP_CLIENT_PORT);
+ IPv4 ipv4 = new IPv4();
+ ipv4.setPayload(udp);
+ ipv4.setDestinationAddress(IP_FOR_CLIENT.toString());
+ ipv4.setSourceAddress(SERVER_IP.toString());
+ Ethernet eth = new Ethernet();
+ if (withNonOnosRelayInfo) {
+ eth.setEtherType(Ethernet.TYPE_IPV4)
+ .setVlanID(SERVER_VLAN.toShort())
+ .setSourceMACAddress(SERVER_MAC)
+ .setDestinationMACAddress(OUTER_RELAY_MAC)
+ .setPayload(ipv4);
+ } else {
+ eth.setEtherType(Ethernet.TYPE_IPV4)
+ .setVlanID(SERVER_VLAN.toShort())
+ .setSourceMACAddress(SERVER_MAC)
+ .setDestinationMACAddress(CLIENT_MAC)
+ .setPayload(ipv4);
+ }
+
+ this.inPacket = new DefaultInboundPacket(SERVER_CONNECT_POINT, eth,
+ ByteBuffer.wrap(eth.serialize()));
+
+ }
+
+ @Override
+ public InboundPacket inPacket() {
+ return this.inPacket;
+ }
+ }
+
+ private class TestArpRequestPacketContext extends PacketContextAdapter {
+ private InboundPacket inPacket;
+
+ public TestArpRequestPacketContext(Interface fromInterface) {
+ super(0, null, null, false);
+ ARP arp = new ARP();
+ arp.setOpCode(ARP.OP_REQUEST);
+
+ IpAddress targetIp = fromInterface.ipAddressesList().get(0).ipAddress();
+ arp.setTargetProtocolAddress(targetIp.toOctets());
+ arp.setTargetHardwareAddress(MacAddress.BROADCAST.toBytes());
+ arp.setSenderHardwareAddress(MacAddress.NONE.toBytes());
+ arp.setSenderProtocolAddress(Ip4Address.valueOf(0).toOctets());
+ arp.setHardwareAddressLength((byte) MacAddress.MAC_ADDRESS_LENGTH);
+ Ethernet eth = new Ethernet();
+ eth.setEtherType(Ethernet.TYPE_ARP);
+ eth.setSourceMACAddress(MacAddress.NONE);
+ eth.setDestinationMACAddress(MacAddress.BROADCAST);
+ eth.setVlanID(fromInterface.vlan().toShort());
+ eth.setPayload(arp);
+
+ this.inPacket = new DefaultInboundPacket(fromInterface.connectPoint(), eth,
+ ByteBuffer.wrap(eth.serialize()));
+ }
+
+ @Override
+ public InboundPacket inPacket() {
+ return this.inPacket;
+ }
+ }
+}
diff --git a/core/api/src/test/java/org/onosproject/net/host/HostStoreAdapter.java b/core/api/src/test/java/org/onosproject/net/host/HostStoreAdapter.java
new file mode 100644
index 0000000..796e6d6
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/net/host/HostStoreAdapter.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2017-present 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.net.host;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.provider.ProviderId;
+
+import java.util.Set;
+
+/**
+ * Test adapter for host store.
+ */
+public class HostStoreAdapter implements HostStore {
+ @Override
+ public void setDelegate(HostStoreDelegate delegate) {
+
+ }
+
+ @Override
+ public void unsetDelegate(HostStoreDelegate delegate) {
+
+ }
+
+ @Override
+ public boolean hasDelegate() {
+ return false;
+ }
+
+ @Override
+ public HostEvent createOrUpdateHost(ProviderId providerId,
+ HostId hostId,
+ HostDescription hostDescription,
+ boolean replaceIps) {
+ return null;
+ }
+
+ @Override
+ public HostEvent removeHost(HostId hostId) {
+ return null;
+ }
+
+ @Override
+ public HostEvent removeIp(HostId hostId, IpAddress ipAddress) {
+ return null;
+ }
+
+ @Override
+ public void removeLocation(HostId hostId, HostLocation location) {
+
+ }
+
+ @Override
+ public int getHostCount() {
+ return 0;
+ }
+
+ @Override
+ public Iterable<Host> getHosts() {
+ return null;
+ }
+
+ @Override
+ public Host getHost(HostId hostId) {
+ return null;
+ }
+
+ @Override
+ public Set<Host> getHosts(VlanId vlanId) {
+ return null;
+ }
+
+ @Override
+ public Set<Host> getHosts(MacAddress mac) {
+ return null;
+ }
+
+ @Override
+ public Set<Host> getHosts(IpAddress ip) {
+ return null;
+ }
+
+ @Override
+ public Set<Host> getConnectedHosts(ConnectPoint connectPoint) {
+ return null;
+ }
+
+ @Override
+ public Set<Host> getConnectedHosts(DeviceId deviceId) {
+ return null;
+ }
+}
diff --git a/incubator/api/src/test/java/org/onosproject/incubator/net/routing/RouteStoreAdapter.java b/incubator/api/src/test/java/org/onosproject/incubator/net/routing/RouteStoreAdapter.java
new file mode 100644
index 0000000..c5250d7
--- /dev/null
+++ b/incubator/api/src/test/java/org/onosproject/incubator/net/routing/RouteStoreAdapter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017-present 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.incubator.net.routing;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Adapter class for the route store.
+ */
+public class RouteStoreAdapter implements RouteStore {
+ @Override
+ public void updateRoute(Route route) {
+
+ }
+
+ @Override
+ public void removeRoute(Route route) {
+
+ }
+
+ @Override
+ public Set<RouteTableId> getRouteTables() {
+ return null;
+ }
+
+ @Override
+ public Collection<RouteSet> getRoutes(RouteTableId table) {
+ return null;
+ }
+
+ @Override
+ public Collection<Route> getRoutesForNextHop(IpAddress ip) {
+ return null;
+ }
+
+ @Override
+ public RouteSet getRoutes(IpPrefix prefix) {
+ return null;
+ }
+
+ @Override
+ public void setDelegate(RouteStoreDelegate delegate) {
+
+ }
+
+ @Override
+ public void unsetDelegate(RouteStoreDelegate delegate) {
+
+ }
+
+ @Override
+ public boolean hasDelegate() {
+ return false;
+ }
+}
diff --git a/providers/host/BUCK b/providers/host/BUCK
index f5cf0c3..2cfed29 100644
--- a/providers/host/BUCK
+++ b/providers/host/BUCK
@@ -1,5 +1,6 @@
COMPILE_DEPS = [
'//lib:CORE_DEPS',
+ '//incubator/api:onos-incubator-api',
]
TEST_DEPS = [
diff --git a/providers/host/pom.xml b/providers/host/pom.xml
index 7ead81d..f6016ca 100644
--- a/providers/host/pom.xml
+++ b/providers/host/pom.xml
@@ -64,6 +64,9 @@
<artifactId>easymock</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-incubator-api</artifactId>
+ </dependency>
</dependencies>
-
</project>
diff --git a/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java b/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
index 996bd43..e197a0d 100644
--- a/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
+++ b/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
@@ -43,6 +43,7 @@
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.intf.InterfaceService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
@@ -77,7 +78,10 @@
import java.nio.ByteBuffer;
import java.util.Dictionary;
+import java.util.Objects;
+import java.util.Optional;
import java.util.concurrent.ExecutorService;
+import java.util.stream.Stream;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static org.onlab.util.Tools.groupedThreads;
@@ -89,7 +93,6 @@
*/
@Component(immediate = true)
public class HostLocationProvider extends AbstractProvider implements HostProvider {
-
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -113,6 +116,9 @@
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ComponentConfigService cfgService;
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InterfaceService interfaceService;
+
private HostProviderService providerService;
private final InternalHostProvider processor = new InternalHostProvider();
@@ -124,18 +130,18 @@
label = "Enable host removal on port/device down events")
private boolean hostRemovalEnabled = true;
- @Property(name = "useArp", boolValue = true,
- label = "Enable using ARP for neighbor discovery by the " +
+ @Property(name = "requestArp", boolValue = true,
+ label = "Request ARP packets for neighbor discovery by the " +
"Host Location Provider; default is true")
- private boolean useArp = true;
+ private boolean requestArp = true;
- @Property(name = "useIpv6ND", boolValue = false,
- label = "Enable using IPv6 Neighbor Discovery by the " +
+ @Property(name = "requestIpv6ND", boolValue = false,
+ label = "Requests IPv6 Neighbor Discovery by the " +
"Host Location Provider; default is false")
- private boolean useIpv6ND = false;
+ private boolean requestIpv6ND = false;
@Property(name = "useDhcp", boolValue = false,
- label = "Enable using DHCP for neighbor discovery by the " +
+ label = "Use DHCP for neighbor discovery by the " +
"Host Location Provider; default is false")
private boolean useDhcp = false;
@@ -202,7 +208,7 @@
TrafficSelector arpSelector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_ARP)
.build();
- if (useArp) {
+ if (requestArp) {
packetService.requestPackets(arpSelector, PacketPriority.CONTROL, appId);
} else {
packetService.cancelPackets(arpSelector, PacketPriority.CONTROL, appId);
@@ -219,7 +225,7 @@
.matchIPProtocol(IPv6.PROTOCOL_ICMP6)
.matchIcmpv6Type(ICMP6.NEIGHBOR_ADVERTISEMENT)
.build();
- if (useIpv6ND) {
+ if (requestIpv6ND) {
packetService.requestPackets(ipv6NsSelector, PacketPriority.CONTROL, appId);
packetService.requestPackets(ipv6NaSelector, PacketPriority.CONTROL, appId);
} else {
@@ -238,13 +244,6 @@
.matchIPProtocol(IPv4.PROTOCOL_UDP)
.matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
.build();
- if (useDhcp) {
- packetService.requestPackets(dhcpServerSelector, PacketPriority.CONTROL, appId);
- packetService.requestPackets(dhcpClientSelector, PacketPriority.CONTROL, appId);
- } else {
- packetService.cancelPackets(dhcpServerSelector, PacketPriority.CONTROL, appId);
- packetService.cancelPackets(dhcpClientSelector, PacketPriority.CONTROL, appId);
- }
}
/**
@@ -278,51 +277,51 @@
flag = Tools.isPropertyEnabled(properties, "hostRemovalEnabled");
if (flag == null) {
log.info("Host removal on port/device down events is not configured, " +
- "using current value of {}", hostRemovalEnabled);
+ "using current value of {}", hostRemovalEnabled);
} else {
hostRemovalEnabled = flag;
log.info("Configured. Host removal on port/device down events is {}",
hostRemovalEnabled ? "enabled" : "disabled");
}
- flag = Tools.isPropertyEnabled(properties, "useArp");
+ flag = Tools.isPropertyEnabled(properties, "requestArp");
if (flag == null) {
log.info("Using ARP is not configured, " +
- "using current value of {}", useArp);
+ "using current value of {}", requestArp);
} else {
- useArp = flag;
+ requestArp = flag;
log.info("Configured. Using ARP is {}",
- useArp ? "enabled" : "disabled");
+ requestArp ? "enabled" : "disabled");
}
- flag = Tools.isPropertyEnabled(properties, "useIpv6ND");
+ flag = Tools.isPropertyEnabled(properties, "requestIpv6ND");
if (flag == null) {
log.info("Using IPv6 Neighbor Discovery is not configured, " +
- "using current value of {}", useIpv6ND);
+ "using current value of {}", requestIpv6ND);
} else {
- useIpv6ND = flag;
+ requestIpv6ND = flag;
log.info("Configured. Using IPv6 Neighbor Discovery is {}",
- useIpv6ND ? "enabled" : "disabled");
+ requestIpv6ND ? "enabled" : "disabled");
}
flag = Tools.isPropertyEnabled(properties, "useDhcp");
if (flag == null) {
log.info("Using DHCP is not configured, " +
- "using current value of {}", useDhcp);
+ "using current value of {}", useDhcp);
} else {
useDhcp = flag;
log.info("Configured. Using DHCP is {}",
- useDhcp ? "enabled" : "disabled");
+ useDhcp ? "enabled" : "disabled");
}
flag = Tools.isPropertyEnabled(properties, "requestInterceptsEnabled");
if (flag == null) {
log.info("Request intercepts is not configured, " +
- "using current value of {}", requestInterceptsEnabled);
+ "using current value of {}", requestInterceptsEnabled);
} else {
requestInterceptsEnabled = flag;
log.info("Configured. Request intercepts is {}",
- requestInterceptsEnabled ? "enabled" : "disabled");
+ requestInterceptsEnabled ? "enabled" : "disabled");
}
}
@@ -475,30 +474,12 @@
createOrUpdateHost(hid, srcMac, vlan, hloc, ip);
// IPv4: update location only
- // DHCP ACK: additionally update IP of DHCP client
} else if (eth.getEtherType() == Ethernet.TYPE_IPV4) {
- IPacket pkt = eth.getPayload();
- if (pkt != null && pkt instanceof IPv4) {
- pkt = pkt.getPayload();
- if (pkt != null && pkt instanceof UDP) {
- pkt = pkt.getPayload();
- if (pkt != null && pkt instanceof DHCP) {
- DHCP dhcp = (DHCP) pkt;
- if (dhcp.getOptions().stream()
- .anyMatch(dhcpOption -> dhcpOption.getCode() ==
- DHCP.DHCPOptionCode.OptionCode_MessageType.getValue() &&
- dhcpOption.getLength() == 1 &&
- dhcpOption.getData()[0] == DHCP.MsgType.DHCPACK.getValue())) {
- MacAddress hostMac = MacAddress.valueOf(dhcp.getClientHardwareAddress());
- VlanId hostVlan = VlanId.vlanId(eth.getVlanID());
- HostId hostId = HostId.hostId(hostMac, hostVlan);
- updateHostIp(hostId, IpAddress.valueOf(dhcp.getYourIPAddress()));
- }
- }
- }
+ // DHCP ACK: additionally update IP of DHCP client
+ Optional<DHCP> dhcp = findDhcp(eth);
+ if (useDhcp || !dhcp.isPresent()) {
+ createOrUpdateHost(hid, srcMac, vlan, hloc, null);
}
- createOrUpdateHost(hid, srcMac, vlan, hloc, null);
-
//
// NeighborAdvertisement and NeighborSolicitation: possible
// new hosts, update both location and IP.
@@ -546,6 +527,21 @@
createOrUpdateHost(hid, srcMac, vlan, hloc, null);
}
}
+
+ private Optional<DHCP> findDhcp(Ethernet eth) {
+ IPacket pkt = eth.getPayload();
+ return Stream.of(pkt)
+ .filter(Objects::nonNull)
+ .filter(p -> p instanceof IPv4)
+ .map(IPacket::getPayload)
+ .filter(Objects::nonNull)
+ .filter(p -> p instanceof UDP)
+ .map(IPacket::getPayload)
+ .filter(Objects::nonNull)
+ .filter(p -> p instanceof DHCP)
+ .map(p -> (DHCP) p)
+ .findFirst();
+ }
}
// Auxiliary listener to device events.
diff --git a/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java b/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
index b28ec17..080e0e4 100644
--- a/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
+++ b/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
@@ -15,16 +15,19 @@
*/
package org.onosproject.provider.host.impl;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.onlab.junit.TestUtils;
import org.onlab.osgi.ComponentContextAdapter;
import org.onlab.packet.ARP;
import org.onlab.packet.ChassisId;
import org.onlab.packet.DHCP;
+import org.onlab.packet.dhcp.CircuitId;
import org.onlab.packet.dhcp.DhcpOption;
import org.onlab.packet.Ethernet;
import org.onlab.packet.ICMP6;
@@ -35,6 +38,7 @@
import org.onlab.packet.MacAddress;
import org.onlab.packet.UDP;
import org.onlab.packet.VlanId;
+import org.onlab.packet.dhcp.DhcpRelayAgentOption;
import org.onlab.packet.ndp.NeighborAdvertisement;
import org.onlab.packet.ndp.NeighborSolicitation;
import org.onlab.packet.ndp.RouterAdvertisement;
@@ -43,6 +47,9 @@
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceListener;
+import org.onosproject.incubator.net.intf.InterfaceService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultDevice;
import org.onosproject.net.DefaultHost;
@@ -60,6 +67,7 @@
import org.onosproject.net.host.HostProviderRegistry;
import org.onosproject.net.host.HostProviderService;
import org.onosproject.net.host.HostServiceAdapter;
+import org.onosproject.net.host.InterfaceIpAddress;
import org.onosproject.net.packet.DefaultInboundPacket;
import org.onosproject.net.packet.InboundPacket;
import org.onosproject.net.packet.PacketContextAdapter;
@@ -85,7 +93,6 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
import static org.onlab.packet.VlanId.vlanId;
import static org.onosproject.net.Device.Type.SWITCH;
import static org.onosproject.net.DeviceId.deviceId;
@@ -109,6 +116,7 @@
private static final String DEV6 = "of:6";
private static final VlanId VLAN = vlanId();
+ private static final VlanId VLAN_100 = VlanId.vlanId("100");
// IPv4 Host
private static final MacAddress MAC = MacAddress.valueOf("00:00:11:00:00:01");
@@ -122,6 +130,10 @@
new DefaultHost(PROVIDER_ID, hostId(MAC), MAC,
VLAN, LOCATION,
ImmutableSet.of(IP_ADDRESS));
+ private static final DefaultHost HOST_VLAN_100 =
+ new DefaultHost(PROVIDER_ID, hostId(MAC, VLAN_100), MAC,
+ VLAN_100, LOCATION,
+ ImmutableSet.of(IP_ADDRESS));
// IPv6 Host
private static final MacAddress MAC2 = MacAddress.valueOf("00:00:22:00:00:02");
@@ -149,6 +161,14 @@
VLAN, LOCATION3,
ImmutableSet.of(IP_ADDRESS3));
+ // Gateway information for relay agent
+ private static final InterfaceIpAddress GW_IFACE_ADDR = InterfaceIpAddress.valueOf("10.0.1.1/32");
+ private static final Interface GW_IFACE = new Interface("gateway",
+ LOCATION,
+ ImmutableList.of(GW_IFACE_ADDR),
+ null,
+ VLAN_100);
+
private static final ComponentContextAdapter CTX_FOR_REMOVE =
new ComponentContextAdapter() {
@Override
@@ -173,6 +193,7 @@
private final TestDeviceService deviceService = new TestDeviceService();
private final TestHostService hostService = new TestHostService();
private final TestPacketService packetService = new TestPacketService();
+ private final TestInterfaceService interfaceService = new TestInterfaceService();
private PacketProcessor testProcessor;
private CoreService coreService;
@@ -183,7 +204,6 @@
@Before
public void setUp() {
-
coreService = createMock(CoreService.class);
expect(coreService.registerApplication(appId.name()))
.andReturn(appId).anyTimes();
@@ -197,6 +217,7 @@
provider.packetService = packetService;
provider.deviceService = deviceService;
provider.hostService = hostService;
+ provider.interfaceService = interfaceService;
provider.activate(CTX_FOR_NO_REMOVE);
@@ -335,8 +356,9 @@
*/
@Test
public void receiveDhcp() {
+ TestUtils.setField(provider, "useDhcp", true);
// DHCP Request
- testProcessor.process(new TestDhcpRequestPacketContext(DEV1));
+ testProcessor.process(new TestDhcpRequestPacketContext(DEV1, VLAN));
assertThat("receiveDhcpRequest. One host description expected",
providerService.descriptions.size(), is(1));
// Should learn the MAC and location of DHCP client
@@ -347,23 +369,30 @@
assertThat(descr.vlan(), is(VLAN));
// DHCP Ack
- testProcessor.process(new TestDhcpAckPacketContext(DEV1));
+ testProcessor.process(new TestDhcpAckPacketContext(DEV1, false));
assertThat("receiveDhcpAck. Two additional host descriptions expected",
- providerService.descriptions.size(), is(3));
- // Should learn the IP of DHCP client
- HostDescription descr2 = providerService.descriptions.get(1);
- assertThat(descr2.location(), is(LOCATION));
- assertThat(descr2.hwAddress(), is(MAC));
- assertThat(descr2.ipAddress().size(), is(1));
- assertTrue(descr2.ipAddress().contains(IP_ADDRESS));
- assertThat(descr2.vlan(), is(VLAN));
- // Should also learn the MAC, location of DHCP server
- HostDescription descr3 = providerService.descriptions.get(2);
- assertThat(descr3.location(), is(LOCATION3));
- assertThat(descr3.hwAddress(), is(MAC3));
- assertThat(descr3.ipAddress().size(), is(0));
- assertThat(descr3.vlan(), is(VLAN));
+ providerService.descriptions.size(), is(2));
+ // Should also learn the MAC, location of DHCP server
+ HostDescription descr2 = providerService.descriptions.get(1);
+ assertThat(descr2.location(), is(LOCATION3));
+ assertThat(descr2.hwAddress(), is(MAC3));
+ assertThat(descr2.ipAddress().size(), is(0));
+ assertThat(descr2.vlan(), is(VLAN));
+
+ // Should not update the IP address of the host.
+ }
+
+ /**
+ * The host store should not updated when we disabled "useDhcp".
+ */
+ @Test
+ public void receiveDhcpButNotEnabled() {
+ TestUtils.setField(provider, "useDhcp", false);
+ // DHCP Request
+ testProcessor.process(new TestDhcpRequestPacketContext(DEV1, VLAN));
+ assertThat("receiveDhcpButNotEnabled. No host description expected",
+ providerService.descriptions.size(), is(0));
}
/**
@@ -604,10 +633,12 @@
*/
private class TestDhcpRequestPacketContext extends PacketContextAdapter {
private final String deviceId;
+ private final VlanId vlanId;
- public TestDhcpRequestPacketContext(String deviceId) {
+ public TestDhcpRequestPacketContext(String deviceId, VlanId vlanId) {
super(0, null, null, false);
this.deviceId = deviceId;
+ this.vlanId = vlanId;
}
@Override
@@ -632,7 +663,7 @@
ipv4.setSourceAddress(IP_ADDRESS.toString());
Ethernet eth = new Ethernet();
eth.setEtherType(Ethernet.TYPE_IPV4)
- .setVlanID(VLAN.toShort())
+ .setVlanID(this.vlanId.toShort())
.setSourceMACAddress(MAC)
.setDestinationMACAddress(MAC3)
.setPayload(ipv4);
@@ -648,10 +679,12 @@
*/
private class TestDhcpAckPacketContext extends PacketContextAdapter {
private final String deviceId;
+ private final boolean withRelayinfo;
- public TestDhcpAckPacketContext(String deviceId) {
+ public TestDhcpAckPacketContext(String deviceId, boolean withRelayInfo) {
super(0, null, null, false);
this.deviceId = deviceId;
+ this.withRelayinfo = withRelayInfo;
}
@Override
@@ -663,10 +696,29 @@
dhcpOption.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
dhcpOption.setData(dhcpMsgType);
dhcpOption.setLength((byte) 1);
+
DHCP dhcp = new DHCP();
- dhcp.setOptions(Collections.singletonList(dhcpOption));
+
+ if (withRelayinfo) {
+ CircuitId cid = new CircuitId(LOCATION.toString(), VLAN_100);
+ byte[] circuitId = cid.serialize();
+ DhcpOption circuitIdSubOption = new DhcpOption();
+ circuitIdSubOption.setCode(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
+ circuitIdSubOption.setData(circuitId);
+ circuitIdSubOption.setLength((byte) circuitId.length);
+
+ DhcpRelayAgentOption relayInfoOption = new DhcpRelayAgentOption();
+ relayInfoOption.setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue());
+ relayInfoOption.addSubOption(circuitIdSubOption);
+ dhcp.setOptions(ImmutableList.of(dhcpOption, relayInfoOption));
+ dhcp.setGatewayIPAddress(GW_IFACE_ADDR.ipAddress().getIp4Address().toInt());
+ } else {
+ dhcp.setOptions(ImmutableList.of(dhcpOption));
+ }
+
dhcp.setClientHardwareAddress(MAC.toBytes());
dhcp.setYourIPAddress(IP_ADDRESS.getIp4Address().toInt());
+
UDP udp = new UDP();
udp.setPayload(dhcp);
udp.setSourcePort(UDP.DHCP_SERVER_PORT);
@@ -961,9 +1013,60 @@
return HOST2;
} else if (hostId.equals(HostId.hostId(MAC3, VLAN))) {
return HOST3;
+ } else if (hostId.equals(HostId.hostId(MAC, VLAN_100))) {
+ return HOST_VLAN_100;
}
return null;
}
+ }
+ private class TestInterfaceService implements InterfaceService {
+ @Override
+ public Set<Interface> getInterfaces() {
+ return null;
+ }
+
+ @Override
+ public Interface getInterfaceByName(ConnectPoint connectPoint, String name) {
+ return null;
+ }
+
+ @Override
+ public Set<Interface> getInterfacesByPort(ConnectPoint port) {
+ return null;
+ }
+
+ public Set<Interface> getInterfacesByIp(IpAddress ip) {
+ if (ip.equals(GW_IFACE_ADDR.ipAddress())) {
+ return ImmutableSet.of(GW_IFACE);
+ } else {
+ return ImmutableSet.of();
+ }
+ }
+
+ @Override
+ public Set<Interface> getInterfacesByVlan(VlanId vlan) {
+ return null;
+ }
+
+ @Override
+ public Interface getMatchingInterface(IpAddress ip) {
+ return null;
+ }
+
+ @Override
+ public Set<Interface> getMatchingInterfaces(IpAddress ip) {
+ return null;
+ }
+
+ @Override
+ public void addListener(InterfaceListener listener) {
+
+ }
+
+ @Override
+ public void removeListener(InterfaceListener listener) {
+
+ }
}
}
diff --git a/utils/misc/src/main/java/org/onlab/packet/DHCP.java b/utils/misc/src/main/java/org/onlab/packet/DHCP.java
index ed1741e..f673da0 100644
--- a/utils/misc/src/main/java/org/onlab/packet/DHCP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/DHCP.java
@@ -583,7 +583,6 @@
boolean foundEndOptionsMarker = false;
while (bb.hasRemaining()) {
DhcpOption option;
-
int pos = bb.position();
int optCode = UNSIGNED_BYTE_MASK & bb.array()[pos]; // to unsigned integer
int optLen;