Skip to content

Commit 716c04d

Browse files
authored
Merge pull request #1 from ti-mo/attribute-codec
Switch to AttributeEncoder/Decoder
2 parents f20d5b6 + 5ebce80 commit 716c04d

File tree

9 files changed

+248
-123
lines changed

9 files changed

+248
-123
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ bench:
4545
bench-integration:
4646
go test -bench=. -tags=integration -exec sudo ./...
4747

48-
cover: cover.out
49-
cover.out: $(SOURCES)
48+
.PHONY: cover
49+
cover:
5050
go test -coverprofile=cover.out -covermode=atomic ./...
5151
# Remove coverage output from files generated by Stringer.
5252
sed -i '/_string.go/d' cover.out

attribute.go

Lines changed: 119 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,33 @@ import (
55
"fmt"
66

77
"github.com/mdlayher/netlink"
8-
"github.com/pkg/errors"
9-
"golang.org/x/sys/unix"
108
)
119

10+
// NewAttributeDecoder instantiates a new netlink.AttributeDecoder
11+
// configured with a Big Endian byte order.
12+
func NewAttributeDecoder(b []byte) (*netlink.AttributeDecoder, error) {
13+
ad, err := netlink.NewAttributeDecoder(b)
14+
if err != nil {
15+
return nil, err
16+
}
17+
18+
// All Netfilter attribute payloads are big-endian. (network byte order)
19+
ad.ByteOrder = binary.BigEndian
20+
21+
return ad, nil
22+
}
23+
24+
// NewAttributeDecoder instantiates a new netlink.AttributeEncoder
25+
// configured with a Big Endian byte order.
26+
func NewAttributeEncoder() *netlink.AttributeEncoder {
27+
ae := netlink.NewAttributeEncoder()
28+
29+
// All Netfilter attribute payloads are big-endian. (network byte order)
30+
ae.ByteOrder = binary.BigEndian
31+
32+
return ae
33+
}
34+
1235
// An Attribute is a copy of a netlink.Attribute that can be nested.
1336
type Attribute struct {
1437

@@ -138,99 +161,127 @@ func Uint64Bytes(u uint64) []byte {
138161
return d
139162
}
140163

141-
// unmarshalAttributes returns an array of netfilter.Attributes decoded from
142-
// a byte array. This byte array should be taken from the netlink.Message's
143-
// Data payload after the nfHeaderLen offset.
144-
func unmarshalAttributes(b []byte) ([]Attribute, error) {
145-
146-
// Obtain a list of parsed netlink attributes possibly holding
147-
// nested Netfilter attributes in their binary Data field.
148-
attrs, err := netlink.UnmarshalAttributes(b)
149-
if err != nil {
150-
return nil, errors.Wrap(err, errWrapNetlinkUnmarshalAttrs)
151-
}
164+
// decode fills the Attribute's Children field with Attributes
165+
// obtained by exhausting ad.
166+
func (a *Attribute) decode(ad *netlink.AttributeDecoder) error {
152167

153-
var ra []Attribute
154-
155-
// Only allocate backing array when there are netlink attributes to decode.
156-
if len(attrs) != 0 {
157-
ra = make([]Attribute, 0, len(attrs))
158-
}
159-
160-
// Wrap all netlink.Attributes into netfilter.Attributes to support nesting
161-
for _, nla := range attrs {
168+
for ad.Next() {
162169

163170
// Copy the netlink attribute's fields into the netfilter attribute.
164171
nfa := Attribute{
165-
// Only consider the rightmost 14 bits for Type
166-
Type: nla.Type & ^(uint16(unix.NLA_F_NESTED) | uint16(unix.NLA_F_NET_BYTEORDER)),
167-
Data: nla.Data,
172+
// Only consider the rightmost 14 bits for Type.
173+
// ad.Type implicitly masks the Nested and NetByteOrder bits.
174+
Type: ad.Type(),
175+
Data: ad.Bytes(),
168176
}
169177

170-
// Boolean flags extracted from the two leftmost bits of Type
171-
nfa.Nested = (nla.Type & uint16(unix.NLA_F_NESTED)) != 0
172-
nfa.NetByteOrder = (nla.Type & uint16(unix.NLA_F_NET_BYTEORDER)) != 0
178+
// Boolean flags extracted from the two leftmost bits of Type.
179+
nfa.Nested = ad.TypeFlags()&netlink.Nested != 0
180+
nfa.NetByteOrder = ad.TypeFlags()&netlink.NetByteOrder != 0
173181

174182
if nfa.NetByteOrder && nfa.Nested {
175-
return nil, errInvalidAttributeFlags
183+
return errInvalidAttributeFlags
176184
}
177185

178-
// Unmarshal recursively if the netlink Nested flag is set
186+
// Unmarshal recursively if the netlink Nested flag is set.
179187
if nfa.Nested {
180-
if nfa.Children, err = unmarshalAttributes(nla.Data); err != nil {
181-
return nil, err
182-
}
188+
ad.Nested(nfa.decode)
183189
}
184190

185-
ra = append(ra, nfa)
191+
a.Children = append(a.Children, nfa)
186192
}
187193

188-
return ra, nil
194+
return ad.Err()
189195
}
190196

191-
// marshalAttributes marshals a nested attribute structure into a byte slice.
192-
// This byte slice can then be copied into a netlink.Message's Data field after
193-
// the nfHeaderLen offset.
194-
func marshalAttributes(attrs []Attribute) ([]byte, error) {
197+
// encode returns a function that takes an AttributeEncoder and returns error.
198+
// This function can be passed to AttributeEncoder.Nested for recursively
199+
// encoding Attributes.
200+
func (a *Attribute) encode(attrs []Attribute) func(*netlink.AttributeEncoder) error {
201+
202+
return func(ae *netlink.AttributeEncoder) error {
195203

196-
// netlink.Attribute to use as scratch buffer, requires a single allocation
197-
nla := netlink.Attribute{}
204+
for _, nfa := range attrs {
198205

199-
// Output array, initialized to the length of the input array
200-
ra := make([]netlink.Attribute, 0, len(attrs))
206+
if nfa.NetByteOrder && nfa.Nested {
207+
return errInvalidAttributeFlags
208+
}
201209

202-
for _, nfa := range attrs {
210+
if nfa.Nested {
211+
ae.Nested(nfa.Type, nfa.encode(nfa.Children))
212+
continue
213+
}
203214

204-
if nfa.NetByteOrder && nfa.Nested {
205-
return nil, errInvalidAttributeFlags
215+
// Manually set the NetByteOrder flag, since ae.Bytes() can't.
216+
if nfa.NetByteOrder {
217+
nfa.Type |= netlink.NetByteOrder
218+
}
219+
ae.Bytes(nfa.Type, nfa.Data)
206220
}
207221

208-
// Save nested or byte order flags back to the netlink.Attribute's
209-
// Type field to include it in the marshaling operation
210-
nla.Type = nfa.Type
222+
return nil
223+
}
224+
}
211225

212-
switch {
213-
case nfa.Nested:
214-
nla.Type = nla.Type | unix.NLA_F_NESTED
215-
case nfa.NetByteOrder:
216-
nla.Type = nla.Type | unix.NLA_F_NET_BYTEORDER
217-
}
226+
// decodeAttributes returns an array of netfilter.Attributes decoded from
227+
// a byte array. This byte array should be taken from the netlink.Message's
228+
// Data payload after the nfHeaderLen offset.
229+
func decodeAttributes(ad *netlink.AttributeDecoder) ([]Attribute, error) {
218230

219-
// Recursively marshal the attribute's children
220-
if nfa.Nested {
221-
nfnab, err := marshalAttributes(nfa.Children)
222-
if err != nil {
223-
return nil, err
224-
}
231+
// Use the Children element of the Attribute to decode into.
232+
// Attribute already has nested decoding implemented on the type.
233+
var a Attribute
225234

226-
nla.Data = nfnab
227-
} else {
228-
nla.Data = nfa.Data
229-
}
235+
// Pre-allocate backing array when there are netlink attributes to decode.
236+
if ad.Len() != 0 {
237+
a.Children = make([]Attribute, 0, ad.Len())
238+
}
239+
240+
// Catch any errors encountered parsing netfilter structures.
241+
if err := a.decode(ad); err != nil {
242+
return nil, err
243+
}
244+
245+
return a.Children, nil
246+
}
247+
248+
// encodeAttributes encodes a list of Attributes into the given netlink.AttributeEncoder.
249+
func encodeAttributes(ae *netlink.AttributeEncoder, attrs []Attribute) error {
250+
251+
if ae == nil {
252+
return errNilAttributeEncoder
253+
}
254+
255+
attr := Attribute{}
256+
return attr.encode(attrs)(ae)
257+
}
258+
259+
// MarshalAttributes marshals a nested attribute structure into a byte slice.
260+
// This byte slice can then be copied into a netlink.Message's Data field after
261+
// the nfHeaderLen offset.
262+
func MarshalAttributes(attrs []Attribute) ([]byte, error) {
263+
264+
ae := NewAttributeEncoder()
265+
266+
if err := encodeAttributes(ae, attrs); err != nil {
267+
return nil, err
268+
}
230269

231-
ra = append(ra, nla)
270+
b, err := ae.Encode()
271+
if err != nil {
272+
return nil, err
273+
}
274+
275+
return b, nil
276+
}
277+
278+
// UnmarshalAttributes unmarshals a byte slice into a list of Attributes.
279+
func UnmarshalAttributes(b []byte) ([]Attribute, error) {
280+
281+
ad, err := NewAttributeDecoder(b)
282+
if err != nil {
283+
return nil, err
232284
}
233285

234-
// Marshal all Netfilter attributes into binary representation of Netlink attributes
235-
return netlink.MarshalAttributes(ra)
286+
return decodeAttributes(ad)
236287
}

attribute_test.go

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package netfilter
22

33
import (
4+
"errors"
45
"strings"
56
"testing"
67

@@ -141,7 +142,7 @@ func TestAttributeMarshalAttributes(t *testing.T) {
141142
for _, tt := range tests {
142143
t.Run(tt.name, func(t *testing.T) {
143144

144-
b, err := marshalAttributes(tt.attrs)
145+
b, err := MarshalAttributes(tt.attrs)
145146
if err != nil {
146147
t.Fatalf("unexpected marshal error: %v", err)
147148
}
@@ -194,7 +195,7 @@ func TestAttributeMarshalErrors(t *testing.T) {
194195

195196
for _, tt := range tests {
196197
t.Run(tt.name, func(t *testing.T) {
197-
_, err := marshalAttributes(tt.attrs)
198+
_, err := MarshalAttributes(tt.attrs)
198199
require.Error(t, err, "marshal must error")
199200

200201
if tt.err != nil {
@@ -209,17 +210,20 @@ func TestAttributeMarshalErrors(t *testing.T) {
209210
}
210211
}
211212

212-
func TestAttributeUnmarshalErrors(t *testing.T) {
213+
func TestAttributeDecoderErrors(t *testing.T) {
213214
tests := []struct {
214215
name string
215216
b []byte
216217
err error
217218
errWrap string
218219
}{
219220
{
220-
name: "netlink unmarshal error",
221-
b: []byte{1},
222-
errWrap: errWrapNetlinkUnmarshalAttrs,
221+
name: "invalid attribute flags on top-level attribute",
222+
b: []byte{
223+
8, 0, 0, 192, // 192 = nested + netByteOrder
224+
0, 0, 0, 0,
225+
},
226+
err: errInvalidAttributeFlags,
223227
},
224228
{
225229
name: "invalid attribute flags on nested attribute",
@@ -230,27 +234,19 @@ func TestAttributeUnmarshalErrors(t *testing.T) {
230234
},
231235
err: errInvalidAttributeFlags,
232236
},
237+
{
238+
name: "decoding invalid attribute",
239+
b: []byte{4, 0, 0},
240+
err: errors.New("invalid attribute; length too short or too large"),
241+
},
233242
}
234243

235244
for _, tt := range tests {
236245
t.Run(tt.name, func(t *testing.T) {
237-
_, err := unmarshalAttributes(tt.b)
238-
239-
if err == nil {
240-
t.Fatal("unmarshal did not error")
241-
}
242-
243-
if tt.err != nil {
244-
if want, got := tt.err, err; want != got {
245-
t.Fatalf("unexpected error:\n- want: %v\n- got: %v",
246-
want, got.Error())
247-
}
248-
} else if tt.errWrap != "" {
249-
if !strings.HasPrefix(err.Error(), tt.errWrap+":") {
250-
t.Fatalf("unexpected wrapped error:\n- expected prefix: %v\n- error string: %v",
251-
tt.errWrap, err)
252-
}
253-
}
246+
_, err := UnmarshalAttributes(tt.b)
247+
require.Error(t, err)
248+
require.Error(t, tt.err)
249+
require.EqualError(t, err, tt.err.Error())
254250
})
255251
}
256252
}
@@ -389,19 +385,28 @@ func TestAttributeMarshalTwoWay(t *testing.T) {
389385
for _, tt := range tests {
390386
t.Run(tt.name, func(t *testing.T) {
391387

388+
ad, err := NewAttributeDecoder(tt.b)
389+
if err != nil {
390+
t.Fatal("unexpected error creating AttributeDecoder:", err)
391+
}
392+
392393
// Unmarshal binary content into nested structures
393-
attrs, err := unmarshalAttributes(tt.b)
394+
attrs, err := decodeAttributes(ad)
394395
require.NoError(t, err)
395396

396397
assert.Empty(t, cmp.Diff(tt.attrs, attrs))
397398

398399
var b []byte
399400

400401
// Attempt re-marshal into binary form
401-
b, err = marshalAttributes(tt.attrs)
402+
b, err = MarshalAttributes(tt.attrs)
402403
require.NoError(t, err)
403404

404405
assert.Empty(t, cmp.Diff(tt.b, b))
405406
})
406407
}
407408
}
409+
410+
func TestErrors(t *testing.T) {
411+
assert.EqualError(t, encodeAttributes(nil, nil), errNilAttributeEncoder.Error())
412+
}

conn_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func TestConnQueryMulticast(t *testing.T) {
9696
func TestConnReceive(t *testing.T) {
9797

9898
// Inject a message directly into the nltest connection
99-
connEcho.conn.Send(nlMsgReqAck)
99+
_, _ = connEcho.conn.Send(nlMsgReqAck)
100100

101101
// Drain the socket
102102
_, err := connEcho.Receive()

errors.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@ import (
44
"errors"
55
)
66

7-
const (
8-
errWrapNetlinkUnmarshalAttrs = "error unmarshaling netlink attributes"
9-
)
10-
117
var (
128
// errInvalidAttributeFlags specifies if an Attribute's flag configuration is invalid.
139
// From a comment in Linux/include/uapi/linux/netlink.h, Nested and NetByteOrder are mutually exclusive.
@@ -18,4 +14,6 @@ var (
1814
errConnIsMulticast = errors.New("Conn is attached to one or more multicast groups and can no longer be used for bidirectional traffic")
1915

2016
errNoMulticastGroups = errors.New("need one or more multicast groups to join")
17+
18+
errNilAttributeEncoder = errors.New("given AttributeEncoder is nil")
2119
)

go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ go 1.12
44

55
require (
66
github.com/google/go-cmp v0.3.1
7-
github.com/mdlayher/netlink v1.0.0
7+
github.com/mdlayher/netlink v1.0.1-0.20191210152442-a1644773bc99
88
github.com/pkg/errors v0.8.1
99
github.com/stretchr/testify v1.3.0
10-
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456
10+
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
11+
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449
1112
)

0 commit comments

Comments
 (0)