| /* |
| * Copyright 2017-present Open Networking Foundation |
| * |
| * 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.ofagent.impl; |
| |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| import io.netty.channel.Channel; |
| import org.onlab.osgi.ServiceDirectory; |
| import org.onosproject.core.ApplicationId; |
| import org.onosproject.incubator.net.virtual.NetworkId; |
| import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService; |
| import org.onosproject.incubator.net.virtual.VirtualNetworkService; |
| import org.onosproject.net.ConnectPoint; |
| import org.onosproject.net.DeviceId; |
| import org.onosproject.net.Port; |
| import org.onosproject.net.PortNumber; |
| import org.onosproject.net.device.PortStatistics; |
| import org.onosproject.net.driver.DriverService; |
| import org.onosproject.net.flow.FlowEntry; |
| import org.onosproject.net.flow.FlowRule; |
| import org.onosproject.net.flow.FlowRuleService; |
| import org.onosproject.net.flow.TableStatisticsEntry; |
| import org.onosproject.net.group.DefaultGroupDescription; |
| import org.onosproject.net.group.DefaultGroupKey; |
| import org.onosproject.net.group.Group; |
| import org.onosproject.net.group.GroupBuckets; |
| import org.onosproject.net.group.GroupDescription; |
| import org.onosproject.net.group.GroupKey; |
| import org.onosproject.net.group.GroupService; |
| import org.onosproject.net.meter.Band; |
| import org.onosproject.net.meter.DefaultBand; |
| import org.onosproject.net.meter.DefaultMeterRequest; |
| import org.onosproject.net.meter.Meter; |
| import org.onosproject.net.meter.MeterId; |
| import org.onosproject.net.meter.MeterRequest; |
| import org.onosproject.net.meter.MeterService; |
| import org.onosproject.net.packet.InboundPacket; |
| import org.onosproject.ofagent.api.OFAgent; |
| import org.onosproject.ofagent.api.OFSwitch; |
| import org.onosproject.ofagent.api.OFSwitchCapabilities; |
| import org.onosproject.ofagent.api.OFSwitchService; |
| import org.onosproject.openflow.controller.Dpid; |
| import org.projectfloodlight.openflow.protocol.OFActionType; |
| import org.projectfloodlight.openflow.protocol.OFBadRequestCode; |
| import org.projectfloodlight.openflow.protocol.OFBarrierReply; |
| import org.projectfloodlight.openflow.protocol.OFBucket; |
| import org.projectfloodlight.openflow.protocol.OFBucketCounter; |
| import org.projectfloodlight.openflow.protocol.OFControllerRole; |
| import org.projectfloodlight.openflow.protocol.OFEchoReply; |
| import org.projectfloodlight.openflow.protocol.OFEchoRequest; |
| import org.projectfloodlight.openflow.protocol.OFFactories; |
| import org.projectfloodlight.openflow.protocol.OFFactory; |
| import org.projectfloodlight.openflow.protocol.OFFeaturesReply; |
| import org.projectfloodlight.openflow.protocol.OFFlowMod; |
| import org.projectfloodlight.openflow.protocol.OFFlowStatsEntry; |
| import org.projectfloodlight.openflow.protocol.OFGetConfigReply; |
| import org.projectfloodlight.openflow.protocol.OFGroupAdd; |
| import org.projectfloodlight.openflow.protocol.OFGroupDescStatsEntry; |
| import org.projectfloodlight.openflow.protocol.OFGroupMod; |
| import org.projectfloodlight.openflow.protocol.OFGroupModify; |
| import org.projectfloodlight.openflow.protocol.OFGroupStatsEntry; |
| import org.projectfloodlight.openflow.protocol.OFGroupType; |
| import org.projectfloodlight.openflow.protocol.OFHello; |
| import org.projectfloodlight.openflow.protocol.OFMessage; |
| import org.projectfloodlight.openflow.protocol.OFMeterFeatures; |
| import org.projectfloodlight.openflow.protocol.OFMeterMod; |
| import org.projectfloodlight.openflow.protocol.OFMeterModFailedCode; |
| import org.projectfloodlight.openflow.protocol.OFPacketIn; |
| import org.projectfloodlight.openflow.protocol.OFPacketInReason; |
| import org.projectfloodlight.openflow.protocol.OFPacketOut; |
| import org.projectfloodlight.openflow.protocol.OFPortConfig; |
| import org.projectfloodlight.openflow.protocol.OFPortDesc; |
| import org.projectfloodlight.openflow.protocol.OFPortMod; |
| import org.projectfloodlight.openflow.protocol.OFPortReason; |
| import org.projectfloodlight.openflow.protocol.OFPortState; |
| import org.projectfloodlight.openflow.protocol.OFPortStatsEntry; |
| import org.projectfloodlight.openflow.protocol.OFPortStatsRequest; |
| import org.projectfloodlight.openflow.protocol.OFPortStatus; |
| import org.projectfloodlight.openflow.protocol.OFRoleReply; |
| import org.projectfloodlight.openflow.protocol.OFRoleRequest; |
| import org.projectfloodlight.openflow.protocol.OFSetConfig; |
| import org.projectfloodlight.openflow.protocol.OFStatsReply; |
| import org.projectfloodlight.openflow.protocol.OFStatsRequest; |
| import org.projectfloodlight.openflow.protocol.OFTableStatsEntry; |
| import org.projectfloodlight.openflow.protocol.OFType; |
| import org.projectfloodlight.openflow.protocol.OFVersion; |
| import org.projectfloodlight.openflow.protocol.action.OFAction; |
| import org.projectfloodlight.openflow.protocol.action.OFActionOutput; |
| import org.projectfloodlight.openflow.protocol.errormsg.OFBadRequestErrorMsg; |
| import org.projectfloodlight.openflow.protocol.errormsg.OFMeterModFailedErrorMsg; |
| import org.projectfloodlight.openflow.protocol.instruction.OFInstruction; |
| import org.projectfloodlight.openflow.protocol.match.Match; |
| import org.projectfloodlight.openflow.protocol.match.MatchField; |
| import org.projectfloodlight.openflow.protocol.meterband.OFMeterBand; |
| import org.projectfloodlight.openflow.protocol.meterband.OFMeterBandDrop; |
| import org.projectfloodlight.openflow.protocol.meterband.OFMeterBandDscpRemark; |
| import org.projectfloodlight.openflow.protocol.meterband.OFMeterBandExperimenter; |
| import org.projectfloodlight.openflow.types.DatapathId; |
| import org.projectfloodlight.openflow.types.OFGroup; |
| import org.projectfloodlight.openflow.types.OFPort; |
| import org.projectfloodlight.openflow.types.TableId; |
| import org.projectfloodlight.openflow.types.U64; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.stream.Collectors; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static org.projectfloodlight.openflow.protocol.OFControllerRole.ROLE_EQUAL; |
| |
| /** |
| * Implementation of the default OpenFlow switch. |
| */ |
| public final class DefaultOFSwitch implements OFSwitch { |
| |
| private static final String ERR_CH_DUPLICATE = "Channel already exists: "; |
| private static final String ERR_CH_NOT_FOUND = "Channel not found: "; |
| private static final long NUM_BUFFERS = 1024; |
| private static final short NUM_TABLES = 3; |
| |
| private final Logger log; |
| |
| private final OFSwitchService ofSwitchService; |
| private final VirtualNetworkAdminService virtualNetworkAdminService; |
| private final FlowRuleService flowRuleService; |
| private final DriverService driverService; |
| private final GroupService groupService; |
| private final MeterService meterService; |
| |
| private final DatapathId dpId; |
| private final OFSwitchCapabilities capabilities; |
| private final NetworkId networkId; |
| private final DeviceId deviceId; |
| |
| // miss_send_len field (in OFSetConfig and OFGetConfig messages) indicates the max |
| // bytes of a packet that the switch sends to the controller |
| private int missSendLen = 0xffff; |
| |
| private final ConcurrentHashMap<Channel, OFControllerRole> controllerRoleMap |
| = new ConcurrentHashMap<>(); |
| private static final OFFactory FACTORY = OFFactories.getFactory(OFVersion.OF_13); |
| |
| private int handshakeTransactionIds = -1; |
| |
| private DefaultOFSwitch(DatapathId dpid, OFSwitchCapabilities capabilities, |
| NetworkId networkId, DeviceId deviceId, |
| ServiceDirectory serviceDirectory) { |
| this.dpId = dpid; |
| this.capabilities = capabilities; |
| this.networkId = networkId; |
| this.deviceId = deviceId; |
| this.ofSwitchService = serviceDirectory.get(OFSwitchService.class); |
| this.driverService = serviceDirectory.get(DriverService.class); |
| this.virtualNetworkAdminService = serviceDirectory.get(VirtualNetworkAdminService.class); |
| VirtualNetworkService virtualNetworkService = serviceDirectory.get(VirtualNetworkService.class); |
| this.flowRuleService = virtualNetworkService.get(networkId, FlowRuleService.class); |
| this.groupService = virtualNetworkService.get(networkId, GroupService.class); |
| this.meterService = virtualNetworkService.get(networkId, MeterService.class); |
| |
| log = LoggerFactory.getLogger(OFAgent.TRACER_LOG_TENANT_ID_PREFIX + virtualNetworkService.getTenantId(networkId) |
| + " " + getClass().getSimpleName() + " : " + dpid); |
| } |
| |
| public static DefaultOFSwitch of(DatapathId dpid, OFSwitchCapabilities capabilities, |
| NetworkId networkId, DeviceId deviceId, |
| ServiceDirectory serviceDirectory) { |
| checkNotNull(dpid, "DPID cannot be null"); |
| checkNotNull(capabilities, "OF capabilities cannot be null"); |
| return new DefaultOFSwitch(dpid, capabilities, networkId, deviceId, serviceDirectory); |
| } |
| |
| @Override |
| public DatapathId dpid() { |
| return this.dpId; |
| } |
| |
| @Override |
| public OFSwitchCapabilities capabilities() { |
| return this.capabilities; |
| } |
| |
| @Override |
| public void addControllerChannel(Channel channel) { |
| controllerRoleMap.compute(channel, (ch, existing) -> { |
| final String error = ERR_CH_DUPLICATE + channel.remoteAddress(); |
| checkArgument(existing == null, error); |
| return ROLE_EQUAL; |
| }); |
| } |
| |
| @Override |
| public void deleteControllerChannel(Channel channel) { |
| if (controllerRoleMap.remove(channel) == null) { |
| final String error = ERR_CH_NOT_FOUND + channel.remoteAddress(); |
| throw new IllegalStateException(error); |
| } |
| } |
| |
| @Override |
| public void setRole(Channel channel, OFControllerRole role) { |
| controllerRoleMap.compute(channel, (ch, existing) -> { |
| final String error = ERR_CH_NOT_FOUND + channel.remoteAddress(); |
| checkNotNull(existing, error); |
| return role; |
| }); |
| } |
| |
| @Override |
| public OFControllerRole role(Channel channel) { |
| OFControllerRole role = controllerRoleMap.get(channel); |
| if (role == null) { |
| final String error = ERR_CH_NOT_FOUND + channel.remoteAddress(); |
| throw new IllegalStateException(error); |
| } |
| return role; |
| } |
| |
| @Override |
| public Set<Channel> controllerChannels() { |
| return ImmutableSet.copyOf(controllerRoleMap.keySet()); |
| } |
| |
| @Override |
| public void processPortAdded(Port port) { |
| sendPortStatus(port, OFPortReason.ADD); |
| } |
| |
| @Override |
| public void processPortRemoved(Port port) { |
| sendPortStatus(port, OFPortReason.DELETE); |
| } |
| |
| @Override |
| public void processPortDown(Port port) { |
| sendPortStatus(port, OFPortReason.MODIFY); |
| } |
| |
| @Override |
| public void processPortUp(Port port) { |
| sendPortStatus(port, OFPortReason.MODIFY); |
| } |
| |
| @Override |
| public void processFlowRemoved(FlowRule flowRule) { |
| // TODO generate FLOW_REMOVED message and send it to the controller |
| log.debug("processFlowRemoved: Functionality not yet supported for {}", flowRule); |
| } |
| |
| @Override |
| public void processPacketIn(InboundPacket packet) { |
| // TODO generate PACKET_IN message and send it to the controller |
| log.debug("processPacketIn: Functionality not yet supported for {}", packet); |
| } |
| |
| private void processPortMod(OFPortMod portMod) { |
| // process specified port |
| PortNumber portNumber = PortNumber.portNumber(portMod.getPortNo().getPortNumber()); |
| boolean disablePort = portMod.getConfig().contains(OFPortConfig.PORT_DOWN); |
| log.debug("processing PORT_MOD message - setting port {} state to {}", |
| portNumber, !disablePort); |
| virtualNetworkAdminService.updatePortState(networkId, deviceId, portNumber, !disablePort); |
| // TODO what side effects (e.g. cleaning flow mods) needs to be handled? |
| } |
| |
| private void processFlowMod(OFFlowMod flowMod) { |
| // convert OFFlowMod to FLowRule object |
| OFAgentVirtualFlowEntryBuilder flowEntryBuilder = |
| new OFAgentVirtualFlowEntryBuilder(deviceId, flowMod, driverService); |
| FlowEntry flowEntry = flowEntryBuilder.build(); |
| flowRuleService.applyFlowRules(flowEntry); |
| } |
| |
| // Methods that support GROUP_MOD |
| |
| private GroupDescription.Type getGroupType(OFGroupType type) { |
| switch (type) { |
| case ALL: |
| return GroupDescription.Type.ALL; |
| case INDIRECT: |
| return GroupDescription.Type.INDIRECT; |
| case SELECT: |
| return GroupDescription.Type.SELECT; |
| case FF: |
| return GroupDescription.Type.FAILOVER; |
| default: |
| log.error("Unsupported OF group type : {}", type); |
| break; |
| } |
| return null; |
| } |
| |
| private void processGroupMod(OFGroupMod groupMod) { |
| log.debug("processing GROUP_MOD {} message", groupMod.getCommand()); |
| |
| ApplicationId appId = ofSwitchService.appId(); |
| GroupKey appCookie = new DefaultGroupKey(networkId.toString().getBytes()); |
| switch (groupMod.getCommand()) { |
| case ADD: |
| // TODO return OFGroupModFailedCode.GROUP_EXISTS if group already exists |
| int groupId = groupMod.getGroup().getGroupNumber(); |
| OFGroupAdd groupAdd = (OFGroupAdd) groupMod; |
| GroupBuckets groupAddBuckets = new OFAgentVirtualGroupBucketEntryBuilder( |
| Dpid.dpid(Dpid.uri(dpid().getLong())), |
| groupAdd.getBuckets(), groupAdd.getGroupType(), driverService) |
| .build(); |
| GroupDescription groupDescription = new DefaultGroupDescription( |
| deviceId, getGroupType(groupAdd.getGroupType()), groupAddBuckets, |
| appCookie, groupId, appId); |
| groupService.addGroup(groupDescription); |
| break; |
| case MODIFY: |
| // TODO return OFGroupModFailedCode.INVALID_GROUP if group does not exist |
| OFGroupModify groupModify = (OFGroupModify) groupMod; |
| GroupBuckets groupModifyBuckets = new OFAgentVirtualGroupBucketEntryBuilder( |
| Dpid.dpid(Dpid.uri(dpid().getLong())), |
| groupModify.getBuckets(), groupModify.getGroupType(), driverService) |
| .build(); |
| groupService.setBucketsForGroup(deviceId, appCookie, groupModifyBuckets, |
| appCookie, appId); |
| break; |
| case DELETE: |
| groupService.removeGroup(deviceId, appCookie, appId); |
| break; |
| default: |
| // INSERT_BUCKET, REMOVE_BUCKET are effective OF 1.5. OFAgent supports 1.3. |
| log.warn("Unsupported GROUP_MOD {} message received for switch {}", |
| groupMod.getCommand(), this); |
| } |
| } |
| |
| // Methods that spport METER_MOD. |
| |
| private Band band(OFMeterBand ofMeterBand) { |
| DefaultBand.Builder builder = DefaultBand.builder(); |
| if (ofMeterBand instanceof OFMeterBandDrop) { |
| OFMeterBandDrop ofMeterBandDrop = (OFMeterBandDrop) ofMeterBand; |
| builder.ofType(Band.Type.DROP) |
| .burstSize(ofMeterBandDrop.getBurstSize()) |
| .withRate(ofMeterBandDrop.getRate()); |
| } else if (ofMeterBand instanceof OFMeterBandDscpRemark) { |
| OFMeterBandDscpRemark ofMeterBandDscpRemark = (OFMeterBandDscpRemark) ofMeterBand; |
| builder.ofType(Band.Type.REMARK) |
| .burstSize(ofMeterBandDscpRemark.getBurstSize()) |
| .withRate(ofMeterBandDscpRemark.getRate()) |
| .dropPrecedence(ofMeterBandDscpRemark.getPrecLevel()); |
| } else if (ofMeterBand instanceof OFMeterBandExperimenter) { |
| OFMeterBandExperimenter ofMeterBandExperimenter = (OFMeterBandExperimenter) ofMeterBand; |
| builder.ofType(Band.Type.EXPERIMENTAL) |
| .burstSize(ofMeterBandExperimenter.getBurstSize()) |
| .withRate(ofMeterBandExperimenter.getRate()); |
| } |
| return builder.build(); |
| } |
| |
| private MeterRequest.Builder meterRequestBuilder(OFMeterMod meterMod) { |
| Collection<Band> bands = meterMod.getBands().stream() |
| .map(ofMeterBand -> band(ofMeterBand)).collect(Collectors.toList()); |
| return DefaultMeterRequest.builder().forDevice(deviceId) |
| .withBands(bands).fromApp(ofSwitchService.appId()); |
| } |
| |
| private void meterModError(OFMeterMod meterMod, OFMeterModFailedCode code, |
| Channel channel) { |
| OFMeterModFailedErrorMsg errorMsg = FACTORY.errorMsgs() |
| .buildMeterModFailedErrorMsg() |
| .setXid(meterMod.getXid()) |
| .setCode(code) |
| .build(); |
| channel.writeAndFlush(Collections.singletonList(errorMsg)); |
| log.debug("Sent meterMod error {}", code); |
| } |
| |
| private void processMeterMod(OFMeterMod meterMod, Channel channel) { |
| log.debug("processing METER_MOD {} message", meterMod.getCommand()); |
| |
| long meterModId = meterMod.getMeterId(); |
| Meter existingMeter = meterService.getMeter(deviceId, MeterId.meterId(meterModId)); |
| MeterRequest meterRequest = null; |
| switch (meterMod.getCommand()) { |
| case ADD: |
| if (existingMeter != null) { |
| meterModError(meterMod, OFMeterModFailedCode.METER_EXISTS, channel); |
| return; |
| } |
| meterRequest = meterRequestBuilder(meterMod).add(); |
| break; |
| case MODIFY: |
| if (existingMeter == null) { |
| meterModError(meterMod, OFMeterModFailedCode.UNKNOWN_METER, channel); |
| return; |
| } |
| meterRequest = meterRequestBuilder(meterMod).add(); |
| break; |
| case DELETE: |
| // non-existing meter id will not result in OFMeterModFailedErrorMsg |
| // being sent to the controller |
| meterRequest = meterRequestBuilder(meterMod).remove(); |
| break; |
| default: |
| log.warn("Unexpected message {} received for switch {}", |
| meterMod.getCommand(), this); |
| return; |
| } |
| meterService.submit(meterRequest); |
| } |
| |
| @Override |
| public void processControllerCommand(Channel channel, OFMessage msg) { |
| |
| OFControllerRole myRole = role(channel); |
| if (OFControllerRole.ROLE_SLAVE.equals(myRole)) { |
| OFBadRequestErrorMsg errorMsg = FACTORY.errorMsgs() |
| .buildBadRequestErrorMsg() |
| .setXid(msg.getXid()) |
| .setCode(OFBadRequestCode.IS_SLAVE) |
| .build(); |
| channel.writeAndFlush(Collections.singletonList(errorMsg)); |
| return; |
| } |
| |
| switch (msg.getType()) { |
| case PORT_MOD: |
| OFPortMod portMod = (OFPortMod) msg; |
| processPortMod(portMod); |
| break; |
| case FLOW_MOD: |
| OFFlowMod flowMod = (OFFlowMod) msg; |
| processFlowMod(flowMod); |
| break; |
| case GROUP_MOD: |
| OFGroupMod groupMod = (OFGroupMod) msg; |
| processGroupMod(groupMod); |
| break; |
| case METER_MOD: |
| OFMeterMod meterMod = (OFMeterMod) msg; |
| processMeterMod(meterMod, channel); |
| break; |
| case TABLE_MOD: |
| log.debug("processControllerCommand: {} not yet supported for {}", |
| msg.getType(), msg); |
| break; |
| default: |
| log.warn("Unexpected message {} received for switch {}", |
| msg.getType(), this); |
| } |
| } |
| |
| private void sendPortStatus(Port port, OFPortReason ofPortReason) { |
| Set<Channel> channels = controllerChannels(); |
| if (channels.isEmpty()) { |
| log.trace("No channels present. Port status will not be sent."); |
| return; |
| } |
| OFPortDesc ofPortDesc = portDesc(port); |
| OFPortStatus ofPortStatus = FACTORY.buildPortStatus() |
| .setDesc(ofPortDesc) |
| .setReason(ofPortReason) |
| .build(); |
| log.trace("Sending port status {}", ofPortStatus); |
| channels.forEach(channel -> { |
| channel.writeAndFlush(Collections.singletonList(ofPortStatus)); |
| }); |
| } |
| |
| private OFPortDesc portDesc(Port port) { |
| OFPort ofPort = OFPort.of((int) port.number().toLong()); |
| Set<OFPortConfig> portConfigs = Sets.newHashSet(); |
| Set<OFPortState> portStates = Sets.newHashSet(); |
| if (!port.isEnabled()) { |
| portConfigs.add(OFPortConfig.PORT_DOWN); |
| portStates.add(OFPortState.LINK_DOWN); |
| } |
| OFPortDesc ofPortDesc = FACTORY.buildPortDesc() |
| .setPortNo(ofPort) |
| .setState(portStates) |
| .setConfig(portConfigs) |
| .build(); |
| return ofPortDesc; |
| } |
| |
| private OFPortStatsEntry portStatsEntry(PortStatistics portStatistic) { |
| OFPortStatsEntry ofPortStatsEntry = FACTORY.buildPortStatsEntry() |
| .setPortNo(OFPort.of(portStatistic.port())) |
| .setTxBytes(U64.of(portStatistic.bytesSent())) |
| .setTxPackets(U64.of(portStatistic.packetsSent())) |
| .setTxDropped(U64.of(portStatistic.packetsTxDropped())) |
| .setTxErrors(U64.of(portStatistic.packetsTxErrors())) |
| .setRxBytes(U64.of(portStatistic.bytesReceived())) |
| .setRxPackets(U64.of(portStatistic.packetsReceived())) |
| .setRxDropped(U64.of(portStatistic.packetsRxDropped())) |
| .setRxErrors(U64.of(portStatistic.packetsRxErrors())) |
| .setDurationSec(portStatistic.durationSec()) |
| .setDurationNsec(portStatistic.durationNano()) |
| .build(); |
| return ofPortStatsEntry; |
| } |
| |
| private OFFlowStatsEntry ofFlowStatsEntry(FlowEntry flowEntry) { |
| // TODO get match from flowEntry.selector() |
| Match.Builder matchB = FACTORY.buildMatch(); |
| OFActionOutput actionOutput = FACTORY.actions() |
| .buildOutput().build(); |
| // TODO get instructions from flowEntry.treatment() |
| OFInstruction instruction = FACTORY.instructions() |
| .applyActions(Collections.singletonList(actionOutput)); |
| OFFlowStatsEntry ofFlowStatsEntry = FACTORY.buildFlowStatsEntry() |
| .setMatch(matchB.build()) |
| .setInstructions(Collections.singletonList(instruction)) |
| .setTableId(TableId.of(flowEntry.tableId())) |
| .setHardTimeout(flowEntry.hardTimeout()) |
| .setIdleTimeout(flowEntry.timeout()) |
| .setCookie(U64.of(flowEntry.id().value())) |
| .setPriority(flowEntry.priority()) |
| .setDurationSec(flowEntry.life()) |
| .setPacketCount(U64.of(flowEntry.packets())) |
| .setByteCount(U64.of(flowEntry.bytes())) |
| .build(); |
| return ofFlowStatsEntry; |
| } |
| |
| private OFTableStatsEntry ofFlowTableStatsEntry(TableStatisticsEntry tableStatisticsEntry) { |
| OFTableStatsEntry ofTableStatsEntry = FACTORY.buildTableStatsEntry() |
| .setTableId(TableId.of(tableStatisticsEntry.tableId())) |
| .setActiveCount(tableStatisticsEntry.activeFlowEntries()) |
| .setLookupCount(U64.of(tableStatisticsEntry.packetsLookedup())) |
| .setMatchedCount(U64.of(tableStatisticsEntry.packetsLookedup())) |
| .build(); |
| return ofTableStatsEntry; |
| } |
| |
| private OFGroupStatsEntry ofGroupStatsEntry(Group group) { |
| List<OFBucketCounter> ofBucketCounters = Lists.newArrayList(); |
| group.buckets().buckets().forEach(groupBucket -> { |
| ofBucketCounters.add(FACTORY.bucketCounter( |
| U64.of(groupBucket.packets()), U64.of(groupBucket.bytes()))); |
| }); |
| OFGroupStatsEntry entry = FACTORY.buildGroupStatsEntry() |
| .setGroup(OFGroup.of(group.id().id())) |
| .setDurationSec(group.life()) |
| .setPacketCount(U64.of(group.packets())) |
| .setByteCount(U64.of(group.bytes())) |
| .setRefCount(group.referenceCount()) |
| .setBucketStats(ofBucketCounters) |
| .build(); |
| return entry; |
| } |
| |
| private OFGroupDescStatsEntry ofGroupDescStatsEntry(Group group) { |
| List<OFBucket> ofBuckets = Lists.newArrayList(); |
| group.buckets().buckets().forEach(groupBucket -> { |
| ofBuckets.add(FACTORY.buildBucket() |
| .setWeight(groupBucket.weight()) |
| .setWatchGroup(OFGroup.of(groupBucket.watchGroup().id())) |
| .setWatchPort(OFPort.of((int) groupBucket.watchPort().toLong())) |
| .build() |
| ); |
| }); |
| OFGroup ofGroup = OFGroup.of(group.givenGroupId()); |
| OFGroupType ofGroupType = OFGroupType.valueOf(group.type().name()); |
| OFGroupDescStatsEntry entry = FACTORY.buildGroupDescStatsEntry() |
| .setGroup(ofGroup) |
| .setGroupType(ofGroupType) |
| .setBuckets(ofBuckets) |
| .build(); |
| return entry; |
| } |
| |
| @Override |
| public void processStatsRequest(Channel channel, OFMessage msg) { |
| if (msg.getType() != OFType.STATS_REQUEST) { |
| log.warn("Ignoring message of type {}.", msg.getType()); |
| return; |
| } |
| |
| OFStatsRequest ofStatsRequest = (OFStatsRequest) msg; |
| OFStatsReply ofStatsReply = null; |
| switch (ofStatsRequest.getStatsType()) { |
| case PORT_DESC: |
| List<OFPortDesc> portDescs = new ArrayList<>(); |
| Set<Port> ports = ofSwitchService.ports(networkId, deviceId); |
| ports.forEach(port -> { |
| OFPortDesc ofPortDesc = portDesc(port); |
| portDescs.add(ofPortDesc); |
| }); |
| ofStatsReply = FACTORY.buildPortDescStatsReply() |
| .setXid(msg.getXid()) |
| .setEntries(portDescs) |
| //TODO add details |
| .build(); |
| break; |
| case PORT: |
| OFPortStatsRequest portStatsRequest = (OFPortStatsRequest) msg; |
| OFPort ofPort = portStatsRequest.getPortNo(); |
| List<OFPortStatsEntry> portStatsEntries = new ArrayList<>(); |
| List<PortStatistics> portStatistics = |
| ofSwitchService.getPortStatistics(networkId, deviceId); |
| if (ofPort.equals(OFPort.ANY)) { |
| portStatistics.forEach(portStatistic -> { |
| OFPortStatsEntry ofPortStatsEntry = portStatsEntry(portStatistic); |
| portStatsEntries.add(ofPortStatsEntry); |
| }); |
| } |
| ofStatsReply = FACTORY.buildPortStatsReply() |
| .setEntries(portStatsEntries) |
| .setXid(msg.getXid()) |
| .build(); |
| break; |
| case METER_FEATURES: |
| OFMeterFeatures ofMeterFeatures = FACTORY.buildMeterFeatures() |
| .build(); |
| ofStatsReply = FACTORY.buildMeterFeaturesStatsReply() |
| .setXid(msg.getXid()) |
| .setFeatures(ofMeterFeatures) |
| //TODO add details |
| .build(); |
| break; |
| case FLOW: |
| List<OFFlowStatsEntry> flowStatsEntries = new ArrayList<>(); |
| List<FlowEntry> flowStats = ofSwitchService.getFlowEntries(networkId, deviceId); |
| flowStats.forEach(flowEntry -> { |
| OFFlowStatsEntry ofFlowStatsEntry = ofFlowStatsEntry(flowEntry); |
| flowStatsEntries.add(ofFlowStatsEntry); |
| }); |
| ofStatsReply = FACTORY.buildFlowStatsReply() |
| .setEntries(flowStatsEntries) |
| .setXid(msg.getXid()) |
| .build(); |
| break; |
| case TABLE: |
| List<OFTableStatsEntry> ofTableStatsEntries = new ArrayList<>(); |
| List<TableStatisticsEntry> tableStats = ofSwitchService.getFlowTableStatistics(networkId, deviceId); |
| tableStats.forEach(tableStatisticsEntry -> { |
| OFTableStatsEntry ofFlowStatsEntry = ofFlowTableStatsEntry(tableStatisticsEntry); |
| ofTableStatsEntries.add(ofFlowStatsEntry); |
| }); |
| ofStatsReply = FACTORY.buildTableStatsReply() |
| .setEntries(ofTableStatsEntries) |
| .setXid(msg.getXid()) |
| .build(); |
| break; |
| case GROUP: |
| List<Group> groupStats = ofSwitchService.getGroups(networkId, deviceId); |
| List<OFGroupStatsEntry> ofGroupStatsEntries = new ArrayList<>(); |
| groupStats.forEach(group -> { |
| OFGroupStatsEntry entry = ofGroupStatsEntry(group); |
| ofGroupStatsEntries.add(entry); |
| }); |
| ofStatsReply = FACTORY.buildGroupStatsReply() |
| .setEntries(ofGroupStatsEntries) |
| .setXid(msg.getXid()) |
| .build(); |
| break; |
| case GROUP_DESC: |
| List<OFGroupDescStatsEntry> ofGroupDescStatsEntries = new ArrayList<>(); |
| List<Group> groupStats2 = ofSwitchService.getGroups(networkId, deviceId); |
| groupStats2.forEach(group -> { |
| OFGroupDescStatsEntry entry = ofGroupDescStatsEntry(group); |
| ofGroupDescStatsEntries.add(entry); |
| }); |
| ofStatsReply = FACTORY.buildGroupDescStatsReply() |
| .setEntries(ofGroupDescStatsEntries) |
| .setXid(msg.getXid()) |
| .build(); |
| break; |
| case DESC: |
| ofStatsReply = FACTORY.buildDescStatsReply() |
| .setXid(msg.getXid()) |
| .build(); |
| break; |
| default: |
| log.debug("Functionality not yet supported for type {} statsType{} msg {}", |
| msg.getType(), ofStatsRequest.getStatsType(), msg); |
| break; |
| } |
| |
| if (ofStatsReply != null) { |
| log.trace("request {}; reply {}", msg, ofStatsReply); |
| channel.writeAndFlush(Collections.singletonList(ofStatsReply)); |
| } |
| |
| } |
| |
| @Override |
| public void processRoleRequest(Channel channel, OFMessage msg) { |
| OFRoleRequest ofRoleRequest = (OFRoleRequest) msg; |
| OFControllerRole oldRole = role(channel); |
| OFControllerRole newRole = ofRoleRequest.getRole(); |
| if (oldRole.equals(newRole)) { |
| log.trace("No change needed to existing role {}", oldRole); |
| } else { |
| log.trace("Changing role from {} to {}", oldRole, newRole); |
| setRole(channel, newRole); |
| } |
| OFRoleReply ofRoleReply = FACTORY.buildRoleReply() |
| .setRole(role(channel)) |
| .setXid(msg.getXid()) |
| .build(); |
| channel.writeAndFlush(Collections.singletonList(ofRoleReply)); |
| log.trace("request {}; reply {}", msg, ofRoleReply); |
| } |
| |
| @Override |
| public void processFeaturesRequest(Channel channel, OFMessage msg) { |
| OFFeaturesReply ofFeaturesReply = FACTORY.buildFeaturesReply() |
| .setDatapathId(dpId) |
| .setNBuffers(NUM_BUFFERS) |
| .setNTables(NUM_TABLES) |
| .setCapabilities(capabilities.ofSwitchCapabilities()) |
| .setXid(msg.getXid()) |
| .build(); |
| channel.writeAndFlush(Collections.singletonList(ofFeaturesReply)); |
| } |
| |
| @Override |
| public void processLldp(Channel channel, OFMessage msg) { |
| log.trace("processLldp msg{}", msg); |
| |
| // For each output port, look up neighbour port. |
| // If neighbour port exists, have the neighbour switch send lldp response. |
| // Modeled after how OpenVirtex handles lldp from external controller. |
| OFPacketOut ofPacketOut = (OFPacketOut) msg; |
| List<OFAction> actions = ofPacketOut.getActions(); |
| for (final OFAction action : actions) { |
| OFActionType actionType = action.getType(); |
| if (actionType.equals(OFActionType.OUTPUT)) { |
| OFActionOutput ofActionOutput = (OFActionOutput) action; |
| OFPort ofPort = ofActionOutput.getPort(); |
| ConnectPoint neighbourCp = |
| ofSwitchService.neighbour(networkId, deviceId, |
| PortNumber.portNumber(ofPort.getPortNumber())); |
| if (neighbourCp == null) { |
| log.trace("No neighbour found for {} {}", deviceId, ofPort); |
| continue; |
| } |
| OFSwitch neighbourSwitch = ofSwitchService.ofSwitch(networkId, |
| neighbourCp.deviceId()); |
| neighbourSwitch.sendLldpResponse(ofPacketOut, neighbourCp.port()); |
| } |
| } |
| } |
| |
| @Override |
| public void sendLldpResponse(OFPacketOut po, PortNumber inPort) { |
| Match.Builder matchB = FACTORY.buildMatch(); |
| matchB.setExact(MatchField.IN_PORT, OFPort.of((int) inPort.toLong())); |
| OFPacketIn pi = FACTORY.buildPacketIn() |
| .setBufferId(po.getBufferId()) |
| .setMatch(matchB.build()) |
| .setReason(OFPacketInReason.NO_MATCH) |
| .setData(po.getData()) |
| .build(); |
| log.trace("Sending packet in {}", pi); |
| controllerChannels().forEach(channel -> { |
| channel.writeAndFlush(Collections.singletonList(pi)); |
| }); |
| } |
| |
| @Override |
| public void sendOfHello(Channel channel) { |
| OFHello ofHello = FACTORY.buildHello() |
| .setXid(this.handshakeTransactionIds--) |
| .build(); |
| channel.writeAndFlush(Collections.singletonList(ofHello)); |
| } |
| |
| @Override |
| public void processEchoRequest(Channel channel, OFMessage msg) { |
| OFEchoReply ofEchoReply = FACTORY.buildEchoReply() |
| .setXid(msg.getXid()) |
| .setData(((OFEchoRequest) msg).getData()) |
| .build(); |
| channel.writeAndFlush(Collections.singletonList(ofEchoReply)); |
| } |
| |
| @Override |
| public void processGetConfigRequest(Channel channel, OFMessage msg) { |
| OFGetConfigReply ofGetConfigReply = FACTORY.buildGetConfigReply() |
| .setXid(msg.getXid()) |
| .setMissSendLen(missSendLen) |
| .build(); |
| log.trace("request {}; reply {}", msg, ofGetConfigReply); |
| channel.writeAndFlush(Collections.singletonList(ofGetConfigReply)); |
| } |
| |
| @Override |
| public void processSetConfigMessage(Channel channel, OFMessage msg) { |
| OFSetConfig ofSetConfig = (OFSetConfig) msg; |
| if (missSendLen != ofSetConfig.getMissSendLen()) { |
| log.trace("Changing missSendLen from {} to {}.", |
| missSendLen, ofSetConfig.getMissSendLen()); |
| missSendLen = ofSetConfig.getMissSendLen(); |
| } |
| |
| // SetConfig message is not acknowledged |
| } |
| |
| @Override |
| public void processBarrierRequest(Channel channel, OFMessage msg) { |
| // TODO check previous state requests have been handled before issuing BarrierReply |
| OFBarrierReply ofBarrierReply = FACTORY.buildBarrierReply() |
| .setXid(msg.getXid()) |
| .build(); |
| log.trace("request {}; reply {}", msg, ofBarrierReply); |
| channel.writeAndFlush(Collections.singletonList(ofBarrierReply)); |
| } |
| } |