Skip to content
This repository has been archived by the owner on Jun 25, 2023. It is now read-only.

Enhancement: IP/NIC selection #122

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions lib/landrush/cap/linux/read_host_visible_ip_address.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,27 @@ module ReadHostVisibleIpAddress
# TODO: Find a better heuristic for this implementation.
#
def self.read_host_visible_ip_address(machine)
result = ""
machine.communicate.execute(command) do |type, data|
landrush_ip = Landrush::Ip.new(machine, '/usr/local/bin/landrush-ip')

if landrush_ip.install
cmd = '/usr/local/bin/landrush-ip'

unless machine.config.landrush.exclude.nil?
[*machine.config.landrush.exclude].each do |iface|
cmd << " -exclude '#{iface}'"
end
end

unless machine.config.landrush.interface.nil?
cmd << " #{machine.config.landrush.interface}"
end
else
machine.env.ui.warn('Warning, unable to install landrush-ip on guest, falling back to `hostname -I`')
cmd = command
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happened to the fallback to hostname? How is this handled now?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't used anymore. We could add it in as an ultimate fallback if the Go binary fails I suppose, but I don't see how that could happen, not on Linux anyway.


result = ''
machine.communicate.execute(cmd) do |type, data|
result << data if type == :stdout
end

Expand All @@ -32,14 +51,14 @@ def self.read_host_visible_ip_address(machine)
addresses = addresses.reject { |address| address.ipv6? }

if addresses.empty?
raise "Cannot detect IP address, command `#{command}` returned `#{result}`"
raise "Cannot detect IP address, command `#{cmd}` returned `#{result}`"
end

addresses.last.to_s
end

def self.command
%Q(hostname -I)
'hostname -I'
end
end
end
Expand Down
8 changes: 7 additions & 1 deletion lib/landrush/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ class Config < Vagrant.plugin('2', :config)
attr_accessor :upstream_servers
attr_accessor :host_ip_address
attr_accessor :guest_redirect_dns
attr_accessor :interface
attr_accessor :exclude

DEFAULTS = {
:enabled => false,
:tld => 'vagrant.test',
:upstream_servers => [[:udp, '8.8.8.8', 53], [:tcp, '8.8.8.8', 53]],
:host_ip_address => nil,
:guest_redirect_dns => true
:guest_redirect_dns => true,
:interface => nil,
:exclude => %w(lo[0-9]* docker[0-9]+ tun[0-9]+)
}

def initialize
Expand All @@ -22,6 +26,8 @@ def initialize
@upstream_servers = UNSET_VALUE
@host_ip_address = UNSET_VALUE
@guest_redirect_dns = UNSET_VALUE
@interface = UNSET_VALUE
@exclude = UNSET_VALUE
end

def enable
Expand Down
176 changes: 176 additions & 0 deletions lib/landrush/landrush_ip.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
require 'vagrant/util/retryable'

module Landrush
class Ip
include Vagrant::Util::Retryable

#
# Constructor. Takes a machine and a path of where to put the binary
#
def initialize(machine, path)
@machine = machine
@path = path
@error = nil

#
# These are the platforms and architectures supported by the binary
# They are not necessarily supported as a Guest OS in Vagrant (yet).
#
@platforms = {
:darwin => %w(386 amd64),
:openbsd => %w(386 amd64),
:freebsd => %w(386 amd64 arm),
:netbsd => %w(386 amd64 arm),
:linux => %w(386 amd64 arm),
:windows => %w(386 amd64),
:plan9 => %w(386),
}
end

def info(msg)
@machine.env.ui.info "[landrush-ip] #{msg}"
end

def error
@error
end

#
# This installs landrush-ip.
#
# landrush-ip is small binary written in GoLang that iterates over all network interfaces.
# This binary is available for all platforms and behaves the same on all of them.
#
# By default, it behaves the same as hostname -I does on UNIX systems; it dumps every interface's IP.
# It however also allows us to filter out a specific interface or to exclude some.
#
def install(platform = nil, arch = nil)
# Check what platform we're on
if platform.nil?
@machine.guest.capability_host_chain.each do |os|
next unless @platforms.has_key?(os[0].to_s.to_sym)

