class Object
Public Instance Methods
command(input)
click to toggle source
# File lib/fault_tolerant_router/monitor.rb, line 1 def command(input) input = [input] if input.is_a?(String) input.each do |c| `#{c}` unless DEMO puts "Command: #{c}" if DEBUG end end
generate_config(file_path)
click to toggle source
# File lib/fault_tolerant_router/generate_config.rb, line 1 def generate_config(file_path) if File.exist?(file_path) puts "Configuration file #{file_path} already exists, will not overwrite!" exit 1 end begin open(file_path, 'w') do |file| file.puts <<END #see https://github.com/drsound/fault_tolerant_router for a complete parameter #description #add as many uplinks as needed, in this example ppp0 is used as default route only if both eth1 and eth2 are down uplinks: - interface: eth1 type: static ip: 1.0.0.2 gateway: 1.0.0.1 description: Example Provider 1 priority_group: 1 #optional parameter weight: 1 - interface: eth2 type: static ip: 2.0.0.2 gateway: 2.0.0.1 description: Example Provider 2 priority_group: 1 #optional parameter weight: 2 - interface: ppp0 type: ppp description: Example Provider 3 priority_group: 2 #optional parameter weight: 1 downlinks: lan: eth0 #leave blank if you have no DMZ dmz: tests: #an array of IP addresses to ping to verify the uplinks state. You can add as #many as you wish. Predefined ones are Google DNS, OpenDNS DNS, other public #DNS. Every time an uplink is tested the IP addresses are shuffled, so listing #order is not important. ips: - 8.8.8.8 - 8.8.4.4 - 208.67.222.222 - 208.67.220.220 - 4.2.2.2 - 4.2.2.3 #number of successfully pinged IP addresses to consider an uplink to be #functional required_successful: 4 #number of ping retries before giving up on an IP ping_retries: 1 #seconds between a check of the uplinks and the next one interval: 60 log: #file: "/var/log/fault_tolerant_router.log" file: "/tmp/fault_tolerant_router.log" #maximum log file size (in bytes). Once reached this size, the log file will #be rotated. max_size: 1024000 #number of old rotated files to keep old_files: 10 email: send: false sender: router@domain.com recipients: - user1@domain.com - user2@domain.com - user3@domain.com #see http://ruby-doc.org/stdlib-2.3.1/libdoc/net/smtp/rdoc/Net/SMTP.html smtp_parameters: address: smtp.gmail.com port: 587 #domain: domain.com authentication: :login enable_starttls_auto: true user_name: user@gmail.com password: secret-password #base IP route table number, just need to change if you are already using #multiple routing tables base_table: 1 #just need to change if you are already using ip policy routing, to avoid #overlapping, must be higher than 32767 (the default routing table priority, #see output of "ip rule" command) base_priority: 40000 #just need to change if you are already using packet marking, to avoid #overlapping base_fwmark: 1 END end puts "Example configuration saved to #{file_path}" rescue puts "Error while saving configuration file #{file_path}!" exit 1 end end
generate_iptables()
click to toggle source
# File lib/fault_tolerant_router/generate_iptables.rb, line 1 def generate_iptables puts <<END #Integrate with your existing "iptables-save" configuration, or adapt to work #with any other iptables configuration system *mangle :PREROUTING ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] :OUTPUT ACCEPT [0:0] :INPUT ACCEPT [0:0] #New outbound connections: force a connection to use a specific uplink instead #of participating in the multipath routing. This can be useful if you have an #SMTP server that should always send emails originating from a specific IP #address (because of PTR DNS records), or if you have some service that you want #always to use a particular slow/fast uplink. # #Uncomment if needed. # #NB: these are just examples, you can add as many options as needed: -s, -d, # --sport, etc. END UPLINKS.each do |uplink| puts "##{uplink.description}" puts "#[0:0] -A PREROUTING -i #{LAN_INTERFACE} -m conntrack --ctstate NEW -p tcp --dport XXX -j CONNMARK --set-mark #{uplink.fwmark}" puts "#[0:0] -A PREROUTING -i #{DMZ_INTERFACE} -m conntrack --ctstate NEW -p tcp --dport XXX -j CONNMARK --set-mark #{uplink.fwmark}" if DMZ_INTERFACE end puts <<END #Mark packets with the outgoing interface: # #- Established outbound connections: mark non-first packets (first packet will # be marked as 0, as a standard unmerked packet, because the connection has not # yet been marked with CONNMARK --set-mark) # #- New outbound connections: mark first packet, only effective if marking has # been done in the section above # #- Inbound connections: mark returning packets (from LAN/DMZ to WAN) [0:0] -A PREROUTING -i #{LAN_INTERFACE} -j CONNMARK --restore-mark END puts "[0:0] -A PREROUTING -i #{DMZ_INTERFACE} -j CONNMARK --restore-mark" if DMZ_INTERFACE puts <<END #New inbound connections: mark the connection with the incoming interface. END UPLINKS.each do |uplink| puts "##{uplink.description}" puts "[0:0] -A PREROUTING -i #{uplink.interface} -m conntrack --ctstate NEW -j CONNMARK --set-mark #{uplink.fwmark}" end puts <<END #New outbound connections: mark the connection with the outgoing interface #(chosen by the multipath routing). END UPLINKS.each do |uplink| puts "##{uplink.description}" puts "[0:0] -A POSTROUTING -o #{uplink.interface} -m conntrack --ctstate NEW -j CONNMARK --set-mark #{uplink.fwmark}" end puts <<END COMMIT *nat :PREROUTING ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] :OUTPUT ACCEPT [0:0] #DNAT: WAN --> LAN/DMZ. The original destination IP (-d) can be any of the IP #addresses assigned to the uplink interface. XXX.XXX.XXX.XXX can be any of your #LAN/DMZ IPs. # #Uncomment if needed. # #NB: these are just examples, you can add as many options as you wish: -s, # --sport, --dport, etc. END UPLINKS.each do |uplink| puts "##{uplink.description}" if uplink.type == :ppp puts "#[0:0] -A PREROUTING -i #{uplink.interface} -j DNAT --to-destination XXX.XXX.XXX.XXX" else puts "#[0:0] -A PREROUTING -i #{uplink.interface} -d #{uplink.ip} -j DNAT --to-destination XXX.XXX.XXX.XXX" end end puts <<END #SNAT: LAN/DMZ --> WAN. Force an outgoing connection to use a specific source #address instead of the default one of the outgoing interface. Of course this #only makes sense if more than one IP address is assigned to the uplink #interface. # #Uncomment if needed. # #NB: these are just examples, you can add as many options as needed: -d, # --sport, --dport, etc. END UPLINKS.each do |uplink| puts "##{uplink.description}" puts "#[0:0] -A POSTROUTING -s XXX.XXX.XXX.XXX -o #{uplink.interface} -j SNAT --to-source YYY.YYY.YYY.YYY" end puts <<END #SNAT: LAN --> WAN END UPLINKS.each do |uplink| puts "##{uplink.description}" if uplink.type == :ppp puts "[0:0] -A POSTROUTING -o #{uplink.interface} -j MASQUERADE" else puts "[0:0] -A POSTROUTING -o #{uplink.interface} -j SNAT --to-source #{uplink.ip}" end end puts <<END COMMIT *filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] :LAN_WAN - [0:0] :WAN_LAN - [0:0] END if DMZ_INTERFACE puts ':DMZ_WAN - [0:0]' puts ':WAN_DMZ - [0:0]' end puts <<END #This is just a very basic example, add your own rules for the FORWARD chain. [0:0] -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT END UPLINKS.each do |uplink| puts "[0:0] -A FORWARD -i #{LAN_INTERFACE} -o #{uplink.interface} -j LAN_WAN" end UPLINKS.each do |uplink| puts "[0:0] -A FORWARD -i #{uplink.interface} -o #{LAN_INTERFACE} -j WAN_LAN" end if DMZ_INTERFACE UPLINKS.each do |uplink| puts "[0:0] -A FORWARD -i #{DMZ_INTERFACE} -o #{uplink.interface} -j DMZ_WAN" end UPLINKS.each do |uplink| puts "[0:0] -A FORWARD -i #{uplink.interface} -o #{DMZ_INTERFACE} -j WAN_DMZ" end end puts <<END [0:0] -A LAN_WAN -j ACCEPT [0:0] -A WAN_LAN -j REJECT END if DMZ_INTERFACE puts '[0:0] -A DMZ_WAN -j ACCEPT' puts '[0:0] -A WAN_DMZ -j ACCEPT' end puts <<END COMMIT END end
log(logger, messages)
click to toggle source
# File lib/fault_tolerant_router/monitor.rb, line 9 def log(logger, messages) logger.warn(messages.join('; ')) if messages.any? end
monitor()
click to toggle source
# File lib/fault_tolerant_router/monitor.rb, line 23 def monitor logger = Logger.new(LOG_FILE, LOG_OLD_FILES, LOG_MAX_SIZE) command(UPLINKS.initialize_routing!) list = UPLINKS.select { |uplink| uplink.default_route }.map { |uplink| uplink.description }.join(', ') log(logger, ["Monitor started, initial default route uplinks: #{list}"]) loop do commands, messages = UPLINKS.test! command(commands) log(logger, messages) if SEND_EMAIL && messages.any? begin send_email(messages.join("\n")) rescue Exception => e puts "Problem sending email: #{e}" if DEBUG logger.error("Problem sending email: #{e}") end end if UPLINKS.all_priority_group_members_down? puts 'No waiting, because all of the priority group members are down' if DEBUG elsif DEMO puts "Waiting just 5 seconds because in demo mode, otherwise would be #{TEST_INTERVAL} seconds..." if DEBUG sleep 5 else puts "Waiting #{TEST_INTERVAL} seconds..." if DEBUG sleep TEST_INTERVAL end end end
send_email(body)
click to toggle source
# File lib/fault_tolerant_router/monitor.rb, line 13 def send_email(body) mail = Mail.new mail.from = EMAIL_SENDER mail.to = EMAIL_RECIPIENTS mail.subject = 'Uplinks status change' mail.body = body mail.delivery_method :smtp, SMTP_PARAMETERS mail.deliver end