Class: OsCtld::NetInterface::Veth

Inherits:
NetInterface::Base
  • Object
show all
Includes:
OsCtl::Lib::Utils::Log, OsCtl::Lib::Utils::System, Utils::Ip, Utils::SwitchUser
Defined in:
lib/osctld/net_interface/veth.rb

Defined Under Namespace

Classes: InvalidHostLink

Constant Summary collapse

HOST_BOOT_ID_PATH =
'/proc/sys/kernel/random/boot_id'.freeze
HOST_BOOT_ID_PATTERN =
/\A[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}\z/

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils::SwitchUser

#ct_attach, #ct_syscmd

Methods included from Utils::Ip

#delete_link_by_ifindex, #ip, #link_ifindex_by_name, #tc

Instance Attribute Details

#ifb_ifindex ⇒ Object (readonly)

Returns the value of attribute ifb_ifindex.



16
17
18
# File 'lib/osctld/net_interface/veth.rb', line 16

def ifb_ifindex
  @ifb_ifindex
end

#rx_queues ⇒ Integer (readonly)

Number of receive queues

Returns:

  • (Integer)


24
25
26
# File 'lib/osctld/net_interface/veth.rb', line 24

def rx_queues
  @rx_queues
end

#tx_queues ⇒ Integer (readonly)

Number of transmit queues

Returns:

  • (Integer)


20
21
22
# File 'lib/osctld/net_interface/veth.rb', line 20

def tx_queues
  @tx_queues
end

#veth ⇒ Object (readonly)

Returns the value of attribute veth.



16
17
18
# File 'lib/osctld/net_interface/veth.rb', line 16

def veth
  @veth
end

#veth_ifindex ⇒ Object (readonly)

Returns the value of attribute veth_ifindex.



16
17
18
# File 'lib/osctld/net_interface/veth.rb', line 16

def veth_ifindex
  @veth_ifindex
end

Class Method Details

.current_host_boot_id ⇒ Object



26
27
28
29
30
31
32
33
34
# File 'lib/osctld/net_interface/veth.rb', line 26

def self.current_host_boot_id
  boot_id = File.read(HOST_BOOT_ID_PATH).strip

  unless HOST_BOOT_ID_PATTERN.match?(boot_id)
    raise InvalidHostLink, "invalid current host boot ID #{boot_id.inspect}"
  end

  boot_id
end

Instance Method Details

#active_ip_versions ⇒ Object



484
485
486
# File 'lib/osctld/net_interface/veth.rb', line 484

def active_ip_versions
  inclusively { [4, 6].delete_if { |v| @ips[v].empty? } }
end

#add_ip(addr) ⇒ Object

Parameters:

  • addr (IPAddress)


507
508
509
# File 'lib/osctld/net_interface/veth.rb', line 507

def add_ip(addr)
  exclusively { @ips[addr.ipv4? ? 4 : 6] << addr }
end

#create(opts) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/osctld/net_interface/veth.rb', line 36

def create(opts)
  super

  @tx_queues = opts.fetch(:tx_queues, 1)
  @rx_queues = opts.fetch(:rx_queues, 1)
  @veth = nil
  @veth_ifindex = nil
  @ifb_ifindex = nil
  @host_link_boot_id = nil
  @host_link_tainted = false
  @setup_state_changed = false
  @ips = { 4 => [], 6 => [] }
end

#del_all_ips(ip_v = nil) ⇒ Object

Parameters:

  • ip_v (Integer, nil) (defaults to: nil)


517
518
519
520
521
522
523
# File 'lib/osctld/net_interface/veth.rb', line 517

def del_all_ips(ip_v = nil)
  exclusively do
    (ip_v ? [ip_v] : [4, 6]).each do |v|
      @ips[v].clone.each { |addr| del_ip(addr) }
    end
  end
end

#del_ip(addr) ⇒ Object

Parameters:

  • addr (IPAddress)


512
513
514
# File 'lib/osctld/net_interface/veth.rb', line 512

def del_ip(addr)
  exclusively { @ips[addr.ipv4? ? 4 : 6].delete_if { |v| v == addr } }
end

#delete_recorded_host_link!(ifindex, expected_name, expected_kind) ⇒ Object (protected)



714
715
716
717
718
719
720
721
722
723
724
725
726
# File 'lib/osctld/net_interface/veth.rb', line 714

