Statistics
| Branch: | Tag: | Revision:

one / share / hooks / OpenNebulaVLAN.rb @ ced96c87

History | View | Annotate | Download (10.7 KB)

1
#!/usr/bin/env ruby
2

    
3
# -------------------------------------------------------------------------- #
4
# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org)             #
5
#                                                                            #
6
# Licensed under the Apache License, Version 2.0 (the "License"); you may    #
7
# not use this file except in compliance with the License. You may obtain    #
8
# a copy of the License at                                                   #
9
#                                                                            #
10
# http://www.apache.org/licenses/LICENSE-2.0                                 #
11
#                                                                            #
12
# Unless required by applicable law or agreed to in writing, software        #
13
# distributed under the License is distributed on an "AS IS" BASIS,          #
14
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
15
# See the License for the specific language governing permissions and        #
16
# limitations under the License.                                             #
17
#--------------------------------------------------------------------------- #
18

    
19
$: << File.dirname(__FILE__)
20

    
21
require 'rexml/document'
22

    
23
CONF = {
24
    :start_vlan => 2
25
}
26

    
27
COMMANDS = {
28
  :ebtables => "sudo /sbin/ebtables",
29
  :iptables => "sudo /usr/sbin/iptables",
30
  :brctl    => "/usr/sbin/brctl",
31
  :virsh    => "virsh -c qemu:///system",
32
  :xm       => "sudo /usr/sbin/xm",
33
  :ovs_vsctl=> "sudo /usr/local/bin/ovs-vsctl",
34
  :lsmod    => "/bin/lsmod"
35
}
36

    
37
class Nics < Array
38
    # finds nics that match 'args'
39
    # 'args' can be a Hash, or an array
40
    #  args example:
41
    #       {:mac => "02:00:C0:A8:01:01", :bridge => "br0"}
42
    #       :mac,  "02:00:C0:A8:01:01"
43
    #  key values may also be an array:
44
    #       {:mac => "02:00:C0:A8:01:01", :bridge => ["br0","br1"]}
45
    def get(*args)
46
        if args.length == 2
47
            dict = Hash.new
48
            dict[args[0]] = args[1]
49
        elsif args.length == 1
50
            dict = args[0]
51
        else
52
            return nil
53
        end
54

    
55
        matching = Array.new
56
        self.each do |e|
57
            e_filter = Hash.new
58
            dict.each_key{|k| e_filter[k] = e[k]}
59
            if compare(e_filter,dict)
60
                matching << e
61
            end
62
        end
63

    
64
        if matching.empty?
65
            nil
66
        else
67
            matching
68
        end
69
    end
70

    
71
    def compare(hash1, hash2)
72
        #hash1 has a single value per key
73
        #hash2 may contain an array of values
74
        hash1.each do |k,v|
75
            return false if !hash2[k]
76
            v2 = hash2[k]
77
            if hash2[k].kind_of?(Array)
78
                return false if !v2.include? v
79
            else
80
                return false if v != v2
81
            end
82
        end
83
        true
84
    end
85
end
86

    
87
class VM
88
    def initialize(vm_root)
89
        @vm_root = vm_root
90
    end
91

    
92
    def [](element)
93
        if @vm_root
94
            val = @vm_root.elements[element]
95
                if val.text
96
                    return val.text
97
                end
98
        end
99
        nil
100
    end
101
end
102

    
103
class OpenNebulaVLAN
104
    attr_reader :vm_info, :hypervisor, :nics
105

    
106
    def initialize(vm_tpl, hypervisor=nil)
107
        @vm_root = REXML::Document.new(vm_tpl).root
108
        @vm      = VM.new(@vm_root)
109
        @vm_info = Hash.new
110

    
111
        if !hypervisor
112
            hypervisor = detect_hypervisor
113
        end
114
        @hypervisor = hypervisor
115

    
116
        case hypervisor
117
        when "kvm"
118
            require 'KVMVLAN'
119
            class <<self
120
                include OpenNebulaVLANKVM
121
            end
122
        when "xen"
123
            require 'XenVLAN'
124
            class <<self
125
                include OpenNebulaVLANXen
126
            end
127
        end
128

    
129
        @vm_info = get_info
130

    
131
        @nics = get_nics
132
        @filtered_nics = @nics
133
    end
134

    
135
    def filter(*filter)
136
        @filtered_nics = @nics.get(*filter)
