class Uplinks

Public Class Methods

new(config) click to toggle source
# File lib/fault_tolerant_router/uplinks.rb, line 4
def initialize(config)
  @uplinks = config.each_with_index.map { |uplink, i| Uplink.new(uplink, i) }
  @uplinks.each { |uplink| uplink.rule_priority_2 = BASE_PRIORITY + @uplinks.size + uplink.id }
  @default_route_table = @uplinks.map { |uplink| uplink.table }.max + 1
end

Public Instance Methods

all_priority_group_members_down?() click to toggle source
# File lib/fault_tolerant_router/uplinks.rb, line 138
def all_priority_group_members_down?
  @uplinks.all? { |uplink| !uplink.priority_group || !uplink.up }
end
each() { |uplink| ... } click to toggle source
# File lib/fault_tolerant_router/uplinks.rb, line 10
def each
  @uplinks.each { |uplink| yield uplink }
end
initialize_routing!() click to toggle source
# File lib/fault_tolerant_router/uplinks.rb, line 14
def initialize_routing!
  commands = []
  rule_priorities = @uplinks.map { |uplink| [uplink.rule_priority_1, uplink.rule_priority_2] }.flatten.minmax
  tables = @uplinks.map { |uplink| uplink.table }.minmax

  #enable IP forwarding
  commands << 'echo 1 > /proc/sys/net/ipv4/ip_forward'

  #clean all previous configurations, try to clean more than needed (double) to avoid problems in case of changes in the
  #number of uplinks between different executions
  ((rule_priorities.max - rule_priorities.min + 2) * 2).times { |i| commands << "ip rule del priority #{rule_priorities.min + i} &> /dev/null" }
  ((tables.max - tables.min + 2) * 2).times { |i| commands << "ip route del table #{tables.min + i} &> /dev/null" }

  #disable "reverse path filtering" on the uplink interfaces
  commands << 'echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter'
  commands += @uplinks.map { |uplink| "echo 2 > /proc/sys/net/ipv4/conf/#{uplink.interface}/rp_filter" }

  #set uplinks routes
  commands += @uplinks.map { |uplink| uplink.route_add_commands }

  #rule for first packet of outbound connections
  commands << "ip rule add priority #{rule_priorities.max + 1} from all lookup #{tables.max + 1}"

  #set default route
  commands += update_default_route!

  #apply the routing changes
  commands << 'ip route flush cache'

  commands.flatten
end
test!() click to toggle source
# File lib/fault_tolerant_router/uplinks.rb, line 96
def test!
  commands = []
  messages = []
  @uplinks.each do |uplink|
    c = uplink.test!
    commands += c
  end

  commands += update_default_route!

  #apply the routing changes, in any
  commands << 'ip route flush cache' if commands.any?

  changes = false
  @uplinks.each do |uplink|
    current = uplink.ip || 'none'
    previous = uplink.previous_ip || 'none'
    changes ||= current != previous
    ip = current == previous ? current : "#{previous} --> #{current}"

    current = uplink.gateway || 'none'
    previous = uplink.previous_gateway || 'none'
    changes ||= current != previous
    gateway = current == previous ? current : "#{previous} --> #{current}"

    current = uplink.up ? 'up' : 'down'
    previous = uplink.previously_up ? 'up' : 'down'
    changes ||= current != previous
    up = current == previous ? current : "#{previous} --> #{current}"

    current = uplink.default_route ? 'routing' : 'standby'
    previous = uplink.previously_default_route ? 'routing' : 'standby'
    changes ||= current != previous
    default_route = current == previous ? current : "#{previous} --> #{current}"

    messages << "Uplink #{uplink.description}: ip #{ip}, gateway #{gateway}, #{up}, #{default_route}"
  end
  messages = [] unless changes

  [commands, messages]
end
update_default_route!() click to toggle source
# File lib/fault_tolerant_router/uplinks.rb, line 46
def update_default_route!
  #select uplinks that are up and with a specified priority group value
  selected = @uplinks.find_all { |uplink| uplink.up && uplink.priority_group }
  puts "Choosing default route: available uplinks: #{selected.map { |uplink| uplink.description }.join(', ')}" if DEBUG

  #restrict the selection to the members of highest priority group
  highest_available_priority = selected.map { |uplink| uplink.priority_group }.min
  selected = selected.find_all { |uplink| uplink.priority_group == highest_available_priority }
  puts "Choosing default route: highest priority group uplinks: #{selected.map { |uplink| uplink.description }.join(', ')}" if DEBUG

  changes = false
  #assign default route status to the uplinks and detect changes from previous configuration
  @uplinks.each do |uplink|
    uplink.previously_default_route = uplink.default_route
    uplink.default_route = selected.include?(uplink)
    changes ||= uplink.default_route != uplink.previously_default_route
  end

  #check if any default route uplink changed its gateway (for example due to a ppp update)
  if @uplinks.any? { |uplink| uplink.default_route && uplink.gateway != uplink.previous_gateway }
    changes ||= true
    puts "Choosing default route: detected gateway change in a default route uplink" if DEBUG
  end

  commands = []
  if selected.size == 0
    puts 'Choosing default route: no available uplinks, no need for an update' if DEBUG
  elsif !changes
    puts 'Choosing default route: no changes, no need for an update' if DEBUG
  else
    puts 'Choosing default route: changes detected, update needed' if DEBUG
    #do not use balancing if there is just one routing uplink
    if selected.size == 1
      nexthops = "via #{selected.first.gateway}"
    else
      nexthops = selected.map do |uplink|
        #the "weight" parameter is optional
        tail = uplink.weight ? " weight #{uplink.weight}" : ''
        "nexthop via #{uplink.gateway}#{tail}"
      end
      nexthops = nexthops.join(' ')
    end

    #set the route for first packet of outbound connections
    commands << "ip route replace table #{@default_route_table} default #{nexthops}"
  end

  commands
end