def delete_recorded_host_link!(ifindex, expected_name, expected_kind)
  delete_link_by_ifindex(
    ifindex,
    expected_name:,
    expected_kind:
  )
rescue Utils::Ip::LinkIdentityError => e
  raise InvalidHostLink, e.message
rescue Errno::ENODEV
  raise InvalidHostLink,
        "recorded network interface #{expected_name.inspect} " \
        "(ifindex #{ifindex}, kind #{expected_kind.inspect}) disappeared before deletion"
end

#down(host_veth = nil) ⇒ Object



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/osctld/net_interface/veth.rb', line 309

def down(host_veth = nil)
  NetInterface.sync_host_link_registry do
    veth_name, expected_ifindex, expected_ifb_ifindex =
      validate_down_callback(host_veth)
    return unless veth_name

    # lxc-user-nic does not support deletion of veth interfaces, delete it ourselves
    log(:info, ct, "Removing host veth #{veth_name} (ifindex #{expected_ifindex})")

    if expected_ifb_ifindex
      delete_recorded_host_link!(
        expected_ifb_ifindex,
        "ifb#{veth_name}",
        'ifb'
      )
      exclusively { @ifb_ifindex = nil }
      persist_host_link_state
    end

    delete_recorded_host_link!(expected_ifindex, veth_name, 'veth')

    exclusively do
      @veth = nil
      @veth_ifindex = nil
      @ifb_ifindex = nil
      @host_link_boot_id = nil
      @host_link_tainted = false
    end
    persist_host_link_state

    Eventd.report(
      :ct_netif,
      action: :down,
      pool: ct.pool.name,
      id: ct.id,
      name:
    )

    veth_name
  rescue StandardError
    taint_host_link!
    raise
  end
end

#dup(new_ct) ⇒ Object



525
526
527
528
529
530
531
532
533
534
535
# File 'lib/osctld/net_interface/veth.rb', line 525

def dup(new_ct)
  ret = super
  ret.instance_variable_set('@veth', nil)
  ret.instance_variable_set('@veth_ifindex', nil)
  ret.instance_variable_set('@ifb_ifindex', nil)
  ret.instance_variable_set('@host_link_boot_id', nil)
  ret.instance_variable_set('@host_link_tainted', false)
  ret.instance_variable_set('@setup_state_changed', false)
  ret.instance_variable_set('@ips', @ips.transform_values(&:dup))
  ret
end

#fetch_veth_name ⇒ Object (protected)



539
540
541
542
543
544
# File 'lib/osctld/net_interface/veth.rb', line 539

def fetch_veth_name
  v = ContainerControl::Commands::VethName.run!(ct, index)
  validate_kernel_ifname(v)
  log(:info, ct, "Discovered name for veth ##{index}: #{v}")
  v
end


580
581
582
583
584
# File 'lib/osctld/net_interface/veth.rb', line 580

def find_host_link(nl, key)
  nl.link[key]
rescue KeyError, Errno::ENODEV
  nil
end

#forget_absent_host_link! ⇒ Object

Used only by the privileged stopped-container recovery operation. Do not delete anything: names and indexes can belong to replacement links. Query both selectors even when one of them is already absent.



438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
# File 'lib/osctld/net_interface/veth.rb', line 438

def forget_absent_host_link!
  NetInterface.sync_host_link_registry do
    veth_name, veth_index, ifb_index = host_link_identity
    unless veth_name && veth_index.is_a?(Integer) && veth_index > 0
      raise InvalidHostLink, 'no valid host-link ownership record'
    end

    Linux::Netlink::Route::Socket.open do |nl|
      links = [[veth_name, veth_index]]
      links << ["ifb#{veth_name}", ifb_index] if ifb_index
      links.each do |link_name, link_index|
        by_name = find_host_link(nl, link_name)
        by_index = find_host_link(nl, link_index)
        unless by_name.nil? && by_index.nil?
          raise InvalidHostLink,
                "host link #{link_name.inspect} or ifindex #{link_index} is still present"
        end
      end
    end

    exclusively do
      @veth = nil
      @veth_ifindex = nil
      @ifb_ifindex = nil
      @host_link_boot_id = nil
      @host_link_tainted = false
    end
    persist_host_link_state
  end
end

#has_ip?(addr, prefix: true) ⇒ Boolean

Parameters:

  • addr (IPAddress)
  • prefix (Boolean) (defaults to: true) —

    check also address prefix

