Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 1 | /* |
| 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 | */ |
| 17 | package org.onosproject.dhcprelay; |
| 18 | |
| 19 | import org.onlab.packet.BasePacket; |
| 20 | import org.onlab.packet.DHCP6; |
| 21 | import org.onlab.packet.DHCP6.MsgType; |
| 22 | import org.onlab.packet.Ip6Address; |
| 23 | import org.onlab.packet.IpAddress; |
| 24 | import org.onlab.packet.VlanId; |
Charles Chan | c7f63b6 | 2018-08-13 10:32:03 -0700 | [diff] [blame^] | 25 | import org.onlab.packet.dhcp.Dhcp6ClientIdOption; |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 26 | import org.onlab.packet.dhcp.Dhcp6RelayOption; |
| 27 | import org.onlab.packet.dhcp.Dhcp6Option; |
| 28 | |
| 29 | import org.onlab.packet.Ethernet; |
| 30 | import org.onlab.packet.IPv6; |
| 31 | import org.onlab.packet.MacAddress; |
| 32 | import org.onlab.packet.UDP; |
| 33 | |
| 34 | import org.onlab.util.HexString; |
| 35 | import org.onosproject.dhcprelay.api.DhcpServerInfo; |
Kalhee Kim | d94ceea | 2017-11-29 19:03:02 +0000 | [diff] [blame] | 36 | import org.onosproject.dhcprelay.store.DhcpRelayCounters; |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 37 | import org.onosproject.net.ConnectPoint; |
| 38 | import org.onosproject.net.host.InterfaceIpAddress; |
| 39 | import org.onosproject.net.intf.Interface; |
| 40 | import org.onosproject.net.packet.PacketContext; |
| 41 | import org.onosproject.net.DeviceId; |
| 42 | |
| 43 | import org.slf4j.Logger; |
| 44 | import org.slf4j.LoggerFactory; |
| 45 | import java.util.Set; |
| 46 | import java.util.List; |
| 47 | import java.util.ArrayList; |
Lior Assouline | a21c0ca | 2018-01-28 16:18:48 -0800 | [diff] [blame] | 48 | import org.onosproject.net.intf.InterfaceService; |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 49 | |
Lior Assouline | a21c0ca | 2018-01-28 16:18:48 -0800 | [diff] [blame] | 50 | import org.onosproject.net.Host; |
| 51 | import org.onosproject.net.host.HostService; |
| 52 | import org.onosproject.net.HostLocation; |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 53 | |
| 54 | import static com.google.common.base.Preconditions.checkNotNull; |
| 55 | |
Taras Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 56 | public final class Dhcp6HandlerUtil { |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 57 | |
Taras Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 58 | private static final Logger log = LoggerFactory.getLogger(Dhcp6HandlerUtil.class); |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 59 | |
Taras Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 60 | private Dhcp6HandlerUtil() { |
| 61 | } |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 62 | |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 63 | // Returns the first v6 interface ip out of a set of interfaces or null. |
| 64 | // Checks all interfaces, and ignores v6 interface ips |
Taras Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 65 | public static Ip6Address getRelayAgentIPv6Address(Set<Interface> intfs) { |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 66 | 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 Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 78 | * 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 Assouline | a21c0ca | 2018-01-28 16:18:48 -0800 | [diff] [blame] | 94 | * |
| 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 Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 107 | public static InternalPacket processLQ6PacketFromServer( |
Lior Assouline | a21c0ca | 2018-01-28 16:18:48 -0800 | [diff] [blame] | 108 | 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 Chan | 3a15b28 | 2018-02-20 10:29:05 -0800 | [diff] [blame] | 160 | if ((directConnFlag || indirectDhcpServerIp == null) |
Lior Assouline | a21c0ca | 2018-01-28 16:18:48 -0800 | [diff] [blame] | 161 | && !inPort.equals(dhcpServerConnectPoint)) { |
| 162 | log.warn("Receiving port {} is not the same as server connect point {} for direct or indirect-null", |
Taras Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 163 | inPort, dhcpServerConnectPoint); |
Lior Assouline | a21c0ca | 2018-01-28 16:18:48 -0800 | [diff] [blame] | 164 | 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 Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 170 | inPort, indirectDhcpServerConnectPoint); |
Lior Assouline | a21c0ca | 2018-01-28 16:18:48 -0800 | [diff] [blame] | 171 | 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 Assouline | a21c0ca | 2018-01-28 16:18:48 -0800 | [diff] [blame] | 202 | // 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 Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 209 | return InternalPacket.internalPacket(etherReply, clientConnectionPoint); |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 210 | } |
| 211 | |
| 212 | /** |
| 213 | * extract DHCP6 payload from dhcp6 relay message within relay-forwrd/reply. |
| 214 | * |
| 215 | * @param dhcp6 dhcp6 relay-reply or relay-foward |
| 216 | * @return dhcp6Packet dhcp6 packet extracted from relay-message |
| 217 | */ |
Taras Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 218 | public static DHCP6 dhcp6PacketFromRelayPacket(DHCP6 dhcp6) { |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 219 | |
| 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 Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 242 | public static DHCP6 getDhcp6Leaf(DHCP6 relayPacket) { |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 243 | 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 Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 267 | * 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 Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 279 | * check if DHCP6 relay-reply is reply. |
| 280 | * |
| 281 | * @param relayPacket dhcp6 relay-reply |
| 282 | * @return boolean relay-reply contains ack |
| 283 | */ |
Taras Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 284 | public static boolean isDhcp6Reply(DHCP6 relayPacket) { |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 285 | 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 Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 306 | public static boolean isDhcp6Release(DHCP6 dhcp6Payload) { |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 307 | 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 Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 334 | public static String getMsgTypeStr(byte msgTypeVal) { |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 335 | 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 Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 346 | public static String findLeafMsgType(boolean directConnFlag, DHCP6 dhcp6Packet) { |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 347 | 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 Kim | d94ceea | 2017-11-29 19:03:02 +0000 | [diff] [blame] | 354 | return DhcpRelayCounters.INVALID_PACKET; |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 355 | } |
| 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 Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 366 | public static boolean interfaceContainsVlan(Interface iface, VlanId vlanId) { |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 367 | 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 Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 377 | * 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 Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 382 | public static boolean directlyConnected(DHCP6 dhcp6Payload) { |
Lior Assouline | a21c0ca | 2018-01-28 16:18:48 -0800 | [diff] [blame] | 383 | |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 384 | log.debug("directlyConnected enters"); |
Lior Assouline | a21c0ca | 2018-01-28 16:18:48 -0800 | [diff] [blame] | 385 | 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 Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 391 | |
| 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()) { |
| 402 | log.debug("directlyConnected false. 1st realy-foward, 2nd MsgType {}", dhcp6Payload2.getMsgType()); |
| 403 | return false; |
| 404 | } else { |
| 405 | // relay-reply |
Taras Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 406 | if (dhcp6Payload2.getMsgType() != DHCP6.MsgType.RELAY_REPL.value() |
| 407 | && dhcp6Payload2.getMsgType() != MsgType.LEASEQUERY_REPLY.value()) { |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 408 | 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 Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 427 | public static boolean isServerIpEmpty(DhcpServerInfo serverInfo) { |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 428 | 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 Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 436 | private static boolean isConnectMacEmpty(DhcpServerInfo serverInfo, Set<Interface> clientInterfaces) { |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 437 | 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 Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 448 | private static boolean isRelayAgentIpFromCfgEmpty(DhcpServerInfo serverInfo, DeviceId receivedFromDevice) { |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 449 | if (!serverInfo.getRelayAgentIp6(receivedFromDevice).isPresent()) { |
| 450 | log.warn("indirect connection: relayAgentIp NOT availale from config file! Use dynamic."); |
| 451 | return true; |
| 452 | } |
| 453 | return false; |
| 454 | } |
| 455 | |
Taras Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 456 | private static Dhcp6Option getInterfaceIdIdOption(PacketContext context, Ethernet clientPacket) { |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 457 | String inPortString = "-" + context.inPacket().receivedFrom().toString() + ":"; |
| 458 | Dhcp6Option interfaceId = new Dhcp6Option(); |
| 459 | interfaceId.setCode(DHCP6.OptionCode.INTERFACE_ID.value()); |
| 460 | byte[] clientSoureMacBytes = clientPacket.getSourceMACAddress(); |
| 461 | byte[] inPortStringBytes = inPortString.getBytes(); |
| 462 | byte[] vlanIdBytes = new byte[2]; |
| 463 | vlanIdBytes[0] = (byte) (clientPacket.getVlanID() & 0xff); |
| 464 | vlanIdBytes[1] = (byte) ((clientPacket.getVlanID() >> 8) & 0xff); |
| 465 | byte[] interfaceIdBytes = new byte[clientSoureMacBytes.length + |
| 466 | inPortStringBytes.length + vlanIdBytes.length]; |
| 467 | log.debug("Length: interfaceIdBytes {} clientSoureMacBytes {} inPortStringBytes {} vlan {}", |
| 468 | interfaceIdBytes.length, clientSoureMacBytes.length, inPortStringBytes.length, |
| 469 | vlanIdBytes.length); |
| 470 | |
| 471 | System.arraycopy(clientSoureMacBytes, 0, interfaceIdBytes, 0, clientSoureMacBytes.length); |
| 472 | System.arraycopy(inPortStringBytes, 0, interfaceIdBytes, clientSoureMacBytes.length, |
| 473 | inPortStringBytes.length); |
| 474 | System.arraycopy(vlanIdBytes, 0, interfaceIdBytes, |
| 475 | clientSoureMacBytes.length + inPortStringBytes.length, |
| 476 | vlanIdBytes.length); |
| 477 | interfaceId.setData(interfaceIdBytes); |
| 478 | interfaceId.setLength((short) interfaceIdBytes.length); |
| 479 | log.debug("interfaceId write srcMac {} portString {}", |
| 480 | HexString.toHexString(clientSoureMacBytes, ":"), inPortString); |
| 481 | return interfaceId; |
| 482 | } |
| 483 | |
Taras Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 484 | private static void addDhcp6OptionsFromClient(List<Dhcp6Option> options, byte[] dhcp6PacketByte, |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 485 | PacketContext context, Ethernet clientPacket) { |
| 486 | Dhcp6Option relayMessage = new Dhcp6Option(); |
| 487 | relayMessage.setCode(DHCP6.OptionCode.RELAY_MSG.value()); |
| 488 | relayMessage.setLength((short) dhcp6PacketByte.length); |
| 489 | relayMessage.setData(dhcp6PacketByte); |
| 490 | options.add(relayMessage); |
| 491 | // create interfaceId option |
| 492 | Dhcp6Option interfaceId = getInterfaceIdIdOption(context, clientPacket); |
| 493 | options.add(interfaceId); |
| 494 | } |
| 495 | |
| 496 | /** |
| 497 | * build the DHCP6 solicit/request packet with gatewayip. |
| 498 | * |
| 499 | * @param context packet context |
| 500 | * @param clientPacket client ethernet packet |
| 501 | * @param clientInterfaces set of client side interfaces |
| 502 | * @param serverInfo target server which a packet is generated for |
| 503 | * @param serverInterface target server interface |
| 504 | * @return ethernet packet with dhcp6 packet info |
| 505 | */ |
Taras Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 506 | public static Ethernet buildDhcp6PacketFromClient(PacketContext context, Ethernet clientPacket, |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 507 | Set<Interface> clientInterfaces, DhcpServerInfo serverInfo, |
| 508 | Interface serverInterface) { |
| 509 | ConnectPoint receivedFrom = context.inPacket().receivedFrom(); |
| 510 | DeviceId receivedFromDevice = receivedFrom.deviceId(); |
| 511 | |
| 512 | Ip6Address relayAgentIp = getRelayAgentIPv6Address(clientInterfaces); |
| 513 | MacAddress relayAgentMac = clientInterfaces.iterator().next().mac(); |
| 514 | if (relayAgentIp == null || relayAgentMac == null) { |
| 515 | log.warn("Missing DHCP relay agent interface Ipv6 addr config for " |
| 516 | + "packet from client on port: {}. Aborting packet processing", |
| 517 | clientInterfaces.iterator().next().connectPoint()); |
| 518 | return null; |
| 519 | } |
| 520 | IPv6 clientIpv6 = (IPv6) clientPacket.getPayload(); |
| 521 | UDP clientUdp = (UDP) clientIpv6.getPayload(); |
| 522 | DHCP6 clientDhcp6 = (DHCP6) clientUdp.getPayload(); |
| 523 | boolean directConnFlag = directlyConnected(clientDhcp6); |
| 524 | |
| 525 | Ip6Address serverIpFacing = getFirstIpFromInterface(serverInterface); |
| 526 | if (serverIpFacing == null || serverInterface.mac() == null) { |
| 527 | log.warn("No IP v6 address for server Interface {}", serverInterface); |
| 528 | return null; |
| 529 | } |
| 530 | |
| 531 | Ethernet etherReply = clientPacket.duplicate(); |
| 532 | etherReply.setSourceMACAddress(serverInterface.mac()); |
| 533 | |
| 534 | // set default info and replace with indirect if available later on. |
| 535 | if (serverInfo.getDhcpConnectMac().isPresent()) { |
| 536 | etherReply.setDestinationMACAddress(serverInfo.getDhcpConnectMac().get()); |
| 537 | } |
| 538 | if (serverInfo.getDhcpConnectVlan().isPresent()) { |
| 539 | etherReply.setVlanID(serverInfo.getDhcpConnectVlan().get().toShort()); |
| 540 | } |
| 541 | IPv6 ipv6Packet = (IPv6) etherReply.getPayload(); |
| 542 | byte[] peerAddress = clientIpv6.getSourceAddress(); |
| 543 | ipv6Packet.setSourceAddress(serverIpFacing.toOctets()); |
| 544 | ipv6Packet.setDestinationAddress(serverInfo.getDhcpServerIp6().get().toOctets()); |
| 545 | UDP udpPacket = (UDP) ipv6Packet.getPayload(); |
| 546 | udpPacket.setSourcePort(UDP.DHCP_V6_SERVER_PORT); |
| 547 | DHCP6 dhcp6Packet = (DHCP6) udpPacket.getPayload(); |
| 548 | byte[] dhcp6PacketByte = dhcp6Packet.serialize(); |
| 549 | |
| 550 | DHCP6 dhcp6Relay = new DHCP6(); |
| 551 | |
| 552 | dhcp6Relay.setMsgType(DHCP6.MsgType.RELAY_FORW.value()); |
| 553 | |
| 554 | if (directConnFlag) { |
| 555 | dhcp6Relay.setLinkAddress(relayAgentIp.toOctets()); |
| 556 | } else { |
| 557 | if (isServerIpEmpty(serverInfo)) { |
| 558 | log.warn("indirect DhcpServerIp empty... use default server "); |
| 559 | } else { |
| 560 | // Indirect case, replace destination to indirect dhcp server if exist |
| 561 | // Check if mac is obtained for valid server ip |
| 562 | if (isConnectMacEmpty(serverInfo, clientInterfaces)) { |
| 563 | log.warn("indirect Dhcp ConnectMac empty ..."); |
| 564 | return null; |
| 565 | } |
| 566 | etherReply.setDestinationMACAddress(serverInfo.getDhcpConnectMac().get()); |
| 567 | etherReply.setVlanID(serverInfo.getDhcpConnectVlan().get().toShort()); |
| 568 | ipv6Packet.setDestinationAddress(serverInfo.getDhcpServerIp6().get().toOctets()); |
| 569 | } |
| 570 | if (isRelayAgentIpFromCfgEmpty(serverInfo, receivedFromDevice)) { |
| 571 | dhcp6Relay.setLinkAddress(relayAgentIp.toOctets()); |
| 572 | log.debug("indirect connection: relayAgentIp NOT availale from config file! Use dynamic. {}", |
| 573 | HexString.toHexString(relayAgentIp.toOctets(), ":")); |
| 574 | } else { |
| 575 | dhcp6Relay.setLinkAddress(serverInfo.getRelayAgentIp6(receivedFromDevice).get().toOctets()); |
| 576 | } |
| 577 | } |
| 578 | // peer address: address of the client or relay agent from which the message to be relayed was received. |
| 579 | dhcp6Relay.setPeerAddress(peerAddress); |
| 580 | // directly connected case, hop count is zero; otherwise, hop count + 1 |
| 581 | if (directConnFlag) { |
| 582 | dhcp6Relay.setHopCount((byte) 0); |
| 583 | } else { |
| 584 | dhcp6Relay.setHopCount((byte) (dhcp6Packet.getHopCount() + 1)); |
| 585 | } |
| 586 | |
| 587 | List<Dhcp6Option> options = new ArrayList<>(); |
| 588 | addDhcp6OptionsFromClient(options, dhcp6PacketByte, context, clientPacket); |
| 589 | dhcp6Relay.setOptions(options); |
| 590 | udpPacket.setPayload(dhcp6Relay); |
| 591 | udpPacket.resetChecksum(); |
| 592 | ipv6Packet.setPayload(udpPacket); |
| 593 | ipv6Packet.setHopLimit((byte) 64); |
| 594 | etherReply.setPayload(ipv6Packet); |
| 595 | |
| 596 | return etherReply; |
| 597 | } |
| 598 | |
| 599 | /** |
| 600 | * build the DHCP6 solicit/request packet with gatewayip. |
| 601 | * |
| 602 | * @param directConnFlag flag indicating if packet is from direct client or not |
| 603 | * @param serverInfo server to check its connect point |
| 604 | * @return boolean true if serverInfo is found; false otherwise |
| 605 | */ |
Taras Lemkin | 4178591 | 2018-03-26 14:52:58 +0000 | [diff] [blame] | 606 | public static boolean checkDhcpServerConnPt(boolean directConnFlag, |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 607 | DhcpServerInfo serverInfo) { |
| 608 | if (serverInfo.getDhcpServerConnectPoint() == null) { |
| 609 | log.warn("DHCP6 server connect point for {} connPt {}", |
| 610 | directConnFlag ? "direct" : "indirect", serverInfo.getDhcpServerConnectPoint()); |
| 611 | return false; |
| 612 | } |
| 613 | return true; |
| 614 | } |
Charles Chan | c7f63b6 | 2018-08-13 10:32:03 -0700 | [diff] [blame^] | 615 | |
| 616 | /** |
| 617 | * extract from dhcp6 packet ClientIdOption. |
| 618 | * |
| 619 | * @param directConnFlag directly connected host |
| 620 | * @param dhcp6Payload the dhcp6 payload |
| 621 | * @return Dhcp6ClientIdOption clientIdOption, or null if not exists. |
| 622 | */ |
| 623 | static Dhcp6ClientIdOption extractClientId(Boolean directConnFlag, DHCP6 dhcp6Payload) { |
| 624 | Dhcp6ClientIdOption clientIdOption; |
| 625 | |
| 626 | if (directConnFlag) { |
| 627 | clientIdOption = dhcp6Payload.getOptions() |
| 628 | .stream() |
| 629 | .filter(opt -> opt instanceof Dhcp6ClientIdOption) |
| 630 | .map(opt -> (Dhcp6ClientIdOption) opt) |
| 631 | .findFirst() |
| 632 | .orElse(null); |
| 633 | } else { |
| 634 | DHCP6 leafDhcp = Dhcp6HandlerUtil.getDhcp6Leaf(dhcp6Payload); |
| 635 | clientIdOption = leafDhcp.getOptions() |
| 636 | .stream() |
| 637 | .filter(opt -> opt instanceof Dhcp6ClientIdOption) |
| 638 | .map(opt -> (Dhcp6ClientIdOption) opt) |
| 639 | .findFirst() |
| 640 | .orElse(null); |
| 641 | } |
| 642 | |
| 643 | return clientIdOption; |
| 644 | } |
Kalhee Kim | 495c9b2 | 2017-11-07 16:32:09 +0000 | [diff] [blame] | 645 | } |