Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use pf's anchor to load the rules for nat and port forwarding #8

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 3 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ source 'https://rubygems.org'
gemspec

group :development do
gem 'vagrant', :git => 'https://github.com/mitchellh/vagrant.git', tag: 'v1.8.4'
gem 'vagrant', :git => 'https://github.com/mitchellh/vagrant.git', tag: 'v2.0.0'
end

group :plugins do
gem 'vagrant-bhyve', path: '.'
gemspec
#gem 'vagrant-bhyve', path: '.'
#gem 'vagrant-sshfs', '1.1.0'
#gem 'vagrant-mutate', :git => 'https://github.com/swills/vagrant-mutate'
end
2 changes: 1 addition & 1 deletion lib/vagrant-bhyve/action/create_bridge.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def call(env)
bridge_list = %w(vagrant_bhyve_default_bridge)
# The bridge name is used as created bridge device's description
bridge_list.each do |bridge|
@driver.create_network_device(bridge, "bridge")
@driver.create_network_device(bridge, "bridge", @ui)
@driver.enable_nat(bridge, @ui)
end
@app.call(env)
Expand Down
3 changes: 2 additions & 1 deletion lib/vagrant-bhyve/action/create_tap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ def initialize(app, env)
def call(env)
@machine = env[:machine]
@driver = @machine.provider.driver
@ui = env[:ui]

env[:ui].detail I18n.t('vagrant_bhyve.action.vm.boot.create_tap')
vm_name = @driver.get_attr('vm_name')
tap_name = "vagrant_bhyve_#{vm_name}"
tap_list = [tap_name]
# The switch name is used as created bridge device's description
tap_list.each do |tap|
@driver.create_network_device(tap, "tap")
@driver.create_network_device(tap, "tap", @ui)
end
@app.call(env)
end
Expand Down
5 changes: 3 additions & 2 deletions lib/vagrant-bhyve/action/forward_ports.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ def initialize(app, env)
def call(env)
@machine = env[:machine]
@driver = @machine.provider.driver
@ui = env[:ui]

env[:ui].info I18n.t('vagrant_bhyve.action.vm.forward_ports')
@ui.info I18n.t('vagrant_bhyve.action.vm.forward_ports')

env[:forwarded_ports] = compile_forwarded_ports(@machine.config)
tap_device = @driver.get_attr('tap')
Expand All @@ -25,7 +26,7 @@ def call(env)
guest_port: item[:guest],
host_port: item[:host]
}
@driver.forward_port(forward_information, tap_device)
@driver.forward_port(forward_information, tap_device, @ui)
end
@app.call(env)
end
Expand Down
122 changes: 49 additions & 73 deletions lib/vagrant-bhyve/driver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,29 @@ def load_module(module_name)
end
end

def create_network_device(device_name, device_type)
def check_or_create_default_pfconf(ui)
if execute(true, "test -s /etc/pf.conf") != 0
ui.warn I18n.t("vagrant_bhyve.action.vm.boot.create_default_pfconf")

# probably this could be done in a nicer way with open and puts...
Copy link
Author

Choose a reason for hiding this comment

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

I'll replace this with a File.open(...) block when I'm rebasing the next time.

execute(false, "echo \"nat-anchor \\\"vagrant/*\\\"\" | #{@sudo} tee -a /etc/pf.conf")
execute(false, "echo \"rdr-anchor \\\"vagrant/*\\\"\" | #{@sudo} tee -a /etc/pf.conf")
execute(false, "echo \"anchor \\\"vagrant/*\\\"\" | #{@sudo} tee -a /etc/pf.conf")
restart_service('pf')
else
if execute(true, 'pfctl -sn | grep -q "nat-anchor .vagrant/"') != 0
ui.warn I18n.t("vagrant_bhyve.errors.nat_anchor_not_found")
end
if execute(true, 'pfctl -sn | grep -q "nat-anchor .vagrant/"') != 0
ui.warn I18n.t("vagrant_bhyve.errors.rdr_anchor_not_found")
end
if execute(true, 'pfctl -sr | grep -q "anchor .vagrant/"') != 0
ui.error I18n.t("vagrant_bhyve.errors.anchor_not_found")
end
end
end