Returns:

  • (Boolean)


494
495
496
497
498
499
500
501
502
503
504
# File 'lib/osctld/net_interface/veth.rb', line 494

def has_ip?(addr, prefix: true)
  ip_v = addr.ipv4? ? 4 : 6

  exclusively do
    if prefix
      @ips[ip_v].include?(addr)
    else
      @ips[ip_v].detect { |v| v.to_s == addr.to_s } ? true : false
    end
  end
end

#hook_path(mode, name = nil) ⇒ Object (protected)



870
871
872
# File 'lib/osctld/net_interface/veth.rb', line 870

def hook_path(mode, name = nil)
  File.join(mode_path(mode), "#{@ct.id}.#{name || self.name}")
end

Return the host-side identities recorded when the links were created.

Returns:

  • (Array(String, Integer, Integer)) —

    veth name/index and IFB index



431
432
433
# File 'lib/osctld/net_interface/veth.rb', line 431

def host_link_identity
  inclusively { [@veth, @veth_ifindex, @ifb_ifindex] }
end

Returns:

  • (Boolean)


469
470
471
# File 'lib/osctld/net_interface/veth.rb', line 469

def host_link_tainted?
  inclusively { @host_link_tainted }
end

#host_veth_ifindex(veth_name, expected_kind:) ⇒ Object (protected)



550
551
552
553
554
555
556
557
558
559
# File 'lib/osctld/net_interface/veth.rb', line 550

def host_veth_ifindex(veth_name, expected_kind:)
  link_ifindex_by_name(
    expected_name: veth_name,
    expected_kind:
  )
rescue Utils::Ip::LinkNotFound
  nil
rescue Utils::Ip::LinkIdentityError => e
  raise InvalidHostLink, e.message
end

#host_veth_ifindex!(veth_name, expected_kind:) ⇒ Object (protected)



561
562
563
564
565
566
567
# File 'lib/osctld/net_interface/veth.rb', line 561

def host_veth_ifindex!(veth_name, expected_kind:)
  host_veth_ifindex(veth_name, expected_kind:) || raise(
    InvalidHostLink,
    "network interface #{veth_name.inspect} " \
    "(kind #{expected_kind.inspect}) is absent"
  )
end

#ifb_veth ⇒ Object (protected)



546
547
548
# File 'lib/osctld/net_interface/veth.rb', line 546

def ifb_veth
  "ifb#{veth}"
end

#ips(v) ⇒ Object



488
489
490
# File 'lib/osctld/net_interface/veth.rb', line 488

def ips(v)
  inclusively { @ips[v].clone }
end

#is_created? ⇒ Boolean

Returns:

  • (Boolean)


480
481
482
# File 'lib/osctld/net_interface/veth.rb', line 480

def is_created?
  inclusively { !veth.nil? }
end

#load(cfg) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/osctld/net_interface/veth.rb', line 50

def load(cfg)
  super

  @tx_queues = cfg.fetch('tx_queues', 1)
  @rx_queues = cfg.fetch('rx_queues', 1)
  @setup_state_changed = false
  load_host_link(cfg['host_link'])

  @ips = if cfg['ip_addresses']
           load_ip_list(cfg['ip_addresses']) do |ips|
             ips.map { |ip| IPAddress.parse(ip) }
           end

         else
           { 4 => [], 6 => [] }
         end
end

Raises:



728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
# File 'lib/osctld/net_interface/veth.rb', line 728

