File: alignedbuff.go

package info (click to toggle)
golang-github-google-nftables 0.2.0-3
  • links: PTS, VCS
  • area: main
  • in suites: experimental, forky, sid, trixie
  • size: 756 kB
  • sloc: makefile: 8
file content (300 lines) | stat: -rw-r--r-- 9,352 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
// Package alignedbuff implements encoding and decoding aligned data elements
// to/from buffers in native endianess.
//
// # Note
//
// The alignment/padding as implemented in this package must match that of
// kernel's and user space C implementations for a particular architecture (bit
// size). Please see also the "dummy structure" _xt_align
// (https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/x_tables.h#L93)
// as well as the associated XT_ALIGN C preprocessor macro.
//
// In particular, we rely on the Go compiler to follow the same architecture
// alignments as the C compiler(s) on Linux.
package alignedbuff

import (
	"bytes"
	"errors"
	"fmt"
	"unsafe"

	"github.com/google/nftables/binaryutil"
)

// ErrEOF signals trying to read beyond the available payload information.
var ErrEOF = errors.New("not enough data left")

// AlignedBuff implements marshalling and unmarshalling information in
// platform/architecture native endianess and data type alignment. It
// additionally covers some of the nftables-xtables translation-specific
// idiosyncracies to the extend needed in order to properly marshal and
// unmarshal Match and Target expressions, and their Info payload in particular.
type AlignedBuff struct {
	data []byte
	pos  int
}

// New returns a new AlignedBuff for marshalling aligned data in native
// endianess.
func New() AlignedBuff {
	return AlignedBuff{}
}

// NewWithData returns a new AlignedBuff for unmarshalling the passed data in
// native endianess.
func NewWithData(data []byte) AlignedBuff {
	return AlignedBuff{data: data}
}

// Data returns the properly padded info payload data written before by calling
// the various Uint8, Uint16, ... marshalling functions.
func (a *AlignedBuff) Data() []byte {
	// The Linux kernel expects payloads to be padded to the next uint64
	// alignment.
	a.alignWrite(uint64AlignMask)
	return a.data
}

// BytesAligned32 unmarshals the given amount of bytes starting with the native
// alignment for uint32 data types. It returns ErrEOF when trying to read beyond
// the payload.
//
// BytesAligned32 is used to unmarshal IP addresses for different IP versions,
// which are always aligned the same way as the native alignment for uint32.
func (a *AlignedBuff) BytesAligned32(size int) ([]byte, error) {
	if err := a.alignCheckedRead(uint32AlignMask); err != nil {
		return nil, err
	}
	if a.pos > len(a.data)-size {
		return nil, ErrEOF
	}
	data := a.data[a.pos : a.pos+size]
	a.pos += size
	return data, nil
}

// Uint8 unmarshals an uint8 in native endianess and alignment. It returns
// ErrEOF when trying to read beyond the payload.
func (a *AlignedBuff) Uint8() (uint8, error) {
	if a.pos >= len(a.data) {
		return 0, ErrEOF
	}
	v := a.data[a.pos]
	a.pos++
	return v, nil
}

// Uint16 unmarshals an uint16 in native endianess and alignment. It returns
// ErrEOF when trying to read beyond the payload.
func (a *AlignedBuff) Uint16() (uint16, error) {
	if err := a.alignCheckedRead(uint16AlignMask); err != nil {
		return 0, err
	}
	v := binaryutil.NativeEndian.Uint16(a.data[a.pos : a.pos+2])
	a.pos += 2
	return v, nil
}

// Uint16BE unmarshals an uint16 in "network" (=big endian) endianess and native
// uint16 alignment. It returns ErrEOF when trying to read beyond the payload.
func (a *AlignedBuff) Uint16BE() (uint16, error) {
	if err := a.alignCheckedRead(uint16AlignMask); err != nil {
		return 0, err
	}
	v := binaryutil.BigEndian.Uint16(a.data[a.pos : a.pos+2])
	a.pos += 2
	return v, nil
}

// Uint32 unmarshals an uint32 in native endianess and alignment. It returns
// ErrEOF when trying to read beyond the payload.
func (a *AlignedBuff) Uint32() (uint32, error) {
	if err := a.alignCheckedRead(uint32AlignMask); err != nil {
		return 0, err
	}
	v := binaryutil.NativeEndian.Uint32(a.data[a.pos : a.pos+4])
	a.pos += 4
	return v, nil
}

// Uint64 unmarshals an uint64 in native endianess and alignment. It returns
// ErrEOF when trying to read beyond the payload.
func (a *AlignedBuff) Uint64() (uint64, error) {
	if err := a.alignCheckedRead(uint64AlignMask); err != nil {
		return 0, err
	}
	v := binaryutil.NativeEndian.Uint64(a.data[a.pos : a.pos+8])
	a.pos += 8
	return v, nil
}

// Int32 unmarshals an int32 in native endianess and alignment. It returns
// ErrEOF when trying to read beyond the payload.
func (a *AlignedBuff) Int32() (int32, error) {
	if err := a.alignCheckedRead(int32AlignMask); err != nil {
		return 0, err
	}
	v := binaryutil.Int32(a.data[a.pos : a.pos+4])
	a.pos += 4
	return v, nil
}

// String unmarshals a null terminated string
func (a *AlignedBuff) String() (string, error) {
	len := 0
	for {
		if a.data[a.pos+len] == 0x00 {
			break
		}
		len++
	}

	v := binaryutil.String(a.data[a.pos : a.pos+len])
	a.pos += len
	return v, nil
}

