nl 0.3.0 → 0.4.2
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 +15 -0
- data/Rakefile +7 -0
- data/lib/nl/async/dispatcher.rb +16 -16
- data/lib/nl/async/driver.rb +0 -2
- data/lib/nl/async/mailbox.rb +0 -2
- data/lib/nl/async/operation.rb +0 -2
- data/lib/nl/async.rb +0 -2
- data/lib/nl/attribute_set.rb +173 -0
- data/lib/nl/bitfield32.rb +86 -0
- data/lib/nl/blocking_transport.rb +12 -12
- data/lib/nl/connection.rb +7 -9
- data/lib/nl/datagram.rb +7 -9
- data/lib/nl/datatypes.rb +454 -0
- data/lib/nl/decoder.rb +4 -0
- data/lib/nl/endian.rb +9 -0
- data/lib/nl/exchange.rb +40 -18
- data/lib/nl/family.rb +50 -55
- data/lib/nl/genl/client.rb +105 -0
- data/lib/nl/genl/protocol.rb +59 -0
- data/lib/nl/genl/wire.rb +95 -0
- data/lib/nl/genl.rb +60 -62
- data/lib/nl/notification.rb +3 -6
- data/lib/nl/notification_router.rb +30 -23
- data/lib/nl/raw/client.rb +83 -0
- data/lib/nl/raw/protocol.rb +112 -0
- data/lib/nl/raw/wire.rb +147 -0
- data/lib/nl/raw.rb +104 -0
- data/lib/nl/sequence_allocator.rb +0 -2
- data/lib/nl/socket.rb +21 -5
- 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 +5 -6
- 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 +29 -0
- data/sig/generated/nl/error.rbs +27 -0
- data/sig/generated/nl/exchange.rbs +75 -0
- data/sig/generated/nl/family.rbs +89 -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 +49 -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 +59 -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 +48 -7
- data/lib/nl/core.rb +0 -94
- data/lib/nl/genl/connection.rb +0 -89
- data/lib/nl/protocols/genl.rb +0 -74
- data/lib/nl/protocols/raw.rb +0 -460
data/lib/nl/family.rb
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#--
|
|
2
|
-
# rbs_inline: enabled
|
|
3
1
|
require_relative 'connection'
|
|
4
2
|
require_relative 'notification'
|
|
5
3
|
|
|
@@ -9,14 +7,14 @@ module Nl
|
|
|
9
7
|
#
|
|
10
8
|
# interface _Connection
|
|
11
9
|
# def exchange: (
|
|
12
|
-
#
|
|
10
|
+
# Raw::Endpoint endpoint,
|
|
13
11
|
# Symbol kind,
|
|
14
12
|
# Class request_class,
|
|
15
13
|
# Class reply_class,
|
|
16
14
|
# Hash[Symbol, untyped] args
|
|
17
15
|
# ) ?{ (untyped) -> void } -> untyped
|
|
18
16
|
# def exchange_async: (
|
|
19
|
-
#
|
|
17
|
+
# Raw::Endpoint endpoint,
|
|
20
18
|
# Symbol kind,
|
|
21
19
|
# Class request_class,
|
|
22
20
|
# Class reply_class,
|
|
@@ -24,97 +22,93 @@ module Nl
|
|
|
24
22
|
# ?stream_capacity: Integer?
|
|
25
23
|
# ) -> (Async::Future[untyped] | Async::Stream[untyped])
|
|
26
24
|
# def async_capable?: () -> bool
|
|
27
|
-
# def register_notifications: (
|
|
25
|
+
# def register_notifications: (Raw::Endpoint, Hash[Integer, Class]) -> NotificationChannel
|
|
28
26
|
# def add_memberships: (Array[Integer]) -> nil
|
|
29
27
|
# def drop_memberships: (Array[Integer]) -> nil
|
|
30
|
-
# def receive_notification: (
|
|
28
|
+
# def receive_notification: (Raw::Endpoint, ?timeout: Numeric?) -> untyped
|
|
31
29
|
# def close: () -> nil
|
|
32
30
|
# end
|
|
31
|
+
#
|
|
32
|
+
# interface _FamilyClass[out F]
|
|
33
|
+
# def new: (_Connection, endpoint: Raw::Endpoint) -> F
|
|
34
|
+
# end
|
|
33
35
|
|
|
34
36
|
class Family
|
|
35
|
-
DEFAULT_NOTIFICATION_CAPACITY = Connection::DEFAULT_NOTIFICATION_CAPACITY
|
|
37
|
+
DEFAULT_NOTIFICATION_CAPACITY = Connection::DEFAULT_NOTIFICATION_CAPACITY #: Integer
|
|
36
38
|
|
|
39
|
+
# Socket-owning family.
|
|
40
|
+
#
|
|
41
|
+
# {Family} instances that owns sockets are extended with this module.
|
|
37
42
|
module Session
|
|
38
|
-
|
|
43
|
+
# Closes the Netlink socket owned by this {Family}.
|
|
44
|
+
# @rbs () -> void
|
|
45
|
+
def close
|
|
39
46
|
@connection.close
|
|
40
47
|
end
|
|
41
48
|
end
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# @rbs return: instance
|
|
47
|
-
def initialize(connection, protocol: self.class::PROTOCOL)
|
|
48
|
-
@protocol = protocol
|
|
50
|
+
# @rbs (_Connection connection, endpoint: Raw::Endpoint) -> instance
|
|
51
|
+
def initialize(connection, endpoint:)
|
|
52
|
+
@endpoint = endpoint
|
|
49
53
|
@connection = connection
|
|
50
|
-
@connection.register_notifications(@
|
|
54
|
+
@connection.register_notifications(@endpoint, notification_classes)
|
|
51
55
|
@notification_stream = NotificationStream.new do |timeout|
|
|
52
|
-
@connection.receive_notification(@
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
#--
|
|
57
|
-
# @rbs (?executor: executor?, ?notification_capacity: Integer?) -> (Session & instance)
|
|
58
|
-
# | [R] (?executor: executor?, ?notification_capacity: Integer?) { (instance) -> R } -> R
|
|
59
|
-
def self.open(executor: nil, notification_capacity: DEFAULT_NOTIFICATION_CAPACITY)
|
|
60
|
-
session = build_session(executor:, notification_capacity:)
|
|
61
|
-
return session unless block_given?
|
|
62
|
-
|
|
63
|
-
begin
|
|
64
|
-
yield session
|
|
65
|
-
ensure
|
|
66
|
-
session.close
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
class << self
|
|
71
|
-
# @rbs (?executor: executor?, notification_capacity: Integer) -> (Session & instance)
|
|
72
|
-
private def build_session(executor: nil, notification_capacity:)
|
|
73
|
-
protocol = self::PROTOCOL
|
|
74
|
-
connection = Connection.new(protocol:, executor:, notification_capacity:)
|
|
75
|
-
new(connection).extend(Session)
|
|
76
|
-
rescue Exception
|
|
77
|
-
connection&.close
|
|
78
|
-
raise
|
|
56
|
+
@connection.receive_notification(@endpoint, timeout:)
|
|
79
57
|
end
|
|
80
58
|
end
|
|
81
59
|
|
|
82
60
|
private def exchange_message(kind, request_class, reply_class, args, &block)
|
|
83
|
-
@connection.exchange(@
|
|
61
|
+
@connection.exchange(@endpoint, kind, request_class, reply_class, args, &block)
|
|
84
62
|
end
|
|
85
63
|
|
|
86
|
-
|
|
64
|
+
# Returns if this family supports asynchronous operations.
|
|
65
|
+
#
|
|
66
|
+
# @rbs () -> bool
|
|
67
|
+
def async_capable?
|
|
87
68
|
@connection.async_capable?
|
|
88
69
|
end
|
|
89
70
|
|
|
90
|
-
# Adds multicast memberships to the
|
|
91
|
-
#
|
|
71
|
+
# Adds multicast memberships to the netlink socket.
|
|
72
|
+
#
|
|
73
|
+
# Membership is additive and remains active until explicitly removed or the owner closes.
|
|
74
|
+
#
|
|
75
|
+
# @param [Array<Symbol>] groups
|
|
76
|
+
# @return [Family] self
|
|
77
|
+
# @rbs (Symbol *groups) -> self
|
|
92
78
|
def subscribe(*groups)
|
|
93
79
|
@connection.add_memberships(multicast_group_ids(groups))
|
|
94
80
|
self
|
|
95
81
|
end
|
|
96
82
|
|
|
83
|
+
# Removes multicast memberships from the netlink socket.
|
|
84
|
+
#
|
|
85
|
+
# @param [Array<Symbol>] groups
|
|
86
|
+
# @return [Family] self
|
|
87
|
+
# @rbs (Symbol *groups) -> self
|
|
97
88
|
def unsubscribe(*groups)
|
|
98
89
|
@connection.drop_memberships(multicast_group_ids(groups))
|
|
99
90
|
self
|
|
100
91
|
end
|
|
101
92
|
|
|
93
|
+
# Receives the next unsolicited message.
|
|
94
|
+
#
|
|
95
|
+
# @rbs (?timeout: Integer?) -> Message
|
|
102
96
|
def receive_notification(timeout: nil)
|
|
103
97
|
@notification_stream.next(timeout:)
|
|
104
98
|
end
|
|
105
99
|
|
|
100
|
+
# Receives unsolicited messages.
|
|
101
|
+
#
|
|
102
|
+
# @rbs () { (Message) -> void } -> void
|
|
106
103
|
def each_notification(&block)
|
|
107
104
|
return @notification_stream.each unless block
|
|
108
105
|
|
|
109
106
|
@notification_stream.each(&block)
|
|
110
107
|
end
|
|
111
108
|
|
|
112
|
-
# Builds a
|
|
113
|
-
#
|
|
114
|
-
|
|
115
|
-
# @rbs operations_class: Class
|
|
116
|
-
# @rbs stream_capacity: Integer?
|
|
117
|
-
# @rbs return: untyped
|
|
109
|
+
# Builds a asynchronous-operation facade.
|
|
110
|
+
#
|
|
111
|
+
# @rbs (Class operations_class, ?stream_capacity: Integer?) -> untyped
|
|
118
112
|
private def build_async_facade(operations_class, stream_capacity: nil)
|
|
119
113
|
unless async_capable?
|
|
120
114
|
raise Async::UnavailableError, 'async operations require an executor'
|
|
@@ -126,7 +120,7 @@ module Nl
|
|
|
126
120
|
end
|
|
127
121
|
|
|
128
122
|
private def exchange_message_async(kind, request_class, reply_class, args, stream_capacity: nil)
|
|
129
|
-
@connection.exchange_async(@
|
|
123
|
+
@connection.exchange_async(@endpoint, kind, request_class, reply_class, args, stream_capacity:)
|
|
130
124
|
end
|
|
131
125
|
|
|
132
126
|
private def notification_classes
|
|
@@ -145,10 +139,11 @@ module Nl
|
|
|
145
139
|
names.map do |name|
|
|
146
140
|
key = name.to_sym
|
|
147
141
|
group = multicast_groups.fetch(key) do
|
|
148
|
-
raise UnknownMulticastGroupError, "unknown multicast group #{name.inspect} for #{@
|
|
142
|
+
raise UnknownMulticastGroupError, "unknown multicast group #{name.inspect} for #{@endpoint.name}"
|
|
149
143
|
end
|
|
150
|
-
@
|
|
144
|
+
@endpoint.multicast_group_id(group.name, group.id)
|
|
151
145
|
end
|
|
152
146
|
end
|
|
153
147
|
end
|
|
148
|
+
|
|
154
149
|
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Generic Netlink client handling
|
|
2
|
+
#-
|
|
3
|
+
|
|
4
|
+
require_relative '../connection'
|
|
5
|
+
require_relative 'wire'
|
|
6
|
+
|
|
7
|
+
module Nl
|
|
8
|
+
module Genl
|
|
9
|
+
# Dynamically resolved information for a generic Netlink family.
|
|
10
|
+
#
|
|
11
|
+
# @!attribute [r] id
|
|
12
|
+
# @return [Integer] the assigned family ID
|
|
13
|
+
# @!attribute [r] multicast_groups
|
|
14
|
+
# @return [Hash<String, Integer>] multicast group names mapped to their IDs
|
|
15
|
+
FamilyInfo = Data.define(
|
|
16
|
+
:id, #: Integer
|
|
17
|
+
:multicast_groups, #: Hash[String, Integer]
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Owns one generic Netlink connection shared by compatible families.
|
|
21
|
+
class Client
|
|
22
|
+
# Opens a generic Netlink client.
|
|
23
|
+
#
|
|
24
|
+
# @overload open(resolver:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY)
|
|
25
|
+
# The caller is responsible for closing the client.
|
|
26
|
+
# @param [#call] resolver a callable that resolves a family name to {FamilyInfo}
|
|
27
|
+
# @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
|
|
28
|
+
# @param [Integer] notification_capacity the maximum number of queued notifications
|
|
29
|
+
# @return [Client] the opened client
|
|
30
|
+
# @overload open(resolver:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY, &block)
|
|
31
|
+
# The client is automatically closed after the block returns.
|
|
32
|
+
# @param [#call] resolver a callable that resolves a family name to {FamilyInfo}
|
|
33
|
+
# @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
|
|
34
|
+
# @param [Integer] notification_capacity the maximum number of queued notifications
|
|
35
|
+
# @yieldparam [Client] client the opened client
|
|
36
|
+
# @return [Object] the value returned from the block
|
|
37
|
+
# @rbs (resolver: ^(instance, ::String) -> FamilyInfo, ?executor: executor?, ?notification_capacity: Integer?) -> instance
|
|
38
|
+
# | [R] (resolver: ^(instance, ::String) -> FamilyInfo, ?executor: executor?, ?notification_capacity: Integer?) { (instance) -> R } -> R
|
|
39
|
+
def self.open(resolver:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY)
|
|
40
|
+
client = new(resolver:, executor:, notification_capacity:)
|
|
41
|
+
return client unless block_given?
|
|
42
|
+
|
|
43
|
+
begin
|
|
44
|
+
yield client
|
|
45
|
+
ensure
|
|
46
|
+
client.close
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @param [#call] resolver a callable that resolves a family name to {FamilyInfo}
|
|
51
|
+
# @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
|
|
52
|
+
# @param [Integer] notification_capacity the maximum number of queued notifications
|
|
53
|
+
# @rbs (resolver: ^(instance, ::String) -> FamilyInfo, ?executor: executor?, ?notification_capacity: Integer?) -> void
|
|
54
|
+
def initialize(resolver:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY)
|
|
55
|
+
@resolver = resolver
|
|
56
|
+
@family_cache = {}
|
|
57
|
+
@family_cache_mutex = Mutex.new
|
|
58
|
+
@connection = Nl::Connection.new(
|
|
59
|
+
protocol: Protocol.new,
|
|
60
|
+
executor:,
|
|
61
|
+
notification_capacity:,
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Builds a family backed by this client's connection.
|
|
66
|
+
#
|
|
67
|
+
# @param [Class<Family>] family_class a generic Netlink family class
|
|
68
|
+
# @return [Family] an instance of `family_class`
|
|
69
|
+
# @raise [TypeError] if +family_class+ does not inherit from {Family}
|
|
70
|
+
# @rbs [F < Family] (_FamilyClass[F] family_class) -> F
|
|
71
|
+
def family(family_class)
|
|
72
|
+
unless family_class <= Family
|
|
73
|
+
raise TypeError, "family class must inherit from #{Family}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
info = family_info(family_class::NAME)
|
|
77
|
+
family_class.new(
|
|
78
|
+
@connection,
|
|
79
|
+
endpoint: Endpoint.new(family_class, info),
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Closes the underlying Netlink connection.
|
|
84
|
+
#
|
|
85
|
+
# @return [void]
|
|
86
|
+
# @rbs () -> void
|
|
87
|
+
def close
|
|
88
|
+
@connection.close
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Resolves and caches information for a family by name.
|
|
92
|
+
#
|
|
93
|
+
# @param [String] name the generic Netlink family name
|
|
94
|
+
# @return [FamilyInfo] the resolved family information
|
|
95
|
+
# @rbs (String name) -> FamilyInfo
|
|
96
|
+
private def family_info(name)
|
|
97
|
+
cached_info = @family_cache_mutex.synchronize { @family_cache[name] }
|
|
98
|
+
return cached_info if cached_info
|
|
99
|
+
|
|
100
|
+
info = @resolver.call(self, name)
|
|
101
|
+
@family_cache_mutex.synchronize { @family_cache[name] ||= info }
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require_relative 'wire'
|
|
2
|
+
require_relative '../raw'
|
|
3
|
+
|
|
4
|
+
module Nl
|
|
5
|
+
module Genl
|
|
6
|
+
# A generated Generic Netlink family bound to nlctrl-provided information.
|
|
7
|
+
class Endpoint < Raw::Endpoint
|
|
8
|
+
attr_reader :info
|
|
9
|
+
|
|
10
|
+
def initialize(definition, info)
|
|
11
|
+
super(definition)
|
|
12
|
+
@info = info
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def family_id = @info.id
|
|
16
|
+
def version = @definition.version
|
|
17
|
+
def frame_type(_message_class) = family_id
|
|
18
|
+
|
|
19
|
+
def multicast_group_id(name, _value)
|
|
20
|
+
@info.multicast_groups.fetch(name) do
|
|
21
|
+
raise UnresolvedMulticastGroupError,
|
|
22
|
+
"Generic Netlink multicast group #{name.inspect} was not resolved"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Socket-wide Generic Netlink wire behavior.
|
|
28
|
+
class Protocol < Raw::Protocol
|
|
29
|
+
def initialize
|
|
30
|
+
super(Raw::NETLINK_GENERIC)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def encode_message(encoder, endpoint, request, seq:, pid:)
|
|
34
|
+
message = request.message
|
|
35
|
+
header = Raw::NlMsgHdr.new(0, request.type, request.flags, seq, pid)
|
|
36
|
+
encoder.measure(Endian::Host::U16) do
|
|
37
|
+
header.encode(encoder)
|
|
38
|
+
Nl::Genl::GenlMsgHdr.new(message.class::TYPE, endpoint.version, 0).encode(encoder)
|
|
39
|
+
message.encode(encoder)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def notification_channel_key(endpoint) = endpoint.family_id
|
|
44
|
+
def notification_route_keys(endpoint, _classes) = [endpoint.family_id]
|
|
45
|
+
def notification_frame_key(header) = header.type
|
|
46
|
+
|
|
47
|
+
def notification_frame?(endpoint, header, _payload)
|
|
48
|
+
header.type == endpoint.family_id
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def notification_class(endpoint, header, payload, classes)
|
|
52
|
+
return unless notification_frame?(endpoint, header, payload)
|
|
53
|
+
|
|
54
|
+
command = Nl::Genl::GenlMsgHdr.decode(Decoder.new(payload)).cmd
|
|
55
|
+
classes[command]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/nl/genl/wire.rb
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Generic Netlink wire definitions
|
|
2
|
+
#-
|
|
3
|
+
|
|
4
|
+
require_relative '../raw/wire'
|
|
5
|
+
require_relative '../endian'
|
|
6
|
+
|
|
7
|
+
module Nl
|
|
8
|
+
module Genl
|
|
9
|
+
# Constants from <linux/genetlink.h>
|
|
10
|
+
module Constants
|
|
11
|
+
GENL_NAMSIZ = 16
|
|
12
|
+
GENL_MIN_ID = Raw::NLMSG_MIN_TYPE
|
|
13
|
+
GENL_MAX_ID = 1023
|
|
14
|
+
|
|
15
|
+
GENL_HDRLEN = 4
|
|
16
|
+
|
|
17
|
+
GENL_ID_GENERATE = 0
|
|
18
|
+
GENL_ID_CTRL = Raw::NLMSG_MIN_TYPE
|
|
19
|
+
GENL_ID_VFS_DQUOT = Raw::NLMSG_MIN_TYPE + 1
|
|
20
|
+
GENL_ID_PMCRAID = Raw::NLMSG_MIN_TYPE + 2
|
|
21
|
+
|
|
22
|
+
CTRL_CMD_UNSPEC = 0
|
|
23
|
+
CTRL_CMD_NEWFAMILY = 1
|
|
24
|
+
CTRL_CMD_DELFAMILY = 2
|
|
25
|
+
CTRL_CMD_GETFAMILY = 3
|
|
26
|
+
CTRL_CMD_NEWOPS = 4
|
|
27
|
+
CTRL_CMD_DELOPS = 5
|
|
28
|
+
CTRL_CMD_GETOPS = 6
|
|
29
|
+
CTRL_CMD_NEWMCAST_GRP = 7
|
|
30
|
+
CTRL_CMD_DELMCAST_GRP = 8
|
|
31
|
+
CTRL_CMD_GETMCAST_GRP = 9
|
|
32
|
+
|
|
33
|
+
CTRL_ATTR_UNSPEC = 0
|
|
34
|
+
CTRL_ATTR_FAMILY_ID = 1
|
|
35
|
+
CTRL_ATTR_FAMILY_NAME = 2
|
|
36
|
+
CTRL_ATTR_VERSION = 3
|
|
37
|
+
CTRL_ATTR_HDRSIZE = 4
|
|
38
|
+
CTRL_ATTR_MAXATTR = 5
|
|
39
|
+
CTRL_ATTR_OPS = 6
|
|
40
|
+
CTRL_ATTR_MCAST_GROUPS = 7
|
|
41
|
+
|
|
42
|
+
CTRL_ATTR_OP_UNSPEC = 0
|
|
43
|
+
CTRL_ATTR_OP_ID = 1
|
|
44
|
+
CTRL_ATTR_OP_FLAGS = 2
|
|
45
|
+
|
|
46
|
+
CTRL_ATTR_MCAST_GRP_UNSPEC = 0
|
|
47
|
+
CTRL_ATTR_MCAST_GRP_NAME = 1
|
|
48
|
+
CTRL_ATTR_MCAST_GRP_ID = 2
|
|
49
|
+
end
|
|
50
|
+
include Constants
|
|
51
|
+
|
|
52
|
+
# Header prepended to a Generic Netlink payload after the Netlink header.
|
|
53
|
+
#
|
|
54
|
+
# This corresponds to Linux's +struct genlmsghdr+.
|
|
55
|
+
#
|
|
56
|
+
# @!attribute [rw] cmd
|
|
57
|
+
# @return [Integer] family-specific command identifier
|
|
58
|
+
# @!attribute [rw] version
|
|
59
|
+
# @return [Integer] family-specific protocol version
|
|
60
|
+
# @!attribute [rw] reserved
|
|
61
|
+
# @return [Integer] reserved field, which must be zero
|
|
62
|
+
GenlMsgHdr = Struct.new(
|
|
63
|
+
:cmd, #: Integer
|
|
64
|
+
:version, #: Integer
|
|
65
|
+
:reserved, #: Integer
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
class GenlMsgHdr
|
|
69
|
+
FORMAT = Ractor.make_shareable([
|
|
70
|
+
Endian::Host::U8,
|
|
71
|
+
Endian::Host::U8,
|
|
72
|
+
Endian::Host::U16,
|
|
73
|
+
])
|
|
74
|
+
private_constant :FORMAT
|
|
75
|
+
|
|
76
|
+
# Decodes a header from the decoder's current position.
|
|
77
|
+
#
|
|
78
|
+
# @param [Decoder] decoder the source decoder
|
|
79
|
+
# @return [GenlMsgHdr] the decoded header
|
|
80
|
+
# @rbs (Decoder decoder) -> instance
|
|
81
|
+
def self.decode(decoder)
|
|
82
|
+
new(*decoder.get_values(FORMAT))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Encodes this header at the encoder's current position.
|
|
86
|
+
#
|
|
87
|
+
# @param [Encoder] encoder the destination encoder
|
|
88
|
+
# @return [void]
|
|
89
|
+
# @rbs (Encoder encoder) -> void
|
|
90
|
+
def encode(encoder)
|
|
91
|
+
encoder.put_values(FORMAT, to_a)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
data/lib/nl/genl.rb
CHANGED
|
@@ -1,74 +1,72 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
require_relative 'core'
|
|
6
|
-
require_relative 'endian'
|
|
1
|
+
require_relative 'genl/wire'
|
|
2
|
+
require_relative 'family'
|
|
3
|
+
require_relative 'raw'
|
|
4
|
+
require_relative 'genl/protocol'
|
|
7
5
|
|
|
8
6
|
module Nl
|
|
7
|
+
# Generic Netlink families.
|
|
9
8
|
module Genl
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
GENL_ID_GENERATE = 0
|
|
19
|
-
GENL_ID_CTRL = Core::NLMSG_MIN_TYPE
|
|
20
|
-
GENL_ID_VFS_DQUOT = Core::NLMSG_MIN_TYPE + 1
|
|
21
|
-
GENL_ID_PMCRAID = Core::NLMSG_MIN_TYPE + 2
|
|
22
|
-
|
|
23
|
-
CTRL_CMD_UNSPEC = 0
|
|
24
|
-
CTRL_CMD_NEWFAMILY = 1
|
|
25
|
-
CTRL_CMD_DELFAMILY = 2
|
|
26
|
-
CTRL_CMD_GETFAMILY = 3
|
|
27
|
-
CTRL_CMD_NEWOPS = 4
|
|
28
|
-
CTRL_CMD_DELOPS = 5
|
|
29
|
-
CTRL_CMD_GETOPS = 6
|
|
30
|
-
CTRL_CMD_NEWMCAST_GRP = 7
|
|
31
|
-
CTRL_CMD_DELMCAST_GRP = 8
|
|
32
|
-
CTRL_CMD_GETMCAST_GRP = 9
|
|
33
|
-
|
|
34
|
-
CTRL_ATTR_UNSPEC = 0
|
|
35
|
-
CTRL_ATTR_FAMILY_ID = 1
|
|
36
|
-
CTRL_ATTR_FAMILY_NAME = 2
|
|
37
|
-
CTRL_ATTR_VERSION = 3
|
|
38
|
-
CTRL_ATTR_HDRSIZE = 4
|
|
39
|
-
CTRL_ATTR_MAXATTR = 5
|
|
40
|
-
CTRL_ATTR_OPS = 6
|
|
41
|
-
CTRL_ATTR_MCAST_GROUPS = 7
|
|
9
|
+
# Base class for Generic Netlink families.
|
|
10
|
+
class Family < Nl::Family
|
|
11
|
+
# Returns the family-specific protocol version.
|
|
12
|
+
#
|
|
13
|
+
# @return [Integer] the protocol version
|
|
14
|
+
# @rbs () -> Integer
|
|
15
|
+
def self.version = self::VERSION
|
|
42
16
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
17
|
+
# Opens a session for this Generic Netlink family.
|
|
18
|
+
#
|
|
19
|
+
# @overload open(resolver:, executor: nil, notification_capacity: DEFAULT_NOTIFICATION_CAPACITY)
|
|
20
|
+
# The caller is responsible for closing the session.
|
|
21
|
+
# @param [#call] resolver a callable that resolves a family name to {FamilyInfo}
|
|
22
|
+
# @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
|
|
23
|
+
# @param [Integer] notification_capacity the maximum number of queued notifications
|
|
24
|
+
# @return [Family] the opened family session
|
|
25
|
+
# @overload open(resolver:, executor: nil, notification_capacity: DEFAULT_NOTIFICATION_CAPACITY, &block)
|
|
26
|
+
# The session is automatically closed after the block returns.
|
|
27
|
+
# @param [#call] resolver a callable that resolves a family name to {FamilyInfo}
|
|
28
|
+
# @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
|
|
29
|
+
# @param [Integer] notification_capacity the maximum number of queued notifications
|
|
30
|
+
# @yieldparam [Family] session the opened family session
|
|
31
|
+
# @return [Object] the value returned from the block
|
|
32
|
+
# @rbs (resolver: ^(Client, ::String) -> FamilyInfo, ?executor: executor?, ?notification_capacity: Integer?) -> (Nl::Family::Session & instance)
|
|
33
|
+
# | [R] (resolver: ^(Client, ::String) -> FamilyInfo, ?executor: executor?, ?notification_capacity: Integer?) { (instance) -> R } -> R
|
|
34
|
+
def self.open(resolver:, executor: nil, notification_capacity: DEFAULT_NOTIFICATION_CAPACITY)
|
|
35
|
+
begin
|
|
36
|
+
owner = Client.new(resolver:, executor:, notification_capacity:)
|
|
37
|
+
session = owner.family(self).extend(Nl::Family::Session)
|
|
38
|
+
rescue Exception
|
|
39
|
+
owner&.close
|
|
40
|
+
raise
|
|
41
|
+
end
|
|
42
|
+
return session unless block_given?
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
GenlMsgHdr = Struct.new(:cmd, :version, :reserved)
|
|
54
|
-
# Generic Netlink message header
|
|
55
|
-
class GenlMsgHdr
|
|
56
|
-
FORMAT = Ractor.make_shareable([
|
|
57
|
-
Endian::Host::U8,
|
|
58
|
-
Endian::Host::U8,
|
|
59
|
-
Endian::Host::U16,
|
|
60
|
-
])
|
|
61
|
-
private_constant :FORMAT
|
|
62
|
-
|
|
63
|
-
def self.decode(decoder)
|
|
64
|
-
new(*decoder.get_values(FORMAT))
|
|
44
|
+
begin
|
|
45
|
+
yield session
|
|
46
|
+
ensure
|
|
47
|
+
session.close
|
|
48
|
+
end
|
|
65
49
|
end
|
|
50
|
+
end
|
|
66
51
|
|
|
67
|
-
|
|
68
|
-
|
|
52
|
+
# Base class for Generic Netlink messages.
|
|
53
|
+
#
|
|
54
|
+
# A Generic Netlink message payload begins with a {GenlMsgHdr}, followed by
|
|
55
|
+
# the optional fixed header and attributes handled by {Raw::Message}.
|
|
56
|
+
class Message < Raw::Message
|
|
57
|
+
# Decodes a Generic Netlink message payload.
|
|
58
|
+
#
|
|
59
|
+
# @param [Decoder] decoder the source decoder
|
|
60
|
+
# @param [Integer] type the family ID from the Netlink message header
|
|
61
|
+
# @return [Message] the decoded message
|
|
62
|
+
# @raise [RuntimeError] if the command does not match the message class's type
|
|
63
|
+
# @rbs (Decoder decoder, type: Integer) -> instance
|
|
64
|
+
def self.decode(decoder, type:)
|
|
65
|
+
genlhdr = GenlMsgHdr.decode(decoder)
|
|
66
|
+
super(decoder, type: genlhdr.cmd)
|
|
69
67
|
end
|
|
70
68
|
end
|
|
71
69
|
end
|
|
72
70
|
end
|
|
73
71
|
|
|
74
|
-
require_relative 'genl/
|
|
72
|
+
require_relative 'genl/client'
|
data/lib/nl/notification.rb
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
# rbs_inline: enabled
|
|
2
|
-
|
|
3
1
|
require_relative 'error'
|
|
4
2
|
|
|
5
3
|
module Nl
|
|
@@ -7,9 +5,6 @@ module Nl
|
|
|
7
5
|
# `id` is the fixed ID when the specification provides one.
|
|
8
6
|
McastGroup = Data.define(:name, :id)
|
|
9
7
|
|
|
10
|
-
# An unsolicited message whose wire type is not present in the loaded spec.
|
|
11
|
-
UnknownNotification = Data.define(:header, :payload)
|
|
12
|
-
|
|
13
8
|
# Thread-safe queue shared by a family's blocking and asynchronous facades.
|
|
14
9
|
class NotificationChannel
|
|
15
10
|
def initialize(capacity:)
|
|
@@ -96,7 +91,7 @@ module Nl
|
|
|
96
91
|
|
|
97
92
|
# An unbounded-in-time, single-family view of unsolicited messages.
|
|
98
93
|
class NotificationStream
|
|
99
|
-
include Enumerable
|
|
94
|
+
include Enumerable #[untyped]
|
|
100
95
|
|
|
101
96
|
def initialize(&receive)
|
|
102
97
|
@receive = receive
|
|
@@ -106,6 +101,8 @@ module Nl
|
|
|
106
101
|
@receive.call(timeout)
|
|
107
102
|
end
|
|
108
103
|
|
|
104
|
+
# @rbs () -> Enumerator[untyped, void]
|
|
105
|
+
# | () { (untyped) -> void } -> void
|
|
109
106
|
def each
|
|
110
107
|
return enum_for(__method__) unless block_given?
|
|
111
108
|
|