SNMP MIB 文法分析--基于Scala Parser模块

本文档介绍了如何使用Scala的Parser模块来分析SNMP MIB文件,提取OID定义及其值。分析的目的是为了在SNMP网管开发中自动生成C++类,简化SNMP消息转换过程。目标是构建C++类,并依据OID属性创建Get、Set和Trap包解析方法。项目采用Scala语言,包括文法分析的各个阶段,提供了辅助数据结构和测试用的Gnugk MIB文件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.为何要分析SNMP MIB文件。 

分析MIB文件主要为了提取MIB文件中的oid定义及其对应的OID值,包含(OBJECT-IDENTIFIER 和OBJECT-TYPE等具有OID值的对象).

2. 提取OID对象和其值后,有何用户呢?

搞SNMP网管开发时,业务对象一般是居于C++,Java,C#等类,而且这些类一般只包含基础成员,没有指针,没有对象成员,没有集合成员等。这些业务对象再也SNMP Agent进行交互时,需要转换为SNMP 消息。 这个转换虽然比较简单。但是很繁琐,特别是网管的规模大的时候,oid不可能只有几个,往往都是上千个oid。这个工作量大,而且容易出错。---如果避免工作量和错误的重复出现,只有自动化的工具才可以。市面上有一些工具,大部分是针对Agent的代码自动生成,都是商业的。

3. 目标

分析提取oid后,根据OBJECT-GROUP,和NOTIFITION-TYPE 等构造对应的C++类。并根据每个OID的属性(SYNTAX,ACCESS,INDEX)等构造SNMP Get,SNMPSet,和SNMP RSP或者Trap包的解析方法。

4.此工程主要使用Scala语言,包含文法分析、文法分析中间结果和文法分析的结果

SnmpToolkit.scala 包含一些辅助数据结构

package cn.newzai.parser.snmp.result

import scala.collection.mutable
import cn.newzai.parser.snmp.SnmpParser

object SnmpGroupType extends Enumeration{
  val OBJECT_GROUP =Value
  val NOTIFICATION_TYPE = Value
  //val NOTIFICATION_GROUP = Value
}

object SnmpSyntax extends Enumeration{
  val NA    = Value
  val OCTET = Value
  val INTEGER = Value
  val Gauge = Value
  val Counter32 = Value
  val Counter = Value
  val Counter64 = Value
  val OBJECT    = Value
  val IpAddress = Value
  val TimeTicks = Value
  val Integer32 = Value
  val Unsigned32 = Value

  val cplusplus_type= Map(
    OCTET->"std::string",
    INTEGER->"int",
    Gauge->"unsigned int",
    Counter32->"unsigned int",
    Counter->"unsigned int",
    Counter64->"unsigned int",
    OBJECT->"std::string",
    IpAddress->"std::string",
    TimeTicks->"unsigned int",
    Integer32->"int",
    Unsigned32->"unsigned int")

  //ASNMP 库对应的数据类型
  val snmp_type =  Map(
    OCTET->"OctetStr",
    INTEGER->"SnmpInt32",
    Gauge->"Gauge32",
    Counter32->"Counter32",
    Counter->"Counter32",
    Counter64->"Counter64",
    OBJECT->"Oid",
    IpAddress->"IpAddress",
    TimeTicks->"TimeTicks",
    Integer32->"SnmpInt32",
    Unsigned32->"SnmpUInt32")

  val convert_to_snmp =  Map(
    OCTET->".c_str()",
    INTEGER->"long",
    Gauge->"unsigned long",
    Counter32->"unsigned long",
    Counter->"unsigned long",
    Counter64->"unsigned long",
    OBJECT->".c_str()",
    IpAddress->".c_str()",
    TimeTicks->"unsigned long",
    Integer32->"long",
    Unsigned32->"unsigned long"
  )
  val convert_to_cplusplus   =  Map(
    OCTET->".to_string()",
    INTEGER->"int",
    Gauge->"unsigned int",
    Counter32->"unsigned int",
    Counter->"unsigned int",
    Counter64->"unsigned int",
    OBJECT->".to_string()",
    IpAddress->".to_string()",
    TimeTicks->"unsigned int",
    Integer32->"int",
    Unsigned32->"unsigned int"
  )

