Skip to content

Instantly share code, notes, and snippets.

@mat813
Created November 20, 2012 19:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mat813/4120316 to your computer and use it in GitHub Desktop.
Save mat813/4120316 to your computer and use it in GitHub Desktop.
Bird 2 Dot
*.conf
*.dot
*.png
require 'rubygems'
require 'pp'
require 'treetop'
$-I << '.'
require 'bird_conf'
parser = BirdConfParser.new
parser.consume_all_input = false
servers = {}
ARGV.each do |f|
result = parser.parse(File.readlines(f).join())
if result.empty?
puts parser.failure_reason
puts parser.failure_line
puts parser.failure_column
else
dot = f.gsub(/\.conf$/, '.dot')
png = f.gsub(/\.conf$/, '.png')
puts dot
router = nil
tables = []
protocols = {}
protocol_tables = []
pipes = []
result.content.each do |what,n,v|
case what
when :router_id
router = n
when :table
tables << n
when :protocol_bgp, :protocol_device, :protocol_direct, :protocol_kernel, :protocol_static
protocols[what.to_s.sub(/^protocol_/, '')] ||= []
protocols[what.to_s.sub(/^protocol_/, '')] << n
pipes << [n,n,v["table"], v["export"] || 'all']
pipes << [n,v["table"],n, v["import"] || 'all']
when :protocol_pipe
pipes << [n, v["table"], v["peer table"], v["export"] || 'all']
pipes << [n, v["peer table"], v["table"], v["import"] || 'all']
end
end
File.open(dot, 'w') do |fout|
fout << <<-eot
digraph lns {
// Router
node [shape=box, fontsize=18];
"#{f.sub(/\.conf$/, '')} #{router}";
node [shape=box, fontsize=14, style="filled"];
table [fillcolor=yellow];
bgp [fillcolor=green];
direct [fillcolor=cyan];
static [fillcolor=red];
kernel [fillcolor=purple];
other [fillcolor=brown];
// Tables
node [shape=hexagon, fillcolor="yellow" style="rounded,filled", fontsize=14];
eot
tables.each do |t|
fout << " \"#{t}\";\n"
end
fout << <<-eot
// Protocols
node [shape=box, style="rounded,filled", fontsize=14];
eot
protocols.each do |k,ps|
ps.each do |p|
fout << " \"#{p}\"[label=\"#{p}"
case k
when 'bgp'
fout << "\", fillcolor=green"
when 'direct'
fout << "\", fillcolor=cyan"
when 'static'
fout << "\", fillcolor=red"
when 'kernel'
fout << "\", fillcolor=purple"
else
fout << "\\n#{k}\", fillcolor=brown"
end
fout << "];\n"
end
end
fout << <<-eot
// Pipes...
edge [fontsize=10]
eot
pipes.each do |n,t,p,e|
if e != 'none'
fout << " \"#{t}\" -> \"#{p}\""
fout << "[label=\"#{e.gsub(/"/, '\\"')}\"]"
fout << ";\n"
end
end
fout << "}\n"
end
system("dot -Gcharset=latin1 -Tpng #{dot} > #{png}")
end
end
require 'ip'
grammar BirdConf
include IP
rule entries
(SPACE / COMMENT / router_id / protocol / table / function / filter / listen)+ {
def content
# XXX bad way to get rid of comments and spaces.
elements.select {|e| e.respond_to?(:content)}.map {|e| e.content}+ [[:table, "master"]]
end
}
end
rule router_id
'router' SPACE1 'id' SPACE1 i:IPv4address SEMICOLON {
def content
[:router_id, i.text_value]
end
}
end
rule protocol
'protocol' SPACE1 protocol:unquoted_string name:(SPACE1 name:string from:(SPACE1 "from" from:string)?)? SPACE0 '{' SPACE0
c:protocol_content
'}' {
def content
ret = [:"protocol_#{protocol.content}"]
unless name.empty?
ret << name.name.content
unless name.from.empty?
ret << name.from.from.content
end
end
detail = c.content
detail["table"] ||= "master"
ret << detail
return ret
end
}
end
rule protocol_content
(protocol_content_key / protocol_content_key_value / protocol_content_key_subvalue / protocol_content_key_value_subvalue)+ {
def content
Hash[elements.map(&:content)]
end
}
end
rule protocol_content_key
key:protocol_key SEMICOLON {
def content
[key.text_value, 1]
end
}
end
rule protocol_content_key_value
key:protocol_key SPACE1 value:value SEMICOLON {
def content
[key.text_value, value.content]
end
}
end
rule protocol_content_key_subvalue
key:protocol_key SPACE0 '{' SPACE0 value_ext:protocol_content '}' SEMICOLON {
def content
[key.text_value, value_ext.content]
end
}
end
rule protocol_content_key_value_subvalue
key:protocol_key SPACE1 value:value SPACE0 '{' SPACE0 value_ext:protocol_content '}' SEMICOLON {
def content
[key.text_value, [value.content, value_ext.content]]
end
}
end
# only multiwords keywords defined here, single words are catched by
# unquoted_string.
rule protocol_key
# global
'router' SPACE1 'id' / 'peer' SPACE1 'table'
/ ('import' / 'export') ( SPACE1 'limit' / '' )
/ ('generate' / 'accept') SPACE1 ('from' / 'to')
# bgp
/ 'source' SPACE1 'address' / 'next' SPACE1 'hop' SPACE1 'self' / 'missing' SPACE1 'lladdr' / 'igp' SPACE1 'table' / 'ttl' SPACE1 'security' / 'rr' SPACE1 'client' / 'rr' SPACE1 'cluster' SPACE1 'id' / 'rs' SPACE1 'client' / 'enable' SPACE1 'route' SPACE1 'refresh' / 'interpret' SPACE1 'communities' / 'enable' SPACE1 'as4' / 'advertise' SPACE1 'ipv4' / 'route' SPACE1 'limit' / 'disable' SPACE1 'after' SPACE1 'error' / 'hold' SPACE1 'time' / 'startup' SPACE1 'hold' SPACE1 'time' / 'keeplive' SPACE1 'time' / 'connect' SPACE1 'retry' SPACE1 'time' / 'start' SPACE1 'delay' SPACE1 'time' / 'error' SPACE1 'wait' SPACE1 'time' / 'error' SPACE1 'forget' SPACE1 'time' / 'path' SPACE1 'metric' / 'med' SPACE1 'metric' / 'deterministic' SPACE1 'med' / 'igp' SPACE1 'metric' / 'prefer' SPACE1 'older' / 'default' SPACE1 'bgp_med' / 'default' SPACE1 'bgp_local_pref'
# device
/ 'scan' SPACE1 'time'
# kernel
/ 'scan' SPACE1 'time' / 'device' SPACE1 'routes' / 'kernel' SPACE1 'table'
# static
/ 'igp' SPACE1 'table'
# bordel
/ unquoted_string
end
rule table
'table' SPACE1 table:unquoted_string SEMICOLON {
def content
[:table, table.content]
end
}
end
rule function
# add arguments
'function' SPACE1 function:unquoted_string '(' ')' SPACE0
# add variables
'{'
c:function_content
'}' {
def content
[:function, function.content, c.text_value]
end
}
end
rule function_content
(!'}' .)* / '{' function_content '}'
end
rule filter
'filter' SPACE1 filter:unquoted_string SPACE0
'{'
c:filter_content
'}' {
def content
[:filter, filter.content, c.text_value]
end
}
end
rule filter_content
(!'}' .)* / '{' filter_content '}'
end
rule listen
'listen' SPACE1 'bgp' SPACE1 value:value SEMICOLON {
def content
[:listen, value.content]
end
}
end
rule CIDR
(IPv4address / IPv6address) ('/' [0-9]+)? {
require 'ipaddr'
def content
IPAddr.new(text_value)
end
}
end
rule IP
(IPv4address / IPv6address) {
require 'ipaddr'
def content
[:router_id, IPAddr.new(text_value)]
end
}
end
# special string
rule value
quoted_string / generic_value
end
rule generic_value
(![;{] .)+ {
def content
text_value
end
}
end
# content is taken care of by each string type.
rule string
unquoted_string / quoted_string
end
rule unquoted_string
[a-zA-Z0-9_]+ {
def content
text_value
end
}
end
rule quoted_string
'"' s:('\"' / !'"' .)* '"' {
def content
s.text_value
end
}
end
rule SEMICOLON
SPACE0 ';' COMMENT_OR_SPACE
end
rule COMMENT_OR_SPACE
SPACE0 COMMENT? SPACE0
end
rule SPACE1
SPACE+
end
rule SPACE0
SPACE*
end
rule SPACE
[ \t\r\n]
end
rule COMMENT
D_COMMENT / C_COMMENT
end
rule D_COMMENT
'#' c:(!EOL .)* EOL
end
rule C_COMMENT
'/*'
c:(
!'*/'
(. / "\n")
)*
'*/'
end
rule EOL
[\r\n]
end
end
#!/usr/bin/env tt
#
# Author:: Iñaki Baz Castillo <ibc@aliax.net>
# Copyright:: 2008 Iñaki Baz Castillo
# License:: Public Domain
#
# ABNF syntax for IPv4 and IPv6 is defined in Appendix A of RFC 3986:
# http://tools.ietf.org/html/rfc3986#appendix-A
grammar IP
rule IPv4address
DIGIT DIGIT? DIGIT? '.' DIGIT DIGIT? DIGIT? '.' DIGIT DIGIT? DIGIT? '.' DIGIT DIGIT? DIGIT?
end
rule IPv6address
( h16 ':' h16 ':' h16 ':' h16 ':' h16 ':' h16 ':' ls32 )
/ ( '::' h16 ':' h16 ':' h16 ':' h16 ':' h16 ':' ls32 )
/ ( h16? '::' h16 ':' h16 ':' h16 ':' h16 ':' ls32 )
/ ( ( '::' / h16 '::' / h16 ':' h16 '::' ) h16 ':' h16 ':' h16 ':' ls32 )
/ ( ( '::' / h16 '::' / h16 ':' h16 '::' / h16 ':' h16 ':' h16 '::' ) h16 ':' h16 ':' ls32 )
/ ( ( '::' / h16 '::' / h16 ':' h16 '::' / h16 ':' h16 ':' h16 '::' / h16 ':' h16 ':' h16 ':' h16 '::' ) h16 ':' ls32 )
/ ( ( '::' / h16 '::' / h16 ':' h16 '::' / h16 ':' h16 ':' h16 '::' / h16 ':' h16 ':' h16 ':' h16 '::' / h16 ':' h16 ':' h16 ':' h16 ':' h16 '::' ) ls32 )
/ ( ( '::' / h16 '::' / h16 ':' h16 '::' / h16 ':' h16 ':' h16 '::' / h16 ':' h16 ':' h16 ':' h16 '::' / h16 ':' h16 ':' h16 ':' h16 ':' h16 '::' / h16 ':' h16 ':' h16 ':' h16 ':' h16 ':' h16 '::' ) h16 )
/ ( '::' / h16 '::' / h16 ':' h16 '::' / h16 ':' h16 ':' h16 '::' / h16 ':' h16 ':' h16 ':' h16 '::' / h16 ':' h16 ':' h16 ':' h16 ':' h16 '::' / h16 ':' h16 ':' h16 ':' h16 ':' h16 ':' h16 '::' / h16 ':' h16 ':' h16 ':' h16 ':' h16 ':' h16 ':' h16 '::' )
end
rule h16
HEXDIG HEXDIG? HEXDIG? HEXDIG?
end
rule ls32
( h16 ":" h16 ) / IPv4address
end
rule HEXDIG
[\x30-\x39a-fA-F]
end
rule DIGIT
[\x30-\x39]
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment