nl 0.1.0
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 +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +3 -0
- data/Rakefile +6 -0
- data/lib/nl/core.rb +94 -0
- data/lib/nl/decoder.rb +86 -0
- data/lib/nl/encoder.rb +65 -0
- data/lib/nl/endian.rb +35 -0
- data/lib/nl/family.rb +163 -0
- data/lib/nl/genl.rb +65 -0
- data/lib/nl/protocols/genl.rb +15 -0
- data/lib/nl/protocols/raw.rb +169 -0
- data/lib/nl/socket.rb +53 -0
- data/lib/nl/version.rb +3 -0
- data/lib/nl.rb +14 -0
- metadata +59 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b834199d097a768959ee9b81d4f8e08e504048b9bb537aefbbb3c737dcbc166b
|
4
|
+
data.tar.gz: 62fbdaaeb0ea9c4a5aefba149476f32dde99b16e210252566340b9627282bd4e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 218aabff17cc161fbb064f22e50c78c11770bc70df76636434bd648af2e3592bbafdb491b61daf8164be306f5d5bfe1e8e0ad968963c9c17756fcb06e826ed47
|
7
|
+
data.tar.gz: 4ff4dc4caf73c20e1b93e090689684534f8f8b184f15538eac47451293c4705918fbea24583949e1454882a850379341784d1226f3c7f8421d362c6b184fcc3e
|
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Kasumi Hanazuki
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
data/lib/nl/core.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# Data types and message handling
|
2
|
+
|
3
|
+
require_relative 'endian'
|
4
|
+
|
5
|
+
module Nl
|
6
|
+
module Core
|
7
|
+
module Constants
|
8
|
+
# From include/uapi/linux/netlink.h
|
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
|
+
NlMsgHdr = Struct.new(:len, :type, :flags, :seq, :pid)
|
48
|
+
# Message header
|
49
|
+
class NlMsgHdr
|
50
|
+
FORMAT = Ractor.make_shareable([
|
51
|
+
Endian::Host::U32,
|
52
|
+
Endian::Host::U16,
|
53
|
+
Endian::Host::U16,
|
54
|
+
Endian::Host::U32,
|
55
|
+
Endian::Host::U32,
|
56
|
+
])
|
57
|
+
private_constant :FORMAT
|
58
|
+
|
59
|
+
def self.decode(decoder)
|
60
|
+
obj = new(*decoder.get_values(FORMAT))
|
61
|
+
decoder.align_to(Constants::NLMSG_ALIGNTO)
|
62
|
+
obj
|
63
|
+
end
|
64
|
+
|
65
|
+
def encode(encoder)
|
66
|
+
encoder.reserve(Constants::NLMSG_HDRLEN)
|
67
|
+
encoder.put_values(FORMAT, to_a)
|
68
|
+
encoder.align_to(Constants::NLMSG_ALIGNTO)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
NlAttr = Struct.new(:len, :type)
|
73
|
+
# Attribute header
|
74
|
+
class NlAttr
|
75
|
+
FORMAT = Ractor.make_shareable([
|
76
|
+
Endian::Host::U16,
|
77
|
+
Endian::Host::U16,
|
78
|
+
])
|
79
|
+
private_constant :FORMAT
|
80
|
+
|
81
|
+
def self.decode(decoder)
|
82
|
+
obj = new(*decoder.get_values(FORMAT))
|
83
|
+
decoder.align_to(Constants::NLA_ALIGNTO)
|
84
|
+
obj
|
85
|
+
end
|
86
|
+
|
87
|
+
def encode(encoder)
|
88
|
+
encoder.reserve(Constants::NLA_HDRLEN)
|
89
|
+
encoder.put_values(FORMAT, to_a)
|
90
|
+
encoder.align_to(Constants::NLA_ALIGNTO)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/nl/decoder.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
module Nl
|
2
|
+
class Decoder
|
3
|
+
class Error < StandardError; end
|
4
|
+
class OutOfBounds < Error
|
5
|
+
def initialize(message = 'out of bounds') = super
|
6
|
+
end
|
7
|
+
class Unterminated < Error
|
8
|
+
def initialize(message = 'unterminated string') = super
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(buffer, offset = 0, length = buffer.size - offset)
|
12
|
+
@buffer = buffer
|
13
|
+
@position = offset
|
14
|
+
@limit = length
|
15
|
+
end
|
16
|
+
|
17
|
+
def available?(size = 1)
|
18
|
+
@position + size <= @limit
|
19
|
+
end
|
20
|
+
|
21
|
+
def limit(size)
|
22
|
+
orig_limit = @limit
|
23
|
+
@limit = @position + size
|
24
|
+
result = yield self
|
25
|
+
if @limit != @position
|
26
|
+
raise "Not all bytes upto specified limit is consumed"
|
27
|
+
end
|
28
|
+
result
|
29
|
+
ensure
|
30
|
+
@limit = orig_limit
|
31
|
+
end
|
32
|
+
|
33
|
+
def skip(length = @limit - @position)
|
34
|
+
nposition = @position + length
|
35
|
+
raise OutOfBounds if nposition > @limit
|
36
|
+
@position = nposition
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_string(length = @limit - @position)
|
40
|
+
nposition = @position + length
|
41
|
+
raise OutOfBounds if nposition > @limit
|
42
|
+
value = @buffer.get_string(@position, length)
|
43
|
+
@position = nposition
|
44
|
+
value
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_zstring(unterminated_ok: false)
|
48
|
+
nposition = @position
|
49
|
+
nul_found = false
|
50
|
+
while nposition < @limit
|
51
|
+
c = @buffer.get_value(:U8, nposition)
|
52
|
+
nposition += 1
|
53
|
+
if c == 0
|
54
|
+
nul_found = true
|
55
|
+
break
|
56
|
+
end
|
57
|
+
end
|
58
|
+
raise Unterminated if !nul_found && !unterminated_ok
|
59
|
+
value = @buffer.get_string(@position, nposition - @position - (nul_found ? 1 : 0))
|
60
|
+
@position = nposition
|
61
|
+
value
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_value(type)
|
65
|
+
nposition = @position + IO::Buffer.size_of(type)
|
66
|
+
raise OutOfBounds if nposition > @limit
|
67
|
+
value = @buffer.get_value(type, @position)
|
68
|
+
@position = nposition
|
69
|
+
value
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_values(types)
|
73
|
+
nposition = @position + IO::Buffer.size_of(types)
|
74
|
+
raise OutOfBounds if nposition > @limit
|
75
|
+
values = @buffer.get_values(types, @position)
|
76
|
+
@position = nposition
|
77
|
+
values
|
78
|
+
end
|
79
|
+
|
80
|
+
def align_to(alignment)
|
81
|
+
nposition = (@position + alignment - 1) & ~(alignment - 1)
|
82
|
+
raise OutOfBounds if nposition > @limit
|
83
|
+
@position = nposition
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/nl/encoder.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module Nl
|
2
|
+
class Encoder
|
3
|
+
attr_reader :position
|
4
|
+
|
5
|
+
def initialize(capacity = 4096)
|
6
|
+
@buffer = IO::Buffer.new(capacity)
|
7
|
+
@position = 0
|
8
|
+
end
|
9
|
+
|
10
|
+
def reserve(size)
|
11
|
+
nposition = @position + size
|
12
|
+
if @buffer.size < nposition
|
13
|
+
@buffer.resize([@buffer.size * 2, nposition].max)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def put_string(value)
|
18
|
+
reserve(value.bytesize)
|
19
|
+
@position = @buffer.set_string(value, @position)
|
20
|
+
end
|
21
|
+
|
22
|
+
def put_zstring(value)
|
23
|
+
reserve(value.bytesize + 1)
|
24
|
+
@position = @buffer.set_string(value, @position)
|
25
|
+
@position = @buffer.set_value(:U8, @position, 0)
|
26
|
+
end
|
27
|
+
|
28
|
+
def put_value(type, value)
|
29
|
+
reserve(IO::Buffer.size_of(type))
|
30
|
+
@position = @buffer.set_value(type, @position, value)
|
31
|
+
end
|
32
|
+
|
33
|
+
def put_values(types, values)
|
34
|
+
reserve(IO::Buffer.size_of(types))
|
35
|
+
@position = @buffer.set_values(types, @position, values)
|
36
|
+
end
|
37
|
+
|
38
|
+
def align_to(alignment)
|
39
|
+
if alignment > 1
|
40
|
+
nposition = (@position + alignment - 1) & ~(alignment - 1)
|
41
|
+
reserve(nposition - @position)
|
42
|
+
@position = nposition
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def put_value_at(offset, type, value)
|
47
|
+
@buffer.set_value(type, offset, value)
|
48
|
+
end
|
49
|
+
|
50
|
+
def put_values_at(offset, types, values)
|
51
|
+
@buffer.set_values(types, offset, values)
|
52
|
+
end
|
53
|
+
|
54
|
+
def buffer
|
55
|
+
@buffer.slice(0, @position)
|
56
|
+
end
|
57
|
+
|
58
|
+
def measure(type, offset = 0)
|
59
|
+
before = @position
|
60
|
+
yield
|
61
|
+
after = @position
|
62
|
+
put_value_at(before + offset, type, after - before)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/nl/endian.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Nl
|
2
|
+
module Endian
|
3
|
+
SIZEOF_INT = [1].pack('i!').bytesize
|
4
|
+
SIZEOF_LONG = [1].pack('l!').bytesize
|
5
|
+
SIZEOF_LLONG = [1].pack('q!').bytesize
|
6
|
+
|
7
|
+
module Little
|
8
|
+
U8, S8, U16, S16, U32, S32, U64, S64, F32, F64 = :U8, :S8, :u16, :s16, :u32, :s32, :u64, :s64, :f32, :f64
|
9
|
+
end
|
10
|
+
|
11
|
+
module Big
|
12
|
+
U8, S8, U16, S16, U32, S32, U64, S64, F32, F64 = :U8, :S8, :U16, :S16, :U32, :S32, :U64, :S64, :F32, :F64
|
13
|
+
end
|
14
|
+
|
15
|
+
module Host
|
16
|
+
include (IO::Buffer::HOST_ENDIAN == IO::Buffer::LITTLE_ENDIAN ? Little : Big)
|
17
|
+
|
18
|
+
UINT, SINT = case SIZEOF_INT
|
19
|
+
when 2; [U16, S16]
|
20
|
+
when 4; [U32, S32]
|
21
|
+
when 8; [U64, S64]
|
22
|
+
else raise "Unsupported 'int' size"
|
23
|
+
end
|
24
|
+
ULONG, SLONG = case SIZEOF_LONG
|
25
|
+
when 4; [U32, S32]
|
26
|
+
when 8; [U64, S64]
|
27
|
+
else raise "Unsupported 'long' size"
|
28
|
+
end
|
29
|
+
ULLONG, SLLONG = case SIZEOF_LLONG
|
30
|
+
when 8; [U64, S64]
|
31
|
+
else raise "Unsupported 'long long' size"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/nl/family.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
require_relative 'socket'
|
2
|
+
|
3
|
+
module Nl
|
4
|
+
class Family
|
5
|
+
def initialize(socket)
|
6
|
+
@socket = socket
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.open
|
10
|
+
begin
|
11
|
+
socket = Socket.new(self::PROTONUM)
|
12
|
+
socket.bind(Socket.sockaddr_nl(0, 0))
|
13
|
+
if block_given?
|
14
|
+
yield new(socket)
|
15
|
+
else
|
16
|
+
return new(socket)
|
17
|
+
end
|
18
|
+
ensure
|
19
|
+
socket&.close if block_given?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def exchange_message(type, request_class, reply_class, args)
|
24
|
+
self.class::PROTOCOL.exchange_message(@socket, type, request_class, reply_class, args)
|
25
|
+
end
|
26
|
+
|
27
|
+
class Message
|
28
|
+
attr_accessor :header, :fixed_header, :attributes
|
29
|
+
|
30
|
+
def initialize(header, fixed_header = nil, attributes = [])
|
31
|
+
@header = header
|
32
|
+
@fixed_header = fixed_header
|
33
|
+
@attributes = attributes
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.from_params(params)
|
37
|
+
header_params = params.slice(*self::HEADER_PARAMS)
|
38
|
+
attribute_params = params.slice(*self::ATTRIBUTE_PARAMS)
|
39
|
+
# TODO: Reject unknown params
|
40
|
+
|
41
|
+
header = Core::NlMsgHdr.new(0, self::TYPE, nil, nil, nil)
|
42
|
+
fixed_header = self::FIXED_HEADER&.new(**header_params)
|
43
|
+
attributes = self::ATTRIBUTE_SET.build_attributes(**attribute_params)
|
44
|
+
new(header, fixed_header, attributes)
|
45
|
+
end
|
46
|
+
|
47
|
+
def append_attribute(attribute)
|
48
|
+
@attributes << attribute
|
49
|
+
end
|
50
|
+
|
51
|
+
def encode(encoder)
|
52
|
+
validate!
|
53
|
+
|
54
|
+
encoder.measure(Endian::Host::U16) do
|
55
|
+
@header.encode(encoder)
|
56
|
+
@fixed_header.encode(encoder) if @fixed_header
|
57
|
+
@attributes.each do |attr|
|
58
|
+
attr.encode(encoder)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.decode(decoder, header)
|
64
|
+
unless self::TYPE == header.type
|
65
|
+
raise "Expected message type #{self::TYPE}, got #{header.type}"
|
66
|
+
end
|
67
|
+
|
68
|
+
if fixed_header_class = self::FIXED_HEADER
|
69
|
+
fixed_header = fixed_header_class.decode(decoder)
|
70
|
+
end
|
71
|
+
|
72
|
+
attributes = self::ATTRIBUTE_SET.decode(decoder)
|
73
|
+
|
74
|
+
new(header, fixed_header, attributes).tap(&:validate!)
|
75
|
+
end
|
76
|
+
|
77
|
+
def validate!
|
78
|
+
unless self.class::TYPE == header.type
|
79
|
+
raise "Expected message type #{self.class::FIXED_HEADER}, got #{header.type}"
|
80
|
+
end
|
81
|
+
# TODO: Validate fixed header and attributes
|
82
|
+
# @fixed_header&.validate!
|
83
|
+
# @attributes.each(&:validate!)
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_h
|
87
|
+
to_h_rec(@attributes, @fixed_header&.to_h || {})
|
88
|
+
end
|
89
|
+
|
90
|
+
# FIXME:
|
91
|
+
private def to_h_rec(attributes, init = {})
|
92
|
+
attributes.each_with_object(init) do |attr, h|
|
93
|
+
if attr.class::DATATYPE.is_a?(Protocols::Raw::DataTypes::NestedAttributes)
|
94
|
+
h[attr.class::NAME] = to_h_rec(attr.value)
|
95
|
+
else
|
96
|
+
h[attr.class::NAME] = attr.value
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class AttributeSet
|
103
|
+
Attribute = Struct.new(:value)
|
104
|
+
class Attribute
|
105
|
+
def self.decode(decoder)
|
106
|
+
value = self::DATATYPE.decode(decoder)
|
107
|
+
new(value)
|
108
|
+
end
|
109
|
+
|
110
|
+
def encode(encoder)
|
111
|
+
self.class::DATATYPE.encode(encoder, self.value)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class << self
|
116
|
+
private def decode1(decoder)
|
117
|
+
nlattr = Core::NlAttr.decode(decoder)
|
118
|
+
attr = decoder.limit(nlattr.len - Core::NLA_HDRLEN) do
|
119
|
+
if attr_class = self::BY_TYPE[nlattr.type & Core::NLA_TYPE_MASK]
|
120
|
+
attr_class.decode(decoder)
|
121
|
+
else
|
122
|
+
decoder.skip
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
end
|
126
|
+
decoder.align_to(Core::NLA_ALIGNTO)
|
127
|
+
attr
|
128
|
+
end
|
129
|
+
|
130
|
+
def decode(decoder)
|
131
|
+
attrs = []
|
132
|
+
while decoder.available?
|
133
|
+
attr = decode1(decoder)
|
134
|
+
attrs << attr
|
135
|
+
end
|
136
|
+
attrs.compact
|
137
|
+
end
|
138
|
+
|
139
|
+
private def encode1(encoder, attr)
|
140
|
+
nlattr = Core::NlAttr.new(attr.class::TYPE, 0)
|
141
|
+
encoder.measure(Endian::Host::U16) do
|
142
|
+
nlattr.encode(encoder)
|
143
|
+
attr.encode(encoder)
|
144
|
+
end
|
145
|
+
encoder.align_to(Core::NLA_ALIGNTO)
|
146
|
+
end
|
147
|
+
|
148
|
+
def encode(encoder, attrs)
|
149
|
+
attrs.each do |attr|
|
150
|
+
encode1(encoder, attr)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def build_attributes(**params)
|
155
|
+
params.map do |name, value|
|
156
|
+
attr_class = self::BY_NAME[name] or raise "Unknown attribute #{name}"
|
157
|
+
attr_class.new(value)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
data/lib/nl/genl.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# General Netlink message handling
|
2
|
+
|
3
|
+
require_relative 'core'
|
4
|
+
require_relative 'endian'
|
5
|
+
|
6
|
+
module Nl
|
7
|
+
module Genl
|
8
|
+
module Constants
|
9
|
+
# From include/uapi/linux/genetlink.
|
10
|
+
GENL_NAMSIZ = 16
|
11
|
+
GENL_MIN_ID = Core::NLMSG_MIN_TYPE
|
12
|
+
GENL_MAX_ID = 1023
|
13
|
+
|
14
|
+
GENL_HDRLEN = 4
|
15
|
+
|
16
|
+
GENL_ID_GENERATE = 0
|
17
|
+
GENL_ID_CTRL = Core::NLMSG_MIN_TYPE
|
18
|
+
GENL_ID_VFS_DQUOT = Core::NLMSG_MIN_TYPE + 1
|
19
|
+
GENL_ID_PMCRAID = Core::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
|
+
GenlMsgHdr ||= Data.define(:cmd, :version, :reserved)
|
52
|
+
class GenlMsgHdr
|
53
|
+
GENLMSGHDR_FMT = Ractor.make_shareable([
|
54
|
+
Endian::Host::U8,
|
55
|
+
Endian::Host::U8,
|
56
|
+
Endian::Host::U16,
|
57
|
+
])
|
58
|
+
private_constant :GENLMSGHDR_FMT
|
59
|
+
|
60
|
+
def self.parse(buffer, offset)
|
61
|
+
new(*buffer.get_values(GENLMSGHDR_FMT, offset))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Nl
|
2
|
+
module Protocols
|
3
|
+
# The Generic Netlink protocol
|
4
|
+
class Genl < Raw # TODO: Implement
|
5
|
+
def initialize(name)
|
6
|
+
super(name, NETLINK_GENERIC)
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse(buffer, offset)
|
10
|
+
nlmsg = super(buffer, offset)
|
11
|
+
genlmsg = GenlMsgHdr.parse(nlmsg.data, 0)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module Nl
|
2
|
+
module Protocols
|
3
|
+
# The raw Netlink protocol
|
4
|
+
class Raw
|
5
|
+
class Done
|
6
|
+
end
|
7
|
+
class Ack
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :name, :protonum
|
11
|
+
|
12
|
+
def initialize(name, protonum)
|
13
|
+
@name = name
|
14
|
+
@protonum = protonum
|
15
|
+
end
|
16
|
+
|
17
|
+
def encode_message(encoder, message)
|
18
|
+
message.encode(encoder)
|
19
|
+
end
|
20
|
+
|
21
|
+
def decode_message(decoder, message_class)
|
22
|
+
header = NlMsgHdr.decode(decoder)
|
23
|
+
decoder.limit(header.len - Core::NLMSG_HDRLEN) do
|
24
|
+
message_class.decode(decoder, header)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def send_message(socket, message)
|
29
|
+
seq_pid = socket.complete(message.header)
|
30
|
+
encoder = Encoder.new
|
31
|
+
encode_message(encoder, message)
|
32
|
+
socket.sendmsg(encoder.buffer.get_string, 0, Socket.sockaddr_nl(0, 0))
|
33
|
+
seq_pid
|
34
|
+
end
|
35
|
+
|
36
|
+
def recv_message(socket, seq_pid, message_class)
|
37
|
+
data, = socket.recvmsg
|
38
|
+
|
39
|
+
decoder = Decoder.new(IO::Buffer.for(data))
|
40
|
+
while decoder.available?(Core::NLMSG_HDRLEN)
|
41
|
+
header = Core::NlMsgHdr.decode(decoder)
|
42
|
+
decoder.align_to(Core::NLMSG_ALIGNTO)
|
43
|
+
raise binding.irb unless [header.seq, header.pid] == seq_pid
|
44
|
+
if header.type < Core::NLMSG_MIN_TYPE
|
45
|
+
# Control messages
|
46
|
+
case header.type
|
47
|
+
when Core::NLMSG_ERROR
|
48
|
+
errno = decoder.get_value(Endian::Host::SINT)
|
49
|
+
if errno == 0
|
50
|
+
yield Ack.new
|
51
|
+
else
|
52
|
+
yield SystemCallError.new(-errno)
|
53
|
+
end
|
54
|
+
decoder.skip(header.len - Core::NLMSG_HDRLEN - 4)
|
55
|
+
when Core::NLMSG_DONE
|
56
|
+
yield Done.new
|
57
|
+
decoder.skip(header.len - Core::NLMSG_HDRLEN)
|
58
|
+
else
|
59
|
+
# just ignore NLMSG_NOOP and other unknown control messages
|
60
|
+
decoder.skip(header.len - Core::NLMSG_HDRLEN)
|
61
|
+
end
|
62
|
+
else
|
63
|
+
# Subsystem-specific messages
|
64
|
+
decoder.limit(header.len - Core::NLMSG_HDRLEN) do
|
65
|
+
decoder.align_to(Core::NLMSG_ALIGNTO)
|
66
|
+
yield message_class.decode(decoder, header)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# @param socket [Socket] Netlink socket
|
73
|
+
# @param type [:do, :dump] Request type
|
74
|
+
# @param request_class [Class] Request message class
|
75
|
+
# @param reply_class [Class] Reply message class
|
76
|
+
# @param args [Hash] Request arguments
|
77
|
+
def exchange_message(socket, type, request_class, reply_class, args)
|
78
|
+
flags = Core::NLM_F_REQUEST | Core::NLM_F_ACK
|
79
|
+
flags |= Core::NLM_F_DUMP if type == :dump
|
80
|
+
|
81
|
+
request = request_class.from_params(args)
|
82
|
+
request.header.flags = flags
|
83
|
+
seq_pid = send_message(socket, request)
|
84
|
+
|
85
|
+
result = []
|
86
|
+
|
87
|
+
done = false
|
88
|
+
acked = false
|
89
|
+
begin
|
90
|
+
recv_message(socket, seq_pid, reply_class) do |message|
|
91
|
+
case message
|
92
|
+
when Done
|
93
|
+
done = true
|
94
|
+
when Exception
|
95
|
+
raise
|
96
|
+
when Ack
|
97
|
+
acked = true
|
98
|
+
else
|
99
|
+
result << message
|
100
|
+
done = true if type == :do
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end until done
|
104
|
+
|
105
|
+
result
|
106
|
+
end
|
107
|
+
|
108
|
+
module DataTypes
|
109
|
+
class Scalar
|
110
|
+
def initialize(type, check)
|
111
|
+
@type = type
|
112
|
+
@check = check
|
113
|
+
end
|
114
|
+
|
115
|
+
def encode(encoder, value)
|
116
|
+
value ||= 0
|
117
|
+
encoder.put_value(@type, value.tap(&@check))
|
118
|
+
end
|
119
|
+
|
120
|
+
def decode(decoder)
|
121
|
+
value = decoder.get_value(@type).tap(&@check)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class String
|
126
|
+
def initialize(check)
|
127
|
+
@check = check
|
128
|
+
end
|
129
|
+
|
130
|
+
def encode(encoder, value)
|
131
|
+
encoder.put_zstring(value)
|
132
|
+
end
|
133
|
+
|
134
|
+
def decode(decoder)
|
135
|
+
decoder.get_zstring
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class Binary
|
140
|
+
def initialize(check)
|
141
|
+
@check = check
|
142
|
+
end
|
143
|
+
|
144
|
+
def encode(encoder, value)
|
145
|
+
encoder.put_string(value)
|
146
|
+
end
|
147
|
+
|
148
|
+
def decode(decoder)
|
149
|
+
decoder.get_string
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class NestedAttributes
|
154
|
+
def initialize(attribute_set)
|
155
|
+
@attribute_set = attribute_set
|
156
|
+
end
|
157
|
+
|
158
|
+
def encode(encoder, value)
|
159
|
+
@attribute_set.encode(encoder, value)
|
160
|
+
end
|
161
|
+
|
162
|
+
def decode(decoder)
|
163
|
+
@attribute_set.decode(decoder)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
data/lib/nl/socket.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Netlink sockets
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module Nl
|
6
|
+
# Netlink socket
|
7
|
+
class Socket < ::Socket
|
8
|
+
module Constants
|
9
|
+
# From include/linux/socket.h
|
10
|
+
PF_NETLINK = AF_NETLINK = 16
|
11
|
+
end
|
12
|
+
include Constants
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def pack_sockaddr_nl(pid, groups) = [Socket::AF_NETLINK, 0, pid, groups].pack('S!S!LL')
|
16
|
+
alias sockaddr_nl pack_sockaddr_nl
|
17
|
+
|
18
|
+
def unpack_sockaddr_nl(sockaddr) = sockaddr.unpack('S!S!LL')[2..3]
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param protonum [Integer] Netlink protocol number
|
22
|
+
def initialize(protonum)
|
23
|
+
super(PF_NETLINK, SOCK_RAW, protonum)
|
24
|
+
@seq = 0 # last-used sequence number
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.open(protonum)
|
28
|
+
return new(protonum) unless block_given?
|
29
|
+
begin
|
30
|
+
socket = new(protonum)
|
31
|
+
yield socket
|
32
|
+
ensure
|
33
|
+
socket&.close
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# XXX: Should Protocol manage next_seq?
|
38
|
+
|
39
|
+
# @return [Integer] Get next sequence number
|
40
|
+
def next_seq
|
41
|
+
nseq = (@seq + 1) & 0xFFFFFFFF
|
42
|
+
nseq = 1 if nseq == 0 # seq=0 is for notification
|
43
|
+
@seq = nseq
|
44
|
+
end
|
45
|
+
|
46
|
+
def complete(hdr)
|
47
|
+
[
|
48
|
+
hdr.seq ||= next_seq,
|
49
|
+
hdr.pid ||= Socket.unpack_sockaddr_nl(local_address.to_sockaddr).first,
|
50
|
+
]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/nl/version.rb
ADDED
data/lib/nl.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative 'nl/version'
|
2
|
+
require_relative 'nl/core'
|
3
|
+
require_relative 'nl/genl'
|
4
|
+
require_relative 'nl/socket'
|
5
|
+
require_relative 'nl/family'
|
6
|
+
require_relative 'nl/protocols/raw'
|
7
|
+
require_relative 'nl/protocols/genl'
|
8
|
+
|
9
|
+
module Nl
|
10
|
+
class Error < StandardError; end
|
11
|
+
|
12
|
+
include Core
|
13
|
+
include Genl
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kasumi Hanazuki
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies: []
|
12
|
+
description: Linux Netlink client
|
13
|
+
email:
|
14
|
+
- kasumi@rollingapple.net
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- ".rspec"
|
20
|
+
- CHANGELOG.md
|
21
|
+
- LICENSE.txt
|
22
|
+
- README.md
|
23
|
+
- Rakefile
|
24
|
+
- lib/nl.rb
|
25
|
+
- lib/nl/core.rb
|
26
|
+
- lib/nl/decoder.rb
|
27
|
+
- lib/nl/encoder.rb
|
28
|
+
- lib/nl/endian.rb
|
29
|
+
- lib/nl/family.rb
|
30
|
+
- lib/nl/genl.rb
|
31
|
+
- lib/nl/protocols/genl.rb
|
32
|
+
- lib/nl/protocols/raw.rb
|
33
|
+
- lib/nl/socket.rb
|
34
|
+
- lib/nl/version.rb
|
35
|
+
homepage: https://github.com/hanazuki/nl
|
36
|
+
licenses:
|
37
|
+
- MIT
|
38
|
+
metadata:
|
39
|
+
homepage_uri: https://github.com/hanazuki/nl
|
40
|
+
source_code_uri: https://github.com/hanazuki/nl/tree/v0.1.0
|
41
|
+
changelog_uri: https://github.com/hanazuki/nl/blob/master/CHANGELOG.md
|
42
|
+
rdoc_options: []
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '3.1'
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
requirements: []
|
56
|
+
rubygems_version: 3.6.8
|
57
|
+
specification_version: 4
|
58
|
+
summary: Linux Netlink client
|
59
|
+
test_files: []
|