  val is_ref_type   =  Map(
    OCTET->true,
    INTEGER->false,
    Gauge->false,
    Counter32->false,
    Counter->false,
    Counter64->false,
    OBJECT->true,
    IpAddress->true,
    TimeTicks->false,
    Integer32->false,
    Unsigned32->false
  )
  val default_value   =  Map(
    OCTET->"\"\"",
    INTEGER->"0",
    Gauge->"0",
    Counter32->"0",
    Counter->"0",
    Counter64->"0",
    OBJECT->"\"\"",
    IpAddress->"\"\"",
    TimeTicks->"0",
    Integer32->"0",
    Unsigned32->"0"
  )
  val format   =  Map(
    OCTET->"%s",
    INTEGER->"%d",
    Gauge->"%d",
    Counter32->"%d",
    Counter->"%d",
    Counter64->"%d",
    OBJECT->"%s",
    IpAddress->"%s",
    TimeTicks->"%d",
    Integer32->"%d",
    Unsigned32->"%d"
  )
  def getSyntax( name :String) ={
    try{
      withName(name)
    } catch{
      case e : NoSuchElementException =>

        NA
    }
  }

  def getCPlusPlusType( syntax : Value) = cplusplus_type(syntax)
  def getSnmpType( syntax :Value) = snmp_type(syntax)
  def getConvertToSnmp( syntax : Value) = convert_to_snmp(syntax)
  def getConvertToCPlusPlus(syntax : Value) = convert_to_cplusplus(syntax)
  def getFormat(syntax : Value) = format(syntax)
}

object SnmpAccess extends  Enumeration {
  val accesses = new mutable.HashSet[String]()
  val read_only , accessible_for_notify, read_write, read_create, not_accessible = Value

  def getAccess( access : String) ={
    try{
      withName(access.trim.replaceAll("-","_"))
    }catch {
      case e : NoSuchElementException => not_accessible
    }
  }
}
object SnmpToolkit {
  def toMethodName( name :String) ={
    var first = true
    val r =for( c <-name) yield {
      if( first){
        first = false
        c.toUpper
      }else{
        c
      }
    }

    r.mkString("")
  }
  def toClassName( name :String) ={
    toMethodName(name)
  }
  def toFieldName( name :String) ={
    var lastIsUpper = false
    val r = for ( c<- name) yield {
      if( lastIsUpper){

        if( c.isUpper){
          s"${c.toLower}"
        }else{
          lastIsUpper = false;
          s"${c}"
        }
      }else{
        if( c.isUpper){
          lastIsUpper = true
          s"_${c.toLower}"

        } else{
          s"${c}"
        }
      }
    }


    r.mkString("").concat("_")

  }


}

SnmpLexical.scala 文件包含SNMP MIB文件的文法和词法分析过程

package cn.newzai.parser.snmp

import scala.util.parsing.combinator.lexical.StdLexical
import scala.util.parsing.input.CharArrayReader._
import scala.util.parsing.combinator.syntactical.StandardTokenParsers
import scala.io.Source
import scala.collection.mutable
import cn.newzai.parser.snmp.result._


/**
 *
 * @param name oid节点的名称
 * @param parent 除了oid为1(iso) 和o(zeroDotZero)两个节点以为,其它oid节点都有一个父节点
 * @param instance  本oid节点在父节点中的位置
 */
class MibNode(val name :String,val parent :  String, val instance : Int){
  override def toString: String = s" ${name} ::= { ${parent} ${instance}}"
}

/**
 *  nodes 保存解析过程中得到的所有 oid节点信息
 *  zeroDotZero  oid值为0的节点
 *  iso          oid值为1的节点 绝大部分OID为iso的下级节点
 */