platform = os[0].to_s.to_sym
end
end

if platform.nil? or !@platforms.has_key?(platform)
@error = "Unsupported guest platform: #{platform}"

return false
end

# Now let's check the architecture
if arch.nil?
case platform
when :windows
#
# See:
# - http://superuser.com/a/293143
# - http://blogs.msdn.com/b/david.wang/archive/2006/03/26/howto-detect-process-bitness.aspx
#
# Quicker than using systeminfo and works from XP and up.
#
script = <<-EOH
set Arch=x64
if "%PROCESSOR_ARCHITECTURE%" == "x86" (
if not defined PROCESSOR_ARCHITEW6432 set Arch=x86
)
echo %Arch%
EOH

result = ''
@machine.communicate.execute(script) do |type, data|
result << data if type == :stdout
end

if result =~ /(x64)/i
arch = 'amd64'
elsif result =~ /(x86)/i
arch = '386'
end
else
#
# Windows aside, every supported UNIX flavour includes uname
# For once there's a UNIX tool that actually behaves relatively consistently across platforms.
#
result = ''
@machine.communicate.execute('uname -mrs') do |type, data|
result << data if type == :stdout
end

#
# uname -mrs will return the following architectures:
# i386 i686 x86_64 ia64 alpha amd64 arm armeb armel hppa m32r m68k
# mips mipsel powerpc ppc64 s390 s390x sh3 sh3eb sh4 sh4eb sparc
#
# The vast majority are irrelevant to us.
#
if result =~ /(x86_64|ia64|amd64)/i
arch = 'amd64'
elsif result =~ /(arm)/i
arch = 'arm'
elsif result =~ /(i386|i686)/i
arch = '386'
end
end
end

if arch.nil? or !@platforms[platform].include?(arch)
@error = "Unsupported guest architecture: #{arch} (#{platform})"

return false
end

# We've got platform and architecture now
info "Platform: #{platform}/#{arch}"

@machine.communicate.tap do |comm|
ssh_info = nil
retryable(on: Vagrant::Errors::SSHNotReady, tries: 3, sleep: 2) do
ssh_info = @machine.ssh_info
raise Vagrant::Errors::SSHNotReady if ssh_info.nil?
end

host_path = @machine.env.tmp_path.join("#{@machine.id}-landrush-ip")
host_path.delete if host_path.file?

begin
http = Net::HTTP.new('api.github.com', 443)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER

response = http.request(Net::HTTP::Get.new('/repos/werelds/landrush-ip/releases/latest'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really want to include latest? Would it not be better to use an explicit tag? Otherwise behavior of a given Landrush version might change, if landrush-ip gets a new release which can change to hard to track problems? WDYT?

result = JSON.parse(response.body)

release_url = nil
unless result['assets'].nil?
result['assets'].each do |asset|
if asset['name'] == "#{platform}_#{arch}_landrush-ip"
release_url = asset['browser_download_url']

break
end
end
end

if release_url.nil?
@error = 'No suitable version of landrush-ip found'

return false
end

info "Using #{release_url}"
Vagrant::Util::Downloader.new(release_url, host_path).download!
comm.upload(host_path, '/tmp/landrush-ip')
comm.sudo("mv /tmp/landrush-ip #{@path}")
comm.sudo("chmod +x #{@path}", error_check: false)
ensure
host_path.delete if host_path.file?
end

return true
end
end
end
end
1 change: 1 addition & 0 deletions lib/landrush/plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def self.post_boot_actions
end

guest_capability('linux', 'read_host_visible_ip_address') do
require_relative 'landrush_ip'
require_relative 'cap/linux/read_host_visible_ip_address'
Cap::Linux::ReadHostVisibleIpAddress
end
Expand Down