Module: Dcmgr::NodeModules::Nat

Includes:
Helpers::NicHelper, Logger
Included in:
ServiceNetfilter
Defined in:
lib/dcmgr/node_modules/service_netfilter.rb

Instance Method Summary collapse

Methods included from Logger

create, default_logdev, included

Methods included from Helpers::NicHelper

#clean_mac, #find_nic, #is_natted?, #nic_state, #valid_nic?

Instance Method Details

#arp_respond(ip, mac_addr) ⇒ Object

Returns ebtables command to respond to ARP requests for the address ip. mac_addr is the mac address that we will reply with.



209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/dcmgr/node_modules/service_netfilter.rb', line 209

def arp_respond(ip,mac_addr)
  ip = IPAddress(ip) if ip.is_a?(String)
  raise "Invalid IP address: #{ip}" unless ip.is_a?(IPAddress)

  #Get the mac address for our physical nic
  #nic = find_nic(@node.manifest.config.hv_ifindex)
  #TODO: Find a prettier way to get the mac address
  #mac_addr = %x{ifconfig | grep '#{nic}' | tr -s ' ' | cut -d ' ' -f5}.chomp

  logger.debug "Replying ARP requests for address: #{ip.address}"

  "ebtables -t nat -A PREROUTING -p arp --arp-ip-dst #{ip.address} --arp-opcode REQUEST -j arpreply --arpreply-mac #{mac_addr}"
end

#build_nat_chains(inst_map, action = :create) ⇒ Object

Builds or deletes the chains for each vnic in an instance. We use different chains for incoming and outgoing packets per vnic This way every packet only needs to be checked against chains that are specifically intended for it. inst_map is a map of the instance to build or delte chains for. action decides wether we will create or delete the rules. It can be either of the following:

  • :create is the default value and creates chains for inst_map

  • :delete deletes the chains for inst_map

Raises:

  • (ArgumentError)


102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/dcmgr/node_modules/service_netfilter.rb', line 102

def build_nat_chains(inst_map, action = :create)
  actions = { :create => ['N'], :delete => ['F', 'X'] }
  raise ArgumentError, "#{action} is not a valid action. Valid actions are #{actions.keys.join(',')}." unless actions.keys.member?(action)
  raise ArgumentError, "inst_map must be a Hash." unless inst_map.is_a?(Hash)

  chain_cmds = []
  inst_map[:instance_nics].each { |nic|
    ['s','d'].each { |bound|
      actions[action].each { |a|
        chain_cmds << "iptables -t nat -#{a} #{bound}_#{nic[:uuid]}"
      }
    }
  }

  chain_cmds
end

#is_natted_ip?(ip) ⇒ Boolean

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)


223
224
225
226
227
228
# File 'lib/dcmgr/node_modules/service_netfilter.rb', line 223

def is_natted_ip?(ip)
  ip = IPAddress(ip) if ip.is_a?(String)
  raise ArgumentError, "Invalid IP address: #{ip}" unless ip.is_a?(IPAddress)

  rpc.request('hva-collector', 'is_natted_ip?', ip.address)
end

#nat_exceptions(inst_map) ⇒ Object

Returns the netfilter rules for destination IP addresses that will not use static nat. These are the IP addresses of other instances in the same security group. inst_map is a map of the instance that the rules will be defined for.



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/dcmgr/node_modules/service_netfilter.rb', line 186

def nat_exceptions(inst_map)
  inside_exception_ips = rpc.request('hva-collector','get_group_instance_ipv4s',inst_map[:uuid]).map {|ip| IPAddress(ip)}
  outside_exception_ips = rpc.request('hva-collector','get_group_instance_ipv4s',inst_map[:uuid],:outside).map {|ip| IPAddress(ip)}

  cmds = []
  inst_map[:instance_nics].each { |nic|
    # strict check
    next unless valid_nic?(nic[:uuid])

    internal_ip = IPAddress(rpc.request('hva-collector', 'get_iplease_for_nic', nic[:uuid]))
    inside_exception_ips.each { |ex_ip|
      cmds << "iptables -t nat -A s_#{nic[:uuid]} -s #{internal_ip.address} -d #{ex_ip.address}/#{ex_ip.prefix} -j ACCEPT"
    }
    outside_exception_ips.each { |ex_ip|
      cmds << "iptables -t nat -A d_#{nic[:uuid]} -s #{internal_ip.address} -d #{ex_ip.address}/#{ex_ip.prefix} -j ACCEPT"
    }
  }

  cmds
end

#nat_instance(inst_map) ⇒ Object

Takes an instance and nats it. If the instance is in a network that has a nat_network mapped to it, it will receive a second ip lease for that network. This lease will then be natted to the ip the instance already had in its own network. For example if 192.168.0.0/24 is natted to 172.16.0.0/16, then an instance with ip 192.168.0.10 might be natted to ip 172.16.46.23.

Raises:

  • (ArgumentError)


125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/dcmgr/node_modules/service_netfilter.rb', line 125