def create_network_device(device_name, device_type, ui)
return if device_name.length == 0

# Check whether the bridge has been created
Expand All @@ -167,34 +189,20 @@ def create_network_device(device_name, device_type)
# with the bridge
mtu = execute(false, "ifconfig #{bridge} | head -n1 | awk '{print $NF}'")
execute(false, "#{@sudo} ifconfig #{interface_name} mtu #{mtu}") if mtu.length != 0 and mtu != '1500'
execute(false, "#{@sudo} ifconfig #{bridge} addm #{interface_name}")
if execute(true, "ifconfig #{bridge} | grep -q \"member: #{interface_name}\"") != 0
execute(false, "#{@sudo} ifconfig #{bridge} addm #{interface_name}")
else
ui.warn "#{interface_name} is already a member of #{bridge}"
end

# Setup VM-specific pf rules
id = get_attr('id')
pf_conf = @data_dir.join('pf.conf')
pf_conf.open('w') do |f|
f.puts "set skip on #{interface_name}"
f.puts "pass quick on #{interface_name}"
end
comment_mark = "# vagrant-bhyve #{interface_name}"
if execute(true, "test -s /etc/pf.conf") == 0
if execute(true, "grep \"#{comment_mark}\" /etc/pf.conf") != 0
comment_mark_bridge = "# vagrant-bhyve #{bridge}"
if execute(true, "grep \"#{comment_mark_bridge}\" /etc/pf.conf") != 0
execute(false, "#{@sudo} sed -i '' '1i\\\n#{comment_mark}\n' /etc/pf.conf")
execute(false, "#{@sudo} sed -i '' '2i\\\ninclude \"#{pf_conf.to_s}\"\n' /etc/pf.conf")
else
bridge_line = execute(false, "grep -A 1 \"#{comment_mark_bridge}\" /etc/pf.conf | tail -1")
bridge_line = bridge_line.gsub("\"", "\\\"")
bridge_line = bridge_line.gsub("/", "\\/")
execute(false, "#{@sudo} sed -i '' '/#{bridge_line}/a\\\n#{comment_mark}\n' /etc/pf.conf")
execute(false, "#{@sudo} sed -i '' '/#{comment_mark}/a\\\ninclude \"#{pf_conf.to_s}\"\n' /etc/pf.conf")
end
end
else
execute(false, "echo \"#{comment_mark}\" | #{@sudo} tee -a /etc/pf.conf")
execute(false, "echo \"include \\\"#{pf_conf.to_s}\\\"\" | #{@sudo} tee -a /etc/pf.conf")
end
restart_service('pf')
#execute(false, "#{@sudo} pfctl -a '/vagrant_#{id}' -f #{pf_conf.to_s}")
check_or_create_default_pfconf(ui)
execute(false, "#{@sudo} pfctl -a 'vagrant/#{id}' -f #{pf_conf.to_s}")
#if !pf_enabled?
# execute(false, "#{@sudo} pfctl -e")
#end
Expand Down Expand Up @@ -225,29 +233,16 @@ def enable_nat(bridge, ui)
execute(false, "#{@sudo} sysctl net.inet.ip.forwarding=1 >/dev/null 2>&1")
execute(false, "#{@sudo} sysctl net.inet6.ip6.forwarding=1 >/dev/null 2>&1")

# Change pf's configuration
pf_conf = directory.join("pf.conf")
pf_conf.open("w") do |pf_file|
pf_file.puts "set skip on #{bridge_name}"
check_or_create_default_pfconf(ui)
# set up bridge pf anchor
pf_bridge_conf = "/usr/local/etc/pf.#{bridge_name}.conf"
Copy link
Author

Choose a reason for hiding this comment

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

Arguably this should also be somewhere in @data_dir, as it's not really permanent configuration...

