PageRenderTime 225ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/src/org/jruby/ext/socket/RubySocket.java

https://github.com/rkh/jruby
Java | 830 lines | 685 code | 88 blank | 57 comment | 131 complexity | ff7ecaedd2952bc32d0c15891a27857e MD5 | raw file
  1. /***** BEGIN LICENSE BLOCK *****
  2. * Version: CPL 1.0/GPL 2.0/LGPL 2.1
  3. *
  4. * The contents of this file are subject to the Common Public
  5. * License Version 1.0 (the "License"); you may not use this file
  6. * except in compliance with the License. You may obtain a copy of
  7. * the License at http://www.eclipse.org/legal/cpl-v10.html
  8. *
  9. * Software distributed under the License is distributed on an "AS
  10. * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  11. * implied. See the License for the specific language governing
  12. * rights and limitations under the License.
  13. *
  14. * Copyright (C) 2007 Ola Bini <ola@ologix.com>
  15. *
  16. * Alternatively, the contents of this file may be used under the terms of
  17. * either of the GNU General Public License Version 2 or later (the "GPL"),
  18. * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  19. * in which case the provisions of the GPL or the LGPL are applicable instead
  20. * of those above. If you wish to allow use of your version of this file only
  21. * under the terms of either the GPL or the LGPL, and not to allow others to
  22. * use your version of this file under the terms of the CPL, indicate your
  23. * decision by deleting the provisions above and replace them with the notice
  24. * and other provisions required by the GPL or the LGPL. If you do not delete
  25. * the provisions above, a recipient may use your version of this file under
  26. * the terms of any one of the CPL, the GPL or the LGPL.
  27. ***** END LICENSE BLOCK *****/
  28. package org.jruby.ext.socket;
  29. import static jnr.constants.platform.AddressFamily.AF_INET;
  30. import static jnr.constants.platform.AddressFamily.AF_INET6;
  31. import static jnr.constants.platform.IPProto.IPPROTO_TCP;
  32. import static jnr.constants.platform.IPProto.IPPROTO_UDP;
  33. import static jnr.constants.platform.NameInfo.NI_NUMERICHOST;
  34. import static jnr.constants.platform.NameInfo.NI_NUMERICSERV;
  35. import static jnr.constants.platform.ProtocolFamily.PF_INET;
  36. import static jnr.constants.platform.ProtocolFamily.PF_INET6;
  37. import static jnr.constants.platform.Sock.SOCK_DGRAM;
  38. import static jnr.constants.platform.Sock.SOCK_STREAM;
  39. import java.io.ByteArrayOutputStream;
  40. import java.io.DataOutputStream;
  41. import java.io.IOException;
  42. import java.net.DatagramSocket;
  43. import java.net.InetAddress;
  44. import java.net.InetSocketAddress;
  45. import java.net.Socket;
  46. import java.net.SocketException;
  47. import java.net.UnknownHostException;
  48. import java.nio.channels.Channel;
  49. import java.nio.channels.DatagramChannel;
  50. import java.nio.channels.SocketChannel;
  51. import java.util.ArrayList;
  52. import java.util.List;
  53. import java.util.regex.Matcher;
  54. import java.util.regex.Pattern;
  55. import org.jruby.Ruby;
  56. import org.jruby.RubyArray;
  57. import org.jruby.RubyClass;
  58. import org.jruby.RubyFixnum;
  59. import org.jruby.RubyModule;
  60. import org.jruby.RubyNumeric;
  61. import org.jruby.RubyString;
  62. import org.jruby.anno.JRubyClass;
  63. import org.jruby.anno.JRubyMethod;
  64. import org.jruby.anno.JRubyModule;
  65. import org.jruby.exceptions.RaiseException;
  66. import org.jruby.platform.Platform;
  67. import org.jruby.runtime.Arity;
  68. import org.jruby.runtime.ObjectAllocator;
  69. import org.jruby.runtime.ThreadContext;
  70. import org.jruby.runtime.builtin.IRubyObject;
  71. import org.jruby.util.ByteList;
  72. import org.jruby.util.io.ChannelDescriptor;
  73. import org.jruby.util.io.InvalidValueException;
  74. import org.jruby.util.io.ModeFlags;
  75. import jnr.constants.platform.AddressFamily;
  76. import jnr.constants.platform.Sock;
  77. import java.net.Inet6Address;
  78. import java.nio.channels.AlreadyConnectedException;
  79. import java.nio.channels.ClosedChannelException;
  80. import java.nio.channels.ConnectionPendingException;
  81. import java.nio.channels.ServerSocketChannel;
  82. import java.nio.channels.spi.AbstractSelectableChannel;
  83. /**
  84. * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
  85. */
  86. @JRubyClass(name="Socket", parent="BasicSocket", include="Socket::Constants")
  87. public class RubySocket extends RubyBasicSocket {
  88. @JRubyClass(name="SocketError", parent="StandardError")
  89. public static class SocketError {}
  90. private static ObjectAllocator SOCKET_ALLOCATOR = new ObjectAllocator() {
  91. public IRubyObject allocate(Ruby runtime, RubyClass klass) {
  92. return new RubySocket(runtime, klass);
  93. }
  94. };
  95. public static final int MSG_OOB = 0x1;
  96. public static final int MSG_PEEK = 0x2;
  97. public static final int MSG_DONTROUTE = 0x4;
  98. @JRubyModule(name="Socket::Constants")
  99. public static class Constants {}
  100. static void createSocket(Ruby runtime) {
  101. RubyClass rb_cSocket = runtime.defineClass("Socket", runtime.getClass("BasicSocket"), SOCKET_ALLOCATOR);
  102. RubyModule rb_mConstants = rb_cSocket.defineModuleUnder("Constants");
  103. // we don't have to define any that we don't support; see socket.c
  104. runtime.loadConstantSet(rb_mConstants, jnr.constants.platform.Sock.class);
  105. runtime.loadConstantSet(rb_mConstants, jnr.constants.platform.SocketOption.class);
  106. runtime.loadConstantSet(rb_mConstants, jnr.constants.platform.SocketLevel.class);
  107. runtime.loadConstantSet(rb_mConstants, jnr.constants.platform.ProtocolFamily.class);
  108. runtime.loadConstantSet(rb_mConstants, jnr.constants.platform.AddressFamily.class);
  109. runtime.loadConstantSet(rb_mConstants, jnr.constants.platform.INAddr.class);
  110. runtime.loadConstantSet(rb_mConstants, jnr.constants.platform.IPProto.class);
  111. runtime.loadConstantSet(rb_mConstants, jnr.constants.platform.Shutdown.class);
  112. runtime.loadConstantSet(rb_mConstants, jnr.constants.platform.TCP.class);
  113. runtime.loadConstantSet(rb_mConstants, jnr.constants.platform.NameInfo.class);
  114. // mandatory constants we haven't implemented
  115. rb_mConstants.setConstant("MSG_OOB", runtime.newFixnum(MSG_OOB));
  116. rb_mConstants.setConstant("MSG_PEEK", runtime.newFixnum(MSG_PEEK));
  117. rb_mConstants.setConstant("MSG_DONTROUTE", runtime.newFixnum(MSG_DONTROUTE));
  118. // constants webrick crashes without
  119. rb_mConstants.setConstant("AI_PASSIVE", runtime.newFixnum(1));
  120. // More constants needed by specs
  121. rb_mConstants.setConstant("IP_MULTICAST_TTL", runtime.newFixnum(10));
  122. rb_mConstants.setConstant("IP_MULTICAST_LOOP", runtime.newFixnum(11));
  123. rb_mConstants.setConstant("IP_ADD_MEMBERSHIP", runtime.newFixnum(12));
  124. rb_mConstants.setConstant("IP_MAX_MEMBERSHIPS", runtime.newFixnum(20));
  125. rb_mConstants.setConstant("IP_DEFAULT_MULTICAST_LOOP", runtime.newFixnum(1));
  126. rb_mConstants.setConstant("IP_DEFAULT_MULTICAST_TTL", runtime.newFixnum(1));
  127. rb_cSocket.includeModule(rb_mConstants);
  128. rb_cSocket.defineAnnotatedMethods(RubySocket.class);
  129. }
  130. public RubySocket(Ruby runtime, RubyClass type) {
  131. super(runtime, type);
  132. }
  133. @Override
  134. protected int getSoTypeDefault() {
  135. return soType;
  136. }
  137. private int soDomain;
  138. private int soType;
  139. private int soProtocol;
  140. @Deprecated
  141. public static IRubyObject for_fd(IRubyObject socketClass, IRubyObject fd) {
  142. return for_fd(socketClass.getRuntime().getCurrentContext(), socketClass, fd);
  143. }
  144. @JRubyMethod(meta = true)
  145. public static IRubyObject for_fd(ThreadContext context, IRubyObject socketClass, IRubyObject fd) {
  146. Ruby ruby = context.getRuntime();
  147. if (fd instanceof RubyFixnum) {
  148. RubySocket socket = (RubySocket)((RubyClass)socketClass).allocate();
  149. // normal file descriptor..try to work with it
  150. ChannelDescriptor descriptor = ChannelDescriptor.getDescriptorByFileno((int)((RubyFixnum)fd).getLongValue());
  151. if (descriptor == null) {
  152. throw ruby.newErrnoEBADFError();
  153. }
  154. Channel mainChannel = descriptor.getChannel();
  155. if (mainChannel instanceof SocketChannel) {
  156. // ok, it's a socket...set values accordingly
  157. // just using AF_INET since we can't tell from SocketChannel...
  158. socket.soDomain = AddressFamily.AF_INET.intValue();
  159. socket.soType = Sock.SOCK_STREAM.intValue();
  160. socket.soProtocol = 0;
  161. } else if (mainChannel instanceof DatagramChannel) {
  162. // datagram, set accordingly
  163. // again, AF_INET
  164. socket.soDomain = AddressFamily.AF_INET.intValue();
  165. socket.soType = Sock.SOCK_DGRAM.intValue();
  166. socket.soProtocol = 0;
  167. } else {
  168. throw context.getRuntime().newErrnoENOTSOCKError("can't Socket.new/for_fd against a non-socket");
  169. }
  170. socket.initSocket(ruby, descriptor);
  171. return socket;
  172. } else {
  173. throw context.getRuntime().newTypeError(fd, context.getRuntime().getFixnum());
  174. }
  175. }
  176. @JRubyMethod
  177. public IRubyObject initialize(ThreadContext context, IRubyObject domain, IRubyObject type, IRubyObject protocol) {
  178. try {
  179. if(domain instanceof RubyString) {
  180. String domainString = domain.toString();
  181. if(domainString.equals("AF_INET")) {
  182. soDomain = AF_INET.intValue();
  183. } else if(domainString.equals("PF_INET")) {
  184. soDomain = PF_INET.intValue();
  185. } else {
  186. throw sockerr(context.getRuntime(), "unknown socket domain " + domainString);
  187. }
  188. } else {
  189. soDomain = RubyNumeric.fix2int(domain);
  190. }
  191. if(type instanceof RubyString) {
  192. String typeString = type.toString();
  193. if(typeString.equals("SOCK_STREAM")) {
  194. soType = SOCK_STREAM.intValue();
  195. } else if(typeString.equals("SOCK_DGRAM")) {
  196. soType = SOCK_DGRAM.intValue();
  197. } else {
  198. throw sockerr(context.getRuntime(), "unknown socket type " + typeString);
  199. }
  200. } else {
  201. soType = RubyNumeric.fix2int(type);
  202. }
  203. soProtocol = RubyNumeric.fix2int(protocol);
  204. Channel channel = null;
  205. if(soType == Sock.SOCK_STREAM.intValue()) {
  206. channel = SocketChannel.open();
  207. } else if(soType == Sock.SOCK_DGRAM.intValue()) {
  208. channel = DatagramChannel.open();
  209. }
  210. initSocket(context.getRuntime(), new ChannelDescriptor(channel, new ModeFlags(ModeFlags.RDWR)));
  211. } catch (InvalidValueException ex) {
  212. throw context.getRuntime().newErrnoEINVALError();
  213. } catch(IOException e) {
  214. throw sockerr(context.getRuntime(), "initialize: " + e.toString());
  215. }
  216. return this;
  217. }
  218. private static RuntimeException sockerr(Ruby runtime, String msg) {
  219. return new RaiseException(runtime, runtime.getClass("SocketError"), msg, true);
  220. }
  221. @Deprecated
  222. public static IRubyObject gethostname(IRubyObject recv) {
  223. return gethostname(recv.getRuntime().getCurrentContext(), recv);
  224. }
  225. @JRubyMethod(meta = true)
  226. public static IRubyObject gethostname(ThreadContext context, IRubyObject recv) {
  227. try {
  228. return context.getRuntime().newString(InetAddress.getLocalHost().getHostName());
  229. } catch(UnknownHostException e) {
  230. try {
  231. return context.getRuntime().newString(InetAddress.getByAddress(new byte[]{0,0,0,0}).getHostName());
  232. } catch(UnknownHostException e2) {
  233. throw sockerr(context.getRuntime(), "gethostname: name or service not known");
  234. }
  235. }
  236. }
  237. private static InetAddress intoAddress(Ruby runtime, String s) {
  238. try {
  239. byte[] bs = ByteList.plain(s);
  240. return InetAddress.getByAddress(bs);
  241. } catch(Exception e) {
  242. throw sockerr(runtime, "strtoaddr: " + e.toString());
  243. }
  244. }
  245. private static String intoString(Ruby runtime, InetAddress as) {
  246. try {
  247. return new String(ByteList.plain(as.getAddress()));
  248. } catch(Exception e) {
  249. throw sockerr(runtime, "addrtostr: " + e.toString());
  250. }
  251. }
  252. @Deprecated
  253. public static IRubyObject gethostbyaddr(IRubyObject recv, IRubyObject[] args) {
  254. return gethostbyaddr(recv.getRuntime().getCurrentContext(), recv, args);
  255. }
  256. @JRubyMethod(required = 1, rest = true, meta = true)
  257. public static IRubyObject gethostbyaddr(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  258. Ruby runtime = context.getRuntime();
  259. IRubyObject[] ret = new IRubyObject[4];
  260. ret[0] = runtime.newString(intoAddress(runtime,args[0].convertToString().toString()).getCanonicalHostName());
  261. ret[1] = runtime.newArray();
  262. ret[2] = runtime.newFixnum(2); // AF_INET
  263. ret[3] = args[0];
  264. return runtime.newArrayNoCopy(ret);
  265. }
  266. @Deprecated
  267. public static IRubyObject getservbyname(IRubyObject recv, IRubyObject[] args) {
  268. return getservbyname(recv.getRuntime().getCurrentContext(), recv, args);
  269. }
  270. @JRubyMethod(required = 1, optional = 1, meta = true)
  271. public static IRubyObject getservbyname(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  272. Ruby runtime = context.getRuntime();
  273. int argc = Arity.checkArgumentCount(runtime, args, 1, 2);
  274. String name = args[0].convertToString().toString();
  275. String proto = argc == 1 ? "tcp" : args[1].convertToString().toString();
  276. jnr.netdb.Service service = jnr.netdb.Service.getServiceByName(name, proto);
  277. int port;
  278. if (service != null) {
  279. port = service.getPort();
  280. } else {
  281. // MRI behavior: try to convert the name string to port directly
  282. try {
  283. port = Integer.parseInt(name.trim());
  284. } catch (NumberFormatException nfe) {
  285. throw sockerr(runtime, "no such service " + name + "/" + proto);
  286. }
  287. }
  288. return runtime.newFixnum(port);
  289. }
  290. @JRubyMethod(name = "listen", backtrace = true)
  291. public IRubyObject listen(ThreadContext context, IRubyObject backlog) {
  292. return context.getRuntime().newFixnum(0);
  293. }
  294. @Deprecated
  295. public static IRubyObject pack_sockaddr_un(IRubyObject recv, IRubyObject filename) {
  296. return pack_sockaddr_un(recv.getRuntime().getCurrentContext(), recv, filename);
  297. }
  298. @JRubyMethod(name = {"pack_sockaddr_un", "sockaddr_un"}, meta = true)
  299. public static IRubyObject pack_sockaddr_un(ThreadContext context, IRubyObject recv, IRubyObject filename) {
  300. StringBuilder sb = new StringBuilder();
  301. sb.append((char)0);
  302. sb.append((char)1);
  303. String str = filename.convertToString().toString();
  304. sb.append(str);
  305. for(int i=str.length();i<104;i++) {
  306. sb.append((char)0);
  307. }
  308. return context.getRuntime().newString(sb.toString());
  309. }
  310. @JRubyMethod(backtrace = true)
  311. public IRubyObject connect_nonblock(ThreadContext context, IRubyObject arg) {
  312. Channel socketChannel = getChannel();
  313. try {
  314. if (socketChannel instanceof AbstractSelectableChannel) {
  315. ((AbstractSelectableChannel) socketChannel).configureBlocking(false);
  316. connect(context, arg);
  317. } else {
  318. throw getRuntime().newErrnoENOPROTOOPTError();
  319. }
  320. } catch(ClosedChannelException e) {
  321. throw context.getRuntime().newErrnoECONNREFUSEDError();
  322. } catch(IOException e) {
  323. throw sockerr(context.getRuntime(), "connect(2): name or service not known");
  324. } catch (Error e) {
  325. // Workaround for a bug in Sun's JDK 1.5.x, see
  326. // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6303753
  327. Throwable cause = e.getCause();
  328. if (cause instanceof SocketException) {
  329. handleSocketException(context.getRuntime(), "connect", (SocketException)cause);
  330. } else {
  331. throw e;
  332. }
  333. }
  334. return RubyFixnum.zero(context.getRuntime());
  335. }
  336. @JRubyMethod(backtrace = true)
  337. public IRubyObject connect(ThreadContext context, IRubyObject arg) {
  338. RubyArray sockaddr = (RubyArray) unpack_sockaddr_in(context, this, arg);
  339. try {
  340. IRubyObject addr = sockaddr.pop(context);
  341. IRubyObject port = sockaddr.pop(context);
  342. InetSocketAddress iaddr = new InetSocketAddress(
  343. addr.convertToString().toString(), RubyNumeric.fix2int(port));
  344. Channel socketChannel = getChannel();
  345. if (socketChannel instanceof SocketChannel) {
  346. if(!((SocketChannel) socketChannel).connect(iaddr)) {
  347. throw context.getRuntime().newErrnoEINPROGRESSError();
  348. }
  349. } else if (socketChannel instanceof DatagramChannel) {
  350. ((DatagramChannel)socketChannel).connect(iaddr);
  351. } else {
  352. throw getRuntime().newErrnoENOPROTOOPTError();
  353. }
  354. } catch(AlreadyConnectedException e) {
  355. throw context.getRuntime().newErrnoEISCONNError();
  356. } catch(ConnectionPendingException e) {
  357. Channel socketChannel = getChannel();
  358. if (socketChannel instanceof SocketChannel) {
  359. try {
  360. if (((SocketChannel) socketChannel).finishConnect()) {
  361. throw context.getRuntime().newErrnoEISCONNError();
  362. }
  363. throw context.getRuntime().newErrnoEINPROGRESSError();
  364. } catch (IOException ex) {
  365. throw sockerr(context.getRuntime(), "connect(2): name or service not known");
  366. }
  367. }
  368. throw context.getRuntime().newErrnoEINPROGRESSError();
  369. } catch(UnknownHostException e) {
  370. throw sockerr(context.getRuntime(), "connect(2): unknown host");
  371. } catch(SocketException e) {
  372. handleSocketException(context.getRuntime(), "connect", e);
  373. } catch(IOException e) {
  374. throw sockerr(context.getRuntime(), "connect(2): name or service not known");
  375. } catch (IllegalArgumentException iae) {
  376. throw sockerr(context.getRuntime(), iae.getMessage());
  377. } catch (Error e) {
  378. // Workaround for a bug in Sun's JDK 1.5.x, see
  379. // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6303753
  380. Throwable cause = e.getCause();
  381. if (cause instanceof SocketException) {
  382. handleSocketException(context.getRuntime(), "connect", (SocketException)cause);
  383. } else {
  384. throw e;
  385. }
  386. }
  387. return RubyFixnum.zero(context.getRuntime());
  388. }
  389. @JRubyMethod(backtrace = true)
  390. public IRubyObject bind(ThreadContext context, IRubyObject arg) {
  391. RubyArray sockaddr = (RubyArray) unpack_sockaddr_in(context, this, arg);
  392. try {
  393. IRubyObject addr = sockaddr.pop(context);
  394. IRubyObject port = sockaddr.pop(context);
  395. InetSocketAddress iaddr = new InetSocketAddress(
  396. addr.convertToString().toString(), RubyNumeric.fix2int(port));
  397. Channel socketChannel = getChannel();
  398. if (socketChannel instanceof SocketChannel) {
  399. Socket socket = ((SocketChannel)socketChannel).socket();
  400. socket.bind(iaddr);
  401. } else if (socketChannel instanceof DatagramChannel) {
  402. DatagramSocket socket = ((DatagramChannel)socketChannel).socket();
  403. socket.bind(iaddr);
  404. } else {
  405. throw getRuntime().newErrnoENOPROTOOPTError();
  406. }
  407. } catch(UnknownHostException e) {
  408. throw sockerr(context.getRuntime(), "bind(2): unknown host");
  409. } catch(SocketException e) {
  410. handleSocketException(context.getRuntime(), "bind", e);
  411. } catch(IOException e) {
  412. throw sockerr(context.getRuntime(), "bind(2): name or service not known");
  413. } catch (IllegalArgumentException iae) {
  414. throw sockerr(context.getRuntime(), iae.getMessage());
  415. } catch (Error e) {
  416. // Workaround for a bug in Sun's JDK 1.5.x, see
  417. // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6303753
  418. Throwable cause = e.getCause();
  419. if (cause instanceof SocketException) {
  420. handleSocketException(context.getRuntime(), "bind", (SocketException)cause);
  421. } else {
  422. throw e;
  423. }
  424. }
  425. return RubyFixnum.zero(context.getRuntime());
  426. }
  427. private void handleSocketException(Ruby runtime, String caller, SocketException e) {
  428. // e.printStackTrace();
  429. String msg = formatMessage(e, "bind");
  430. // This is ugly, but what can we do, Java provides the same exception type
  431. // for different situations, so we differentiate the errors
  432. // based on the exception's message.
  433. if (ALREADY_BOUND_PATTERN.matcher(msg).find()) {
  434. throw runtime.newErrnoEINVALError(msg);
  435. } else if (ADDR_NOT_AVAIL_PATTERN.matcher(msg).find()) {
  436. throw runtime.newErrnoEADDRNOTAVAILError(msg);
  437. } else if (PERM_DENIED_PATTERN.matcher(msg).find()) {
  438. throw runtime.newErrnoEACCESError(msg);
  439. } else {
  440. throw runtime.newErrnoEADDRINUSEError(msg);
  441. }
  442. }
  443. private static String formatMessage(Throwable e, String defaultMsg) {
  444. String msg = e.getMessage();
  445. if (msg == null) {
  446. msg = defaultMsg;
  447. } else {
  448. msg = defaultMsg + " - " + msg;
  449. }
  450. return msg;
  451. }
  452. @Deprecated
  453. public static IRubyObject pack_sockaddr_in(IRubyObject recv, IRubyObject port, IRubyObject host) {
  454. return pack_sockaddr_in(recv.getRuntime().getCurrentContext(), recv, port, host);
  455. }
  456. @JRubyMethod(name = {"pack_sockaddr_in", "sockaddr_in"}, meta = true)
  457. public static IRubyObject pack_sockaddr_in(ThreadContext context, IRubyObject recv, IRubyObject port, IRubyObject host) {
  458. return pack_sockaddr_in(context, recv,
  459. RubyNumeric.fix2int(port),
  460. host.isNil() ? null : host.convertToString().toString());
  461. }
  462. public static IRubyObject pack_sockaddr_in(ThreadContext context, IRubyObject recv, int iport, String host) {
  463. ByteArrayOutputStream bufS = new ByteArrayOutputStream();
  464. try {
  465. DataOutputStream ds = new DataOutputStream(bufS);
  466. writeSockaddrHeader(ds);
  467. writeSockaddrPort(ds, iport);
  468. try {
  469. if(host != null && "".equals(host)) {
  470. ds.writeInt(0);
  471. } else {
  472. InetAddress[] addrs = InetAddress.getAllByName(host);
  473. byte[] addr = addrs[0].getAddress();
  474. ds.write(addr, 0, addr.length);
  475. }
  476. } catch (UnknownHostException e) {
  477. throw sockerr(context.getRuntime(), "getaddrinfo: No address associated with nodename");
  478. }
  479. writeSockaddrFooter(ds);
  480. } catch (IOException e) {
  481. throw sockerr(context.getRuntime(), "pack_sockaddr_in: internal error");
  482. }
  483. return context.getRuntime().newString(new ByteList(bufS.toByteArray(),
  484. false));
  485. }
  486. static IRubyObject pack_sockaddr_in(ThreadContext context, InetSocketAddress sock) {
  487. ByteArrayOutputStream bufS = new ByteArrayOutputStream();
  488. try {
  489. DataOutputStream ds = new DataOutputStream(bufS);
  490. writeSockaddrHeader(ds);
  491. writeSockaddrPort(ds, sock);
  492. String host = sock.getAddress().getHostAddress();
  493. if(host != null && "".equals(host)) {
  494. ds.writeInt(0);
  495. } else {
  496. byte[] addr = sock.getAddress().getAddress();
  497. ds.write(addr, 0, addr.length);
  498. }
  499. writeSockaddrFooter(ds);
  500. } catch (IOException e) {
  501. throw sockerr(context.getRuntime(), "pack_sockaddr_in: internal error");
  502. }
  503. return context.getRuntime().newString(new ByteList(bufS.toByteArray(),
  504. false));
  505. }
  506. @Deprecated
  507. public static IRubyObject unpack_sockaddr_in(IRubyObject recv, IRubyObject addr) {
  508. return unpack_sockaddr_in(recv.getRuntime().getCurrentContext(), recv, addr);
  509. }
  510. @JRubyMethod(meta = true)
  511. public static IRubyObject unpack_sockaddr_in(ThreadContext context, IRubyObject recv, IRubyObject addr) {
  512. ByteList val = addr.convertToString().getByteList();
  513. if((Platform.IS_BSD && val.get(0) != 16 && val.get(1) != 2) || (!Platform.IS_BSD && val.get(0) != 2)) {
  514. throw context.getRuntime().newArgumentError("can't resolve socket address of wrong type");
  515. }
  516. int port = ((val.get(2)&0xff) << 8) + (val.get(3)&0xff);
  517. StringBuilder sb = new StringBuilder();
  518. sb.append(val.get(4)&0xff);
  519. sb.append(".");
  520. sb.append(val.get(5)&0xff);
  521. sb.append(".");
  522. sb.append(val.get(6)&0xff);
  523. sb.append(".");
  524. sb.append(val.get(7)&0xff);
  525. IRubyObject[] result = new IRubyObject[]{
  526. context.getRuntime().newFixnum(port),
  527. context.getRuntime().newString(sb.toString())};
  528. return context.getRuntime().newArrayNoCopy(result);
  529. }
  530. private static final ByteList BROADCAST = new ByteList("<broadcast>".getBytes());
  531. private static final byte[] INADDR_BROADCAST = new byte[] {-1,-1,-1,-1}; // 255.255.255.255
  532. private static final ByteList ANY = new ByteList("<any>".getBytes());
  533. private static final byte[] INADDR_ANY = new byte[] {0,0,0,0}; // 0.0.0.0
  534. public static InetAddress getRubyInetAddress(ByteList address) throws UnknownHostException {
  535. if (address.equal(BROADCAST)) {
  536. return InetAddress.getByAddress(INADDR_BROADCAST);
  537. } else if (address.equal(ANY)) {
  538. return InetAddress.getByAddress(INADDR_ANY);
  539. } else {
  540. return InetAddress.getByName(address.toString());
  541. }
  542. }
  543. @Deprecated
  544. public static IRubyObject gethostbyname(IRubyObject recv, IRubyObject hostname) {
  545. return gethostbyname(recv.getRuntime().getCurrentContext(), recv, hostname);
  546. }
  547. @JRubyMethod(meta = true)
  548. public static IRubyObject gethostbyname(ThreadContext context, IRubyObject recv, IRubyObject hostname) {
  549. try {
  550. InetAddress addr = getRubyInetAddress(hostname.convertToString().getByteList());
  551. Ruby runtime = context.getRuntime();
  552. IRubyObject[] ret = new IRubyObject[4];
  553. ret[0] = runtime.newString(addr.getCanonicalHostName());
  554. ret[1] = runtime.newArray();
  555. ret[2] = runtime.newFixnum(2); // AF_INET
  556. ret[3] = runtime.newString(new ByteList(addr.getAddress()));
  557. return runtime.newArrayNoCopy(ret);
  558. } catch(UnknownHostException e) {
  559. throw sockerr(context.getRuntime(), "gethostbyname: name or service not known");
  560. }
  561. }
  562. @Deprecated
  563. public static IRubyObject getaddrinfo(IRubyObject recv, IRubyObject[] args) {
  564. return getaddrinfo(recv.getRuntime().getCurrentContext(), recv, args);
  565. }
  566. //def self.getaddrinfo(host, port, family = nil, socktype = nil, protocol = nil, flags = nil)
  567. @JRubyMethod(required = 2, optional = 4, meta = true)
  568. public static IRubyObject getaddrinfo(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  569. args = Arity.scanArgs(context.getRuntime(),args,2,4);
  570. try {
  571. Ruby r = context.getRuntime();
  572. IRubyObject host = args[0];
  573. IRubyObject port = args[1];
  574. boolean emptyHost = host.isNil() || host.convertToString().isEmpty();
  575. if(port instanceof RubyString) {
  576. port = getservbyname(context, recv, new IRubyObject[]{port});
  577. }
  578. IRubyObject family = args[2];
  579. IRubyObject socktype = args[3];
  580. //IRubyObject protocol = args[4];
  581. IRubyObject flags = args[5];
  582. boolean is_ipv6 = (! family.isNil()) && (RubyNumeric.fix2int(family) & AF_INET6.intValue()) == AF_INET6.intValue();
  583. boolean sock_stream = true;
  584. boolean sock_dgram = true;
  585. if(!socktype.isNil()) {
  586. int val = RubyNumeric.fix2int(socktype);
  587. if(val == SOCK_STREAM.intValue()) {
  588. sock_dgram = false;
  589. } else if(val == SOCK_DGRAM.intValue()) {
  590. sock_stream = false;
  591. }
  592. }
  593. // When Socket::AI_PASSIVE and host is nil, return 'any' address.
  594. InetAddress[] addrs = null;
  595. if(!flags.isNil() && RubyFixnum.fix2int(flags) > 0) {
  596. // The value of 1 is for Socket::AI_PASSIVE.
  597. int flag = RubyNumeric.fix2int(flags);
  598. if ((flag == 1) && emptyHost ) {
  599. // use RFC 2732 style string to ensure that we get Inet6Address
  600. addrs = InetAddress.getAllByName(is_ipv6 ? "[::]" : "0.0.0.0");
  601. }
  602. }
  603. if (addrs == null) {
  604. addrs = InetAddress.getAllByName(emptyHost ? (is_ipv6 ? "[::1]" : null) : host.convertToString().toString());
  605. }
  606. List<IRubyObject> l = new ArrayList<IRubyObject>();
  607. for(int i = 0; i < addrs.length; i++) {
  608. IRubyObject[] c;
  609. if(sock_dgram) {
  610. c = new IRubyObject[7];
  611. c[0] = r.newString(is_ipv6 ? "AF_INET6" : "AF_INET");
  612. c[1] = port;
  613. c[2] = r.newString(getHostAddress(recv, addrs[i]));
  614. c[3] = r.newString(addrs[i].getHostAddress());
  615. c[4] = r.newFixnum(is_ipv6 ? PF_INET6 : PF_INET);
  616. c[5] = r.newFixnum(SOCK_DGRAM);
  617. c[6] = r.newFixnum(IPPROTO_UDP);
  618. l.add(r.newArrayNoCopy(c));
  619. }
  620. if(sock_stream) {
  621. c = new IRubyObject[7];
  622. c[0] = r.newString(is_ipv6 ? "AF_INET6" : "AF_INET");
  623. c[1] = port;
  624. c[2] = r.newString(getHostAddress(recv, addrs[i]));
  625. c[3] = r.newString(addrs[i].getHostAddress());
  626. c[4] = r.newFixnum(is_ipv6 ? PF_INET6 : PF_INET);
  627. c[5] = r.newFixnum(SOCK_STREAM);
  628. c[6] = r.newFixnum(IPPROTO_TCP);
  629. l.add(r.newArrayNoCopy(c));
  630. }
  631. }
  632. return r.newArray(l);
  633. } catch(UnknownHostException e) {
  634. throw sockerr(context.getRuntime(), "getaddrinfo: name or service not known");
  635. }
  636. }
  637. @Deprecated
  638. public static IRubyObject getnameinfo(IRubyObject recv, IRubyObject[] args) {
  639. return getnameinfo(recv.getRuntime().getCurrentContext(), recv, args);
  640. }
  641. @JRubyMethod(required = 1, optional = 1, meta = true)
  642. public static IRubyObject getnameinfo(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
  643. Ruby runtime = context.getRuntime();
  644. int argc = Arity.checkArgumentCount(runtime, args, 1, 2);
  645. int flags = argc == 2 ? RubyNumeric.num2int(args[1]) : 0;
  646. IRubyObject arg0 = args[0];
  647. String host, port;
  648. if (arg0 instanceof RubyArray) {
  649. List list = ((RubyArray)arg0).getList();
  650. int len = list.size();
  651. if (len < 3 || len > 4) {
  652. throw runtime.newArgumentError("array size should be 3 or 4, "+len+" given");
  653. }
  654. // if array has 4 elements, third element is ignored
  655. host = list.size() == 3 ? list.get(2).toString() : list.get(3).toString();
  656. port = list.get(1).toString();
  657. } else if (arg0 instanceof RubyString) {
  658. String arg = ((RubyString)arg0).toString();
  659. Matcher m = STRING_IPV4_ADDRESS_PATTERN.matcher(arg);
  660. if (!m.matches()) {
  661. IRubyObject obj = unpack_sockaddr_in(context, recv, arg0);
  662. if (obj instanceof RubyArray) {
  663. List list = ((RubyArray)obj).getList();
  664. int len = list.size();
  665. if (len != 2) {
  666. throw runtime.newArgumentError("invalid address representation");
  667. }
  668. host = list.get(1).toString();
  669. port = list.get(0).toString();
  670. }
  671. else {
  672. throw runtime.newArgumentError("invalid address string");
  673. }
  674. } else if ((host = m.group(IPV4_HOST_GROUP)) == null || host.length() == 0 ||
  675. (port = m.group(IPV4_PORT_GROUP)) == null || port.length() == 0) {
  676. throw runtime.newArgumentError("invalid address string");
  677. } else {
  678. // Try IPv6
  679. try {
  680. InetAddress ipv6_addr = InetAddress.getByName(host);
  681. if (ipv6_addr instanceof Inet6Address) {
  682. host = ipv6_addr.getHostAddress();
  683. }
  684. } catch (UnknownHostException uhe) {
  685. throw runtime.newArgumentError("invalid address string");
  686. }
  687. }
  688. } else {
  689. throw runtime.newArgumentError("invalid args");
  690. }
  691. InetAddress addr;
  692. try {
  693. addr = InetAddress.getByName(host);
  694. } catch (UnknownHostException e) {
  695. throw sockerr(runtime, "unknown host: "+ host);
  696. }
  697. if ((flags & NI_NUMERICHOST.intValue()) == 0) {
  698. host = addr.getCanonicalHostName();
  699. } else {
  700. host = addr.getHostAddress();
  701. }
  702. jnr.netdb.Service serv = jnr.netdb.Service.getServiceByPort(Integer.parseInt(port), null);
  703. if (serv != null) {
  704. if ((flags & NI_NUMERICSERV.intValue()) == 0) {
  705. port = serv.getName();
  706. } else {
  707. port = Integer.toString(serv.getPort());
  708. }
  709. }
  710. return runtime.newArray(runtime.newString(host), runtime.newString(port));
  711. }
  712. private static String getHostAddress(IRubyObject recv, InetAddress addr) {
  713. return do_not_reverse_lookup(recv).isTrue() ? addr.getHostAddress() : addr.getCanonicalHostName();
  714. }
  715. private static void writeSockaddrHeader(DataOutputStream ds) throws IOException {
  716. if (Platform.IS_BSD) {
  717. ds.write(16);
  718. ds.write(2);
  719. } else {
  720. ds.write(2);
  721. ds.write(0);
  722. }
  723. }
  724. private static void writeSockaddrFooter(DataOutputStream ds) throws IOException {
  725. ds.writeInt(0);
  726. ds.writeInt(0);
  727. }
  728. private static void writeSockaddrPort(DataOutputStream ds, InetSocketAddress sockaddr) throws IOException {
  729. writeSockaddrPort(ds, sockaddr.getPort());
  730. }
  731. private static void writeSockaddrPort(DataOutputStream ds, int port) throws IOException {
  732. ds.write(port >> 8);
  733. ds.write(port);
  734. }
  735. private static final Pattern STRING_IPV4_ADDRESS_PATTERN =
  736. Pattern.compile("((.*)\\/)?([\\.0-9]+)(:([0-9]+))?");
  737. private final static Pattern ALREADY_BOUND_PATTERN = Pattern.compile("[Aa]lready.*bound");
  738. private final static Pattern ADDR_NOT_AVAIL_PATTERN = Pattern.compile("assign.*address");
  739. private final static Pattern PERM_DENIED_PATTERN = Pattern.compile("[Pp]ermission.*denied");
  740. private static final int IPV4_HOST_GROUP = 3;
  741. private static final int IPV4_PORT_GROUP = 5;
  742. }// RubySocket