def nat_instance(inst_map)
  raise ArgumentError, "inst_map must be a Hash." unless inst_map.is_a?(Hash)

  nat_cmd = []

  inst_map[:instance_nics].each { |nic|
    # strict check
    next unless valid_nic?(nic[:uuid])

    nat_ips = rpc.request('hva-collector', 'get_nat_leases', nic[:uuid]).map {|ip| IPAddress(ip)}

    #Get the internal ip for this nic
    internal_ip = IPAddress rpc.request('hva-collector', 'get_iplease_for_nic', nic[:uuid])
    inside_exception_ips = rpc.request('hva-collector','get_group_instance_ipv4s',inst_map[:uuid]).map {|ip| IPAddress(ip)}
    outside_exception_ips = rpc.request('hva-collector','get_group_instance_ipv4s',inst_map[:uuid],:outside).map {|ip| IPAddress(ip)}

    #output the commands to nat this nic and answer arp requests for its outside ip
    friend_ipset  = nic[:uuid] + "_friend_ips"
    nat_ips.each { |external_ip|
      if @node.manifest.config.use_ipset

        nat_cmd << "ipset -N #{friend_ipset} iphash"

        inside_exception_ips.each { |ex_ip|
          nat_cmd << "ipset -A #{friend_ipset} #{ex_ip.address}"
        }

        # The good rules that use ipset              
        postrouting_command = "iptables -t nat -A s_#{nic[:uuid]} -s #{internal_ip.address} -m set ! --match-set #{friend_ipset} dst"
        prerouting_command = "iptables -t nat -A d_#{nic[:uuid]} -d #{external_ip.address} -m set ! --match-set #{friend_ipset} src"
      else
        # The ugly rules to use in case ipset is not installed
        postrouting_command = "iptables -t nat -A s_#{nic[:uuid]} -s #{internal_ip.address}"
        prerouting_command = "iptables -t nat -A d_#{nic[:uuid]} -d #{external_ip.address}"
      end

      # Set up the proper chain jumps
      nat_cmd << "iptables -t nat -A PREROUTING -d #{external_ip.address} -j d_#{nic[:uuid]}"
      nat_cmd << "iptables -t nat -A POSTROUTING -s #{internal_ip.address} -j s_#{nic[:uuid]}"

      # Build the final nat rules and log any packets that traverse them
      nat_cmd << postrouting_command  + " -j LOG --log-prefix 'Snat '" if @node.manifest.config.packet_drop_log
      nat_cmd << postrouting_command  + " -j SNAT --to #{external_ip.address}"

      nat_cmd << prerouting_command + " -j LOG --log-prefix 'Dnat '" if @node.manifest.config.packet_drop_log
      nat_cmd << prerouting_command + " -j DNAT --to #{internal_ip.address}"

      logger.debug "Natting #{internal_ip.address} to #{external_ip.address}"

      mac = clean_mac(nic[:mac_addr])
      nat_cmd << arp_respond(external_ip,mac)
    }
  }

  nat_cmd
end

#stop_arp_reply(inst_map) ⇒ Object

Similar hack to unlink_nat_chains. Once an instance is terminated, we no longer know which IP it had so we grep the rules by mac address and delete them by rule numer

Raises:

  • (ArgumentError)


79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/dcmgr/node_modules/service_netfilter.rb', line 79

def stop_arp_reply(inst_map)
  raise ArgumentError, "inst_map must be a Hash." unless inst_map.is_a?(Hash)

  del_cmds = []

  inst_map[:instance_nics].each { |nic|
    mac = clean_mac(nic[:mac_addr])
    rule_number = %x{ebtables -t nat -L --Ln --Lmac2 | grep #{mac} | cut -d '.' -f1}
    del_cmds << "ebtables -t nat -D PREROUTING #{rule_number}" unless rule_number.empty?
  }

  del_cmds
end

Quick and dirty hack to unlink the nat chains before deleting them. It would be cleaner to recall the creation method with a :delete action but NAT rules are based on IP leases and those are deleted on instance termination Therefore we use grep to get the referring rules based on vnic uuid and then delete them. Run build_nat_chains(inst_map, :delete) afterwards to delete the chains themselves

Raises:

  • (ArgumentError)


61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/dcmgr/node_modules/service_netfilter.rb', line 61

def unlink_nat_chains(inst_map)
  raise ArgumentError, "inst_map must be a Hash." unless inst_map.is_a?(Hash)

  del_cmds = []
  inst_map[:instance_nics].each { |nic|
    post = %x{iptables -t nat -L POSTROUTING --line-numbers | grep s_#{nic[:uuid]} | tr -s ' ' | cut -d ' ' -f1}.chomp
    pre = %x{iptables -t nat -L PREROUTING --line-numbers | grep d_#{nic[:uuid]} | tr -s ' ' | cut -d ' ' -f1}.chomp

    del_cmds << "iptables -t nat -D POSTROUTING #{post}" unless post.empty?
    del_cmds << "iptables -t nat -D PREROUTING #{pre}" unless pre.empty?
  }

  del_cmds
end