blob: 45e94c011fce24cc5f2726935cee516c28411dbe [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;
Charles Chan24a96ff2018-08-13 10:32:03 -070025import org.onlab.packet.dhcp.Dhcp6ClientIdOption;
Kalhee Kim495c9b22017-11-07 16:32:09 +000026import org.onlab.packet.dhcp.Dhcp6RelayOption;
27import org.onlab.packet.dhcp.Dhcp6Option;
28
29import org.onlab.packet.Ethernet;
30import org.onlab.packet.IPv6;
31import org.onlab.packet.MacAddress;
32import org.onlab.packet.UDP;
33
34import org.onlab.util.HexString;
35import org.onosproject.dhcprelay.api.DhcpServerInfo;
Kalhee Kimd94ceea2017-11-29 19:03:02 +000036import org.onosproject.dhcprelay.store.DhcpRelayCounters;
Kalhee Kim495c9b22017-11-07 16:32:09 +000037import org.onosproject.net.ConnectPoint;
38import org.onosproject.net.host.InterfaceIpAddress;
39import org.onosproject.net.intf.Interface;
40import org.onosproject.net.packet.PacketContext;
41import org.onosproject.net.DeviceId;
42
43import org.slf4j.Logger;
44import org.slf4j.LoggerFactory;
45import java.util.Set;
46import java.util.List;
47import java.util.ArrayList;
Lior Assoulinea21c0ca2018-01-28 16:18:48 -080048import org.onosproject.net.intf.InterfaceService;
Kalhee Kim495c9b22017-11-07 16:32:09 +000049
Lior Assoulinea21c0ca2018-01-28 16:18:48 -080050import org.onosproject.net.Host;
51import org.onosproject.net.host.HostService;
52import org.onosproject.net.HostLocation;
Kalhee Kim495c9b22017-11-07 16:32:09 +000053
54import static com.google.common.base.Preconditions.checkNotNull;
55
Taras Lemkin96a0d342018-03-26 14:52:58 +000056public final class Dhcp6HandlerUtil {
Kalhee Kim495c9b22017-11-07 16:32:09 +000057
Taras Lemkin96a0d342018-03-26 14:52:58 +000058 private static final Logger log = LoggerFactory.getLogger(Dhcp6HandlerUtil.class);
Kalhee Kim495c9b22017-11-07 16:32:09 +000059
Taras Lemkin96a0d342018-03-26 14:52:58 +000060 private Dhcp6HandlerUtil() {
61 }
Kalhee Kim495c9b22017-11-07 16:32:09 +000062
Kalhee Kim495c9b22017-11-07 16:32:09 +000063 // Returns the first v6 interface ip out of a set of interfaces or null.
64 // Checks all interfaces, and ignores v6 interface ips
Taras Lemkin96a0d342018-03-26 14:52:58 +000065 public static Ip6Address getRelayAgentIPv6Address(Set<Interface> intfs) {
Kalhee Kim495c9b22017-11-07 16:32:09 +000066 for (Interface intf : intfs) {
67 for (InterfaceIpAddress ip : intf.ipAddressesList()) {
68 Ip6Address relayAgentIp = ip.ipAddress().getIp6Address();
69 if (relayAgentIp != null) {
70 return relayAgentIp;
71 }
72 }
73 }
74 return null;
75 }
76
77 /**
Taras Lemkin96a0d342018-03-26 14:52:58 +000078 * Returns the first interface ip from interface.
79 *
80 * @param iface interface of one connect point
81 * @return the first interface IP; null if not exists an IP address in
82 * these interfaces
83 */
84 private static Ip6Address getFirstIpFromInterface(Interface iface) {
85 checkNotNull(iface, "Interface can't be null");
86 return iface.ipAddressesList().stream()
87 .map(InterfaceIpAddress::ipAddress)
88 .filter(IpAddress::isIp6)
89 .map(IpAddress::getIp6Address)
90 .findFirst()
91 .orElse(null);
92 }
93 /**
Lior Assoulinea21c0ca2018-01-28 16:18:48 -080094 *
95 * process the LQ reply packet from dhcp server.
96 *
97 * @param defaultServerInfoList default server list
98 * @param indirectServerInfoList default indirect server list
99 * @param serverInterface server interface
100 * @param interfaceService interface service
101 * @param hostService host service
102 * @param context packet context
103 * @param receivedPacket server ethernet packet
104 * @param recevingInterfaces set of server side interfaces
105 * @return a packet ready to be sent to relevant output interface
106 */
Taras Lemkin96a0d342018-03-26 14:52:58 +0000107 public static InternalPacket processLQ6PacketFromServer(
Lior Assoulinea21c0ca2018-01-28 16:18:48 -0800108 List<DhcpServerInfo> defaultServerInfoList,
109 List<DhcpServerInfo> indirectServerInfoList,
110 Interface serverInterface,
111 InterfaceService interfaceService,
112 HostService hostService,
113 PacketContext context,
114 Ethernet receivedPacket, Set<Interface> recevingInterfaces) {
115 // get dhcp6 header.
116 Ethernet etherReply = (Ethernet) receivedPacket.clone();
117 IPv6 ipv6Packet = (IPv6) etherReply.getPayload();
118 UDP udpPacket = (UDP) ipv6Packet.getPayload();
119 DHCP6 lq6Reply = (DHCP6) udpPacket.getPayload();
120
121 // TODO: refactor
122 ConnectPoint receivedFrom = context.inPacket().receivedFrom();
123 DeviceId receivedFromDevice = receivedFrom.deviceId();
124 DhcpServerInfo serverInfo;
125 Ip6Address dhcpServerIp = null;
126 ConnectPoint dhcpServerConnectPoint = null;
127 MacAddress dhcpConnectMac = null;
128 VlanId dhcpConnectVlan = null;
129 Ip6Address dhcpGatewayIp = null;
130
131 // todo: refactor
132 Ip6Address indirectDhcpServerIp = null;
133 ConnectPoint indirectDhcpServerConnectPoint = null;
134 MacAddress indirectDhcpConnectMac = null;
135 VlanId indirectDhcpConnectVlan = null;
136 Ip6Address indirectDhcpGatewayIp = null;
137 Ip6Address indirectRelayAgentIpFromCfg = null;
138
139 if (!defaultServerInfoList.isEmpty()) {
140 serverInfo = defaultServerInfoList.get(0);
141 dhcpConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
142 dhcpGatewayIp = serverInfo.getDhcpGatewayIp6().orElse(null);
143 dhcpServerIp = serverInfo.getDhcpServerIp6().orElse(null);
144 dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
145 dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
146 }
147
148 if (!indirectServerInfoList.isEmpty()) {
149 serverInfo = indirectServerInfoList.get(0);
150 indirectDhcpConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
151 indirectDhcpGatewayIp = serverInfo.getDhcpGatewayIp6().orElse(null);
152 indirectDhcpServerIp = serverInfo.getDhcpServerIp6().orElse(null);
153 indirectDhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
154 indirectDhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
155 indirectRelayAgentIpFromCfg = serverInfo.getRelayAgentIp6(receivedFromDevice).orElse(null);
156 }
157
158 Boolean directConnFlag = directlyConnected(lq6Reply);
159 ConnectPoint inPort = context.inPacket().receivedFrom();
Charles Chan3a15b282018-02-20 10:29:05 -0800160 if ((directConnFlag || indirectDhcpServerIp == null)
Lior Assoulinea21c0ca2018-01-28 16:18:48 -0800161 && !inPort.equals(dhcpServerConnectPoint)) {
162 log.warn("Receiving port {} is not the same as server connect point {} for direct or indirect-null",
Taras Lemkin96a0d342018-03-26 14:52:58 +0000163 inPort, dhcpServerConnectPoint);
Lior Assoulinea21c0ca2018-01-28 16:18:48 -0800164 return null;
165 }
166
167 if (!directConnFlag && indirectDhcpServerIp != null &&
168 !inPort.equals(indirectDhcpServerConnectPoint)) {
169 log.warn("Receiving port {} is not the same as server connect point {} for indirect",
Taras Lemkin96a0d342018-03-26 14:52:58 +0000170 inPort, indirectDhcpServerConnectPoint);
Lior Assoulinea21c0ca2018-01-28 16:18:48 -0800171 return null;
172 }
173
174
175 Ip6Address nextHopIP = Ip6Address.valueOf(ipv6Packet.getDestinationAddress());
176 // use hosts store to find out the next hop mac and connection point
177 Set<Host> hosts = hostService.getHostsByIp(nextHopIP);
178 Host host;
179 if (!hosts.isEmpty()) {
180 host = hosts.iterator().next();
181 } else {
182 log.warn("Host {} is not in store", nextHopIP);
183 return null;
184 }
185
186 HostLocation hl = host.location();
187 String clientConnectionPointStr = hl.toString(); // iterator().next());
188 ConnectPoint clientConnectionPoint = ConnectPoint.deviceConnectPoint(clientConnectionPointStr);
189
190
191 VlanId originalPacketVlanId = VlanId.vlanId(etherReply.getVlanID());
192 Interface iface;
193 iface = interfaceService.getInterfacesByPort(clientConnectionPoint)
194 .stream()
195 .filter(iface1 -> interfaceContainsVlan(iface1, originalPacketVlanId))
196 .findFirst()
197 .orElse(null);
198
199 etherReply.setSourceMACAddress(iface.mac());
200 etherReply.setDestinationMACAddress(host.mac());
201
Lior Assoulinea21c0ca2018-01-28 16:18:48 -0800202 // workaround for a bug where core sends src port as 547 (server)
203 udpPacket.setDestinationPort(UDP.DHCP_V6_SERVER_PORT);
204 udpPacket.setPayload(lq6Reply);
205 udpPacket.resetChecksum();
206 ipv6Packet.setPayload(udpPacket);
207 etherReply.setPayload(ipv6Packet);
208
Taras Lemkin96a0d342018-03-26 14:52:58 +0000209 return InternalPacket.internalPacket(etherReply, clientConnectionPoint);
Kalhee Kim495c9b22017-11-07 16:32:09 +0000210 }
211
212 /**
213 * extract DHCP6 payload from dhcp6 relay message within relay-forwrd/reply.
214 *
Sangyeok Sim61ac5702018-06-13 13:33:51 +0900215 * @param dhcp6 dhcp6 relay-reply or relay-forward
Kalhee Kim495c9b22017-11-07 16:32:09 +0000216 * @return dhcp6Packet dhcp6 packet extracted from relay-message
217 */
Taras Lemkin96a0d342018-03-26 14:52:58 +0000218 public static DHCP6 dhcp6PacketFromRelayPacket(DHCP6 dhcp6) {
Kalhee Kim495c9b22017-11-07 16:32:09 +0000219
220 // extract the relay message if exist
221 DHCP6 dhcp6Payload = dhcp6.getOptions().stream()
222 .filter(opt -> opt instanceof Dhcp6RelayOption)
223 .map(BasePacket::getPayload)
224 .map(pld -> (DHCP6) pld)
225 .findFirst()
226 .orElse(null);
227 if (dhcp6Payload == null) {
228 // Can't find dhcp payload
229 log.debug("Can't find dhcp6 payload from relay message");
230 } else {
231 log.debug("dhcp6 payload found from relay message {}", dhcp6Payload);
232 }
233 return dhcp6Payload;
234 }
235
236 /**
237 * find the leaf DHCP6 packet from multi-level relay packet.
238 *
239 * @param relayPacket dhcp6 relay packet
240 * @return leafPacket non-relay dhcp6 packet
241 */
Taras Lemkin96a0d342018-03-26 14:52:58 +0000242 public static DHCP6 getDhcp6Leaf(DHCP6 relayPacket) {
Kalhee Kim495c9b22017-11-07 16:32:09 +0000243 DHCP6 dhcp6Parent = relayPacket;
244 DHCP6 dhcp6Child = null;
245
246 log.debug("getDhcp6Leaf entered.");
247 while (dhcp6Parent != null) {
248 dhcp6Child = dhcp6PacketFromRelayPacket(dhcp6Parent);
249 if (dhcp6Child != null) {
250 if (dhcp6Child.getMsgType() != DHCP6.MsgType.RELAY_FORW.value() &&
251 dhcp6Child.getMsgType() != DHCP6.MsgType.RELAY_REPL.value()) {
252 log.debug("leaf dhcp6 packet found.");
253 break;
254 } else {
255 // found another relay, go for another loop
256 dhcp6Parent = dhcp6Child;
257 }
258 } else {
259 log.debug("Expected dhcp6 within relay pkt, but no dhcp6 leaf found.");
260 break;
261 }
262 }
263 return dhcp6Child;
264 }
265
266 /**
Taras Lemkin96a0d342018-03-26 14:52:58 +0000267 * Determine DHCP message type (direct DHCPv6 or wrapped into relay messages).
268 *
269 * @param relayPacket {@link DHCP6} packet to be parsed
270 * @return {@link DHCP6.MsgType} contained message type of dhcpv6 packet/relay-message
271 */
272 public static DHCP6.MsgType getDhcp6LeafMessageType(DHCP6 relayPacket) {
273 checkNotNull(relayPacket);
274 DHCP6 dhcp6Child = getDhcp6Leaf(relayPacket);
275 return DHCP6.MsgType.getType(dhcp6Child != null ? dhcp6Child.getMsgType() : relayPacket.getMsgType());
276 }
277
278 /**
Kalhee Kim495c9b22017-11-07 16:32:09 +0000279 * check if DHCP6 relay-reply is reply.
280 *
281 * @param relayPacket dhcp6 relay-reply
282 * @return boolean relay-reply contains ack
283 */
Taras Lemkin96a0d342018-03-26 14:52:58 +0000284 public static boolean isDhcp6Reply(DHCP6 relayPacket) {
Kalhee Kim495c9b22017-11-07 16:32:09 +0000285 DHCP6 leafDhcp6 = getDhcp6Leaf(relayPacket);
286 if (leafDhcp6 != null) {
287 if (leafDhcp6.getMsgType() == DHCP6.MsgType.REPLY.value()) {
288 log.debug("isDhcp6Reply true.");
289 return true; // must be directly connected
290 } else {
291 log.debug("isDhcp6Reply false. leaf dhcp6 is not replay. MsgType {}", leafDhcp6.getMsgType());
292 }
293 } else {
294 log.debug("isDhcp6Reply false. Expected dhcp6 within relay pkt but not found.");
295 }
296 log.debug("isDhcp6Reply false.");
297 return false;
298 }
299
300 /**
301 * check if DHCP6 is release or relay-forward contains release.
302 *
303 * @param dhcp6Payload dhcp6 packet
304 * @return boolean dhcp6 contains release
305 */
Taras Lemkin96a0d342018-03-26 14:52:58 +0000306 public static boolean isDhcp6Release(DHCP6 dhcp6Payload) {
Kalhee Kim495c9b22017-11-07 16:32:09 +0000307 if (dhcp6Payload.getMsgType() == DHCP6.MsgType.RELEASE.value()) {
308 log.debug("isDhcp6Release true.");
309 return true; // must be directly connected
310 } else {
311 DHCP6 dhcp6Leaf = getDhcp6Leaf(dhcp6Payload);
312 if (dhcp6Leaf != null) {
313 if (dhcp6Leaf.getMsgType() == DHCP6.MsgType.RELEASE.value()) {
314 log.debug("isDhcp6Release true. indirectlry connected");
315 return true;
316 } else {
317 log.debug("leaf dhcp6 is not release. MsgType {}", dhcp6Leaf.getMsgType());
318 return false;
319 }
320 } else {
321 log.debug("isDhcp6Release false. dhcp6 is niether relay nor release.");
322 return false;
323 }
324 }
325 }
326
327
328 /**
329 * convert dhcp6 msgType to String.
330 *
331 * @param msgTypeVal msgType byte of dhcp6 packet
332 * @return String string value of dhcp6 msg type
333 */
Taras Lemkin96a0d342018-03-26 14:52:58 +0000334 public static String getMsgTypeStr(byte msgTypeVal) {
Kalhee Kim495c9b22017-11-07 16:32:09 +0000335 MsgType msgType = DHCP6.MsgType.getType(msgTypeVal);
336 return DHCP6.MsgType.getMsgTypeStr(msgType);
337 }
338
339 /**
340 * find the string of dhcp6 leaf packets's msg type.
341 *
342 * @param directConnFlag boolean value indicating direct/indirect connection
343 * @param dhcp6Packet dhcp6 packet
344 * @return String string value of dhcp6 leaf packet msg type
345 */
Taras Lemkin96a0d342018-03-26 14:52:58 +0000346 public static String findLeafMsgType(boolean directConnFlag, DHCP6 dhcp6Packet) {
Kalhee Kim495c9b22017-11-07 16:32:09 +0000347 if (directConnFlag) {
348 return getMsgTypeStr(dhcp6Packet.getMsgType());
349 } else {
350 DHCP6 leafDhcp = getDhcp6Leaf(dhcp6Packet);
351 if (leafDhcp != null) {
352 return getMsgTypeStr(leafDhcp.getMsgType());
353 } else {
Kalhee Kimd94ceea2017-11-29 19:03:02 +0000354 return DhcpRelayCounters.INVALID_PACKET;
Kalhee Kim495c9b22017-11-07 16:32:09 +0000355 }
356 }
357 }
358
359 /**
360 * Determind if an Interface contains a vlan id.
361 *
362 * @param iface the Interface
363 * @param vlanId the vlan id
364 * @return true if the Interface contains the vlan id
365 */
Taras Lemkin96a0d342018-03-26 14:52:58 +0000366 public static boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
Kalhee Kim495c9b22017-11-07 16:32:09 +0000367 if (vlanId.equals(VlanId.NONE)) {
368 // untagged packet, check if vlan untagged or vlan native is not NONE
369 return !iface.vlanUntagged().equals(VlanId.NONE) ||
370 !iface.vlanNative().equals(VlanId.NONE);
371 }
372 // tagged packet, check if the interface contains the vlan
373 return iface.vlanTagged().contains(vlanId);
374 }
375
376 /**
Kalhee Kim495c9b22017-11-07 16:32:09 +0000377 * Check if the host is directly connected to the network or not.
378 *
379 * @param dhcp6Payload the dhcp6 payload
380 * @return true if the host is directly connected to the network; false otherwise
381 */
Taras Lemkin96a0d342018-03-26 14:52:58 +0000382 public static boolean directlyConnected(DHCP6 dhcp6Payload) {
Lior Assoulinea21c0ca2018-01-28 16:18:48 -0800383
Kalhee Kim495c9b22017-11-07 16:32:09 +0000384 log.debug("directlyConnected enters");
Lior Assoulinea21c0ca2018-01-28 16:18:48 -0800385 if (dhcp6Payload.getMsgType() == DHCP6.MsgType.LEASEQUERY.value() ||
386 dhcp6Payload.getMsgType() == DHCP6.MsgType.LEASEQUERY_REPLY.value()) {
387 log.debug("directlyConnected false. MsgType {}", dhcp6Payload.getMsgType());
388
389 return false;
390 }
Kalhee Kim495c9b22017-11-07 16:32:09 +0000391
392 if (dhcp6Payload.getMsgType() != DHCP6.MsgType.RELAY_FORW.value() &&
393 dhcp6Payload.getMsgType() != DHCP6.MsgType.RELAY_REPL.value()) {
394 log.debug("directlyConnected true. MsgType {}", dhcp6Payload.getMsgType());
395
396 return true;
397 }
398 // Regardless of relay-forward or relay-replay, check if we see another relay message
399 DHCP6 dhcp6Payload2 = dhcp6PacketFromRelayPacket(dhcp6Payload);
400 if (dhcp6Payload2 != null) {
401 if (dhcp6Payload.getMsgType() == DHCP6.MsgType.RELAY_FORW.value()) {
Sangyeok Sim61ac5702018-06-13 13:33:51 +0900402 log.debug("directlyConnected false. 1st relay-forward, 2nd MsgType {}", dhcp6Payload2.getMsgType());
Kalhee Kim495c9b22017-11-07 16:32:09 +0000403 return false;
404 } else {
405 // relay-reply
Taras Lemkin96a0d342018-03-26 14:52:58 +0000406 if (dhcp6Payload2.getMsgType() != DHCP6.MsgType.RELAY_REPL.value()
407 && dhcp6Payload2.getMsgType() != MsgType.LEASEQUERY_REPLY.value()) {
Kalhee Kim495c9b22017-11-07 16:32:09 +0000408 log.debug("directlyConnected true. 2nd MsgType {}", dhcp6Payload2.getMsgType());
409 return true; // must be directly connected
410 } else {
411 log.debug("directlyConnected false. 1st relay-reply, 2nd relay-reply MsgType {}",
412 dhcp6Payload2.getMsgType());
413 return false; // must be indirectly connected
414 }
415 }
416 } else {
417 log.debug("directlyConnected true.");
418 return true;
419 }
420 }
421 /**
422 * Check if a given server info has v6 ipaddress.
423 *
424 * @param serverInfo server info to check
425 * @return true if server info has v6 ip address; false otherwise
426 */
Taras Lemkin96a0d342018-03-26 14:52:58 +0000427 public static boolean isServerIpEmpty(DhcpServerInfo serverInfo) {
Kalhee Kim495c9b22017-11-07 16:32:09 +0000428 if (!serverInfo.getDhcpServerIp6().isPresent()) {
429 log.warn("DhcpServerIp not available, use default DhcpServerIp {}",
430 HexString.toHexString(serverInfo.getDhcpServerIp6().get().toOctets()));
431 return true;
432 }
433 return false;
434 }
435
Taras Lemkin96a0d342018-03-26 14:52:58 +0000436 private static boolean isConnectMacEmpty(DhcpServerInfo serverInfo, Set<Interface> clientInterfaces) {
Kalhee Kim495c9b22017-11-07 16:32:09 +0000437 if (!serverInfo.getDhcpConnectMac().isPresent()) {
438 log.warn("DHCP6 {} not yet resolved .. Aborting DHCP "
439 + "packet processing from client on port: {}",
440 !serverInfo.getDhcpGatewayIp6().isPresent() ? "server IP " + serverInfo.getDhcpServerIp6()
441 : "gateway IP " + serverInfo.getDhcpGatewayIp6(),
442 clientInterfaces.iterator().next().connectPoint());
443 return true;
444 }
445 return false;
446 }
447
Taras Lemkin96a0d342018-03-26 14:52:58 +0000448 private static Dhcp6Option getInterfaceIdIdOption(PacketContext context, Ethernet clientPacket) {
Kalhee Kim495c9b22017-11-07 16:32:09 +0000449 String inPortString = "-" + context.inPacket().receivedFrom().toString() + ":";
450 Dhcp6Option interfaceId = new Dhcp6Option();
451 interfaceId.setCode(DHCP6.OptionCode.INTERFACE_ID.value());
452 byte[] clientSoureMacBytes = clientPacket.getSourceMACAddress();
453 byte[] inPortStringBytes = inPortString.getBytes();
454 byte[] vlanIdBytes = new byte[2];
455 vlanIdBytes[0] = (byte) (clientPacket.getVlanID() & 0xff);
456 vlanIdBytes[1] = (byte) ((clientPacket.getVlanID() >> 8) & 0xff);
457 byte[] interfaceIdBytes = new byte[clientSoureMacBytes.length +
458 inPortStringBytes.length + vlanIdBytes.length];
459 log.debug("Length: interfaceIdBytes {} clientSoureMacBytes {} inPortStringBytes {} vlan {}",
460 interfaceIdBytes.length, clientSoureMacBytes.length, inPortStringBytes.length,
461 vlanIdBytes.length);
462
463 System.arraycopy(clientSoureMacBytes, 0, interfaceIdBytes, 0, clientSoureMacBytes.length);
464 System.arraycopy(inPortStringBytes, 0, interfaceIdBytes, clientSoureMacBytes.length,
465 inPortStringBytes.length);
466 System.arraycopy(vlanIdBytes, 0, interfaceIdBytes,
467 clientSoureMacBytes.length + inPortStringBytes.length,
468 vlanIdBytes.length);
469 interfaceId.setData(interfaceIdBytes);
470 interfaceId.setLength((short) interfaceIdBytes.length);
471 log.debug("interfaceId write srcMac {} portString {}",
472 HexString.toHexString(clientSoureMacBytes, ":"), inPortString);
473 return interfaceId;
474 }
475
Taras Lemkin96a0d342018-03-26 14:52:58 +0000476 private static void addDhcp6OptionsFromClient(List<Dhcp6Option> options, byte[] dhcp6PacketByte,
Kalhee Kim495c9b22017-11-07 16:32:09 +0000477 PacketContext context, Ethernet clientPacket) {
478 Dhcp6Option relayMessage = new Dhcp6Option();
479 relayMessage.setCode(DHCP6.OptionCode.RELAY_MSG.value());
480 relayMessage.setLength((short) dhcp6PacketByte.length);
481 relayMessage.setData(dhcp6PacketByte);
482 options.add(relayMessage);
483 // create interfaceId option
484 Dhcp6Option interfaceId = getInterfaceIdIdOption(context, clientPacket);
485 options.add(interfaceId);
486 }
487
488 /**
489 * build the DHCP6 solicit/request packet with gatewayip.
490 *
491 * @param context packet context
492 * @param clientPacket client ethernet packet
493 * @param clientInterfaces set of client side interfaces
494 * @param serverInfo target server which a packet is generated for
495 * @param serverInterface target server interface
496 * @return ethernet packet with dhcp6 packet info
497 */
Taras Lemkin96a0d342018-03-26 14:52:58 +0000498 public static Ethernet buildDhcp6PacketFromClient(PacketContext context, Ethernet clientPacket,
Kalhee Kim495c9b22017-11-07 16:32:09 +0000499 Set<Interface> clientInterfaces, DhcpServerInfo serverInfo,
500 Interface serverInterface) {
501 ConnectPoint receivedFrom = context.inPacket().receivedFrom();
502 DeviceId receivedFromDevice = receivedFrom.deviceId();
503
504 Ip6Address relayAgentIp = getRelayAgentIPv6Address(clientInterfaces);
505 MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
506 if (relayAgentIp == null || relayAgentMac == null) {
507 log.warn("Missing DHCP relay agent interface Ipv6 addr config for "
508 + "packet from client on port: {}. Aborting packet processing",
509 clientInterfaces.iterator().next().connectPoint());
510 return null;
511 }
512 IPv6 clientIpv6 = (IPv6) clientPacket.getPayload();
513 UDP clientUdp = (UDP) clientIpv6.getPayload();
514 DHCP6 clientDhcp6 = (DHCP6) clientUdp.getPayload();
515 boolean directConnFlag = directlyConnected(clientDhcp6);
516
517 Ip6Address serverIpFacing = getFirstIpFromInterface(serverInterface);
518 if (serverIpFacing == null || serverInterface.mac() == null) {
519 log.warn("No IP v6 address for server Interface {}", serverInterface);
520 return null;
521 }
522
523 Ethernet etherReply = clientPacket.duplicate();
524 etherReply.setSourceMACAddress(serverInterface.mac());
525
526 // set default info and replace with indirect if available later on.
527 if (serverInfo.getDhcpConnectMac().isPresent()) {
528 etherReply.setDestinationMACAddress(serverInfo.getDhcpConnectMac().get());
529 }
530 if (serverInfo.getDhcpConnectVlan().isPresent()) {
531 etherReply.setVlanID(serverInfo.getDhcpConnectVlan().get().toShort());
532 }
533 IPv6 ipv6Packet = (IPv6) etherReply.getPayload();
534 byte[] peerAddress = clientIpv6.getSourceAddress();
535 ipv6Packet.setSourceAddress(serverIpFacing.toOctets());
536 ipv6Packet.setDestinationAddress(serverInfo.getDhcpServerIp6().get().toOctets());
537 UDP udpPacket = (UDP) ipv6Packet.getPayload();
538 udpPacket.setSourcePort(UDP.DHCP_V6_SERVER_PORT);
539 DHCP6 dhcp6Packet = (DHCP6) udpPacket.getPayload();
540 byte[] dhcp6PacketByte = dhcp6Packet.serialize();
541
542 DHCP6 dhcp6Relay = new DHCP6();
543
544 dhcp6Relay.setMsgType(DHCP6.MsgType.RELAY_FORW.value());
545
546 if (directConnFlag) {
547 dhcp6Relay.setLinkAddress(relayAgentIp.toOctets());
548 } else {
549 if (isServerIpEmpty(serverInfo)) {
550 log.warn("indirect DhcpServerIp empty... use default server ");
551 } else {
552 // Indirect case, replace destination to indirect dhcp server if exist
553 // Check if mac is obtained for valid server ip
554 if (isConnectMacEmpty(serverInfo, clientInterfaces)) {
555 log.warn("indirect Dhcp ConnectMac empty ...");
556 return null;
557 }
558 etherReply.setDestinationMACAddress(serverInfo.getDhcpConnectMac().get());
559 etherReply.setVlanID(serverInfo.getDhcpConnectVlan().get().toShort());
560 ipv6Packet.setDestinationAddress(serverInfo.getDhcpServerIp6().get().toOctets());
561 }
Ruchi Sahota089415e2019-06-19 16:46:32 +0000562 if (!serverInfo.getRelayAgentIp6(receivedFromDevice).isPresent()) {
Kalhee Kim495c9b22017-11-07 16:32:09 +0000563 log.debug("indirect connection: relayAgentIp NOT availale from config file! Use dynamic. {}",
564 HexString.toHexString(relayAgentIp.toOctets(), ":"));
Mayank Tiwari30149832018-10-19 12:12:44 -0400565 serverIpFacing = relayAgentIp;
Kalhee Kim495c9b22017-11-07 16:32:09 +0000566 } else {
Mayank Tiwari30149832018-10-19 12:12:44 -0400567 serverIpFacing = serverInfo.getRelayAgentIp6(receivedFromDevice).get();
Kalhee Kim495c9b22017-11-07 16:32:09 +0000568 }
Mayank Tiwari30149832018-10-19 12:12:44 -0400569 log.debug("Source IP address set as relay agent IP with value: {}", serverIpFacing);
570 dhcp6Relay.setLinkAddress(serverIpFacing.toOctets());
571 ipv6Packet.setSourceAddress(serverIpFacing.toOctets());
Kalhee Kim495c9b22017-11-07 16:32:09 +0000572 }
573 // peer address: address of the client or relay agent from which the message to be relayed was received.
574 dhcp6Relay.setPeerAddress(peerAddress);
575 // directly connected case, hop count is zero; otherwise, hop count + 1
576 if (directConnFlag) {
577 dhcp6Relay.setHopCount((byte) 0);
578 } else {
579 dhcp6Relay.setHopCount((byte) (dhcp6Packet.getHopCount() + 1));
580 }
581
582 List<Dhcp6Option> options = new ArrayList<>();
583 addDhcp6OptionsFromClient(options, dhcp6PacketByte, context, clientPacket);
584 dhcp6Relay.setOptions(options);
585 udpPacket.setPayload(dhcp6Relay);
586 udpPacket.resetChecksum();
587 ipv6Packet.setPayload(udpPacket);
588 ipv6Packet.setHopLimit((byte) 64);
589 etherReply.setPayload(ipv6Packet);
590
591 return etherReply;
592 }
593
594 /**
595 * build the DHCP6 solicit/request packet with gatewayip.
596 *
597 * @param directConnFlag flag indicating if packet is from direct client or not
598 * @param serverInfo server to check its connect point
599 * @return boolean true if serverInfo is found; false otherwise
600 */
Taras Lemkin96a0d342018-03-26 14:52:58 +0000601 public static boolean checkDhcpServerConnPt(boolean directConnFlag,
Kalhee Kim495c9b22017-11-07 16:32:09 +0000602 DhcpServerInfo serverInfo) {
603 if (serverInfo.getDhcpServerConnectPoint() == null) {
604 log.warn("DHCP6 server connect point for {} connPt {}",
605 directConnFlag ? "direct" : "indirect", serverInfo.getDhcpServerConnectPoint());
606 return false;
607 }
608 return true;
609 }
Charles Chan24a96ff2018-08-13 10:32:03 -0700610
611 /**
612 * extract from dhcp6 packet ClientIdOption.
613 *
614 * @param directConnFlag directly connected host
615 * @param dhcp6Payload the dhcp6 payload
616 * @return Dhcp6ClientIdOption clientIdOption, or null if not exists.
617 */
618 static Dhcp6ClientIdOption extractClientId(Boolean directConnFlag, DHCP6 dhcp6Payload) {
619 Dhcp6ClientIdOption clientIdOption;
620
621 if (directConnFlag) {
622 clientIdOption = dhcp6Payload.getOptions()
623 .stream()
624 .filter(opt -> opt instanceof Dhcp6ClientIdOption)
625 .map(opt -> (Dhcp6ClientIdOption) opt)
626 .findFirst()
627 .orElse(null);
628 } else {
629 DHCP6 leafDhcp = Dhcp6HandlerUtil.getDhcp6Leaf(dhcp6Payload);
630 clientIdOption = leafDhcp.getOptions()
631 .stream()
632 .filter(opt -> opt instanceof Dhcp6ClientIdOption)
633 .map(opt -> (Dhcp6ClientIdOption) opt)
634 .findFirst()
635 .orElse(null);
636 }
637
638 return clientIdOption;
639 }
Kalhee Kim495c9b22017-11-07 16:32:09 +0000640}