View Javadoc
1   /*
2    * Copyright 2024 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.channel.uring;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.channel.AddressedEnvelope;
20  import io.netty.channel.ChannelException;
21  import io.netty.channel.ChannelFuture;
22  import io.netty.channel.ChannelMetadata;
23  import io.netty.channel.ChannelOutboundBuffer;
24  import io.netty.channel.ChannelPipeline;
25  import io.netty.channel.ChannelPromise;
26  import io.netty.channel.DefaultAddressedEnvelope;
27  import io.netty.channel.IoRegistration;
28  import io.netty.channel.socket.DatagramChannel;
29  import io.netty.channel.socket.DatagramChannelConfig;
30  import io.netty.channel.socket.DatagramPacket;
31  import io.netty.channel.socket.SocketProtocolFamily;
32  import io.netty.channel.unix.Errors;
33  import io.netty.channel.unix.Errors.NativeIoException;
34  import io.netty.channel.unix.SegmentedDatagramPacket;
35  import io.netty.channel.unix.Socket;
36  import io.netty.util.ReferenceCountUtil;
37  import io.netty.util.ReferenceCounted;
38  import io.netty.util.UncheckedBooleanSupplier;
39  import io.netty.util.internal.ObjectUtil;
40  import io.netty.util.internal.StringUtil;
41  import io.netty.util.internal.SystemPropertyUtil;
42  import io.netty.util.internal.logging.InternalLogger;
43  import io.netty.util.internal.logging.InternalLoggerFactory;
44  
45  import java.io.IOException;
46  import java.net.Inet4Address;
47  import java.net.InetAddress;
48  import java.net.InetSocketAddress;
49  import java.net.NetworkInterface;
50  import java.net.PortUnreachableException;
51  import java.net.SocketAddress;
52  import java.net.SocketException;
53  import java.nio.channels.UnresolvedAddressException;
54  import java.util.ArrayList;
55  import java.util.List;
56  
57  import static io.netty.channel.unix.Errors.ioResult;
58  
59  public final class IoUringDatagramChannel extends AbstractIoUringChannel implements DatagramChannel {
60      private static final InternalLogger logger = InternalLoggerFactory.getInstance(IoUringDatagramChannel.class);
61      private static final boolean IP_MULTICAST_ALL =
62              SystemPropertyUtil.getBoolean("io.netty.channel.iouring.ipMulticastAll", false);
63      private static final ChannelMetadata METADATA = new ChannelMetadata(true, 16);
64      private static final String EXPECTED_TYPES =
65              " (expected: " + StringUtil.simpleClassName(DatagramPacket.class) + ", " +
66              StringUtil.simpleClassName(AddressedEnvelope.class) + '<' +
67              StringUtil.simpleClassName(ByteBuf.class) + ", " +
68              StringUtil.simpleClassName(InetSocketAddress.class) + ">, " +
69              StringUtil.simpleClassName(ByteBuf.class) + ')';
70  
71      private final IoUringDatagramChannelConfig config;
72      private volatile boolean connected;
73  
74      static {
75          if (logger.isDebugEnabled()) {
76              logger.debug("-Dio.netty.channel.iouring.ipMulticastAll: {}", IP_MULTICAST_ALL);
77          }
78      }
79  
80      // These buffers are used for msghdr, iov, sockaddr_in / sockaddr_in6 when doing recvmsg / sendmsg
81      //
82      // TODO: Alternative we could also allocate these everytime from the ByteBufAllocator or we could use
83      //       some sort of other pool. Let's keep it simple for now.
84      //
85      // Consider exposing some configuration for that.
86      private final MsgHdrMemoryArray recvmsgHdrs = new MsgHdrMemoryArray((short) 256);
87      private final MsgHdrMemoryArray sendmsgHdrs = new MsgHdrMemoryArray((short) 256);
88      private final int[] sendmsgResArray = new int[sendmsgHdrs.capacity()];
89  
90      /**
91       * Create a new instance which selects the {@link SocketProtocolFamily} to use depending
92       * on the Operation Systems default which will be chosen.
93       */
94      public IoUringDatagramChannel() {
95          this(null);
96      }
97  
98      /**
99       * Create a new instance using the given {@link SocketProtocolFamily}. If {@code null} is used it will depend
100      * on the Operation Systems default which will be chosen.
101      */
102     public IoUringDatagramChannel(SocketProtocolFamily family) {
103         this(LinuxSocket.newSocketDgram(useIpv6(family)), false);
104     }
105 
106     private static boolean useIpv6(SocketProtocolFamily family) {
107         if (family == null) {
108             return Socket.isIPv6Preferred();
109         }
110         return family == SocketProtocolFamily.INET6;
111     }
112 
113     /**
114      * Create a new instance which selects the {@link SocketProtocolFamily} to use depending
115      * on the Operation Systems default which will be chosen.
116      */
117     public IoUringDatagramChannel(int fd) {
118         this(new LinuxSocket(fd), true);
119     }
120 
121     private IoUringDatagramChannel(LinuxSocket fd, boolean active) {
122         // Always use a blocking fd and so make use of fast-poll.
123         super(null, fd, active);
124 
125         // Configure IP_MULTICAST_ALL - disable by default to match the behaviour of NIO.
126         try {
127             fd.setIpMulticastAll(IP_MULTICAST_ALL);
128         } catch (IOException | ChannelException e) {
129             logger.debug("Failed to set IP_MULTICAST_ALL to {}", IP_MULTICAST_ALL, e);
130         }
131 
132         config = new IoUringDatagramChannelConfig(this);
133     }
134 
135     @Override
136     protected boolean isStreamSocket() {
137         return false;
138     }
139 
140     @Override
141     public InetSocketAddress remoteAddress() {
142         return (InetSocketAddress) super.remoteAddress();
143     }
144 
145     @Override
146     public InetSocketAddress localAddress() {
147         return (InetSocketAddress) super.localAddress();
148     }
149 
150     @Override
151     public ChannelMetadata metadata() {
152         return METADATA;
153     }
154 
155     @Override
156     public boolean isActive() {
157         return socket.isOpen() && (config.getActiveOnOpen() && isRegistered() || super.isActive());
158     }
159 
160     @Override
161     public boolean isConnected() {
162         return connected;
163     }
164 
165     @Override
166     public ChannelFuture joinGroup(InetAddress multicastAddress) {
167         return joinGroup(multicastAddress, newPromise());
168     }
169 
170     @Override
171     public ChannelFuture joinGroup(InetAddress multicastAddress, ChannelPromise promise) {
172         try {
173             return joinGroup(multicastAddress, defaultMulticastInterface(), null, promise);
174         } catch (IOException e) {
175             promise.setFailure(e);
176         }
177         return promise;
178     }
179 
180     /**
181      * Returns the interface to use when none was given: the one set via
182      * {@link io.netty.channel.ChannelOption#IP_MULTICAST_IF} or, if unset, the interface of the local address.
183      */
184     private NetworkInterface defaultMulticastInterface() throws SocketException {
185         NetworkInterface iface = config().getNetworkInterface();
186         if (iface == null) {
187             iface = NetworkInterface.getByInetAddress(localAddress().getAddress());
188         }
189         return iface;
190     }
191 
192     @Override
193     public ChannelFuture joinGroup(
194             InetSocketAddress multicastAddress, NetworkInterface networkInterface) {
195         return joinGroup(multicastAddress, networkInterface, newPromise());
196     }
197 
198     @Override
199     public ChannelFuture joinGroup(
200             InetSocketAddress multicastAddress, NetworkInterface networkInterface,
201             ChannelPromise promise) {
202         return joinGroup(multicastAddress.getAddress(), networkInterface, null, promise);
203     }
204 
205     @Override
206     public ChannelFuture joinGroup(
207             InetAddress multicastAddress, NetworkInterface networkInterface, InetAddress source) {
208         return joinGroup(multicastAddress, networkInterface, source, newPromise());
209     }
210 
211     @Override
212     public ChannelFuture joinGroup(
213             final InetAddress multicastAddress, final NetworkInterface networkInterface,
214             final InetAddress source, final ChannelPromise promise) {
215 
216         ObjectUtil.checkNotNull(multicastAddress, "multicastAddress");
217         ObjectUtil.checkNotNull(networkInterface, "networkInterface");
218 
219         try {
220             socket.joinGroup(multicastAddress, networkInterface, source);
221             promise.setSuccess();
222         } catch (IOException e) {
223             promise.setFailure(e);
224         }
225         return promise;
226     }
227 
228     @Override
229     public ChannelFuture leaveGroup(InetAddress multicastAddress) {
230         return leaveGroup(multicastAddress, newPromise());
231     }
232 
233     @Override
234     public ChannelFuture leaveGroup(InetAddress multicastAddress, ChannelPromise promise) {
235         try {
236             return leaveGroup(multicastAddress, defaultMulticastInterface(), null, promise);
237         } catch (IOException e) {
238             promise.setFailure(e);
239         }
240         return promise;
241     }
242 
243     @Override
244     public ChannelFuture leaveGroup(
245             InetSocketAddress multicastAddress, NetworkInterface networkInterface) {
246         return leaveGroup(multicastAddress, networkInterface, newPromise());
247     }
248 
249     @Override
250     public ChannelFuture leaveGroup(
251             InetSocketAddress multicastAddress,
252             NetworkInterface networkInterface, ChannelPromise promise) {
253         return leaveGroup(multicastAddress.getAddress(), networkInterface, null, promise);
254     }
255 
256     @Override
257     public ChannelFuture leaveGroup(
258             InetAddress multicastAddress, NetworkInterface networkInterface, InetAddress source) {
259         return leaveGroup(multicastAddress, networkInterface, source, newPromise());
260     }
261 
262     @Override
263     public ChannelFuture leaveGroup(
264             final InetAddress multicastAddress, final NetworkInterface networkInterface, final InetAddress source,
265             final ChannelPromise promise) {
266         ObjectUtil.checkNotNull(multicastAddress, "multicastAddress");
267         ObjectUtil.checkNotNull(networkInterface, "networkInterface");
268 
269         try {
270             socket.leaveGroup(multicastAddress, networkInterface, source);
271             promise.setSuccess();
272         } catch (IOException e) {
273             promise.setFailure(e);
274         }
275         return promise;
276     }
277 
278     @Override
279     public ChannelFuture block(
280             InetAddress multicastAddress, NetworkInterface networkInterface,
281             InetAddress sourceToBlock) {
282         return block(multicastAddress, networkInterface, sourceToBlock, newPromise());
283     }
284 
285     @Override
286     public ChannelFuture block(
287             final InetAddress multicastAddress, final NetworkInterface networkInterface,
288             final InetAddress sourceToBlock, final ChannelPromise promise) {
289         ObjectUtil.checkNotNull(multicastAddress, "multicastAddress");
290         ObjectUtil.checkNotNull(sourceToBlock, "sourceToBlock");
291         ObjectUtil.checkNotNull(networkInterface, "networkInterface");
292 
293         promise.setFailure(new UnsupportedOperationException("Multicast not supported"));
294         return promise;
295     }
296 
297     @Override
298     public ChannelFuture block(InetAddress multicastAddress, InetAddress sourceToBlock) {
299         return block(multicastAddress, sourceToBlock, newPromise());
300     }
301 
302     @Override
303     public ChannelFuture block(
304             InetAddress multicastAddress, InetAddress sourceToBlock, ChannelPromise promise) {
305         try {
306             return block(multicastAddress, defaultMulticastInterface(), sourceToBlock, promise);
307         } catch (Throwable e) {
308             promise.setFailure(e);
309         }
310         return promise;
311     }
312 
313     @Override
314     protected AbstractUnsafe newUnsafe() {
315         return new IoUringDatagramChannelUnsafe();
316     }
317 
318     @Override
319     protected void doBind(SocketAddress localAddress) throws Exception {
320         if (localAddress instanceof InetSocketAddress) {
321             InetSocketAddress socketAddress = (InetSocketAddress) localAddress;
322             if (socketAddress.getAddress().isAnyLocalAddress() &&
323                     socketAddress.getAddress() instanceof Inet4Address) {
324                 if (socket.family() == SocketProtocolFamily.INET6) {
325                     localAddress = new InetSocketAddress(LinuxSocket.INET6_ANY, socketAddress.getPort());
326                 }
327             }
328         }
329         super.doBind(localAddress);
330         active = true;
331     }
332 
333     private static void checkUnresolved(AddressedEnvelope<?, ?> envelope) {
334         if (envelope.recipient() instanceof InetSocketAddress
335                 && (((InetSocketAddress) envelope.recipient()).isUnresolved())) {
336             throw new UnresolvedAddressException();
337         }
338     }
339 
340     @Override
341     protected Object filterOutboundMessage(Object msg) {
342         if (msg instanceof DatagramPacket) {
343             DatagramPacket packet = (DatagramPacket) msg;
344             checkUnresolved(packet);
345             ByteBuf content = packet.content();
346             return !content.hasMemoryAddress() ?
347                     packet.replace(newDirectBuffer(packet, content)) : msg;
348         }
349 
350         if (msg instanceof ByteBuf) {
351             ByteBuf buf = (ByteBuf) msg;
352             return !buf.hasMemoryAddress()? newDirectBuffer(buf) : buf;
353         }
354 
355         if (msg instanceof AddressedEnvelope) {
356             @SuppressWarnings("unchecked")
357             AddressedEnvelope<Object, SocketAddress> e = (AddressedEnvelope<Object, SocketAddress>) msg;
358             checkUnresolved(e);
359             if (e.content() instanceof ByteBuf &&
360                 (e.recipient() == null || e.recipient() instanceof InetSocketAddress)) {
361 
362                 ByteBuf content = (ByteBuf) e.content();
363                 return !content.hasMemoryAddress()?
364                         new DefaultAddressedEnvelope<>(
365                             newDirectBuffer(e, content), (InetSocketAddress) e.recipient()) : e;
366             }
367         }
368 
369         throw new UnsupportedOperationException(
370                 "unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES);
371     }
372 
373     @Override
374     public DatagramChannelConfig config() {
375         return config;
376     }
377 
378     @Override
379     protected void doDisconnect() throws Exception {
380         // TODO: use io_uring for this too...
381         socket.disconnect();
382         connected = active = false;
383 
384         resetCachedAddresses();
385     }
386 
387     @Override
388     protected void doClose() throws Exception {
389         super.doClose();
390         connected = false;
391     }
392 
393     private final class IoUringDatagramChannelUnsafe extends AbstractUringUnsafe {
394         private final WriteProcessor writeProcessor = new WriteProcessor();
395 
396         private ByteBuf readBuffer;
397 
398         private final class WriteProcessor implements ChannelOutboundBuffer.MessageProcessor {
399             private int written;
400             @Override
401             public boolean processMessage(Object msg) {
402                 if (scheduleWrite(msg, written == 0)) {
403                     written++;
404                     return true;
405                 }
406                 return false;
407             }
408 
409             int write(ChannelOutboundBuffer in) {
410                 written = 0;
411                 try {
412                     in.forEachFlushedMessage(this);
413                 } catch (Exception e) {
414                     // This should never happen as our processMessage(...) never throws.
415                     throw new IllegalStateException(e);
416                 }
417                 return written;
418             }
419         }
420 
421         @Override
422         protected void readComplete0(byte op, int res, int flags, short data, int outstanding) {
423             assert outstanding != -1 : "multi-shot not implemented yet";
424 
425             final IoUringRecvByteAllocatorHandle allocHandle = recvBufAllocHandle();
426             final ChannelPipeline pipeline = pipeline();
427             ByteBuf byteBuf = this.readBuffer;
428             assert byteBuf != null;
429             MsgHdrMemory hdr = recvmsgHdrs.hdr(data);
430             // Reset the id as this read was completed and so don't need to be cancelled later.
431             recvmsgHdrs.setId(data, MsgHdrMemoryArray.NO_ID);
432 
433             try {
434                 if (res < 0) {
435                     if (res != Native.ERRNO_ECANCELED_NEGATIVE) {
436                         // If res is negative we should pass it to ioResult(...) which will either throw
437                         // or convert it to 0 if we could not read because the socket was not readable.
438                         allocHandle.lastBytesRead(ioResult("io_uring recvmsg", res));
439                     }
440                 } else {
441                     allocHandle.lastBytesRead(res);
442                     if (hdr.hasPort(IoUringDatagramChannel.this)) {
443                         allocHandle.incMessagesRead(1);
444                         DatagramPacket packet = hdr.get(
445                                 IoUringDatagramChannel.this, registration().attachment(), byteBuf, res);
446                         pipeline.fireChannelRead(packet);
447                     }
448                 }
449             } catch (Throwable t) {
450                 Throwable e = (connected && t instanceof NativeIoException) ?
451                         translateForConnected((NativeIoException) t) : t;
452                 pipeline.fireExceptionCaught(e);
453             }
454 
455             if (outstanding == 0) {
456                 // There are no outstanding completion events, release the readBuffer and see if we need to schedule
457                 // another one or if the user will do it.
458                 this.readBuffer.release();
459                 this.readBuffer = null;
460                 recvmsgHdrs.clear();
461 
462                 if (res != Native.ERRNO_ECANCELED_NEGATIVE) {
463                     if (allocHandle.lastBytesRead() > 0 &&
464                             allocHandle.continueReading(UncheckedBooleanSupplier.TRUE_SUPPLIER) &&
465                             // If IORING_CQE_F_SOCK_NONEMPTY is supported we should check for it first before
466                             // trying to schedule a read. If it's supported and not part of the flags we know for sure
467                             // that the next read (which would be using Native.MSG_DONTWAIT) will complete without
468                             // be able to read any data. This is useless work and we can skip it.
469                             (!IoUring.isCqeFSockNonEmptySupported() ||
470                                     (flags & Native.IORING_CQE_F_SOCK_NONEMPTY) != 0)) {
471                         // Let's schedule another read.
472                         scheduleRead(false);
473                     } else {
474                         // the read was completed with EAGAIN.
475                         allocHandle.readComplete();
476                         pipeline.fireChannelReadComplete();
477                     }
478                 }
479             }
480         }
481 
482         @Override
483         protected int scheduleRead0(boolean first, boolean socketIsEmpty) {
484             final IoUringRecvByteAllocatorHandle allocHandle = recvBufAllocHandle();
485             IoUringIoHandler ioUringIoHandler = registration().attachment();
486             int submissionQueueRemaining = ioUringIoHandler.submitIfFullAndGetRemaining();
487             ByteBuf byteBuf = allocHandle.allocate(alloc());
488             assert readBuffer == null;
489             readBuffer = byteBuf;
490 
491             int writable = byteBuf.writableBytes();
492             allocHandle.attemptedBytesRead(writable);
493             int datagramSize = ((IoUringDatagramChannelConfig) config()).getMaxDatagramPayloadSize();
494 
495             int numDatagram = datagramSize == 0 ? 1 : Math.max(1, byteBuf.writableBytes() / datagramSize);
496             numDatagram = Math.min(Math.min(submissionQueueRemaining, recvmsgHdrs.capacity()), numDatagram);
497             int scheduled = scheduleRecvmsg(byteBuf, numDatagram, datagramSize);
498             if (scheduled == 0) {
499                 // We could not schedule any recvmmsg so we need to release the buffer as there will be no
500                 // completion event.
501                 readBuffer = null;
502                 byteBuf.release();
503             }
504             return scheduled;
505         }
506 
507         private int scheduleRecvmsg(ByteBuf byteBuf, int numDatagram, int datagramSize) {
508             int writable = byteBuf.writableBytes();
509             long bufferAddress = IoUring.memoryAddress(byteBuf) + byteBuf.writerIndex();
510             if (numDatagram <= 1) {
511                 return scheduleRecvmsg0(bufferAddress, writable, true, false) ? 1 : 0;
512             }
513             int i = 0;
514             // Add multiple IORING_OP_RECVMSG to the submission queue. This basically emulates recvmmsg(...)
515             for (; i < numDatagram && writable >= datagramSize; i++) {
516                 if (!scheduleRecvmsg0(bufferAddress, datagramSize, i == 0, i + 1 < numDatagram)) {
517                     break;
518                 }
519                 bufferAddress += datagramSize;
520                 writable -= datagramSize;
521             }
522             return i;
523         }
524 
525         private boolean scheduleRecvmsg0(long bufferAddress, int bufferLength, boolean first, boolean more) {
526             MsgHdrMemory msgHdrMemory = recvmsgHdrs.nextHdr();
527             if (msgHdrMemory == null) {
528                 // We can not continue reading before we did not submit the recvmsg(s) and received the results.
529                 return false;
530             }
531             msgHdrMemory.set(socket, null, bufferAddress, bufferLength, (short) 0);
532 
533             int fd = fd().intValue();
534             int msgFlags = first ? 0 : Native.MSG_DONTWAIT;
535             int sqeFlags = more ? Native.IOSQE_HARDLINK : 0;
536             IoRegistration registration = registration();
537             // We always use idx here so we can detect if no idx was used by checking if data < 0 in
538             // readComplete0(...)
539             IoUringIoOps ops = IoUringIoOps.newRecvmsg(
540                     fd, (byte) sqeFlags, msgFlags, msgHdrMemory.address(), msgHdrMemory.idx());
541             long id = registration.submit(ops);
542             if (id == 0) {
543                 // Submission failed we don't used the MsgHdrMemory and so should give it back.
544                 recvmsgHdrs.restoreNextHdr(msgHdrMemory);
545                 return false;
546             }
547             recvmsgHdrs.setId(msgHdrMemory.idx(), id);
548             return true;
549         }
550 
551         @Override
552         boolean writeComplete0(byte op, int res, int flags, long data, int outstanding) {
553             // data is the MsgHdrMemoryArray index that scheduleSendmsg(...) submitted, so it is always within
554             // [0, sendmsgHdrs.capacity()) (256) and never uses the overflow id range. Narrowing to int is safe.
555             assert data >= 0 && data < sendmsgHdrs.capacity();
556             int idx = (int) data;
557             // Reset the id as this write was completed and so don't need to be cancelled later.
558             sendmsgHdrs.setId(idx, MsgHdrMemoryArray.NO_ID);
559             sendmsgResArray[idx] = res;
560             // Store the result so we can handle it as soon as we have no outstanding writes anymore.
561             if (outstanding == 0) {
562                 // All writes are done as part of a batch. Let's remove these from the ChannelOutboundBuffer
563                 boolean writtenSomething = false;
564                 int numWritten = sendmsgHdrs.length();
565                 sendmsgHdrs.clear();
566                 ChannelOutboundBuffer outboundBuffer = outboundBuffer();
567                 if (retainedWriteBuffers != null) {
568                     // Close, shutdownOutput() or an inactive flush may already have failed this batch.
569                     return true;
570                 }
571                 for (int i = 0; i < numWritten; i++) {
572                     writtenSomething |= removeFromOutboundBuffer(
573                             outboundBuffer, sendmsgResArray[i], "io_uring sendmsg");
574                 }
575                 return writtenSomething;
576             }
577             return true;
578         }
579 
580         private boolean removeFromOutboundBuffer(ChannelOutboundBuffer outboundBuffer, int res, String errormsg) {
581             if (res >= 0) {
582                 // When using Datagram we should consider the message written as long as res is not negative.
583                 return outboundBuffer.remove();
584             }
585             if (res == Native.ERRNO_ECANCELED_NEGATIVE) {
586                 return false;
587             }
588             try {
589                 return ioResult(errormsg, res) != 0;
590             } catch (Throwable cause) {
591                 Throwable e = (connected && cause instanceof NativeIoException) ?
592                         translateForConnected((NativeIoException) cause) : cause;
593                 return outboundBuffer.remove(e);
594             }
595         }
596 
597         @Override
598         void connectComplete(byte op, int res, int flags, long data) {
599             if (res >= 0) {
600                 connected = true;
601             }
602             super.connectComplete(op, res, flags, data);
603         }
604 
605         @Override
606         protected int scheduleWriteMultiple(ChannelOutboundBuffer in) {
607             return writeProcessor.write(in);
608         }
609 
610         @Override
611         protected int scheduleWriteSingle(Object msg) {
612             return scheduleWrite(msg, true) ? 1 : 0;
613         }
614 
615         private boolean scheduleWrite(Object msg, boolean first) {
616             final ByteBuf data;
617             final InetSocketAddress remoteAddress;
618             final int segmentSize;
619             if (msg instanceof AddressedEnvelope) {
620                 @SuppressWarnings("unchecked")
621                 AddressedEnvelope<ByteBuf, InetSocketAddress> envelope =
622                         (AddressedEnvelope<ByteBuf, InetSocketAddress>) msg;
623                 data = envelope.content();
624                 remoteAddress = envelope.recipient();
625                 if (msg instanceof SegmentedDatagramPacket) {
626                     segmentSize = ((SegmentedDatagramPacket) msg).segmentSize();
627                 } else {
628                     segmentSize = 0;
629                 }
630             } else {
631                 data = (ByteBuf) msg;
632                 remoteAddress = (InetSocketAddress) remoteAddress();
633                 segmentSize = 0;
634             }
635 
636             long bufferAddress = IoUring.memoryAddress(data) + data.readerIndex();
637             return scheduleSendmsg(remoteAddress, bufferAddress, data.readableBytes(), segmentSize, first);
638         }
639 
640         private boolean scheduleSendmsg(InetSocketAddress remoteAddress, long bufferAddress,
641                                         int bufferLength, int segmentSize, boolean first) {
642             MsgHdrMemory hdr = sendmsgHdrs.nextHdr();
643             if (hdr == null) {
644                 // There is no MsgHdrMemory left to use. We need to submit and wait for the writes to complete
645                 // before we can write again.
646                 return false;
647             }
648             long id = 0;
649             try {
650                 hdr.set(socket, remoteAddress, bufferAddress, bufferLength, (short) segmentSize);
651                 int msgFlags = first ? 0 : Native.MSG_DONTWAIT;
652                 IoUringIoOps ops = IoUringIoOps.newSendmsg(
653                         fd().intValue(), (byte) 0, msgFlags, hdr.address(), hdr.idx());
654                 id = submitWrite(ops);
655                 if (id == 0) {
656                     return false;
657                 }
658                 sendmsgHdrs.setId(hdr.idx(), id);
659                 return true;
660             } finally {
661                 if (id == 0) {
662                     sendmsgHdrs.restoreNextHdr(hdr);
663                 }
664             }
665         }
666 
667         @Override
668         List<ReferenceCounted> retainWriteBuffers(ChannelOutboundBuffer buffer) {
669             final int count = (int) currentWrite.userData() + 1;
670             final List<ReferenceCounted> buffers = new ArrayList<>(count);
671             try {
672                 buffer.forEachFlushedMessage(new ChannelOutboundBuffer.MessageProcessor() {
673                     @Override
674                     public boolean processMessage(Object msg) {
675                         int index = buffers.size();
676                         buffers.add(sendmsgHdrs.id(index) != MsgHdrMemoryArray.NO_ID ?
677                                 ((ReferenceCounted) msg).retain() : null);
678                         return buffers.size() < count;
679                     }
680                 });
681             } catch (Exception e) {
682                 throw new IllegalStateException(e);
683             }
684             return buffers;
685         }
686 
687         @Override
688         void releaseWriteBuffers(List<ReferenceCounted> buffers, long data) {
689             // Batched SENDMSG requests may complete out of order; clear and release only the slot indexed by data.
690             ReferenceCountUtil.safeRelease(buffers.set((int) data, null));
691         }
692 
693         @Override
694         public void unregistered() {
695             super.unregistered();
696             sendmsgHdrs.release();
697             recvmsgHdrs.release();
698             assert readBuffer == null;
699         }
700     }
701 
702     private static IOException translateForConnected(NativeIoException e) {
703         // We need to correctly translate connect errors to match NIO behaviour.
704         if (e.expectedErr() == Errors.ERROR_ECONNREFUSED_NEGATIVE) {
705             PortUnreachableException error = new PortUnreachableException(e.getMessage());
706             error.initCause(e);
707             return error;
708         }
709         return e;
710     }
711 
712     /**
713      * Returns {@code true} if the usage of {@link io.netty.channel.unix.SegmentedDatagramPacket} is supported.
714      *
715      * @return {@code true} if supported, {@code false} otherwise.
716      */
717     public static boolean isSegmentedDatagramPacketSupported() {
718         return IoUring.isAvailable();
719     }
720 
721     @Override
722     protected void cancelOutstandingReads(IoRegistration registration, int numOutstandingReads) {
723         if (numOutstandingReads > 0) {
724             int canceled = cancel(registration, Native.IORING_OP_RECVMSG, recvmsgHdrs);
725             assert canceled == numOutstandingReads;
726         }
727     }
728 
729     @Override
730     protected void cancelOutstandingWrites(IoRegistration registration, int numOutstandingWrites) {
731         if (numOutstandingWrites > 0) {
732             int canceled = cancel(registration, Native.IORING_OP_SENDMSG, sendmsgHdrs);
733             assert canceled == numOutstandingWrites;
734         }
735     }
736 
737     private int cancel(IoRegistration registration, byte op, MsgHdrMemoryArray array) {
738         int cancelled = 0;
739         for (int idx = 0; idx < array.length(); idx++) {
740             long id = array.id(idx);
741             if (id == MsgHdrMemoryArray.NO_ID) {
742                 continue;
743             }
744             // Let's try to cancel outstanding op as these might be submitted and waiting for data
745             // (via fastpoll).
746             IoUringIoOps ops = IoUringIoOps.newAsyncCancel((byte) 0, id, op);
747             registration.submit(ops);
748             cancelled++;
749         }
750         return cancelled;
751     }
752 
753     @Override
754     protected boolean socketIsEmpty(int flags) {
755         return IoUring.isCqeFSockNonEmptySupported() && (flags & Native.IORING_CQE_F_SOCK_NONEMPTY) == 0;
756     }
757 
758     @Override
759     boolean isPollInFirst() {
760         return false;
761     }
762 }