SDN开源项目实践:OpenDayLight南向协议扩展开发

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.csdn.net/SYQ_aa/article/details/80628737

 

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页