def load_host_link(cfg)
  unless cfg
    @veth = nil
    @veth_ifindex = nil
    @ifb_ifindex = nil
    @host_link_boot_id = nil
    @host_link_tainted = false
    return
  end

  raise InvalidHostLink, 'persisted host-link identity is not a map' unless cfg.is_a?(Hash)

  current_boot_id = self.class.current_host_boot_id
  boot_id = cfg['boot_id']
  if boot_id && (
    !boot_id.is_a?(String) || !HOST_BOOT_ID_PATTERN.match?(boot_id)
  )
    raise InvalidHostLink,
          "persisted host-link boot ID #{boot_id.inspect} is invalid"
  end

  if boot_id && boot_id != current_boot_id
    @veth = nil
    @veth_ifindex = nil
    @ifb_ifindex = nil
    @host_link_boot_id = nil
    @host_link_tainted = false
    @setup_state_changed = true
    return
  end

  veth_name = validate_kernel_ifname(cfg['name'])
  veth_ifindex = cfg['ifindex']
  ifb_ifindex = cfg['ifb_ifindex']
  tainted = cfg.fetch('tainted', false)

  unless veth_ifindex.is_a?(Integer) && veth_ifindex > 0
    raise InvalidHostLink,
          "persisted host veth #{veth_name.inspect} has invalid ifindex " \
          "#{veth_ifindex.inspect}"
  end
  if ifb_ifindex && (!ifb_ifindex.is_a?(Integer) || ifb_ifindex <= 0)
    raise InvalidHostLink,
          "persisted host veth #{veth_name.inspect} has invalid IFB ifindex " \
          "#{ifb_ifindex.inspect}"
  end
  if ifb_ifindex == veth_ifindex
    raise InvalidHostLink,
          "persisted host veth #{veth_name.inspect} reuses ifindex " \
          "#{veth_ifindex} for its IFB"
  end
  unless [true, false].include?(tainted)
    raise InvalidHostLink,
          "persisted host veth #{veth_name.inspect} has invalid taint " \
          "#{tainted.inspect}"
  end

  @veth = veth_name
  @veth_ifindex = veth_ifindex
  @ifb_ifindex = ifb_ifindex
  @host_link_boot_id = boot_id || current_boot_id
  @host_link_tainted = tainted
  @setup_state_changed = true unless boot_id
end

#load_ip_list(ip_list) {|value| ... } ⇒ Hash<Integer, String>, Hash<Integer, Array<String>> (protected)

Take an IP list stored in a config file and return an internal representation, see ##save_ip_list.

Yield Parameters:

  • value (String, Array<String>)

Returns:

  • (Hash<Integer, String>, Hash<Integer, Array<String>>)


897
898
899
900
901
902
903
904
905
906
907
908
909
910
# File 'lib/osctld/net_interface/veth.rb', line 897

def load_ip_list(ip_list)
  ip_list.to_h do |ip_v, value|
    # Load also integer keys for backward compatibility
    if [4, 6].include?(ip_v)
      [ip_v, yield(value)]

    elsif /^v(4|6)$/ =~ ip_v
      [::Regexp.last_match(1).to_i, yield(value)]

    else
      raise "unsupported IP version '#{ip_v}': expected v4 or v6"
    end
  end
end

#mode_path(mode) ⇒ Object (protected)



866
867
868
# File 'lib/osctld/net_interface/veth.rb', line 866

def mode_path(mode)
  File.join(veth_hook_dir, mode)
end


793
794
795
# File 'lib/osctld/net_interface/veth.rb', line 793

def persist_host_link_state
  ct.save_config
end

#rename(new_name) ⇒ Object



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/osctld/net_interface/veth.rb', line 233

def rename(new_name)
  NetInterface.sync_host_link_registry do
    if inclusively { !@veth.nil? }
      raise InvalidHostLink,
            "network interface #{name.inspect} still owns a host link; " \
            'complete lifecycle cleanup before renaming it'
    end

    %w[up down].each do |v|
      begin
        File.unlink(hook_path(v, name))
      rescue Errno::ENOENT
        # pass
      end

      File.symlink(OsCtld.hook_src("veth-#{v}"), hook_path(v, new_name))
    end

    super
  end
end

#render_opts ⇒ Object



255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/osctld/net_interface/veth.rb', line 255

def render_opts
  inclusively do
    {
      name:,
      index:,
      hwaddr:,
      tx_queues:,
      rx_queues:,
      hook_veth_up: hook_path('up'),
      hook_veth_down: hook_path('down')
    }
  end
end

#rollback_created_ifb!(ifindex, name, claim_error) ⇒ Object (protected)



833
834
835
836
837
838
839
840
841
842
# File 'lib/osctld/net_interface/veth.rb', line 833

def rollback_created_ifb!(ifindex, name, claim_error)
  delete_recorded_host_link!(ifindex, name, 'ifb')
  exclusively { @ifb_ifindex = nil }
  persist_host_link_state
rescue StandardError => e
  raise InvalidHostLink,
        "#{claim_error.message}; unable to roll back newly created IFB " \
        "#{name.inspect} (ifindex #{ifindex}): " \
        "#{e.class}: #{e.message}"
end

#save ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/osctld/net_interface/veth.rb', line 68

