From 447d0dfc424034d6dfbf6508de8c8cfabe2bdbba Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sun, 11 Jan 2015 20:59:38 -0200 Subject: [PATCH] Experimental support for private networking [GH-298] --- lib/vagrant-lxc/action.rb | 5 +- .../action/gc_private_network_bridges.rb | 35 ++++++++++++-- lib/vagrant-lxc/action/private_networks.rb | 40 ++-------------- lib/vagrant-lxc/command/sudoers.rb | 7 +-- lib/vagrant-lxc/driver.rb | 46 +++++++++++++++++++ locales/en.yml | 4 ++ scripts/{private-network => pipework} | 0 templates/sudoers.rb.erb | 27 +++++++++-- 8 files changed, 111 insertions(+), 53 deletions(-) rename scripts/{private-network => pipework} (100%) diff --git a/lib/vagrant-lxc/action.rb b/lib/vagrant-lxc/action.rb index 883654c..49f0707 100644 --- a/lib/vagrant-lxc/action.rb +++ b/lib/vagrant-lxc/action.rb @@ -56,9 +56,9 @@ module Vagrant b.use Builtin::SetHostname b.use WarnNetworks b.use ForwardPorts + b.use PrivateNetworks b.use Boot b.use Builtin::WaitForCommunicator - b.use PrivateNetworks end end @@ -127,12 +127,12 @@ module Vagrant b2.use ClearForwardedPorts b2.use RemoveTemporaryFiles + b2.use GcPrivateNetworkBridges b2.use Builtin::Call, Builtin::GracefulHalt, :stopped, :running do |env2, b3| if !env2[:result] b3.use ForcedHalt end end - b2.use GcPrivateNetworkBridges end end end @@ -147,7 +147,6 @@ module Vagrant next end - # TODO: Use Vagrant's built in action once we drop support for vagrant 1.2 b2.use Builtin::Call, DestroyConfirm do |env2, b3| if env2[:result] b3.use Builtin::ConfigValidate diff --git a/lib/vagrant-lxc/action/gc_private_network_bridges.rb b/lib/vagrant-lxc/action/gc_private_network_bridges.rb index d7bb83f..dc33a92 100644 --- a/lib/vagrant-lxc/action/gc_private_network_bridges.rb +++ b/lib/vagrant-lxc/action/gc_private_network_bridges.rb @@ -1,5 +1,3 @@ -# sudo ifconfig br1 down && sudo brctl delbr br1 - module Vagrant module LXC module Action @@ -9,11 +7,38 @@ module Vagrant end def call(env) - if env[:machine].provider.state.id != :running - puts 'Cleanup bridges!' - end + was_running = env[:machine].provider.state.id == :running + # Continue execution, we need the container to be stopped @app.call(env) + + was_running = was_running && env[:machine].provider.state.id != :running + + if was_running && private_network_configured?(env[:machine].config) + private_network_configured?(env[:machine].config) + remove_bridges_that_are_not_in_use(env) + end + end + + def private_network_configured?(config) + config.vm.networks.find do |type, _| + type.to_sym == :private_network + end + end + + def remove_bridges_that_are_not_in_use(env) + env[:machine].config.vm.networks.find do |type, config| + next if type.to_sym != :private_network + + bridge = config.fetch(:lxc__bridge_name) + driver = env[:machine].provider.driver + + if ! driver.bridge_is_in_use?(bridge) + env[:ui].info I18n.t("vagrant_lxc.messages.remove_bridge", name: bridge) + # TODO: Output that bridge is being removed + driver.remove_bridge(bridge) + end + end end end end diff --git a/lib/vagrant-lxc/action/private_networks.rb b/lib/vagrant-lxc/action/private_networks.rb index c0794e7..f91a584 100644 --- a/lib/vagrant-lxc/action/private_networks.rb +++ b/lib/vagrant-lxc/action/private_networks.rb @@ -10,6 +10,7 @@ module Vagrant @app.call(env) if private_network_configured?(env[:machine].config) + env[:ui].output(I18n.t("vagrant_lxc.messages.setup_private_network")) configure_private_networks(env) end end @@ -27,50 +28,15 @@ module Vagrant container_name = env[:machine].provider.driver.container_name ip = config[:ip] bridge_ip = config.fetch(:lxc__bridge_ip) { build_bridge_ip(ip) } - bridge = config.fetch(:lxc__bridge_name) # { build_bridge_name(config.fetch(:lxc__bridge_prefix, 'br'), bridge_ip) } + bridge = config.fetch(:lxc__bridge_name) - # TODO: ensure_ip_is_not_in_use! - configure_single_network(bridge, bridge_ip, container_name, ip) + env[:machine].provider.driver.configure_private_network(bridge, bridge_ip, container_name, ip) end end - def configure_single_network(bridge, bridge_ip, container_name, ip) - cmd = [ - 'sudo', - Vagrant::LXC.source_root.join('scripts/private-network').to_s, - bridge, - container_name, - "#{ip}/24" - ] - execute(cmd) - - # TODO: Run only if bridge is not up and move it to the private network script - cmd = [ - 'sudo', - 'ip', - 'addr', - 'add', - "#{bridge_ip}/24", - 'dev', - bridge - ] - execute(cmd) - end - - def execute(cmd) - puts cmd.join(' ') - system cmd.join(' ') - end - def build_bridge_ip(ip) ip.sub(/^(\d+\.\d+\.\d+)\.\d+/, '\1.254') end - - def bridge_name(prefix, bridge_ip) - # if a bridge with the provided ip and prefix exist, get its name and return it - # if no bridges can be found, grab the max bridge number, increment it and return the new name - 'br3' - end end end end diff --git a/lib/vagrant-lxc/command/sudoers.rb b/lib/vagrant-lxc/command/sudoers.rb index 8d27974..fcb2099 100644 --- a/lib/vagrant-lxc/command/sudoers.rb +++ b/lib/vagrant-lxc/command/sudoers.rb @@ -46,8 +46,9 @@ module Vagrant wrapper = Tempfile.new('lxc-wrapper').tap do |file| template = Vagrant::Util::TemplateRenderer.new( 'sudoers.rb', - :template_root => Vagrant::LXC.source_root.join('templates').to_s, - :cmd_paths => build_cmd_paths_hash + :template_root => Vagrant::LXC.source_root.join('templates').to_s, + :cmd_paths => build_cmd_paths_hash, + :pipework_regex => "#{ENV['HOME']}/\.vagrant\.d/gems/gems/vagrant-lxc.+/scripts/pipework" ) file.puts template.render end @@ -78,7 +79,7 @@ module Vagrant def build_cmd_paths_hash {}.tap do |hash| - %w( which cat mkdir cp chown chmod rm tar chown ).each do |cmd| + %w( which cat mkdir cp chown chmod rm tar chown ip ifconfig brctl ).each do |cmd| hash[cmd] = `which #{cmd}`.strip end hash['lxc_bin'] = Pathname(`which lxc-create`.strip).parent.to_s diff --git a/lib/vagrant-lxc/driver.rb b/lib/vagrant-lxc/driver.rb index 4f66381..d318e0b 100644 --- a/lib/vagrant-lxc/driver.rb +++ b/lib/vagrant-lxc/driver.rb @@ -126,6 +126,52 @@ module Vagrant @cli.attach(*command) end + def configure_private_network(bridge_name, bridge_ip, container_name, ip) + @logger.info "Configuring network interface for #{container_name} using #{ip} and bridge #{bridge_name}" + cmd = [ + Vagrant::LXC.source_root.join('scripts/pipework').to_s, + bridge_name, + container_name, + "#{ip}/24" + ] + @sudo_wrapper.run(*cmd) + + if ! bridge_has_an_ip?(bridge_name) + @logger.info "Adding #{bridge_ip} to the bridge #{bridge_name}" + cmd = [ + 'ip', + 'addr', + 'add', + "#{bridge_ip}/24", + 'dev', + bridge_name + ] + @sudo_wrapper.run(*cmd) + end + end + + def bridge_has_an_ip?(bridge_name) + @logger.info "Checking whether the bridge #{bridge_name} has an IP" + `ip -4 addr show scope global #{bridge_name}` =~ /^\s+inet ([0-9.]+)\/[0-9]+\s+/ + end + + def bridge_is_in_use?(bridge_name) + # REFACTOR: This method is **VERY** hacky + @logger.info "Checking if bridge #{bridge_name} is in use" + brctl_output = `brctl show #{bridge_name} 2>/dev/null | tail -n +2 | grep -q veth` + $?.to_i == 0 + end + + def remove_bridge(bridge_name) + @logger.info "Checking whether bridge #{bridge_name} exists" + brctl_output = `ifconfig -a | grep -q #{bridge_name}` + return if $?.to_i != 0 + + @logger.info "Removing bridge #{bridge_name}" + @sudo_wrapper.run('ifconfig', bridge_name, 'down') + @sudo_wrapper.run('brctl', 'delbr', bridge_name) + end + def version @version ||= @cli.version end diff --git a/locales/en.yml b/locales/en.yml index 145cbf0..b407e8e 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -21,6 +21,10 @@ en: warn_owner: |- Warning! The LXC provider doesn't support the :owner parameter for synced folders. It will be silently ignored. + setup_private_network: |- + Setting up private networks... + remove_bridge: |- + Removing bridge '%{name}'... vagrant: commands: diff --git a/scripts/private-network b/scripts/pipework similarity index 100% rename from scripts/private-network rename to scripts/pipework diff --git a/templates/sudoers.rb.erb b/templates/sudoers.rb.erb index b446531..94db7f7 100644 --- a/templates/sudoers.rb.erb +++ b/templates/sudoers.rb.erb @@ -4,24 +4,36 @@ class Whitelist class << self def add(command, *args) + list[command] ||= [] list[command] << args end + def add_regex(regex, *args) + regex_list << [regex, [args]] + end + def list - @list ||= Hash.new do |key, hsh| - key[hsh] = [] - end + @list ||= {} + end + + def regex_list + @regex_list ||= [] end def allowed(command) - list[command] || [] + list[command] || allowed_regex(command) || [] + end + + def allowed_regex(command) + found = regex_list.find { |r| r[0] =~ command } + return found[1] if found end def run!(argv) begin command, args = `which #{argv.shift}`.chomp, argv || [] check!(command, args) - puts `#{command} #{args.join(" ")}` + system "#{command} #{args.join(" ")}" exit_code = $?.to_i exit_code = 1 if exit_code == 256 @@ -92,6 +104,11 @@ Whitelist.add '<%= cmd_paths['rm'] %>', templates_path # - Packaging Whitelist.add '<%= cmd_paths['tar'] %>', '--numeric-owner', '-cvzf', %r{/tmp/.*/rootfs.tar.gz}, '-C', base_path, './rootfs' Whitelist.add '<%= cmd_paths['chown'] %>', /\A\d+:\d+\z/, %r{\A/tmp/.*/rootfs\.tar\.gz\z} +# - Private network script and commands +Whitelist.add '<%= cmd_paths['ip'] %>', 'addr', 'add', /(\d+|\.)+\/24/, 'dev', /.+/ +Whitelist.add '<%= cmd_paths['ifconfig'] %>', /.+/, 'down' +Whitelist.add '<%= cmd_paths['brctl'] %>', 'delbr', /.+/ +Whitelist.add_regex %r{<%= pipework_regex %>}, '**' ## # Commands from driver/cli.rb