blob: 9d8bd71a0f1ecd3ea0b10c517965b117391d11d0 [file] [log] [blame]
/*
* Copyright 2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.openstacknetworking.routing;
import com.google.common.collect.Maps;
import org.onlab.packet.Ethernet;
import org.onlab.packet.ICMP;
import org.onlab.packet.IPv4;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.MacAddress;
import org.onosproject.core.ApplicationId;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Port;
import org.onosproject.net.PortNumber;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.packet.DefaultOutboundPacket;
import org.onosproject.net.packet.OutboundPacket;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.packet.PacketPriority;
import org.onosproject.net.packet.PacketService;
import org.onosproject.openstackinterface.OpenstackInterfaceService;
import org.onosproject.openstackinterface.OpenstackPort;
import org.onosproject.openstacknetworking.OpenstackPortInfo;
import org.onosproject.openstacknetworking.OpenstackSwitchingService;
import org.slf4j.Logger;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.Optional;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Handle ICMP packet sent from Openstack Gateway nodes.
*/
public class OpenstackIcmpHandler {
protected final Logger log = getLogger(getClass());
private final PacketService packetService;
private final DeviceService deviceService;
private final Map<String, OpenstackPortInfo> icmpInfoMap = Maps.newHashMap();
private final OpenstackSwitchingService openstackSwitchingService;
private final OpenstackInterfaceService openstackService;
private final OpenstackRoutingConfig config;
private static final MacAddress GATEWAY_MAC = MacAddress.valueOf("1f:1f:1f:1f:1f:1f");
private static final String NETWORK_ROUTER_INTERFACE = "network:router_interface";
private static final String PORTNAME = "portName";
/**
* Default constructor.
*
* @param packetService packet service
* @param deviceService device service
* @param openstackService openstackInterface service
* @param config openstackRoutingConfig
* @param openstackSwitchingService openstackSwitching service
*/
OpenstackIcmpHandler(PacketService packetService, DeviceService deviceService,
OpenstackInterfaceService openstackService, OpenstackRoutingConfig config,
OpenstackSwitchingService openstackSwitchingService) {
this.packetService = packetService;
this.deviceService = deviceService;
this.openstackService = checkNotNull(openstackService);
this.config = checkNotNull(config);
this.openstackSwitchingService = checkNotNull(openstackSwitchingService);
}
/**
* Requests ICMP packet.
*
* @param appId Application Id
*/
public void requestPacket(ApplicationId appId) {
TrafficSelector icmpSelector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_ICMP)
.build();
packetService.requestPackets(icmpSelector,
PacketPriority.CONTROL,
appId,
Optional.of(DeviceId.deviceId(config.gatewayBridgeId())));
}
/**
* Handles ICMP packet.
*
* @param context packet context
* @param ethernet ethernet
*/
public void processIcmpPacket(PacketContext context, Ethernet ethernet) {
checkNotNull(context, "context can not be null");
checkNotNull(ethernet, "ethernet can not be null");
IPv4 ipPacket = (IPv4) ethernet.getPayload();
log.debug("icmpEvent called from ip {}, mac {}", Ip4Address.valueOf(ipPacket.getSourceAddress()).toString(),
ethernet.getSourceMAC().toString());
ICMP icmp = (ICMP) ipPacket.getPayload();
short icmpId = getIcmpId(icmp);
DeviceId deviceId = context.inPacket().receivedFrom().deviceId();
if (icmp.getIcmpType() == ICMP.TYPE_ECHO_REQUEST) {
//TODO: Considers icmp between internal subnets which are belonged to the same router.
OpenstackPortInfo openstackPortInfo =
getOpenstackPortInfo(Ip4Address.valueOf(ipPacket.getSourceAddress()), ethernet.getSourceMAC());
checkNotNull(openstackPortInfo, "openstackPortInfo can not be null");
if (ipPacket.getDestinationAddress() == openstackPortInfo.gatewayIP().toInt()) {
processIcmpPacketSentToGateway(ipPacket, icmp, openstackPortInfo);
} else {
Ip4Address pNatIpAddress = pNatIpForPort(openstackPortInfo);
checkNotNull(pNatIpAddress, "pNatIpAddress can not be null");
sendRequestPacketToExt(ipPacket, icmp, deviceId, pNatIpAddress);
String icmpInfoKey = String.valueOf(icmpId)
.concat(String.valueOf(pNatIpAddress.toInt()))
.concat(String.valueOf(ipPacket.getDestinationAddress()));
icmpInfoMap.putIfAbsent(icmpInfoKey, openstackPortInfo);
}
} else if (icmp.getIcmpType() == ICMP.TYPE_ECHO_REPLY) {
String icmpInfoKey = String.valueOf(icmpId)
.concat(String.valueOf(ipPacket.getDestinationAddress()))
.concat(String.valueOf(ipPacket.getSourceAddress()));
processResponsePacketFromExternalToHost(ipPacket, icmp, icmpInfoMap.get(icmpInfoKey));
icmpInfoMap.remove(icmpInfoKey);
}
}
private void processIcmpPacketSentToGateway(IPv4 icmpRequestIpv4, ICMP icmpRequest,
OpenstackPortInfo openstackPortInfo) {
icmpRequest.setIcmpType(ICMP.TYPE_ECHO_REPLY)
.resetChecksum();
icmpRequestIpv4.setSourceAddress(icmpRequestIpv4.getDestinationAddress())
.setDestinationAddress(openstackPortInfo.ip().toInt())
.resetChecksum();
icmpRequestIpv4.setPayload(icmpRequest);
Ethernet icmpResponseEth = new Ethernet();
icmpResponseEth.setEtherType(Ethernet.TYPE_IPV4)
.setSourceMACAddress(GATEWAY_MAC)
.setDestinationMACAddress(openstackPortInfo.mac())
.setPayload(icmpRequestIpv4);
sendResponsePacketToHost(icmpResponseEth, openstackPortInfo);
}
private void sendRequestPacketToExt(IPv4 icmpRequestIpv4, ICMP icmpRequest, DeviceId deviceId,
Ip4Address pNatIpAddress) {
icmpRequest.resetChecksum();
icmpRequestIpv4.setSourceAddress(pNatIpAddress.toInt())
.resetChecksum();
icmpRequestIpv4.setPayload(icmpRequest);
Ethernet icmpRequestEth = new Ethernet();
icmpRequestEth.setEtherType(Ethernet.TYPE_IPV4)
.setSourceMACAddress(MacAddress.valueOf(config.gatewayExternalInterfaceMac()))
.setDestinationMACAddress(MacAddress.valueOf(config.physicalRouterMac()))
.setPayload(icmpRequestIpv4);
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(getPortForAnnotationPortName(DeviceId.deviceId(config.gatewayBridgeId()),
config.gatewayExternalInterfaceName()))
.build();
OutboundPacket packet = new DefaultOutboundPacket(deviceId,
treatment, ByteBuffer.wrap(icmpRequestEth.serialize()));
packetService.emit(packet);
}
private void processResponsePacketFromExternalToHost(IPv4 icmpResponseIpv4, ICMP icmpResponse,
OpenstackPortInfo openstackPortInfo) {
icmpResponse.resetChecksum();
icmpResponseIpv4.setDestinationAddress(openstackPortInfo.ip().toInt())
.resetChecksum();
icmpResponseIpv4.setPayload(icmpResponse);
Ethernet icmpResponseEth = new Ethernet();
icmpResponseEth.setEtherType(Ethernet.TYPE_IPV4)
.setSourceMACAddress(GATEWAY_MAC)
.setDestinationMACAddress(openstackPortInfo.mac())
.setPayload(icmpResponseIpv4);
sendResponsePacketToHost(icmpResponseEth, openstackPortInfo);
}
private void sendResponsePacketToHost(Ethernet icmpResponseEth, OpenstackPortInfo openstackPortInfo) {
Map.Entry<String, OpenstackPortInfo> entry = openstackSwitchingService.openstackPortInfo().entrySet().stream()
.filter(e -> e.getValue().mac().equals(openstackPortInfo.mac()))
.findAny().orElse(null);
if (entry == null) {
return;
}
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(getPortForAnnotationPortName(openstackPortInfo.deviceId(), entry.getKey()))
.build();
OutboundPacket packet = new DefaultOutboundPacket(openstackPortInfo.deviceId(),
treatment, ByteBuffer.wrap(icmpResponseEth.serialize()));
packetService.emit(packet);
}
private OpenstackPortInfo getOpenstackPortInfo(Ip4Address sourceIp, MacAddress sourceMac) {
checkNotNull(openstackSwitchingService.openstackPortInfo(), "openstackportinfo collection can not be null");
return openstackSwitchingService.openstackPortInfo().values()
.stream().filter(p -> p.ip().equals(sourceIp) && p.mac().equals(sourceMac))
.findAny().orElse(null);
}
private short getIcmpId(ICMP icmp) {
return ByteBuffer.wrap(icmp.serialize(), 4, 2).getShort();
}
private Ip4Address pNatIpForPort(OpenstackPortInfo openstackPortInfo) {
OpenstackPort openstackPort = openstackService.ports().stream()
.filter(p -> p.deviceOwner().equals(NETWORK_ROUTER_INTERFACE) &&
p.networkId().equals(openstackPortInfo.networkId()))
.findAny().orElse(null);
checkNotNull(openstackPort, "openstackPort can not be null");
return openstackService.router(openstackPort.deviceId())
.gatewayExternalInfo().externalFixedIps().values()
.stream().findAny().orElse(null);
}
private PortNumber getPortForAnnotationPortName(DeviceId deviceId, String match) {
Port port = deviceService.getPorts(deviceId).stream()
.filter(p -> p.annotations().value(PORTNAME).equals(match))
.findAny().orElse(null);
checkNotNull(port, "port cannot be null");
return port.number();
}
}