Do not use char[]-based union access
[netvirt.git] / dhcpservice / impl / src / main / java / org / opendaylight / netvirt / dhcpservice / DhcpPktHandler.java
1 /*
2  * Copyright © 2015, 2018 Ericsson India Global Services Pvt Ltd. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.netvirt.dhcpservice;
9
10 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
11 import java.io.ByteArrayOutputStream;
12 import java.io.IOException;
13 import java.math.BigInteger;
14 import java.net.InetAddress;
15 import java.net.UnknownHostException;
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.Collections;
19 import java.util.List;
20 import java.util.concurrent.ExecutionException;
21 import java.util.concurrent.Future;
22 import javax.annotation.Nonnull;
23 import javax.inject.Inject;
24 import javax.inject.Singleton;
25 import org.apache.commons.net.util.SubnetUtils;
26 import org.apache.commons.net.util.SubnetUtils.SubnetInfo;
27 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
28 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
29 import org.opendaylight.genius.interfacemanager.globals.InterfaceInfo;
30 import org.opendaylight.genius.interfacemanager.interfaces.IInterfaceManager;
31 import org.opendaylight.genius.mdsalutil.MDSALUtil;
32 import org.opendaylight.genius.mdsalutil.MetaDataUtil;
33 import org.opendaylight.genius.mdsalutil.NwConstants;
34 import org.opendaylight.genius.mdsalutil.packet.Ethernet;
35 import org.opendaylight.genius.mdsalutil.packet.IEEE8021Q;
36 import org.opendaylight.genius.mdsalutil.packet.IPProtocols;
37 import org.opendaylight.genius.mdsalutil.packet.IPv4;
38 import org.opendaylight.genius.mdsalutil.packet.UDP;
39 import org.opendaylight.infrautils.metrics.Counter;
40 import org.opendaylight.infrautils.metrics.Labeled;
41 import org.opendaylight.infrautils.metrics.MetricDescriptor;
42 import org.opendaylight.infrautils.metrics.MetricProvider;
43 import org.opendaylight.infrautils.utils.concurrent.JdkFutures;
44 import org.opendaylight.netvirt.dhcpservice.api.DHCP;
45 import org.opendaylight.netvirt.dhcpservice.api.DHCPConstants;
46 import org.opendaylight.netvirt.dhcpservice.api.DHCPUtils;
47 import org.opendaylight.netvirt.dhcpservice.api.DhcpMConstants;
48 import org.opendaylight.openflowplugin.libraries.liblldp.EtherTypes;
49 import org.opendaylight.openflowplugin.libraries.liblldp.NetUtils;
50 import org.opendaylight.openflowplugin.libraries.liblldp.PacketException;
51 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.GetEgressActionsForInterfaceInputBuilder;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.GetEgressActionsForInterfaceOutput;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.GetInterfaceFromIfIndexInput;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.GetInterfaceFromIfIndexInputBuilder;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.GetInterfaceFromIfIndexOutput;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.OdlInterfaceRpcService;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.GetEgressActionsForTunnelInputBuilder;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.GetEgressActionsForTunnelOutput;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.ItmRpcService;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.dhcp_allocation_pool.rev161214.dhcp_allocation_pool.network.AllocationPool;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.port.attributes.FixedIps;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.ports.attributes.ports.Port;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.subnets.rev150712.subnet.attributes.HostRoutes;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.subnets.rev150712.subnets.attributes.subnets.Subnet;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketInReason;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingListener;
69 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingService;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketReceived;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.SendToController;
72 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInput;
73 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.dhcpservice.api.rev150710.subnet.dhcp.port.data.SubnetToDhcpPort;
74 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.dhcpservice.config.rev150710.DhcpserviceConfig;
75 import org.opendaylight.yangtools.yang.common.RpcResult;
76 import org.slf4j.Logger;
77 import org.slf4j.LoggerFactory;
78
79
80 @Singleton
81 public class DhcpPktHandler implements PacketProcessingListener {
82
83     private static final Logger LOG = LoggerFactory.getLogger(DhcpPktHandler.class);
84
85     private static final String UNKNOWN_LABEL = "unknown";
86
87     private enum PktDropReason {
88         INTERFACE_NAME_NOT_FOUND,
89         INTERFACE_INFO_NOT_FOUND,
90         SUBNET_NOT_FOUND,
91         EGRESS_ACTIONS_NOT_FOUND,
92         EXCEPTION,
93         PKT_DESERIALIZATION_ERROR
94     }
95
96     private final DhcpManager dhcpMgr;
97     private final OdlInterfaceRpcService interfaceManagerRpc;
98     private final PacketProcessingService pktService;
99     private final DhcpExternalTunnelManager dhcpExternalTunnelManager;
100     private final IInterfaceManager interfaceManager;
101     private final DhcpserviceConfig config;
102     private final DhcpAllocationPoolManager dhcpAllocationPoolMgr;
103     private final DataBroker broker;
104     private final ItmRpcService itmRpcService;
105     private final Labeled<Labeled<Labeled<Counter>>> pktDropCounter;
106     private final Labeled<Counter> pktInCounter;
107
108     @Inject
109     public DhcpPktHandler(final DhcpManager dhcpManager,
110                           final DhcpExternalTunnelManager dhcpExternalTunnelManager,
111                           final OdlInterfaceRpcService interfaceManagerRpc,
112                           final PacketProcessingService pktService,
113                           final IInterfaceManager interfaceManager,
114                           final DhcpserviceConfig config,
115                           final DhcpAllocationPoolManager dhcpAllocationPoolMgr,
116                           final DataBroker dataBroker,
117                           final ItmRpcService itmRpcService,
118                           final MetricProvider metricProvider) {
119         this.interfaceManagerRpc = interfaceManagerRpc;
120         this.pktService = pktService;
121         this.dhcpExternalTunnelManager = dhcpExternalTunnelManager;
122         this.dhcpMgr = dhcpManager;
123         this.interfaceManager = interfaceManager;
124         this.config = config;
125         this.dhcpAllocationPoolMgr = dhcpAllocationPoolMgr;
126         this.broker = dataBroker;
127         this.itmRpcService = itmRpcService;
128         this.pktDropCounter = metricProvider.newCounter(buildDhcpMetricDescriptor("packet_drop"),
129                 "mac", "interface", "reason");
130         this.pktInCounter = metricProvider.newCounter(buildDhcpMetricDescriptor("packet_in"), "mac");
131     }
132
133     private MetricDescriptor buildDhcpMetricDescriptor(String id) {
134         return MetricDescriptor.builder().anchor(this).project("netvirt").module("dhcpservice").id(id).build();
135     }
136
137     @Override
138     @SuppressWarnings("checkstyle:IllegalCatch")
139     public void onPacketReceived(PacketReceived packet) {
140         try {
141             onPacketReceivedInternal(packet);
142         } catch (Exception e) {
143             pktDropCounter.label(getSourceMacAddress(packet)).label(UNKNOWN_LABEL)
144                     .label(PktDropReason.EXCEPTION.name()).increment();
145             LOG.error("Failed to handle dhcp packet in ", e);
146         }
147     }
148
149     private String getSourceMacAddress(PacketReceived packet) {
150         Ethernet ethPkt = new Ethernet();
151         try {
152             byte[] inPayload = packet.getPayload();
153             ethPkt.deserialize(inPayload, 0, inPayload.length * NetUtils.NUM_BITS_IN_A_BYTE);
154             return DHCPUtils.byteArrayToString(ethPkt.getSourceMACAddress());
155         } catch (PacketException pktException) {
156             LOG.error("Failed to parse the packet {}", packet);
157         }
158         return UNKNOWN_LABEL;
159     }
160
161     public void onPacketReceivedInternal(PacketReceived packet) {
162         if (!config.isControllerDhcpEnabled()) {
163             return;
164         }
165         Class<? extends PacketInReason> pktInReason = packet.getPacketInReason();
166         short tableId = packet.getTableId().getValue();
167         if ((tableId == NwConstants.DHCP_TABLE || tableId == NwConstants.DHCP_TABLE_EXTERNAL_TUNNEL)
168                 && isPktInReasonSendtoCtrl(pktInReason)) {
169             byte[] inPayload = packet.getPayload();
170             Ethernet ethPkt = new Ethernet();
171             try {
172                 ethPkt.deserialize(inPayload, 0, inPayload.length * NetUtils.NUM_BITS_IN_A_BYTE);
173             } catch (PacketException e) {
174                 pktDropCounter.label(UNKNOWN_LABEL).label(UNKNOWN_LABEL).label(
175                         PktDropReason.PKT_DESERIALIZATION_ERROR.name()).increment();
176                 LOG.warn("Failed to decode DHCP Packet.", e);
177                 LOG.trace("Received packet {}", packet);
178                 return;
179             }
180             DHCP pktIn;
181             pktIn = getDhcpPktIn(ethPkt);
182             if (pktIn != null) {
183                 LOG.trace("DHCPPkt received: {}", pktIn);
184                 LOG.trace("Received Packet: {}", packet);
185                 BigInteger metadata = packet.getMatch().getMetadata().getMetadata();
186                 long portTag = MetaDataUtil.getLportFromMetadata(metadata).intValue();
187                 String macAddress = DHCPUtils.byteArrayToString(ethPkt.getSourceMACAddress());
188                 pktInCounter.label(macAddress).increment();
189                 BigInteger tunnelId =
190                         packet.getMatch().getTunnel() == null ? null : packet.getMatch().getTunnel().getTunnelId();
191                 String interfaceName = getInterfaceNameFromTag(portTag);
192                 if (interfaceName == null) {
193                     pktDropCounter.label(macAddress).label(UNKNOWN_LABEL).label(
194                             PktDropReason.INTERFACE_NAME_NOT_FOUND.name()).increment();
195                 }
196                 InterfaceInfo interfaceInfo =
197                         interfaceManager.getInterfaceInfoFromOperationalDataStore(interfaceName);
198                 if (interfaceInfo == null) {
199                     LOG.error("Failed to get interface info for interface name {}", interfaceName);
200                     pktDropCounter.label(macAddress).label(interfaceName).label(
201                             PktDropReason.INTERFACE_INFO_NOT_FOUND.name()).increment();
202                     return;
203                 }
204                 Port port;
205                 if (tunnelId != null) {
206                     port = dhcpExternalTunnelManager.readVniMacToPortCache(tunnelId, macAddress);
207                 } else {
208                     port = getNeutronPort(interfaceName);
209                 }
210                 Subnet subnet = getNeutronSubnet(port);
211                 String serverMacAddress = interfaceInfo.getMacAddress();
212                 String serverIp = null;
213                 if (subnet != null) {
214                     java.util.Optional<SubnetToDhcpPort> dhcpPortData = DhcpServiceUtils
215                             .getSubnetDhcpPortData(broker, subnet.getUuid().getValue());
216                     /* If enable_dhcp_service flag was enabled and an ODL network DHCP Port data was made available use
217                      * the ports Fixed IP as server IP for DHCP communication.
218                      */
219                     if (dhcpPortData.isPresent()) {
220                         serverIp = dhcpPortData.get().getPortFixedip();
221                         serverMacAddress = dhcpPortData.get().getPortMacaddress();
222                     } else {
223                         // DHCP Neutron Port not found for this network
224                         LOG.error("Neutron DHCP port is not available for the Subnet {} and port {}.", subnet.getUuid(),
225                                 port.getUuid());
226                         pktDropCounter.label(macAddress).label(interfaceName).label(
227                                 PktDropReason.SUBNET_NOT_FOUND.name()).increment();
228                         return;
229                     }
230                 } else {
231                     pktDropCounter.label(macAddress).label(interfaceName).label(
232                             PktDropReason.SUBNET_NOT_FOUND.name()).increment();
233                 }
234                 DHCP replyPkt = handleDhcpPacket(pktIn, interfaceName, macAddress, port, subnet, serverIp);
235                 if (replyPkt == null) {
236                     LOG.warn("Unable to construct reply packet for interface name {}", interfaceName);
237                     return;
238                 }
239                 byte[] pktOut = getDhcpPacketOut(replyPkt, ethPkt, serverMacAddress);
240                 sendPacketOut(pktOut, macAddress, interfaceInfo.getDpId(), interfaceName, tunnelId);
241             }
242         }
243     }
244
245     private void sendPacketOut(byte[] pktOut, String mac, BigInteger dpnId, String interfaceName,
246                                BigInteger tunnelId) {
247         List<Action> action = getEgressAction(interfaceName, tunnelId);
248         if (action == null) {
249             pktDropCounter.label(mac).label(interfaceName).label(
250                     PktDropReason.EGRESS_ACTIONS_NOT_FOUND.name()).increment();
251             return;
252         }
253         TransmitPacketInput output = MDSALUtil.getPacketOut(action, pktOut, dpnId);
254         LOG.trace("Transmitting packet: {}", output);
255         JdkFutures.addErrorLogging(pktService.transmitPacket(output), LOG, "Transmit packet");
256     }
257
258     private DHCP handleDhcpPacket(DHCP dhcpPkt, String interfaceName, String macAddress, Port interfacePort,
259                                   Subnet subnet, String serverIp) {
260         LOG.trace("DHCP pkt rcvd {}", dhcpPkt);
261         byte msgType = dhcpPkt.getMsgType();
262         DhcpInfo dhcpInfo = null;
263         if (interfacePort != null) {
264             dhcpInfo = handleDhcpNeutronPacket(msgType, interfacePort, subnet, serverIp);
265         } else if (config.isDhcpDynamicAllocationPoolEnabled()) {
266             dhcpInfo = handleDhcpAllocationPoolPacket(msgType, interfaceName, macAddress);
267         }
268         DHCP reply = null;
269         if (dhcpInfo != null) {
270             if (msgType == DHCPConstants.MSG_DISCOVER) {
271                 reply = getReplyToDiscover(dhcpPkt, dhcpInfo);
272             } else if (msgType == DHCPConstants.MSG_REQUEST) {
273                 reply = getReplyToRequest(dhcpPkt, dhcpInfo);
274             }
275         }
276
277         return reply;
278     }
279
280     private DhcpInfo handleDhcpNeutronPacket(byte msgType, Port port, Subnet subnet, String serverIp) {
281         if (msgType == DHCPConstants.MSG_DECLINE) {
282             LOG.trace("DHCPDECLINE received");
283             return null;
284         } else if (msgType == DHCPConstants.MSG_RELEASE) {
285             LOG.trace("DHCPRELEASE received");
286             return null;
287         }
288         return getDhcpInfoFromNeutronPort(port, subnet, serverIp);
289     }
290
291
292     private DhcpInfo handleDhcpAllocationPoolPacket(byte msgType, String interfaceName, String macAddress) {
293         try {
294             String networkId = dhcpAllocationPoolMgr.getNetworkByPort(interfaceName);
295             AllocationPool pool = networkId != null ? dhcpAllocationPoolMgr.getAllocationPoolByNetwork(networkId)
296                     : null;
297             if (networkId == null || pool == null) {
298                 LOG.warn("No Dhcp Allocation Pool was found for interface: {}", interfaceName);
299                 return null;
300             }
301             switch (msgType) {
302                 case DHCPConstants.MSG_DISCOVER:
303                 case DHCPConstants.MSG_REQUEST:
304                     // FIXME: requested ip is currently ignored in moment of allocation
305                     return getDhcpInfoFromAllocationPool(networkId, pool, macAddress);
306                 case DHCPConstants.MSG_RELEASE:
307                     dhcpAllocationPoolMgr.releaseIpAllocation(networkId, pool, macAddress);
308                     break;
309                 default:
310                     break;
311             }
312         } catch (ReadFailedException e) {
313             LOG.error("Error reading from MD-SAL", e);
314         }
315         return null;
316     }
317
318     private DhcpInfo getDhcpInfoFromNeutronPort(Port port, Subnet subnet, String serverIp) {
319         DhcpInfo dhcpInfo = getDhcpInfo(port, subnet, serverIp);
320         LOG.trace("NeutronPort: {} \n NeutronSubnet: {}, dhcpInfo{}", port, subnet, dhcpInfo);
321         return dhcpInfo;
322     }
323
324     private DhcpInfo getDhcpInfoFromAllocationPool(String networkId, AllocationPool pool, String macAddress) {
325         IpAddress allocatedIp = dhcpAllocationPoolMgr.getIpAllocation(networkId, pool, macAddress);
326         DhcpInfo dhcpInfo = getApDhcpInfo(pool, allocatedIp);
327         LOG.info("AllocationPoolNetwork: {}, dhcpInfo {}", networkId, dhcpInfo);
328         return dhcpInfo;
329     }
330
331     private DhcpInfo getDhcpInfo(Port port, Subnet subnet, String serverIp) {
332         DhcpInfo dhcpInfo = null;
333         if (port != null && subnet != null) {
334             String clientIp = getIpv4Address(port);
335             List<IpAddress> dnsServers = subnet.getDnsNameservers();
336             dhcpInfo = new DhcpInfo();
337             if (isIpv4Address(subnet.getGatewayIp())) {
338                 dhcpInfo.setGatewayIp(subnet.getGatewayIp().getIpv4Address().getValue());
339             }
340             if (clientIp != null && serverIp != null) {
341                 List<HostRoutes> subnetHostRoutes = new ArrayList<>(subnet.getHostRoutes().size());
342                 for (HostRoutes hostRoute : subnet.getHostRoutes()) {
343                     if (!hostRoute.getNexthop().stringValue().equals(clientIp)) {
344                         subnetHostRoutes.add(hostRoute);
345                     }
346                 }
347                 dhcpInfo.setClientIp(clientIp).setServerIp(serverIp)
348                         .setCidr(subnet.getCidr().stringValue()).setHostRoutes(subnetHostRoutes)
349                         .setDnsServersIpAddrs(dnsServers);
350             }
351         }
352         return dhcpInfo;
353     }
354
355     private static DhcpInfo getApDhcpInfo(AllocationPool ap, IpAddress allocatedIp) {
356         DhcpInfo dhcpInfo = null;
357
358         String clientIp = allocatedIp.stringValue();
359         String serverIp = ap.getGateway().stringValue();
360         List<IpAddress> dnsServers = ap.getDnsServers();
361         dhcpInfo = new DhcpInfo();
362         dhcpInfo.setClientIp(clientIp).setServerIp(serverIp).setCidr(ap.getSubnet().stringValue())
363             .setHostRoutes(Collections.emptyList()).setDnsServersIpAddrs(dnsServers).setGatewayIp(serverIp);
364
365         return dhcpInfo;
366     }
367
368     /* TODO:
369      * getIpv4Address and isIpv4Address
370      * Many other modules use/need similar methods. Should
371      * be refactored to a common NeutronUtils module.     *
372      */
373     private static String getIpv4Address(Port port) {
374
375         for (FixedIps fixedIp : port.getFixedIps()) {
376             if (isIpv4Address(fixedIp.getIpAddress())) {
377                 return fixedIp.getIpAddress().getIpv4Address().getValue();
378             }
379         }
380         LOG.error("Could not find ipv4 address for port {}", port);
381         return null;
382     }
383
384     private static boolean isIpv4Address(IpAddress ip) {
385         return ip != null && ip.getIpv4Address() != null;
386     }
387
388     private Subnet getNeutronSubnet(Port port) {
389         return dhcpMgr.getNeutronSubnet(port);
390     }
391
392     private Port getNeutronPort(String interfaceName) {
393         return dhcpMgr.getNeutronPort(interfaceName);
394     }
395
396     private static DHCP getDhcpPktIn(Ethernet actualEthernetPacket) {
397         Ethernet ethPkt = actualEthernetPacket;
398         if (ethPkt.getEtherType() == (short)NwConstants.ETHTYPE_802_1Q) {
399             ethPkt = (Ethernet)ethPkt.getPayload();
400         }
401         // Currently only IPv4 is supported
402         if (ethPkt.getPayload() instanceof IPv4) {
403             IPv4 ipPkt = (IPv4) ethPkt.getPayload();
404             if (ipPkt.getPayload() instanceof UDP) {
405                 UDP udpPkt = (UDP) ipPkt.getPayload();
406                 if (udpPkt.getSourcePort() == DhcpMConstants.DHCP_CLIENT_PORT
407                         && udpPkt.getDestinationPort() == DhcpMConstants.DHCP_SERVER_PORT) {
408                     LOG.trace("Matched DHCP_CLIENT_PORT and DHCP_SERVER_PORT");
409                     byte[] rawDhcpPayload = udpPkt.getRawPayload();
410                     DHCP reply = new DHCP();
411                     try {
412                         reply.deserialize(rawDhcpPayload, 0, rawDhcpPayload.length);
413                     } catch (PacketException e) {
414                         LOG.warn("Failed to deserialize DHCP pkt");
415                         LOG.trace("Reason for failure", e);
416                         return null;
417                     }
418                     return reply;
419                 }
420             }
421         }
422         return null;
423     }
424
425     DHCP getReplyToDiscover(DHCP dhcpPkt, DhcpInfo dhcpInfo) {
426         DHCP reply = new DHCP();
427         reply.setOp(DHCPConstants.BOOTREPLY);
428         reply.setHtype(dhcpPkt.getHtype());
429         reply.setHlen(dhcpPkt.getHlen());
430         reply.setHops((byte) 0);
431         reply.setXid(dhcpPkt.getXid());
432         reply.setSecs((short) 0);
433
434         reply.setYiaddr(dhcpInfo.getClientIp());
435         reply.setSiaddr(dhcpInfo.getServerIp());
436
437         reply.setFlags(dhcpPkt.getFlags());
438         reply.setGiaddr(dhcpPkt.getGiaddr());
439         reply.setChaddr(dhcpPkt.getChaddr());
440
441         reply.setMsgType(DHCPConstants.MSG_OFFER);
442         if (dhcpPkt.containsOption(DHCPConstants.OPT_PARAMETER_REQUEST_LIST)) {
443             setParameterListOptions(dhcpPkt, reply, dhcpInfo);
444         }
445         setCommonOptions(reply, dhcpInfo);
446         return reply;
447     }
448
449     DHCP getReplyToRequest(DHCP dhcpPkt, DhcpInfo dhcpInfo) {
450         boolean sendAck = false;
451         byte[] requestedIp = null;
452         DHCP reply = new DHCP();
453         reply.setOp(DHCPConstants.BOOTREPLY);
454         reply.setHtype(dhcpPkt.getHtype());
455         reply.setHlen(dhcpPkt.getHlen());
456         reply.setHops((byte) 0);
457         reply.setXid(dhcpPkt.getXid());
458         reply.setSecs((short) 0);
459
460         reply.setFlags(dhcpPkt.getFlags());
461         reply.setGiaddr(dhcpPkt.getGiaddr());
462         reply.setChaddr(dhcpPkt.getChaddr());
463         byte[] allocatedIp;
464         try {
465             allocatedIp = DHCPUtils.strAddrToByteArray(dhcpInfo.getClientIp());
466         } catch (UnknownHostException e) {
467             LOG.debug("strAddrToByteArray", e);
468             allocatedIp = null;
469         }
470
471         if (Arrays.equals(allocatedIp, dhcpPkt.getCiaddr())) {
472             //This means a renew request
473             sendAck = true;
474         } else {
475             requestedIp = dhcpPkt.getOptionBytes(DHCPConstants.OPT_REQUESTED_ADDRESS);
476             sendAck = Arrays.equals(allocatedIp, requestedIp);
477         }
478
479         if (sendAck) {
480             reply.setCiaddr(dhcpPkt.getCiaddr());
481             reply.setYiaddr(dhcpInfo.getClientIp());
482             reply.setSiaddr(dhcpInfo.getServerIp());
483             reply.setMsgType(DHCPConstants.MSG_ACK);
484             if (dhcpPkt.containsOption(DHCPConstants.OPT_PARAMETER_REQUEST_LIST)) {
485                 setParameterListOptions(dhcpPkt, reply, dhcpInfo);
486             }
487         } else {
488             reply.setMsgType(DHCPConstants.MSG_NAK);
489         }
490         setCommonOptions(reply, dhcpInfo);
491         return reply;
492     }
493
494     // "Consider returning a zero length array rather than null" - the eventual user of the returned byte[] likely
495     // expects null and it's unclear what the behavior would be if empty array was returned.
496     @SuppressFBWarnings("PZLA_PREFER_ZERO_LENGTH_ARRAYS")
497     protected byte[] getDhcpPacketOut(DHCP reply, Ethernet etherPkt, String phyAddrees) {
498         if (reply == null) {
499             /*
500              * DECLINE or RELEASE don't result in reply packet
501              */
502             return null;
503         }
504         LOG.trace("Sending DHCP Pkt {}", reply);
505         InetAddress serverIp = reply.getOptionInetAddr(DHCPConstants.OPT_SERVER_IDENTIFIER);
506         // create UDP pkt
507         UDP udpPkt = new UDP();
508         byte[] rawPkt;
509         try {
510             rawPkt = reply.serialize();
511         } catch (PacketException e) {
512             LOG.warn("Failed to serialize packet", e);
513             return null;
514         }
515         udpPkt.setRawPayload(rawPkt);
516         udpPkt.setDestinationPort(DhcpMConstants.DHCP_CLIENT_PORT);
517         udpPkt.setSourcePort(DhcpMConstants.DHCP_SERVER_PORT);
518         udpPkt.setLength((short) (rawPkt.length + 8));
519         //Create IP Pkt
520         try {
521             rawPkt = udpPkt.serialize();
522         } catch (PacketException e) {
523             LOG.warn("Failed to serialize packet", e);
524             return null;
525         }
526         short checkSum = 0;
527         boolean computeUdpChecksum = true;
528         if (computeUdpChecksum) {
529             checkSum = computeChecksum(rawPkt, serverIp.getAddress(),
530                     NetUtils.intToByteArray4(DhcpMConstants.BCAST_IP));
531         }
532         udpPkt.setChecksum(checkSum);
533         IPv4 ip4Reply = new IPv4();
534         ip4Reply.setPayload(udpPkt);
535         ip4Reply.setProtocol(IPProtocols.UDP.byteValue());
536         ip4Reply.setSourceAddress(serverIp);
537         ip4Reply.setDestinationAddress(DhcpMConstants.BCAST_IP);
538         ip4Reply.setTotalLength((short) (rawPkt.length + 20));
539         ip4Reply.setTtl((byte) 32);
540         // create Ethernet Frame
541         Ethernet ether = new Ethernet();
542         if (etherPkt.getEtherType() == (short)NwConstants.ETHTYPE_802_1Q) {
543             IEEE8021Q vlanPacket = (IEEE8021Q) etherPkt.getPayload();
544             IEEE8021Q vlanTagged = new IEEE8021Q();
545             vlanTagged.setCFI(vlanPacket.getCfi());
546             vlanTagged.setPriority(vlanPacket.getPriority());
547             vlanTagged.setVlanId(vlanPacket.getVlanId());
548             vlanTagged.setPayload(ip4Reply);
549             vlanTagged.setEtherType(EtherTypes.IPv4.shortValue());
550             ether.setPayload(vlanTagged);
551             ether.setEtherType((short) NwConstants.ETHTYPE_802_1Q);
552         } else {
553             ether.setEtherType(EtherTypes.IPv4.shortValue());
554             ether.setPayload(ip4Reply);
555         }
556         ether.setSourceMACAddress(getServerMacAddress(phyAddrees));
557         ether.setDestinationMACAddress(etherPkt.getSourceMACAddress());
558
559         try {
560             rawPkt = ether.serialize();
561         } catch (PacketException e) {
562             LOG.warn("Failed to serialize ethernet reply",e);
563             return null;
564         }
565         return rawPkt;
566     }
567
568     private static byte[] getServerMacAddress(String phyAddress) {
569         // Should we return ControllerMac instead?
570         return DHCPUtils.strMacAddrtoByteArray(phyAddress);
571     }
572
573     public short computeChecksum(byte[] inData, byte[] srcAddr, byte[] destAddr) {
574         int sum = 0;
575         int carry = 0;
576         int wordData;
577         int index;
578
579         for (index = 0; index < inData.length - 1; index = index + 2) {
580             // Skip, if the current bytes are checkSum bytes
581             wordData = (inData[index] << 8 & 0xFF00) + (inData[index + 1] & 0xFF);
582             sum = sum + wordData;
583         }
584
585         if (index < inData.length) {
586             wordData = (inData[index] << 8 & 0xFF00) + (0 & 0xFF);
587             sum = sum + wordData;
588         }
589
590         for (index = 0; index < 4; index = index + 2) {
591             wordData = (srcAddr[index] << 8 & 0xFF00) + (srcAddr[index + 1] & 0xFF);
592             sum = sum + wordData;
593         }
594
595         for (index = 0; index < 4; index = index + 2) {
596             wordData = (destAddr[index] << 8 & 0xFF00) + (destAddr[index + 1] & 0xFF);
597             sum = sum + wordData;
598         }
599         sum = sum + 17 + inData.length;
600
601         while (sum >> 16 != 0) {
602             carry = sum >> 16;
603             sum = (sum & 0xFFFF) + carry;
604         }
605         short checkSum = (short) ~((short) sum & 0xFFFF);
606         if (checkSum == 0) {
607             checkSum = (short)0xffff;
608         }
609         return checkSum;
610     }
611
612     private void setCommonOptions(DHCP pkt, DhcpInfo dhcpInfo) {
613         String serverIp = dhcpInfo.getServerIp();
614         if (pkt.getMsgType() != DHCPConstants.MSG_NAK) {
615             setNonNakOptions(pkt, dhcpInfo);
616         }
617         try {
618             /*
619              * setParameterListOptions may have initialized some of these
620              * options to maintain order. If we can't fill them, unset to avoid
621              * sending wrong information in reply.
622              */
623             if (serverIp != null) {
624                 pkt.setOptionInetAddr(DHCPConstants.OPT_SERVER_IDENTIFIER, serverIp);
625             } else {
626                 pkt.unsetOption(DHCPConstants.OPT_SERVER_IDENTIFIER);
627             }
628         } catch (UnknownHostException e) {
629             LOG.warn("Failed to set option", e);
630         }
631     }
632
633     private void setNonNakOptions(DHCP pkt, DhcpInfo dhcpInfo) {
634         pkt.setOptionInt(DHCPConstants.OPT_LEASE_TIME, dhcpMgr.getDhcpLeaseTime());
635         if (dhcpMgr.getDhcpDefDomain() != null) {
636             pkt.setOptionString(DHCPConstants.OPT_DOMAIN_NAME, dhcpMgr.getDhcpDefDomain());
637         }
638         if (dhcpMgr.getDhcpLeaseTime() > 0) {
639             pkt.setOptionInt(DHCPConstants.OPT_REBINDING_TIME, dhcpMgr.getDhcpRebindingTime());
640             pkt.setOptionInt(DHCPConstants.OPT_RENEWAL_TIME, dhcpMgr.getDhcpRenewalTime());
641         }
642         SubnetUtils util = null;
643         SubnetInfo info = null;
644         util = new SubnetUtils(dhcpInfo.getCidr());
645         info = util.getInfo();
646         String gwIp = dhcpInfo.getGatewayIp();
647         List<String> dnServers = dhcpInfo.getDnsServers();
648         try {
649             /*
650              * setParameterListOptions may have initialized some of these
651              * options to maintain order. If we can't fill them, unset to avoid
652              * sending wrong information in reply.
653              */
654             if (gwIp != null) {
655                 pkt.setOptionInetAddr(DHCPConstants.OPT_ROUTERS, gwIp);
656             } else {
657                 pkt.unsetOption(DHCPConstants.OPT_ROUTERS);
658             }
659             if (info != null) {
660                 pkt.setOptionInetAddr(DHCPConstants.OPT_SUBNET_MASK, info.getNetmask());
661                 pkt.setOptionInetAddr(DHCPConstants.OPT_BROADCAST_ADDRESS, info.getBroadcastAddress());
662             } else {
663                 pkt.unsetOption(DHCPConstants.OPT_SUBNET_MASK);
664                 pkt.unsetOption(DHCPConstants.OPT_BROADCAST_ADDRESS);
665             }
666             if (dnServers != null && dnServers.size() > 0) {
667                 pkt.setOptionStrAddrs(DHCPConstants.OPT_DOMAIN_NAME_SERVERS, dnServers);
668             } else {
669                 pkt.unsetOption(DHCPConstants.OPT_DOMAIN_NAME_SERVERS);
670             }
671         } catch (UnknownHostException e) {
672             // TODO Auto-generated catch block
673             LOG.warn("Failed to set option", e);
674         }
675     }
676
677     private void setParameterListOptions(DHCP req, DHCP reply, DhcpInfo dhcpInfo) {
678         byte[] paramList = req.getOptionBytes(DHCPConstants.OPT_PARAMETER_REQUEST_LIST);
679         for (byte element : paramList) {
680             switch (element) {
681                 case DHCPConstants.OPT_SUBNET_MASK:
682                 case DHCPConstants.OPT_ROUTERS:
683                 case DHCPConstants.OPT_SERVER_IDENTIFIER:
684                 case DHCPConstants.OPT_DOMAIN_NAME_SERVERS:
685                 case DHCPConstants.OPT_BROADCAST_ADDRESS:
686                 case DHCPConstants.OPT_LEASE_TIME:
687                 case DHCPConstants.OPT_RENEWAL_TIME:
688                 case DHCPConstants.OPT_REBINDING_TIME:
689                     /* These values will be filled in setCommonOptions
690                      * Setting these just to preserve order as
691                      * specified in PARAMETER_REQUEST_LIST.
692                      */
693                     reply.setOptionInt(element, 0);
694                     break;
695                 case DHCPConstants.OPT_DOMAIN_NAME:
696                     reply.setOptionString(element, " ");
697                     break;
698                 case DHCPConstants.OPT_CLASSLESS_ROUTE:
699                     setOptionClasslessRoute(reply, dhcpInfo);
700                     break;
701                 default:
702                     LOG.trace("DHCP Option code {} not supported yet", element);
703                     break;
704             }
705         }
706     }
707
708     private void setOptionClasslessRoute(DHCP reply, DhcpInfo dhcpInfo) {
709         List<HostRoutes> hostRoutes = dhcpInfo.getHostRoutes();
710         if (hostRoutes == null) {
711             //we can't set this option, so return
712             return;
713         }
714         ByteArrayOutputStream result = new ByteArrayOutputStream();
715         for (HostRoutes hostRoute : hostRoutes) {
716             if (hostRoute.getNexthop().getIpv4Address() == null
717                     || hostRoute.getDestination().getIpv4Prefix() == null) {
718                 // we only deal with IPv4 addresses
719                 return;
720             }
721             String router = hostRoute.getNexthop().getIpv4Address().getValue();
722             String dest = hostRoute.getDestination().getIpv4Prefix().getValue();
723             try {
724                 result.write(convertToClasslessRouteOption(dest, router));
725             } catch (IOException | NullPointerException e) {
726                 LOG.trace("Exception {}", e.getMessage());
727             }
728         }
729         if (result.size() > 0) {
730             reply.setOptionBytes(DHCPConstants.OPT_CLASSLESS_ROUTE , result.toByteArray());
731         }
732     }
733
734     @Nonnull
735     protected byte[] convertToClasslessRouteOption(String dest, String router) {
736         ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
737         if (dest == null || router == null) {
738             return new byte[0];
739         }
740
741         //get prefix
742         Short prefix = null;
743         String[] parts = dest.split("/");
744         if (parts.length < 2) {
745             prefix = (short) 0;
746         } else {
747             prefix = Short.valueOf(parts[1]);
748         }
749
750         byteArray.write(prefix.byteValue());
751         SubnetUtils util = new SubnetUtils(dest);
752         SubnetInfo info = util.getInfo();
753         String strNetAddr = info.getNetworkAddress();
754         try {
755             byte[] netAddr = InetAddress.getByName(strNetAddr).getAddress();
756           //Strip any trailing 0s from netAddr
757             for (int i = 0; i < netAddr.length;i++) {
758                 if (netAddr[i] != 0) {
759                     byteArray.write(netAddr,i,1);
760                 }
761             }
762             byteArray.write(InetAddress.getByName(router).getAddress());
763         } catch (IOException e) {
764             return new byte[0];
765         }
766         return byteArray.toByteArray();
767     }
768
769     private boolean isPktInReasonSendtoCtrl(Class<? extends PacketInReason> pktInReason) {
770         return pktInReason == SendToController.class;
771     }
772
773     private String getInterfaceNameFromTag(long portTag) {
774         String interfaceName = null;
775         GetInterfaceFromIfIndexInput input =
776                 new GetInterfaceFromIfIndexInputBuilder().setIfIndex((int) portTag).build();
777         Future<RpcResult<GetInterfaceFromIfIndexOutput>> futureOutput =
778                 interfaceManagerRpc.getInterfaceFromIfIndex(input);
779         try {
780             GetInterfaceFromIfIndexOutput output = futureOutput.get().getResult();
781             interfaceName = output.getInterfaceName();
782         } catch (InterruptedException | ExecutionException e) {
783             LOG.error("Error while retrieving the interfaceName from tag using getInterfaceFromIfIndex RPC");
784         }
785         LOG.trace("Returning interfaceName {} for tag {} form getInterfaceNameFromTag", interfaceName, portTag);
786         return interfaceName;
787     }
788
789     private List<Action> getEgressAction(String interfaceName, BigInteger tunnelId) {
790         try {
791             if (interfaceManager.isItmDirectTunnelsEnabled() && tunnelId != null) {
792                 GetEgressActionsForTunnelInputBuilder egressAction =
793                         new GetEgressActionsForTunnelInputBuilder().setIntfName(interfaceName);
794                 egressAction.setTunnelKey(tunnelId.longValue());
795                 RpcResult<GetEgressActionsForTunnelOutput> rpcResult =
796                         itmRpcService.getEgressActionsForTunnel(egressAction.build()).get();
797                 if (!rpcResult.isSuccessful()) {
798                     LOG.warn("RPC Call to Get egress actions for interface {} returned with Errors {}",
799                             interfaceName, rpcResult.getErrors());
800                 } else {
801                     return rpcResult.getResult().getAction();
802                 }
803             } else {
804                 GetEgressActionsForInterfaceInputBuilder egressAction =
805                         new GetEgressActionsForInterfaceInputBuilder().setIntfName(interfaceName);
806                 if (tunnelId != null) {
807                     egressAction.setTunnelKey(tunnelId.longValue());
808                 }
809                 Future<RpcResult<GetEgressActionsForInterfaceOutput>> result =
810                         interfaceManagerRpc.getEgressActionsForInterface(egressAction.build());
811                 RpcResult<GetEgressActionsForInterfaceOutput> rpcResult = result.get();
812                 if (!rpcResult.isSuccessful()) {
813                     LOG.warn("RPC Call to Get egress actions for interface {} returned with Errors {}",
814                             interfaceName, rpcResult.getErrors());
815                 } else {
816                     return rpcResult.getResult().getAction();
817                 }
818             }
819         } catch (InterruptedException | ExecutionException e) {
820             LOG.warn("Exception when egress actions for interface {}", interfaceName, e);
821         }
822         return Collections.emptyList();
823     }
824 }