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