/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * NL7C (Network Layer 7 Cache) as part of SOCKFS provides an in-kernel * gateway cache for the request/response message based L7 protocol HTTP * (Hypertext Transfer Protocol, see HTTP/1.1 RFC2616) in a semantically * transparent manner. * * Neither the requesting user agent (client, e.g. web browser) nor the * origin server (e.g. webserver) that provided the response cached by * NL7C are impacted in any way. * * Note, currently NL7C only processes HTTP messages via the embedded * URI of scheme http (not https nor any other), additional scheme are * intended to be supported as is practical such that much of the NL7C * framework may appear more general purpose then would be needed just * for an HTTP gateway cache. * * NL7C replaces NCA (Network Cache and Accelerator) and in the future * NCAS (NCA/SSL). * * Further, NL7C uses all NCA configuration files, see "/etc/nca/", the * NCA socket API, "AF_NCA", and "ndd /dev/nca" for backwards compatibility. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * NL7C, NCA, NL7C logger enabled: */ boolean_t nl7c_enabled = B_FALSE; boolean_t nl7c_logd_enabled = B_FALSE; boolean_t nl7c_logd_started = B_FALSE; boolean_t nl7c_logd_cycle = B_TRUE; /* * Some externs: */ extern int inet_pton(int, char *, void *); extern void nl7c_uri_init(void); extern boolean_t nl7c_logd_init(int, caddr_t *); extern void nl7c_nca_init(void); /* * nl7c_addr_t - a singly linked grounded list, pointed to by *nl7caddrs, * constructed at init time by parsing "/etc/nca/ncaport.conf". * * This list is searched at bind(3SOCKET) time when an application doesn't * explicitly set AF_NCA but instead uses AF_INET, if a match is found then * the underlying socket is marked sti_nl7c_flags NL7C_ENABLED. */ typedef struct nl7c_addr_s { struct nl7c_addr_s *next; /* next entry */ sa_family_t family; /* addr type, only INET and INET6 */ uint16_t port; /* port */ union { ipaddr_t v4; /* IPv4 address */ in6_addr_t v6; /* IPv6 address */ void *align; /* foce alignment */ } addr; /* address */ struct sonode *listener; /* listen()er's sonode */ boolean_t temp; /* temporary addr via add_addr() ? */ } nl7c_addr_t; nl7c_addr_t *nl7caddrs = NULL; /* * Called for an NL7C_ENABLED listen()er socket for the nl7c_addr_t * previously returned by nl7c_lookup_addr(). */ void nl7c_listener_addr(void *arg, struct sonode *so) { nl7c_addr_t *p = (nl7c_addr_t *)arg; if (p->listener == NULL) p->listener = so; SOTOTPI(so)->sti_nl7c_addr = arg; } struct sonode * nl7c_addr2portso(void *arg) { nl7c_addr_t *p = (nl7c_addr_t *)arg; return (p->listener); } void * nl7c_lookup_addr(void *addr, t_uscalar_t addrlen) { struct sockaddr *sap = addr; struct sockaddr_in *v4p = addr; nl7c_addr_t *p = nl7caddrs; if (sap->sa_family != AF_INET || addrlen != sizeof (*v4p)) { /* Only support IPv4 */ return (B_FALSE); } while (p) { if (sap->sa_family == p->family && v4p->sin_port == p->port && (v4p->sin_addr.s_addr == p->addr.v4 || p->addr.v4 == INADDR_ANY)) { /* Match */ return (p); } p = p->next; } return (NULL); } void * nl7c_add_addr(void *addr, t_uscalar_t addrlen) { struct sockaddr *sap = addr; struct sockaddr_in *v4p = addr; nl7c_addr_t *new = NULL; nl7c_addr_t *old; nl7c_addr_t *p; boolean_t alloced; if (sap->sa_family != AF_INET || addrlen != sizeof (*v4p)) { /* Only support IPv4 */ return (NULL); } again: p = nl7caddrs; while (p) { if (new == NULL && p->port == 0) new = p; if (sap->sa_family == p->family && v4p->sin_port == p->port && (v4p->sin_addr.s_addr == p->addr.v4 || p->addr.v4 == INADDR_ANY)) { /* Match */ return (p); } p = p->next; } if (new == NULL) { new = kmem_zalloc(sizeof (*new), KM_SLEEP); alloced = B_TRUE; } else alloced = B_FALSE; new->family = sap->sa_family; new->port = v4p->sin_port; new->addr.v4 = v4p->sin_addr.s_addr; new->temp = B_TRUE; if (alloced) { old = nl7caddrs; new->next = old; if (atomic_cas_ptr(&nl7caddrs, old, new) != old) { kmem_free(new, sizeof (*new)); goto again; } } return (new); } boolean_t nl7c_close_addr(struct sonode *so) { nl7c_addr_t *p = nl7caddrs; while (p) { if (p->listener == so) { if (p->temp) p->port = (uint16_t)-1; p->listener = NULL; return (B_TRUE); } p = p->next; } return (B_FALSE); } static void nl7c_addr_add(nl7c_addr_t *p) { p->next = nl7caddrs; nl7caddrs = p; } void nl7c_mi_report_addr(mblk_t *mp) { ipaddr_t ip; uint16_t port; nl7c_addr_t *p = nl7caddrs; struct sonode *so; char addr[32]; (void) mi_mpprintf(mp, "Door Up-Call-Queue IPaddr:TCPport Listenning"); while (p) { if (p->port != (uint16_t)-1) { /* Don't report freed slots */ ip = ntohl(p->addr.v4); port = ntohs(p->port); if (ip == INADDR_ANY) { (void) strcpy(addr, "*"); } else { int a1 = (ip >> 24) & 0xFF; int a2 = (ip >> 16) & 0xFF; int a3 = (ip >> 8) & 0xFF; int a4 = ip & 0xFF; (void) mi_sprintf(addr, "%d.%d.%d.%d", a1, a2, a3, a4); } so = p->listener; (void) mi_mpprintf(mp, "%p %s:%d %d", so ? (void *)strvp2wq(SOTOV(so)) : NULL, addr, port, p->listener ? 1 : 0); } p = p->next; } } /* * ASCII to unsigned. * * Note, it's assumed that *p is a valid zero byte terminated string. */ static unsigned atou(const char *p) { int c; int v = 0; /* Shift and add digit by digit */ while ((c = *p++) != NULL && isdigit(c)) { v *= 10; v += c - '0'; } return (v); } /* * strdup(), yet another strdup() in the kernel. */ static char * strdup(char *s) { int len = strlen(s) + 1; char *ret = kmem_alloc(len, KM_SLEEP); bcopy(s, ret, len); return (ret); } /* * Inet ASCII to binary. * * Note, it's assumed that *s is a valid zero byte terminated string, and * that *p is a zero initialized struct (this is important as the value of * INADDR_ANY and IN6ADDR_ANY is zero). */ static int inet_atob(char *s, nl7c_addr_t *p) { if (strcmp(s, "*") == 0) { /* INADDR_ANY */ p->family = AF_INET; return (0); } if (strcmp(s, "::") == 0) { /* IN6ADDR_ANY */ p->family = AF_INET6; return (0); } /* IPv4 address ? */ if (inet_pton(AF_INET, s, &p->addr.v4) != 1) { /* Nop, IPv6 address ? */ if (inet_pton(AF_INET6, s, &p->addr.v6) != 1) { /* Nop, return error */ return (1); } p->family = AF_INET6; } else { p->family = AF_INET; p->addr.v4 = ntohl(p->addr.v4); } return (0); } /* * Open and read each line from "/etc/nca/ncaport.conf", the syntax of a * ncaport.conf file line is: * * ncaport=IPaddr/Port[/Proxy] * * Where: * * ncaport - the only token recognized. * * IPaddr - an IPv4 numeric dot address (e.g. 192.168.84.71) or '*' for * INADDR_ANY, or an IPv6 numeric address or "::" for IN6ADDR_ANY. * * / - IPaddr/Port separator. * * Port - a TCP decimal port number. * * Note, all other lines will be ignored. */ static void ncaportconf_read(void) { int ret; struct vnode *vp; char c; ssize_t resid; char buf[1024]; char *ebp = &buf[sizeof (buf)]; char *bp = ebp; offset_t off = 0; enum parse_e {START, TOK, ADDR, PORT, EOL} parse = START; nl7c_addr_t *addrp = NULL; char *ncaport = "ncaport"; char string[] = "XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX"; char *stringp; char *tok; char *portconf = "/etc/nca/ncaport.conf"; ret = vn_open(portconf, UIO_SYSSPACE, FREAD, 0, &vp, 0, 0); if (ret == ENOENT) { /* No portconf file, nothing to do */ return; } if (ret != 0) { /* Error of some sort, tell'm about it */ cmn_err(CE_WARN, "%s: open error %d", portconf, ret); return; } /* * Read portconf one buf[] at a time, parse one char at a time. */ for (;;) { if (bp == ebp) { /* Nothing left in buf[], read another */ ret = vn_rdwr(UIO_READ, vp, buf, sizeof (buf), off, UIO_SYSSPACE, 0, (rlim64_t)0, CRED(), &resid); if (ret != 0) { /* Error of some sort, tell'm about it */ cmn_err(CE_WARN, "%s: read error %d", portconf, ret); break; } if (resid == sizeof (buf)) { /* EOF, done */ break; } /* Initilize per buf[] state */ bp = buf; ebp = &buf[sizeof (buf) - resid]; off += sizeof (buf) - resid; } c = *bp++; switch (parse) { case START: /* Initilize all per file line state */ if (addrp == NULL) { addrp = kmem_zalloc(sizeof (*addrp), KM_NOSLEEP); } tok = ncaport; stringp = string; parse = TOK; /*FALLTHROUGH*/ case TOK: if (c == '#') { /* Comment through end of line */ parse = EOL; break; } if (isalpha(c)) { if (c != *tok++) { /* Only know one token, skip */ parse = EOL; } } else if (c == '=') { if (*tok != NULL) { /* Only know one token, skip */ parse = EOL; break; } parse = ADDR; } else if (c == '\n') { /* Found EOL, empty line, next line */ parse = START; } else { /* Unexpected char, skip */ parse = EOL; } break; case ADDR: if (c == '/') { /* addr/port separator, end of addr */ *stringp = NULL; if (inet_atob(string, addrp)) { /* Bad addr, skip */ parse = EOL; } else { stringp = string; parse = PORT; } } else { /* Save char to string */ if (stringp == &string[sizeof (string) - 1]) { /* Would overflow, skip */ parse = EOL; } else { /* Copy IP addr char */ *stringp++ = c; } } break; case PORT: if (isdigit(c)) { /* Save char to string */ if (stringp == &string[sizeof (string) - 1]) { /* Would overflow, skip */ parse = EOL; } else { /* Copy port digit char */ *stringp++ = c; } break; } else if (c == '#' || isspace(c)) { /* End of port number, convert */ *stringp = NULL; addrp->port = ntohs(atou(string)); /* End of parse, add entry */ nl7c_addr_add(addrp); addrp = NULL; parse = EOL; } else { /* Unrecognized char, skip */ parse = EOL; break; } if (c == '\n') { /* Found EOL, start on next line */ parse = START; } break; case EOL: if (c == '\n') { /* Found EOL, start on next line */ parse = START; } break; } } if (addrp != NULL) { kmem_free(addrp, sizeof (*addrp)); } (void) VOP_CLOSE(vp, FREAD, 1, (offset_t)0, CRED(), NULL); VN_RELE(vp); } /* * Open and read each line from "/etc/nca/ncakmod.conf" and parse looking * for the NCA enabled, the syntax is: status=enabled, all other lines will * be ignored. */ static void ncakmodconf_read(void) { int ret; struct vnode *vp; char c; ssize_t resid; char buf[1024]; char *ebp = &buf[sizeof (buf)]; char *bp = ebp; offset_t off = 0; enum parse_e {START, TOK, EOL} parse = START; char *status = "status=enabled"; char *tok; char *ncakmod = "/etc/nca/ncakmod.conf"; ret = vn_open(ncakmod, UIO_SYSSPACE, FREAD, 0, &vp, 0, 0); if (ret == ENOENT) { /* No ncakmod file, nothing to do */ return; } if (ret != 0) { /* Error of some sort, tell'm about it */ cmn_err(CE_WARN, "%s: open error %d", status, ret); return; } /* * Read ncakmod one buf[] at a time, parse one char at a time. */ for (;;) { if (bp == ebp) { /* Nothing left in buf[], read another */ ret = vn_rdwr(UIO_READ, vp, buf, sizeof (buf), off, UIO_SYSSPACE, 0, (rlim64_t)0, CRED(), &resid); if (ret != 0) { /* Error of some sort, tell'm about it */ cmn_err(CE_WARN, "%s: read error %d", status, ret); break; } if (resid == sizeof (buf)) { /* EOF, done */ break; } /* Initilize per buf[] state */ bp = buf; ebp = &buf[sizeof (buf) - resid]; off += sizeof (buf) - resid; } c = *bp++; switch (parse) { case START: /* Initilize all per file line state */ tok = status; parse = TOK; /*FALLTHROUGH*/ case TOK: if (c == '#') { /* Comment through end of line */ parse = EOL; break; } if (isalpha(c) || c == '=') { if (c != *tok++) { /* Only know one token, skip */ parse = EOL; } } else if (c == '\n') { /* * Found EOL, if tok found done, * else start on next-line. */ if (*tok == NULL) { nl7c_enabled = B_TRUE; goto done; } parse = START; } else { /* Unexpected char, skip */ parse = EOL; } break; case EOL: if (c == '\n') { /* Found EOL, start on next line */ parse = START; } break; } } done: (void) VOP_CLOSE(vp, FREAD, 1, (offset_t)0, CRED(), NULL); VN_RELE(vp); } /* * Open and read each line from "/etc/nca/ncalogd.conf" and parse for * the tokens and token text (i.e. key and value ncalogd.conf(4)): * * status=enabled * * logd_file_size=[0-9]+ * * logd_file_name=["]filename( filename)*["] */ static int file_size = 1000000; static caddr_t fnv[NCA_FIOV_SZ]; static void ncalogdconf_read(void) { int ret; struct vnode *vp; char c; int sz; ssize_t resid; char buf[1024]; char *ebp = &buf[sizeof (buf)]; char *bp = ebp; offset_t off = 0; enum parse_e {START, TOK, TEXT, EOL} parse = START; char *tokstatus = "status\0enabled"; char *toksize = "logd_file_size"; char *tokfile = "logd_path_name"; char *tokstatusp; char *toksizep; char *tokfilep; char *tok; int tokdelim = 0; char *ncalogd = "/etc/nca/ncalogd.conf"; char *ncadeflog = "/var/nca/log"; char file[TYPICALMAXPATHLEN] = {0}; char *fp = file; caddr_t *fnvp = fnv; ret = vn_open(ncalogd, UIO_SYSSPACE, FREAD, 0, &vp, 0, 0); if (ret == ENOENT) { /* No ncalogd file, nothing to do */ return; } if (ret != 0) { /* Error of some sort, tell'm about it */ cmn_err(CE_WARN, "ncalogdconf_read: %s: open error(%d).", ncalogd, ret); return; } /* * Read ncalogd.conf one buf[] at a time, parse one char at a time. */ for (;;) { if (bp == ebp) { /* Nothing left in buf[], read another */ ret = vn_rdwr(UIO_READ, vp, buf, sizeof (buf), off, UIO_SYSSPACE, 0, (rlim64_t)0, CRED(), &resid); if (ret != 0) { /* Error of some sort, tell'm about it */ cmn_err(CE_WARN, "%s: read error %d", ncalogd, ret); break; } if (resid == sizeof (buf)) { /* EOF, done */ break; } /* Initilize per buf[] state */ bp = buf; ebp = &buf[sizeof (buf) - resid]; off += sizeof (buf) - resid; } c = *bp++; switch (parse) { case START: /* Initilize all per file line state */ tokstatusp = tokstatus; toksizep = toksize; tokfilep = tokfile; tok = NULL; parse = TOK; sz = 0; /*FALLTHROUGH*/ case TOK: if (isalpha(c) || c == '_') { /* * Found a valid tok char, if matches * any of the tokens continue else NULL * then string pointer. */ if (tokstatusp != NULL && c != *tokstatusp++) tokstatusp = NULL; if (toksizep != NULL && c != *toksizep++) toksizep = NULL; if (tokfilep != NULL && c != *tokfilep++) tokfilep = NULL; if (tokstatusp == NULL && toksizep == NULL && tokfilep == NULL) { /* * All tok string pointers are NULL * so skip rest of line. */ parse = EOL; } } else if (c == '=') { /* * Found tok separator, if tok found get * tok text, else skip rest of line. */ if (tokstatusp != NULL && *tokstatusp == NULL) tok = tokstatus; else if (toksizep != NULL && *toksizep == NULL) tok = toksize; else if (tokfilep != NULL && *tokfilep == NULL) tok = tokfile; if (tok != NULL) parse = TEXT; else parse = EOL; } else if (c == '\n') { /* Found EOL, start on next line */ parse = START; } else { /* Comment or unknown char, skip rest of line */ parse = EOL; } break; case TEXT: if (c == '\n') { /* * Found EOL, finish up tok text processing * (if any) and start on next line. */ if (tok == tokstatus) { if (*++tokstatusp == NULL) nl7c_logd_enabled = B_TRUE; } else if (tok == toksize) { file_size = sz; } else if (tok == tokfile) { if (tokdelim == 0) { /* Non delimited path name */ *fnvp++ = strdup(file); } else if (fp != file) { /* No closing delimiter */ /*EMPTY*/; } } parse = START; } else if (tok == tokstatus) { if (! isalpha(c) || *++tokstatusp == NULL || c != *tokstatusp) { /* Not enabled, skip line */ parse = EOL; } } else if (tok == toksize) { if (isdigit(c)) { sz *= 10; sz += c - '0'; } else { /* Not a decimal digit, skip line */ parse = EOL; } } else { /* File name */ if (c == '"' && tokdelim++ == 0) { /* Opening delimiter, skip */ /*EMPTY*/; } else if (c == '"' || c == ' ') { /* List delim or filename separator */ *fnvp++ = strdup(file); fp = file; } else if (fp < &file[sizeof (file) - 1]) { /* Filename char */ *fp++ = c; } else { /* Filename to long, skip line */ parse = EOL; } } break; case EOL: if (c == '\n') { /* Found EOL, start on next line */ parse = START; } break; } } done: (void) VOP_CLOSE(vp, FREAD, 1, (offset_t)0, CRED(), NULL); VN_RELE(vp); if (nl7c_logd_enabled) { if (fnvp == fnv) { /* * No logfile was specified and found so * so use defualt NCA log file path. */ *fnvp++ = strdup(ncadeflog); } if (fnvp < &fnv[NCA_FIOV_SZ]) { /* NULL terminate list */ *fnvp = NULL; } } } void nl7clogd_startup(void) { static kmutex_t startup; /* * Called on the first log() attempt, have to wait until then to * initialize logd as at logdconf_read() the root fs is read-only. */ mutex_enter(&startup); if (nl7c_logd_started) { /* Lost the race, nothing todo */ mutex_exit(&startup); return; } nl7c_logd_started = B_TRUE; if (! nl7c_logd_init(file_size, fnv)) { /* Failure, disable logging */ nl7c_logd_enabled = B_FALSE; cmn_err(CE_WARN, "nl7clogd_startup: failed, disabling loggin"); mutex_exit(&startup); return; } mutex_exit(&startup); } void nl7c_startup() { /* * Open, read, and parse the NCA logd configuration file, * then initialize URI processing and NCA compat. */ ncalogdconf_read(); nl7c_uri_init(); nl7c_nca_init(); } void nl7c_init() { /* Open, read, and parse the NCA kmod configuration file */ ncakmodconf_read(); if (nl7c_enabled) { /* * NL7C is enabled so open, read, and parse * the NCA address/port configuration file * and call startup() to finish config/init. */ ncaportconf_read(); nl7c_startup(); } } /* * The main processing function called by accept() on a newly created * socket prior to returning it to the caller of accept(). * * Here data is read from the socket until a completed L7 request parse * is completed. Data will be read in the context of the user thread * which called accept(), when parse has been completed either B_TRUE * or B_FALSE will be returned. * * If NL7C successfully process the L7 protocol request, i.e. generates * a response, B_TRUE will be returned. * * Else, B_FALSE will be returned if NL7C can't process the request: * * 1) Couldn't locate a URI within the request. * * 2) URI scheme not reqcognized. * * 3) A request which can't be processed. * * 4) A request which could be processed but NL7C dosen't currently have * the response data. In which case NL7C will parse the returned response * from the application for possible caching for subsequent request(s). */ volatile uint64_t nl7c_proc_cnt = 0; volatile uint64_t nl7c_proc_error = 0; volatile uint64_t nl7c_proc_ETIME = 0; volatile uint64_t nl7c_proc_again = 0; volatile uint64_t nl7c_proc_next = 0; volatile uint64_t nl7c_proc_rcv = 0; volatile uint64_t nl7c_proc_noLRI = 0; volatile uint64_t nl7c_proc_nodata = 0; volatile uint64_t nl7c_proc_parse = 0; boolean_t nl7c_process(struct sonode *so, boolean_t nonblocking) { vnode_t *vp = SOTOV(so); sotpi_info_t *sti = SOTOTPI(so); mblk_t *rmp = sti->sti_nl7c_rcv_mp; clock_t timout; rval_t rval; uchar_t pri; int pflag; int error; boolean_t more; boolean_t ret = B_FALSE; boolean_t first = B_TRUE; boolean_t pollin = (sti->sti_nl7c_flags & NL7C_POLLIN); nl7c_proc_cnt++; /* Caller has so_lock enter()ed */ error = so_lock_read_intr(so, nonblocking ? FNDELAY|FNONBLOCK : 0); if (error) { /* Couldn't read lock, pass on this socket */ sti->sti_nl7c_flags = 0; nl7c_proc_noLRI++; return (B_FALSE); } /* Exit so_lock for now, will be reenter()ed prior to return */ mutex_exit(&so->so_lock); if (pollin) sti->sti_nl7c_flags &= ~NL7C_POLLIN; /* Initialize some kstrgetmsg() constants */ pflag = MSG_ANY | MSG_DELAYERROR; pri = 0; if (nonblocking) { /* Non blocking so don't block */ timout = 0; } else if (sti->sti_nl7c_flags & NL7C_SOPERSIST) { /* 2nd or more time(s) here so use keep-alive value */ timout = nca_http_keep_alive_timeout; } else { /* 1st time here so use connection value */ timout = nca_http_timeout; } rval.r_vals = 0; do { /* * First time through, if no data left over from a previous * kstrgetmsg() then try to get some, else just process it. * * Thereafter, rmp = NULL after the successful kstrgetmsg() * so try to get some new data and append to list (i.e. until * enough fragments are collected for a successful parse). */ if (rmp == NULL) { error = kstrgetmsg(vp, &rmp, NULL, &pri, &pflag, timout, &rval); if (error) { if (error == ETIME) { /* Timeout */ nl7c_proc_ETIME++; } else if (error != EWOULDBLOCK) { /* Error of some sort */ nl7c_proc_error++; rval.r_v.r_v2 = error; sti->sti_nl7c_flags = 0; break; } error = 0; } if (rmp != NULL) { mblk_t *mp = sti->sti_nl7c_rcv_mp; if (mp == NULL) { /* Just new data, common case */ sti->sti_nl7c_rcv_mp = rmp; } else { /* Add new data to tail */ while (mp->b_cont != NULL) mp = mp->b_cont; mp->b_cont = rmp; } } if (sti->sti_nl7c_rcv_mp == NULL) { /* No data */ nl7c_proc_nodata++; if (timout > 0 || (first && pollin)) { /* Expected data so EOF */ ret = B_TRUE; } else if (sti->sti_nl7c_flags & NL7C_SOPERSIST) { /* Persistent so just checking */ ret = B_FALSE; } break; } rmp = NULL; } first = B_FALSE; again: nl7c_proc_parse++; more = nl7c_parse(so, nonblocking, &ret); if (ret == B_TRUE && (sti->sti_nl7c_flags & NL7C_SOPERSIST)) { /* * Parse complete, cache hit, response on its way, * socket is persistent so try to process the next * request. */ if (nonblocking) { ret = B_FALSE; break; } if (sti->sti_nl7c_rcv_mp) { /* More recv-side data, pipelined */ nl7c_proc_again++; goto again; } nl7c_proc_next++; if (nonblocking) timout = 0; else timout = nca_http_keep_alive_timeout; more = B_TRUE; } } while (more); if (sti->sti_nl7c_rcv_mp) { nl7c_proc_rcv++; } sti->sti_nl7c_rcv_rval = rval.r_vals; /* Renter so_lock, caller called with it enter()ed */ mutex_enter(&so->so_lock); so_unlock_read(so); return (ret); }