blob: 9ac2b2660a3ed308b35af7b7496da4bf794fc37c [file] [log] [blame]
sangho80f11cb2015-04-01 13:05:26 -07001/*
Brian O'Connor43b53542016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
sangho80f11cb2015-04-01 13:05:26 -07003 *
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 */
16package org.onosproject.segmentrouting;
17
sangho80f11cb2015-04-01 13:05:26 -070018import org.onlab.packet.Ethernet;
19import org.onlab.packet.ICMP;
20import org.onlab.packet.IPv4;
21import org.onlab.packet.Ip4Address;
Pier Ventre1a655962016-11-28 16:48:06 -080022import org.onlab.packet.Ip6Address;
Pier Ventreadb4ae62016-11-23 09:57:42 -080023import org.onlab.packet.IpAddress;
sangho80f11cb2015-04-01 13:05:26 -070024import org.onlab.packet.IpPrefix;
25import org.onlab.packet.MPLS;
Pier Ventre1a655962016-11-28 16:48:06 -080026import org.onlab.packet.MacAddress;
27import org.onlab.packet.VlanId;
Pier Ventreb6a7f342016-11-26 21:05:22 -080028import org.onosproject.incubator.net.neighbour.NeighbourMessageContext;
Pier Ventre1a655962016-11-28 16:48:06 -080029import org.onosproject.incubator.net.neighbour.NeighbourMessageType;
sangho80f11cb2015-04-01 13:05:26 -070030import org.onosproject.net.ConnectPoint;
31import org.onosproject.net.DeviceId;
Pier Ventre1a655962016-11-28 16:48:06 -080032import org.onosproject.net.Host;
33import org.onosproject.net.HostId;
sangho80f11cb2015-04-01 13:05:26 -070034import org.onosproject.net.flow.DefaultTrafficTreatment;
35import org.onosproject.net.flow.TrafficTreatment;
Pier Ventre1a655962016-11-28 16:48:06 -080036import org.onosproject.net.host.HostService;
sangho80f11cb2015-04-01 13:05:26 -070037import org.onosproject.net.packet.DefaultOutboundPacket;
38import org.onosproject.net.packet.InboundPacket;
39import org.onosproject.net.packet.OutboundPacket;
Charles Chan319d1a22015-11-03 10:42:14 -080040import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
41import org.onosproject.segmentrouting.config.DeviceConfiguration;
Pier Ventre1a655962016-11-28 16:48:06 -080042import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
sangho80f11cb2015-04-01 13:05:26 -070043import org.slf4j.Logger;
44import org.slf4j.LoggerFactory;
45
Jonathan Hartd53ebc42015-04-07 16:46:33 -070046import java.nio.ByteBuffer;
Saurav Dasc28b3432015-10-30 17:45:38 -070047import java.util.Set;
Jonathan Hartd53ebc42015-04-07 16:46:33 -070048
sangho80f11cb2015-04-01 13:05:26 -070049import static com.google.common.base.Preconditions.checkNotNull;
50
Charles Chanb7f75ac2016-01-11 18:28:54 -080051/**
52 * Handler of ICMP packets that responses or forwards ICMP packets that
53 * are sent to the controller.
54 */
sangho80f11cb2015-04-01 13:05:26 -070055public class IcmpHandler {
56
57 private static Logger log = LoggerFactory.getLogger(IcmpHandler.class);
58 private SegmentRoutingManager srManager;
sangho9b169e32015-04-14 16:27:13 -070059 private DeviceConfiguration config;
sangho80f11cb2015-04-01 13:05:26 -070060
61 /**
62 * Creates an IcmpHandler object.
63 *
64 * @param srManager SegmentRoutingManager object
65 */
66 public IcmpHandler(SegmentRoutingManager srManager) {
67 this.srManager = srManager;
sangho9b169e32015-04-14 16:27:13 -070068 this.config = checkNotNull(srManager.deviceConfiguration);
sangho80f11cb2015-04-01 13:05:26 -070069 }
70
Pier Ventre1a655962016-11-28 16:48:06 -080071 //////////////////////////////////////
72 // ICMP Echo/Reply Protocol //
73 //////////////////////////////////////
74
sangho80f11cb2015-04-01 13:05:26 -070075 /**
76 * Process incoming ICMP packet.
77 * If it is an ICMP request to router or known host, then sends an ICMP response.
78 * If it is an ICMP packet to known host and forward the packet to the host.
79 * If it is an ICMP packet to unknown host in a subnet, then sends an ARP request
80 * to the subnet.
81 *
Thomas Vachuska8a075092015-04-15 18:20:08 -070082 * @param pkt inbound packet
sangho80f11cb2015-04-01 13:05:26 -070083 */
84 public void processPacketIn(InboundPacket pkt) {
85
86 Ethernet ethernet = pkt.parsed();
87 IPv4 ipv4 = (IPv4) ethernet.getPayload();
88
89 ConnectPoint connectPoint = pkt.receivedFrom();
90 DeviceId deviceId = connectPoint.deviceId();
91 Ip4Address destinationAddress =
92 Ip4Address.valueOf(ipv4.getDestinationAddress());
Pier Ventreb6a7f342016-11-26 21:05:22 -080093 Set<IpAddress> gatewayIpAddresses = config.getPortIPs(deviceId);
Pier Ventreadb4ae62016-11-23 09:57:42 -080094 IpAddress routerIp;
Charles Chan319d1a22015-11-03 10:42:14 -080095 try {
Pier Ventreadb4ae62016-11-23 09:57:42 -080096 routerIp = config.getRouterIpv4(deviceId);
Charles Chan319d1a22015-11-03 10:42:14 -080097 } catch (DeviceConfigNotFoundException e) {
98 log.warn(e.getMessage() + " Aborting processPacketIn.");
99 return;
100 }
sangho9b169e32015-04-14 16:27:13 -0700101 IpPrefix routerIpPrefix = IpPrefix.valueOf(routerIp, IpPrefix.MAX_INET_MASK_LENGTH);
sangho80f11cb2015-04-01 13:05:26 -0700102 Ip4Address routerIpAddress = routerIpPrefix.getIp4Prefix().address();
103
104 // ICMP to the router IP or gateway IP
105 if (((ICMP) ipv4.getPayload()).getIcmpType() == ICMP.TYPE_ECHO_REQUEST &&
106 (destinationAddress.equals(routerIpAddress) ||
Srikanth Vavilapalli37a461b2015-04-07 15:12:32 -0700107 gatewayIpAddresses.contains(destinationAddress))) {
Pier Ventreadb4ae62016-11-23 09:57:42 -0800108 sendIcmpResponse(ethernet, connectPoint);
sangho80f11cb2015-04-01 13:05:26 -0700109
110 // ICMP for any known host
111 } else if (!srManager.hostService.getHostsByIp(destinationAddress).isEmpty()) {
Saurav Das2d94d312015-11-24 23:21:05 -0800112 // TODO: known host packet should not be coming to controller - resend flows?
sangho80f11cb2015-04-01 13:05:26 -0700113 srManager.ipHandler.forwardPackets(deviceId, destinationAddress);
114
115 // ICMP for an unknown host in the subnet of the router
116 } else if (config.inSameSubnet(deviceId, destinationAddress)) {
117 srManager.arpHandler.sendArpRequest(deviceId, destinationAddress, connectPoint);
118
119 // ICMP for an unknown host
120 } else {
121 log.debug("ICMP request for unknown host {} ", destinationAddress);
122 // Do nothing
123 }
124 }
125
Charles Chanf4586112015-11-09 16:37:23 -0800126 /**
127 * Sends an ICMP reply message.
128 *
129 * Note: we assume that packets sending from the edge switches to the hosts
130 * have untagged VLAN.
131 * @param icmpRequest the original ICMP request
132 * @param outport the output port where the ICMP reply should be sent to
133 */
Pier Ventreadb4ae62016-11-23 09:57:42 -0800134 private void sendIcmpResponse(Ethernet icmpRequest, ConnectPoint outport) {
Charles Chanf4586112015-11-09 16:37:23 -0800135 // Note: We assume that packets arrive at the edge switches have
136 // untagged VLAN.
sangho80f11cb2015-04-01 13:05:26 -0700137 Ethernet icmpReplyEth = new Ethernet();
138
139 IPv4 icmpRequestIpv4 = (IPv4) icmpRequest.getPayload();
140 IPv4 icmpReplyIpv4 = new IPv4();
141
142 int destAddress = icmpRequestIpv4.getDestinationAddress();
143 icmpReplyIpv4.setDestinationAddress(icmpRequestIpv4.getSourceAddress());
144 icmpReplyIpv4.setSourceAddress(destAddress);
145 icmpReplyIpv4.setTtl((byte) 64);
146 icmpReplyIpv4.setChecksum((short) 0);
147
Jonathan Hartd53ebc42015-04-07 16:46:33 -0700148 ICMP icmpReply = new ICMP();
Saurav Dasa5bb7cb2015-09-30 10:00:49 -0700149 icmpReply.setPayload(((ICMP) icmpRequestIpv4.getPayload()).getPayload());
sangho80f11cb2015-04-01 13:05:26 -0700150 icmpReply.setIcmpType(ICMP.TYPE_ECHO_REPLY);
151 icmpReply.setIcmpCode(ICMP.SUBTYPE_ECHO_REPLY);
152 icmpReply.setChecksum((short) 0);
sangho80f11cb2015-04-01 13:05:26 -0700153 icmpReplyIpv4.setPayload(icmpReply);
154
155 icmpReplyEth.setPayload(icmpReplyIpv4);
156 icmpReplyEth.setEtherType(Ethernet.TYPE_IPV4);
157 icmpReplyEth.setDestinationMACAddress(icmpRequest.getSourceMACAddress());
158 icmpReplyEth.setSourceMACAddress(icmpRequest.getDestinationMACAddress());
sangho80f11cb2015-04-01 13:05:26 -0700159
160 Ip4Address destIpAddress = Ip4Address.valueOf(icmpReplyIpv4.getDestinationAddress());
sangho9b169e32015-04-14 16:27:13 -0700161 Ip4Address destRouterAddress = config.getRouterIpAddressForASubnetHost(destIpAddress);
Pier Ventreadb4ae62016-11-23 09:57:42 -0800162 int destSid = config.getIPv4SegmentId(destRouterAddress);
Charles Chan70661362016-12-09 12:54:49 -0800163 if (destSid < 0) {
sangho80f11cb2015-04-01 13:05:26 -0700164 log.warn("Cannot find the Segment ID for {}", destAddress);
165 return;
166 }
167
Charles Chan70661362016-12-09 12:54:49 -0800168 sendPacketOut(outport, icmpReplyEth, destSid);
sangho80f11cb2015-04-01 13:05:26 -0700169
170 }
171
Charles Chan70661362016-12-09 12:54:49 -0800172 private void sendPacketOut(ConnectPoint outport, Ethernet payload, int destSid) {
sangho80f11cb2015-04-01 13:05:26 -0700173
174 IPv4 ipPacket = (IPv4) payload.getPayload();
175 Ip4Address destIpAddress = Ip4Address.valueOf(ipPacket.getDestinationAddress());
176
Pier Ventreadb4ae62016-11-23 09:57:42 -0800177 if (destSid == -1 || config.getIPv4SegmentId(payload.getDestinationMAC()) == destSid ||
sangho80f11cb2015-04-01 13:05:26 -0700178 config.inSameSubnet(outport.deviceId(), destIpAddress)) {
179 TrafficTreatment treatment = DefaultTrafficTreatment.builder().
180 setOutput(outport.port()).build();
181 OutboundPacket packet = new DefaultOutboundPacket(outport.deviceId(),
182 treatment, ByteBuffer.wrap(payload.serialize()));
183 srManager.packetService.emit(packet);
184 } else {
Saurav Dasd44e8802016-10-21 14:06:29 -0700185 log.debug("Send a MPLS packet as a ICMP response");
sangho80f11cb2015-04-01 13:05:26 -0700186 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
187 .setOutput(outport.port())
188 .build();
189
190 payload.setEtherType(Ethernet.MPLS_UNICAST);
191 MPLS mplsPkt = new MPLS();
Charles Chan70661362016-12-09 12:54:49 -0800192 mplsPkt.setLabel(destSid);
sangho80f11cb2015-04-01 13:05:26 -0700193 mplsPkt.setTtl(((IPv4) payload.getPayload()).getTtl());
194 mplsPkt.setPayload(payload.getPayload());
195 payload.setPayload(mplsPkt);
196
197 OutboundPacket packet = new DefaultOutboundPacket(outport.deviceId(),
198 treatment, ByteBuffer.wrap(payload.serialize()));
199
200 srManager.packetService.emit(packet);
201 }
202 }
sangho9b169e32015-04-14 16:27:13 -0700203
Pier Ventre1a655962016-11-28 16:48:06 -0800204 ///////////////////////////////////////////
205 // ICMPv6 Neighbour Discovery Protocol //
206 ///////////////////////////////////////////
sangho9b169e32015-04-14 16:27:13 -0700207
Pier Ventre1a655962016-11-28 16:48:06 -0800208 /**
209 * Process incoming NDP packet.
210 *
211 * If it is an NDP request for the router or for the gateway, then sends a NDP reply.
212 * If it is an NDP request to unknown host flood in the subnet.
213 * If it is an NDP packet to known host forward the packet to the host.
214 *
215 * FIXME If the NDP packets use link local addresses we fail.
216 *
217 * @param pkt inbound packet
218 * @param hostService the host service
219 */
220 public void processPacketIn(NeighbourMessageContext pkt, HostService hostService) {
221 /*
222 * First we validate the ndp packet
223 */
224 SegmentRoutingAppConfig appConfig = srManager.cfgService
225 .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
226 if (appConfig != null && appConfig.suppressSubnet().contains(pkt.inPort())) {
227 // Ignore NDP packets come from suppressed ports
228 pkt.drop();
229 return;
230 }
231 if (!validateSrcIp(pkt)) {
232 log.debug("Ignore NDP packet discovered on {} with unexpected src ip address {}.",
233 pkt.inPort(), pkt.sender());
234 pkt.drop();
235 return;
236 }
237
238 if (pkt.type() == NeighbourMessageType.REQUEST) {
239 handleNdpRequest(pkt, hostService);
240 } else {
241 handleNdpReply(pkt, hostService);
242 }
243
244 }
245
246 /**
247 * Utility function to verify if the src ip belongs to the same
248 * subnet configured on the port it is seen.
249 *
250 * @param pkt the ndp packet and context information
251 * @return true if the src ip is a valid address for the subnet configured
252 * for the connect point
253 */
254 private boolean validateSrcIp(NeighbourMessageContext pkt) {
255 ConnectPoint connectPoint = pkt.inPort();
256 IpPrefix subnet = config.getPortIPv6Subnet(
257 connectPoint.deviceId(),
258 connectPoint.port()
259 ).getIp6Prefix();
260 return subnet != null && subnet.contains(pkt.sender());
261 }
262
263 /**
264 * Helper method to handle the ndp requests.
265 *
266 * @param pkt the ndp packet request and context information
267 * @param hostService the host service
268 */
269 private void handleNdpRequest(NeighbourMessageContext pkt, HostService hostService) {
270 /*
271 * ND request for the gateway. We have to reply on behalf
272 * of the gateway.
273 */
274 if (isNdpForGateway(pkt)) {
275 log.debug("Sending NDP reply on behalf of the router");
276 sendNdpReply(pkt, config.getRouterMacForAGatewayIp(pkt.target()));
277 } else {
278 /*
279 * ND request for an host. We do a search by Ip.
280 */
281 Set<Host> hosts = hostService.getHostsByIp(pkt.target());
282 /*
283 * Possible misconfiguration ? In future this case
284 * should be handled we can have same hosts in different
285 * vlans.
286 */
287 if (hosts.size() > 1) {
288 log.warn("More than one host with IP {}", pkt.target());
289 }
290 Host targetHost = hosts.stream().findFirst().orElse(null);
291 /*
292 * If we know the host forward to its attachment
293 * point.
294 */
295 if (targetHost != null) {
296 log.debug("Forward NDP request to the target host");
297 pkt.forward(targetHost.location());
298 } else {
299 /*
300 * Flood otherwise.
301 */
302 log.debug("Flood NDP request to the target subnet");
303 flood(pkt);
304 }
305 }
306 }
307
308 /**
309 * Helper method to handle the ndp replies.
310 *
311 * @param pkt the ndp packet reply and context information
312 * @param hostService the host service
313 */
314 private void handleNdpReply(NeighbourMessageContext pkt, HostService hostService) {
315 if (isNdpForGateway(pkt)) {
316 log.debug("Forwarding all the ip packets we stored");
317 Ip6Address hostIpAddress = pkt.sender().getIp6Address();
318 srManager.ipHandler.forwardPackets(pkt.inPort().deviceId(), hostIpAddress);
319 } else {
320 HostId hostId = HostId.hostId(pkt.dstMac(), pkt.vlan());
321 Host targetHost = hostService.getHost(hostId);
322 if (targetHost != null) {
323 log.debug("Forwarding the reply to the host");
324 pkt.forward(targetHost.location());
325 } else {
326 /*
327 * We don't have to flood towards spine facing ports.
328 */
329 if (pkt.vlan().equals(VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET))) {
330 return;
331 }
332 log.debug("Flooding the reply to the subnet");
333 flood(pkt);
334 }
335 }
336 }
337
338 /**
339 * Utility to verify if the ND are for the gateway.
340 *
341 * @param pkt the ndp packet
342 * @return true if the ndp is for the gateway. False otherwise
343 */
344 private boolean isNdpForGateway(NeighbourMessageContext pkt) {
345 DeviceId deviceId = pkt.inPort().deviceId();
346 Set<IpAddress> gatewayIpAddresses = null;
347 try {
348 if (pkt.target().equals(config.getRouterIpv6(deviceId))) {
349 return true;
350 }
351 gatewayIpAddresses = config.getPortIPs(deviceId);
352 } catch (DeviceConfigNotFoundException e) {
353 log.warn(e.getMessage() + " Aborting check for router IP in processing ndp");
354 }
355 if (gatewayIpAddresses != null &&
356 gatewayIpAddresses.contains(pkt.target())) {
357 return true;
358 }
359 return false;
360 }
361
362 /**
363 * Utility to send a ND reply using the supplied information.
364 *
365 * @param pkt the ndp request
366 * @param targetMac the target mac
367 */
368 private void sendNdpReply(NeighbourMessageContext pkt, MacAddress targetMac) {
369 HostId dstId = HostId.hostId(pkt.srcMac(), pkt.vlan());
370 Host dst = srManager.hostService.getHost(dstId);
371 if (dst == null) {
372 log.warn("Cannot send NDP response to host {} - does not exist in the store",
373 dstId);
374 return;
375 }
376 pkt.reply(targetMac);
377 }
378
379 /*
380 * Floods only on the port which have been configured with the subnet
381 * of the target address. The in port is excluded.
382 *
383 * @param pkt the ndp packet and context information
384 */
385 private void flood(NeighbourMessageContext pkt) {
386 try {
387 srManager.deviceConfiguration
388 .getSubnetPortsMap(pkt.inPort().deviceId())
389 .forEach((subnet, ports) -> {
390 if (subnet.contains(pkt.target())) {
391 ports.stream()
392 .filter(portNumber -> portNumber != pkt.inPort().port())
393 .forEach(portNumber -> {
394 ConnectPoint outPoint = new ConnectPoint(
395 pkt.inPort().deviceId(),
396 portNumber
397 );
398 pkt.forward(outPoint);
399 });
400 }
401 });
402 } catch (DeviceConfigNotFoundException e) {
403 log.warn(e.getMessage()
404 + " Cannot flood in subnet as device config not available"
405 + " for device: " + pkt.inPort().deviceId());
406 }
407 }
sangho80f11cb2015-04-01 13:05:26 -0700408}