Road to Cisco ACL Parser(3)

いやあ、(2) 書いたのが id:stereocat:20090830:1251623216 って何年前から放置してるんだよっていうネタをここに来てひっぱり出している。この2週間くらい、仕事の谷間ができて割と暇だったんだよね。なので休眠状態のこのネタに手をつけてみたんだけど、来週くらいからまた次の仕事始まりそうで、始まっちゃうとこの辺に手をつける気力が湧かない可能性が高い。と、いうことで、結局使えるところまではできていないけど、中間状態のまとめとして記録しておく。

やったこと

  • Parse::Yapp, Parse::Eyapp のドキュメント読み直した
  • ACL Syntax は、前まで見ていたのは古いツールからもらってきたのがベースだったのでちょっと物足りない。ということで、書き直した。前回までは extended ACL だけ対象だったけど、いっそのこと ACL 全般対応しようと。この辺も parse できるように機能強化。
    • Numbered access-list (standard/extended)
    • Named access-list (standard/extended)
    • Object group
  • テスト面倒! 組み合わせ無限大! ある程度テストコードは書いたけど(というかテストケースを自動生成してテストするようにした)、まだテストし切れていない部分多数。

文法だけ書いておくとこういう感じ。object-group についてはいまのところ ACL の一種として扱ってしまっている。もう本当にきりがないので、手元の 1812J [IOS/12.4(24)T] で確認できる機能だけでやることにした。

expr : ipacl
        | ipacl expr
        ;

ipacl : numbered_acl
        | named_acl
        | objgrp
        ;

# standard access-list

numbered_acl : STDACL std_acl
        | EXTACL ext_acl
        ;

std_acl : action ip_spec std_acl_log_spec
        | REMARK
        ;

std_acl_log_spec :
        | LOG log_cookie
        ;

# extended access-list

ext_acl : dynamic_spec ext_acl_body other_qualifier_list
        | REMARK
        ;

ext_acl_body : action ip_proto ip_spec ip_spec
        | action icmp_proto ip_spec ip_spec icmp_qualifier
        | action tcp_proto tcp_src_spec tcp_dst_spec tcp_flags_list
        | action udp_proto udp_src_spec udp_dst_spec
        | action objgrp_service_spec objgrp_src_spec objgrp_dst_spec
        ;

# protocols

ip_proto : AHP          # 51
        | EIGRP         # 88
        | ESP           # 50
        | GRE           # 47
        | IGMP          # 2
        | IGRP
        | IP            # IS NOT 0! (no number)
        | IPINIP        # 94
        | NOS
        | OSPF          # 89
        | PCP           # 108
        | PIM           # 103
        | NUMBER        # ip protocol number (0-255)
        ;

icmp_proto : ICMP       # 1
        ;

tcp_proto : TCP         # 6
        ;

udp_proto : UDP         # 17
        ;

tcp_src_spec: objgrp_src_spec tcp_port_spec
        ;
tcp_dst_spec: objgrp_dst_spec tcp_port_spec
        ;
udp_src_spec: objgrp_src_spec udp_port_spec
        ;
udp_dst_spec: objgrp_dst_spec udp_port_spec
        ;

# ip named access-list

named_acl : std_named_acl_header std_named_acl_entry_list
        | ext_named_acl_header ext_named_acl_entry_list
        ;

std_named_acl_header : IPACL STANDARD STRING
        ;

std_named_acl_entry_list :
        | std_named_acl_entry std_named_acl_entry_list
        ;

std_named_acl_entry: seq_number std_acl
        | seq_number EVALUATE
        ;

ext_named_acl_header : IPACL EXTENDED STRING
        ;

ext_named_acl_entry_list :
        | ext_named_acl_entry ext_named_acl_entry_list
        ;

ext_named_acl_entry: seq_number ext_acl
        | seq_number EVALUATE STRING
        ;

seq_number :
        | NUMBER # (1-2147483647)
        ;

# access-list common components

action : PERMIT
        | DENY
        ;

ip_spec : HOST IPV4_ADDR
        | IPV4_ADDR IPV4_ADDR # ipaddr wildcard
        | ANY
        ;

dynamic_spec :
        | DYNAMIC STRING timeout_spec
        ;

timeout_spec :
        | TIMEOUT NUMBER
        ;

# object-group
objgrp : objgrp_service
        | objgrp_network
        ;

objgrp_service_spec : OBJGRP STRING # service object group
        ;

objgrp_src_spec : objgrp_network_spec
        | ip_spec
        ;

objgrp_dst_spec : objgrp_network_spec
        | ip_spec
        ;

