Skip to content

Instantly share code, notes, and snippets.

@keichi
Last active December 18, 2015 11:09
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 keichi/5773906 to your computer and use it in GitHub Desktop.
Save keichi/5773906 to your computer and use it in GitHub Desktop.
An experimental OpenFlow controller which discovers OpenFlow switch network topology. Written with the trema framework.
require 'rubygems'
require 'bindata'
class BasicLLDPPacket < BinData::Record
endian :big
uint48 :dst_mac
uint48 :src_mac, :value => 0x0180c200000e
uint16 :ether_type, :value => 0x88cc
bit7 :chassis_tlv_type, :value => 1
bit9 :chassis_tlv_length, :value => lambda { chassis_id.length + 1 }
uint8 :chassis_id_type, :value => 7
string :chassis_id, :read_length => lambda { chassis_tlv_length - 1}
bit7 :port_tlv_type, :value => 2
bit9 :port_tlv_length, :value => lambda { port_id.length + 1 }
uint8 :port_id_type, :value => 7
string :port_id, :read_length => lambda { port_tlv_length - 1}
bit7 :ttl_tlv_type, :value => 3
bit9 :ttl_tlv_length, :value => 2
uint16 :ttl, :initial_value => 180
bit7 :end_tlv_type, :value => 0
bit9 :end_tlv_length, :value => 0
end
class TopologyFinder < Controller
periodic_timer_event :flood_lldp_packets, 10
def start
@switch_ports = {}
end
def switch_ready datapath_id
send_message datapath_id, FeaturesRequest.new
end
def features_reply datapath_id, message
info "List of ports on switch #{datapath_id.to_hex}:"
ports = message.ports.select {|each|
each.up?
}.collect {|each|
each.number
}
p ports
@switch_ports[datapath_id] = ports
end
def packet_in datapath_id, message
if message.lldp?
dst_datapath_id = datapath_id
dst_port = message.in_port
src_datapath_id, src_port = parse_lldp_packet message.data
info "SW#{src_datapath_id.to_hex} -> SW#{dst_datapath_id.to_hex} [label=\"#{src_port} to #{dst_port}\"];"
end
end
def flood_lldp_packets
info "Start LLDP flooding"
@switch_ports.each_pair {|datapath_id, ports|
ports.each {|port|
packet = get_lldp_packet datapath_id, port, 180
send_packet_out(
datapath_id,
:data => packet,
:actions => SendOutPort.new(port)
)
}
}
end
private
def parse_lldp_packet data
packet = BasicLLDPPacket.read data
datapath_id = packet.chassis_id.hex
port = packet.port_id.to_i
return datapath_id, port
end
def get_lldp_packet datapath_id, port, ttl
packet = BasicLLDPPacket.new
packet.dst_mac = 0x000e0cb84897
packet.chassis_id = datapath_id.to_hex
packet.port_id = port.to_s
packet.ttl = ttl
data = packet.to_binary_s
if data.size < 64
data << "\xa5" * (64 - data.size)
end
return data
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment