org.jruby.ext.socket.SocketUtils Maven / Gradle / Ivy
/***** BEGIN LICENSE BLOCK *****
* Version: CPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Common Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/cpl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the CPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the CPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby.ext.socket;
import jnr.constants.platform.AddressFamily;
import jnr.constants.platform.ProtocolFamily;
import jnr.constants.platform.Sock;
import jnr.netdb.Service;
import org.jruby.CompatVersion;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyClass;
import org.jruby.RubyFixnum;
import org.jruby.RubyInteger;
import org.jruby.RubyNumeric;
import org.jruby.RubyString;
import org.jruby.RubySymbol;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.util.io.Sockaddr;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static jnr.constants.platform.AddressFamily.AF_INET6;
import static jnr.constants.platform.IPProto.IPPROTO_TCP;
import static jnr.constants.platform.IPProto.IPPROTO_UDP;
import static jnr.constants.platform.NameInfo.NI_NUMERICHOST;
import static jnr.constants.platform.NameInfo.NI_NUMERICSERV;
import static jnr.constants.platform.ProtocolFamily.PF_INET;
import static jnr.constants.platform.ProtocolFamily.PF_INET6;
import static jnr.constants.platform.Sock.SOCK_DGRAM;
import static jnr.constants.platform.Sock.SOCK_STREAM;
/**
* Socket class methods for addresses, structures, and so on.
*/
public class SocketUtils {
public static IRubyObject gethostname(ThreadContext context) {
Ruby runtime = context.runtime;
try {
return runtime.newString(InetAddress.getLocalHost().getHostName());
} catch(UnknownHostException e) {
try {
return runtime.newString(InetAddress.getByAddress(new byte[]{0,0,0,0}).getHostName());
} catch(UnknownHostException e2) {
throw sockerr(runtime, "gethostname: name or service not known");
}
}
}
public static IRubyObject gethostbyaddr(ThreadContext context, IRubyObject[] args) {
Ruby runtime = context.runtime;
IRubyObject[] ret = new IRubyObject[4];
ret[0] = runtime.newString(Sockaddr.addressFromString(runtime, args[0].convertToString().toString()).getCanonicalHostName());
ret[1] = runtime.newArray();
ret[2] = runtime.newFixnum(2); // AF_INET
ret[3] = args[0];
return runtime.newArrayNoCopy(ret);
}
public static IRubyObject getservbyname(ThreadContext context, IRubyObject[] args) {
Ruby runtime = context.runtime;
String name = args[0].convertToString().toString();
String proto = args.length == 1 ? "tcp" : args[1].convertToString().toString();
Service service = Service.getServiceByName(name, proto);
int port;
if (service != null) {
port = service.getPort();
} else {
// MRI behavior: try to convert the name string to port directly
try {
port = Integer.parseInt(name.trim());
} catch (NumberFormatException nfe) {
throw sockerr(runtime, "no such service " + name + "/" + proto);
}
}
return runtime.newFixnum(port);
}
public static IRubyObject pack_sockaddr_in(ThreadContext context, IRubyObject port, IRubyObject host) {
int portNum = port instanceof RubyString ?
Integer.parseInt(port.convertToString().toString()) :
RubyNumeric.fix2int(port);
return Sockaddr.pack_sockaddr_in(
context,
portNum,
host.isNil() ? null : host.convertToString().toString());
}
public static IRubyObject unpack_sockaddr_in(ThreadContext context, IRubyObject addr) {
return Sockaddr.unpack_sockaddr_in(context, addr);
}
public static IRubyObject pack_sockaddr_un(ThreadContext context, IRubyObject filename) {
ByteList str = filename.convertToString().getByteList();
AddressFamily af = AddressFamily.AF_UNIX;
int high = (af.intValue() & 0xff00) >> 8;
int low = af.intValue() & 0xff;
ByteList bl = new ByteList();
bl.append((byte)high);
bl.append((byte)low);
bl.append(str);
for(int i=str.length();i<104;i++) {
bl.append((byte)0);
}
return context.runtime.newString(bl);
}
public static IRubyObject gethostbyname(ThreadContext context, IRubyObject hostname) {
Ruby runtime = context.runtime;
try {
InetAddress addr = getRubyInetAddress(hostname.convertToString().getByteList());
IRubyObject[] ret = new IRubyObject[4];
ret[0] = runtime.newString(addr.getCanonicalHostName());
ret[1] = runtime.newArray();
ret[2] = runtime.newFixnum(2); // AF_INET
ret[3] = runtime.newString(new ByteList(addr.getAddress()));
return runtime.newArrayNoCopy(ret);
} catch(UnknownHostException e) {
throw sockerr(runtime, "gethostbyname: name or service not known");
}
}
/**
* Ruby definition would look like:
*
* def self.getaddrinfo(host, port, family = nil, socktype = nil, protocol = nil, flags = nil)
*/
public static IRubyObject getaddrinfo(ThreadContext context, IRubyObject[] args) {
Ruby runtime = context.runtime;
IRubyObject host = args[0];
IRubyObject port = args[1];
boolean emptyHost = host.isNil() || host.convertToString().isEmpty();
try {
if(port instanceof RubyString) {
port = getservbyname(context, new IRubyObject[]{port});
}
IRubyObject family = args.length > 2 ? args[2] : context.nil;
IRubyObject socktype = args.length > 3 ? args[3] : context.nil;
//IRubyObject protocol = args[4];
IRubyObject flags = args.length > 5 ? args[5] : context.nil;
boolean is_ipv6 = (! family.isNil()) && (RubyNumeric.fix2int(family) & AF_INET6.intValue()) == AF_INET6.intValue();
boolean sock_stream = true;
boolean sock_dgram = true;
if(!socktype.isNil()) {
int val = RubyNumeric.fix2int(socktype);
if(val == SOCK_STREAM.intValue()) {
sock_dgram = false;
} else if(val == SOCK_DGRAM.intValue()) {
sock_stream = false;
}
}
// When Socket::AI_PASSIVE and host is nil, return 'any' address.
InetAddress[] addrs = null;
if(!flags.isNil() && RubyFixnum.fix2int(flags) > 0) {
// The value of 1 is for Socket::AI_PASSIVE.
int flag = RubyNumeric.fix2int(flags);
if ((flag == 1) && emptyHost ) {
// use RFC 2732 style string to ensure that we get Inet6Address
addrs = InetAddress.getAllByName(is_ipv6 ? "[::]" : "0.0.0.0");
}
}
if (addrs == null) {
addrs = InetAddress.getAllByName(emptyHost ? (is_ipv6 ? "[::1]" : null) : host.convertToString().toString());
}
List l = new ArrayList();
for(int i = 0; i < addrs.length; i++) {
IRubyObject[] c;
if(sock_dgram) {
c = new IRubyObject[7];
c[0] = runtime.newString(is_ipv6 ? "AF_INET6" : "AF_INET");
c[1] = port;
c[2] = runtime.newString(getHostAddress(context, addrs[i]));
c[3] = runtime.newString(addrs[i].getHostAddress());
c[4] = runtime.newFixnum(is_ipv6 ? PF_INET6 : PF_INET);
c[5] = runtime.newFixnum(SOCK_DGRAM);
c[6] = runtime.newFixnum(IPPROTO_UDP);
l.add(runtime.newArrayNoCopy(c));
}
if(sock_stream) {
c = new IRubyObject[7];
c[0] = runtime.newString(is_ipv6 ? "AF_INET6" : "AF_INET");
c[1] = port;
c[2] = runtime.newString(getHostAddress(context, addrs[i]));
c[3] = runtime.newString(addrs[i].getHostAddress());
c[4] = runtime.newFixnum(is_ipv6 ? PF_INET6 : PF_INET);
c[5] = runtime.newFixnum(SOCK_STREAM);
c[6] = runtime.newFixnum(IPPROTO_TCP);
l.add(runtime.newArrayNoCopy(c));
}
}
return runtime.newArray(l);
} catch(UnknownHostException e) {
throw sockerr(runtime, "getaddrinfo: name or service not known");
}
}
public static IRubyObject getnameinfo(ThreadContext context, IRubyObject[] args) {
Ruby runtime = context.runtime;
int flags = args.length == 2 ? RubyNumeric.num2int(args[1]) : 0;
IRubyObject arg0 = args[0];
String host, port;
if (arg0 instanceof RubyArray) {
List list = ((RubyArray)arg0).getList();
int len = list.size();
if (len < 3 || len > 4) {
throw runtime.newArgumentError("array size should be 3 or 4, "+len+" given");
}
// if array has 4 elements, third element is ignored
host = list.size() == 3 ? list.get(2).toString() : list.get(3).toString();
port = list.get(1).toString();
} else if (arg0 instanceof RubyString) {
String arg = ((RubyString)arg0).toString();
Matcher m = STRING_IPV4_ADDRESS_PATTERN.matcher(arg);
if (!m.matches()) {
IRubyObject obj = unpack_sockaddr_in(context, arg0);
if (obj instanceof RubyArray) {
List list = ((RubyArray)obj).getList();
int len = list.size();
if (len != 2) {
throw runtime.newArgumentError("invalid address representation");
}
host = list.get(1).toString();
port = list.get(0).toString();
} else {
throw runtime.newArgumentError("invalid address string");
}
} else if ((host = m.group(IPV4_HOST_GROUP)) == null || host.length() == 0 ||
(port = m.group(IPV4_PORT_GROUP)) == null || port.length() == 0) {
throw runtime.newArgumentError("invalid address string");
} else {
// Try IPv6
try {
InetAddress ipv6_addr = InetAddress.getByName(host);
if (ipv6_addr instanceof Inet6Address) {
host = ipv6_addr.getHostAddress();
}
} catch (UnknownHostException uhe) {
throw runtime.newArgumentError("invalid address string");
}
}
} else {
throw runtime.newArgumentError("invalid args");
}
InetAddress addr;
try {
addr = InetAddress.getByName(host);
} catch (UnknownHostException e) {
throw sockerr(runtime, "unknown host: "+ host);
}
if ((flags & NI_NUMERICHOST.intValue()) == 0) {
host = addr.getCanonicalHostName();
} else {
host = addr.getHostAddress();
}
jnr.netdb.Service serv = jnr.netdb.Service.getServiceByPort(Integer.parseInt(port), null);
if (serv != null) {
if ((flags & NI_NUMERICSERV.intValue()) == 0) {
port = serv.getName();
} else {
port = Integer.toString(serv.getPort());
}
}
return runtime.newArray(runtime.newString(host), runtime.newString(port));
}
public static IRubyObject ip_address_list(ThreadContext context) {
Ruby runtime = context.runtime;
try {
RubyArray list = RubyArray.newArray(runtime);
RubyClass addrInfoCls = runtime.getClass("Addrinfo");
for (Enumeration networkIfcs = NetworkInterface.getNetworkInterfaces() ; networkIfcs.hasMoreElements() ; ) {
for (Enumeration addresses = networkIfcs.nextElement().getInetAddresses() ; addresses.hasMoreElements() ; ) {
list.append(new Addrinfo(runtime, addrInfoCls, addresses.nextElement()));
}
}
return list;
} catch (SocketException se) {
throw sockerr(runtime, se.getLocalizedMessage());
}
}
public static InetAddress getRubyInetAddress(ByteList address) throws UnknownHostException {
// switched to String because the ByteLists were not comparing properly in 1.9 mode (encoding?
String addressString = address.toString();
if (addressString.equals(BROADCAST)) {
return InetAddress.getByAddress(INADDR_BROADCAST);
} else if (addressString.equals(ANY)) {
return InetAddress.getByAddress(INADDR_ANY);
} else {
return InetAddress.getByName(address.toString());
}
}
public static IRubyObject getaddress(ThreadContext context, IRubyObject hostname) {
try {
return context.runtime.newString(InetAddress.getByName(hostname.convertToString().toString()).getHostAddress());
} catch(UnknownHostException e) {
throw sockerr(context.runtime, "getaddress: name or service not known");
}
}
public static RuntimeException sockerr(Ruby runtime, String msg) {
return new RaiseException(runtime, runtime.getClass("SocketError"), msg, true);
}
public static int getPortFrom(ThreadContext context, IRubyObject _port) {
int port;
if (_port instanceof RubyInteger) {
port = RubyNumeric.fix2int(_port);
} else {
IRubyObject portString = _port.convertToString();
IRubyObject portInteger = portString.convertToInteger( "to_i");
port = RubyNumeric.fix2int(portInteger);
if (port <= 0) {
port = RubyNumeric.fix2int(RubySocket.getservbyname(
context, context.runtime.getObject(), new IRubyObject[]{portString}));
}
}
return port;
}
private static String getHostAddress(ThreadContext context, InetAddress addr) {
return context.runtime.isDoNotReverseLookupEnabled() ? addr.getHostAddress() : addr.getCanonicalHostName();
}
private static final Pattern STRING_IPV4_ADDRESS_PATTERN =
Pattern.compile("((.*)\\/)?([\\.0-9]+)(:([0-9]+))?");
private static final int IPV4_HOST_GROUP = 3;
private static final int IPV4_PORT_GROUP = 5;
private static final String BROADCAST = "";
private static final byte[] INADDR_BROADCAST = new byte[] {-1,-1,-1,-1}; // 255.255.255.255
private static final String ANY = "";
private static final byte[] INADDR_ANY = new byte[] {0,0,0,0}; // 0.0.0.0
static AddressFamily addressFamilyFromArg(IRubyObject domain) {
AddressFamily addressFamily = null;
if(domain instanceof RubyString || domain instanceof RubySymbol) {
String domainString = domain.toString();
addressFamily = AddressFamily.valueOf("AF_" + domainString);
} else {
int domainInt = RubyNumeric.fix2int(domain);
addressFamily = AddressFamily.valueOf(domainInt);
}
return addressFamily;
}
static Sock sockFromArg(IRubyObject type) {
Sock sockType = null;
if(type instanceof RubyString || type instanceof RubySymbol) {
String typeString = type.toString();
sockType = Sock.valueOf("SOCK_" + typeString);
} else {
int typeInt = RubyNumeric.fix2int(type);
sockType = Sock.valueOf(typeInt);
}
return sockType;
}
static ProtocolFamily protocolFamilyFromArg(IRubyObject protocol) {
ProtocolFamily protocolFamily = null;
if(protocol instanceof RubyString || protocol instanceof RubySymbol) {
String protocolString = protocol.toString();
protocolFamily = ProtocolFamily.valueOf("PF_" + protocolString);
} else {
int protocolInt = RubyNumeric.fix2int(protocol);
if (protocolInt == 0) return null;
protocolFamily = ProtocolFamily.valueOf(protocolInt);
}
return protocolFamily;
}
public static int portToInt(IRubyObject port) {
return port.isNil() ? 0 : RubyNumeric.fix2int(port);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy