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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/Rakefile +7 -0
  4. data/lib/nl/async/dispatcher.rb +199 -0
  5. data/lib/nl/async/driver.rb +37 -0
  6. data/lib/nl/async/mailbox.rb +81 -0
  7. data/lib/nl/async/operation.rb +277 -0
  8. data/lib/nl/async.rb +13 -0
  9. data/lib/nl/attribute_set.rb +173 -0
  10. data/lib/nl/bitfield32.rb +86 -0
  11. data/lib/nl/blocking_transport.rb +131 -0
  12. data/lib/nl/connection.rb +61 -0
  13. data/lib/nl/datagram.rb +22 -0
  14. data/lib/nl/datatypes.rb +454 -0
  15. data/lib/nl/decoder.rb +13 -1
  16. data/lib/nl/endian.rb +10 -0
  17. data/lib/nl/error.rb +10 -0
  18. data/lib/nl/exchange.rb +124 -0
  19. data/lib/nl/family.rb +117 -23
  20. data/lib/nl/genl/client.rb +104 -0
  21. data/lib/nl/genl/protocol.rb +59 -0
  22. data/lib/nl/genl/wire.rb +94 -0
  23. data/lib/nl/genl.rb +60 -73
  24. data/lib/nl/notification.rb +112 -0
  25. data/lib/nl/notification_router.rb +92 -0
  26. data/lib/nl/raw/client.rb +83 -0
  27. data/lib/nl/raw/protocol.rb +112 -0
  28. data/lib/nl/raw/wire.rb +146 -0
  29. data/lib/nl/raw.rb +104 -0
  30. data/lib/nl/sequence_allocator.rb +34 -0
  31. data/lib/nl/socket.rb +15 -12
  32. data/lib/nl/structured_payload.rb +61 -0
  33. data/lib/nl/sub_message.rb +94 -0
  34. data/lib/nl/version.rb +1 -1
  35. data/lib/nl.rb +10 -12
  36. data/sig/generated/nl/async/dispatcher.rbs +53 -0
  37. data/sig/generated/nl/async/driver.rbs +21 -0
  38. data/sig/generated/nl/async/mailbox.rbs +32 -0
  39. data/sig/generated/nl/async/operation.rbs +123 -0
  40. data/sig/generated/nl/async.rbs +9 -0
  41. data/sig/generated/nl/attribute_set.rbs +50 -0
  42. data/sig/generated/nl/bitfield32.rbs +41 -0
  43. data/sig/generated/nl/blocking_transport.rbs +33 -0
  44. data/sig/generated/nl/connection.rbs +26 -0
  45. data/sig/generated/nl/datagram.rbs +11 -0
  46. data/sig/generated/nl/datatypes.rbs +181 -0
  47. data/sig/generated/nl/decoder.rbs +38 -0
  48. data/sig/generated/nl/encoder.rbs +29 -0
  49. data/sig/generated/nl/endian.rbs +28 -0
  50. data/sig/generated/nl/error.rbs +27 -0
  51. data/sig/generated/nl/exchange.rbs +75 -0
  52. data/sig/generated/nl/family.rbs +71 -0
  53. data/sig/generated/nl/genl/client.rbs +74 -0
  54. data/sig/generated/nl/genl/protocol.rbs +37 -0
  55. data/sig/generated/nl/genl/wire.rbs +113 -0
  56. data/sig/generated/nl/genl.rbs +50 -0
  57. data/sig/generated/nl/notification.rbs +53 -0
  58. data/sig/generated/nl/notification_router.rbs +30 -0
  59. data/sig/generated/nl/raw/client.rbs +50 -0
  60. data/sig/generated/nl/raw/protocol.rbs +122 -0
  61. data/sig/generated/nl/raw/wire.rbs +151 -0
  62. data/sig/generated/nl/raw.rbs +70 -0
  63. data/sig/generated/nl/sequence_allocator.rbs +15 -0
  64. data/sig/generated/nl/socket.rbs +40 -0
  65. data/sig/generated/nl/structured_payload.rbs +25 -0
  66. data/sig/generated/nl/sub_message.rbs +74 -0
  67. data/sig/generated/nl/version.rbs +5 -0
  68. data/sig/generated/nl.rbs +2 -0
  69. metadata +65 -9
  70. data/lib/nl/core.rb +0 -94
  71. data/lib/nl/protocols/genl.rb +0 -55
  72. 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
@@ -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