1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-01-08 21:17:43 +03:00

M #-: Redesign of map vnets mapping script (#4461)

New design of vnets mapping script based on 'open3' instead of directly
command execution.
Added option to select which private and public networks are going to be
used to perform the mapping.

Signed-off-by: Ricardo Diaz <rdiaz@opennebula.systems>
This commit is contained in:
Ricardo Diaz 2020-04-01 13:31:49 +02:00 committed by GitHub
parent 1452f59f5c
commit e239bfc0d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 163 additions and 65 deletions

View File

@ -17,6 +17,6 @@ map_vnets_script_src="$MOUNT_DIR/${script_name}"
cp "${map_vnets_script_src}" "${map_vnets_script_dst}"
chmod +x "${map_vnets_script_dst}"
(crontab -l ; echo "*/1 * * * * ${map_vnets_script_dst}") | crontab -
(crontab -l ; echo "*/1 * * * * ${map_vnets_script_dst} > /var/log/${script_name}.log 2>&1") | crontab -
exit 0

View File

@ -16,91 +16,189 @@
# limitations under the License. #
#--------------------------------------------------------------------------- #
MAP_VNETS_START_SCRIPT_LOGFILE = '/var/log/map_vnets_start_script.log'
IPTABLES_NAT_PREFIX = 'iptables -tnat'
CHAIN_VROUTER_SNAT = 'chain-vrouter-snat'
CHAIN_VROUTER_DNAT = 'chain-vrouter-dnat'
require 'getoptlong'
require 'ipaddr'
require 'json'
require 'logger'
require 'open3'
require 'tempfile'
require 'yaml'
log = Logger.new(MAP_VNETS_START_SCRIPT_LOGFILE.to_s, 'daily')
log.level = Logger::INFO
CHAINS = {
'PREROUTING' => 'chain-onevnetmap-dnat',
'POSTROUTING' => 'chain-onevnetmap-snat'
}
log.info 'map_vnets_start_script executed'
# Read the configuration file
def read_configuration_file(cfile)
begin
YAML.safe_load(File.read(cfile))
rescue StandardError => e
puts "Error: #{e}"
exit(-1)
end
end
service = JSON.parse(`onegate service show --json --extended`)
log.debug "Service: #{service}"
# Get a list of NIC/NIC_ALIAS hashes as long as NIC_ALIAS belongs to
# public network and NIC belongs to private network
def do_ip_mapping(service, private_ip = '0.0.0.0/0', public_ip = '0.0.0.0/0')
private_network = IPAddr.new private_ip
public_network = IPAddr.new public_ip
sdnats = []
retval = []
roles = service['SERVICE']['roles'].flatten
roles.each do |role|
next unless role['nodes']
roles = service['SERVICE']['roles'].flatten
role['nodes'].each do |node|
nic_aliases = [node['vm_info']['VM']['TEMPLATE']['NIC_ALIAS']].flatten
next unless nic_aliases.any?
roles.each do |role|
next unless role['nodes']
nics = [node['vm_info']['VM']['TEMPLATE']['NIC']].flatten
nic_aliases.each do |nic_alias|
nic = nics.detect {|n| n['NAME'] == nic_alias['PARENT'] }
sdnats << { 'NIC' => nic['IP'], 'NIC_ALIAS' => nic_alias['IP'] }
role['nodes'].each do |node|
nic_aliases =
[node['vm_info']['VM']['TEMPLATE']['NIC_ALIAS']].flatten
next unless nic_aliases.any?
nics = [node['vm_info']['VM']['TEMPLATE']['NIC']].flatten
nic_aliases.each do |nic_alias|
next unless public_network.include?(nic_alias['IP'])
nic = nics.detect {|n| n['NAME'] == nic_alias['PARENT'] }
next unless private_network.include?(nic['IP'])
retval << { 'NIC' => nic['IP'], 'NIC_ALIAS' => nic_alias['IP'] }
end
end
end
retval
end
log.debug "IPs: #{sdnats}"
# Get iptables rules to apply for NAT table if no NIC/NIC_ALIAS detected
def iptables_tnat_apply_init
rules = ''
rules = ''
CHAINS.each do |nat_chain, custom_chain|
o, _, rc = Open3.capture3("iptables -tnat -S #{custom_chain}")
begin
f = Tempfile.new
if rc.exitstatus != 0
# The chain does not exist, add rules to create it and add it to the
# corresponding table NAT chain
rules += "-N #{custom_chain}\n"
rules += "-A #{nat_chain} -j #{custom_chain}\n"
else
o.each_line do |r|
next if r.include?("-N #{custom_chain}")
f << `iptables -tnat -S #{CHAIN_VROUTER_DNAT} >/dev/null 2>&1 ||\
echo "-N #{CHAIN_VROUTER_DNAT}"`
f << `iptables -tnat -S #{CHAIN_VROUTER_SNAT} >/dev/null 2>&1 ||\
echo "-N #{CHAIN_VROUTER_SNAT}"`
f << `iptables -tnat -C PREROUTING -j #{CHAIN_VROUTER_DNAT} 2>/dev/null ||\
echo "-A PREROUTING -j #{CHAIN_VROUTER_DNAT}"`
f << `iptables -tnat -C POSTROUTING -j #{CHAIN_VROUTER_SNAT} 2>/dev/null ||\
echo "-A POSTROUTING -j #{CHAIN_VROUTER_SNAT}"`
f << `iptables -t nat -S #{CHAIN_VROUTER_DNAT} 2>/dev/null |\
sed -n 's/-A\\(.*\\)/-D\\1/p'`
f << `iptables -t nat -S #{CHAIN_VROUTER_SNAT} 2>/dev/null |\
sed -n 's/-A\\(.*\\)/-D\\1/p'`
f.close
sdnats.each do |nat|
`iptables -tnat -C #{CHAIN_VROUTER_DNAT} -d #{nat['NIC_ALIAS']} -j DNAT\
--to-destination #{nat['NIC']} 2>/dev/null &&\
sed -i '/.*#{CHAIN_VROUTER_DNAT} -d #{nat['NIC_ALIAS']}\\/32 -j DNAT \
--to-destination #{nat['NIC']}/d' #{f.path} ||\
echo '-A #{CHAIN_VROUTER_DNAT} -d #{nat['NIC_ALIAS']} -j DNAT \
--to-destination #{nat['NIC']}' >> #{f.path}`
`iptables -tnat -C #{CHAIN_VROUTER_SNAT} -s #{nat['NIC']} -j SNAT \
--to-source #{nat['NIC_ALIAS']} 2>/dev/null &&\
sed -i '/.*#{CHAIN_VROUTER_SNAT} -s #{nat['NIC']}\\/32 -j SNAT \
--to-source #{nat['NIC_ALIAS']}/d' #{f.path}||\
echo '-A #{CHAIN_VROUTER_SNAT} -s #{nat['NIC']} -j SNAT \
--to-source #{nat['NIC_ALIAS']}' >> #{f.path}`
# The chain does exist, add all rules belonging to the chain and
# mark them to be deleted initially
rules += r.gsub(/-A (.*)/, '-D \1')
end
end
end
rules << `cat #{f.path}`
ensure
f.unlink
rules
end
log.debug "Rules: #{rules}"
# Merge intial iptables rules to apply for NAT with the ones needed by
# NIC/NIC_ALIAS mapping
def iptables_tnat_apply_merge(rules, nics_maps)
nics_maps.each do |nat|
# DNAT rule
jdnat = "#{CHAINS['PREROUTING']} -d #{nat['NIC_ALIAS']}/32 -j DNAT \
--to-destination #{nat['NIC']}"
# Try to delete -D DNAT rule which means previously NIC_ALIAS still
# attached
if !rules.gsub!(/-D #{jdnat}\n/, '')
# Add -A rule if not DNAT rule found which means new NIC_ALIAS
# has been attached
rules += "-A #{jdnat}\n"
end
rules.each_line do |rule|
`iptables -tnat #{rule}`
# DNAT rule
jsnat = "#{CHAINS['POSTROUTING']} -s #{nat['NIC']}/32 -j SNAT \
--to-source #{nat['NIC_ALIAS']}"
# Try to delete -D SNAT rule which means previously NIC_ALIAS still
# attached
next if rules.gsub!(/-D #{jsnat}\n/, '')
# Add -A rule if not SNAT rule found which means new NIC_ALIAS
# has been attached
rules += "-A #{jsnat}\n"
end
rules
end
log.debug "iptables-save: #{`iptables-save`}"
def usage
puts <<~EOT
#{File.basename($PROGRAM_NAME)} [-f|--configuration-file CONFIG_FILE] [-h|--help]
-f, --configuration-file=CONFIG_FILE use CONFIG_FILE in YAML format like this:
networks:
public: IP/MASK
private: IP/MASK
Only NIC_ALIASes and NICs belonging to
public and private networks respectively
will be used.
EOT
STDOUT.flush
end
# Parse arguments
opts = GetoptLong.new(
['--configuration-file', '-f', GetoptLong::REQUIRED_ARGUMENT],
['--help', '-h', GetoptLong::NO_ARGUMENT]
)
# Configuration file path
cfile = ''
begin
opts.each do |opt, arg|
case opt
when '--configuration-file'
cfile = arg
when '--help'
usage
exit(0)
end
end
rescue StandardError
exit(-1)
end
# Read configuration
config = read_configuration_file(cfile) unless cfile.empty?
# Get service info through OneGate
o, e, rc = Open3.capture3('onegate service show --json --extended')
if rc.exitstatus != 0
STDERR.puts "Error: #{o}"
exit(-1)
end
service = JSON.parse(o)
# Get IP network addresses of private and public networks
# If no network configuration passed each NIC_ALIAS found will be mapped to the
# parent NIC
private_ip = '0.0.0.0/0'
public_ip = '0.0.0.0/0'
if !config.nil?
private_ip = config['networks']['private']
public_ip = config['networks']['public']
end
# Perform the NIC_ALIAS/NIC mapping
nics_maps = do_ip_mapping(service, private_ip, public_ip)
# Get initial iptables rules required as if there no NIC_ALIAS/NIC mappings
rules_pre = iptables_tnat_apply_init
# Modify initial iptables rules with NIC_ALIAS/NIC mappings
rules_post = iptables_tnat_apply_merge(rules_pre, nics_maps)
# Apply the inferred iptables rules
rules_post.each_line do |rule|
o, e, rc = Open3.capture3("iptables -tnat #{rule}")
if rc.exitstatus != 0
STDERR.puts e
end
end