VirtualBox

source: vbox/trunk/src/VBox/Devices/Network/slirp/socket.c@ 3021

Last change on this file since 3021 was 1824, checked in by vboxsync, 18 years ago

Disable NAT UDP broadcast resend to local address. Removed the inline declarations and the useless _MSC_VER checks. And eliminated a few compiler warnings.

  • Property svn:eol-style set to native
File size: 17.3 KB
Line 
1/*
2 * Copyright (c) 1995 Danny Gasparovski.
3 *
4 * Please read the file COPYRIGHT for the
5 * terms and conditions of the copyright.
6 */
7
8#define WANT_SYS_IOCTL_H
9#include <slirp.h>
10#include "ip_icmp.h"
11#include "main.h"
12#ifdef __sun__
13#include <sys/filio.h>
14#endif
15
16void
17so_init()
18{
19 /* Nothing yet */
20}
21
22
23struct socket *
24solookup(head, laddr, lport, faddr, fport)
25 struct socket *head;
26 struct in_addr laddr;
27 u_int lport;
28 struct in_addr faddr;
29 u_int fport;
30{
31 struct socket *so;
32
33 for (so = head->so_next; so != head; so = so->so_next) {
34 if (so->so_lport == lport &&
35 so->so_laddr.s_addr == laddr.s_addr &&
36 so->so_faddr.s_addr == faddr.s_addr &&
37 so->so_fport == fport)
38 break;
39 }
40
41 if (so == head)
42 return (struct socket *)NULL;
43 return so;
44
45}
46
47/*
48 * Create a new socket, initialise the fields
49 * It is the responsibility of the caller to
50 * insque() it into the correct linked-list
51 */
52struct socket *
53socreate()
54{
55 struct socket *so;
56
57 so = (struct socket *)malloc(sizeof(struct socket));
58 if(so) {
59 memset(so, 0, sizeof(struct socket));
60 so->so_state = SS_NOFDREF;
61 so->s = -1;
62 }
63 return(so);
64}
65
66/*
67 * remque and free a socket, clobber cache
68 */
69void
70sofree(PNATState pData, struct socket *so)
71{
72 if (so->so_emu==EMU_RSH && so->extra) {
73 sofree(pData, so->extra);
74 so->extra=NULL;
75 }
76 if (so == tcp_last_so)
77 tcp_last_so = &tcb;
78 else if (so == udp_last_so)
79 udp_last_so = &udb;
80
81 m_free(pData, so->so_m);
82
83 if(so->so_next && so->so_prev)
84 remque(pData, so); /* crashes if so is not in a queue */
85
86 free(so);
87}
88
89/*
90 * Read from so's socket into sb_snd, updating all relevant sbuf fields
91 * NOTE: This will only be called if it is select()ed for reading, so
92 * a read() of 0 (or less) means it's disconnected
93 */
94int
95soread(PNATState pData, struct socket *so)
96{
97 int n, nn, lss, total;
98 struct sbuf *sb = &so->so_snd;
99 int len = sb->sb_datalen - sb->sb_cc;
100 struct iovec iov[2];
101 int mss = so->so_tcpcb->t_maxseg;
102
103 DEBUG_CALL("soread");
104 DEBUG_ARG("so = %lx", (long )so);
105
106 /*
107 * No need to check if there's enough room to read.
108 * soread wouldn't have been called if there weren't
109 */
110
111 len = sb->sb_datalen - sb->sb_cc;
112
113 iov[0].iov_base = sb->sb_wptr;
114 if (sb->sb_wptr < sb->sb_rptr) {
115 iov[0].iov_len = sb->sb_rptr - sb->sb_wptr;
116 /* Should never succeed, but... */
117 if (iov[0].iov_len > len)
118 iov[0].iov_len = len;
119 if (iov[0].iov_len > mss)
120 iov[0].iov_len -= iov[0].iov_len%mss;
121 n = 1;
122 } else {
123 iov[0].iov_len = (sb->sb_data + sb->sb_datalen) - sb->sb_wptr;
124 /* Should never succeed, but... */
125 if (iov[0].iov_len > len) iov[0].iov_len = len;
126 len -= iov[0].iov_len;
127 if (len) {
128 iov[1].iov_base = sb->sb_data;
129 iov[1].iov_len = sb->sb_rptr - sb->sb_data;
130 if(iov[1].iov_len > len)
131 iov[1].iov_len = len;
132 total = iov[0].iov_len + iov[1].iov_len;
133 if (total > mss) {
134 lss = total%mss;
135 if (iov[1].iov_len > lss) {
136 iov[1].iov_len -= lss;
137 n = 2;
138 } else {
139 lss -= iov[1].iov_len;
140 iov[0].iov_len -= lss;
141 n = 1;
142 }
143 } else
144 n = 2;
145 } else {
146 if (iov[0].iov_len > mss)
147 iov[0].iov_len -= iov[0].iov_len%mss;
148 n = 1;
149 }
150 }
151
152#ifdef HAVE_READV
153 nn = readv(so->s, (struct iovec *)iov, n);
154 DEBUG_MISC((dfd, " ... read nn = %d bytes\n", nn));
155#else
156 nn = recv(so->s, iov[0].iov_base, iov[0].iov_len,0);
157#endif
158 if (nn <= 0) {
159 if (nn < 0 && (errno == EINTR || errno == EAGAIN))
160 return 0;
161 else {
162 DEBUG_MISC((dfd, " --- soread() disconnected, nn = %d, errno = %d-%s\n", nn, errno,strerror(errno)));
163 sofcantrcvmore(so);
164 tcp_sockclosed(pData, sototcpcb(so));
165 return -1;
166 }
167 }
168
169#ifndef HAVE_READV
170 /*
171 * If there was no error, try and read the second time round
172 * We read again if n = 2 (ie, there's another part of the buffer)
173 * and we read as much as we could in the first read
174 * We don't test for <= 0 this time, because there legitimately
175 * might not be any more data (since the socket is non-blocking),
176 * a close will be detected on next iteration.
177 * A return of -1 wont (shouldn't) happen, since it didn't happen above
178 */
179 if (n == 2 && nn == iov[0].iov_len) {
180 int ret;
181 ret = recv(so->s, iov[1].iov_base, iov[1].iov_len,0);
182 if (ret > 0)
183 nn += ret;
184 }
185
186 DEBUG_MISC((dfd, " ... read nn = %d bytes\n", nn));
187#endif
188
189 /* Update fields */
190 sb->sb_cc += nn;
191 sb->sb_wptr += nn;
192 if (sb->sb_wptr >= (sb->sb_data + sb->sb_datalen))
193 sb->sb_wptr -= sb->sb_datalen;
194 return nn;
195}
196
197/*
198 * Get urgent data
199 *
200 * When the socket is created, we set it SO_OOBINLINE,
201 * so when OOB data arrives, we soread() it and everything
202 * in the send buffer is sent as urgent data
203 */
204void
205sorecvoob(PNATState pData, struct socket *so)
206{
207 struct tcpcb *tp = sototcpcb(so);
208
209 DEBUG_CALL("sorecvoob");
210 DEBUG_ARG("so = %lx", (long)so);
211
212 /*
213 * We take a guess at how much urgent data has arrived.
214 * In most situations, when urgent data arrives, the next
215 * read() should get all the urgent data. This guess will
216 * be wrong however if more data arrives just after the
217 * urgent data, or the read() doesn't return all the
218 * urgent data.
219 */
220 soread(pData, so);
221 tp->snd_up = tp->snd_una + so->so_snd.sb_cc;
222 tp->t_force = 1;
223 tcp_output(pData, tp);
224 tp->t_force = 0;
225}
226
227/*
228 * Send urgent data
229 * There's a lot duplicated code here, but...
230 */
231int
232sosendoob(so)
233 struct socket *so;
234{
235 struct sbuf *sb = &so->so_rcv;
236 char buff[2048]; /* XXX Shouldn't be sending more oob data than this */
237
238 int n, len;
239
240 DEBUG_CALL("sosendoob");
241 DEBUG_ARG("so = %lx", (long)so);
242 DEBUG_ARG("sb->sb_cc = %d", sb->sb_cc);
243
244 if (so->so_urgc > 2048)
245 so->so_urgc = 2048; /* XXXX */
246
247 if (sb->sb_rptr < sb->sb_wptr) {
248 /* We can send it directly */
249 n = send(so->s, sb->sb_rptr, so->so_urgc, (MSG_OOB)); /* |MSG_DONTWAIT)); */
250 so->so_urgc -= n;
251
252 DEBUG_MISC((dfd, " --- sent %d bytes urgent data, %d urgent bytes left\n", n, so->so_urgc));
253 } else {
254 /*
255 * Since there's no sendv or sendtov like writev,
256 * we must copy all data to a linear buffer then
257 * send it all
258 */
259 len = (sb->sb_data + sb->sb_datalen) - sb->sb_rptr;
260 if (len > so->so_urgc) len = so->so_urgc;
261 memcpy(buff, sb->sb_rptr, len);
262 so->so_urgc -= len;
263 if (so->so_urgc) {
264 n = sb->sb_wptr - sb->sb_data;
265 if (n > so->so_urgc) n = so->so_urgc;
266 memcpy((buff + len), sb->sb_data, n);
267 so->so_urgc -= n;
268 len += n;
269 }
270 n = send(so->s, buff, len, (MSG_OOB)); /* |MSG_DONTWAIT)); */
271#ifdef DEBUG
272 if (n != len)
273 DEBUG_ERROR((dfd, "Didn't send all data urgently XXXXX\n"));
274#endif
275 DEBUG_MISC((dfd, " ---2 sent %d bytes urgent data, %d urgent bytes left\n", n, so->so_urgc));
276 }
277
278 sb->sb_cc -= n;
279 sb->sb_rptr += n;
280 if (sb->sb_rptr >= (sb->sb_data + sb->sb_datalen))
281 sb->sb_rptr -= sb->sb_datalen;
282
283 return n;
284}
285
286/*
287 * Write data from so_rcv to so's socket,
288 * updating all sbuf field as necessary
289 */
290int
291sowrite(PNATState pData, struct socket *so)
292{
293 int n,nn;
294 struct sbuf *sb = &so->so_rcv;
295 int len = sb->sb_cc;
296 struct iovec iov[2];
297
298 DEBUG_CALL("sowrite");
299 DEBUG_ARG("so = %lx", (long)so);
300
301 if (so->so_urgc) {
302 sosendoob(so);
303 if (sb->sb_cc == 0)
304 return 0;
305 }
306
307 /*
308 * No need to check if there's something to write,
309 * sowrite wouldn't have been called otherwise
310 */
311
312 len = sb->sb_cc;
313
314 iov[0].iov_base = sb->sb_rptr;
315 if (sb->sb_rptr < sb->sb_wptr) {
316 iov[0].iov_len = sb->sb_wptr - sb->sb_rptr;
317 /* Should never succeed, but... */
318 if (iov[0].iov_len > len) iov[0].iov_len = len;
319 n = 1;
320 } else {
321 iov[0].iov_len = (sb->sb_data + sb->sb_datalen) - sb->sb_rptr;
322 if (iov[0].iov_len > len) iov[0].iov_len = len;
323 len -= iov[0].iov_len;
324 if (len) {
325 iov[1].iov_base = sb->sb_data;
326 iov[1].iov_len = sb->sb_wptr - sb->sb_data;
327 if (iov[1].iov_len > len) iov[1].iov_len = len;
328 n = 2;
329 } else
330 n = 1;
331 }
332 /* Check if there's urgent data to send, and if so, send it */
333
334#ifdef HAVE_READV
335 nn = writev(so->s, (const struct iovec *)iov, n);
336
337 DEBUG_MISC((dfd, " ... wrote nn = %d bytes\n", nn));
338#else
339 nn = send(so->s, iov[0].iov_base, iov[0].iov_len,0);
340#endif
341 /* This should never happen, but people tell me it does *shrug* */
342 if (nn < 0 && (errno == EAGAIN || errno == EINTR))
343 return 0;
344
345 if (nn <= 0) {
346 DEBUG_MISC((dfd, " --- sowrite disconnected, so->so_state = %x, errno = %d\n",
347 so->so_state, errno));
348 sofcantsendmore(so);
349 tcp_sockclosed(pData, sototcpcb(so));
350 return -1;
351 }
352
353#ifndef HAVE_READV
354 if (n == 2 && nn == iov[0].iov_len) {
355 int ret;
356 ret = send(so->s, iov[1].iov_base, iov[1].iov_len,0);
357 if (ret > 0)
358 nn += ret;
359 }
360 DEBUG_MISC((dfd, " ... wrote nn = %d bytes\n", nn));
361#endif
362
363 /* Update sbuf */
364 sb->sb_cc -= nn;
365 sb->sb_rptr += nn;
366 if (sb->sb_rptr >= (sb->sb_data + sb->sb_datalen))
367 sb->sb_rptr -= sb->sb_datalen;
368
369 /*
370 * If in DRAIN mode, and there's no more data, set
371 * it CANTSENDMORE
372 */
373 if ((so->so_state & SS_FWDRAIN) && sb->sb_cc == 0)
374 sofcantsendmore(so);
375
376 return nn;
377}
378
379/*
380 * recvfrom() a UDP socket
381 */
382void
383sorecvfrom(PNATState pData, struct socket *so)
384{
385 struct sockaddr_in addr;
386 socklen_t addrlen = sizeof(struct sockaddr_in);
387
388 DEBUG_CALL("sorecvfrom");
389 DEBUG_ARG("so = %lx", (long)so);
390
391 if (so->so_type == IPPROTO_ICMP) { /* This is a "ping" reply */
392 char buff[256];
393 int len;
394
395 len = recvfrom(so->s, buff, 256, 0,
396 (struct sockaddr *)&addr, &addrlen);
397 /* XXX Check if reply is "correct"? */
398
399 if(len == -1 || len == 0) {
400 u_char code=ICMP_UNREACH_PORT;
401
402 if(errno == EHOSTUNREACH) code=ICMP_UNREACH_HOST;
403 else if(errno == ENETUNREACH) code=ICMP_UNREACH_NET;
404
405 DEBUG_MISC((dfd," udp icmp rx errno = %d-%s\n",
406 errno,strerror(errno)));
407 icmp_error(pData, so->so_m, ICMP_UNREACH,code, 0,strerror(errno));
408 } else {
409 icmp_reflect(pData, so->so_m);
410 so->so_m = 0; /* Don't m_free() it again! */
411 }
412 /* No need for this socket anymore, udp_detach it */
413 udp_detach(pData, so);
414 } else { /* A "normal" UDP packet */
415 struct mbuf *m;
416 int len, n;
417
418 if (!(m = m_get(pData))) return;
419 m->m_data += if_maxlinkhdr;
420
421 /*
422 * XXX Shouldn't FIONREAD packets destined for port 53,
423 * but I don't know the max packet size for DNS lookups
424 */
425 len = M_FREEROOM(m);
426 /* if (so->so_fport != htons(53)) { */
427 ioctlsocket(so->s, FIONREAD, &n);
428
429 if (n > len) {
430 n = (m->m_data - m->m_dat) + m->m_len + n + 1;
431 m_inc(m, n);
432 len = M_FREEROOM(m);
433 }
434 /* } */
435
436 m->m_len = recvfrom(so->s, m->m_data, len, 0,
437 (struct sockaddr *)&addr, &addrlen);
438 DEBUG_MISC((dfd, " did recvfrom %d, errno = %d-%s\n",
439 m->m_len, errno,strerror(errno)));
440 if(m->m_len<0) {
441 u_char code=ICMP_UNREACH_PORT;
442
443 if(errno == EHOSTUNREACH) code=ICMP_UNREACH_HOST;
444 else if(errno == ENETUNREACH) code=ICMP_UNREACH_NET;
445
446 DEBUG_MISC((dfd," rx error, tx icmp ICMP_UNREACH:%i\n", code));
447 icmp_error(pData, so->so_m, ICMP_UNREACH,code, 0,strerror(errno));
448 m_free(pData, m);
449 } else {
450 /*
451 * Hack: domain name lookup will be used the most for UDP,
452 * and since they'll only be used once there's no need
453 * for the 4 minute (or whatever) timeout... So we time them
454 * out much quicker (10 seconds for now...)
455 */
456 if (so->so_expire) {
457 if (so->so_fport == htons(53))
458 so->so_expire = curtime + SO_EXPIREFAST;
459 else
460 so->so_expire = curtime + SO_EXPIRE;
461 }
462
463 /* if (m->m_len == len) {
464 * m_inc(m, MINCSIZE);
465 * m->m_len = 0;
466 * }
467 */
468
469 /*
470 * If this packet was destined for CTL_ADDR,
471 * make it look like that's where it came from, done by udp_output
472 */
473 udp_output(pData, so, m, &addr);
474 } /* rx error */
475 } /* if ping packet */
476}
477
478/*
479 * sendto() a socket
480 */
481int
482sosendto(PNATState pData, struct socket *so, struct mbuf *m)
483{
484 int ret;
485 struct sockaddr_in addr;
486 struct sockaddr_in host_addr;
487
488 DEBUG_CALL("sosendto");
489 DEBUG_ARG("so = %lx", (long)so);
490 DEBUG_ARG("m = %lx", (long)m);
491
492 addr.sin_family = AF_INET;
493 if ((so->so_faddr.s_addr & htonl(0xffffff00)) == special_addr.s_addr) {
494 /* It's an alias */
495 switch(ntohl(so->so_faddr.s_addr) & 0xff) {
496 case CTL_BROADCAST:
497 addr.sin_addr.s_addr = INADDR_BROADCAST;
498#if 0
499 /* Send the packet to host to fully emulate broadcast */
500 /** @todo r=klaus: on Linux host this causes the host to receive
501 * the packet twice for some reason. And I cannot find any place
502 * in the man pages which states that sending a broadcast does not
503 * reach the host itself. */
504 host_addr.sin_family = AF_INET;
505 host_addr.sin_port = so->so_fport;
506 host_addr.sin_addr = our_addr;
507 sendto(so->s, m->m_data, m->m_len, 0,
508 (struct sockaddr *)&host_addr, sizeof (struct sockaddr));
509#endif
510 break;
511 case CTL_DNS:
512 addr.sin_addr = dns_addr;
513 break;
514 case CTL_ALIAS:
515 default:
516 addr.sin_addr = loopback_addr;
517 break;
518 }
519 } else
520 addr.sin_addr = so->so_faddr;
521 addr.sin_port = so->so_fport;
522
523 DEBUG_MISC((dfd, " sendto()ing, addr.sin_port=%d, addr.sin_addr.s_addr=%.16s\n", ntohs(addr.sin_port), inet_ntoa(addr.sin_addr)));
524
525 /* Don't care what port we get */
526 ret = sendto(so->s, m->m_data, m->m_len, 0,
527 (struct sockaddr *)&addr, sizeof (struct sockaddr));
528 if (ret < 0)
529 return -1;
530
531 /*
532 * Kill the socket if there's no reply in 4 minutes,
533 * but only if it's an expirable socket
534 */
535 if (so->so_expire)
536 so->so_expire = curtime + SO_EXPIRE;
537 so->so_state = SS_ISFCONNECTED; /* So that it gets select()ed */
538 return 0;
539}
540
541/*
542 * XXX This should really be tcp_listen
543 */
544struct socket *
545solisten(PNATState pData, u_int port, u_int32_t laddr, u_int lport, int flags)
546{
547 struct sockaddr_in addr;
548 struct socket *so;
549 socklen_t addrlen = sizeof(addr);
550 int s, opt = 1;
551
552 DEBUG_CALL("solisten");
553 DEBUG_ARG("port = %d", port);
554 DEBUG_ARG("laddr = %x", laddr);
555 DEBUG_ARG("lport = %d", lport);
556 DEBUG_ARG("flags = %x", flags);
557
558 if ((so = socreate()) == NULL) {
559 /* free(so); Not sofree() ??? free(NULL) == NOP */
560 return NULL;
561 }
562
563 /* Don't tcp_attach... we don't need so_snd nor so_rcv */
564 if ((so->so_tcpcb = tcp_newtcpcb(pData, so)) == NULL) {
565 free(so);
566 return NULL;
567 }
568 insque(pData, so,&tcb);
569
570 /*
571 * SS_FACCEPTONCE sockets must time out.
572 */
573 if (flags & SS_FACCEPTONCE)
574 so->so_tcpcb->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT*2;
575
576 so->so_state = (SS_FACCEPTCONN|flags);
577 so->so_lport = lport; /* Kept in network format */
578 so->so_laddr.s_addr = laddr; /* Ditto */
579
580 addr.sin_family = AF_INET;
581 addr.sin_addr.s_addr = INADDR_ANY;
582 addr.sin_port = port;
583
584 if (((s = socket(AF_INET,SOCK_STREAM,0)) < 0) ||
585 (setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char *)&opt,sizeof(int)) < 0) ||
586 (bind(s,(struct sockaddr *)&addr, sizeof(addr)) < 0) ||
587 (listen(s,1) < 0)) {
588#ifdef __WIN__
589 int tmperrno = WSAGetLastError(); /* Don't clobber the real reason we failed */
590 closesocket(s);
591 sofree(pData, so);
592 /* Restore the real errno */
593 WSASetLastError(tmperrno);
594#else
595 int tmperrno = errno; /* Don't clobber the real reason we failed */
596 close(s);
597 sofree(pData, so);
598 /* Restore the real errno */
599 errno = tmperrno;
600#endif
601 return NULL;
602 }
603 setsockopt(s,SOL_SOCKET,SO_OOBINLINE,(char *)&opt,sizeof(int));
604
605 getsockname(s,(struct sockaddr *)&addr,&addrlen);
606 so->so_fport = addr.sin_port;
607 if (addr.sin_addr.s_addr == 0 || addr.sin_addr.s_addr == loopback_addr.s_addr)
608 so->so_faddr = alias_addr;
609 else
610 so->so_faddr = addr.sin_addr;
611
612 so->s = s;
613 return so;
614}
615
616/*
617 * Data is available in so_rcv
618 * Just write() the data to the socket
619 * XXX not yet...
620 */
621void
622sorwakeup(so)
623 struct socket *so;
624{
625/* sowrite(so); */
626/* FD_CLR(so->s,&writefds); */
627}
628
629/*
630 * Data has been freed in so_snd
631 * We have room for a read() if we want to
632 * For now, don't read, it'll be done in the main loop
633 */
634void
635sowwakeup(so)
636 struct socket *so;
637{
638 /* Nothing, yet */
639}
640
641/*
642 * Various session state calls
643 * XXX Should be #define's
644 * The socket state stuff needs work, these often get call 2 or 3
645 * times each when only 1 was needed
646 */
647void
648soisfconnecting(so)
649 register struct socket *so;
650{
651 so->so_state &= ~(SS_NOFDREF|SS_ISFCONNECTED|SS_FCANTRCVMORE|
652 SS_FCANTSENDMORE|SS_FWDRAIN);
653 so->so_state |= SS_ISFCONNECTING; /* Clobber other states */
654}
655
656void
657soisfconnected(so)
658 register struct socket *so;
659{
660 so->so_state &= ~(SS_ISFCONNECTING|SS_FWDRAIN|SS_NOFDREF);
661 so->so_state |= SS_ISFCONNECTED; /* Clobber other states */
662}
663
664void
665sofcantrcvmore(so)
666 struct socket *so;
667{
668 if ((so->so_state & SS_NOFDREF) == 0) {
669 shutdown(so->s,0);
670 }
671 so->so_state &= ~(SS_ISFCONNECTING);
672 if (so->so_state & SS_FCANTSENDMORE)
673 so->so_state = SS_NOFDREF; /* Don't select it */ /* XXX close() here as well? */
674 else
675 so->so_state |= SS_FCANTRCVMORE;
676}
677
678void
679sofcantsendmore(so)
680 struct socket *so;
681{
682 if ((so->so_state & SS_NOFDREF) == 0) {
683 shutdown(so->s,1); /* send FIN to fhost */
684 }
685 so->so_state &= ~(SS_ISFCONNECTING);
686 if (so->so_state & SS_FCANTRCVMORE)
687 so->so_state = SS_NOFDREF; /* as above */
688 else
689 so->so_state |= SS_FCANTSENDMORE;
690}
691
692void
693soisfdisconnected(so)
694 struct socket *so;
695{
696/* so->so_state &= ~(SS_ISFCONNECTING|SS_ISFCONNECTED); */
697/* close(so->s); */
698/* so->so_state = SS_ISFDISCONNECTED; */
699 /*
700 * XXX Do nothing ... ?
701 */
702}
703
704/*
705 * Set write drain mode
706 * Set CANTSENDMORE once all data has been write()n
707 */
708void
709sofwdrain(so)
710 struct socket *so;
711{
712 if (so->so_rcv.sb_cc)
713 so->so_state |= SS_FWDRAIN;
714 else
715 sofcantsendmore(so);
716}
717
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette