OpenDayLight (ODL) 是一款开源SDN控制器框架,在linux平台上运行。北向接口为RESTAPI,南向接口可下发多种协议至路由器设备。因工作中需要测试pcep path protection功能,于是对ODL的南向协议模块bgpcep这个项目进行了相关的扩展。
1. 支持path protection lsp的创建、update和remove
需求:
(1)支持使用initiatelsp报文来创建workinglsp、protectionlsp;
(2)支持使用updatelsp报文来update更新workinglsp、protectionlsp;
(3)支持使用initiatelsp报文来remove删除workinglsp、protectionlsp;
(4)支持对于包含pathprotection 的report报文的处理;
设计:
(1) 使用针对lspa对象的处理流程来输入associationgroup的参数;
(2) 然后利用serialize串行化流程来把这些associationgroup参数组成initiatelsp报文;
(3) 处理pcc返回的report报文,使得lspname和plsp-id的关系能够input到pce中的lspdb中,以便支持随后的update或removelsp操作;
实现:
通过lspa对象的处理流程映射为associationgroup的处理。
开发步骤:
(1) 为新的TLV设计从RESTCONF接口传入的数据类型,并绑定在lspa对象下。
Association group TLV format:
Path protection TLV as optional TLV of association group TLV:
首先,根据TLV需求在lspa对象的yang模型下定义新的子结点。其中obj-class子结点可配是由于目前路由器版本采用旧版的tlv(class = 100),而官方的class已更新成40。这样做方便为以后路由器代码变动而做下发报文的改变。Lspa的数据类型定义在pcep-types.yang这个文件中。
container lspa {
//ppag related
leaf r-flag {
type boolean;
default false;
}
leaf association-type {
type uint8;
default 1;
}
leaf association-id {
type uint8;
mandatory true;
}
leaf association-source {
type inet:ipv4-address;
mandatory true;
}
leaf s-flag {
type boolean;
}
leaf p-flag {
type boolean;
}
leaf obj-class {
type uint8;
default 40;
}
leaf pt-flag {
type uint8 {
range 0..63;
}
}
}
新定义的对象在path protection tlv中的对应关系为:
编辑好yang文件后,在pcep/api路径下执行mvnclean install,对应的新的java API就生成了。新的API里面包含新增对象的get,set以及build等方法。(Yang和javaAPI的映射关系详细参见ODLyangtools说明)
(2) 改变现有lspa的串行方法。
在PCEPLspaObjectParser.java文件中删除原有对象的引用,利用get()方法导入Input的新类型对象,调用串行函数将对象按照所需tlv格式写入buffer中。
上述改完以后发现,ODL不能处理盒子回的report报文而关闭了pcepsession。这是个很严重的问题。跟踪log以后定位出原因是在解析report报文的时候ODL还是把扩展的ppag报文当成lspa报文来处理,且ppag的report报文比ODL所期望的lspareport报文要短,产生了Exception(异常)。于是,不仅要修改串行函数,解析函数也做了一个调整,很简单就是把解析完tlv的前3行之后的代码给注释掉了。
篡改后的函数如下:
public class PCEPLspaObjectParser extends AbstractObjectWithTlvsParser<TlvsBuilder> {
public static int CLASS = 100;
public static final int TYPE = 1;
/*
* lengths of fields in bytes
*/
private static final int FLAGS_SIZE = 8;
private static final int A_FLAGS_SIZE = 32; //association flags size
/*
* offsets of flags inside flags field in bits
*/
private static final int L_FLAG_OFFSET = 7;
private static final int S_FLAG_OFFSET = 30; //association flag s
private static final int P_FLAG_OFFSET = 31; //association flag p
private static final int R_FLAG_OFFSET = 15; //association flag r
private static final int RESERVED = 1;
public PCEPLspaObjectParser(final TlvRegistry tlvReg, final VendorInformationTlvRegistry viTlvReg) {
super(tlvReg, viTlvReg);
}
@Override
public Lspa parseObject(final ObjectHeader header, final ByteBuf bytes) throws PCEPDeserializerException {
Preconditions.checkArgument(bytes != null && bytes.isReadable(), "Array of bytes is mandatory. Can't be null or empty.");
final LspaBuilder builder = new LspaBuilder();
builder.setIgnore(header.isIgnore());
builder.setProcessingRule(header.isProcessingRule());
builder.setExcludeAny(new AttributeFilter(bytes.readUnsignedInt()));
builder.setIncludeAll(new AttributeFilter(bytes.readUnsignedInt()));
builder.setIncludeAny(new AttributeFilter(bytes.readUnsignedInt()));
/* builder.setSetupPriority(bytes.readUnsignedByte());
builder.setHoldPriority(bytes.readUnsignedByte());
final BitArray flags = BitArray.valueOf(bytes.readByte());
builder.setLocalProtectionDesired(flags.get(L_FLAG_OFFSET));
final TlvsBuilder tbuilder = new TlvsBuilder();
bytes.skipBytes(RESERVED);
parseTlvs(tbuilder, bytes.slice());
builder.setTlvs(tbuilder.build()); */
return builder.build();
}
@Override
public void serializeObject(final Object object, final ByteBuf buffer) {
Preconditions.checkArgument(object instanceof Lspa, "Wrong instance of PCEPObject. Passed %s. Needed LspaObject.", object.getClass());
final Lspa lspaObj = (Lspa) object;
final ByteBuf body = Unpooled.buffer();
writeShort((short) 0,body); //reserved
final BitArray flags1 = new BitArray(16);
flags1.set(R_FLAG_OFFSET, lspaObj.isRFlag());
flags1.toByteBuf(body);
writeShort((short) lspaObj.getAssociationType(), body);
writeShort((short) lspaObj.getAssociationId(), body);
writeIpv4Address(lspaObj.getAssociationSource(), body);
if(lspaObj.isSFlag()!=null && lspaObj.isPFlag()!=null) {
writeShort((short) 29,body); //ppag type
writeShort((short) 4,body); //ppag length
writeUnsignedByte((short) (lspaObj.getPtFlag()<<2),body); //write extension pt tlv
final BitArray flags2 = new BitArray(A_FLAGS_SIZE-8);
flags2.set(S_FLAG_OFFSET-8, lspaObj.isSFlag());
flags2.set(P_FLAG_OFFSET-8, lspaObj.isPFlag());
flags2.toByteBuf(body);}
serializeTlvs(lspaObj.getTlvs(), body);
//get class
CLASS = (int) lspaObj.getObjClass();
ObjectUtil.formatSubobject(TYPE, CLASS, object.isProcessingRule(), object.isIgnore(), body, buffer);
}
public void serializeTlvs(final Tlvs tlvs, final ByteBuf body) {
if (tlvs == null) {
return;
}
serializeVendorInformationTlvs(tlvs.getVendorInformationTlv(), body);
}
private static void writeAttributeFilter(final AttributeFilter attributeFilter, final ByteBuf body) {
writeUnsignedInt(attributeFilter != null ? attributeFilter.getValue() : null, body);
}
@Override
protected final void addVendorInformationTlvs(final TlvsBuilder builder, final List<VendorInformationTlv> tlvs) {
if (!tlvs.isEmpty()) {
builder.setVendorInformationTlv(tlvs);
}
}
}
(3) 编译,拷贝最终版本的压缩包致运行opendaylight的服务器上,解压,运行并下载需要的feature到新版本。
在git根目录下执行mvnclean install -DskipTests,编译完后bgpcep-stable-nitrogen\distribution-karaf\target路径下会产生两个名为distribution-karaf-0.8.4-SNAPSHOT的发布的版本压缩包(一个是tar格式的一个是zip格式的)。
拷贝压缩包到ODL运行的服务器上,解压后运行./distribution-karaf-0.8.4-SNAPSHOT/bin/karaf,在弹出的命令行下执行
feature:install odl-restconf
feature:install odl-bgpcep-pcep
feature:installodl-bgpcep-pcep-topology
2. 支持1个initiatelsp报文中下发多个lsp
需求:
(1) 支持1个initiate报文中包含创建2个lsp的信息;
(2) 上述2个lsp必须属于以下二种方式:
normal lsp1 + normal lsp2或者working lsp1 + protection lsp2;
其中normal lsp指不带assocaition group的lsp,working lsp指带有associationgroup的lsp,但不置S和Pflag。Protection lsp指带有associationgroup,并且置S或P flag。
(3) 其中SR对象支持nodesid和adj sid的设置;支持label stack变长;
(4) Working lsp不带有path protection TLV;protection lsp带有path protection TLV;
(5) 支持update、remove使用(1)方式创建后的lsp;支持处理使用(1)方式创建的lsp上报的report报文;
约定和限制:
(1)1个initiate报文中创建的lsp的endpoint是相同的,也就是src和dst目的地址是相同的;
(2)lsp1和lsp2的path name是类似的,差异之处只是在最后1个字节(或2个字节),例如lsp 1 path name:pcc_lsp_1,lsp2 pathname:pcc_lsp_2。其中支持lsp2的path name最后1个字节(或2个字节)是可配置的。
(3)默认下发的是SR的label stack。
(4)initiate 报文格式如下所示:
实现:
使用pcep METRIC object来实现上述功能,metric支持循环嵌套,所以从理论上可以输入任意多的信息。该object定义如下:
在metric对象下新定义几个子结点:sid,address和r-address用于传递label-stack的信息。我们使用type,sid, address和r-address这4个field来定义如下输入信息:
Type(16进制)
Value
说明
0x00
基础信息(定义见下)
Lsp name, S falg, P flag
0x10
Node sid label
Sid, ip address
0x20
Adj sid label
Sid, ip address, remote ip address
基础信息 -- type=0时,address(32bit,4字节)的定义:
字节
说明
1
Lsp name
2
0
3
Lsp2-S flag
4
Lsp2-P flag
处理流程:
(1) Serializelsp1的信息,包括srp,lsp,endpoint,ero,lspa对象;
(2) 从metric对象读入上述输入信息;
(3) Serializelsp2的srp和lsp对象;
(4) 通过回退writerindex指针,使用输入的【lsp name】来覆盖buffer中lsp name的最后一个字符;
(5) Serializelsp2的endpoint;
(6) 根据输入的metric type来判断node/adjacency类型,根据输入的<sid>, <address>和<r-address>来重新构造ERO对象;
(7) Serializelsp2的lspa对象(实际映射为association group对象);
(8) 针对association group对象有如下处理:
(a) 如果是normal+normal方式,则正常处理;
(b) 如果是working+protection方式,则根据输入的【Lsp2-S flag】和【Lsp2-P flag】,来编码lsp2的path protection信息。
开发步骤:
(1) 为新的TLV设计从RESTCONF接口传入的数据类型,并绑定在metric对象下。
Metric的数据模型也定义在pcep-types.yang这个文件中。改动后的metric如下:
container metric {
uses object;
leaf metric-type {
type uint8;
mandatory true;
}
leaf bound {
type boolean;
default false;
}
leaf computed {
type boolean;
default false;
}
leaf value {
type ieee754:float32;
}
//added
leaf address {
type inet:ipv4-address;
}
leaf sid {
type uint32;
}
leaf r-address {
type inet:ipv4-address;
}
}
(2) 在PECPMetricObjectParser.java文件中改变现有metric的串行方法。原理与修改lspa一致。
public void serializeObject(final Object object, final ByteBuf buffer) {
Preconditions.checkArgument(object instanceof Metric, "Wrong instance of PCEPObject. Passed %s. Needed MetricObject.", object.getClass());
final Metric mObj = (Metric) object;
final ByteBuf body = Unpooled.buffer(SIZE);
body.writeZero(RESERVED);
final BitArray flags = new BitArray(FLAGS_SIZE);
flags.set(C_FLAG_OFFSET, mObj.isComputed());
flags.set(B_FLAG_OFFSET, mObj.isBound());
flags.toByteBuf(body);
Preconditions.checkArgument(mObj.getMetricType() != null, "MetricType is mandatory.");
writeUnsignedByte(mObj.getMetricType(), body);
//writeFloat32(mObj.getValue(), body);
writeUnsignedInt(mObj.getSid(), body);
writeIpv4Address(mObj.getAddress(), body);
writeIpv4Address(mObj.getRAddress(), body);
ObjectUtil.formatSubobject(TYPE, CLASS, object.isProcessingRule(), object.isIgnore(), body, buffer);
}
(3) 修改initiate message的串行函数
位于CInitiated00PCInitiateMessageParser.java文件中的Initiatemessage的串行函数引用了所有pecpinitiate message中要包括的对象的串行方法。在这里我们要把对于metric的引用方式改掉,模拟另一条lsp的下发。
public void serializeMessage(final Message message, final ByteBuf out) {
Preconditions.checkArgument(message instanceof Pcinitiate, "Wrong instance of Message. Passed instance of %s. Need PcinitiateMessage.", message.getClass());
final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.crabbe.initiated.rev131126.pcinitiate.message.PcinitiateMessage init = ((Pcinitiate) message).getPcinitiateMessage();
final ByteBuf buffer = Unpooled.buffer();
for (final Requests req : init.getRequests()) {
serializeRequest(req, buffer);
}
MessageUtil.formatMessage(TYPE, buffer, out);
}
//add log
private static final Logger LOG = LoggerFactory.getLogger(CInitiated00PCInitiateMessageParser.class);
protected void serializeRequest(final Requests req, final ByteBuf buffer) {
serializeObject(req.getSrp(), buffer);
serializeObject(req.getLsp(), buffer);
serializeObject(req.getEndpointsObj(), buffer);
serializeObject(req.getEro(), buffer);
serializeObject(req.getLspa(), buffer);
//serializeObject(req.getBandwidth(), buffer);
/* if (req.getMetrics() != null) {
for (final Metrics m : req.getMetrics()) {
serializeObject(m.getMetric(), buffer);
}
} */
serializeObject(req.getIro(), buffer);
//additional lsp
/*type00: | 000000.... |
|pcc_lsp_x| 0x00 | s | p |
| 000000.... |
*/
/* type10: | node_sid_label |
| nai (ipv4 address) |
| 000000.... |
*/
/* type20:| adj_sid_label |
| nai ( local ipv4 address) |
| nai (remote ipv4 address) |
*/
int s = 0; //s flag
int p = 0; //p flag
int label_length = 0; //total length of sr labels
if (req.getMetrics() != null) {
for (final Metrics m : req.getMetrics()) {
final ByteBuf metrics = Unpooled.buffer();
serializeObject(m.getMetric(), metrics);
final int type = metrics.getUnsignedByte(metrics.writerIndex()-13);
switch (type) {
case 0: //type = 0x00
serializeObject(req.getSrp(), buffer);
serializeObject(req.getLsp(), buffer);
final int lsp_name = metrics.getUnsignedByte(metrics.writerIndex()-8);
//skip paddings
int skip_bytes = 1;
while(buffer.getUnsignedByte(buffer.writerIndex()-skip_bytes) == 0) {
skip_bytes++;
}
buffer.setByte(buffer.writerIndex()-skip_bytes,lsp_name+48);
s = metrics.getUnsignedByte(metrics.writerIndex()-6);
p = metrics.getUnsignedByte(metrics.writerIndex()-5);
serializeObject(req.getEndpointsObj(), buffer);
buffer.writeByte(7);
buffer.writeByte(16);
buffer.writeShort(4); //set an abstract ero length
break;
case 16: //type = 0x10
label_length = label_length+12;
final int sidn = metrics.getInt(metrics.writerIndex()-12);
writeSRlabel(sidn, 1, buffer);
final int nain = metrics.getInt(metrics.writerIndex()-8);
buffer.writeInt(nain);
break;
case 32: //type = 0x20
label_length = label_length+16;
final int sida = metrics.getInt(metrics.writerIndex()-12);
writeSRlabel(sida, 3, buffer);
final int naia1 = metrics.getInt(metrics.writerIndex()-8);
buffer.writeInt(naia1);
final int naia2 = metrics.getInt(metrics.writerIndex()-4);
buffer.writeInt(naia2);
break;
default:
throw new IllegalArgumentException("we donot find the type value.");
} //end switch(type)
} //end forloop
buffer.setShort(buffer.writerIndex()-(label_length+2), label_length+4); //set actual length of ero
final ByteBuf lspa = Unpooled.buffer();
if (req.getLspa()!=null) {
serializeObject(req.getLspa(), lspa);
lspa.readerIndex(0);
lspa.readBytes(buffer, 16);
buffer.setByte(buffer.writerIndex()-13,24);
writePPAG(s,p,buffer);
}else {//normal lsp, nothing to do here
}
} //end if getMetrics() != null
}
//write ERO SR byte function
private void writeSRlabel(final int label, final int sid_type, final ByteBuf buffer) {
buffer.writeByte(5); //type
if(sid_type==1) {buffer.writeByte(12);}
else {buffer.writeByte(16);} //length
buffer.writeByte(sid_type*16);
buffer.writeByte(1);
buffer.writeMedium(label*16);
buffer.writeByte(0);
}
//write ppag extension tlv
private void writePPAG(final int s, final int p, final ByteBuf buffer) {
buffer.writeShort(29);
buffer.writeShort(4);
if(s==1 && p==1) {
buffer.writeInt(3);
}else if(s==1 && p==0) {
buffer.writeInt(2);
}else if(s==0 && p==1) {
buffer.writeInt(1);
}else if(s==0 && p==0) {
buffer.writeInt(0);
}
}
(4) 编译流程参照项目1
---------------------
转载自:https://blog.youkuaiyun.com/SYQ_aa/article/details/80628737