blob: 5eb71b682164fce609c0cffa0251c0f5fb228dd0 [file] [log] [blame]
Kalhee Kim495c9b22017-11-07 16:32:09 +00001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17package org.onosproject.dhcprelay;
18
19import org.onlab.packet.BasePacket;
20import org.onlab.packet.DHCP6;
21import org.onlab.packet.DHCP6.MsgType;
22import org.onlab.packet.Ip6Address;
23import org.onlab.packet.IpAddress;
24import org.onlab.packet.VlanId;
25import org.onlab.packet.dhcp.Dhcp6RelayOption;
26import org.onlab.packet.dhcp.Dhcp6Option;
27
28import org.onlab.packet.Ethernet;
29import org.onlab.packet.IPv6;
30import org.onlab.packet.MacAddress;
31import org.onlab.packet.UDP;
32
33import org.onlab.util.HexString;
34import org.onosproject.dhcprelay.api.DhcpServerInfo;
35import org.onosproject.net.ConnectPoint;
36import org.onosproject.net.host.InterfaceIpAddress;
37import org.onosproject.net.intf.Interface;
38import org.onosproject.net.packet.PacketContext;
39import org.onosproject.net.DeviceId;
40
41import org.slf4j.Logger;
42import org.slf4j.LoggerFactory;
43import java.util.Set;
44import java.util.List;
45import java.util.ArrayList;
46
47
48
49import static com.google.common.base.Preconditions.checkNotNull;
50
51
52
53public class Dhcp6HandlerUtil {
54
55 private final Logger log = LoggerFactory.getLogger(getClass());
56 // Returns the first v6 interface ip out of a set of interfaces or null.
57 // Checks all interfaces, and ignores v6 interface ips
58 public Ip6Address getRelayAgentIPv6Address(Set<Interface> intfs) {
59 for (Interface intf : intfs) {
60 for (InterfaceIpAddress ip : intf.ipAddressesList()) {
61 Ip6Address relayAgentIp = ip.ipAddress().getIp6Address();
62 if (relayAgentIp != null) {
63 return relayAgentIp;
64 }
65 }
66 }
67 return null;
68 }
69
70 /**
71 * Returns the first interface ip from interface.
72 *
73 * @param iface interface of one connect point
74 * @return the first interface IP; null if not exists an IP address in
75 * these interfaces
76 */
77 public Ip6Address getFirstIpFromInterface(Interface iface) {
78 checkNotNull(iface, "Interface can't be null");
79 return iface.ipAddressesList().stream()
80 .map(InterfaceIpAddress::ipAddress)
81 .filter(IpAddress::isIp6)
82 .map(IpAddress::getIp6Address)
83 .findFirst()
84 .orElse(null);
85 }
86
87 /**
88 * extract DHCP6 payload from dhcp6 relay message within relay-forwrd/reply.
89 *
90 * @param dhcp6 dhcp6 relay-reply or relay-foward
91 * @return dhcp6Packet dhcp6 packet extracted from relay-message
92 */
93 public DHCP6 dhcp6PacketFromRelayPacket(DHCP6 dhcp6) {
94
95 // extract the relay message if exist
96 DHCP6 dhcp6Payload = dhcp6.getOptions().stream()
97 .filter(opt -> opt instanceof Dhcp6RelayOption)
98 .map(BasePacket::getPayload)
99 .map(pld -> (DHCP6) pld)
100 .findFirst()
101 .orElse(null);
102 if (dhcp6Payload == null) {
103 // Can't find dhcp payload
104 log.debug("Can't find dhcp6 payload from relay message");
105 } else {
106 log.debug("dhcp6 payload found from relay message {}", dhcp6Payload);
107 }
108 return dhcp6Payload;
109 }
110
111 /**
112 * find the leaf DHCP6 packet from multi-level relay packet.
113 *
114 * @param relayPacket dhcp6 relay packet
115 * @return leafPacket non-relay dhcp6 packet
116 */
117 public DHCP6 getDhcp6Leaf(DHCP6 relayPacket) {
118 DHCP6 dhcp6Parent = relayPacket;
119 DHCP6 dhcp6Child = null;
120
121 log.debug("getDhcp6Leaf entered.");
122 while (dhcp6Parent != null) {
123 dhcp6Child = dhcp6PacketFromRelayPacket(dhcp6Parent);
124 if (dhcp6Child != null) {
125 if (dhcp6Child.getMsgType() != DHCP6.MsgType.RELAY_FORW.value() &&
126 dhcp6Child.getMsgType() != DHCP6.MsgType.RELAY_REPL.value()) {
127 log.debug("leaf dhcp6 packet found.");
128 break;
129 } else {
130 // found another relay, go for another loop
131 dhcp6Parent = dhcp6Child;
132 }
133 } else {
134 log.debug("Expected dhcp6 within relay pkt, but no dhcp6 leaf found.");
135 break;
136 }
137 }
138 return dhcp6Child;
139 }
140
141 /**
142 * check if DHCP6 relay-reply is reply.
143 *
144 * @param relayPacket dhcp6 relay-reply
145 * @return boolean relay-reply contains ack
146 */
147 public boolean isDhcp6Reply(DHCP6 relayPacket) {
148 DHCP6 leafDhcp6 = getDhcp6Leaf(relayPacket);
149 if (leafDhcp6 != null) {
150 if (leafDhcp6.getMsgType() == DHCP6.MsgType.REPLY.value()) {
151 log.debug("isDhcp6Reply true.");
152 return true; // must be directly connected
153 } else {
154 log.debug("isDhcp6Reply false. leaf dhcp6 is not replay. MsgType {}", leafDhcp6.getMsgType());
155 }
156 } else {
157 log.debug("isDhcp6Reply false. Expected dhcp6 within relay pkt but not found.");
158 }
159 log.debug("isDhcp6Reply false.");
160 return false;
161 }
162
163 /**
164 * check if DHCP6 is release or relay-forward contains release.
165 *
166 * @param dhcp6Payload dhcp6 packet
167 * @return boolean dhcp6 contains release
168 */
169 public boolean isDhcp6Release(DHCP6 dhcp6Payload) {
170 if (dhcp6Payload.getMsgType() == DHCP6.MsgType.RELEASE.value()) {
171 log.debug("isDhcp6Release true.");
172 return true; // must be directly connected
173 } else {
174 DHCP6 dhcp6Leaf = getDhcp6Leaf(dhcp6Payload);
175 if (dhcp6Leaf != null) {
176 if (dhcp6Leaf.getMsgType() == DHCP6.MsgType.RELEASE.value()) {
177 log.debug("isDhcp6Release true. indirectlry connected");
178 return true;
179 } else {
180 log.debug("leaf dhcp6 is not release. MsgType {}", dhcp6Leaf.getMsgType());
181 return false;
182 }
183 } else {
184 log.debug("isDhcp6Release false. dhcp6 is niether relay nor release.");
185 return false;
186 }
187 }
188 }
189
190
191 /**
192 * convert dhcp6 msgType to String.
193 *
194 * @param msgTypeVal msgType byte of dhcp6 packet
195 * @return String string value of dhcp6 msg type
196 */
197 public String getMsgTypeStr(byte msgTypeVal) {
198 MsgType msgType = DHCP6.MsgType.getType(msgTypeVal);
199 return DHCP6.MsgType.getMsgTypeStr(msgType);
200 }
201
202 /**
203 * find the string of dhcp6 leaf packets's msg type.
204 *
205 * @param directConnFlag boolean value indicating direct/indirect connection
206 * @param dhcp6Packet dhcp6 packet
207 * @return String string value of dhcp6 leaf packet msg type
208 */
209 public String findLeafMsgType(boolean directConnFlag, DHCP6 dhcp6Packet) {
210 if (directConnFlag) {
211 return getMsgTypeStr(dhcp6Packet.getMsgType());
212 } else {
213 DHCP6 leafDhcp = getDhcp6Leaf(dhcp6Packet);
214 if (leafDhcp != null) {
215 return getMsgTypeStr(leafDhcp.getMsgType());
216 } else {
217 return "INVALID"; //DhcpRelayCounters.INVALID_PACKET;
218 }
219 }
220 }
221
222 /**
223 * find the string of dhcp6 leaf packets's msg type.
224 *
225 * @param fromClient indicate from what side a packet is received
226 * @param directConnFlag boolean value indicating direct/indirect connection
227 * @param dhcp6Packet dhcp6 packet
228 * @return String string value of dhcp6 leaf packet msg type
229 */
230 public String findMsgType(boolean fromClient, boolean directConnFlag, DHCP6 dhcp6Packet) {
231 if (fromClient) {
232 return findLeafMsgType(directConnFlag, dhcp6Packet);
233 } else {
234 DHCP6 embeddedDhcp6 = dhcp6Packet.getOptions().stream()
235 .filter(opt -> opt instanceof Dhcp6RelayOption)
236 .map(BasePacket::getPayload)
237 .map(pld -> (DHCP6) pld)
238 .findFirst()
239 .orElse(null);
240 if (embeddedDhcp6 != null) {
241 return findLeafMsgType(directConnFlag, embeddedDhcp6);
242 } else {
243 return "INVALID"; //DhcpRelayCounters.INVALID_PACKET;
244 }
245 }
246 }
247
248 /**
249 * Determind if an Interface contains a vlan id.
250 *
251 * @param iface the Interface
252 * @param vlanId the vlan id
253 * @return true if the Interface contains the vlan id
254 */
255 public boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
256 if (vlanId.equals(VlanId.NONE)) {
257 // untagged packet, check if vlan untagged or vlan native is not NONE
258 return !iface.vlanUntagged().equals(VlanId.NONE) ||
259 !iface.vlanNative().equals(VlanId.NONE);
260 }
261 // tagged packet, check if the interface contains the vlan
262 return iface.vlanTagged().contains(vlanId);
263 }
264
265 /**
266 * the new class the contains Ethernet packet and destination port.
267 */
268 public class InternalPacket {
269 Ethernet packet;
270 ConnectPoint destLocation;
271 public InternalPacket(Ethernet newPacket, ConnectPoint newLocation) {
272 packet = newPacket;
273 destLocation = newLocation;
274 }
275 void setLocation(ConnectPoint newLocation) {
276 destLocation = newLocation;
277 }
278 }
279 /**
280 * Check if the host is directly connected to the network or not.
281 *
282 * @param dhcp6Payload the dhcp6 payload
283 * @return true if the host is directly connected to the network; false otherwise
284 */
285 public boolean directlyConnected(DHCP6 dhcp6Payload) {
286 log.debug("directlyConnected enters");
287
288 if (dhcp6Payload.getMsgType() != DHCP6.MsgType.RELAY_FORW.value() &&
289 dhcp6Payload.getMsgType() != DHCP6.MsgType.RELAY_REPL.value()) {
290 log.debug("directlyConnected true. MsgType {}", dhcp6Payload.getMsgType());
291
292 return true;
293 }
294 // Regardless of relay-forward or relay-replay, check if we see another relay message
295 DHCP6 dhcp6Payload2 = dhcp6PacketFromRelayPacket(dhcp6Payload);
296 if (dhcp6Payload2 != null) {
297 if (dhcp6Payload.getMsgType() == DHCP6.MsgType.RELAY_FORW.value()) {
298 log.debug("directlyConnected false. 1st realy-foward, 2nd MsgType {}", dhcp6Payload2.getMsgType());
299 return false;
300 } else {
301 // relay-reply
302 if (dhcp6Payload2.getMsgType() != DHCP6.MsgType.RELAY_REPL.value()) {
303 log.debug("directlyConnected true. 2nd MsgType {}", dhcp6Payload2.getMsgType());
304 return true; // must be directly connected
305 } else {
306 log.debug("directlyConnected false. 1st relay-reply, 2nd relay-reply MsgType {}",
307 dhcp6Payload2.getMsgType());
308 return false; // must be indirectly connected
309 }
310 }
311 } else {
312 log.debug("directlyConnected true.");
313 return true;
314 }
315 }
316 /**
317 * Check if a given server info has v6 ipaddress.
318 *
319 * @param serverInfo server info to check
320 * @return true if server info has v6 ip address; false otherwise
321 */
322 public boolean isServerIpEmpty(DhcpServerInfo serverInfo) {
323 if (!serverInfo.getDhcpServerIp6().isPresent()) {
324 log.warn("DhcpServerIp not available, use default DhcpServerIp {}",
325 HexString.toHexString(serverInfo.getDhcpServerIp6().get().toOctets()));
326 return true;
327 }
328 return false;
329 }
330
331 private boolean isConnectMacEmpty(DhcpServerInfo serverInfo, Set<Interface> clientInterfaces) {
332 if (!serverInfo.getDhcpConnectMac().isPresent()) {
333 log.warn("DHCP6 {} not yet resolved .. Aborting DHCP "
334 + "packet processing from client on port: {}",
335 !serverInfo.getDhcpGatewayIp6().isPresent() ? "server IP " + serverInfo.getDhcpServerIp6()
336 : "gateway IP " + serverInfo.getDhcpGatewayIp6(),
337 clientInterfaces.iterator().next().connectPoint());
338 return true;
339 }
340 return false;
341 }
342
343 private boolean isRelayAgentIpFromCfgEmpty(DhcpServerInfo serverInfo, DeviceId receivedFromDevice) {
344 if (!serverInfo.getRelayAgentIp6(receivedFromDevice).isPresent()) {
345 log.warn("indirect connection: relayAgentIp NOT availale from config file! Use dynamic.");
346 return true;
347 }
348 return false;
349 }
350
351 private Dhcp6Option getInterfaceIdIdOption(PacketContext context, Ethernet clientPacket) {
352 String inPortString = "-" + context.inPacket().receivedFrom().toString() + ":";
353 Dhcp6Option interfaceId = new Dhcp6Option();
354 interfaceId.setCode(DHCP6.OptionCode.INTERFACE_ID.value());
355 byte[] clientSoureMacBytes = clientPacket.getSourceMACAddress();
356 byte[] inPortStringBytes = inPortString.getBytes();
357 byte[] vlanIdBytes = new byte[2];
358 vlanIdBytes[0] = (byte) (clientPacket.getVlanID() & 0xff);
359 vlanIdBytes[1] = (byte) ((clientPacket.getVlanID() >> 8) & 0xff);
360 byte[] interfaceIdBytes = new byte[clientSoureMacBytes.length +
361 inPortStringBytes.length + vlanIdBytes.length];
362 log.debug("Length: interfaceIdBytes {} clientSoureMacBytes {} inPortStringBytes {} vlan {}",
363 interfaceIdBytes.length, clientSoureMacBytes.length, inPortStringBytes.length,
364 vlanIdBytes.length);
365
366 System.arraycopy(clientSoureMacBytes, 0, interfaceIdBytes, 0, clientSoureMacBytes.length);
367 System.arraycopy(inPortStringBytes, 0, interfaceIdBytes, clientSoureMacBytes.length,
368 inPortStringBytes.length);
369 System.arraycopy(vlanIdBytes, 0, interfaceIdBytes,
370 clientSoureMacBytes.length + inPortStringBytes.length,
371 vlanIdBytes.length);
372 interfaceId.setData(interfaceIdBytes);
373 interfaceId.setLength((short) interfaceIdBytes.length);
374 log.debug("interfaceId write srcMac {} portString {}",
375 HexString.toHexString(clientSoureMacBytes, ":"), inPortString);
376 return interfaceId;
377 }
378
379 private void addDhcp6OptionsFromClient(List<Dhcp6Option> options, byte[] dhcp6PacketByte,
380 PacketContext context, Ethernet clientPacket) {
381 Dhcp6Option relayMessage = new Dhcp6Option();
382 relayMessage.setCode(DHCP6.OptionCode.RELAY_MSG.value());
383 relayMessage.setLength((short) dhcp6PacketByte.length);
384 relayMessage.setData(dhcp6PacketByte);
385 options.add(relayMessage);
386 // create interfaceId option
387 Dhcp6Option interfaceId = getInterfaceIdIdOption(context, clientPacket);
388 options.add(interfaceId);
389 }
390
391 /**
392 * build the DHCP6 solicit/request packet with gatewayip.
393 *
394 * @param context packet context
395 * @param clientPacket client ethernet packet
396 * @param clientInterfaces set of client side interfaces
397 * @param serverInfo target server which a packet is generated for
398 * @param serverInterface target server interface
399 * @return ethernet packet with dhcp6 packet info
400 */
401 public Ethernet buildDhcp6PacketFromClient(PacketContext context, Ethernet clientPacket,
402 Set<Interface> clientInterfaces, DhcpServerInfo serverInfo,
403 Interface serverInterface) {
404 ConnectPoint receivedFrom = context.inPacket().receivedFrom();
405 DeviceId receivedFromDevice = receivedFrom.deviceId();
406
407 Ip6Address relayAgentIp = getRelayAgentIPv6Address(clientInterfaces);
408 MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
409 if (relayAgentIp == null || relayAgentMac == null) {
410 log.warn("Missing DHCP relay agent interface Ipv6 addr config for "
411 + "packet from client on port: {}. Aborting packet processing",
412 clientInterfaces.iterator().next().connectPoint());
413 return null;
414 }
415 IPv6 clientIpv6 = (IPv6) clientPacket.getPayload();
416 UDP clientUdp = (UDP) clientIpv6.getPayload();
417 DHCP6 clientDhcp6 = (DHCP6) clientUdp.getPayload();
418 boolean directConnFlag = directlyConnected(clientDhcp6);
419
420 Ip6Address serverIpFacing = getFirstIpFromInterface(serverInterface);
421 if (serverIpFacing == null || serverInterface.mac() == null) {
422 log.warn("No IP v6 address for server Interface {}", serverInterface);
423 return null;
424 }
425
426 Ethernet etherReply = clientPacket.duplicate();
427 etherReply.setSourceMACAddress(serverInterface.mac());
428
429 // set default info and replace with indirect if available later on.
430 if (serverInfo.getDhcpConnectMac().isPresent()) {
431 etherReply.setDestinationMACAddress(serverInfo.getDhcpConnectMac().get());
432 }
433 if (serverInfo.getDhcpConnectVlan().isPresent()) {
434 etherReply.setVlanID(serverInfo.getDhcpConnectVlan().get().toShort());
435 }
436 IPv6 ipv6Packet = (IPv6) etherReply.getPayload();
437 byte[] peerAddress = clientIpv6.getSourceAddress();
438 ipv6Packet.setSourceAddress(serverIpFacing.toOctets());
439 ipv6Packet.setDestinationAddress(serverInfo.getDhcpServerIp6().get().toOctets());
440 UDP udpPacket = (UDP) ipv6Packet.getPayload();
441 udpPacket.setSourcePort(UDP.DHCP_V6_SERVER_PORT);
442 DHCP6 dhcp6Packet = (DHCP6) udpPacket.getPayload();
443 byte[] dhcp6PacketByte = dhcp6Packet.serialize();
444
445 DHCP6 dhcp6Relay = new DHCP6();
446
447 dhcp6Relay.setMsgType(DHCP6.MsgType.RELAY_FORW.value());
448
449 if (directConnFlag) {
450 dhcp6Relay.setLinkAddress(relayAgentIp.toOctets());
451 } else {
452 if (isServerIpEmpty(serverInfo)) {
453 log.warn("indirect DhcpServerIp empty... use default server ");
454 } else {
455 // Indirect case, replace destination to indirect dhcp server if exist
456 // Check if mac is obtained for valid server ip
457 if (isConnectMacEmpty(serverInfo, clientInterfaces)) {
458 log.warn("indirect Dhcp ConnectMac empty ...");
459 return null;
460 }
461 etherReply.setDestinationMACAddress(serverInfo.getDhcpConnectMac().get());
462 etherReply.setVlanID(serverInfo.getDhcpConnectVlan().get().toShort());
463 ipv6Packet.setDestinationAddress(serverInfo.getDhcpServerIp6().get().toOctets());
464 }
465 if (isRelayAgentIpFromCfgEmpty(serverInfo, receivedFromDevice)) {
466 dhcp6Relay.setLinkAddress(relayAgentIp.toOctets());
467 log.debug("indirect connection: relayAgentIp NOT availale from config file! Use dynamic. {}",
468 HexString.toHexString(relayAgentIp.toOctets(), ":"));
469 } else {
470 dhcp6Relay.setLinkAddress(serverInfo.getRelayAgentIp6(receivedFromDevice).get().toOctets());
471 }
472 }
473 // peer address: address of the client or relay agent from which the message to be relayed was received.
474 dhcp6Relay.setPeerAddress(peerAddress);
475 // directly connected case, hop count is zero; otherwise, hop count + 1
476 if (directConnFlag) {
477 dhcp6Relay.setHopCount((byte) 0);
478 } else {
479 dhcp6Relay.setHopCount((byte) (dhcp6Packet.getHopCount() + 1));
480 }
481
482 List<Dhcp6Option> options = new ArrayList<>();
483 addDhcp6OptionsFromClient(options, dhcp6PacketByte, context, clientPacket);
484 dhcp6Relay.setOptions(options);
485 udpPacket.setPayload(dhcp6Relay);
486 udpPacket.resetChecksum();
487 ipv6Packet.setPayload(udpPacket);
488 ipv6Packet.setHopLimit((byte) 64);
489 etherReply.setPayload(ipv6Packet);
490
491 return etherReply;
492 }
493
494 /**
495 * build the DHCP6 solicit/request packet with gatewayip.
496 *
497 * @param directConnFlag flag indicating if packet is from direct client or not
498 * @param serverInfo server to check its connect point
499 * @return boolean true if serverInfo is found; false otherwise
500 */
501 public boolean checkDhcpServerConnPt(boolean directConnFlag,
502 DhcpServerInfo serverInfo) {
503 if (serverInfo.getDhcpServerConnectPoint() == null) {
504 log.warn("DHCP6 server connect point for {} connPt {}",
505 directConnFlag ? "direct" : "indirect", serverInfo.getDhcpServerConnectPoint());
506 return false;
507 }
508 return true;
509 }
510}