device.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. package vhostnet
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "os"
  7. "runtime"
  8. "github.com/slackhq/nebula/overlay/vhost"
  9. "github.com/slackhq/nebula/overlay/virtqueue"
  10. "github.com/slackhq/nebula/packet"
  11. "github.com/slackhq/nebula/util/virtio"
  12. "golang.org/x/sys/unix"
  13. )
  14. // ErrDeviceClosed is returned when the [Device] is closed while operations are
  15. // still running.
  16. var ErrDeviceClosed = errors.New("device was closed")
  17. // The indexes for the receive and transmit queues.
  18. const (
  19. receiveQueueIndex = 0
  20. transmitQueueIndex = 1
  21. )
  22. // Device represents a vhost networking device within the kernel-level virtio
  23. // implementation and provides methods to interact with it.
  24. type Device struct {
  25. controlFD int
  26. ReceiveQueue *virtqueue.SplitQueue
  27. TransmitQueue *virtqueue.SplitQueue
  28. }
  29. // NewDevice initializes a new vhost networking device within the
  30. // kernel-level virtio implementation, sets up the virtqueues and returns a
  31. // [Device] instance that can be used to communicate with that vhost device.
  32. //
  33. // There are multiple options that can be passed to this constructor to
  34. // influence device creation:
  35. // - [WithQueueSize]
  36. // - [WithBackendFD]
  37. // - [WithBackendDevice]
  38. //
  39. // Remember to call [Device.Close] after use to free up resources.
  40. func NewDevice(options ...Option) (*Device, error) {
  41. var err error
  42. opts := optionDefaults
  43. opts.apply(options)
  44. if err = opts.validate(); err != nil {
  45. return nil, fmt.Errorf("invalid options: %w", err)
  46. }
  47. dev := Device{
  48. controlFD: -1,
  49. }
  50. // Clean up a partially initialized device when something fails.
  51. defer func() {
  52. if err != nil {
  53. _ = dev.Close()
  54. }
  55. }()
  56. // Retrieve a new control file descriptor. This will be used to configure
  57. // the vhost networking device in the kernel.
  58. dev.controlFD, err = unix.Open("/dev/vhost-net", os.O_RDWR, 0666)
  59. if err != nil {
  60. return nil, fmt.Errorf("get control file descriptor: %w", err)
  61. }
  62. if err = vhost.OwnControlFD(dev.controlFD); err != nil {
  63. return nil, fmt.Errorf("own control file descriptor: %w", err)
  64. }
  65. // Advertise the supported features. This isn't much for now.
  66. // TODO: Add feature options and implement proper feature negotiation.
  67. getFeatures, err := vhost.GetFeatures(dev.controlFD) //0x1033D008000 but why
  68. if err != nil {
  69. return nil, fmt.Errorf("get features: %w", err)
  70. }
  71. if getFeatures == 0 {
  72. }
  73. //const funky = virtio.Feature(1 << 27)
  74. //features := virtio.FeatureVersion1 | funky // | todo virtio.FeatureNetMergeRXBuffers
  75. features := virtio.FeatureVersion1 | virtio.FeatureNetMergeRXBuffers
  76. if err = vhost.SetFeatures(dev.controlFD, features); err != nil {
  77. return nil, fmt.Errorf("set features: %w", err)
  78. }
  79. itemSize := os.Getpagesize() * 4 //todo config
  80. // Initialize and register the queues needed for the networking device.
  81. if dev.ReceiveQueue, err = createQueue(dev.controlFD, receiveQueueIndex, opts.queueSize, itemSize); err != nil {
  82. return nil, fmt.Errorf("create receive queue: %w", err)
  83. }
  84. if dev.TransmitQueue, err = createQueue(dev.controlFD, transmitQueueIndex, opts.queueSize, itemSize); err != nil {
  85. return nil, fmt.Errorf("create transmit queue: %w", err)
  86. }
  87. // Set up memory mappings for all buffers used by the queues. This has to
  88. // happen before a backend for the queues can be registered.
  89. memoryLayout := vhost.NewMemoryLayoutForQueues(
  90. []*virtqueue.SplitQueue{dev.ReceiveQueue, dev.TransmitQueue},
  91. )
  92. if err = vhost.SetMemoryLayout(dev.controlFD, memoryLayout); err != nil {
  93. return nil, fmt.Errorf("setup memory layout: %w", err)
  94. }
  95. // Set the queue backends. This activates the queues within the kernel.
  96. if err = SetQueueBackend(dev.controlFD, receiveQueueIndex, opts.backendFD); err != nil {
  97. return nil, fmt.Errorf("set receive queue backend: %w", err)
  98. }
  99. if err = SetQueueBackend(dev.controlFD, transmitQueueIndex, opts.backendFD); err != nil {
  100. return nil, fmt.Errorf("set transmit queue backend: %w", err)
  101. }
  102. // Fully populate the rx queue with available buffers which the device
  103. // can write new packets into.
  104. if err = dev.refillReceiveQueue(); err != nil {
  105. return nil, fmt.Errorf("refill receive queue: %w", err)
  106. }
  107. if err = dev.prefillTxQueue(); err != nil {
  108. return nil, fmt.Errorf("prefill tx queue: %w", err)
  109. }
  110. // Make sure to clean up even when the device gets garbage collected without
  111. // Close being called first.
  112. devPtr := &dev
  113. runtime.SetFinalizer(devPtr, (*Device).Close)
  114. return devPtr, nil
  115. }
  116. // refillReceiveQueue offers as many new device-writable buffers to the device
  117. // as the queue can fit. The device will then use these to write received
  118. // packets.
  119. func (dev *Device) refillReceiveQueue() error {
  120. for {
  121. _, err := dev.ReceiveQueue.OfferInDescriptorChains()
  122. if err != nil {
  123. if errors.Is(err, virtqueue.ErrNotEnoughFreeDescriptors) {
  124. // Queue is full, job is done.
  125. return nil
  126. }
  127. return fmt.Errorf("offer descriptor chain: %w", err)
  128. }
  129. }
  130. }
  131. func (dev *Device) prefillTxQueue() error {
  132. for {
  133. dt := dev.TransmitQueue.DescriptorTable()
  134. for {
  135. x, _, err := dt.CreateDescriptorForOutputs()
  136. if err != nil {
  137. if errors.Is(err, virtqueue.ErrNotEnoughFreeDescriptors) {
  138. // Queue is full, job is done.
  139. return nil
  140. }
  141. return err
  142. }
  143. err = dev.TransmitQueue.OfferDescriptorChains([]uint16{x}, false)
  144. if err != nil {
  145. return err
  146. }
  147. }
  148. }
  149. }
  150. // Close cleans up the vhost networking device within the kernel and releases
  151. // all resources used for it.
  152. // The implementation will try to release as many resources as possible and
  153. // collect potential errors before returning them.
  154. func (dev *Device) Close() error {
  155. // Closing the control file descriptor will unregister all queues from the
  156. // kernel.
  157. if dev.controlFD >= 0 {
  158. if err := unix.Close(dev.controlFD); err != nil {
  159. // Return an error and do not continue, because the memory used for
  160. // the queues should not be released before they were unregistered
  161. // from the kernel.
  162. return fmt.Errorf("close control file descriptor: %w", err)
  163. }
  164. dev.controlFD = -1
  165. }
  166. var errs []error
  167. if dev.ReceiveQueue != nil {
  168. if err := dev.ReceiveQueue.Close(); err == nil {
  169. dev.ReceiveQueue = nil
  170. } else {
  171. errs = append(errs, fmt.Errorf("close receive queue: %w", err))
  172. }
  173. }
  174. if dev.TransmitQueue != nil {
  175. if err := dev.TransmitQueue.Close(); err == nil {
  176. dev.TransmitQueue = nil
  177. } else {
  178. errs = append(errs, fmt.Errorf("close transmit queue: %w", err))
  179. }
  180. }
  181. if len(errs) == 0 {
  182. // Everything was cleaned up. No need to run the finalizer anymore.
  183. runtime.SetFinalizer(dev, nil)
  184. }
  185. return errors.Join(errs...)
  186. }
  187. // createQueue creates a new virtqueue and registers it with the vhost device
  188. // using the given index.
  189. func createQueue(controlFD int, queueIndex int, queueSize int, itemSize int) (*virtqueue.SplitQueue, error) {
  190. queue, err := virtqueue.NewSplitQueue(queueSize, itemSize)
  191. if err != nil {
  192. return nil, fmt.Errorf("create virtqueue: %w", err)
  193. }
  194. if err = vhost.RegisterQueue(controlFD, uint32(queueIndex), queue); err != nil {
  195. return nil, fmt.Errorf("register virtqueue with index %d: %w", queueIndex, err)
  196. }
  197. return queue, nil
  198. }
  199. func (dev *Device) GetPacketForTx() (uint16, []byte, error) {
  200. idx, err := dev.TransmitQueue.TakeSingleIndex(context.TODO())
  201. if err != nil {
  202. return 0, nil, fmt.Errorf("transmit queue: %w", err)
  203. }
  204. buf := dev.TransmitQueue.GetDescriptorItem(idx)
  205. return idx, buf, nil
  206. }
  207. func (dev *Device) TransmitPacket(pkt *packet.OutPacket, kick bool) error {
  208. if len(pkt.SegmentIDs) == 0 {
  209. return nil
  210. }
  211. for idx := range pkt.SegmentIDs {
  212. segmentID := pkt.SegmentIDs[idx]
  213. dev.TransmitQueue.SetDescSize(segmentID, len(pkt.Segments[idx]))
  214. }
  215. err := dev.TransmitQueue.OfferDescriptorChains(pkt.SegmentIDs, false)
  216. if err != nil {
  217. return fmt.Errorf("offer descriptor chains: %w", err)
  218. }
  219. pkt.Reset()
  220. if kick {
  221. return dev.TransmitQueue.Kick()
  222. }
  223. return nil
  224. }
  225. func (dev *Device) TransmitPackets(pkts []*packet.OutPacket) error {
  226. if len(pkts) == 0 {
  227. return nil
  228. }
  229. for i := range pkts {
  230. if err := dev.TransmitPacket(pkts[i], false); err != nil {
  231. return err
  232. }
  233. }
  234. if err := dev.TransmitQueue.Kick(); err != nil {
  235. return err
  236. }
  237. return nil
  238. }
  239. // ProcessRxChain processes a single chain to create one packet. The number of processed chains is returned.
  240. func (dev *Device) ProcessRxChain(pkt *VirtIOPacket, chain virtqueue.UsedElement) (int, error) {
  241. //read first element to see how many descriptors we need:
  242. pkt.Chain = uint16(chain.DescriptorIndex)
  243. buf := dev.ReceiveQueue.GetDescriptorItem(pkt.Chain)
  244. // The specification requires that the first descriptor chain starts
  245. // with a virtio-net header. It is not clear, whether it is also
  246. // required to be fully contained in the first buffer of that
  247. // descriptor chain, but it is reasonable to assume that this is
  248. // always the case.
  249. // The decode method already does the buffer length check.
  250. //HACK: we only want the last bit of the header, the NumBuffers field. So, let's grab just that:
  251. //numBuffers := binary.BigEndian.Uint16(buf[virtio.NetHdrSize-3:])
  252. //even bigger hack: apparently this is hitting some kind of memory access pitfall? Let's only grab the last byte:
  253. //numBuffers := buf[virtio.NetHdrSize-2]
  254. //if err = pkt.header.Decode(buf); err != nil {
  255. // // The device misbehaved. There is no way we can gracefully
  256. // // recover from this, because we don't know how many of the
  257. // // following descriptor chains belong to this packet.
  258. // return 0, fmt.Errorf("decode vnethdr: %w", err)
  259. //}
  260. //we have the header now: what do we need to do?
  261. //todo we're ignoring the header lol
  262. //if int(numBuffers) != 1 {
  263. // return 0, fmt.Errorf("too smol-brain to handle more than one buffer per Chain item right now: %d chains, %d bufs", 1, int(numBuffers))
  264. //}
  265. if chain.Length > 16000 {
  266. //todo!
  267. pkt.payload = nil
  268. return 1, fmt.Errorf("too big packet length: %d", chain.Length)
  269. }
  270. //shift the buffer out of out:
  271. pkt.payload = buf[virtio.NetHdrSize:chain.Length]
  272. return 1, nil
  273. }
  274. type VirtIOPacket struct {
  275. payload []byte
  276. //header virtio.NetHdr
  277. Chain uint16
  278. }
  279. func NewVIO() *VirtIOPacket {
  280. out := new(VirtIOPacket)
  281. out.payload = nil
  282. out.Chain = 0
  283. return out
  284. }
  285. func (v *VirtIOPacket) Reset() {
  286. v.payload = nil
  287. v.Chain = 0
  288. }
  289. func (v *VirtIOPacket) GetPayload() []byte {
  290. return v.payload //todo this could be dev.ReceiveQueue.GetDescriptorItem(idx)
  291. }
  292. func (v *VirtIOPacket) SetPayload(x []byte) {
  293. v.payload = x
  294. }