alignedbuff.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. // Package alignedbuff implements encoding and decoding aligned data elements
  2. // to/from buffers in native endianess.
  3. //
  4. // # Note
  5. //
  6. // The alignment/padding as implemented in this package must match that of
  7. // kernel's and user space C implementations for a particular architecture (bit
  8. // size). Please see also the "dummy structure" _xt_align
  9. // (https://elixir.bootlin.com/linux/v5.17.7/source/include/uapi/linux/netfilter/x_tables.h#L93)
  10. // as well as the associated XT_ALIGN C preprocessor macro.
  11. //
  12. // In particular, we rely on the Go compiler to follow the same architecture
  13. // alignments as the C compiler(s) on Linux.
  14. package alignedbuff
  15. import (
  16. "bytes"
  17. "errors"
  18. "fmt"
  19. "unsafe"
  20. "github.com/google/nftables/binaryutil"
  21. )
  22. // ErrEOF signals trying to read beyond the available payload information.
  23. var ErrEOF = errors.New("not enough data left")
  24. // AlignedBuff implements marshalling and unmarshalling information in
  25. // platform/architecture native endianess and data type alignment. It
  26. // additionally covers some of the nftables-xtables translation-specific
  27. // idiosyncracies to the extend needed in order to properly marshal and
  28. // unmarshal Match and Target expressions, and their Info payload in particular.
  29. type AlignedBuff struct {
  30. data []byte
  31. pos int
  32. }
  33. // New returns a new AlignedBuff for marshalling aligned data in native
  34. // endianess.
  35. func New() AlignedBuff {
  36. return AlignedBuff{}
  37. }
  38. // NewWithData returns a new AlignedBuff for unmarshalling the passed data in
  39. // native endianess.
  40. func NewWithData(data []byte) AlignedBuff {
  41. return AlignedBuff{data: data}
  42. }
  43. // Data returns the properly padded info payload data written before by calling
  44. // the various Uint8, Uint16, ... marshalling functions.
  45. func (a *AlignedBuff) Data() []byte {
  46. // The Linux kernel expects payloads to be padded to the next uint64
  47. // alignment.
  48. a.alignWrite(uint64AlignMask)
  49. return a.data
  50. }
  51. // BytesAligned32 unmarshals the given amount of bytes starting with the native
  52. // alignment for uint32 data types. It returns ErrEOF when trying to read beyond
  53. // the payload.
  54. //
  55. // BytesAligned32 is used to unmarshal IP addresses for different IP versions,
  56. // which are always aligned the same way as the native alignment for uint32.
  57. func (a *AlignedBuff) BytesAligned32(size int) ([]byte, error) {
  58. if err := a.alignCheckedRead(uint32AlignMask); err != nil {
  59. return nil, err
  60. }
  61. if a.pos > len(a.data)-size {
  62. return nil, ErrEOF
  63. }
  64. data := a.data[a.pos : a.pos+size]
  65. a.pos += size
  66. return data, nil
  67. }
  68. // Uint8 unmarshals an uint8 in native endianess and alignment. It returns
  69. // ErrEOF when trying to read beyond the payload.
  70. func (a *AlignedBuff) Uint8() (uint8, error) {
  71. if a.pos >= len(a.data) {
  72. return 0, ErrEOF
  73. }
  74. v := a.data[a.pos]
  75. a.pos++
  76. return v, nil
  77. }
  78. // Uint16 unmarshals an uint16 in native endianess and alignment. It returns
  79. // ErrEOF when trying to read beyond the payload.
  80. func (a *AlignedBuff) Uint16() (uint16, error) {
  81. if err := a.alignCheckedRead(uint16AlignMask); err != nil {
  82. return 0, err
  83. }
  84. v := binaryutil.NativeEndian.Uint16(a.data[a.pos : a.pos+2])
  85. a.pos += 2
  86. return v, nil
  87. }
  88. // Uint16BE unmarshals an uint16 in "network" (=big endian) endianess and native
  89. // uint16 alignment. It returns ErrEOF when trying to read beyond the payload.
  90. func (a *AlignedBuff) Uint16BE() (uint16, error) {
  91. if err := a.alignCheckedRead(uint16AlignMask); err != nil {
  92. return 0, err
  93. }
  94. v := binaryutil.BigEndian.Uint16(a.data[a.pos : a.pos+2])
  95. a.pos += 2
  96. return v, nil
  97. }
  98. // Uint32 unmarshals an uint32 in native endianess and alignment. It returns
  99. // ErrEOF when trying to read beyond the payload.
  100. func (a *AlignedBuff) Uint32() (uint32, error) {
  101. if err := a.alignCheckedRead(uint32AlignMask); err != nil {
  102. return 0, err
  103. }
  104. v := binaryutil.NativeEndian.Uint32(a.data[a.pos : a.pos+4])
  105. a.pos += 4
  106. return v, nil
  107. }
  108. // Uint64 unmarshals an uint64 in native endianess and alignment. It returns
  109. // ErrEOF when trying to read beyond the payload.
  110. func (a *AlignedBuff) Uint64() (uint64, error) {
  111. if err := a.alignCheckedRead(uint64AlignMask); err != nil {
  112. return 0, err
  113. }
  114. v := binaryutil.NativeEndian.Uint64(a.data[a.pos : a.pos+8])
  115. a.pos += 8
  116. return v, nil
  117. }
  118. // Int32 unmarshals an int32 in native endianess and alignment. It returns
  119. // ErrEOF when trying to read beyond the payload.
  120. func (a *AlignedBuff) Int32() (int32, error) {
  121. if err := a.alignCheckedRead(int32AlignMask); err != nil {
  122. return 0, err
  123. }
  124. v := binaryutil.Int32(a.data[a.pos : a.pos+4])
  125. a.pos += 4
  126. return v, nil
  127. }
  128. // String unmarshals a null terminated string
  129. func (a *AlignedBuff) String() (string, error) {
  130. len := 0
  131. for {
  132. if a.data[a.pos+len] == 0x00 {
  133. break
  134. }
  135. len++
  136. }
  137. v := binaryutil.String(a.data[a.pos : a.pos+len])
  138. a.pos += len
  139. return v, nil
  140. }
  141. // StringWithLength unmarshals a string of a given length (for non-null
  142. // terminated strings)
  143. func (a *AlignedBuff) StringWithLength(len int) (string, error) {
  144. v := binaryutil.String(a.data[a.pos : a.pos+len])
  145. a.pos += len
  146. return v, nil
  147. }
  148. // Uint unmarshals an uint in native endianess and alignment for the C "unsigned
  149. // int" type. It returns ErrEOF when trying to read beyond the payload. Please
  150. // note that on 64bit platforms, the size and alignment of C's and Go's unsigned
  151. // integer data types differ, so we encapsulate this difference here.
  152. func (a *AlignedBuff) Uint() (uint, error) {
  153. switch uintSize {
  154. case 2:
  155. v, err := a.Uint16()
  156. return uint(v), err
  157. case 4:
  158. v, err := a.Uint32()
  159. return uint(v), err
  160. case 8:
  161. v, err := a.Uint64()
  162. return uint(v), err
  163. default:
  164. panic(fmt.Sprintf("unsupported uint size %d", uintSize))
  165. }
  166. }
  167. // PutBytesAligned32 marshals the given bytes starting with the native alignment
  168. // for uint32 data types. It additionaly adds padding to reach the specified
  169. // size.
  170. //
  171. // PutBytesAligned32 is used to marshal IP addresses for different IP versions,
  172. // which are always aligned the same way as the native alignment for uint32.
  173. func (a *AlignedBuff) PutBytesAligned32(data []byte, size int) {
  174. a.alignWrite(uint32AlignMask)
  175. a.data = append(a.data, data...)
  176. a.pos += len(data)
  177. if len(data) < size {
  178. padding := size - len(data)
  179. a.data = append(a.data, bytes.Repeat([]byte{0}, padding)...)
  180. a.pos += padding
  181. }
  182. }
  183. // PutUint8 marshals an uint8 in native endianess and alignment.
  184. func (a *AlignedBuff) PutUint8(v uint8) {
  185. a.data = append(a.data, v)
  186. a.pos++
  187. }
  188. // PutUint16 marshals an uint16 in native endianess and alignment.
  189. func (a *AlignedBuff) PutUint16(v uint16) {
  190. a.alignWrite(uint16AlignMask)
  191. a.data = append(a.data, binaryutil.NativeEndian.PutUint16(v)...)
  192. a.pos += 2
  193. }
  194. // PutUint16BE marshals an uint16 in "network" (=big endian) endianess and
  195. // native uint16 alignment.
  196. func (a *AlignedBuff) PutUint16BE(v uint16) {
  197. a.alignWrite(uint16AlignMask)
  198. a.data = append(a.data, binaryutil.BigEndian.PutUint16(v)...)
  199. a.pos += 2
  200. }
  201. // PutUint32 marshals an uint32 in native endianess and alignment.
  202. func (a *AlignedBuff) PutUint32(v uint32) {
  203. a.alignWrite(uint32AlignMask)
  204. a.data = append(a.data, binaryutil.NativeEndian.PutUint32(v)...)
  205. a.pos += 4
  206. }
  207. // PutUint64 marshals an uint64 in native endianess and alignment.
  208. func (a *AlignedBuff) PutUint64(v uint64) {
  209. a.alignWrite(uint64AlignMask)
  210. a.data = append(a.data, binaryutil.NativeEndian.PutUint64(v)...)
  211. a.pos += 8
  212. }
  213. // PutInt32 marshals an int32 in native endianess and alignment.
  214. func (a *AlignedBuff) PutInt32(v int32) {
  215. a.alignWrite(int32AlignMask)
  216. a.data = append(a.data, binaryutil.PutInt32(v)...)
  217. a.pos += 4
  218. }
  219. // PutString marshals a string.
  220. func (a *AlignedBuff) PutString(v string) {
  221. a.data = append(a.data, binaryutil.PutString(v)...)
  222. a.pos += len(v)
  223. }
  224. // PutUint marshals an uint in native endianess and alignment for the C
  225. // "unsigned int" type. Please note that on 64bit platforms, the size and
  226. // alignment of C's and Go's unsigned integer data types differ, so we
  227. // encapsulate this difference here.
  228. func (a *AlignedBuff) PutUint(v uint) {
  229. switch uintSize {
  230. case 2:
  231. a.PutUint16(uint16(v))
  232. case 4:
  233. a.PutUint32(uint32(v))
  234. case 8:
  235. a.PutUint64(uint64(v))
  236. default:
  237. panic(fmt.Sprintf("unsupported uint size %d", uintSize))
  238. }
  239. }
  240. // alignCheckedRead aligns the (read) position if necessary and suitable
  241. // according to the specified alignment mask. alignCheckedRead returns an error
  242. // if after any necessary alignment there isn't enough data left to be read into
  243. // a value of the size corresponding to the specified alignment mask.
  244. func (a *AlignedBuff) alignCheckedRead(m int) error {
  245. a.pos = (a.pos + m) & ^m
  246. if a.pos > len(a.data)-(m+1) {
  247. return ErrEOF
  248. }
  249. return nil
  250. }
  251. // alignWrite aligns the (write) position if necessary and suitable according to
  252. // the specified alignment mask. It doubles as final payload padding helpmate in
  253. // order to keep the kernel happy.
  254. func (a *AlignedBuff) alignWrite(m int) {
  255. pos := (a.pos + m) & ^m
  256. if pos != a.pos {
  257. a.data = append(a.data, padding[:pos-a.pos]...)
  258. a.pos = pos
  259. }
  260. }
  261. // This is ... ugly.
  262. var uint16AlignMask = int(unsafe.Alignof(uint16(0)) - 1)
  263. var uint32AlignMask = int(unsafe.Alignof(uint32(0)) - 1)
  264. var uint64AlignMask = int(unsafe.Alignof(uint64(0)) - 1)
  265. var padding = bytes.Repeat([]byte{0}, uint64AlignMask)
  266. var int32AlignMask = int(unsafe.Alignof(int32(0)) - 1)
  267. // And this even worse.
  268. var uintSize = unsafe.Sizeof(uint32(0))