File.open(pf_bridge_conf, "w") do |pf_file|
pf_file.puts "nat on #{gateway} from {#{sub_net}.0/24} to any -> (#{gateway})"
pf_file.puts "pass quick on #{bridge_name}"
end
pf_bridge_conf = "/usr/local/etc/pf.#{bridge_name}.conf"
comment_mark = "# vagrant-bhyve #{bridge_name}"
execute(false, "#{@sudo} mv #{pf_conf.to_s} #{pf_bridge_conf}")
if execute(true, "test -s /etc/pf.conf") == 0
if execute(true, "grep \"#{comment_mark}\" /etc/pf.conf") != 0
execute(false, "#{@sudo} sed -i '' '1i\\\n#{comment_mark}\n' /etc/pf.conf")
execute(false, "#{@sudo} sed -i '' '2i\\\ninclude \"#{pf_bridge_conf}\"\n' /etc/pf.conf")
end
else
execute(false, "echo \"#{comment_mark}\" | #{@sudo} tee -a /etc/pf.conf")
execute(false, "echo \"include \\\"#{pf_bridge_conf}\\\"\" | #{@sudo} tee -a /etc/pf.conf")
end
restart_service('pf')

# Use pfctl to enable pf rules
#execute(false, "#{@sudo} cp #{pf_conf.to_s} /usr/local/etc/pf.#{bridge_name}.conf")
#execute(false, "#{@sudo} pfctl -a '/vagrant_#{bridge_name}' -f /usr/local/etc/pf.#{bridge_name}.conf")
# execute(false, "#{@sudo} pfctl -a '/vagrant_#{bridge_name}' -sr")
execute(false, "#{@sudo} pfctl -a 'vagrant/#{bridge_name}' -f /usr/local/etc/pf.#{bridge_name}.conf")

# Create a basic dnsmasq setting
# Basic settings
Expand Down Expand Up @@ -457,31 +452,19 @@ def shutdown(ui)
end
end

def forward_port(forward_information, tap_device)
def forward_port(forward_information, tap_device, ui)
id = get_attr('id')
ip_address = get_ip_address(tap_device)
pf_conf = @data_dir.join('pf.conf')
rule = "rdr on #{forward_information[:adapter]} proto {udp, tcp} from any to any port #{forward_information[:host_port]} -> #{ip_address} port #{forward_information[:guest_port]}"

# FIXME: does this work for multiple port forwards, or should we rather set up a list with them and template that out to the pf.conf file?
pf_conf.open('a') do |pf_file|
pf_file.puts rule
end
# Update pf rules
comment_mark = "# vagrant-bhyve #{tap_device}"
if execute(true, "test -s /etc/pf.conf") == 0
if execute(true, "grep \"#{comment_mark}\" /etc/pf.conf") != 0
execute(false, "#{@sudo} sed -i '' '1i\\\n#{comment_mark}\n' /etc/pf.conf")
execute(false, "#{@sudo} sed -i '' '2i\\\ninclude \"#{pf_conf.to_s}\"\n' /etc/pf.conf")
end
else
execute(false, "echo \"#{comment_mark}\" | #{@sudo} tee -a /etc/pf.conf")
execute(false, "echo \"include \\\"#{pf_conf.to_s}\\\"\" | #{@sudo} tee -a /etc/pf.conf")
end
restart_service('pf')
#execute(false, "#{@sudo} pfctl -a '/vagrant_#{id}' -f #{pf_conf.to_s}")
#execute(false, "#{@sudo} pfctl -a '/vagrant_#{id}' -sr")
#execute(false, "#{@sudo} pfctl -a vagrant_#{id} -F all")

check_or_create_default_pfconf(ui)
execute(false, "#{@sudo} pfctl -a 'vagrant/#{id}' -f #{pf_conf.to_s}")
end

def cleanup
Expand All @@ -497,11 +480,8 @@ def cleanup
execute(false, "#{@sudo} bhyvectl --destroy --vm=#{vm_name} >/dev/null 2>&1") if state(vm_name) == :uncleaned

