From 3a5d8d3a92b84addb9560a5cb99703357e916ed2 Mon Sep 17 00:00:00 2001 From: Jaime Melis Date: Thu, 12 Jul 2012 19:08:50 +0200 Subject: [PATCH 1/4] Bug #892: Add an untested draft of the router init.rb --- share/router/init.rb | 374 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 share/router/init.rb diff --git a/share/router/init.rb b/share/router/init.rb new file mode 100644 index 0000000000..6ba355e782 --- /dev/null +++ b/share/router/init.rb @@ -0,0 +1,374 @@ +#!/usr/bin/env ruby + +require 'rexml/document' +require 'base64' + +class Router + # Default files + FILES = { + :resolv_conf => "/etc/resolv.conf", + :context => "/mnt/context/context.sh", + :dnsmasq_conf => "/etc/dnsmasq.conf" + } + + # Default MAC prefix + MAC_PREFIX = "02:00" + + # Default gateway (last byte) + DEFAULT_GW = "1" + + # Default netmask + DEFAULT_NETMASK = "255.255.255.0" + + # Context parameters that are base64 encoded + BASE_64_KEYS = [:privnet, :pubnet, :template] + + # The specification on how to fetch these attributes. + # The order in the array matters, the first non-empty one is returned. + ATTRIBUTES = { + :dns => [ + { + :resource => :context + }, + { + :resource => :pubnet, + :resource_xpath => 'TEMPLATE/DNS' + } + ], + :search => [ + { + :resource => :context + }, + { + :resource => :pubnet, + :resource_xpath => 'TEMPLATE/SEARCH' + } + ], + :gateway => [ + { + :resource => :context + }, + { + :resource => :pubnet, + :resource_xpath => 'TEMPLATE/GATEWAY' + } + ], + :netmask_pub => [ + { + :resource => :context + }, + { + :resource => :pubnet, + :resource_xpath => 'TEMPLATE/NETWORK_MASK' + } + ], + :netmask_priv => [ + { + :resource => :context + }, + { + :resource => :privnet, + :resource_xpath => 'TEMPLATE/NETWORK_MASK' + } + ], + :nat => [ + { + :resource => :context + }, + { + :resource => :pubnet, + :resource_xpath => 'TEMPLATE/NAT' + } + ], + :dhcp => [ + { + :resource => :context + }, + { + :resource => :privnet, + :resource_xpath => 'TEMPLATE/DHCP' + } + ] + } + + def initialize + @context = read_context + unpack + end + + ############################################################################ + # GETTERS + ############################################################################ + + def dns + dns_raw = get_attribute(:dns) + dns_raw.split if !dns_raw.nil? + end + + def gateway + get_attribute(:gateway) + end + + def search + get_attribute(:search) + end + + def netmask(nic) + if nic == "eth0" + resource = :netmask_pub + elsif nic == "eth1" + resource = :netmask_priv + end + + get_attribute(resource) || DEFAULT_NETMASK + end + + def nat + nat_raw = get_attribute(:nat) + nat_raw.split if !nat_raw.nil? + end + + def dhcp + dhcp_raw = get_attribute(:dhcp) + if dhcp_raw.downcase.match(/^y(es)?$/) + true + else + false + end + end + + def mac_ip(nic) + mac = File.read("/sys/class/net/#{nic}/address") + ip = mac2ip(mac) + + [mac, ip] + end + + ############################################################################ + # ACTIONS + ############################################################################ + + def write_resolv_conf + if dns + File.open(FILE[:resolv_conf], 'w') do |resolv_conf| + if search + resolv_conf.puts "search #{search}" + end + + dns.each do |nameserver| + resolv_conf.puts "nameserver #{nameserver}" + end + end + elsif search + File.open(FILE[:resolv_conf], 'a') do |resolv_conf| + resolv_conf.puts "search #{search}" + end + end + end + + def set_network(nic) + mac, ip = mac_ip(nic) + network_mask = netmask(nic) + + run "ip link set #{nic} up" + run "ip addr add #{ip}/#{network_mask} dev #{nic}" + + if nic == "eth0" + if gateway + gw = gateway + else + gw = ip.gsub(/\.\d{1,3}$/,".#{DEFAULT_GW}") + end + + run "ip route add default via #{gw}" + end + end + + def generate_dnsmasq_conf + File.open(FILES[:dnsmasq_conf],'w') do |dnsmasq_conf| + _,ip_start,_ = dhcp_ip_mac_pairs[0] + _,ip_end,_ = dhcp_ip_mac_pairs[-1] + + mac, ip = mac_ip("eth1") + + dnsmasq_conf.puts "dhcp-range=#{ip_start},#{ip_end},infinite" + + dnsmasq_conf.puts "dhcp-option=42,#{ip} # ntp server" + dnsmasq_conf.puts "dhcp-option=4,#{ip} # name server" + + dhcp_ip_mac_pairs.each do |pair| + mac, ip = pair + dnsmasq_conf.puts "dhcp-host=#{mac},#{ip}" + end + end + end + + def enable_dhcp + start_service "dnsmasq" + end + + def set_nat_rules + nat.each do |nat_rule| + nat_rule = nat_rule.split(":") + if nat_rule.length == 2 + ip, inport = nat_rule + outport = inport + elsif nat_rule.length == 3 + outport, ip, inport = nat_rule + end + + run "iptables -t nat -A PREROUTING -p tcp --dport #{outport} " \ + "-j DNAT --to-destination #{ip}:#{inport}" + end + end + + def set_masquerade + run "iptables -A POSTROUTING -o eth0 -j MASQUERADE" + end + + ############################################################################ + # Private methods + ############################################################################ + +private + + def start_service(service) + run "/etc/rc.d/#{service} start" + end + + def run(cmd) + puts(cmd) + end + + def dhcp_ip_mac_pairs + netxml = @xml[:privnet] + + pairs = Array.new + + if netxml.elements['RANGE'] + ip_start = netxml.elements['RANGE/IP_START'].text + ip_end = netxml.elements['RANGE/IP_END'].text + + (ip_to_int(ip_start)..ip_to_int(ip_end)).each do |int_ip| + ip = int_to_ip(int_ip) + mac = ip2mac(ip) + + pairs << [mac, ip, int_ip] + end + elsif netxml.elements['LEASES/LEASE'] + netxml.elements.each('LEASES/LEASE') do |lease| + ip = lease.elements['IP'].text + mac = lease.elements['MAC'].text + int_ip = ip_to_int(ip) + + pairs << [mac, ip, int_ip] + end + end + + pairs.sort{|a,b| a[2] <=> b[2]} + end + + def ip_to_int(ip) + num = 0 + ip.split(".").each{|i| num *= 256; num = num + i.to_i} + num + end + + def int_to_ip(num) + ip = Array.new + + (0..3).reverse_each do |i| + ip << (((num>>(8*i)) & 0xff)) + end + ip.join('.') + end + + def ip2mac(ip) + mac = MAC_PREFIX + ':' \ + + ip.split('.').collect{|i| sprintf("%02X",i)}.join(':') + + mac.downcase + end + + def mac2ip(mac) + mac.split(':')[2..-1].collect{|i| i.to_i(16)}.join('.') + end + + def unpack + @xml = Hash.new + + BASE_64_KEYS.each do |key| + if @context.include? key + tpl = Base64::decode64(@context[key]) + @xml[key] = REXML::Document.new(tpl).root + end + end + end + + def get_attribute(name) + order = ATTRIBUTES[name] + + order.each do |e| + if e[:resource] != :context + resource = e[:resource] + xpath = e[:resource_xpath] + + element = @xml[resource].elements[xpath] + return element.text.to_s if !element.nil? + else + if e[:resource_name] + resource_name = e[:resource_name] + else + resource_name = name + end + + element = @context[resource_name] + return element if !element.nil? + end + end + + return nil + end + + def read_context + context = Hash.new + context_file = File.read(FILES[:context]) + + context_file.each_line do |line| + next if line.match(/^#/) + + if (m = line.match(/^(.*?)="(.*)"$/)) + key = m[1].downcase.to_sym + value = m[2] + context[key] = value + end + end + context + end +end + +router = Router.new + +if router.dns || router.search + router.write_resolv_conf +end + +# Public Network +router.set_network("eth0") + +# Private Network +router.set_network("eth1") + +# Generate DHCP +if router.dhcp + router.generate_dnsmasq_conf + router.enable_dhcp +end + +# # Set masquerade +router.set_masquerade + +# # Set NAT rules +if router.nat + router.set_nat_rules +end From c0f0ac9f5484318a54a4362dce4733b00cb7d609 Mon Sep 17 00:00:00 2001 From: Jaime Melis Date: Fri, 13 Jul 2012 20:15:01 +0200 Subject: [PATCH 2/4] Feature #892: Fix many things in the router script: - support for ntp - support for a single interface - separate context for private server and router - improved network configuration model --- share/router/init.rb | 292 +++++++++++++++++++++++++++++-------------- 1 file changed, 195 insertions(+), 97 deletions(-) diff --git a/share/router/init.rb b/share/router/init.rb index 6ba355e782..f23ae6f69f 100644 --- a/share/router/init.rb +++ b/share/router/init.rb @@ -2,13 +2,15 @@ require 'rexml/document' require 'base64' +require 'fileutils' class Router # Default files FILES = { :resolv_conf => "/etc/resolv.conf", :context => "/mnt/context/context.sh", - :dnsmasq_conf => "/etc/dnsmasq.conf" + :dnsmasq_conf => "/etc/dnsmasq.conf", + :log_file => "/var/log/router.log" } # Default MAC prefix @@ -44,40 +46,14 @@ class Router :resource_xpath => 'TEMPLATE/SEARCH' } ], - :gateway => [ + :nat => [ { - :resource => :context - }, - { - :resource => :pubnet, - :resource_xpath => 'TEMPLATE/GATEWAY' - } - ], - :netmask_pub => [ - { - :resource => :context - }, - { - :resource => :pubnet, - :resource_xpath => 'TEMPLATE/NETWORK_MASK' - } - ], - :netmask_priv => [ - { - :resource => :context + :resource => :context, + :resource_name => "FORWARDING" }, { :resource => :privnet, - :resource_xpath => 'TEMPLATE/NETWORK_MASK' - } - ], - :nat => [ - { - :resource => :context - }, - { - :resource => :pubnet, - :resource_xpath => 'TEMPLATE/NAT' + :resource_xpath => 'TEMPLATE/FORWARDING' } ], :dhcp => [ @@ -88,39 +64,61 @@ class Router :resource => :privnet, :resource_xpath => 'TEMPLATE/DHCP' } + ], + :ntp => [ + { + :resource => :context + }, + { + :resource => :privnet, + :resource_xpath => 'TEMPLATE/NTP' + } + ], + :root_pubkey => [ + { + :resource => :context + } + ], + :root_password => [ + { + :resource => :context + } ] } def initialize @context = read_context unpack + get_network_information end ############################################################################ # GETTERS ############################################################################ + def pubnet + !@pubnet[:network_id].nil? + end + + def privnet + !@privnet[:network_id].nil? + end + def dns dns_raw = get_attribute(:dns) dns_raw.split if !dns_raw.nil? end - def gateway - get_attribute(:gateway) - end - def search get_attribute(:search) end - def netmask(nic) - if nic == "eth0" - resource = :netmask_pub - elsif nic == "eth1" - resource = :netmask_priv - end + def root_password + get_attribute(:root_password) + end - get_attribute(resource) || DEFAULT_NETMASK + def root_pubkey + get_attribute(:root_pubkey) end def nat @@ -129,7 +127,7 @@ class Router end def dhcp - dhcp_raw = get_attribute(:dhcp) + dhcp_raw = get_attribute(:dhcp) || "" if dhcp_raw.downcase.match(/^y(es)?$/) true else @@ -137,11 +135,13 @@ class Router end end - def mac_ip(nic) - mac = File.read("/sys/class/net/#{nic}/address") - ip = mac2ip(mac) - - [mac, ip] + def ntp + ntp_raw = get_attribute(:ntp) || "" + if ntp_raw.downcase.match(/^y(es)?$/) + true + else + false + end end ############################################################################ @@ -150,7 +150,7 @@ class Router def write_resolv_conf if dns - File.open(FILE[:resolv_conf], 'w') do |resolv_conf| + File.open(FILES[:resolv_conf], 'w') do |resolv_conf| if search resolv_conf.puts "search #{search}" end @@ -166,36 +166,41 @@ class Router end end - def set_network(nic) - mac, ip = mac_ip(nic) - network_mask = netmask(nic) + def configure_network + get_network_information - run "ip link set #{nic} up" - run "ip addr add #{ip}/#{network_mask} dev #{nic}" + if pubnet + ip = @pubnet[:ip] + nic = @pubnet[:interface] + netmask = @pubnet[:netmask] + gateway = @pubnet[:gateway] - if nic == "eth0" - if gateway - gw = gateway - else - gw = ip.gsub(/\.\d{1,3}$/,".#{DEFAULT_GW}") - end + run "ip link set #{nic} up" + run "ip addr add #{ip}/#{netmask} dev #{nic}" + run "ip route add default via #{gateway}" + end - run "ip route add default via #{gw}" + if privnet + ip = @privnet[:ip] + nic = @privnet[:interface] + netmask = @privnet[:netmask] + + run "ip link set #{nic} up" + run "ip addr add #{ip}/#{netmask} dev #{nic}" end end - def generate_dnsmasq_conf + + def configure_dnsmasq File.open(FILES[:dnsmasq_conf],'w') do |dnsmasq_conf| _,ip_start,_ = dhcp_ip_mac_pairs[0] _,ip_end,_ = dhcp_ip_mac_pairs[-1] - mac, ip = mac_ip("eth1") - dnsmasq_conf.puts "dhcp-range=#{ip_start},#{ip_end},infinite" - - dnsmasq_conf.puts "dhcp-option=42,#{ip} # ntp server" - dnsmasq_conf.puts "dhcp-option=4,#{ip} # name server" - + dnsmasq_conf.puts + dnsmasq_conf.puts "dhcp-option=42,#{@privnet[:ip]} # ntp server" + dnsmasq_conf.puts "dhcp-option=4,#{@privnet[:ip]} # name server" + dnsmasq_conf.puts dhcp_ip_mac_pairs.each do |pair| mac, ip = pair dnsmasq_conf.puts "dhcp-host=#{mac},#{ip}" @@ -203,11 +208,7 @@ class Router end end - def enable_dhcp - start_service "dnsmasq" - end - - def set_nat_rules + def configure_nat nat.each do |nat_rule| nat_rule = nat_rule.split(":") if nat_rule.length == 2 @@ -222,8 +223,28 @@ class Router end end - def set_masquerade - run "iptables -A POSTROUTING -o eth0 -j MASQUERADE" + def configure_masquerade + run "iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE" + end + + def configure_ip_forward + run "sysctl -w net.ipv4.ip_forward=1" + end + + def configure_root_password + run "usermod -p #{root_password} root" + end + + def configure_root_pubkey + FileUtils.mkdir_p("/root/.ssh",:mode => 0700) + File.open("/root/.ssh/authorized_keys", "a", 0600) do |f| + f.write(root_pubkey) + end + end + + def service(service, action = :start) + action = action.to_s + run "/etc/rc.d/#{service} #{action}" end ############################################################################ @@ -232,12 +253,63 @@ class Router private - def start_service(service) - run "/etc/rc.d/#{service} start" + def get_network_information + @pubnet = Hash.new + @privnet = Hash.new + + mac_interfaces = Hash[ + Dir["/sys/class/net/*/address"].collect do |f| + [ File.read(f).strip, f.split('/')[4] ] + end + ] + + if (pubnet_id = get_element_xpath(:pubnet, 'ID')) + @pubnet[:network_id] = pubnet_id + + xpath_ip = "TEMPLATE/NIC[NETWORK_ID='#{pubnet_id}']/IP" + xpath_mac = "TEMPLATE/NIC[NETWORK_ID='#{pubnet_id}']/MAC" + + @pubnet[:ip] = get_element_xpath(:template, xpath_ip) + @pubnet[:mac] = get_element_xpath(:template, xpath_mac) + + @pubnet[:interface] = mac_interfaces[@pubnet[:mac]] + + netmask = get_element_xpath(:pubnet, 'TEMPLATE/NETWORK_MASK') + @pubnet[:netmask] = netmask || DEFAULT_NETMASK + + gateway = get_element_xpath(:pubnet, 'TEMPLATE/GATEWAY') + if gateway.nil? + gateway = @pubnet[:ip].gsub(/\.\d{1,3}$/,".#{DEFAULT_GW}") + end + @pubnet[:gateway] = gateway + end + + if (privnet_id = get_element_xpath(:privnet, 'ID')) + @privnet[:network_id] = privnet_id + + xpath_ip = "TEMPLATE/NIC[NETWORK_ID='#{privnet_id}']/IP" + xpath_mac = "TEMPLATE/NIC[NETWORK_ID='#{privnet_id}']/MAC" + + @privnet[:ip] = get_element_xpath(:template, xpath_ip) + @privnet[:mac] = get_element_xpath(:template, xpath_mac) + + @privnet[:interface] = mac_interfaces[@privnet[:mac]] + + netmask = get_element_xpath(:privnet, 'TEMPLATE/NETWORK_MASK') + @privnet[:netmask] = netmask || DEFAULT_NETMASK + end + end + + def log(msg) + File.open(FILES[:log_file],'a') {|f| f.puts msg} end def run(cmd) - puts(cmd) + log(cmd) + output = `#{cmd} 2>&1` + exitstatus = $?.exitstatus + log(output) if !output.empty? + log("Error: exit code #{exitstatus}") if exitstatus != 0 end def dhcp_ip_mac_pairs @@ -308,13 +380,16 @@ private def get_attribute(name) order = ATTRIBUTES[name] + return nil if order.nil? + order.each do |e| if e[:resource] != :context resource = e[:resource] xpath = e[:resource_xpath] - element = @xml[resource].elements[xpath] - return element.text.to_s if !element.nil? + value = get_element_xpath(resource, xpath) + + return value if !value.nil? else if e[:resource_name] resource_name = e[:resource_name] @@ -330,6 +405,14 @@ private return nil end + def get_element_xpath(resource, xpath) + xml_resource = @xml[resource] + return nil if xml_resource.nil? + + element = xml_resource.elements[xpath] + return element.text.to_s if !element.nil? + end + def read_context context = Hash.new context_file = File.read(FILES[:context]) @@ -349,26 +432,41 @@ end router = Router.new -if router.dns || router.search - router.write_resolv_conf +router.configure_network + +if router.pubnet + if router.dns || router.search + router.write_resolv_conf + end + + # Set masquerade + router.configure_masquerade + + # Set ipv4 forward + router.configure_ip_forward + + # Set NAT rules + if router.nat + router.configure_nat + end end -# Public Network -router.set_network("eth0") +if router.privnet + if router.dhcp + router.configure_dnsmasq + router.service("dnsmasq") + end -# Private Network -router.set_network("eth1") - -# Generate DHCP -if router.dhcp - router.generate_dnsmasq_conf - router.enable_dhcp + if router.ntp + router.configure_ntp + router.service("ntpd") + end end -# # Set masquerade -router.set_masquerade - -# # Set NAT rules -if router.nat - router.set_nat_rules +if router.root_password + router.configure_root_password +end + +if router.root_pubkey + router.configure_root_pubkey end From edc7fcf99d165d2fa751ce746e383513b92ae05c Mon Sep 17 00:00:00 2001 From: Jaime Melis Date: Fri, 13 Jul 2012 20:16:54 +0200 Subject: [PATCH 3/4] Feature #892: Add banner --- share/router/init.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/share/router/init.rb b/share/router/init.rb index f23ae6f69f..13dc22ce27 100644 --- a/share/router/init.rb +++ b/share/router/init.rb @@ -1,5 +1,21 @@ #!/usr/bin/env ruby +# -------------------------------------------------------------------------- # +# Copyright 2002-2012, OpenNebula Project Leads (OpenNebula.org) # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +#--------------------------------------------------------------------------- # + require 'rexml/document' require 'base64' require 'fileutils' From 01c087cdc199e32a805dc70eed6e946844033324 Mon Sep 17 00:00:00 2001 From: Jaime Melis Date: Sat, 14 Jul 2012 01:50:12 +0200 Subject: [PATCH 4/4] Feature #892: remove unexisting method --- share/router/init.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/share/router/init.rb b/share/router/init.rb index 13dc22ce27..9d49eaa5fe 100644 --- a/share/router/init.rb +++ b/share/router/init.rb @@ -474,7 +474,6 @@ if router.privnet end if router.ntp - router.configure_ntp router.service("ntpd") end end