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