nl 0.3.0 → 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 +13 -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 +10 -0
- data/lib/nl/exchange.rb +40 -18
- data/lib/nl/family.rb +18 -41
- 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 +61 -61
- 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 +146 -0
- data/lib/nl/raw.rb +104 -0
- data/lib/nl/sequence_allocator.rb +0 -2
- 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 +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 +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,12 +22,16 @@ 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
37
|
DEFAULT_NOTIFICATION_CAPACITY = Connection::DEFAULT_NOTIFICATION_CAPACITY
|
|
@@ -42,45 +44,19 @@ module Nl
|
|
|
42
44
|
|
|
43
45
|
#--
|
|
44
46
|
# @rbs connection: _Connection
|
|
45
|
-
# @rbs
|
|
47
|
+
# @rbs endpoint: Raw::Endpoint
|
|
46
48
|
# @rbs return: instance
|
|
47
|
-
def initialize(connection,
|
|
48
|
-
@
|
|
49
|
+
def initialize(connection, endpoint:)
|
|
50
|
+
@endpoint = endpoint
|
|
49
51
|
@connection = connection
|
|
50
|
-
@connection.register_notifications(@
|
|
52
|
+
@connection.register_notifications(@endpoint, notification_classes)
|
|
51
53
|
@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
|
|
54
|
+
@connection.receive_notification(@endpoint, timeout:)
|
|
79
55
|
end
|
|
80
56
|
end
|
|
81
57
|
|
|
82
58
|
private def exchange_message(kind, request_class, reply_class, args, &block)
|
|
83
|
-
@connection.exchange(@
|
|
59
|
+
@connection.exchange(@endpoint, kind, request_class, reply_class, args, &block)
|
|
84
60
|
end
|
|
85
61
|
|
|
86
62
|
def async_capable? #: bool
|
|
@@ -126,7 +102,7 @@ module Nl
|
|
|
126
102
|
end
|
|
127
103
|
|
|
128
104
|
private def exchange_message_async(kind, request_class, reply_class, args, stream_capacity: nil)
|
|
129
|
-
@connection.exchange_async(@
|
|
105
|
+
@connection.exchange_async(@endpoint, kind, request_class, reply_class, args, stream_capacity:)
|
|
130
106
|
end
|
|
131
107
|
|
|
132
108
|
private def notification_classes
|
|
@@ -145,10 +121,11 @@ module Nl
|
|
|
145
121
|
names.map do |name|
|
|
146
122
|
key = name.to_sym
|
|
147
123
|
group = multicast_groups.fetch(key) do
|
|
148
|
-
raise UnknownMulticastGroupError, "unknown multicast group #{name.inspect} for #{@
|
|
124
|
+
raise UnknownMulticastGroupError, "unknown multicast group #{name.inspect} for #{@endpoint.name}"
|
|
149
125
|
end
|
|
150
|
-
@
|
|
126
|
+
@endpoint.multicast_group_id(group.name, group.id)
|
|
151
127
|
end
|
|
152
128
|
end
|
|
153
129
|
end
|
|
130
|
+
|
|
154
131
|
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Generic Netlink client handling
|
|
2
|
+
|
|
3
|
+
require_relative '../connection'
|
|
4
|
+
require_relative 'wire'
|
|
5
|
+
|
|
6
|
+
module Nl
|
|
7
|
+
module Genl
|
|
8
|
+
# Dynamically resolved information for a generic Netlink family.
|
|
9
|
+
#
|
|
10
|
+
# @!attribute [r] id
|
|
11
|
+
# @return [Integer] the assigned family ID
|
|
12
|
+
# @!attribute [r] multicast_groups
|
|
13
|
+
# @return [Hash<String, Integer>] multicast group names mapped to their IDs
|
|
14
|
+
FamilyInfo = Data.define(
|
|
15
|
+
:id, #: Integer
|
|
16
|
+
:multicast_groups, #: Hash[String, Integer]
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# Owns one generic Netlink connection shared by compatible families.
|
|
20
|
+
class Client
|
|
21
|
+
# Opens a generic Netlink client.
|
|
22
|
+
#
|
|
23
|
+
# @overload open(resolver:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY)
|
|
24
|
+
# The caller is responsible for closing the client.
|
|
25
|
+
# @param [#call] resolver a callable that resolves a family name to {FamilyInfo}
|
|
26
|
+
# @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
|
|
27
|
+
# @param [Integer] notification_capacity the maximum number of queued notifications
|
|
28
|
+
# @return [Client] the opened client
|
|
29
|
+
# @overload open(resolver:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY, &block)
|
|
30
|
+
# The client is automatically closed after the block returns.
|
|
31
|
+
# @param [#call] resolver a callable that resolves a family name to {FamilyInfo}
|
|
32
|
+
# @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
|
|
33
|
+
# @param [Integer] notification_capacity the maximum number of queued notifications
|
|
34
|
+
# @yieldparam [Client] client the opened client
|
|
35
|
+
# @return [Object] the value returned from the block
|
|
36
|
+
# @rbs (resolver: ^(instance, ::String) -> FamilyInfo, ?executor: executor?, ?notification_capacity: Integer?) -> instance
|
|
37
|
+
# | [R] (resolver: ^(instance, ::String) -> FamilyInfo, ?executor: executor?, ?notification_capacity: Integer?) { (instance) -> R } -> R
|
|
38
|
+
def self.open(resolver:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY)
|
|
39
|
+
client = new(resolver:, executor:, notification_capacity:)
|
|
40
|
+
return client unless block_given?
|
|
41
|
+
|
|
42
|
+
begin
|
|
43
|
+
yield client
|
|
44
|
+
ensure
|
|
45
|
+
client.close
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @param [#call] resolver a callable that resolves a family name to {FamilyInfo}
|
|
50
|
+
# @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
|
|
51
|
+
# @param [Integer] notification_capacity the maximum number of queued notifications
|
|
52
|
+
# @rbs (resolver: ^(instance, ::String) -> FamilyInfo, ?executor: executor?, ?notification_capacity: Integer?) -> void
|
|
53
|
+
def initialize(resolver:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY)
|
|
54
|
+
@resolver = resolver
|
|
55
|
+
@family_cache = {}
|
|
56
|
+
@family_cache_mutex = Mutex.new
|
|
57
|
+
@connection = Nl::Connection.new(
|
|
58
|
+
protocol: Protocol.new,
|
|
59
|
+
executor:,
|
|
60
|
+
notification_capacity:,
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Builds a family backed by this client's connection.
|
|
65
|
+
#
|
|
66
|
+
# @param [Class<Family>] family_class a generic Netlink family class
|
|
67
|
+
# @return [Family] an instance of `family_class`
|
|
68
|
+
# @raise [TypeError] if +family_class+ does not inherit from {Family}
|
|
69
|
+
# @rbs [F < Family] (_FamilyClass[F] family_class) -> F
|
|
70
|
+
def family(family_class)
|
|
71
|
+
unless family_class <= Family
|
|
72
|
+
raise TypeError, "family class must inherit from #{Family}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
info = family_info(family_class::NAME)
|
|
76
|
+
family_class.new(
|
|
77
|
+
@connection,
|
|
78
|
+
endpoint: Endpoint.new(family_class, info),
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Closes the underlying Netlink connection.
|
|
83
|
+
#
|
|
84
|
+
# @return [void]
|
|
85
|
+
# @rbs () -> void
|
|
86
|
+
def close
|
|
87
|
+
@connection.close
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Resolves and caches information for a family by name.
|
|
91
|
+
#
|
|
92
|
+
# @param [String] name the generic Netlink family name
|
|
93
|
+
# @return [FamilyInfo] the resolved family information
|
|
94
|
+
# @rbs (String name) -> FamilyInfo
|
|
95
|
+
private def family_info(name)
|
|
96
|
+
cached_info = @family_cache_mutex.synchronize { @family_cache[name] }
|
|
97
|
+
return cached_info if cached_info
|
|
98
|
+
|
|
99
|
+
info = @resolver.call(self, name)
|
|
100
|
+
@family_cache_mutex.synchronize { @family_cache[name] ||= info }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
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,94 @@
|
|
|
1
|
+
# Generic Netlink wire definitions
|
|
2
|
+
|
|
3
|
+
require_relative '../raw/wire'
|
|
4
|
+
require_relative '../endian'
|
|
5
|
+
|
|
6
|
+
module Nl
|
|
7
|
+
module Genl
|
|
8
|
+
# Constants from <linux/genetlink.h>
|
|
9
|
+
module Constants
|
|
10
|
+
GENL_NAMSIZ = 16
|
|
11
|
+
GENL_MIN_ID = Raw::NLMSG_MIN_TYPE
|
|
12
|
+
GENL_MAX_ID = 1023
|
|
13
|
+
|
|
14
|
+
GENL_HDRLEN = 4
|
|
15
|
+
|
|
16
|
+
GENL_ID_GENERATE = 0
|
|
17
|
+
GENL_ID_CTRL = Raw::NLMSG_MIN_TYPE
|
|
18
|
+
GENL_ID_VFS_DQUOT = Raw::NLMSG_MIN_TYPE + 1
|
|
19
|
+
GENL_ID_PMCRAID = Raw::NLMSG_MIN_TYPE + 2
|
|
20
|
+
|
|
21
|
+
CTRL_CMD_UNSPEC = 0
|
|
22
|
+
CTRL_CMD_NEWFAMILY = 1
|
|
23
|
+
CTRL_CMD_DELFAMILY = 2
|
|
24
|
+
CTRL_CMD_GETFAMILY = 3
|
|
25
|
+
CTRL_CMD_NEWOPS = 4
|
|
26
|
+
CTRL_CMD_DELOPS = 5
|
|
27
|
+
CTRL_CMD_GETOPS = 6
|
|
28
|
+
CTRL_CMD_NEWMCAST_GRP = 7
|
|
29
|
+
CTRL_CMD_DELMCAST_GRP = 8
|
|
30
|
+
CTRL_CMD_GETMCAST_GRP = 9
|
|
31
|
+
|
|
32
|
+
CTRL_ATTR_UNSPEC = 0
|
|
33
|
+
CTRL_ATTR_FAMILY_ID = 1
|
|
34
|
+
CTRL_ATTR_FAMILY_NAME = 2
|
|
35
|
+
CTRL_ATTR_VERSION = 3
|
|
36
|
+
CTRL_ATTR_HDRSIZE = 4
|
|
37
|
+
CTRL_ATTR_MAXATTR = 5
|
|
38
|
+
CTRL_ATTR_OPS = 6
|
|
39
|
+
CTRL_ATTR_MCAST_GROUPS = 7
|
|
40
|
+
|
|
41
|
+
CTRL_ATTR_OP_UNSPEC = 0
|
|
42
|
+
CTRL_ATTR_OP_ID = 1
|
|
43
|
+
CTRL_ATTR_OP_FLAGS = 2
|
|
44
|
+
|
|
45
|
+
CTRL_ATTR_MCAST_GRP_UNSPEC = 0
|
|
46
|
+
CTRL_ATTR_MCAST_GRP_NAME = 1
|
|
47
|
+
CTRL_ATTR_MCAST_GRP_ID = 2
|
|
48
|
+
end
|
|
49
|
+
include Constants
|
|
50
|
+
|
|
51
|
+
# Header prepended to a Generic Netlink payload after the Netlink header.
|
|
52
|
+
#
|
|
53
|
+
# This corresponds to Linux's +struct genlmsghdr+.
|
|
54
|
+
#
|
|
55
|
+
# @!attribute [rw] cmd
|
|
56
|
+
# @return [Integer] family-specific command identifier
|
|
57
|
+
# @!attribute [rw] version
|
|
58
|
+
# @return [Integer] family-specific protocol version
|
|
59
|
+
# @!attribute [rw] reserved
|
|
60
|
+
# @return [Integer] reserved field, which must be zero
|
|
61
|
+
GenlMsgHdr = Struct.new(
|
|
62
|
+
:cmd, #: Integer
|
|
63
|
+
:version, #: Integer
|
|
64
|
+
:reserved, #: Integer
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
class GenlMsgHdr
|
|
68
|
+
FORMAT = Ractor.make_shareable([
|
|
69
|
+
Endian::Host::U8,
|
|
70
|
+
Endian::Host::U8,
|
|
71
|
+
Endian::Host::U16,
|
|
72
|
+
])
|
|
73
|
+
private_constant :FORMAT
|
|
74
|
+
|
|
75
|
+
# Decodes a header from the decoder's current position.
|
|
76
|
+
#
|
|
77
|
+
# @param [Decoder] decoder the source decoder
|
|
78
|
+
# @return [GenlMsgHdr] the decoded header
|
|
79
|
+
# @rbs (Decoder decoder) -> instance
|
|
80
|
+
def self.decode(decoder)
|
|
81
|
+
new(*decoder.get_values(FORMAT))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Encodes this header at the encoder's current position.
|
|
85
|
+
#
|
|
86
|
+
# @param [Encoder] encoder the destination encoder
|
|
87
|
+
# @return [void]
|
|
88
|
+
# @rbs (Encoder encoder) -> void
|
|
89
|
+
def encode(encoder)
|
|
90
|
+
encoder.put_values(FORMAT, to_a)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
data/lib/nl/genl.rb
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
# Generic Netlink
|
|
2
|
-
#--
|
|
3
|
-
# rbs_inline: enabled
|
|
1
|
+
# Generic Netlink family support
|
|
4
2
|
|
|
5
|
-
require_relative '
|
|
6
|
-
require_relative '
|
|
3
|
+
require_relative 'genl/wire'
|
|
4
|
+
require_relative 'family'
|
|
5
|
+
require_relative 'raw'
|
|
6
|
+
require_relative 'genl/protocol'
|
|
7
7
|
|
|
8
8
|
module Nl
|
|
9
|
+
# Generic Netlink families.
|
|
9
10
|
module Genl
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
# Base class for Generic Netlink families.
|
|
12
|
+
class Family < Nl::Family
|
|
13
|
+
# Returns the family-specific protocol version.
|
|
14
|
+
#
|
|
15
|
+
# @return [Integer] the protocol version
|
|
16
|
+
# @rbs () -> Integer
|
|
17
|
+
def self.version = self::VERSION
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
# Opens a session for this Generic Netlink family.
|
|
20
|
+
#
|
|
21
|
+
# @overload open(resolver:, executor: nil, notification_capacity: DEFAULT_NOTIFICATION_CAPACITY)
|
|
22
|
+
# The caller is responsible for closing the session.
|
|
23
|
+
# @param [#call] resolver a callable that resolves a family name to {FamilyInfo}
|
|
24
|
+
# @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
|
|
25
|
+
# @param [Integer] notification_capacity the maximum number of queued notifications
|
|
26
|
+
# @return [Family] the opened family session
|
|
27
|
+
# @overload open(resolver:, executor: nil, notification_capacity: DEFAULT_NOTIFICATION_CAPACITY, &block)
|
|
28
|
+
# The session is automatically closed after the block returns.
|
|
29
|
+
# @param [#call] resolver a callable that resolves a family name to {FamilyInfo}
|
|
30
|
+
# @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
|
|
31
|
+
# @param [Integer] notification_capacity the maximum number of queued notifications
|
|
32
|
+
# @yieldparam [Family] session the opened family session
|
|
33
|
+
# @return [Object] the value returned from the block
|
|
34
|
+
# @rbs (resolver: ^(Client, ::String) -> FamilyInfo, ?executor: executor?, ?notification_capacity: Integer?) -> (Nl::Family::Session & instance)
|
|
35
|
+
# | [R] (resolver: ^(Client, ::String) -> FamilyInfo, ?executor: executor?, ?notification_capacity: Integer?) { (instance) -> R } -> R
|
|
36
|
+
def self.open(resolver:, executor: nil, notification_capacity: DEFAULT_NOTIFICATION_CAPACITY)
|
|
37
|
+
begin
|
|
38
|
+
owner = Client.new(resolver:, executor:, notification_capacity:)
|
|
39
|
+
session = owner.family(self).extend(Nl::Family::Session)
|
|
40
|
+
rescue Exception
|
|
41
|
+
owner&.close
|
|
42
|
+
raise
|
|
43
|
+
end
|
|
44
|
+
return session unless block_given?
|
|
17
45
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
42
|
-
|
|
43
|
-
CTRL_ATTR_OP_UNSPEC = 0
|
|
44
|
-
CTRL_ATTR_OP_ID = 1
|
|
45
|
-
CTRL_ATTR_OP_FLAGS = 2
|
|
46
|
-
|
|
47
|
-
CTRL_ATTR_MCAST_GRP_UNSPEC = 0
|
|
48
|
-
CTRL_ATTR_MCAST_GRP_NAME = 1
|
|
49
|
-
CTRL_ATTR_MCAST_GRP_ID = 2
|
|
50
|
-
end
|
|
51
|
-
include Constants
|
|
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))
|
|
46
|
+
begin
|
|
47
|
+
yield session
|
|
48
|
+
ensure
|
|
49
|
+
session.close
|
|
50
|
+
end
|
|
65
51
|
end
|
|
52
|
+
end
|
|
66
53
|
|
|
67
|
-
|
|
68
|
-
|
|
54
|
+
# Base class for Generic Netlink messages.
|
|
55
|
+
#
|
|
56
|
+
# A Generic Netlink message payload begins with a {GenlMsgHdr}, followed by
|
|
57
|
+
# the optional fixed header and attributes handled by {Raw::Message}.
|
|
58
|
+
class Message < Raw::Message
|
|
59
|
+
# Decodes a Generic Netlink message payload.
|
|
60
|
+
#
|
|
61
|
+
# @param [Decoder] decoder the source decoder
|
|
62
|
+
# @param [Integer] type the family ID from the Netlink message header
|
|
63
|
+
# @return [Message] the decoded message
|
|
64
|
+
# @raise [RuntimeError] if the command does not match the message class's type
|
|
65
|
+
# @rbs (Decoder decoder, type: Integer) -> instance
|
|
66
|
+
def self.decode(decoder, type:)
|
|
67
|
+
genlhdr = GenlMsgHdr.decode(decoder)
|
|
68
|
+
super(decoder, type: genlhdr.cmd)
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
require_relative 'genl/
|
|
74
|
+
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
|
|
|
@@ -1,64 +1,70 @@
|
|
|
1
|
-
require_relative '
|
|
1
|
+
require_relative 'raw/wire'
|
|
2
2
|
require_relative 'notification'
|
|
3
3
|
|
|
4
4
|
module Nl
|
|
5
5
|
# Routes unsolicited frames to per-family notification channels.
|
|
6
6
|
class NotificationRouter
|
|
7
|
-
Entry = Struct.new(:
|
|
7
|
+
Entry = Struct.new(:endpoint, :classes, :channel)
|
|
8
8
|
private_constant :Entry
|
|
9
9
|
|
|
10
|
-
def initialize(
|
|
11
|
-
@
|
|
10
|
+
def initialize(protocol:, capacity:)
|
|
11
|
+
@protocol = protocol
|
|
12
12
|
@capacity = capacity
|
|
13
13
|
@mutex = Mutex.new
|
|
14
14
|
@entries = {}
|
|
15
|
+
@routes = {}
|
|
15
16
|
@closed = false
|
|
16
17
|
end
|
|
17
18
|
|
|
18
|
-
def register(
|
|
19
|
+
def register(endpoint, classes)
|
|
19
20
|
@mutex.synchronize do
|
|
20
21
|
raise ClosedError, 'notification router is closed' if @closed
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
channel_key = @protocol.notification_channel_key(endpoint)
|
|
24
|
+
entry = @entries[channel_key]
|
|
25
|
+
merged_classes = entry ? entry.classes.merge(classes) : classes.dup
|
|
26
|
+
route_keys = @protocol.notification_route_keys(endpoint, merged_classes)
|
|
27
|
+
if route_key = route_keys.find { @routes[it] && !@routes[it].equal?(entry) }
|
|
28
|
+
raise ArgumentError, "notification route #{route_key.inspect} is already registered"
|
|
28
29
|
end
|
|
30
|
+
|
|
31
|
+
unless entry
|
|
32
|
+
entry = Entry.new(endpoint, merged_classes, NotificationChannel.new(capacity: @capacity))
|
|
33
|
+
@entries[channel_key] = entry
|
|
34
|
+
end
|
|
35
|
+
entry.classes.replace(merged_classes)
|
|
36
|
+
route_keys.each { @routes[it] = entry }
|
|
29
37
|
entry.channel
|
|
30
38
|
end
|
|
31
39
|
end
|
|
32
40
|
|
|
33
|
-
def channel(
|
|
41
|
+
def channel(endpoint)
|
|
34
42
|
@mutex.synchronize do
|
|
35
|
-
@entries.fetch(@
|
|
43
|
+
@entries.fetch(@protocol.notification_channel_key(endpoint)).channel
|
|
36
44
|
end
|
|
37
45
|
end
|
|
38
46
|
|
|
39
47
|
# Returns true if the frame belongs to a registered notification family.
|
|
40
48
|
def route(header, payload)
|
|
41
|
-
if header.type ==
|
|
49
|
+
if header.type == Raw::NLMSG_OVERRUN
|
|
42
50
|
entries = @mutex.synchronize { @entries.values.dup }
|
|
43
51
|
entries.each { it.channel.fail(NotificationLossError.new('kernel reported Netlink overrun')) }
|
|
44
52
|
return true
|
|
45
53
|
end
|
|
46
54
|
|
|
47
55
|
entry = @mutex.synchronize do
|
|
48
|
-
@
|
|
56
|
+
@routes[@protocol.notification_frame_key(header)]
|
|
49
57
|
end
|
|
50
58
|
return false unless entry
|
|
51
|
-
return false unless
|
|
59
|
+
return false unless @protocol.notification_frame?(entry.endpoint, header, payload)
|
|
52
60
|
|
|
53
|
-
message_class =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
UnknownNotification.new(header:, payload: payload.get_string)
|
|
58
|
-
end
|
|
61
|
+
message_class = @protocol.notification_class(entry.endpoint, header, payload, entry.classes)
|
|
62
|
+
return true unless message_class
|
|
63
|
+
|
|
64
|
+
notification = @protocol.decode_notification(entry.endpoint, header, payload, message_class)
|
|
59
65
|
entry.channel.push(notification)
|
|
60
66
|
true
|
|
61
|
-
rescue
|
|
67
|
+
rescue => error
|
|
62
68
|
entry&.channel&.fail(error)
|
|
63
69
|
true
|
|
64
70
|
end
|
|
@@ -70,6 +76,7 @@ module Nl
|
|
|
70
76
|
@closed = true
|
|
71
77
|
old = @entries.values
|
|
72
78
|
@entries.clear
|
|
79
|
+
@routes.clear
|
|
73
80
|
old
|
|
74
81
|
end
|
|
75
82
|
entries.each { it.channel.close }
|