# Clean instance-specific pf rules
#execute(false, "#{@sudo} pfctl -a '/vagrant_#{id}' -F all")
comment_mark_tap = "# vagrant-bhyve #{tap}"
if execute(true, "grep \"#{comment_mark_tap}\" /etc/pf.conf") == 0
execute(false, "#{@sudo} sed -i '' '/#{comment_mark_tap}/,+1d' /etc/pf.conf")
end
execute(false, "#{@sudo} pfctl -a 'vagrant/#{id}' -F all")

# Destory tap interfaces
execute(false, "#{@sudo} ifconfig #{tap} destroy") if execute(true, "ifconfig #{tap}") == 0
execute(false, "#{@sudo} sed -i '' '/#{mac}/d' /var/run/dnsmasq.#{bridge}.leases") if execute(true, "grep \"#{mac}\" /var/run/dnsmasq.#{bridge}.leases") == 0
Expand All @@ -516,19 +496,15 @@ def cleanup
member_num = execute(false, "ifconfig #{bridge} | grep -c 'member' || true") if bridge_exist == 0

if bridge_exist != 0 || member_num.to_i < 2
#execute(false, "#{@sudo} pfctl -a '/vagrant_#{bridge}' -F all")
comment_mark_bridge = "# vagrant-bhyve #{bridge}"
if execute(true, "grep \"#{comment_mark_bridge}\" /etc/pf.conf") == 0
execute(false, "#{@sudo} sed -i '' '/#{comment_mark_bridge}/,+1d' /etc/pf.conf")
end
restart_service('pf')
execute(false, "#{@sudo} pfctl -a 'vagrant/#{bridge}' -F all")

#if directory.join('pf_disabled').exist?
# FileUtils.rm directory.join('pf_disabled')
# execute(false, "#{@sudo} pfctl -d")
#end
execute(false, "#{@sudo} ifconfig #{bridge} destroy") if bridge_exist == 0
pf_conf = "/usr/local/etc/pf.#{bridge}.conf"
execute(false, "#{@sudo} rm #{pf_conf}") if execute(true, "test -e #{pf_conf}") == 0
pf_bridge_conf = "/usr/local/etc/pf.#{bridge}.conf"
execute(false, "#{@sudo} rm #{pf_bridge_conf}") if execute(true, "test -e #{pf_bridge_conf}") == 0
if execute(true, "test -e /var/run/dnsmasq.#{bridge}.pid") == 0
dnsmasq_cmd = "dnsmasq -C /usr/local/etc/dnsmasq.#{bridge}.conf -l /var/run/dnsmasq.#{bridge}.leases -x /var/run/dnsmasq.#{bridge}.pid"
dnsmasq_conf = "/var/run/dnsmasq.#{bridge}.leases"
Expand Down
16 changes: 16 additions & 0 deletions locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ en:
forward_ports: |-
Setup port forwarding(if any).
boot:
create_default_pfconf: |-
It seems you had no default pf configuration. A basic default
was set up for you in /etc/pf.conf
setup_nat: |-
Setting up a nat environment using pf and dnsmasq to provide
network for VMs. You will need to have dnsmasq installed on
Expand Down Expand Up @@ -88,3 +91,16 @@ en:
not_found_leases_info: |-
Unable to found IP from dnsmasq's leases file, please retry after a
few seconds.
nat_anchor_not_found: |-
NAT will not work: your PF configuration does not contain a NAT
anchor. Add 'nat-anchor "vagrant/*"' to your PF configuration in
the TRANSLATION section. See pf.conf(5) for details.
rdr_anchor_not_found: |-
Port forwarding will not work: your PF configuration does not
contain a redirection anchor. Add 'rdr-anchor "vagrant/*"' to your
PF configuration in the TRANSLATION section. See pf.conf(5) for
details.
anchor_not_found: |-
Your PF configuration does not contain a filter anchor. Add
'anchor "vagrant/*"' to your PF configuration in the TRANSLATION
section. See pf.conf(5) for details.