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 NativeThread 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 = null; 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 (NativeThread.isNativeThread(thread)) 257 thread.signal(); 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 == null) { 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 }