object MibNode {

  private val nodes =new  mutable.HashMap[String, MibNode]()
  private val zeroDotZero = MibNode("0",0)
  private val iso = MibNode("iso",1)

  def clear() {
    nodes.clear()

  }
  def apply( name :String, parent :String, instance : Int) = {
    new MibNode(name,parent,instance)
  }

  def apply( name :String,instance : Int) = {
    new MibNode(name,null,instance)
  }

  def addMibNode( name :String,parent :String, instance : Int){
     val newNode = MibNode(name,parent,instance)
     nodes(name) = newNode
  }
  def hasMibNode( name :String) = {
    name match {
      case "0" => true
      case "iso" => true
      case _ => nodes.contains(name)
    }
  }
  def getMibNode(name :String) ={
    name match {
      case "0" => zeroDotZero
      case "iso" => iso
      case _ => nodes(name)

    }
  }

  private def getOid_(name :String) : List[Int]={
    name match {
      case "0" => List(0)
      case "iso" => List(1)
      case _ =>
        val node = nodes(name)
        node.instance :: getOid_(node.parent)
    }
  }


  private def getOid( name :String ) = getOid_(name).reverse

  /**
   *
   * @param name  oid名称
   * @return      获取name对于的oid只,得到的是点分号的格式, eg “1.3.4.1334”
   */
  def getOidString( name :String) = {
    val oids = getOid(name)
    val oidsStr = for( o <- oids) yield { o.toString }
    oidsStr.mkString(".")
  }
}

/**
 * SNMP 词法分析,Scala提供的标准的词法分析无法满足SNMP的词法分析。
 * 主要为 标识符,snmp可以包含 破折号(-)
 * 注释 SNMP注释以 两个破折号(--)考开始,直到行尾.
 */
class SnmpLexical extends StdLexical{

  // SNMP MIB文件的标识符(OID名称等包含 - 符号
  override def identChar = letter | elem('_') | elem('-')
  // SNMP MIB文件的注释,包含 -- ,均为含注释
  override def whitespace:Parser[Any] = rep(
    whitespaceChar
      | '/' ~ '*' ~ comment
      | '/' ~ '/' ~ rep( chrExcept(EofCh, '\n') )
      | '-' ~ '-' ~ rep( chrExcept(EofCh, '\n') )
      | '/' ~ '*' ~ failure("unclosed comment")
  )
  override def token: Parser[Token] =
    ( identChar ~ rep( identChar | digit )              ^^ { case first ~ rest => processIdent(first :: rest mkString "") }
      | digit ~ rep( digit )                              ^^ { case first ~ rest => NumericLit(first :: rest mkString "") }
      | '\'' ~ rep( chrExcept('\'', '\n', EofCh) ) ~ '\'' ^^ { case '\'' ~ chars ~ '\'' => StringLit(chars mkString "") }
      | '\"' ~ rep( chrExcept('\"', EofCh) ) ~ '\"' ^^ { case '\"' ~ chars ~ '\"' => StringLit(chars mkString "") }
      | EofCh                                             ^^^ EOF
      | '\'' ~> failure("unclosed string literal")
      | '\"' ~> failure("unclosed string literal")
      | delim
      | failure("illegal character")
      )
}

/**
 * SNMP MIB文件词法分析,每个SnmpParser对于一个SNMP MIB 文件 , 从StandardTokenParsers继承
 * @param dir  SNMP MIB文件的所在文件夹,被import进来的文件都在相同的文件夹
 * @param file  当前解析的MIB文件
 * @param importFile  标识是因和原因解析该文件,true时,表示该文件为import文件,不提取OBJECT-GROUP信息,只提供OID值。
 *                    顶层文件时为false,分析所有内容.
 */
class SnmpParser(val dir :String, val file :String, val importFile : Boolean) extends StandardTokenParsers {

  /**
   * 覆盖基类的lexical字段,scala 的字段和方法统一处理,也可以被覆盖.
   */
  override val lexical = new SnmpLexical

