/* * Copyright (c) 2016 Pantheon Technologies s.r.o. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.openflowplugin.impl.protocol.serialization.messages; import com.google.common.base.MoreObjects; import io.netty.buffer.ByteBuf; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import org.opendaylight.openflowjava.protocol.api.extensibility.OFSerializer; import org.opendaylight.openflowjava.protocol.api.extensibility.SerializerRegistry; import org.opendaylight.openflowjava.protocol.api.extensibility.SerializerRegistryInjector; import org.opendaylight.openflowjava.protocol.api.keys.MessageTypeKey; import org.opendaylight.openflowjava.protocol.api.util.EncodeConstants; import org.opendaylight.openflowjava.util.ByteBufUtils; import org.opendaylight.openflowplugin.api.OFConstants; import org.opendaylight.openflowplugin.impl.protocol.serialization.util.ActionUtil; import org.opendaylight.openflowplugin.impl.protocol.serialization.util.InstructionUtil; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.ActionList; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.VlanCfi; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.PushVlanActionCaseBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.SetTpDstActionCase; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.SetTpDstActionCaseBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.SetTpSrcActionCase; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.SetTpSrcActionCaseBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.SetVlanIdActionCase; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.push.vlan.action._case.PushVlanActionBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.set.tp.dst.action._case.SetTpDstActionBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.set.tp.src.action._case.SetTpSrcActionBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.Flow; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowCookie; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowMessage; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowMessageBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowModFlags; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.InstructionsBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.MatchBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.Instruction; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.ApplyActionsCase; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.ApplyActionsCaseBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.apply.actions._case.ApplyActionsBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.InstructionBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.l2.types.rev130827.VlanId; import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.Match; import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.VlanMatch; import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.VlanMatchBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.vlan.match.fields.VlanIdBuilder; /** * Translates FlowMod messages. * OF protocol versions: 1.3 */ public class FlowMessageSerializer extends AbstractMessageSerializer<FlowMessage> implements SerializerRegistryInjector { private static final FlowCookie DEFAULT_COOKIE = new FlowCookie(OFConstants.DEFAULT_COOKIE); private static final FlowCookie DEFAULT_COOKIE_MASK = new FlowCookie(OFConstants.DEFAULT_COOKIE_MASK); private static final Short DEFAULT_TABLE_ID = (short) 0; private static final Integer DEFAULT_IDLE_TIMEOUT = 0; private static final Integer DEFAULT_HARD_TIMEOUT = 0; private static final Integer DEFAULT_PRIORITY = OFConstants.DEFAULT_FLOW_PRIORITY; private static final Long DEFAULT_BUFFER_ID = OFConstants.OFP_NO_BUFFER; private static final BigInteger DEFAULT_OUT_PORT = BigInteger.valueOf(OFConstants.OFPP_ANY); private static final Long DEFAULT_OUT_GROUP = OFConstants.OFPG_ANY; private static final byte PADDING_IN_FLOW_MOD_MESSAGE = 2; private static final FlowModFlags DEFAULT_FLAGS = new FlowModFlags(false, false, false, false, false); private static final Integer PUSH_VLAN = 0x8100; private static final Comparator<org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026 .instruction.list.Instruction> COMPARATOR = (inst1, inst2) -> { if (inst1.getOrder() == null || inst2.getOrder() == null) return 0; return inst1.getOrder().compareTo(inst2.getOrder()); }; private static final VlanMatch VLAN_MATCH_FALSE = new VlanMatchBuilder() .setVlanId(new VlanIdBuilder() .setVlanIdPresent(false) .setVlanId(new VlanId(0)) .build()) .build(); private static final VlanMatch VLAN_MATCH_TRUE = new VlanMatchBuilder() .setVlanId(new VlanIdBuilder() .setVlanIdPresent(true) .setVlanId(new VlanId(0)) .build()) .build(); private SerializerRegistry registry; @Override public void serialize(FlowMessage message, ByteBuf outBuffer) { if (!isVlanMatchPresent(message) && isSetVlanIdActionCasePresent(message)) { writeVlanFlow(message, outBuffer); } else { writeFlow(message, outBuffer); } } /** * Serialize flow message. Needs to be separated from main serialize method to prevent recursion * when serializing SetVlanId flows * @param message flow message * @param outBuffer output buffer */ private void writeFlow(final FlowMessage message, final ByteBuf outBuffer) { int index = outBuffer.writerIndex(); super.serialize(message, outBuffer); outBuffer.writeLong(MoreObjects.firstNonNull(message.getCookie(), DEFAULT_COOKIE).getValue().longValue()); outBuffer.writeLong(MoreObjects.firstNonNull(message.getCookieMask(), DEFAULT_COOKIE_MASK).getValue().longValue()); outBuffer.writeByte(MoreObjects.firstNonNull(message.getTableId(), DEFAULT_TABLE_ID)); outBuffer.writeByte(message.getCommand().getIntValue()); outBuffer.writeShort(MoreObjects.firstNonNull(message.getIdleTimeout(), DEFAULT_IDLE_TIMEOUT)); outBuffer.writeShort(MoreObjects.firstNonNull(message.getHardTimeout(), DEFAULT_HARD_TIMEOUT)); outBuffer.writeShort(MoreObjects.firstNonNull(message.getPriority(), DEFAULT_PRIORITY)); outBuffer.writeInt(MoreObjects.firstNonNull(message.getBufferId(), DEFAULT_BUFFER_ID).intValue()); outBuffer.writeInt(MoreObjects.firstNonNull(message.getOutPort(), DEFAULT_OUT_PORT).intValue()); outBuffer.writeInt(MoreObjects.firstNonNull(message.getOutGroup(), DEFAULT_OUT_GROUP).intValue()); outBuffer.writeShort(createFlowModFlagsBitmask(MoreObjects.firstNonNull(message.getFlags(), DEFAULT_FLAGS))); outBuffer.writeZero(PADDING_IN_FLOW_MOD_MESSAGE); writeMatch(message, outBuffer); writeInstructions(message, outBuffer); outBuffer.setShort(index + 2, outBuffer.writerIndex() - index); } /** * Instead of serializing this flow normally, we need to split it to two parts if flow contains * #{@link org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.SetVlanIdActionCase} * @param message flow mod message * @param outBuffer output buffer */ private void writeVlanFlow(final FlowMessage message, final ByteBuf outBuffer) { writeFlow( new FlowMessageBuilder(message) .setMatch(new MatchBuilder(message.getMatch()) .setVlanMatch(VLAN_MATCH_FALSE) .build()) .setInstructions(new InstructionsBuilder() .setInstruction(updateSetVlanIdAction(message)) .build()) .build(), outBuffer); writeFlow( new FlowMessageBuilder(message) .setMatch(new MatchBuilder(message.getMatch()) .setVlanMatch(VLAN_MATCH_TRUE) .build()) .build(), outBuffer); } /** * Serialize OpenFlowPlugin match to raw bytes * @param message OpenFlow flow mod message * @param outBuffer output buffer */ private void writeMatch(final FlowMessage message, final ByteBuf outBuffer) { registry.<Match, OFSerializer<Match>>getSerializer(new MessageTypeKey<>(message.getVersion(), Match.class)) .serialize(message.getMatch(), outBuffer); } /** * Serialize OpenFlowPlugin instructions and set ip protocol of set-tp-src and set-tp-dst actions of need * @param message OpenFlow flow mod message * @param outBuffer output buffer */ @SuppressWarnings("unchecked") private void writeInstructions(final FlowMessage message, final ByteBuf outBuffer) { // Try to get IP protocol from IP match final Optional<Short> protocol = Optional .ofNullable(message.getMatch()) .flatMap(m -> Optional.ofNullable(m.getIpMatch())) .flatMap(ipm -> Optional.ofNullable(ipm.getIpProtocol())); // Update instructions if needed and then serialize all instructions Optional.ofNullable(message.getInstructions()) .flatMap(is -> Optional.ofNullable(is.getInstruction())) .ifPresent(is -> is .stream() .filter(Objects::nonNull) .sorted(COMPARATOR) .map(org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.Instruction::getInstruction) .filter(Objects::nonNull) .map(i -> protocol.flatMap(p -> updateInstruction(i, p)).orElse(i)) .forEach(i -> InstructionUtil.writeInstruction(i, EncodeConstants.OF13_VERSION_ID, registry, outBuffer))); } /** * Determine type of instruction and update it's actions if it is apply-actions instruction * @param instruction instruction * @param protocol protocol * @return updated instruction or empty */ private static Optional<Instruction> updateInstruction(final Instruction instruction, final Short protocol) { if( ApplyActionsCase.class.isInstance(instruction)) { return Optional .ofNullable(ApplyActionsCase.class.cast(instruction).getApplyActions()) .flatMap(aa -> Optional.ofNullable(aa.getAction())) .map(as -> new ApplyActionsCaseBuilder() .setApplyActions(new ApplyActionsBuilder() .setAction(as .stream() .filter(Objects::nonNull) .map(a -> updateSetTpActions(a, protocol)) .collect(Collectors.toList())) .build()) .build()); } return Optional.empty(); } /** * If action is set-tp-src or set-tp-dst, inject IP protocol into it, otherwise return original action * @param action OpenFlow action * @param protocol IP protocol * @return updated OpenFlow action */ private static Action updateSetTpActions(Action action, Short protocol) { if (SetTpSrcActionCase.class.isInstance(action.getAction())) { final SetTpSrcActionCase actionCase = SetTpSrcActionCase.class.cast(action.getAction()); return new ActionBuilder(action) .setAction(new SetTpSrcActionCaseBuilder(actionCase) .setSetTpSrcAction(new SetTpSrcActionBuilder( actionCase.getSetTpSrcAction()) .setIpProtocol(protocol) .build()) .build()) .build(); } else if (SetTpDstActionCase.class.isInstance(action.getAction())) { final SetTpDstActionCase actionCase = SetTpDstActionCase.class.cast(action.getAction()); return new ActionBuilder(action) .setAction(new SetTpDstActionCaseBuilder(actionCase) .setSetTpDstAction(new SetTpDstActionBuilder( actionCase.getSetTpDstAction()) .setIpProtocol(protocol) .build()) .build()) .build(); } // Return original action if no modifications are needed return action; } /** * Create copy of instructions of original flow but insert #{@link org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.PushVlanActionCase} * before each #{@link org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.SetVlanIdActionCase} * @param message OpenFlowPlugin flow mod message * @return list of instructions */ private static List<org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list .Instruction> updateSetVlanIdAction(final FlowMessage message) { return message.getInstructions().getInstruction() .stream() .map(i -> { final int[] offset = { 0 }; return ApplyActionsCase.class.isInstance(i.getInstruction()) ? Optional .ofNullable(ApplyActionsCase.class.cast(i.getInstruction()).getApplyActions()) .flatMap(as -> Optional.ofNullable(as.getAction())) .map(a -> ActionUtil.sortActions(a) .stream() .flatMap(action -> { final List<org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112 .action.list.Action> actions = new ArrayList<>(); // If current action is SetVlanId, insert PushVlan action before it and update order if (SetVlanIdActionCase.class.isInstance(action.getAction())) { actions.add(new ActionBuilder() .setAction(new PushVlanActionCaseBuilder() .setPushVlanAction(new PushVlanActionBuilder() .setCfi(new VlanCfi(1)) .setVlanId(SetVlanIdActionCase.class.cast(action.getAction()).getSetVlanIdAction().getVlanId()) .setEthernetType(PUSH_VLAN) .setTag(PUSH_VLAN) .build()) .build()) .setKey(action.getKey()) .setOrder(action.getOrder() + offset[0]) .build()); offset[0]++; } // Update offset of action if there is any inserted PushVlan actions actions.add(offset[0] > 0 ? new ActionBuilder(action).setOrder(action.getOrder() + offset[0]).build() : action); return actions.stream(); })) .map(as -> new InstructionBuilder(i) .setInstruction(new ApplyActionsCaseBuilder() .setApplyActions(new ApplyActionsBuilder() .setAction(as.collect(Collectors.toList())) .build()) .build()) .build()) .orElse(i) : i; }).collect(Collectors.toList()); } /** * Create integer bit mask from #{@link org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowModFlags} * @param flags flow mod flags * @return bit mask */ private static int createFlowModFlagsBitmask(final FlowModFlags flags) { return ByteBufUtils.fillBitMask(0, flags.isSENDFLOWREM(), flags.isCHECKOVERLAP(), flags.isRESETCOUNTS(), flags.isNOPKTCOUNTS(), flags.isNOBYTCOUNTS()); } /** * Determine if flow contains vlan match * @param flow flow * @return true if flow contains vlan match */ private static boolean isVlanMatchPresent(final Flow flow) { return Optional .ofNullable(flow.getMatch()) .flatMap(m -> Optional.ofNullable(m.getVlanMatch())) .isPresent(); } /** * Determine if flow contains #{@link org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.ApplyActionsCase} * instruction with #{@link org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.SetVlanIdActionCase} action * @param flow OpenFlowPlugin flow * @return true if flow contains SetVlanIdAction */ private static boolean isSetVlanIdActionCasePresent(final Flow flow) { return Optional .ofNullable(flow.getInstructions()) .flatMap(is -> Optional.ofNullable(is.getInstruction())) .flatMap(is -> is .stream() .map(org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.Instruction::getInstruction) .filter(ApplyActionsCase.class::isInstance) .map(i -> ApplyActionsCase.class.cast(i).getApplyActions()) .filter(Objects::nonNull) .map(ActionList::getAction) .filter(Objects::nonNull) .flatMap(Collection::stream) .map(Action::getAction) .filter(SetVlanIdActionCase.class::isInstance) .findFirst()) .isPresent(); } @Override protected byte getMessageType() { return 14; } @Override public void injectSerializerRegistry(SerializerRegistry serializerRegistry) { registry = serializerRegistry; } }