1
0
mirror of https://github.com/OpenNebula/one.git synced 2024-12-22 13:33:52 +03:00
one/share/start-scripts/map_vnets_start_script
2022-04-07 19:49:58 +02:00

205 lines
6.6 KiB
Ruby
Executable File

#!/usr/bin/env ruby
# -------------------------------------------------------------------------- #
# Copyright 2002-2022, OpenNebula Project, OpenNebula Systems #
# #
# 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 'getoptlong'
require 'ipaddr'
require 'json'
require 'logger'
require 'open3'
require 'tempfile'
require 'yaml'
CHAINS = {
'PREROUTING' => 'chain-onevnetmap-dnat',
'POSTROUTING' => 'chain-onevnetmap-snat'
}
# 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
# 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
retval = []
roles = service['SERVICE']['roles'].flatten
roles.each do |role|
next unless role['nodes']
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
# Get iptables rules to apply for NAT table if no NIC/NIC_ALIAS detected
def iptables_tnat_apply_init
rules = ''
CHAINS.each do |nat_chain, custom_chain|
o, _, rc = Open3.capture3("iptables -tnat -S #{custom_chain}")
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}")
# 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
end
# 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
# 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
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