  /*
  定义SNMP MIB 文件的分隔符,分隔符定义时,采用最大化匹配原则
   */
  lexical.delimiters +=("::=","(",")","{","}",",",";","..","-","+")
  /*
  SNMP MIB 的保留字(包含关键字和一部分SNMP MIB的类型定义)
   */
  lexical.reserved   +=("DEFINITIONS","BEGIN","END","IMPORTS","FROM","MODULE-IDENTITY","MODULE-IDENTITY",
    "OBJECT-TYPE", "NOTIFICATION-TYPE", "MODULE-COMPLIANCE", "OBJECT-GROUP", "NOTIFICATION-GROUP","OBJECT",
    "IDENTIFIER","OBJECT-TYPE","SIZE","MAX-ACCESS","ACCESS","MIN-ACCESS","SYNTAX","STATUS","INDEX","SEQUENCE",
    "MANDATORY-GROUPS","GROUP","MODULE","OBJECTS","NOTIFICATIONS","TEXTUAL-CONVENTION","OCTET","STRING","OF","AUGMENTS",
    "DEFVAL","OBJECT-IDENTITY")


  def keyword: Parser[String] =
    "MODULE-IDENTITY"|"MODULE-IDENTITY"|"OBJECT-TYPE"|"NOTIFICATION-TYPE"|
      "MODULE-COMPLIANCE"|"OBJECT-GROUP"|"NOTIFICATION-GROUP"|"OBJECT"|"IDENTIFIER"|"SEQUENCE"|"OF"|
      "TEXTUAL-CONVENTION"|"OBJECT-IDENTITY"|"OCTET"|"STRING" ^^ { case x => x}

  /**
   *
   * @param oid "::={ parent instance } " 格式的定义,
   * @return Option[(String,String)] 提取 parent,instance 值
   */
  def getOidParent( oid :String) ={
    val matchParent = """\s*::=\s+\{\s*([\w-]+)\s+([\d]+)\s*\}""".r
    oid match {
      case matchParent(parent,instance) => Some((parent,instance))
      case _ => None
    }
  }

  /**
   * 解析SNMP 内容
   * @return
   */
  def parserContent : Parser[Any] = {
    parserObjectIdentifier|
      parserObjectIdentify|
      parserTextualConvention |
      parserSimpleTextualConvention|
      parserObjectType|
      parserSequence|
      parserNotificationType|
      parserModuleCompliance|
      parserObjectGroup

  }

  /**
   * 解析SNMP文件的定义,SNMP 文件解析的入口方法.
   * @return
   */
  def parserDefinitions : Parser[Any] = {
     ident ~ "DEFINITIONS" ~ "::=" ~ "BEGIN" ~opt(parserImports)~opt(parserMODULE_IDENTITY)~rep(parserContent)~ "END"   ^^ {
       case n~ "DEFINITIONS" ~ "::=" ~ "BEGIN"~ im~mi~contents ~ "END" =>
         s"""${n} DEFINITIONS ::= BEGIN
           |${im.getOrElse("")}
           |${mi.getOrElse("")}
           |${contents mkString("\n")}
           |END
         """.stripMargin
     }
  }

  /*
  解析导入的文件名,并且进入被导入的文件进行递归解析
   */
  def parserFrom    : Parser[String] ={
    repsep(ident|keyword,",")~"FROM"~ident ^^ {
      case f ~ "FROM"~n =>
      val macroMibFile = Set("SNMPv2-CONF","RFC-1212")
      if( !macroMibFile.contains(n) && !SnmpParser.parseredFile.contains(n))
        {
          SnmpParser.parseredFile.add(n)

          val fromParser = new SnmpParser(this.dir, n, true)
          val r = fromParser.parserFile(fromParser.parserDefinitions)
          if (!r.successful) {
            println(s"parser file ${n} error.")
            println(r)
          } else {
            println(s"parser file ${n} OK")
          }
        }
       s"""${f.mkString(",")} FROM ${n}"""
    }
  }

