流表(Flow Table):动作(Actions)
OpenFlow流表(Flow Table)
流表记录(Flow Table Entry)应当包含三个部分:
- 头部字段(header fields);
- 计数器(counters);
- 动作(actions)。
头部字段 | 计数器 | 动作 |
---|---|---|
用于匹配包 | 通过统计来更新已匹配的包 | 应用已匹配的包 |
其中,要求&可选实现动作操作中如下的端口功能:
必须动作
- 转发动作中的:
- ALL:从所有非接收端口发包
- LOCAL:发送包给交换机本地网络栈(本地端口)
- TABLE:在流表中实施动作
- IN_PORT:从输入端口发包
- 丢弃动作
可选动作
- 转发动作中的:
- NOEMAL:以寻常的L2/L3转发方式处理
- FLOOD:沿着最小生成树来泛洪包(不包括输入端口)
- 入队动作
- 修改字段动作包含以下子项:
- 设置 VLAN ID
- 设置 VLAN 优先级
- 去除 VLAN 头部
- 修改以太网源MAC地址
- 修改以太网目的MAC地址
- 修改IPv4源地址
- 修改IPv4目的地址
- 修改IPv4 ToS位
- 修改传输源端口
- 修改传输目的端口
其对应协议5.2.1的端口结构一节,其定义的端口动作类型如下:
enum ofp_port {
OFPP_MAX = 0xff00,/* 物理端口的最大数量 */
OFPP_IN_PORT = 0xfff8,
OFPP_TABLE = 0xfff9,
OFPP_NORMAL = 0xfffa,
OFPP_FLOOD = 0xfffb,
OFPP_ALL = 0xfffc,
OFPP_CONTROLLER = 0xfffd, /* 发送给控制器 */
OFPP_LOCAL = 0xfffe,
OFPP_NONE = 0xffff
};
在对应的OFPort.java源文件中,仅实现获取获取端口动作类型值的功能,其代码如下所示:
package org.openflow.protocol;
public enum OFPort {
OFPP_MAX ((short)0xff00),
OFPP_IN_PORT ((short)0xfff8),
OFPP_TABLE ((short)0xfff9),
OFPP_NORMAL ((short)0xfffa),
OFPP_FLOOD ((short)0xfffb),
OFPP_ALL ((short)0xfffc),
OFPP_CONTROLLER ((short)0xfffd),
OFPP_LOCAL ((short)0xfffe),
OFPP_NONE ((short)0xffff);
protected short value;
private OFPort(short value) {
this.value = value;
}
// 获取端口动作类型值
public short getValue() {
return value;
}
}
同时在OpenFlow 1.0协议中,定义了动作消息头部结构(合计为64位数据):
- 16位的类型值;
- 16位的长度值;
- 8 * 4 位的填充值。
struct ofp_action_header {
uint16_t type;
uint16_t len;
uint8_t pad[4];
};
OFP_ASSERT(sizeof(struct ofp_action_header) == 8);
在下方OpenFlowJ项目的OFAction.java源文件中实现可克隆接口,并定义以下功能:
- 初始化:最小长度为4字节(代码实现仅读取前4字节做处理);偏移长度为2;偏移类型为0;
- 获取&设置动作消息头部长度(以有&无符号形式);
- 获取&设置动作类型;
- 由动作获取消息概述(toString方法);由信消息概述获取动作对象(fromString);
- 从ByteBuffer中读取数据(类型&长度);
- 将数据(类型&长度)写入到ByteBuffer当中;
- 重写hashCode与equals函数。
- 浅克隆
package org.openflow.protocol.action;
import java.nio.ByteBuffer;
import org.openflow.util.U16;
// OFAction.java文件
// 所有OpenFLow动作的基类
public class OFAction implements Cloneable {
/**
* 注意:实际的头部最小长为8字节(包含了为了对齐64位的填充值),
* 由于此基类被用于复用于传入的动作消息,仅需要读取首位4个字节。
* 所有动作都扩展此类去负责读取/写入首位8字节(包含所需的填充)。
*/
public static int MINIMUM_LENGTH = 4;
public static int OFFSET_LENGTH = 2;
public static int OFFSET_TYPE = 0;
protected OFActionType type;
protected short length;
// 获取&设置消息长度(以有&无符号形式)
public short getLength() {
return length;
}
public int getLengthU() {
return U16.f(length);
}
public OFAction setLength(short length) {
this.length = length;
return this;
}
// 获取&设置动作类型
public OFActionType getType() {
return this.type;
}
public OFAction setType(OFActionType type) {
this.type = type;
return this;
}
// 由动作获取消息概述
public String toString() {
return "ofaction" +
";t=" + this.getType() +
";l=" + this.getLength();
}
// 由信消息概述获取动作对象
public static OFAction fromString(String val) {
String tokens[] = val.split(";");
if (!tokens[0].equals("ofaction"))
throw new IllegalArgumentException("expected 'ofaction' but got '" +
tokens[0] + "'");
String type_tokens[] = tokens[1].split("=");
String len_tokens[] = tokens[2].split("=");
OFAction action = new OFAction();
action.setLength(Short.valueOf(len_tokens[1]));
action.setType(OFActionType.valueOf(type_tokens[1]));
return action;
}
// 读取:字节缓冲区(ByteBuffer)-->数据
// 写入:数据-->字节缓冲区(ByteBuffer)
// 忽略填充值
public void readFrom(ByteBuffer data) {
this.type = OFActionType.valueOf(data.getShort());
this.length = data.getShort();
}
public void writeTo(ByteBuffer data) {
data.putShort(type.getTypeValue());
data.putShort(length);
}
// 重写hashCode函数与equals函数
@Override
public int hashCode() {
final int prime = 347;
int result = 1;
result = prime * result + length;
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof OFAction)) {
return false;
}
OFAction other = (OFAction) obj;
if (length != other.length) {
return false;
}
if (type == null) {
if (other.type != null) {
return false;
}
} else if (!type.equals(other.type)) {
return false;
}
return true;
}
// 浅克隆
@Override
public OFAction clone() throws CloneNotSupportedException {
return (OFAction) super.clone();
}
}
同时OpenFlow 1.0协议中定义了动作类型:
- 发出动作
- 设置VLAN VID
- 设置VLAN PCP(设置802.1q优先级)
- 剥离VLAN操作(剥离802.1q头部)
- 设置以太网源地址
- 设置以太网目的地址
- 设置IP源地址
- 设置IP目的地址
- 设置IP ToS(6位)
- 设置TCP/IP源端口
- 设置TCP/IP目的端口
- 输出到队列
- 供应商(默认为0xffff,16位)
enum ofp_action_type {
OFPAT_OUTPUT,
OFPAT_SET_VLAN_VID,
OFPAT_SET_VLAN_PCP,
OFPAT_STRIP_VLAN,
OFPAT_SET_DL_SRC,
OFPAT_SET_DL_DST,
OFPAT_SET_NW_SRC,
OFPAT_SET_NW_DST,
OFPAT_SET_NW_TOS,
OFPAT_SET_TP_SRC,
OFPAT_SET_TP_DST,
OFPAT_ENQUEUE,
OFPAT_VENDOR = 0xffff
};
对应的OFActionType.java源文件结构与前两篇所描述的类型文件结构类似:
- 枚举所有类型名称,及其对应子类
- 初始化(存储基本信息、添加映射操作)
- 映射操作(添加映射)
- 查询操作(根据类型值查询类型、查询此类型名、查询此类型类、获取此类构造器)
- 实例化操作(实例化接口获取&设置、新建此类型实例)
package org.openflow.protocol.action;
import java.lang.reflect.Constructor;
import org.openflow.protocol.Instantiable;
// 列出OpenFLow动作类型,并映射到类型值与派生类
public enum OFActionType {
OUTPUT (0, OFActionOutput.class, new Instantiable<OFAction>() {
@Override
public OFAction instantiate() {
return new OFActionOutput();
}}),
SET_VLAN_VID (1, OFActionVirtualLanIdentifier.class, new Instantiable<OFAction>() {
@Override
public OFAction instantiate() {
return new OFActionVirtualLanIdentifier();
}}),
SET_VLAN_PCP (2, OFActionVirtualLanPriorityCodePoint.class, new Instantiable<OFAction>() {
@Override
public OFAction instantiate() {
return new OFActionVirtualLanPriorityCodePoint();
}}),
STRIP_VLAN (3, OFActionStripVirtualLan.class, new Instantiable<OFAction>() {
@Override
public OFAction instantiate() {
return new OFActionStripVirtualLan();
}}),
SET_DL_SRC (4, OFActionDataLayerSource.class, new Instantiable<OFAction>() {
@Override
public OFAction instantiate() {
return new OFActionDataLayerSource();
}}),
SET_DL_DST (5, OFActionDataLayerDestination.class, new Instantiable<OFAction>() {
@Override
public OFAction instantiate() {
return new OFActionDataLayerDestination();
}}),
SET_NW_SRC (6, OFActionNetworkLayerSource.class, new Instantiable<OFAction>() {
@Override
public OFAction instantiate() {
return new OFActionNetworkLayerSource();
}}),
SET_NW_DST (7, OFActionNetworkLayerDestination.class, new Instantiable<OFAction>() {
@Override
public OFAction instantiate() {
return new OFActionNetworkLayerDestination();
}}),
SET_NW_TOS (8, OFActionNetworkTypeOfService.class, new Instantiable<OFAction>() {
@Override
public OFAction instantiate() {
return new OFActionNetworkTypeOfService();
}}),
SET_TP_SRC (9, OFActionTransportLayerSource.class, new Instantiable<OFAction>() {
@Override
public OFAction instantiate() {
return new OFActionTransportLayerSource();
}}),
SET_TP_DST (10, OFActionTransportLayerDestination.class, new Instantiable<OFAction>() {
@Override
public OFAction instantiate() {
return new OFActionTransportLayerDestination();
}}),
OPAQUE_ENQUEUE (11, OFActionEnqueue.class, new Instantiable<OFAction>() {
@Override
public OFAction instantiate() {
return new OFActionEnqueue();
}}),
VENDOR (0xffff, OFActionVendor.class, new Instantiable<OFAction>() {
@Override
public OFAction instantiate() {
return new OFActionVendor();
}});
protected static OFActionType[] mapping;
protected Class<? extends OFAction> clazz;
protected Constructor<? extends OFAction> constructor;
protected Instantiable<OFAction> instantiable;
protected int minLen;
protected short type;
// 初始化OFActionType类,初始化数据包括类型值、长度与派生类
OFActionType(int type, Class<? extends OFAction> clazz, Instantiable<OFAction> instantiable) {
this.type = (short) type;
this.clazz = clazz;
this.instantiable = instantiable;
try {
this.constructor = clazz.getConstructor(new Class[]{});
} catch (Exception e) {
throw new RuntimeException(
"Failure getting constructor for class: " + clazz, e);
}
OFActionType.addMapping(this.type, this);
}
// 添加:一个从类型值到OPActionType的枚举映射
static public void addMapping(short i, OFActionType t) {
if (mapping == null)
mapping = new OFActionType[16];
// bring higher mappings down to the edge of our array
if (i < 0)
i = (short) (16 + i);
OFActionType.mapping[i] = t;
}
// 通过类型值获取OPActionType枚举类型
static public OFActionType valueOf(short i) {
if (i < 0)
i = (short) (16+i);
return OFActionType.mapping[i];
}
// 获取与此OFActionType对应的类型值
public short getTypeValue() {
return this.type;
}
// 获取与此OFActionType对应的OFAction子类
public Class<? extends OFAction> toClass() {
return clazz;
}
// 获取此OFType实现类的无参构造函数
public Constructor<? extends OFAction> getConstructor() {
return constructor;
}
// 获取一个通过此OFType表示的OFMessage的新实例
public OFAction newInstance() {
return instantiable.instantiate();
}
// 获取&设置:可实例化接口
public Instantiable<OFAction> getInstantiable() {
return instantiable;
}
public void setInstantiable(Instantiable<OFAction> instantiable) {
this.instantiable = instantiable;
}
}
总的来说OPAction的操作仅包含头部64位中的前32位(包含16位类型值与16位长度值),同时实现了可克隆接口,在之后的源码详细文件解析中再来看其实现拷贝的作用,同时对于协议定义的类型而言,其对应的枚举类结构大同小异,与之前两篇关于流表的头部字段、计数器的枚举类结构相同。