2 * Copyright (c) 2015 Ericsson India Global Services Pvt Ltd. and others. All rights reserved.
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
8 package org.opendaylight.vpnservice.dhcpservice;
10 import java.io.ByteArrayOutputStream;
11 import java.io.IOException;
12 import java.net.InetAddress;
13 import java.net.UnknownHostException;
14 import java.util.Arrays;
15 import java.util.Iterator;
16 import java.util.List;
18 import org.apache.commons.net.util.SubnetUtils;
19 import org.apache.commons.net.util.SubnetUtils.SubnetInfo;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
22 import org.opendaylight.controller.liblldp.EtherTypes;
23 import org.opendaylight.controller.liblldp.HexEncode;
24 import org.opendaylight.controller.liblldp.NetUtils;
25 import org.opendaylight.controller.liblldp.PacketException;
26 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
27 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
28 import org.opendaylight.vpnservice.dhcpservice.api.DHCP;
29 import org.opendaylight.vpnservice.dhcpservice.api.DHCPConstants;
30 import org.opendaylight.vpnservice.dhcpservice.api.DHCPMConstants;
31 import org.opendaylight.vpnservice.dhcpservice.api.DHCPUtils;
32 import org.opendaylight.vpnservice.mdsalutil.MDSALDataStoreUtils;
33 import org.opendaylight.vpnservice.mdsalutil.packet.Ethernet;
34 import org.opendaylight.vpnservice.mdsalutil.packet.IPProtocols;
35 import org.opendaylight.vpnservice.mdsalutil.packet.IPv4;
36 import org.opendaylight.vpnservice.mdsalutil.packet.UDP;
37 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpAddress;
38 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev100924.MacAddress;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeConnector;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.ports.attributes.ports.Port;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.subnets.rev150712.subnet.attributes.HostRoutes;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.subnets.rev150712.subnets.attributes.subnets.Subnet;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketInReason;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingService;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketReceived;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingListener;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.SendToController;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInput;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInputBuilder;
54 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
56 import com.google.common.base.Optional;
58 public class DhcpPktHandler implements AutoCloseable, PacketProcessingListener {
60 private static final Logger LOG = LoggerFactory.getLogger(DhcpPktHandler.class);
61 private final DataBroker dataBroker;
62 private final DhcpManager dhcpMgr;
64 private boolean computeUdpChecksum = true;
65 private PacketProcessingService pktService;
67 public DhcpPktHandler(final DataBroker broker, final DhcpManager dhcpManager) {
68 this.dataBroker = broker;
69 dhcpMgr = dhcpManager;
72 //TODO: Handle this in a separate thread
74 public void onPacketReceived(PacketReceived packet) {
75 LOG.trace("Pkt received: {}", packet);
76 Class<? extends PacketInReason> pktInReason = packet.getPacketInReason();
77 short tableId = packet.getTableId().getValue();
78 if (isPktInReasonSendtoCtrl(pktInReason) && ((DHCPMConstants.DHCP_TABLE == tableId))) {
79 byte[] inPayload = packet.getPayload();
80 Ethernet ethPkt = new Ethernet();
82 ethPkt.deserialize(inPayload, 0, inPayload.length * NetUtils.NumBitsInAByte);
83 } catch (Exception e) {
84 LOG.warn("Failed to decode DHCP Packet", e);
88 DHCP pktIn = getDhcpPktIn(ethPkt);
89 LOG.trace("DHCPPkt received: {}", pktIn);
91 NodeConnectorRef inNcRef = packet.getIngress();
92 FlowCapableNodeConnector fcNc = this.getFlowCapableNodeConnector(inNcRef);
93 DHCP replyPkt = handleDhcpPacket(pktIn, fcNc);
94 byte[] pktOut = getDhcpPacketOut(replyPkt, ethPkt, fcNc);
95 sendPacketOut(pktOut, inNcRef);
97 } catch (Exception e) {
98 LOG.warn("Failed to get DHCP Reply", e);
103 private void sendPacketOut(byte[] pktOut, NodeConnectorRef ingress) {
104 // We go out the same port we came in on
105 InstanceIdentifier<Node> egressNodePath = getNodePath(ingress.getValue());
106 TransmitPacketInput input = new TransmitPacketInputBuilder()
107 .setPayload(pktOut).setNode(new NodeRef(egressNodePath))
108 .setEgress(ingress).build();
109 LOG.trace("Transmitting packet: {}",input);
110 this.pktService.transmitPacket(input);
113 private InstanceIdentifier<Node> getNodePath(InstanceIdentifier<?> nodeInstanceId) {
114 return nodeInstanceId.firstIdentifierOf(Node.class);
117 private DHCP handleDhcpPacket(DHCP dhcpPkt, FlowCapableNodeConnector fcNc) {
118 LOG.debug("DHCP pkt rcvd {}", dhcpPkt);
119 byte msgType = dhcpPkt.getMsgType();
120 if (msgType == DHCPConstants.MSG_DECLINE) {
121 LOG.debug("DHCPDECLINE received");
123 } else if (msgType == DHCPConstants.MSG_RELEASE) {
124 LOG.debug("DHCPRELEASE received");
128 Port nPort = getNeutronPort(fcNc);
129 Subnet nSubnet = getNeutronSubnet(nPort);
130 DhcpInfo dhcpInfo = getDhcpInfo(nPort, nSubnet);
131 LOG.trace("NeutronPort: {} \n NeutronSubnet: {}, dhcpInfo{}",nPort, nSubnet, dhcpInfo);
133 if (dhcpInfo != null) {
134 if (msgType == DHCPConstants.MSG_DISCOVER) {
135 reply = getReplyToDiscover(dhcpPkt, dhcpInfo);
136 } else if (msgType == DHCPConstants.MSG_REQUEST) {
137 reply = getReplyToRequest(dhcpPkt, dhcpInfo);
144 private DhcpInfo getDhcpInfo(Port nPort, Subnet nSubnet) {
145 DhcpInfo dhcpInfo = null;
146 if( (nPort != null) && (nSubnet != null) ) {
147 String clientIp = nPort.getFixedIps().get(0).getIpAddress().getIpv4Address().getValue();
148 String serverIp = nSubnet.getGatewayIp().getIpv4Address().getValue();
149 List<IpAddress> dnsServers = nSubnet.getDnsNameservers();
150 dhcpInfo = new DhcpInfo();
151 dhcpInfo.setClientIp(clientIp).setServerIp(serverIp)
152 .setCidr(nSubnet.getCidr()).setHostRoutes(nSubnet.getHostRoutes())
153 .setDnsServersIpAddrs(dnsServers).setGatewayIp(serverIp);
155 //FIXME: Delete this test code
156 LOG.error("TestOnly Code");
157 dhcpInfo = new DhcpInfo();
158 dhcpInfo.setClientIp("1.1.1.3").setServerIp("1.1.1.1")
159 .setCidr("1.1.1.0/24").addDnsServer("1.1.1.1");
160 LOG.warn("Failed to get Subnet info for DHCP reply");
165 private Subnet getNeutronSubnet(Port nPort) {
166 return dhcpMgr.getNeutronSubnet(nPort);
169 private Port getNeutronPort(FlowCapableNodeConnector fcNc) {
170 return dhcpMgr.getNeutronPort(fcNc.getName());
173 private FlowCapableNodeConnector getFlowCapableNodeConnector(NodeConnectorRef inNcRef) {
174 InstanceIdentifier<NodeConnector> ncId = inNcRef.getValue().firstIdentifierOf(NodeConnector.class);
175 Optional<NodeConnector> nodeConnector =
176 MDSALDataStoreUtils.read(dataBroker, LogicalDatastoreType.OPERATIONAL, ncId);
177 if(nodeConnector.isPresent()) {
178 NodeConnector nc = nodeConnector.get();
179 LOG.trace("Incoming pkt's NodeConnector: {}", nc);
180 FlowCapableNodeConnector fcnc = nc.getAugmentation(FlowCapableNodeConnector.class);
186 private DHCP getDhcpPktIn(Ethernet ethPkt) {
187 if (ethPkt.getPayload() instanceof IPv4) {
188 IPv4 ipPkt = (IPv4) ethPkt.getPayload();
189 if (ipPkt.getPayload() instanceof UDP) {
190 UDP udpPkt = (UDP) ipPkt.getPayload();
191 if ((udpPkt.getSourcePort() == DHCPMConstants.dhcpClientPort)
192 && (udpPkt.getDestinationPort() == DHCPMConstants.dhcpServerPort)) {
193 byte[] rawDhcpPayload = udpPkt.getRawPayload();
194 DHCP reply = new DHCP();
196 reply.deserialize(rawDhcpPayload, 0, rawDhcpPayload.length);
197 } catch (PacketException e) {
198 LOG.warn("Failed to deserialize DHCP pkt", e);
208 DHCP getReplyToDiscover(DHCP dhcpPkt, DhcpInfo dhcpInfo) {
209 DHCP reply = new DHCP();
210 reply.setOp(DHCPConstants.BOOTREPLY);
211 reply.setHtype(dhcpPkt.getHtype());
212 reply.setHlen(dhcpPkt.getHlen());
213 reply.setHops((byte) 0);
214 reply.setXid(dhcpPkt.getXid());
215 reply.setSecs((short) 0);
217 reply.setYiaddr(dhcpInfo.getClientIp());
218 reply.setSiaddr(dhcpInfo.getServerIp());
220 reply.setFlags(dhcpPkt.getFlags());
221 reply.setGiaddr(dhcpPkt.getGiaddr());
222 reply.setChaddr(dhcpPkt.getChaddr());
224 reply.setMsgType(DHCPConstants.MSG_OFFER);
225 if(dhcpPkt.containsOption(DHCPConstants.OPT_PARAMETER_REQUEST_LIST)) {
226 setParameterListOptions(dhcpPkt, reply, dhcpInfo);
228 setCommonOptions(reply, dhcpInfo);
232 DHCP getReplyToRequest(DHCP dhcpPkt, DhcpInfo dhcpInfo) {
233 boolean sendAck = false;
234 byte[] requestedIp = null;
235 DHCP reply = new DHCP();
236 reply.setOp(DHCPConstants.BOOTREPLY);
237 reply.setHtype(dhcpPkt.getHtype());
238 reply.setHlen(dhcpPkt.getHlen());
239 reply.setHops((byte) 0);
240 reply.setXid(dhcpPkt.getXid());
241 reply.setSecs((short) 0);
243 reply.setFlags(dhcpPkt.getFlags());
244 reply.setGiaddr(dhcpPkt.getGiaddr());
245 reply.setChaddr(dhcpPkt.getChaddr());
246 byte[] allocatedIp = DHCPUtils.strAddrToByteArray(dhcpInfo.getClientIp());
247 if(Arrays.equals(allocatedIp, dhcpPkt.getCiaddr())) {
248 //This means a renew request
251 requestedIp = dhcpPkt.getOptionBytes(DHCPConstants.OPT_REQUESTED_ADDRESS);
252 sendAck = Arrays.equals(allocatedIp, requestedIp);
256 reply.setCiaddr(dhcpPkt.getCiaddr());
257 reply.setYiaddr(dhcpInfo.getClientIp());
258 reply.setSiaddr(dhcpInfo.getServerIp());
259 reply.setMsgType(DHCPConstants.MSG_ACK);
260 if(dhcpPkt.containsOption(DHCPConstants.OPT_PARAMETER_REQUEST_LIST)) {
261 setParameterListOptions(dhcpPkt, reply, dhcpInfo);
263 setCommonOptions(reply, dhcpInfo);
266 reply.setMsgType(DHCPConstants.MSG_NAK);
271 protected byte[] getDhcpPacketOut(DHCP reply, Ethernet etherPkt, FlowCapableNodeConnector fcNc) {
274 * DECLINE or RELEASE don't result in reply packet
278 LOG.debug("Sending DHCP Pkt {}", reply);
280 UDP udpPkt = new UDP();
283 rawPkt = reply.serialize();
284 } catch (PacketException e2) {
285 // TODO Auto-generated catch block
286 e2.printStackTrace();
289 udpPkt.setRawPayload(rawPkt);
290 udpPkt.setDestinationPort(DHCPMConstants.dhcpClientPort);
291 udpPkt.setSourcePort(DHCPMConstants.dhcpServerPort);
292 udpPkt.setLength((short) (rawPkt.length + 8));
294 IPv4 ip4Reply = new IPv4();
296 rawPkt = udpPkt.serialize();
297 } catch (PacketException e) {
298 // TODO Auto-generated catch block
303 if(this.computeUdpChecksum) {
304 checkSum = computeChecksum(rawPkt, reply.getSiaddr(),
305 NetUtils.intToByteArray4(DHCPMConstants.BCAST_IP));
307 udpPkt.setChecksum(checkSum);
308 ip4Reply.setPayload(udpPkt);
309 ip4Reply.setProtocol(IPProtocols.UDP.byteValue());
310 ip4Reply.setSourceAddress(reply.getSiaddrAsInetAddr());
311 ip4Reply.setDestinationAddress(DHCPMConstants.BCAST_IP);
312 ip4Reply.setTotalLength((short) (rawPkt.length+20));
313 ip4Reply.setTtl((byte) 32);
314 // create Ethernet Frame
315 Ethernet ether = new Ethernet();
317 ether.setSourceMACAddress(getServerMacAddress(fcNc));
318 ether.setDestinationMACAddress(etherPkt.getSourceMACAddress());
319 ether.setEtherType(EtherTypes.IPv4.shortValue());
320 ether.setPayload(ip4Reply);
322 rawPkt = ether.serialize();
323 } catch (PacketException e) {
324 LOG.warn("Failed to serialize ethernet reply",e);
330 private byte[] getServerMacAddress(FlowCapableNodeConnector fcNc) {
331 // Should we return ControllerMac instead?
332 MacAddress macAddress = fcNc.getHardwareAddress();
333 return DHCPUtils.strMacAddrtoByteArray(macAddress.getValue());
336 public short computeChecksum(byte[] inData, byte[] srcAddr, byte[] destAddr) {
337 short checkSum = (short) 0;
338 int sum = 0, carry = 0;
341 for (i = 0; i < inData.length - 1; i = i + 2) {
342 // Skip, if the current bytes are checkSum bytes
343 wordData = ((inData[i] << 8) & 0xFF00) + (inData[i + 1] & 0xFF);
344 sum = sum + wordData;
347 if (i < inData.length) {
348 wordData = ((inData[i] << 8) & 0xFF00) + (0 & 0xFF);
349 sum = sum + wordData;
352 for (i = 0; i < 4; i = i + 2) {
353 wordData = ((srcAddr[i] << 8) & 0xFF00) + (srcAddr[i + 1] & 0xFF);
354 sum = sum + wordData;
357 for (i = 0; i < 4; i = i + 2) {
358 wordData = ((destAddr[i] << 8) & 0xFF00) + (destAddr[i + 1] & 0xFF);
359 sum = sum + wordData;
361 sum = sum + 17 + inData.length;
363 while((sum >> 16) != 0) {
365 sum = (sum & 0xFFFF)+ carry;
367 checkSum = (short) ~((short) sum & 0xFFFF);
369 checkSum = (short)0xffff;
374 private void setCommonOptions(DHCP pkt, DhcpInfo dhcpInfo) {
375 pkt.setOptionInt(DHCPConstants.OPT_LEASE_TIME, dhcpMgr.getDhcpLeaseTime());
376 if (dhcpMgr.getDhcpDefDomain() != null) {
377 pkt.setOptionString(DHCPConstants.OPT_DOMAIN_NAME, dhcpMgr.getDhcpDefDomain());
379 if(dhcpMgr.getDhcpLeaseTime() > 0) {
380 pkt.setOptionInt(DHCPConstants.OPT_REBINDING_TIME, dhcpMgr.getDhcpRebindingTime());
381 pkt.setOptionInt(DHCPConstants.OPT_RENEWAL_TIME, dhcpMgr.getDhcpRenewalTime());
383 SubnetUtils util = null;
384 SubnetInfo info = null;
385 util = new SubnetUtils(dhcpInfo.getCidr());
386 info = util.getInfo();
387 String gwIp = dhcpInfo.getGatewayIp();
388 List<String> dnServers = dhcpInfo.getDnsServers();
391 * setParameterListOptions may have initialized some of these
392 * options to maintain order. If we can't fill them, unset to avoid
393 * sending wrong information in reply.
396 pkt.setOptionInetAddr(DHCPConstants.OPT_SERVER_IDENTIFIER, gwIp);
397 pkt.setOptionInetAddr(DHCPConstants.OPT_ROUTERS, gwIp);
399 pkt.unsetOption(DHCPConstants.OPT_SERVER_IDENTIFIER);
400 pkt.unsetOption(DHCPConstants.OPT_ROUTERS);
403 pkt.setOptionInetAddr(DHCPConstants.OPT_SUBNET_MASK, info.getNetmask());
404 pkt.setOptionInetAddr(DHCPConstants.OPT_BROADCAST_ADDRESS, info.getBroadcastAddress());
406 pkt.unsetOption(DHCPConstants.OPT_SUBNET_MASK);
407 pkt.unsetOption(DHCPConstants.OPT_BROADCAST_ADDRESS);
409 if ((dnServers != null) && (dnServers.size() > 0)) {
410 pkt.setOptionStrAddrs(DHCPConstants.OPT_DOMAIN_NAME_SERVERS, dnServers);
412 pkt.unsetOption(DHCPConstants.OPT_DOMAIN_NAME_SERVERS);
414 } catch (UnknownHostException e) {
415 // TODO Auto-generated catch block
420 private void setParameterListOptions(DHCP req, DHCP reply, DhcpInfo dhcpInfo) {
421 byte[] paramList = req.getOptionBytes(DHCPConstants.OPT_PARAMETER_REQUEST_LIST);
422 for(int i = 0; i < paramList.length; i++) {
423 switch (paramList[i]) {
424 case DHCPConstants.OPT_SUBNET_MASK:
425 case DHCPConstants.OPT_ROUTERS:
426 case DHCPConstants.OPT_SERVER_IDENTIFIER:
427 case DHCPConstants.OPT_DOMAIN_NAME_SERVERS:
428 case DHCPConstants.OPT_BROADCAST_ADDRESS:
429 case DHCPConstants.OPT_LEASE_TIME:
430 case DHCPConstants.OPT_RENEWAL_TIME:
431 case DHCPConstants.OPT_REBINDING_TIME:
432 /* These values will be filled in setCommonOptions
433 * Setting these just to preserve order as
434 * specified in PARAMETER_REQUEST_LIST.
436 reply.setOptionInt(paramList[i], 0);
438 case DHCPConstants.OPT_DOMAIN_NAME:
439 reply.setOptionString(paramList[i], " ");
441 case DHCPConstants.OPT_CLASSLESS_ROUTE:
442 setOptionClasslessRoute(reply, dhcpInfo);
445 LOG.debug("DHCP Option code {} not supported yet", paramList[i]);
450 private void setOptionClasslessRoute(DHCP reply, DhcpInfo dhcpInfo) {
451 List<HostRoutes> hostRoutes = dhcpInfo.getHostRoutes();
452 if(hostRoutes == null) {
453 //we can't set this option, so return
456 ByteArrayOutputStream result = new ByteArrayOutputStream();
457 Iterator<HostRoutes> iter = hostRoutes.iterator();
458 while(iter.hasNext()) {
459 HostRoutes hostRoute = iter.next();
460 if(hostRoute.getNexthop().getIpv4Address() == null ||
461 hostRoute.getDestination().getIpv4Prefix() == null ) {
462 // we only deal with IPv4 addresses
465 String router = hostRoute.getNexthop().getIpv4Address().getValue();
466 String dest = hostRoute.getDestination().getIpv4Prefix().getValue();
468 result.write(convertToClasslessRouteOption(dest, router));
469 } catch (IOException | NullPointerException e) {
470 LOG.debug("Exception {}",e.getMessage());
473 if (result.size() > 0) {
474 reply.setOptionBytes(DHCPConstants.OPT_CLASSLESS_ROUTE , result.toByteArray());
478 protected byte[] convertToClasslessRouteOption(String dest, String router) {
479 ByteArrayOutputStream bArr = new ByteArrayOutputStream();
487 String[] parts = dest.split("/");
488 if (parts.length < 2) {
489 prefix = new Short((short)0);
491 prefix = Short.valueOf(parts[1]);
494 bArr.write(prefix.byteValue());
495 SubnetUtils util = new SubnetUtils(dest);
496 SubnetInfo info = util.getInfo();
497 String strNetAddr = info.getNetworkAddress();
499 byte[] netAddr = InetAddress.getByName(strNetAddr).getAddress();
500 //Strip any trailing 0s from netAddr
501 for(int i = 0; i < netAddr.length;i++) {
502 if(netAddr[i] != 0) {
503 bArr.write(netAddr,i,1);
506 bArr.write(InetAddress.getByName(router).getAddress());
507 } catch (IOException e) {
510 return bArr.toByteArray();
513 private boolean isPktInReasonSendtoCtrl(Class<? extends PacketInReason> pktInReason) {
514 return (pktInReason == SendToController.class);
518 public void close() throws Exception {
519 // TODO Auto-generated method stub
522 public void setPacketProcessingService(PacketProcessingService packetService) {
523 this.pktService = packetService;