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