objgrp_network_spec : OBJGRP STRING # network object group
        ;

objgrp_network : objgrp_network_header objgrp_network_entry_list
        ;

objgrp_network_header : OBJGRP NETWORK STRING
        ;

objgrp_network_entry_list :
        | objgrp_network_entry objgrp_network_entry_list
        ;

objgrp_network_entry : DESCRIPTION
        | HOST IPV4_ADDR
        | IPV4_ADDR IPV4_ADDR
        | IPV4_ADDR SLASH NUMBER # 0-32
        | RANGE IPV4_ADDR IPV4_ADDR
        | GRPOBJ STRING # nested object-group
        ;

objgrp_service : objgrp_service_header objgrp_service_entry_list
        ;

objgrp_service_header : OBJGRP SERVICE STRING
        ;

objgrp_service_entry_list :
        | objgrp_service_entry objgrp_service_entry_list
        ;

objgrp_service_entry : DESCRIPTION
        | ip_proto
        | icmp_proto icmp_qualifier
        | tcp_proto objgrp_tcp_proto
        | udp_proto objgrp_udp_proto
        | TCPUDP objgrp_tcpudp_proto
        | GRPOBJ STRING # nested object-group
        ;

objgrp_tcp_proto :
        | objgrp_tcp_proto_spec
        | SOURCE objgrp_tcp_proto_spec
        ;
objgrp_tcp_proto_spec :
        | unary_operator tcp_port_qualifier
        | RANGE tcp_port_qualifier tcp_port_qualifier
        | tcp_port_qualifier
        ;

objgrp_udp_proto :
        | objgrp_udp_proto_spec
        | SOURCE objgrp_udp_proto_spec
        ;
objgrp_udp_proto_spec :
        | unary_operator udp_port_qualifier
        | RANGE udp_port_qualifier udp_port_qualifier
        | udp_port_qualifier
        ;

objgrp_tcpudp_proto :
        | objgrp_tcpudp_proto_spec
        | SOURCE objgrp_tcpudp_proto_spec
        ;
objgrp_tcpudp_proto_spec :
        | unary_operator tcpudp_port_qualifier
        | RANGE tcpudp_port_qualifier tcpudp_port_qualifier
        | tcpudp_port_qualifier
        ;

tcpudp_port_qualifier : NUMBER        # port number (0-65535)
        | DISCARD
        | DOMAIN
        | ECHO
        | PIM_AUTO_RP
        | SUNRPC
        | SYSLOG
        | TACACS
        | TALK
        ;

# icmp qualifier

icmp_qualifier :
        | ADM_PROHIB
        | ALT_ADDR
        | CONV_ERR 
        | DOD_HOST_PROHIB  
        | DOD_NET_PROHIB  
        | ECHO 
        | ECHO_REPLY 
        | GEN_PARAM_PROB 
        | HOST_ISOL 
        | MOB_REDIR 
        | NET_REDIR 
        | NET_TOS_REDIR
        | NET_UNREACH 
        | NET_UNKN 
        | NO_ROOM_OPT 
        | OPT_MISSING 
        | PKT_TOO_BIG 
        | PARAM_PROB 
        | PORT_UNREACH 
        | PREC_UNREACH 
        | PROT_UNREACH 
        | HOST_PREC_UNREACH 
        | HOST_REDIR 
        | HOST_TOS_REDIR 
        | HOST_UNKN 
        | HOST_UNREACH 
        | INFO_REPLY 
        | INFO_REQ 
        | MASK_REPLY
        | MASK_REQ 
        | REASS_TIMEOUT 
        | REDIR 
        | ROUTER_ADV 
        | ROUTER_SOL 
        | SRC_QUENCH 
        | SRC_ROUTE_FAIL 
        | TIME_EXC 
        | TIME_REPLY 
        | TIME_REQ 
        | TRACERT 
        | TTL_EXC 
        | UNREACH
        | icmp_numtype icmp_numcode
        ;

icmp_numtype : NUMBER # icmp message type (0-255)
        ;

icmp_numcode :
        | NUMBER # icmp message code (0-255)
        ;


# tcp/udp port spec

tcp_port_spec :
        | unary_operator tcp_port_qualifier
        | RANGE tcp_port_qualifier tcp_port_qualifier
        ;

udp_port_spec :
        | unary_operator udp_port_qualifier
        | RANGE udp_port_qualifier udp_port_qualifier
        ;

unary_operator : GT
        | EQ
        | NEQ
        | LT
        ;

