From f329eeffd552e4ccd8b02352e7a1c69cbbdcd8c6 Mon Sep 17 00:00:00 2001 From: Hardy Ferentschik Date: Mon, 18 Jan 2016 16:21:59 +0100 Subject: [PATCH] Issue #171 Adding support for automatic configuration of host visibility on Windows - Making use of netsh and Windows registry to modify network configuration - Adding test harness - Removing Gemfile.lock since depdenencies differ between OS X, Linux and Windows - Cleaning up Gemfile and moving runtime dependencies into the gemspec file - Updating Travis config to install latest Bundler version --- .gitignore | 16 +-- .travis.yml | 3 + Gemfile | 9 +- Gemfile.lock | 175 ----------------------- README.md | 86 +++++++---- landrush.gemspec | 1 + lib/landrush.rb | 2 +- lib/landrush/action/setup.rb | 21 ++- lib/landrush/resolver_config.rb | 10 +- lib/landrush/server.rb | 2 +- lib/landrush/util/retry.rb | 16 +++ lib/landrush/win_network_config.rb | 169 ++++++++++++++++++++++ test/landrush/action/setup_test.rb | 10 ++ test/landrush/resolver_config_test.rb | 2 - test/landrush/util/rety_test.rb | 50 +++++++ test/landrush/win_network_config_test.rb | 70 +++++++++ test/support/fake_ui.rb | 1 + test/test_helper.rb | 2 + 18 files changed, 408 insertions(+), 237 deletions(-) delete mode 100644 Gemfile.lock create mode 100644 lib/landrush/util/retry.rb create mode 100644 lib/landrush/win_network_config.rb create mode 100644 test/landrush/util/rety_test.rb create mode 100644 test/landrush/win_network_config_test.rb diff --git a/.gitignore b/.gitignore index d3c74b5..b0661ee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,22 @@ +# Bundler +Gemfile.lock +.bundle + +# Ruby / RVM .ruby-gemset .ruby-version -*.rbc -.bundle + +# Rake / Build .yardoc -InstalledFiles _yardoc coverage lib/bundler/man pkg rdoc spec/reports -test/tmp -test/version_tmp build tmp -tags - -.vagrant +# IDE *.iml .idea diff --git a/.travis.yml b/.travis.yml index 2b42653..352b0f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,8 @@ language: ruby +before_install: +- gem install bundler -v 1.12.5 + cache: bundler rvm: diff --git a/Gemfile b/Gemfile index 2aba9a4..53a388d 100644 --- a/Gemfile +++ b/Gemfile @@ -3,9 +3,7 @@ source 'https://rubygems.org' # Can't use `gemspec` to pull in dependencies, because the landrush gem needs # to be in the :plugins group for Vagrant to detect and load it in development -gem 'rubydns', '0.8.5' -gem 'win32-process' -gem 'json' +gemspec # Vagrant's special group group :plugins do @@ -13,16 +11,13 @@ group :plugins do gem 'landrush-ip', '~> 0.2.3' end -group :test do - gem 'rubocop', '~> 0.38.0' -end - group :development do gem 'vagrant', :git => 'git://github.com/mitchellh/vagrant.git', :ref => 'v1.8.4' gem 'rake', '~> 10' + gem 'rubocop', '~> 0.38.0' gem 'byebug' gem 'mocha' gem 'minitest' diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 31cac4b..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,175 +0,0 @@ -GIT - remote: git://github.com/mitchellh/vagrant.git - revision: bedaeee190b2bc0a7962b143ad82eec49d7f0d60 - ref: v1.8.4 - specs: - vagrant (1.8.4) - bundler (= 1.12.5) - childprocess (~> 0.5.0) - erubis (~> 2.7.0) - hashicorp-checkpoint (~> 0.1.1) - i18n (>= 0.6.0, <= 0.8.0) - listen (~> 3.0.2) - log4r (~> 1.1.9, < 1.1.11) - net-scp (~> 1.1.0) - net-sftp (~> 2.1) - net-ssh (~> 3.0.1) - nokogiri (= 1.6.7.1) - rb-kqueue (~> 0.2.0) - rest-client (>= 1.6.0, < 2.0) - wdm (~> 0.1.0) - winrm (~> 1.6) - winrm-fs (~> 0.3.0) - -PATH - remote: . - specs: - landrush (1.1.0.dev) - rubydns (= 0.8.5) - win32-process - -GEM - remote: https://rubygems.org/ - specs: - aruba (0.14.1) - childprocess (~> 0.5.6) - contracts (~> 0.9) - cucumber (>= 1.3.19) - ffi (~> 1.9.10) - rspec-expectations (>= 2.99) - thor (~> 0.19) - ast (2.3.0) - builder (3.2.2) - byebug (9.0.5) - childprocess (0.5.9) - ffi (~> 1.0, >= 1.0.11) - contracts (0.14.0) - cucumber (2.4.0) - builder (>= 2.1.2) - cucumber-core (~> 1.5.0) - cucumber-wire (~> 0.0.1) - diff-lcs (>= 1.1.3) - gherkin (~> 4.0) - multi_json (>= 1.7.5, < 2.0) - multi_test (>= 0.1.2) - cucumber-core (1.5.0) - gherkin (~> 4.0) - cucumber-wire (0.0.1) - diff-lcs (1.2.5) - domain_name (0.5.20160615) - unf (>= 0.0.5, < 1.0.0) - erubis (2.7.0) - eventmachine (1.0.9.1) - events (0.9.8) - ffi (1.9.10) - gherkin (4.0.0) - gssapi (1.2.0) - ffi (>= 1.0.1) - gyoku (1.3.1) - builder (>= 2.1.2) - hashicorp-checkpoint (0.1.4) - http-cookie (1.0.2) - domain_name (~> 0.5) - httpclient (2.8.0) - i18n (0.7.0) - json (1.8.3) - komenda (0.1.6) - events (~> 0.9.8) - landrush-ip (0.2.3) - listen (3.0.8) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - little-plugger (1.1.4) - log4r (1.1.10) - logging (2.1.0) - little-plugger (~> 1.1) - multi_json (~> 1.10) - metaclass (0.0.4) - mime-types (2.99.2) - mini_portile2 (2.0.0) - minitest (5.9.0) - mocha (1.1.0) - metaclass (~> 0.0.1) - multi_json (1.12.1) - multi_test (0.1.2) - net-scp (1.1.2) - net-ssh (>= 2.6.5) - net-sftp (2.1.2) - net-ssh (>= 2.6.5) - net-ssh (3.0.2) - netrc (0.11.0) - nokogiri (1.6.7.1) - mini_portile2 (~> 2.0.0.rc2) - nori (2.6.0) - parser (2.3.1.2) - ast (~> 2.2) - powerpack (0.1.1) - rainbow (2.1.0) - rake (10.5.0) - rb-fsevent (0.9.7) - rb-inotify (0.9.7) - ffi (>= 0.5.0) - rb-kqueue (0.2.4) - ffi (>= 0.5.0) - rest-client (1.8.0) - http-cookie (>= 1.0.2, < 2.0) - mime-types (>= 1.16, < 3.0) - netrc (~> 0.7) - rspec-expectations (3.4.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.4.0) - rspec-support (3.4.1) - rubocop (0.38.0) - parser (>= 2.3.0.6, < 3.0) - powerpack (~> 0.1) - rainbow (>= 1.99.1, < 3.0) - ruby-progressbar (~> 1.7) - unicode-display_width (~> 1.0, >= 1.0.1) - ruby-progressbar (1.8.1) - rubydns (0.8.5) - eventmachine (~> 1.0.0) - rubyntlm (0.6.0) - rubyzip (1.2.0) - thor (0.19.1) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.2) - unicode-display_width (1.0.5) - wdm (0.1.1) - win32-process (0.8.3) - ffi (>= 1.0.0) - winrm (1.8.1) - builder (>= 2.1.2) - gssapi (~> 1.2) - gyoku (~> 1.0) - httpclient (~> 2.2, >= 2.2.0.2) - logging (>= 1.6.1, < 3.0) - nori (~> 2.0) - rubyntlm (~> 0.6.0) - winrm-fs (0.3.2) - erubis (~> 2.7) - logging (>= 1.6.1, < 3.0) - rubyzip (~> 1.1) - winrm (~> 1.5) - -PLATFORMS - ruby - -DEPENDENCIES - aruba (~> 0.13) - byebug - cucumber (~> 2.1) - json - komenda (~> 0.1.6) - landrush! - landrush-ip (~> 0.2.3) - minitest - mocha - rake (~> 10) - rubocop (~> 0.38.0) - rubydns (= 0.8.5) - vagrant! - win32-process - -BUNDLED WITH - 1.12.5 diff --git a/README.md b/README.md index 216df27..fae8a43 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ # Landrush: DNS for Vagrant [![Build Status](https://travis-ci.org/vagrant-landrush/landrush.png)](https://travis-ci.org/vagrant-landrush/landrush) -Simple cross-platform DNS that's visible on both the guest and the host. +Landrush is a simple cross-platform DNS for Vagrant VMs that is visible on both, +the guest and the host. -Landrush spins up a small DNS server and redirects DNS traffic from your +It spins up a small DNS server and redirects DNS traffic from your VMs to use it, automatically registering/unregistering IP addresses of guests -as they come up and down. +as they come up and go down. **Note**: Windows support is currently considered experimental. If you having problems using Landrush on Windows please let us know. @@ -13,7 +14,7 @@ problems using Landrush on Windows please let us know. - [Installation](#installation) - [Usage](#usage) - - [Get started](#get-started) + - [Getting started](#getting-started) - [Dynamic entries](#dynamic-entries) - [Static entries](#static-entries) - [Wildcard Subdomains](#wildcard-subdomains) @@ -40,24 +41,27 @@ Install under Vagrant (1.1 or later): ## Usage - -### Get started + +### Getting started -Enable the plugin in your `Vagrantfile`: +1. Enable the plugin in your `Vagrantfile`: - config.landrush.enabled = true + config.landrush.enabled = true -Bring up a machine. +1. Bring up a machine. - $ vagrant up + $ vagrant up -And you should be able to get your hostname from your host: +1. You are able to get your VM's hostname resolved on your host: $ dig -p 10053 @localhost myhost.vagrant.test -If you shut down your guest, the entries associated with it will be removed. +1. If you shut down your guest, the entries associated with it will be removed. -Landrush assigns your vm's hostname from either the vagrant config (see the `examples/Vagrantfile`) or system's actual hostname by running the `hostname` command. A default of "guest-vm" is assumed if hostname is otherwise not available. +Landrush retrieves your VM's hostname from either the vagrant config (see the +[examples/Vagrantfile](examples/Vagrantfile)) +or it uses the system's actual hostname by running the `hostname` command. +A default of "guest-vm" is assumed if hostname is otherwise not available. ### Dynamic entries @@ -79,7 +83,7 @@ If you need or want to select an interface explicitly and you know its name, you config.landrush.host_interface = 'eth0' -Do note that if you specify an interface explicitly, it will have priority over +Do note that, if you specify an interface explicitly, it will have priority over `config.landrush.host_interface_excludes`. In other words, if `config.landrush.host_interface_excludes` is set to `[/eth[0-9]*/]`, but `config.landrush.host_interface` is set to `eth0` and `eth0` exists as an interface, the IP address of `eth0` is returned. The interface setting takes precedence over @@ -102,25 +106,35 @@ You can add static host entries to the DNS server in your `Vagrantfile` like so: config.landrush.host 'myhost.example.com', '1.2.3.4' -This is great for overriding production services for nodes you might be testing locally. For example, perhaps you might want to override the hostname of your puppetmaster to point to a local vagrant box instead. +This is great for overriding production services for nodes you might be testing locally. For example, +perhaps you might want to override the hostname of your puppetmaster to point to a local vagrant box +instead. ### Wildcard Subdomains -For your convenience, any subdomain of a DNS entry known to landrush will resolve to the same IP address as the entry. For example: given `myhost.vagrant.test -> 1.2.3.4`, both `foo.myhost.vagrant.test` and `bar.myhost.vagrant.test` will also resolve to `1.2.3.4`. +For your convenience, any subdomain of a DNS entry known to landrush will resolve to the same IP +address as the entry. For example: given `myhost.vagrant.test -> 1.2.3.4`, both +`foo.myhost.vagrant.test` and `bar.myhost.vagrant.test` will also resolve to `1.2.3.4`. -If you would like to configure your guests to be accessible from the host as subdomains of something other than the default `vagrant.test`, you can use the `config.landrush.tld` option in your Vagrantfile like so: +If you would like to configure your guests to be accessible from the host as subdomains of something +other than the default `vagrant.test`, you can use the `config.landrush.tld` option in your +Vagrantfile like so: config.landrush.tld = 'vm' -Note that from the __host__, you will only be able to access subdomains of your configured TLD by default- so wildcard subdomains only apply to that space. For the __guest__, wildcard subdomains work for anything. +Note that from the __host__, you will only be able to access subdomains of your configured TLD by +default- so wildcard subdomains only apply to that space. For the __guest__, wildcard subdomains +work for anything. ### Unmatched Queries -Any DNS queries that do not match will be passed through to an upstream DNS server, so this will be able to serve as the one-stop shop for your guests' DNS needs. +Any DNS queries that do not match will be passed through to an upstream DNS server, so this will be +able to serve as the one-stop shop for your guests' DNS needs. -If you would like to configure your own upstream servers, add upstream entries to your `Vagrantfile` like so: +If you would like to configure your own upstream servers, add upstream entries to your +`Vagrantfile` like so: config.landrush.upstream '10.1.1.10' # Set the port to 1001 @@ -143,34 +157,44 @@ have DNS servers that you can easily set as upstreams in the daemon (e.g. DNS re ### Visibility on the Host +Visibility on the host means that the hostname of the VMs can be resolved on the host's DNS system. +Depending on the OS this might require some manual configuration. + #### OS X -If you're on an OS X host, we use a nice trick to unobtrusively add a secondary DNS server only for specific domains. -Landrush adds a file into `/etc/resolver` that points lookups for hostnames ending in your `config.landrush.tld` domain -name to its DNS server. (Check out `man 5 resolver` on your Mac OS X host for more information on this file's syntax.) - +If you're on an OS X host, we use a nice trick to unobtrusively add a secondary DNS server only for +specific domains. Landrush adds automatically during startup a file into `/etc/resolver` +that points lookups for hostnames ending in your `config.landrush.tld` domain to its DNS server. +(Check out `man 5 resolver` on your Mac OS X host for more information on this file's syntax.) #### Linux -Though it's not automatically set up by landrush, similar behavior can be achieved on Linux hosts with `dnsmasq`. You -can integrate Landrush with dnsmasq on Ubuntu like so (tested on Ubuntu 13.10): +Though it's not automatically set up by Landrush, similar behavior can be achieved on Linux hosts +with `dnsmasq`. You can integrate Landrush with `dnsmasq` on Ubuntu like so (tested on Ubuntu 13.10): sudo apt-get install -y resolvconf dnsmasq sudo sh -c 'echo "server=/vagrant.test/127.0.0.1#10053" > /etc/dnsmasq.d/vagrant-landrush' sudo service dnsmasq restart -If you use a TLD other than the default `vagrant.test`, replace the TLD in the above instructions accordingly. Please be aware that anything ending in '.local' as TLD will not work because the `avahi` daemon reserves this TLD for its own uses. +If you use a TLD other than the default `vagrant.test`, replace the TLD in the above instructions +accordingly. Please be aware that anything ending in '.local' as TLD will not work because the +`avahi` daemon reserves this TLD for its own uses. #### Windows -**Note**: Windows support is currently considered experimental. If you having -problems using Landrush on Windows please let us know. +On Windows a secondary DNS server can be configured via the properties of the +network adapter used by the VM. Landrush will attempt to configure the adapter automatically +during startup. If this fails, please follow the manual setup instructions below. + +It is recommended to use an elevated command prompt (command prompt with full administrator +permissions), since admin privileges are needed to make the required changes. Landrush will +try to elevate your prompt automatically, but this requires to spawn additional processes which in +turn loose some potentially important log messages. -On Windows a secondary DNS server can be configured via the properties of a -network adapter. This will be illustrated in the following using Windows 10 with +In the following section manual network configuration is described using Windows 10 and VirtualBox. When running VirtualBox on Windows in combination with Landrush the Network diff --git a/landrush.gemspec b/landrush.gemspec index 56eed7d..924cb82 100644 --- a/landrush.gemspec +++ b/landrush.gemspec @@ -30,4 +30,5 @@ Gem::Specification.new do |spec| spec.add_dependency 'rubydns', '0.8.5' spec.add_dependency 'win32-process' + spec.add_dependency 'json' end diff --git a/lib/landrush.rb b/lib/landrush.rb index be16029..dc2fee7 100644 --- a/lib/landrush.rb +++ b/lib/landrush.rb @@ -9,11 +9,11 @@ require 'win32/process' if Vagrant::Util::Platform.windows? require 'rubydns' -require 'ipaddr' require 'landrush/dependent_vms' require 'landrush/plugin' require 'landrush/resolver_config' +require 'landrush/win_network_config' require 'landrush/server' require 'landrush/store' require 'landrush/version' diff --git a/lib/landrush/action/setup.rb b/lib/landrush/action/setup.rb index 7cd0e62..f2b70f2 100644 --- a/lib/landrush/action/setup.rb +++ b/lib/landrush/action/setup.rb @@ -15,24 +15,35 @@ def call(env) # This is after the middleware stack returns, which, since we're right # before the Network action, should mean that all interfaces are good # to go. - record_machine_dns_entry if enabled? - setup_static_dns if enabled? + post_boot_setup if enabled? end def pre_boot_setup record_dependent_vm add_prerequisite_network_interface - setup_host_resolver configure_server start_server end + def post_boot_setup + record_machine_dns_entry + setup_static_dns + setup_host_resolver(env) + end + def record_dependent_vm DependentVMs.add(machine_hostname) end - def setup_host_resolver - ResolverConfig.new(env).ensure_config_exists! + def setup_host_resolver(env) + if Vagrant::Util::Platform.windows? + network_config = WinNetworkConfig.new(env) + if network_config.ensure_prerequisites + network_config.update_network_adapter(host_ip_address, '127.0.0.1', config.tld) + end + elsif Vagrant::Util::Platform.darwin? + ResolverConfig.new(env).ensure_config_exists! + end end def add_prerequisite_network_interface diff --git a/lib/landrush/resolver_config.rb b/lib/landrush/resolver_config.rb index 2b3f12a..faae68f 100644 --- a/lib/landrush/resolver_config.rb +++ b/lib/landrush/resolver_config.rb @@ -29,10 +29,6 @@ def desired_contents; <<-EOS.gsub(/^ /, '') EOS end - def osx? - `uname`.chomp == 'Darwin' - end - def config_dir self.class.config_dir end @@ -46,7 +42,7 @@ def contents_match? end def write_config! - info "Momentarily using sudo to put the host config in place..." + info 'Momentarily using sudo to put the host config in place...' system "#{self.class.sudo} mkdir #{config_dir}" unless config_dir.directory? Tempfile.open('vagrant_landrush_host_config') do |f| f.write(desired_contents) @@ -58,9 +54,9 @@ def write_config! def ensure_config_exists! if contents_match? - info "Host DNS resolver config looks good." + info 'Host DNS resolver config looks good.' else - info "Need to configure the host." + info 'Need to configure the host.' write_config! end end diff --git a/lib/landrush/server.rb b/lib/landrush/server.rb index bfbd420..1692735 100644 --- a/lib/landrush/server.rb +++ b/lib/landrush/server.rb @@ -1,6 +1,6 @@ require 'rubydns' require 'ipaddr' -require "vagrant/util/platform" +require 'vagrant/util/platform' require_relative 'store' diff --git a/lib/landrush/util/retry.rb b/lib/landrush/util/retry.rb new file mode 100644 index 0000000..0d564ed --- /dev/null +++ b/lib/landrush/util/retry.rb @@ -0,0 +1,16 @@ +module Landrush + module Util + module Retry + def retry(opts=nil) + opts = {tries: 1}.merge(opts || {}) + n = 0 + while n < opts[:tries] + return true if yield + sleep opts[:sleep].to_f if opts[:sleep] + n += 1 + end + false + end + end + end +end diff --git a/lib/landrush/win_network_config.rb b/lib/landrush/win_network_config.rb new file mode 100644 index 0000000..178a501 --- /dev/null +++ b/lib/landrush/win_network_config.rb @@ -0,0 +1,169 @@ +require 'ipaddr' +require 'vagrant/util/which' +require_relative 'util/retry' + +# This class configures the network interface on Windows for use with the Landrush DNS server. +# It makes use of the netsh executable which is assumed to be available on the Windows host. +module Landrush + class WinNetworkConfig + include Landrush::Util::Retry + + # Windows registry path under which network interface configuration is stored + INTERFACES = 'SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces'.freeze + + def initialize(env={}) + @env = env + end + + # Checks that all required tools are on the PATH and that the Wired AutoConfig service is started + def ensure_prerequisites + info('Checking prerequisites for configuring DNS on host') + + return false unless command_found('netsh') + return false unless command_found('net') + return false unless command_found('reg') + + unless wired_autoconfig_service_running? + info('Starting \'Wired AutoConfig\' service') + if self.class.admin_mode? + `net start dot3svc` + else + require 'win32ole' + shell = WIN32OLE.new('Shell.Application') + shell.ShellExecute('net', 'start dot3svc', nil, 'runas', 1) + end + service_has_started = self.retry(tries: 5, sleep: 1) do + wired_autoconfig_service_running? + end + unless service_has_started + info('Unable to start \'Wired AutoConfig\' service. Unable to configure DNS on host. Try manual configuration.') + return false + end + info('\'Wired AutoConfig\' service has started.') + end + true + end + + # Does the actual update of the network configuration + def update_network_adapter(ip, name_server, domain) + # Need to defer loading to ensure cross OS compatibility + require 'win32/registry' + ensure_admin_privileges(__FILE__.to_s, ip, name_server, domain) + + network_name = get_network_name(ip) + if network_name.nil? + info("Unable to determine network interface for #{ip}. DNS on host cannot be configured. Try manual configuration.") + return + else + info("Setting DNS server for network '#{network_name}' to #{ip} and search domain to '#{domain}'") + end + network_guid = get_guid(network_name) + if network_guid.nil? + info("Unable to determine network GUID for #{ip}. DNS on host cannot be configured. Try manual configuration.") + return + end + interface_path = INTERFACES + "\\{#{network_guid}}" + Win32::Registry::HKEY_LOCAL_MACHINE.open(interface_path, Win32::Registry::KEY_ALL_ACCESS) do |reg| + reg['NameServer'] = name_server + reg['Domain'] = domain + end + end + + # If this registry query succeeds we assume we have Admin rights + # http://stackoverflow.com/questions/8268154/run-ruby-script-in-elevated-mode/27954953 + def self.admin_mode? + (`reg query HKU\\S-1-5-19 2>&1` =~ /ERROR/).nil? + end + + private + + # Given a network name (as displayed on 'Control Panel\Network and Internet\Network Connections'), + # determines the GUID of this network interface using 'netsh'. + # + # To make this work the "Wired Autoconfig" service must be started (go figure). + # + # Output of netsh command which is being processed: + # + # There are 4 interfaces on the system: + # + # Name : Ethernet + # Description : Intel(R) Ethernet Connection (3) I218-LM + # GUID : fd9270f6-aff6-4f24-bc4a-1f90c032d5c3 + # Physical Address : 50-7B-9D-AB-25-1D + # \n\n + # ... + def get_guid(network_name) + cmd_out = `netsh lan show interfaces` + interface_details = cmd_out.split(/\n\n/).select { |settings| settings.match(/#{Regexp.quote(network_name)}/m) } + return nil if interface_details.empty? + interface_details[0].split(/\n/)[2].match(/.*:(.*)/).captures[0].strip + end + + # Given an IP determines the network name, if any. Uses netsh which generates output like this: + # + # ... + # \n\n + # Configuration for interface "Ethernet 2" + # DHCP enabled: Yes + # IP Address: 10.10.10.1 + # Subnet Prefix: 10.10.10.0/24 (mask 255.255.255.0) + # InterfaceMetric: 10 + # DNS servers configured through DHCP: None + # Register with which suffix: Primary only + # WINS servers configured through DHCP: None + # \n\n + # ... + def get_network_name(ip) + cmd_out = `netsh interface ip show config` + network_details = cmd_out.split(/\n\n/).select do |settings| + lines = settings.split(/\n/).reject(&:empty?) + subnet = lines[3] + next false unless subnet =~ /Subnet Prefix/ + + mask = IPAddr.new(subnet.match(%r{.* (\d{1,3}\.\d{1,3}\.\d{1,3}.\d{1,3}/\d{1,3}).*}).captures[0]) + address = IPAddr.new(ip) + + mask.include?(address) + end + return nil if network_details[0].nil? + network_details[0].split(/\n/)[0].match(/Configuration for interface "(.*)"/).captures[0].strip + end + + # Makes sure that we have admin privileges and if nor starts a new shell with the required + # privileges + def ensure_admin_privileges(file, *args) + unless self.class.admin_mode? + require 'win32ole' + shell = WIN32OLE.new('Shell.Application') + shell.ShellExecute('ruby', "#{file} #{args.join(' ')}", nil, 'runas', 1) + # need to exit current execution, changes will occur in new environment + exit + end + end + + def info(msg) + @env[:ui].info("[landrush] #{msg}") unless @env.nil? + end + + def wired_autoconfig_service_running? + cmd_out = `net start` + cmd_out =~ /Wired AutoConfig/m + end + + def command_found(cmd) + if Vagrant::Util::Which.which(cmd).nil? + info("Cannot find '#{cmd}' on the PATH. Unable to configure DNS. Try manual configuration.") + false + else + true + end + end + end +end + +# Only run the following code when this file is the main file being run +# instead of having been required or loaded by another file +if __FILE__ == $0 + config = Landrush::WinNetworkConfig.new nil + config.update_network_adapter(ARGV[0], ARGV[1], ARGV[2]) +end diff --git a/test/landrush/action/setup_test.rb b/test/landrush/action/setup_test.rb index b11e79b..d4502b2 100644 --- a/test/landrush/action/setup_test.rb +++ b/test/landrush/action/setup_test.rb @@ -14,6 +14,7 @@ module Action end it "calls the next app in the chain" do + skip('Not working on Windows, since it will also do the network config') if Vagrant::Util::Platform.windows? env = fake_environment app = -> (e) { e[:called] = true } setup = Setup.new(app, nil) @@ -24,6 +25,7 @@ module Action end it "records the booting host as a dependent VM" do + skip('Not working on Windows, since it will also do the network config') if Vagrant::Util::Platform.windows? app = proc {} setup = Setup.new(app, nil) env = fake_environment @@ -34,6 +36,7 @@ module Action end it "starts the landrush server if it's not already started" do + skip('Not working on Windows, since it will also do the network config') if Vagrant::Util::Platform.windows? app = proc {} setup = Setup.new(app, nil) env = fake_environment @@ -44,6 +47,7 @@ module Action end it "does not attempt to start the server if it's already up" do + skip('Not working on Windows, since it will also do the network config') if Vagrant::Util::Platform.windows? app = proc {} setup = Setup.new(app, nil) env = fake_environment @@ -58,6 +62,7 @@ module Action end it "does nothing if it is not enabled via config" do + skip('Not working on Windows, since it will also do the network config') if Vagrant::Util::Platform.windows? app = proc {} setup = Setup.new(app, nil) env = fake_environment @@ -69,6 +74,7 @@ module Action end it "for single private network IP host visible IP can be retrieved w/o starting the VM" do + skip('Not working on Windows, since it will also do the network config') if Vagrant::Util::Platform.windows? app = proc {} setup = Setup.new(app, nil) env = fake_environment @@ -79,6 +85,7 @@ module Action end it "for multiple private network IPs host visible IP cannot be retrieved w/o starting the VM" do + skip('Not working on Windows, since it will also do the network config') if Vagrant::Util::Platform.windows? app = proc {} setup = Setup.new(app, nil) env = fake_environment @@ -90,6 +97,7 @@ module Action end it "for multiple private network IPs host visible IP cant be retrieved if host_ip_address is set" do + skip('Not working on Windows, since it will also do the network config') if Vagrant::Util::Platform.windows? app = proc {} setup = Setup.new(app, nil) env = fake_environment @@ -103,6 +111,7 @@ module Action describe 'after boot' do it "stores the machine's hostname => ip address" do + skip('Not working on Windows, since it will also do the network config') if Vagrant::Util::Platform.windows? app = proc {} setup = Setup.new(app, nil) env = fake_environment @@ -113,6 +122,7 @@ module Action end it "does nothing if it is not enabled via config" do + skip('Not working on Windows, since it will also do the network config') if Vagrant::Util::Platform.windows? app = proc {} setup = Setup.new(app, nil) env = fake_environment(enabled: false) diff --git a/test/landrush/resolver_config_test.rb b/test/landrush/resolver_config_test.rb index 2c4849d..1455e14 100644 --- a/test/landrush/resolver_config_test.rb +++ b/test/landrush/resolver_config_test.rb @@ -5,8 +5,6 @@ module Landrush describe 'ensure_config_exists' do it 'writes a resolver config on the host if one is not already there' do resolver_config = ResolverConfig.new(fake_environment) - skip("Only supported on OSX") unless resolver_config.osx? - resolver_config.config_file.exist?.must_equal false resolver_config.ensure_config_exists! resolver_config.config_file.exist?.must_equal true diff --git a/test/landrush/util/rety_test.rb b/test/landrush/util/rety_test.rb new file mode 100644 index 0000000..68fcfca --- /dev/null +++ b/test/landrush/util/rety_test.rb @@ -0,0 +1,50 @@ +require_relative '../../test_helper' + +class DummyClass + include Landrush::Util::Retry +end + +module Landrush + module Util + describe Retry do + before do + @dummy = DummyClass.new + end + + describe 'retry' do + it 'retries the provided block up to the specified count' do + retries = 0 + result = @dummy.retry(tries: 2) do + retries += 1 + false + end + retries.must_equal 2 + result.must_equal false + end + + it 'does not retry if \'true\' is returned' do + retries = 0 + result = @dummy.retry(tries: 2) do + retries += 1 + true + end + retries.must_equal 1 + result.must_equal true + end + + it 'does sleep between executions if requested' do + retries = 0 + t1 = Time.now + result = @dummy.retry(tries: 1, sleep: 1) do + retries += 1 + false + end + t2 = Time.now + retries.must_equal 1 + result.must_equal false + assert t2 - t1 > 1 + end + end + end + end +end diff --git a/test/landrush/win_network_config_test.rb b/test/landrush/win_network_config_test.rb new file mode 100644 index 0000000..c778274 --- /dev/null +++ b/test/landrush/win_network_config_test.rb @@ -0,0 +1,70 @@ +require_relative '../test_helper' + +module Landrush + TEST_IP = '10.42.42.42'.freeze + + describe WinNetworkConfig do + before do + @vboxmanage_found = !Vagrant::Util::Which.which('VBoxManage').nil? + @has_admin_privileges = Landrush::WinNetworkConfig.admin_mode? + end + + describe 'modify DNS settings of network adapter' do + it 'sets 127.0.0.1 as DNS server on the interface' do + skip('Only supported on Windows') unless Vagrant::Util::Platform.windows? && @vboxmanage_found && @has_admin_privileges + + # VBoxManage uses the network description for its commands whereas netsh uses the name + # We need to get both + begin + old_network_state = network_state + network_description = create_test_interface + new_network_state = network_state + network_name = get_network_name(old_network_state, new_network_state) + + get_dns_for_name(network_name).must_be_nil + + config = Landrush::WinNetworkConfig.new(fake_environment) + config.ensure_prerequisites.must_equal true + config.update_network_adapter(TEST_IP, '127.0.0.1', 'landrush.test') + + get_dns_for_name(network_name).must_equal '127.0.0.1' + rescue + delete_test_interface network_description + end + end + end + + def network_state + `netsh interface ip show config`.split(/\n/).reject(&:empty?) + end + + def get_network_name(old_network_state, new_network_state) + new_network_state.reject! { |line| old_network_state.include? line } + new_network_state[0].match(/.*\"(.*)\"$/).captures[0] + end + + # Creates a test interface using VBoxMange and sets a known test IP + def create_test_interface + cmd_out = `VBoxManage hostonlyif create` + network_description = cmd_out.match(/.*'(.*)'.*/).captures[0] + `VBoxManage.exe hostonlyif ipconfig \"#{network_description}\" --ip #{TEST_IP}` + sleep 3 + network_description + end + + def delete_test_interface(name) + `VBoxManage hostonlyif remove \"#{name}\"` + end + + def get_dns_for_name(name) + cmd_out = `netsh interface ip show config name=\"#{name}\"` + dns = cmd_out.split(/\n/).select { |settings| settings.match(/Statically Configured DNS Servers/m) } + # TODO: better error handling + begin + dns[0].match(/.* (\d{1,3}\.\d{1,3}\.\d{1,3}.\d{1,3}).*/).captures[0] + rescue + return nil + end + end + end +end diff --git a/test/support/fake_ui.rb b/test/support/fake_ui.rb index 0ff2302..204e1c2 100644 --- a/test/support/fake_ui.rb +++ b/test/support/fake_ui.rb @@ -1,4 +1,5 @@ class FakeUI def self.info(*args) + # puts "#{args.join(' ')}" end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 8d7dc43..03ab50c 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -7,6 +7,8 @@ require 'landrush/cap/linux/configured_dns_servers' require 'landrush/cap/linux/redirect_dns' require 'landrush/cap/all/read_host_visible_ip_address' +require 'landrush/win_network_config' +require 'landrush/util/retry' require 'minitest/autorun' require 'mocha/mini_test'