1 /*
2 * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package sun.nio.ch.sctp;
26
27 import java.net.SocketAddress;
28 import java.net.InetSocketAddress;
29 import java.net.InetAddress;
30 import java.io.FileDescriptor;
31 import java.io.IOException;
32 import java.util.Collections;
33 import java.util.Set;
34 import java.util.HashSet;
35 import java.nio.channels.SelectionKey;
36 import java.nio.channels.ClosedChannelException;
37 import java.nio.channels.NotYetBoundException;
38 import java.nio.channels.spi.SelectorProvider;
39 import com.sun.nio.sctp.IllegalUnbindException;
40 import com.sun.nio.sctp.SctpChannel;
41 import com.sun.nio.sctp.SctpServerChannel;
42 import com.sun.nio.sctp.SctpSocketOption;
43 import com.sun.nio.sctp.SctpStandardSocketOptions;
44 import sun.nio.ch.NativeThread;
45 import sun.nio.ch.IOStatus;
46 import sun.nio.ch.IOUtil;
47 import sun.nio.ch.Net;
48 import sun.nio.ch.SelChImpl;
49 import sun.nio.ch.SelectionKeyImpl;
50
51 /**
52 * An implementation of SctpServerChannel
53 */
54 public class SctpServerChannelImpl extends SctpServerChannel
55 implements SelChImpl
56 {
57 private final FileDescriptor fd;
58
59 private final int fdVal;
60
61 /* IDs of native thread doing accept, for signalling */
62 private volatile long thread;
63
64 /* Lock held by thread currently blocked in this channel */
65 private final Object lock = new Object();
66
67 /* Lock held by any thread that modifies the state fields declared below
68 * DO NOT invoke a blocking I/O operation while holding this lock! */
69 private final Object stateLock = new Object();
70
71 private enum ChannelState {
72 UNINITIALIZED,
73 INUSE,
74 KILLPENDING,
75 KILLED,
76 }
77 /* -- The following fields are protected by stateLock -- */
78 private ChannelState state = ChannelState.UNINITIALIZED;
79
80 /* Binding: Once bound the port will remain constant. */
81 int port = -1;
82 private final HashSet<InetSocketAddress> localAddresses = new HashSet<>();
83 /* Has the channel been bound to the wildcard address */
84 private boolean wildcard; /* false */
85
86 /* -- End of fields protected by stateLock -- */
87
88 /**
89 * Initializes a new instance of this class.
90 */
91 public SctpServerChannelImpl(SelectorProvider provider)
92 throws IOException {
93 //TODO: update provider remove public modifier
94 super(provider);
95 this.fd = SctpNet.socket(true);
96 this.fdVal = IOUtil.fdVal(fd);
97 this.state = ChannelState.INUSE;
98 }
99
100 @Override
101 public SctpServerChannel bind(SocketAddress local, int backlog)
102 throws IOException {
103 synchronized (lock) {
104 synchronized (stateLock) {
105 if (!isOpen())
106 throw new ClosedChannelException();
107 if (isBound())
108 SctpNet.throwAlreadyBoundException();
109
110 InetSocketAddress isa = (local == null) ?
111 new InetSocketAddress(0) : Net.checkAddress(local);
112 Net.bind(fd, isa.getAddress(), isa.getPort());
113
114 InetSocketAddress boundIsa = Net.localAddress(fd);
115 port = boundIsa.getPort();
116 localAddresses.add(isa);
117 if (isa.getAddress().isAnyLocalAddress())
118 wildcard = true;
119
120 SctpNet.listen(fdVal, backlog < 1 ? 50 : backlog);
121 }
122 }
123 return this;
124 }
125
126 @Override
127 public SctpServerChannel bindAddress(InetAddress address)
128 throws IOException {
129 return bindUnbindAddress(address, true);
130 }
131
132 @Override
133 public SctpServerChannel unbindAddress(InetAddress address)
134 throws IOException {
135 return bindUnbindAddress(address, false);
136 }
137
138 private SctpServerChannel bindUnbindAddress(InetAddress address, boolean add)
139 throws IOException {
140 if (address == null)
141 throw new IllegalArgumentException();
142
143 synchronized (lock) {
144 synchronized (stateLock) {
145 if (!isOpen())
146 throw new ClosedChannelException();
147 if (!isBound())
148 throw new NotYetBoundException();
149 if (wildcard)
150 throw new IllegalStateException(
151 "Cannot add or remove addresses from a channel that is bound to the wildcard address");
152 if (address.isAnyLocalAddress())
153 throw new IllegalArgumentException(
154 "Cannot add or remove the wildcard address");
155 if (add) {
156 for (InetSocketAddress addr : localAddresses) {
157 if (addr.getAddress().equals(address)) {
158 SctpNet.throwAlreadyBoundException();
159 }
160 }
161 } else { /*removing */
162 /* Verify that there is more than one address
163 * and that address is already bound */
164 if (localAddresses.size() <= 1)
165 throw new IllegalUnbindException("Cannot remove address from a channel with only one address bound");
166 boolean foundAddress = false;
167 for (InetSocketAddress addr : localAddresses) {
168 if (addr.getAddress().equals(address)) {
169 foundAddress = true;
170 break;
171 }
172 }
173 if (!foundAddress )
174 throw new IllegalUnbindException("Cannot remove address from a channel that is not bound to that address");
175 }
176
177 SctpNet.bindx(fdVal, new InetAddress[]{address}, port, add);
178
179 /* Update our internal Set to reflect the addition/removal */
180 if (add)
181 localAddresses.add(new InetSocketAddress(address, port));
182 else {
183 for (InetSocketAddress addr : localAddresses) {
184 if (addr.getAddress().equals(address)) {
185 localAddresses.remove(addr);
186 break;
187 }
188 }
189 }
190 }
191 }
192 return this;
193 }
194
195 private boolean isBound() {
196 synchronized (stateLock) {
197 return port != -1;
198 }
199 }
200
201 private void acceptCleanup() throws IOException {
202 synchronized (stateLock) {
203 thread = 0;
204 if (state == ChannelState.KILLPENDING)
205 kill();
206 }
207 }
208
209 @Override
210 public SctpChannel accept() throws IOException {
211 synchronized (lock) {
212 if (!isOpen())
213 throw new ClosedChannelException();
214 if (!isBound())
215 throw new NotYetBoundException();
216
217 int n = 0;
218 FileDescriptor newfd = new FileDescriptor();
219 InetSocketAddress[] isaa = new InetSocketAddress[1];
220
221 try {
222 begin();
223 if (!isOpen())
224 return null;
225 thread = NativeThread.current();
226 for (;;) {
227 n = Net.accept(fd, newfd, isaa);
228 if ((n == IOStatus.INTERRUPTED) && isOpen())
229 continue;
230 break;
231 }
232 } finally {
233 acceptCleanup();
234 end(n > 0);
235 assert IOStatus.check(n);
236 }
237
238 if (n < 1)
239 return null;
240
241 IOUtil.configureBlocking(newfd, true);
242 return new SctpChannelImpl(provider(), newfd);
243 }
244 }
245
246 @Override
247 protected void implConfigureBlocking(boolean block) throws IOException {
248 IOUtil.configureBlocking(fd, block);
249 }
250
251 @Override
252 public void implCloseSelectableChannel() throws IOException {
253 synchronized (stateLock) {
254 if (state != ChannelState.KILLED)
255 SctpNet.preClose(fdVal);
256 if (thread != 0)
257 NativeThread.signal(thread);
258 if (!isRegistered())
259 kill();
260 }
261 }
262
263 @Override
264 public void kill() throws IOException {
265 synchronized (stateLock) {
266 if (state == ChannelState.KILLED)
267 return;
268 if (state == ChannelState.UNINITIALIZED) {
269 state = ChannelState.KILLED;
270 SctpNet.close(fdVal);
271 return;
272 }
273 assert !isOpen() && !isRegistered();
274
275 // Postpone the kill if there is a thread in accept
276 if (thread == 0) {
277 state = ChannelState.KILLED;
278 SctpNet.close(fdVal);
279 } else {
280 state = ChannelState.KILLPENDING;
281 }
282 }
283 }
284
285 @Override
286 public FileDescriptor getFD() {
287 return fd;
288 }
289
290 @Override
291 public int getFDVal() {
292 return fdVal;
293 }
294
295 /**
296 * Translates native poll revent ops into a ready operation ops
297 */
298 private boolean translateReadyOps(int ops, int initialOps,
299 SelectionKeyImpl sk) {
300 int intOps = sk.nioInterestOps();
301 int oldOps = sk.nioReadyOps();
302 int newOps = initialOps;
303
304 if ((ops & Net.POLLNVAL) != 0) {
305 /* This should only happen if this channel is pre-closed while a
306 * selection operation is in progress
307 * ## Throw an error if this channel has not been pre-closed */
308 return false;
309 }
310
311 if ((ops & (Net.POLLERR | Net.POLLHUP)) != 0) {
312 newOps = intOps;
313 sk.nioReadyOps(newOps);
314 return (newOps & ~oldOps) != 0;
315 }
316
317 if (((ops & Net.POLLIN) != 0) &&
318 ((intOps & SelectionKey.OP_ACCEPT) != 0))
319 newOps |= SelectionKey.OP_ACCEPT;
320
321 sk.nioReadyOps(newOps);
322 return (newOps & ~oldOps) != 0;
323 }
324
325 @Override
326 public boolean translateAndUpdateReadyOps(int ops, SelectionKeyImpl sk) {
327 return translateReadyOps(ops, sk.nioReadyOps(), sk);
328 }
329
330 @Override
331 public boolean translateAndSetReadyOps(int ops, SelectionKeyImpl sk) {
332 return translateReadyOps(ops, 0, sk);
333 }
334
335 @Override
336 public int translateInterestOps(int ops) {
337 int newOps = 0;
338 if ((ops & SelectionKey.OP_ACCEPT) != 0)
339 newOps |= Net.POLLIN;
340 return newOps;
341 }
342
343 @Override
344 public <T> SctpServerChannel setOption(SctpSocketOption<T> name, T value)
345 throws IOException {
346 if (name == null)
347 throw new NullPointerException();
348 if (!supportedOptions().contains(name))
349 throw new UnsupportedOperationException("'" + name + "' not supported");
350
351 synchronized (stateLock) {
352 if (!isOpen())
353 throw new ClosedChannelException();
354
355 SctpNet.setSocketOption(fdVal, name, value, 0 /*oneToOne*/);
356 return this;
357 }
358 }
359
360 @Override
361 @SuppressWarnings("unchecked")
362 public <T> T getOption(SctpSocketOption<T> name) throws IOException {
363 if (name == null)
364 throw new NullPointerException();
365 if (!supportedOptions().contains(name))
366 throw new UnsupportedOperationException("'" + name + "' not supported");
367
368 synchronized (stateLock) {
369 if (!isOpen())
370 throw new ClosedChannelException();
371
372 return (T) SctpNet.getSocketOption(fdVal, name, 0 /*oneToOne*/);
373 }
374 }
375
376 @Override
377 public final Set<SctpSocketOption<?>> supportedOptions() {
378 final class Holder {
379 static final Set<SctpSocketOption<?>> DEFAULT_OPTIONS =
380 Set.of(SctpStandardSocketOptions.SCTP_INIT_MAXSTREAMS);
381 }
382 return Holder.DEFAULT_OPTIONS;
383 }
384
385 @Override
386 public Set<SocketAddress> getAllLocalAddresses()
387 throws IOException {
388 synchronized (stateLock) {
389 if (!isOpen())
390 throw new ClosedChannelException();
391 if (!isBound())
392 return Collections.emptySet();
393
394 return SctpNet.getLocalAddresses(fdVal);
395 }
396 }
397 }