tcp_port_qualifier : NUMBER        # port number (0-65535)
        | BGP
        | CHARGEN 
        | CMD 
        | DAYTIME 
        | DISCARD 
        | DOMAIN 
        | ECHO 
        | EXEC 
        | FINGER 
        | FTP
        | FTP_DATA 
        | GOPHER 
        | HOSTNAME 
        | IDENT 
        | IRC 
        | KLOGIN 
        | KSHELL 
        | LOGIN 
        | LPD
        | NNTP 
        | PIM_AUTO_RP 
        | POP2 
        | POP3 
        | SMTP 
        | SUNRPC 
        | SYSLOG 
        | TACACS 
        | TALK 
        | TELNET
        | TIME 
        | UUCP 
        | WHOIS 
        | WWW
        ;

udp_port_qualifier : NUMBER        # port number (0-65535)
        | BIFF 
        | BOOTPC 
        | BOOTPS 
        | DISCARD 
        | DNSIX 
        | DOMAIN 
        | ECHO 
        | ISAKMP 
        | MOBILE_IP
        | NAMESERVER 
        | NETBIOS_DGM  
        | NETBIOS_NS
        | NETBIOS_SS
        | NON500_ISAKMP
        | NTP 
        | PIM_AUTO_RP 
        | RIP 
        | SNMP
        | SNMPTRAP 
        | SUNRPC 
        | SYSLOG 
        | TACACS 
        | TALK 
        | TFTP 
        | TIME 
        | WHO 
        | XDMCP
        ;


# tcp flags list

tcp_flags_list:
        | tcp_flags_list tcp_flag
        ;

tcp_flag : ack_flag
        | SYN
        | FIN
        | PSH
        | URG
        | RST
        ;

ack_flag : ESTABL
        | ACK
        ;


# oether qualifier list

other_qualifier_list :
        |  other_qualifier other_qualifier_list
        ;

other_qualifier: dscp_rule
        | FRAGMENTS
        | logging
        | tos_qualifier
        | precedence_qualifier
        | time_range_spec
        | recursive_qualifier
        | ttl_qualifier      # IOS 12.4
        | option_qualifier   # IOS 12.3(4)T,12.2(25)S, IP Options
        ;

dscp_rule : DSCP dscp_spec
        ;

dscp_spec : NUMBER # 0-63
        | AF11     # 001010
        | AF12     # 001100
        | AF13     # 001110
        | AF21     # 010010
        | AF22     # 010100
        | AF23     # 010110
        | AF31     # 011010
        | AF32     # 011100
        | AF33     # 011110
        | AF41     # 100010
        | AF42     # 100100
        | AF43     # 100110
        | CS1      # 001000
        | CS2      # 010000
        | CS3      # 011000
        | CS4      # 100000
        | CS5      # 101000
        | CS6      # 110000
        | CS7      # 111000
        | DEFAULT  # 000000
        | EF       # 101110
        ;

logging: LOG_INPUT log_cookie
        | LOG log_cookie
        ;

log_cookie :
        | STRING
        ;

tos_qualifier: TOS tos_string
        | TOS NUMBER
        ;

tos_string: TOS_MAX_REL
        | TOS_MAX_THRPUT
        | TOS_MIN_DELAY
        | TOS_MIN_MONET_COST
        | TOS_NORMAL
        ;

precedence_qualifier: PRECEDENCE precedence_string
        | PRECEDENCE NUMBER # 0-7
        ;

precedence_string: PREC_CRITICAL # 5
        | PREC_FLASH             # 3
        | PREC_FLASH_OVERR       # 4
        | PREC_IMMED             # 2
        | PREC_INET              # 6
        | NETWORK                # 7
        | PREC_PRIO              # 1
        | PREC_ROUTINE           # 0
        ;

time_range_spec: TIME_RANGE STRING
        ;

recursive_qualifier : REFLECT STRING timeout_spec
        ;

ttl_qualifier : TTL unary_operator NUMBER # 0-255
        | TTL RANGE NUMBER NUMBER
        ;

option_qualifier : OPTION option_spec
        ;

option_spec : ADD_EXT   # opt 147
        | ANY_OPTS
        | COM_SECURITY  # opt 134
        | DPS           # opt 151
        | ENCODE        # opt 15
        | EOOL          # opt 0
        | EXT_IP        # opt 145
        | EXT_SECURITY  # opt 133
        | FINN          # opt 205
        | IMITD         # opt 144
        | LSR           # opt 131
        | MTUP          # opt 11
        | MTUR          # opt 12
        | NO_OP         # opt 1 
        | NSAPA         # opt 150
        | RECORD_ROUTE  # opt 7
        | ROUTER_ALERT  # opt 148
        | SDB           # opt 149
        | SECURITY      # opt 130
        | SSR           # opt 137
        | STREAM_ID     # opt 136
        | TIMESTAMP     # opt 68
        | TRACEROUTE    # opt 82
        | UMP           # opt 152
        | VISA          # opt 142
        | ZSU           # opt 10
        | NUMBER        # ip options vlaue (0-255)
        ;

