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
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Raw Netlink client handling
|
|
2
|
+
|
|
3
|
+
require_relative '../connection'
|
|
4
|
+
require_relative 'protocol'
|
|
5
|
+
|
|
6
|
+
module Nl
|
|
7
|
+
module Raw
|
|
8
|
+
# Owns one raw Netlink connection shared by compatible families.
|
|
9
|
+
class Client
|
|
10
|
+
# Opens a client for a raw Netlink protocol.
|
|
11
|
+
#
|
|
12
|
+
# @overload open(protonum:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY)
|
|
13
|
+
# The caller is responsible for closing the client.
|
|
14
|
+
# @param [Integer] protonum the Netlink protocol number
|
|
15
|
+
# @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
|
|
16
|
+
# @param [Integer] notification_capacity the maximum number of queued notifications
|
|
17
|
+
# @return [Client] the opened client
|
|
18
|
+
# @overload open(protonum:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY, &block)
|
|
19
|
+
# The client is automatically closed after the block returns.
|
|
20
|
+
# @param [Integer] protonum the Netlink protocol number
|
|
21
|
+
# @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
|
|
22
|
+
# @param [Integer] notification_capacity the maximum number of queued notifications
|
|
23
|
+
# @yieldparam [Client] client the opened client
|
|
24
|
+
# @return [Object] the value returned from the block
|
|
25
|
+
# @rbs (protonum: Integer, ?executor: executor?, ?notification_capacity: Integer?) -> instance
|
|
26
|
+
# | [R] (protonum: Integer, ?executor: executor?, ?notification_capacity: Integer?) { (instance) -> R } -> R
|
|
27
|
+
def self.open(protonum:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY)
|
|
28
|
+
client = new(protonum:, executor:, notification_capacity:)
|
|
29
|
+
return client unless block_given?
|
|
30
|
+
|
|
31
|
+
begin
|
|
32
|
+
yield client
|
|
33
|
+
ensure
|
|
34
|
+
client.close
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @param [Integer] protonum the Netlink protocol number
|
|
39
|
+
# @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
|
|
40
|
+
# @param [Integer] notification_capacity the maximum number of queued notifications
|
|
41
|
+
# @rbs (protonum: Integer, ?executor: executor?, ?notification_capacity: Integer?) -> void
|
|
42
|
+
def initialize(protonum:, executor: nil, notification_capacity: Nl::Connection::DEFAULT_NOTIFICATION_CAPACITY)
|
|
43
|
+
@protonum = protonum
|
|
44
|
+
@connection = Nl::Connection.new(
|
|
45
|
+
protocol: Protocol.new(protonum),
|
|
46
|
+
executor:,
|
|
47
|
+
notification_capacity:,
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Builds a family backed by this client's connection.
|
|
52
|
+
#
|
|
53
|
+
# @param [Class<Family>] family_class a raw Netlink family class
|
|
54
|
+
# @return [Family] an instance of `family_class`
|
|
55
|
+
# @raise [TypeError] if +family_class+ does not inherit from {Family}
|
|
56
|
+
# @raise [ArgumentError] if the family's protocol number differs from the client's protocol number
|
|
57
|
+
# @rbs [F < Family] (_FamilyClass[F] family_class) -> F
|
|
58
|
+
def family(family_class)
|
|
59
|
+
unless family_class <= Family
|
|
60
|
+
raise TypeError, "family class must inherit from #{Family}"
|
|
61
|
+
end
|
|
62
|
+
unless family_class::PROTONUM == @protonum
|
|
63
|
+
raise ArgumentError,
|
|
64
|
+
"family protonum #{family_class::PROTONUM} does not match client protonum #{@protonum}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
family_class.new(
|
|
68
|
+
@connection,
|
|
69
|
+
endpoint: Endpoint.new(family_class),
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Closes the underlying Netlink connection.
|
|
74
|
+
#
|
|
75
|
+
# @return [void]
|
|
76
|
+
#--
|
|
77
|
+
# @rbs () -> void
|
|
78
|
+
def close
|
|
79
|
+
@connection.close
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
require_relative 'wire'
|
|
2
|
+
require_relative '../decoder'
|
|
3
|
+
require_relative '../encoder'
|
|
4
|
+
require_relative '../error'
|
|
5
|
+
require_relative '../socket'
|
|
6
|
+
|
|
7
|
+
module Nl
|
|
8
|
+
module Raw
|
|
9
|
+
AckFrame = Data.define(:header)
|
|
10
|
+
ErrorFrame = Data.define(:header, :errno)
|
|
11
|
+
DoneFrame = Data.define(:header, :errno)
|
|
12
|
+
UnknownFrame = Data.define(:header)
|
|
13
|
+
DataFrame = Data.define(:header, :message)
|
|
14
|
+
Request = Data.define(:type, :flags, :message)
|
|
15
|
+
|
|
16
|
+
# A generated raw Netlink family bound to its fixed wire identity.
|
|
17
|
+
class Endpoint
|
|
18
|
+
attr_reader :definition
|
|
19
|
+
|
|
20
|
+
def initialize(definition)
|
|
21
|
+
@definition = definition
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def name = @definition::NAME
|
|
25
|
+
def frame_type(message_class) = message_class::TYPE
|
|
26
|
+
|
|
27
|
+
def multicast_group_id(name, value)
|
|
28
|
+
value or raise UnresolvedMulticastGroupError,
|
|
29
|
+
"multicast group #{name.inspect} has no fixed ID"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Socket-wide wire behavior for a classic (netlink-raw) protocol.
|
|
34
|
+
class Protocol
|
|
35
|
+
attr_reader :protonum
|
|
36
|
+
|
|
37
|
+
def initialize(protonum)
|
|
38
|
+
@protonum = protonum
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def encode_message(encoder, _endpoint, request, seq:, pid:)
|
|
42
|
+
header = Raw::NlMsgHdr.new(0, request.type, request.flags, seq, pid)
|
|
43
|
+
encoder.measure(Endian::Host::U16) do
|
|
44
|
+
header.encode(encoder)
|
|
45
|
+
request.message.encode(encoder)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Decodes one frame using the reply class associated with its sequence.
|
|
50
|
+
def decode_frame(_endpoint, header, payload, message_class)
|
|
51
|
+
decoder = Decoder.new(payload)
|
|
52
|
+
if header.type < Raw::NLMSG_MIN_TYPE
|
|
53
|
+
case header.type
|
|
54
|
+
when Raw::NLMSG_ERROR
|
|
55
|
+
errno = decoder.get_value(Endian::Host::SINT)
|
|
56
|
+
if errno.positive?
|
|
57
|
+
raise ProtocolViolation, "expected zero or negative NLMSG_ERROR errno, got #{errno}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
errno.zero? ? AckFrame.new(header:) : ErrorFrame.new(header:, errno: -errno)
|
|
61
|
+
when Raw::NLMSG_DONE
|
|
62
|
+
return DoneFrame.new(header:, errno: nil) unless decoder.available?
|
|
63
|
+
|
|
64
|
+
errno = decoder.get_value(Endian::Host::SINT)
|
|
65
|
+
if errno.positive?
|
|
66
|
+
raise ProtocolViolation, "expected zero or negative NLMSG_DONE errno, got #{errno}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
DoneFrame.new(header:, errno: errno.negative? ? -errno : nil)
|
|
70
|
+
else
|
|
71
|
+
UnknownFrame.new(header:)
|
|
72
|
+
end
|
|
73
|
+
else
|
|
74
|
+
raise ArgumentError, 'reply class is required for a data message' unless message_class
|
|
75
|
+
|
|
76
|
+
DataFrame.new(header:, message: message_class.decode(decoder, type: header.type))
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def send_message(socket, endpoint, request, seq:, pid:)
|
|
81
|
+
encoder = Encoder.new
|
|
82
|
+
encode_message(encoder, endpoint, request, seq:, pid:)
|
|
83
|
+
socket.sendmsg(encoder.buffer.get_string, 0, Socket.sockaddr_nl(0, 0))
|
|
84
|
+
nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def build_request(endpoint, kind, request_class, args)
|
|
88
|
+
flags = Raw::NLM_F_REQUEST
|
|
89
|
+
flags |= kind == :dump ? Raw::NLM_F_DUMP : Raw::NLM_F_ACK
|
|
90
|
+
|
|
91
|
+
message = request_class.from_params(args)
|
|
92
|
+
Request.new(type: endpoint.frame_type(request_class), flags:, message:)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def notification_channel_key(endpoint) = endpoint.definition
|
|
96
|
+
def notification_route_keys(_endpoint, classes) = classes.keys
|
|
97
|
+
def notification_frame_key(header) = header.type
|
|
98
|
+
|
|
99
|
+
def notification_frame?(_endpoint, header, _payload)
|
|
100
|
+
header.type >= Raw::NLMSG_MIN_TYPE
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def notification_class(_endpoint, header, _payload, classes)
|
|
104
|
+
classes[header.type]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def decode_notification(endpoint, header, payload, message_class)
|
|
108
|
+
decode_frame(endpoint, header, payload, message_class).message
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
data/lib/nl/raw/wire.rb
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Netlink wire definitions
|
|
2
|
+
|
|
3
|
+
require_relative '../endian'
|
|
4
|
+
|
|
5
|
+
module Nl
|
|
6
|
+
module Raw
|
|
7
|
+
# Constants from <linux/netlink.h>
|
|
8
|
+
module Constants
|
|
9
|
+
NETLINK_ROUTE = 0
|
|
10
|
+
NETLINK_NETFILTER = 12
|
|
11
|
+
NETLINK_GENERIC = 16
|
|
12
|
+
|
|
13
|
+
NLM_F_REQUEST = 1
|
|
14
|
+
NLM_F_MULTI = 2
|
|
15
|
+
NLM_F_ACK = 4
|
|
16
|
+
NLM_F_ECHO = 8
|
|
17
|
+
NLM_F_DUMP_INTR = 16
|
|
18
|
+
NLM_F_DUMP_FILTERED = 32
|
|
19
|
+
NLM_F_ROOT = 0x100
|
|
20
|
+
NLM_F_MATCH = 0x200
|
|
21
|
+
NLM_F_ATOMIC = 0x400
|
|
22
|
+
NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH
|
|
23
|
+
NLM_F_REPLACE = 0x100
|
|
24
|
+
NLM_F_EXCL = 0x200
|
|
25
|
+
NLM_F_CREATE = 0x400
|
|
26
|
+
NLM_F_APPEND = 0x800
|
|
27
|
+
|
|
28
|
+
NLMSG_ALIGNTO = 4
|
|
29
|
+
NLMSG_HDRLEN = 16
|
|
30
|
+
|
|
31
|
+
NLMSG_NOOP = 0x1
|
|
32
|
+
NLMSG_ERROR = 0x2
|
|
33
|
+
NLMSG_DONE = 0x3
|
|
34
|
+
NLMSG_OVERRUN = 0x4
|
|
35
|
+
|
|
36
|
+
NLMSG_MIN_TYPE = 0x10
|
|
37
|
+
|
|
38
|
+
NLA_F_NESTED = 1 << 15
|
|
39
|
+
NLA_F_NET_BYTEORDER = 1 << 14
|
|
40
|
+
NLA_TYPE_MASK = ~(NLA_F_NESTED | NLA_F_NET_BYTEORDER)
|
|
41
|
+
|
|
42
|
+
NLA_ALIGNTO = 4
|
|
43
|
+
NLA_HDRLEN = 4
|
|
44
|
+
end
|
|
45
|
+
include Constants
|
|
46
|
+
|
|
47
|
+
# Fixed-format metadata header prepended to every Netlink message.
|
|
48
|
+
#
|
|
49
|
+
# This corresponds to Linux's +struct nlmsghdr+.
|
|
50
|
+
#
|
|
51
|
+
# @!attribute [rw] len
|
|
52
|
+
# @return [Integer] message length in bytes, including this header
|
|
53
|
+
# @!attribute [rw] type
|
|
54
|
+
# @return [Integer] message content type
|
|
55
|
+
# @!attribute [rw] flags
|
|
56
|
+
# @return [Integer] bitwise combination of +NLM_F_+ flags
|
|
57
|
+
# @!attribute [rw] seq
|
|
58
|
+
# @return [Integer] sequence number used to correlate requests and replies
|
|
59
|
+
# @!attribute [rw] pid
|
|
60
|
+
# @return [Integer] sender's Netlink port ID
|
|
61
|
+
NlMsgHdr = Struct.new(
|
|
62
|
+
:len, #: Integer
|
|
63
|
+
:type, #: Integer
|
|
64
|
+
:flags, #: Integer
|
|
65
|
+
:seq, #: Integer
|
|
66
|
+
:pid, #: Integer
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
class NlMsgHdr
|
|
70
|
+
FORMAT = Ractor.make_shareable([
|
|
71
|
+
Endian::Host::U32,
|
|
72
|
+
Endian::Host::U16,
|
|
73
|
+
Endian::Host::U16,
|
|
74
|
+
Endian::Host::U32,
|
|
75
|
+
Endian::Host::U32,
|
|
76
|
+
])
|
|
77
|
+
private_constant :FORMAT
|
|
78
|
+
|
|
79
|
+
# Decodes a header from the decoder's current position.
|
|
80
|
+
#
|
|
81
|
+
# @param [Decoder] decoder the source decoder
|
|
82
|
+
# @return [NlMsgHdr] the decoded header
|
|
83
|
+
# @rbs (Decoder decoder) -> instance
|
|
84
|
+
def self.decode(decoder)
|
|
85
|
+
obj = new(*decoder.get_values(FORMAT))
|
|
86
|
+
decoder.align_to(Constants::NLMSG_ALIGNTO)
|
|
87
|
+
obj
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Encodes this header at the encoder's current position.
|
|
91
|
+
#
|
|
92
|
+
# @param [Encoder] encoder the destination encoder
|
|
93
|
+
# @return [void]
|
|
94
|
+
# @rbs (Encoder encoder) -> void
|
|
95
|
+
def encode(encoder)
|
|
96
|
+
encoder.reserve(Constants::NLMSG_HDRLEN)
|
|
97
|
+
encoder.put_values(FORMAT, to_a)
|
|
98
|
+
encoder.align_to(Constants::NLMSG_ALIGNTO)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Fixed-format header prepended to every Netlink attribute.
|
|
103
|
+
#
|
|
104
|
+
# This corresponds to Linux's +struct nlattr+.
|
|
105
|
+
#
|
|
106
|
+
# @!attribute [rw] len
|
|
107
|
+
# @return [Integer] attribute length in bytes, including this header but
|
|
108
|
+
# excluding trailing alignment padding
|
|
109
|
+
# @!attribute [rw] type
|
|
110
|
+
# @return [Integer] attribute type combined with optional +NLA_F_+ flags
|
|
111
|
+
NlAttr = Struct.new(
|
|
112
|
+
:len, #: Integer
|
|
113
|
+
:type, #: Integer
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
class NlAttr
|
|
117
|
+
FORMAT = Ractor.make_shareable([
|
|
118
|
+
Endian::Host::U16,
|
|
119
|
+
Endian::Host::U16,
|
|
120
|
+
])
|
|
121
|
+
private_constant :FORMAT
|
|
122
|
+
|
|
123
|
+
# Decodes an attribute header from the decoder's current position.
|
|
124
|
+
#
|
|
125
|
+
# @param [Decoder] decoder the source decoder
|
|
126
|
+
# @return [NlAttr] the decoded header
|
|
127
|
+
# @rbs (Decoder decoder) -> instance
|
|
128
|
+
def self.decode(decoder)
|
|
129
|
+
obj = new(*decoder.get_values(FORMAT))
|
|
130
|
+
decoder.align_to(Constants::NLA_ALIGNTO)
|
|
131
|
+
obj
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Encodes this attribute header at the encoder's current position.
|
|
135
|
+
#
|
|
136
|
+
# @param [Encoder] encoder the destination encoder
|
|
137
|
+
# @return [void]
|
|
138
|
+
# @rbs (Encoder encoder) -> void
|
|
139
|
+
def encode(encoder)
|
|
140
|
+
encoder.reserve(Constants::NLA_HDRLEN)
|
|
141
|
+
encoder.put_values(FORMAT, to_a)
|
|
142
|
+
encoder.align_to(Constants::NLA_ALIGNTO)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
data/lib/nl/raw.rb
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require_relative 'family'
|
|
2
|
+
require_relative 'raw/protocol'
|
|
3
|
+
require_relative 'raw/client'
|
|
4
|
+
require_relative 'attribute_set'
|
|
5
|
+
require_relative 'structured_payload'
|
|
6
|
+
|
|
7
|
+
module Nl
|
|
8
|
+
# Classic (Raw) Netlink families.
|
|
9
|
+
module Raw
|
|
10
|
+
# Base class for Raw Netlink families.
|
|
11
|
+
class Family < Nl::Family
|
|
12
|
+
# Opens a session for this raw Netlink family.
|
|
13
|
+
#
|
|
14
|
+
# @overload open(executor: nil, notification_capacity: DEFAULT_NOTIFICATION_CAPACITY)
|
|
15
|
+
# The caller is responsible for closing the session.
|
|
16
|
+
# @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
|
|
17
|
+
# @param [Integer] notification_capacity the maximum number of queued notifications
|
|
18
|
+
# @return [Family] the opened family session
|
|
19
|
+
# @overload open(executor: nil, notification_capacity: DEFAULT_NOTIFICATION_CAPACITY, &block)
|
|
20
|
+
# The session is automatically closed after the block returns.
|
|
21
|
+
# @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
|
|
22
|
+
# @param [Integer] notification_capacity the maximum number of queued notifications
|
|
23
|
+
# @yieldparam [Family] session the opened family session
|
|
24
|
+
# @return [Object] the value returned from the block
|
|
25
|
+
# @rbs (?executor: executor?, ?notification_capacity: Integer?) -> (Nl::Family::Session & instance)
|
|
26
|
+
# | [R] (?executor: executor?, ?notification_capacity: Integer?) { (instance) -> R } -> R
|
|
27
|
+
def self.open(executor: nil, notification_capacity: DEFAULT_NOTIFICATION_CAPACITY)
|
|
28
|
+
session = build_session(executor:, notification_capacity:)
|
|
29
|
+
return session unless block_given?
|
|
30
|
+
|
|
31
|
+
begin
|
|
32
|
+
yield session
|
|
33
|
+
ensure
|
|
34
|
+
session.close
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
# Builds a session that owns its underlying client.
|
|
40
|
+
#
|
|
41
|
+
# @param [:thread, :fiber, nil] executor the asynchronous executor, or `nil` for blocking operation
|
|
42
|
+
# @param [Integer] notification_capacity the maximum number of queued notifications
|
|
43
|
+
# @return [Family] the opened family session
|
|
44
|
+
# @rbs (executor: executor?, notification_capacity: Integer) -> (Nl::Family::Session & instance)
|
|
45
|
+
private def build_session(executor:, notification_capacity:)
|
|
46
|
+
owner = Client.new(protonum: self::PROTONUM, executor:, notification_capacity:)
|
|
47
|
+
owner.family(self).extend(Nl::Family::Session)
|
|
48
|
+
rescue Exception
|
|
49
|
+
owner&.close
|
|
50
|
+
raise
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Base class for raw Netlink messages.
|
|
56
|
+
#
|
|
57
|
+
# A message may contain a fixed header followed by a set of Netlink
|
|
58
|
+
# attributes.
|
|
59
|
+
class Message
|
|
60
|
+
include Nl::StructuredPayload
|
|
61
|
+
|
|
62
|
+
# @param [Object, nil] fixed_header the family-specific fixed header
|
|
63
|
+
# @param [AttributeSet, nil] attributes the message's attributes
|
|
64
|
+
# @rbs (?untyped fixed_header, ?AttributeSet? attributes) -> void
|
|
65
|
+
def initialize(fixed_header = nil, attributes = self.class::ATTRIBUTE_SET.new)
|
|
66
|
+
super
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Appends an attribute to the message.
|
|
70
|
+
#
|
|
71
|
+
# @param [AttributeSet::Attribute] attribute the attribute to append
|
|
72
|
+
# @return [void]
|
|
73
|
+
# @rbs (AttributeSet::Attribute attribute) -> void
|
|
74
|
+
def append_attribute(attribute)
|
|
75
|
+
@attributes << attribute
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Decodes a raw Netlink message payload.
|
|
79
|
+
#
|
|
80
|
+
# @param [Decoder] decoder the source decoder
|
|
81
|
+
# @param [Integer] type the message type from its Netlink header
|
|
82
|
+
# @return [Message] the decoded message
|
|
83
|
+
# @raise [RuntimeError] if +type+ does not match the message class's type
|
|
84
|
+
# @rbs (Decoder decoder, type: Integer) -> instance
|
|
85
|
+
def self.decode(decoder, type:)
|
|
86
|
+
unless self::TYPE == type
|
|
87
|
+
raise "Expected message type #{self::TYPE}, got #{type}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
super(decoder)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
class << self
|
|
94
|
+
# Returns the attribute names accepted by this message.
|
|
95
|
+
#
|
|
96
|
+
# @return [Array<Symbol>] the attribute names
|
|
97
|
+
# @rbs () -> Array[Symbol]
|
|
98
|
+
private def attribute_names
|
|
99
|
+
self::ATTRIBUTES
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Nl
|
|
2
|
+
# Shared handling for a payload containing an optional fixed header followed
|
|
3
|
+
# by an optional attribute set.
|
|
4
|
+
module StructuredPayload
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.extend(ClassMethods)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
attr_accessor :fixed_header, :attributes
|
|
10
|
+
|
|
11
|
+
def initialize(fixed_header = nil, attributes = nil)
|
|
12
|
+
@fixed_header = fixed_header
|
|
13
|
+
@attributes = attributes
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module ClassMethods
|
|
17
|
+
def from_params(params = nil, external_selectors: [], **keywords)
|
|
18
|
+
params = (params || {}).merge(keywords).transform_keys(&:to_sym)
|
|
19
|
+
|
|
20
|
+
if self::FIXED_HEADER
|
|
21
|
+
header_params = params.slice(*self::FIXED_HEADER.members)
|
|
22
|
+
fixed_header = self::FIXED_HEADER.new(**header_params)
|
|
23
|
+
else
|
|
24
|
+
header_params = {}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
if self::ATTRIBUTE_SET
|
|
28
|
+
attribute_params = params.slice(*attribute_names)
|
|
29
|
+
attributes = self::ATTRIBUTE_SET.build_attributes(
|
|
30
|
+
attribute_params,
|
|
31
|
+
external_selectors:,
|
|
32
|
+
)
|
|
33
|
+
else
|
|
34
|
+
attribute_params = {}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
unknown = params.keys - header_params.keys - attribute_params.keys
|
|
38
|
+
unless unknown.empty?
|
|
39
|
+
raise ArgumentError, "unknown parameters: #{unknown.join(', ')}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
new(fixed_header, attributes)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def decode(decoder, external_selectors: [])
|
|
46
|
+
fixed_header = self::FIXED_HEADER&.decode(decoder)
|
|
47
|
+
attributes = self::ATTRIBUTE_SET&.decode(decoder, external_selectors:)
|
|
48
|
+
new(fixed_header, attributes)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private def attribute_names
|
|
52
|
+
self::ATTRIBUTE_SET::BY_NAME.keys
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def encode(encoder, external_selectors: [])
|
|
57
|
+
fixed_header&.encode(encoder)
|
|
58
|
+
attributes&.encode(encoder, external_selectors:)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require_relative 'structured_payload'
|
|
2
|
+
|
|
3
|
+
module Nl
|
|
4
|
+
module Selector
|
|
5
|
+
class MissingSelectorValueError < StandardError
|
|
6
|
+
attr_reader :scope, :index
|
|
7
|
+
|
|
8
|
+
def initialize(scope, index)
|
|
9
|
+
@scope = scope
|
|
10
|
+
@index = index
|
|
11
|
+
super()
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class UnknownSelectorValueError < StandardError
|
|
16
|
+
attr_reader :value
|
|
17
|
+
|
|
18
|
+
def initialize(value)
|
|
19
|
+
@value = value
|
|
20
|
+
super()
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module Reference
|
|
25
|
+
def select(context, formats)
|
|
26
|
+
value = read(context)
|
|
27
|
+
formats.fetch(value) do
|
|
28
|
+
return yield(value) if block_given?
|
|
29
|
+
raise UnknownSelectorValueError.new(value)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
Local = Data.define(:index) do
|
|
35
|
+
include Reference
|
|
36
|
+
|
|
37
|
+
def read(context)
|
|
38
|
+
context.fetch_local(index)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
External = Data.define(:index) do
|
|
43
|
+
include Reference
|
|
44
|
+
|
|
45
|
+
def read(context)
|
|
46
|
+
context.fetch_external(index)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class State
|
|
51
|
+
def initialize(local_count, external)
|
|
52
|
+
@local = Array.new(local_count)
|
|
53
|
+
@external = external
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def set_local(index, value)
|
|
57
|
+
@local[index] = value
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def fetch_local(index)
|
|
61
|
+
fetch(@local, index) { raise MissingSelectorValueError.new(:local, index) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def fetch_external(index)
|
|
65
|
+
fetch(@external, index) { raise MissingSelectorValueError.new(:external, index) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def fetch(values, key)
|
|
69
|
+
value = values[key]
|
|
70
|
+
value.nil? ? yield : value
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# A selector-specific payload containing an optional fixed header followed by
|
|
76
|
+
# an optional set of Netlink attributes.
|
|
77
|
+
class SubMessage
|
|
78
|
+
include StructuredPayload
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# An unrecognized selector-specific payload retained without interpretation.
|
|
82
|
+
class RawSubMessage < SubMessage
|
|
83
|
+
attr_reader :payload, :nlattr_type_flags
|
|
84
|
+
|
|
85
|
+
def initialize(payload, nlattr_type_flags:)
|
|
86
|
+
@payload = payload
|
|
87
|
+
@nlattr_type_flags = nlattr_type_flags
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def encode(encoder, external_selectors: [])
|
|
91
|
+
encoder.put_string(payload)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
data/lib/nl/version.rb
CHANGED
data/lib/nl.rb
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
require_relative 'nl/error'
|
|
2
|
-
require_relative 'nl/
|
|
2
|
+
require_relative 'nl/bitfield32'
|
|
3
|
+
require_relative 'nl/raw/wire'
|
|
4
|
+
require_relative 'nl/structured_payload'
|
|
5
|
+
require_relative 'nl/sub_message'
|
|
3
6
|
require_relative 'nl/notification'
|
|
4
7
|
require_relative 'nl/connection'
|
|
5
8
|
require_relative 'nl/family'
|
|
9
|
+
require_relative 'nl/raw'
|
|
6
10
|
require_relative 'nl/genl'
|
|
7
11
|
require_relative 'nl/socket'
|
|
8
12
|
require_relative 'nl/version'
|
|
9
|
-
|
|
10
|
-
module Nl
|
|
11
|
-
include Core
|
|
12
|
-
include Genl
|
|
13
|
-
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Generated from lib/nl/async/dispatcher.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Nl
|
|
4
|
+
module Async
|
|
5
|
+
# Owns the single receive loop for a socket and routes replies by sequence.
|
|
6
|
+
class Dispatcher
|
|
7
|
+
class Pending < Data
|
|
8
|
+
attr_reader exchange(): untyped
|
|
9
|
+
|
|
10
|
+
attr_reader endpoint(): untyped
|
|
11
|
+
|
|
12
|
+
attr_reader reply_class(): untyped
|
|
13
|
+
|
|
14
|
+
attr_reader sink(): untyped
|
|
15
|
+
|
|
16
|
+
def self.new: (untyped exchange, untyped endpoint, untyped reply_class, untyped sink) -> instance
|
|
17
|
+
| (exchange: untyped, endpoint: untyped, reply_class: untyped, sink: untyped) -> instance
|
|
18
|
+
|
|
19
|
+
def self.members: () -> [ :exchange, :endpoint, :reply_class, :sink ]
|
|
20
|
+
|
|
21
|
+
def members: () -> [ :exchange, :endpoint, :reply_class, :sink ]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def initialize: (untyped socket, protocol: untyped, notifications: untyped, ?executor: untyped) -> untyped
|
|
25
|
+
|
|
26
|
+
def exchange: (untyped endpoint, untyped kind, untyped request_class, untyped reply_class, untyped args) ?{ (?) -> untyped } -> untyped
|
|
27
|
+
|
|
28
|
+
def exchange_async: (untyped endpoint, untyped kind, untyped request_class, untyped reply_class, untyped args, ?stream_capacity: untyped) -> untyped
|
|
29
|
+
|
|
30
|
+
def async_capable?: () -> true
|
|
31
|
+
|
|
32
|
+
def receive_notification: (untyped endpoint, ?timeout: untyped) -> untyped
|
|
33
|
+
|
|
34
|
+
def close: () -> untyped
|
|
35
|
+
|
|
36
|
+
private def receive_loop: () -> untyped
|
|
37
|
+
|
|
38
|
+
private def fail_receive_loop: (untyped error) -> untyped
|
|
39
|
+
|
|
40
|
+
private def dispatch_datagram: (untyped buffer) -> untyped
|
|
41
|
+
|
|
42
|
+
private def dispatch_outcome: (untyped key, untyped pending, untyped outcome) -> untyped
|
|
43
|
+
|
|
44
|
+
private def complete_pending: (untyped key, untyped pending, untyped value) -> untyped
|
|
45
|
+
|
|
46
|
+
private def discard: (untyped key) -> untyped
|
|
47
|
+
|
|
48
|
+
private def fail_pending: (untyped key, untyped error) -> untyped
|
|
49
|
+
|
|
50
|
+
private def fail_all: (untyped error) -> untyped
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|