TunnellingConnectivityManager.java 7.71 KB
/*
 * Copyright 2015 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.bgprouter;

import org.onlab.packet.Ethernet;
import org.onlab.packet.IPv4;
import org.onlab.packet.IpAddress;
import org.onlab.packet.TCP;
import org.onlab.packet.TpPort;
import org.onosproject.core.ApplicationId;
import org.onosproject.incubator.net.intf.Interface;
import org.onosproject.incubator.net.intf.InterfaceService;
import org.onosproject.net.ConnectPoint;
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.flowobjective.DefaultForwardingObjective;
import org.onosproject.net.flowobjective.FlowObjectiveService;
import org.onosproject.net.flowobjective.ForwardingObjective;
import org.onosproject.net.packet.DefaultOutboundPacket;
import org.onosproject.net.packet.OutboundPacket;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.onosproject.routing.config.BgpConfig;
import org.slf4j.Logger;

import java.util.Optional;
import java.util.Set;

import static org.slf4j.LoggerFactory.getLogger;


/**
 * Manages connectivity between peers by tunnelling BGP traffic through
 * OpenFlow packet-ins and packet-outs.
 */
public class TunnellingConnectivityManager {

    private static final short BGP_PORT = 179;
    private final Logger log = getLogger(getClass());
    private final ApplicationId appId;

    private final BgpConfig.BgpSpeakerConfig bgpSpeaker;

    private final PacketService packetService;
    private final InterfaceService interfaceService;
    private final FlowObjectiveService flowObjectiveService;

    private final BgpProcessor processor = new BgpProcessor();

    public TunnellingConnectivityManager(ApplicationId appId,
                                         BgpConfig bgpConfig,
                                         InterfaceService interfaceService,
                                         PacketService packetService,
                                         FlowObjectiveService flowObjectiveService) {
        this.appId = appId;
        this.interfaceService = interfaceService;
        this.packetService = packetService;
        this.flowObjectiveService = flowObjectiveService;

        Optional<BgpConfig.BgpSpeakerConfig> bgpSpeaker =
                bgpConfig.bgpSpeakers().stream().findAny();

        if (!bgpSpeaker.isPresent()) {
            throw new IllegalArgumentException("Must have at least one BGP speaker configured");
        }

        this.bgpSpeaker = bgpSpeaker.get();

    }

    public void start() {
        packetService.addProcessor(processor, PacketProcessor.director(3));
    }

    public void stop() {
        packetService.removeProcessor(processor);
        // Should revoke packet requests in the future
    }

    /**
     * Pushes the flow rules for forwarding BGP TCP packets to controller.
     * It is called when switches are connected and available.
     */
    public void notifySwitchAvailable() {
        // control plane OVS is available, push default flows
        TrafficSelector selectorDst = DefaultTrafficSelector.builder()
                .matchEthType(Ethernet.TYPE_IPV4)
                .matchIPProtocol(IPv4.PROTOCOL_TCP)
                .matchTcpDst(TpPort.tpPort(BGP_PORT))
                .build();

        TrafficSelector selectorSrc = DefaultTrafficSelector.builder()
                .matchEthType(Ethernet.TYPE_IPV4)
                .matchIPProtocol(IPv4.PROTOCOL_TCP)
                .matchTcpSrc(TpPort.tpPort(BGP_PORT))
                .build();

        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
                .punt()
                .build();

        ForwardingObjective puntSrc = DefaultForwardingObjective.builder()
                .fromApp(appId)
                .makePermanent()
                .withSelector(selectorSrc)
                .withTreatment(treatment)
                .withFlag(ForwardingObjective.Flag.VERSATILE)
                .add();
        flowObjectiveService.forward(bgpSpeaker.connectPoint().deviceId(),
                puntSrc);

        ForwardingObjective puntDst = DefaultForwardingObjective.builder()
                .fromApp(appId)
                .makePermanent()
                .withSelector(selectorDst)
                .withTreatment(treatment)
                .withFlag(ForwardingObjective.Flag.VERSATILE)
                .add();
        flowObjectiveService.forward(bgpSpeaker.connectPoint().deviceId(),
                puntDst);
        log.info("Sent punt forwarding objective to {}", bgpSpeaker.connectPoint().deviceId());
    }

    /**
     * Forwards a BGP packet to another connect point.
     *
     * @param context the packet context of the incoming packet
     */
    private void forward(PacketContext context) {
        ConnectPoint outputPort = null;

        IPv4 ipv4 = (IPv4) context.inPacket().parsed().getPayload();
        IpAddress dstAddress = IpAddress.valueOf(ipv4.getDestinationAddress());

        if (context.inPacket().receivedFrom().equals(bgpSpeaker.connectPoint())) {
            if (bgpSpeaker.peers().contains(dstAddress)) {
                Interface intf = interfaceService.getMatchingInterface(dstAddress);
                if (intf != null) {
                    outputPort = intf.connectPoint();
                }
            }
        } else {
            Set<Interface> interfaces =
                    interfaceService.getInterfacesByPort(context.inPacket().receivedFrom());

            if (interfaces.stream()
                    .flatMap(intf -> intf.ipAddresses().stream())
                    .anyMatch(ia -> ia.ipAddress().equals(dstAddress))) {
                outputPort = bgpSpeaker.connectPoint();
            }
        }

        if (outputPort != null) {
            TrafficTreatment t = DefaultTrafficTreatment.builder()
                    .setOutput(outputPort.port()).build();
            OutboundPacket o = new DefaultOutboundPacket(
                    outputPort.deviceId(), t, context.inPacket().unparsed());
            packetService.emit(o);
        }
    }

    /**
     * Packet processor responsible receiving and filtering BGP packets.
     */
    private class BgpProcessor implements PacketProcessor {

        @Override
        public void process(PacketContext context) {
            // Stop processing if the packet has been handled, since we
            // can't do any more to it.
            if (context.isHandled()) {
                return;
            }

            Ethernet packet = context.inPacket().parsed();

            if (packet == null) {
                return;
            }

            if (packet.getEtherType() == Ethernet.TYPE_IPV4) {
                IPv4 ipv4Packet = (IPv4) packet.getPayload();
                if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_TCP) {
                    TCP tcpPacket = (TCP) ipv4Packet.getPayload();

                    if (tcpPacket.getDestinationPort() == BGP_PORT ||
                            tcpPacket.getSourcePort() == BGP_PORT) {
                        forward(context);
                    }
                }
            }
        }
    }
}