PageRenderTime 42ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

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

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