  /**
   * 解析IMPORTS 段
   * @return
   */
  def parserImports : Parser[String] = {
    "IMPORTS"~rep(parserFrom)~";" ^^ {
      case "IMPORTS"~f~";" =>
        s"""
          |IMPORTS
          |${f.mkString("\n")}
          |;""".stripMargin

    }
  }

  /**
   * 解析 DESCRIPTION和那些以字符串作为值的属性,例如LAST-UPDATED等。
   * @return
   */
  def parserDescription : Parser[String] = {
    ident~stringLit ^^ {case f ~ d =>
      s"""${f} "${d}" """}
  }
  def parserOIDPath : Parser[Any] = {
    ident~"("~numericLit~")" ^^ {
      case p~"("~i~")" => s"${p}(${i})"
    }
  }
  def parserOIDPaths : Parser[Any] ={
    rep(parserOIDPath) ^^ {
      case paths => paths.mkString(" ")
    }
  }

  /**
   * 解析OID 值
   *
   * @return
   */
  def parserOID : Parser[String] ={
    "::="~"{"~ident~parserOIDPaths~numericLit~"}"  ^^ {
      case   "::="~"{"~i~p~n~"}" => s"::= { ${i} ${n}}"
    }
  }

  def parserOIDGetValue : Parser[(String,Int)] = {
    "::="~"{"~ident~numericLit~"}"  ^^ {
      case   "::="~"{"~i~n~"}" =>(i, n.toInt)
    }
  }
  def getOIDValue( parentOID : String) = {
    val r = phrase(parserOIDGetValue)( new lexical.Scanner(parentOID))
    r.get
  }

  /**
   * 捕获解析结果,保存在中间变量中.
   * @param name   oid名称
   * @param parent  parent 路径
   */
  def insertOid( name :String, parent : String){
    val p = getOIDValue(parent)
    MibNode.addMibNode(name,p._1,p._2)
  }

  /**
   * 解析模块定义
   * @return
   */
  def parserMODULE_IDENTITY : Parser[String] ={
    ident ~ "MODULE-IDENTITY"  ~rep(parserDescription)~parserOID ^^ {
      case i ~ "MODULE-IDENTITY" ~ d ~ oid =>

      insertOid(i, oid)

        s"""${i}  MODULE-IDENTITY
          |${d mkString("\n")}
          |${oid}
        """.stripMargin
    }
  }

  /**
   * 解析 OBJECT IDENTITIFER
   * @return
   */
  def parserObjectIdentifier : Parser[Any] = {

    ident ~ "OBJECT"~"IDENTIFIER"~parserOID ^^ {
      case i ~ "OBJECT"~"IDENTIFIER" ~ oid =>
        insertOid(i,oid)
       s"${i} OBJECT IDENTIFIER ${oid}"
    }
  }
  def parserObjectIdentify : Parser[Any] = {
    ident ~"OBJECT-IDENTITY"~parserStatus~parserDescription~parserOID ^^ {
      case i ~"OBJECT-IDENTITY"~s~d~oid =>
      insertOid(i,oid)
        s"""${i} OBJECT-IDENTITY
          |${s}
          |${d}
          |${oid}
        """.stripMargin
    }
  }

  /**
   * 解析OCTET STRING 的大小限制
   * @return
   */
  def parserSize :Parser[Any] = {
    "("~"SIZE"~"("~numericLit~".."~numericLit~")"~")"  ^^ {
      case "("~"SIZE"~"("~start~".."~end~")"~")" => s"(SIZE(${start}..${end}))"
    }
  }

  /**
   * 解析整数的 范围限制
   * @return
   */
  def parserRange:Parser[Any] = {
    "("~numericLit~".."~numericLit~")" ^^ {
      case "("~start~".."~end~")" => s"(${start}..${end})"
    }
  }
  def parserEnumSimple : Parser[Any] ={
    ident ~ "("~numericLit~")" ^^ {
      case n ~ "("~v~")" &
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值