blob: abb53a2a480935ecb85c722a5828e72c2848b627 [file] [log] [blame]
/*
* 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();
}
@Override
public Optional<MacAddress> getDhcpServerMacAddress() {
return Optional.ofNullable(dhcpConnectMac);
}
/**
* 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);
if (!outInterface.isPresent()) {
log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
return;
}
Interface outIface = outInterface.get();
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.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
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;
}
}
}
}