137
        self
138
    end
139

    
140
    def unfilter
141
        @filtered_nics = @nics
142
        self
143
    end
144

    
145
    def process(&block)
146
        if @filtered_nics
147
            @filtered_nics.each do |n|
148
                yield(n)
149
            end
150
        end
151
    end
152

    
153
    def detect_hypervisor
154
        uname_a = `uname -a`
155
        lsmod   = `#{COMMANDS[:lsmod]}`
156

    
157
        if uname_a.match(/xen/i)
158
            "xen"
159
        elsif lsmod.match(/kvm/)
160
            "kvm"
161
        end
162
    end
163

    
164
    def get_nics
165
        nics = Nics.new
166
        @vm_root.elements.each("TEMPLATE/NIC") do |nic_element|
167
            nic =  new_nic(@hypervisor)
168
            nic_element.elements.each('*') do |nic_attribute|
169
                key = nic_attribute.xpath.split('/')[-1].downcase.to_sym
170
                nic[key] = nic_attribute.text
171
            end
172
            nic.get_tap(self)
173
            nics << nic
174
        end
175
        nics
176
    end
177

    
178
    def get_interfaces
179
        bridges    = Hash.new
180
        brctl_exit =`#{COMMANDS[:brctl]} show`
181
        
182
        cur_bridge = ""
183

    
184
        brctl_exit.split("\n")[1..-1].each do |l|
185
            l = l.split
186

    
187
            if l.length > 1
188
                cur_bridge = l[0]
189

    
190
                bridges[cur_bridge] = Array.new
191
                bridges[cur_bridge] << l[3]
192
            else
193
                bridges[cur_bridge] << l[0]
194
            end
195
        end
196

    
197
        bridges
198
    end
199
end
200

    
201
class OpenvSwitchVLAN < OpenNebulaVLAN
202
    def initialize(vm, hypervisor = nil)
203
        super(vm,hypervisor)
204
    end
205

    
206
    def activate
207
        process do |nic|
208
            cmd =   "#{COMMANDS[:ovs_vsctl]} set Port #{nic[:tap]} "
209
            cmd <<  "tap=#{nic[:network_id].to_i + CONF[:start_vlan]}"
210

    
211
            system(cmd)
212
        end
213
    end
214
end
215

    
216
class EbtablesVLAN < OpenNebulaVLAN
217
    def initialize(vm, hypervisor = nil)
218
        super(vm,hypervisor)
219
    end
220

    
221
    def ebtables(rule)
222
        system("#{COMMANDS[:ebtables]} -A #{rule}")
223
    end
224

    
225
    def activate
226
        process do |nic|
227
            tap = nic[:tap]
228
            if tap
229
                iface_mac = nic[:mac]
230

    
231
                mac     = iface_mac.split(':')
232
                mac[-1] = '00'
233

    
234
                net_mac = mac.join(':')
235

    
236
                in_rule="FORWARD -s ! #{net_mac}/ff:ff:ff:ff:ff:00 " <<
237
                        "-o #{tap} -j DROP"
238
                out_rule="FORWARD -s ! #{iface_mac} -i #{tap} -j DROP"
239

    
240
                ebtables(in_rule)
241
                ebtables(out_rule)
242
            end
243
        end
244
    end
245

    
246
    def deactivate
247
        process do |nic|
248
            mac = nic[:mac]
249
            # remove 0-padding
250
            mac = mac.split(":").collect{|e| e.hex.to_s(16)}.join(":")
251

    
252
            tap = ""
253
            rules.each do |rule|