def save
  inclusively do
    data = super.merge(
      'tx_queues' => tx_queues,
      'rx_queues' => rx_queues,
      'ip_addresses' => save_ip_list(@ips) { |v| v.map(&:to_string) }
    )

    if @veth
      data['host_link'] = {
        'name' => @veth,
        'ifindex' => @veth_ifindex,
        'ifb_ifindex' => @ifb_ifindex,
        'boot_id' => @host_link_boot_id,
        'tainted' => @host_link_tainted
      }
    end

    data
  end
end

#save_ip_list(ip_list) {|value| ... } ⇒ Hash<String, String>, Hash<String, Array<String>> (protected)

Take an internal representation of an IP list and return a version to store in the config file.

The internal representation is a hash, where keys are IP versions as integer and the yielded value is either a list of addresses, i.e. an array of string, or just one address (string). The caller decides how to encode the value.

The returned hash has IP versions in the hash encoded as strings, i.e. v4 or v6`. This is to allow storing the config in JSON, which does not support integer object keys.

Yield Parameters:

  • value (String, Array<String>)

Returns:

  • (Hash<String, String>, Hash<String, Array<String>>)


888
889
890
# File 'lib/osctld/net_interface/veth.rb', line 888

def save_ip_list(ip_list)
  ip_list.to_h { |ip_v, value| ["v#{ip_v}", yield(value)] }
end

#set(opts) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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
# File 'lib/osctld/net_interface/veth.rb', line 90

def set(opts)
  host_mutation_started = false

  NetInterface.sync_host_link_registry do
    orig_max_rx = max_rx
    orig_max_tx = max_tx
    orig_enable = enable
    host_change =
      (opts.has_key?(:max_rx) && opts[:max_rx] != orig_max_rx) ||
      (opts.has_key?(:max_tx) && opts[:max_tx] != orig_max_tx) ||
      (opts.has_key?(:enable) && opts[:enable] != orig_enable)

    if veth && host_change
      veth_name, veth_ifindex, = validate_down_callback
      if host_link_tainted?
        raise InvalidHostLink,
              "host veth #{veth_name.inspect} is cleanup-tainted; " \
              'complete recorded link deletion before changing host networking'
      end

      validate_live_host_link!(
        veth_name,
        veth_ifindex,
        expected_kind: 'veth'
      )
    end

    @tx_queues = opts[:tx_queues] if opts[:tx_queues]
    @rx_queues = opts[:rx_queues] if opts[:rx_queues]

    # max_tx/rx is assigned by the parent
    super

    return if veth.nil?

    host_mutation_started = host_change

    if opts[:max_rx] && opts[:max_rx] != orig_max_rx
      if max_rx > 0
        set_shaper_rx
      else
        unset_shaper_rx
      end
    end

    if opts[:max_tx] && opts[:max_tx] != orig_max_tx
      if max_tx > 0
        set_shaper_tx
      else
        unset_shaper_tx
      end
    end

    if opts.has_key?(:enable) && opts[:enable] != orig_enable
      if !opts[:enable] && orig_enable
        begin
          ip(:all, %W[link set #{veth} down])
        rescue SystemCommandFailed => e
          log(:warn, ct, "Unable to disable host veth #{veth}: #{e.message}")
        end
      elsif opts[:enable] && !orig_enable
        begin
          ip(:all, %W[link set #{veth} up])
        rescue SystemCommandFailed => e
          log(:warn, ct, "Unable to enable host veth #{veth}: #{e.message}")
        end
      end
    end
  end
rescue StandardError
  taint_host_link! if host_mutation_started
  raise
end

#set_shaper_rx ⇒ Object (protected)



586
587
588
589
# File 'lib/osctld/net_interface/veth.rb', line 586

def set_shaper_rx
  tc(%W[qdisc delete root dev #{veth}], valid_rcs: [2])
  tc(%W[qdisc add root dev #{veth} cake bandwidth #{max_rx}bit])
end

#set_shaper_tx ⇒ Object (protected)



595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
# File 'lib/osctld/net_interface/veth.rb', line 595

def set_shaper_tx
  NetInterface.sync_host_link_registry do
    veth_name, veth_ifindex, recorded_ifb_ifindex =
      validate_down_callback
    unless veth_name
      raise InvalidHostLink, 'cannot configure IFB without a recorded host veth'
    end

    if host_link_tainted?
      raise InvalidHostLink,
            "host veth #{veth_name.inspect} is cleanup-tainted; " \
            'complete recorded link deletion before configuring its IFB'
    end

    validate_live_host_link!(
      veth_name,
      veth_ifindex,
      expected_kind: 'veth'
    )

    ifb_name = "ifb#{veth_name}"
    validate_unshared_host_link!(
      veth_name,
      veth_ifindex,
      recorded_ifb_ifindex,
      ifb_name:
    )
    ifb_exists = Dir.exist?("/sys/devices/virtual/net/#{ifb_name}")

    if ifb_exists && !recorded_ifb_ifindex
      raise InvalidHostLink,
            "host IFB #{ifb_name.inspect} exists without a recorded identity"
    elsif !ifb_exists && recorded_ifb_ifindex
      raise InvalidHostLink,
            "recorded host IFB #{ifb_name.inspect} " \
            "(ifindex #{recorded_ifb_ifindex}) is absent"
    end

    created_ifb = !ifb_exists
    unless ifb_exists
      ip(:all, %W[link add name #{ifb_name} type ifb])
    end

    live_ifb_ifindex = host_veth_ifindex!(ifb_name, expected_kind: 'ifb')
    if recorded_ifb_ifindex && recorded_ifb_ifindex != live_ifb_ifindex
      raise InvalidHostLink,
            "host IFB #{ifb_name.inspect} changed from ifindex " \
            "#{recorded_ifb_ifindex} to #{live_ifb_ifindex}"
    end

    # Persist newly created deletion authority before any later check or
    # traffic-control operation can fail. If the allocated ifindex
    # conflicts with a stale daemon claim, recovery must never be left
    # with an unrecorded IFB.
    exclusively { @ifb_ifindex = live_ifb_ifindex }
    persist_host_link_state

    begin
      validate_unshared_host_link!(
        veth_name,
        veth_ifindex,
        live_ifb_ifindex,
        ifb_name:
      )
    rescue StandardError => e
      if created_ifb
        rollback_created_ifb!(
          live_ifb_ifindex,
          ifb_name,
          e
        )
      end
      raise
    end

    unless ifb_exists
      tc(%W[qdisc del dev #{veth} ingress], valid_rcs: [2])
      tc(%W[qdisc add dev #{veth} handle ffff: ingress])
    end

    tc(%W[qdisc del dev #{ifb_name} root], valid_rcs: [2])
    tc(%W[qdisc add dev #{ifb_name} root cake bandwidth #{max_tx}bit besteffort])
    return if ifb_exists

    ip(:all, %W[link set #{ifb_name} up])
    tc(%W[filter add dev #{veth} parent ffff: matchall action mirred egress redirect dev #{ifb_name}])
  rescue StandardError
    taint_host_link!
    raise
  end
end

#setup(discover_host_links: true) ⇒ Object



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/osctld/net_interface/veth.rb', line 164

def setup(discover_host_links: true)
  # Setup links for veth up/down hooks in rundir
  #
  # Because a CT can have multiple veth interfaces and they can be of
  # different types, we need to create hooks for specific veth interfaces,
  # so that we can identify which veth was the hook called for. We simply
  # symlink the hook to rundir and the symlink's name identifies the veth.
  begin
    Dir.mkdir(veth_hook_dir, 0o711)
  rescue Errno::EEXIST
    # ignore
  end

  %w[up down].each do |v|
    begin
      Dir.mkdir(mode_path(v), 0o711)
    rescue Errno::EEXIST
      # ignore
    end

    symlink = hook_path(v)
    hook_src = OsCtld.hook_src("veth-#{v}")

    if File.symlink?(symlink)
      next if File.readlink(symlink) == hook_src

      File.unlink(symlink)

    end

    File.symlink(hook_src, symlink)
  end

  # Imported/replaced config cannot cause legacy name discovery. It may
  # carry an identity only when Container overlaid the daemon's current
  # internal record before this manager was loaded.
  return unless discover_host_links

  NetInterface.sync_host_link_registry do
    # A persisted identity is the only safe authority after restart. Never
    # replace it through name discovery, even when the recorded link is now
    # absent or its index has been reused; down/recovery revalidates the
    # complete name/index/kind tuple on one netlink socket.
    return if inclusively { !@veth.nil? }
    return if ct.fresh_state != :running

    veth_name = fetch_veth_name
    veth_ifindex = host_veth_ifindex!(veth_name, expected_kind: 'veth')
    ifb_name = "ifb#{veth_name}"
    ifb_ifindex = if max_tx > 0
                    host_veth_ifindex(ifb_name, expected_kind: 'ifb')
                  end
    validate_unshared_host_link!(
      veth_name,
      veth_ifindex,
      ifb_ifindex,
      ifb_name: ifb_ifindex && ifb_name
    )
    exclusively do
      @veth = veth_name
      @veth_ifindex = veth_ifindex
      @ifb_ifindex = ifb_ifindex
      @host_link_boot_id = self.class.current_host_boot_id
      @host_link_tainted = false
      @setup_state_changed = true
    end
  end
end

#setup_state_changed? ⇒ Boolean

True when startup discovered or changed host-link state which was not in the loaded config. Container persists it after installing the manager and releasing its config-load lock.

Returns:

  • (Boolean)


476
477
478
# File 'lib/osctld/net_interface/veth.rb', line 476

def setup_state_changed?
  inclusively { @setup_state_changed }
end

#taint_host_link! ⇒ Object (protected)



808
809
810
811
812
813
814
815
816
817
818
819
820
821
# File 'lib/osctld/net_interface/veth.rb', line 808

def taint_host_link!
  NetInterface.sync_host_link_registry do
    changed = exclusively do
      next false unless @veth
      next false if @host_link_tainted

      @host_link_tainted = true
      true
    end
    return unless changed

    ct.taint_recovery!
  end
end


797
798
799
800
801
802
803
804
805
806
# File 'lib/osctld/net_interface/veth.rb', line 797

def taint_host_link_on_setup!
  NetInterface.sync_host_link_registry do
    exclusively do
      next false unless @veth

      @host_link_tainted = true
      @setup_state_changed = true
    end
  end
end

#unset_shaper_rx ⇒ Object (protected)



591
592
593
# File 'lib/osctld/net_interface/veth.rb', line 591

def unset_shaper_rx
  tc(%W[qdisc delete root dev #{veth}], valid_rcs: [2])
end

#unset_shaper_tx ⇒ Object (protected)



687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
# File 'lib/osctld/net_interface/veth.rb', line 687

def unset_shaper_tx
  NetInterface.sync_host_link_registry do
    veth_name, veth_ifindex, ifb_ifindex = validate_down_callback
    return unless ifb_ifindex

    validate_live_host_link!(
      veth_name,
      veth_ifindex,
      expected_kind: 'veth'
    )
    validate_live_host_link!(
      "ifb#{veth_name}",
      ifb_ifindex,
      expected_kind: 'ifb'
    )
    tc(%W[filter delete dev #{veth} parent ffff:])
    tc(%W[qdisc delete dev #{veth} handle ffff: ingress])

    delete_recorded_host_link!(ifb_ifindex, "ifb#{veth_name}", 'ifb')
    exclusively { @ifb_ifindex = nil }
    persist_host_link_state
  rescue StandardError
    taint_host_link!
    raise
  end
end

#up(veth) ⇒ Object



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/osctld/net_interface/veth.rb', line 269

def up(veth)
  NetInterface.sync_host_link_registry do
    veth_name, ifindex = validate_up_callback(veth)

    exclusively do
      if @veth && (@veth != veth_name || @veth_ifindex != ifindex)
        raise InvalidHostLink,
              "host veth identity changed from #{@veth}(#{@veth_ifindex}) " \
              "to #{veth_name}(#{ifindex})"
      end

      @veth = veth_name
      @veth_ifindex = ifindex
      @host_link_boot_id = self.class.current_host_boot_id
      @host_link_tainted = false
    end
    persist_host_link_state

    ip(:all, %W[link set #{veth_name} down]) unless enable

    set_shaper_rx if max_rx > 0
    set_shaper_tx if max_tx > 0

    Eventd.report(
      :ct_netif,
      action: :up,
      pool: ct.pool.name,
      id: ct.id,
      name:,
      veth: veth_name,
      enable:
    )

    veth_name
  rescue StandardError
    taint_host_link!
    raise
  end
end

#validate_down_callback(host_veth = nil) ⇒ Array(String, Integer, Integer)?

Validate an LXC veth-down callback against the identity recorded at up.

Returns:

  • (Array(String, Integer, Integer), nil)


388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
# File 'lib/osctld/net_interface/veth.rb', line 388

def validate_down_callback(host_veth = nil)
  NetInterface.sync_host_link_registry do
    veth_name, expected_ifindex, expected_ifb_ifindex = host_link_identity
    return unless veth_name

    if host_veth
      callback_name = validate_kernel_ifname(host_veth)
      if callback_name != veth_name
        raise InvalidHostLink,
              "host veth #{callback_name.inspect} does not match #{veth_name.inspect}"
      end
    end

    unless expected_ifindex.is_a?(Integer) && expected_ifindex > 0
      raise InvalidHostLink,
            "host veth #{veth_name.inspect} has no valid recorded ifindex"
    end

    if expected_ifb_ifindex && (
      !expected_ifb_ifindex.is_a?(Integer) || expected_ifb_ifindex <= 0
    )
      raise InvalidHostLink,
            "host veth #{veth_name.inspect} has invalid recorded IFB ifindex " \
            "#{expected_ifb_ifindex.inspect}"
    end
    if expected_ifb_ifindex == expected_ifindex
      raise InvalidHostLink,
            "host veth #{veth_name.inspect} reuses ifindex #{expected_ifindex} for its IFB"
    end

    validate_unshared_host_link!(
      veth_name,
      expected_ifindex,
      expected_ifb_ifindex,
      ifb_name: expected_ifb_ifindex && "ifb#{veth_name}"
    )

    [veth_name, expected_ifindex, expected_ifb_ifindex]
  end
end

#validate_kernel_ifname(name) ⇒ Object (protected)



569
570
571
572
573
574
575
576
577
578
# File 'lib/osctld/net_interface/veth.rb', line 569

def validate_kernel_ifname(name)
  value = String(name)

  if value.empty? || value.bytesize > 15 || %w[. ..].include?(value) \
     || value.match?(%r{[\x00-\x20/:]})
    raise InvalidHostLink, "invalid network interface name #{value.inspect}"
  end

  value
end

#validate_live_host_link!(name, ifindex, expected_kind:) ⇒ Object (protected)

Raises:



823
824
825
826
827
828
829
830
831
# File 'lib/osctld/net_interface/veth.rb', line 823

def validate_live_host_link!(name, ifindex, expected_kind:)
  live_ifindex = host_veth_ifindex!(name, expected_kind:)
  return live_ifindex if live_ifindex == ifindex

  raise InvalidHostLink,
        "host network interface #{name.inspect} " \
        "(kind #{expected_kind.inspect}) changed from ifindex " \
        "#{ifindex} to #{live_ifindex}"
end

#validate_unshared_host_link!(name, veth_ifindex, ifb_ifindex, ifb_name:) ⇒ Object (protected)



844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
# File 'lib/osctld/net_interface/veth.rb', line 844

def validate_unshared_host_link!(
  name,
  veth_ifindex,
  ifb_ifindex,
  ifb_name:
)
  NetInterface.validate_host_link_claim!(
    owner_ct: ct,
    owner_netif: self,
    name:,
    veth_ifindex:,
    ifb_ifindex:,
    ifb_name:
  )
rescue NetInterface::HostLinkClaimError => e
  raise InvalidHostLink, e.message
end

#validate_up_callback(host_veth) ⇒ Array(String, Integer)

Validate an LXC veth-up callback without changing interface state.

Returns:

  • (Array(String, Integer)) —

    host link name and ifindex



356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/osctld/net_interface/veth.rb', line 356

def validate_up_callback(host_veth)
  NetInterface.sync_host_link_registry do
    veth_name = validate_kernel_ifname(host_veth)
    ifindex = host_veth_ifindex!(veth_name, expected_kind: 'veth')

    inclusively do
      if @host_link_tainted
        raise InvalidHostLink,
              "host veth #{@veth.inspect} is cleanup-tainted; " \
              'complete recorded link deletion before accepting another up callback'
      end

      if @veth && (@veth != veth_name || @veth_ifindex != ifindex)
        raise InvalidHostLink,
              "host veth identity changed from #{@veth}(#{@veth_ifindex}) " \
              "to #{veth_name}(#{ifindex})"
      end
    end

    validate_unshared_host_link!(
      veth_name,
      ifindex,
      nil,
      ifb_name: max_tx > 0 ? "ifb#{veth_name}" : nil
    )

    [veth_name, ifindex]
  end
end

#veth_hook_dir ⇒ Object (protected)



862
863
864
# File 'lib/osctld/net_interface/veth.rb', line 862

def veth_hook_dir
  File.join(ct.pool.hook_dir, 'veth')
end