View Javadoc
1   /*
2    * Copyright 2014 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.epoll;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.channel.ChannelOutboundBuffer;
20  import io.netty.channel.ChannelOutboundBuffer.MessageProcessor;
21  import io.netty.channel.socket.DatagramPacket;
22  import io.netty.channel.unix.IovArray;
23  import io.netty.channel.unix.Limits;
24  import io.netty.channel.unix.SegmentedDatagramPacket;
25  import io.netty.util.internal.UnstableApi;
26  
27  import java.net.Inet6Address;
28  import java.net.InetAddress;
29  import java.net.InetSocketAddress;
30  import java.net.UnknownHostException;
31  
32  import static io.netty.channel.unix.Limits.UIO_MAX_IOV;
33  import static io.netty.channel.unix.NativeInetAddress.copyIpv4MappedIpv6Address;
34  
35  /**
36   * Support <a href="https://linux.die.net//man/2/sendmmsg">sendmmsg(...)</a> on linux with GLIBC 2.14+
37   */
38  final class NativeDatagramPacketArray {
39  
40      // Use UIO_MAX_IOV as this is the maximum number we can write with one sendmmsg(...) call.
41      private final NativeDatagramPacket[] packets = new NativeDatagramPacket[UIO_MAX_IOV];
42  
43      // We share one IovArray for all NativeDatagramPackets to reduce memory overhead. This will allow us to write
44      // up to IOV_MAX iovec across all messages in one sendmmsg(...) call.
45      private final IovArray iovArray = new IovArray();
46  
47      // temporary array to copy the ipv4 part of ipv6-mapped-ipv4 addresses and then create a Inet4Address out of it.
48      private final byte[] ipv4Bytes = new byte[4];
49      private final MyMessageProcessor processor = new MyMessageProcessor();
50  
51      private int count;
52  
53      NativeDatagramPacketArray() {
54          for (int i = 0; i < packets.length; i++) {
55              packets[i] = new NativeDatagramPacket();
56          }
57      }
58  
59      boolean addWritable(ByteBuf buf, int index, int len) {
60          return add0(buf, index, len, 0, null);
61      }
62  
63      private boolean add0(ByteBuf buf, int index, int len, int segmentLen, InetSocketAddress recipient) {
64          if (count == packets.length) {
65              // We already filled up to UIO_MAX_IOV messages. This is the max allowed per
66              // recvmmsg(...) / sendmmsg(...) call, we will try again later.
67              return false;
68          }
69          // A zero-length datagram is valid and gets a packet with no iovecs: skipping it here would make
70          // EpollDatagramChannel.doWrite(...) remove the wrong messages for the packets sendmmsg reports as sent.
71          int offset = iovArray.count();
72          if (offset == Limits.IOV_MAX || !iovArray.add(buf, index, len)) {
73              // Not enough space to hold the whole content, we will try again later.
74              return false;
75          }
76          NativeDatagramPacket p = packets[count];
77          p.init(iovArray.memoryAddress(offset), iovArray.count() - offset, segmentLen, recipient);
78  
79          count++;
80          return true;
81      }
82  
83      void add(ChannelOutboundBuffer buffer, boolean connected, int maxMessagesPerWrite) throws Exception {
84          processor.connected = connected;
85          processor.maxMessagesPerWrite = maxMessagesPerWrite;
86          buffer.forEachFlushedMessage(processor);
87      }
88  
89      /**
90       * Returns the count
91       */
92      int count() {
93          return count;
94      }
95  
96      /**
97       * Returns an array with {@link #count()} {@link NativeDatagramPacket}s filled.
98       */
99      NativeDatagramPacket[] packets() {
100         return packets;
101     }
102 
103     void clear() {
104         this.count = 0;
105         this.iovArray.clear();
106     }
107 
108     void release() {
109         iovArray.release();
110     }
111 
112     private final class MyMessageProcessor implements MessageProcessor {
113         private boolean connected;
114         private int maxMessagesPerWrite;
115 
116         @Override
117         public boolean processMessage(Object msg) {
118             final boolean added;
119             if (msg instanceof DatagramPacket) {
120                 DatagramPacket packet = (DatagramPacket) msg;
121                 ByteBuf buf = packet.content();
122                 int segmentSize = 0;
123                 if (packet instanceof io.netty.channel.unix.SegmentedDatagramPacket) {
124                     int seg = ((io.netty.channel.unix.SegmentedDatagramPacket) packet).segmentSize();
125                     // We only need to tell the kernel that we want to use UDP_SEGMENT if there are multiple
126                     // segments in the packet.
127                     if (buf.readableBytes() > seg) {
128                         segmentSize = seg;
129                     }
130                 }
131                 added = add0(buf, buf.readerIndex(), buf.readableBytes(), segmentSize, packet.recipient());
132             } else if (msg instanceof ByteBuf && connected) {
133                 ByteBuf buf = (ByteBuf) msg;
134                 added = add0(buf, buf.readerIndex(), buf.readableBytes(), 0, null);
135             } else {
136                 added = false;
137             }
138             if (added) {
139                 maxMessagesPerWrite--;
140                 return maxMessagesPerWrite > 0;
141             }
142             return false;
143         }
144     }
145 
146     private static InetSocketAddress newAddress(byte[] addr, int addrLen, int port, int scopeId, byte[] ipv4Bytes)
147             throws UnknownHostException {
148         final InetAddress address;
149         if (addrLen == ipv4Bytes.length) {
150             System.arraycopy(addr, 0, ipv4Bytes, 0, addrLen);
151             address = InetAddress.getByAddress(ipv4Bytes);
152         } else {
153             address = Inet6Address.getByAddress(null, addr, scopeId);
154         }
155         return new InetSocketAddress(address, port);
156     }
157 
158     /**
159      * Used to pass needed data to JNI.
160      */
161     @SuppressWarnings("unused")
162     @UnstableApi
163     public final class NativeDatagramPacket {
164 
165         // IMPORTANT: Most of the below variables are accessed via JNI. Be aware if you change any of these you also
166         // need to change these in the related .c file!
167 
168         // This is the actual struct iovec*
169         private long memoryAddress;
170         private int count;
171 
172         private final byte[] senderAddr = new byte[16];
173         private int senderAddrLen;
174         private int senderScopeId;
175         private int senderPort;
176 
177         private final byte[] recipientAddr = new byte[16];
178         private int recipientAddrLen;
179         private int recipientScopeId;
180         private int recipientPort;
181 
182         private int segmentSize;
183 
184         private void init(long memoryAddress, int count, int segmentSize, InetSocketAddress recipient) {
185             this.memoryAddress = memoryAddress;
186             this.count = count;
187             this.segmentSize = segmentSize;
188 
189             this.senderScopeId = 0;
190             this.senderPort = -1;
191             this.senderAddrLen = 0;
192 
193             if (recipient == null) {
194                 this.recipientScopeId = 0;
195                 this.recipientPort = 0;
196                 this.recipientAddrLen = 0;
197             } else {
198                 InetAddress address = recipient.getAddress();
199                 if (address instanceof Inet6Address) {
200                     System.arraycopy(address.getAddress(), 0, recipientAddr, 0, recipientAddr.length);
201                     recipientScopeId = ((Inet6Address) address).getScopeId();
202                 } else {
203                     copyIpv4MappedIpv6Address(address.getAddress(), recipientAddr);
204                     recipientScopeId = 0;
205                 }
206                 recipientAddrLen = recipientAddr.length;
207                 recipientPort = recipient.getPort();
208             }
209         }
210 
211         boolean hasSender() {
212             return senderPort >= 0;
213         }
214 
215         DatagramPacket newDatagramPacket(ByteBuf buffer, InetSocketAddress recipient) throws UnknownHostException {
216             InetSocketAddress sender = newAddress(senderAddr, senderAddrLen, senderPort, senderScopeId, ipv4Bytes);
217             if (recipientAddrLen != 0) {
218                 recipient = newAddress(recipientAddr, recipientAddrLen, recipientPort, recipientScopeId, ipv4Bytes);
219             }
220 
221             // Slice out the buffer with the correct length.
222             ByteBuf slice = buffer.retainedSlice(buffer.readerIndex(), count);
223 
224             // UDP_GRO
225             if (segmentSize > 0) {
226                 return new SegmentedDatagramPacket(slice, segmentSize, recipient, sender);
227             }
228             return new DatagramPacket(slice, recipient, sender);
229         }
230     }
231 }