Ravn::Silvus::
Configurator
class
Calculate network parameters and settings for an arbitrary batch of Silvus Radios.
- configs R
Calculated networks for each radio.
- encryption_key R
The encryption key used for this group of radios.
- network_name R
The mesh network name for this group of radios.
Instantiate a new Configurator
.
def initialize
@encryption_key = self.class.encryption_key || self.make_encryption_key
@network_name = self.class.network_name || self.make_network_name
@configs = {}
self.generate_networks
end
Use a single radio in the configuration to queue a change across the existing mesh network. This uses a deferred API, useful to alter crypto keys for radios that are present, but not connected via ethernet.
NOTE: Only mesh/crypto settings are updated – you’ll likely need to still perform network configurations per-radio.
def deferred_update( radio )
radio.defer do
self.update_mesh_settings( radio )
end
end
For each radio in the set, calculate valid network settings.
def generate_networks
self.class.radios.sort_by!{|r| self.nodeid(r.ip) }.each do |radio|
gateway, dhcp_start, dhcp_end = self.calculate_next_network
self.configs[ radio ] = {
gateway: gateway,
dhcp_start: dhcp_start,
dhcp_end: dhcp_end
}
end
ensure
@last_network = nil
end
Calculate the maximum number of radios that will fit within the configured subnet range.
def max_radios
return @max_radios if @max_radios
radio_clients = self.class.virtual_network.to_range.count
virtual_network = IPAddr.new "%s/%s" % [
self.class.virtual_network.to_s,
self.class.mesh_netmask
]
mesh_clients = virtual_network.to_range.count
@max_radios = mesh_clients / radio_clients
return @max_radios
end
update( *specified_radios )
Write the changes to each radio. Returns a hash:
{ success: [ (radios) ], failure: { radio1 => error, radio2 => error } }
def update( *specified_radios )
result = { success: [], failure: {} }
specified_radios.flatten!
radios = self.configs.filter do |radio|
specified_radios.empty? || specified_radios.include?( radio.label )
end
radios.each do |radio, network|
self.log.info "Writing changes to %p" % [ radio.label ]
begin
radio.batch do
radio.virtual_network( true )
radio.virtual_ip_address( network[:gateway] )
radio.virtual_ip_gateway( network[:gateway] )
radio.virtual_ip_netmask( self.class.mesh_netmask )
radio.dhcp_settings({
dhcp: "on",
dhcp_start: network[ :dhcp_start ],
dhcp_end: network[ :dhcp_end ],
dhcp_router: network[ :gateway ],
dhcp_subnet: self.class.mesh_netmask,
lease_time: 1800
})
radio.dhcp_on_mesh( false )
end
radio.batch do
self.update_mesh_settings( radio )
end
result[ :success ] << radio
rescue Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout => err
self.log.error "Unable to talk to radio %p, skipping" % [ radio.label ]
result[ :failure ][ radio ] = err
end
end
return result
end
Protected Instance Methods
Get all the network specifics necessary for configuration.
def calculate_next_network
virtual_network = @next_network || self.class.virtual_network
netrange = virtual_network.to_range.lazy
netrangerev = netrange.reverse_each
_, gateway = netrange.next, netrange.next
broadcast = netrangerev.next
dhcp_start = netrange.next
dhcp_end = netrangerev.next
@next_network = IPAddr.new( "%s/%s" % [
virtual_network.to_range.end.succ, virtual_network.netmask
] )
return [ gateway, dhcp_start, dhcp_end ].map( &:to_s )
end
Generate a reproducable mesh encryption key for the current radio set. Truncate at 32 characters – the web interface claims there’s a limit.
def make_encryption_key
return Digest::SHA256.hexdigest( 'ravn-' + self.radio_hash )[0..31]
end
Generate a reproducable mesh network name for the current radio set.
def make_network_name
return 'ravn-' + self.radio_hash[ 0..5 ]
end
Generate a Silvus nodeid for the given ip
address.
def nodeid( ip )
oct3, oct4 = ip.to_s.split( '.' )[2, 3]
id = ( oct3.to_i * 256 ) + oct4.to_i
return id <= 65535 ? id + 4 * 65536 : id
end
Return the sum of all nodes identifiers in this radio set. Note, this does not use the radio API.
def nodeids_sum
ips = self.class.radios.map( &:ip )
return ips.map{|ip| self.nodeid(ip) }.sum
end
Return a sha256 for the current radio set.
def radio_hash
return @radio_hash if @radio_hash
@radio_hash = Digest::SHA256.hexdigest( self.nodeids_sum.to_s )
return @radio_hash
end
update_mesh_settings( radio )
Update all settings specific to the mesh network.
def update_mesh_settings( radio )
radio.freq_bandwidth( self.class.frequency, self.class.bandwidth )
radio.distance( self.class.distance )
radio.routing_protocol( self.class.routing_protocol )
radio.mesh_network( self.network_name )
key = self.encryption_key
hmac = Digest::SHA256.hexdigest( key )
wrap = Digest::SHA256.hexdigest( hmac )
case self.class.encryption_algorithm
when :des
radio.encryption_algorithm( :des_ecb, 64 )
radio.encryption_hmac_key( hmac )
radio.encryption_key( self.encryption_key )
radio.encryption_wrap_key( wrap )
when :aes
uni = Digest::SHA256.hexdigest( wrap )
bcast = Digest::SHA256.hexdigest( uni )
radio.encryption_algorithm( :aes_gcm )
radio.encryption_key( key )
radio.encryption_hmac_key( hmac )
radio.encryption_wrap_key( wrap )
radio.encryption_rfauth_key( uni )
radio.encryption_rfbcast_key( bcast )
else
raise "Unknown encryption type: %p" % [ self.class.encryption_algorithm ]
end
radio.encryption( true )
end