From f4f76b78d88471cde3b9e165dbacffd29f2bc817 Mon Sep 17 00:00:00 2001 From: Anton Todorov Date: Fri, 15 Nov 2019 09:53:15 +0200 Subject: [PATCH] B #3354: implement spoofing filters handle ARP + Alias IPs handle IPv4 + Alias IPs handle IPv6 + Alias IPs, SLAAC handle HOTPLUG_NIC events for Alias IPs via a hook --- install.sh | 16 +- share/hooks/alias_ip/vnm_filter.rb | 194 +++++++++++++ src/vnm_mad/remotes/802.1Q/clean.d/.gitignore | 1 + .../remotes/802.1Q/clean.d/vnm_filter_clean | 1 + src/vnm_mad/remotes/802.1Q/post.d/.gitignore | 1 + .../remotes/802.1Q/post.d/vnm_filter_post | 1 + src/vnm_mad/remotes/common/vnm_filter_clean | 36 +++ src/vnm_mad/remotes/common/vnm_filter_post | 36 +++ src/vnm_mad/remotes/fw/clean.d/.gitignore | 1 + .../remotes/fw/clean.d/vnm_filter_clean | 1 + src/vnm_mad/remotes/fw/post.d/.gitignore | 1 + src/vnm_mad/remotes/fw/post.d/vnm_filter_post | 1 + src/vnm_mad/remotes/lib/vnm_filter.rb | 258 ++++++++++++++++++ 13 files changed, 547 insertions(+), 1 deletion(-) create mode 100755 share/hooks/alias_ip/vnm_filter.rb create mode 120000 src/vnm_mad/remotes/802.1Q/clean.d/vnm_filter_clean create mode 120000 src/vnm_mad/remotes/802.1Q/post.d/vnm_filter_post create mode 100755 src/vnm_mad/remotes/common/vnm_filter_clean create mode 100755 src/vnm_mad/remotes/common/vnm_filter_post create mode 120000 src/vnm_mad/remotes/fw/clean.d/vnm_filter_clean create mode 120000 src/vnm_mad/remotes/fw/post.d/vnm_filter_post create mode 100644 src/vnm_mad/remotes/lib/vnm_filter.rb diff --git a/install.sh b/install.sh index 1f773340ab2..c08459dca7b 100755 --- a/install.sh +++ b/install.sh @@ -633,11 +633,15 @@ INSTALL_FILES=( NETWORK_HOOKS_CLEAN_FILES:$VAR_LOCATION/remotes/vnm/hooks/clean NETWORK_ETC_FILES:$VAR_LOCATION/remotes/etc/vnm NETWORK_8021Q_FILES:$VAR_LOCATION/remotes/vnm/802.1Q + NETWORK_8021Q_CLEAN_FILES:$VAR_LOCATION/remotes/vnm/802.1Q/clean.d + NETWORK_8021Q_POST_FILES:$VAR_LOCATION/remotes/vnm/802.1Q/post.d NETWORK_VXLAN_FILES:$VAR_LOCATION/remotes/vnm/vxlan NETWORK_DUMMY_FILES:$VAR_LOCATION/remotes/vnm/dummy NETWORK_BRIDGE_FILES:$VAR_LOCATION/remotes/vnm/bridge NETWORK_EBTABLES_FILES:$VAR_LOCATION/remotes/vnm/ebtables NETWORK_FW_FILES:$VAR_LOCATION/remotes/vnm/fw + NETWORK_FW_CLEAN_FILES:$VAR_LOCATION/remotes/vnm/fw/clean.d + NETWORK_FW_POST_FILES:$VAR_LOCATION/remotes/vnm/fw/post.d NETWORK_OVSWITCH_FILES:$VAR_LOCATION/remotes/vnm/ovswitch NETWORK_OVSWITCH_VXLAN_FILES:$VAR_LOCATION/remotes/vnm/ovswitch_vxlan NETWORK_VCENTER_FILES:$VAR_LOCATION/remotes/vnm/vcenter @@ -1478,6 +1482,7 @@ NETWORK_FILES="src/vnm_mad/remotes/lib/vnm_driver.rb \ src/vnm_mad/remotes/lib/no_vlan.rb \ src/vnm_mad/remotes/lib/security_groups.rb \ src/vnm_mad/remotes/lib/security_groups_iptables.rb \ + src/vnm_mad/remotes/lib/vnm_filter.rb \ src/vnm_mad/remotes/lib/nic.rb" NETWORK_HOOKS_PRE_FILES="src/vnm_mad/remotes/hooks/pre/firecracker" @@ -1490,6 +1495,10 @@ NETWORK_8021Q_FILES="src/vnm_mad/remotes/802.1Q/clean \ src/vnm_mad/remotes/802.1Q/update_sg \ src/vnm_mad/remotes/802.1Q/vlan_tag_driver.rb" +NETWORK_8021Q_CLEAN_FILES="src/vnm_mad/remotes/802.1Q/clean.d/vnm_filter_clean" + +NETWORK_8021Q_POST_FILES="src/vnm_mad/remotes/802.1Q/post.d/vnm_filter_post" + NETWORK_VXLAN_FILES="src/vnm_mad/remotes/vxlan/clean \ src/vnm_mad/remotes/vxlan/post \ src/vnm_mad/remotes/vxlan/pre \ @@ -1519,6 +1528,10 @@ NETWORK_FW_FILES="src/vnm_mad/remotes/fw/post \ src/vnm_mad/remotes/fw/update_sg \ src/vnm_mad/remotes/fw/clean" +NETWORK_FW_CLEAN_FILES="src/vnm_mad/remotes/fw/clean.d/vnm_filter_clean" + +NETWORK_FW_POST_FILES="src/vnm_mad/remotes/fw/post.d/vnm_filter_post" + NETWORK_OVSWITCH_FILES="src/vnm_mad/remotes/ovswitch/clean \ src/vnm_mad/remotes/ovswitch/post \ src/vnm_mad/remotes/ovswitch/pre \ @@ -2026,7 +2039,8 @@ HOOK_RAFT_FILES="share/hooks/raft/vip.sh" # HOOK scripts, to be installed under $VAR_LOCATION/remotes/hooks/alias_ip #------------------------------------------------------------------------------- -HOOK_ALIAS_IP_FILES="share/hooks/alias_ip/alias_ip.rb" +HOOK_ALIAS_IP_FILES="share/hooks/alias_ip/alias_ip.rb \ + share/hooks/alias_ip/vnm_filter.rb" #------------------------------------------------------------------------------- # Installation scripts, to be installed under $SHARE_LOCATION diff --git a/share/hooks/alias_ip/vnm_filter.rb b/share/hooks/alias_ip/vnm_filter.rb new file mode 100755 index 00000000000..50ddcbb753b --- /dev/null +++ b/share/hooks/alias_ip/vnm_filter.rb @@ -0,0 +1,194 @@ +#!/usr/bin/env ruby + +# -------------------------------------------------------------------------- # +# Copyright 2002-2019, 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. # +#--------------------------------------------------------------------------- # + +# hook definition +# +#NAME = "vnm_filter" +#TYPE = "state" +#ON = "CUSTOM" +#ARGUMENTS = "$TEMPLATE" +#ARGUMENTS_STDIN="YES" +#COMMAND="alias_ip/vnm_filter.rb" +#REMOTE="YES" +#RESOURCE="VM" +#STATE="ACTIVE" +#LCM_STATE="HOTPLUG_NIC" + +ONE_LOCATION = ENV['ONE_LOCATION'] + +if !ONE_LOCATION + RUBY_LIB_LOCATION = '/usr/lib/one/ruby' + GEMS_LOCATION = '/usr/share/one/gems' + PACKET_LOCATION = '/usr/lib/one/ruby/vendors/packethost/lib' + LOG_FILE = '/var/log/one/hook-alias_ip.log' +else + RUBY_LIB_LOCATION = ONE_LOCATION + '/lib/ruby' + GEMS_LOCATION = ONE_LOCATION + '/share/gems' + PACKET_LOCATION = ONE_LOCATION + '/ruby/vendors/packethost/lib' + LOG_FILE = ONE_LOCATION + '/var/net_fw_hook.log' +end + +if File.directory?(GEMS_LOCATION) + Gem.use_paths(GEMS_LOCATION) +end + +$LOAD_PATH << RUBY_LIB_LOCATION +$LOAD_PATH << PACKET_LOCATION + +require 'base64' +require 'nokogiri' +require 'open3' +require 'shellwords' +require 'syslog/logger' + +########## +# Helpers + +@slog = Syslog::Logger.new 'vnm_filter_hook' + +def log(msg, level = 'I') + msg.lines do |line| + puts(line) + @slog.info "[#{level}] #{line}" + end +end + +def log_error(msg) + log(msg, 'E') +end + +def get_data(xpath, entries) + data = Hash.new + xentry = VM_XML.xpath(xpath) + entries.each do |e| + val = xentry.xpath(e) + if !val.nil? + key = e.downcase.to_sym + if e.end_with?("_ID") + data[key] = val.text.to_i + else + data[key] = val.text + end + end + end + data +end + +def alias_nic_data() + xpath = '//TEMPLATE/NIC_ALIAS[ATTACH="YES"]' + entries = %w[ALIAS_ID PARENT_ID NAME IP IP6 IP6_GLOBAL IP6_LINK] + get_data(xpath, entries) +end + +def nic_data(nic_id) + xpath = "//TEMPLATE/NIC[NIC_ID=#{nic_id}]" + entries = %w[IP IP6 IP6_GLOBAL IP6_LINK VN_MAD ALIAS_IDS + FILTER FILTER_IP_SPOOFING FILTER_MAC_SPOOFING] + get_data(xpath, entries) +end + +def vm_data() + vm = Hash.new + data = get_data("//VM", %w[ID]) + vm[:id] = VM_XML.xpath('//VM/ID').text.to_i + vm[:domain] = "one-#{vm[:id]}" + vm[:a] = alias_nic_data() + nic_id = vm[:a][:parent_id] + vm[:n] = nic_data(nic_id) + + vm[:nicdev] = "#{vm[:domain]}-#{nic_id}" + vm[:a][:idx] = vm[:a][:name].split('_ALIAS')[1].to_i + + vm[:action] = 'del' + if !vm[:n][:alias_ids].nil? and !vm[:n][:alias_ids].empty? + vm[:n][:alias_ids].split(',').each do |idx| + if vm[:a][:idx] == idx.to_i + vm[:action] = 'add' + end + end + end + #log("#{vm}") + vm +end + +def run(cmds) + cmd = String.new + cmds.each do |c| + cmd.concat(" #{Shellwords.escape(c)}") + end + stdout, stderr, status = Open3.capture3(cmd) + log("(#{status.exitstatus}) #{cmd}") + if !status.success? + log_error("PID[#{status.pid}] #{stderr}") + end +end + +def toggle_ebtables_filter(vm) + if !vm[:a][:ip].nil? and !vm[:a][:ip].empty? + action = vm[:action]=='add'? '-A' : '-D' + ['i', 'o'].each do |d| + rule = d=='o'? '--arp-ip-dst' : '--arp-ip-src' + chain = "#{vm[:nicdev]}-#{d}-arp4" + run(['sudo', 'ebtables', '--concurrent', '-t', 'nat', action, + chain, '-p', 'ARP', rule, vm[:a][:ip], '-j', 'RETURN']) + end + end +end + +def toggle_ipset_filter(vm) + ['IP', 'IP6', 'IP6_GLOBAL'].each do |e| + key = e.downcase.to_sym + if !vm[:a][key].nil? and !vm[:a][key].empty? + chain = "#{vm[:nicdev]}-#{e.split('_')[0].downcase}-spoofing" + run(['sudo', 'ipset', '-exist', vm[:action], chain, vm[:a][key]]) + if e == 'IP6_CLOBAL' and !vm[:a][:ip6_link].nil? + link = vm[:a][:ip6_link] + run(['sudo', 'ipset', '-exist', vm[:action], chain, link]) + end + end + end +end + + +############################################################################### +# Main +# + +log("Net fw hook BEGIN") + +vm_xml_raw = Base64.decode64(STDIN.read) +vm_xml = Nokogiri::XML(vm_xml_raw) +VM_XML = vm_xml + +vm = vm_data() + +filters = Hash.new +filters[:filter_ip_spoofing] = method(:toggle_ipset_filter) +filters[:filter_mac_spoofing] = method(:toggle_ebtables_filter) + +filters.each do |key, method| + if !vm[:n][key].nil? + if vm[:n][key] == 'YES' + method.(vm) + end + end +end + +log('Net fw hook END') + +exit 0 diff --git a/src/vnm_mad/remotes/802.1Q/clean.d/.gitignore b/src/vnm_mad/remotes/802.1Q/clean.d/.gitignore index cfb61272111..a8951e0126f 100644 --- a/src/vnm_mad/remotes/802.1Q/clean.d/.gitignore +++ b/src/vnm_mad/remotes/802.1Q/clean.d/.gitignore @@ -1,3 +1,4 @@ # Do not track files in this directory except for .gitignore file * !.gitignore +!vnm_filter_clean diff --git a/src/vnm_mad/remotes/802.1Q/clean.d/vnm_filter_clean b/src/vnm_mad/remotes/802.1Q/clean.d/vnm_filter_clean new file mode 120000 index 00000000000..a658c0fcff3 --- /dev/null +++ b/src/vnm_mad/remotes/802.1Q/clean.d/vnm_filter_clean @@ -0,0 +1 @@ +../../common/vnm_filter_clean \ No newline at end of file diff --git a/src/vnm_mad/remotes/802.1Q/post.d/.gitignore b/src/vnm_mad/remotes/802.1Q/post.d/.gitignore index cfb61272111..9273fb0d227 100644 --- a/src/vnm_mad/remotes/802.1Q/post.d/.gitignore +++ b/src/vnm_mad/remotes/802.1Q/post.d/.gitignore @@ -1,3 +1,4 @@ # Do not track files in this directory except for .gitignore file * !.gitignore +!vnm_filter_post diff --git a/src/vnm_mad/remotes/802.1Q/post.d/vnm_filter_post b/src/vnm_mad/remotes/802.1Q/post.d/vnm_filter_post new file mode 120000 index 00000000000..2c72386ff62 --- /dev/null +++ b/src/vnm_mad/remotes/802.1Q/post.d/vnm_filter_post @@ -0,0 +1 @@ +../../common/vnm_filter_post \ No newline at end of file diff --git a/src/vnm_mad/remotes/common/vnm_filter_clean b/src/vnm_mad/remotes/common/vnm_filter_clean new file mode 100755 index 00000000000..343cebfba45 --- /dev/null +++ b/src/vnm_mad/remotes/common/vnm_filter_clean @@ -0,0 +1,36 @@ +#!/usr/bin/env ruby + +# -------------------------------------------------------------------------- # +# Copyright 2002-2019, 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. # +#--------------------------------------------------------------------------- # + +$: << File.dirname(__FILE__) +$: << File.join(File.dirname(__FILE__), "..") +$: << File.join(File.dirname(__FILE__), "../..") + +require 'vnm_filter' + +template64 = STDIN.read +deploy_id = nil +xpath_filter = nil + +begin + vnmfilter = VnmFilter.from_base64(template64, xpath_filter, deploy_id) + vnmfilter.deactivate +rescue Exception => e + OpenNebula.log_error(e.message) + OpenNebula.log_error(e.backtrace) + exit 1 +end diff --git a/src/vnm_mad/remotes/common/vnm_filter_post b/src/vnm_mad/remotes/common/vnm_filter_post new file mode 100755 index 00000000000..08f4358df42 --- /dev/null +++ b/src/vnm_mad/remotes/common/vnm_filter_post @@ -0,0 +1,36 @@ +#!/usr/bin/env ruby + +# -------------------------------------------------------------------------- # +# Copyright 2002-2019, 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. # +#--------------------------------------------------------------------------- # + +$: << File.dirname(__FILE__) +$: << File.join(File.dirname(__FILE__), "..") +$: << File.join(File.dirname(__FILE__), "../..") + +require 'vnm_filter' + +template64 = STDIN.read +deploy_id = nil +xpath_filter = nil + +begin + vnmfilter = VnmFilter.from_base64(template64, xpath_filter, deploy_id) + vnmfilter.activate +rescue Exception => e + OpenNebula.log_error(e.message) + OpenNebula.log_error(e.backtrace) + exit 1 +end diff --git a/src/vnm_mad/remotes/fw/clean.d/.gitignore b/src/vnm_mad/remotes/fw/clean.d/.gitignore index cfb61272111..a8951e0126f 100644 --- a/src/vnm_mad/remotes/fw/clean.d/.gitignore +++ b/src/vnm_mad/remotes/fw/clean.d/.gitignore @@ -1,3 +1,4 @@ # Do not track files in this directory except for .gitignore file * !.gitignore +!vnm_filter_clean diff --git a/src/vnm_mad/remotes/fw/clean.d/vnm_filter_clean b/src/vnm_mad/remotes/fw/clean.d/vnm_filter_clean new file mode 120000 index 00000000000..a658c0fcff3 --- /dev/null +++ b/src/vnm_mad/remotes/fw/clean.d/vnm_filter_clean @@ -0,0 +1 @@ +../../common/vnm_filter_clean \ No newline at end of file diff --git a/src/vnm_mad/remotes/fw/post.d/.gitignore b/src/vnm_mad/remotes/fw/post.d/.gitignore index cfb61272111..9273fb0d227 100644 --- a/src/vnm_mad/remotes/fw/post.d/.gitignore +++ b/src/vnm_mad/remotes/fw/post.d/.gitignore @@ -1,3 +1,4 @@ # Do not track files in this directory except for .gitignore file * !.gitignore +!vnm_filter_post diff --git a/src/vnm_mad/remotes/fw/post.d/vnm_filter_post b/src/vnm_mad/remotes/fw/post.d/vnm_filter_post new file mode 120000 index 00000000000..2c72386ff62 --- /dev/null +++ b/src/vnm_mad/remotes/fw/post.d/vnm_filter_post @@ -0,0 +1 @@ +../../common/vnm_filter_post \ No newline at end of file diff --git a/src/vnm_mad/remotes/lib/vnm_filter.rb b/src/vnm_mad/remotes/lib/vnm_filter.rb new file mode 100644 index 00000000000..9581556d0b0 --- /dev/null +++ b/src/vnm_mad/remotes/lib/vnm_filter.rb @@ -0,0 +1,258 @@ +# rubocop:disable Naming/FileName +# vim: ts=4 sw=4 et +# -------------------------------------------------------------------------- # +# Copyright 2002-2019, 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 'vnmmad' +require 'syslog/logger' + +# IP filter for aliases +class VnmFilter < VNMMAD::VNMDriver + + DRIVER = 'vnm_filter' + XPATH_FILTER = 'TEMPLATE/NIC|TEMPLATE/NIC_ALIAS' + + def initialize(vm_template, xpath_filter = nil, deploy_id = nil) + @locking = true + @slog = Syslog::Logger.new 'vnm_filter' + xpath_filter ||= XPATH_FILTER + @slog.info "initialize #{xpath_filter}" + super(vm_template, xpath_filter, deploy_id) + end + + def activate + lock + vm_id = vm['ID'] + attach_nic_id = vm['TEMPLATE/NIC[ATTACH="YES"]/NIC_ID'] + @slog.info "activate() VM #{vm_id} (#{attach_nic_id}) BEGIN" + # pre-process + nics = Hash.new + process do |nic| + nic_id = nic[:nic_id] + ip4 = Array.new + ip6 = Array.new + [:ip, :vrouter_ip].each do |key| + if !nic[key].nil? && !nic[key].empty? + ip4 << nic[key] + end + end + [:ip6, :ip6_global, :ip6_link].each do |key| + if !nic[key].nil? && !nic[key].empty? + ip6 << nic[key] + end + end + if !nic[:alias_id].nil? + parent_id = nic[:parent_id] + if nics[parent_id].nil? + nics[parent_id] = Hash.new + nics[parent_id][:ip4] = Array.new + nics[parent_id][:ip6] = Array.new + end + nics[parent_id][:ip4].push(*ip4) + nics[parent_id][:ip6].push(*ip6) + next + end + if nics[nic_id].nil? + nics[nic_id] = Hash.new + nics[nic_id][:ip4] = ip4 + nics[nic_id][:ip6] = ip6 + else + nics[nic_id][:ip4].push(*ip4) + nics[nic_id][:ip6].push(*ip6) + end + nics[nic_id][:nic] = nic + end + + nics.each do |nic_id, nicdata| + nic = nicdata[:nic] + @slog.info "VM #{vm_id} nic_id #{nic_id} attach_nic_id:#{attach_nic_id}" + OpenNebula.log_info "activate #{vm_id} nic_id #{nic_id} attach_nic_id #{attach_nic_id}" + next if attach_nic_id and attach_nic_id != nic_id + chain = "one-#{vm_id}-#{nic_id}" + chain_i = "#{chain}-i" + chain_o = "#{chain}-o" + + commands = VNMMAD::VNMNetwork::Commands.new + + if nic[:filter_ip_spoofing] == "YES" + @slog.info "VM #{vm_id} NIC #{nic_id} FILTER_IP_SPOOFING" + commands.add :iptables, "-S #{chain_o}" + begin + iptables_s = commands.run! + rescue + @slog.warn "Can't process chain #{chain_o}" + next + end + iptables_s.each_line { |c| @slog.info "[iptables -S] #{c}" } + if iptables_s !~ /#{chain}-ip-spoofing/ + @slog.info "altering #{chain_o} to add #{chain}-ip-spoofing" + commands.add :ipset, "create -exist #{chain}-ip-spoofing hash:ip family inet" + commands.add :iptables, "-R #{chain_o} 2 -m set ! --match-set #{chain}-ip-spoofing src -j DROP" + commands.add :iptables, "-I #{chain_o} 2 -s 0.0.0.0/32 -d 255.255.255.255/32 -p udp -m udp --sport 68 --dport 67 -j RETURN" + end + if !nicdata[:ip4].nil? and !nicdata[:ip4].empty? + nicdata[:ip4].each do |ip| + @slog.info "ipset add #{chain}-ip-spoofing #{ip}" + commands.add :ipset, "add -exist #{chain}-ip-spoofing #{ip}" + end + commands.run! + end + commands.add :ip6tables, "-S #{chain_o}" + ip6tables_s = commands.run! + ip6tables_s.each_line { |c| @slog.info "[ip6tables -S] #{c}" } + if ip6tables_s !~ /#{chain}-ip6-spoofing/ + @slog.debug "altering #{chain_o} to add #{chain}-ip6-spoofing" + commands.add :ipset, "create -exist #{chain}-ip6-spoofing hash:ip family inet6" + commands.add :ip6tables, "-R #{chain_o} 5 -m set ! --match-set #{chain}-ip6-spoofing src -j DROP" + end + if !nicdata[:ip6].nil? and !nicdata[:ip6].empty? + nicdata[:ip6].each do |ip| + @slog.info "ipset add #{chain}-ip6-spoofing #{ip}" + commands.add :ipset, "add -exist #{chain}-ip6-spoofing #{ip}" + end + commands.run! + end + end + + if nic[:filter_mac_spoofing] == "YES" + @slog.info "VM #{vm_id} NIC #{nic_id} FILTER_MAC_SPOOFING" + deactivate_ebtables(chain) + commands.add :ebtables, "-t nat -N #{chain_i}-arp4 -P DROP" + commands.add :ebtables, "-t nat -N #{chain_o}-arp4 -P DROP" + if !nicdata[:ip4].nil? and !nicdata[:ip4].empty? + nicdata[:ip4].each do |ip| + @slog.info "ARP whitelist #{ip} (#{chain})" + commands.add :ebtables, "-t nat -A #{chain_i}-arp4 -p ARP "\ + "--arp-ip-src #{ip} -j RETURN" + commands.add :ebtables, "-t nat -A #{chain_o}-arp4 -p ARP "\ + "--arp-ip-dst #{ip} -j RETURN" + end + end + # Input + commands.add :ebtables, "-t nat -N #{chain_i}-arp -P DROP" + commands.add :ebtables, "-t nat -A #{chain_i}-arp -p ARP "\ + "-s ! #{nic[:mac]} -j DROP" + commands.add :ebtables, "-t nat -A #{chain_i}-arp -p ARP "\ + "--arp-mac-src ! #{nic[:mac]} -j DROP" + commands.add :ebtables, "-t nat -A #{chain_i}-arp -p ARP "\ + "-j #{chain_i}-arp4" + commands.add :ebtables, "-t nat -A #{chain_i}-arp -p ARP "\ + "--arp-op Request -j ACCEPT" + commands.add :ebtables, "-t nat -A #{chain_i}-arp -p ARP "\ + "--arp-op Reply -j ACCEPT" + commands.add :ebtables, "-t nat -N #{chain_i}-rarp -P DROP" + commands.add :ebtables, "-t nat -A #{chain_i}-rarp -p 0x8035 "\ + "-s #{nic[:mac]} -d Broadcast --arp-op Request_Reverse "\ + "--arp-ip-src 0.0.0.0 --arp-ip-dst 0.0.0.0 "\ + "--arp-mac-src #{nic[:mac]} --arp-mac-dst #{nic[:mac]} "\ + "-j ACCEPT" + commands.add :ebtables, "-t nat -N #{chain_i} -P ACCEPT" +# commands.add :ebtables, "-t nat -N #{chain_i}-ip4 -P ACCEPT" +# commands.add :ebtables, "-t nat -A #{chain_i}-ip4 "\ +# "-s ! #{nic[:mac]} -j DROP" +# commands.add :ebtables, "-t nat -A #{chain_i} -p IPv4 "\ +# "-j #{chain_i}-ip4" + commands.add :ebtables, "-t nat -A #{chain_i} -p IPv4 "\ + "-j ACCEPT" + commands.add :ebtables, "-t nat -A #{chain_i} -p IPv6 "\ + "-j ACCEPT" + commands.add :ebtables, "-t nat -A #{chain_i} -p ARP "\ + "-j #{chain_i}-arp" + commands.add :ebtables, "-t nat -A #{chain_i} -p 0x8035 "\ + "-j #{chain_i}-rarp" + commands.add :ebtables, "-t nat -A PREROUTING -i #{chain} "\ + "-j #{chain_i}" + # Output + commands.add :ebtables, "-t nat -N #{chain_o}-arp -P DROP" + commands.add :ebtables, "-t nat -A #{chain_o}-arp -p ARP "\ + "--arp-op Reply --arp-mac-dst ! #{nic[:mac]} -j DROP" + commands.add :ebtables, "-t nat -A #{chain_o}-arp -p ARP "\ + "-j #{chain_o}-arp4" + commands.add :ebtables, "-t nat -A #{chain_o}-arp -p ARP "\ + "--arp-op Request -j ACCEPT" + commands.add :ebtables, "-t nat -A #{chain_o}-arp -p ARP "\ + "--arp-op Reply -j ACCEPT" + commands.add :ebtables, "-t nat -N #{chain_o}-rarp -P DROP" + commands.add :ebtables, "-t nat -A #{chain_o}-rarp -p 0x8035 "\ + "-d Broadcast --arp-op Request_Reverse "\ + "--arp-ip-src 0.0.0.0 --arp-ip-dst 0.0.0.0 "\ + "--arp-mac-src #{nic[:mac]} --arp-mac-dst #{nic[:mac]} "\ + "-j ACCEPT" + commands.add :ebtables, "-t nat -N #{chain_o} -P ACCEPT" +# commands.add :ebtables, "-t nat -N #{chain_o}-ip4 -P ACCEPT" +# commands.add :ebtables, "-t nat -A #{chain_o} -p IPv4 "\ +# "-j #{chain_o}-ip4" + commands.add :ebtables, "-t nat -A #{chain_o} -p IPv4 "\ + "-j ACCEPT" + commands.add :ebtables, "-t nat -A #{chain_o} -p IPv6 "\ + "-j ACCEPT" + commands.add :ebtables, "-t nat -A #{chain_o} -p ARP "\ + "-j #{chain_o}-arp" + commands.add :ebtables, "-t nat -A #{chain_o} -p 0x8035 "\ + "-j #{chain_o}-rarp" + commands.add :ebtables, "-t nat -A POSTROUTING -o #{chain} "\ + "-j #{chain_o}" + + commands.run! + end + end + @slog.info "activate() VM #{vm_id} END" + unlock + end + + def deactivate + lock + vm_id = vm['ID'] + attach_nic_id = vm['TEMPLATE/NIC[ATTACH="YES"]/NIC_ID'] + @slog.info "deactivate() VM #{vm_id} (#{attach_nic_id}) BEGIN" + process do |nic| + nic_id = nic[:nic_id] + next if attach_nic_id and attach_nic_id != nic_id + chain = "one-#{vm_id}-#{nic_id}" + deactivate_ebtables(chain) + end + @slog.info "deactivate() VM #{vm_id} END" + unlock + end + + def deactivate_ebtables(chain) + commands = VNMMAD::VNMNetwork::Commands.new + @slog.info "deactivate_ebtables(#{chain})" + commands.add :ebtables, "-t nat -L --Lx" + ebtables_nat = commands.run! + if !ebtables_nat.nil? + ebtables = Array.new + ebtables_nat.split("\n").each do |rule| + if rule.match(/-j #{chain}/) + rule_e = rule.split + if rule_e[5] == "-p" + ebtables.push("-t nat -X #{rule_e[-1]}") + ebtables.unshift("-t nat -D #{rule_e[4..-1].join(" ")}") + else + ebtables.push("-t nat -D #{rule_e[4..-1].join(" ")}") + ebtables.push("-t nat -X #{rule_e[-1]}") + end + end + end + if ebtables.any? + ebtables.each { |c| @slog.info "ebtables #{c}" } + ebtables.each { |c| commands.add :ebtables, c } + commands.run! + end + end + end + +end