nl 0.2.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/Rakefile +7 -0
- data/lib/nl/async/dispatcher.rb +199 -0
- data/lib/nl/async/driver.rb +37 -0
- data/lib/nl/async/mailbox.rb +81 -0
- data/lib/nl/async/operation.rb +277 -0
- data/lib/nl/async.rb +13 -0
- data/lib/nl/attribute_set.rb +173 -0
- data/lib/nl/bitfield32.rb +86 -0
- data/lib/nl/blocking_transport.rb +131 -0
- data/lib/nl/connection.rb +61 -0
- data/lib/nl/datagram.rb +22 -0
- data/lib/nl/datatypes.rb +454 -0
- data/lib/nl/decoder.rb +13 -1
- data/lib/nl/endian.rb +10 -0
- data/lib/nl/error.rb +10 -0
- data/lib/nl/exchange.rb +124 -0
- data/lib/nl/family.rb +117 -23
- data/lib/nl/genl/client.rb +104 -0
- data/lib/nl/genl/protocol.rb +59 -0
- data/lib/nl/genl/wire.rb +94 -0
- data/lib/nl/genl.rb +60 -73
- data/lib/nl/notification.rb +112 -0
- data/lib/nl/notification_router.rb +92 -0
- data/lib/nl/raw/client.rb +83 -0
- data/lib/nl/raw/protocol.rb +112 -0
- data/lib/nl/raw/wire.rb +146 -0
- data/lib/nl/raw.rb +104 -0
- data/lib/nl/sequence_allocator.rb +34 -0
- data/lib/nl/socket.rb +15 -12
- data/lib/nl/structured_payload.rb +61 -0
- data/lib/nl/sub_message.rb +94 -0
- data/lib/nl/version.rb +1 -1
- data/lib/nl.rb +10 -12
- data/sig/generated/nl/async/dispatcher.rbs +53 -0
- data/sig/generated/nl/async/driver.rbs +21 -0
- data/sig/generated/nl/async/mailbox.rbs +32 -0
- data/sig/generated/nl/async/operation.rbs +123 -0
- data/sig/generated/nl/async.rbs +9 -0
- data/sig/generated/nl/attribute_set.rbs +50 -0
- data/sig/generated/nl/bitfield32.rbs +41 -0
- data/sig/generated/nl/blocking_transport.rbs +33 -0
- data/sig/generated/nl/connection.rbs +26 -0
- data/sig/generated/nl/datagram.rbs +11 -0
- data/sig/generated/nl/datatypes.rbs +181 -0
- data/sig/generated/nl/decoder.rbs +38 -0
- data/sig/generated/nl/encoder.rbs +29 -0
- data/sig/generated/nl/endian.rbs +28 -0
- data/sig/generated/nl/error.rbs +27 -0
- data/sig/generated/nl/exchange.rbs +75 -0
- data/sig/generated/nl/family.rbs +71 -0
- data/sig/generated/nl/genl/client.rbs +74 -0
- data/sig/generated/nl/genl/protocol.rbs +37 -0
- data/sig/generated/nl/genl/wire.rbs +113 -0
- data/sig/generated/nl/genl.rbs +50 -0
- data/sig/generated/nl/notification.rbs +53 -0
- data/sig/generated/nl/notification_router.rbs +30 -0
- data/sig/generated/nl/raw/client.rbs +50 -0
- data/sig/generated/nl/raw/protocol.rbs +122 -0
- data/sig/generated/nl/raw/wire.rbs +151 -0
- data/sig/generated/nl/raw.rbs +70 -0
- data/sig/generated/nl/sequence_allocator.rbs +15 -0
- data/sig/generated/nl/socket.rbs +40 -0
- data/sig/generated/nl/structured_payload.rbs +25 -0
- data/sig/generated/nl/sub_message.rbs +74 -0
- data/sig/generated/nl/version.rbs +5 -0
- data/sig/generated/nl.rbs +2 -0
- metadata +65 -9
- data/lib/nl/core.rb +0 -94
- data/lib/nl/protocols/genl.rb +0 -55
- data/lib/nl/protocols/raw.rb +0 -395
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
require_relative 'datatypes'
|
|
2
|
+
|
|
3
|
+
module Nl
|
|
4
|
+
class AttributeSet
|
|
5
|
+
Attribute = Struct.new(:value)
|
|
6
|
+
class Attribute
|
|
7
|
+
MULTI = false
|
|
8
|
+
ORDER = nil
|
|
9
|
+
SELECTOR_SLOT = nil
|
|
10
|
+
|
|
11
|
+
def self.multi?
|
|
12
|
+
self::MULTI
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.decode(decoder, context: nil, nlattr_type_flags: 0)
|
|
16
|
+
new(self::DATATYPE.decode(decoder, context:, nlattr_type_flags:))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def encode(encoder, context: nil)
|
|
20
|
+
self.class::DATATYPE.encode(encoder, self.value, context:)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def initialize(attributes)
|
|
25
|
+
attr_class = self.class::Attribute
|
|
26
|
+
|
|
27
|
+
attributes.each do |attr|
|
|
28
|
+
unless attr.kind_of?(attr_class)
|
|
29
|
+
raise TypeError, "attribute must be an instance of #{attr_class}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
@attributes = Array(attributes)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def [](type)
|
|
37
|
+
case type
|
|
38
|
+
when Symbol
|
|
39
|
+
attr_class = self.class.by_name(type)
|
|
40
|
+
when Integer
|
|
41
|
+
attr_class = self.class.by_type(type)
|
|
42
|
+
else
|
|
43
|
+
raise TypeError, "attribute type must be a Symbol or an Integer"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
attributes = @attributes.select { it.kind_of?(attr_class) }
|
|
47
|
+
attr_class.multi? ? attributes : attributes.first
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def <<(attr)
|
|
51
|
+
attr_class = self.class::Attribute
|
|
52
|
+
unless attr.kind_of?(attr_class)
|
|
53
|
+
raise TypeError, "attribute must be an instance of #{attr_class}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
@attributes << attr
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
SELECTOR_NAMES = {local: [].freeze, external: [].freeze}.freeze
|
|
60
|
+
|
|
61
|
+
private def encode1(encoder, attr, context)
|
|
62
|
+
datatype = attr.class::DATATYPE
|
|
63
|
+
flags = datatype.nlattr_type_flags(attr.value, context:)
|
|
64
|
+
type = attr.class::TYPE | flags
|
|
65
|
+
nlattr = Raw::NlAttr.new(0, type)
|
|
66
|
+
encoder.measure(Endian::Host::U16) do
|
|
67
|
+
nlattr.encode(encoder)
|
|
68
|
+
attr.encode(encoder, context:)
|
|
69
|
+
end
|
|
70
|
+
encoder.align_to(Raw::NLA_ALIGNTO)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def encode(encoder, external_selectors: [])
|
|
74
|
+
context = Selector::State.new(self.class::SELECTOR_NAMES.fetch(:local).length, external_selectors)
|
|
75
|
+
@attributes.each do |attr|
|
|
76
|
+
if slot = attr.class::SELECTOR_SLOT
|
|
77
|
+
context.set_local(slot, attr.value)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
attributes = @attributes.sort_by { it.class::ORDER }
|
|
82
|
+
attributes.each do |attr|
|
|
83
|
+
encode1(encoder, attr, context)
|
|
84
|
+
end
|
|
85
|
+
rescue Selector::MissingSelectorValueError => error
|
|
86
|
+
raise ArgumentError, "selector #{selector_name(error).inspect} is required by a dependent attribute"
|
|
87
|
+
rescue Selector::UnknownSelectorValueError => error
|
|
88
|
+
raise ArgumentError, "unknown sub-message selector value: #{error.value.inspect}"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private def selector_name(error)
|
|
92
|
+
self.class::SELECTOR_NAMES.fetch(error.scope).fetch(error.index)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class << self
|
|
96
|
+
private def decode1(decoder, context)
|
|
97
|
+
nlattr = Raw::NlAttr.decode(decoder)
|
|
98
|
+
flags = nlattr.type & (Raw::NLA_F_NESTED | Raw::NLA_F_NET_BYTEORDER)
|
|
99
|
+
attr = decoder.limit(nlattr.len - Raw::NLA_HDRLEN) do
|
|
100
|
+
if attr_class = self::BY_TYPE[nlattr.type & Raw::NLA_TYPE_MASK]
|
|
101
|
+
attr_class.decode(decoder, context:, nlattr_type_flags: flags)
|
|
102
|
+
else
|
|
103
|
+
decoder.skip
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
decoder.align_to(Raw::NLA_ALIGNTO)
|
|
108
|
+
if attr && (slot = attr.class::SELECTOR_SLOT)
|
|
109
|
+
context.set_local(slot, attr.value)
|
|
110
|
+
end
|
|
111
|
+
attr
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def decode(decoder, external_selectors: [])
|
|
115
|
+
context = Selector::State.new(self::SELECTOR_NAMES.fetch(:local).length, external_selectors)
|
|
116
|
+
attrs = []
|
|
117
|
+
while decoder.available?
|
|
118
|
+
attr = decode1(decoder, context)
|
|
119
|
+
attrs << attr
|
|
120
|
+
end
|
|
121
|
+
new(attrs.compact)
|
|
122
|
+
rescue Selector::MissingSelectorValueError => error
|
|
123
|
+
raise Decoder::Error,
|
|
124
|
+
"selector #{selector_name(error).inspect} must precede the dependent attribute"
|
|
125
|
+
rescue Selector::UnknownSelectorValueError => error
|
|
126
|
+
raise Decoder::Error, "unknown sub-message selector value: #{error.value.inspect}"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def build_attributes(params = nil, external_selectors: [], **keywords)
|
|
130
|
+
params = (params || {}).merge(keywords)
|
|
131
|
+
unknown = params.keys - self::BY_NAME.keys
|
|
132
|
+
raise ArgumentError, "unknown attributes: #{unknown.join(', ')}" unless unknown.empty?
|
|
133
|
+
|
|
134
|
+
context = Selector::State.new(self::SELECTOR_NAMES.fetch(:local).length, external_selectors)
|
|
135
|
+
coerced_selectors = {}
|
|
136
|
+
self::SELECTOR_NAMES.fetch(:local).each_with_index do |name, slot|
|
|
137
|
+
next unless params.key?(name)
|
|
138
|
+
attr_class = self::BY_NAME.fetch(name)
|
|
139
|
+
value = attr_class::DATATYPE.coerce(params.fetch(name))
|
|
140
|
+
coerced_selectors[name] = value
|
|
141
|
+
context.set_local(slot, value)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
attrs = params.sort_by { |name, _| self::BY_NAME.fetch(name)::ORDER }.flat_map do |name, value|
|
|
145
|
+
attr_class = self::BY_NAME[name] or raise "Unknown attribute #{name}"
|
|
146
|
+
if attr_class.multi?
|
|
147
|
+
unless value.is_a?(Array)
|
|
148
|
+
raise TypeError, "value for multi-attribute #{name} must be an Array"
|
|
149
|
+
end
|
|
150
|
+
value.map { attr_class.new(coerce(attr_class::DATATYPE, it, context)) }
|
|
151
|
+
else
|
|
152
|
+
value = coerced_selectors.fetch(name) { coerce(attr_class::DATATYPE, value, context) }
|
|
153
|
+
attr_class.new(value)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
new(attrs)
|
|
157
|
+
rescue Selector::MissingSelectorValueError => error
|
|
158
|
+
raise ArgumentError, "selector #{selector_name(error).inspect} is required by a dependent attribute"
|
|
159
|
+
rescue Selector::UnknownSelectorValueError => error
|
|
160
|
+
raise ArgumentError, "unknown sub-message selector value: #{error.value.inspect}"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private def coerce(datatype, value, context)
|
|
164
|
+
datatype.coerce(value, context:)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
private def selector_name(error)
|
|
168
|
+
self::SELECTOR_NAMES.fetch(error.scope).fetch(error.index)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module Nl
|
|
2
|
+
# A 32-bit value paired with a mask selecting the meaningful bits.
|
|
3
|
+
class Bitfield32
|
|
4
|
+
UINT32_RANGE = (0...2**32)
|
|
5
|
+
|
|
6
|
+
# A 32-bit flags
|
|
7
|
+
# @return [Integer]
|
|
8
|
+
attr_reader :value #: Integer
|
|
9
|
+
|
|
10
|
+
# A 32-bit mask selecting the meaningful bits
|
|
11
|
+
# @return [Integer]
|
|
12
|
+
attr_reader :selector #: Integer
|
|
13
|
+
|
|
14
|
+
# @param [Integer] value
|
|
15
|
+
# @param [Integer] selector
|
|
16
|
+
# @rbs (Integer value, Integer selector) -> void
|
|
17
|
+
def initialize(value, selector)
|
|
18
|
+
validate_uint32(value, :value)
|
|
19
|
+
validate_uint32(selector, :selector)
|
|
20
|
+
if value & ~selector != 0
|
|
21
|
+
raise ArgumentError, 'value contains bits which are not selected'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
@value = value
|
|
25
|
+
@selector = selector
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns 1 or 0 for a selected bit, or nil for an unselected bit.
|
|
29
|
+
# @param [Integer] index Bits are indexed from the least significant bit, starting at zero.
|
|
30
|
+
# @return [0, 1, nil]
|
|
31
|
+
# @rbs (Integer index) -> (0 | 1 | nil)
|
|
32
|
+
def [](index)
|
|
33
|
+
mask = bit_mask(index)
|
|
34
|
+
return nil if selector & mask == 0
|
|
35
|
+
|
|
36
|
+
value & mask == 0 ? 0 : 1
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Selects and sets or clears a bit. Assigning nil unselects it.
|
|
40
|
+
# @param [Integer] index Bits are indexed from the least significant bit, starting at zero.
|
|
41
|
+
# @param [0, 1, nil] state
|
|
42
|
+
# @rbs (Integer index, (0 | 1 | nil) state) -> (0 | 1 | nil)
|
|
43
|
+
def []=(index, state)
|
|
44
|
+
mask = bit_mask(index)
|
|
45
|
+
case state
|
|
46
|
+
when 1
|
|
47
|
+
@selector |= mask
|
|
48
|
+
@value |= mask
|
|
49
|
+
when 0
|
|
50
|
+
@selector |= mask
|
|
51
|
+
@value &= ~mask
|
|
52
|
+
when nil
|
|
53
|
+
@selector &= ~mask
|
|
54
|
+
@value &= ~mask
|
|
55
|
+
else
|
|
56
|
+
raise TypeError, 'bit state must be 0, 1, or nil'
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
state
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Converts a bit index to a bit mask
|
|
63
|
+
# @rbs (Integer index) -> Integer
|
|
64
|
+
private def bit_mask(index)
|
|
65
|
+
unless index.is_a?(Integer)
|
|
66
|
+
raise TypeError, 'bit index must be an Integer'
|
|
67
|
+
end
|
|
68
|
+
unless (0...32).cover?(index)
|
|
69
|
+
raise RangeError, "bit index #{index.inspect} is outside the 0...32 range"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
1 << index
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Validates if an Integer fits in uint32
|
|
76
|
+
# @rbs (Integer integer, Symbol name) -> void
|
|
77
|
+
private def validate_uint32(integer, name)
|
|
78
|
+
unless integer.is_a?(Integer)
|
|
79
|
+
raise TypeError, "#{name} must be an Integer"
|
|
80
|
+
end
|
|
81
|
+
unless UINT32_RANGE.cover?(integer)
|
|
82
|
+
raise RangeError, "#{name} #{integer.inspect} is outside the #{UINT32_RANGE} range"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
require_relative 'datagram'
|
|
2
|
+
require_relative 'exchange'
|
|
3
|
+
require_relative 'notification_router'
|
|
4
|
+
require_relative 'sequence_allocator'
|
|
5
|
+
|
|
6
|
+
module Nl
|
|
7
|
+
# Drives one exchange at a time with blocking socket operations.
|
|
8
|
+
class BlockingTransport
|
|
9
|
+
class ConcurrentOperationError < StandardError; end
|
|
10
|
+
|
|
11
|
+
class UnexpectedSequenceError < StandardError
|
|
12
|
+
attr_reader :expected, :actual
|
|
13
|
+
|
|
14
|
+
def initialize(expected, actual)
|
|
15
|
+
@expected = expected
|
|
16
|
+
@actual = actual
|
|
17
|
+
super("expected Netlink sequence and port ID #{expected.inspect}, got #{actual.inspect}")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(socket, protocol:, notifications:)
|
|
22
|
+
@socket = socket
|
|
23
|
+
@protocol = protocol
|
|
24
|
+
@sequences = SequenceAllocator.new
|
|
25
|
+
@mutex = Mutex.new
|
|
26
|
+
@notifications = notifications
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def exchange(endpoint, kind, request_class, reply_class, args)
|
|
30
|
+
unless locked = @mutex.try_lock
|
|
31
|
+
raise ConcurrentOperationError, 'BlockingTransport supports only one active operation'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
request = @protocol.build_request(endpoint, kind, request_class, args)
|
|
35
|
+
seq = @sequences.next
|
|
36
|
+
pid = @socket.local_port_id
|
|
37
|
+
key = [seq, pid]
|
|
38
|
+
@protocol.send_message(@socket, endpoint, request, seq:, pid:)
|
|
39
|
+
mode = kind == :dump ? :dump : (reply_class ? :reply : :no_reply)
|
|
40
|
+
exchange = Exchange.new(mode:)
|
|
41
|
+
result = [] unless block_given?
|
|
42
|
+
|
|
43
|
+
until exchange.complete?
|
|
44
|
+
receive(endpoint, key, reply_class) do |message|
|
|
45
|
+
case outcome = exchange.accept(message)
|
|
46
|
+
when Exchange::Item
|
|
47
|
+
block_given? ? yield(outcome.value) : result << outcome.value
|
|
48
|
+
when Exchange::Failure
|
|
49
|
+
raise outcome.exception
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
return if block_given?
|
|
55
|
+
|
|
56
|
+
kind == :dump ? result : exchange.result
|
|
57
|
+
ensure
|
|
58
|
+
@mutex.unlock if locked
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def async_capable? #: false
|
|
62
|
+
false
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def receive_notification(endpoint, timeout: nil)
|
|
66
|
+
channel = @notifications.channel(endpoint)
|
|
67
|
+
return channel.pop(timeout: 0)
|
|
68
|
+
rescue TimeoutError
|
|
69
|
+
unless locked = @mutex.try_lock
|
|
70
|
+
raise ConcurrentOperationError, 'BlockingTransport supports only one active operation'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout if timeout
|
|
74
|
+
loop do
|
|
75
|
+
begin
|
|
76
|
+
return channel.pop(timeout: 0)
|
|
77
|
+
rescue TimeoutError
|
|
78
|
+
# Read another datagram below.
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
remaining = deadline && deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
82
|
+
raise TimeoutError, 'notification receive timed out' if remaining && remaining <= 0
|
|
83
|
+
if remaining && !@socket.wait_readable(remaining)
|
|
84
|
+
raise TimeoutError, 'notification receive timed out'
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
receive_notifications
|
|
88
|
+
end
|
|
89
|
+
ensure
|
|
90
|
+
@mutex.unlock if locked
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def close #: void
|
|
94
|
+
@socket.close unless @socket.closed?
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private def receive(endpoint, expected_key, reply_class)
|
|
99
|
+
Datagram.each_frame(receive_datagram) do |header, payload|
|
|
100
|
+
actual_key = [header.seq, header.pid]
|
|
101
|
+
if actual_key == expected_key
|
|
102
|
+
yield @protocol.decode_frame(endpoint, header, payload, reply_class)
|
|
103
|
+
elsif header.seq.zero?
|
|
104
|
+
@notifications.route(header, payload)
|
|
105
|
+
else
|
|
106
|
+
raise UnexpectedSequenceError.new(expected_key, actual_key)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
rescue Errno::ENOBUFS
|
|
110
|
+
@notifications.lose_all(NotificationLossError.new('kernel receive buffer overflowed'))
|
|
111
|
+
raise
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private def receive_notifications
|
|
115
|
+
Datagram.each_frame(receive_datagram) do |header, payload|
|
|
116
|
+
if header.seq.zero?
|
|
117
|
+
@notifications.route(header, payload)
|
|
118
|
+
else
|
|
119
|
+
raise UnexpectedSequenceError.new(nil, [header.seq, header.pid])
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
rescue Errno::ENOBUFS
|
|
123
|
+
@notifications.lose_all(NotificationLossError.new('kernel receive buffer overflowed'))
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private def receive_datagram
|
|
127
|
+
data, = @socket.recvmsg
|
|
128
|
+
IO::Buffer.for(data)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require_relative 'async'
|
|
2
|
+
require_relative 'blocking_transport'
|
|
3
|
+
require_relative 'notification_router'
|
|
4
|
+
require_relative 'socket'
|
|
5
|
+
|
|
6
|
+
module Nl
|
|
7
|
+
# Owns one Netlink socket and the facilities shared by families using it.
|
|
8
|
+
class Connection
|
|
9
|
+
DEFAULT_NOTIFICATION_CAPACITY = 1_024
|
|
10
|
+
|
|
11
|
+
def initialize(protocol:, executor: nil, notification_capacity: DEFAULT_NOTIFICATION_CAPACITY)
|
|
12
|
+
socket = Socket.new(protocol.protonum)
|
|
13
|
+
socket.bind(Socket.sockaddr_nl(0, 0))
|
|
14
|
+
notifications = NotificationRouter.new(
|
|
15
|
+
protocol:,
|
|
16
|
+
capacity: notification_capacity,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
@socket = socket
|
|
20
|
+
@notifications = notifications
|
|
21
|
+
@transport = if executor
|
|
22
|
+
Async::Dispatcher.new(socket, protocol:, executor:, notifications:)
|
|
23
|
+
else
|
|
24
|
+
BlockingTransport.new(socket, protocol:, notifications:)
|
|
25
|
+
end
|
|
26
|
+
rescue Exception
|
|
27
|
+
@transport ? @transport.close : socket&.close
|
|
28
|
+
notifications&.close
|
|
29
|
+
raise
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def exchange(...) = @transport.exchange(...)
|
|
33
|
+
def exchange_async(...) = @transport.exchange_async(...)
|
|
34
|
+
def async_capable? = @transport.async_capable?
|
|
35
|
+
|
|
36
|
+
def register_notifications(endpoint, classes)
|
|
37
|
+
@notifications.register(endpoint, classes)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def add_memberships(group_ids)
|
|
41
|
+
group_ids.each { @socket.add_membership(it) }
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def drop_memberships(group_ids)
|
|
46
|
+
group_ids.each { @socket.drop_membership(it) }
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def receive_notification(endpoint, timeout: nil)
|
|
51
|
+
@transport.receive_notification(endpoint, timeout:)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def close
|
|
55
|
+
@transport.close
|
|
56
|
+
nil
|
|
57
|
+
ensure
|
|
58
|
+
@notifications.close
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/nl/datagram.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require_relative 'raw/wire'
|
|
2
|
+
require_relative 'decoder'
|
|
3
|
+
|
|
4
|
+
module Nl
|
|
5
|
+
# Splits Netlink datagrams into aligned header/payload frames.
|
|
6
|
+
module Datagram
|
|
7
|
+
# @rbs (IO::Buffer buffer) { (Raw::NlMsgHdr, IO::Buffer) -> void } -> nil
|
|
8
|
+
# | (IO::Buffer buffer) -> Enumerator[[Raw::NlMsgHdr, IO::Buffer], nil]
|
|
9
|
+
def self.each_frame(buffer)
|
|
10
|
+
return enum_for(__method__, buffer) unless block_given?
|
|
11
|
+
|
|
12
|
+
decoder = Decoder.new(buffer)
|
|
13
|
+
while decoder.available?(Raw::NLMSG_HDRLEN)
|
|
14
|
+
header = Raw::NlMsgHdr.decode(decoder)
|
|
15
|
+
payload_size = header.len - Raw::NLMSG_HDRLEN
|
|
16
|
+
payload = decoder.get_buffer(payload_size)
|
|
17
|
+
decoder.align_to(Raw::NLMSG_ALIGNTO)
|
|
18
|
+
yield header, payload
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|