├── .gitignore ├── src ├── main │ ├── java │ │ └── com │ │ │ └── savarese │ │ │ └── rocksaw │ │ │ ├── overview.html │ │ │ └── net │ │ │ └── RawSocket.java │ └── native │ │ ├── Makefile.win32 │ │ ├── Makefile │ │ ├── RawSocket.h │ │ └── RawSocket.c └── test │ └── java │ └── rocksaw │ └── TestPing.java ├── .editorconfig ├── .travis.yml ├── NOTICE ├── Dockerfile ├── README.md ├── CHANGES ├── pom.xml └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .settings 3 | .classpath 4 | .project 5 | .scala_dependencies 6 | *.iml 7 | .idea 8 | build -------------------------------------------------------------------------------- /src/main/java/com/savarese/rocksaw/overview.html: -------------------------------------------------------------------------------- 1 |
2 | The RockSaw library contains packages implementing an API for 3 | using raw sockets from Java. 4 | 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | 6 | indent_style = space 7 | indent_size = 2 8 | 9 | end_of_line = lf 10 | 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [Makefile] 15 | insert_final_newline = false 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | services: 4 | - docker 5 | 6 | cache: 7 | directories: 8 | - $HOME/.m2 9 | 10 | before_install: 11 | - docker build -t mlaccetti/rocksaw:develop . 12 | 13 | script: 14 | - docker run --rm -v $(pwd):/opt/rocksaw -v $HOME/.m2:/root/.m2 mlaccetti/rocksaw:develop 15 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | RockSaw 2 | Copyright 2004-2007 Daniel F. Savarese 3 | Copyright 2007-2009 Savarese Software Research Corporation 4 | 5 | This product includes software developed by 6 | Daniel F. Savarese (http://www.savarese.org/). 7 | 8 | This product includes software developed by 9 | Savarese Software Research Corporation (http://www.savarese.com/). 10 | -------------------------------------------------------------------------------- /src/main/native/Makefile.win32: -------------------------------------------------------------------------------- 1 | # Copyright 2004-2005 Daniel F. Savarese 2 | # Copyright 2009 Savarese Software Research Corporation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.savarese.com/software/ApacheLicense-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, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | JAVA_INCDIR = $(JDK_HOME)\include 17 | JAVA_INCDIR_PLAF = $(JAVA_INCDIR)\win32 18 | 19 | CC = cl 20 | CFLAGS = -TC 21 | CPPFLAGS = -I$(JAVA_INCDIR) -I$(JAVA_INCDIR_PLAF) 22 | WINSOCK = ws2_32 23 | LDFLAGS = $(WINSOCK).lib 24 | 25 | SRC = RawSocket.c 26 | OBJ = $(SRC:.c=.obj) 27 | 28 | LIBNAME = rocksaw 29 | LIBEXTENSION = dll 30 | LIBROCKSAW = $(LIBNAME).$(LIBEXTENSION) 31 | CLEAN_EXTENSIONS = *.obj *.$(LIBEXTENSION) *.lib *.exp 32 | 33 | all: $(LIBROCKSAW) 34 | 35 | .c.obj: 36 | $(CC) -nologo $(CFLAGS) $(CPPFLAGS) -c $< -o $@ 37 | 38 | $(LIBROCKSAW): $(OBJ) 39 | $(CC) -nologo -MD -LD -o $@ $** $(LDFLAGS) 40 | 41 | clean: 42 | del $(CLEAN_EXTENSIONS) 43 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # RockSaw Testing Container Container 3 | # 4 | # Runs a super-tiny container with Java/Maven/gcc for building/testing RockSaw. 5 | # 6 | # Building the container: 7 | # $ docker build -t mlaccetti/rocksaw-dev . 8 | # 9 | # Interactive usage: 10 | # $ docker run -v $(pwd):/opt/rocksaw -it --rm mlaccetti/rocksaw-dev /bin/bash 11 | # 12 | # If you want to cache Maven dependencies between runs, you can change the run command: 13 | # $ docker run -v $(pwd):/opt/rocksaw -v ~/.m2:/root/.m2 -it --rm mlaccetti/rocksaw-dev /bin/bash 14 | # 15 | # Automated build usage: 16 | # $ docker run -v $(pwd):/opt/rocksaw --rm mlaccetti/rocksaw-dev 17 | # 18 | # Automated build usage with Maven cache: 19 | # $ docker run -v $(pwd):/opt/rocksaw -v ~/.m2:/root/.m2 --rm mlaccetti/rocksaw-dev 20 | 21 | FROM anapsix/alpine-java:jdk8 22 | 23 | MAINTAINER Michael Laccetti "michael@laccetti.com" 24 | 25 | ENV MAVEN_HOME="/opt/maven" 26 | ENV MAVEN_VERSION="3.3.9" 27 | 28 | RUN echo "http://mirror.leaseweb.com/alpine/v3.3/main" | tee /etc/apk/repositories 29 | 30 | RUN apk update && \ 31 | apk upgrade --update && \ 32 | apk add build-base curl vim && \ 33 | cd /opt && \ 34 | curl -LS "http://archive.apache.org/dist/maven/maven-3/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz" -o apache-maven-$MAVEN_VERSION-bin.tar.gz && \ 35 | tar xvzf apache-maven-$MAVEN_VERSION-bin.tar.gz && \ 36 | mv apache-maven-$MAVEN_VERSION /opt/maven && \ 37 | ln -s /opt/maven/bin/mvn /usr/bin/mvn && \ 38 | rm /opt/apache-maven-$MAVEN_VERSION-bin.tar.gz 39 | 40 | WORKDIR /opt/rocksaw 41 | 42 | CMD ["mvn", "clean", "test"] 43 | -------------------------------------------------------------------------------- /src/main/native/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2004-2006 Daniel F. Savarese 2 | # Copyright 2009 Savarese Software Research Corporation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.savarese.com/software/ApacheLicense-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, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | UNAME := $(shell uname) 17 | CYGWIN := $(findstring CYGWIN,$(UNAME)) 18 | DARWIN := $(findstring Darwin,$(UNAME)) 19 | 20 | MKDIR_P = mkdir -p 21 | CC = gcc 22 | SHARED = -shared 23 | CFLAGS = -Wall -O2 -pipe -pthread 24 | WINSOCK = ws2_32 25 | EXTRA_LDFLAGS = -fPIC 26 | EXTRA_CPPFLAGS = 27 | LDFLAGS = $(EXTRA_LDFLAGS) 28 | CPPFLAGS = $(EXTRA_CPPFLAGS) 29 | LIBNAME = librocksaw 30 | LIBEXTENSION = so 31 | OUT_DIR = ../../../target/native 32 | 33 | ifeq ($(DARWIN),Darwin) 34 | LIBEXTENSION = jnilib 35 | CPPFLAGS += -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/darwin 36 | LDFLAGS += -dynamiclib -framework JavaVM 37 | SHARED = 38 | else 39 | ifeq ($(CYGWIN),CYGWIN) 40 | override CC += -mno-cygwin 41 | CPPFLAGS += -D__int64="long long" 42 | LDFLAGS += -Wl,--kill-at -l$(WINSOCK) 43 | JDK_HOME := $(shell cygpath $(JDK_HOME)) 44 | LIBNAME = rocksaw 45 | LIBEXTENSION = dll 46 | endif 47 | 48 | JAVA_INCDIR = $(JAVA_HOME)/include 49 | JAVA_INCDIR_PLAF = $(dir $(wildcard $(JAVA_INCDIR)/*/jni_md.h)) 50 | CPPFLAGS += -I$(JAVA_INCDIR) -I$(JAVA_INCDIR_PLAF) 51 | CFLAGS += -ansi -pthread -DPIC -fPIC 52 | endif 53 | 54 | SRC := $(shell find . -name "*.c" -print) 55 | OBJ := $(SRC:%.c=%.o) 56 | 57 | CLEAN_EXTENSIONS = o $(LIBEXTENSION) 58 | 59 | LIBROCKSAW = $(LIBNAME).$(LIBEXTENSION) 60 | 61 | .PHONY: clean directories move_target 62 | 63 | all: directories $(LIBROCKSAW) move_target 64 | 65 | directories: 66 | $(MKDIR_P) $(OUT_DIR) 67 | 68 | %.o: %.c 69 | $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ 70 | 71 | $(LIBROCKSAW): $(OBJ) 72 | $(CC) $(SHARED) $(LDFLAGS) -o $@ $^ 73 | 74 | move_target: $(OBJ) 75 | mv $(OBJ) $(OUT_DIR) 76 | mv $(LIBROCKSAW) $(OUT_DIR) 77 | 78 | clean: 79 | rm -fR $(OUT_DIR) 80 | rm -f *.$(LIBEXTENSION) *.o -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## RockSaw 2 | 3 | TravisCI: [](https://travis-ci.org/mlaccetti/rocksaw) 4 | 5 | A fork of RockSaw (http://www.savarese.com/software/rocksaw/) that includes Maven support. 6 | 7 | Please note that the JNI stuff needs a bit of love to build - the Makefile was mangled (by me/Michael Laccetti). 8 | 9 | ### About 10 | 11 | RockSaw is a simple API for performing network I/O with raw 12 | sockets in Java. 13 | 14 | IPv6 support was graciously funded by ByteSphere Technologies 15 | (www.bytesphere.com). 16 | 17 | Commercial support is provided by Savarese Software Research 18 | Corporation (www.savarese.com). 19 | 20 | ### Requirements 21 | 22 | The 1.1.0 version of RockSaw has been compiled and tested on Linux, 23 | Win32 with Cygwin/MinGW/Winsock or Visual C++, and Mac OS X 10.11.4. It 24 | should compile on other POSIX systems using the GNU tool chain. 25 | 26 | No binary distributions are presently released; you will have to compile 27 | for yourself. 28 | 29 | Java 1.8 or greater is required to compile/run. 30 | 31 | ### Compiling 32 | 33 | You must have the JAVA_HOME environment variable set and pointing to 34 | the directory where the Java Development Kit is installed. Otherwise, 35 | the JNI headers will not be found. 36 | 37 | The project requires `maven` to build; The command `mvn clean package` 38 | should be sufficient to compile the JAR and associated library. 39 | 40 | There are very few files in the source tree: 41 | 42 | - src/main/java Java source code 43 | - src/main/native The C JNI source and Makefile 44 | 45 | #### Note about make 46 | 47 | The default Makefile requires GNU make. 48 | 49 | #### Win32: Visual C++ (Outdated Instructions) 50 | 51 | To compile using Visual C++, you have to override the default 52 | compiler command, make command, and makefile properties: 53 | 54 | jni.cc 55 | jni.make 56 | jni.makefile 57 | 58 | You can override these on the command line or in build.properties. 59 | For example, to compile using Visual C++, you would use the 60 | following command: 61 | 62 | ant -Djni.cc=cl -Djni.make=nmake -Djni.makefile=Makefile.win32 jar 63 | 64 | Make sure your JDK_HOME environment variable is set and that 65 | you've run either the vcvars.bat or vsvars32.bat command 66 | (depending on the version of Visual C++ you're using) to set 67 | your paths for the command line tools. 68 | 69 | #### Mac OS X 70 | 71 | Be sure to set JAVA_HOME to the right location. It will typically be 72 | something like 73 | `/Library/Java/JavaVirtualMachines/jdk1.8.0_66.jdk/Contents/Home`. 74 | 75 | ``` 76 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_66.jdk/Contents/Home 77 | mvn clean pacakge 78 | ``` 79 | 80 | ### Licensing 81 | 82 | RockSaw is 83 | Copyright 2004-2007 by Daniel F. Savarese 84 | Copyright 2007-2009 by Savarese Software Research Corporation 85 | and licensed under the Apache License 2.0 as described in the files: 86 | 87 | LICENSE 88 | NOTICE 89 | 90 | ### Notes 91 | 92 | On most operating systems, you must have root access or administrative 93 | privileges to use raw sockets. If you are running a firewall, you will have to make sure it allows ICMP requests through. 94 | 95 | The API is minimalist, yet functional. Don't hesitate to submit patches 96 | that enhance the functionality. 97 | 98 | 99 | ### Contact 100 | 101 | http://www.savarese.com/contact.html 102 | -------------------------------------------------------------------------------- /src/main/native/RawSocket.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2004-2005 Daniel F. Savarese 3 | * Copyright 2009 Savarese Software Research Corporation 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.savarese.com/software/ApacheLicense-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | #ifndef __ROCKSAW_RAW_SOCKET_H 18 | #define __ROCKSAW_RAW_SOCKET_H 19 | 20 | #include43 | * The Ping class is a simple demo showing how you can send ICMP echo requests 44 | * and receive echo replies using raw sockets. It has been updated to work with 45 | * both IPv4 and IPv6. 46 | *
47 | *48 | * Note, this is not a model of good programming. The point of the example is to 49 | * show how the RawSocket API calls work. There is much kluginess surrounding 50 | * the actual packet and protocol handling, all of which is outside of the scope 51 | * of what RockSaw does. 52 | *
53 | * 54 | * @author Daniel F. Savarese 55 | */ 56 | public class TestPing { 57 | private static final Logger log = LoggerFactory.getLogger(TestPing.class); 58 | 59 | @Test 60 | public void testPingLocalhost() { 61 | final String localhost = "localhost"; 62 | final int pingCount = 5; 63 | 64 | final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2); 65 | 66 | try { 67 | final InetAddress address = InetAddress.getByName(localhost); 68 | final String hostname = address.getCanonicalHostName(); 69 | final String hostaddr = address.getHostAddress(); 70 | 71 | // Ping programs usually use the process ID for the identifier, 72 | // but we can't get it and this is only a demo. 73 | final int id = 65535; 74 | final Pinger ping; 75 | 76 | if (address instanceof Inet6Address) { 77 | ping = new PingerIPv6(id); 78 | } else { 79 | ping = new Pinger(id); 80 | } 81 | 82 | ping.setEchoReplyListener(new EchoReplyListener() { 83 | StringBuffer buffer = new StringBuffer(128); 84 | 85 | public void notifyEchoReply(final ICMPEchoPacket packet, final byte[] data, final int dataOffset, final byte[] srcAddress) throws IOException { 86 | final long end = System.nanoTime(); 87 | final long start = OctetConverter.octetsToLong(data, dataOffset); 88 | // Note: Java and JNI overhead will be noticeable (100-200 89 | // microseconds) for sub-millisecond transmission times. 90 | // The first ping may even show several seconds of delay 91 | // because of initial JIT compilation overhead. 92 | final double rtt = (double) (end - start) / 1e6; 93 | 94 | buffer.setLength(0); 95 | buffer.append(packet.getICMPPacketByteLength()).append(" bytes from ").append(hostname).append(" ("); 96 | buffer.append(InetAddress.getByAddress(srcAddress).toString()); 97 | buffer.append("): icmp_seq=").append(packet.getSequenceNumber()).append(" ttl=").append(packet.getTTL()).append(" time=").append(rtt).append(" ms"); 98 | log.debug(buffer.toString()); 99 | } 100 | }); 101 | 102 | log.debug("PING {} ({}) {} ({}) bytes of data).", hostname, hostaddr, ping.getRequestDataLength(), ping.getRequestPacketLength()); 103 | 104 | final CountDownLatch latch = new CountDownLatch(1); 105 | 106 | executor.scheduleAtFixedRate(new Runnable() { 107 | int counter = pingCount; 108 | 109 | public void run() { 110 | try { 111 | if (counter > 0) { 112 | ping.sendEchoRequest(address); 113 | if (counter == pingCount) { 114 | latch.countDown(); 115 | } 116 | --counter; 117 | } else { 118 | executor.shutdown(); 119 | } 120 | } catch (final IOException ioe) { 121 | ioe.printStackTrace(); 122 | } 123 | } 124 | }, 0, 1, TimeUnit.SECONDS); 125 | 126 | // We wait for first ping to be sent because Windows times out 127 | // with WSAETIMEDOUT if echo request hasn't been sent first. 128 | // POSIX does the right thing and just blocks on the first receive. 129 | // An alternative is to bind the socket first, which should allow a 130 | // receive to be performed first on Windows. 131 | latch.await(); 132 | 133 | for (int i = 0; i < pingCount; ++i) { 134 | ping.receiveEchoReply(); 135 | } 136 | 137 | ping.close(); 138 | } catch (final Exception e) { 139 | executor.shutdown(); 140 | e.printStackTrace(); 141 | } 142 | } 143 | 144 | public interface EchoReplyListener { 145 | void notifyEchoReply(ICMPEchoPacket packet, byte[] data, int dataOffset, byte[] srcAddress) throws IOException; 146 | } 147 | 148 | public static class Pinger { 149 | private static final int TIMEOUT = 10000; 150 | 151 | protected RawSocket socket; 152 | protected ICMPEchoPacket sendPacket, recvPacket; 153 | protected int offset, length, dataOffset; 154 | protected int requestType, replyType; 155 | protected byte[] sendData, recvData, srcAddress; 156 | protected int sequence, identifier; 157 | protected EchoReplyListener listener; 158 | 159 | protected Pinger(final int id, final int protocolFamily, final int protocol) throws IOException { 160 | sequence = 0; 161 | identifier = id; 162 | setEchoReplyListener(null); 163 | 164 | sendPacket = new ICMPEchoPacket(1); 165 | recvPacket = new ICMPEchoPacket(1); 166 | sendData = new byte[84]; 167 | recvData = new byte[84]; 168 | 169 | sendPacket.setData(sendData); 170 | recvPacket.setData(recvData); 171 | sendPacket.setIPHeaderLength(5); 172 | recvPacket.setIPHeaderLength(5); 173 | sendPacket.setICMPDataByteLength(56); 174 | recvPacket.setICMPDataByteLength(56); 175 | 176 | offset = sendPacket.getIPHeaderByteLength(); 177 | dataOffset = offset + sendPacket.getICMPHeaderByteLength(); 178 | length = sendPacket.getICMPPacketByteLength(); 179 | 180 | socket = new RawSocket(); 181 | socket.open(protocolFamily, protocol); 182 | 183 | try { 184 | socket.setSendTimeout(TIMEOUT); 185 | socket.setReceiveTimeout(TIMEOUT); 186 | } catch (final SocketException se) { 187 | socket.setUseSelectTimeout(true); 188 | socket.setSendTimeout(TIMEOUT); 189 | socket.setReceiveTimeout(TIMEOUT); 190 | } 191 | } 192 | 193 | public Pinger(final int id) throws IOException { 194 | this(id, PF_INET, getProtocolByName("icmp")); 195 | 196 | srcAddress = new byte[4]; 197 | requestType = ICMPPacket.TYPE_ECHO_REQUEST; 198 | replyType = ICMPPacket.TYPE_ECHO_REPLY; 199 | } 200 | 201 | protected void computeSendChecksum(final InetAddress host) 202 | throws IOException { 203 | sendPacket.computeICMPChecksum(); 204 | } 205 | 206 | public void setEchoReplyListener(final EchoReplyListener l) { 207 | listener = l; 208 | } 209 | 210 | /** 211 | * Closes the raw socket opened by the constructor. After calling 212 | * this method, the object cannot be used. 213 | */ 214 | public void close() throws IOException { 215 | socket.close(); 216 | } 217 | 218 | public void sendEchoRequest(final InetAddress host) throws IOException { 219 | sendPacket.setType(requestType); 220 | sendPacket.setCode(0); 221 | sendPacket.setIdentifier(identifier); 222 | sendPacket.setSequenceNumber(sequence++); 223 | 224 | OctetConverter.longToOctets(System.nanoTime(), sendData, dataOffset); 225 | 226 | computeSendChecksum(host); 227 | 228 | socket.write(host, sendData, offset, length); 229 | } 230 | 231 | public void receive() throws IOException { 232 | socket.read(recvData, srcAddress); 233 | } 234 | 235 | public void receiveEchoReply() throws IOException { 236 | do { 237 | receive(); 238 | } while (recvPacket.getType() != replyType || recvPacket.getIdentifier() != identifier); 239 | 240 | if (listener != null) { 241 | listener.notifyEchoReply(recvPacket, recvData, dataOffset, srcAddress); 242 | } 243 | } 244 | 245 | /** 246 | * @return The number of bytes in the data portion of the ICMP ping request 247 | * packet. 248 | */ 249 | public int getRequestDataLength() { 250 | return sendPacket.getICMPDataByteLength(); 251 | } 252 | 253 | /** 254 | * @return The number of bytes in the entire IP ping request packet. 255 | */ 256 | public int getRequestPacketLength() { 257 | return sendPacket.getIPPacketLength(); 258 | } 259 | } 260 | 261 | public static class PingerIPv6 extends Pinger { 262 | private static final int IPPROTO_ICMPV6 = 58; 263 | private static final int ICMPv6_TYPE_ECHO_REQUEST = 128; 264 | private static final int ICMPv6_TYPE_ECHO_REPLY = 129; 265 | private final byte[] localAddress; 266 | private final ICMPv6ChecksumCalculator icmpv6Checksummer; 267 | 268 | public PingerIPv6(final int id) throws IOException { 269 | super(id, PF_INET6, IPPROTO_ICMPV6 /*getProtocolByName("ipv6-icmp")*/); 270 | 271 | icmpv6Checksummer = new ICMPv6ChecksumCalculator(); 272 | srcAddress = new byte[16]; 273 | localAddress = new byte[16]; 274 | requestType = ICMPv6_TYPE_ECHO_REQUEST; 275 | replyType = ICMPv6_TYPE_ECHO_REPLY; 276 | } 277 | 278 | protected void computeSendChecksum(final InetAddress host) 279 | throws IOException { 280 | // This is necessary only for Windows, which doesn't implement 281 | // RFC 2463 correctly. 282 | socket.getSourceAddressForDestination(host, localAddress); 283 | icmpv6Checksummer.computeChecksum(sendData, sendPacket, 284 | host.getAddress(), localAddress); 285 | } 286 | 287 | public void receive() throws IOException { 288 | socket.read(recvData, offset, length, srcAddress); 289 | } 290 | 291 | public int getRequestPacketLength() { 292 | return (getRequestDataLength() + 40); 293 | } 294 | 295 | /** 296 | * Operating system kernels are supposed to calculate the ICMPv6 297 | * checksum for the sender, but Microsoft's IPv6 stack does not do 298 | * this. Nor does it support the IPV6_CHECKSUM socket option. 299 | * Therefore, in order to work on the Windows family of operating 300 | * systems, we have to calculate the ICMPv6 checksum. 301 | */ 302 | private static class ICMPv6ChecksumCalculator extends IPPacket { 303 | ICMPv6ChecksumCalculator() { 304 | super(1); 305 | } 306 | 307 | private int computeVirtualHeaderTotal(final byte[] destination, final byte[] source, final int icmpLength) { 308 | int total = 0; 309 | 310 | for (int i = 0; i < source.length; ) { 311 | total += (((source[i++] & 0xff) << 8) | (source[i++] & 0xff)); 312 | } 313 | for (int i = 0; i < destination.length; ) { 314 | total += (((destination[i++] & 0xff) << 8) | (destination[i++] & 0xff)); 315 | } 316 | 317 | total += (icmpLength >>> 16); 318 | total += (icmpLength & 0xffff); 319 | total += IPPROTO_ICMPV6; 320 | 321 | return total; 322 | } 323 | 324 | int computeChecksum(final byte[] data, final ICMPPacket packet, final byte[] destination, final byte[] source) { 325 | final int startOffset = packet.getIPHeaderByteLength(); 326 | final int checksumOffset = startOffset + OFFSET_ICMP_CHECKSUM; 327 | final int ipLength = packet.getIPPacketLength(); 328 | final int icmpLength = packet.getICMPPacketByteLength(); 329 | 330 | setData(data); 331 | 332 | return _computeChecksum_(startOffset, checksumOffset, ipLength, computeVirtualHeaderTotal(destination, source, icmpLength), true); 333 | } 334 | } 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /src/main/native/RawSocket.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2004-2008 Daniel F. Savarese 2 | * Copyright 2009-2014 Savarese Software Research Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.savarese.com/software/ApacheLicense-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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #includeThe RawSocket class provides a strictly utilitarian API for 27 | * performing I/O with IPv4 and IPv6 raw sockets. The API is 28 | * minimalist, yet functional.
29 | * 30 | *We throw java.io.InterruptedIOException when read/write 31 | * operations time out because java.net.SocketTimeoutException is 32 | * present only in J2SE 1.4 and up. By using InterruptedIOException, 33 | * we allow programmers to use the software with J2SE 1.2 and 1.3.
34 | * 35 | *Socket options should not be set until the socket has been 36 | * opened.
37 | * 38 | *Important! On most operating systems, you must have root 39 | * access or administrative privileges to use raw sockets.
40 | */ 41 | public class RawSocket { 42 | 43 | private native static int __PF_INET(); 44 | private native static int __PF_INET6(); 45 | 46 | /** 47 | * A protocol family constant for {@link #open} indicating IPv4. 48 | * This should be moved to another class. 49 | */ 50 | public static final int PF_INET; 51 | 52 | /** 53 | * A protocol family constant for {@link #open} indicating IPv6. 54 | * This should be moved to another class. 55 | */ 56 | public static final int PF_INET6; 57 | 58 | /** 59 | * Initializes any system resources used by the RockSaw library. 60 | * Really, all it does is call WSAStartup on Win32. It may be 61 | * called multiple times (only the first call has any effect), but 62 | * each call must be matched with a corresponding call to 63 | * RockSawShutdown(). 64 | * 65 | * @return zero if successful, otherwise some non-zero value. 66 | */ 67 | private native static int __RockSawStartup(); 68 | 69 | /** 70 | * Deallocates any system resources used by the RockSaw library. 71 | * Really, all it does is call WSACleanup on Win32. 72 | */ 73 | private native static void __RockSawShutdown(); 74 | 75 | static { 76 | System.loadLibrary("rocksaw"); 77 | if(__RockSawStartup() != 0) 78 | throw new UnsatisfiedLinkError(__getErrorMessage()); 79 | 80 | Runtime.getRuntime().addShutdownHook(new Thread() { 81 | public void run() { 82 | __RockSawShutdown(); 83 | } 84 | }); 85 | 86 | PF_INET = __PF_INET(); 87 | PF_INET6 = __PF_INET6(); 88 | } 89 | 90 | private static final int __UNDEFINED = -1; 91 | 92 | private int __socket; 93 | private int __family; 94 | private int __stimeout, __rtimeout; 95 | private boolean __useSelectTimeout; 96 | 97 | /** 98 | * Creates an uninitialized socket. If the {@code os.name} system 99 | * property starts with the string "SunOS", 100 | * {@link #setUseSelectTimeout} is set to true (because Solaris does not 101 | * support socket send and receive timeouts), otherwise it is false 102 | * by default. 103 | */ 104 | public RawSocket() { 105 | __socket = __UNDEFINED; 106 | __family = __UNDEFINED; 107 | __stimeout = 0; 108 | __rtimeout = 0; 109 | 110 | String os = System.getProperty("os.name"); 111 | 112 | if(os != null && os.startsWith("SunOS")) { 113 | setUseSelectTimeout(true); 114 | } else { 115 | setUseSelectTimeout(false); 116 | } 117 | } 118 | 119 | 120 | /** 121 | * Tests if the socket has been opened. 122 | * 123 | * @return True if the socket is open. 124 | */ 125 | public boolean isOpen() { 126 | return (__socket > 0); 127 | } 128 | 129 | 130 | /** 131 | * Writes a system error message into a StringBuffer. 132 | * The message is appended to the supplied StringBuffer argument. 133 | * This is a thread safe call on systems that support per-thread 134 | * instances of errno. 135 | * 136 | * @param buffer The buffer in which to store the error message. 137 | */ 138 | private native static void __getErrorMessage(StringBuffer buffer); 139 | 140 | private static String __getErrorMessage() { 141 | StringBuffer buf = new StringBuffer(); 142 | __getErrorMessage(buf); 143 | return buf.toString(); 144 | } 145 | 146 | private static void __throwIOException() throws IOException { 147 | throw new IOException(__getErrorMessage()); 148 | } 149 | 150 | private static void __throwSocketException() throws SocketException { 151 | throw new SocketException(__getErrorMessage()); 152 | } 153 | 154 | private static void __throwInterruptedIOException() 155 | throws InterruptedIOException 156 | { 157 | throw new InterruptedIOException(__getErrorMessage()); 158 | } 159 | 160 | private int __getScopeId(InetAddress address) { 161 | if(__family == PF_INET6 && address instanceof Inet6Address) { 162 | return ((Inet6Address)address).getScopeId(); 163 | } 164 | return 0; 165 | } 166 | 167 | private native static int __socket(int protocolFamily, int protocol); 168 | 169 | 170 | /** 171 | *Returns the protocol number corresponding to the given protocol name. 172 | * For example, {@code getProtocolByName("icmp");} should return 1 and 173 | * {@code getProtocolByName("udp");} should return 17. The native system 174 | * protocol database is used to look up the protocol numbers.
175 | * 176 | *This method really belongs in another class, probably in 177 | * vserv-tcpip, but is currently included here as a convenience. It 178 | * may be moved elsewhere in the 1.0 release API.
179 | * 180 | * @return The protocol number corresponding to the given protocol name. 181 | * If the protocol name cannot be found, returns a negative value. 182 | */ 183 | public native static final int getProtocolByName(String name); 184 | 185 | private native static 186 | int __query_routing_interface(int socket, int family, 187 | byte[] destination, byte[] source); 188 | 189 | /** 190 | *Returns by out parameter the address of the network interface that 191 | * will be used to send a packet to the given destination. This 192 | * works on Windows only and is necessary in order to compute ICMPv6 193 | * checksums.
194 | * 195 | *This method really belongs in another class,, but is 196 | * currently included here for expediency. It may be moved 197 | * elsewhere in the 1.0 release API.
198 | * 199 | * @param destination The address of the destination. 200 | * @param source A byte array in which to store the returned source 201 | * address. On platforms other than Microsoft Windows, the array is 202 | * left unchanged. 203 | * @exception IOException If an I/O error occurs. 204 | */ 205 | public void 206 | getSourceAddressForDestination(InetAddress destination, byte[] source) 207 | throws IOException 208 | { 209 | if(__query_routing_interface(__socket, __family, 210 | destination.getAddress(), source) < 0) 211 | __throwIOException(); 212 | } 213 | 214 | /** 215 | * Opens a raw socket. 216 | * 217 | * @param protocolFamily The protocol family of the socket (e.g., 218 | * {@link #PF_INET} or {@link #PF_INET6}). 219 | * @param protocol The protocol within the protocol family. {@link 220 | * #getProtocolByName} should be used to obtain protocol numbers. 221 | * @exception IllegalStateException If the object instance is 222 | * already open. 223 | * @exception IOException If an error occurs while opening the socket. 224 | */ 225 | public void open(int protocolFamily, int protocol) 226 | throws IllegalStateException, IOException 227 | { 228 | if(isOpen()) 229 | throw new IllegalStateException(); 230 | __socket = __socket(protocolFamily, protocol); 231 | 232 | if(__socket < 0) { 233 | __socket = __UNDEFINED; 234 | __throwIOException(); 235 | } 236 | 237 | __family = protocolFamily; 238 | } 239 | 240 | 241 | private native static int __bind(int socket, int family, byte[] address, 242 | int scope_id); 243 | 244 | /** 245 | * Binds a local network address to a previously opened raw socket. 246 | * On most operating systems, binding a raw socket to an address 247 | * causes only packets with a destination matching the address to be 248 | * delivered to the socket. Also, the kernel will set the source 249 | * address of outbound packets to the bound address (unless {@link 250 | * #setIPHeaderInclude setIPHeaderInclude(true)} has been called). 251 | * 252 | * @param address The address to bind. 253 | * @exception IllegalStateException If the socket has not been opened first. 254 | * @exception IOException If the address cannot be bound. 255 | */ 256 | public void bind(InetAddress address) 257 | throws IllegalStateException, IOException 258 | { 259 | int scope_id = __getScopeId(address); 260 | 261 | if(!isOpen()) { 262 | throw new IllegalStateException(); 263 | } 264 | 265 | if(__bind(__socket, __family, address.getAddress(), scope_id) != 0) { 266 | __throwIOException(); 267 | } 268 | } 269 | 270 | // Returns a positive value if unsupported operation. 271 | private native static int __bindDevice(int socket, String device); 272 | 273 | /** 274 | * Binds a network device (e.g., eth0) to a previously opened raw socket. 275 | * This is implemented currently only for Linux using the SO_BINDTODEVICE 276 | * socket option. Sent packets will leave through the bound device and 277 | * only packets arriving via the device will be delivered to the socket. 278 | * 279 | * @param device The name of the device to bind (e.g., "eth0"). 280 | * Passing a zero-length string will remove the current binding. 281 | * The loopback interface ("lo") and device aliases (e.g., "eth0:1") 282 | * cannot be bound. 283 | * @exception IllegalStateException If the socket has not been opened first. 284 | * @exception UnsupportedOperationException If binding a device 285 | * name is not supported on the runtime platform. 286 | * @exception IOException If the device cannot be bound. 287 | */ 288 | public void bindDevice(String device) 289 | throws UnsupportedOperationException, IllegalStateException, IOException 290 | { 291 | if(!isOpen()) 292 | throw new IllegalStateException(); 293 | 294 | int result = __bindDevice(__socket, device); 295 | 296 | if(result < 0) 297 | __throwIOException(); 298 | else if(result > 0) 299 | throw new UnsupportedOperationException(); 300 | } 301 | 302 | private native static int __close(int socket); 303 | 304 | /** 305 | * Closes the socket. 306 | * 307 | * @exception IOException If an I/O error occurs. 308 | */ 309 | public void close() throws IOException { 310 | int result = __close(__socket); 311 | __socket = __UNDEFINED; 312 | __family = __UNDEFINED; 313 | 314 | if(result != 0) 315 | __throwIOException(); 316 | } 317 | 318 | /** 319 | * @return True if errno equals EAGAIN or EWOULDBLOCK. 320 | */ 321 | private native boolean __isErrorEAGAIN(); 322 | 323 | private native static int __setIPHeaderInclude(int socket, boolean on); 324 | 325 | /** 326 | * @return A negative value if an error occurs, else zero for false and 327 | * a positive value for true. 328 | */ 329 | private native static int __getIPHeaderInclude(int socket); 330 | 331 | 332 | /** 333 | * Sets or unsets the IP_HDRINCL socket option. Setting this option 334 | * causes IPv4 packet writes to expect the entire IP packet, 335 | * starting from the header. The default behavior is to only expect 336 | * the data payload. This option is valid only for IPv4 sockets. 337 | * 338 | * @param on True if headers should be included, false if not. 339 | * @exception SocketException If the option setting could not be altered. 340 | */ 341 | public void setIPHeaderInclude(boolean on) throws SocketException { 342 | int result = __setIPHeaderInclude(__socket, on); 343 | 344 | if(result < 0) 345 | __throwSocketException(); 346 | } 347 | 348 | 349 | /** 350 | * Retrieves the current setting of the IP_HDRINCL option. 351 | * 352 | * @return True if the IP_HDRINCL option is set, false if not. 353 | * @exception SocketException If the option value could not be retrieved. 354 | */ 355 | public boolean getIPHeaderInclude() throws SocketException { 356 | int result = __getIPHeaderInclude(__socket); 357 | 358 | if(result < 0) 359 | __throwSocketException(); 360 | 361 | return (result > 0); 362 | } 363 | 364 | 365 | private native static int __setSendBufferSize(int socket, int size); 366 | 367 | /** 368 | * Sets the send buffer size (SO_SNDBUF). 369 | * 370 | * @param size The size of the send buffer. 371 | * @exception SocketException If the option value could not be set. 372 | */ 373 | public void setSendBufferSize(int size) throws SocketException { 374 | int result = __setSendBufferSize(__socket, size); 375 | 376 | if(result < 0) 377 | __throwSocketException(); 378 | } 379 | 380 | 381 | private native static int __getSendBufferSize(int __socket); 382 | 383 | /** 384 | * Retrieves the send buffer size (SO_SNDBUF). 385 | * 386 | * @return The size of the send buffer. 387 | * @exception SocketException If the option value could not be retrieved. 388 | */ 389 | public int getSendBufferSize() throws SocketException { 390 | int result = __getSendBufferSize(__socket); 391 | 392 | if(result < 0) 393 | __throwSocketException(); 394 | 395 | return result; 396 | } 397 | 398 | 399 | private native static int __setReceiveBufferSize(int socket, int size); 400 | 401 | /** 402 | * Sets the receive buffer size (SO_RCVBUF). 403 | * 404 | * @param size The size of the receive buffer. 405 | * @exception SocketException If the option value could not be set. 406 | */ 407 | public void setReceiveBufferSize(int size) throws SocketException { 408 | int result = __setReceiveBufferSize(__socket, size); 409 | 410 | if(result < 0) 411 | __throwSocketException(); 412 | } 413 | 414 | 415 | private native static int __getReceiveBufferSize(int socket); 416 | 417 | /** 418 | * Retrieves the receive buffer size (SO_RCVBUF). 419 | * 420 | * @return The size of the receive buffer. 421 | * @exception SocketException If the option value could not be retrieved. 422 | */ 423 | public int getReceiveBufferSize() throws SocketException { 424 | int result = __getReceiveBufferSize(__socket); 425 | 426 | if(result < 0) 427 | __throwSocketException(); 428 | 429 | return result; 430 | } 431 | 432 | /** 433 | * @return Zero if the socket is ready for I/O, negative value if 434 | * timed out or error occurred. 435 | */ 436 | private native static 437 | int __select(int socket, boolean read, int milliseconds); 438 | 439 | 440 | /** 441 | *Sets whether or not socket send/receive timeouts should be 442 | * emulated by using the POSIX {@code select} function. Not all 443 | * platforms support socket send/receive timeouts and this method 444 | * provides a means to reproduce the same effect.
445 | * 446 | * @param useSelect true if {@code select} should be used to 447 | * implement timeouts, false if not. 448 | */ 449 | public void setUseSelectTimeout(boolean useSelect) { 450 | __useSelectTimeout = useSelect; 451 | } 452 | 453 | 454 | /** 455 | *Determines whether or not socket send/receive timeouts are 456 | * emulated by using the POSIX {@code select} system function. 457 | * Not all platforms support socket send/receive timeouts. The 458 | * default value is false except for platforms where the {@code 459 | * os.name} property starts with the string "SunOS".
460 | * 461 | * @return True if send/receive timeouts are emulated with select, 462 | * false if not. 463 | */ 464 | public boolean getUseSelectTimeout() { 465 | return __useSelectTimeout; 466 | } 467 | 468 | 469 | private native static int __setSendTimeout(int socket, int timeout); 470 | 471 | /** 472 | * Sets the send timeout (SO_SNDTIMEO). A timeout of zero indicates 473 | * an infinite timeout. A negative timeout is undefined. 474 | * 475 | * @param timeout The send timeout in milliseconds. 476 | * @exception SocketException If the option value could not be set. 477 | */ 478 | public void setSendTimeout(int timeout) throws SocketException { 479 | __stimeout = timeout; 480 | 481 | if(!getUseSelectTimeout()) { 482 | int result = __setSendTimeout(__socket, timeout); 483 | 484 | if(result < 0) { 485 | __throwSocketException(); 486 | } 487 | } 488 | } 489 | 490 | 491 | private native static int __getSendTimeout(int socket); 492 | 493 | /** 494 | * Retrieves the send timeout (SO_SNDTIMEO). 495 | * 496 | * @return The send timeout in milliseconds. 497 | * @exception SocketException If the option value could not be set. 498 | */ 499 | public int getSendTimeout() throws SocketException { 500 | int result; 501 | 502 | if(getUseSelectTimeout()) { 503 | result = __stimeout; 504 | } else { 505 | result = __getSendTimeout(__socket); 506 | 507 | if(result < 0) { 508 | __throwSocketException(); 509 | } 510 | } 511 | 512 | return result; 513 | } 514 | 515 | 516 | private native static int __setReceiveTimeout(int socket, int timeout); 517 | 518 | /** 519 | * Sets the receive timeout (SO_RCVTIMEO). A timeout of zero indicates 520 | * an infinite timeout. A negative timeout is undefined. 521 | * 522 | * @param timeout The receive timeout in milliseconds. 523 | * @exception SocketException If the option value could not be set. 524 | */ 525 | public void setReceiveTimeout(int timeout) throws SocketException { 526 | __rtimeout = timeout; 527 | 528 | if(!getUseSelectTimeout()) { 529 | int result = __setReceiveTimeout(__socket, timeout); 530 | 531 | if(result < 0) { 532 | __throwSocketException(); 533 | } 534 | } 535 | } 536 | 537 | 538 | private native static int __getReceiveTimeout(int socket); 539 | 540 | /** 541 | * Retrieves the receive timeout (SO_RCVTIMEO). 542 | * 543 | * @return The receive timeout in milliseconds. 544 | * @exception SocketException If the option value could not be set. 545 | */ 546 | public int getReceiveTimeout() throws SocketException { 547 | int result; 548 | 549 | if(getUseSelectTimeout()) { 550 | result = __rtimeout; 551 | } else { 552 | result = __getReceiveTimeout(__socket); 553 | 554 | if(result < 0) { 555 | __throwSocketException(); 556 | } 557 | } 558 | 559 | return result; 560 | } 561 | 562 | 563 | private native static int __recvfrom1(int socket, byte[] data, int offset, 564 | int length, int family); 565 | private native static int __recvfrom2(int socket, byte[] data, int offset, 566 | int length, int family, 567 | byte[] address); 568 | 569 | /** 570 | * Reads packet data from the socket. IPv4 ({@link #PF_INET}) 571 | * packets will be delivered in their entirety, including the IP 572 | * header. IPv6 ({@link #PF_INET6}) packet data will not include 573 | * the IPV6 header. 574 | * 575 | * @param data The buffer in which to store the packet data. 576 | * @param offset The offset into the buffer where the data should 577 | * be stored. 578 | * @param length The number of bytes to read. 579 | * @param address A byte array in which to store the source address 580 | * of the received packet. It may be null if you don't want to 581 | * retrieve the source address. Otherwise, it must be the right 582 | * size to store the address (e.g., 4 bytes for an IPv4 address). 583 | * @exception IllegalArgumentException If the offset or lengths are 584 | * invalid or if the address parameter is the wrong length. 585 | * @exception IOException If an I/O error occurs. 586 | * @exception InterruptedIOException If the read operation times out. 587 | * @return The number of bytes read. 588 | */ 589 | public int read(byte[] data, int offset, int length, byte[] address) 590 | throws IllegalArgumentException, IOException, InterruptedIOException 591 | { 592 | if(offset < 0 || length < 0 || length > data.length - offset) 593 | throw new IllegalArgumentException("Invalid offset or length."); 594 | 595 | if(address != null && 596 | ((__family == PF_INET && address.length != 4) || 597 | (__family == PF_INET6 && address.length != 16))) 598 | throw new IllegalArgumentException("Invalid address length."); 599 | 600 | int result = 0; 601 | 602 | if(getUseSelectTimeout() && __rtimeout > 0) { 603 | result = __select(__socket, true, __rtimeout); 604 | } 605 | 606 | if(result == 0) 607 | result = 608 | (address == null ? 609 | __recvfrom1(__socket, data, offset, length, __family) : 610 | __recvfrom2(__socket, data, offset, length, __family, address)); 611 | 612 | if(result < 0) { 613 | if(__isErrorEAGAIN()) 614 | __throwInterruptedIOException(); 615 | else 616 | __throwIOException(); 617 | } 618 | 619 | return result; 620 | } 621 | 622 | /** Same as {@code read(data, 0, length, null);} */ 623 | public int read(byte[] data, int offset, int length) 624 | throws IllegalArgumentException, IOException, InterruptedIOException 625 | { 626 | return read(data, offset, length, null); 627 | } 628 | 629 | /** Same as {@code read(data, 0, data.length, address);} */ 630 | public int read(byte[] data, byte[] address) 631 | throws IOException, InterruptedIOException 632 | { 633 | return read(data, 0, data.length, address); 634 | } 635 | 636 | /** Same as {@code read(address, data, 0, data.length, null);} */ 637 | public int read(byte[] data) 638 | throws IOException, InterruptedIOException 639 | { 640 | return read(data, 0, data.length, null); 641 | } 642 | 643 | private native static int __sendto(int socket, byte[] data, int offset, 644 | int length, int family, byte[] address, 645 | int scope_id); 646 | 647 | /** 648 | * Writes packet data to the socket. The data should not include 649 | * the IP header. IPv4 ({@link #PF_INET}) sockets may set the 650 | * IP_HDRINCL option with {@link #setIPHeaderInclude}, in which case the 651 | * packet data should include the IP header. 652 | * 653 | * @param address The destination to write to. 654 | * @param data The buffer from which to copy the packet data. 655 | * @param offset The offset into the buffer where the data starts. 656 | * @param length The number of bytes to write. 657 | * @exception IllegalArgumentException If the offset or lengths are invalid. 658 | * @exception IOException If an I/O error occurs. 659 | * @exception InterruptedIOException If the write operation times out. 660 | * @return The number of bytes written. 661 | */ 662 | public int write(InetAddress address, byte[] data, int offset, int length) 663 | throws IllegalArgumentException, IOException, InterruptedIOException 664 | { 665 | int scope_id = __getScopeId(address); 666 | 667 | if(offset < 0 || length < 0 || length > data.length - offset) { 668 | throw new IllegalArgumentException("Invalid offset or length."); 669 | } 670 | 671 | int result = 0; 672 | 673 | if(getUseSelectTimeout() && __stimeout > 0) { 674 | result = __select(__socket, false, __stimeout); 675 | } 676 | 677 | if(result == 0) 678 | result = __sendto(__socket, data, offset, length, __family, 679 | address.getAddress(), scope_id); 680 | 681 | if(result < 0) { 682 | if(__isErrorEAGAIN()) { 683 | __throwInterruptedIOException(); 684 | } else { 685 | __throwIOException(); 686 | } 687 | } 688 | 689 | return result; 690 | } 691 | 692 | /** Same as {@code write(address, data, 0, data.length);} */ 693 | public int write(InetAddress address, byte[] data) 694 | throws IOException, InterruptedIOException 695 | { 696 | return write(address, data, 0, data.length); 697 | } 698 | 699 | } 700 | --------------------------------------------------------------------------------