// StringWithLength unmarshals a string of a given length (for non-null
// terminated strings)
func (a *AlignedBuff) StringWithLength(len int) (string, error) {
	v := binaryutil.String(a.data[a.pos : a.pos+len])
	a.pos += len
	return v, nil
}

// Uint unmarshals an uint in native endianess and alignment for the C "unsigned
// int" type. It returns ErrEOF when trying to read beyond the payload. Please
// note that on 64bit platforms, the size and alignment of C's and Go's unsigned
// integer data types differ, so we encapsulate this difference here.
func (a *AlignedBuff) Uint() (uint, error) {
	switch uintSize {
	case 2:
		v, err := a.Uint16()
		return uint(v), err
	case 4:
		v, err := a.Uint32()
		return uint(v), err
	case 8:
		v, err := a.Uint64()
		return uint(v), err
	default:
		panic(fmt.Sprintf("unsupported uint size %d", uintSize))
	}
}

// PutBytesAligned32 marshals the given bytes starting with the native alignment
// for uint32 data types. It additionaly adds padding to reach the specified
// size.
//
// PutBytesAligned32 is used to marshal IP addresses for different IP versions,
// which are always aligned the same way as the native alignment for uint32.
func (a *AlignedBuff) PutBytesAligned32(data []byte, size int) {
	a.alignWrite(uint32AlignMask)
	a.data = append(a.data, data...)
	a.pos += len(data)
	if len(data) < size {
		padding := size - len(data)
		a.data = append(a.data, bytes.Repeat([]byte{0}, padding)...)
		a.pos += padding
	}
}

// PutUint8 marshals an uint8 in native endianess and alignment.
func (a *AlignedBuff) PutUint8(v uint8) {
	a.data = append(a.data, v)
	a.pos++
}

// PutUint16 marshals an uint16 in native endianess and alignment.
func (a *AlignedBuff) PutUint16(v uint16) {
	a.alignWrite(uint16AlignMask)
	a.data = append(a.data, binaryutil.NativeEndian.PutUint16(v)...)
	a.pos += 2
}

// PutUint16BE marshals an uint16 in "network" (=big endian) endianess and
// native uint16 alignment.
func (a *AlignedBuff) PutUint16BE(v uint16) {
	a.alignWrite(uint16AlignMask)
	a.data = append(a.data, binaryutil.BigEndian.PutUint16(v)...)
	a.pos += 2
}

// PutUint32 marshals an uint32 in native endianess and alignment.
func (a *AlignedBuff) PutUint32(v uint32) {
	a.alignWrite(uint32AlignMask)
	a.data = append(a.data, binaryutil.NativeEndian.PutUint32(v)...)
	a.pos += 4
}

// PutUint64 marshals an uint64 in native endianess and alignment.
func (a *AlignedBuff) PutUint64(v uint64) {
	a.alignWrite(uint64AlignMask)
	a.data = append(a.data, binaryutil.NativeEndian.PutUint64(v)...)
	a.pos += 8
}

// PutInt32 marshals an int32 in native endianess and alignment.
func (a *AlignedBuff) PutInt32(v int32) {
	a.alignWrite(int32AlignMask)
	a.data = append(a.data, binaryutil.PutInt32(v)...)
	a.pos += 4
}

// PutString marshals a string.
func (a *AlignedBuff) PutString(v string) {
	a.data = append(a.data, binaryutil.PutString(v)...)
	a.pos += len(v)
}

// PutUint marshals an uint in native endianess and alignment for the C
// "unsigned int" type. Please note that on 64bit platforms, the size and
// alignment of C's and Go's unsigned integer data types differ, so we
// encapsulate this difference here.
func (a *AlignedBuff) PutUint(v uint) {
	switch uintSize {
	case 2:
		a.PutUint16(uint16(v))
	case 4:
		a.PutUint32(uint32(v))
	case 8:
		a.PutUint64(uint64(v))
	default:
		panic(fmt.Sprintf("unsupported uint size %d", uintSize))
	}
}

// alignCheckedRead aligns the (read) position if necessary and suitable
// according to the specified alignment mask. alignCheckedRead returns an error
// if after any necessary alignment there isn't enough data left to be read into
// a value of the size corresponding to the specified alignment mask.
func (a *AlignedBuff) alignCheckedRead(m int) error {
	a.pos = (a.pos + m) & ^m
	if a.pos > len(a.data)-(m+1) {
		return ErrEOF
	}
	return nil
}

// alignWrite aligns the (write) position if necessary and suitable according to
// the specified alignment mask. It doubles as final payload padding helpmate in
// order to keep the kernel happy.
func (a *AlignedBuff) alignWrite(m int) {
	pos := (a.pos + m) & ^m
	if pos != a.pos {
		a.data = append(a.data, padding[:pos-a.pos]...)
		a.pos = pos
	}
}

// This is ... ugly.
var uint16AlignMask = int(unsafe.Alignof(uint16(0)) - 1)
var uint32AlignMask = int(unsafe.Alignof(uint32(0)) - 1)
var uint64AlignMask = int(unsafe.Alignof(uint64(0)) - 1)
var padding = bytes.Repeat([]byte{0}, uint64AlignMask)

var int32AlignMask = int(unsafe.Alignof(int32(0)) - 1)

// And this even worse.
var uintSize = unsafe.Sizeof(uint32(0))