図にするとこうなる。

と、いうわけで現状

  • Parse すること自体はそれなりにできているはず。でも action がまだ全然書けていないので (どういうデータ吐けばいいのか考え中)、実用上は syntax check がギリギリできるくらいかな。
  • そもそも ACL ってマニュアルとか見ていると結構知らない機能が多い。ACL の文法カオス過ぎじゃねー?

参考資料:

  • 基本的な機能の参考資料はやっぱりこれになるのかな。ほとんど付録しか見てないけど。細かいオプションとかは結局検索に頼ることになる。

Cisco IOSアクセスリスト

Cisco IOSアクセスリスト


作るにあたっていろいろあったのはこのへん:

  • Conflict とか修正するの死にそう。
    • だって、だいたいどのドキュメント見たって「これは yacc/bison のと同じだから、yacc か bison のドキュメント見てね」で終わっちゃってるんだもんなー。それがしんどいんだっつの。まあ、Eyapp は結構チュートリアルとかデバッグのしかたのドキュメントがあるのでそこはありがたいのだが。(使いこなせてないけど)
    • 考え方については Rubyを256倍使うための本 無道編 あたりがとても参考になった (だったら racc で作れって? まえのは Parse::Eyapp である程度組んであったし、これのまえに Yapp/Eyapp のドキュメント読んてそっちでやろうとしてたんだよね…)。ただ256倍シリーズはもう店舗には並んでないので入手がね…。

Rubyを256倍使うための本 無道編

Rubyを256倍使うための本 無道編

    • とはいえこれもそんなに細かいこととか書いてる訳じゃないからねえ。エラーリカバリとかよく分かってないです正直。
  • Lexer 作るのが結構厄介だ
    • remark とか description って任意の文字列埋め込めるものだから、何も考えずに空白区切りで token 考えてると、たとえば 'remark ntp rule for ip acl" みたいな ACL を読んだときに >REMARK< >NTP< >STRING< >STRING< >IP< >STRING< とかで解釈されてしまうので、変に改行コード捨てたりとかできない。
    • 同じワードが異なるオプションで使用されるのをどう取り扱おうか…。
      • tcp/udp ポート指定くらいはまあそれでも同じものを指しているからなあ、と思うけど。tcp port 指定でつかう 'echo' (protocol) と icmp type 指定の 'echo' とか。今回 lexer 側で、数字と1対1対応するものについては数字変換して値返そうと思ったのだけど、icmp echo と tcp/udp echo だとそもそもモノが違うのでそういう対応ができない。まあ reduce action 側でどうにかするしかないんだろう。
      • はまったのは 'object-group network' と 'precedence network' のところで 'network' token が予期しない終端記号になってしまったり。
  • 意外と知らない機能がある
    • dynamic とか precedence とか、そもそもそんなに使ったことのないオプションについては調べ直すのでそれは良いとして。たとえば、tcp/udp ポート指定時の eq/neq operator が 10 個までポートのリストを取ることができる、とか知らなかった…(未実装)
    • object-group まわりって単純にぐぐるとなんかいろんな変種があるように錯覚。そもそも ASA/FWSM の方のが引っかかるし。そしてそれぞれまた書き方が微妙に異なるのか…。とりあえず "object-group (Policy-Based ACL (PBACL))" というのと "Object Groups for ACLs" は異なるようだというのが一つ。やりたいことは同じように見えるけど。今回は PBACL は対応していない。
    • TCPフラグ指定についても、単純にフラグ名を羅列する方法と、最近のだと "match-all/match-any" による指定とかあるのね。これもまだ未実装。

機能自体はいろいろ作ったけど、結局まだ各種 option 類のテストができてないのでその辺をやらないといけないかな。一般的によく使われるであろう書き方については一通りテストしたはずなので、たぶん大丈夫なんじゃないかと思う。

さて、あとはちゃんと action 実装して使えるところまで持って行けるかどうかだな。外圧がないとやりきらんかもしれないけど。とりあえず github のアカウントは作ったので、そこそこ公開できそうな形になったらそっちに移そうか (希望的観測)。