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))
|