254
                if m = rule.match(/#{mac} -i (\w+)/)
255
                    tap = m[1]
256
                    break
257
                end
258
            end
259
            remove_rules(tap)
260
        end
261
    end
262

    
263
    def rules
264
        `#{COMMANDS[:ebtables]} -L FORWARD`.split("\n")[3..-1]
265
    end
266

    
267
    def remove_rules(tap)
268
        rules.each do |rule|
269
            if rule.match(tap)
270
                remove_rule(rule)
271
            end
272
        end
273
    end
274

    
275
    def remove_rule(rule)
276
        system("#{COMMANDS[:ebtables]} -D FORWARD #{rule}")
277
    end
278
end
279

    
280
class OpenNebulaFirewall < OpenNebulaVLAN
281
    def initialize(vm, hypervisor = nil)
282
        super(vm,hypervisor)
283
    end
284
    def activate
285
        vm_id =  @vm['ID']
286
        process do |nic|
287
            #:white_ports_tcp => iptables_range
288
            #:white_ports_udp => iptables_range
289
            #:black_ports_tcp => iptables_range
290
            #:black_ports_udp => iptables_range
291
            #:icmp            => 'DROP' or 'NO'
292
            
293
            nic_rules = Array.new
294
            
295
            chain   = "one-#{vm_id}-#{nic[:network_id]}"
296
            tap     = nic[:tap]
297

    
298
            if tap
299
                #TCP
300
                if range = nic[:white_ports_tcp]
301
                    nic_rules << filter_established(chain, :tcp, :accept)
302
                    nic_rules << filter_ports(chain, :tcp, range, :accept)
303
                    nic_rules << filter_protocol(chain, :tcp, :drop)
304
                elsif range = nic[:black_ports_tcp]
305
                    nic_rules << filter_ports(chain, :tcp, range, :drop)
306
                end
307

    
308
                #UDP
309
                if range = nic[:white_ports_udp]
310
                    nic_rules << filter_established(chain, :udp, :accept)
311
                    nic_rules << filter_ports(chain, :udp, range, :accept)
312
                    nic_rules << filter_protocol(chain, :udp, :drop)
313
                elsif range = nic[:black_ports_udp]
314
                    nic_rules << filter_ports(chain, :udp, range, :drop)
315
                end
316

    
317
                #ICMP
318
                if nic[:icmp]
319
                    if %w(no drop).include? nic[:icmp].downcase
320
                        nic_rules << filter_established(chain, :icmp, :accept)
321
                        nic_rules << filter_protocol(chain, :icmp, :drop)
322
                    end
323
                end
324

    
325
                process_chain(chain, tap, nic_rules)
326
            end
327
        end
328
    end
329

    
330
    def deactivate
331
        vm_id =  @vm['ID']
332
        process do |nic|
333
            chain   = "one-#{vm_id}-#{nic[:network_id]}"
334
            iptables_out = `#{COMMANDS[:iptables]} -n -v --line-numbers -L FORWARD`
335
            if m = iptables_out.match(/.*#{chain}.*/)
336
                rule_num = m[0].split(/\s+/)[0]
337
                purge_chain(chain, rule_num)
338
            end
339
        end
340
    end
341

    
342
    def purge_chain(chain, rule_num)
343
        rules = Array.new
344
        rules << rule("-D FORWARD #{rule_num}")
345
        rules << rule("-F #{chain}")
346
        rules << rule("-X #{chain}")
347
        run_rules rules
348
    end
349

    
350
    def process_chain(chain, tap, nic_rules)
351
        rules = Array.new
352
        if !nic_rules.empty?
353
            # new chain
354
            rules << new_chain(chain)
355
            # move tap traffic to chain
356
            rules << tap_to_chain(tap, chain)
357

    
358
            rules << nic_rules
359
        end
360
        run_rules rules
361
    end
362

    
363
    def filter_established(chain, protocol, policy)
364
        policy   = policy.to_s.upcase
365
        rule "-A #{chain} -p #{protocol} -m state --state ESTABLISHED -j #{policy}"
366
    end
367

    
368
    def run_rules(rules)
369
        rules.flatten.each do |rule|
370
            system(rule)
371
        end
372
    end
373

    
374
    def range?(range)
375
        range.match(/^(?:(?:\d+|\d+:\d+),)*(?:\d+|\d+:\d+)$/)
376
    end
377

    
378
    def filter_protocol(chain, protocol, policy)
379
        policy   = policy.to_s.upcase
380
        rule "-A #{chain} -p #{protocol} -j #{policy}"
381
    end
382

    
383
    def filter_ports(chain, protocol, range, policy)
384
        policy   = policy.to_s.upcase
385
        if range? range
386
           rule "-A #{chain} -p #{protocol} -m multiport --dports #{range} -j #{policy}"
387
        end
388
    end
389

    
390
    def tap_to_chain(tap, chain)
391
        rule "-A FORWARD -m physdev --physdev-out #{tap} -j #{chain}"
392
    end
393

    
394
    def new_chain(chain)
395
        rule "-N #{chain}"
396
    end
397

    
398
    def rule(rule)
399
        "#{COMMANDS[:iptables]} #{rule}"
400
    end
401
end