blob: cd6bfeff722d46c2a207b8c9742d124dd5a83d65 [file] [log] [blame]
/*
* 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.openflow.drivers;
import org.onosproject.openflow.controller.Dpid;
import org.onosproject.openflow.controller.RoleState;
import org.onosproject.openflow.controller.driver.AbstractOpenFlowSwitch;
import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeAlreadyStarted;
import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeCompleted;
import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeNotStarted;
import org.projectfloodlight.openflow.protocol.OFAsyncGetReply;
import org.projectfloodlight.openflow.protocol.OFBarrierRequest;
import org.projectfloodlight.openflow.protocol.OFErrorMsg;
import org.projectfloodlight.openflow.protocol.OFGroupDescStatsReply;
import org.projectfloodlight.openflow.protocol.OFGroupFeaturesStatsReply;
import org.projectfloodlight.openflow.protocol.OFMatchV3;
import org.projectfloodlight.openflow.protocol.OFOxmList;
import org.projectfloodlight.openflow.protocol.OFPortDesc;
import org.projectfloodlight.openflow.protocol.OFStatsReply;
import org.projectfloodlight.openflow.protocol.OFFlowMod;
import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
import org.projectfloodlight.openflow.protocol.OFFactory;
import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFType;
import org.projectfloodlight.openflow.protocol.action.OFAction;
import org.projectfloodlight.openflow.protocol.instruction.OFInstruction;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmEthType;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmInPort;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmVlanVid;
import org.projectfloodlight.openflow.types.EthType;
import org.projectfloodlight.openflow.types.MacAddress;
import org.projectfloodlight.openflow.types.OFBufferId;
import org.projectfloodlight.openflow.types.OFPort;
import org.projectfloodlight.openflow.types.OFVlanVidMatch;
import org.projectfloodlight.openflow.types.TableId;
import org.projectfloodlight.openflow.types.U32;
import org.projectfloodlight.openflow.util.HexString;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public class OFSwitchImplSpringOpenTTP extends AbstractOpenFlowSwitch {
private OFFactory factory;
private final AtomicBoolean driverHandshakeComplete;
private AtomicBoolean haltStateMachine;
private DriverState driverState;
/* Default table ID - compatible with CpqD switch */
private static final int TABLE_VLAN = 0;
private static final int TABLE_TMAC = 1;
private static final int TABLE_IPV4_UNICAST = 2;
private static final int TABLE_MPLS = 3;
private static final int TABLE_ACL = 5;
private static final long TEST_FLOW_REMOVED_MASK = 0xf;
private static final long TEST_PACKET_IN_MASK = 0x7;
private static final long TEST_PORT_STATUS_MASK = 0x7;
private static final int OFPCML_NO_BUFFER = 0xffff;
private long barrierXidToWaitFor = -1;
/* Set the default values. These variables will get
* overwritten based on the switch vendor type
*/
protected int vlanTableId = TABLE_VLAN;
protected int tmacTableId = TABLE_TMAC;
protected int ipv4UnicastTableId = TABLE_IPV4_UNICAST;
protected int mplsTableId = TABLE_MPLS;
protected int aclTableId = TABLE_ACL;
/* priority values for OF message */
private static final short MAX_PRIORITY = (short) 0xffff;
private static final short PRIORITY_MULTIPLIER = (short) 2046;
private static final short MIN_PRIORITY = 0x0;
protected OFSwitchImplSpringOpenTTP(Dpid dpid, OFDescStatsReply desc) {
super(dpid);
driverHandshakeComplete = new AtomicBoolean(false);
haltStateMachine = new AtomicBoolean(false);
driverState = DriverState.INIT;
setSwitchDescription(desc);
}
@Override
public String toString() {
return "OFSwitchImplSpringOpenTTP [" + ((channel != null)
? channel.getRemoteAddress() : "?")
+ " DPID[" + ((this.getStringId() != null) ?
this.getStringId() : "?") + "]]";
}
@Override
public Boolean supportNxRole() {
return null;
}
@Override
public void startDriverHandshake() {
log.debug("Starting driver handshake for sw {}", getStringId());
if (startDriverHandshakeCalled) {
throw new SwitchDriverSubHandshakeAlreadyStarted();
}
startDriverHandshakeCalled = true;
factory = this.factory();
try {
nextDriverState();
} catch (IOException e) {
log.error("Error {} during driver handshake for sw {}", e.getCause(),
getStringId());
}
}
@Override
public boolean isDriverHandshakeComplete() {
if (!startDriverHandshakeCalled) {
throw new SwitchDriverSubHandshakeNotStarted();
}
return driverHandshakeComplete.get();
}
@Override
public void processDriverHandshakeMessage(OFMessage m) {
if (!startDriverHandshakeCalled) {
throw new SwitchDriverSubHandshakeNotStarted();
}
if (driverHandshakeComplete.get()) {
throw new SwitchDriverSubHandshakeCompleted(m);
}
try {
processOFMessage(m);
} catch (IOException e) {
log.error("Error generated when processing OFMessage {}", e.getCause());
}
}
@Override
public void write(OFMessage msg) {
this.channel.write(Collections.singletonList(msg));
}
@Override
public void write(List<OFMessage> msgs) {
this.channel.write(msgs);
}
@Override
public void sendMsg(OFMessage m, TableType tableType) {
if (m.getType() == OFType.FLOW_MOD) {
OFFlowMod flowMod = (OFFlowMod) m;
OFFlowMod.Builder builder = flowMod.createBuilder();
builder.setTableId(getTableId(tableType));
OFFlowMod newFlowMod = builder.build();
if (role == RoleState.MASTER) {
this.write(newFlowMod);
}
} else {
if (role == RoleState.MASTER) {
this.write(m);
}
}
}
/*
* Driver handshake state machine
*/
enum DriverState {
INIT,
SET_TABLE_MISS_ENTRIES,
SET_TABLE_VLAN_TMAC,
AUDIT_GROUPS,
SET_GROUPS,
VERIFY_GROUPS,
SET_ADJACENCY_LABELS,
EXIT
}
protected void nextDriverState() throws IOException {
DriverState currentState = driverState;
if (haltStateMachine.get()) {
return;
}
switch (currentState) {
case INIT:
driverState = DriverState.SET_TABLE_MISS_ENTRIES;
setTableMissEntries();
sendHandshakeBarrier();
break;
case SET_TABLE_MISS_ENTRIES:
driverState = DriverState.SET_TABLE_VLAN_TMAC;
/* TODO: read network configuration
boolean isConfigured = getNetworkConfig();
if (!isConfigured) {
return; // this will result in a handshake timeout
}
*/
populateTableVlan();
populateTableTMac();
sendHandshakeBarrier();
break;
case SET_TABLE_VLAN_TMAC:
driverState = DriverState.EXIT;
driverHandshakeComplete.set(true);
log.debug("Driver handshake is complete");
break;
case EXIT:
default:
driverState = DriverState.EXIT;
log.error("Driver handshake has exited for sw: {}", getStringId());
}
}
private void processStatsReply(OFStatsReply sr) {
switch (sr.getStatsType()) {
case AGGREGATE:
break;
case DESC:
break;
case EXPERIMENTER:
break;
case FLOW:
break;
case GROUP_DESC:
processGroupDesc((OFGroupDescStatsReply) sr);
break;
case GROUP_FEATURES:
processGroupFeatures((OFGroupFeaturesStatsReply) sr);
break;
case METER_CONFIG:
break;
case METER_FEATURES:
break;
case PORT_DESC:
break;
case TABLE_FEATURES:
break;
default:
break;
}
}
private void processOFMessage(OFMessage m) throws IOException {
switch (m.getType()) {
case BARRIER_REPLY:
processBarrierReply(m);
break;
case ERROR:
processErrorMessage(m);
break;
case GET_ASYNC_REPLY:
OFAsyncGetReply asrep = (OFAsyncGetReply) m;
decodeAsyncGetReply(asrep);
break;
case PACKET_IN:
// not ready to handle packet-ins
break;
case QUEUE_GET_CONFIG_REPLY:
// not doing queue config yet
break;
case STATS_REPLY:
processStatsReply((OFStatsReply) m);
break;
case ROLE_REPLY: // channelHandler should handle this
case PORT_STATUS: // channelHandler should handle this
case FEATURES_REPLY: // don't care
case FLOW_REMOVED: // don't care
default:
log.debug("Received message {} during switch-driver subhandshake "
+ "from switch {} ... Ignoring message", m, getStringId());
}
}
private void processBarrierReply(OFMessage m) throws IOException {
if (m.getXid() == barrierXidToWaitFor) {
// Driver state-machine progresses to the next state.
// If Barrier messages is not received, then eventually
// the ChannelHandler state machine will timeout, and the switch
// will be disconnected.
nextDriverState();
} else {
log.error("Received incorrect barrier-message xid {} (expected: {}) in "
+ "switch-driver state {} for switch {}", m, barrierXidToWaitFor,
driverState, getStringId());
}
}
private void processErrorMessage(OFMessage m) {
log.error("Switch {} Error {} in DriverState", getStringId(),
(OFErrorMsg) m, driverState);
}
private void processGroupFeatures(OFGroupFeaturesStatsReply gfsr) {
log.info("Sw: {} Group Features {}", getStringId(), gfsr);
}
private void processGroupDesc(OFGroupDescStatsReply gdsr) {
log.info("Sw: {} Group Desc {}", getStringId(), gdsr);
}
/*
* Utility functions
*/
private void decodeAsyncGetReply(OFAsyncGetReply rep) {
long frm = rep.getFlowRemovedMaskEqualMaster();
//long frs = rep.getFlowRemovedMaskSlave();
long pim = rep.getPacketInMaskEqualMaster();
//long pis = rep.getPacketInMaskSlave();
long psm = rep.getPortStatusMaskEqualMaster();
//long pss = rep.getPortStatusMaskSlave();
if (role == RoleState.MASTER || role == RoleState.EQUAL) { // should separate
log.info("FRM:{}", HexString.toHexString((frm & TEST_FLOW_REMOVED_MASK)));
log.info("PIM:{}", HexString.toHexString((pim & TEST_PACKET_IN_MASK)));
log.info("PSM:{}", HexString.toHexString((psm & TEST_PORT_STATUS_MASK)));
}
}
protected void setTableMissEntries() throws IOException {
// set all table-miss-entries
populateTableMissEntry(vlanTableId, true, false, false, -1);
populateTableMissEntry(tmacTableId, true, false, false, -1);
populateTableMissEntry(ipv4UnicastTableId, false, true, true,
aclTableId);
populateTableMissEntry(mplsTableId, false, true, true,
aclTableId);
populateTableMissEntry(aclTableId, false, false, false, -1);
log.debug("TableMissEntries are set");
}
/**
* Adds a table-miss-entry to a pipeline table.
* <p>
* The table-miss-entry can be added with 'write-actions' or
* 'apply-actions'. It can also add a 'goto-table' instruction. By default
* if none of the booleans in the call are set, then the table-miss entry is
* added with no instructions, which means that if a packet hits the
* table-miss-entry, pipeline execution will stop, and the action set
* associated with the packet will be executed.
*
* @param tableToAdd the table to where the table-miss-entry will be added
* @param toControllerNow as an APPLY_ACTION instruction
* @param toControllerWrite as a WRITE_ACTION instruction
* @param toTable as a GOTO_TABLE instruction
* @param tableToSend the table to send as per the GOTO_TABLE instruction it
* needs to be set if 'toTable' is true. Ignored of 'toTable' is
* false.
*/
protected void populateTableMissEntry(int tableToAdd, boolean toControllerNow,
boolean toControllerWrite,
boolean toTable, int tableToSend) {
OFOxmList oxmList = OFOxmList.EMPTY;
OFMatchV3 match = factory.buildMatchV3()
.setOxmList(oxmList)
.build();
OFAction outc = factory.actions()
.buildOutput()
.setPort(OFPort.CONTROLLER)
.setMaxLen(OFPCML_NO_BUFFER)
.build();
List<OFInstruction> instructions = new ArrayList<OFInstruction>();
if (toControllerNow) {
// table-miss instruction to send to controller immediately
OFInstruction instr = factory.instructions()
.buildApplyActions()
.setActions(Collections.singletonList(outc))
.build();
instructions.add(instr);
}
if (toControllerWrite) {
// table-miss instruction to write-action to send to controller
// this will be executed whenever the action-set gets executed
OFInstruction instr = factory.instructions()
.buildWriteActions()
.setActions(Collections.singletonList(outc))
.build();
instructions.add(instr);
}
if (toTable) {
// table-miss instruction to goto-table x
OFInstruction instr = factory.instructions()
.gotoTable(TableId.of(tableToSend));
instructions.add(instr);
}
if (!toControllerNow && !toControllerWrite && !toTable) {
// table-miss has no instruction - at which point action-set will be
// executed - if there is an action to output/group in the action
// set
// the packet will be sent there, otherwise it will be dropped.
instructions = Collections.<OFInstruction>emptyList();
}
OFMessage tableMissEntry = factory.buildFlowAdd()
.setTableId(TableId.of(tableToAdd))
.setMatch(match) // match everything
.setInstructions(instructions)
.setPriority(MIN_PRIORITY)
.setBufferId(OFBufferId.NO_BUFFER)
.setIdleTimeout(0)
.setHardTimeout(0)
.setXid(getNextTransactionId())
.build();
write(tableMissEntry);
}
private void populateTableVlan() throws IOException {
List<OFMessage> msglist = new ArrayList<OFMessage>();
for (OFPortDesc p : getPorts()) {
int pnum = p.getPortNo().getPortNumber();
if (U32.of(pnum).compareTo(U32.of(OFPort.MAX.getPortNumber())) < 1) {
OFOxmInPort oxp = factory.oxms().inPort(p.getPortNo());
OFOxmVlanVid oxv = factory.oxms()
.vlanVid(OFVlanVidMatch.UNTAGGED);
OFOxmList oxmList = OFOxmList.of(oxp, oxv);
OFMatchV3 match = factory.buildMatchV3()
.setOxmList(oxmList).build();
// TODO: match on vlan-tagged packets for vlans configured on
// subnet ports and strip-vlan
OFInstruction gotoTbl = factory.instructions().buildGotoTable()
.setTableId(TableId.of(tmacTableId)).build();
List<OFInstruction> instructions = new ArrayList<OFInstruction>();
instructions.add(gotoTbl);
OFMessage flowEntry = factory.buildFlowAdd()
.setTableId(TableId.of(vlanTableId))
.setMatch(match)
.setInstructions(instructions)
.setPriority(1000) // does not matter - all rules
// exclusive
.setBufferId(OFBufferId.NO_BUFFER)
.setIdleTimeout(0)
.setHardTimeout(0)
.setXid(getNextTransactionId())
.build();
msglist.add(flowEntry);
}
}
write(msglist);
log.debug("Adding {} port/vlan-rules in sw {}", msglist.size(), getStringId());
}
private void populateTableTMac() throws IOException {
// match for router-mac and ip-packets
OFOxmEthType oxe = factory.oxms().ethType(EthType.IPv4);
/* TODO: need to read network config and need to allow only
the packets with DMAC as the correspondent router MAC address
Until network configuration is implemented, all packets are allowed
OFOxmEthDst dmac = factory.oxms().ethDst(getRouterMacAddr());
OFOxmList oxmListIp = OFOxmList.of(dmac, oxe);
OFMatchV3 matchIp = factory.buildMatchV3()
.setOxmList(oxmListIp).build();
*/
OFOxmList oxmList = OFOxmList.EMPTY;
OFMatchV3 matchIp = factory.buildMatchV3()
.setOxmList(oxmList)
.build();
OFInstruction gotoTblIp = factory.instructions().buildGotoTable()
.setTableId(TableId.of(ipv4UnicastTableId)).build();
List<OFInstruction> instructionsIp = Collections.singletonList(gotoTblIp);
OFMessage ipEntry = factory.buildFlowAdd()
.setTableId(TableId.of(tmacTableId))
.setMatch(matchIp)
.setInstructions(instructionsIp)
.setPriority(1000) // strict priority required lower than
// multicastMac
.setBufferId(OFBufferId.NO_BUFFER)
.setIdleTimeout(0)
.setHardTimeout(0)
.setXid(getNextTransactionId())
.build();
// match for router-mac and mpls packets
OFOxmEthType oxmpls = factory.oxms().ethType(EthType.MPLS_UNICAST);
/* TODO: need to read network config and need to allow only
the packets with DMAC as the correspondent router MAC address
OFOxmList oxmListMpls = OFOxmList.of(dmac, oxmpls);
OFMatchV3 matchMpls = factory.buildMatchV3()
.setOxmList(oxmListMpls).build();
*/
OFOxmList oxmListMpls = OFOxmList.EMPTY;
OFMatchV3 matchMpls = factory.buildMatchV3()
.setOxmList(oxmList)
.build();
OFInstruction gotoTblMpls = factory.instructions().buildGotoTable()
.setTableId(TableId.of(mplsTableId)).build();
List<OFInstruction> instructionsMpls = Collections.singletonList(gotoTblMpls);
OFMessage mplsEntry = factory.buildFlowAdd()
.setTableId(TableId.of(tmacTableId))
.setMatch(matchMpls)
.setInstructions(instructionsMpls)
.setPriority(1001) // strict priority required lower than
// multicastMac
.setBufferId(OFBufferId.NO_BUFFER)
.setIdleTimeout(0)
.setHardTimeout(0)
.setXid(getNextTransactionId())
.build();
log.debug("Adding termination-mac-rules in sw {}", getStringId());
List<OFMessage> msglist = new ArrayList<OFMessage>(2);
msglist.add(ipEntry);
msglist.add(mplsEntry);
write(msglist);
}
private MacAddress getRouterMacAddr() {
// TODO: need to read network config : RouterIp
return MacAddress.of("00:00:00:00:00:00");
}
private TableId getTableId(TableType tableType) {
switch (tableType) {
case IP:
return TableId.of(ipv4UnicastTableId);
case MPLS:
return TableId.of(mplsTableId);
case ACL:
return TableId.of(aclTableId);
default: {
log.error("Table type {} is not supported in the driver", tableType);
return TableId.NONE;
}
}
}
private void sendHandshakeBarrier() throws IOException {
long xid = getNextTransactionId();
barrierXidToWaitFor = xid;
OFBarrierRequest br = factory()
.buildBarrierRequest()
.setXid(xid)
.build();
write(br);
}
}