diff --git a/sys/kern/uipc_sockbuf.c b/sys/kern/uipc_sockbuf.c index be9084bb6fc0..4afe8c570fae 100644 --- a/sys/kern/uipc_sockbuf.c +++ b/sys/kern/uipc_sockbuf.c @@ -1,1524 +1,1524 @@ /*- * Copyright (c) 1982, 1986, 1988, 1990, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)uipc_socket2.c 8.1 (Berkeley) 6/10/93 */ #include __FBSDID("$FreeBSD$"); #include "opt_mac.h" #include "opt_param.h" #include #include /* for aio_swake proto */ #include #include #include /* for maxfiles */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int maxsockets; void (*aio_swake)(struct socket *, struct sockbuf *); /* * Primitive routines for operating on sockets and socket buffers */ u_long sb_max = SB_MAX; static u_long sb_max_adj = SB_MAX * MCLBYTES / (MSIZE + MCLBYTES); /* adjusted sb_max */ static u_long sb_efficiency = 8; /* parameter for sbreserve() */ #ifdef REGRESSION static int regression_sonewconn_earlytest = 1; SYSCTL_INT(_regression, OID_AUTO, sonewconn_earlytest, CTLFLAG_RW, ®ression_sonewconn_earlytest, 0, "Perform early sonewconn limit test"); #endif /* * Procedures to manipulate state flags of socket * and do appropriate wakeups. Normal sequence from the * active (originating) side is that soisconnecting() is * called during processing of connect() call, * resulting in an eventual call to soisconnected() if/when the * connection is established. When the connection is torn down * soisdisconnecting() is called during processing of disconnect() call, * and soisdisconnected() is called when the connection to the peer * is totally severed. The semantics of these routines are such that * connectionless protocols can call soisconnected() and soisdisconnected() * only, bypassing the in-progress calls when setting up a ``connection'' * takes no time. * * From the passive side, a socket is created with * two queues of sockets: so_incomp for connections in progress * and so_comp for connections already made and awaiting user acceptance. * As a protocol is preparing incoming connections, it creates a socket * structure queued on so_incomp by calling sonewconn(). When the connection * is established, soisconnected() is called, and transfers the * socket structure to so_comp, making it available to accept(). * * If a socket is closed with sockets on either * so_incomp or so_comp, these sockets are dropped. * * If higher level protocols are implemented in * the kernel, the wakeups done here will sometimes * cause software-interrupt process scheduling. */ void soisconnecting(so) register struct socket *so; { SOCK_LOCK(so); so->so_state &= ~(SS_ISCONNECTED|SS_ISDISCONNECTING); so->so_state |= SS_ISCONNECTING; SOCK_UNLOCK(so); } void soisconnected(so) struct socket *so; { struct socket *head; ACCEPT_LOCK(); SOCK_LOCK(so); so->so_state &= ~(SS_ISCONNECTING|SS_ISDISCONNECTING|SS_ISCONFIRMING); so->so_state |= SS_ISCONNECTED; head = so->so_head; if (head != NULL && (so->so_qstate & SQ_INCOMP)) { if ((so->so_options & SO_ACCEPTFILTER) == 0) { SOCK_UNLOCK(so); TAILQ_REMOVE(&head->so_incomp, so, so_list); head->so_incqlen--; so->so_qstate &= ~SQ_INCOMP; TAILQ_INSERT_TAIL(&head->so_comp, so, so_list); head->so_qlen++; so->so_qstate |= SQ_COMP; ACCEPT_UNLOCK(); sorwakeup(head); wakeup_one(&head->so_timeo); } else { ACCEPT_UNLOCK(); so->so_upcall = head->so_accf->so_accept_filter->accf_callback; so->so_upcallarg = head->so_accf->so_accept_filter_arg; so->so_rcv.sb_flags |= SB_UPCALL; so->so_options &= ~SO_ACCEPTFILTER; SOCK_UNLOCK(so); so->so_upcall(so, so->so_upcallarg, M_DONTWAIT); } return; } SOCK_UNLOCK(so); ACCEPT_UNLOCK(); wakeup(&so->so_timeo); sorwakeup(so); sowwakeup(so); } void soisdisconnecting(so) register struct socket *so; { /* * XXXRW: This code assumes that SOCK_LOCK(so) and * SOCKBUF_LOCK(&so->so_rcv) are the same. */ SOCKBUF_LOCK(&so->so_rcv); so->so_state &= ~SS_ISCONNECTING; so->so_state |= SS_ISDISCONNECTING; so->so_rcv.sb_state |= SBS_CANTRCVMORE; sorwakeup_locked(so); SOCKBUF_LOCK(&so->so_snd); so->so_snd.sb_state |= SBS_CANTSENDMORE; sowwakeup_locked(so); wakeup(&so->so_timeo); } void soisdisconnected(so) register struct socket *so; { /* * XXXRW: This code assumes that SOCK_LOCK(so) and * SOCKBUF_LOCK(&so->so_rcv) are the same. */ SOCKBUF_LOCK(&so->so_rcv); so->so_state &= ~(SS_ISCONNECTING|SS_ISCONNECTED|SS_ISDISCONNECTING); so->so_state |= SS_ISDISCONNECTED; so->so_rcv.sb_state |= SBS_CANTRCVMORE; sorwakeup_locked(so); SOCKBUF_LOCK(&so->so_snd); so->so_snd.sb_state |= SBS_CANTSENDMORE; sbdrop_locked(&so->so_snd, so->so_snd.sb_cc); sowwakeup_locked(so); wakeup(&so->so_timeo); } /* * When an attempt at a new connection is noted on a socket * which accepts connections, sonewconn is called. If the * connection is possible (subject to space constraints, etc.) * then we allocate a new structure, propoerly linked into the * data structure of the original socket, and return this. * Connstatus may be 0, or SO_ISCONFIRMING, or SO_ISCONNECTED. * * note: the ref count on the socket is 0 on return */ struct socket * sonewconn(head, connstatus) register struct socket *head; int connstatus; { register struct socket *so; int over; ACCEPT_LOCK(); over = (head->so_qlen > 3 * head->so_qlimit / 2); ACCEPT_UNLOCK(); #ifdef REGRESSION if (regression_sonewconn_earlytest && over) #else if (over) #endif return (NULL); so = soalloc(M_NOWAIT); if (so == NULL) return (NULL); if ((head->so_options & SO_ACCEPTFILTER) != 0) connstatus = 0; so->so_head = head; so->so_type = head->so_type; so->so_options = head->so_options &~ SO_ACCEPTCONN; so->so_linger = head->so_linger; so->so_state = head->so_state | SS_NOFDREF; so->so_proto = head->so_proto; so->so_timeo = head->so_timeo; so->so_cred = crhold(head->so_cred); #ifdef MAC SOCK_LOCK(head); mac_create_socket_from_socket(head, so); SOCK_UNLOCK(head); #endif knlist_init(&so->so_rcv.sb_sel.si_note, SOCKBUF_MTX(&so->so_rcv), NULL, NULL, NULL); knlist_init(&so->so_snd.sb_sel.si_note, SOCKBUF_MTX(&so->so_snd), NULL, NULL, NULL); if (soreserve(so, head->so_snd.sb_hiwat, head->so_rcv.sb_hiwat) || (*so->so_proto->pr_usrreqs->pru_attach)(so, 0, NULL)) { sodealloc(so); return (NULL); } so->so_state |= connstatus; ACCEPT_LOCK(); if (connstatus) { TAILQ_INSERT_TAIL(&head->so_comp, so, so_list); so->so_qstate |= SQ_COMP; head->so_qlen++; } else { /* * Keep removing sockets from the head until there's room for * us to insert on the tail. In pre-locking revisions, this * was a simple if(), but as we could be racing with other * threads and soabort() requires dropping locks, we must * loop waiting for the condition to be true. */ while (head->so_incqlen > head->so_qlimit) { struct socket *sp; sp = TAILQ_FIRST(&head->so_incomp); TAILQ_REMOVE(&head->so_incomp, sp, so_list); head->so_incqlen--; sp->so_qstate &= ~SQ_INCOMP; sp->so_head = NULL; ACCEPT_UNLOCK(); soabort(sp); ACCEPT_LOCK(); } TAILQ_INSERT_TAIL(&head->so_incomp, so, so_list); so->so_qstate |= SQ_INCOMP; head->so_incqlen++; } ACCEPT_UNLOCK(); if (connstatus) { sorwakeup(head); wakeup_one(&head->so_timeo); } return (so); } /* * Socantsendmore indicates that no more data will be sent on the * socket; it would normally be applied to a socket when the user * informs the system that no more data is to be sent, by the protocol * code (in case PRU_SHUTDOWN). Socantrcvmore indicates that no more data * will be received, and will normally be applied to the socket by a * protocol when it detects that the peer will send no more data. * Data queued for reading in the socket may yet be read. */ void socantsendmore_locked(so) struct socket *so; { SOCKBUF_LOCK_ASSERT(&so->so_snd); so->so_snd.sb_state |= SBS_CANTSENDMORE; sowwakeup_locked(so); mtx_assert(SOCKBUF_MTX(&so->so_snd), MA_NOTOWNED); } void socantsendmore(so) struct socket *so; { SOCKBUF_LOCK(&so->so_snd); socantsendmore_locked(so); mtx_assert(SOCKBUF_MTX(&so->so_snd), MA_NOTOWNED); } void socantrcvmore_locked(so) struct socket *so; { SOCKBUF_LOCK_ASSERT(&so->so_rcv); so->so_rcv.sb_state |= SBS_CANTRCVMORE; sorwakeup_locked(so); mtx_assert(SOCKBUF_MTX(&so->so_rcv), MA_NOTOWNED); } void socantrcvmore(so) struct socket *so; { SOCKBUF_LOCK(&so->so_rcv); socantrcvmore_locked(so); mtx_assert(SOCKBUF_MTX(&so->so_rcv), MA_NOTOWNED); } /* * Wait for data to arrive at/drain from a socket buffer. */ int sbwait(sb) struct sockbuf *sb; { SOCKBUF_LOCK_ASSERT(sb); sb->sb_flags |= SB_WAIT; return (msleep(&sb->sb_cc, &sb->sb_mtx, (sb->sb_flags & SB_NOINTR) ? PSOCK : PSOCK | PCATCH, "sbwait", sb->sb_timeo)); } /* * Lock a sockbuf already known to be locked; * return any error returned from sleep (EINTR). */ int sb_lock(sb) register struct sockbuf *sb; { int error; SOCKBUF_LOCK_ASSERT(sb); while (sb->sb_flags & SB_LOCK) { sb->sb_flags |= SB_WANT; error = msleep(&sb->sb_flags, &sb->sb_mtx, (sb->sb_flags & SB_NOINTR) ? PSOCK : PSOCK|PCATCH, "sblock", 0); if (error) return (error); } sb->sb_flags |= SB_LOCK; return (0); } /* * Wakeup processes waiting on a socket buffer. Do asynchronous * notification via SIGIO if the socket has the SS_ASYNC flag set. * * Called with the socket buffer lock held; will release the lock by the end * of the function. This allows the caller to acquire the socket buffer lock * while testing for the need for various sorts of wakeup and hold it through * to the point where it's no longer required. We currently hold the lock * through calls out to other subsystems (with the exception of kqueue), and * then release it to avoid lock order issues. It's not clear that's * correct. */ void sowakeup(so, sb) register struct socket *so; register struct sockbuf *sb; { SOCKBUF_LOCK_ASSERT(sb); selwakeuppri(&sb->sb_sel, PSOCK); sb->sb_flags &= ~SB_SEL; if (sb->sb_flags & SB_WAIT) { sb->sb_flags &= ~SB_WAIT; wakeup(&sb->sb_cc); } KNOTE_LOCKED(&sb->sb_sel.si_note, 0); SOCKBUF_UNLOCK(sb); if ((so->so_state & SS_ASYNC) && so->so_sigio != NULL) pgsigio(&so->so_sigio, SIGIO, 0); if (sb->sb_flags & SB_UPCALL) (*so->so_upcall)(so, so->so_upcallarg, M_DONTWAIT); if (sb->sb_flags & SB_AIO) aio_swake(so, sb); mtx_assert(SOCKBUF_MTX(sb), MA_NOTOWNED); } /* * Socket buffer (struct sockbuf) utility routines. * * Each socket contains two socket buffers: one for sending data and * one for receiving data. Each buffer contains a queue of mbufs, * information about the number of mbufs and amount of data in the * queue, and other fields allowing select() statements and notification * on data availability to be implemented. * * Data stored in a socket buffer is maintained as a list of records. * Each record is a list of mbufs chained together with the m_next * field. Records are chained together with the m_nextpkt field. The upper * level routine soreceive() expects the following conventions to be * observed when placing information in the receive buffer: * * 1. If the protocol requires each message be preceded by the sender's * name, then a record containing that name must be present before * any associated data (mbuf's must be of type MT_SONAME). * 2. If the protocol supports the exchange of ``access rights'' (really * just additional data associated with the message), and there are * ``rights'' to be received, then a record containing this data * should be present (mbuf's must be of type MT_RIGHTS). * 3. If a name or rights record exists, then it must be followed by * a data record, perhaps of zero length. * * Before using a new socket structure it is first necessary to reserve * buffer space to the socket, by calling sbreserve(). This should commit * some of the available buffer space in the system buffer pool for the * socket (currently, it does nothing but enforce limits). The space * should be released by calling sbrelease() when the socket is destroyed. */ int soreserve(so, sndcc, rcvcc) register struct socket *so; u_long sndcc, rcvcc; { struct thread *td = curthread; SOCKBUF_LOCK(&so->so_snd); SOCKBUF_LOCK(&so->so_rcv); if (sbreserve_locked(&so->so_snd, sndcc, so, td) == 0) goto bad; if (sbreserve_locked(&so->so_rcv, rcvcc, so, td) == 0) goto bad2; if (so->so_rcv.sb_lowat == 0) so->so_rcv.sb_lowat = 1; if (so->so_snd.sb_lowat == 0) so->so_snd.sb_lowat = MCLBYTES; if (so->so_snd.sb_lowat > so->so_snd.sb_hiwat) so->so_snd.sb_lowat = so->so_snd.sb_hiwat; SOCKBUF_UNLOCK(&so->so_rcv); SOCKBUF_UNLOCK(&so->so_snd); return (0); bad2: sbrelease_locked(&so->so_snd, so); bad: SOCKBUF_UNLOCK(&so->so_rcv); SOCKBUF_UNLOCK(&so->so_snd); return (ENOBUFS); } static int sysctl_handle_sb_max(SYSCTL_HANDLER_ARGS) { int error = 0; u_long old_sb_max = sb_max; error = SYSCTL_OUT(req, arg1, sizeof(u_long)); if (error || !req->newptr) return (error); error = SYSCTL_IN(req, arg1, sizeof(u_long)); if (error) return (error); if (sb_max < MSIZE + MCLBYTES) { sb_max = old_sb_max; return (EINVAL); } sb_max_adj = (u_quad_t)sb_max * MCLBYTES / (MSIZE + MCLBYTES); return (0); } /* * Allot mbufs to a sockbuf. * Attempt to scale mbmax so that mbcnt doesn't become limiting * if buffering efficiency is near the normal case. */ int sbreserve_locked(sb, cc, so, td) struct sockbuf *sb; u_long cc; struct socket *so; struct thread *td; { rlim_t sbsize_limit; SOCKBUF_LOCK_ASSERT(sb); /* * td will only be NULL when we're in an interrupt * (e.g. in tcp_input()) */ if (cc > sb_max_adj) return (0); if (td != NULL) { PROC_LOCK(td->td_proc); sbsize_limit = lim_cur(td->td_proc, RLIMIT_SBSIZE); PROC_UNLOCK(td->td_proc); } else sbsize_limit = RLIM_INFINITY; if (!chgsbsize(so->so_cred->cr_uidinfo, &sb->sb_hiwat, cc, sbsize_limit)) return (0); sb->sb_mbmax = min(cc * sb_efficiency, sb_max); if (sb->sb_lowat > sb->sb_hiwat) sb->sb_lowat = sb->sb_hiwat; return (1); } int sbreserve(sb, cc, so, td) struct sockbuf *sb; u_long cc; struct socket *so; struct thread *td; { int error; SOCKBUF_LOCK(sb); error = sbreserve_locked(sb, cc, so, td); SOCKBUF_UNLOCK(sb); return (error); } /* * Free mbufs held by a socket, and reserved mbuf space. */ void sbrelease_locked(sb, so) struct sockbuf *sb; struct socket *so; { SOCKBUF_LOCK_ASSERT(sb); sbflush_locked(sb); (void)chgsbsize(so->so_cred->cr_uidinfo, &sb->sb_hiwat, 0, RLIM_INFINITY); sb->sb_mbmax = 0; } void sbrelease(sb, so) struct sockbuf *sb; struct socket *so; { SOCKBUF_LOCK(sb); sbrelease_locked(sb, so); SOCKBUF_UNLOCK(sb); } /* * Routines to add and remove * data from an mbuf queue. * * The routines sbappend() or sbappendrecord() are normally called to * append new mbufs to a socket buffer, after checking that adequate * space is available, comparing the function sbspace() with the amount * of data to be added. sbappendrecord() differs from sbappend() in * that data supplied is treated as the beginning of a new record. * To place a sender's address, optional access rights, and data in a * socket receive buffer, sbappendaddr() should be used. To place * access rights and data in a socket receive buffer, sbappendrights() * should be used. In either case, the new data begins a new record. * Note that unlike sbappend() and sbappendrecord(), these routines check * for the caller that there will be enough space to store the data. * Each fails if there is not enough space, or if it cannot find mbufs * to store additional information in. * * Reliable protocols may use the socket send buffer to hold data * awaiting acknowledgement. Data is normally copied from a socket * send buffer in a protocol with m_copy for output to a peer, * and then removing the data from the socket buffer with sbdrop() * or sbdroprecord() when the data is acknowledged by the peer. */ #ifdef SOCKBUF_DEBUG void sblastrecordchk(struct sockbuf *sb, const char *file, int line) { struct mbuf *m = sb->sb_mb; SOCKBUF_LOCK_ASSERT(sb); while (m && m->m_nextpkt) m = m->m_nextpkt; if (m != sb->sb_lastrecord) { printf("%s: sb_mb %p sb_lastrecord %p last %p\n", __func__, sb->sb_mb, sb->sb_lastrecord, m); printf("packet chain:\n"); for (m = sb->sb_mb; m != NULL; m = m->m_nextpkt) printf("\t%p\n", m); panic("%s from %s:%u", __func__, file, line); } } void sblastmbufchk(struct sockbuf *sb, const char *file, int line) { struct mbuf *m = sb->sb_mb; struct mbuf *n; SOCKBUF_LOCK_ASSERT(sb); while (m && m->m_nextpkt) m = m->m_nextpkt; while (m && m->m_next) m = m->m_next; if (m != sb->sb_mbtail) { printf("%s: sb_mb %p sb_mbtail %p last %p\n", __func__, sb->sb_mb, sb->sb_mbtail, m); printf("packet tree:\n"); for (m = sb->sb_mb; m != NULL; m = m->m_nextpkt) { printf("\t"); for (n = m; n != NULL; n = n->m_next) printf("%p ", n); printf("\n"); } panic("%s from %s:%u", __func__, file, line); } } #endif /* SOCKBUF_DEBUG */ #define SBLINKRECORD(sb, m0) do { \ SOCKBUF_LOCK_ASSERT(sb); \ if ((sb)->sb_lastrecord != NULL) \ (sb)->sb_lastrecord->m_nextpkt = (m0); \ else \ (sb)->sb_mb = (m0); \ (sb)->sb_lastrecord = (m0); \ } while (/*CONSTCOND*/0) /* * Append mbuf chain m to the last record in the * socket buffer sb. The additional space associated * the mbuf chain is recorded in sb. Empty mbufs are * discarded and mbufs are compacted where possible. */ void sbappend_locked(sb, m) struct sockbuf *sb; struct mbuf *m; { register struct mbuf *n; SOCKBUF_LOCK_ASSERT(sb); if (m == 0) return; SBLASTRECORDCHK(sb); n = sb->sb_mb; if (n) { while (n->m_nextpkt) n = n->m_nextpkt; do { if (n->m_flags & M_EOR) { sbappendrecord_locked(sb, m); /* XXXXXX!!!! */ return; } } while (n->m_next && (n = n->m_next)); } else { /* * XXX Would like to simply use sb_mbtail here, but * XXX I need to verify that I won't miss an EOR that * XXX way. */ if ((n = sb->sb_lastrecord) != NULL) { do { if (n->m_flags & M_EOR) { sbappendrecord_locked(sb, m); /* XXXXXX!!!! */ return; } } while (n->m_next && (n = n->m_next)); } else { /* * If this is the first record in the socket buffer, * it's also the last record. */ sb->sb_lastrecord = m; } } sbcompress(sb, m, n); SBLASTRECORDCHK(sb); } /* * Append mbuf chain m to the last record in the * socket buffer sb. The additional space associated * the mbuf chain is recorded in sb. Empty mbufs are * discarded and mbufs are compacted where possible. */ void sbappend(sb, m) struct sockbuf *sb; struct mbuf *m; { SOCKBUF_LOCK(sb); sbappend_locked(sb, m); SOCKBUF_UNLOCK(sb); } /* * This version of sbappend() should only be used when the caller * absolutely knows that there will never be more than one record * in the socket buffer, that is, a stream protocol (such as TCP). */ void sbappendstream_locked(struct sockbuf *sb, struct mbuf *m) { SOCKBUF_LOCK_ASSERT(sb); KASSERT(m->m_nextpkt == NULL,("sbappendstream 0")); KASSERT(sb->sb_mb == sb->sb_lastrecord,("sbappendstream 1")); SBLASTMBUFCHK(sb); sbcompress(sb, m, sb->sb_mbtail); sb->sb_lastrecord = sb->sb_mb; SBLASTRECORDCHK(sb); } /* * This version of sbappend() should only be used when the caller * absolutely knows that there will never be more than one record * in the socket buffer, that is, a stream protocol (such as TCP). */ void sbappendstream(struct sockbuf *sb, struct mbuf *m) { SOCKBUF_LOCK(sb); sbappendstream_locked(sb, m); SOCKBUF_UNLOCK(sb); } #ifdef SOCKBUF_DEBUG void sbcheck(sb) struct sockbuf *sb; { struct mbuf *m; struct mbuf *n = 0; u_long len = 0, mbcnt = 0; SOCKBUF_LOCK_ASSERT(sb); for (m = sb->sb_mb; m; m = n) { n = m->m_nextpkt; for (; m; m = m->m_next) { len += m->m_len; mbcnt += MSIZE; if (m->m_flags & M_EXT) /*XXX*/ /* pretty sure this is bogus */ mbcnt += m->m_ext.ext_size; } } if (len != sb->sb_cc || mbcnt != sb->sb_mbcnt) { printf("cc %ld != %u || mbcnt %ld != %u\n", len, sb->sb_cc, mbcnt, sb->sb_mbcnt); panic("sbcheck"); } } #endif /* * As above, except the mbuf chain * begins a new record. */ void sbappendrecord_locked(sb, m0) register struct sockbuf *sb; register struct mbuf *m0; { register struct mbuf *m; SOCKBUF_LOCK_ASSERT(sb); if (m0 == 0) return; m = sb->sb_mb; if (m) while (m->m_nextpkt) m = m->m_nextpkt; /* * Put the first mbuf on the queue. * Note this permits zero length records. */ sballoc(sb, m0); SBLASTRECORDCHK(sb); SBLINKRECORD(sb, m0); if (m) m->m_nextpkt = m0; else sb->sb_mb = m0; m = m0->m_next; m0->m_next = 0; if (m && (m0->m_flags & M_EOR)) { m0->m_flags &= ~M_EOR; m->m_flags |= M_EOR; } sbcompress(sb, m, m0); } /* * As above, except the mbuf chain * begins a new record. */ void sbappendrecord(sb, m0) register struct sockbuf *sb; register struct mbuf *m0; { SOCKBUF_LOCK(sb); sbappendrecord_locked(sb, m0); SOCKBUF_UNLOCK(sb); } /* * As above except that OOB data * is inserted at the beginning of the sockbuf, * but after any other OOB data. */ void sbinsertoob_locked(sb, m0) register struct sockbuf *sb; register struct mbuf *m0; { register struct mbuf *m; register struct mbuf **mp; SOCKBUF_LOCK_ASSERT(sb); if (m0 == 0) return; for (mp = &sb->sb_mb; *mp ; mp = &((*mp)->m_nextpkt)) { m = *mp; again: switch (m->m_type) { case MT_OOBDATA: continue; /* WANT next train */ case MT_CONTROL: m = m->m_next; if (m) goto again; /* inspect THIS train further */ } break; } /* * Put the first mbuf on the queue. * Note this permits zero length records. */ sballoc(sb, m0); m0->m_nextpkt = *mp; *mp = m0; m = m0->m_next; m0->m_next = 0; if (m && (m0->m_flags & M_EOR)) { m0->m_flags &= ~M_EOR; m->m_flags |= M_EOR; } sbcompress(sb, m, m0); } /* * As above except that OOB data * is inserted at the beginning of the sockbuf, * but after any other OOB data. */ void sbinsertoob(sb, m0) register struct sockbuf *sb; register struct mbuf *m0; { SOCKBUF_LOCK(sb); sbinsertoob_locked(sb, m0); SOCKBUF_UNLOCK(sb); } /* * Append address and data, and optionally, control (ancillary) data * to the receive queue of a socket. If present, * m0 must include a packet header with total length. * Returns 0 if no space in sockbuf or insufficient mbufs. */ int sbappendaddr_locked(sb, asa, m0, control) struct sockbuf *sb; const struct sockaddr *asa; struct mbuf *m0, *control; { struct mbuf *m, *n, *nlast; int space = asa->sa_len; SOCKBUF_LOCK_ASSERT(sb); if (m0 && (m0->m_flags & M_PKTHDR) == 0) panic("sbappendaddr_locked"); if (m0) space += m0->m_pkthdr.len; space += m_length(control, &n); if (space > sbspace(sb)) return (0); #if MSIZE <= 256 if (asa->sa_len > MLEN) return (0); #endif MGET(m, M_DONTWAIT, MT_SONAME); if (m == 0) return (0); m->m_len = asa->sa_len; bcopy(asa, mtod(m, caddr_t), asa->sa_len); if (n) n->m_next = m0; /* concatenate data to control */ else control = m0; m->m_next = control; for (n = m; n->m_next != NULL; n = n->m_next) sballoc(sb, n); sballoc(sb, n); nlast = n; SBLINKRECORD(sb, m); sb->sb_mbtail = nlast; SBLASTMBUFCHK(sb); SBLASTRECORDCHK(sb); return (1); } /* * Append address and data, and optionally, control (ancillary) data * to the receive queue of a socket. If present, * m0 must include a packet header with total length. * Returns 0 if no space in sockbuf or insufficient mbufs. */ int sbappendaddr(sb, asa, m0, control) struct sockbuf *sb; const struct sockaddr *asa; struct mbuf *m0, *control; { int retval; SOCKBUF_LOCK(sb); retval = sbappendaddr_locked(sb, asa, m0, control); SOCKBUF_UNLOCK(sb); return (retval); } int sbappendcontrol_locked(sb, m0, control) struct sockbuf *sb; struct mbuf *control, *m0; { struct mbuf *m, *n, *mlast; int space; SOCKBUF_LOCK_ASSERT(sb); if (control == 0) panic("sbappendcontrol_locked"); space = m_length(control, &n) + m_length(m0, NULL); if (space > sbspace(sb)) return (0); n->m_next = m0; /* concatenate data to control */ SBLASTRECORDCHK(sb); for (m = control; m->m_next; m = m->m_next) sballoc(sb, m); sballoc(sb, m); mlast = m; SBLINKRECORD(sb, control); sb->sb_mbtail = mlast; SBLASTMBUFCHK(sb); SBLASTRECORDCHK(sb); return (1); } int sbappendcontrol(sb, m0, control) struct sockbuf *sb; struct mbuf *control, *m0; { int retval; SOCKBUF_LOCK(sb); retval = sbappendcontrol_locked(sb, m0, control); SOCKBUF_UNLOCK(sb); return (retval); } /* * Append the data in mbuf chain (m) into the socket buffer sb following mbuf * (n). If (n) is NULL, the buffer is presumed empty. * * When the data is compressed, mbufs in the chain may be handled in one of * three ways: * * (1) The mbuf may simply be dropped, if it contributes nothing (no data, no * record boundary, and no change in data type). * * (2) The mbuf may be coalesced -- i.e., data in the mbuf may be copied into * an mbuf already in the socket buffer. This can occur if an * appropriate mbuf exists, there is room, and no merging of data types * will occur. * * (3) The mbuf may be appended to the end of the existing mbuf chain. * * If any of the new mbufs is marked as M_EOR, mark the last mbuf appended as * end-of-record. */ void sbcompress(sb, m, n) register struct sockbuf *sb; register struct mbuf *m, *n; { register int eor = 0; register struct mbuf *o; SOCKBUF_LOCK_ASSERT(sb); while (m) { eor |= m->m_flags & M_EOR; if (m->m_len == 0 && (eor == 0 || (((o = m->m_next) || (o = n)) && o->m_type == m->m_type))) { if (sb->sb_lastrecord == m) sb->sb_lastrecord = m->m_next; m = m_free(m); continue; } if (n && (n->m_flags & M_EOR) == 0 && M_WRITABLE(n) && m->m_len <= MCLBYTES / 4 && /* XXX: Don't copy too much */ m->m_len <= M_TRAILINGSPACE(n) && n->m_type == m->m_type) { bcopy(mtod(m, caddr_t), mtod(n, caddr_t) + n->m_len, (unsigned)m->m_len); n->m_len += m->m_len; sb->sb_cc += m->m_len; if (m->m_type != MT_DATA && m->m_type != MT_OOBDATA) /* XXX: Probably don't need.*/ sb->sb_ctl += m->m_len; m = m_free(m); continue; } if (n) n->m_next = m; else sb->sb_mb = m; sb->sb_mbtail = m; sballoc(sb, m); n = m; m->m_flags &= ~M_EOR; m = m->m_next; n->m_next = 0; } if (eor) { KASSERT(n != NULL, ("sbcompress: eor && n == NULL")); n->m_flags |= eor; } SBLASTMBUFCHK(sb); } /* * Free all mbufs in a sockbuf. * Check that all resources are reclaimed. */ void sbflush_locked(sb) register struct sockbuf *sb; { SOCKBUF_LOCK_ASSERT(sb); if (sb->sb_flags & SB_LOCK) panic("sbflush_locked: locked"); while (sb->sb_mbcnt) { /* * Don't call sbdrop(sb, 0) if the leading mbuf is non-empty: * we would loop forever. Panic instead. */ if (!sb->sb_cc && (sb->sb_mb == NULL || sb->sb_mb->m_len)) break; sbdrop_locked(sb, (int)sb->sb_cc); } if (sb->sb_cc || sb->sb_mb || sb->sb_mbcnt) panic("sbflush_locked: cc %u || mb %p || mbcnt %u", sb->sb_cc, (void *)sb->sb_mb, sb->sb_mbcnt); } void sbflush(sb) register struct sockbuf *sb; { SOCKBUF_LOCK(sb); sbflush_locked(sb); SOCKBUF_UNLOCK(sb); } /* * Drop data from (the front of) a sockbuf. */ void sbdrop_locked(sb, len) register struct sockbuf *sb; register int len; { register struct mbuf *m; struct mbuf *next; SOCKBUF_LOCK_ASSERT(sb); next = (m = sb->sb_mb) ? m->m_nextpkt : 0; while (len > 0) { if (m == 0) { if (next == 0) panic("sbdrop"); m = next; next = m->m_nextpkt; continue; } if (m->m_len > len) { m->m_len -= len; m->m_data += len; sb->sb_cc -= len; if (m->m_type != MT_DATA && m->m_type != MT_OOBDATA) sb->sb_ctl -= len; break; } len -= m->m_len; sbfree(sb, m); m = m_free(m); } while (m && m->m_len == 0) { sbfree(sb, m); m = m_free(m); } if (m) { sb->sb_mb = m; m->m_nextpkt = next; } else sb->sb_mb = next; /* * First part is an inline SB_EMPTY_FIXUP(). Second part * makes sure sb_lastrecord is up-to-date if we dropped * part of the last record. */ m = sb->sb_mb; if (m == NULL) { sb->sb_mbtail = NULL; sb->sb_lastrecord = NULL; } else if (m->m_nextpkt == NULL) { sb->sb_lastrecord = m; } } /* * Drop data from (the front of) a sockbuf. */ void sbdrop(sb, len) register struct sockbuf *sb; register int len; { SOCKBUF_LOCK(sb); sbdrop_locked(sb, len); SOCKBUF_UNLOCK(sb); } /* * Drop a record off the front of a sockbuf * and move the next record to the front. */ void sbdroprecord_locked(sb) register struct sockbuf *sb; { register struct mbuf *m; SOCKBUF_LOCK_ASSERT(sb); m = sb->sb_mb; if (m) { sb->sb_mb = m->m_nextpkt; do { sbfree(sb, m); m = m_free(m); } while (m); } SB_EMPTY_FIXUP(sb); } /* * Drop a record off the front of a sockbuf * and move the next record to the front. */ void sbdroprecord(sb) register struct sockbuf *sb; { SOCKBUF_LOCK(sb); sbdroprecord_locked(sb); SOCKBUF_UNLOCK(sb); } /* * Create a "control" mbuf containing the specified data * with the specified type for presentation on a socket buffer. */ struct mbuf * sbcreatecontrol(p, size, type, level) caddr_t p; register int size; int type, level; { register struct cmsghdr *cp; struct mbuf *m; if (CMSG_SPACE((u_int)size) > MCLBYTES) return ((struct mbuf *) NULL); if (CMSG_SPACE((u_int)size) > MLEN) m = m_getcl(M_DONTWAIT, MT_CONTROL, 0); else m = m_get(M_DONTWAIT, MT_CONTROL); if (m == NULL) return ((struct mbuf *) NULL); cp = mtod(m, struct cmsghdr *); m->m_len = 0; KASSERT(CMSG_SPACE((u_int)size) <= M_TRAILINGSPACE(m), ("sbcreatecontrol: short mbuf")); if (p != NULL) (void)memcpy(CMSG_DATA(cp), p, size); m->m_len = CMSG_SPACE(size); cp->cmsg_len = CMSG_LEN(size); cp->cmsg_level = level; cp->cmsg_type = type; return (m); } /* * Some routines that return EOPNOTSUPP for entry points that are not * supported by a protocol. Fill in as needed. */ void pru_abort_notsupp(struct socket *so) { } int pru_accept_notsupp(struct socket *so, struct sockaddr **nam) { return EOPNOTSUPP; } int pru_attach_notsupp(struct socket *so, int proto, struct thread *td) { return EOPNOTSUPP; } int pru_bind_notsupp(struct socket *so, struct sockaddr *nam, struct thread *td) { return EOPNOTSUPP; } int pru_connect_notsupp(struct socket *so, struct sockaddr *nam, struct thread *td) { return EOPNOTSUPP; } int pru_connect2_notsupp(struct socket *so1, struct socket *so2) { return EOPNOTSUPP; } int pru_control_notsupp(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp, struct thread *td) { return EOPNOTSUPP; } -int +void pru_detach_notsupp(struct socket *so) { - return EOPNOTSUPP; + } int pru_disconnect_notsupp(struct socket *so) { return EOPNOTSUPP; } int pru_listen_notsupp(struct socket *so, int backlog, struct thread *td) { return EOPNOTSUPP; } int pru_peeraddr_notsupp(struct socket *so, struct sockaddr **nam) { return EOPNOTSUPP; } int pru_rcvd_notsupp(struct socket *so, int flags) { return EOPNOTSUPP; } int pru_rcvoob_notsupp(struct socket *so, struct mbuf *m, int flags) { return EOPNOTSUPP; } int pru_send_notsupp(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *control, struct thread *td) { return EOPNOTSUPP; } /* * This isn't really a ``null'' operation, but it's the default one * and doesn't do anything destructive. */ int pru_sense_null(struct socket *so, struct stat *sb) { sb->st_blksize = so->so_snd.sb_hiwat; return 0; } int pru_shutdown_notsupp(struct socket *so) { return EOPNOTSUPP; } int pru_sockaddr_notsupp(struct socket *so, struct sockaddr **nam) { return EOPNOTSUPP; } int pru_sosend_notsupp(struct socket *so, struct sockaddr *addr, struct uio *uio, struct mbuf *top, struct mbuf *control, int flags, struct thread *td) { return EOPNOTSUPP; } int pru_soreceive_notsupp(struct socket *so, struct sockaddr **paddr, struct uio *uio, struct mbuf **mp0, struct mbuf **controlp, int *flagsp) { return EOPNOTSUPP; } int pru_sopoll_notsupp(struct socket *so, int events, struct ucred *cred, struct thread *td) { return EOPNOTSUPP; } /* * For protocol types that don't keep cached copies of labels in their * pcbs, provide a null sosetlabel that does a NOOP. */ void pru_sosetlabel_null(struct socket *so) { } /* * Make a copy of a sockaddr in a malloced buffer of type M_SONAME. */ struct sockaddr * sodupsockaddr(const struct sockaddr *sa, int mflags) { struct sockaddr *sa2; sa2 = malloc(sa->sa_len, M_SONAME, mflags); if (sa2) bcopy(sa, sa2, sa->sa_len); return sa2; } /* * Create an external-format (``xsocket'') structure using the information * in the kernel-format socket structure pointed to by so. This is done * to reduce the spew of irrelevant information over this interface, * to isolate user code from changes in the kernel structure, and * potentially to provide information-hiding if we decide that * some of this information should be hidden from users. */ void sotoxsocket(struct socket *so, struct xsocket *xso) { xso->xso_len = sizeof *xso; xso->xso_so = so; xso->so_type = so->so_type; xso->so_options = so->so_options; xso->so_linger = so->so_linger; xso->so_state = so->so_state; xso->so_pcb = so->so_pcb; xso->xso_protocol = so->so_proto->pr_protocol; xso->xso_family = so->so_proto->pr_domain->dom_family; xso->so_qlen = so->so_qlen; xso->so_incqlen = so->so_incqlen; xso->so_qlimit = so->so_qlimit; xso->so_timeo = so->so_timeo; xso->so_error = so->so_error; xso->so_pgid = so->so_sigio ? so->so_sigio->sio_pgid : 0; xso->so_oobmark = so->so_oobmark; sbtoxsockbuf(&so->so_snd, &xso->so_snd); sbtoxsockbuf(&so->so_rcv, &xso->so_rcv); xso->so_uid = so->so_cred->cr_uid; } /* * This does the same for sockbufs. Note that the xsockbuf structure, * since it is always embedded in a socket, does not include a self * pointer nor a length. We make this entry point public in case * some other mechanism needs it. */ void sbtoxsockbuf(struct sockbuf *sb, struct xsockbuf *xsb) { xsb->sb_cc = sb->sb_cc; xsb->sb_hiwat = sb->sb_hiwat; xsb->sb_mbcnt = sb->sb_mbcnt; xsb->sb_mbmax = sb->sb_mbmax; xsb->sb_lowat = sb->sb_lowat; xsb->sb_flags = sb->sb_flags; xsb->sb_timeo = sb->sb_timeo; } /* * Here is the definition of some of the basic objects in the kern.ipc * branch of the MIB. */ SYSCTL_NODE(_kern, KERN_IPC, ipc, CTLFLAG_RW, 0, "IPC"); /* This takes the place of kern.maxsockbuf, which moved to kern.ipc. */ static int dummy; SYSCTL_INT(_kern, KERN_DUMMY, dummy, CTLFLAG_RW, &dummy, 0, ""); SYSCTL_OID(_kern_ipc, KIPC_MAXSOCKBUF, maxsockbuf, CTLTYPE_ULONG|CTLFLAG_RW, &sb_max, 0, sysctl_handle_sb_max, "LU", "Maximum socket buffer size"); SYSCTL_INT(_kern_ipc, OID_AUTO, maxsockets, CTLFLAG_RDTUN, &maxsockets, 0, "Maximum number of sockets avaliable"); SYSCTL_ULONG(_kern_ipc, KIPC_SOCKBUF_WASTE, sockbuf_waste_factor, CTLFLAG_RW, &sb_efficiency, 0, ""); /* * Initialise maxsockets */ static void init_maxsockets(void *ignored) { TUNABLE_INT_FETCH("kern.ipc.maxsockets", &maxsockets); maxsockets = imax(maxsockets, imax(maxfiles, nmbclusters)); } SYSINIT(param, SI_SUB_TUNABLES, SI_ORDER_ANY, init_maxsockets, NULL); diff --git a/sys/kern/uipc_socket.c b/sys/kern/uipc_socket.c index 35bb1474d6c1..dadb06dcc630 100644 --- a/sys/kern/uipc_socket.c +++ b/sys/kern/uipc_socket.c @@ -1,2451 +1,2451 @@ /*- * Copyright (c) 1982, 1986, 1988, 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 2004 The FreeBSD Foundation * Copyright (c) 2004-2006 Robert N. M. Watson * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)uipc_socket.c 8.3 (Berkeley) 4/15/94 */ /* * Comments on the socket life cycle: * * soalloc() sets of socket layer state for a socket, called only by * socreate() and sonewconn(). Socket layer private. * * sdealloc() tears down socket layer state for a socket, called only by * sofree() and sonewconn(). Socket layer private. * * pru_attach() associates protocol layer state with an allocated socket; * called only once, may fail, aborting socket allocation. This is called * from socreate() and sonewconn(). Socket layer private. * * pru_detach() disassociates protocol layer state from an attached socket, * and will be called exactly once for sockets in which pru_attach() has * been successfully called. If pru_attach() returned an error, * pru_detach() will not be called. Socket layer private. * * socreate() creates a socket and attaches protocol state. This is a public * interface that may be used by socket layer consumers to create new * sockets. * * sonewconn() creates a socket and attaches protocol state. This is a * public interface that may be used by protocols to create new sockets when * a new connection is received and will be available for accept() on a * listen socket. * * soclose() destroys a socket after possibly waiting for it to disconnect. * This is a public interface that socket consumers should use to close and * release a socket when done with it. * * soabort() destroys a socket without waiting for it to disconnect (used * only for incoming connections that are already partially or fully * connected). This is used internally by the socket layer when clearing * listen socket queues (due to overflow or close on the listen socket), but * is also a public interface protocols may use to abort connections in * their incomplete listen queues should they no longer be required. Sockets * placed in completed connection listen queues should not be aborted. * * sofree() will free a socket and its protocol state if all references on * the socket have been released, and is the public interface to attempt to * free a socket when a reference is removed. This is a socket layer private * interface. * * NOTE: In addition to socreate() and soclose(), which provide a single * socket reference to the consumer to be managed as required, there are two * calls to explicitly manage socket references, soref(), and sorele(). * Currently, these are generally required only when transitioning a socket * from a listen queue to a file descriptor, in order to prevent garbage * collection of the socket at an untimely moment. For a number of reasons, * these interfaces are not preferred, and should be avoided. * * XXXRW: The behavior of sockets after soclose() but before the last * sorele() is poorly defined. We can probably entirely eliminate them with * a little work, since consumers are managing references anyway. */ #include __FBSDID("$FreeBSD$"); #include "opt_inet.h" #include "opt_mac.h" #include "opt_zero.h" #include "opt_compat.h" #include #include #include #include #include #include #include #include #include #include #include /* for struct knote */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef COMPAT_IA32 #include #include extern struct sysentvec ia32_freebsd_sysvec; #endif static int soreceive_rcvoob(struct socket *so, struct uio *uio, int flags); static void filt_sordetach(struct knote *kn); static int filt_soread(struct knote *kn, long hint); static void filt_sowdetach(struct knote *kn); static int filt_sowrite(struct knote *kn, long hint); static int filt_solisten(struct knote *kn, long hint); static struct filterops solisten_filtops = { 1, NULL, filt_sordetach, filt_solisten }; static struct filterops soread_filtops = { 1, NULL, filt_sordetach, filt_soread }; static struct filterops sowrite_filtops = { 1, NULL, filt_sowdetach, filt_sowrite }; uma_zone_t socket_zone; so_gen_t so_gencnt; /* generation count for sockets */ MALLOC_DEFINE(M_SONAME, "soname", "socket name"); MALLOC_DEFINE(M_PCB, "pcb", "protocol control block"); SYSCTL_DECL(_kern_ipc); static int somaxconn = SOMAXCONN; static int somaxconn_sysctl(SYSCTL_HANDLER_ARGS); /* XXX: we dont have SYSCTL_USHORT */ SYSCTL_PROC(_kern_ipc, KIPC_SOMAXCONN, somaxconn, CTLTYPE_UINT | CTLFLAG_RW, 0, sizeof(int), somaxconn_sysctl, "I", "Maximum pending socket connection " "queue size"); static int numopensockets; SYSCTL_INT(_kern_ipc, OID_AUTO, numopensockets, CTLFLAG_RD, &numopensockets, 0, "Number of open sockets"); #ifdef ZERO_COPY_SOCKETS /* These aren't static because they're used in other files. */ int so_zero_copy_send = 1; int so_zero_copy_receive = 1; SYSCTL_NODE(_kern_ipc, OID_AUTO, zero_copy, CTLFLAG_RD, 0, "Zero copy controls"); SYSCTL_INT(_kern_ipc_zero_copy, OID_AUTO, receive, CTLFLAG_RW, &so_zero_copy_receive, 0, "Enable zero copy receive"); SYSCTL_INT(_kern_ipc_zero_copy, OID_AUTO, send, CTLFLAG_RW, &so_zero_copy_send, 0, "Enable zero copy send"); #endif /* ZERO_COPY_SOCKETS */ /* * accept_mtx locks down per-socket fields relating to accept queues. See * socketvar.h for an annotation of the protected fields of struct socket. */ struct mtx accept_mtx; MTX_SYSINIT(accept_mtx, &accept_mtx, "accept", MTX_DEF); /* * so_global_mtx protects so_gencnt, numopensockets, and the per-socket * so_gencnt field. */ static struct mtx so_global_mtx; MTX_SYSINIT(so_global_mtx, &so_global_mtx, "so_glabel", MTX_DEF); /* * Socket operation routines. * These routines are called by the routines in * sys_socket.c or from a system process, and * implement the semantics of socket operations by * switching out to the protocol specific routines. */ /* * Get a socket structure from our zone, and initialize it. * Note that it would probably be better to allocate socket * and PCB at the same time, but I'm not convinced that all * the protocols can be easily modified to do this. * * soalloc() returns a socket with a ref count of 0. */ struct socket * soalloc(int mflags) { struct socket *so; so = uma_zalloc(socket_zone, mflags | M_ZERO); if (so != NULL) { #ifdef MAC if (mac_init_socket(so, mflags) != 0) { uma_zfree(socket_zone, so); return (NULL); } #endif SOCKBUF_LOCK_INIT(&so->so_snd, "so_snd"); SOCKBUF_LOCK_INIT(&so->so_rcv, "so_rcv"); TAILQ_INIT(&so->so_aiojobq); mtx_lock(&so_global_mtx); so->so_gencnt = ++so_gencnt; ++numopensockets; mtx_unlock(&so_global_mtx); } return (so); } /* * socreate returns a socket with a ref count of 1. The socket should be * closed with soclose(). */ int socreate(dom, aso, type, proto, cred, td) int dom; struct socket **aso; int type; int proto; struct ucred *cred; struct thread *td; { struct protosw *prp; struct socket *so; int error; if (proto) prp = pffindproto(dom, proto, type); else prp = pffindtype(dom, type); if (prp == NULL || prp->pr_usrreqs->pru_attach == NULL || prp->pr_usrreqs->pru_attach == pru_attach_notsupp) return (EPROTONOSUPPORT); if (jailed(cred) && jail_socket_unixiproute_only && prp->pr_domain->dom_family != PF_LOCAL && prp->pr_domain->dom_family != PF_INET && prp->pr_domain->dom_family != PF_ROUTE) { return (EPROTONOSUPPORT); } if (prp->pr_type != type) return (EPROTOTYPE); so = soalloc(M_WAITOK); if (so == NULL) return (ENOBUFS); TAILQ_INIT(&so->so_incomp); TAILQ_INIT(&so->so_comp); so->so_type = type; so->so_cred = crhold(cred); so->so_proto = prp; #ifdef MAC mac_create_socket(cred, so); #endif knlist_init(&so->so_rcv.sb_sel.si_note, SOCKBUF_MTX(&so->so_rcv), NULL, NULL, NULL); knlist_init(&so->so_snd.sb_sel.si_note, SOCKBUF_MTX(&so->so_snd), NULL, NULL, NULL); so->so_count = 1; error = (*prp->pr_usrreqs->pru_attach)(so, proto, td); if (error) { ACCEPT_LOCK(); SOCK_LOCK(so); so->so_state |= SS_NOFDREF; sorele(so); return (error); } *aso = so; return (0); } int sobind(so, nam, td) struct socket *so; struct sockaddr *nam; struct thread *td; { return ((*so->so_proto->pr_usrreqs->pru_bind)(so, nam, td)); } void sodealloc(struct socket *so) { KASSERT(so->so_count == 0, ("sodealloc(): so_count %d", so->so_count)); KASSERT(so->so_pcb == NULL, ("sodealloc(): so_pcb != NULL")); mtx_lock(&so_global_mtx); so->so_gencnt = ++so_gencnt; mtx_unlock(&so_global_mtx); if (so->so_rcv.sb_hiwat) (void)chgsbsize(so->so_cred->cr_uidinfo, &so->so_rcv.sb_hiwat, 0, RLIM_INFINITY); if (so->so_snd.sb_hiwat) (void)chgsbsize(so->so_cred->cr_uidinfo, &so->so_snd.sb_hiwat, 0, RLIM_INFINITY); #ifdef INET /* remove acccept filter if one is present. */ if (so->so_accf != NULL) do_setopt_accept_filter(so, NULL); #endif #ifdef MAC mac_destroy_socket(so); #endif crfree(so->so_cred); SOCKBUF_LOCK_DESTROY(&so->so_snd); SOCKBUF_LOCK_DESTROY(&so->so_rcv); uma_zfree(socket_zone, so); mtx_lock(&so_global_mtx); --numopensockets; mtx_unlock(&so_global_mtx); } /* * solisten() transitions a socket from a non-listening state to a listening * state, but can also be used to update the listen queue depth on an * existing listen socket. The protocol will call back into the sockets * layer using solisten_proto_check() and solisten_proto() to check and set * socket-layer listen state. Call backs are used so that the protocol can * acquire both protocol and socket layer locks in whatever order is required * by the protocol. * * Protocol implementors are advised to hold the socket lock across the * socket-layer test and set to avoid races at the socket layer. */ int solisten(so, backlog, td) struct socket *so; int backlog; struct thread *td; { return ((*so->so_proto->pr_usrreqs->pru_listen)(so, backlog, td)); } int solisten_proto_check(so) struct socket *so; { SOCK_LOCK_ASSERT(so); if (so->so_state & (SS_ISCONNECTED | SS_ISCONNECTING | SS_ISDISCONNECTING)) return (EINVAL); return (0); } void solisten_proto(so, backlog) struct socket *so; int backlog; { SOCK_LOCK_ASSERT(so); if (backlog < 0 || backlog > somaxconn) backlog = somaxconn; so->so_qlimit = backlog; so->so_options |= SO_ACCEPTCONN; } /* * Attempt to free a socket. This should really be sotryfree(). * - * We free the socket if the protocol is no longer interested in the socket, - * there's no file descriptor reference, and the refcount is 0. While the - * calling macro sotryfree() tests the refcount, sofree() has to test it - * again as it's possible to race with an accept()ing thread if the socket is - * in an listen queue of a listen socket, as being in the listen queue - * doesn't elevate the reference count. sofree() acquires the accept mutex - * early for this test in order to avoid that race. + * sofree() will succeed if: + * + * - There are no outstanding file descriptor references or related consumers + * (so_count == 0). + * + * - The socket has been closed by user space, if ever open (SS_NOFDREF). + * + * - The protocol does not have an outstanding strong reference on the socket + * (SS_PROTOREF). + * + * Otherwise, it will quietly abort so that a future call to sofree(), when + * conditions are right, can succeed. */ void sofree(so) struct socket *so; { struct socket *head; ACCEPT_LOCK_ASSERT(); SOCK_LOCK_ASSERT(so); - if (so->so_pcb != NULL || (so->so_state & SS_NOFDREF) == 0 || - so->so_count != 0 || (so->so_state & SS_PROTOREF)) { + if ((so->so_state & SS_NOFDREF) == 0 || so->so_count != 0 || + (so->so_state & SS_PROTOREF)) { SOCK_UNLOCK(so); ACCEPT_UNLOCK(); return; } head = so->so_head; if (head != NULL) { KASSERT((so->so_qstate & SQ_COMP) != 0 || (so->so_qstate & SQ_INCOMP) != 0, ("sofree: so_head != NULL, but neither SQ_COMP nor " "SQ_INCOMP")); KASSERT((so->so_qstate & SQ_COMP) == 0 || (so->so_qstate & SQ_INCOMP) == 0, ("sofree: so->so_qstate is SQ_COMP and also SQ_INCOMP")); /* * accept(2) is responsible draining the completed * connection queue and freeing those sockets, so * we just return here if this socket is currently * on the completed connection queue. Otherwise, * accept(2) may hang after select(2) has indicating * that a listening socket was ready. If it's an * incomplete connection, we remove it from the queue * and free it; otherwise, it won't be released until * the listening socket is closed. */ if ((so->so_qstate & SQ_COMP) != 0) { SOCK_UNLOCK(so); ACCEPT_UNLOCK(); return; } TAILQ_REMOVE(&head->so_incomp, so, so_list); head->so_incqlen--; so->so_qstate &= ~SQ_INCOMP; so->so_head = NULL; } KASSERT((so->so_qstate & SQ_COMP) == 0 && (so->so_qstate & SQ_INCOMP) == 0, ("sofree: so_head == NULL, but still SQ_COMP(%d) or SQ_INCOMP(%d)", so->so_qstate & SQ_COMP, so->so_qstate & SQ_INCOMP)); SOCK_UNLOCK(so); ACCEPT_UNLOCK(); + SOCKBUF_LOCK(&so->so_snd); so->so_snd.sb_flags |= SB_NOINTR; (void)sblock(&so->so_snd, M_WAITOK); /* * socantsendmore_locked() drops the socket buffer mutex so that it * can safely perform wakeups. Re-acquire the mutex before * continuing. */ socantsendmore_locked(so); SOCKBUF_LOCK(&so->so_snd); sbunlock(&so->so_snd); sbrelease_locked(&so->so_snd, so); SOCKBUF_UNLOCK(&so->so_snd); sorflush(so); knlist_destroy(&so->so_rcv.sb_sel.si_note); knlist_destroy(&so->so_snd.sb_sel.si_note); sodealloc(so); } /* * Close a socket on last file table reference removal. * Initiate disconnect if connected. * Free socket when disconnect complete. * * This function will sorele() the socket. Note that soclose() may be * called prior to the ref count reaching zero. The actual socket * structure will not be freed until the ref count reaches zero. */ int soclose(so) struct socket *so; { int error = 0; KASSERT(!(so->so_state & SS_NOFDREF), ("soclose: SS_NOFDREF on enter")); funsetown(&so->so_sigio); if (so->so_options & SO_ACCEPTCONN) { struct socket *sp; ACCEPT_LOCK(); while ((sp = TAILQ_FIRST(&so->so_incomp)) != NULL) { TAILQ_REMOVE(&so->so_incomp, sp, so_list); so->so_incqlen--; sp->so_qstate &= ~SQ_INCOMP; sp->so_head = NULL; ACCEPT_UNLOCK(); soabort(sp); ACCEPT_LOCK(); } while ((sp = TAILQ_FIRST(&so->so_comp)) != NULL) { TAILQ_REMOVE(&so->so_comp, sp, so_list); so->so_qlen--; sp->so_qstate &= ~SQ_COMP; sp->so_head = NULL; ACCEPT_UNLOCK(); soabort(sp); ACCEPT_LOCK(); } ACCEPT_UNLOCK(); } - if (so->so_pcb == NULL) - goto discard; if (so->so_state & SS_ISCONNECTED) { if ((so->so_state & SS_ISDISCONNECTING) == 0) { error = sodisconnect(so); if (error) goto drop; } if (so->so_options & SO_LINGER) { if ((so->so_state & SS_ISDISCONNECTING) && (so->so_state & SS_NBIO)) goto drop; while (so->so_state & SS_ISCONNECTED) { error = tsleep(&so->so_timeo, PSOCK | PCATCH, "soclos", so->so_linger * hz); if (error) break; } } } + drop: - if (so->so_pcb != NULL) { - int error2 = (*so->so_proto->pr_usrreqs->pru_detach)(so); - if (error == 0) - error = error2; - } -discard: + (*so->so_proto->pr_usrreqs->pru_detach)(so); ACCEPT_LOCK(); SOCK_LOCK(so); KASSERT((so->so_state & SS_NOFDREF) == 0, ("soclose: NOFDREF")); so->so_state |= SS_NOFDREF; sorele(so); return (error); } /* * soabort() allows the socket code or protocol code to detach a socket that * has been in an incomplete or completed listen queue, but has not yet been * accepted. * * This interface is tricky, because it is called on an unreferenced socket, * and must be called only by a thread that has actually removed the socket * from the listen queue it was on, or races with other threads are risked. * * This interface will call into the protocol code, so must not be called * with any socket locks held. Protocols do call it while holding their own * recursible protocol mutexes, but this is something that should be subject * to review in the future. * * XXXRW: Why do we maintain a distinction between pru_abort() and * pru_detach()? */ void soabort(so) struct socket *so; { /* * In as much as is possible, assert that no references to this * socket are held. This is not quite the same as asserting that the * current thread is responsible for arranging for no references, but * is as close as we can get for now. */ KASSERT(so->so_count == 0, ("soabort: so_count")); KASSERT(!(so->so_state & SS_PROTOREF), ("soabort: SS_PROTOREF")); KASSERT(so->so_state & SS_NOFDREF, ("soabort: !SS_NOFDREF")); (*so->so_proto->pr_usrreqs->pru_abort)(so); ACCEPT_LOCK(); SOCK_LOCK(so); sofree(so); } int soaccept(so, nam) struct socket *so; struct sockaddr **nam; { int error; SOCK_LOCK(so); KASSERT((so->so_state & SS_NOFDREF) != 0, ("soaccept: !NOFDREF")); so->so_state &= ~SS_NOFDREF; SOCK_UNLOCK(so); error = (*so->so_proto->pr_usrreqs->pru_accept)(so, nam); return (error); } int soconnect(so, nam, td) struct socket *so; struct sockaddr *nam; struct thread *td; { int error; if (so->so_options & SO_ACCEPTCONN) return (EOPNOTSUPP); /* * If protocol is connection-based, can only connect once. * Otherwise, if connected, try to disconnect first. * This allows user to disconnect by connecting to, e.g., * a null address. */ if (so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING) && ((so->so_proto->pr_flags & PR_CONNREQUIRED) || (error = sodisconnect(so)))) { error = EISCONN; } else { /* * Prevent accumulated error from previous connection * from biting us. */ so->so_error = 0; error = (*so->so_proto->pr_usrreqs->pru_connect)(so, nam, td); } return (error); } int soconnect2(so1, so2) struct socket *so1; struct socket *so2; { return ((*so1->so_proto->pr_usrreqs->pru_connect2)(so1, so2)); } int sodisconnect(so) struct socket *so; { int error; if ((so->so_state & SS_ISCONNECTED) == 0) return (ENOTCONN); if (so->so_state & SS_ISDISCONNECTING) return (EALREADY); error = (*so->so_proto->pr_usrreqs->pru_disconnect)(so); return (error); } #ifdef ZERO_COPY_SOCKETS struct so_zerocopy_stats{ int size_ok; int align_ok; int found_ifp; }; struct so_zerocopy_stats so_zerocp_stats = {0,0,0}; #include #include #include #include #include #include #endif /*ZERO_COPY_SOCKETS*/ /* * sosend_copyin() accepts a uio and prepares an mbuf chain holding part or * all of the data referenced by the uio. If desired, it uses zero-copy. * *space will be updated to reflect data copied in. * * NB: If atomic I/O is requested, the caller must already have checked that * space can hold resid bytes. * * NB: In the event of an error, the caller may need to free the partial * chain pointed to by *mpp. The contents of both *uio and *space may be * modified even in the case of an error. */ static int sosend_copyin(struct uio *uio, struct mbuf **retmp, int atomic, long *space, int flags) { struct mbuf *m, **mp, *top; long len, resid; int error; #ifdef ZERO_COPY_SOCKETS int cow_send; #endif *retmp = top = NULL; mp = ⊤ len = 0; resid = uio->uio_resid; error = 0; do { #ifdef ZERO_COPY_SOCKETS cow_send = 0; #endif /* ZERO_COPY_SOCKETS */ if (resid >= MINCLSIZE) { #ifdef ZERO_COPY_SOCKETS if (top == NULL) { MGETHDR(m, M_TRYWAIT, MT_DATA); if (m == NULL) { error = ENOBUFS; goto out; } m->m_pkthdr.len = 0; m->m_pkthdr.rcvif = NULL; } else { MGET(m, M_TRYWAIT, MT_DATA); if (m == NULL) { error = ENOBUFS; goto out; } } if (so_zero_copy_send && resid>=PAGE_SIZE && *space>=PAGE_SIZE && uio->uio_iov->iov_len>=PAGE_SIZE) { so_zerocp_stats.size_ok++; so_zerocp_stats.align_ok++; cow_send = socow_setup(m, uio); len = cow_send; } if (!cow_send) { MCLGET(m, M_TRYWAIT); if ((m->m_flags & M_EXT) == 0) { m_free(m); m = NULL; } else { len = min(min(MCLBYTES, resid), *space); } } #else /* ZERO_COPY_SOCKETS */ if (top == NULL) { m = m_getcl(M_TRYWAIT, MT_DATA, M_PKTHDR); m->m_pkthdr.len = 0; m->m_pkthdr.rcvif = NULL; } else m = m_getcl(M_TRYWAIT, MT_DATA, 0); len = min(min(MCLBYTES, resid), *space); #endif /* ZERO_COPY_SOCKETS */ } else { if (top == NULL) { m = m_gethdr(M_TRYWAIT, MT_DATA); m->m_pkthdr.len = 0; m->m_pkthdr.rcvif = NULL; len = min(min(MHLEN, resid), *space); /* * For datagram protocols, leave room * for protocol headers in first mbuf. */ if (atomic && m && len < MHLEN) MH_ALIGN(m, len); } else { m = m_get(M_TRYWAIT, MT_DATA); len = min(min(MLEN, resid), *space); } } if (m == NULL) { error = ENOBUFS; goto out; } *space -= len; #ifdef ZERO_COPY_SOCKETS if (cow_send) error = 0; else #endif /* ZERO_COPY_SOCKETS */ error = uiomove(mtod(m, void *), (int)len, uio); resid = uio->uio_resid; m->m_len = len; *mp = m; top->m_pkthdr.len += len; if (error) goto out; mp = &m->m_next; if (resid <= 0) { if (flags & MSG_EOR) top->m_flags |= M_EOR; break; } } while (*space > 0 && atomic); out: *retmp = top; return (error); } #define SBLOCKWAIT(f) (((f) & MSG_DONTWAIT) ? M_NOWAIT : M_WAITOK) int sosend_dgram(so, addr, uio, top, control, flags, td) struct socket *so; struct sockaddr *addr; struct uio *uio; struct mbuf *top; struct mbuf *control; int flags; struct thread *td; { long space, resid; int clen = 0, error, dontroute; int atomic = sosendallatonce(so) || top; KASSERT(so->so_type == SOCK_DGRAM, ("sodgram_send: !SOCK_DGRAM")); KASSERT(so->so_proto->pr_flags & PR_ATOMIC, ("sodgram_send: !PR_ATOMIC")); if (uio != NULL) resid = uio->uio_resid; else resid = top->m_pkthdr.len; /* * In theory resid should be unsigned. * However, space must be signed, as it might be less than 0 * if we over-committed, and we must use a signed comparison * of space and resid. On the other hand, a negative resid * causes us to loop sending 0-length segments to the protocol. * * Also check to make sure that MSG_EOR isn't used on SOCK_STREAM * type sockets since that's an error. */ if (resid < 0) { error = EINVAL; goto out; } dontroute = (flags & MSG_DONTROUTE) && (so->so_options & SO_DONTROUTE) == 0; if (td != NULL) td->td_proc->p_stats->p_ru.ru_msgsnd++; if (control != NULL) clen = control->m_len; SOCKBUF_LOCK(&so->so_snd); if (so->so_snd.sb_state & SBS_CANTSENDMORE) { SOCKBUF_UNLOCK(&so->so_snd); error = EPIPE; goto out; } if (so->so_error) { error = so->so_error; so->so_error = 0; SOCKBUF_UNLOCK(&so->so_snd); goto out; } if ((so->so_state & SS_ISCONNECTED) == 0) { /* * `sendto' and `sendmsg' is allowed on a connection- * based socket if it supports implied connect. * Return ENOTCONN if not connected and no address is * supplied. */ if ((so->so_proto->pr_flags & PR_CONNREQUIRED) && (so->so_proto->pr_flags & PR_IMPLOPCL) == 0) { if ((so->so_state & SS_ISCONFIRMING) == 0 && !(resid == 0 && clen != 0)) { SOCKBUF_UNLOCK(&so->so_snd); error = ENOTCONN; goto out; } } else if (addr == NULL) { if (so->so_proto->pr_flags & PR_CONNREQUIRED) error = ENOTCONN; else error = EDESTADDRREQ; SOCKBUF_UNLOCK(&so->so_snd); goto out; } } /* * Do we need MSG_OOB support in SOCK_DGRAM? Signs here may be a * problem and need fixing. */ space = sbspace(&so->so_snd); if (flags & MSG_OOB) space += 1024; space -= clen; if (resid > space) { error = EMSGSIZE; goto out; } SOCKBUF_UNLOCK(&so->so_snd); if (uio == NULL) { resid = 0; if (flags & MSG_EOR) top->m_flags |= M_EOR; } else { error = sosend_copyin(uio, &top, atomic, &space, flags); if (error) goto out; resid = uio->uio_resid; } KASSERT(resid == 0, ("sosend_dgram: resid != 0")); /* * XXXRW: Frobbing SO_DONTROUTE here is even worse without sblock * than with. */ if (dontroute) { SOCK_LOCK(so); so->so_options |= SO_DONTROUTE; SOCK_UNLOCK(so); } /* * XXX all the SBS_CANTSENDMORE checks previously * done could be out of date. We could have recieved * a reset packet in an interrupt or maybe we slept * while doing page faults in uiomove() etc. We could * probably recheck again inside the locking protection * here, but there are probably other places that this * also happens. We must rethink this. */ error = (*so->so_proto->pr_usrreqs->pru_send)(so, (flags & MSG_OOB) ? PRUS_OOB : /* * If the user set MSG_EOF, the protocol * understands this flag and nothing left to * send then use PRU_SEND_EOF instead of PRU_SEND. */ ((flags & MSG_EOF) && (so->so_proto->pr_flags & PR_IMPLOPCL) && (resid <= 0)) ? PRUS_EOF : /* If there is more to send set PRUS_MORETOCOME */ (resid > 0 && space > 0) ? PRUS_MORETOCOME : 0, top, addr, control, td); if (dontroute) { SOCK_LOCK(so); so->so_options &= ~SO_DONTROUTE; SOCK_UNLOCK(so); } clen = 0; control = NULL; top = NULL; out: if (top != NULL) m_freem(top); if (control != NULL) m_freem(control); return (error); } /* * Send on a socket. * If send must go all at once and message is larger than * send buffering, then hard error. * Lock against other senders. * If must go all at once and not enough room now, then * inform user that this would block and do nothing. * Otherwise, if nonblocking, send as much as possible. * The data to be sent is described by "uio" if nonzero, * otherwise by the mbuf chain "top" (which must be null * if uio is not). Data provided in mbuf chain must be small * enough to send all at once. * * Returns nonzero on error, timeout or signal; callers * must check for short counts if EINTR/ERESTART are returned. * Data and control buffers are freed on return. */ #define snderr(errno) { error = (errno); goto release; } int sosend(so, addr, uio, top, control, flags, td) struct socket *so; struct sockaddr *addr; struct uio *uio; struct mbuf *top; struct mbuf *control; int flags; struct thread *td; { long space, resid; int clen = 0, error, dontroute; int atomic = sosendallatonce(so) || top; if (uio != NULL) resid = uio->uio_resid; else resid = top->m_pkthdr.len; /* * In theory resid should be unsigned. * However, space must be signed, as it might be less than 0 * if we over-committed, and we must use a signed comparison * of space and resid. On the other hand, a negative resid * causes us to loop sending 0-length segments to the protocol. * * Also check to make sure that MSG_EOR isn't used on SOCK_STREAM * type sockets since that's an error. */ if (resid < 0 || (so->so_type == SOCK_STREAM && (flags & MSG_EOR))) { error = EINVAL; goto out; } dontroute = (flags & MSG_DONTROUTE) && (so->so_options & SO_DONTROUTE) == 0 && (so->so_proto->pr_flags & PR_ATOMIC); if (td != NULL) td->td_proc->p_stats->p_ru.ru_msgsnd++; if (control != NULL) clen = control->m_len; SOCKBUF_LOCK(&so->so_snd); restart: SOCKBUF_LOCK_ASSERT(&so->so_snd); error = sblock(&so->so_snd, SBLOCKWAIT(flags)); if (error) goto out_locked; do { SOCKBUF_LOCK_ASSERT(&so->so_snd); if (so->so_snd.sb_state & SBS_CANTSENDMORE) snderr(EPIPE); if (so->so_error) { error = so->so_error; so->so_error = 0; goto release; } if ((so->so_state & SS_ISCONNECTED) == 0) { /* * `sendto' and `sendmsg' is allowed on a connection- * based socket if it supports implied connect. * Return ENOTCONN if not connected and no address is * supplied. */ if ((so->so_proto->pr_flags & PR_CONNREQUIRED) && (so->so_proto->pr_flags & PR_IMPLOPCL) == 0) { if ((so->so_state & SS_ISCONFIRMING) == 0 && !(resid == 0 && clen != 0)) snderr(ENOTCONN); } else if (addr == NULL) snderr(so->so_proto->pr_flags & PR_CONNREQUIRED ? ENOTCONN : EDESTADDRREQ); } space = sbspace(&so->so_snd); if (flags & MSG_OOB) space += 1024; if ((atomic && resid > so->so_snd.sb_hiwat) || clen > so->so_snd.sb_hiwat) snderr(EMSGSIZE); if (space < resid + clen && (atomic || space < so->so_snd.sb_lowat || space < clen)) { if ((so->so_state & SS_NBIO) || (flags & MSG_NBIO)) snderr(EWOULDBLOCK); sbunlock(&so->so_snd); error = sbwait(&so->so_snd); if (error) goto out_locked; goto restart; } SOCKBUF_UNLOCK(&so->so_snd); space -= clen; do { if (uio == NULL) { resid = 0; if (flags & MSG_EOR) top->m_flags |= M_EOR; } else { error = sosend_copyin(uio, &top, atomic, &space, flags); if (error != 0) { SOCKBUF_LOCK(&so->so_snd); goto release; } resid = uio->uio_resid; } if (dontroute) { SOCK_LOCK(so); so->so_options |= SO_DONTROUTE; SOCK_UNLOCK(so); } /* * XXX all the SBS_CANTSENDMORE checks previously * done could be out of date. We could have recieved * a reset packet in an interrupt or maybe we slept * while doing page faults in uiomove() etc. We could * probably recheck again inside the locking protection * here, but there are probably other places that this * also happens. We must rethink this. */ error = (*so->so_proto->pr_usrreqs->pru_send)(so, (flags & MSG_OOB) ? PRUS_OOB : /* * If the user set MSG_EOF, the protocol * understands this flag and nothing left to * send then use PRU_SEND_EOF instead of PRU_SEND. */ ((flags & MSG_EOF) && (so->so_proto->pr_flags & PR_IMPLOPCL) && (resid <= 0)) ? PRUS_EOF : /* If there is more to send set PRUS_MORETOCOME */ (resid > 0 && space > 0) ? PRUS_MORETOCOME : 0, top, addr, control, td); if (dontroute) { SOCK_LOCK(so); so->so_options &= ~SO_DONTROUTE; SOCK_UNLOCK(so); } clen = 0; control = NULL; top = NULL; if (error) { SOCKBUF_LOCK(&so->so_snd); goto release; } } while (resid && space > 0); SOCKBUF_LOCK(&so->so_snd); } while (resid); release: SOCKBUF_LOCK_ASSERT(&so->so_snd); sbunlock(&so->so_snd); out_locked: SOCKBUF_LOCK_ASSERT(&so->so_snd); SOCKBUF_UNLOCK(&so->so_snd); out: if (top != NULL) m_freem(top); if (control != NULL) m_freem(control); return (error); } #undef snderr /* * The part of soreceive() that implements reading non-inline out-of-band * data from a socket. For more complete comments, see soreceive(), from * which this code originated. * * Note that soreceive_rcvoob(), unlike the remainder of soreceive(), is * unable to return an mbuf chain to the caller. */ static int soreceive_rcvoob(so, uio, flags) struct socket *so; struct uio *uio; int flags; { struct protosw *pr = so->so_proto; struct mbuf *m; int error; KASSERT(flags & MSG_OOB, ("soreceive_rcvoob: (flags & MSG_OOB) == 0")); m = m_get(M_TRYWAIT, MT_DATA); if (m == NULL) return (ENOBUFS); error = (*pr->pr_usrreqs->pru_rcvoob)(so, m, flags & MSG_PEEK); if (error) goto bad; do { #ifdef ZERO_COPY_SOCKETS if (so_zero_copy_receive) { int disposable; if ((m->m_flags & M_EXT) && (m->m_ext.ext_type == EXT_DISPOSABLE)) disposable = 1; else disposable = 0; error = uiomoveco(mtod(m, void *), min(uio->uio_resid, m->m_len), uio, disposable); } else #endif /* ZERO_COPY_SOCKETS */ error = uiomove(mtod(m, void *), (int) min(uio->uio_resid, m->m_len), uio); m = m_free(m); } while (uio->uio_resid && error == 0 && m); bad: if (m != NULL) m_freem(m); return (error); } /* * Following replacement or removal of the first mbuf on the first mbuf chain * of a socket buffer, push necessary state changes back into the socket * buffer so that other consumers see the values consistently. 'nextrecord' * is the callers locally stored value of the original value of * sb->sb_mb->m_nextpkt which must be restored when the lead mbuf changes. * NOTE: 'nextrecord' may be NULL. */ static __inline void sockbuf_pushsync(struct sockbuf *sb, struct mbuf *nextrecord) { SOCKBUF_LOCK_ASSERT(sb); /* * First, update for the new value of nextrecord. If necessary, make * it the first record. */ if (sb->sb_mb != NULL) sb->sb_mb->m_nextpkt = nextrecord; else sb->sb_mb = nextrecord; /* * Now update any dependent socket buffer fields to reflect the new * state. This is an expanded inline of SB_EMPTY_FIXUP(), with the * addition of a second clause that takes care of the case where * sb_mb has been updated, but remains the last record. */ if (sb->sb_mb == NULL) { sb->sb_mbtail = NULL; sb->sb_lastrecord = NULL; } else if (sb->sb_mb->m_nextpkt == NULL) sb->sb_lastrecord = sb->sb_mb; } /* * Implement receive operations on a socket. * We depend on the way that records are added to the sockbuf * by sbappend*. In particular, each record (mbufs linked through m_next) * must begin with an address if the protocol so specifies, * followed by an optional mbuf or mbufs containing ancillary data, * and then zero or more mbufs of data. * In order to avoid blocking network interrupts for the entire time here, * we splx() while doing the actual copy to user space. * Although the sockbuf is locked, new data may still be appended, * and thus we must maintain consistency of the sockbuf during that time. * * The caller may receive the data as a single mbuf chain by supplying * an mbuf **mp0 for use in returning the chain. The uio is then used * only for the count in uio_resid. */ int soreceive(so, psa, uio, mp0, controlp, flagsp) struct socket *so; struct sockaddr **psa; struct uio *uio; struct mbuf **mp0; struct mbuf **controlp; int *flagsp; { struct mbuf *m, **mp; int flags, len, error, offset; struct protosw *pr = so->so_proto; struct mbuf *nextrecord; int moff, type = 0; int orig_resid = uio->uio_resid; mp = mp0; if (psa != NULL) *psa = NULL; if (controlp != NULL) *controlp = NULL; if (flagsp != NULL) flags = *flagsp &~ MSG_EOR; else flags = 0; if (flags & MSG_OOB) return (soreceive_rcvoob(so, uio, flags)); if (mp != NULL) *mp = NULL; if ((pr->pr_flags & PR_WANTRCVD) && (so->so_state & SS_ISCONFIRMING) && uio->uio_resid) (*pr->pr_usrreqs->pru_rcvd)(so, 0); SOCKBUF_LOCK(&so->so_rcv); restart: SOCKBUF_LOCK_ASSERT(&so->so_rcv); error = sblock(&so->so_rcv, SBLOCKWAIT(flags)); if (error) goto out; m = so->so_rcv.sb_mb; /* * If we have less data than requested, block awaiting more * (subject to any timeout) if: * 1. the current count is less than the low water mark, or * 2. MSG_WAITALL is set, and it is possible to do the entire * receive operation at once if we block (resid <= hiwat). * 3. MSG_DONTWAIT is not set * If MSG_WAITALL is set but resid is larger than the receive buffer, * we have to do the receive in sections, and thus risk returning * a short count if a timeout or signal occurs after we start. */ if (m == NULL || (((flags & MSG_DONTWAIT) == 0 && so->so_rcv.sb_cc < uio->uio_resid) && (so->so_rcv.sb_cc < so->so_rcv.sb_lowat || ((flags & MSG_WAITALL) && uio->uio_resid <= so->so_rcv.sb_hiwat)) && m->m_nextpkt == NULL && (pr->pr_flags & PR_ATOMIC) == 0)) { KASSERT(m != NULL || !so->so_rcv.sb_cc, ("receive: m == %p so->so_rcv.sb_cc == %u", m, so->so_rcv.sb_cc)); if (so->so_error) { if (m != NULL) goto dontblock; error = so->so_error; if ((flags & MSG_PEEK) == 0) so->so_error = 0; goto release; } SOCKBUF_LOCK_ASSERT(&so->so_rcv); if (so->so_rcv.sb_state & SBS_CANTRCVMORE) { if (m) goto dontblock; else goto release; } for (; m != NULL; m = m->m_next) if (m->m_type == MT_OOBDATA || (m->m_flags & M_EOR)) { m = so->so_rcv.sb_mb; goto dontblock; } if ((so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING)) == 0 && (so->so_proto->pr_flags & PR_CONNREQUIRED)) { error = ENOTCONN; goto release; } if (uio->uio_resid == 0) goto release; if ((so->so_state & SS_NBIO) || (flags & (MSG_DONTWAIT|MSG_NBIO))) { error = EWOULDBLOCK; goto release; } SBLASTRECORDCHK(&so->so_rcv); SBLASTMBUFCHK(&so->so_rcv); sbunlock(&so->so_rcv); error = sbwait(&so->so_rcv); if (error) goto out; goto restart; } dontblock: /* * From this point onward, we maintain 'nextrecord' as a cache of the * pointer to the next record in the socket buffer. We must keep the * various socket buffer pointers and local stack versions of the * pointers in sync, pushing out modifications before dropping the * socket buffer mutex, and re-reading them when picking it up. * * Otherwise, we will race with the network stack appending new data * or records onto the socket buffer by using inconsistent/stale * versions of the field, possibly resulting in socket buffer * corruption. * * By holding the high-level sblock(), we prevent simultaneous * readers from pulling off the front of the socket buffer. */ SOCKBUF_LOCK_ASSERT(&so->so_rcv); if (uio->uio_td) uio->uio_td->td_proc->p_stats->p_ru.ru_msgrcv++; KASSERT(m == so->so_rcv.sb_mb, ("soreceive: m != so->so_rcv.sb_mb")); SBLASTRECORDCHK(&so->so_rcv); SBLASTMBUFCHK(&so->so_rcv); nextrecord = m->m_nextpkt; if (pr->pr_flags & PR_ADDR) { KASSERT(m->m_type == MT_SONAME, ("m->m_type == %d", m->m_type)); orig_resid = 0; if (psa != NULL) *psa = sodupsockaddr(mtod(m, struct sockaddr *), M_NOWAIT); if (flags & MSG_PEEK) { m = m->m_next; } else { sbfree(&so->so_rcv, m); so->so_rcv.sb_mb = m_free(m); m = so->so_rcv.sb_mb; sockbuf_pushsync(&so->so_rcv, nextrecord); } } /* * Process one or more MT_CONTROL mbufs present before any data mbufs * in the first mbuf chain on the socket buffer. If MSG_PEEK, we * just copy the data; if !MSG_PEEK, we call into the protocol to * perform externalization (or freeing if controlp == NULL). */ if (m != NULL && m->m_type == MT_CONTROL) { struct mbuf *cm = NULL, *cmn; struct mbuf **cme = &cm; do { if (flags & MSG_PEEK) { if (controlp != NULL) { *controlp = m_copy(m, 0, m->m_len); controlp = &(*controlp)->m_next; } m = m->m_next; } else { sbfree(&so->so_rcv, m); so->so_rcv.sb_mb = m->m_next; m->m_next = NULL; *cme = m; cme = &(*cme)->m_next; m = so->so_rcv.sb_mb; } } while (m != NULL && m->m_type == MT_CONTROL); if ((flags & MSG_PEEK) == 0) sockbuf_pushsync(&so->so_rcv, nextrecord); while (cm != NULL) { cmn = cm->m_next; cm->m_next = NULL; if (pr->pr_domain->dom_externalize != NULL) { SOCKBUF_UNLOCK(&so->so_rcv); error = (*pr->pr_domain->dom_externalize) (cm, controlp); SOCKBUF_LOCK(&so->so_rcv); } else if (controlp != NULL) *controlp = cm; else m_freem(cm); if (controlp != NULL) { orig_resid = 0; while (*controlp != NULL) controlp = &(*controlp)->m_next; } cm = cmn; } if (so->so_rcv.sb_mb) nextrecord = so->so_rcv.sb_mb->m_nextpkt; else nextrecord = NULL; orig_resid = 0; } if (m != NULL) { if ((flags & MSG_PEEK) == 0) { KASSERT(m->m_nextpkt == nextrecord, ("soreceive: post-control, nextrecord !sync")); if (nextrecord == NULL) { KASSERT(so->so_rcv.sb_mb == m, ("soreceive: post-control, sb_mb!=m")); KASSERT(so->so_rcv.sb_lastrecord == m, ("soreceive: post-control, lastrecord!=m")); } } type = m->m_type; if (type == MT_OOBDATA) flags |= MSG_OOB; } else { if ((flags & MSG_PEEK) == 0) { KASSERT(so->so_rcv.sb_mb == nextrecord, ("soreceive: sb_mb != nextrecord")); if (so->so_rcv.sb_mb == NULL) { KASSERT(so->so_rcv.sb_lastrecord == NULL, ("soreceive: sb_lastercord != NULL")); } } } SOCKBUF_LOCK_ASSERT(&so->so_rcv); SBLASTRECORDCHK(&so->so_rcv); SBLASTMBUFCHK(&so->so_rcv); /* * Now continue to read any data mbufs off of the head of the socket * buffer until the read request is satisfied. Note that 'type' is * used to store the type of any mbuf reads that have happened so far * such that soreceive() can stop reading if the type changes, which * causes soreceive() to return only one of regular data and inline * out-of-band data in a single socket receive operation. */ moff = 0; offset = 0; while (m != NULL && uio->uio_resid > 0 && error == 0) { /* * If the type of mbuf has changed since the last mbuf * examined ('type'), end the receive operation. */ SOCKBUF_LOCK_ASSERT(&so->so_rcv); if (m->m_type == MT_OOBDATA) { if (type != MT_OOBDATA) break; } else if (type == MT_OOBDATA) break; else KASSERT(m->m_type == MT_DATA, ("m->m_type == %d", m->m_type)); so->so_rcv.sb_state &= ~SBS_RCVATMARK; len = uio->uio_resid; if (so->so_oobmark && len > so->so_oobmark - offset) len = so->so_oobmark - offset; if (len > m->m_len - moff) len = m->m_len - moff; /* * If mp is set, just pass back the mbufs. * Otherwise copy them out via the uio, then free. * Sockbuf must be consistent here (points to current mbuf, * it points to next record) when we drop priority; * we must note any additions to the sockbuf when we * block interrupts again. */ if (mp == NULL) { SOCKBUF_LOCK_ASSERT(&so->so_rcv); SBLASTRECORDCHK(&so->so_rcv); SBLASTMBUFCHK(&so->so_rcv); SOCKBUF_UNLOCK(&so->so_rcv); #ifdef ZERO_COPY_SOCKETS if (so_zero_copy_receive) { int disposable; if ((m->m_flags & M_EXT) && (m->m_ext.ext_type == EXT_DISPOSABLE)) disposable = 1; else disposable = 0; error = uiomoveco(mtod(m, char *) + moff, (int)len, uio, disposable); } else #endif /* ZERO_COPY_SOCKETS */ error = uiomove(mtod(m, char *) + moff, (int)len, uio); SOCKBUF_LOCK(&so->so_rcv); if (error) goto release; } else uio->uio_resid -= len; SOCKBUF_LOCK_ASSERT(&so->so_rcv); if (len == m->m_len - moff) { if (m->m_flags & M_EOR) flags |= MSG_EOR; if (flags & MSG_PEEK) { m = m->m_next; moff = 0; } else { nextrecord = m->m_nextpkt; sbfree(&so->so_rcv, m); if (mp != NULL) { *mp = m; mp = &m->m_next; so->so_rcv.sb_mb = m = m->m_next; *mp = NULL; } else { so->so_rcv.sb_mb = m_free(m); m = so->so_rcv.sb_mb; } sockbuf_pushsync(&so->so_rcv, nextrecord); SBLASTRECORDCHK(&so->so_rcv); SBLASTMBUFCHK(&so->so_rcv); } } else { if (flags & MSG_PEEK) moff += len; else { if (mp != NULL) { int copy_flag; if (flags & MSG_DONTWAIT) copy_flag = M_DONTWAIT; else copy_flag = M_TRYWAIT; if (copy_flag == M_TRYWAIT) SOCKBUF_UNLOCK(&so->so_rcv); *mp = m_copym(m, 0, len, copy_flag); if (copy_flag == M_TRYWAIT) SOCKBUF_LOCK(&so->so_rcv); if (*mp == NULL) { /* * m_copym() couldn't allocate an mbuf. * Adjust uio_resid back (it was adjusted * down by len bytes, which we didn't end * up "copying" over). */ uio->uio_resid += len; break; } } m->m_data += len; m->m_len -= len; so->so_rcv.sb_cc -= len; } } SOCKBUF_LOCK_ASSERT(&so->so_rcv); if (so->so_oobmark) { if ((flags & MSG_PEEK) == 0) { so->so_oobmark -= len; if (so->so_oobmark == 0) { so->so_rcv.sb_state |= SBS_RCVATMARK; break; } } else { offset += len; if (offset == so->so_oobmark) break; } } if (flags & MSG_EOR) break; /* * If the MSG_WAITALL flag is set (for non-atomic socket), * we must not quit until "uio->uio_resid == 0" or an error * termination. If a signal/timeout occurs, return * with a short count but without error. * Keep sockbuf locked against other readers. */ while (flags & MSG_WAITALL && m == NULL && uio->uio_resid > 0 && !sosendallatonce(so) && nextrecord == NULL) { SOCKBUF_LOCK_ASSERT(&so->so_rcv); if (so->so_error || so->so_rcv.sb_state & SBS_CANTRCVMORE) break; /* * Notify the protocol that some data has been * drained before blocking. */ - if (pr->pr_flags & PR_WANTRCVD && so->so_pcb != NULL) { + if (pr->pr_flags & PR_WANTRCVD) { SOCKBUF_UNLOCK(&so->so_rcv); (*pr->pr_usrreqs->pru_rcvd)(so, flags); SOCKBUF_LOCK(&so->so_rcv); } SBLASTRECORDCHK(&so->so_rcv); SBLASTMBUFCHK(&so->so_rcv); error = sbwait(&so->so_rcv); if (error) goto release; m = so->so_rcv.sb_mb; if (m != NULL) nextrecord = m->m_nextpkt; } } SOCKBUF_LOCK_ASSERT(&so->so_rcv); if (m != NULL && pr->pr_flags & PR_ATOMIC) { flags |= MSG_TRUNC; if ((flags & MSG_PEEK) == 0) (void) sbdroprecord_locked(&so->so_rcv); } if ((flags & MSG_PEEK) == 0) { if (m == NULL) { /* * First part is an inline SB_EMPTY_FIXUP(). Second * part makes sure sb_lastrecord is up-to-date if * there is still data in the socket buffer. */ so->so_rcv.sb_mb = nextrecord; if (so->so_rcv.sb_mb == NULL) { so->so_rcv.sb_mbtail = NULL; so->so_rcv.sb_lastrecord = NULL; } else if (nextrecord->m_nextpkt == NULL) so->so_rcv.sb_lastrecord = nextrecord; } SBLASTRECORDCHK(&so->so_rcv); SBLASTMBUFCHK(&so->so_rcv); /* * If soreceive() is being done from the socket callback, then * don't need to generate ACK to peer to update window, since * ACK will be generated on return to TCP. */ if (!(flags & MSG_SOCALLBCK) && - (pr->pr_flags & PR_WANTRCVD) && so->so_pcb) { + (pr->pr_flags & PR_WANTRCVD)) { SOCKBUF_UNLOCK(&so->so_rcv); (*pr->pr_usrreqs->pru_rcvd)(so, flags); SOCKBUF_LOCK(&so->so_rcv); } } SOCKBUF_LOCK_ASSERT(&so->so_rcv); if (orig_resid == uio->uio_resid && orig_resid && (flags & MSG_EOR) == 0 && (so->so_rcv.sb_state & SBS_CANTRCVMORE) == 0) { sbunlock(&so->so_rcv); goto restart; } if (flagsp != NULL) *flagsp |= flags; release: SOCKBUF_LOCK_ASSERT(&so->so_rcv); sbunlock(&so->so_rcv); out: SOCKBUF_LOCK_ASSERT(&so->so_rcv); SOCKBUF_UNLOCK(&so->so_rcv); return (error); } int soshutdown(so, how) struct socket *so; int how; { struct protosw *pr = so->so_proto; if (!(how == SHUT_RD || how == SHUT_WR || how == SHUT_RDWR)) return (EINVAL); if (how != SHUT_WR) sorflush(so); if (how != SHUT_RD) return ((*pr->pr_usrreqs->pru_shutdown)(so)); return (0); } void sorflush(so) struct socket *so; { struct sockbuf *sb = &so->so_rcv; struct protosw *pr = so->so_proto; struct sockbuf asb; /* * XXXRW: This is quite ugly. Previously, this code made a copy of * the socket buffer, then zero'd the original to clear the buffer * fields. However, with mutexes in the socket buffer, this causes * problems. We only clear the zeroable bits of the original; * however, we have to initialize and destroy the mutex in the copy * so that dom_dispose() and sbrelease() can lock t as needed. */ SOCKBUF_LOCK(sb); sb->sb_flags |= SB_NOINTR; (void) sblock(sb, M_WAITOK); /* * socantrcvmore_locked() drops the socket buffer mutex so that it * can safely perform wakeups. Re-acquire the mutex before * continuing. */ socantrcvmore_locked(so); SOCKBUF_LOCK(sb); sbunlock(sb); /* * Invalidate/clear most of the sockbuf structure, but leave * selinfo and mutex data unchanged. */ bzero(&asb, offsetof(struct sockbuf, sb_startzero)); bcopy(&sb->sb_startzero, &asb.sb_startzero, sizeof(*sb) - offsetof(struct sockbuf, sb_startzero)); bzero(&sb->sb_startzero, sizeof(*sb) - offsetof(struct sockbuf, sb_startzero)); SOCKBUF_UNLOCK(sb); SOCKBUF_LOCK_INIT(&asb, "so_rcv"); if (pr->pr_flags & PR_RIGHTS && pr->pr_domain->dom_dispose != NULL) (*pr->pr_domain->dom_dispose)(asb.sb_mb); sbrelease(&asb, so); SOCKBUF_LOCK_DESTROY(&asb); } /* * Perhaps this routine, and sooptcopyout(), below, ought to come in * an additional variant to handle the case where the option value needs * to be some kind of integer, but not a specific size. * In addition to their use here, these functions are also called by the * protocol-level pr_ctloutput() routines. */ int sooptcopyin(sopt, buf, len, minlen) struct sockopt *sopt; void *buf; size_t len; size_t minlen; { size_t valsize; /* * If the user gives us more than we wanted, we ignore it, * but if we don't get the minimum length the caller * wants, we return EINVAL. On success, sopt->sopt_valsize * is set to however much we actually retrieved. */ if ((valsize = sopt->sopt_valsize) < minlen) return EINVAL; if (valsize > len) sopt->sopt_valsize = valsize = len; if (sopt->sopt_td != NULL) return (copyin(sopt->sopt_val, buf, valsize)); bcopy(sopt->sopt_val, buf, valsize); return (0); } /* * Kernel version of setsockopt(2)/ * XXX: optlen is size_t, not socklen_t */ int so_setsockopt(struct socket *so, int level, int optname, void *optval, size_t optlen) { struct sockopt sopt; sopt.sopt_level = level; sopt.sopt_name = optname; sopt.sopt_dir = SOPT_SET; sopt.sopt_val = optval; sopt.sopt_valsize = optlen; sopt.sopt_td = NULL; return (sosetopt(so, &sopt)); } int sosetopt(so, sopt) struct socket *so; struct sockopt *sopt; { int error, optval; struct linger l; struct timeval tv; u_long val; #ifdef MAC struct mac extmac; #endif error = 0; if (sopt->sopt_level != SOL_SOCKET) { if (so->so_proto && so->so_proto->pr_ctloutput) return ((*so->so_proto->pr_ctloutput) (so, sopt)); error = ENOPROTOOPT; } else { switch (sopt->sopt_name) { #ifdef INET case SO_ACCEPTFILTER: error = do_setopt_accept_filter(so, sopt); if (error) goto bad; break; #endif case SO_LINGER: error = sooptcopyin(sopt, &l, sizeof l, sizeof l); if (error) goto bad; SOCK_LOCK(so); so->so_linger = l.l_linger; if (l.l_onoff) so->so_options |= SO_LINGER; else so->so_options &= ~SO_LINGER; SOCK_UNLOCK(so); break; case SO_DEBUG: case SO_KEEPALIVE: case SO_DONTROUTE: case SO_USELOOPBACK: case SO_BROADCAST: case SO_REUSEADDR: case SO_REUSEPORT: case SO_OOBINLINE: case SO_TIMESTAMP: case SO_BINTIME: case SO_NOSIGPIPE: error = sooptcopyin(sopt, &optval, sizeof optval, sizeof optval); if (error) goto bad; SOCK_LOCK(so); if (optval) so->so_options |= sopt->sopt_name; else so->so_options &= ~sopt->sopt_name; SOCK_UNLOCK(so); break; case SO_SNDBUF: case SO_RCVBUF: case SO_SNDLOWAT: case SO_RCVLOWAT: error = sooptcopyin(sopt, &optval, sizeof optval, sizeof optval); if (error) goto bad; /* * Values < 1 make no sense for any of these * options, so disallow them. */ if (optval < 1) { error = EINVAL; goto bad; } switch (sopt->sopt_name) { case SO_SNDBUF: case SO_RCVBUF: if (sbreserve(sopt->sopt_name == SO_SNDBUF ? &so->so_snd : &so->so_rcv, (u_long)optval, so, curthread) == 0) { error = ENOBUFS; goto bad; } break; /* * Make sure the low-water is never greater than * the high-water. */ case SO_SNDLOWAT: SOCKBUF_LOCK(&so->so_snd); so->so_snd.sb_lowat = (optval > so->so_snd.sb_hiwat) ? so->so_snd.sb_hiwat : optval; SOCKBUF_UNLOCK(&so->so_snd); break; case SO_RCVLOWAT: SOCKBUF_LOCK(&so->so_rcv); so->so_rcv.sb_lowat = (optval > so->so_rcv.sb_hiwat) ? so->so_rcv.sb_hiwat : optval; SOCKBUF_UNLOCK(&so->so_rcv); break; } break; case SO_SNDTIMEO: case SO_RCVTIMEO: #ifdef COMPAT_IA32 if (curthread->td_proc->p_sysent == &ia32_freebsd_sysvec) { struct timeval32 tv32; error = sooptcopyin(sopt, &tv32, sizeof tv32, sizeof tv32); CP(tv32, tv, tv_sec); CP(tv32, tv, tv_usec); } else #endif error = sooptcopyin(sopt, &tv, sizeof tv, sizeof tv); if (error) goto bad; /* assert(hz > 0); */ if (tv.tv_sec < 0 || tv.tv_sec > INT_MAX / hz || tv.tv_usec < 0 || tv.tv_usec >= 1000000) { error = EDOM; goto bad; } /* assert(tick > 0); */ /* assert(ULONG_MAX - INT_MAX >= 1000000); */ val = (u_long)(tv.tv_sec * hz) + tv.tv_usec / tick; if (val > INT_MAX) { error = EDOM; goto bad; } if (val == 0 && tv.tv_usec != 0) val = 1; switch (sopt->sopt_name) { case SO_SNDTIMEO: so->so_snd.sb_timeo = val; break; case SO_RCVTIMEO: so->so_rcv.sb_timeo = val; break; } break; case SO_LABEL: #ifdef MAC error = sooptcopyin(sopt, &extmac, sizeof extmac, sizeof extmac); if (error) goto bad; error = mac_setsockopt_label(sopt->sopt_td->td_ucred, so, &extmac); #else error = EOPNOTSUPP; #endif break; default: error = ENOPROTOOPT; break; } if (error == 0 && so->so_proto != NULL && so->so_proto->pr_ctloutput != NULL) { (void) ((*so->so_proto->pr_ctloutput) (so, sopt)); } } bad: return (error); } /* Helper routine for getsockopt */ int sooptcopyout(struct sockopt *sopt, const void *buf, size_t len) { int error; size_t valsize; error = 0; /* * Documented get behavior is that we always return a value, * possibly truncated to fit in the user's buffer. * Traditional behavior is that we always tell the user * precisely how much we copied, rather than something useful * like the total amount we had available for her. * Note that this interface is not idempotent; the entire answer must * generated ahead of time. */ valsize = min(len, sopt->sopt_valsize); sopt->sopt_valsize = valsize; if (sopt->sopt_val != NULL) { if (sopt->sopt_td != NULL) error = copyout(buf, sopt->sopt_val, valsize); else bcopy(buf, sopt->sopt_val, valsize); } return (error); } int sogetopt(so, sopt) struct socket *so; struct sockopt *sopt; { int error, optval; struct linger l; struct timeval tv; #ifdef MAC struct mac extmac; #endif error = 0; if (sopt->sopt_level != SOL_SOCKET) { if (so->so_proto && so->so_proto->pr_ctloutput) { return ((*so->so_proto->pr_ctloutput) (so, sopt)); } else return (ENOPROTOOPT); } else { switch (sopt->sopt_name) { #ifdef INET case SO_ACCEPTFILTER: error = do_getopt_accept_filter(so, sopt); break; #endif case SO_LINGER: SOCK_LOCK(so); l.l_onoff = so->so_options & SO_LINGER; l.l_linger = so->so_linger; SOCK_UNLOCK(so); error = sooptcopyout(sopt, &l, sizeof l); break; case SO_USELOOPBACK: case SO_DONTROUTE: case SO_DEBUG: case SO_KEEPALIVE: case SO_REUSEADDR: case SO_REUSEPORT: case SO_BROADCAST: case SO_OOBINLINE: case SO_ACCEPTCONN: case SO_TIMESTAMP: case SO_BINTIME: case SO_NOSIGPIPE: optval = so->so_options & sopt->sopt_name; integer: error = sooptcopyout(sopt, &optval, sizeof optval); break; case SO_TYPE: optval = so->so_type; goto integer; case SO_ERROR: optval = so->so_error; so->so_error = 0; goto integer; case SO_SNDBUF: optval = so->so_snd.sb_hiwat; goto integer; case SO_RCVBUF: optval = so->so_rcv.sb_hiwat; goto integer; case SO_SNDLOWAT: optval = so->so_snd.sb_lowat; goto integer; case SO_RCVLOWAT: optval = so->so_rcv.sb_lowat; goto integer; case SO_SNDTIMEO: case SO_RCVTIMEO: optval = (sopt->sopt_name == SO_SNDTIMEO ? so->so_snd.sb_timeo : so->so_rcv.sb_timeo); tv.tv_sec = optval / hz; tv.tv_usec = (optval % hz) * tick; #ifdef COMPAT_IA32 if (curthread->td_proc->p_sysent == &ia32_freebsd_sysvec) { struct timeval32 tv32; CP(tv, tv32, tv_sec); CP(tv, tv32, tv_usec); error = sooptcopyout(sopt, &tv32, sizeof tv32); } else #endif error = sooptcopyout(sopt, &tv, sizeof tv); break; case SO_LABEL: #ifdef MAC error = sooptcopyin(sopt, &extmac, sizeof(extmac), sizeof(extmac)); if (error) return (error); error = mac_getsockopt_label(sopt->sopt_td->td_ucred, so, &extmac); if (error) return (error); error = sooptcopyout(sopt, &extmac, sizeof extmac); #else error = EOPNOTSUPP; #endif break; case SO_PEERLABEL: #ifdef MAC error = sooptcopyin(sopt, &extmac, sizeof(extmac), sizeof(extmac)); if (error) return (error); error = mac_getsockopt_peerlabel( sopt->sopt_td->td_ucred, so, &extmac); if (error) return (error); error = sooptcopyout(sopt, &extmac, sizeof extmac); #else error = EOPNOTSUPP; #endif break; case SO_LISTENQLIMIT: optval = so->so_qlimit; goto integer; case SO_LISTENQLEN: optval = so->so_qlen; goto integer; case SO_LISTENINCQLEN: optval = so->so_incqlen; goto integer; default: error = ENOPROTOOPT; break; } return (error); } } /* XXX; prepare mbuf for (__FreeBSD__ < 3) routines. */ int soopt_getm(struct sockopt *sopt, struct mbuf **mp) { struct mbuf *m, *m_prev; int sopt_size = sopt->sopt_valsize; MGET(m, sopt->sopt_td ? M_TRYWAIT : M_DONTWAIT, MT_DATA); if (m == NULL) return ENOBUFS; if (sopt_size > MLEN) { MCLGET(m, sopt->sopt_td ? M_TRYWAIT : M_DONTWAIT); if ((m->m_flags & M_EXT) == 0) { m_free(m); return ENOBUFS; } m->m_len = min(MCLBYTES, sopt_size); } else { m->m_len = min(MLEN, sopt_size); } sopt_size -= m->m_len; *mp = m; m_prev = m; while (sopt_size) { MGET(m, sopt->sopt_td ? M_TRYWAIT : M_DONTWAIT, MT_DATA); if (m == NULL) { m_freem(*mp); return ENOBUFS; } if (sopt_size > MLEN) { MCLGET(m, sopt->sopt_td != NULL ? M_TRYWAIT : M_DONTWAIT); if ((m->m_flags & M_EXT) == 0) { m_freem(m); m_freem(*mp); return ENOBUFS; } m->m_len = min(MCLBYTES, sopt_size); } else { m->m_len = min(MLEN, sopt_size); } sopt_size -= m->m_len; m_prev->m_next = m; m_prev = m; } return (0); } /* XXX; copyin sopt data into mbuf chain for (__FreeBSD__ < 3) routines. */ int soopt_mcopyin(struct sockopt *sopt, struct mbuf *m) { struct mbuf *m0 = m; if (sopt->sopt_val == NULL) return (0); while (m != NULL && sopt->sopt_valsize >= m->m_len) { if (sopt->sopt_td != NULL) { int error; error = copyin(sopt->sopt_val, mtod(m, char *), m->m_len); if (error != 0) { m_freem(m0); return(error); } } else bcopy(sopt->sopt_val, mtod(m, char *), m->m_len); sopt->sopt_valsize -= m->m_len; sopt->sopt_val = (char *)sopt->sopt_val + m->m_len; m = m->m_next; } if (m != NULL) /* should be allocated enoughly at ip6_sooptmcopyin() */ panic("ip6_sooptmcopyin"); return (0); } /* XXX; copyout mbuf chain data into soopt for (__FreeBSD__ < 3) routines. */ int soopt_mcopyout(struct sockopt *sopt, struct mbuf *m) { struct mbuf *m0 = m; size_t valsize = 0; if (sopt->sopt_val == NULL) return (0); while (m != NULL && sopt->sopt_valsize >= m->m_len) { if (sopt->sopt_td != NULL) { int error; error = copyout(mtod(m, char *), sopt->sopt_val, m->m_len); if (error != 0) { m_freem(m0); return(error); } } else bcopy(mtod(m, char *), sopt->sopt_val, m->m_len); sopt->sopt_valsize -= m->m_len; sopt->sopt_val = (char *)sopt->sopt_val + m->m_len; valsize += m->m_len; m = m->m_next; } if (m != NULL) { /* enough soopt buffer should be given from user-land */ m_freem(m0); return(EINVAL); } sopt->sopt_valsize = valsize; return (0); } void sohasoutofband(so) struct socket *so; { if (so->so_sigio != NULL) pgsigio(&so->so_sigio, SIGURG, 0); selwakeuppri(&so->so_rcv.sb_sel, PSOCK); } int sopoll(struct socket *so, int events, struct ucred *active_cred, struct thread *td) { int revents = 0; SOCKBUF_LOCK(&so->so_snd); SOCKBUF_LOCK(&so->so_rcv); if (events & (POLLIN | POLLRDNORM)) if (soreadable(so)) revents |= events & (POLLIN | POLLRDNORM); if (events & POLLINIGNEOF) if (so->so_rcv.sb_cc >= so->so_rcv.sb_lowat || !TAILQ_EMPTY(&so->so_comp) || so->so_error) revents |= POLLINIGNEOF; if (events & (POLLOUT | POLLWRNORM)) if (sowriteable(so)) revents |= events & (POLLOUT | POLLWRNORM); if (events & (POLLPRI | POLLRDBAND)) if (so->so_oobmark || (so->so_rcv.sb_state & SBS_RCVATMARK)) revents |= events & (POLLPRI | POLLRDBAND); if (revents == 0) { if (events & (POLLIN | POLLINIGNEOF | POLLPRI | POLLRDNORM | POLLRDBAND)) { selrecord(td, &so->so_rcv.sb_sel); so->so_rcv.sb_flags |= SB_SEL; } if (events & (POLLOUT | POLLWRNORM)) { selrecord(td, &so->so_snd.sb_sel); so->so_snd.sb_flags |= SB_SEL; } } SOCKBUF_UNLOCK(&so->so_rcv); SOCKBUF_UNLOCK(&so->so_snd); return (revents); } int soo_kqfilter(struct file *fp, struct knote *kn) { struct socket *so = kn->kn_fp->f_data; struct sockbuf *sb; switch (kn->kn_filter) { case EVFILT_READ: if (so->so_options & SO_ACCEPTCONN) kn->kn_fop = &solisten_filtops; else kn->kn_fop = &soread_filtops; sb = &so->so_rcv; break; case EVFILT_WRITE: kn->kn_fop = &sowrite_filtops; sb = &so->so_snd; break; default: return (EINVAL); } SOCKBUF_LOCK(sb); knlist_add(&sb->sb_sel.si_note, kn, 1); sb->sb_flags |= SB_KNOTE; SOCKBUF_UNLOCK(sb); return (0); } static void filt_sordetach(struct knote *kn) { struct socket *so = kn->kn_fp->f_data; SOCKBUF_LOCK(&so->so_rcv); knlist_remove(&so->so_rcv.sb_sel.si_note, kn, 1); if (knlist_empty(&so->so_rcv.sb_sel.si_note)) so->so_rcv.sb_flags &= ~SB_KNOTE; SOCKBUF_UNLOCK(&so->so_rcv); } /*ARGSUSED*/ static int filt_soread(struct knote *kn, long hint) { struct socket *so; so = kn->kn_fp->f_data; SOCKBUF_LOCK_ASSERT(&so->so_rcv); kn->kn_data = so->so_rcv.sb_cc - so->so_rcv.sb_ctl; if (so->so_rcv.sb_state & SBS_CANTRCVMORE) { kn->kn_flags |= EV_EOF; kn->kn_fflags = so->so_error; return (1); } else if (so->so_error) /* temporary udp error */ return (1); else if (kn->kn_sfflags & NOTE_LOWAT) return (kn->kn_data >= kn->kn_sdata); else return (so->so_rcv.sb_cc >= so->so_rcv.sb_lowat); } static void filt_sowdetach(struct knote *kn) { struct socket *so = kn->kn_fp->f_data; SOCKBUF_LOCK(&so->so_snd); knlist_remove(&so->so_snd.sb_sel.si_note, kn, 1); if (knlist_empty(&so->so_snd.sb_sel.si_note)) so->so_snd.sb_flags &= ~SB_KNOTE; SOCKBUF_UNLOCK(&so->so_snd); } /*ARGSUSED*/ static int filt_sowrite(struct knote *kn, long hint) { struct socket *so; so = kn->kn_fp->f_data; SOCKBUF_LOCK_ASSERT(&so->so_snd); kn->kn_data = sbspace(&so->so_snd); if (so->so_snd.sb_state & SBS_CANTSENDMORE) { kn->kn_flags |= EV_EOF; kn->kn_fflags = so->so_error; return (1); } else if (so->so_error) /* temporary udp error */ return (1); else if (((so->so_state & SS_ISCONNECTED) == 0) && (so->so_proto->pr_flags & PR_CONNREQUIRED)) return (0); else if (kn->kn_sfflags & NOTE_LOWAT) return (kn->kn_data >= kn->kn_sdata); else return (kn->kn_data >= so->so_snd.sb_lowat); } /*ARGSUSED*/ static int filt_solisten(struct knote *kn, long hint) { struct socket *so = kn->kn_fp->f_data; kn->kn_data = so->so_qlen; return (! TAILQ_EMPTY(&so->so_comp)); } int socheckuid(struct socket *so, uid_t uid) { if (so == NULL) return (EPERM); if (so->so_cred->cr_uid != uid) return (EPERM); return (0); } static int somaxconn_sysctl(SYSCTL_HANDLER_ARGS) { int error; int val; val = somaxconn; error = sysctl_handle_int(oidp, &val, sizeof(int), req); if (error || !req->newptr ) return (error); if (val < 1 || val > USHRT_MAX) return (EINVAL); somaxconn = val; return (0); } diff --git a/sys/kern/uipc_socket2.c b/sys/kern/uipc_socket2.c index be9084bb6fc0..4afe8c570fae 100644 --- a/sys/kern/uipc_socket2.c +++ b/sys/kern/uipc_socket2.c @@ -1,1524 +1,1524 @@ /*- * Copyright (c) 1982, 1986, 1988, 1990, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)uipc_socket2.c 8.1 (Berkeley) 6/10/93 */ #include __FBSDID("$FreeBSD$"); #include "opt_mac.h" #include "opt_param.h" #include #include /* for aio_swake proto */ #include #include #include /* for maxfiles */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int maxsockets; void (*aio_swake)(struct socket *, struct sockbuf *); /* * Primitive routines for operating on sockets and socket buffers */ u_long sb_max = SB_MAX; static u_long sb_max_adj = SB_MAX * MCLBYTES / (MSIZE + MCLBYTES); /* adjusted sb_max */ static u_long sb_efficiency = 8; /* parameter for sbreserve() */ #ifdef REGRESSION static int regression_sonewconn_earlytest = 1; SYSCTL_INT(_regression, OID_AUTO, sonewconn_earlytest, CTLFLAG_RW, ®ression_sonewconn_earlytest, 0, "Perform early sonewconn limit test"); #endif /* * Procedures to manipulate state flags of socket * and do appropriate wakeups. Normal sequence from the * active (originating) side is that soisconnecting() is * called during processing of connect() call, * resulting in an eventual call to soisconnected() if/when the * connection is established. When the connection is torn down * soisdisconnecting() is called during processing of disconnect() call, * and soisdisconnected() is called when the connection to the peer * is totally severed. The semantics of these routines are such that * connectionless protocols can call soisconnected() and soisdisconnected() * only, bypassing the in-progress calls when setting up a ``connection'' * takes no time. * * From the passive side, a socket is created with * two queues of sockets: so_incomp for connections in progress * and so_comp for connections already made and awaiting user acceptance. * As a protocol is preparing incoming connections, it creates a socket * structure queued on so_incomp by calling sonewconn(). When the connection * is established, soisconnected() is called, and transfers the * socket structure to so_comp, making it available to accept(). * * If a socket is closed with sockets on either * so_incomp or so_comp, these sockets are dropped. * * If higher level protocols are implemented in * the kernel, the wakeups done here will sometimes * cause software-interrupt process scheduling. */ void soisconnecting(so) register struct socket *so; { SOCK_LOCK(so); so->so_state &= ~(SS_ISCONNECTED|SS_ISDISCONNECTING); so->so_state |= SS_ISCONNECTING; SOCK_UNLOCK(so); } void soisconnected(so) struct socket *so; { struct socket *head; ACCEPT_LOCK(); SOCK_LOCK(so); so->so_state &= ~(SS_ISCONNECTING|SS_ISDISCONNECTING|SS_ISCONFIRMING); so->so_state |= SS_ISCONNECTED; head = so->so_head; if (head != NULL && (so->so_qstate & SQ_INCOMP)) { if ((so->so_options & SO_ACCEPTFILTER) == 0) { SOCK_UNLOCK(so); TAILQ_REMOVE(&head->so_incomp, so, so_list); head->so_incqlen--; so->so_qstate &= ~SQ_INCOMP; TAILQ_INSERT_TAIL(&head->so_comp, so, so_list); head->so_qlen++; so->so_qstate |= SQ_COMP; ACCEPT_UNLOCK(); sorwakeup(head); wakeup_one(&head->so_timeo); } else { ACCEPT_UNLOCK(); so->so_upcall = head->so_accf->so_accept_filter->accf_callback; so->so_upcallarg = head->so_accf->so_accept_filter_arg; so->so_rcv.sb_flags |= SB_UPCALL; so->so_options &= ~SO_ACCEPTFILTER; SOCK_UNLOCK(so); so->so_upcall(so, so->so_upcallarg, M_DONTWAIT); } return; } SOCK_UNLOCK(so); ACCEPT_UNLOCK(); wakeup(&so->so_timeo); sorwakeup(so); sowwakeup(so); } void soisdisconnecting(so) register struct socket *so; { /* * XXXRW: This code assumes that SOCK_LOCK(so) and * SOCKBUF_LOCK(&so->so_rcv) are the same. */ SOCKBUF_LOCK(&so->so_rcv); so->so_state &= ~SS_ISCONNECTING; so->so_state |= SS_ISDISCONNECTING; so->so_rcv.sb_state |= SBS_CANTRCVMORE; sorwakeup_locked(so); SOCKBUF_LOCK(&so->so_snd); so->so_snd.sb_state |= SBS_CANTSENDMORE; sowwakeup_locked(so); wakeup(&so->so_timeo); } void soisdisconnected(so) register struct socket *so; { /* * XXXRW: This code assumes that SOCK_LOCK(so) and * SOCKBUF_LOCK(&so->so_rcv) are the same. */ SOCKBUF_LOCK(&so->so_rcv); so->so_state &= ~(SS_ISCONNECTING|SS_ISCONNECTED|SS_ISDISCONNECTING); so->so_state |= SS_ISDISCONNECTED; so->so_rcv.sb_state |= SBS_CANTRCVMORE; sorwakeup_locked(so); SOCKBUF_LOCK(&so->so_snd); so->so_snd.sb_state |= SBS_CANTSENDMORE; sbdrop_locked(&so->so_snd, so->so_snd.sb_cc); sowwakeup_locked(so); wakeup(&so->so_timeo); } /* * When an attempt at a new connection is noted on a socket * which accepts connections, sonewconn is called. If the * connection is possible (subject to space constraints, etc.) * then we allocate a new structure, propoerly linked into the * data structure of the original socket, and return this. * Connstatus may be 0, or SO_ISCONFIRMING, or SO_ISCONNECTED. * * note: the ref count on the socket is 0 on return */ struct socket * sonewconn(head, connstatus) register struct socket *head; int connstatus; { register struct socket *so; int over; ACCEPT_LOCK(); over = (head->so_qlen > 3 * head->so_qlimit / 2); ACCEPT_UNLOCK(); #ifdef REGRESSION if (regression_sonewconn_earlytest && over) #else if (over) #endif return (NULL); so = soalloc(M_NOWAIT); if (so == NULL) return (NULL); if ((head->so_options & SO_ACCEPTFILTER) != 0) connstatus = 0; so->so_head = head; so->so_type = head->so_type; so->so_options = head->so_options &~ SO_ACCEPTCONN; so->so_linger = head->so_linger; so->so_state = head->so_state | SS_NOFDREF; so->so_proto = head->so_proto; so->so_timeo = head->so_timeo; so->so_cred = crhold(head->so_cred); #ifdef MAC SOCK_LOCK(head); mac_create_socket_from_socket(head, so); SOCK_UNLOCK(head); #endif knlist_init(&so->so_rcv.sb_sel.si_note, SOCKBUF_MTX(&so->so_rcv), NULL, NULL, NULL); knlist_init(&so->so_snd.sb_sel.si_note, SOCKBUF_MTX(&so->so_snd), NULL, NULL, NULL); if (soreserve(so, head->so_snd.sb_hiwat, head->so_rcv.sb_hiwat) || (*so->so_proto->pr_usrreqs->pru_attach)(so, 0, NULL)) { sodealloc(so); return (NULL); } so->so_state |= connstatus; ACCEPT_LOCK(); if (connstatus) { TAILQ_INSERT_TAIL(&head->so_comp, so, so_list); so->so_qstate |= SQ_COMP; head->so_qlen++; } else { /* * Keep removing sockets from the head until there's room for * us to insert on the tail. In pre-locking revisions, this * was a simple if(), but as we could be racing with other * threads and soabort() requires dropping locks, we must * loop waiting for the condition to be true. */ while (head->so_incqlen > head->so_qlimit) { struct socket *sp; sp = TAILQ_FIRST(&head->so_incomp); TAILQ_REMOVE(&head->so_incomp, sp, so_list); head->so_incqlen--; sp->so_qstate &= ~SQ_INCOMP; sp->so_head = NULL; ACCEPT_UNLOCK(); soabort(sp); ACCEPT_LOCK(); } TAILQ_INSERT_TAIL(&head->so_incomp, so, so_list); so->so_qstate |= SQ_INCOMP; head->so_incqlen++; } ACCEPT_UNLOCK(); if (connstatus) { sorwakeup(head); wakeup_one(&head->so_timeo); } return (so); } /* * Socantsendmore indicates that no more data will be sent on the * socket; it would normally be applied to a socket when the user * informs the system that no more data is to be sent, by the protocol * code (in case PRU_SHUTDOWN). Socantrcvmore indicates that no more data * will be received, and will normally be applied to the socket by a * protocol when it detects that the peer will send no more data. * Data queued for reading in the socket may yet be read. */ void socantsendmore_locked(so) struct socket *so; { SOCKBUF_LOCK_ASSERT(&so->so_snd); so->so_snd.sb_state |= SBS_CANTSENDMORE; sowwakeup_locked(so); mtx_assert(SOCKBUF_MTX(&so->so_snd), MA_NOTOWNED); } void socantsendmore(so) struct socket *so; { SOCKBUF_LOCK(&so->so_snd); socantsendmore_locked(so); mtx_assert(SOCKBUF_MTX(&so->so_snd), MA_NOTOWNED); } void socantrcvmore_locked(so) struct socket *so; { SOCKBUF_LOCK_ASSERT(&so->so_rcv); so->so_rcv.sb_state |= SBS_CANTRCVMORE; sorwakeup_locked(so); mtx_assert(SOCKBUF_MTX(&so->so_rcv), MA_NOTOWNED); } void socantrcvmore(so) struct socket *so; { SOCKBUF_LOCK(&so->so_rcv); socantrcvmore_locked(so); mtx_assert(SOCKBUF_MTX(&so->so_rcv), MA_NOTOWNED); } /* * Wait for data to arrive at/drain from a socket buffer. */ int sbwait(sb) struct sockbuf *sb; { SOCKBUF_LOCK_ASSERT(sb); sb->sb_flags |= SB_WAIT; return (msleep(&sb->sb_cc, &sb->sb_mtx, (sb->sb_flags & SB_NOINTR) ? PSOCK : PSOCK | PCATCH, "sbwait", sb->sb_timeo)); } /* * Lock a sockbuf already known to be locked; * return any error returned from sleep (EINTR). */ int sb_lock(sb) register struct sockbuf *sb; { int error; SOCKBUF_LOCK_ASSERT(sb); while (sb->sb_flags & SB_LOCK) { sb->sb_flags |= SB_WANT; error = msleep(&sb->sb_flags, &sb->sb_mtx, (sb->sb_flags & SB_NOINTR) ? PSOCK : PSOCK|PCATCH, "sblock", 0); if (error) return (error); } sb->sb_flags |= SB_LOCK; return (0); } /* * Wakeup processes waiting on a socket buffer. Do asynchronous * notification via SIGIO if the socket has the SS_ASYNC flag set. * * Called with the socket buffer lock held; will release the lock by the end * of the function. This allows the caller to acquire the socket buffer lock * while testing for the need for various sorts of wakeup and hold it through * to the point where it's no longer required. We currently hold the lock * through calls out to other subsystems (with the exception of kqueue), and * then release it to avoid lock order issues. It's not clear that's * correct. */ void sowakeup(so, sb) register struct socket *so; register struct sockbuf *sb; { SOCKBUF_LOCK_ASSERT(sb); selwakeuppri(&sb->sb_sel, PSOCK); sb->sb_flags &= ~SB_SEL; if (sb->sb_flags & SB_WAIT) { sb->sb_flags &= ~SB_WAIT; wakeup(&sb->sb_cc); } KNOTE_LOCKED(&sb->sb_sel.si_note, 0); SOCKBUF_UNLOCK(sb); if ((so->so_state & SS_ASYNC) && so->so_sigio != NULL) pgsigio(&so->so_sigio, SIGIO, 0); if (sb->sb_flags & SB_UPCALL) (*so->so_upcall)(so, so->so_upcallarg, M_DONTWAIT); if (sb->sb_flags & SB_AIO) aio_swake(so, sb); mtx_assert(SOCKBUF_MTX(sb), MA_NOTOWNED); } /* * Socket buffer (struct sockbuf) utility routines. * * Each socket contains two socket buffers: one for sending data and * one for receiving data. Each buffer contains a queue of mbufs, * information about the number of mbufs and amount of data in the * queue, and other fields allowing select() statements and notification * on data availability to be implemented. * * Data stored in a socket buffer is maintained as a list of records. * Each record is a list of mbufs chained together with the m_next * field. Records are chained together with the m_nextpkt field. The upper * level routine soreceive() expects the following conventions to be * observed when placing information in the receive buffer: * * 1. If the protocol requires each message be preceded by the sender's * name, then a record containing that name must be present before * any associated data (mbuf's must be of type MT_SONAME). * 2. If the protocol supports the exchange of ``access rights'' (really * just additional data associated with the message), and there are * ``rights'' to be received, then a record containing this data * should be present (mbuf's must be of type MT_RIGHTS). * 3. If a name or rights record exists, then it must be followed by * a data record, perhaps of zero length. * * Before using a new socket structure it is first necessary to reserve * buffer space to the socket, by calling sbreserve(). This should commit * some of the available buffer space in the system buffer pool for the * socket (currently, it does nothing but enforce limits). The space * should be released by calling sbrelease() when the socket is destroyed. */ int soreserve(so, sndcc, rcvcc) register struct socket *so; u_long sndcc, rcvcc; { struct thread *td = curthread; SOCKBUF_LOCK(&so->so_snd); SOCKBUF_LOCK(&so->so_rcv); if (sbreserve_locked(&so->so_snd, sndcc, so, td) == 0) goto bad; if (sbreserve_locked(&so->so_rcv, rcvcc, so, td) == 0) goto bad2; if (so->so_rcv.sb_lowat == 0) so->so_rcv.sb_lowat = 1; if (so->so_snd.sb_lowat == 0) so->so_snd.sb_lowat = MCLBYTES; if (so->so_snd.sb_lowat > so->so_snd.sb_hiwat) so->so_snd.sb_lowat = so->so_snd.sb_hiwat; SOCKBUF_UNLOCK(&so->so_rcv); SOCKBUF_UNLOCK(&so->so_snd); return (0); bad2: sbrelease_locked(&so->so_snd, so); bad: SOCKBUF_UNLOCK(&so->so_rcv); SOCKBUF_UNLOCK(&so->so_snd); return (ENOBUFS); } static int sysctl_handle_sb_max(SYSCTL_HANDLER_ARGS) { int error = 0; u_long old_sb_max = sb_max; error = SYSCTL_OUT(req, arg1, sizeof(u_long)); if (error || !req->newptr) return (error); error = SYSCTL_IN(req, arg1, sizeof(u_long)); if (error) return (error); if (sb_max < MSIZE + MCLBYTES) { sb_max = old_sb_max; return (EINVAL); } sb_max_adj = (u_quad_t)sb_max * MCLBYTES / (MSIZE + MCLBYTES); return (0); } /* * Allot mbufs to a sockbuf. * Attempt to scale mbmax so that mbcnt doesn't become limiting * if buffering efficiency is near the normal case. */ int sbreserve_locked(sb, cc, so, td) struct sockbuf *sb; u_long cc; struct socket *so; struct thread *td; { rlim_t sbsize_limit; SOCKBUF_LOCK_ASSERT(sb); /* * td will only be NULL when we're in an interrupt * (e.g. in tcp_input()) */ if (cc > sb_max_adj) return (0); if (td != NULL) { PROC_LOCK(td->td_proc); sbsize_limit = lim_cur(td->td_proc, RLIMIT_SBSIZE); PROC_UNLOCK(td->td_proc); } else sbsize_limit = RLIM_INFINITY; if (!chgsbsize(so->so_cred->cr_uidinfo, &sb->sb_hiwat, cc, sbsize_limit)) return (0); sb->sb_mbmax = min(cc * sb_efficiency, sb_max); if (sb->sb_lowat > sb->sb_hiwat) sb->sb_lowat = sb->sb_hiwat; return (1); } int sbreserve(sb, cc, so, td) struct sockbuf *sb; u_long cc; struct socket *so; struct thread *td; { int error; SOCKBUF_LOCK(sb); error = sbreserve_locked(sb, cc, so, td); SOCKBUF_UNLOCK(sb); return (error); } /* * Free mbufs held by a socket, and reserved mbuf space. */ void sbrelease_locked(sb, so) struct sockbuf *sb; struct socket *so; { SOCKBUF_LOCK_ASSERT(sb); sbflush_locked(sb); (void)chgsbsize(so->so_cred->cr_uidinfo, &sb->sb_hiwat, 0, RLIM_INFINITY); sb->sb_mbmax = 0; } void sbrelease(sb, so) struct sockbuf *sb; struct socket *so; { SOCKBUF_LOCK(sb); sbrelease_locked(sb, so); SOCKBUF_UNLOCK(sb); } /* * Routines to add and remove * data from an mbuf queue. * * The routines sbappend() or sbappendrecord() are normally called to * append new mbufs to a socket buffer, after checking that adequate * space is available, comparing the function sbspace() with the amount * of data to be added. sbappendrecord() differs from sbappend() in * that data supplied is treated as the beginning of a new record. * To place a sender's address, optional access rights, and data in a * socket receive buffer, sbappendaddr() should be used. To place * access rights and data in a socket receive buffer, sbappendrights() * should be used. In either case, the new data begins a new record. * Note that unlike sbappend() and sbappendrecord(), these routines check * for the caller that there will be enough space to store the data. * Each fails if there is not enough space, or if it cannot find mbufs * to store additional information in. * * Reliable protocols may use the socket send buffer to hold data * awaiting acknowledgement. Data is normally copied from a socket * send buffer in a protocol with m_copy for output to a peer, * and then removing the data from the socket buffer with sbdrop() * or sbdroprecord() when the data is acknowledged by the peer. */ #ifdef SOCKBUF_DEBUG void sblastrecordchk(struct sockbuf *sb, const char *file, int line) { struct mbuf *m = sb->sb_mb; SOCKBUF_LOCK_ASSERT(sb); while (m && m->m_nextpkt) m = m->m_nextpkt; if (m != sb->sb_lastrecord) { printf("%s: sb_mb %p sb_lastrecord %p last %p\n", __func__, sb->sb_mb, sb->sb_lastrecord, m); printf("packet chain:\n"); for (m = sb->sb_mb; m != NULL; m = m->m_nextpkt) printf("\t%p\n", m); panic("%s from %s:%u", __func__, file, line); } } void sblastmbufchk(struct sockbuf *sb, const char *file, int line) { struct mbuf *m = sb->sb_mb; struct mbuf *n; SOCKBUF_LOCK_ASSERT(sb); while (m && m->m_nextpkt) m = m->m_nextpkt; while (m && m->m_next) m = m->m_next; if (m != sb->sb_mbtail) { printf("%s: sb_mb %p sb_mbtail %p last %p\n", __func__, sb->sb_mb, sb->sb_mbtail, m); printf("packet tree:\n"); for (m = sb->sb_mb; m != NULL; m = m->m_nextpkt) { printf("\t"); for (n = m; n != NULL; n = n->m_next) printf("%p ", n); printf("\n"); } panic("%s from %s:%u", __func__, file, line); } } #endif /* SOCKBUF_DEBUG */ #define SBLINKRECORD(sb, m0) do { \ SOCKBUF_LOCK_ASSERT(sb); \ if ((sb)->sb_lastrecord != NULL) \ (sb)->sb_lastrecord->m_nextpkt = (m0); \ else \ (sb)->sb_mb = (m0); \ (sb)->sb_lastrecord = (m0); \ } while (/*CONSTCOND*/0) /* * Append mbuf chain m to the last record in the * socket buffer sb. The additional space associated * the mbuf chain is recorded in sb. Empty mbufs are * discarded and mbufs are compacted where possible. */ void sbappend_locked(sb, m) struct sockbuf *sb; struct mbuf *m; { register struct mbuf *n; SOCKBUF_LOCK_ASSERT(sb); if (m == 0) return; SBLASTRECORDCHK(sb); n = sb->sb_mb; if (n) { while (n->m_nextpkt) n = n->m_nextpkt; do { if (n->m_flags & M_EOR) { sbappendrecord_locked(sb, m); /* XXXXXX!!!! */ return; } } while (n->m_next && (n = n->m_next)); } else { /* * XXX Would like to simply use sb_mbtail here, but * XXX I need to verify that I won't miss an EOR that * XXX way. */ if ((n = sb->sb_lastrecord) != NULL) { do { if (n->m_flags & M_EOR) { sbappendrecord_locked(sb, m); /* XXXXXX!!!! */ return; } } while (n->m_next && (n = n->m_next)); } else { /* * If this is the first record in the socket buffer, * it's also the last record. */ sb->sb_lastrecord = m; } } sbcompress(sb, m, n); SBLASTRECORDCHK(sb); } /* * Append mbuf chain m to the last record in the * socket buffer sb. The additional space associated * the mbuf chain is recorded in sb. Empty mbufs are * discarded and mbufs are compacted where possible. */ void sbappend(sb, m) struct sockbuf *sb; struct mbuf *m; { SOCKBUF_LOCK(sb); sbappend_locked(sb, m); SOCKBUF_UNLOCK(sb); } /* * This version of sbappend() should only be used when the caller * absolutely knows that there will never be more than one record * in the socket buffer, that is, a stream protocol (such as TCP). */ void sbappendstream_locked(struct sockbuf *sb, struct mbuf *m) { SOCKBUF_LOCK_ASSERT(sb); KASSERT(m->m_nextpkt == NULL,("sbappendstream 0")); KASSERT(sb->sb_mb == sb->sb_lastrecord,("sbappendstream 1")); SBLASTMBUFCHK(sb); sbcompress(sb, m, sb->sb_mbtail); sb->sb_lastrecord = sb->sb_mb; SBLASTRECORDCHK(sb); } /* * This version of sbappend() should only be used when the caller * absolutely knows that there will never be more than one record * in the socket buffer, that is, a stream protocol (such as TCP). */ void sbappendstream(struct sockbuf *sb, struct mbuf *m) { SOCKBUF_LOCK(sb); sbappendstream_locked(sb, m); SOCKBUF_UNLOCK(sb); } #ifdef SOCKBUF_DEBUG void sbcheck(sb) struct sockbuf *sb; { struct mbuf *m; struct mbuf *n = 0; u_long len = 0, mbcnt = 0; SOCKBUF_LOCK_ASSERT(sb); for (m = sb->sb_mb; m; m = n) { n = m->m_nextpkt; for (; m; m = m->m_next) { len += m->m_len; mbcnt += MSIZE; if (m->m_flags & M_EXT) /*XXX*/ /* pretty sure this is bogus */ mbcnt += m->m_ext.ext_size; } } if (len != sb->sb_cc || mbcnt != sb->sb_mbcnt) { printf("cc %ld != %u || mbcnt %ld != %u\n", len, sb->sb_cc, mbcnt, sb->sb_mbcnt); panic("sbcheck"); } } #endif /* * As above, except the mbuf chain * begins a new record. */ void sbappendrecord_locked(sb, m0) register struct sockbuf *sb; register struct mbuf *m0; { register struct mbuf *m; SOCKBUF_LOCK_ASSERT(sb); if (m0 == 0) return; m = sb->sb_mb; if (m) while (m->m_nextpkt) m = m->m_nextpkt; /* * Put the first mbuf on the queue. * Note this permits zero length records. */ sballoc(sb, m0); SBLASTRECORDCHK(sb); SBLINKRECORD(sb, m0); if (m) m->m_nextpkt = m0; else sb->sb_mb = m0; m = m0->m_next; m0->m_next = 0; if (m && (m0->m_flags & M_EOR)) { m0->m_flags &= ~M_EOR; m->m_flags |= M_EOR; } sbcompress(sb, m, m0); } /* * As above, except the mbuf chain * begins a new record. */ void sbappendrecord(sb, m0) register struct sockbuf *sb; register struct mbuf *m0; { SOCKBUF_LOCK(sb); sbappendrecord_locked(sb, m0); SOCKBUF_UNLOCK(sb); } /* * As above except that OOB data * is inserted at the beginning of the sockbuf, * but after any other OOB data. */ void sbinsertoob_locked(sb, m0) register struct sockbuf *sb; register struct mbuf *m0; { register struct mbuf *m; register struct mbuf **mp; SOCKBUF_LOCK_ASSERT(sb); if (m0 == 0) return; for (mp = &sb->sb_mb; *mp ; mp = &((*mp)->m_nextpkt)) { m = *mp; again: switch (m->m_type) { case MT_OOBDATA: continue; /* WANT next train */ case MT_CONTROL: m = m->m_next; if (m) goto again; /* inspect THIS train further */ } break; } /* * Put the first mbuf on the queue. * Note this permits zero length records. */ sballoc(sb, m0); m0->m_nextpkt = *mp; *mp = m0; m = m0->m_next; m0->m_next = 0; if (m && (m0->m_flags & M_EOR)) { m0->m_flags &= ~M_EOR; m->m_flags |= M_EOR; } sbcompress(sb, m, m0); } /* * As above except that OOB data * is inserted at the beginning of the sockbuf, * but after any other OOB data. */ void sbinsertoob(sb, m0) register struct sockbuf *sb; register struct mbuf *m0; { SOCKBUF_LOCK(sb); sbinsertoob_locked(sb, m0); SOCKBUF_UNLOCK(sb); } /* * Append address and data, and optionally, control (ancillary) data * to the receive queue of a socket. If present, * m0 must include a packet header with total length. * Returns 0 if no space in sockbuf or insufficient mbufs. */ int sbappendaddr_locked(sb, asa, m0, control) struct sockbuf *sb; const struct sockaddr *asa; struct mbuf *m0, *control; { struct mbuf *m, *n, *nlast; int space = asa->sa_len; SOCKBUF_LOCK_ASSERT(sb); if (m0 && (m0->m_flags & M_PKTHDR) == 0) panic("sbappendaddr_locked"); if (m0) space += m0->m_pkthdr.len; space += m_length(control, &n); if (space > sbspace(sb)) return (0); #if MSIZE <= 256 if (asa->sa_len > MLEN) return (0); #endif MGET(m, M_DONTWAIT, MT_SONAME); if (m == 0) return (0); m->m_len = asa->sa_len; bcopy(asa, mtod(m, caddr_t), asa->sa_len); if (n) n->m_next = m0; /* concatenate data to control */ else control = m0; m->m_next = control; for (n = m; n->m_next != NULL; n = n->m_next) sballoc(sb, n); sballoc(sb, n); nlast = n; SBLINKRECORD(sb, m); sb->sb_mbtail = nlast; SBLASTMBUFCHK(sb); SBLASTRECORDCHK(sb); return (1); } /* * Append address and data, and optionally, control (ancillary) data * to the receive queue of a socket. If present, * m0 must include a packet header with total length. * Returns 0 if no space in sockbuf or insufficient mbufs. */ int sbappendaddr(sb, asa, m0, control) struct sockbuf *sb; const struct sockaddr *asa; struct mbuf *m0, *control; { int retval; SOCKBUF_LOCK(sb); retval = sbappendaddr_locked(sb, asa, m0, control); SOCKBUF_UNLOCK(sb); return (retval); } int sbappendcontrol_locked(sb, m0, control) struct sockbuf *sb; struct mbuf *control, *m0; { struct mbuf *m, *n, *mlast; int space; SOCKBUF_LOCK_ASSERT(sb); if (control == 0) panic("sbappendcontrol_locked"); space = m_length(control, &n) + m_length(m0, NULL); if (space > sbspace(sb)) return (0); n->m_next = m0; /* concatenate data to control */ SBLASTRECORDCHK(sb); for (m = control; m->m_next; m = m->m_next) sballoc(sb, m); sballoc(sb, m); mlast = m; SBLINKRECORD(sb, control); sb->sb_mbtail = mlast; SBLASTMBUFCHK(sb); SBLASTRECORDCHK(sb); return (1); } int sbappendcontrol(sb, m0, control) struct sockbuf *sb; struct mbuf *control, *m0; { int retval; SOCKBUF_LOCK(sb); retval = sbappendcontrol_locked(sb, m0, control); SOCKBUF_UNLOCK(sb); return (retval); } /* * Append the data in mbuf chain (m) into the socket buffer sb following mbuf * (n). If (n) is NULL, the buffer is presumed empty. * * When the data is compressed, mbufs in the chain may be handled in one of * three ways: * * (1) The mbuf may simply be dropped, if it contributes nothing (no data, no * record boundary, and no change in data type). * * (2) The mbuf may be coalesced -- i.e., data in the mbuf may be copied into * an mbuf already in the socket buffer. This can occur if an * appropriate mbuf exists, there is room, and no merging of data types * will occur. * * (3) The mbuf may be appended to the end of the existing mbuf chain. * * If any of the new mbufs is marked as M_EOR, mark the last mbuf appended as * end-of-record. */ void sbcompress(sb, m, n) register struct sockbuf *sb; register struct mbuf *m, *n; { register int eor = 0; register struct mbuf *o; SOCKBUF_LOCK_ASSERT(sb); while (m) { eor |= m->m_flags & M_EOR; if (m->m_len == 0 && (eor == 0 || (((o = m->m_next) || (o = n)) && o->m_type == m->m_type))) { if (sb->sb_lastrecord == m) sb->sb_lastrecord = m->m_next; m = m_free(m); continue; } if (n && (n->m_flags & M_EOR) == 0 && M_WRITABLE(n) && m->m_len <= MCLBYTES / 4 && /* XXX: Don't copy too much */ m->m_len <= M_TRAILINGSPACE(n) && n->m_type == m->m_type) { bcopy(mtod(m, caddr_t), mtod(n, caddr_t) + n->m_len, (unsigned)m->m_len); n->m_len += m->m_len; sb->sb_cc += m->m_len; if (m->m_type != MT_DATA && m->m_type != MT_OOBDATA) /* XXX: Probably don't need.*/ sb->sb_ctl += m->m_len; m = m_free(m); continue; } if (n) n->m_next = m; else sb->sb_mb = m; sb->sb_mbtail = m; sballoc(sb, m); n = m; m->m_flags &= ~M_EOR; m = m->m_next; n->m_next = 0; } if (eor) { KASSERT(n != NULL, ("sbcompress: eor && n == NULL")); n->m_flags |= eor; } SBLASTMBUFCHK(sb); } /* * Free all mbufs in a sockbuf. * Check that all resources are reclaimed. */ void sbflush_locked(sb) register struct sockbuf *sb; { SOCKBUF_LOCK_ASSERT(sb); if (sb->sb_flags & SB_LOCK) panic("sbflush_locked: locked"); while (sb->sb_mbcnt) { /* * Don't call sbdrop(sb, 0) if the leading mbuf is non-empty: * we would loop forever. Panic instead. */ if (!sb->sb_cc && (sb->sb_mb == NULL || sb->sb_mb->m_len)) break; sbdrop_locked(sb, (int)sb->sb_cc); } if (sb->sb_cc || sb->sb_mb || sb->sb_mbcnt) panic("sbflush_locked: cc %u || mb %p || mbcnt %u", sb->sb_cc, (void *)sb->sb_mb, sb->sb_mbcnt); } void sbflush(sb) register struct sockbuf *sb; { SOCKBUF_LOCK(sb); sbflush_locked(sb); SOCKBUF_UNLOCK(sb); } /* * Drop data from (the front of) a sockbuf. */ void sbdrop_locked(sb, len) register struct sockbuf *sb; register int len; { register struct mbuf *m; struct mbuf *next; SOCKBUF_LOCK_ASSERT(sb); next = (m = sb->sb_mb) ? m->m_nextpkt : 0; while (len > 0) { if (m == 0) { if (next == 0) panic("sbdrop"); m = next; next = m->m_nextpkt; continue; } if (m->m_len > len) { m->m_len -= len; m->m_data += len; sb->sb_cc -= len; if (m->m_type != MT_DATA && m->m_type != MT_OOBDATA) sb->sb_ctl -= len; break; } len -= m->m_len; sbfree(sb, m); m = m_free(m); } while (m && m->m_len == 0) { sbfree(sb, m); m = m_free(m); } if (m) { sb->sb_mb = m; m->m_nextpkt = next; } else sb->sb_mb = next; /* * First part is an inline SB_EMPTY_FIXUP(). Second part * makes sure sb_lastrecord is up-to-date if we dropped * part of the last record. */ m = sb->sb_mb; if (m == NULL) { sb->sb_mbtail = NULL; sb->sb_lastrecord = NULL; } else if (m->m_nextpkt == NULL) { sb->sb_lastrecord = m; } } /* * Drop data from (the front of) a sockbuf. */ void sbdrop(sb, len) register struct sockbuf *sb; register int len; { SOCKBUF_LOCK(sb); sbdrop_locked(sb, len); SOCKBUF_UNLOCK(sb); } /* * Drop a record off the front of a sockbuf * and move the next record to the front. */ void sbdroprecord_locked(sb) register struct sockbuf *sb; { register struct mbuf *m; SOCKBUF_LOCK_ASSERT(sb); m = sb->sb_mb; if (m) { sb->sb_mb = m->m_nextpkt; do { sbfree(sb, m); m = m_free(m); } while (m); } SB_EMPTY_FIXUP(sb); } /* * Drop a record off the front of a sockbuf * and move the next record to the front. */ void sbdroprecord(sb) register struct sockbuf *sb; { SOCKBUF_LOCK(sb); sbdroprecord_locked(sb); SOCKBUF_UNLOCK(sb); } /* * Create a "control" mbuf containing the specified data * with the specified type for presentation on a socket buffer. */ struct mbuf * sbcreatecontrol(p, size, type, level) caddr_t p; register int size; int type, level; { register struct cmsghdr *cp; struct mbuf *m; if (CMSG_SPACE((u_int)size) > MCLBYTES) return ((struct mbuf *) NULL); if (CMSG_SPACE((u_int)size) > MLEN) m = m_getcl(M_DONTWAIT, MT_CONTROL, 0); else m = m_get(M_DONTWAIT, MT_CONTROL); if (m == NULL) return ((struct mbuf *) NULL); cp = mtod(m, struct cmsghdr *); m->m_len = 0; KASSERT(CMSG_SPACE((u_int)size) <= M_TRAILINGSPACE(m), ("sbcreatecontrol: short mbuf")); if (p != NULL) (void)memcpy(CMSG_DATA(cp), p, size); m->m_len = CMSG_SPACE(size); cp->cmsg_len = CMSG_LEN(size); cp->cmsg_level = level; cp->cmsg_type = type; return (m); } /* * Some routines that return EOPNOTSUPP for entry points that are not * supported by a protocol. Fill in as needed. */ void pru_abort_notsupp(struct socket *so) { } int pru_accept_notsupp(struct socket *so, struct sockaddr **nam) { return EOPNOTSUPP; } int pru_attach_notsupp(struct socket *so, int proto, struct thread *td) { return EOPNOTSUPP; } int pru_bind_notsupp(struct socket *so, struct sockaddr *nam, struct thread *td) { return EOPNOTSUPP; } int pru_connect_notsupp(struct socket *so, struct sockaddr *nam, struct thread *td) { return EOPNOTSUPP; } int pru_connect2_notsupp(struct socket *so1, struct socket *so2) { return EOPNOTSUPP; } int pru_control_notsupp(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp, struct thread *td) { return EOPNOTSUPP; } -int +void pru_detach_notsupp(struct socket *so) { - return EOPNOTSUPP; + } int pru_disconnect_notsupp(struct socket *so) { return EOPNOTSUPP; } int pru_listen_notsupp(struct socket *so, int backlog, struct thread *td) { return EOPNOTSUPP; } int pru_peeraddr_notsupp(struct socket *so, struct sockaddr **nam) { return EOPNOTSUPP; } int pru_rcvd_notsupp(struct socket *so, int flags) { return EOPNOTSUPP; } int pru_rcvoob_notsupp(struct socket *so, struct mbuf *m, int flags) { return EOPNOTSUPP; } int pru_send_notsupp(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *control, struct thread *td) { return EOPNOTSUPP; } /* * This isn't really a ``null'' operation, but it's the default one * and doesn't do anything destructive. */ int pru_sense_null(struct socket *so, struct stat *sb) { sb->st_blksize = so->so_snd.sb_hiwat; return 0; } int pru_shutdown_notsupp(struct socket *so) { return EOPNOTSUPP; } int pru_sockaddr_notsupp(struct socket *so, struct sockaddr **nam) { return EOPNOTSUPP; } int pru_sosend_notsupp(struct socket *so, struct sockaddr *addr, struct uio *uio, struct mbuf *top, struct mbuf *control, int flags, struct thread *td) { return EOPNOTSUPP; } int pru_soreceive_notsupp(struct socket *so, struct sockaddr **paddr, struct uio *uio, struct mbuf **mp0, struct mbuf **controlp, int *flagsp) { return EOPNOTSUPP; } int pru_sopoll_notsupp(struct socket *so, int events, struct ucred *cred, struct thread *td) { return EOPNOTSUPP; } /* * For protocol types that don't keep cached copies of labels in their * pcbs, provide a null sosetlabel that does a NOOP. */ void pru_sosetlabel_null(struct socket *so) { } /* * Make a copy of a sockaddr in a malloced buffer of type M_SONAME. */ struct sockaddr * sodupsockaddr(const struct sockaddr *sa, int mflags) { struct sockaddr *sa2; sa2 = malloc(sa->sa_len, M_SONAME, mflags); if (sa2) bcopy(sa, sa2, sa->sa_len); return sa2; } /* * Create an external-format (``xsocket'') structure using the information * in the kernel-format socket structure pointed to by so. This is done * to reduce the spew of irrelevant information over this interface, * to isolate user code from changes in the kernel structure, and * potentially to provide information-hiding if we decide that * some of this information should be hidden from users. */ void sotoxsocket(struct socket *so, struct xsocket *xso) { xso->xso_len = sizeof *xso; xso->xso_so = so; xso->so_type = so->so_type; xso->so_options = so->so_options; xso->so_linger = so->so_linger; xso->so_state = so->so_state; xso->so_pcb = so->so_pcb; xso->xso_protocol = so->so_proto->pr_protocol; xso->xso_family = so->so_proto->pr_domain->dom_family; xso->so_qlen = so->so_qlen; xso->so_incqlen = so->so_incqlen; xso->so_qlimit = so->so_qlimit; xso->so_timeo = so->so_timeo; xso->so_error = so->so_error; xso->so_pgid = so->so_sigio ? so->so_sigio->sio_pgid : 0; xso->so_oobmark = so->so_oobmark; sbtoxsockbuf(&so->so_snd, &xso->so_snd); sbtoxsockbuf(&so->so_rcv, &xso->so_rcv); xso->so_uid = so->so_cred->cr_uid; } /* * This does the same for sockbufs. Note that the xsockbuf structure, * since it is always embedded in a socket, does not include a self * pointer nor a length. We make this entry point public in case * some other mechanism needs it. */ void sbtoxsockbuf(struct sockbuf *sb, struct xsockbuf *xsb) { xsb->sb_cc = sb->sb_cc; xsb->sb_hiwat = sb->sb_hiwat; xsb->sb_mbcnt = sb->sb_mbcnt; xsb->sb_mbmax = sb->sb_mbmax; xsb->sb_lowat = sb->sb_lowat; xsb->sb_flags = sb->sb_flags; xsb->sb_timeo = sb->sb_timeo; } /* * Here is the definition of some of the basic objects in the kern.ipc * branch of the MIB. */ SYSCTL_NODE(_kern, KERN_IPC, ipc, CTLFLAG_RW, 0, "IPC"); /* This takes the place of kern.maxsockbuf, which moved to kern.ipc. */ static int dummy; SYSCTL_INT(_kern, KERN_DUMMY, dummy, CTLFLAG_RW, &dummy, 0, ""); SYSCTL_OID(_kern_ipc, KIPC_MAXSOCKBUF, maxsockbuf, CTLTYPE_ULONG|CTLFLAG_RW, &sb_max, 0, sysctl_handle_sb_max, "LU", "Maximum socket buffer size"); SYSCTL_INT(_kern_ipc, OID_AUTO, maxsockets, CTLFLAG_RDTUN, &maxsockets, 0, "Maximum number of sockets avaliable"); SYSCTL_ULONG(_kern_ipc, KIPC_SOCKBUF_WASTE, sockbuf_waste_factor, CTLFLAG_RW, &sb_efficiency, 0, ""); /* * Initialise maxsockets */ static void init_maxsockets(void *ignored) { TUNABLE_INT_FETCH("kern.ipc.maxsockets", &maxsockets); maxsockets = imax(maxsockets, imax(maxfiles, nmbclusters)); } SYSINIT(param, SI_SUB_TUNABLES, SI_ORDER_ANY, init_maxsockets, NULL); diff --git a/sys/kern/uipc_usrreq.c b/sys/kern/uipc_usrreq.c index f30431145e6a..956013ab9451 100644 --- a/sys/kern/uipc_usrreq.c +++ b/sys/kern/uipc_usrreq.c @@ -1,1827 +1,1826 @@ /*- * Copyright (c) 1982, 1986, 1989, 1991, 1993 * The Regents of the University of California. * Copyright 2004-2006 Robert N. M. Watson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * From: @(#)uipc_usrreq.c 8.3 (Berkeley) 1/4/94 */ #include __FBSDID("$FreeBSD$"); #include "opt_mac.h" #include #include #include #include /* XXX must be before */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static uma_zone_t unp_zone; static unp_gen_t unp_gencnt; static u_int unp_count; static struct unp_head unp_shead, unp_dhead; /* * Unix communications domain. * * TODO: * SEQPACKET, RDM * rethink name space problems * need a proper out-of-band * lock pushdown */ static const struct sockaddr sun_noname = { sizeof(sun_noname), AF_LOCAL }; static ino_t unp_ino; /* prototype for fake inode numbers */ struct mbuf *unp_addsockcred(struct thread *, struct mbuf *); /* * Currently, UNIX domain sockets are protected by a single subsystem lock, * which covers global data structures and variables, the contents of each * per-socket unpcb structure, and the so_pcb field in sockets attached to * the UNIX domain. This provides for a moderate degree of paralellism, as * receive operations on UNIX domain sockets do not need to acquire the * subsystem lock. Finer grained locking to permit send() without acquiring * a global lock would be a logical next step. * * The UNIX domain socket lock preceds all socket layer locks, including the * socket lock and socket buffer lock, permitting UNIX domain socket code to * call into socket support routines without releasing its locks. * * Some caution is required in areas where the UNIX domain socket code enters * VFS in order to create or find rendezvous points. This results in * dropping of the UNIX domain socket subsystem lock, acquisition of the * Giant lock, and potential sleeping. This increases the chances of races, * and exposes weaknesses in the socket->protocol API by offering poor * failure modes. */ static struct mtx unp_mtx; #define UNP_LOCK_INIT() \ mtx_init(&unp_mtx, "unp", NULL, MTX_DEF) #define UNP_LOCK() mtx_lock(&unp_mtx) #define UNP_UNLOCK() mtx_unlock(&unp_mtx) #define UNP_LOCK_ASSERT() mtx_assert(&unp_mtx, MA_OWNED) #define UNP_UNLOCK_ASSERT() mtx_assert(&unp_mtx, MA_NOTOWNED) /* * Garbage collection of cyclic file descriptor/socket references occurs * asynchronously in a taskqueue context in order to avoid recursion and * reentrance in the UNIX domain socket, file descriptor, and socket layer * code. See unp_gc() for a full description. */ static struct task unp_gc_task; static int unp_attach(struct socket *); static void unp_detach(struct unpcb *); static int unp_bind(struct unpcb *,struct sockaddr *, struct thread *); static int unp_connect(struct socket *,struct sockaddr *, struct thread *); static int unp_connect2(struct socket *so, struct socket *so2, int); static void unp_disconnect(struct unpcb *); static void unp_shutdown(struct unpcb *); static void unp_drop(struct unpcb *, int); static void unp_gc(__unused void *, int); static void unp_scan(struct mbuf *, void (*)(struct file *)); static void unp_mark(struct file *); static void unp_discard(struct file *); static void unp_freerights(struct file **, int); static int unp_internalize(struct mbuf **, struct thread *); static int unp_listen(struct socket *, struct unpcb *, int, struct thread *); static void uipc_abort(struct socket *so) { struct unpcb *unp; unp = sotounpcb(so); KASSERT(unp != NULL, ("uipc_abort: unp == NULL")); UNP_LOCK(); unp_drop(unp, ECONNABORTED); unp_detach(unp); UNP_UNLOCK_ASSERT(); } static int uipc_accept(struct socket *so, struct sockaddr **nam) { struct unpcb *unp; const struct sockaddr *sa; /* * Pass back name of connected socket, * if it was bound and we are still connected * (our peer may have closed already!). */ unp = sotounpcb(so); KASSERT(unp != NULL, ("uipc_accept: unp == NULL")); *nam = malloc(sizeof(struct sockaddr_un), M_SONAME, M_WAITOK); UNP_LOCK(); if (unp->unp_conn != NULL && unp->unp_conn->unp_addr != NULL) sa = (struct sockaddr *) unp->unp_conn->unp_addr; else sa = &sun_noname; bcopy(sa, *nam, sa->sa_len); UNP_UNLOCK(); return (0); } static int uipc_attach(struct socket *so, int proto, struct thread *td) { return (unp_attach(so)); } static int uipc_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { struct unpcb *unp; int error; unp = sotounpcb(so); KASSERT(unp != NULL, ("uipc_bind: unp == NULL")); UNP_LOCK(); error = unp_bind(unp, nam, td); UNP_UNLOCK(); return (error); } static int uipc_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { struct unpcb *unp; int error; KASSERT(td == curthread, ("uipc_connect: td != curthread")); unp = sotounpcb(so); KASSERT(unp != NULL, ("uipc_connect: unp == NULL")); UNP_LOCK(); error = unp_connect(so, nam, td); UNP_UNLOCK(); return (error); } int uipc_connect2(struct socket *so1, struct socket *so2) { struct unpcb *unp; int error; unp = sotounpcb(so1); KASSERT(unp != NULL, ("uipc_connect2: unp == NULL")); UNP_LOCK(); error = unp_connect2(so1, so2, PRU_CONNECT2); UNP_UNLOCK(); return (error); } /* control is EOPNOTSUPP */ -static int +static void uipc_detach(struct socket *so) { struct unpcb *unp; unp = sotounpcb(so); KASSERT(unp != NULL, ("uipc_detach: unp == NULL")); UNP_LOCK(); unp_detach(unp); UNP_UNLOCK_ASSERT(); - return (0); } static int uipc_disconnect(struct socket *so) { struct unpcb *unp; unp = sotounpcb(so); KASSERT(unp != NULL, ("uipc_disconnect: unp == NULL")); UNP_LOCK(); unp_disconnect(unp); UNP_UNLOCK(); return (0); } static int uipc_listen(struct socket *so, int backlog, struct thread *td) { struct unpcb *unp; int error; unp = sotounpcb(so); KASSERT(unp != NULL, ("uipc_listen: unp == NULL")); UNP_LOCK(); if (unp->unp_vnode == NULL) { UNP_UNLOCK(); return (EINVAL); } error = unp_listen(so, unp, backlog, td); UNP_UNLOCK(); return (error); } static int uipc_peeraddr(struct socket *so, struct sockaddr **nam) { struct unpcb *unp; const struct sockaddr *sa; unp = sotounpcb(so); KASSERT(unp != NULL, ("uipc_peeraddr: unp == NULL")); *nam = malloc(sizeof(struct sockaddr_un), M_SONAME, M_WAITOK); UNP_LOCK(); if (unp->unp_conn != NULL && unp->unp_conn->unp_addr!= NULL) sa = (struct sockaddr *) unp->unp_conn->unp_addr; else { /* * XXX: It seems that this test always fails even when * connection is established. So, this else clause is * added as workaround to return PF_LOCAL sockaddr. */ sa = &sun_noname; } bcopy(sa, *nam, sa->sa_len); UNP_UNLOCK(); return (0); } static int uipc_rcvd(struct socket *so, int flags) { struct unpcb *unp; struct socket *so2; u_long newhiwat; unp = sotounpcb(so); KASSERT(unp != NULL, ("uipc_rcvd: unp == NULL")); UNP_LOCK(); switch (so->so_type) { case SOCK_DGRAM: panic("uipc_rcvd DGRAM?"); /*NOTREACHED*/ case SOCK_STREAM: if (unp->unp_conn == NULL) break; so2 = unp->unp_conn->unp_socket; SOCKBUF_LOCK(&so2->so_snd); SOCKBUF_LOCK(&so->so_rcv); /* * Adjust backpressure on sender * and wakeup any waiting to write. */ so2->so_snd.sb_mbmax += unp->unp_mbcnt - so->so_rcv.sb_mbcnt; unp->unp_mbcnt = so->so_rcv.sb_mbcnt; newhiwat = so2->so_snd.sb_hiwat + unp->unp_cc - so->so_rcv.sb_cc; (void)chgsbsize(so2->so_cred->cr_uidinfo, &so2->so_snd.sb_hiwat, newhiwat, RLIM_INFINITY); unp->unp_cc = so->so_rcv.sb_cc; SOCKBUF_UNLOCK(&so->so_rcv); sowwakeup_locked(so2); break; default: panic("uipc_rcvd unknown socktype"); } UNP_UNLOCK(); return (0); } /* pru_rcvoob is EOPNOTSUPP */ static int uipc_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, struct mbuf *control, struct thread *td) { int error = 0; struct unpcb *unp; struct socket *so2; u_long newhiwat; unp = sotounpcb(so); KASSERT(unp != NULL, ("uipc_send: unp == NULL")); if (flags & PRUS_OOB) { error = EOPNOTSUPP; goto release; } if (control != NULL && (error = unp_internalize(&control, td))) goto release; UNP_LOCK(); switch (so->so_type) { case SOCK_DGRAM: { const struct sockaddr *from; if (nam != NULL) { if (unp->unp_conn != NULL) { error = EISCONN; break; } error = unp_connect(so, nam, td); if (error) break; } else { if (unp->unp_conn == NULL) { error = ENOTCONN; break; } } so2 = unp->unp_conn->unp_socket; if (unp->unp_addr != NULL) from = (struct sockaddr *)unp->unp_addr; else from = &sun_noname; if (unp->unp_conn->unp_flags & UNP_WANTCRED) control = unp_addsockcred(td, control); SOCKBUF_LOCK(&so2->so_rcv); if (sbappendaddr_locked(&so2->so_rcv, from, m, control)) { sorwakeup_locked(so2); m = NULL; control = NULL; } else { SOCKBUF_UNLOCK(&so2->so_rcv); error = ENOBUFS; } if (nam != NULL) unp_disconnect(unp); break; } case SOCK_STREAM: /* Connect if not connected yet. */ /* * Note: A better implementation would complain * if not equal to the peer's address. */ if ((so->so_state & SS_ISCONNECTED) == 0) { if (nam != NULL) { error = unp_connect(so, nam, td); if (error) break; /* XXX */ } else { error = ENOTCONN; break; } } SOCKBUF_LOCK(&so->so_snd); if (so->so_snd.sb_state & SBS_CANTSENDMORE) { SOCKBUF_UNLOCK(&so->so_snd); error = EPIPE; break; } if (unp->unp_conn == NULL) panic("uipc_send connected but no connection?"); so2 = unp->unp_conn->unp_socket; SOCKBUF_LOCK(&so2->so_rcv); if (unp->unp_conn->unp_flags & UNP_WANTCRED) { /* * Credentials are passed only once on * SOCK_STREAM. */ unp->unp_conn->unp_flags &= ~UNP_WANTCRED; control = unp_addsockcred(td, control); } /* * Send to paired receive port, and then reduce * send buffer hiwater marks to maintain backpressure. * Wake up readers. */ if (control != NULL) { if (sbappendcontrol_locked(&so2->so_rcv, m, control)) control = NULL; } else { sbappend_locked(&so2->so_rcv, m); } so->so_snd.sb_mbmax -= so2->so_rcv.sb_mbcnt - unp->unp_conn->unp_mbcnt; unp->unp_conn->unp_mbcnt = so2->so_rcv.sb_mbcnt; newhiwat = so->so_snd.sb_hiwat - (so2->so_rcv.sb_cc - unp->unp_conn->unp_cc); (void)chgsbsize(so->so_cred->cr_uidinfo, &so->so_snd.sb_hiwat, newhiwat, RLIM_INFINITY); SOCKBUF_UNLOCK(&so->so_snd); unp->unp_conn->unp_cc = so2->so_rcv.sb_cc; sorwakeup_locked(so2); m = NULL; break; default: panic("uipc_send unknown socktype"); } /* * SEND_EOF is equivalent to a SEND followed by * a SHUTDOWN. */ if (flags & PRUS_EOF) { socantsendmore(so); unp_shutdown(unp); } UNP_UNLOCK(); if (control != NULL && error != 0) unp_dispose(control); release: if (control != NULL) m_freem(control); if (m != NULL) m_freem(m); return (error); } static int uipc_sense(struct socket *so, struct stat *sb) { struct unpcb *unp; struct socket *so2; unp = sotounpcb(so); KASSERT(unp != NULL, ("uipc_sense: unp == NULL")); UNP_LOCK(); sb->st_blksize = so->so_snd.sb_hiwat; if (so->so_type == SOCK_STREAM && unp->unp_conn != NULL) { so2 = unp->unp_conn->unp_socket; sb->st_blksize += so2->so_rcv.sb_cc; } sb->st_dev = NODEV; if (unp->unp_ino == 0) unp->unp_ino = (++unp_ino == 0) ? ++unp_ino : unp_ino; sb->st_ino = unp->unp_ino; UNP_UNLOCK(); return (0); } static int uipc_shutdown(struct socket *so) { struct unpcb *unp; unp = sotounpcb(so); KASSERT(unp != NULL, ("uipc_shutdown: unp == NULL")); UNP_LOCK(); socantsendmore(so); unp_shutdown(unp); UNP_UNLOCK(); return (0); } static int uipc_sockaddr(struct socket *so, struct sockaddr **nam) { struct unpcb *unp; const struct sockaddr *sa; unp = sotounpcb(so); KASSERT(unp != NULL, ("uipc_sockaddr: unp == NULL")); *nam = malloc(sizeof(struct sockaddr_un), M_SONAME, M_WAITOK); UNP_LOCK(); if (unp->unp_addr != NULL) sa = (struct sockaddr *) unp->unp_addr; else sa = &sun_noname; bcopy(sa, *nam, sa->sa_len); UNP_UNLOCK(); return (0); } struct pr_usrreqs uipc_usrreqs = { .pru_abort = uipc_abort, .pru_accept = uipc_accept, .pru_attach = uipc_attach, .pru_bind = uipc_bind, .pru_connect = uipc_connect, .pru_connect2 = uipc_connect2, .pru_detach = uipc_detach, .pru_disconnect = uipc_disconnect, .pru_listen = uipc_listen, .pru_peeraddr = uipc_peeraddr, .pru_rcvd = uipc_rcvd, .pru_send = uipc_send, .pru_sense = uipc_sense, .pru_shutdown = uipc_shutdown, .pru_sockaddr = uipc_sockaddr, .pru_sosend = sosend, .pru_soreceive = soreceive, .pru_sopoll = sopoll, }; int uipc_ctloutput(struct socket *so, struct sockopt *sopt) { struct unpcb *unp; struct xucred xu; int error, optval; if (sopt->sopt_level != 0) return (EINVAL); unp = sotounpcb(so); KASSERT(unp != NULL, ("uipc_ctloutput: unp == NULL")); UNP_LOCK(); error = 0; switch (sopt->sopt_dir) { case SOPT_GET: switch (sopt->sopt_name) { case LOCAL_PEERCRED: if (unp->unp_flags & UNP_HAVEPC) xu = unp->unp_peercred; else { if (so->so_type == SOCK_STREAM) error = ENOTCONN; else error = EINVAL; } if (error == 0) error = sooptcopyout(sopt, &xu, sizeof(xu)); break; case LOCAL_CREDS: optval = unp->unp_flags & UNP_WANTCRED ? 1 : 0; error = sooptcopyout(sopt, &optval, sizeof(optval)); break; case LOCAL_CONNWAIT: optval = unp->unp_flags & UNP_CONNWAIT ? 1 : 0; error = sooptcopyout(sopt, &optval, sizeof(optval)); break; default: error = EOPNOTSUPP; break; } break; case SOPT_SET: switch (sopt->sopt_name) { case LOCAL_CREDS: case LOCAL_CONNWAIT: error = sooptcopyin(sopt, &optval, sizeof(optval), sizeof(optval)); if (error) break; #define OPTSET(bit) \ if (optval) \ unp->unp_flags |= bit; \ else \ unp->unp_flags &= ~bit; switch (sopt->sopt_name) { case LOCAL_CREDS: OPTSET(UNP_WANTCRED); break; case LOCAL_CONNWAIT: OPTSET(UNP_CONNWAIT); break; default: break; } break; #undef OPTSET default: error = ENOPROTOOPT; break; } break; default: error = EOPNOTSUPP; break; } UNP_UNLOCK(); return (error); } /* * Both send and receive buffers are allocated PIPSIZ bytes of buffering * for stream sockets, although the total for sender and receiver is * actually only PIPSIZ. * Datagram sockets really use the sendspace as the maximum datagram size, * and don't really want to reserve the sendspace. Their recvspace should * be large enough for at least one max-size datagram plus address. */ #ifndef PIPSIZ #define PIPSIZ 8192 #endif static u_long unpst_sendspace = PIPSIZ; static u_long unpst_recvspace = PIPSIZ; static u_long unpdg_sendspace = 2*1024; /* really max datagram size */ static u_long unpdg_recvspace = 4*1024; static int unp_rights; /* file descriptors in flight */ SYSCTL_DECL(_net_local_stream); SYSCTL_ULONG(_net_local_stream, OID_AUTO, sendspace, CTLFLAG_RW, &unpst_sendspace, 0, ""); SYSCTL_ULONG(_net_local_stream, OID_AUTO, recvspace, CTLFLAG_RW, &unpst_recvspace, 0, ""); SYSCTL_DECL(_net_local_dgram); SYSCTL_ULONG(_net_local_dgram, OID_AUTO, maxdgram, CTLFLAG_RW, &unpdg_sendspace, 0, ""); SYSCTL_ULONG(_net_local_dgram, OID_AUTO, recvspace, CTLFLAG_RW, &unpdg_recvspace, 0, ""); SYSCTL_DECL(_net_local); SYSCTL_INT(_net_local, OID_AUTO, inflight, CTLFLAG_RD, &unp_rights, 0, ""); static int unp_attach(struct socket *so) { struct unpcb *unp; int error; KASSERT(so->so_pcb == NULL, ("unp_attach: so_pcb != NULL")); if (so->so_snd.sb_hiwat == 0 || so->so_rcv.sb_hiwat == 0) { switch (so->so_type) { case SOCK_STREAM: error = soreserve(so, unpst_sendspace, unpst_recvspace); break; case SOCK_DGRAM: error = soreserve(so, unpdg_sendspace, unpdg_recvspace); break; default: panic("unp_attach"); } if (error) return (error); } unp = uma_zalloc(unp_zone, M_WAITOK | M_ZERO); if (unp == NULL) return (ENOBUFS); LIST_INIT(&unp->unp_refs); unp->unp_socket = so; so->so_pcb = unp; UNP_LOCK(); unp->unp_gencnt = ++unp_gencnt; unp_count++; LIST_INSERT_HEAD(so->so_type == SOCK_DGRAM ? &unp_dhead : &unp_shead, unp, unp_link); UNP_UNLOCK(); return (0); } static void unp_detach(struct unpcb *unp) { struct vnode *vp; int local_unp_rights; UNP_LOCK_ASSERT(); LIST_REMOVE(unp, unp_link); unp->unp_gencnt = ++unp_gencnt; --unp_count; if ((vp = unp->unp_vnode) != NULL) { /* * XXXRW: should v_socket be frobbed only while holding * Giant? */ unp->unp_vnode->v_socket = NULL; unp->unp_vnode = NULL; } if (unp->unp_conn != NULL) unp_disconnect(unp); while (!LIST_EMPTY(&unp->unp_refs)) { struct unpcb *ref = LIST_FIRST(&unp->unp_refs); unp_drop(ref, ECONNRESET); } soisdisconnected(unp->unp_socket); unp->unp_socket->so_pcb = NULL; local_unp_rights = unp_rights; UNP_UNLOCK(); if (unp->unp_addr != NULL) FREE(unp->unp_addr, M_SONAME); uma_zfree(unp_zone, unp); if (vp) { int vfslocked; vfslocked = VFS_LOCK_GIANT(vp->v_mount); vrele(vp); VFS_UNLOCK_GIANT(vfslocked); } if (local_unp_rights) taskqueue_enqueue(taskqueue_thread, &unp_gc_task); } static int unp_bind(struct unpcb *unp, struct sockaddr *nam, struct thread *td) { struct sockaddr_un *soun = (struct sockaddr_un *)nam; struct vnode *vp; struct mount *mp; struct vattr vattr; int error, namelen; struct nameidata nd; char *buf; UNP_LOCK_ASSERT(); /* * XXXRW: This test-and-set of unp_vnode is non-atomic; the * unlocked read here is fine, but the value of unp_vnode needs * to be tested again after we do all the lookups to see if the * pcb is still unbound? */ if (unp->unp_vnode != NULL) return (EINVAL); namelen = soun->sun_len - offsetof(struct sockaddr_un, sun_path); if (namelen <= 0) return (EINVAL); UNP_UNLOCK(); buf = malloc(namelen + 1, M_TEMP, M_WAITOK); strlcpy(buf, soun->sun_path, namelen + 1); mtx_lock(&Giant); restart: mtx_assert(&Giant, MA_OWNED); NDINIT(&nd, CREATE, NOFOLLOW | LOCKPARENT | SAVENAME, UIO_SYSSPACE, buf, td); /* SHOULD BE ABLE TO ADOPT EXISTING AND wakeup() ALA FIFO's */ error = namei(&nd); if (error) goto done; vp = nd.ni_vp; if (vp != NULL || vn_start_write(nd.ni_dvp, &mp, V_NOWAIT) != 0) { NDFREE(&nd, NDF_ONLY_PNBUF); if (nd.ni_dvp == vp) vrele(nd.ni_dvp); else vput(nd.ni_dvp); if (vp != NULL) { vrele(vp); error = EADDRINUSE; goto done; } error = vn_start_write(NULL, &mp, V_XSLEEP | PCATCH); if (error) goto done; goto restart; } VATTR_NULL(&vattr); vattr.va_type = VSOCK; vattr.va_mode = (ACCESSPERMS & ~td->td_proc->p_fd->fd_cmask); #ifdef MAC error = mac_check_vnode_create(td->td_ucred, nd.ni_dvp, &nd.ni_cnd, &vattr); #endif if (error == 0) { VOP_LEASE(nd.ni_dvp, td, td->td_ucred, LEASE_WRITE); error = VOP_CREATE(nd.ni_dvp, &nd.ni_vp, &nd.ni_cnd, &vattr); } NDFREE(&nd, NDF_ONLY_PNBUF); vput(nd.ni_dvp); if (error) { vn_finished_write(mp); goto done; } vp = nd.ni_vp; ASSERT_VOP_LOCKED(vp, "unp_bind"); soun = (struct sockaddr_un *)sodupsockaddr(nam, M_WAITOK); UNP_LOCK(); vp->v_socket = unp->unp_socket; unp->unp_vnode = vp; unp->unp_addr = soun; UNP_UNLOCK(); VOP_UNLOCK(vp, 0, td); vn_finished_write(mp); done: mtx_unlock(&Giant); free(buf, M_TEMP); UNP_LOCK(); return (error); } static int unp_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { struct sockaddr_un *soun = (struct sockaddr_un *)nam; struct vnode *vp; struct socket *so2, *so3; struct unpcb *unp, *unp2, *unp3; int error, len; struct nameidata nd; char buf[SOCK_MAXADDRLEN]; struct sockaddr *sa; UNP_LOCK_ASSERT(); unp = sotounpcb(so); KASSERT(unp != NULL, ("unp_connect: unp == NULL")); len = nam->sa_len - offsetof(struct sockaddr_un, sun_path); if (len <= 0) return (EINVAL); strlcpy(buf, soun->sun_path, len + 1); UNP_UNLOCK(); sa = malloc(sizeof(struct sockaddr_un), M_SONAME, M_WAITOK); mtx_lock(&Giant); NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_SYSSPACE, buf, td); error = namei(&nd); if (error) vp = NULL; else vp = nd.ni_vp; ASSERT_VOP_LOCKED(vp, "unp_connect"); NDFREE(&nd, NDF_ONLY_PNBUF); if (error) goto bad; if (vp->v_type != VSOCK) { error = ENOTSOCK; goto bad; } error = VOP_ACCESS(vp, VWRITE, td->td_ucred, td); if (error) goto bad; mtx_unlock(&Giant); UNP_LOCK(); unp = sotounpcb(so); KASSERT(unp != NULL, ("unp_connect: unp == NULL")); so2 = vp->v_socket; if (so2 == NULL) { error = ECONNREFUSED; goto bad2; } if (so->so_type != so2->so_type) { error = EPROTOTYPE; goto bad2; } if (so->so_proto->pr_flags & PR_CONNREQUIRED) { if (so2->so_options & SO_ACCEPTCONN) { /* * NB: drop locks here so unp_attach is entered * w/o locks; this avoids a recursive lock * of the head and holding sleep locks across * a (potentially) blocking malloc. */ UNP_UNLOCK(); so3 = sonewconn(so2, 0); UNP_LOCK(); } else so3 = NULL; if (so3 == NULL) { error = ECONNREFUSED; goto bad2; } unp = sotounpcb(so); unp2 = sotounpcb(so2); unp3 = sotounpcb(so3); if (unp2->unp_addr != NULL) { bcopy(unp2->unp_addr, sa, unp2->unp_addr->sun_len); unp3->unp_addr = (struct sockaddr_un *) sa; sa = NULL; } /* * unp_peercred management: * * The connecter's (client's) credentials are copied * from its process structure at the time of connect() * (which is now). */ cru2x(td->td_ucred, &unp3->unp_peercred); unp3->unp_flags |= UNP_HAVEPC; /* * The receiver's (server's) credentials are copied * from the unp_peercred member of socket on which the * former called listen(); unp_listen() cached that * process's credentials at that time so we can use * them now. */ KASSERT(unp2->unp_flags & UNP_HAVEPCCACHED, ("unp_connect: listener without cached peercred")); memcpy(&unp->unp_peercred, &unp2->unp_peercred, sizeof(unp->unp_peercred)); unp->unp_flags |= UNP_HAVEPC; #ifdef MAC SOCK_LOCK(so); mac_set_socket_peer_from_socket(so, so3); mac_set_socket_peer_from_socket(so3, so); SOCK_UNLOCK(so); #endif so2 = so3; } error = unp_connect2(so, so2, PRU_CONNECT); bad2: UNP_UNLOCK(); mtx_lock(&Giant); bad: mtx_assert(&Giant, MA_OWNED); if (vp != NULL) vput(vp); mtx_unlock(&Giant); free(sa, M_SONAME); UNP_LOCK(); return (error); } static int unp_connect2(struct socket *so, struct socket *so2, int req) { struct unpcb *unp = sotounpcb(so); struct unpcb *unp2; UNP_LOCK_ASSERT(); if (so2->so_type != so->so_type) return (EPROTOTYPE); unp2 = sotounpcb(so2); KASSERT(unp2 != NULL, ("unp_connect2: unp2 == NULL")); unp->unp_conn = unp2; switch (so->so_type) { case SOCK_DGRAM: LIST_INSERT_HEAD(&unp2->unp_refs, unp, unp_reflink); soisconnected(so); break; case SOCK_STREAM: unp2->unp_conn = unp; if (req == PRU_CONNECT && ((unp->unp_flags | unp2->unp_flags) & UNP_CONNWAIT)) soisconnecting(so); else soisconnected(so); soisconnected(so2); break; default: panic("unp_connect2"); } return (0); } static void unp_disconnect(struct unpcb *unp) { struct unpcb *unp2 = unp->unp_conn; struct socket *so; UNP_LOCK_ASSERT(); if (unp2 == NULL) return; unp->unp_conn = NULL; switch (unp->unp_socket->so_type) { case SOCK_DGRAM: LIST_REMOVE(unp, unp_reflink); so = unp->unp_socket; SOCK_LOCK(so); so->so_state &= ~SS_ISCONNECTED; SOCK_UNLOCK(so); break; case SOCK_STREAM: soisdisconnected(unp->unp_socket); unp2->unp_conn = NULL; soisdisconnected(unp2->unp_socket); break; } } #ifdef notdef void unp_abort(struct unpcb *unp) { unp_detach(unp); UNP_UNLOCK_ASSERT(); } #endif /* * unp_pcblist() assumes that UNIX domain socket memory is never reclaimed * by the zone (UMA_ZONE_NOFREE), and as such potentially stale pointers * are safe to reference. It first scans the list of struct unpcb's to * generate a pointer list, then it rescans its list one entry at a time to * externalize and copyout. It checks the generation number to see if a * struct unpcb has been reused, and will skip it if so. */ static int unp_pcblist(SYSCTL_HANDLER_ARGS) { int error, i, n; struct unpcb *unp, **unp_list; unp_gen_t gencnt; struct xunpgen *xug; struct unp_head *head; struct xunpcb *xu; head = ((intptr_t)arg1 == SOCK_DGRAM ? &unp_dhead : &unp_shead); /* * The process of preparing the PCB list is too time-consuming and * resource-intensive to repeat twice on every request. */ if (req->oldptr == NULL) { n = unp_count; req->oldidx = 2 * (sizeof *xug) + (n + n/8) * sizeof(struct xunpcb); return (0); } if (req->newptr != NULL) return (EPERM); /* * OK, now we're committed to doing something. */ xug = malloc(sizeof(*xug), M_TEMP, M_WAITOK); UNP_LOCK(); gencnt = unp_gencnt; n = unp_count; UNP_UNLOCK(); xug->xug_len = sizeof *xug; xug->xug_count = n; xug->xug_gen = gencnt; xug->xug_sogen = so_gencnt; error = SYSCTL_OUT(req, xug, sizeof *xug); if (error) { free(xug, M_TEMP); return (error); } unp_list = malloc(n * sizeof *unp_list, M_TEMP, M_WAITOK); UNP_LOCK(); for (unp = LIST_FIRST(head), i = 0; unp && i < n; unp = LIST_NEXT(unp, unp_link)) { if (unp->unp_gencnt <= gencnt) { if (cr_cansee(req->td->td_ucred, unp->unp_socket->so_cred)) continue; unp_list[i++] = unp; } } UNP_UNLOCK(); n = i; /* in case we lost some during malloc */ error = 0; xu = malloc(sizeof(*xu), M_TEMP, M_WAITOK | M_ZERO); for (i = 0; i < n; i++) { unp = unp_list[i]; if (unp->unp_gencnt <= gencnt) { xu->xu_len = sizeof *xu; xu->xu_unpp = unp; /* * XXX - need more locking here to protect against * connect/disconnect races for SMP. */ if (unp->unp_addr != NULL) bcopy(unp->unp_addr, &xu->xu_addr, unp->unp_addr->sun_len); if (unp->unp_conn != NULL && unp->unp_conn->unp_addr != NULL) bcopy(unp->unp_conn->unp_addr, &xu->xu_caddr, unp->unp_conn->unp_addr->sun_len); bcopy(unp, &xu->xu_unp, sizeof *unp); sotoxsocket(unp->unp_socket, &xu->xu_socket); error = SYSCTL_OUT(req, xu, sizeof *xu); } } free(xu, M_TEMP); if (!error) { /* * Give the user an updated idea of our state. * If the generation differs from what we told * her before, she knows that something happened * while we were processing this request, and it * might be necessary to retry. */ xug->xug_gen = unp_gencnt; xug->xug_sogen = so_gencnt; xug->xug_count = unp_count; error = SYSCTL_OUT(req, xug, sizeof *xug); } free(unp_list, M_TEMP); free(xug, M_TEMP); return (error); } SYSCTL_PROC(_net_local_dgram, OID_AUTO, pcblist, CTLFLAG_RD, (caddr_t)(long)SOCK_DGRAM, 0, unp_pcblist, "S,xunpcb", "List of active local datagram sockets"); SYSCTL_PROC(_net_local_stream, OID_AUTO, pcblist, CTLFLAG_RD, (caddr_t)(long)SOCK_STREAM, 0, unp_pcblist, "S,xunpcb", "List of active local stream sockets"); static void unp_shutdown(struct unpcb *unp) { struct socket *so; UNP_LOCK_ASSERT(); if (unp->unp_socket->so_type == SOCK_STREAM && unp->unp_conn && (so = unp->unp_conn->unp_socket)) socantrcvmore(so); } static void unp_drop(struct unpcb *unp, int errno) { struct socket *so = unp->unp_socket; UNP_LOCK_ASSERT(); so->so_error = errno; unp_disconnect(unp); } #ifdef notdef void unp_drain(void) { } #endif static void unp_freerights(struct file **rp, int fdcount) { int i; struct file *fp; for (i = 0; i < fdcount; i++) { fp = *rp; /* * zero the pointer before calling * unp_discard since it may end up * in unp_gc().. * * XXXRW: This is less true than it used to be. */ *rp++ = 0; unp_discard(fp); } } int unp_externalize(struct mbuf *control, struct mbuf **controlp) { struct thread *td = curthread; /* XXX */ struct cmsghdr *cm = mtod(control, struct cmsghdr *); int i; int *fdp; struct file **rp; struct file *fp; void *data; socklen_t clen = control->m_len, datalen; int error, newfds; int f; u_int newlen; UNP_UNLOCK_ASSERT(); error = 0; if (controlp != NULL) /* controlp == NULL => free control messages */ *controlp = NULL; while (cm != NULL) { if (sizeof(*cm) > clen || cm->cmsg_len > clen) { error = EINVAL; break; } data = CMSG_DATA(cm); datalen = (caddr_t)cm + cm->cmsg_len - (caddr_t)data; if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SCM_RIGHTS) { newfds = datalen / sizeof(struct file *); rp = data; /* If we're not outputting the descriptors free them. */ if (error || controlp == NULL) { unp_freerights(rp, newfds); goto next; } FILEDESC_LOCK(td->td_proc->p_fd); /* if the new FD's will not fit free them. */ if (!fdavail(td, newfds)) { FILEDESC_UNLOCK(td->td_proc->p_fd); error = EMSGSIZE; unp_freerights(rp, newfds); goto next; } /* * now change each pointer to an fd in the global * table to an integer that is the index to the * local fd table entry that we set up to point * to the global one we are transferring. */ newlen = newfds * sizeof(int); *controlp = sbcreatecontrol(NULL, newlen, SCM_RIGHTS, SOL_SOCKET); if (*controlp == NULL) { FILEDESC_UNLOCK(td->td_proc->p_fd); error = E2BIG; unp_freerights(rp, newfds); goto next; } fdp = (int *) CMSG_DATA(mtod(*controlp, struct cmsghdr *)); for (i = 0; i < newfds; i++) { if (fdalloc(td, 0, &f)) panic("unp_externalize fdalloc failed"); fp = *rp++; td->td_proc->p_fd->fd_ofiles[f] = fp; FILE_LOCK(fp); fp->f_msgcount--; FILE_UNLOCK(fp); unp_rights--; *fdp++ = f; } FILEDESC_UNLOCK(td->td_proc->p_fd); } else { /* We can just copy anything else across */ if (error || controlp == NULL) goto next; *controlp = sbcreatecontrol(NULL, datalen, cm->cmsg_type, cm->cmsg_level); if (*controlp == NULL) { error = ENOBUFS; goto next; } bcopy(data, CMSG_DATA(mtod(*controlp, struct cmsghdr *)), datalen); } controlp = &(*controlp)->m_next; next: if (CMSG_SPACE(datalen) < clen) { clen -= CMSG_SPACE(datalen); cm = (struct cmsghdr *) ((caddr_t)cm + CMSG_SPACE(datalen)); } else { clen = 0; cm = NULL; } } m_freem(control); return (error); } void unp_init(void) { unp_zone = uma_zcreate("unpcb", sizeof(struct unpcb), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_NOFREE); if (unp_zone == NULL) panic("unp_init"); uma_zone_set_max(unp_zone, nmbclusters); LIST_INIT(&unp_dhead); LIST_INIT(&unp_shead); TASK_INIT(&unp_gc_task, 0, unp_gc, NULL); UNP_LOCK_INIT(); } static int unp_internalize(struct mbuf **controlp, struct thread *td) { struct mbuf *control = *controlp; struct proc *p = td->td_proc; struct filedesc *fdescp = p->p_fd; struct cmsghdr *cm = mtod(control, struct cmsghdr *); struct cmsgcred *cmcred; struct file **rp; struct file *fp; struct timeval *tv; int i, fd, *fdp; void *data; socklen_t clen = control->m_len, datalen; int error, oldfds; u_int newlen; UNP_UNLOCK_ASSERT(); error = 0; *controlp = NULL; while (cm != NULL) { if (sizeof(*cm) > clen || cm->cmsg_level != SOL_SOCKET || cm->cmsg_len > clen) { error = EINVAL; goto out; } data = CMSG_DATA(cm); datalen = (caddr_t)cm + cm->cmsg_len - (caddr_t)data; switch (cm->cmsg_type) { /* * Fill in credential information. */ case SCM_CREDS: *controlp = sbcreatecontrol(NULL, sizeof(*cmcred), SCM_CREDS, SOL_SOCKET); if (*controlp == NULL) { error = ENOBUFS; goto out; } cmcred = (struct cmsgcred *) CMSG_DATA(mtod(*controlp, struct cmsghdr *)); cmcred->cmcred_pid = p->p_pid; cmcred->cmcred_uid = td->td_ucred->cr_ruid; cmcred->cmcred_gid = td->td_ucred->cr_rgid; cmcred->cmcred_euid = td->td_ucred->cr_uid; cmcred->cmcred_ngroups = MIN(td->td_ucred->cr_ngroups, CMGROUP_MAX); for (i = 0; i < cmcred->cmcred_ngroups; i++) cmcred->cmcred_groups[i] = td->td_ucred->cr_groups[i]; break; case SCM_RIGHTS: oldfds = datalen / sizeof (int); /* * check that all the FDs passed in refer to legal files * If not, reject the entire operation. */ fdp = data; FILEDESC_LOCK(fdescp); for (i = 0; i < oldfds; i++) { fd = *fdp++; if ((unsigned)fd >= fdescp->fd_nfiles || fdescp->fd_ofiles[fd] == NULL) { FILEDESC_UNLOCK(fdescp); error = EBADF; goto out; } fp = fdescp->fd_ofiles[fd]; if (!(fp->f_ops->fo_flags & DFLAG_PASSABLE)) { FILEDESC_UNLOCK(fdescp); error = EOPNOTSUPP; goto out; } } /* * Now replace the integer FDs with pointers to * the associated global file table entry.. */ newlen = oldfds * sizeof(struct file *); *controlp = sbcreatecontrol(NULL, newlen, SCM_RIGHTS, SOL_SOCKET); if (*controlp == NULL) { FILEDESC_UNLOCK(fdescp); error = E2BIG; goto out; } fdp = data; rp = (struct file **) CMSG_DATA(mtod(*controlp, struct cmsghdr *)); for (i = 0; i < oldfds; i++) { fp = fdescp->fd_ofiles[*fdp++]; *rp++ = fp; FILE_LOCK(fp); fp->f_count++; fp->f_msgcount++; FILE_UNLOCK(fp); unp_rights++; } FILEDESC_UNLOCK(fdescp); break; case SCM_TIMESTAMP: *controlp = sbcreatecontrol(NULL, sizeof(*tv), SCM_TIMESTAMP, SOL_SOCKET); if (*controlp == NULL) { error = ENOBUFS; goto out; } tv = (struct timeval *) CMSG_DATA(mtod(*controlp, struct cmsghdr *)); microtime(tv); break; default: error = EINVAL; goto out; } controlp = &(*controlp)->m_next; if (CMSG_SPACE(datalen) < clen) { clen -= CMSG_SPACE(datalen); cm = (struct cmsghdr *) ((caddr_t)cm + CMSG_SPACE(datalen)); } else { clen = 0; cm = NULL; } } out: m_freem(control); return (error); } struct mbuf * unp_addsockcred(struct thread *td, struct mbuf *control) { struct mbuf *m, *n; struct sockcred *sc; int ngroups; int i; ngroups = MIN(td->td_ucred->cr_ngroups, CMGROUP_MAX); m = sbcreatecontrol(NULL, SOCKCREDSIZE(ngroups), SCM_CREDS, SOL_SOCKET); if (m == NULL) return (control); m->m_next = NULL; sc = (struct sockcred *) CMSG_DATA(mtod(m, struct cmsghdr *)); sc->sc_uid = td->td_ucred->cr_ruid; sc->sc_euid = td->td_ucred->cr_uid; sc->sc_gid = td->td_ucred->cr_rgid; sc->sc_egid = td->td_ucred->cr_gid; sc->sc_ngroups = ngroups; for (i = 0; i < sc->sc_ngroups; i++) sc->sc_groups[i] = td->td_ucred->cr_groups[i]; /* * If a control message already exists, append us to the end. */ if (control != NULL) { for (n = control; n->m_next != NULL; n = n->m_next) ; n->m_next = m; } else control = m; return (control); } /* * unp_defer indicates whether additional work has been defered for a future * pass through unp_gc(). It is thread local and does not require explicit * synchronization. */ static int unp_defer; static int unp_taskcount; SYSCTL_INT(_net_local, OID_AUTO, taskcount, CTLFLAG_RD, &unp_taskcount, 0, ""); static int unp_recycled; SYSCTL_INT(_net_local, OID_AUTO, recycled, CTLFLAG_RD, &unp_recycled, 0, ""); static void unp_gc(__unused void *arg, int pending) { struct file *fp, *nextfp; struct socket *so; struct file **extra_ref, **fpp; int nunref, i; int nfiles_snap; int nfiles_slack = 20; unp_taskcount++; unp_defer = 0; /* * before going through all this, set all FDs to * be NOT defered and NOT externally accessible */ sx_slock(&filelist_lock); LIST_FOREACH(fp, &filehead, f_list) fp->f_gcflag &= ~(FMARK|FDEFER); do { KASSERT(unp_defer >= 0, ("unp_gc: unp_defer %d", unp_defer)); LIST_FOREACH(fp, &filehead, f_list) { FILE_LOCK(fp); /* * If the file is not open, skip it -- could be a * file in the process of being opened, or in the * process of being closed. If the file is * "closing", it may have been marked for deferred * consideration. Clear the flag now if so. */ if (fp->f_count == 0) { if (fp->f_gcflag & FDEFER) unp_defer--; fp->f_gcflag &= ~(FMARK|FDEFER); FILE_UNLOCK(fp); continue; } /* * If we already marked it as 'defer' in a * previous pass, then try process it this time * and un-mark it */ if (fp->f_gcflag & FDEFER) { fp->f_gcflag &= ~FDEFER; unp_defer--; } else { /* * if it's not defered, then check if it's * already marked.. if so skip it */ if (fp->f_gcflag & FMARK) { FILE_UNLOCK(fp); continue; } /* * If all references are from messages * in transit, then skip it. it's not * externally accessible. */ if (fp->f_count == fp->f_msgcount) { FILE_UNLOCK(fp); continue; } /* * If it got this far then it must be * externally accessible. */ fp->f_gcflag |= FMARK; } /* * either it was defered, or it is externally * accessible and not already marked so. * Now check if it is possibly one of OUR sockets. */ if (fp->f_type != DTYPE_SOCKET || (so = fp->f_data) == NULL) { FILE_UNLOCK(fp); continue; } FILE_UNLOCK(fp); if (so->so_proto->pr_domain != &localdomain || (so->so_proto->pr_flags&PR_RIGHTS) == 0) continue; /* * So, Ok, it's one of our sockets and it IS externally * accessible (or was defered). Now we look * to see if we hold any file descriptors in its * message buffers. Follow those links and mark them * as accessible too. */ SOCKBUF_LOCK(&so->so_rcv); unp_scan(so->so_rcv.sb_mb, unp_mark); SOCKBUF_UNLOCK(&so->so_rcv); } } while (unp_defer); sx_sunlock(&filelist_lock); /* * XXXRW: The following comments need updating for a post-SMPng and * deferred unp_gc() world, but are still generally accurate. * * We grab an extra reference to each of the file table entries * that are not otherwise accessible and then free the rights * that are stored in messages on them. * * The bug in the orginal code is a little tricky, so I'll describe * what's wrong with it here. * * It is incorrect to simply unp_discard each entry for f_msgcount * times -- consider the case of sockets A and B that contain * references to each other. On a last close of some other socket, * we trigger a gc since the number of outstanding rights (unp_rights) * is non-zero. If during the sweep phase the gc code unp_discards, * we end up doing a (full) closef on the descriptor. A closef on A * results in the following chain. Closef calls soo_close, which * calls soclose. Soclose calls first (through the switch * uipc_usrreq) unp_detach, which re-invokes unp_gc. Unp_gc simply * returns because the previous instance had set unp_gcing, and * we return all the way back to soclose, which marks the socket * with SS_NOFDREF, and then calls sofree. Sofree calls sorflush * to free up the rights that are queued in messages on the socket A, * i.e., the reference on B. The sorflush calls via the dom_dispose * switch unp_dispose, which unp_scans with unp_discard. This second * instance of unp_discard just calls closef on B. * * Well, a similar chain occurs on B, resulting in a sorflush on B, * which results in another closef on A. Unfortunately, A is already * being closed, and the descriptor has already been marked with * SS_NOFDREF, and soclose panics at this point. * * Here, we first take an extra reference to each inaccessible * descriptor. Then, we call sorflush ourself, since we know * it is a Unix domain socket anyhow. After we destroy all the * rights carried in messages, we do a last closef to get rid * of our extra reference. This is the last close, and the * unp_detach etc will shut down the socket. * * 91/09/19, bsy@cs.cmu.edu */ again: nfiles_snap = openfiles + nfiles_slack; /* some slack */ extra_ref = malloc(nfiles_snap * sizeof(struct file *), M_TEMP, M_WAITOK); sx_slock(&filelist_lock); if (nfiles_snap < openfiles) { sx_sunlock(&filelist_lock); free(extra_ref, M_TEMP); nfiles_slack += 20; goto again; } for (nunref = 0, fp = LIST_FIRST(&filehead), fpp = extra_ref; fp != NULL; fp = nextfp) { nextfp = LIST_NEXT(fp, f_list); FILE_LOCK(fp); /* * If it's not open, skip it */ if (fp->f_count == 0) { FILE_UNLOCK(fp); continue; } /* * If all refs are from msgs, and it's not marked accessible * then it must be referenced from some unreachable cycle * of (shut-down) FDs, so include it in our * list of FDs to remove */ if (fp->f_count == fp->f_msgcount && !(fp->f_gcflag & FMARK)) { *fpp++ = fp; nunref++; fp->f_count++; } FILE_UNLOCK(fp); } sx_sunlock(&filelist_lock); /* * for each FD on our hit list, do the following two things */ for (i = nunref, fpp = extra_ref; --i >= 0; ++fpp) { struct file *tfp = *fpp; FILE_LOCK(tfp); if (tfp->f_type == DTYPE_SOCKET && tfp->f_data != NULL) { FILE_UNLOCK(tfp); sorflush(tfp->f_data); } else { FILE_UNLOCK(tfp); } } for (i = nunref, fpp = extra_ref; --i >= 0; ++fpp) { closef(*fpp, (struct thread *) NULL); unp_recycled++; } free(extra_ref, M_TEMP); } void unp_dispose(struct mbuf *m) { if (m) unp_scan(m, unp_discard); } static int unp_listen(struct socket *so, struct unpcb *unp, int backlog, struct thread *td) { int error; UNP_LOCK_ASSERT(); SOCK_LOCK(so); error = solisten_proto_check(so); if (error == 0) { cru2x(td->td_ucred, &unp->unp_peercred); unp->unp_flags |= UNP_HAVEPCCACHED; solisten_proto(so, backlog); } SOCK_UNLOCK(so); return (error); } static void unp_scan(struct mbuf *m0, void (*op)(struct file *)) { struct mbuf *m; struct file **rp; struct cmsghdr *cm; void *data; int i; socklen_t clen, datalen; int qfds; while (m0 != NULL) { for (m = m0; m; m = m->m_next) { if (m->m_type != MT_CONTROL) continue; cm = mtod(m, struct cmsghdr *); clen = m->m_len; while (cm != NULL) { if (sizeof(*cm) > clen || cm->cmsg_len > clen) break; data = CMSG_DATA(cm); datalen = (caddr_t)cm + cm->cmsg_len - (caddr_t)data; if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SCM_RIGHTS) { qfds = datalen / sizeof (struct file *); rp = data; for (i = 0; i < qfds; i++) (*op)(*rp++); } if (CMSG_SPACE(datalen) < clen) { clen -= CMSG_SPACE(datalen); cm = (struct cmsghdr *) ((caddr_t)cm + CMSG_SPACE(datalen)); } else { clen = 0; cm = NULL; } } } m0 = m0->m_act; } } static void unp_mark(struct file *fp) { if (fp->f_gcflag & FMARK) return; unp_defer++; fp->f_gcflag |= (FMARK|FDEFER); } static void unp_discard(struct file *fp) { UNP_LOCK(); FILE_LOCK(fp); fp->f_msgcount--; unp_rights--; FILE_UNLOCK(fp); UNP_UNLOCK(); (void) closef(fp, (struct thread *)NULL); } diff --git a/sys/net/raw_cb.c b/sys/net/raw_cb.c index 31566d1cb96f..f45be72cb766 100644 --- a/sys/net/raw_cb.c +++ b/sys/net/raw_cb.c @@ -1,151 +1,150 @@ /*- * Copyright (c) 1980, 1986, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)raw_cb.c 8.1 (Berkeley) 6/10/93 * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include /* * Routines to manage the raw protocol control blocks. * * TODO: * hash lookups by protocol family/protocol + address family * take care of unique address problems per AF? * redo address binding to allow wildcards */ struct mtx rawcb_mtx; struct rawcb_list_head rawcb_list; const static u_long raw_sendspace = RAWSNDQ; const static u_long raw_recvspace = RAWRCVQ; /* * Allocate a control block and a nominal amount * of buffer space for the socket. */ int raw_attach(so, proto) register struct socket *so; int proto; { register struct rawcb *rp = sotorawcb(so); int error; /* * It is assumed that raw_attach is called * after space has been allocated for the * rawcb. */ if (rp == 0) return (ENOBUFS); error = soreserve(so, raw_sendspace, raw_recvspace); if (error) return (error); rp->rcb_socket = so; rp->rcb_proto.sp_family = so->so_proto->pr_domain->dom_family; rp->rcb_proto.sp_protocol = proto; mtx_lock(&rawcb_mtx); LIST_INSERT_HEAD(&rawcb_list, rp, list); mtx_unlock(&rawcb_mtx); return (0); } /* * Detach the raw connection block and discard * socket resources. */ void raw_detach(rp) register struct rawcb *rp; { struct socket *so = rp->rcb_socket; - ACCEPT_LOCK(); - SOCK_LOCK(so); - so->so_pcb = 0; - sotryfree(so); + KASSERT(so->so_pcb == rp, ("raw_detach: so_pcb != rp")); + + so->so_pcb = NULL; mtx_lock(&rawcb_mtx); LIST_REMOVE(rp, list); mtx_unlock(&rawcb_mtx); #ifdef notdef if (rp->rcb_laddr) m_freem(dtom(rp->rcb_laddr)); rp->rcb_laddr = 0; #endif free((caddr_t)(rp), M_PCB); } /* * Disconnect and possibly release resources. */ void raw_disconnect(rp) struct rawcb *rp; { #ifdef notdef if (rp->rcb_faddr) m_freem(dtom(rp->rcb_faddr)); rp->rcb_faddr = 0; #endif if (rp->rcb_socket->so_state & SS_NOFDREF) raw_detach(rp); } #ifdef notdef #include int raw_bind(so, nam) register struct socket *so; struct mbuf *nam; { struct sockaddr *addr = mtod(nam, struct sockaddr *); register struct rawcb *rp; if (ifnet == 0) return (EADDRNOTAVAIL); rp = sotorawcb(so); nam = m_copym(nam, 0, M_COPYALL, M_TRYWAIT); rp->rcb_laddr = mtod(nam, struct sockaddr *); return (0); } #endif diff --git a/sys/net/raw_usrreq.c b/sys/net/raw_usrreq.c index c6f1a7d994a9..300dd917eac2 100644 --- a/sys/net/raw_usrreq.c +++ b/sys/net/raw_usrreq.c @@ -1,305 +1,304 @@ /*- * Copyright (c) 1980, 1986, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)raw_usrreq.c 8.1 (Berkeley) 6/10/93 * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include MTX_SYSINIT(rawcb_mtx, &rawcb_mtx, "rawcb", MTX_DEF); /* * Initialize raw connection block q. */ void raw_init() { LIST_INIT(&rawcb_list); } /* * Raw protocol input routine. Find the socket * associated with the packet(s) and move them over. If * nothing exists for this packet, drop it. */ /* * Raw protocol interface. */ void raw_input(m0, proto, src, dst) struct mbuf *m0; register struct sockproto *proto; struct sockaddr *src, *dst; { register struct rawcb *rp; register struct mbuf *m = m0; struct socket *last; last = 0; mtx_lock(&rawcb_mtx); LIST_FOREACH(rp, &rawcb_list, list) { if (rp->rcb_proto.sp_family != proto->sp_family) continue; if (rp->rcb_proto.sp_protocol && rp->rcb_proto.sp_protocol != proto->sp_protocol) continue; /* * We assume the lower level routines have * placed the address in a canonical format * suitable for a structure comparison. * * Note that if the lengths are not the same * the comparison will fail at the first byte. */ #define equal(a1, a2) \ (bcmp((caddr_t)(a1), (caddr_t)(a2), a1->sa_len) == 0) if (rp->rcb_laddr && !equal(rp->rcb_laddr, dst)) continue; if (rp->rcb_faddr && !equal(rp->rcb_faddr, src)) continue; if (last) { struct mbuf *n; n = m_copy(m, 0, (int)M_COPYALL); if (n) { if (sbappendaddr(&last->so_rcv, src, n, (struct mbuf *)0) == 0) /* should notify about lost packet */ m_freem(n); else { sorwakeup(last); } } } last = rp->rcb_socket; } if (last) { if (sbappendaddr(&last->so_rcv, src, m, (struct mbuf *)0) == 0) m_freem(m); else { sorwakeup(last); } } else m_freem(m); mtx_unlock(&rawcb_mtx); } /*ARGSUSED*/ void raw_ctlinput(cmd, arg, dummy) int cmd; struct sockaddr *arg; void *dummy; { if (cmd < 0 || cmd >= PRC_NCMDS) return; /* INCOMPLETE */ } static void raw_uabort(struct socket *so) { struct rawcb *rp = sotorawcb(so); KASSERT(rp != NULL, ("raw_uabort: rp == NULL")); raw_disconnect(rp); soisdisconnected(so); } /* pru_accept is EOPNOTSUPP */ static int raw_uattach(struct socket *so, int proto, struct thread *td) { struct rawcb *rp = sotorawcb(so); int error; if (rp == 0) return EINVAL; if (td && (error = suser(td)) != 0) return error; return raw_attach(so, proto); } static int raw_ubind(struct socket *so, struct sockaddr *nam, struct thread *td) { return EINVAL; } static int raw_uconnect(struct socket *so, struct sockaddr *nam, struct thread *td) { return EINVAL; } /* pru_connect2 is EOPNOTSUPP */ /* pru_control is EOPNOTSUPP */ -static int +static void raw_udetach(struct socket *so) { struct rawcb *rp = sotorawcb(so); if (rp == 0) - return EINVAL; + return; raw_detach(rp); - return 0; } static int raw_udisconnect(struct socket *so) { struct rawcb *rp = sotorawcb(so); if (rp == 0) return EINVAL; if (rp->rcb_faddr == 0) { return ENOTCONN; } raw_disconnect(rp); soisdisconnected(so); return 0; } /* pru_listen is EOPNOTSUPP */ static int raw_upeeraddr(struct socket *so, struct sockaddr **nam) { struct rawcb *rp = sotorawcb(so); if (rp == 0) return EINVAL; if (rp->rcb_faddr == 0) { return ENOTCONN; } *nam = sodupsockaddr(rp->rcb_faddr, M_WAITOK); return 0; } /* pru_rcvd is EOPNOTSUPP */ /* pru_rcvoob is EOPNOTSUPP */ static int raw_usend(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, struct mbuf *control, struct thread *td) { int error; struct rawcb *rp = sotorawcb(so); if (rp == 0) { error = EINVAL; goto release; } if (flags & PRUS_OOB) { error = EOPNOTSUPP; goto release; } if (control && control->m_len) { error = EOPNOTSUPP; goto release; } if (nam) { if (rp->rcb_faddr) { error = EISCONN; goto release; } rp->rcb_faddr = nam; } else if (rp->rcb_faddr == 0) { error = ENOTCONN; goto release; } error = (*so->so_proto->pr_output)(m, so); m = NULL; if (nam) rp->rcb_faddr = 0; release: if (m != NULL) m_freem(m); return (error); } /* pru_sense is null */ static int raw_ushutdown(struct socket *so) { struct rawcb *rp = sotorawcb(so); if (rp == 0) return EINVAL; socantsendmore(so); return 0; } static int raw_usockaddr(struct socket *so, struct sockaddr **nam) { struct rawcb *rp = sotorawcb(so); if (rp == 0) return EINVAL; if (rp->rcb_laddr == 0) return EINVAL; *nam = sodupsockaddr(rp->rcb_laddr, M_WAITOK); return 0; } struct pr_usrreqs raw_usrreqs = { .pru_abort = raw_uabort, .pru_attach = raw_uattach, .pru_bind = raw_ubind, .pru_connect = raw_uconnect, .pru_detach = raw_udetach, .pru_disconnect = raw_udisconnect, .pru_peeraddr = raw_upeeraddr, .pru_send = raw_usend, .pru_shutdown = raw_ushutdown, .pru_sockaddr = raw_usockaddr, }; diff --git a/sys/net/rtsock.c b/sys/net/rtsock.c index ad64ef0bd47b..701662d24146 100644 --- a/sys/net/rtsock.c +++ b/sys/net/rtsock.c @@ -1,1283 +1,1279 @@ /*- * Copyright (c) 1988, 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)rtsock.c 8.7 (Berkeley) 10/12/95 * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MALLOC_DEFINE(M_RTABLE, "routetbl", "routing tables"); /* NB: these are not modified */ static struct sockaddr route_dst = { 2, PF_ROUTE, }; static struct sockaddr route_src = { 2, PF_ROUTE, }; static struct sockaddr sa_zero = { sizeof(sa_zero), AF_INET, }; static struct { int ip_count; /* attached w/ AF_INET */ int ip6_count; /* attached w/ AF_INET6 */ int ipx_count; /* attached w/ AF_IPX */ int any_count; /* total attached */ } route_cb; struct mtx rtsock_mtx; MTX_SYSINIT(rtsock, &rtsock_mtx, "rtsock route_cb lock", MTX_DEF); #define RTSOCK_LOCK() mtx_lock(&rtsock_mtx) #define RTSOCK_UNLOCK() mtx_unlock(&rtsock_mtx) #define RTSOCK_LOCK_ASSERT() mtx_assert(&rtsock_mtx, MA_OWNED) static struct ifqueue rtsintrq; SYSCTL_NODE(_net, OID_AUTO, route, CTLFLAG_RD, 0, ""); SYSCTL_INT(_net_route, OID_AUTO, netisr_maxqlen, CTLFLAG_RW, &rtsintrq.ifq_maxlen, 0, "maximum routing socket dispatch queue length"); struct walkarg { int w_tmemsize; int w_op, w_arg; caddr_t w_tmem; struct sysctl_req *w_req; }; static void rts_input(struct mbuf *m); static struct mbuf *rt_msg1(int type, struct rt_addrinfo *rtinfo); static int rt_msg2(int type, struct rt_addrinfo *rtinfo, caddr_t cp, struct walkarg *w); static int rt_xaddrs(caddr_t cp, caddr_t cplim, struct rt_addrinfo *rtinfo); static int sysctl_dumpentry(struct radix_node *rn, void *vw); static int sysctl_iflist(int af, struct walkarg *w); static int sysctl_ifmalist(int af, struct walkarg *w); static int route_output(struct mbuf *m, struct socket *so); static void rt_setmetrics(u_long which, const struct rt_metrics *in, struct rt_metrics_lite *out); static void rt_getmetrics(const struct rt_metrics_lite *in, struct rt_metrics *out); static void rt_dispatch(struct mbuf *, const struct sockaddr *); static void rts_init(void) { int tmp; rtsintrq.ifq_maxlen = 256; if (TUNABLE_INT_FETCH("net.route.netisr_maxqlen", &tmp)) rtsintrq.ifq_maxlen = tmp; mtx_init(&rtsintrq.ifq_mtx, "rts_inq", NULL, MTX_DEF); netisr_register(NETISR_ROUTE, rts_input, &rtsintrq, NETISR_MPSAFE); } SYSINIT(rtsock, SI_SUB_PROTO_DOMAIN, SI_ORDER_THIRD, rts_init, 0) static void rts_input(struct mbuf *m) { struct sockproto route_proto; unsigned short *family; struct m_tag *tag; route_proto.sp_family = PF_ROUTE; tag = m_tag_find(m, PACKET_TAG_RTSOCKFAM, NULL); if (tag != NULL) { family = (unsigned short *)(tag + 1); route_proto.sp_protocol = *family; m_tag_delete(m, tag); } else route_proto.sp_protocol = 0; raw_input(m, &route_proto, &route_src, &route_dst); } /* * It really doesn't make any sense at all for this code to share much * with raw_usrreq.c, since its functionality is so restricted. XXX */ static void rts_abort(struct socket *so) { raw_usrreqs.pru_abort(so); } /* pru_accept is EOPNOTSUPP */ static int rts_attach(struct socket *so, int proto, struct thread *td) { struct rawcb *rp; int s, error; - if (sotorawcb(so) != NULL) - return EISCONN; /* XXX panic? */ + KASSERT(so->so_pcb == NULL, ("rts_attach: so_pcb != NULL")); + /* XXX */ MALLOC(rp, struct rawcb *, sizeof *rp, M_PCB, M_WAITOK | M_ZERO); if (rp == NULL) return ENOBUFS; /* * The splnet() is necessary to block protocols from sending * error notifications (like RTM_REDIRECT or RTM_LOSING) while * this PCB is extant but incompletely initialized. * Probably we should try to do more of this work beforehand and * eliminate the spl. */ s = splnet(); so->so_pcb = (caddr_t)rp; error = raw_attach(so, proto); rp = sotorawcb(so); if (error) { splx(s); so->so_pcb = NULL; free(rp, M_PCB); return error; } RTSOCK_LOCK(); switch(rp->rcb_proto.sp_protocol) { case AF_INET: route_cb.ip_count++; break; case AF_INET6: route_cb.ip6_count++; break; case AF_IPX: route_cb.ipx_count++; break; } rp->rcb_faddr = &route_src; route_cb.any_count++; RTSOCK_UNLOCK(); soisconnected(so); so->so_options |= SO_USELOOPBACK; splx(s); return 0; } static int rts_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { return (raw_usrreqs.pru_bind(so, nam, td)); /* xxx just EINVAL */ } static int rts_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { return (raw_usrreqs.pru_connect(so, nam, td)); /* XXX just EINVAL */ } /* pru_connect2 is EOPNOTSUPP */ /* pru_control is EOPNOTSUPP */ -static int +static void rts_detach(struct socket *so) { struct rawcb *rp = sotorawcb(so); - int s, error; - s = splnet(); - if (rp != NULL) { - RTSOCK_LOCK(); - switch(rp->rcb_proto.sp_protocol) { - case AF_INET: - route_cb.ip_count--; - break; - case AF_INET6: - route_cb.ip6_count--; - break; - case AF_IPX: - route_cb.ipx_count--; - break; - } - route_cb.any_count--; - RTSOCK_UNLOCK(); + KASSERT(rp != NULL, ("rts_detach: rp == NULL")); + + RTSOCK_LOCK(); + switch(rp->rcb_proto.sp_protocol) { + case AF_INET: + route_cb.ip_count--; + break; + case AF_INET6: + route_cb.ip6_count--; + break; + case AF_IPX: + route_cb.ipx_count--; + break; } - error = raw_usrreqs.pru_detach(so); - splx(s); - return error; + route_cb.any_count--; + RTSOCK_UNLOCK(); + raw_usrreqs.pru_detach(so); } static int rts_disconnect(struct socket *so) { return (raw_usrreqs.pru_disconnect(so)); } /* pru_listen is EOPNOTSUPP */ static int rts_peeraddr(struct socket *so, struct sockaddr **nam) { return (raw_usrreqs.pru_peeraddr(so, nam)); } /* pru_rcvd is EOPNOTSUPP */ /* pru_rcvoob is EOPNOTSUPP */ static int rts_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, struct mbuf *control, struct thread *td) { return (raw_usrreqs.pru_send(so, flags, m, nam, control, td)); } /* pru_sense is null */ static int rts_shutdown(struct socket *so) { return (raw_usrreqs.pru_shutdown(so)); } static int rts_sockaddr(struct socket *so, struct sockaddr **nam) { return (raw_usrreqs.pru_sockaddr(so, nam)); } static struct pr_usrreqs route_usrreqs = { .pru_abort = rts_abort, .pru_attach = rts_attach, .pru_bind = rts_bind, .pru_connect = rts_connect, .pru_detach = rts_detach, .pru_disconnect = rts_disconnect, .pru_peeraddr = rts_peeraddr, .pru_send = rts_send, .pru_shutdown = rts_shutdown, .pru_sockaddr = rts_sockaddr, }; /*ARGSUSED*/ static int route_output(struct mbuf *m, struct socket *so) { #define sa_equal(a1, a2) (bcmp((a1), (a2), (a1)->sa_len) == 0) struct rt_msghdr *rtm = NULL; struct rtentry *rt = NULL; struct radix_node_head *rnh; struct rt_addrinfo info; int len, error = 0; struct ifnet *ifp = NULL; struct ifaddr *ifa = NULL; struct sockaddr_in jail; #define senderr(e) { error = e; goto flush;} if (m == NULL || ((m->m_len < sizeof(long)) && (m = m_pullup(m, sizeof(long))) == NULL)) return (ENOBUFS); if ((m->m_flags & M_PKTHDR) == 0) panic("route_output"); len = m->m_pkthdr.len; if (len < sizeof(*rtm) || len != mtod(m, struct rt_msghdr *)->rtm_msglen) { info.rti_info[RTAX_DST] = NULL; senderr(EINVAL); } R_Malloc(rtm, struct rt_msghdr *, len); if (rtm == NULL) { info.rti_info[RTAX_DST] = NULL; senderr(ENOBUFS); } m_copydata(m, 0, len, (caddr_t)rtm); if (rtm->rtm_version != RTM_VERSION) { info.rti_info[RTAX_DST] = NULL; senderr(EPROTONOSUPPORT); } rtm->rtm_pid = curproc->p_pid; bzero(&info, sizeof(info)); info.rti_addrs = rtm->rtm_addrs; if (rt_xaddrs((caddr_t)(rtm + 1), len + (caddr_t)rtm, &info)) { info.rti_info[RTAX_DST] = NULL; senderr(EINVAL); } info.rti_flags = rtm->rtm_flags; if (info.rti_info[RTAX_DST] == NULL || info.rti_info[RTAX_DST]->sa_family >= AF_MAX || (info.rti_info[RTAX_GATEWAY] != NULL && info.rti_info[RTAX_GATEWAY]->sa_family >= AF_MAX)) senderr(EINVAL); if (info.rti_info[RTAX_GENMASK]) { struct radix_node *t; t = rn_addmask((caddr_t) info.rti_info[RTAX_GENMASK], 0, 1); if (t != NULL && bcmp((char *)(void *)info.rti_info[RTAX_GENMASK] + 1, (char *)(void *)t->rn_key + 1, ((struct sockaddr *)t->rn_key)->sa_len - 1) == 0) info.rti_info[RTAX_GENMASK] = (struct sockaddr *)t->rn_key; else senderr(ENOBUFS); } /* * Verify that the caller has the appropriate privilege; RTM_GET * is the only operation the non-superuser is allowed. */ if (rtm->rtm_type != RTM_GET && (error = suser(curthread)) != 0) senderr(error); switch (rtm->rtm_type) { struct rtentry *saved_nrt; case RTM_ADD: if (info.rti_info[RTAX_GATEWAY] == NULL) senderr(EINVAL); saved_nrt = NULL; error = rtrequest1(RTM_ADD, &info, &saved_nrt); if (error == 0 && saved_nrt) { RT_LOCK(saved_nrt); rt_setmetrics(rtm->rtm_inits, &rtm->rtm_rmx, &saved_nrt->rt_rmx); rtm->rtm_index = saved_nrt->rt_ifp->if_index; RT_REMREF(saved_nrt); saved_nrt->rt_genmask = info.rti_info[RTAX_GENMASK]; RT_UNLOCK(saved_nrt); } break; case RTM_DELETE: saved_nrt = NULL; error = rtrequest1(RTM_DELETE, &info, &saved_nrt); if (error == 0) { RT_LOCK(saved_nrt); rt = saved_nrt; goto report; } break; case RTM_GET: case RTM_CHANGE: case RTM_LOCK: rnh = rt_tables[info.rti_info[RTAX_DST]->sa_family]; if (rnh == NULL) senderr(EAFNOSUPPORT); RADIX_NODE_HEAD_LOCK(rnh); rt = (struct rtentry *) rnh->rnh_lookup(info.rti_info[RTAX_DST], info.rti_info[RTAX_NETMASK], rnh); if (rt == NULL) { /* XXX looks bogus */ RADIX_NODE_HEAD_UNLOCK(rnh); senderr(ESRCH); } RT_LOCK(rt); RT_ADDREF(rt); RADIX_NODE_HEAD_UNLOCK(rnh); /* * Fix for PR: 82974 * * RTM_CHANGE/LOCK need a perfect match, rn_lookup() * returns a perfect match in case a netmask is * specified. For host routes only a longest prefix * match is returned so it is necessary to compare the * existence of the netmask. If both have a netmask * rnh_lookup() did a perfect match and if none of them * have a netmask both are host routes which is also a * perfect match. */ if (rtm->rtm_type != RTM_GET && (!rt_mask(rt) != !info.rti_info[RTAX_NETMASK])) { RT_UNLOCK(rt); senderr(ESRCH); } switch(rtm->rtm_type) { case RTM_GET: report: RT_LOCK_ASSERT(rt); info.rti_info[RTAX_DST] = rt_key(rt); info.rti_info[RTAX_GATEWAY] = rt->rt_gateway; info.rti_info[RTAX_NETMASK] = rt_mask(rt); info.rti_info[RTAX_GENMASK] = rt->rt_genmask; if (rtm->rtm_addrs & (RTA_IFP | RTA_IFA)) { ifp = rt->rt_ifp; if (ifp) { info.rti_info[RTAX_IFP] = ifp->if_addr->ifa_addr; if (jailed(so->so_cred)) { bzero(&jail, sizeof(jail)); jail.sin_family = PF_INET; jail.sin_len = sizeof(jail); jail.sin_addr.s_addr = htonl(prison_getip(so->so_cred)); info.rti_info[RTAX_IFA] = (struct sockaddr *)&jail; } else info.rti_info[RTAX_IFA] = rt->rt_ifa->ifa_addr; if (ifp->if_flags & IFF_POINTOPOINT) info.rti_info[RTAX_BRD] = rt->rt_ifa->ifa_dstaddr; rtm->rtm_index = ifp->if_index; } else { info.rti_info[RTAX_IFP] = NULL; info.rti_info[RTAX_IFA] = NULL; } } else if ((ifp = rt->rt_ifp) != NULL) { rtm->rtm_index = ifp->if_index; } len = rt_msg2(rtm->rtm_type, &info, NULL, NULL); if (len > rtm->rtm_msglen) { struct rt_msghdr *new_rtm; R_Malloc(new_rtm, struct rt_msghdr *, len); if (new_rtm == NULL) { RT_UNLOCK(rt); senderr(ENOBUFS); } bcopy(rtm, new_rtm, rtm->rtm_msglen); Free(rtm); rtm = new_rtm; } (void)rt_msg2(rtm->rtm_type, &info, (caddr_t)rtm, NULL); rtm->rtm_flags = rt->rt_flags; rtm->rtm_use = 0; rt_getmetrics(&rt->rt_rmx, &rtm->rtm_rmx); rtm->rtm_addrs = info.rti_addrs; break; case RTM_CHANGE: /* * New gateway could require new ifaddr, ifp; * flags may also be different; ifp may be specified * by ll sockaddr when protocol address is ambiguous */ if (((rt->rt_flags & RTF_GATEWAY) && info.rti_info[RTAX_GATEWAY] != NULL) || info.rti_info[RTAX_IFP] != NULL || (info.rti_info[RTAX_IFA] != NULL && !sa_equal(info.rti_info[RTAX_IFA], rt->rt_ifa->ifa_addr))) { RT_UNLOCK(rt); if ((error = rt_getifa(&info)) != 0) senderr(error); RT_LOCK(rt); } if (info.rti_info[RTAX_GATEWAY] != NULL && (error = rt_setgate(rt, rt_key(rt), info.rti_info[RTAX_GATEWAY])) != 0) { RT_UNLOCK(rt); senderr(error); } if ((ifa = info.rti_ifa) != NULL) { struct ifaddr *oifa = rt->rt_ifa; if (oifa != ifa) { if (oifa) { if (oifa->ifa_rtrequest) oifa->ifa_rtrequest( RTM_DELETE, rt, &info); IFAFREE(oifa); } IFAREF(ifa); rt->rt_ifa = ifa; rt->rt_ifp = info.rti_ifp; } } /* Allow some flags to be toggled on change. */ if (rtm->rtm_fmask & RTF_FMASK) rt->rt_flags = (rt->rt_flags & ~rtm->rtm_fmask) | (rtm->rtm_flags & rtm->rtm_fmask); rt_setmetrics(rtm->rtm_inits, &rtm->rtm_rmx, &rt->rt_rmx); rtm->rtm_index = rt->rt_ifp->if_index; if (rt->rt_ifa && rt->rt_ifa->ifa_rtrequest) rt->rt_ifa->ifa_rtrequest(RTM_ADD, rt, &info); if (info.rti_info[RTAX_GENMASK]) rt->rt_genmask = info.rti_info[RTAX_GENMASK]; /* FALLTHROUGH */ case RTM_LOCK: /* We don't support locks anymore */ break; } RT_UNLOCK(rt); break; default: senderr(EOPNOTSUPP); } flush: if (rtm) { if (error) rtm->rtm_errno = error; else rtm->rtm_flags |= RTF_DONE; } if (rt) /* XXX can this be true? */ RTFREE(rt); { struct rawcb *rp = NULL; /* * Check to see if we don't want our own messages. */ if ((so->so_options & SO_USELOOPBACK) == 0) { if (route_cb.any_count <= 1) { if (rtm) Free(rtm); m_freem(m); return (error); } /* There is another listener, so construct message */ rp = sotorawcb(so); } if (rtm) { m_copyback(m, 0, rtm->rtm_msglen, (caddr_t)rtm); if (m->m_pkthdr.len < rtm->rtm_msglen) { m_freem(m); m = NULL; } else if (m->m_pkthdr.len > rtm->rtm_msglen) m_adj(m, rtm->rtm_msglen - m->m_pkthdr.len); Free(rtm); } if (m) { if (rp) { /* * XXX insure we don't get a copy by * invalidating our protocol */ unsigned short family = rp->rcb_proto.sp_family; rp->rcb_proto.sp_family = 0; rt_dispatch(m, info.rti_info[RTAX_DST]); rp->rcb_proto.sp_family = family; } else rt_dispatch(m, info.rti_info[RTAX_DST]); } } return (error); #undef sa_equal } static void rt_setmetrics(u_long which, const struct rt_metrics *in, struct rt_metrics_lite *out) { #define metric(f, e) if (which & (f)) out->e = in->e; /* * Only these are stored in the routing entry since introduction * of tcp hostcache. The rest is ignored. */ metric(RTV_MTU, rmx_mtu); metric(RTV_EXPIRE, rmx_expire); #undef metric } static void rt_getmetrics(const struct rt_metrics_lite *in, struct rt_metrics *out) { #define metric(e) out->e = in->e; bzero(out, sizeof(*out)); metric(rmx_mtu); metric(rmx_expire); #undef metric } /* * Extract the addresses of the passed sockaddrs. * Do a little sanity checking so as to avoid bad memory references. * This data is derived straight from userland. */ static int rt_xaddrs(caddr_t cp, caddr_t cplim, struct rt_addrinfo *rtinfo) { struct sockaddr *sa; int i; for (i = 0; i < RTAX_MAX && cp < cplim; i++) { if ((rtinfo->rti_addrs & (1 << i)) == 0) continue; sa = (struct sockaddr *)cp; /* * It won't fit. */ if (cp + sa->sa_len > cplim) return (EINVAL); /* * there are no more.. quit now * If there are more bits, they are in error. * I've seen this. route(1) can evidently generate these. * This causes kernel to core dump. * for compatibility, If we see this, point to a safe address. */ if (sa->sa_len == 0) { rtinfo->rti_info[i] = &sa_zero; return (0); /* should be EINVAL but for compat */ } /* accept it */ rtinfo->rti_info[i] = sa; cp += SA_SIZE(sa); } return (0); } static struct mbuf * rt_msg1(int type, struct rt_addrinfo *rtinfo) { struct rt_msghdr *rtm; struct mbuf *m; int i; struct sockaddr *sa; int len, dlen; switch (type) { case RTM_DELADDR: case RTM_NEWADDR: len = sizeof(struct ifa_msghdr); break; case RTM_DELMADDR: case RTM_NEWMADDR: len = sizeof(struct ifma_msghdr); break; case RTM_IFINFO: len = sizeof(struct if_msghdr); break; case RTM_IFANNOUNCE: case RTM_IEEE80211: len = sizeof(struct if_announcemsghdr); break; default: len = sizeof(struct rt_msghdr); } if (len > MCLBYTES) panic("rt_msg1"); m = m_gethdr(M_DONTWAIT, MT_DATA); if (m && len > MHLEN) { MCLGET(m, M_DONTWAIT); if ((m->m_flags & M_EXT) == 0) { m_free(m); m = NULL; } } if (m == NULL) return (m); m->m_pkthdr.len = m->m_len = len; m->m_pkthdr.rcvif = NULL; rtm = mtod(m, struct rt_msghdr *); bzero((caddr_t)rtm, len); for (i = 0; i < RTAX_MAX; i++) { if ((sa = rtinfo->rti_info[i]) == NULL) continue; rtinfo->rti_addrs |= (1 << i); dlen = SA_SIZE(sa); m_copyback(m, len, dlen, (caddr_t)sa); len += dlen; } if (m->m_pkthdr.len != len) { m_freem(m); return (NULL); } rtm->rtm_msglen = len; rtm->rtm_version = RTM_VERSION; rtm->rtm_type = type; return (m); } static int rt_msg2(int type, struct rt_addrinfo *rtinfo, caddr_t cp, struct walkarg *w) { int i; int len, dlen, second_time = 0; caddr_t cp0; rtinfo->rti_addrs = 0; again: switch (type) { case RTM_DELADDR: case RTM_NEWADDR: len = sizeof(struct ifa_msghdr); break; case RTM_IFINFO: len = sizeof(struct if_msghdr); break; case RTM_NEWMADDR: len = sizeof(struct ifma_msghdr); break; default: len = sizeof(struct rt_msghdr); } cp0 = cp; if (cp0) cp += len; for (i = 0; i < RTAX_MAX; i++) { struct sockaddr *sa; if ((sa = rtinfo->rti_info[i]) == NULL) continue; rtinfo->rti_addrs |= (1 << i); dlen = SA_SIZE(sa); if (cp) { bcopy((caddr_t)sa, cp, (unsigned)dlen); cp += dlen; } len += dlen; } len = ALIGN(len); if (cp == NULL && w != NULL && !second_time) { struct walkarg *rw = w; if (rw->w_req) { if (rw->w_tmemsize < len) { if (rw->w_tmem) free(rw->w_tmem, M_RTABLE); rw->w_tmem = (caddr_t) malloc(len, M_RTABLE, M_NOWAIT); if (rw->w_tmem) rw->w_tmemsize = len; } if (rw->w_tmem) { cp = rw->w_tmem; second_time = 1; goto again; } } } if (cp) { struct rt_msghdr *rtm = (struct rt_msghdr *)cp0; rtm->rtm_version = RTM_VERSION; rtm->rtm_type = type; rtm->rtm_msglen = len; } return (len); } /* * This routine is called to generate a message from the routing * socket indicating that a redirect has occured, a routing lookup * has failed, or that a protocol has detected timeouts to a particular * destination. */ void rt_missmsg(int type, struct rt_addrinfo *rtinfo, int flags, int error) { struct rt_msghdr *rtm; struct mbuf *m; struct sockaddr *sa = rtinfo->rti_info[RTAX_DST]; if (route_cb.any_count == 0) return; m = rt_msg1(type, rtinfo); if (m == NULL) return; rtm = mtod(m, struct rt_msghdr *); rtm->rtm_flags = RTF_DONE | flags; rtm->rtm_errno = error; rtm->rtm_addrs = rtinfo->rti_addrs; rt_dispatch(m, sa); } /* * This routine is called to generate a message from the routing * socket indicating that the status of a network interface has changed. */ void rt_ifmsg(struct ifnet *ifp) { struct if_msghdr *ifm; struct mbuf *m; struct rt_addrinfo info; if (route_cb.any_count == 0) return; bzero((caddr_t)&info, sizeof(info)); m = rt_msg1(RTM_IFINFO, &info); if (m == NULL) return; ifm = mtod(m, struct if_msghdr *); ifm->ifm_index = ifp->if_index; ifm->ifm_flags = ifp->if_flags | ifp->if_drv_flags; ifm->ifm_data = ifp->if_data; ifm->ifm_addrs = 0; rt_dispatch(m, NULL); } /* * This is called to generate messages from the routing socket * indicating a network interface has had addresses associated with it. * if we ever reverse the logic and replace messages TO the routing * socket indicate a request to configure interfaces, then it will * be unnecessary as the routing socket will automatically generate * copies of it. */ void rt_newaddrmsg(int cmd, struct ifaddr *ifa, int error, struct rtentry *rt) { struct rt_addrinfo info; struct sockaddr *sa = NULL; int pass; struct mbuf *m = NULL; struct ifnet *ifp = ifa->ifa_ifp; KASSERT(cmd == RTM_ADD || cmd == RTM_DELETE, ("unexpected cmd %u", cmd)); if (route_cb.any_count == 0) return; for (pass = 1; pass < 3; pass++) { bzero((caddr_t)&info, sizeof(info)); if ((cmd == RTM_ADD && pass == 1) || (cmd == RTM_DELETE && pass == 2)) { struct ifa_msghdr *ifam; int ncmd = cmd == RTM_ADD ? RTM_NEWADDR : RTM_DELADDR; info.rti_info[RTAX_IFA] = sa = ifa->ifa_addr; info.rti_info[RTAX_IFP] = ifp->if_addr->ifa_addr; info.rti_info[RTAX_NETMASK] = ifa->ifa_netmask; info.rti_info[RTAX_BRD] = ifa->ifa_dstaddr; if ((m = rt_msg1(ncmd, &info)) == NULL) continue; ifam = mtod(m, struct ifa_msghdr *); ifam->ifam_index = ifp->if_index; ifam->ifam_metric = ifa->ifa_metric; ifam->ifam_flags = ifa->ifa_flags; ifam->ifam_addrs = info.rti_addrs; } if ((cmd == RTM_ADD && pass == 2) || (cmd == RTM_DELETE && pass == 1)) { struct rt_msghdr *rtm; if (rt == NULL) continue; info.rti_info[RTAX_NETMASK] = rt_mask(rt); info.rti_info[RTAX_DST] = sa = rt_key(rt); info.rti_info[RTAX_GATEWAY] = rt->rt_gateway; if ((m = rt_msg1(cmd, &info)) == NULL) continue; rtm = mtod(m, struct rt_msghdr *); rtm->rtm_index = ifp->if_index; rtm->rtm_flags |= rt->rt_flags; rtm->rtm_errno = error; rtm->rtm_addrs = info.rti_addrs; } rt_dispatch(m, sa); } } /* * This is the analogue to the rt_newaddrmsg which performs the same * function but for multicast group memberhips. This is easier since * there is no route state to worry about. */ void rt_newmaddrmsg(int cmd, struct ifmultiaddr *ifma) { struct rt_addrinfo info; struct mbuf *m = NULL; struct ifnet *ifp = ifma->ifma_ifp; struct ifma_msghdr *ifmam; if (route_cb.any_count == 0) return; bzero((caddr_t)&info, sizeof(info)); info.rti_info[RTAX_IFA] = ifma->ifma_addr; info.rti_info[RTAX_IFP] = ifp ? ifp->if_addr->ifa_addr : NULL; /* * If a link-layer address is present, present it as a ``gateway'' * (similarly to how ARP entries, e.g., are presented). */ info.rti_info[RTAX_GATEWAY] = ifma->ifma_lladdr; m = rt_msg1(cmd, &info); if (m == NULL) return; ifmam = mtod(m, struct ifma_msghdr *); ifmam->ifmam_index = ifp->if_index; ifmam->ifmam_addrs = info.rti_addrs; rt_dispatch(m, ifma->ifma_addr); } static struct mbuf * rt_makeifannouncemsg(struct ifnet *ifp, int type, int what, struct rt_addrinfo *info) { struct if_announcemsghdr *ifan; struct mbuf *m; if (route_cb.any_count == 0) return NULL; bzero((caddr_t)info, sizeof(*info)); m = rt_msg1(type, info); if (m != NULL) { ifan = mtod(m, struct if_announcemsghdr *); ifan->ifan_index = ifp->if_index; strlcpy(ifan->ifan_name, ifp->if_xname, sizeof(ifan->ifan_name)); ifan->ifan_what = what; } return m; } /* * This is called to generate routing socket messages indicating * IEEE80211 wireless events. * XXX we piggyback on the RTM_IFANNOUNCE msg format in a clumsy way. */ void rt_ieee80211msg(struct ifnet *ifp, int what, void *data, size_t data_len) { struct mbuf *m; struct rt_addrinfo info; m = rt_makeifannouncemsg(ifp, RTM_IEEE80211, what, &info); if (m != NULL) { /* * Append the ieee80211 data. Try to stick it in the * mbuf containing the ifannounce msg; otherwise allocate * a new mbuf and append. * * NB: we assume m is a single mbuf. */ if (data_len > M_TRAILINGSPACE(m)) { struct mbuf *n = m_get(M_NOWAIT, MT_DATA); if (n == NULL) { m_freem(m); return; } bcopy(data, mtod(n, void *), data_len); n->m_len = data_len; m->m_next = n; } else if (data_len > 0) { bcopy(data, mtod(m, u_int8_t *) + m->m_len, data_len); m->m_len += data_len; } if (m->m_flags & M_PKTHDR) m->m_pkthdr.len += data_len; mtod(m, struct if_announcemsghdr *)->ifan_msglen += data_len; rt_dispatch(m, NULL); } } /* * This is called to generate routing socket messages indicating * network interface arrival and departure. */ void rt_ifannouncemsg(struct ifnet *ifp, int what) { struct mbuf *m; struct rt_addrinfo info; m = rt_makeifannouncemsg(ifp, RTM_IFANNOUNCE, what, &info); if (m != NULL) rt_dispatch(m, NULL); } static void rt_dispatch(struct mbuf *m, const struct sockaddr *sa) { struct m_tag *tag; /* * Preserve the family from the sockaddr, if any, in an m_tag for * use when injecting the mbuf into the routing socket buffer from * the netisr. */ if (sa != NULL) { tag = m_tag_get(PACKET_TAG_RTSOCKFAM, sizeof(unsigned short), M_NOWAIT); if (tag == NULL) { m_freem(m); return; } *(unsigned short *)(tag + 1) = sa->sa_family; m_tag_prepend(m, tag); } netisr_queue(NETISR_ROUTE, m); /* mbuf is free'd on failure. */ } /* * This is used in dumping the kernel table via sysctl(). */ static int sysctl_dumpentry(struct radix_node *rn, void *vw) { struct walkarg *w = vw; struct rtentry *rt = (struct rtentry *)rn; int error = 0, size; struct rt_addrinfo info; if (w->w_op == NET_RT_FLAGS && !(rt->rt_flags & w->w_arg)) return 0; bzero((caddr_t)&info, sizeof(info)); info.rti_info[RTAX_DST] = rt_key(rt); info.rti_info[RTAX_GATEWAY] = rt->rt_gateway; info.rti_info[RTAX_NETMASK] = rt_mask(rt); info.rti_info[RTAX_GENMASK] = rt->rt_genmask; if (rt->rt_ifp) { info.rti_info[RTAX_IFP] = rt->rt_ifp->if_addr->ifa_addr; info.rti_info[RTAX_IFA] = rt->rt_ifa->ifa_addr; if (rt->rt_ifp->if_flags & IFF_POINTOPOINT) info.rti_info[RTAX_BRD] = rt->rt_ifa->ifa_dstaddr; } size = rt_msg2(RTM_GET, &info, NULL, w); if (w->w_req && w->w_tmem) { struct rt_msghdr *rtm = (struct rt_msghdr *)w->w_tmem; rtm->rtm_flags = rt->rt_flags; rtm->rtm_use = rt->rt_rmx.rmx_pksent; rt_getmetrics(&rt->rt_rmx, &rtm->rtm_rmx); rtm->rtm_index = rt->rt_ifp->if_index; rtm->rtm_errno = rtm->rtm_pid = rtm->rtm_seq = 0; rtm->rtm_addrs = info.rti_addrs; error = SYSCTL_OUT(w->w_req, (caddr_t)rtm, size); return (error); } return (error); } static int sysctl_iflist(int af, struct walkarg *w) { struct ifnet *ifp; struct ifaddr *ifa; struct rt_addrinfo info; int len, error = 0; bzero((caddr_t)&info, sizeof(info)); IFNET_RLOCK(); TAILQ_FOREACH(ifp, &ifnet, if_link) { if (w->w_arg && w->w_arg != ifp->if_index) continue; ifa = ifp->if_addr; info.rti_info[RTAX_IFP] = ifa->ifa_addr; len = rt_msg2(RTM_IFINFO, &info, NULL, w); info.rti_info[RTAX_IFP] = NULL; if (w->w_req && w->w_tmem) { struct if_msghdr *ifm; ifm = (struct if_msghdr *)w->w_tmem; ifm->ifm_index = ifp->if_index; ifm->ifm_flags = ifp->if_flags | ifp->if_drv_flags; ifm->ifm_data = ifp->if_data; ifm->ifm_addrs = info.rti_addrs; error = SYSCTL_OUT(w->w_req,(caddr_t)ifm, len); if (error) goto done; } while ((ifa = TAILQ_NEXT(ifa, ifa_link)) != NULL) { if (af && af != ifa->ifa_addr->sa_family) continue; if (jailed(curthread->td_ucred) && prison_if(curthread->td_ucred, ifa->ifa_addr)) continue; info.rti_info[RTAX_IFA] = ifa->ifa_addr; info.rti_info[RTAX_NETMASK] = ifa->ifa_netmask; info.rti_info[RTAX_BRD] = ifa->ifa_dstaddr; len = rt_msg2(RTM_NEWADDR, &info, NULL, w); if (w->w_req && w->w_tmem) { struct ifa_msghdr *ifam; ifam = (struct ifa_msghdr *)w->w_tmem; ifam->ifam_index = ifa->ifa_ifp->if_index; ifam->ifam_flags = ifa->ifa_flags; ifam->ifam_metric = ifa->ifa_metric; ifam->ifam_addrs = info.rti_addrs; error = SYSCTL_OUT(w->w_req, w->w_tmem, len); if (error) goto done; } } info.rti_info[RTAX_IFA] = info.rti_info[RTAX_NETMASK] = info.rti_info[RTAX_BRD] = NULL; } done: IFNET_RUNLOCK(); return (error); } int sysctl_ifmalist(int af, struct walkarg *w) { struct ifnet *ifp; struct ifmultiaddr *ifma; struct rt_addrinfo info; int len, error = 0; struct ifaddr *ifa; bzero((caddr_t)&info, sizeof(info)); IFNET_RLOCK(); TAILQ_FOREACH(ifp, &ifnet, if_link) { if (w->w_arg && w->w_arg != ifp->if_index) continue; ifa = ifp->if_addr; info.rti_info[RTAX_IFP] = ifa ? ifa->ifa_addr : NULL; IF_ADDR_LOCK(ifp); TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { if (af && af != ifma->ifma_addr->sa_family) continue; if (jailed(curproc->p_ucred) && prison_if(curproc->p_ucred, ifma->ifma_addr)) continue; info.rti_info[RTAX_IFA] = ifma->ifma_addr; info.rti_info[RTAX_GATEWAY] = (ifma->ifma_addr->sa_family != AF_LINK) ? ifma->ifma_lladdr : NULL; len = rt_msg2(RTM_NEWMADDR, &info, NULL, w); if (w->w_req && w->w_tmem) { struct ifma_msghdr *ifmam; ifmam = (struct ifma_msghdr *)w->w_tmem; ifmam->ifmam_index = ifma->ifma_ifp->if_index; ifmam->ifmam_flags = 0; ifmam->ifmam_addrs = info.rti_addrs; error = SYSCTL_OUT(w->w_req, w->w_tmem, len); if (error) { IF_ADDR_UNLOCK(ifp); goto done; } } } IF_ADDR_UNLOCK(ifp); } done: IFNET_RUNLOCK(); return (error); } static int sysctl_rtsock(SYSCTL_HANDLER_ARGS) { int *name = (int *)arg1; u_int namelen = arg2; struct radix_node_head *rnh; int i, lim, error = EINVAL; u_char af; struct walkarg w; name ++; namelen--; if (req->newptr) return (EPERM); if (namelen != 3) return ((namelen < 3) ? EISDIR : ENOTDIR); af = name[0]; if (af > AF_MAX) return (EINVAL); bzero(&w, sizeof(w)); w.w_op = name[1]; w.w_arg = name[2]; w.w_req = req; error = sysctl_wire_old_buffer(req, 0); if (error) return (error); switch (w.w_op) { case NET_RT_DUMP: case NET_RT_FLAGS: if (af == 0) { /* dump all tables */ i = 1; lim = AF_MAX; } else /* dump only one table */ i = lim = af; for (error = 0; error == 0 && i <= lim; i++) if ((rnh = rt_tables[i]) != NULL) { RADIX_NODE_HEAD_LOCK(rnh); error = rnh->rnh_walktree(rnh, sysctl_dumpentry, &w); RADIX_NODE_HEAD_UNLOCK(rnh); } else if (af != 0) error = EAFNOSUPPORT; break; case NET_RT_IFLIST: error = sysctl_iflist(af, &w); break; case NET_RT_IFMALIST: error = sysctl_ifmalist(af, &w); break; } if (w.w_tmem) free(w.w_tmem, M_RTABLE); return (error); } SYSCTL_NODE(_net, PF_ROUTE, routetable, CTLFLAG_RD, sysctl_rtsock, ""); /* * Definitions of protocols supported in the ROUTE domain. */ static struct domain routedomain; /* or at least forward */ static struct protosw routesw[] = { { .pr_type = SOCK_RAW, .pr_domain = &routedomain, .pr_flags = PR_ATOMIC|PR_ADDR, .pr_output = route_output, .pr_ctlinput = raw_ctlinput, .pr_init = raw_init, .pr_usrreqs = &route_usrreqs } }; static struct domain routedomain = { .dom_family = PF_ROUTE, .dom_name = "route", .dom_protosw = routesw, .dom_protoswNPROTOSW = &routesw[sizeof(routesw)/sizeof(routesw[0])] }; DOMAIN_SET(route); diff --git a/sys/netatalk/ddp_usrreq.c b/sys/netatalk/ddp_usrreq.c index a652ca264321..00815c823131 100644 --- a/sys/netatalk/ddp_usrreq.c +++ b/sys/netatalk/ddp_usrreq.c @@ -1,276 +1,275 @@ /*- * Copyright (c) 2004-2005 Robert N. M. Watson * Copyright (c) 1990,1994 Regents of The University of Michigan. * All Rights Reserved. * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby granted, * provided that the above copyright notice appears in all copies and * that both that copyright notice and this permission notice appear * in supporting documentation, and that the name of The University * of Michigan not be used in advertising or publicity pertaining to * distribution of the software without specific, written prior * permission. This software is supplied as is without expressed or * implied warranties of any kind. * * This product includes software developed by the University of * California, Berkeley and its contributors. * * Research Systems Unix Group * The University of Michigan * c/o Wesley Craig * 535 W. William Street * Ann Arbor, Michigan * +1-313-764-2278 * netatalk@umich.edu * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static u_long ddp_sendspace = DDP_MAXSZ; /* Max ddp size + 1 (ddp_type) */ static u_long ddp_recvspace = 10 * (587 + sizeof(struct sockaddr_at)); static struct ifqueue atintrq1, atintrq2, aarpintrq; static int ddp_attach(struct socket *so, int proto, struct thread *td) { struct ddpcb *ddp; int error = 0; ddp = sotoddpcb(so); KASSERT(ddp == NULL, ("ddp_attach: ddp != NULL")); /* * Allocate socket buffer space first so that it's present * before first use. */ error = soreserve(so, ddp_sendspace, ddp_recvspace); if (error) return (error); DDP_LIST_XLOCK(); error = at_pcballoc(so); DDP_LIST_XUNLOCK(); return (error); } -static int +static void ddp_detach(struct socket *so) { struct ddpcb *ddp; ddp = sotoddpcb(so); KASSERT(ddp != NULL, ("ddp_detach: ddp == NULL")); DDP_LIST_XLOCK(); DDP_LOCK(ddp); at_pcbdetach(so, ddp); DDP_LIST_XUNLOCK(); - return (0); } static int ddp_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { struct ddpcb *ddp; int error = 0; ddp = sotoddpcb(so); KASSERT(ddp != NULL, ("ddp_bind: ddp == NULL")); DDP_LIST_XLOCK(); DDP_LOCK(ddp); error = at_pcbsetaddr(ddp, nam, td); DDP_UNLOCK(ddp); DDP_LIST_XUNLOCK(); return (error); } static int ddp_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { struct ddpcb *ddp; int error = 0; ddp = sotoddpcb(so); KASSERT(ddp != NULL, ("ddp_connect: ddp == NULL")); DDP_LIST_XLOCK(); DDP_LOCK(ddp); if (ddp->ddp_fsat.sat_port != ATADDR_ANYPORT) { DDP_UNLOCK(ddp); DDP_LIST_XUNLOCK(); return (EISCONN); } error = at_pcbconnect( ddp, nam, td ); DDP_UNLOCK(ddp); DDP_LIST_XUNLOCK(); if (error == 0) soisconnected(so); return (error); } static int ddp_disconnect(struct socket *so) { struct ddpcb *ddp; ddp = sotoddpcb(so); KASSERT(ddp != NULL, ("ddp_disconnect: ddp == NULL")); DDP_LOCK(ddp); if (ddp->ddp_fsat.sat_addr.s_node == ATADDR_ANYNODE) { DDP_UNLOCK(ddp); return (ENOTCONN); } at_pcbdisconnect(ddp); ddp->ddp_fsat.sat_addr.s_node = ATADDR_ANYNODE; DDP_UNLOCK(ddp); soisdisconnected(so); return (0); } static int ddp_shutdown(struct socket *so) { struct ddpcb *ddp; ddp = sotoddpcb(so); KASSERT(ddp != NULL, ("ddp_shutdown: ddp == NULL")); socantsendmore(so); return (0); } static int ddp_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *control, struct thread *td) { struct ddpcb *ddp; int error = 0; ddp = sotoddpcb(so); KASSERT(ddp != NULL, ("ddp_send: ddp == NULL")); if (control && control->m_len) { return (EINVAL); } if (addr != NULL) { DDP_LIST_XLOCK(); DDP_LOCK(ddp); if (ddp->ddp_fsat.sat_port != ATADDR_ANYPORT) { error = EISCONN; goto out; } error = at_pcbconnect(ddp, addr, td); if (error == 0) { error = ddp_output(m, so); at_pcbdisconnect(ddp); } out: DDP_UNLOCK(ddp); DDP_LIST_XUNLOCK(); } else { DDP_LOCK(ddp); if (ddp->ddp_fsat.sat_port == ATADDR_ANYPORT) error = ENOTCONN; else error = ddp_output(m, so); DDP_UNLOCK(ddp); } return (error); } static void ddp_abort(struct socket *so) { struct ddpcb *ddp; ddp = sotoddpcb(so); KASSERT(ddp != NULL, ("ddp_abort: ddp == NULL")); DDP_LIST_XLOCK(); DDP_LOCK(ddp); at_pcbdetach(so, ddp); DDP_LIST_XUNLOCK(); } void ddp_init(void) { atintrq1.ifq_maxlen = IFQ_MAXLEN; atintrq2.ifq_maxlen = IFQ_MAXLEN; aarpintrq.ifq_maxlen = IFQ_MAXLEN; mtx_init(&atintrq1.ifq_mtx, "at1_inq", NULL, MTX_DEF); mtx_init(&atintrq2.ifq_mtx, "at2_inq", NULL, MTX_DEF); mtx_init(&aarpintrq.ifq_mtx, "aarp_inq", NULL, MTX_DEF); DDP_LIST_LOCK_INIT(); netisr_register(NETISR_ATALK1, at1intr, &atintrq1, NETISR_MPSAFE); netisr_register(NETISR_ATALK2, at2intr, &atintrq2, NETISR_MPSAFE); netisr_register(NETISR_AARP, aarpintr, &aarpintrq, NETISR_MPSAFE); } #if 0 static void ddp_clean(void) { struct ddpcb *ddp; for (ddp = ddpcb_list; ddp != NULL; ddp = ddp->ddp_next) { at_pcbdetach(ddp->ddp_socket, ddp); } DDP_LIST_LOCK_DESTROY(); } #endif static int at_setpeeraddr(struct socket *so, struct sockaddr **nam) { return (EOPNOTSUPP); } static int at_setsockaddr(struct socket *so, struct sockaddr **nam) { struct ddpcb *ddp; ddp = sotoddpcb(so); KASSERT(ddp != NULL, ("at_setsockaddr: ddp == NULL")); DDP_LOCK(ddp); at_sockaddr(ddp, nam); DDP_UNLOCK(ddp); return (0); } struct pr_usrreqs ddp_usrreqs = { .pru_abort = ddp_abort, .pru_attach = ddp_attach, .pru_bind = ddp_bind, .pru_connect = ddp_connect, .pru_control = at_control, .pru_detach = ddp_detach, .pru_disconnect = ddp_disconnect, .pru_peeraddr = at_setpeeraddr, .pru_send = ddp_send, .pru_shutdown = ddp_shutdown, .pru_sockaddr = at_setsockaddr, }; diff --git a/sys/netatm/atm_aal5.c b/sys/netatm/atm_aal5.c index 2a5e1e303b63..f21ad7980b56 100644 --- a/sys/netatm/atm_aal5.c +++ b/sys/netatm/atm_aal5.c @@ -1,928 +1,928 @@ /*- * =================================== * HARP | Host ATM Research Platform * =================================== * * * This Host ATM Research Platform ("HARP") file (the "Software") is * made available by Network Computing Services, Inc. ("NetworkCS") * "AS IS". NetworkCS does not provide maintenance, improvements or * support of any kind. * * NETWORKCS MAKES NO WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED, * INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE, AS TO ANY ELEMENT OF THE * SOFTWARE OR ANY SUPPORT PROVIDED IN CONNECTION WITH THIS SOFTWARE. * In no event shall NetworkCS be responsible for any damages, including * but not limited to consequential damages, arising from or relating to * any use of the Software or related support. * * Copyright 1994-1998 Network Computing Services, Inc. * * Copies of this Software may be made, however, the above copyright * notice must be reproduced on all copies. */ /* * Core ATM Services * ----------------- * * ATM AAL5 socket protocol processing */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Global variables */ u_long atm_aal5_sendspace = 64 * 1024; /* XXX */ u_long atm_aal5_recvspace = 64 * 1024; /* XXX */ /* * Local functions */ static int atm_aal5_attach(struct socket *, int, struct thread *td); -static int atm_aal5_detach(struct socket *); +static void atm_aal5_detach(struct socket *); static int atm_aal5_bind(struct socket *, struct sockaddr *, struct thread *td); static int atm_aal5_listen(struct socket *, int backlog, struct thread *td); static int atm_aal5_connect(struct socket *, struct sockaddr *, struct thread *td); static int atm_aal5_accept(struct socket *, struct sockaddr **); static int atm_aal5_disconnect(struct socket *); static int atm_aal5_shutdown(struct socket *); static int atm_aal5_send(struct socket *, int, KBuffer *, struct sockaddr *, KBuffer *, struct thread *td); static void atm_aal5_abort(struct socket *); static int atm_aal5_control(struct socket *, u_long, caddr_t, struct ifnet *, struct thread *td); static int atm_aal5_sense(struct socket *, struct stat *); static int atm_aal5_sockaddr(struct socket *, struct sockaddr **); static int atm_aal5_peeraddr(struct socket *, struct sockaddr **); static int atm_aal5_incoming(void *, Atm_connection *, Atm_attributes *, void **); static void atm_aal5_cpcs_data(void *, KBuffer *); static caddr_t atm_aal5_getname(void *); /* * New-style socket request routines */ struct pr_usrreqs atm_aal5_usrreqs = { .pru_abort = atm_aal5_abort, .pru_accept = atm_aal5_accept, .pru_attach = atm_aal5_attach, .pru_bind = atm_aal5_bind, .pru_connect = atm_aal5_connect, .pru_control = atm_aal5_control, .pru_detach = atm_aal5_detach, .pru_disconnect = atm_aal5_disconnect, .pru_listen = atm_aal5_listen, .pru_peeraddr = atm_aal5_peeraddr, .pru_send = atm_aal5_send, .pru_sense = atm_aal5_sense, .pru_shutdown = atm_aal5_shutdown, .pru_sockaddr = atm_aal5_sockaddr, }; /* * Local variables */ static Atm_endpoint atm_aal5_endpt = { NULL, ENDPT_SOCK_AAL5, NULL, atm_aal5_getname, atm_sock_connected, atm_sock_cleared, atm_aal5_incoming, NULL, NULL, NULL, atm_aal5_cpcs_data, NULL, NULL, NULL, NULL }; static Atm_attributes atm_aal5_defattr = { NULL, /* nif */ CMAPI_CPCS, /* api */ 0, /* api_init */ 0, /* headin */ 0, /* headout */ { /* aal */ T_ATM_PRESENT, ATM_AAL5 }, { /* traffic */ T_ATM_ABSENT, }, { /* bearer */ T_ATM_ABSENT, }, { /* bhli */ T_ATM_ABSENT }, { /* blli */ T_ATM_ABSENT, T_ATM_ABSENT, }, { /* llc */ T_ATM_ABSENT, }, { /* called */ T_ATM_ABSENT, { T_ATM_ABSENT, 0 }, { T_ATM_ABSENT, 0 } }, { /* calling */ T_ATM_ABSENT }, { /* qos */ T_ATM_ABSENT, }, { /* transit */ T_ATM_ABSENT }, { /* cause */ T_ATM_ABSENT } }; /* * Handy common code macros */ #ifdef DIAGNOSTIC #define ATM_INTRO(f) \ int s, err = 0; \ s = splnet(); \ ATM_DEBUG2("aal5 socket %s (%p)\n", f, so); \ /* \ * Stack queue should have been drained \ */ \ if (atm_stackq_head != NULL) \ panic("atm_aal5: stack queue not empty"); \ ; #else /* !DIAGNOSTIC */ #define ATM_INTRO(f) \ int s, err = 0; \ s = splnet(); \ ; #endif /* DIAGNOSTIC */ #define ATM_INTRO_NOERR(f) \ int s; \ s = splnet(); \ ; #define ATM_OUTRO() \ /* \ * Drain any deferred calls \ */ \ STACK_DRAIN(); \ (void) splx(s); \ return (err); \ ; #define ATM_OUTRO_NOERR() \ /* \ * Drain any deferred calls \ */ \ STACK_DRAIN(); \ (void) splx(s); \ ; #define ATM_RETERR(errno) { \ err = errno; \ goto out; \ } /* * Attach protocol to socket * * Arguments: * so pointer to socket * proto protocol identifier * p pointer to process * * Returns: * 0 request processed * errno error processing request - reason indicated * */ static int atm_aal5_attach(so, proto, td) struct socket *so; int proto; struct thread *td; { Atm_pcb *atp; ATM_INTRO("attach"); /* * Do general attach stuff */ err = atm_sock_attach(so, atm_aal5_sendspace, atm_aal5_recvspace); if (err) ATM_RETERR(err); /* * Finish up any protocol specific stuff */ atp = sotoatmpcb(so); atp->atp_type = ATPT_AAL5; /* * Set default connection attributes */ atp->atp_attr = atm_aal5_defattr; strncpy(atp->atp_name, "(AAL5)", T_ATM_APP_NAME_LEN); out: ATM_OUTRO(); } /* * Detach protocol from socket * * Arguments: * so pointer to socket * * Returns: * 0 request processed * errno error processing request - reason indicated * */ -static int +static void atm_aal5_detach(so) struct socket *so; { - ATM_INTRO("detach"); + ATM_INTRO_NOERR("detach"); - err = atm_sock_detach(so); + atm_sock_detach(so); - ATM_OUTRO(); + ATM_OUTRO_NOERR(); } /* * Bind address to socket * * Arguments: * so pointer to socket * addr pointer to protocol address * p pointer to process * * Returns: * 0 request processed * errno error processing request - reason indicated * */ static int atm_aal5_bind(so, addr, td) struct socket *so; struct sockaddr *addr; struct thread *td; { ATM_INTRO("bind"); err = atm_sock_bind(so, addr); ATM_OUTRO(); } /* * Listen for incoming connections * * Arguments: * so pointer to socket * p pointer to process * * Returns: * 0 request processed * errno error processing request - reason indicated * */ static int atm_aal5_listen(so, backlog, td) struct socket *so; int backlog; struct thread *td; { ATM_INTRO("listen"); err = atm_sock_listen(so, &atm_aal5_endpt, backlog); ATM_OUTRO(); } /* * Connect socket to peer * * Arguments: * so pointer to socket * addr pointer to protocol address * p pointer to process * * Returns: * 0 request processed * errno error processing request - reason indicated * */ static int atm_aal5_connect(so, addr, td) struct socket *so; struct sockaddr *addr; struct thread *td; { Atm_pcb *atp; ATM_INTRO("connect"); atp = sotoatmpcb(so); /* * Resize send socket buffer to maximum sdu size */ if (atp->atp_attr.aal.tag == T_ATM_PRESENT) { long size; size = atp->atp_attr.aal.v.aal5.forward_max_SDU_size; if (size != T_ATM_ABSENT) if (!sbreserve(&so->so_snd, size, so, td)) { err = ENOBUFS; ATM_OUTRO(); } } /* * Now get the socket connected */ err = atm_sock_connect(so, addr, &atm_aal5_endpt); ATM_OUTRO(); } /* * Accept pending connection * * Arguments: * so pointer to socket * addr pointer to pointer to contain protocol address * * Returns: * 0 request processed * errno error processing request - reason indicated * */ static int atm_aal5_accept(so, addr) struct socket *so; struct sockaddr **addr; { ATM_INTRO("accept"); /* * Everything is pretty much done already, we just need to * return the caller's address to the user. */ err = atm_sock_peeraddr(so, addr); ATM_OUTRO(); } /* * Disconnect connected socket * * Arguments: * so pointer to socket * * Returns: * 0 request processed * errno error processing request - reason indicated * */ static int atm_aal5_disconnect(so) struct socket *so; { ATM_INTRO("disconnect"); err = atm_sock_disconnect(so); ATM_OUTRO(); } /* * Shut down socket data transmission * * Arguments: * so pointer to socket * * Returns: * 0 request processed * errno error processing request - reason indicated * */ static int atm_aal5_shutdown(so) struct socket *so; { ATM_INTRO("shutdown"); socantsendmore(so); ATM_OUTRO(); } /* * Send user data * * Arguments: * so pointer to socket * flags send data flags * m pointer to buffer containing user data * addr pointer to protocol address * control pointer to buffer containing protocol control data * p pointer to process * * Returns: * 0 request processed * errno error processing request - reason indicated * */ static int atm_aal5_send(so, flags, m, addr, control, td) struct socket *so; int flags; KBuffer *m; struct sockaddr *addr; KBuffer *control; struct thread *td; { Atm_pcb *atp; ATM_INTRO("send"); /* * We don't support any control functions */ if (control) { int clen; clen = KB_LEN(control); KB_FREEALL(control); if (clen) { KB_FREEALL(m); ATM_RETERR(EINVAL); } } /* * We also don't support any flags or send-level addressing */ if (flags || addr) { KB_FREEALL(m); ATM_RETERR(EINVAL); } /* * All we've got left is the data, so push it out */ atp = sotoatmpcb(so); err = atm_cm_cpcs_data(atp->atp_conn, m); if (err) { /* * Output problem, drop packet */ atm_sock_stat.as_outdrop[atp->atp_type]++; KB_FREEALL(m); } out: ATM_OUTRO(); } /* * Abnormally terminate service * * Arguments: * so pointer to socket * * Returns: * 0 request processed * errno error processing request - reason indicated * */ static void atm_aal5_abort(so) struct socket *so; { ATM_INTRO_NOERR("abort"); so->so_error = ECONNABORTED; atm_sock_detach(so); ATM_OUTRO_NOERR(); } /* * Do control operation - ioctl system call * * Arguments: * so pointer to socket * cmd ioctl code * data pointer to code specific parameter data area * ifp pointer to ifnet structure if it's an interface ioctl * p pointer to process * * Returns: * 0 request processed * errno error processing request - reason indicated * */ static int atm_aal5_control(so, cmd, data, ifp, td) struct socket *so; u_long cmd; caddr_t data; struct ifnet *ifp; struct thread *td; { ATM_INTRO("control"); switch (cmd) { default: err = EOPNOTSUPP; } ATM_OUTRO(); } /* * Sense socket status - fstat system call * * Arguments: * so pointer to socket * st pointer to file status structure * * Returns: * 0 request processed * errno error processing request - reason indicated * */ static int atm_aal5_sense(so, st) struct socket *so; struct stat *st; { ATM_INTRO("sense"); /* * Just return the max sdu size for the connection */ st->st_blksize = so->so_snd.sb_hiwat; ATM_OUTRO(); } /* * Retrieve local socket address * * Arguments: * so pointer to socket * addr pointer to pointer to contain protocol address * * Returns: * 0 request processed * errno error processing request - reason indicated * */ static int atm_aal5_sockaddr(so, addr) struct socket *so; struct sockaddr **addr; { ATM_INTRO("sockaddr"); err = atm_sock_sockaddr(so, addr); ATM_OUTRO(); } /* * Retrieve peer socket address * * Arguments: * so pointer to socket * addr pointer to pointer to contain protocol address * * Returns: * 0 request processed * errno error processing request - reason indicated * */ static int atm_aal5_peeraddr(so, addr) struct socket *so; struct sockaddr **addr; { ATM_INTRO("peeraddr"); err = atm_sock_peeraddr(so, addr); ATM_OUTRO(); } /* * Process Incoming Calls * * This function will receive control when an incoming call has been matched * to one of our registered listen parameter blocks. Assuming the call passes * acceptance criteria and all required resources are available, we will * create a new protocol control block and socket association. We must * then await notification of the final SVC setup results. If any * problems are encountered, we will just tell the connection manager to * reject the call. * * Called at splnet. * * Arguments: * tok owner's matched listening token * cop pointer to incoming call's connection block * ap pointer to incoming call's attributes * tokp pointer to location to store our connection token * * Returns: * 0 call is accepted * errno call rejected - reason indicated * */ static int atm_aal5_incoming(tok, cop, ap, tokp) void *tok; Atm_connection *cop; Atm_attributes *ap; void **tokp; { Atm_pcb *atp0 = tok, *atp; struct socket *so; int err = 0; /* * Allocate a new socket and pcb for this connection. * * Note that our attach function will be called via sonewconn * and it will allocate and setup most of the pcb. */ atm_sock_stat.as_inconn[atp0->atp_type]++; so = sonewconn(atp0->atp_socket, 0); if (so) { /* * Finish pcb setup and pass pcb back to CM */ atp = sotoatmpcb(so); atp->atp_conn = cop; atp->atp_attr = *atp0->atp_conn->co_lattr; strncpy(atp->atp_name, atp0->atp_name, T_ATM_APP_NAME_LEN); *tokp = atp; } else { err = ECONNABORTED; atm_sock_stat.as_connfail[atp0->atp_type]++; } return (err); } /* * Process Socket VCC Input Data * * Arguments: * tok owner's connection token (atm_pcb) * m pointer to input packet buffer chain * * Returns: * none * */ static void atm_aal5_cpcs_data(tok, m) void *tok; KBuffer *m; { Atm_pcb *atp = tok; struct socket *so; int len; so = atp->atp_socket; KB_PLENGET(m, len); /* * Ensure that the socket is able to receive data and * that there's room in the socket buffer */ if (((so->so_state & SS_ISCONNECTED) == 0) || (so->so_rcv.sb_state & SBS_CANTRCVMORE) || (len > sbspace(&so->so_rcv))) { atm_sock_stat.as_indrop[atp->atp_type]++; KB_FREEALL(m); return; } /* * Queue the data and notify the user */ sbappendrecord(&so->so_rcv, m); sorwakeup(so); return; } /* * Process getsockopt/setsockopt system calls * * Arguments: * so pointer to socket * sopt pointer to socket option info * * Returns: * 0 request processed * errno error processing request - reason indicated * */ int atm_aal5_ctloutput(so, sopt) struct socket *so; struct sockopt *sopt; { Atm_pcb *atp; ATM_INTRO("ctloutput"); /* * Make sure this is for us */ if (sopt->sopt_level != T_ATM_SIGNALING) { ATM_RETERR(EINVAL); } atp = sotoatmpcb(so); if (atp == NULL) { ATM_RETERR(ENOTCONN); } switch (sopt->sopt_dir) { case SOPT_SET: /* * setsockopt() */ /* * Validate socket state */ switch (sopt->sopt_name) { case T_ATM_ADD_LEAF: case T_ATM_DROP_LEAF: if ((so->so_state & SS_ISCONNECTED) == 0) { ATM_RETERR(ENOTCONN); } break; case T_ATM_CAUSE: case T_ATM_APP_NAME: break; default: if (so->so_state & SS_ISCONNECTED) { ATM_RETERR(EISCONN); } break; } /* * Validate and save user-supplied option data */ err = atm_sock_setopt(so, sopt, atp); break; case SOPT_GET: /* * getsockopt() */ /* * Return option data */ err = atm_sock_getopt(so, sopt, atp); break; } out: ATM_OUTRO(); } /* * Initialize AAL5 Sockets * * Arguments: * none * * Returns: * none * */ void atm_aal5_init() { /* * Register our endpoint */ if (atm_endpoint_register(&atm_aal5_endpt)) panic("atm_aal5_init: register"); /* * Set default connection attributes */ atm_aal5_defattr.aal.v.aal5.forward_max_SDU_size = T_ATM_ABSENT; atm_aal5_defattr.aal.v.aal5.backward_max_SDU_size = T_ATM_ABSENT; atm_aal5_defattr.aal.v.aal5.SSCS_type = T_ATM_NULL; } /* * Get Connection's Application/Owner Name * * Arguments: * tok owner's connection token (atm_pcb) * * Returns: * addr pointer to string containing our name * */ static caddr_t atm_aal5_getname(tok) void *tok; { Atm_pcb *atp = tok; return (atp->atp_name); } diff --git a/sys/netatm/atm_socket.c b/sys/netatm/atm_socket.c index 2cdab95ebb3a..d2aecf1b592b 100644 --- a/sys/netatm/atm_socket.c +++ b/sys/netatm/atm_socket.c @@ -1,1325 +1,1312 @@ /*- * =================================== * HARP | Host ATM Research Platform * =================================== * * * This Host ATM Research Platform ("HARP") file (the "Software") is * made available by Network Computing Services, Inc. ("NetworkCS") * "AS IS". NetworkCS does not provide maintenance, improvements or * support of any kind. * * NETWORKCS MAKES NO WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED, * INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE, AS TO ANY ELEMENT OF THE * SOFTWARE OR ANY SUPPORT PROVIDED IN CONNECTION WITH THIS SOFTWARE. * In no event shall NetworkCS be responsible for any damages, including * but not limited to consequential damages, arising from or relating to * any use of the Software or related support. * * Copyright 1994-1998 Network Computing Services, Inc. * * Copies of this Software may be made, however, the above copyright * notice must be reproduced on all copies. */ /* * Core ATM Services * ----------------- * * ATM common socket protocol processing */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Local functions */ /* * Local variables */ static uma_zone_t atm_pcb_zone; static struct t_atm_cause atm_sock_cause = { T_ATM_ITU_CODING, T_ATM_LOC_USER, T_ATM_CAUSE_UNSPECIFIED_NORMAL, {0, 0, 0, 0} }; void atm_sock_init(void) { atm_pcb_zone = uma_zcreate("atm pcb", sizeof(Atm_pcb), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); if (atm_pcb_zone == NULL) panic("atm_sock_init: unable to initialize atm_pcb_zone"); } /* * Allocate resources for a new ATM socket * * Called at splnet. * * Arguments: * so pointer to socket * send socket send buffer maximum * recv socket receive buffer maximum * * Returns: * 0 attach successful * errno attach failed - reason indicated * */ int atm_sock_attach(so, send, recv) struct socket *so; u_long send; u_long recv; { Atm_pcb *atp = sotoatmpcb(so); int err; /* * Make sure initialization has happened */ if (!atm_init) atm_initialize(); /* * Make sure we're not already attached */ if (atp) return (EISCONN); /* * Reserve socket buffer space, if not already done */ if ((so->so_snd.sb_hiwat == 0) || (so->so_rcv.sb_hiwat == 0)) { err = soreserve(so, send, recv); if (err) return (err); } /* * Allocate and initialize our control block */ atp = uma_zalloc(atm_pcb_zone, M_ZERO | M_NOWAIT); if (atp == NULL) return (ENOMEM); atp->atp_socket = so; so->so_pcb = (caddr_t)atp; return (0); } /* * Detach from socket and free resources * * Called at splnet. * * Arguments: * so pointer to socket * - * Returns: - * 0 detach successful - * errno detach failed - reason indicated - * */ -int +void atm_sock_detach(so) struct socket *so; { Atm_pcb *atp = sotoatmpcb(so); /* * Make sure we're still attached */ - if (atp == NULL) - return (ENOTCONN); + KASSERT(atp != NULL, ("atm_sock_detach: atp == NULL")); /* * Terminate any (possibly pending) connection */ if (atp->atp_conn) { (void) atm_sock_disconnect(so); } - /* - * Break links and free control blocks - */ - ACCEPT_LOCK(); - SOCK_LOCK(so); so->so_pcb = NULL; - sotryfree(so); uma_zfree(atm_pcb_zone, atp); - - return (0); } /* * Bind local address to socket * * Called at splnet. * * Arguments: * so pointer to socket * addr pointer to protocol address * * Returns: * 0 request processed * errno error processing request - reason indicated * */ int atm_sock_bind(so, addr) struct socket *so; struct sockaddr *addr; { Atm_pcb *atp = sotoatmpcb(so); Atm_attributes attr; struct sockaddr_atm *satm; struct t_atm_sap_addr *sapadr; struct t_atm_sap_layer2 *sapl2; struct t_atm_sap_layer3 *sapl3; struct t_atm_sap_appl *sapapl; /* * Make sure we're still attached */ if (atp == NULL) return (ENOTCONN); /* * Can't change local address once we've started connection process */ if (atp->atp_conn != NULL) return (EADDRNOTAVAIL); /* * Validate requested local address */ satm = (struct sockaddr_atm *)addr; if (satm->satm_family != AF_ATM) return (EAFNOSUPPORT); sapadr = &satm->satm_addr.t_atm_sap_addr; if (sapadr->SVE_tag_addr == T_ATM_PRESENT) { if (sapadr->address_format == T_ATM_ENDSYS_ADDR) { if (sapadr->SVE_tag_selector != T_ATM_PRESENT) return (EINVAL); } else if (sapadr->address_format == T_ATM_E164_ADDR) { if (sapadr->SVE_tag_selector != T_ATM_ABSENT) return (EINVAL); } else return (EINVAL); } else if ((sapadr->SVE_tag_addr != T_ATM_ABSENT) && (sapadr->SVE_tag_addr != T_ATM_ANY)) return (EINVAL); if (sapadr->address_length > ATM_ADDR_LEN) return (EINVAL); sapl2 = &satm->satm_addr.t_atm_sap_layer2; if (sapl2->SVE_tag == T_ATM_PRESENT) { if ((sapl2->ID_type != T_ATM_SIMPLE_ID) && (sapl2->ID_type != T_ATM_USER_ID)) return (EINVAL); } else if ((sapl2->SVE_tag != T_ATM_ABSENT) && (sapl2->SVE_tag != T_ATM_ANY)) return (EINVAL); sapl3 = &satm->satm_addr.t_atm_sap_layer3; if (sapl3->SVE_tag == T_ATM_PRESENT) { if ((sapl3->ID_type != T_ATM_SIMPLE_ID) && (sapl3->ID_type != T_ATM_IPI_ID) && (sapl3->ID_type != T_ATM_SNAP_ID) && (sapl3->ID_type != T_ATM_USER_ID)) return (EINVAL); } else if ((sapl3->SVE_tag != T_ATM_ABSENT) && (sapl3->SVE_tag != T_ATM_ANY)) return (EINVAL); sapapl = &satm->satm_addr.t_atm_sap_appl; if (sapapl->SVE_tag == T_ATM_PRESENT) { if ((sapapl->ID_type != T_ATM_ISO_APP_ID) && (sapapl->ID_type != T_ATM_USER_APP_ID) && (sapapl->ID_type != T_ATM_VENDOR_APP_ID)) return (EINVAL); } else if ((sapapl->SVE_tag != T_ATM_ABSENT) && (sapapl->SVE_tag != T_ATM_ANY)) return (EINVAL); /* * Create temporary attributes list so that we can check out the * new bind parameters before we modify the socket's values; */ attr = atp->atp_attr; attr.called.tag = sapadr->SVE_tag_addr; bcopy(&sapadr->address_format, &attr.called.addr, sizeof(Atm_addr)); attr.blli.tag_l2 = sapl2->SVE_tag; if (sapl2->SVE_tag == T_ATM_PRESENT) { attr.blli.v.layer_2_protocol.ID_type = sapl2->ID_type; bcopy(&sapl2->ID, &attr.blli.v.layer_2_protocol.ID, sizeof(attr.blli.v.layer_2_protocol.ID)); } attr.blli.tag_l3 = sapl3->SVE_tag; if (sapl3->SVE_tag == T_ATM_PRESENT) { attr.blli.v.layer_3_protocol.ID_type = sapl3->ID_type; bcopy(&sapl3->ID, &attr.blli.v.layer_3_protocol.ID, sizeof(attr.blli.v.layer_3_protocol.ID)); } attr.bhli.tag = sapapl->SVE_tag; if (sapapl->SVE_tag == T_ATM_PRESENT) { attr.bhli.v.ID_type = sapapl->ID_type; bcopy(&sapapl->ID, &attr.bhli.v.ID, sizeof(attr.bhli.v.ID)); } /* * Make sure we have unique listening attributes */ if (atm_cm_match(&attr, NULL) != NULL) return (EADDRINUSE); /* * Looks good, save new attributes */ atp->atp_attr = attr; return (0); } /* * Listen for incoming connections * * Called at splnet. * * Arguments: * so pointer to socket * epp pointer to endpoint definition structure * * Returns: * 0 request processed * errno error processing request - reason indicated * */ int atm_sock_listen(so, epp, backlog) struct socket *so; Atm_endpoint *epp; int backlog; { Atm_pcb *atp = sotoatmpcb(so); /* * Make sure we're still attached */ if (atp == NULL) return (ENOTCONN); /* * Start listening for incoming calls */ return (atm_cm_listen(so, epp, atp, &atp->atp_attr, &atp->atp_conn, backlog)); } /* * Connect socket to peer * * Called at splnet. * * Arguments: * so pointer to socket * addr pointer to protocol address * epp pointer to endpoint definition structure * * Returns: * 0 request processed * errno error processing request - reason indicated * */ int atm_sock_connect(so, addr, epp) struct socket *so; struct sockaddr *addr; Atm_endpoint *epp; { Atm_pcb *atp = sotoatmpcb(so); struct sockaddr_atm *satm; struct t_atm_sap_addr *sapadr; struct t_atm_sap_layer2 *sapl2; struct t_atm_sap_layer3 *sapl3; struct t_atm_sap_appl *sapapl; int err; /* * Make sure we're still attached */ if (atp == NULL) return (ENOTCONN); /* * Validate requested peer address */ satm = (struct sockaddr_atm *)addr; if (satm->satm_family != AF_ATM) return (EAFNOSUPPORT); sapadr = &satm->satm_addr.t_atm_sap_addr; if (sapadr->SVE_tag_addr != T_ATM_PRESENT) return (EINVAL); if (sapadr->address_format == T_ATM_ENDSYS_ADDR) { if (sapadr->SVE_tag_selector != T_ATM_PRESENT) return (EINVAL); } else if (sapadr->address_format == T_ATM_E164_ADDR) { if (sapadr->SVE_tag_selector != T_ATM_ABSENT) return (EINVAL); } else if (sapadr->address_format == T_ATM_PVC_ADDR) { if (sapadr->SVE_tag_selector != T_ATM_ABSENT) return (EINVAL); } else return (EINVAL); if (sapadr->address_length > ATM_ADDR_LEN) return (EINVAL); sapl2 = &satm->satm_addr.t_atm_sap_layer2; if (sapl2->SVE_tag == T_ATM_PRESENT) { if ((sapl2->ID_type != T_ATM_SIMPLE_ID) && (sapl2->ID_type != T_ATM_USER_ID)) return (EINVAL); } else if (sapl2->SVE_tag != T_ATM_ABSENT) return (EINVAL); sapl3 = &satm->satm_addr.t_atm_sap_layer3; if (sapl3->SVE_tag == T_ATM_PRESENT) { if ((sapl3->ID_type != T_ATM_SIMPLE_ID) && (sapl3->ID_type != T_ATM_IPI_ID) && (sapl3->ID_type != T_ATM_SNAP_ID) && (sapl3->ID_type != T_ATM_USER_ID)) return (EINVAL); } else if (sapl3->SVE_tag != T_ATM_ABSENT) return (EINVAL); sapapl = &satm->satm_addr.t_atm_sap_appl; if (sapapl->SVE_tag == T_ATM_PRESENT) { if ((sapapl->ID_type != T_ATM_ISO_APP_ID) && (sapapl->ID_type != T_ATM_USER_APP_ID) && (sapapl->ID_type != T_ATM_VENDOR_APP_ID)) return (EINVAL); } else if (sapapl->SVE_tag != T_ATM_ABSENT) return (EINVAL); /* * Select an outgoing network interface */ if (atp->atp_attr.nif == NULL) { struct atm_pif *pip; for (pip = atm_interface_head; pip != NULL; pip = pip->pif_next) { if (pip->pif_nif != NULL) { atp->atp_attr.nif = pip->pif_nif; break; } } if (atp->atp_attr.nif == NULL) return (ENXIO); } /* * Set supplied connection attributes */ atp->atp_attr.called.tag = T_ATM_PRESENT; bcopy(&sapadr->address_format, &atp->atp_attr.called.addr, sizeof(Atm_addr)); atp->atp_attr.blli.tag_l2 = sapl2->SVE_tag; if (sapl2->SVE_tag == T_ATM_PRESENT) { atp->atp_attr.blli.v.layer_2_protocol.ID_type = sapl2->ID_type; bcopy(&sapl2->ID, &atp->atp_attr.blli.v.layer_2_protocol.ID, sizeof(atp->atp_attr.blli.v.layer_2_protocol.ID)); } atp->atp_attr.blli.tag_l3 = sapl3->SVE_tag; if (sapl3->SVE_tag == T_ATM_PRESENT) { atp->atp_attr.blli.v.layer_3_protocol.ID_type = sapl3->ID_type; bcopy(&sapl3->ID, &atp->atp_attr.blli.v.layer_3_protocol.ID, sizeof(atp->atp_attr.blli.v.layer_3_protocol.ID)); } atp->atp_attr.bhli.tag = sapapl->SVE_tag; if (sapapl->SVE_tag == T_ATM_PRESENT) { atp->atp_attr.bhli.v.ID_type = sapapl->ID_type; bcopy(&sapapl->ID, &atp->atp_attr.bhli.v.ID, sizeof(atp->atp_attr.bhli.v.ID)); } /* * We're finally ready to initiate the ATM connection */ soisconnecting(so); atm_sock_stat.as_connreq[atp->atp_type]++; err = atm_cm_connect(epp, atp, &atp->atp_attr, &atp->atp_conn); if (err == 0) { /* * Connection is setup */ atm_sock_stat.as_conncomp[atp->atp_type]++; soisconnected(so); } else if (err == EINPROGRESS) { /* * We've got to wait for a connected event */ err = 0; } else { /* * Call failed... */ atm_sock_stat.as_connfail[atp->atp_type]++; soisdisconnected(so); } return (err); } /* * Disconnect connected socket * * Called at splnet. * * Arguments: * so pointer to socket * * Returns: * 0 request processed * errno error processing request - reason indicated * */ int atm_sock_disconnect(so) struct socket *so; { Atm_pcb *atp = sotoatmpcb(so); struct t_atm_cause *cause; int err; /* * Make sure we're still attached */ if (atp == NULL) return (ENOTCONN); /* * Release the ATM connection */ if (atp->atp_conn) { if (atp->atp_attr.cause.tag == T_ATM_PRESENT) cause = &atp->atp_attr.cause.v; else cause = &atm_sock_cause; err = atm_cm_release(atp->atp_conn, cause); if (err) log(LOG_ERR, "atm_sock_disconnect: release fail (%d)\n", err); atm_sock_stat.as_connrel[atp->atp_type]++; atp->atp_conn = NULL; } soisdisconnected(so); return (0); } /* * Retrieve local socket address * * Called at splnet. * * Arguments: * so pointer to socket * addr pointer to pointer to contain protocol address * * Returns: * 0 request processed * errno error processing request - reason indicated * */ int atm_sock_sockaddr(so, addr) struct socket *so; struct sockaddr **addr; { struct sockaddr_atm *satm; struct t_atm_sap_addr *saddr; Atm_pcb *atp = sotoatmpcb(so); /* * Return local interface address, if known */ satm = malloc(sizeof(*satm), M_SONAME, M_WAITOK | M_ZERO); if (satm == NULL) return (ENOMEM); satm->satm_family = AF_ATM; satm->satm_len = sizeof(*satm); saddr = &satm->satm_addr.t_atm_sap_addr; if (atp->atp_attr.nif && atp->atp_attr.nif->nif_pif->pif_siginst) { saddr->SVE_tag_addr = T_ATM_PRESENT; ATM_ADDR_SEL_COPY( &atp->atp_attr.nif->nif_pif->pif_siginst->si_addr, atp->atp_attr.nif->nif_sel, saddr); if (saddr->address_format == T_ATM_ENDSYS_ADDR) saddr->SVE_tag_selector = T_ATM_PRESENT; else saddr->SVE_tag_selector = T_ATM_ABSENT; } else { saddr->SVE_tag_addr = T_ATM_ABSENT; saddr->SVE_tag_selector = T_ATM_ABSENT; saddr->address_format = T_ATM_ABSENT; } satm->satm_addr.t_atm_sap_layer2.SVE_tag = T_ATM_ABSENT; satm->satm_addr.t_atm_sap_layer3.SVE_tag = T_ATM_ABSENT; satm->satm_addr.t_atm_sap_appl.SVE_tag = T_ATM_ABSENT; *addr = (struct sockaddr *)satm; return (0); } /* * Retrieve peer socket address * * Called at splnet. * * Arguments: * so pointer to socket * addr pointer to pointer to contain protocol address * * Returns: * 0 request processed * errno error processing request - reason indicated * */ int atm_sock_peeraddr(so, addr) struct socket *so; struct sockaddr **addr; { struct sockaddr_atm *satm; struct t_atm_sap_addr *saddr; Atm_pcb *atp = sotoatmpcb(so); Atm_connvc *cvp; /* * Return remote address, if known */ satm = malloc(sizeof(*satm), M_SONAME, M_WAITOK | M_ZERO); if (satm == NULL) return (ENOMEM); satm->satm_family = AF_ATM; satm->satm_len = sizeof(*satm); saddr = &satm->satm_addr.t_atm_sap_addr; if (so->so_state & SS_ISCONNECTED) { cvp = atp->atp_conn->co_connvc; saddr->SVE_tag_addr = T_ATM_PRESENT; if (cvp->cvc_flags & CVCF_CALLER) { ATM_ADDR_COPY(&cvp->cvc_attr.called.addr, saddr); } else { if (cvp->cvc_attr.calling.tag == T_ATM_PRESENT) { ATM_ADDR_COPY(&cvp->cvc_attr.calling.addr, saddr); } else { saddr->SVE_tag_addr = T_ATM_ABSENT; saddr->address_format = T_ATM_ABSENT; } } if (saddr->address_format == T_ATM_ENDSYS_ADDR) saddr->SVE_tag_selector = T_ATM_PRESENT; else saddr->SVE_tag_selector = T_ATM_ABSENT; } else { saddr->SVE_tag_addr = T_ATM_ABSENT; saddr->SVE_tag_selector = T_ATM_ABSENT; saddr->address_format = T_ATM_ABSENT; } satm->satm_addr.t_atm_sap_layer2.SVE_tag = T_ATM_ABSENT; satm->satm_addr.t_atm_sap_layer3.SVE_tag = T_ATM_ABSENT; satm->satm_addr.t_atm_sap_appl.SVE_tag = T_ATM_ABSENT; *addr = (struct sockaddr *)satm; return (0); } /* * Common setsockopt processing * * Called at splnet. * * Arguments: * so pointer to socket * sopt pointer to socket option info * atp pointer to ATM PCB * * Returns: * 0 request processed * errno error processing request - reason indicated * */ int atm_sock_setopt(so, sopt, atp) struct socket *so; struct sockopt *sopt; Atm_pcb *atp; { int err = 0; union { struct t_atm_aal5 aal5; struct t_atm_traffic trf; struct t_atm_bearer brr; struct t_atm_bhli bhl; struct t_atm_blli bll; Atm_addr addr; struct t_atm_cause cau; struct t_atm_qos qos; struct t_atm_transit trn; struct t_atm_net_intf nif; struct t_atm_llc llc; struct t_atm_app_name appn; } p; #define MAXVAL(bits) ((1 << bits) - 1) #define MAXMASK(bits) (~MAXVAL(bits)) switch (sopt->sopt_name) { case T_ATM_AAL5: err = sooptcopyin(sopt, &p.aal5, sizeof p.aal5, sizeof p.aal5); if (err) break; if ((p.aal5.forward_max_SDU_size != T_ATM_ABSENT) && (p.aal5.forward_max_SDU_size & MAXMASK(16))) return (EINVAL); if ((p.aal5.backward_max_SDU_size != T_ATM_ABSENT) && (p.aal5.backward_max_SDU_size & MAXMASK(16))) return (EINVAL); if ((p.aal5.SSCS_type != T_ATM_ABSENT) && (p.aal5.SSCS_type != T_ATM_NULL) && (p.aal5.SSCS_type != T_ATM_SSCS_SSCOP_REL) && (p.aal5.SSCS_type != T_ATM_SSCS_SSCOP_UNREL) && (p.aal5.SSCS_type != T_ATM_SSCS_FR)) return (EINVAL); if ((p.aal5.forward_max_SDU_size == T_ATM_ABSENT) && (p.aal5.backward_max_SDU_size == T_ATM_ABSENT) && (p.aal5.SSCS_type == T_ATM_ABSENT)) atp->atp_attr.aal.tag = T_ATM_ABSENT; else { atp->atp_attr.aal.tag = T_ATM_PRESENT; atp->atp_attr.aal.type = ATM_AAL5; atp->atp_attr.aal.v.aal5 = p.aal5; } break; case T_ATM_TRAFFIC: err = sooptcopyin(sopt, &p.trf, sizeof p.trf, sizeof p.trf); if (err) break; if ((p.trf.forward.PCR_high_priority != T_ATM_ABSENT) && (p.trf.forward.PCR_high_priority & MAXMASK(24))) return (EINVAL); if (p.trf.forward.PCR_all_traffic & MAXMASK(24)) return (EINVAL); if ((p.trf.forward.SCR_high_priority != T_ATM_ABSENT) && (p.trf.forward.SCR_high_priority & MAXMASK(24))) return (EINVAL); if ((p.trf.forward.SCR_all_traffic != T_ATM_ABSENT) && (p.trf.forward.SCR_all_traffic & MAXMASK(24))) return (EINVAL); if ((p.trf.forward.MBS_high_priority != T_ATM_ABSENT) && (p.trf.forward.MBS_high_priority & MAXMASK(24))) return (EINVAL); if ((p.trf.forward.MBS_all_traffic != T_ATM_ABSENT) && (p.trf.forward.MBS_all_traffic & MAXMASK(24))) return (EINVAL); if ((p.trf.forward.tagging != T_YES) && (p.trf.forward.tagging != T_NO)) return (EINVAL); if ((p.trf.backward.PCR_high_priority != T_ATM_ABSENT) && (p.trf.backward.PCR_high_priority & MAXMASK(24))) return (EINVAL); if (p.trf.backward.PCR_all_traffic & MAXMASK(24)) return (EINVAL); if ((p.trf.backward.SCR_high_priority != T_ATM_ABSENT) && (p.trf.backward.SCR_high_priority & MAXMASK(24))) return (EINVAL); if ((p.trf.backward.SCR_all_traffic != T_ATM_ABSENT) && (p.trf.backward.SCR_all_traffic & MAXMASK(24))) return (EINVAL); if ((p.trf.backward.MBS_high_priority != T_ATM_ABSENT) && (p.trf.backward.MBS_high_priority & MAXMASK(24))) return (EINVAL); if ((p.trf.backward.MBS_all_traffic != T_ATM_ABSENT) && (p.trf.backward.MBS_all_traffic & MAXMASK(24))) return (EINVAL); if ((p.trf.backward.tagging != T_YES) && (p.trf.backward.tagging != T_NO)) return (EINVAL); if ((p.trf.best_effort != T_YES) && (p.trf.best_effort != T_NO)) return (EINVAL); atp->atp_attr.traffic.tag = T_ATM_PRESENT; atp->atp_attr.traffic.v = p.trf; break; case T_ATM_BEARER_CAP: err = sooptcopyin(sopt, &p.brr, sizeof p.brr, sizeof p.brr); if (err) break; if ((p.brr.bearer_class != T_ATM_CLASS_A) && (p.brr.bearer_class != T_ATM_CLASS_C) && (p.brr.bearer_class != T_ATM_CLASS_X)) return (EINVAL); if ((p.brr.traffic_type != T_ATM_NULL) && (p.brr.traffic_type != T_ATM_CBR) && (p.brr.traffic_type != T_ATM_VBR) && (p.brr.traffic_type != T_ATM_ABR) && (p.brr.traffic_type != T_ATM_UBR)) return (EINVAL); if ((p.brr.timing_requirements != T_ATM_NULL) && (p.brr.timing_requirements != T_ATM_END_TO_END) && (p.brr.timing_requirements != T_ATM_NO_END_TO_END)) return (EINVAL); if ((p.brr.clipping_susceptibility != T_NO) && (p.brr.clipping_susceptibility != T_YES)) return (EINVAL); if ((p.brr.connection_configuration != T_ATM_1_TO_1) && (p.brr.connection_configuration != T_ATM_1_TO_MANY)) return (EINVAL); atp->atp_attr.bearer.tag = T_ATM_PRESENT; atp->atp_attr.bearer.v = p.brr; break; case T_ATM_BHLI: err = sooptcopyin(sopt, &p.bhl, sizeof p.bhl, sizeof p.bhl); if (err) break; if ((p.bhl.ID_type != T_ATM_ABSENT) && (p.bhl.ID_type != T_ATM_ISO_APP_ID) && (p.bhl.ID_type != T_ATM_USER_APP_ID) && (p.bhl.ID_type != T_ATM_VENDOR_APP_ID)) return (EINVAL); if (p.bhl.ID_type == T_ATM_ABSENT) atp->atp_attr.bhli.tag = T_ATM_ABSENT; else { atp->atp_attr.bhli.tag = T_ATM_PRESENT; atp->atp_attr.bhli.v = p.bhl; } break; case T_ATM_BLLI: err = sooptcopyin(sopt, &p.bll, sizeof p.bll, sizeof p.bll); if (err) break; if ((p.bll.layer_2_protocol.ID_type != T_ATM_ABSENT) && (p.bll.layer_2_protocol.ID_type != T_ATM_SIMPLE_ID) && (p.bll.layer_2_protocol.ID_type != T_ATM_USER_ID)) return (EINVAL); if ((p.bll.layer_2_protocol.mode != T_ATM_ABSENT) && (p.bll.layer_2_protocol.mode != T_ATM_BLLI_NORMAL_MODE) && (p.bll.layer_2_protocol.mode != T_ATM_BLLI_EXTENDED_MODE)) return (EINVAL); if ((p.bll.layer_2_protocol.window_size != T_ATM_ABSENT) && (p.bll.layer_2_protocol.window_size < 1)) return (EINVAL); if ((p.bll.layer_3_protocol.ID_type != T_ATM_ABSENT) && (p.bll.layer_3_protocol.ID_type != T_ATM_SIMPLE_ID) && (p.bll.layer_3_protocol.ID_type != T_ATM_IPI_ID) && (p.bll.layer_3_protocol.ID_type != T_ATM_SNAP_ID) && (p.bll.layer_3_protocol.ID_type != T_ATM_USER_ID)) return (EINVAL); if ((p.bll.layer_3_protocol.mode != T_ATM_ABSENT) && (p.bll.layer_3_protocol.mode != T_ATM_BLLI_NORMAL_MODE) && (p.bll.layer_3_protocol.mode != T_ATM_BLLI_EXTENDED_MODE)) return (EINVAL); if ((p.bll.layer_3_protocol.packet_size != T_ATM_ABSENT) && (p.bll.layer_3_protocol.packet_size & MAXMASK(4))) return (EINVAL); if ((p.bll.layer_3_protocol.window_size != T_ATM_ABSENT) && (p.bll.layer_3_protocol.window_size < 1)) return (EINVAL); if (p.bll.layer_2_protocol.ID_type == T_ATM_ABSENT) atp->atp_attr.blli.tag_l2 = T_ATM_ABSENT; else atp->atp_attr.blli.tag_l2 = T_ATM_PRESENT; if (p.bll.layer_3_protocol.ID_type == T_ATM_ABSENT) atp->atp_attr.blli.tag_l3 = T_ATM_ABSENT; else atp->atp_attr.blli.tag_l3 = T_ATM_PRESENT; if ((atp->atp_attr.blli.tag_l2 == T_ATM_PRESENT) || (atp->atp_attr.blli.tag_l3 == T_ATM_PRESENT)) atp->atp_attr.blli.v = p.bll; break; case T_ATM_DEST_ADDR: err = sooptcopyin(sopt, &p.addr, sizeof p.addr, sizeof p.addr); if (err) break; if ((p.addr.address_format != T_ATM_ENDSYS_ADDR) && (p.addr.address_format != T_ATM_E164_ADDR)) return (EINVAL); if (p.addr.address_length > ATM_ADDR_LEN) return (EINVAL); atp->atp_attr.called.tag = T_ATM_PRESENT; atp->atp_attr.called.addr = p.addr; break; case T_ATM_DEST_SUB: err = sooptcopyin(sopt, &p.addr, sizeof p.addr, sizeof p.addr); if (err) break; if ((p.addr.address_format != T_ATM_ABSENT) && (p.addr.address_format != T_ATM_NSAP_ADDR)) return (EINVAL); if (p.addr.address_length > ATM_ADDR_LEN) return (EINVAL); /* T_ATM_DEST_ADDR controls tag */ atp->atp_attr.called.subaddr = p.addr; break; case T_ATM_ORIG_ADDR: return (EACCES); case T_ATM_ORIG_SUB: return (EACCES); case T_ATM_CALLER_ID: return (EACCES); case T_ATM_CAUSE: err = sooptcopyin(sopt, &p.cau, sizeof p.cau, sizeof p.cau); if (err) break; if ((p.cau.coding_standard != T_ATM_ABSENT) && (p.cau.coding_standard != T_ATM_ITU_CODING) && (p.cau.coding_standard != T_ATM_NETWORK_CODING)) return (EINVAL); if ((p.cau.location != T_ATM_LOC_USER) && (p.cau.location != T_ATM_LOC_LOCAL_PRIVATE_NET) && (p.cau.location != T_ATM_LOC_LOCAL_PUBLIC_NET) && (p.cau.location != T_ATM_LOC_TRANSIT_NET) && (p.cau.location != T_ATM_LOC_REMOTE_PUBLIC_NET) && (p.cau.location != T_ATM_LOC_REMOTE_PRIVATE_NET) && (p.cau.location != T_ATM_LOC_INTERNATIONAL_NET) && (p.cau.location != T_ATM_LOC_BEYOND_INTERWORKING)) return (EINVAL); if (p.cau.coding_standard == T_ATM_ABSENT) atp->atp_attr.cause.tag = T_ATM_ABSENT; else { atp->atp_attr.cause.tag = T_ATM_PRESENT; atp->atp_attr.cause.v = p.cau; } break; case T_ATM_QOS: err = sooptcopyin(sopt, &p.qos, sizeof p.qos, sizeof p.qos); if (err) break; if ((p.qos.coding_standard != T_ATM_ABSENT) && (p.qos.coding_standard != T_ATM_ITU_CODING) && (p.qos.coding_standard != T_ATM_NETWORK_CODING)) return (EINVAL); if ((p.qos.forward.qos_class != T_ATM_QOS_CLASS_0) && (p.qos.forward.qos_class != T_ATM_QOS_CLASS_1) && (p.qos.forward.qos_class != T_ATM_QOS_CLASS_2) && (p.qos.forward.qos_class != T_ATM_QOS_CLASS_3) && (p.qos.forward.qos_class != T_ATM_QOS_CLASS_4)) return (EINVAL); if ((p.qos.backward.qos_class != T_ATM_QOS_CLASS_0) && (p.qos.backward.qos_class != T_ATM_QOS_CLASS_1) && (p.qos.backward.qos_class != T_ATM_QOS_CLASS_2) && (p.qos.backward.qos_class != T_ATM_QOS_CLASS_3) && (p.qos.backward.qos_class != T_ATM_QOS_CLASS_4)) return (EINVAL); if (p.qos.coding_standard == T_ATM_ABSENT) atp->atp_attr.qos.tag = T_ATM_ABSENT; else { atp->atp_attr.qos.tag = T_ATM_PRESENT; atp->atp_attr.qos.v = p.qos; } break; case T_ATM_TRANSIT: err = sooptcopyin(sopt, &p.trn, sizeof p.trn, sizeof p.trn); if (err) break; if (p.trn.length > T_ATM_MAX_NET_ID) return (EINVAL); if (p.trn.length == 0) atp->atp_attr.transit.tag = T_ATM_ABSENT; else { atp->atp_attr.transit.tag = T_ATM_PRESENT; atp->atp_attr.transit.v = p.trn; } break; case T_ATM_ADD_LEAF: return (EPROTONOSUPPORT); /* XXX */ case T_ATM_DROP_LEAF: return (EPROTONOSUPPORT); /* XXX */ case T_ATM_NET_INTF: err = sooptcopyin(sopt, &p.nif, sizeof p.nif, sizeof p.nif); if (err) break; atp->atp_attr.nif = atm_nifname(p.nif.net_intf); if (atp->atp_attr.nif == NULL) return (ENXIO); break; case T_ATM_LLC: err = sooptcopyin(sopt, &p.llc, sizeof p.llc, sizeof p.llc); if (err) break; if ((p.llc.llc_len < T_ATM_LLC_MIN_LEN) || (p.llc.llc_len > T_ATM_LLC_MAX_LEN)) return (EINVAL); atp->atp_attr.llc.tag = T_ATM_PRESENT; atp->atp_attr.llc.v = p.llc; break; case T_ATM_APP_NAME: err = sooptcopyin(sopt, &p.appn, sizeof p.appn, sizeof p.appn); if (err) break; strncpy(atp->atp_name, p.appn.app_name, T_ATM_APP_NAME_LEN); break; default: return (ENOPROTOOPT); } return (err); } /* * Common getsockopt processing * * Called at splnet. * * Arguments: * so pointer to socket * sopt pointer to socket option info * atp pointer to ATM PCB * * Returns: * 0 request processed * errno error processing request - reason indicated * */ int atm_sock_getopt(so, sopt, atp) struct socket *so; struct sockopt *sopt; Atm_pcb *atp; { Atm_attributes *ap; /* * If socket is connected, return attributes for the VCC in use, * otherwise just return what the user has setup so far. */ if (so->so_state & SS_ISCONNECTED) ap = &atp->atp_conn->co_connvc->cvc_attr; else ap = &atp->atp_attr; switch (sopt->sopt_name) { case T_ATM_AAL5: if ((ap->aal.tag == T_ATM_PRESENT) && (ap->aal.type == ATM_AAL5)) { return (sooptcopyout(sopt, &ap->aal.v.aal5, sizeof ap->aal.v.aal5)); } else { return (ENOENT); } break; case T_ATM_TRAFFIC: if (ap->traffic.tag == T_ATM_PRESENT) { return (sooptcopyout(sopt, &ap->traffic.v, sizeof ap->traffic.v)); } else { return (ENOENT); } break; case T_ATM_BEARER_CAP: if (ap->bearer.tag == T_ATM_PRESENT) { return (sooptcopyout(sopt, &ap->bearer.v, sizeof ap->bearer.v)); } else { return (ENOENT); } break; case T_ATM_BHLI: if (ap->bhli.tag == T_ATM_PRESENT) { return (sooptcopyout(sopt, &ap->bhli.v, sizeof ap->bhli.v)); } else { return (ENOENT); } break; case T_ATM_BLLI: if ((ap->blli.tag_l2 == T_ATM_PRESENT) || (ap->blli.tag_l3 == T_ATM_PRESENT)) { return (sooptcopyout(sopt, &ap->blli.v, sizeof ap->blli.v)); } else { return (ENOENT); } break; case T_ATM_DEST_ADDR: if (ap->called.tag == T_ATM_PRESENT) { return (sooptcopyout(sopt, &ap->called.addr, sizeof ap->called.addr)); } else { return (ENOENT); } break; case T_ATM_DEST_SUB: if (ap->called.tag == T_ATM_PRESENT) { return (sooptcopyout(sopt, &ap->called.subaddr, sizeof ap->called.subaddr)); } else { return (ENOENT); } break; case T_ATM_ORIG_ADDR: if (ap->calling.tag == T_ATM_PRESENT) { return (sooptcopyout(sopt, &ap->calling.addr, sizeof ap->calling.addr)); } else { return (ENOENT); } break; case T_ATM_ORIG_SUB: if (ap->calling.tag == T_ATM_PRESENT) { return (sooptcopyout(sopt, &ap->calling.subaddr, sizeof ap->calling.subaddr)); } else { return (ENOENT); } break; case T_ATM_CALLER_ID: if (ap->calling.tag == T_ATM_PRESENT) { return (sooptcopyout(sopt, &ap->calling.cid, sizeof ap->calling.cid)); } else { return (ENOENT); } break; case T_ATM_CAUSE: if (ap->cause.tag == T_ATM_PRESENT) { return (sooptcopyout(sopt, &ap->cause.v, sizeof ap->cause.v)); } else { return (ENOENT); } break; case T_ATM_QOS: if (ap->qos.tag == T_ATM_PRESENT) { return (sooptcopyout(sopt, &ap->qos.v, sizeof ap->qos.v)); } else { return (ENOENT); } break; case T_ATM_TRANSIT: if (ap->transit.tag == T_ATM_PRESENT) { return (sooptcopyout(sopt, &ap->transit.v, sizeof ap->transit.v)); } else { return (ENOENT); } break; case T_ATM_LEAF_IND: return (EPROTONOSUPPORT); /* XXX */ case T_ATM_NET_INTF: if (ap->nif) { struct t_atm_net_intf netif; struct ifnet *ifp; ifp = ANIF2IFP(ap->nif); (void) snprintf(netif.net_intf, sizeof(netif.net_intf), "%s", ifp->if_xname); return (sooptcopyout(sopt, &netif, sizeof netif)); } else { return (ENOENT); } break; case T_ATM_LLC: if (ap->llc.tag == T_ATM_PRESENT) { return (sooptcopyout(sopt, &ap->llc.v, sizeof ap->llc.v)); } else { return (ENOENT); } break; default: return (ENOPROTOOPT); } return (0); } /* * Process Socket VCC Connected Notification * * Arguments: * toku owner's connection token (atm_pcb protocol block) * * Returns: * none * */ void atm_sock_connected(toku) void *toku; { Atm_pcb *atp = (Atm_pcb *)toku; /* * Connection is setup */ atm_sock_stat.as_conncomp[atp->atp_type]++; soisconnected(atp->atp_socket); } /* * Process Socket VCC Cleared Notification * * Arguments: * toku owner's connection token (atm_pcb protocol block) * cause pointer to cause code * * Returns: * none * */ void atm_sock_cleared(toku, cause) void *toku; struct t_atm_cause *cause; { Atm_pcb *atp = (Atm_pcb *)toku; struct socket *so; so = atp->atp_socket; /* * Save call clearing cause */ atp->atp_attr.cause.tag = T_ATM_PRESENT; atp->atp_attr.cause.v = *cause; /* * Set user error code */ if (so->so_state & SS_ISCONNECTED) { so->so_error = ECONNRESET; atm_sock_stat.as_connclr[atp->atp_type]++; } else { so->so_error = ECONNREFUSED; atm_sock_stat.as_connfail[atp->atp_type]++; } /* * Connection is gone */ atp->atp_conn = NULL; soisdisconnected(so); /* * Cleanup failed incoming connection setup */ if (so->so_state & SS_NOFDREF) { (void) atm_sock_detach(so); } } diff --git a/sys/netatm/atm_usrreq.c b/sys/netatm/atm_usrreq.c index db5a2ab61f3b..8facb95994bc 100644 --- a/sys/netatm/atm_usrreq.c +++ b/sys/netatm/atm_usrreq.c @@ -1,725 +1,725 @@ /*- * =================================== * HARP | Host ATM Research Platform * =================================== * * * This Host ATM Research Platform ("HARP") file (the "Software") is * made available by Network Computing Services, Inc. ("NetworkCS") * "AS IS". NetworkCS does not provide maintenance, improvements or * support of any kind. * * NETWORKCS MAKES NO WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED, * INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE, AS TO ANY ELEMENT OF THE * SOFTWARE OR ANY SUPPORT PROVIDED IN CONNECTION WITH THIS SOFTWARE. * In no event shall NetworkCS be responsible for any damages, including * but not limited to consequential damages, arising from or relating to * any use of the Software or related support. * * Copyright 1994-1998 Network Computing Services, Inc. * * Copies of this Software may be made, however, the above copyright * notice must be reproduced on all copies. */ /* * Core ATM Services * ----------------- * * ATM DGRAM socket protocol processing */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Local functions */ static int atm_dgram_attach(struct socket *, int, struct thread *); static int atm_dgram_control(struct socket *, u_long, caddr_t, struct ifnet *, struct thread *); static int atm_dgram_info(caddr_t); /* * New-style socket request routines */ struct pr_usrreqs atm_dgram_usrreqs = { .pru_abort = atm_proto_notsupp5, .pru_attach = atm_dgram_attach, .pru_bind = atm_proto_notsupp2, .pru_control = atm_dgram_control, - .pru_detach = atm_proto_notsupp1, + .pru_detach = atm_proto_notsupp5, .pru_disconnect = atm_proto_notsupp1, .pru_peeraddr = atm_proto_notsupp3, .pru_send = atm_proto_notsupp4, .pru_shutdown = atm_proto_notsupp1, .pru_sockaddr = atm_proto_notsupp3, .pru_sosend = NULL, .pru_soreceive = NULL, .pru_sopoll = NULL, }; /* * Handy common code macros */ #ifdef DIAGNOSTIC #define ATM_INTRO() \ int s, err = 0; \ s = splnet(); \ /* \ * Stack queue should have been drained \ */ \ if (atm_stackq_head != NULL) \ panic("atm_usrreq: stack queue not empty"); \ ; #else #define ATM_INTRO() \ int s, err = 0; \ s = splnet(); \ ; #endif #define ATM_OUTRO() \ /* \ * Drain any deferred calls \ */ \ STACK_DRAIN(); \ (void) splx(s); \ return (err); \ ; #define ATM_RETERR(errno) { \ err = errno; \ goto out; \ } /* * Attach protocol to socket * * Arguments: * so pointer to socket * proto protocol identifier * p pointer to process * * Returns: * 0 request processed * errno error processing request - reason indicated * */ static int atm_dgram_attach(so, proto, td) struct socket *so; int proto; struct thread *td; { ATM_INTRO(); /* * Nothing to do here for ioctl()-only sockets */ ATM_OUTRO(); } /* * Process ioctl system calls * * Arguments: * so pointer to socket * cmd ioctl code * data pointer to code specific parameter data area * ifp pointer to ifnet structure if it's an interface ioctl * p pointer to process * * Returns: * 0 request processed * errno error processing request - reason indicated * */ static int atm_dgram_control(so, cmd, data, ifp, td) struct socket *so; u_long cmd; caddr_t data; struct ifnet *ifp; struct thread *td; { ATM_INTRO(); /* * First, figure out which ioctl we're dealing with and * then process it based on the sub-op code */ switch (cmd) { case AIOCCFG: { struct atmcfgreq *acp = (struct atmcfgreq *)data; struct atm_pif *pip; if (td && (suser(td) != 0)) ATM_RETERR(EPERM); switch (acp->acr_opcode) { case AIOCS_CFG_ATT: /* * Attach signalling manager */ if ((pip = atm_pifname(acp->acr_att_intf)) == NULL) ATM_RETERR(ENXIO); err = atm_sigmgr_attach(pip, acp->acr_att_proto); break; case AIOCS_CFG_DET: /* * Detach signalling manager */ if ((pip = atm_pifname(acp->acr_det_intf)) == NULL) ATM_RETERR(ENXIO); err = atm_sigmgr_detach(pip); break; default: err = EOPNOTSUPP; } break; } case AIOCADD: { struct atmaddreq *aap = (struct atmaddreq *)data; Atm_endpoint *epp; if (td && (suser(td) != 0)) ATM_RETERR(EPERM); switch (aap->aar_opcode) { case AIOCS_ADD_PVC: /* * Add a PVC definition */ /* * Locate requested endpoint service */ epp = aap->aar_pvc_sap > ENDPT_MAX ? NULL : atm_endpoints[aap->aar_pvc_sap]; if (epp == NULL) ATM_RETERR(ENOPROTOOPT); /* * Let endpoint service handle it from here */ err = (*epp->ep_ioctl)(AIOCS_ADD_PVC, data, NULL); break; case AIOCS_ADD_ARP: /* * Add an ARP mapping */ epp = atm_endpoints[ENDPT_IP]; if (epp == NULL) ATM_RETERR(ENOPROTOOPT); /* * Let IP/ATM endpoint handle this */ err = (*epp->ep_ioctl) (AIOCS_ADD_ARP, data, NULL); break; default: err = EOPNOTSUPP; } break; } case AIOCDEL: { struct atmdelreq *adp = (struct atmdelreq *)data; struct atm_pif *pip; struct sigmgr *smp; Atm_endpoint *epp; if (td && (suser(td) != 0)) ATM_RETERR(EPERM); switch (adp->adr_opcode) { case AIOCS_DEL_PVC: case AIOCS_DEL_SVC: /* * Delete a PVC or SVC */ /* * Locate appropriate sigmgr */ if ((pip = atm_pifname(adp->adr_pvc_intf)) == NULL) ATM_RETERR(ENXIO); if ((smp = pip->pif_sigmgr) == NULL) ATM_RETERR(ENOENT); /* * Let sigmgr handle it from here */ err = (*smp->sm_ioctl)(adp->adr_opcode, data, (caddr_t)pip->pif_siginst); break; case AIOCS_DEL_ARP: /* * Delete an ARP mapping */ epp = atm_endpoints[ENDPT_IP]; if (epp == NULL) ATM_RETERR(ENOPROTOOPT); /* * Let IP/ATM endpoint handle this */ err = (*epp->ep_ioctl) (AIOCS_DEL_ARP, data, NULL); break; default: err = EOPNOTSUPP; } break; } case AIOCSET: { struct atmsetreq *asp = (struct atmsetreq *)data; struct atm_pif *pip; struct atm_nif *nip; struct sigmgr *smp; struct ifnet *ifp2; if (td && (suser(td) != 0)) ATM_RETERR(EPERM); switch (asp->asr_opcode) { case AIOCS_SET_ASV: /* * Set an ARP server address */ /* * Locate appropriate sigmgr */ if ((nip = atm_nifname(asp->asr_arp_intf)) == NULL) ATM_RETERR(ENXIO); pip = nip->nif_pif; if ((smp = pip->pif_sigmgr) == NULL) ATM_RETERR(ENOENT); /* * Let sigmgr handle it from here */ err = (*smp->sm_ioctl)(AIOCS_SET_ASV, data, (caddr_t)nip); break; case AIOCS_SET_MAC: /* * Set physical interface MAC/ESI address */ /* * Locate physical interface */ if ((pip = atm_pifname(asp->asr_mac_intf)) == NULL) ATM_RETERR(ENXIO); /* * Interface must be detached */ if (pip->pif_sigmgr != NULL) ATM_RETERR(EADDRINUSE); /* * Just plunk the address into the pif */ bcopy((caddr_t)&asp->asr_mac_addr, (caddr_t)&pip->pif_macaddr, sizeof(struct mac_addr)); break; case AIOCS_SET_NIF: /* * Define network interfaces */ if ((pip = atm_pifname(asp->asr_nif_intf)) == NULL) ATM_RETERR(ENXIO); /* * Validate interface count - logical interfaces * are differentiated by the atm address selector. */ if (asp->asr_nif_cnt == 0 || asp->asr_nif_cnt > 256) ATM_RETERR(EINVAL); /* * Make sure prefix name is unique */ IFNET_RLOCK(); TAILQ_FOREACH(ifp2, &ifnet, if_link) { if (!strcmp(ifp2->if_dname, asp->asr_nif_pref)) { /* * If this is for the interface we're * (re-)defining, let it through */ for (nip = pip->pif_nif; nip; nip = nip->nif_pnext) { if (ANIF2IFP(nip) == ifp2) break; } if (nip) continue; IFNET_RUNLOCK(); ATM_RETERR(EEXIST); } } IFNET_RUNLOCK(); /* * Let interface handle it from here */ err = (*pip->pif_ioctl)(AIOCS_SET_NIF, data, (caddr_t)pip); break; case AIOCS_SET_PRF: /* * Set interface NSAP Prefix */ /* * Locate appropriate sigmgr */ if ((pip = atm_pifname(asp->asr_prf_intf)) == NULL) ATM_RETERR(ENXIO); if ((smp = pip->pif_sigmgr) == NULL) ATM_RETERR(ENOENT); /* * Let sigmgr handle it from here */ err = (*smp->sm_ioctl)(AIOCS_SET_PRF, data, (caddr_t)pip->pif_siginst); break; default: err = EOPNOTSUPP; } break; } case AIOCINFO: err = atm_dgram_info(data); break; default: err = EOPNOTSUPP; } out: ATM_OUTRO(); } /* * Process AIOCINFO ioctl system calls * * Called at splnet. * * Arguments: * data pointer to AIOCINFO parameter structure * * Returns: * 0 request processed * errno error processing request - reason indicated * */ static int atm_dgram_info(data) caddr_t data; { struct atminfreq *aip = (struct atminfreq *)data; struct atm_pif *pip; struct atm_nif *nip; struct sigmgr *smp; Atm_endpoint *epp; int len = aip->air_buf_len; int err = 0; switch (aip->air_opcode) { case AIOCS_INF_VST: case AIOCS_INF_CFG: /* * Get vendor interface information */ if (aip->air_vinfo_intf[0] != '\0') { /* * Interface specified */ if ((pip = atm_pifname(aip->air_vinfo_intf))) { err = (*pip->pif_ioctl)(aip->air_opcode, data, (caddr_t)pip); } else { err = ENXIO; } } else { /* * Want info for every interface */ for (pip = atm_interface_head; pip; pip = pip->pif_next) { err = (*pip->pif_ioctl)(aip->air_opcode, data, (caddr_t)pip); if (err) break; } } break; case AIOCS_INF_IPM: /* * Get IP Map information */ epp = atm_endpoints[ENDPT_IP]; if (epp) { err = (*epp->ep_ioctl) (AIOCS_INF_IPM, data, NULL); } else { err = ENOPROTOOPT; } break; case AIOCS_INF_ARP: /* * Get ARP table information */ for (pip = atm_interface_head; pip; pip = pip->pif_next) { if ((smp = pip->pif_sigmgr) != NULL) { err = (*smp->sm_ioctl)(AIOCS_INF_ARP, data, (caddr_t)pip->pif_siginst); } if (err) break; } break; case AIOCS_INF_ASV: /* * Get ARP server information */ if (aip->air_asrv_intf[0] != '\0') { /* * Interface specified */ if ((nip = atm_nifname(aip->air_asrv_intf))) { if ((smp = nip->nif_pif->pif_sigmgr) != NULL) { err = (*smp->sm_ioctl)(AIOCS_INF_ASV, data, (caddr_t)nip); } } else { err = ENXIO; } } else { /* * Want info for all arp servers */ for (pip = atm_interface_head; pip; pip = pip->pif_next) { if ((smp = pip->pif_sigmgr) != NULL) { for (nip = pip->pif_nif; nip; nip = nip->nif_pnext) { err = (*smp->sm_ioctl) (AIOCS_INF_ASV, data, (caddr_t)nip); if (err) break; } if (err) break; } } } break; case AIOCS_INF_INT: /* * Get physical interface info */ if (aip->air_int_intf[0] != '\0') { /* * Interface specified */ if ((pip = atm_pifname(aip->air_int_intf))) { err = (*pip->pif_ioctl)(AIOCS_INF_INT, data, (caddr_t)pip); } else { err = ENXIO; } } else { /* * Want info for every physical interface */ for (pip = atm_interface_head; pip; pip = pip->pif_next) { err = (*pip->pif_ioctl)(AIOCS_INF_INT, data, (caddr_t)pip); if (err) break; } } break; case AIOCS_INF_VCC: /* * Get VCC information */ if (aip->air_vcc_intf[0] != '\0') { /* * Interface specified */ if ((pip = atm_pifname(aip->air_vcc_intf))) { if ((smp = pip->pif_sigmgr) != NULL) { err = (*smp->sm_ioctl)(AIOCS_INF_VCC, data, (caddr_t)pip->pif_siginst); } } else { err = ENXIO; } } else { /* * Want info for every interface */ for (pip = atm_interface_head; pip; pip = pip->pif_next) { if ((smp = pip->pif_sigmgr) != NULL) { err = (*smp->sm_ioctl)(AIOCS_INF_VCC, data, (caddr_t)pip->pif_siginst); } if (err) break; } } break; case AIOCS_INF_NIF: /* * Get network interface info */ if (aip->air_int_intf[0] != '\0') { /* * Interface specified */ if ((nip = atm_nifname(aip->air_int_intf))) { pip = nip->nif_pif; err = (*pip->pif_ioctl)(AIOCS_INF_NIF, data, (caddr_t)nip); } else { err = ENXIO; } } else { /* * Want info for every network interface */ for (pip = atm_interface_head; pip; pip = pip->pif_next) { for (nip = pip->pif_nif; nip; nip = nip->nif_pnext) { err = (*pip->pif_ioctl)(AIOCS_INF_NIF, data, (caddr_t)nip); if (err) break; } if (err) break; } } break; case AIOCS_INF_PIS: /* * Get physical interface statistics */ if (aip->air_physt_intf[0] != '\0') { /* * Interface specified */ if ((pip = atm_pifname(aip->air_physt_intf))) { err = (*pip->pif_ioctl)(AIOCS_INF_PIS, data, (caddr_t)pip); } else { err = ENXIO; } } else { /* * Want statistics for every physical interface */ for (pip = atm_interface_head; pip; pip = pip->pif_next) { err = (*pip->pif_ioctl)(AIOCS_INF_PIS, data, (caddr_t)pip); if (err) break; } } break; case AIOCS_INF_VER: /* * Get ATM software version */ if (len < sizeof(atm_version)) { err = ENOSPC; break; } if ((err = copyout((caddr_t)&atm_version, aip->air_buf_addr, sizeof(atm_version))) != 0) { break; } aip->air_buf_addr += sizeof(atm_version); aip->air_buf_len -= sizeof(atm_version); break; default: err = EOPNOTSUPP; } /* * Calculate returned buffer length */ aip->air_buf_len = len - aip->air_buf_len; return (err); } diff --git a/sys/netatm/atm_var.h b/sys/netatm/atm_var.h index 23e4319240d1..788acc9b2688 100644 --- a/sys/netatm/atm_var.h +++ b/sys/netatm/atm_var.h @@ -1,185 +1,185 @@ /*- * * =================================== * HARP | Host ATM Research Platform * =================================== * * * This Host ATM Research Platform ("HARP") file (the "Software") is * made available by Network Computing Services, Inc. ("NetworkCS") * "AS IS". NetworkCS does not provide maintenance, improvements or * support of any kind. * * NETWORKCS MAKES NO WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED, * INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE, AS TO ANY ELEMENT OF THE * SOFTWARE OR ANY SUPPORT PROVIDED IN CONNECTION WITH THIS SOFTWARE. * In no event shall NetworkCS be responsible for any damages, including * but not limited to consequential damages, arising from or relating to * any use of the Software or related support. * * Copyright 1994-1998 Network Computing Services, Inc. * * Copies of this Software may be made, however, the above copyright * notice must be reproduced on all copies. * * @(#) $FreeBSD$ * */ /* * Core ATM Services * ----------------- * * ATM system variables * */ #ifndef _NETATM_ATM_VAR_H #define _NETATM_ATM_VAR_H #ifdef _KERNEL #include typedef void (*atm_init_fn)(void); /* * Global variable declarations */ extern struct pr_usrreqs atm_aal5_usrreqs; /* atm_proto.c */ extern struct domain atmdomain; /* atm_subr.c */ extern struct atm_pif *atm_interface_head; extern struct atm_ncm *atm_netconv_head; extern Atm_endpoint *atm_endpoints[]; extern struct stackq_entry *atm_stackq_head; extern struct stackq_entry *atm_stackq_tail; extern struct atm_sock_stat atm_sock_stat; extern int atm_init; extern int atm_version; extern int atm_debug; extern struct timeval atm_debugtime; extern int atm_dev_print; extern int atm_print_data; extern uma_zone_t atm_attributes_zone; extern struct pr_usrreqs atm_dgram_usrreqs; /* * Global function declarations */ /* atm_aal5.c */ int atm_aal5_ctloutput(struct socket *, struct sockopt *); void atm_aal5_init(void); /* atm_cm.c */ int atm_cm_connect(Atm_endpoint *, void *, Atm_attributes *, Atm_connection **); int atm_cm_listen(struct socket *, Atm_endpoint *, void *, Atm_attributes *, Atm_connection **, int); int atm_cm_addllc(Atm_endpoint *, void *, struct attr_llc *, Atm_connection *, Atm_connection **); int atm_cm_addparty(Atm_connection *, int, struct t_atm_sap *); int atm_cm_dropparty(Atm_connection *, int, struct t_atm_cause *); int atm_cm_release(Atm_connection *, struct t_atm_cause *); int atm_cm_abort(Atm_connvc *, struct t_atm_cause *); int atm_cm_incoming(struct vccb *, Atm_attributes *); void atm_cm_connected(Atm_connvc *); void atm_cm_cleared(Atm_connvc *); Atm_connection *atm_cm_match(Atm_attributes *, Atm_connection *); int atm_cm_cpcs_ctl(int, Atm_connection *, void *); int atm_cm_cpcs_data(Atm_connection *, KBuffer *); int atm_cm_saal_ctl(int, Atm_connection *, void *); int atm_cm_saal_data(Atm_connection *, KBuffer *); int atm_cm_sscop_ctl(int, Atm_connection *, void *, void *); int atm_cm_sscop_data(Atm_connection *, KBuffer *); int atm_endpoint_register(Atm_endpoint *); int atm_endpoint_deregister(Atm_endpoint *); /* atm_device.c */ int atm_dev_inst(struct stack_defn **, Atm_connvc *); void atm_dev_lower(int, void *, intptr_t, intptr_t); void * atm_dev_alloc(u_int, u_int, u_int); void atm_dev_free(volatile void *); KBuffer * atm_dev_compress(KBuffer *); Cmn_vcc * atm_dev_vcc_find(Cmn_unit *, u_int, u_int, u_int); void atm_dev_pdu_print(const Cmn_unit *, const Cmn_vcc *, const KBuffer *, const char *); /* atm_if.c */ int atm_physif_register(Cmn_unit *, const char *, struct stack_defn *); int atm_physif_deregister(Cmn_unit *); void atm_physif_freenifs(struct atm_pif *, uma_zone_t); int atm_netconv_register(struct atm_ncm *); int atm_netconv_deregister(struct atm_ncm *); int atm_nif_attach(struct atm_nif *); void atm_nif_detach(struct atm_nif *); int atm_nif_setaddr(struct atm_nif *, struct ifaddr *); int atm_ifoutput(struct ifnet *, KBuffer *, struct sockaddr *, struct rtentry *); struct atm_pif * atm_pifname(char *); struct atm_nif * atm_nifname(char *); /* atm_proto.c */ int atm_proto_notsupp1(struct socket *); int atm_proto_notsupp2(struct socket *, struct sockaddr *, struct thread *); int atm_proto_notsupp3(struct socket *, struct sockaddr **); int atm_proto_notsupp4(struct socket *, int, KBuffer *, struct sockaddr *, KBuffer *, struct thread *); void atm_proto_notsupp5(struct socket *); /* atm_signal.c */ int atm_sigmgr_register(struct sigmgr *); int atm_sigmgr_deregister(struct sigmgr *); int atm_sigmgr_attach(struct atm_pif *, u_char); int atm_sigmgr_detach(struct atm_pif *); int atm_stack_register(struct stack_defn *); int atm_stack_deregister(struct stack_defn *); int atm_create_stack(Atm_connvc *, struct stack_list *, void (*)(int, void *, intptr_t, intptr_t) ); /* atm_socket.c */ void atm_sock_init(void); int atm_sock_attach(struct socket *, u_long, u_long); -int atm_sock_detach(struct socket *); +void atm_sock_detach(struct socket *); int atm_sock_bind(struct socket *, struct sockaddr *); int atm_sock_listen(struct socket *, Atm_endpoint *, int); int atm_sock_connect(struct socket *, struct sockaddr *, Atm_endpoint *); int atm_sock_disconnect(struct socket *); int atm_sock_sockaddr(struct socket *, struct sockaddr **); int atm_sock_peeraddr(struct socket *, struct sockaddr **); int atm_sock_setopt(struct socket *, struct sockopt *, Atm_pcb *); int atm_sock_getopt(struct socket *, struct sockopt *, Atm_pcb *); void atm_sock_connected(void *); void atm_sock_cleared(void *, struct t_atm_cause *); /* atm_subr.c */ void atm_initialize(void); void atm_timeout(struct atm_time *, int, void (*)(struct atm_time *) ); int atm_untimeout(struct atm_time *); int atm_stack_enq(int, void (*)(int, void *, intptr_t, intptr_t), void *, Atm_connvc *, intptr_t, intptr_t); void atm_stack_drain(void); void atm_pdu_print(const KBuffer *, const char *); #ifdef SYSCTL_DECL SYSCTL_DECL(_net_harp); SYSCTL_DECL(_net_harp_atm); #endif #endif /* _KERNEL */ #endif /* _NETATM_ATM_VAR_H */ diff --git a/sys/netgraph/bluetooth/include/ng_btsocket_hci_raw.h b/sys/netgraph/bluetooth/include/ng_btsocket_hci_raw.h index cd7c84981175..0f2837770cb1 100644 --- a/sys/netgraph/bluetooth/include/ng_btsocket_hci_raw.h +++ b/sys/netgraph/bluetooth/include/ng_btsocket_hci_raw.h @@ -1,89 +1,89 @@ /* * ng_btsocket_hci_raw.h */ /*- * Copyright (c) 2001-2002 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ng_btsocket_hci_raw.h,v 1.3 2003/03/25 23:53:32 max Exp $ * $FreeBSD$ */ #ifndef _NETGRAPH_BTSOCKET_HCI_RAW_H_ #define _NETGRAPH_BTSOCKET_HCI_RAW_H_ #define NG_BTSOCKET_HCI_RAW_SENDSPACE (4 * 1024) #define NG_BTSOCKET_HCI_RAW_RECVSPACE (4 * 1024) /* * Bluetooth raw HCI socket PCB */ struct ng_btsocket_hci_raw_pcb { struct socket *so; /* socket */ u_int32_t flags; /* flags */ #define NG_BTSOCKET_HCI_RAW_DIRECTION (1 << 0) #define NG_BTSOCKET_HCI_RAW_PRIVILEGED (1 << 1) struct sockaddr_hci addr; /* local address */ struct ng_btsocket_hci_raw_filter filter; /* filter */ u_int32_t token; /* message token */ struct ng_mesg *msg; /* message */ LIST_ENTRY(ng_btsocket_hci_raw_pcb) next; /* link to next */ struct mtx pcb_mtx; /* pcb mutex */ }; typedef struct ng_btsocket_hci_raw_pcb ng_btsocket_hci_raw_pcb_t; typedef struct ng_btsocket_hci_raw_pcb * ng_btsocket_hci_raw_pcb_p; #define so2hci_raw_pcb(so) \ ((struct ng_btsocket_hci_raw_pcb *)((so)->so_pcb)) /* * Bluetooth raw HCI socket methods */ #ifdef _KERNEL void ng_btsocket_hci_raw_init (void); void ng_btsocket_hci_raw_abort (struct socket *); int ng_btsocket_hci_raw_attach (struct socket *, int, struct thread *); int ng_btsocket_hci_raw_bind (struct socket *, struct sockaddr *, struct thread *); int ng_btsocket_hci_raw_connect (struct socket *, struct sockaddr *, struct thread *); int ng_btsocket_hci_raw_control (struct socket *, u_long, caddr_t, struct ifnet *, struct thread *); int ng_btsocket_hci_raw_ctloutput (struct socket *, struct sockopt *); -int ng_btsocket_hci_raw_detach (struct socket *); +void ng_btsocket_hci_raw_detach (struct socket *); int ng_btsocket_hci_raw_disconnect (struct socket *); int ng_btsocket_hci_raw_peeraddr (struct socket *, struct sockaddr **); int ng_btsocket_hci_raw_send (struct socket *, int, struct mbuf *, struct sockaddr *, struct mbuf *, struct thread *); int ng_btsocket_hci_raw_sockaddr (struct socket *, struct sockaddr **); #endif /* _KERNEL */ #endif /* ndef _NETGRAPH_BTSOCKET_HCI_RAW_H_ */ diff --git a/sys/netgraph/bluetooth/include/ng_btsocket_l2cap.h b/sys/netgraph/bluetooth/include/ng_btsocket_l2cap.h index 2c6171ad1d00..a8523ab44f6b 100644 --- a/sys/netgraph/bluetooth/include/ng_btsocket_l2cap.h +++ b/sys/netgraph/bluetooth/include/ng_btsocket_l2cap.h @@ -1,208 +1,208 @@ /* * ng_btsocket_l2cap.h */ /*- * Copyright (c) 2001-2002 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ng_btsocket_l2cap.h,v 1.4 2003/03/25 23:53:33 max Exp $ * $FreeBSD$ */ #ifndef _NETGRAPH_BTSOCKET_L2CAP_H_ #define _NETGRAPH_BTSOCKET_L2CAP_H_ /* * L2CAP routing entry */ struct ng_hook; struct ng_message; struct ng_btsocket_l2cap_rtentry { bdaddr_t src; /* source BD_ADDR */ struct ng_hook *hook; /* downstream hook */ LIST_ENTRY(ng_btsocket_l2cap_rtentry) next; /* link to next */ }; typedef struct ng_btsocket_l2cap_rtentry ng_btsocket_l2cap_rtentry_t; typedef struct ng_btsocket_l2cap_rtentry * ng_btsocket_l2cap_rtentry_p; /***************************************************************************** ***************************************************************************** ** SOCK_RAW L2CAP sockets ** ***************************************************************************** *****************************************************************************/ #define NG_BTSOCKET_L2CAP_RAW_SENDSPACE NG_L2CAP_MTU_DEFAULT #define NG_BTSOCKET_L2CAP_RAW_RECVSPACE NG_L2CAP_MTU_DEFAULT /* * Bluetooth raw L2CAP socket PCB */ struct ng_btsocket_l2cap_raw_pcb { struct socket *so; /* socket */ u_int32_t flags; /* flags */ #define NG_BTSOCKET_L2CAP_RAW_PRIVILEGED (1 << 0) bdaddr_t src; /* source address */ bdaddr_t dst; /* dest address */ ng_btsocket_l2cap_rtentry_p rt; /* routing info */ u_int32_t token; /* message token */ struct ng_mesg *msg; /* message */ struct mtx pcb_mtx; /* pcb mutex */ LIST_ENTRY(ng_btsocket_l2cap_raw_pcb) next; /* link to next PCB */ }; typedef struct ng_btsocket_l2cap_raw_pcb ng_btsocket_l2cap_raw_pcb_t; typedef struct ng_btsocket_l2cap_raw_pcb * ng_btsocket_l2cap_raw_pcb_p; #define so2l2cap_raw_pcb(so) \ ((struct ng_btsocket_l2cap_raw_pcb *)((so)->so_pcb)) /* * Bluetooth raw L2CAP socket methods */ #ifdef _KERNEL void ng_btsocket_l2cap_raw_init (void); void ng_btsocket_l2cap_raw_abort (struct socket *); int ng_btsocket_l2cap_raw_attach (struct socket *, int, struct thread *); int ng_btsocket_l2cap_raw_bind (struct socket *, struct sockaddr *, struct thread *); int ng_btsocket_l2cap_raw_connect (struct socket *, struct sockaddr *, struct thread *); int ng_btsocket_l2cap_raw_control (struct socket *, u_long, caddr_t, struct ifnet *, struct thread *); -int ng_btsocket_l2cap_raw_detach (struct socket *); +void ng_btsocket_l2cap_raw_detach (struct socket *); int ng_btsocket_l2cap_raw_disconnect (struct socket *); int ng_btsocket_l2cap_raw_peeraddr (struct socket *, struct sockaddr **); int ng_btsocket_l2cap_raw_send (struct socket *, int, struct mbuf *, struct sockaddr *, struct mbuf *, struct thread *); int ng_btsocket_l2cap_raw_sockaddr (struct socket *, struct sockaddr **); #endif /* _KERNEL */ /***************************************************************************** ***************************************************************************** ** SOCK_SEQPACKET L2CAP sockets ** ***************************************************************************** *****************************************************************************/ #define NG_BTSOCKET_L2CAP_SENDSPACE NG_L2CAP_MTU_DEFAULT /* (64 * 1024) */ #define NG_BTSOCKET_L2CAP_RECVSPACE (64 * 1024) /* * Bluetooth L2CAP socket PCB */ struct ng_btsocket_l2cap_pcb { struct socket *so; /* Pointer to socket */ bdaddr_t src; /* Source address */ bdaddr_t dst; /* Destination address */ u_int16_t psm; /* PSM */ u_int16_t cid; /* Local channel ID */ u_int16_t flags; /* socket flags */ #define NG_BTSOCKET_L2CAP_CLIENT (1 << 0) /* socket is client */ #define NG_BTSOCKET_L2CAP_TIMO (1 << 1) /* timeout pending */ u_int8_t state; /* socket state */ #define NG_BTSOCKET_L2CAP_CLOSED 0 /* socket closed */ #define NG_BTSOCKET_L2CAP_CONNECTING 1 /* wait for connect */ #define NG_BTSOCKET_L2CAP_CONFIGURING 2 /* wait for config */ #define NG_BTSOCKET_L2CAP_OPEN 3 /* socket open */ #define NG_BTSOCKET_L2CAP_DISCONNECTING 4 /* wait for disconnect */ u_int8_t cfg_state; /* config state */ #define NG_BTSOCKET_L2CAP_CFG_IN (1 << 0) /* incoming path done */ #define NG_BTSOCKET_L2CAP_CFG_OUT (1 << 1) /* outgoing path done */ #define NG_BTSOCKET_L2CAP_CFG_BOTH \ (NG_BTSOCKET_L2CAP_CFG_IN | NG_BTSOCKET_L2CAP_CFG_OUT) #define NG_BTSOCKET_L2CAP_CFG_IN_SENT (1 << 2) /* L2CAP ConfigReq sent */ #define NG_BTSOCKET_L2CAP_CFG_OUT_SENT (1 << 3) /* ---/--- */ u_int16_t imtu; /* Incoming MTU */ ng_l2cap_flow_t iflow; /* Input flow spec */ u_int16_t omtu; /* Outgoing MTU */ ng_l2cap_flow_t oflow; /* Outgoing flow spec */ u_int16_t flush_timo; /* flush timeout */ u_int16_t link_timo; /* link timeout */ struct callout_handle timo; /* timeout */ u_int32_t token; /* message token */ ng_btsocket_l2cap_rtentry_p rt; /* routing info */ struct mtx pcb_mtx; /* pcb mutex */ LIST_ENTRY(ng_btsocket_l2cap_pcb) next; /* link to next PCB */ }; typedef struct ng_btsocket_l2cap_pcb ng_btsocket_l2cap_pcb_t; typedef struct ng_btsocket_l2cap_pcb * ng_btsocket_l2cap_pcb_p; #define so2l2cap_pcb(so) \ ((struct ng_btsocket_l2cap_pcb *)((so)->so_pcb)) /* * Bluetooth L2CAP socket methods */ #ifdef _KERNEL void ng_btsocket_l2cap_init (void); void ng_btsocket_l2cap_abort (struct socket *); int ng_btsocket_l2cap_accept (struct socket *, struct sockaddr **); int ng_btsocket_l2cap_attach (struct socket *, int, struct thread *); int ng_btsocket_l2cap_bind (struct socket *, struct sockaddr *, struct thread *); int ng_btsocket_l2cap_connect (struct socket *, struct sockaddr *, struct thread *); int ng_btsocket_l2cap_control (struct socket *, u_long, caddr_t, struct ifnet *, struct thread *); int ng_btsocket_l2cap_ctloutput (struct socket *, struct sockopt *); -int ng_btsocket_l2cap_detach (struct socket *); +void ng_btsocket_l2cap_detach (struct socket *); int ng_btsocket_l2cap_disconnect (struct socket *); int ng_btsocket_l2cap_listen (struct socket *, int, struct thread *); int ng_btsocket_l2cap_peeraddr (struct socket *, struct sockaddr **); int ng_btsocket_l2cap_send (struct socket *, int, struct mbuf *, struct sockaddr *, struct mbuf *, struct thread *); int ng_btsocket_l2cap_sockaddr (struct socket *, struct sockaddr **); #endif /* _KERNEL */ #endif /* _NETGRAPH_BTSOCKET_L2CAP_H_ */ diff --git a/sys/netgraph/bluetooth/include/ng_btsocket_rfcomm.h b/sys/netgraph/bluetooth/include/ng_btsocket_rfcomm.h index 4b92cf81cf71..1939964bbc76 100644 --- a/sys/netgraph/bluetooth/include/ng_btsocket_rfcomm.h +++ b/sys/netgraph/bluetooth/include/ng_btsocket_rfcomm.h @@ -1,339 +1,339 @@ /* * ng_btsocket_rfcomm.h */ /*- * Copyright (c) 2001-2003 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ng_btsocket_rfcomm.h,v 1.10 2003/03/29 22:27:42 max Exp $ * $FreeBSD$ */ #ifndef _NETGRAPH_BTSOCKET_RFCOMM_H_ #define _NETGRAPH_BTSOCKET_RFCOMM_H_ /***************************************************************************** ***************************************************************************** ** RFCOMM ** ***************************************************************************** *****************************************************************************/ /* XXX FIXME this does not belong here */ #define RFCOMM_DEFAULT_MTU 667 #define RFCOMM_MAX_MTU 1024 #define RFCOMM_DEFAULT_CREDITS 7 #define RFCOMM_MAX_CREDITS 40 /* RFCOMM frame types */ #define RFCOMM_FRAME_SABM 0x2f #define RFCOMM_FRAME_DISC 0x43 #define RFCOMM_FRAME_UA 0x63 #define RFCOMM_FRAME_DM 0x0f #define RFCOMM_FRAME_UIH 0xef /* RFCOMM MCC commands */ #define RFCOMM_MCC_TEST 0x08 /* Test */ #define RFCOMM_MCC_FCON 0x28 /* Flow Control on */ #define RFCOMM_MCC_FCOFF 0x18 /* Flow Control off */ #define RFCOMM_MCC_MSC 0x38 /* Modem Status Command */ #define RFCOMM_MCC_RPN 0x24 /* Remote Port Negotiation */ #define RFCOMM_MCC_RLS 0x14 /* Remote Line Status */ #define RFCOMM_MCC_PN 0x20 /* Port Negotiation */ #define RFCOMM_MCC_NSC 0x04 /* Non Supported Command */ /* RFCOMM modem signals */ #define RFCOMM_MODEM_FC 0x02 /* Flow Control asserted */ #define RFCOMM_MODEM_RTC 0x04 /* Ready To Communicate */ #define RFCOMM_MODEM_RTR 0x08 /* Ready To Receive */ #define RFCOMM_MODEM_IC 0x40 /* Incomming Call */ #define RFCOMM_MODEM_DV 0x80 /* Data Valid */ /* RPN parameters - baud rate */ #define RFCOMM_RPN_BR_2400 0x0 #define RFCOMM_RPN_BR_4800 0x1 #define RFCOMM_RPN_BR_7200 0x2 #define RFCOMM_RPN_BR_9600 0x3 #define RFCOMM_RPN_BR_19200 0x4 #define RFCOMM_RPN_BR_38400 0x5 #define RFCOMM_RPN_BR_57600 0x6 #define RFCOMM_RPN_BR_115200 0x7 #define RFCOMM_RPN_BR_230400 0x8 /* RPN parameters - data bits */ #define RFCOMM_RPN_DATA_5 0x0 #define RFCOMM_RPN_DATA_6 0x1 #define RFCOMM_RPN_DATA_7 0x2 #define RFCOMM_RPN_DATA_8 0x3 /* RPN parameters - stop bit */ #define RFCOMM_RPN_STOP_1 0 #define RFCOMM_RPN_STOP_15 1 /* RPN parameters - parity */ #define RFCOMM_RPN_PARITY_NONE 0x0 #define RFCOMM_RPN_PARITY_ODD 0x4 #define RFCOMM_RPN_PARITY_EVEN 0x5 #define RFCOMM_RPN_PARITY_MARK 0x6 #define RFCOMM_RPN_PARITY_SPACE 0x7 /* RPN parameters - flow control */ #define RFCOMM_RPN_FLOW_NONE 0x00 #define RFCOMM_RPN_XON_CHAR 0x11 #define RFCOMM_RPN_XOFF_CHAR 0x13 /* RPN parameters - mask */ #define RFCOMM_RPN_PM_BITRATE 0x0001 #define RFCOMM_RPN_PM_DATA 0x0002 #define RFCOMM_RPN_PM_STOP 0x0004 #define RFCOMM_RPN_PM_PARITY 0x0008 #define RFCOMM_RPN_PM_PARITY_TYPE 0x0010 #define RFCOMM_RPN_PM_XON 0x0020 #define RFCOMM_RPN_PM_XOFF 0x0040 #define RFCOMM_RPN_PM_FLOW 0x3F00 #define RFCOMM_RPN_PM_ALL 0x3F7F /* RFCOMM frame header */ struct rfcomm_frame_hdr { u_int8_t address; u_int8_t control; u_int8_t length; /* Actual size could be 2 bytes */ } __attribute__ ((packed)); /* RFCOMM command frame header */ struct rfcomm_cmd_hdr { u_int8_t address; u_int8_t control; u_int8_t length; u_int8_t fcs; } __attribute__ ((packed)); /* RFCOMM MCC command header */ struct rfcomm_mcc_hdr { u_int8_t type; u_int8_t length; /* XXX FIXME Can actual size be 2 bytes?? */ } __attribute__ ((packed)); /* RFCOMM MSC command */ struct rfcomm_mcc_msc { u_int8_t address; u_int8_t modem; } __attribute__ ((packed)); /* RFCOMM RPN command */ struct rfcomm_mcc_rpn { u_int8_t dlci; u_int8_t bit_rate; u_int8_t line_settings; u_int8_t flow_control; u_int8_t xon_char; u_int8_t xoff_char; u_int16_t param_mask; } __attribute__ ((packed)); /* RFCOMM RLS command */ struct rfcomm_mcc_rls { u_int8_t address; u_int8_t status; } __attribute__ ((packed)); /* RFCOMM PN command */ struct rfcomm_mcc_pn { u_int8_t dlci; u_int8_t flow_control; u_int8_t priority; u_int8_t ack_timer; u_int16_t mtu; u_int8_t max_retrans; u_int8_t credits; } __attribute__ ((packed)); /* RFCOMM frame parsing macros */ #define RFCOMM_DLCI(b) (((b) & 0xfc) >> 2) #define RFCOMM_CHANNEL(b) (((b) & 0xf8) >> 3) #define RFCOMM_DIRECTION(b) (((b) & 0x04) >> 2) #define RFCOMM_TYPE(b) (((b) & 0xef)) #define RFCOMM_EA(b) (((b) & 0x01)) #define RFCOMM_CR(b) (((b) & 0x02) >> 1) #define RFCOMM_PF(b) (((b) & 0x10) >> 4) #define RFCOMM_SRVCHANNEL(dlci) ((dlci) >> 1) #define RFCOMM_MKADDRESS(cr, dlci) \ ((((dlci) & 0x3f) << 2) | ((cr) << 1) | 0x01) #define RFCOMM_MKCONTROL(type, pf) ((((type) & 0xef) | ((pf) << 4))) #define RFCOMM_MKDLCI(dir, channel) ((((channel) & 0x1f) << 1) | (dir)) #define RFCOMM_MKLEN8(len) (((len) << 1) | 1) #define RFCOMM_MKLEN16(len) ((len) << 1) /* RFCOMM MCC macros */ #define RFCOMM_MCC_TYPE(b) (((b) & 0xfc) >> 2) #define RFCOMM_MCC_LENGTH(b) (((b) & 0xfe) >> 1) #define RFCOMM_MKMCC_TYPE(cr, type) ((((type) << 2) | ((cr) << 1) | 0x01)) /* RPN macros */ #define RFCOMM_RPN_DATA_BITS(line) ((line) & 0x3) #define RFCOMM_RPN_STOP_BITS(line) (((line) >> 2) & 0x1) #define RFCOMM_RPN_PARITY(line) (((line) >> 3) & 0x3) #define RFCOMM_MKRPN_LINE_SETTINGS(data, stop, parity) \ (((data) & 0x3) | (((stop) & 0x1) << 2) | (((parity) & 0x3) << 3)) /***************************************************************************** ***************************************************************************** ** SOCK_STREAM RFCOMM sockets ** ***************************************************************************** *****************************************************************************/ #define NG_BTSOCKET_RFCOMM_SENDSPACE \ (RFCOMM_MAX_CREDITS * RFCOMM_DEFAULT_MTU * 2) #define NG_BTSOCKET_RFCOMM_RECVSPACE \ (RFCOMM_MAX_CREDITS * RFCOMM_DEFAULT_MTU * 2) /* * Bluetooth RFCOMM session. One L2CAP connection == one RFCOMM session */ struct ng_btsocket_rfcomm_pcb; struct ng_btsocket_rfcomm_session; struct ng_btsocket_rfcomm_session { struct socket *l2so; /* L2CAP socket */ u_int16_t state; /* session state */ #define NG_BTSOCKET_RFCOMM_SESSION_CLOSED 0 #define NG_BTSOCKET_RFCOMM_SESSION_LISTENING 1 #define NG_BTSOCKET_RFCOMM_SESSION_CONNECTING 2 #define NG_BTSOCKET_RFCOMM_SESSION_CONNECTED 3 #define NG_BTSOCKET_RFCOMM_SESSION_OPEN 4 #define NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING 5 u_int16_t flags; /* session flags */ #define NG_BTSOCKET_RFCOMM_SESSION_INITIATOR (1 << 0) /* initiator */ #define NG_BTSOCKET_RFCOMM_SESSION_LFC (1 << 1) /* local flow */ #define NG_BTSOCKET_RFCOMM_SESSION_RFC (1 << 2) /* remote flow */ #define INITIATOR(s) \ (((s)->flags & NG_BTSOCKET_RFCOMM_SESSION_INITIATOR)? 1 : 0) u_int16_t mtu; /* default MTU */ struct ng_bt_mbufq outq; /* outgoing queue */ struct mtx session_mtx; /* session lock */ LIST_HEAD(, ng_btsocket_rfcomm_pcb) dlcs; /* active DLC */ LIST_ENTRY(ng_btsocket_rfcomm_session) next; /* link to next */ }; typedef struct ng_btsocket_rfcomm_session ng_btsocket_rfcomm_session_t; typedef struct ng_btsocket_rfcomm_session * ng_btsocket_rfcomm_session_p; /* * Bluetooth RFCOMM socket PCB (DLC) */ struct ng_btsocket_rfcomm_pcb { struct socket *so; /* RFCOMM socket */ struct ng_btsocket_rfcomm_session *session; /* RFCOMM session */ u_int16_t flags; /* DLC flags */ #define NG_BTSOCKET_RFCOMM_DLC_TIMO (1 << 0) /* timeout pending */ #define NG_BTSOCKET_RFCOMM_DLC_CFC (1 << 1) /* credit flow ctrl */ #define NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT (1 << 2) /* timeout happend */ #define NG_BTSOCKET_RFCOMM_DLC_DETACHED (1 << 3) /* DLC detached */ #define NG_BTSOCKET_RFCOMM_DLC_SENDING (1 << 4) /* send pending */ u_int16_t state; /* DLC state */ #define NG_BTSOCKET_RFCOMM_DLC_CLOSED 0 #define NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT 1 #define NG_BTSOCKET_RFCOMM_DLC_CONFIGURING 2 #define NG_BTSOCKET_RFCOMM_DLC_CONNECTING 3 #define NG_BTSOCKET_RFCOMM_DLC_CONNECTED 4 #define NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING 5 bdaddr_t src; /* source address */ bdaddr_t dst; /* dest. address */ u_int8_t channel; /* RFCOMM channel */ u_int8_t dlci; /* RFCOMM DLCI */ u_int8_t lmodem; /* local mdm signls */ u_int8_t rmodem; /* remote -/- */ u_int16_t mtu; /* MTU */ int16_t rx_cred; /* RX credits */ int16_t tx_cred; /* TX credits */ struct mtx pcb_mtx; /* PCB lock */ struct callout_handle timo; /* timeout */ LIST_ENTRY(ng_btsocket_rfcomm_pcb) session_next;/* link to next */ LIST_ENTRY(ng_btsocket_rfcomm_pcb) next; /* link to next */ }; typedef struct ng_btsocket_rfcomm_pcb ng_btsocket_rfcomm_pcb_t; typedef struct ng_btsocket_rfcomm_pcb * ng_btsocket_rfcomm_pcb_p; #define so2rfcomm_pcb(so) \ ((struct ng_btsocket_rfcomm_pcb *)((so)->so_pcb)) /* * Bluetooth RFCOMM socket methods */ #ifdef _KERNEL void ng_btsocket_rfcomm_init (void); void ng_btsocket_rfcomm_abort (struct socket *); int ng_btsocket_rfcomm_accept (struct socket *, struct sockaddr **); int ng_btsocket_rfcomm_attach (struct socket *, int, struct thread *); int ng_btsocket_rfcomm_bind (struct socket *, struct sockaddr *, struct thread *); int ng_btsocket_rfcomm_connect (struct socket *, struct sockaddr *, struct thread *); int ng_btsocket_rfcomm_control (struct socket *, u_long, caddr_t, struct ifnet *, struct thread *); int ng_btsocket_rfcomm_ctloutput (struct socket *, struct sockopt *); -int ng_btsocket_rfcomm_detach (struct socket *); +void ng_btsocket_rfcomm_detach (struct socket *); int ng_btsocket_rfcomm_disconnect (struct socket *); int ng_btsocket_rfcomm_listen (struct socket *, int, struct thread *); int ng_btsocket_rfcomm_peeraddr (struct socket *, struct sockaddr **); int ng_btsocket_rfcomm_send (struct socket *, int, struct mbuf *, struct sockaddr *, struct mbuf *, struct thread *); int ng_btsocket_rfcomm_sockaddr (struct socket *, struct sockaddr **); #endif /* _KERNEL */ #endif /* _NETGRAPH_BTSOCKET_RFCOMM_H_ */ diff --git a/sys/netgraph/bluetooth/socket/ng_btsocket_hci_raw.c b/sys/netgraph/bluetooth/socket/ng_btsocket_hci_raw.c index d89bdafa0fbf..04bf4262e920 100644 --- a/sys/netgraph/bluetooth/socket/ng_btsocket_hci_raw.c +++ b/sys/netgraph/bluetooth/socket/ng_btsocket_hci_raw.c @@ -1,1576 +1,1571 @@ /* * ng_btsocket_hci_raw.c */ /*- * Copyright (c) 2001-2002 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ng_btsocket_hci_raw.c,v 1.14 2003/09/14 23:29:06 max Exp $ * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* MALLOC define */ #ifdef NG_SEPARATE_MALLOC MALLOC_DEFINE(M_NETGRAPH_BTSOCKET_HCI_RAW, "netgraph_btsocks_hci_raw", "Netgraph Bluetooth raw HCI sockets"); #else #define M_NETGRAPH_BTSOCKET_HCI_RAW M_NETGRAPH #endif /* NG_SEPARATE_MALLOC */ /* Netgraph node methods */ static ng_constructor_t ng_btsocket_hci_raw_node_constructor; static ng_rcvmsg_t ng_btsocket_hci_raw_node_rcvmsg; static ng_shutdown_t ng_btsocket_hci_raw_node_shutdown; static ng_newhook_t ng_btsocket_hci_raw_node_newhook; static ng_connect_t ng_btsocket_hci_raw_node_connect; static ng_rcvdata_t ng_btsocket_hci_raw_node_rcvdata; static ng_disconnect_t ng_btsocket_hci_raw_node_disconnect; static void ng_btsocket_hci_raw_input (void *, int); static void ng_btsocket_hci_raw_output(node_p, hook_p, void *, int); static void ng_btsocket_hci_raw_savctl(ng_btsocket_hci_raw_pcb_p, struct mbuf **, struct mbuf *); static int ng_btsocket_hci_raw_filter(ng_btsocket_hci_raw_pcb_p, struct mbuf *, int); #define ng_btsocket_hci_raw_wakeup_input_task() \ taskqueue_enqueue(taskqueue_swi, &ng_btsocket_hci_raw_task) /* Security filter */ struct ng_btsocket_hci_raw_sec_filter { bitstr_t bit_decl(events, 0xff); bitstr_t bit_decl(commands[0x3f], 0x3ff); }; /* Netgraph type descriptor */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_BTSOCKET_HCI_RAW_NODE_TYPE, .constructor = ng_btsocket_hci_raw_node_constructor, .rcvmsg = ng_btsocket_hci_raw_node_rcvmsg, .shutdown = ng_btsocket_hci_raw_node_shutdown, .newhook = ng_btsocket_hci_raw_node_newhook, .connect = ng_btsocket_hci_raw_node_connect, .rcvdata = ng_btsocket_hci_raw_node_rcvdata, .disconnect = ng_btsocket_hci_raw_node_disconnect, }; /* Globals */ extern int ifqmaxlen; static u_int32_t ng_btsocket_hci_raw_debug_level; static u_int32_t ng_btsocket_hci_raw_ioctl_timeout; static node_p ng_btsocket_hci_raw_node; static struct ng_bt_itemq ng_btsocket_hci_raw_queue; static struct mtx ng_btsocket_hci_raw_queue_mtx; static struct task ng_btsocket_hci_raw_task; static LIST_HEAD(, ng_btsocket_hci_raw_pcb) ng_btsocket_hci_raw_sockets; static struct mtx ng_btsocket_hci_raw_sockets_mtx; static u_int32_t ng_btsocket_hci_raw_token; static struct mtx ng_btsocket_hci_raw_token_mtx; static struct ng_btsocket_hci_raw_sec_filter *ng_btsocket_hci_raw_sec_filter; /* Sysctl tree */ SYSCTL_DECL(_net_bluetooth_hci_sockets); SYSCTL_NODE(_net_bluetooth_hci_sockets, OID_AUTO, raw, CTLFLAG_RW, 0, "Bluetooth raw HCI sockets family"); SYSCTL_INT(_net_bluetooth_hci_sockets_raw, OID_AUTO, debug_level, CTLFLAG_RW, &ng_btsocket_hci_raw_debug_level, NG_BTSOCKET_WARN_LEVEL, "Bluetooth raw HCI sockets debug level"); SYSCTL_INT(_net_bluetooth_hci_sockets_raw, OID_AUTO, ioctl_timeout, CTLFLAG_RW, &ng_btsocket_hci_raw_ioctl_timeout, 5, "Bluetooth raw HCI sockets ioctl timeout"); SYSCTL_INT(_net_bluetooth_hci_sockets_raw, OID_AUTO, queue_len, CTLFLAG_RD, &ng_btsocket_hci_raw_queue.len, 0, "Bluetooth raw HCI sockets input queue length"); SYSCTL_INT(_net_bluetooth_hci_sockets_raw, OID_AUTO, queue_maxlen, CTLFLAG_RD, &ng_btsocket_hci_raw_queue.maxlen, 0, "Bluetooth raw HCI sockets input queue max. length"); SYSCTL_INT(_net_bluetooth_hci_sockets_raw, OID_AUTO, queue_drops, CTLFLAG_RD, &ng_btsocket_hci_raw_queue.drops, 0, "Bluetooth raw HCI sockets input queue drops"); /* Debug */ #define NG_BTSOCKET_HCI_RAW_INFO \ if (ng_btsocket_hci_raw_debug_level >= NG_BTSOCKET_INFO_LEVEL) \ printf #define NG_BTSOCKET_HCI_RAW_WARN \ if (ng_btsocket_hci_raw_debug_level >= NG_BTSOCKET_WARN_LEVEL) \ printf #define NG_BTSOCKET_HCI_RAW_ERR \ if (ng_btsocket_hci_raw_debug_level >= NG_BTSOCKET_ERR_LEVEL) \ printf #define NG_BTSOCKET_HCI_RAW_ALERT \ if (ng_btsocket_hci_raw_debug_level >= NG_BTSOCKET_ALERT_LEVEL) \ printf /**************************************************************************** **************************************************************************** ** Netgraph specific **************************************************************************** ****************************************************************************/ /* * Netgraph node constructor. Do not allow to create node of this type. */ static int ng_btsocket_hci_raw_node_constructor(node_p node) { return (EINVAL); } /* ng_btsocket_hci_raw_node_constructor */ /* * Netgraph node destructor. Just let old node go and create new fresh one. */ static int ng_btsocket_hci_raw_node_shutdown(node_p node) { int error = 0; NG_NODE_UNREF(node); error = ng_make_node_common(&typestruct, &ng_btsocket_hci_raw_node); if (error != 0) { NG_BTSOCKET_HCI_RAW_ALERT( "%s: Could not create Netgraph node, error=%d\n", __func__, error); ng_btsocket_hci_raw_node = NULL; return (ENOMEM); } error = ng_name_node(ng_btsocket_hci_raw_node, NG_BTSOCKET_HCI_RAW_NODE_TYPE); if (error != 0) { NG_BTSOCKET_HCI_RAW_ALERT( "%s: Could not name Netgraph node, error=%d\n", __func__, error); NG_NODE_UNREF(ng_btsocket_hci_raw_node); ng_btsocket_hci_raw_node = NULL; return (EINVAL); } return (0); } /* ng_btsocket_hci_raw_node_shutdown */ /* * Create new hook. Just say "yes" */ static int ng_btsocket_hci_raw_node_newhook(node_p node, hook_p hook, char const *name) { return (0); } /* ng_btsocket_hci_raw_node_newhook */ /* * Connect hook. Just say "yes" */ static int ng_btsocket_hci_raw_node_connect(hook_p hook) { return (0); } /* ng_btsocket_hci_raw_node_connect */ /* * Disconnect hook */ static int ng_btsocket_hci_raw_node_disconnect(hook_p hook) { return (0); } /* ng_btsocket_hci_raw_node_disconnect */ /* * Receive control message. * Make sure it is a message from HCI node and it is a response. * Enqueue item and schedule input task. */ static int ng_btsocket_hci_raw_node_rcvmsg(node_p node, item_p item, hook_p lasthook) { struct ng_mesg *msg = NGI_MSG(item); /* item still has message */ int error = 0; /* * Check for empty sockets list creates LOR when both sender and * receiver device are connected to the same host, so remove it * for now */ if (msg != NULL && msg->header.typecookie == NGM_HCI_COOKIE && msg->header.flags & NGF_RESP) { if (msg->header.token == 0) { NG_FREE_ITEM(item); return (0); } mtx_lock(&ng_btsocket_hci_raw_queue_mtx); if (NG_BT_ITEMQ_FULL(&ng_btsocket_hci_raw_queue)) { NG_BTSOCKET_HCI_RAW_ERR( "%s: Input queue is full\n", __func__); NG_BT_ITEMQ_DROP(&ng_btsocket_hci_raw_queue); NG_FREE_ITEM(item); error = ENOBUFS; } else { NG_BT_ITEMQ_ENQUEUE(&ng_btsocket_hci_raw_queue, item); error = ng_btsocket_hci_raw_wakeup_input_task(); } mtx_unlock(&ng_btsocket_hci_raw_queue_mtx); } else { NG_FREE_ITEM(item); error = EINVAL; } return (error); } /* ng_btsocket_hci_raw_node_rcvmsg */ /* * Receive packet from the one of our hook. * Prepend every packet with sockaddr_hci and record sender's node name. * Enqueue item and schedule input task. */ static int ng_btsocket_hci_raw_node_rcvdata(hook_p hook, item_p item) { struct mbuf *nam = NULL; int error; /* * Check for empty sockets list creates LOR when both sender and * receiver device are connected to the same host, so remove it * for now */ MGET(nam, M_DONTWAIT, MT_SONAME); if (nam != NULL) { struct sockaddr_hci *sa = mtod(nam, struct sockaddr_hci *); nam->m_len = sizeof(struct sockaddr_hci); sa->hci_len = sizeof(*sa); sa->hci_family = AF_BLUETOOTH; strlcpy(sa->hci_node, NG_PEER_NODE_NAME(hook), sizeof(sa->hci_node)); NGI_GET_M(item, nam->m_next); NGI_M(item) = nam; mtx_lock(&ng_btsocket_hci_raw_queue_mtx); if (NG_BT_ITEMQ_FULL(&ng_btsocket_hci_raw_queue)) { NG_BTSOCKET_HCI_RAW_ERR( "%s: Input queue is full\n", __func__); NG_BT_ITEMQ_DROP(&ng_btsocket_hci_raw_queue); NG_FREE_ITEM(item); error = ENOBUFS; } else { NG_BT_ITEMQ_ENQUEUE(&ng_btsocket_hci_raw_queue, item); error = ng_btsocket_hci_raw_wakeup_input_task(); } mtx_unlock(&ng_btsocket_hci_raw_queue_mtx); } else { NG_BTSOCKET_HCI_RAW_ERR( "%s: Failed to allocate address mbuf\n", __func__); NG_FREE_ITEM(item); error = ENOBUFS; } return (error); } /* ng_btsocket_hci_raw_node_rcvdata */ /**************************************************************************** **************************************************************************** ** Sockets specific **************************************************************************** ****************************************************************************/ /* * Get next token. We need token to avoid theoretical race where process * submits ioctl() message then interrupts ioctl() and re-submits another * ioctl() on the same socket *before* first ioctl() complete. */ static void ng_btsocket_hci_raw_get_token(u_int32_t *token) { mtx_lock(&ng_btsocket_hci_raw_token_mtx); if (++ ng_btsocket_hci_raw_token == 0) ng_btsocket_hci_raw_token = 1; *token = ng_btsocket_hci_raw_token; mtx_unlock(&ng_btsocket_hci_raw_token_mtx); } /* ng_btsocket_hci_raw_get_token */ /* * Send Netgraph message to the node - do not expect reply */ static int ng_btsocket_hci_raw_send_ngmsg(char *path, int cmd, void *arg, int arglen) { struct ng_mesg *msg = NULL; int error = 0; NG_MKMESSAGE(msg, NGM_HCI_COOKIE, cmd, arglen, M_NOWAIT); if (msg == NULL) return (ENOMEM); if (arg != NULL && arglen > 0) bcopy(arg, msg->data, arglen); NG_SEND_MSG_PATH(error, ng_btsocket_hci_raw_node, msg, path, 0); return (error); } /* ng_btsocket_hci_raw_send_ngmsg */ /* * Send Netgraph message to the node (no data) and wait for reply */ static int ng_btsocket_hci_raw_send_sync_ngmsg(ng_btsocket_hci_raw_pcb_p pcb, char *path, int cmd, void *rsp, int rsplen) { struct ng_mesg *msg = NULL; int error = 0; mtx_assert(&pcb->pcb_mtx, MA_OWNED); NG_MKMESSAGE(msg, NGM_HCI_COOKIE, cmd, 0, M_NOWAIT); if (msg == NULL) return (ENOMEM); ng_btsocket_hci_raw_get_token(&msg->header.token); pcb->token = msg->header.token; pcb->msg = NULL; NG_SEND_MSG_PATH(error, ng_btsocket_hci_raw_node, msg, path, 0); if (error != 0) { pcb->token = 0; return (error); } error = msleep(&pcb->msg, &pcb->pcb_mtx, PZERO|PCATCH, "hcictl", ng_btsocket_hci_raw_ioctl_timeout * hz); pcb->token = 0; if (error != 0) return (error); if (pcb->msg != NULL && pcb->msg->header.cmd == cmd) bcopy(pcb->msg->data, rsp, rsplen); else error = EINVAL; NG_FREE_MSG(pcb->msg); /* checks for != NULL */ return (0); } /* ng_btsocket_hci_raw_send_sync_ngmsg */ /* * Create control information for the packet */ static void ng_btsocket_hci_raw_savctl(ng_btsocket_hci_raw_pcb_p pcb, struct mbuf **ctl, struct mbuf *m) { int dir; struct timeval tv; mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->flags & NG_BTSOCKET_HCI_RAW_DIRECTION) { dir = (m->m_flags & M_PROTO1)? 1 : 0; *ctl = sbcreatecontrol((caddr_t) &dir, sizeof(dir), SCM_HCI_RAW_DIRECTION, SOL_HCI_RAW); if (*ctl != NULL) ctl = &((*ctl)->m_next); } if (pcb->so->so_options & SO_TIMESTAMP) { microtime(&tv); *ctl = sbcreatecontrol((caddr_t) &tv, sizeof(tv), SCM_TIMESTAMP, SOL_SOCKET); if (*ctl != NULL) ctl = &((*ctl)->m_next); } } /* ng_btsocket_hci_raw_savctl */ /* * Raw HCI sockets data input routine */ static void ng_btsocket_hci_raw_data_input(struct mbuf *nam) { ng_btsocket_hci_raw_pcb_p pcb = NULL; struct mbuf *m0 = NULL, *m = NULL; struct sockaddr_hci *sa = NULL; m0 = nam->m_next; nam->m_next = NULL; KASSERT((nam->m_type == MT_SONAME), ("%s: m_type=%d\n", __func__, nam->m_type)); KASSERT((m0->m_flags & M_PKTHDR), ("%s: m_flags=%#x\n", __func__, m0->m_flags)); sa = mtod(nam, struct sockaddr_hci *); mtx_lock(&ng_btsocket_hci_raw_sockets_mtx); LIST_FOREACH(pcb, &ng_btsocket_hci_raw_sockets, next) { mtx_lock(&pcb->pcb_mtx); /* * If socket was bound then check address and * make sure it matches. */ if (pcb->addr.hci_node[0] != 0 && strcmp(sa->hci_node, pcb->addr.hci_node) != 0) goto next; /* * Check packet against filters * XXX do we have to call m_pullup() here? */ if (ng_btsocket_hci_raw_filter(pcb, m0, 1) != 0) goto next; /* * Make a copy of the packet, append to the socket's * receive queue and wakeup socket. sbappendaddr() * will check if socket has enough buffer space. */ m = m_dup(m0, M_DONTWAIT); if (m != NULL) { struct mbuf *ctl = NULL; ng_btsocket_hci_raw_savctl(pcb, &ctl, m); if (sbappendaddr(&pcb->so->so_rcv, (struct sockaddr *) sa, m, ctl)) sorwakeup(pcb->so); else { NG_BTSOCKET_HCI_RAW_INFO( "%s: sbappendaddr() failed\n", __func__); NG_FREE_M(m); NG_FREE_M(ctl); } } next: mtx_unlock(&pcb->pcb_mtx); } mtx_unlock(&ng_btsocket_hci_raw_sockets_mtx); NG_FREE_M(nam); NG_FREE_M(m0); } /* ng_btsocket_hci_raw_data_input */ /* * Raw HCI sockets message input routine */ static void ng_btsocket_hci_raw_msg_input(struct ng_mesg *msg) { ng_btsocket_hci_raw_pcb_p pcb = NULL; mtx_lock(&ng_btsocket_hci_raw_sockets_mtx); LIST_FOREACH(pcb, &ng_btsocket_hci_raw_sockets, next) { mtx_lock(&pcb->pcb_mtx); if (msg->header.token == pcb->token) { pcb->msg = msg; wakeup(&pcb->msg); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_hci_raw_sockets_mtx); return; } mtx_unlock(&pcb->pcb_mtx); } mtx_unlock(&ng_btsocket_hci_raw_sockets_mtx); NG_FREE_MSG(msg); /* checks for != NULL */ } /* ng_btsocket_hci_raw_msg_input */ /* * Raw HCI sockets input routines */ static void ng_btsocket_hci_raw_input(void *context, int pending) { item_p item = NULL; for (;;) { mtx_lock(&ng_btsocket_hci_raw_queue_mtx); NG_BT_ITEMQ_DEQUEUE(&ng_btsocket_hci_raw_queue, item); mtx_unlock(&ng_btsocket_hci_raw_queue_mtx); if (item == NULL) break; switch(item->el_flags & NGQF_TYPE) { case NGQF_DATA: { struct mbuf *m = NULL; NGI_GET_M(item, m); ng_btsocket_hci_raw_data_input(m); } break; case NGQF_MESG: { struct ng_mesg *msg = NULL; NGI_GET_MSG(item, msg); ng_btsocket_hci_raw_msg_input(msg); } break; default: KASSERT(0, ("%s: invalid item type=%ld\n", __func__, (item->el_flags & NGQF_TYPE))); break; } NG_FREE_ITEM(item); } } /* ng_btsocket_hci_raw_input */ /* * Raw HCI sockets output routine */ static void ng_btsocket_hci_raw_output(node_p node, hook_p hook, void *arg1, int arg2) { struct mbuf *nam = (struct mbuf *) arg1, *m = NULL; struct sockaddr_hci *sa = NULL; int error; m = nam->m_next; nam->m_next = NULL; KASSERT((nam->m_type == MT_SONAME), ("%s: m_type=%d\n", __func__, nam->m_type)); KASSERT((m->m_flags & M_PKTHDR), ("%s: m_flags=%#x\n", __func__, m->m_flags)); sa = mtod(nam, struct sockaddr_hci *); /* * Find downstream hook * XXX For now access node hook list directly. Should be safe because * we used ng_send_fn() and we should have exclusive lock on the node. */ LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) { if (hook == NULL || NG_HOOK_NOT_VALID(hook) || NG_NODE_NOT_VALID(NG_PEER_NODE(hook))) continue; if (strcmp(sa->hci_node, NG_PEER_NODE_NAME(hook)) == 0) { NG_SEND_DATA_ONLY(error, hook, m); /* sets m to NULL */ break; } } NG_FREE_M(nam); /* check for != NULL */ NG_FREE_M(m); } /* ng_btsocket_hci_raw_output */ /* * Check frame against security and socket filters. * d (direction bit) == 1 means incoming frame. */ static int ng_btsocket_hci_raw_filter(ng_btsocket_hci_raw_pcb_p pcb, struct mbuf *m, int d) { int type, event, opcode; mtx_assert(&pcb->pcb_mtx, MA_OWNED); switch ((type = *mtod(m, u_int8_t *))) { case NG_HCI_CMD_PKT: if (!(pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED)) { opcode = le16toh(mtod(m, ng_hci_cmd_pkt_t *)->opcode); if (!bit_test( ng_btsocket_hci_raw_sec_filter->commands[NG_HCI_OGF(opcode) - 1], NG_HCI_OCF(opcode) - 1)) return (EPERM); } if (d && !bit_test(pcb->filter.packet_mask, NG_HCI_CMD_PKT - 1)) return (EPERM); break; case NG_HCI_ACL_DATA_PKT: case NG_HCI_SCO_DATA_PKT: if (!(pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED) || !bit_test(pcb->filter.packet_mask, type - 1) || !d) return (EPERM); break; case NG_HCI_EVENT_PKT: if (!d) return (EINVAL); event = mtod(m, ng_hci_event_pkt_t *)->event - 1; if (!(pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED)) if (!bit_test(ng_btsocket_hci_raw_sec_filter->events, event)) return (EPERM); if (!bit_test(pcb->filter.event_mask, event)) return (EPERM); break; default: return (EINVAL); } return (0); } /* ng_btsocket_hci_raw_filter */ /* * Initialize everything */ void ng_btsocket_hci_raw_init(void) { bitstr_t *f = NULL; int error = 0; ng_btsocket_hci_raw_node = NULL; ng_btsocket_hci_raw_debug_level = NG_BTSOCKET_WARN_LEVEL; ng_btsocket_hci_raw_ioctl_timeout = 5; /* Register Netgraph node type */ error = ng_newtype(&typestruct); if (error != 0) { NG_BTSOCKET_HCI_RAW_ALERT( "%s: Could not register Netgraph node type, error=%d\n", __func__, error); return; } /* Create Netgrapg node */ error = ng_make_node_common(&typestruct, &ng_btsocket_hci_raw_node); if (error != 0) { NG_BTSOCKET_HCI_RAW_ALERT( "%s: Could not create Netgraph node, error=%d\n", __func__, error); ng_btsocket_hci_raw_node = NULL; return; } error = ng_name_node(ng_btsocket_hci_raw_node, NG_BTSOCKET_HCI_RAW_NODE_TYPE); if (error != 0) { NG_BTSOCKET_HCI_RAW_ALERT( "%s: Could not name Netgraph node, error=%d\n", __func__, error); NG_NODE_UNREF(ng_btsocket_hci_raw_node); ng_btsocket_hci_raw_node = NULL; return; } /* Create input queue */ NG_BT_ITEMQ_INIT(&ng_btsocket_hci_raw_queue, ifqmaxlen); mtx_init(&ng_btsocket_hci_raw_queue_mtx, "btsocks_hci_raw_queue_mtx", NULL, MTX_DEF); TASK_INIT(&ng_btsocket_hci_raw_task, 0, ng_btsocket_hci_raw_input, NULL); /* Create list of sockets */ LIST_INIT(&ng_btsocket_hci_raw_sockets); mtx_init(&ng_btsocket_hci_raw_sockets_mtx, "btsocks_hci_raw_sockets_mtx", NULL, MTX_DEF); /* Tokens */ ng_btsocket_hci_raw_token = 0; mtx_init(&ng_btsocket_hci_raw_token_mtx, "btsocks_hci_raw_token_mtx", NULL, MTX_DEF); /* * Security filter * XXX never FREE()ed */ ng_btsocket_hci_raw_sec_filter = NULL; MALLOC(ng_btsocket_hci_raw_sec_filter, struct ng_btsocket_hci_raw_sec_filter *, sizeof(struct ng_btsocket_hci_raw_sec_filter), M_NETGRAPH_BTSOCKET_HCI_RAW, M_NOWAIT|M_ZERO); if (ng_btsocket_hci_raw_sec_filter == NULL) { printf("%s: Could not allocate security filter!\n", __func__); return; } /* * XXX How paranoid can we get? * * Initialize security filter. If bit is set in the mask then * unprivileged socket is allowed to send (receive) this command * (event). */ /* Enable all events */ memset(&ng_btsocket_hci_raw_sec_filter->events, 0xff, sizeof(ng_btsocket_hci_raw_sec_filter->events)/ sizeof(ng_btsocket_hci_raw_sec_filter->events[0])); /* Disable some critical events */ f = ng_btsocket_hci_raw_sec_filter->events; bit_clear(f, NG_HCI_EVENT_RETURN_LINK_KEYS - 1); bit_clear(f, NG_HCI_EVENT_LINK_KEY_NOTIFICATION - 1); bit_clear(f, NG_HCI_EVENT_VENDOR - 1); /* Commands - Link control */ f = ng_btsocket_hci_raw_sec_filter->commands[NG_HCI_OGF_LINK_CONTROL-1]; bit_set(f, NG_HCI_OCF_INQUIRY - 1); bit_set(f, NG_HCI_OCF_INQUIRY_CANCEL - 1); bit_set(f, NG_HCI_OCF_PERIODIC_INQUIRY - 1); bit_set(f, NG_HCI_OCF_EXIT_PERIODIC_INQUIRY - 1); bit_set(f, NG_HCI_OCF_REMOTE_NAME_REQ - 1); bit_set(f, NG_HCI_OCF_READ_REMOTE_FEATURES - 1); bit_set(f, NG_HCI_OCF_READ_REMOTE_VER_INFO - 1); bit_set(f, NG_HCI_OCF_READ_CLOCK_OFFSET - 1); /* Commands - Link policy */ f = ng_btsocket_hci_raw_sec_filter->commands[NG_HCI_OGF_LINK_POLICY-1]; bit_set(f, NG_HCI_OCF_ROLE_DISCOVERY - 1); bit_set(f, NG_HCI_OCF_READ_LINK_POLICY_SETTINGS - 1); /* Commands - Host controller and baseband */ f = ng_btsocket_hci_raw_sec_filter->commands[NG_HCI_OGF_HC_BASEBAND-1]; bit_set(f, NG_HCI_OCF_READ_PIN_TYPE - 1); bit_set(f, NG_HCI_OCF_READ_LOCAL_NAME - 1); bit_set(f, NG_HCI_OCF_READ_CON_ACCEPT_TIMO - 1); bit_set(f, NG_HCI_OCF_READ_PAGE_TIMO - 1); bit_set(f, NG_HCI_OCF_READ_SCAN_ENABLE - 1); bit_set(f, NG_HCI_OCF_READ_PAGE_SCAN_ACTIVITY - 1); bit_set(f, NG_HCI_OCF_READ_INQUIRY_SCAN_ACTIVITY - 1); bit_set(f, NG_HCI_OCF_READ_AUTH_ENABLE - 1); bit_set(f, NG_HCI_OCF_READ_ENCRYPTION_MODE - 1); bit_set(f, NG_HCI_OCF_READ_UNIT_CLASS - 1); bit_set(f, NG_HCI_OCF_READ_VOICE_SETTINGS - 1); bit_set(f, NG_HCI_OCF_READ_AUTO_FLUSH_TIMO - 1); bit_set(f, NG_HCI_OCF_READ_NUM_BROADCAST_RETRANS - 1); bit_set(f, NG_HCI_OCF_READ_HOLD_MODE_ACTIVITY - 1); bit_set(f, NG_HCI_OCF_READ_XMIT_LEVEL - 1); bit_set(f, NG_HCI_OCF_READ_SCO_FLOW_CONTROL - 1); bit_set(f, NG_HCI_OCF_READ_LINK_SUPERVISION_TIMO - 1); bit_set(f, NG_HCI_OCF_READ_SUPPORTED_IAC_NUM - 1); bit_set(f, NG_HCI_OCF_READ_IAC_LAP - 1); bit_set(f, NG_HCI_OCF_READ_PAGE_SCAN_PERIOD - 1); bit_set(f, NG_HCI_OCF_READ_PAGE_SCAN - 1); /* Commands - Informational */ f = ng_btsocket_hci_raw_sec_filter->commands[NG_HCI_OGF_INFO - 1]; bit_set(f, NG_HCI_OCF_READ_LOCAL_VER - 1); bit_set(f, NG_HCI_OCF_READ_LOCAL_FEATURES - 1); bit_set(f, NG_HCI_OCF_READ_BUFFER_SIZE - 1); bit_set(f, NG_HCI_OCF_READ_COUNTRY_CODE - 1); bit_set(f, NG_HCI_OCF_READ_BDADDR - 1); /* Commands - Status */ f = ng_btsocket_hci_raw_sec_filter->commands[NG_HCI_OGF_STATUS - 1]; bit_set(f, NG_HCI_OCF_READ_FAILED_CONTACT_CNTR - 1); bit_set(f, NG_HCI_OCF_GET_LINK_QUALITY - 1); bit_set(f, NG_HCI_OCF_READ_RSSI - 1); /* Commands - Testing */ f = ng_btsocket_hci_raw_sec_filter->commands[NG_HCI_OGF_TESTING - 1]; bit_set(f, NG_HCI_OCF_READ_LOOPBACK_MODE - 1); } /* ng_btsocket_hci_raw_init */ /* * Abort connection on socket */ void ng_btsocket_hci_raw_abort(struct socket *so) { ng_btsocket_hci_raw_detach(so); } /* ng_btsocket_hci_raw_abort */ /* * Create new raw HCI socket */ int ng_btsocket_hci_raw_attach(struct socket *so, int proto, struct thread *td) { ng_btsocket_hci_raw_pcb_p pcb = so2hci_raw_pcb(so); int error = 0; if (pcb != NULL) return (EISCONN); if (ng_btsocket_hci_raw_node == NULL) return (EPROTONOSUPPORT); if (proto != BLUETOOTH_PROTO_HCI) return (EPROTONOSUPPORT); if (so->so_type != SOCK_RAW) return (ESOCKTNOSUPPORT); error = soreserve(so, NG_BTSOCKET_HCI_RAW_SENDSPACE, NG_BTSOCKET_HCI_RAW_RECVSPACE); if (error != 0) return (error); MALLOC(pcb, ng_btsocket_hci_raw_pcb_p, sizeof(*pcb), M_NETGRAPH_BTSOCKET_HCI_RAW, M_NOWAIT|M_ZERO); if (pcb == NULL) return (ENOMEM); so->so_pcb = (caddr_t) pcb; pcb->so = so; if (suser(td) == 0) pcb->flags |= NG_BTSOCKET_HCI_RAW_PRIVILEGED; /* * Set default socket filter. By default socket only accepts HCI * Command_Complete and Command_Status event packets. */ bit_set(pcb->filter.event_mask, NG_HCI_EVENT_COMMAND_COMPL - 1); bit_set(pcb->filter.event_mask, NG_HCI_EVENT_COMMAND_STATUS - 1); mtx_init(&pcb->pcb_mtx, "btsocks_hci_raw_pcb_mtx", NULL, MTX_DEF); mtx_lock(&ng_btsocket_hci_raw_sockets_mtx); LIST_INSERT_HEAD(&ng_btsocket_hci_raw_sockets, pcb, next); mtx_unlock(&ng_btsocket_hci_raw_sockets_mtx); return (0); } /* ng_btsocket_hci_raw_attach */ /* * Bind raw HCI socket */ int ng_btsocket_hci_raw_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { ng_btsocket_hci_raw_pcb_p pcb = so2hci_raw_pcb(so); struct sockaddr_hci *sa = (struct sockaddr_hci *) nam; if (pcb == NULL) return (EINVAL); if (ng_btsocket_hci_raw_node == NULL) return (EINVAL); if (sa == NULL) return (EINVAL); if (sa->hci_family != AF_BLUETOOTH) return (EAFNOSUPPORT); if (sa->hci_len != sizeof(*sa)) return (EINVAL); if (sa->hci_node[0] == 0) return (EINVAL); mtx_lock(&pcb->pcb_mtx); bcopy(sa, &pcb->addr, sizeof(pcb->addr)); mtx_unlock(&pcb->pcb_mtx); return (0); } /* ng_btsocket_hci_raw_bind */ /* * Connect raw HCI socket */ int ng_btsocket_hci_raw_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { ng_btsocket_hci_raw_pcb_p pcb = so2hci_raw_pcb(so); struct sockaddr_hci *sa = (struct sockaddr_hci *) nam; if (pcb == NULL) return (EINVAL); if (ng_btsocket_hci_raw_node == NULL) return (EINVAL); if (sa == NULL) return (EINVAL); if (sa->hci_family != AF_BLUETOOTH) return (EAFNOSUPPORT); if (sa->hci_len != sizeof(*sa)) return (EINVAL); if (sa->hci_node[0] == 0) return (EDESTADDRREQ); mtx_lock(&pcb->pcb_mtx); if (bcmp(sa, &pcb->addr, sizeof(pcb->addr)) != 0) { mtx_unlock(&pcb->pcb_mtx); return (EADDRNOTAVAIL); } soisconnected(so); mtx_unlock(&pcb->pcb_mtx); return (0); } /* ng_btsocket_hci_raw_connect */ /* * Process ioctl on socket */ int ng_btsocket_hci_raw_control(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp, struct thread *td) { ng_btsocket_hci_raw_pcb_p pcb = so2hci_raw_pcb(so); char path[NG_NODESIZ + 1]; struct ng_mesg *msg = NULL; int error = 0; if (pcb == NULL) return (EINVAL); if (ng_btsocket_hci_raw_node == NULL) return (EINVAL); mtx_lock(&pcb->pcb_mtx); /* Check if we have device name */ if (pcb->addr.hci_node[0] == 0) { mtx_unlock(&pcb->pcb_mtx); return (EHOSTUNREACH); } /* Check if we have pending ioctl() */ if (pcb->token != 0) { mtx_unlock(&pcb->pcb_mtx); return (EBUSY); } snprintf(path, sizeof(path), "%s:", pcb->addr.hci_node); switch (cmd) { case SIOC_HCI_RAW_NODE_GET_STATE: { struct ng_btsocket_hci_raw_node_state *p = (struct ng_btsocket_hci_raw_node_state *) data; error = ng_btsocket_hci_raw_send_sync_ngmsg(pcb, path, NGM_HCI_NODE_GET_STATE, &p->state, sizeof(p->state)); } break; case SIOC_HCI_RAW_NODE_INIT: if (pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED) error = ng_btsocket_hci_raw_send_ngmsg(path, NGM_HCI_NODE_INIT, NULL, 0); else error = EPERM; break; case SIOC_HCI_RAW_NODE_GET_DEBUG: { struct ng_btsocket_hci_raw_node_debug *p = (struct ng_btsocket_hci_raw_node_debug *) data; error = ng_btsocket_hci_raw_send_sync_ngmsg(pcb, path, NGM_HCI_NODE_GET_DEBUG, &p->debug, sizeof(p->debug)); } break; case SIOC_HCI_RAW_NODE_SET_DEBUG: { struct ng_btsocket_hci_raw_node_debug *p = (struct ng_btsocket_hci_raw_node_debug *) data; if (pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED) error = ng_btsocket_hci_raw_send_ngmsg(path, NGM_HCI_NODE_SET_DEBUG, &p->debug, sizeof(p->debug)); else error = EPERM; } break; case SIOC_HCI_RAW_NODE_GET_BUFFER: { struct ng_btsocket_hci_raw_node_buffer *p = (struct ng_btsocket_hci_raw_node_buffer *) data; error = ng_btsocket_hci_raw_send_sync_ngmsg(pcb, path, NGM_HCI_NODE_GET_BUFFER, &p->buffer, sizeof(p->buffer)); } break; case SIOC_HCI_RAW_NODE_GET_BDADDR: { struct ng_btsocket_hci_raw_node_bdaddr *p = (struct ng_btsocket_hci_raw_node_bdaddr *) data; error = ng_btsocket_hci_raw_send_sync_ngmsg(pcb, path, NGM_HCI_NODE_GET_BDADDR, &p->bdaddr, sizeof(p->bdaddr)); } break; case SIOC_HCI_RAW_NODE_GET_FEATURES: { struct ng_btsocket_hci_raw_node_features *p = (struct ng_btsocket_hci_raw_node_features *) data; error = ng_btsocket_hci_raw_send_sync_ngmsg(pcb, path, NGM_HCI_NODE_GET_FEATURES, &p->features, sizeof(p->features)); } break; case SIOC_HCI_RAW_NODE_GET_STAT: { struct ng_btsocket_hci_raw_node_stat *p = (struct ng_btsocket_hci_raw_node_stat *) data; error = ng_btsocket_hci_raw_send_sync_ngmsg(pcb, path, NGM_HCI_NODE_GET_STAT, &p->stat, sizeof(p->stat)); } break; case SIOC_HCI_RAW_NODE_RESET_STAT: if (pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED) error = ng_btsocket_hci_raw_send_ngmsg(path, NGM_HCI_NODE_RESET_STAT, NULL, 0); else error = EPERM; break; case SIOC_HCI_RAW_NODE_FLUSH_NEIGHBOR_CACHE: if (pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED) error = ng_btsocket_hci_raw_send_ngmsg(path, NGM_HCI_NODE_FLUSH_NEIGHBOR_CACHE, NULL, 0); else error = EPERM; break; case SIOC_HCI_RAW_NODE_GET_NEIGHBOR_CACHE: { struct ng_btsocket_hci_raw_node_neighbor_cache *p = (struct ng_btsocket_hci_raw_node_neighbor_cache *) data; ng_hci_node_get_neighbor_cache_ep *p1 = NULL; ng_hci_node_neighbor_cache_entry_ep *p2 = NULL; if (p->num_entries <= 0 || p->num_entries > NG_HCI_MAX_NEIGHBOR_NUM || p->entries == NULL) { error = EINVAL; break; } NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_NODE_GET_NEIGHBOR_CACHE, 0, M_NOWAIT); if (msg == NULL) { error = ENOMEM; break; } ng_btsocket_hci_raw_get_token(&msg->header.token); pcb->token = msg->header.token; pcb->msg = NULL; NG_SEND_MSG_PATH(error, ng_btsocket_hci_raw_node, msg, path, 0); if (error != 0) { pcb->token = 0; break; } error = msleep(&pcb->msg, &pcb->pcb_mtx, PZERO|PCATCH, "hcictl", ng_btsocket_hci_raw_ioctl_timeout * hz); pcb->token = 0; if (error != 0) break; if (pcb->msg != NULL && pcb->msg->header.cmd == NGM_HCI_NODE_GET_NEIGHBOR_CACHE) { /* Return data back to user space */ p1 = (ng_hci_node_get_neighbor_cache_ep *) (pcb->msg->data); p2 = (ng_hci_node_neighbor_cache_entry_ep *) (p1 + 1); p->num_entries = min(p->num_entries, p1->num_entries); if (p->num_entries > 0) error = copyout((caddr_t) p2, (caddr_t) p->entries, p->num_entries * sizeof(*p2)); } else error = EINVAL; NG_FREE_MSG(pcb->msg); /* checks for != NULL */ }break; case SIOC_HCI_RAW_NODE_GET_CON_LIST: { struct ng_btsocket_hci_raw_con_list *p = (struct ng_btsocket_hci_raw_con_list *) data; ng_hci_node_con_list_ep *p1 = NULL; ng_hci_node_con_ep *p2 = NULL; if (p->num_connections == 0 || p->num_connections > NG_HCI_MAX_CON_NUM || p->connections == NULL) { error = EINVAL; break; } NG_MKMESSAGE(msg, NGM_HCI_COOKIE, NGM_HCI_NODE_GET_CON_LIST, 0, M_NOWAIT); if (msg == NULL) { error = ENOMEM; break; } ng_btsocket_hci_raw_get_token(&msg->header.token); pcb->token = msg->header.token; pcb->msg = NULL; NG_SEND_MSG_PATH(error, ng_btsocket_hci_raw_node, msg, path, 0); if (error != 0) { pcb->token = 0; break; } error = msleep(&pcb->msg, &pcb->pcb_mtx, PZERO|PCATCH, "hcictl", ng_btsocket_hci_raw_ioctl_timeout * hz); pcb->token = 0; if (error != 0) break; if (pcb->msg != NULL && pcb->msg->header.cmd == NGM_HCI_NODE_GET_CON_LIST) { /* Return data back to user space */ p1 = (ng_hci_node_con_list_ep *)(pcb->msg->data); p2 = (ng_hci_node_con_ep *)(p1 + 1); p->num_connections = min(p->num_connections, p1->num_connections); if (p->num_connections > 0) error = copyout((caddr_t) p2, (caddr_t) p->connections, p->num_connections * sizeof(*p2)); } else error = EINVAL; NG_FREE_MSG(pcb->msg); /* checks for != NULL */ } break; case SIOC_HCI_RAW_NODE_GET_LINK_POLICY_MASK: { struct ng_btsocket_hci_raw_node_link_policy_mask *p = (struct ng_btsocket_hci_raw_node_link_policy_mask *) data; error = ng_btsocket_hci_raw_send_sync_ngmsg(pcb, path, NGM_HCI_NODE_GET_LINK_POLICY_SETTINGS_MASK, &p->policy_mask, sizeof(p->policy_mask)); } break; case SIOC_HCI_RAW_NODE_SET_LINK_POLICY_MASK: { struct ng_btsocket_hci_raw_node_link_policy_mask *p = (struct ng_btsocket_hci_raw_node_link_policy_mask *) data; if (pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED) error = ng_btsocket_hci_raw_send_ngmsg(path, NGM_HCI_NODE_SET_LINK_POLICY_SETTINGS_MASK, &p->policy_mask, sizeof(p->policy_mask)); else error = EPERM; } break; case SIOC_HCI_RAW_NODE_GET_PACKET_MASK: { struct ng_btsocket_hci_raw_node_packet_mask *p = (struct ng_btsocket_hci_raw_node_packet_mask *) data; error = ng_btsocket_hci_raw_send_sync_ngmsg(pcb, path, NGM_HCI_NODE_GET_PACKET_MASK, &p->packet_mask, sizeof(p->packet_mask)); } break; case SIOC_HCI_RAW_NODE_SET_PACKET_MASK: { struct ng_btsocket_hci_raw_node_packet_mask *p = (struct ng_btsocket_hci_raw_node_packet_mask *) data; if (pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED) error = ng_btsocket_hci_raw_send_ngmsg(path, NGM_HCI_NODE_SET_PACKET_MASK, &p->packet_mask, sizeof(p->packet_mask)); else error = EPERM; } break; case SIOC_HCI_RAW_NODE_GET_ROLE_SWITCH: { struct ng_btsocket_hci_raw_node_role_switch *p = (struct ng_btsocket_hci_raw_node_role_switch *) data; error = ng_btsocket_hci_raw_send_sync_ngmsg(pcb, path, NGM_HCI_NODE_GET_ROLE_SWITCH, &p->role_switch, sizeof(p->role_switch)); } break; case SIOC_HCI_RAW_NODE_SET_ROLE_SWITCH: { struct ng_btsocket_hci_raw_node_role_switch *p = (struct ng_btsocket_hci_raw_node_role_switch *) data; if (pcb->flags & NG_BTSOCKET_HCI_RAW_PRIVILEGED) error = ng_btsocket_hci_raw_send_ngmsg(path, NGM_HCI_NODE_SET_ROLE_SWITCH, &p->role_switch, sizeof(p->role_switch)); else error = EPERM; } break; default: error = EINVAL; break; } mtx_unlock(&pcb->pcb_mtx); return (error); } /* ng_btsocket_hci_raw_control */ /* * Process getsockopt/setsockopt system calls */ int ng_btsocket_hci_raw_ctloutput(struct socket *so, struct sockopt *sopt) { ng_btsocket_hci_raw_pcb_p pcb = so2hci_raw_pcb(so); struct ng_btsocket_hci_raw_filter filter; int error = 0, dir; if (pcb == NULL) return (EINVAL); if (ng_btsocket_hci_raw_node == NULL) return (EINVAL); if (sopt->sopt_level != SOL_HCI_RAW) return (0); mtx_lock(&pcb->pcb_mtx); switch (sopt->sopt_dir) { case SOPT_GET: switch (sopt->sopt_name) { case SO_HCI_RAW_FILTER: error = sooptcopyout(sopt, &pcb->filter, sizeof(pcb->filter)); break; case SO_HCI_RAW_DIRECTION: dir = (pcb->flags & NG_BTSOCKET_HCI_RAW_DIRECTION)?1:0; error = sooptcopyout(sopt, &dir, sizeof(dir)); break; default: error = EINVAL; break; } break; case SOPT_SET: switch (sopt->sopt_name) { case SO_HCI_RAW_FILTER: error = sooptcopyin(sopt, &filter, sizeof(filter), sizeof(filter)); if (error == 0) bcopy(&filter, &pcb->filter, sizeof(pcb->filter)); break; case SO_HCI_RAW_DIRECTION: error = sooptcopyin(sopt, &dir, sizeof(dir), sizeof(dir)); if (error != 0) break; if (dir) pcb->flags |= NG_BTSOCKET_HCI_RAW_DIRECTION; else pcb->flags &= ~NG_BTSOCKET_HCI_RAW_DIRECTION; break; default: error = EINVAL; break; } break; default: error = EINVAL; break; } mtx_unlock(&pcb->pcb_mtx); return (error); } /* ng_btsocket_hci_raw_ctloutput */ /* * Detach raw HCI socket */ -int +void ng_btsocket_hci_raw_detach(struct socket *so) { ng_btsocket_hci_raw_pcb_p pcb = so2hci_raw_pcb(so); - if (pcb == NULL) - return (EINVAL); + KASSERT(pcb != NULL, ("ng_btsocket_hci_raw_detach: pcb == NULL")); + if (ng_btsocket_hci_raw_node == NULL) - return (EINVAL); + return; mtx_lock(&ng_btsocket_hci_raw_sockets_mtx); mtx_lock(&pcb->pcb_mtx); LIST_REMOVE(pcb, next); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_hci_raw_sockets_mtx); mtx_destroy(&pcb->pcb_mtx); bzero(pcb, sizeof(*pcb)); FREE(pcb, M_NETGRAPH_BTSOCKET_HCI_RAW); - ACCEPT_LOCK(); - SOCK_LOCK(so); so->so_pcb = NULL; - sotryfree(so); - - return (0); } /* ng_btsocket_hci_raw_detach */ /* * Disconnect raw HCI socket */ int ng_btsocket_hci_raw_disconnect(struct socket *so) { ng_btsocket_hci_raw_pcb_p pcb = so2hci_raw_pcb(so); if (pcb == NULL) return (EINVAL); if (ng_btsocket_hci_raw_node == NULL) return (EINVAL); mtx_lock(&pcb->pcb_mtx); soisdisconnected(so); mtx_unlock(&pcb->pcb_mtx); return (0); } /* ng_btsocket_hci_raw_disconnect */ /* * Get socket peer's address */ int ng_btsocket_hci_raw_peeraddr(struct socket *so, struct sockaddr **nam) { return (ng_btsocket_hci_raw_sockaddr(so, nam)); } /* ng_btsocket_hci_raw_peeraddr */ /* * Send data */ int ng_btsocket_hci_raw_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *sa, struct mbuf *control, struct thread *td) { ng_btsocket_hci_raw_pcb_p pcb = so2hci_raw_pcb(so); struct mbuf *nam = NULL; int error = 0; if (ng_btsocket_hci_raw_node == NULL) { error = ENETDOWN; goto drop; } if (pcb == NULL) { error = EINVAL; goto drop; } if (control != NULL) { error = EINVAL; goto drop; } if (m->m_pkthdr.len < sizeof(ng_hci_cmd_pkt_t) || m->m_pkthdr.len > sizeof(ng_hci_cmd_pkt_t) + NG_HCI_CMD_PKT_SIZE) { error = EMSGSIZE; goto drop; } if (m->m_len < sizeof(ng_hci_cmd_pkt_t)) { if ((m = m_pullup(m, sizeof(ng_hci_cmd_pkt_t))) == NULL) { error = ENOBUFS; goto drop; } } if (*mtod(m, u_int8_t *) != NG_HCI_CMD_PKT) { error = ENOTSUP; goto drop; } mtx_lock(&pcb->pcb_mtx); error = ng_btsocket_hci_raw_filter(pcb, m, 0); if (error != 0) { mtx_unlock(&pcb->pcb_mtx); goto drop; } if (sa == NULL) { if (pcb->addr.hci_node[0] == 0) { mtx_unlock(&pcb->pcb_mtx); error = EDESTADDRREQ; goto drop; } sa = (struct sockaddr *) &pcb->addr; } MGET(nam, M_DONTWAIT, MT_SONAME); if (nam == NULL) { mtx_unlock(&pcb->pcb_mtx); error = ENOBUFS; goto drop; } nam->m_len = sizeof(struct sockaddr_hci); bcopy(sa,mtod(nam, struct sockaddr_hci *),sizeof(struct sockaddr_hci)); nam->m_next = m; m = NULL; mtx_unlock(&pcb->pcb_mtx); return (ng_send_fn(ng_btsocket_hci_raw_node, NULL, ng_btsocket_hci_raw_output, nam, 0)); drop: NG_FREE_M(control); /* NG_FREE_M checks for != NULL */ NG_FREE_M(nam); NG_FREE_M(m); return (error); } /* ng_btsocket_hci_raw_send */ /* * Get socket address */ int ng_btsocket_hci_raw_sockaddr(struct socket *so, struct sockaddr **nam) { ng_btsocket_hci_raw_pcb_p pcb = so2hci_raw_pcb(so); struct sockaddr_hci sa; if (pcb == NULL) return (EINVAL); if (ng_btsocket_hci_raw_node == NULL) return (EINVAL); bzero(&sa, sizeof(sa)); sa.hci_len = sizeof(sa); sa.hci_family = AF_BLUETOOTH; mtx_lock(&pcb->pcb_mtx); strlcpy(sa.hci_node, pcb->addr.hci_node, sizeof(sa.hci_node)); mtx_unlock(&pcb->pcb_mtx); *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); return ((*nam == NULL)? ENOMEM : 0); } /* ng_btsocket_hci_raw_sockaddr */ diff --git a/sys/netgraph/bluetooth/socket/ng_btsocket_l2cap.c b/sys/netgraph/bluetooth/socket/ng_btsocket_l2cap.c index 09b5d76fd7b9..21d3766eb742 100644 --- a/sys/netgraph/bluetooth/socket/ng_btsocket_l2cap.c +++ b/sys/netgraph/bluetooth/socket/ng_btsocket_l2cap.c @@ -1,2872 +1,2867 @@ /* * ng_btsocket_l2cap.c */ /*- * Copyright (c) 2001-2002 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ng_btsocket_l2cap.c,v 1.16 2003/09/14 23:29:06 max Exp $ * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* MALLOC define */ #ifdef NG_SEPARATE_MALLOC MALLOC_DEFINE(M_NETGRAPH_BTSOCKET_L2CAP, "netgraph_btsocks_l2cap", "Netgraph Bluetooth L2CAP sockets"); #else #define M_NETGRAPH_BTSOCKET_L2CAP M_NETGRAPH #endif /* NG_SEPARATE_MALLOC */ /* Netgraph node methods */ static ng_constructor_t ng_btsocket_l2cap_node_constructor; static ng_rcvmsg_t ng_btsocket_l2cap_node_rcvmsg; static ng_shutdown_t ng_btsocket_l2cap_node_shutdown; static ng_newhook_t ng_btsocket_l2cap_node_newhook; static ng_connect_t ng_btsocket_l2cap_node_connect; static ng_rcvdata_t ng_btsocket_l2cap_node_rcvdata; static ng_disconnect_t ng_btsocket_l2cap_node_disconnect; static void ng_btsocket_l2cap_input (void *, int); static void ng_btsocket_l2cap_rtclean (void *, int); /* Netgraph type descriptor */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_BTSOCKET_L2CAP_NODE_TYPE, .constructor = ng_btsocket_l2cap_node_constructor, .rcvmsg = ng_btsocket_l2cap_node_rcvmsg, .shutdown = ng_btsocket_l2cap_node_shutdown, .newhook = ng_btsocket_l2cap_node_newhook, .connect = ng_btsocket_l2cap_node_connect, .rcvdata = ng_btsocket_l2cap_node_rcvdata, .disconnect = ng_btsocket_l2cap_node_disconnect, }; /* Globals */ extern int ifqmaxlen; static u_int32_t ng_btsocket_l2cap_debug_level; static node_p ng_btsocket_l2cap_node; static struct ng_bt_itemq ng_btsocket_l2cap_queue; static struct mtx ng_btsocket_l2cap_queue_mtx; static struct task ng_btsocket_l2cap_queue_task; static LIST_HEAD(, ng_btsocket_l2cap_pcb) ng_btsocket_l2cap_sockets; static struct mtx ng_btsocket_l2cap_sockets_mtx; static LIST_HEAD(, ng_btsocket_l2cap_rtentry) ng_btsocket_l2cap_rt; static struct mtx ng_btsocket_l2cap_rt_mtx; static struct task ng_btsocket_l2cap_rt_task; /* Sysctl tree */ SYSCTL_DECL(_net_bluetooth_l2cap_sockets); SYSCTL_NODE(_net_bluetooth_l2cap_sockets, OID_AUTO, seq, CTLFLAG_RW, 0, "Bluetooth SEQPACKET L2CAP sockets family"); SYSCTL_INT(_net_bluetooth_l2cap_sockets_seq, OID_AUTO, debug_level, CTLFLAG_RW, &ng_btsocket_l2cap_debug_level, NG_BTSOCKET_WARN_LEVEL, "Bluetooth SEQPACKET L2CAP sockets debug level"); SYSCTL_INT(_net_bluetooth_l2cap_sockets_seq, OID_AUTO, queue_len, CTLFLAG_RD, &ng_btsocket_l2cap_queue.len, 0, "Bluetooth SEQPACKET L2CAP sockets input queue length"); SYSCTL_INT(_net_bluetooth_l2cap_sockets_seq, OID_AUTO, queue_maxlen, CTLFLAG_RD, &ng_btsocket_l2cap_queue.maxlen, 0, "Bluetooth SEQPACKET L2CAP sockets input queue max. length"); SYSCTL_INT(_net_bluetooth_l2cap_sockets_seq, OID_AUTO, queue_drops, CTLFLAG_RD, &ng_btsocket_l2cap_queue.drops, 0, "Bluetooth SEQPACKET L2CAP sockets input queue drops"); /* Debug */ #define NG_BTSOCKET_L2CAP_INFO \ if (ng_btsocket_l2cap_debug_level >= NG_BTSOCKET_INFO_LEVEL) \ printf #define NG_BTSOCKET_L2CAP_WARN \ if (ng_btsocket_l2cap_debug_level >= NG_BTSOCKET_WARN_LEVEL) \ printf #define NG_BTSOCKET_L2CAP_ERR \ if (ng_btsocket_l2cap_debug_level >= NG_BTSOCKET_ERR_LEVEL) \ printf #define NG_BTSOCKET_L2CAP_ALERT \ if (ng_btsocket_l2cap_debug_level >= NG_BTSOCKET_ALERT_LEVEL) \ printf /* * Netgraph message processing routines */ static int ng_btsocket_l2cap_process_l2ca_con_req_rsp (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p, struct socket **); static int ng_btsocket_l2cap_process_l2ca_con_rsp_rsp (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p, struct socket **); static int ng_btsocket_l2cap_process_l2ca_con_ind (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p, struct socket **); static int ng_btsocket_l2cap_process_l2ca_cfg_req_rsp (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p, struct socket **); static int ng_btsocket_l2cap_process_l2ca_cfg_rsp_rsp (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p, struct socket **); static int ng_btsocket_l2cap_process_l2ca_cfg_ind (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p, struct socket **); static int ng_btsocket_l2cap_process_l2ca_discon_rsp (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p, struct socket **); static int ng_btsocket_l2cap_process_l2ca_discon_ind (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p, struct socket **); static int ng_btsocket_l2cap_process_l2ca_write_rsp (struct ng_mesg *, ng_btsocket_l2cap_rtentry_p, struct socket **); /* * Send L2CA_xxx messages to the lower layer */ static int ng_btsocket_l2cap_send_l2ca_con_req (ng_btsocket_l2cap_pcb_p); static int ng_btsocket_l2cap_send_l2ca_con_rsp_req (u_int32_t, ng_btsocket_l2cap_rtentry_p, bdaddr_p, int, int, int); static int ng_btsocket_l2cap_send_l2ca_cfg_req (ng_btsocket_l2cap_pcb_p); static int ng_btsocket_l2cap_send_l2ca_cfg_rsp (ng_btsocket_l2cap_pcb_p); static int ng_btsocket_l2cap_send_l2ca_discon_req (u_int32_t, ng_btsocket_l2cap_pcb_p); static int ng_btsocket_l2cap_send2 (ng_btsocket_l2cap_pcb_p); /* * Timeout processing routines */ static void ng_btsocket_l2cap_timeout (ng_btsocket_l2cap_pcb_p); static void ng_btsocket_l2cap_untimeout (ng_btsocket_l2cap_pcb_p); static void ng_btsocket_l2cap_process_timeout (void *); /* * Other stuff */ static ng_btsocket_l2cap_pcb_p ng_btsocket_l2cap_pcb_by_addr(bdaddr_p, int); static ng_btsocket_l2cap_pcb_p ng_btsocket_l2cap_pcb_by_token(u_int32_t); static ng_btsocket_l2cap_pcb_p ng_btsocket_l2cap_pcb_by_cid (bdaddr_p, int); static int ng_btsocket_l2cap_result2errno(int); #define ng_btsocket_l2cap_wakeup_input_task() \ taskqueue_enqueue(taskqueue_swi_giant, &ng_btsocket_l2cap_queue_task) #define ng_btsocket_l2cap_wakeup_route_task() \ taskqueue_enqueue(taskqueue_swi_giant, &ng_btsocket_l2cap_rt_task) /***************************************************************************** ***************************************************************************** ** Netgraph node interface ***************************************************************************** *****************************************************************************/ /* * Netgraph node constructor. Do not allow to create node of this type. */ static int ng_btsocket_l2cap_node_constructor(node_p node) { return (EINVAL); } /* ng_btsocket_l2cap_node_constructor */ /* * Do local shutdown processing. Let old node go and create new fresh one. */ static int ng_btsocket_l2cap_node_shutdown(node_p node) { int error = 0; NG_NODE_UNREF(node); /* Create new node */ error = ng_make_node_common(&typestruct, &ng_btsocket_l2cap_node); if (error != 0) { NG_BTSOCKET_L2CAP_ALERT( "%s: Could not create Netgraph node, error=%d\n", __func__, error); ng_btsocket_l2cap_node = NULL; return (error); } error = ng_name_node(ng_btsocket_l2cap_node, NG_BTSOCKET_L2CAP_NODE_TYPE); if (error != 0) { NG_BTSOCKET_L2CAP_ALERT( "%s: Could not name Netgraph node, error=%d\n", __func__, error); NG_NODE_UNREF(ng_btsocket_l2cap_node); ng_btsocket_l2cap_node = NULL; return (error); } return (0); } /* ng_btsocket_l2cap_node_shutdown */ /* * We allow any hook to be connected to the node. */ static int ng_btsocket_l2cap_node_newhook(node_p node, hook_p hook, char const *name) { return (0); } /* ng_btsocket_l2cap_node_newhook */ /* * Just say "YEP, that's OK by me!" */ static int ng_btsocket_l2cap_node_connect(hook_p hook) { NG_HOOK_SET_PRIVATE(hook, NULL); NG_HOOK_REF(hook); /* Keep extra reference to the hook */ #if 0 NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); NG_HOOK_FORCE_QUEUE(hook); #endif return (0); } /* ng_btsocket_l2cap_node_connect */ /* * Hook disconnection. Schedule route cleanup task */ static int ng_btsocket_l2cap_node_disconnect(hook_p hook) { /* * If hook has private information than we must have this hook in * the routing table and must schedule cleaning for the routing table. * Otherwise hook was connected but we never got "hook_info" message, * so we have never added this hook to the routing table and it save * to just delete it. */ if (NG_HOOK_PRIVATE(hook) != NULL) return (ng_btsocket_l2cap_wakeup_route_task()); NG_HOOK_UNREF(hook); /* Remove extra reference */ return (0); } /* ng_btsocket_l2cap_node_disconnect */ /* * Process incoming messages */ static int ng_btsocket_l2cap_node_rcvmsg(node_p node, item_p item, hook_p hook) { struct ng_mesg *msg = NGI_MSG(item); /* item still has message */ int error = 0; if (msg != NULL && msg->header.typecookie == NGM_L2CAP_COOKIE) { mtx_lock(&ng_btsocket_l2cap_queue_mtx); if (NG_BT_ITEMQ_FULL(&ng_btsocket_l2cap_queue)) { NG_BTSOCKET_L2CAP_ERR( "%s: Input queue is full (msg)\n", __func__); NG_BT_ITEMQ_DROP(&ng_btsocket_l2cap_queue); NG_FREE_ITEM(item); error = ENOBUFS; } else { if (hook != NULL) { NG_HOOK_REF(hook); NGI_SET_HOOK(item, hook); } NG_BT_ITEMQ_ENQUEUE(&ng_btsocket_l2cap_queue, item); error = ng_btsocket_l2cap_wakeup_input_task(); } mtx_unlock(&ng_btsocket_l2cap_queue_mtx); } else { NG_FREE_ITEM(item); error = EINVAL; } return (error); } /* ng_btsocket_l2cap_node_rcvmsg */ /* * Receive data on a hook */ static int ng_btsocket_l2cap_node_rcvdata(hook_p hook, item_p item) { int error = 0; mtx_lock(&ng_btsocket_l2cap_queue_mtx); if (NG_BT_ITEMQ_FULL(&ng_btsocket_l2cap_queue)) { NG_BTSOCKET_L2CAP_ERR( "%s: Input queue is full (data)\n", __func__); NG_BT_ITEMQ_DROP(&ng_btsocket_l2cap_queue); NG_FREE_ITEM(item); error = ENOBUFS; } else { NG_HOOK_REF(hook); NGI_SET_HOOK(item, hook); NG_BT_ITEMQ_ENQUEUE(&ng_btsocket_l2cap_queue, item); error = ng_btsocket_l2cap_wakeup_input_task(); } mtx_unlock(&ng_btsocket_l2cap_queue_mtx); return (error); } /* ng_btsocket_l2cap_node_rcvdata */ /* * Process L2CA_Connect respose. Socket layer must have initiated connection, * so we have to have a socket associated with message token. */ static int ng_btsocket_l2cap_process_l2ca_con_req_rsp(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt, struct socket **sop) { ng_l2cap_l2ca_con_op *op = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL; int error = 0; if (msg->header.arglen != sizeof(*op)) return (EMSGSIZE); op = (ng_l2cap_l2ca_con_op *)(msg->data); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* Look for the socket with the token */ pcb = ng_btsocket_l2cap_pcb_by_token(msg->header.token); if (pcb == NULL) { mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CA_Connect response, token=%d, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, result=%d, status=%d, " \ "state=%d\n", __func__, msg->header.token, pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], pcb->psm, op->lcid, op->result, op->status, pcb->state); if (pcb->state != NG_BTSOCKET_L2CAP_CONNECTING) { mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } ng_btsocket_l2cap_untimeout(pcb); if (op->result == NG_L2CAP_PENDING) { ng_btsocket_l2cap_timeout(pcb); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (0); } if (op->result == NG_L2CAP_SUCCESS) { /* * Channel is now open, so update local channel ID and * start configuration process. Source and destination * addresses as well as route must be already set. */ pcb->cid = op->lcid; error = ng_btsocket_l2cap_send_l2ca_cfg_req(pcb); if (error != 0) { /* Send disconnect request with "zero" token */ ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); /* ... and close the socket */ pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); if (pcb->so->so_state & SS_NOFDREF) *sop = pcb->so; } else { pcb->cfg_state = NG_BTSOCKET_L2CAP_CFG_IN_SENT; pcb->state = NG_BTSOCKET_L2CAP_CONFIGURING; ng_btsocket_l2cap_timeout(pcb); } } else { /* * We have failed to open connection, so convert result * code to "errno" code and disconnect the socket. Channel * already has been closed. */ pcb->so->so_error = ng_btsocket_l2cap_result2errno(op->result); pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); if (pcb->so->so_state & SS_NOFDREF) *sop = pcb->so; } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (error); } /* ng_btsocket_l2cap_process_l2ca_con_req_rsp */ /* * Process L2CA_ConnectRsp response */ static int ng_btsocket_l2cap_process_l2ca_con_rsp_rsp(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt, struct socket **sop) { ng_l2cap_l2ca_con_rsp_op *op = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL; if (msg->header.arglen != sizeof(*op)) return (EMSGSIZE); op = (ng_l2cap_l2ca_con_rsp_op *)(msg->data); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* Look for the socket with the token */ pcb = ng_btsocket_l2cap_pcb_by_token(msg->header.token); if (pcb == NULL) { mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CA_ConnectRsp response, token=%d, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, result=%d, state=%d\n", __func__, msg->header.token, pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], pcb->psm, pcb->cid, op->result, pcb->state); if (pcb->state != NG_BTSOCKET_L2CAP_CONNECTING) { mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } ng_btsocket_l2cap_untimeout(pcb); /* Check the result and disconnect the socket on failure */ if (op->result != NG_L2CAP_SUCCESS) { /* Close the socket - channel already closed */ pcb->so->so_error = ng_btsocket_l2cap_result2errno(op->result); pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); if (pcb->so->so_state & SS_NOFDREF) *sop = pcb->so; } else { /* Move to CONFIGURING state and wait for CONFIG_IND */ pcb->cfg_state = 0; pcb->state = NG_BTSOCKET_L2CAP_CONFIGURING; ng_btsocket_l2cap_timeout(pcb); } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (0); } /* ng_btsocket_process_l2ca_con_rsp_rsp */ /* * Process L2CA_Connect indicator. Find socket that listens on address * and PSM. Find exact or closest match. Create new socket and initiate * connection. */ static int ng_btsocket_l2cap_process_l2ca_con_ind(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt, struct socket **sop) { ng_l2cap_l2ca_con_ind_ip *ip = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL, *pcb1 = NULL; int error = 0; u_int32_t token = 0; u_int16_t result = 0; if (msg->header.arglen != sizeof(*ip)) return (EMSGSIZE); ip = (ng_l2cap_l2ca_con_ind_ip *)(msg->data); NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CA_Connect indicator, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, ident=%d\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], ip->bdaddr.b[5], ip->bdaddr.b[4], ip->bdaddr.b[3], ip->bdaddr.b[2], ip->bdaddr.b[1], ip->bdaddr.b[0], ip->psm, ip->lcid, ip->ident); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); pcb = ng_btsocket_l2cap_pcb_by_addr(&rt->src, ip->psm); if (pcb != NULL) { struct socket *so1 = NULL; mtx_lock(&pcb->pcb_mtx); /* * First check the pending connections queue and if we have * space then create new socket and set proper source address. */ if (pcb->so->so_qlen <= pcb->so->so_qlimit) so1 = sonewconn(pcb->so, 0); if (so1 == NULL) { result = NG_L2CAP_NO_RESOURCES; goto respond; } /* * If we got here than we have created new socket. So complete * connection. If we we listening on specific address then copy * source address from listening socket, otherwise copy source * address from hook's routing information. */ pcb1 = so2l2cap_pcb(so1); KASSERT((pcb1 != NULL), ("%s: pcb1 == NULL\n", __func__)); mtx_lock(&pcb1->pcb_mtx); if (bcmp(&pcb->src, NG_HCI_BDADDR_ANY, sizeof(pcb->src)) != 0) bcopy(&pcb->src, &pcb1->src, sizeof(pcb1->src)); else bcopy(&rt->src, &pcb1->src, sizeof(pcb1->src)); pcb1->flags &= ~NG_BTSOCKET_L2CAP_CLIENT; bcopy(&ip->bdaddr, &pcb1->dst, sizeof(pcb1->dst)); pcb1->psm = ip->psm; pcb1->cid = ip->lcid; pcb1->rt = rt; /* Copy socket settings */ pcb1->imtu = pcb->imtu; bcopy(&pcb->oflow, &pcb1->oflow, sizeof(pcb1->oflow)); pcb1->flush_timo = pcb->flush_timo; token = pcb1->token; } else /* Nobody listens on requested BDADDR/PSM */ result = NG_L2CAP_PSM_NOT_SUPPORTED; respond: error = ng_btsocket_l2cap_send_l2ca_con_rsp_req(token, rt, &ip->bdaddr, ip->ident, ip->lcid, result); if (pcb1 != NULL) { if (error != 0) { pcb1->so->so_error = error; pcb1->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb1->so); if (pcb1->so->so_state & SS_NOFDREF) *sop = pcb1->so; } else { pcb1->state = NG_BTSOCKET_L2CAP_CONNECTING; soisconnecting(pcb1->so); ng_btsocket_l2cap_timeout(pcb1); } mtx_unlock(&pcb1->pcb_mtx); } if (pcb != NULL) mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (error); } /* ng_btsocket_l2cap_process_l2ca_con_ind */ /* * Process L2CA_Config response */ static int ng_btsocket_l2cap_process_l2ca_cfg_req_rsp(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt, struct socket **sop) { ng_l2cap_l2ca_cfg_op *op = NULL; ng_btsocket_l2cap_pcb_p pcb = NULL; if (msg->header.arglen != sizeof(*op)) return (EMSGSIZE); op = (ng_l2cap_l2ca_cfg_op *)(msg->data); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* * Socket must have issued a Configure request, so we must have a * socket that wants to be configured. Use Netgraph message token * to find it */ pcb = ng_btsocket_l2cap_pcb_by_token(msg->header.token); if (pcb == NULL) { /* * XXX FIXME what to do here? We could not find a * socket with requested token. We even can not send * Disconnect, because we do not know channel ID */ mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CA_Config response, token=%d, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, result=%d, state=%d, " \ "cfg_state=%x\n", __func__, msg->header.token, pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], pcb->psm, pcb->cid, op->result, pcb->state, pcb->cfg_state); if (pcb->state != NG_BTSOCKET_L2CAP_CONFIGURING) { mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } if (op->result == NG_L2CAP_SUCCESS) { /* * XXX FIXME Actually set flush and link timeout. * Set QoS here if required. Resolve conficts (flush_timo). * Save incoming MTU (peer's outgoing MTU) and outgoing flow * spec. */ pcb->imtu = op->imtu; bcopy(&op->oflow, &pcb->oflow, sizeof(pcb->oflow)); pcb->flush_timo = op->flush_timo; /* * We have configured incoming side, so record it and check * if configuration is complete. If complete then mark socket * as connected, otherwise wait for the peer. */ pcb->cfg_state &= ~NG_BTSOCKET_L2CAP_CFG_IN_SENT; pcb->cfg_state |= NG_BTSOCKET_L2CAP_CFG_IN; if (pcb->cfg_state == NG_BTSOCKET_L2CAP_CFG_BOTH) { /* Configuration complete - mark socket as open */ ng_btsocket_l2cap_untimeout(pcb); pcb->state = NG_BTSOCKET_L2CAP_OPEN; soisconnected(pcb->so); } } else { /* * Something went wrong. Could be unacceptable parameters, * reject or unknown option. That's too bad, but we will * not negotiate. Send Disconnect and close the channel. */ ng_btsocket_l2cap_untimeout(pcb); switch (op->result) { case NG_L2CAP_UNACCEPTABLE_PARAMS: case NG_L2CAP_UNKNOWN_OPTION: pcb->so->so_error = EINVAL; break; default: pcb->so->so_error = ECONNRESET; break; } /* Send disconnect with "zero" token */ ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); /* ... and close the socket */ pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); if (pcb->so->so_state & SS_NOFDREF) *sop = pcb->so; } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (0); } /* ng_btsocket_l2cap_process_l2ca_cfg_req_rsp */ /* * Process L2CA_ConfigRsp response */ static int ng_btsocket_l2cap_process_l2ca_cfg_rsp_rsp(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt, struct socket **sop) { ng_l2cap_l2ca_cfg_rsp_op *op = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL; int error = 0; if (msg->header.arglen != sizeof(*op)) return (EMSGSIZE); op = (ng_l2cap_l2ca_cfg_rsp_op *)(msg->data); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* Look for the socket with the token */ pcb = ng_btsocket_l2cap_pcb_by_token(msg->header.token); if (pcb == NULL) { mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CA_ConfigRsp response, token=%d, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, result=%d, state=%d, " \ "cfg_state=%x\n", __func__, msg->header.token, pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], pcb->psm, pcb->cid, op->result, pcb->state, pcb->cfg_state); if (pcb->state != NG_BTSOCKET_L2CAP_CONFIGURING) { mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } /* Check the result and disconnect socket of failure */ if (op->result != NG_L2CAP_SUCCESS) goto disconnect; /* * Now we done with remote side configuration. Configure local * side if we have not done it yet. */ pcb->cfg_state &= ~NG_BTSOCKET_L2CAP_CFG_OUT_SENT; pcb->cfg_state |= NG_BTSOCKET_L2CAP_CFG_OUT; if (pcb->cfg_state == NG_BTSOCKET_L2CAP_CFG_BOTH) { /* Configuration complete - mask socket as open */ ng_btsocket_l2cap_untimeout(pcb); pcb->state = NG_BTSOCKET_L2CAP_OPEN; soisconnected(pcb->so); } else { if (!(pcb->cfg_state & NG_BTSOCKET_L2CAP_CFG_IN_SENT)) { /* Send L2CA_Config request - incoming path */ error = ng_btsocket_l2cap_send_l2ca_cfg_req(pcb); if (error != 0) goto disconnect; pcb->cfg_state |= NG_BTSOCKET_L2CAP_CFG_IN_SENT; } } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (error); disconnect: ng_btsocket_l2cap_untimeout(pcb); /* Send disconnect with "zero" token */ ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); /* ... and close the socket */ pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); if (pcb->so->so_state & SS_NOFDREF) *sop = pcb->so; mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (error); } /* ng_btsocket_l2cap_process_l2ca_cfg_rsp_rsp */ /* * Process L2CA_Config indicator */ static int ng_btsocket_l2cap_process_l2ca_cfg_ind(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt, struct socket **sop) { ng_l2cap_l2ca_cfg_ind_ip *ip = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL; int error = 0; if (msg->header.arglen != sizeof(*ip)) return (EMSGSIZE); ip = (ng_l2cap_l2ca_cfg_ind_ip *)(msg->data); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* Check for the open socket that has given channel ID */ pcb = ng_btsocket_l2cap_pcb_by_cid(&rt->src, ip->lcid); if (pcb == NULL) { mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CA_Config indicator, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, state=%d, cfg_state=%x\n", __func__, pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], pcb->psm, pcb->cid, pcb->state, pcb->cfg_state); /* XXX FIXME re-configuration on open socket */ if (pcb->state != NG_BTSOCKET_L2CAP_CONFIGURING) { mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } /* * XXX FIXME Actually set flush and link timeout. Set QoS here if * required. Resolve conficts (flush_timo). Note outgoing MTU (peer's * incoming MTU) and incoming flow spec. */ pcb->omtu = ip->omtu; bcopy(&ip->iflow, &pcb->iflow, sizeof(pcb->iflow)); pcb->flush_timo = ip->flush_timo; /* * Send L2CA_Config response to our peer and check for the errors, * if any send disconnect to close the channel. */ if (!(pcb->cfg_state & NG_BTSOCKET_L2CAP_CFG_OUT_SENT)) { error = ng_btsocket_l2cap_send_l2ca_cfg_rsp(pcb); if (error != 0) { ng_btsocket_l2cap_untimeout(pcb); pcb->so->so_error = error; /* Send disconnect with "zero" token */ ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); /* ... and close the socket */ pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); if (pcb->so->so_state & SS_NOFDREF) *sop = pcb->so; } else pcb->cfg_state |= NG_BTSOCKET_L2CAP_CFG_OUT_SENT; } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (error); } /* ng_btsocket_l2cap_process_l2cap_cfg_ind */ /* * Process L2CA_Disconnect response */ static int ng_btsocket_l2cap_process_l2ca_discon_rsp(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt, struct socket **sop) { ng_l2cap_l2ca_discon_op *op = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL; /* Check message */ if (msg->header.arglen != sizeof(*op)) return (EMSGSIZE); op = (ng_l2cap_l2ca_discon_op *)(msg->data); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* * Socket layer must have issued L2CA_Disconnect request, so there * must be a socket that wants to be disconnected. Use Netgraph * message token to find it. */ pcb = ng_btsocket_l2cap_pcb_by_token(msg->header.token); if (pcb == NULL) { mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (0); } mtx_lock(&pcb->pcb_mtx); /* XXX Close socket no matter what op->result says */ if (pcb->state != NG_BTSOCKET_L2CAP_CLOSED) { NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CA_Disconnect response, token=%d, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, result=%d, state=%d\n", __func__, msg->header.token, pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], pcb->psm, pcb->cid, op->result, pcb->state); ng_btsocket_l2cap_untimeout(pcb); pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); if (pcb->so->so_state & SS_NOFDREF) *sop = pcb->so; } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (0); } /* ng_btsocket_l2cap_process_l2ca_discon_rsp */ /* * Process L2CA_Disconnect indicator */ static int ng_btsocket_l2cap_process_l2ca_discon_ind(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt, struct socket **sop) { ng_l2cap_l2ca_discon_ind_ip *ip = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL; /* Check message */ if (msg->header.arglen != sizeof(*ip)) return (EMSGSIZE); ip = (ng_l2cap_l2ca_discon_ind_ip *)(msg->data); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* Look for the socket with given channel ID */ pcb = ng_btsocket_l2cap_pcb_by_cid(&rt->src, ip->lcid); if (pcb == NULL) { mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (0); } /* * Channel has already been destroyed, so disconnect the socket * and be done with it. If there was any pending request we can * not do anything here anyway. */ mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CA_Disconnect indicator, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, state=%d\n", __func__, pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], pcb->psm, pcb->cid, pcb->state); if (pcb->flags & NG_BTSOCKET_L2CAP_TIMO) ng_btsocket_l2cap_untimeout(pcb); pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); if (pcb->so->so_state & SS_NOFDREF) *sop = pcb->so; mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (0); } /* ng_btsocket_l2cap_process_l2ca_discon_ind */ /* * Process L2CA_Write response */ static int ng_btsocket_l2cap_process_l2ca_write_rsp(struct ng_mesg *msg, ng_btsocket_l2cap_rtentry_p rt, struct socket **sop) { ng_l2cap_l2ca_write_op *op = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL; /* Check message */ if (msg->header.arglen != sizeof(*op)) return (EMSGSIZE); op = (ng_l2cap_l2ca_write_op *)(msg->data); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* Look for the socket with given token */ pcb = ng_btsocket_l2cap_pcb_by_token(msg->header.token); if (pcb == NULL) { mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CA_Write response, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dst bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, lcid=%d, result=%d, length=%d, " \ "state=%d\n", __func__, pcb->src.b[5], pcb->src.b[4], pcb->src.b[3], pcb->src.b[2], pcb->src.b[1], pcb->src.b[0], pcb->dst.b[5], pcb->dst.b[4], pcb->dst.b[3], pcb->dst.b[2], pcb->dst.b[1], pcb->dst.b[0], pcb->psm, pcb->cid, op->result, op->length, pcb->state); if (pcb->state != NG_BTSOCKET_L2CAP_OPEN) { mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (ENOENT); } ng_btsocket_l2cap_untimeout(pcb); /* * Check if we have more data to send */ sbdroprecord(&pcb->so->so_snd); if (pcb->so->so_snd.sb_cc > 0) { if (ng_btsocket_l2cap_send2(pcb) == 0) ng_btsocket_l2cap_timeout(pcb); else sbdroprecord(&pcb->so->so_snd); /* XXX */ } /* * Now set the result, drop packet from the socket send queue and * ask for more (wakeup sender) */ pcb->so->so_error = ng_btsocket_l2cap_result2errno(op->result); sowwakeup(pcb->so); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (0); } /* ng_btsocket_l2cap_process_l2ca_write_rsp */ /* * Send L2CA_Connect request */ static int ng_btsocket_l2cap_send_l2ca_con_req(ng_btsocket_l2cap_pcb_p pcb) { struct ng_mesg *msg = NULL; ng_l2cap_l2ca_con_ip *ip = NULL; int error = 0; mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->rt == NULL || pcb->rt->hook == NULL || NG_HOOK_NOT_VALID(pcb->rt->hook)) return (ENETDOWN); NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_CON, sizeof(*ip), M_NOWAIT); if (msg == NULL) return (ENOMEM); msg->header.token = pcb->token; ip = (ng_l2cap_l2ca_con_ip *)(msg->data); bcopy(&pcb->dst, &ip->bdaddr, sizeof(ip->bdaddr)); ip->psm = pcb->psm; NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_node, msg,pcb->rt->hook, 0); return (error); } /* ng_btsocket_l2cap_send_l2ca_con_req */ /* * Send L2CA_Connect response */ static int ng_btsocket_l2cap_send_l2ca_con_rsp_req(u_int32_t token, ng_btsocket_l2cap_rtentry_p rt, bdaddr_p dst, int ident, int lcid, int result) { struct ng_mesg *msg = NULL; ng_l2cap_l2ca_con_rsp_ip *ip = NULL; int error = 0; if (rt == NULL || rt->hook == NULL || NG_HOOK_NOT_VALID(rt->hook)) return (ENETDOWN); NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_CON_RSP, sizeof(*ip), M_NOWAIT); if (msg == NULL) return (ENOMEM); msg->header.token = token; ip = (ng_l2cap_l2ca_con_rsp_ip *)(msg->data); bcopy(dst, &ip->bdaddr, sizeof(ip->bdaddr)); ip->ident = ident; ip->lcid = lcid; ip->result = result; ip->status = 0; NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_node, msg, rt->hook, 0); return (error); } /* ng_btsocket_l2cap_send_l2ca_con_rsp_req */ /* * Send L2CA_Config request */ static int ng_btsocket_l2cap_send_l2ca_cfg_req(ng_btsocket_l2cap_pcb_p pcb) { struct ng_mesg *msg = NULL; ng_l2cap_l2ca_cfg_ip *ip = NULL; int error = 0; mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->rt == NULL || pcb->rt->hook == NULL || NG_HOOK_NOT_VALID(pcb->rt->hook)) return (ENETDOWN); NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_CFG, sizeof(*ip), M_NOWAIT); if (msg == NULL) return (ENOMEM); msg->header.token = pcb->token; ip = (ng_l2cap_l2ca_cfg_ip *)(msg->data); ip->lcid = pcb->cid; ip->imtu = pcb->imtu; bcopy(&pcb->oflow, &ip->oflow, sizeof(ip->oflow)); ip->flush_timo = pcb->flush_timo; ip->link_timo = pcb->link_timo; NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_node, msg,pcb->rt->hook, 0); return (error); } /* ng_btsocket_l2cap_send_l2ca_cfg_req */ /* * Send L2CA_Config response */ static int ng_btsocket_l2cap_send_l2ca_cfg_rsp(ng_btsocket_l2cap_pcb_p pcb) { struct ng_mesg *msg = NULL; ng_l2cap_l2ca_cfg_rsp_ip *ip = NULL; int error = 0; mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->rt == NULL || pcb->rt->hook == NULL || NG_HOOK_NOT_VALID(pcb->rt->hook)) return (ENETDOWN); NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_CFG_RSP, sizeof(*ip), M_NOWAIT); if (msg == NULL) return (ENOMEM); msg->header.token = pcb->token; ip = (ng_l2cap_l2ca_cfg_rsp_ip *)(msg->data); ip->lcid = pcb->cid; ip->omtu = pcb->omtu; bcopy(&pcb->iflow, &ip->iflow, sizeof(ip->iflow)); NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_node, msg, pcb->rt->hook, 0); return (error); } /* ng_btsocket_l2cap_send_l2ca_cfg_rsp */ /* * Send L2CA_Disconnect request */ static int ng_btsocket_l2cap_send_l2ca_discon_req(u_int32_t token, ng_btsocket_l2cap_pcb_p pcb) { struct ng_mesg *msg = NULL; ng_l2cap_l2ca_discon_ip *ip = NULL; int error = 0; mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->rt == NULL || pcb->rt->hook == NULL || NG_HOOK_NOT_VALID(pcb->rt->hook)) return (ENETDOWN); NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_DISCON, sizeof(*ip), M_NOWAIT); if (msg == NULL) return (ENOMEM); msg->header.token = token; ip = (ng_l2cap_l2ca_discon_ip *)(msg->data); ip->lcid = pcb->cid; NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_node, msg,pcb->rt->hook, 0); return (error); } /* ng_btsocket_l2cap_send_l2ca_discon_req */ /***************************************************************************** ***************************************************************************** ** Socket interface ***************************************************************************** *****************************************************************************/ /* * L2CAP sockets data input routine */ static void ng_btsocket_l2cap_data_input(struct mbuf *m, hook_p hook) { ng_l2cap_hdr_t *hdr = NULL; ng_l2cap_clt_hdr_t *clt_hdr = NULL; ng_btsocket_l2cap_pcb_t *pcb = NULL; ng_btsocket_l2cap_rtentry_t *rt = NULL; if (hook == NULL) { NG_BTSOCKET_L2CAP_ALERT( "%s: Invalid source hook for L2CAP data packet\n", __func__); goto drop; } rt = (ng_btsocket_l2cap_rtentry_t *) NG_HOOK_PRIVATE(hook); if (rt == NULL) { NG_BTSOCKET_L2CAP_ALERT( "%s: Could not find out source bdaddr for L2CAP data packet\n", __func__); goto drop; } /* Make sure we can access header */ if (m->m_pkthdr.len < sizeof(*hdr)) { NG_BTSOCKET_L2CAP_ERR( "%s: L2CAP data packet too small, len=%d\n", __func__, m->m_pkthdr.len); goto drop; } if (m->m_len < sizeof(*hdr)) { m = m_pullup(m, sizeof(*hdr)); if (m == NULL) goto drop; } /* Strip L2CAP packet header and verify packet length */ hdr = mtod(m, ng_l2cap_hdr_t *); m_adj(m, sizeof(*hdr)); if (hdr->length != m->m_pkthdr.len) { NG_BTSOCKET_L2CAP_ERR( "%s: Bad L2CAP data packet length, len=%d, length=%d\n", __func__, m->m_pkthdr.len, hdr->length); goto drop; } /* * Now process packet. Two cases: * * 1) Normal packet (cid != 2) then find connected socket and append * mbuf to the socket queue. Wakeup socket. * * 2) Broadcast packet (cid == 2) then find all sockets that connected * to the given PSM and have SO_BROADCAST bit set and append mbuf * to the socket queue. Wakeup socket. */ NG_BTSOCKET_L2CAP_INFO( "%s: Received L2CAP data packet: src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dcid=%d, length=%d\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], hdr->dcid, hdr->length); if (hdr->dcid >= NG_L2CAP_FIRST_CID) { mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* Normal packet: find connected socket */ pcb = ng_btsocket_l2cap_pcb_by_cid(&rt->src, hdr->dcid); if (pcb == NULL) { mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); goto drop; } mtx_lock(&pcb->pcb_mtx); if (pcb->state != NG_BTSOCKET_L2CAP_OPEN) { NG_BTSOCKET_L2CAP_ERR( "%s: No connected socket found, src bdaddr=%x:%x:%x:%x:%x:%x, dcid=%d, " \ "state=%d\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], hdr->dcid, pcb->state); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); goto drop; } /* Check packet size against socket's incoming MTU */ if (hdr->length > pcb->imtu) { NG_BTSOCKET_L2CAP_ERR( "%s: L2CAP data packet too big, src bdaddr=%x:%x:%x:%x:%x:%x, " \ "dcid=%d, length=%d, imtu=%d\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], hdr->dcid, hdr->length, pcb->imtu); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); goto drop; } /* Check if we have enough space in socket receive queue */ if (m->m_pkthdr.len > sbspace(&pcb->so->so_rcv)) { /* * This is really bad. Receive queue on socket does * not have enough space for the packet. We do not * have any other choice but drop the packet. L2CAP * does not provide any flow control. */ NG_BTSOCKET_L2CAP_ERR( "%s: Not enough space in socket receive queue. Dropping L2CAP data packet, " \ "src bdaddr=%x:%x:%x:%x:%x:%x, dcid=%d, len=%d, space=%ld\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], hdr->dcid, m->m_pkthdr.len, sbspace(&pcb->so->so_rcv)); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); goto drop; } /* Append packet to the socket receive queue and wakeup */ sbappendrecord(&pcb->so->so_rcv, m); m = NULL; sorwakeup(pcb->so); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); } else if (hdr->dcid == NG_L2CAP_CLT_CID) { /* Broadcast packet: give packet to all sockets */ /* Check packet size against connectionless MTU */ if (hdr->length > NG_L2CAP_MTU_DEFAULT) { NG_BTSOCKET_L2CAP_ERR( "%s: Connectionless L2CAP data packet too big, " \ "src bdaddr=%x:%x:%x:%x:%x:%x, length=%d\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], hdr->length); goto drop; } /* Make sure we can access connectionless header */ if (m->m_pkthdr.len < sizeof(*clt_hdr)) { NG_BTSOCKET_L2CAP_ERR( "%s: Can not get L2CAP connectionless packet header, " \ "src bdaddr=%x:%x:%x:%x:%x:%x, length=%d\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], hdr->length); goto drop; } if (m->m_len < sizeof(*clt_hdr)) { m = m_pullup(m, sizeof(*clt_hdr)); if (m == NULL) goto drop; } /* Strip connectionless header and deliver packet */ clt_hdr = mtod(m, ng_l2cap_clt_hdr_t *); m_adj(m, sizeof(*clt_hdr)); NG_BTSOCKET_L2CAP_INFO( "%s: Got L2CAP connectionless data packet, " \ "src bdaddr=%x:%x:%x:%x:%x:%x, psm=%d, length=%d\n", __func__, rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0], clt_hdr->psm, hdr->length); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); LIST_FOREACH(pcb, &ng_btsocket_l2cap_sockets, next) { struct mbuf *copy = NULL; mtx_lock(&pcb->pcb_mtx); if (bcmp(&rt->src, &pcb->src, sizeof(pcb->src)) != 0 || pcb->psm != clt_hdr->psm || pcb->state != NG_BTSOCKET_L2CAP_OPEN || (pcb->so->so_options & SO_BROADCAST) == 0 || m->m_pkthdr.len > sbspace(&pcb->so->so_rcv)) goto next; /* * Create a copy of the packet and append it to the * socket's queue. If m_dup() failed - no big deal * it is a broadcast traffic after all */ copy = m_dup(m, M_DONTWAIT); if (copy != NULL) { sbappendrecord(&pcb->so->so_rcv, copy); sorwakeup(pcb->so); } next: mtx_unlock(&pcb->pcb_mtx); } mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); } drop: NG_FREE_M(m); /* checks for m != NULL */ } /* ng_btsocket_l2cap_data_input */ /* * L2CAP sockets default message input routine */ static void ng_btsocket_l2cap_default_msg_input(struct ng_mesg *msg, hook_p hook) { switch (msg->header.cmd) { case NGM_L2CAP_NODE_HOOK_INFO: { ng_btsocket_l2cap_rtentry_t *rt = NULL; if (hook == NULL || msg->header.arglen != sizeof(bdaddr_t)) break; if (bcmp(msg->data, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) break; mtx_lock(&ng_btsocket_l2cap_rt_mtx); rt = (ng_btsocket_l2cap_rtentry_t *) NG_HOOK_PRIVATE(hook); if (rt == NULL) { MALLOC(rt, ng_btsocket_l2cap_rtentry_p, sizeof(*rt), M_NETGRAPH_BTSOCKET_L2CAP, M_NOWAIT|M_ZERO); if (rt == NULL) { mtx_unlock(&ng_btsocket_l2cap_rt_mtx); break; } LIST_INSERT_HEAD(&ng_btsocket_l2cap_rt, rt, next); NG_HOOK_SET_PRIVATE(hook, rt); } bcopy(msg->data, &rt->src, sizeof(rt->src)); rt->hook = hook; mtx_unlock(&ng_btsocket_l2cap_rt_mtx); NG_BTSOCKET_L2CAP_INFO( "%s: Updating hook \"%s\", src bdaddr=%x:%x:%x:%x:%x:%x\n", __func__, NG_HOOK_NAME(hook), rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0]); } break; default: NG_BTSOCKET_L2CAP_WARN( "%s: Unknown message, cmd=%d\n", __func__, msg->header.cmd); break; } NG_FREE_MSG(msg); /* Checks for msg != NULL */ } /* ng_btsocket_l2cap_default_msg_input */ /* * L2CAP sockets L2CA message input routine */ static void ng_btsocket_l2cap_l2ca_msg_input(struct ng_mesg *msg, hook_p hook) { ng_btsocket_l2cap_rtentry_p rt = NULL; struct socket *so = NULL; if (hook == NULL) { NG_BTSOCKET_L2CAP_ALERT( "%s: Invalid source hook for L2CA message\n", __func__); goto drop; } rt = (ng_btsocket_l2cap_rtentry_p) NG_HOOK_PRIVATE(hook); if (rt == NULL) { NG_BTSOCKET_L2CAP_ALERT( "%s: Could not find out source bdaddr for L2CA message\n", __func__); goto drop; } switch (msg->header.cmd) { case NGM_L2CAP_L2CA_CON: /* L2CA_Connect response */ ng_btsocket_l2cap_process_l2ca_con_req_rsp(msg, rt, &so); break; case NGM_L2CAP_L2CA_CON_RSP: /* L2CA_ConnectRsp response */ ng_btsocket_l2cap_process_l2ca_con_rsp_rsp(msg, rt, &so); break; case NGM_L2CAP_L2CA_CON_IND: /* L2CA_Connect indicator */ ng_btsocket_l2cap_process_l2ca_con_ind(msg, rt, &so); break; case NGM_L2CAP_L2CA_CFG: /* L2CA_Config response */ ng_btsocket_l2cap_process_l2ca_cfg_req_rsp(msg, rt, &so); break; case NGM_L2CAP_L2CA_CFG_RSP: /* L2CA_ConfigRsp response */ ng_btsocket_l2cap_process_l2ca_cfg_rsp_rsp(msg, rt, &so); break; case NGM_L2CAP_L2CA_CFG_IND: /* L2CA_Config indicator */ ng_btsocket_l2cap_process_l2ca_cfg_ind(msg, rt, &so); break; case NGM_L2CAP_L2CA_DISCON: /* L2CA_Disconnect response */ ng_btsocket_l2cap_process_l2ca_discon_rsp(msg, rt, &so); break; case NGM_L2CAP_L2CA_DISCON_IND: /* L2CA_Disconnect indicator */ ng_btsocket_l2cap_process_l2ca_discon_ind(msg, rt, &so); break; case NGM_L2CAP_L2CA_WRITE: /* L2CA_Write response */ ng_btsocket_l2cap_process_l2ca_write_rsp(msg, rt, &so); break; /* XXX FIXME add other L2CA messages */ default: NG_BTSOCKET_L2CAP_WARN( "%s: Unknown L2CA message, cmd=%d\n", __func__, msg->header.cmd); break; } drop: NG_FREE_MSG(msg); if (so != NULL) ng_btsocket_l2cap_detach(so); } /* ng_btsocket_l2cap_l2ca_msg_input */ /* * L2CAP sockets input routine */ static void ng_btsocket_l2cap_input(void *context, int pending) { item_p item = NULL; hook_p hook = NULL; for (;;) { mtx_lock(&ng_btsocket_l2cap_queue_mtx); NG_BT_ITEMQ_DEQUEUE(&ng_btsocket_l2cap_queue, item); mtx_unlock(&ng_btsocket_l2cap_queue_mtx); if (item == NULL) break; NGI_GET_HOOK(item, hook); if (hook != NULL && NG_HOOK_NOT_VALID(hook)) goto drop; switch(item->el_flags & NGQF_TYPE) { case NGQF_DATA: { struct mbuf *m = NULL; NGI_GET_M(item, m); ng_btsocket_l2cap_data_input(m, hook); } break; case NGQF_MESG: { struct ng_mesg *msg = NULL; NGI_GET_MSG(item, msg); switch (msg->header.cmd) { case NGM_L2CAP_L2CA_CON: case NGM_L2CAP_L2CA_CON_RSP: case NGM_L2CAP_L2CA_CON_IND: case NGM_L2CAP_L2CA_CFG: case NGM_L2CAP_L2CA_CFG_RSP: case NGM_L2CAP_L2CA_CFG_IND: case NGM_L2CAP_L2CA_DISCON: case NGM_L2CAP_L2CA_DISCON_IND: case NGM_L2CAP_L2CA_WRITE: /* XXX FIXME add other L2CA messages */ ng_btsocket_l2cap_l2ca_msg_input(msg, hook); break; default: ng_btsocket_l2cap_default_msg_input(msg, hook); break; } } break; default: KASSERT(0, ("%s: invalid item type=%ld\n", __func__, (item->el_flags & NGQF_TYPE))); break; } drop: if (hook != NULL) NG_HOOK_UNREF(hook); NG_FREE_ITEM(item); } } /* ng_btsocket_l2cap_input */ /* * Route cleanup task. Gets scheduled when hook is disconnected. Here we * will find all sockets that use "invalid" hook and disconnect them. */ static void ng_btsocket_l2cap_rtclean(void *context, int pending) { ng_btsocket_l2cap_pcb_p pcb = NULL, pcb_next = NULL; ng_btsocket_l2cap_rtentry_p rt = NULL; mtx_lock(&ng_btsocket_l2cap_rt_mtx); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); /* * First disconnect all sockets that use "invalid" hook */ for (pcb = LIST_FIRST(&ng_btsocket_l2cap_sockets); pcb != NULL; ) { mtx_lock(&pcb->pcb_mtx); pcb_next = LIST_NEXT(pcb, next); if (pcb->rt != NULL && pcb->rt->hook != NULL && NG_HOOK_NOT_VALID(pcb->rt->hook)) { if (pcb->flags & NG_BTSOCKET_L2CAP_TIMO) ng_btsocket_l2cap_untimeout(pcb); pcb->so->so_error = ENETDOWN; pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); pcb->token = 0; pcb->cid = 0; pcb->rt = NULL; if (pcb->so->so_state & SS_NOFDREF) { struct socket *so = pcb->so; LIST_REMOVE(pcb, next); mtx_unlock(&pcb->pcb_mtx); mtx_destroy(&pcb->pcb_mtx); bzero(pcb, sizeof(*pcb)); FREE(pcb, M_NETGRAPH_BTSOCKET_L2CAP); soisdisconnected(so); ACCEPT_LOCK(); SOCK_LOCK(so); so->so_pcb = NULL; sotryfree(so); goto next; } } mtx_unlock(&pcb->pcb_mtx); next: pcb = pcb_next; } /* * Now cleanup routing table */ for (rt = LIST_FIRST(&ng_btsocket_l2cap_rt); rt != NULL; ) { ng_btsocket_l2cap_rtentry_p rt_next = LIST_NEXT(rt, next); if (rt->hook != NULL && NG_HOOK_NOT_VALID(rt->hook)) { LIST_REMOVE(rt, next); NG_HOOK_SET_PRIVATE(rt->hook, NULL); NG_HOOK_UNREF(rt->hook); /* Remove extra reference */ bzero(rt, sizeof(*rt)); FREE(rt, M_NETGRAPH_BTSOCKET_L2CAP); } rt = rt_next; } mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); mtx_unlock(&ng_btsocket_l2cap_rt_mtx); } /* ng_btsocket_l2cap_rtclean */ /* * Initialize everything */ void ng_btsocket_l2cap_init(void) { int error = 0; ng_btsocket_l2cap_node = NULL; ng_btsocket_l2cap_debug_level = NG_BTSOCKET_WARN_LEVEL; /* Register Netgraph node type */ error = ng_newtype(&typestruct); if (error != 0) { NG_BTSOCKET_L2CAP_ALERT( "%s: Could not register Netgraph node type, error=%d\n", __func__, error); return; } /* Create Netgrapg node */ error = ng_make_node_common(&typestruct, &ng_btsocket_l2cap_node); if (error != 0) { NG_BTSOCKET_L2CAP_ALERT( "%s: Could not create Netgraph node, error=%d\n", __func__, error); ng_btsocket_l2cap_node = NULL; return; } error = ng_name_node(ng_btsocket_l2cap_node, NG_BTSOCKET_L2CAP_NODE_TYPE); if (error != 0) { NG_BTSOCKET_L2CAP_ALERT( "%s: Could not name Netgraph node, error=%d\n", __func__, error); NG_NODE_UNREF(ng_btsocket_l2cap_node); ng_btsocket_l2cap_node = NULL; return; } /* Create input queue */ NG_BT_ITEMQ_INIT(&ng_btsocket_l2cap_queue, ifqmaxlen); mtx_init(&ng_btsocket_l2cap_queue_mtx, "btsocks_l2cap_queue_mtx", NULL, MTX_DEF); TASK_INIT(&ng_btsocket_l2cap_queue_task, 0, ng_btsocket_l2cap_input, NULL); /* Create list of sockets */ LIST_INIT(&ng_btsocket_l2cap_sockets); mtx_init(&ng_btsocket_l2cap_sockets_mtx, "btsocks_l2cap_sockets_mtx", NULL, MTX_DEF); /* Routing table */ LIST_INIT(&ng_btsocket_l2cap_rt); mtx_init(&ng_btsocket_l2cap_rt_mtx, "btsocks_l2cap_rt_mtx", NULL, MTX_DEF); TASK_INIT(&ng_btsocket_l2cap_rt_task, 0, ng_btsocket_l2cap_rtclean, NULL); } /* ng_btsocket_l2cap_init */ /* * Abort connection on socket */ void ng_btsocket_l2cap_abort(struct socket *so) { so->so_error = ECONNABORTED; ng_btsocket_l2cap_detach(so); } /* ng_btsocket_l2cap_abort */ /* * Accept connection on socket. Nothing to do here, socket must be connected * and ready, so just return peer address and be done with it. */ int ng_btsocket_l2cap_accept(struct socket *so, struct sockaddr **nam) { if (ng_btsocket_l2cap_node == NULL) return (EINVAL); return (ng_btsocket_l2cap_peeraddr(so, nam)); } /* ng_btsocket_l2cap_accept */ /* * Create and attach new socket */ int ng_btsocket_l2cap_attach(struct socket *so, int proto, struct thread *td) { static u_int32_t token = 0; ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); int error; /* Check socket and protocol */ if (ng_btsocket_l2cap_node == NULL) return (EPROTONOSUPPORT); if (so->so_type != SOCK_SEQPACKET) return (ESOCKTNOSUPPORT); #if 0 /* XXX sonewconn() calls "pru_attach" with proto == 0 */ if (proto != 0) if (proto != BLUETOOTH_PROTO_L2CAP) return (EPROTONOSUPPORT); #endif /* XXX */ if (pcb != NULL) return (EISCONN); /* Reserve send and receive space if it is not reserved yet */ if ((so->so_snd.sb_hiwat == 0) || (so->so_rcv.sb_hiwat == 0)) { error = soreserve(so, NG_BTSOCKET_L2CAP_SENDSPACE, NG_BTSOCKET_L2CAP_RECVSPACE); if (error != 0) return (error); } /* Allocate the PCB */ MALLOC(pcb, ng_btsocket_l2cap_pcb_p, sizeof(*pcb), M_NETGRAPH_BTSOCKET_L2CAP, M_NOWAIT | M_ZERO); if (pcb == NULL) return (ENOMEM); /* Link the PCB and the socket */ so->so_pcb = (caddr_t) pcb; pcb->so = so; pcb->state = NG_BTSOCKET_L2CAP_CLOSED; /* Initialize PCB */ pcb->imtu = pcb->omtu = NG_L2CAP_MTU_DEFAULT; /* Default flow */ pcb->iflow.flags = 0x0; pcb->iflow.service_type = NG_HCI_SERVICE_TYPE_BEST_EFFORT; pcb->iflow.token_rate = 0xffffffff; /* maximum */ pcb->iflow.token_bucket_size = 0xffffffff; /* maximum */ pcb->iflow.peak_bandwidth = 0x00000000; /* maximum */ pcb->iflow.latency = 0xffffffff; /* don't care */ pcb->iflow.delay_variation = 0xffffffff; /* don't care */ bcopy(&pcb->iflow, &pcb->oflow, sizeof(pcb->oflow)); pcb->flush_timo = NG_L2CAP_FLUSH_TIMO_DEFAULT; pcb->link_timo = NG_L2CAP_LINK_TIMO_DEFAULT; callout_handle_init(&pcb->timo); /* * XXX Mark PCB mutex as DUPOK to prevent "duplicated lock of * the same type" message. When accepting new L2CAP connection * ng_btsocket_l2cap_process_l2ca_con_ind() holds both PCB mutexes * for "old" (accepting) PCB and "new" (created) PCB. */ mtx_init(&pcb->pcb_mtx, "btsocks_l2cap_pcb_mtx", NULL, MTX_DEF|MTX_DUPOK); /* * Add the PCB to the list * * XXX FIXME VERY IMPORTANT! * * This is totally FUBAR. We could get here in two cases: * * 1) When user calls socket() * 2) When we need to accept new incomming connection and call * sonewconn() * * In the first case we must aquire ng_btsocket_l2cap_sockets_mtx. * In the second case we hold ng_btsocket_l2cap_sockets_mtx already. * So we now need to distinguish between these cases. From reading * /sys/kern/uipc_socket2.c we can find out that sonewconn() calls * pru_attach with proto == 0 and td == NULL. For now use this fact * to figure out if we were called from socket() or from sonewconn(). */ if (td != NULL) mtx_lock(&ng_btsocket_l2cap_sockets_mtx); else mtx_assert(&ng_btsocket_l2cap_sockets_mtx, MA_OWNED); /* Set PCB token. Use ng_btsocket_l2cap_sockets_mtx for protection */ if (++ token == 0) token ++; pcb->token = token; LIST_INSERT_HEAD(&ng_btsocket_l2cap_sockets, pcb, next); if (td != NULL) mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (0); } /* ng_btsocket_l2cap_attach */ /* * Bind socket */ int ng_btsocket_l2cap_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { ng_btsocket_l2cap_pcb_t *pcb = NULL; struct sockaddr_l2cap *sa = (struct sockaddr_l2cap *) nam; int psm, error = 0; if (ng_btsocket_l2cap_node == NULL) return (EINVAL); /* Verify address */ if (sa == NULL) return (EINVAL); if (sa->l2cap_family != AF_BLUETOOTH) return (EAFNOSUPPORT); if (sa->l2cap_len != sizeof(*sa)) return (EINVAL); psm = le16toh(sa->l2cap_psm); /* * Check if other socket has this address already (look for exact * match PSM and bdaddr) and assign socket address if it's available. * * Note: socket can be bound to ANY PSM (zero) thus allowing several * channels with the same PSM between the same pair of BD_ADDR'es. */ mtx_lock(&ng_btsocket_l2cap_sockets_mtx); LIST_FOREACH(pcb, &ng_btsocket_l2cap_sockets, next) if (psm != 0 && psm == pcb->psm && bcmp(&pcb->src, &sa->l2cap_bdaddr, sizeof(bdaddr_t)) == 0) break; if (pcb == NULL) { /* Set socket address */ pcb = so2l2cap_pcb(so); if (pcb != NULL) { bcopy(&sa->l2cap_bdaddr, &pcb->src, sizeof(pcb->src)); pcb->psm = psm; } else error = EINVAL; } else error = EADDRINUSE; mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); return (error); } /* ng_btsocket_l2cap_bind */ /* * Connect socket */ int ng_btsocket_l2cap_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { ng_btsocket_l2cap_pcb_t *pcb = so2l2cap_pcb(so); struct sockaddr_l2cap *sa = (struct sockaddr_l2cap *) nam; ng_btsocket_l2cap_rtentry_t *rt = NULL; int have_src, error = 0; /* Check socket */ if (pcb == NULL) return (EINVAL); if (ng_btsocket_l2cap_node == NULL) return (EINVAL); if (pcb->state == NG_BTSOCKET_L2CAP_CONNECTING) return (EINPROGRESS); /* Verify address */ if (sa == NULL) return (EINVAL); if (sa->l2cap_family != AF_BLUETOOTH) return (EAFNOSUPPORT); if (sa->l2cap_len != sizeof(*sa)) return (EINVAL); if (sa->l2cap_psm == 0 || bcmp(&sa->l2cap_bdaddr, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) return (EDESTADDRREQ); if (pcb->psm != 0 && pcb->psm != le16toh(sa->l2cap_psm)) return (EINVAL); /* * Routing. Socket should be bound to some source address. The source * address can be ANY. Destination address must be set and it must not * be ANY. If source address is ANY then find first rtentry that has * src != dst. */ mtx_lock(&ng_btsocket_l2cap_rt_mtx); mtx_lock(&ng_btsocket_l2cap_sockets_mtx); mtx_lock(&pcb->pcb_mtx); /* Send destination address and PSM */ bcopy(&sa->l2cap_bdaddr, &pcb->dst, sizeof(pcb->dst)); pcb->psm = le16toh(sa->l2cap_psm); pcb->rt = NULL; have_src = bcmp(&pcb->src, NG_HCI_BDADDR_ANY, sizeof(pcb->src)); LIST_FOREACH(rt, &ng_btsocket_l2cap_rt, next) { if (rt->hook == NULL || NG_HOOK_NOT_VALID(rt->hook)) continue; /* Match src and dst */ if (have_src) { if (bcmp(&pcb->src, &rt->src, sizeof(rt->src)) == 0) break; } else { if (bcmp(&pcb->dst, &rt->src, sizeof(rt->src)) != 0) break; } } if (rt != NULL) { pcb->rt = rt; if (!have_src) bcopy(&rt->src, &pcb->src, sizeof(pcb->src)); } else error = EHOSTUNREACH; /* * Send L2CA_Connect request */ if (error == 0) { error = ng_btsocket_l2cap_send_l2ca_con_req(pcb); if (error == 0) { pcb->flags |= NG_BTSOCKET_L2CAP_CLIENT; pcb->state = NG_BTSOCKET_L2CAP_CONNECTING; soisconnecting(pcb->so); ng_btsocket_l2cap_timeout(pcb); } } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); mtx_unlock(&ng_btsocket_l2cap_rt_mtx); return (error); } /* ng_btsocket_l2cap_connect */ /* * Process ioctl's calls on socket */ int ng_btsocket_l2cap_control(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp, struct thread *td) { return (EINVAL); } /* ng_btsocket_l2cap_control */ /* * Process getsockopt/setsockopt system calls */ int ng_btsocket_l2cap_ctloutput(struct socket *so, struct sockopt *sopt) { ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); int error = 0; ng_l2cap_cfg_opt_val_t v; if (pcb == NULL) return (EINVAL); if (ng_btsocket_l2cap_node == NULL) return (EINVAL); if (sopt->sopt_level != SOL_L2CAP) return (0); mtx_lock(&pcb->pcb_mtx); switch (sopt->sopt_dir) { case SOPT_GET: switch (sopt->sopt_name) { case SO_L2CAP_IMTU: /* get incoming MTU */ error = sooptcopyout(sopt, &pcb->imtu, sizeof(pcb->imtu)); break; case SO_L2CAP_OMTU: /* get outgoing (peer incoming) MTU */ error = sooptcopyout(sopt, &pcb->omtu, sizeof(pcb->omtu)); break; case SO_L2CAP_IFLOW: /* get incoming flow spec. */ error = sooptcopyout(sopt, &pcb->iflow, sizeof(pcb->iflow)); break; case SO_L2CAP_OFLOW: /* get outgoing flow spec. */ error = sooptcopyout(sopt, &pcb->oflow, sizeof(pcb->oflow)); break; case SO_L2CAP_FLUSH: /* get flush timeout */ error = sooptcopyout(sopt, &pcb->flush_timo, sizeof(pcb->flush_timo)); break; default: error = ENOPROTOOPT; break; } break; case SOPT_SET: /* * XXX * We do not allow to change these parameters while socket is * connected or we are in the process of creating a connection. * May be this should indicate re-configuration of the open * channel? */ if (pcb->state != NG_BTSOCKET_L2CAP_CLOSED) return (EACCES); switch (sopt->sopt_name) { case SO_L2CAP_IMTU: /* set incoming MTU */ error = sooptcopyin(sopt, &v, sizeof(v), sizeof(v.mtu)); if (error == 0) pcb->imtu = v.mtu; break; case SO_L2CAP_OFLOW: /* set outgoing flow spec. */ error = sooptcopyin(sopt, &v, sizeof(v),sizeof(v.flow)); if (error == 0) bcopy(&v.flow, &pcb->oflow, sizeof(pcb->oflow)); break; case SO_L2CAP_FLUSH: /* set flush timeout */ error = sooptcopyin(sopt, &v, sizeof(v), sizeof(v.flush_timo)); if (error == 0) pcb->flush_timo = v.flush_timo; break; default: error = ENOPROTOOPT; break; } break; default: error = EINVAL; break; } mtx_unlock(&pcb->pcb_mtx); return (error); } /* ng_btsocket_l2cap_ctloutput */ /* * Detach and destroy socket */ -int +void ng_btsocket_l2cap_detach(struct socket *so) { ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); - if (pcb == NULL) - return (EINVAL); + KASSERT(pcb != NULL, ("ng_btsocket_l2cap_detach: pcb == NULL")); + if (ng_btsocket_l2cap_node == NULL) - return (EINVAL); + return; mtx_lock(&ng_btsocket_l2cap_sockets_mtx); mtx_lock(&pcb->pcb_mtx); /* XXX what to do with pending request? */ if (pcb->flags & NG_BTSOCKET_L2CAP_TIMO) ng_btsocket_l2cap_untimeout(pcb); if (pcb->state != NG_BTSOCKET_L2CAP_CLOSED && pcb->state != NG_BTSOCKET_L2CAP_DISCONNECTING) /* Send disconnect request with "zero" token */ ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); pcb->state = NG_BTSOCKET_L2CAP_CLOSED; LIST_REMOVE(pcb, next); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_sockets_mtx); mtx_destroy(&pcb->pcb_mtx); bzero(pcb, sizeof(*pcb)); FREE(pcb, M_NETGRAPH_BTSOCKET_L2CAP); soisdisconnected(so); - ACCEPT_LOCK(); - SOCK_LOCK(so); so->so_pcb = NULL; - sotryfree(so); - - return (0); } /* ng_btsocket_l2cap_detach */ /* * Disconnect socket */ int ng_btsocket_l2cap_disconnect(struct socket *so) { ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); int error = 0; if (pcb == NULL) return (EINVAL); if (ng_btsocket_l2cap_node == NULL) return (EINVAL); mtx_lock(&pcb->pcb_mtx); if (pcb->state == NG_BTSOCKET_L2CAP_DISCONNECTING) { mtx_unlock(&pcb->pcb_mtx); return (EINPROGRESS); } if (pcb->state != NG_BTSOCKET_L2CAP_CLOSED) { /* XXX FIXME what to do with pending request? */ if (pcb->flags & NG_BTSOCKET_L2CAP_TIMO) ng_btsocket_l2cap_untimeout(pcb); error = ng_btsocket_l2cap_send_l2ca_discon_req(pcb->token, pcb); if (error == 0) { pcb->state = NG_BTSOCKET_L2CAP_DISCONNECTING; soisdisconnecting(so); ng_btsocket_l2cap_timeout(pcb); } /* XXX FIXME what to do if error != 0 */ } mtx_unlock(&pcb->pcb_mtx); return (error); } /* ng_btsocket_l2cap_disconnect */ /* * Listen on socket */ int ng_btsocket_l2cap_listen(struct socket *so, int backlog, struct thread *td) { ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); int error; SOCK_LOCK(so); error = solisten_proto_check(so); if (error != 0) goto out; if (pcb == NULL) { error = EINVAL; goto out; } if (ng_btsocket_l2cap_node == NULL) { error = EINVAL; goto out; } if (pcb->psm == 0) { error = EDESTADDRREQ; goto out; } solisten_proto(so, backlog); out: SOCK_UNLOCK(so); return (error); } /* ng_btsocket_listen */ /* * Get peer address */ int ng_btsocket_l2cap_peeraddr(struct socket *so, struct sockaddr **nam) { ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); struct sockaddr_l2cap sa; if (pcb == NULL) return (EINVAL); if (ng_btsocket_l2cap_node == NULL) return (EINVAL); bcopy(&pcb->dst, &sa.l2cap_bdaddr, sizeof(sa.l2cap_bdaddr)); sa.l2cap_psm = htole16(pcb->psm); sa.l2cap_len = sizeof(sa); sa.l2cap_family = AF_BLUETOOTH; *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); return ((*nam == NULL)? ENOMEM : 0); } /* ng_btsocket_l2cap_peeraddr */ /* * Send data to socket */ int ng_btsocket_l2cap_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, struct mbuf *control, struct thread *td) { ng_btsocket_l2cap_pcb_t *pcb = so2l2cap_pcb(so); int error = 0; if (ng_btsocket_l2cap_node == NULL) { error = ENETDOWN; goto drop; } /* Check socket and input */ if (pcb == NULL || m == NULL || control != NULL) { error = EINVAL; goto drop; } mtx_lock(&pcb->pcb_mtx); /* Make sure socket is connected */ if (pcb->state != NG_BTSOCKET_L2CAP_OPEN) { mtx_unlock(&pcb->pcb_mtx); error = ENOTCONN; goto drop; } /* Check route */ if (pcb->rt == NULL || pcb->rt->hook == NULL || NG_HOOK_NOT_VALID(pcb->rt->hook)) { mtx_unlock(&pcb->pcb_mtx); error = ENETDOWN; goto drop; } /* Check packet size agains outgoing (peer's incoming) MTU) */ if (m->m_pkthdr.len > pcb->omtu) { NG_BTSOCKET_L2CAP_ERR( "%s: Packet too big, len=%d, omtu=%d\n", __func__, m->m_pkthdr.len, pcb->omtu); mtx_unlock(&pcb->pcb_mtx); error = EMSGSIZE; goto drop; } /* * First put packet on socket send queue. Then check if we have * pending timeout. If we do not have timeout then we must send * packet and schedule timeout. Otherwise do nothing and wait for * L2CA_WRITE_RSP. */ sbappendrecord(&pcb->so->so_snd, m); m = NULL; if (!(pcb->flags & NG_BTSOCKET_L2CAP_TIMO)) { error = ng_btsocket_l2cap_send2(pcb); if (error == 0) ng_btsocket_l2cap_timeout(pcb); else sbdroprecord(&pcb->so->so_snd); /* XXX */ } mtx_unlock(&pcb->pcb_mtx); drop: NG_FREE_M(m); /* checks for != NULL */ NG_FREE_M(control); return (error); } /* ng_btsocket_l2cap_send */ /* * Send first packet in the socket queue to the L2CAP layer */ static int ng_btsocket_l2cap_send2(ng_btsocket_l2cap_pcb_p pcb) { struct mbuf *m = NULL; ng_l2cap_l2ca_hdr_t *hdr = NULL; int error = 0; mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->so->so_snd.sb_cc == 0) return (EINVAL); /* XXX */ m = m_dup(pcb->so->so_snd.sb_mb, M_DONTWAIT); if (m == NULL) return (ENOBUFS); /* Create L2CA packet header */ M_PREPEND(m, sizeof(*hdr), M_DONTWAIT); if (m != NULL) if (m->m_len < sizeof(*hdr)) m = m_pullup(m, sizeof(*hdr)); if (m == NULL) { NG_BTSOCKET_L2CAP_ERR( "%s: Failed to create L2CA packet header\n", __func__); return (ENOBUFS); } hdr = mtod(m, ng_l2cap_l2ca_hdr_t *); hdr->token = pcb->token; hdr->length = m->m_pkthdr.len - sizeof(*hdr); hdr->lcid = pcb->cid; NG_BTSOCKET_L2CAP_INFO( "%s: Sending packet: len=%d, length=%d, lcid=%d, token=%d, state=%d\n", __func__, m->m_pkthdr.len, hdr->length, hdr->lcid, hdr->token, pcb->state); /* * If we got here than we have successfuly creates new L2CAP * data packet and now we can send it to the L2CAP layer */ NG_SEND_DATA_ONLY(error, pcb->rt->hook, m); return (error); } /* ng_btsocket_l2cap_send2 */ /* * Get socket address */ int ng_btsocket_l2cap_sockaddr(struct socket *so, struct sockaddr **nam) { ng_btsocket_l2cap_pcb_p pcb = so2l2cap_pcb(so); struct sockaddr_l2cap sa; if (pcb == NULL) return (EINVAL); if (ng_btsocket_l2cap_node == NULL) return (EINVAL); bcopy(&pcb->src, &sa.l2cap_bdaddr, sizeof(sa.l2cap_bdaddr)); sa.l2cap_psm = htole16(pcb->psm); sa.l2cap_len = sizeof(sa); sa.l2cap_family = AF_BLUETOOTH; *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); return ((*nam == NULL)? ENOMEM : 0); } /* ng_btsocket_l2cap_sockaddr */ /***************************************************************************** ***************************************************************************** ** Misc. functions ***************************************************************************** *****************************************************************************/ /* * Look for the socket that listens on given PSM and bdaddr. Returns exact or * close match (if any). Caller must hold ng_btsocket_l2cap_sockets_mtx. */ static ng_btsocket_l2cap_pcb_p ng_btsocket_l2cap_pcb_by_addr(bdaddr_p bdaddr, int psm) { ng_btsocket_l2cap_pcb_p p = NULL, p1 = NULL; mtx_assert(&ng_btsocket_l2cap_sockets_mtx, MA_OWNED); LIST_FOREACH(p, &ng_btsocket_l2cap_sockets, next) { if (p->so == NULL || !(p->so->so_options & SO_ACCEPTCONN) || p->psm != psm) continue; if (bcmp(&p->src, bdaddr, sizeof(p->src)) == 0) break; if (bcmp(&p->src, NG_HCI_BDADDR_ANY, sizeof(p->src)) == 0) p1 = p; } return ((p != NULL)? p : p1); } /* ng_btsocket_l2cap_pcb_by_addr */ /* * Look for the socket that has given token. * Caller must hold ng_btsocket_l2cap_sockets_mtx. */ static ng_btsocket_l2cap_pcb_p ng_btsocket_l2cap_pcb_by_token(u_int32_t token) { ng_btsocket_l2cap_pcb_p p = NULL; if (token == 0) return (NULL); mtx_assert(&ng_btsocket_l2cap_sockets_mtx, MA_OWNED); LIST_FOREACH(p, &ng_btsocket_l2cap_sockets, next) if (p->token == token) break; return (p); } /* ng_btsocket_l2cap_pcb_by_token */ /* * Look for the socket that assigned to given source address and channel ID. * Caller must hold ng_btsocket_l2cap_sockets_mtx */ static ng_btsocket_l2cap_pcb_p ng_btsocket_l2cap_pcb_by_cid(bdaddr_p src, int cid) { ng_btsocket_l2cap_pcb_p p = NULL; mtx_assert(&ng_btsocket_l2cap_sockets_mtx, MA_OWNED); LIST_FOREACH(p, &ng_btsocket_l2cap_sockets, next) if (p->cid == cid && bcmp(src, &p->src, sizeof(p->src)) == 0) break; return (p); } /* ng_btsocket_l2cap_pcb_by_cid */ /* * Set timeout on socket */ static void ng_btsocket_l2cap_timeout(ng_btsocket_l2cap_pcb_p pcb) { mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (!(pcb->flags & NG_BTSOCKET_L2CAP_TIMO)) { pcb->flags |= NG_BTSOCKET_L2CAP_TIMO; pcb->timo = timeout(ng_btsocket_l2cap_process_timeout, pcb, bluetooth_l2cap_ertx_timeout()); } else KASSERT(0, ("%s: Duplicated socket timeout?!\n", __func__)); } /* ng_btsocket_l2cap_timeout */ /* * Unset timeout on socket */ static void ng_btsocket_l2cap_untimeout(ng_btsocket_l2cap_pcb_p pcb) { mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->flags & NG_BTSOCKET_L2CAP_TIMO) { untimeout(ng_btsocket_l2cap_process_timeout, pcb, pcb->timo); pcb->flags &= ~NG_BTSOCKET_L2CAP_TIMO; } else KASSERT(0, ("%s: No socket timeout?!\n", __func__)); } /* ng_btsocket_l2cap_untimeout */ /* * Process timeout on socket */ static void ng_btsocket_l2cap_process_timeout(void *xpcb) { ng_btsocket_l2cap_pcb_p pcb = (ng_btsocket_l2cap_pcb_p) xpcb; struct socket *so = NULL; mtx_lock(&pcb->pcb_mtx); pcb->flags &= ~NG_BTSOCKET_L2CAP_TIMO; pcb->so->so_error = ETIMEDOUT; switch (pcb->state) { case NG_BTSOCKET_L2CAP_CONNECTING: case NG_BTSOCKET_L2CAP_CONFIGURING: /* Send disconnect request with "zero" token */ if (pcb->cid != 0) ng_btsocket_l2cap_send_l2ca_discon_req(0, pcb); /* ... and close the socket */ pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); if (pcb->so->so_state & SS_NOFDREF) so = pcb->so; break; case NG_BTSOCKET_L2CAP_OPEN: /* Send timeout - drop packet and wakeup sender */ sbdroprecord(&pcb->so->so_snd); sowwakeup(pcb->so); break; case NG_BTSOCKET_L2CAP_DISCONNECTING: /* Disconnect timeout - disconnect the socket anyway */ pcb->state = NG_BTSOCKET_L2CAP_CLOSED; soisdisconnected(pcb->so); if (pcb->so->so_state & SS_NOFDREF) so = pcb->so; break; default: NG_BTSOCKET_L2CAP_ERR( "%s: Invalid socket state=%d\n", __func__, pcb->state); break; } mtx_unlock(&pcb->pcb_mtx); if (so != NULL) ng_btsocket_l2cap_detach(so); } /* ng_btsocket_l2cap_process_timeout */ /* * Translate HCI/L2CAP error code into "errno" code * XXX Note: Some L2CAP and HCI error codes have the same value, but * different meaning */ static int ng_btsocket_l2cap_result2errno(int result) { switch (result) { case 0x00: /* No error */ return (0); case 0x01: /* Unknown HCI command */ return (ENODEV); case 0x02: /* No connection */ return (ENOTCONN); case 0x03: /* Hardware failure */ return (EIO); case 0x04: /* Page timeout */ return (EHOSTDOWN); case 0x05: /* Authentication failure */ case 0x06: /* Key missing */ case 0x18: /* Pairing not allowed */ case 0x21: /* Role change not allowed */ case 0x24: /* LMP PSU not allowed */ case 0x25: /* Encryption mode not acceptable */ case 0x26: /* Unit key used */ return (EACCES); case 0x07: /* Memory full */ return (ENOMEM); case 0x08: /* Connection timeout */ case 0x10: /* Host timeout */ case 0x22: /* LMP response timeout */ case 0xee: /* HCI timeout */ case 0xeeee: /* L2CAP timeout */ return (ETIMEDOUT); case 0x09: /* Max number of connections */ case 0x0a: /* Max number of SCO connections to a unit */ return (EMLINK); case 0x0b: /* ACL connection already exists */ return (EEXIST); case 0x0c: /* Command disallowed */ return (EBUSY); case 0x0d: /* Host rejected due to limited resources */ case 0x0e: /* Host rejected due to securiity reasons */ case 0x0f: /* Host rejected due to remote unit is a personal unit */ case 0x1b: /* SCO offset rejected */ case 0x1c: /* SCO interval rejected */ case 0x1d: /* SCO air mode rejected */ return (ECONNREFUSED); case 0x11: /* Unsupported feature or parameter value */ case 0x19: /* Unknown LMP PDU */ case 0x1a: /* Unsupported remote feature */ case 0x20: /* Unsupported LMP parameter value */ case 0x27: /* QoS is not supported */ case 0x29: /* Paring with unit key not supported */ return (EOPNOTSUPP); case 0x12: /* Invalid HCI command parameter */ case 0x1e: /* Invalid LMP parameters */ return (EINVAL); case 0x13: /* Other end terminated connection: User ended connection */ case 0x14: /* Other end terminated connection: Low resources */ case 0x15: /* Other end terminated connection: About to power off */ return (ECONNRESET); case 0x16: /* Connection terminated by local host */ return (ECONNABORTED); #if 0 /* XXX not yet */ case 0x17: /* Repeated attempts */ case 0x1f: /* Unspecified error */ case 0x23: /* LMP error transaction collision */ case 0x28: /* Instant passed */ #endif } return (ENOSYS); } /* ng_btsocket_l2cap_result2errno */ diff --git a/sys/netgraph/bluetooth/socket/ng_btsocket_l2cap_raw.c b/sys/netgraph/bluetooth/socket/ng_btsocket_l2cap_raw.c index 0fda1a1dd1f5..85700f981c68 100644 --- a/sys/netgraph/bluetooth/socket/ng_btsocket_l2cap_raw.c +++ b/sys/netgraph/bluetooth/socket/ng_btsocket_l2cap_raw.c @@ -1,1303 +1,1297 @@ /* * ng_btsocket_l2cap_raw.c */ /*- * Copyright (c) 2001-2002 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ng_btsocket_l2cap_raw.c,v 1.12 2003/09/14 23:29:06 max Exp $ * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* MALLOC define */ #ifdef NG_SEPARATE_MALLOC MALLOC_DEFINE(M_NETGRAPH_BTSOCKET_L2CAP_RAW, "netgraph_btsocks_l2cap_raw", "Netgraph Bluetooth raw L2CAP sockets"); #else #define M_NETGRAPH_BTSOCKET_L2CAP_RAW M_NETGRAPH #endif /* NG_SEPARATE_MALLOC */ /* Netgraph node methods */ static ng_constructor_t ng_btsocket_l2cap_raw_node_constructor; static ng_rcvmsg_t ng_btsocket_l2cap_raw_node_rcvmsg; static ng_shutdown_t ng_btsocket_l2cap_raw_node_shutdown; static ng_newhook_t ng_btsocket_l2cap_raw_node_newhook; static ng_connect_t ng_btsocket_l2cap_raw_node_connect; static ng_rcvdata_t ng_btsocket_l2cap_raw_node_rcvdata; static ng_disconnect_t ng_btsocket_l2cap_raw_node_disconnect; static void ng_btsocket_l2cap_raw_input (void *, int); static void ng_btsocket_l2cap_raw_rtclean (void *, int); static void ng_btsocket_l2cap_raw_get_token (u_int32_t *); static int ng_btsocket_l2cap_raw_send_ngmsg (hook_p, int, void *, int); static int ng_btsocket_l2cap_raw_send_sync_ngmsg (ng_btsocket_l2cap_raw_pcb_p, int, void *, int); #define ng_btsocket_l2cap_raw_wakeup_input_task() \ taskqueue_enqueue(taskqueue_swi, &ng_btsocket_l2cap_raw_queue_task) #define ng_btsocket_l2cap_raw_wakeup_route_task() \ taskqueue_enqueue(taskqueue_swi, &ng_btsocket_l2cap_raw_rt_task) /* Netgraph type descriptor */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_BTSOCKET_L2CAP_RAW_NODE_TYPE, .constructor = ng_btsocket_l2cap_raw_node_constructor, .rcvmsg = ng_btsocket_l2cap_raw_node_rcvmsg, .shutdown = ng_btsocket_l2cap_raw_node_shutdown, .newhook = ng_btsocket_l2cap_raw_node_newhook, .connect = ng_btsocket_l2cap_raw_node_connect, .rcvdata = ng_btsocket_l2cap_raw_node_rcvdata, .disconnect = ng_btsocket_l2cap_raw_node_disconnect, }; /* Globals */ extern int ifqmaxlen; static u_int32_t ng_btsocket_l2cap_raw_debug_level; static u_int32_t ng_btsocket_l2cap_raw_ioctl_timeout; static node_p ng_btsocket_l2cap_raw_node; static struct ng_bt_itemq ng_btsocket_l2cap_raw_queue; static struct mtx ng_btsocket_l2cap_raw_queue_mtx; static struct task ng_btsocket_l2cap_raw_queue_task; static LIST_HEAD(, ng_btsocket_l2cap_raw_pcb) ng_btsocket_l2cap_raw_sockets; static struct mtx ng_btsocket_l2cap_raw_sockets_mtx; static u_int32_t ng_btsocket_l2cap_raw_token; static struct mtx ng_btsocket_l2cap_raw_token_mtx; static LIST_HEAD(, ng_btsocket_l2cap_rtentry) ng_btsocket_l2cap_raw_rt; static struct mtx ng_btsocket_l2cap_raw_rt_mtx; static struct task ng_btsocket_l2cap_raw_rt_task; /* Sysctl tree */ SYSCTL_DECL(_net_bluetooth_l2cap_sockets); SYSCTL_NODE(_net_bluetooth_l2cap_sockets, OID_AUTO, raw, CTLFLAG_RW, 0, "Bluetooth raw L2CAP sockets family"); SYSCTL_INT(_net_bluetooth_l2cap_sockets_raw, OID_AUTO, debug_level, CTLFLAG_RW, &ng_btsocket_l2cap_raw_debug_level, NG_BTSOCKET_WARN_LEVEL, "Bluetooth raw L2CAP sockets debug level"); SYSCTL_INT(_net_bluetooth_l2cap_sockets_raw, OID_AUTO, ioctl_timeout, CTLFLAG_RW, &ng_btsocket_l2cap_raw_ioctl_timeout, 5, "Bluetooth raw L2CAP sockets ioctl timeout"); SYSCTL_INT(_net_bluetooth_l2cap_sockets_raw, OID_AUTO, queue_len, CTLFLAG_RD, &ng_btsocket_l2cap_raw_queue.len, 0, "Bluetooth raw L2CAP sockets input queue length"); SYSCTL_INT(_net_bluetooth_l2cap_sockets_raw, OID_AUTO, queue_maxlen, CTLFLAG_RD, &ng_btsocket_l2cap_raw_queue.maxlen, 0, "Bluetooth raw L2CAP sockets input queue max. length"); SYSCTL_INT(_net_bluetooth_l2cap_sockets_raw, OID_AUTO, queue_drops, CTLFLAG_RD, &ng_btsocket_l2cap_raw_queue.drops, 0, "Bluetooth raw L2CAP sockets input queue drops"); /* Debug */ #define NG_BTSOCKET_L2CAP_RAW_INFO \ if (ng_btsocket_l2cap_raw_debug_level >= NG_BTSOCKET_INFO_LEVEL) \ printf #define NG_BTSOCKET_L2CAP_RAW_WARN \ if (ng_btsocket_l2cap_raw_debug_level >= NG_BTSOCKET_WARN_LEVEL) \ printf #define NG_BTSOCKET_L2CAP_RAW_ERR \ if (ng_btsocket_l2cap_raw_debug_level >= NG_BTSOCKET_ERR_LEVEL) \ printf #define NG_BTSOCKET_L2CAP_RAW_ALERT \ if (ng_btsocket_l2cap_raw_debug_level >= NG_BTSOCKET_ALERT_LEVEL) \ printf /***************************************************************************** ***************************************************************************** ** Netgraph node interface ***************************************************************************** *****************************************************************************/ /* * Netgraph node constructor. Do not allow to create node of this type. */ static int ng_btsocket_l2cap_raw_node_constructor(node_p node) { return (EINVAL); } /* ng_btsocket_l2cap_raw_node_constructor */ /* * Do local shutdown processing. Let old node go and create new fresh one. */ static int ng_btsocket_l2cap_raw_node_shutdown(node_p node) { int error = 0; NG_NODE_UNREF(node); /* Create new node */ error = ng_make_node_common(&typestruct, &ng_btsocket_l2cap_raw_node); if (error != 0) { NG_BTSOCKET_L2CAP_RAW_ALERT( "%s: Could not create Netgraph node, error=%d\n", __func__, error); ng_btsocket_l2cap_raw_node = NULL; return (error); } error = ng_name_node(ng_btsocket_l2cap_raw_node, NG_BTSOCKET_L2CAP_RAW_NODE_TYPE); if (error != 0) { NG_BTSOCKET_L2CAP_RAW_ALERT( "%s: Could not name Netgraph node, error=%d\n", __func__, error); NG_NODE_UNREF(ng_btsocket_l2cap_raw_node); ng_btsocket_l2cap_raw_node = NULL; return (error); } return (0); } /* ng_btsocket_l2cap_raw_node_shutdown */ /* * We allow any hook to be connected to the node. */ static int ng_btsocket_l2cap_raw_node_newhook(node_p node, hook_p hook, char const *name) { return (0); } /* ng_btsocket_l2cap_raw_node_newhook */ /* * Just say "YEP, that's OK by me!" */ static int ng_btsocket_l2cap_raw_node_connect(hook_p hook) { NG_HOOK_SET_PRIVATE(hook, NULL); NG_HOOK_REF(hook); /* Keep extra reference to the hook */ return (0); } /* ng_btsocket_l2cap_raw_node_connect */ /* * Hook disconnection. Schedule route cleanup task */ static int ng_btsocket_l2cap_raw_node_disconnect(hook_p hook) { /* * If hook has private information than we must have this hook in * the routing table and must schedule cleaning for the routing table. * Otherwise hook was connected but we never got "hook_info" message, * so we have never added this hook to the routing table and it save * to just delete it. */ if (NG_HOOK_PRIVATE(hook) != NULL) return (ng_btsocket_l2cap_raw_wakeup_route_task()); NG_HOOK_UNREF(hook); /* Remove extra reference */ return (0); } /* ng_btsocket_l2cap_raw_node_disconnect */ /* * Process incoming messages */ static int ng_btsocket_l2cap_raw_node_rcvmsg(node_p node, item_p item, hook_p hook) { struct ng_mesg *msg = NGI_MSG(item); /* item still has message */ int error = 0; if (msg != NULL && msg->header.typecookie == NGM_L2CAP_COOKIE) { /* * NGM_L2CAP_NODE_HOOK_INFO is special message initiated by * L2CAP layer. Ignore all other messages if they are not * replies or token is zero */ if (msg->header.cmd != NGM_L2CAP_NODE_HOOK_INFO) { if (msg->header.token == 0 || !(msg->header.flags & NGF_RESP)) { NG_FREE_ITEM(item); return (0); } } mtx_lock(&ng_btsocket_l2cap_raw_queue_mtx); if (NG_BT_ITEMQ_FULL(&ng_btsocket_l2cap_raw_queue)) { NG_BTSOCKET_L2CAP_RAW_ERR( "%s: Input queue is full\n", __func__); NG_BT_ITEMQ_DROP(&ng_btsocket_l2cap_raw_queue); NG_FREE_ITEM(item); error = ENOBUFS; } else { if (hook != NULL) { NG_HOOK_REF(hook); NGI_SET_HOOK(item, hook); } NG_BT_ITEMQ_ENQUEUE(&ng_btsocket_l2cap_raw_queue, item); error = ng_btsocket_l2cap_raw_wakeup_input_task(); } mtx_unlock(&ng_btsocket_l2cap_raw_queue_mtx); } else { NG_FREE_ITEM(item); error = EINVAL; } return (error); } /* ng_btsocket_l2cap_raw_node_rcvmsg */ /* * Receive data on a hook */ static int ng_btsocket_l2cap_raw_node_rcvdata(hook_p hook, item_p item) { NG_FREE_ITEM(item); return (EINVAL); } /* ng_btsocket_l2cap_raw_node_rcvdata */ /***************************************************************************** ***************************************************************************** ** Socket interface ***************************************************************************** *****************************************************************************/ /* * L2CAP sockets input routine */ static void ng_btsocket_l2cap_raw_input(void *context, int pending) { item_p item = NULL; hook_p hook = NULL; struct ng_mesg *msg = NULL; for (;;) { mtx_lock(&ng_btsocket_l2cap_raw_queue_mtx); NG_BT_ITEMQ_DEQUEUE(&ng_btsocket_l2cap_raw_queue, item); mtx_unlock(&ng_btsocket_l2cap_raw_queue_mtx); if (item == NULL) break; KASSERT((item->el_flags & NGQF_TYPE) == NGQF_MESG, ("%s: invalid item type=%ld\n", __func__, (item->el_flags & NGQF_TYPE))); NGI_GET_MSG(item, msg); NGI_GET_HOOK(item, hook); NG_FREE_ITEM(item); switch (msg->header.cmd) { case NGM_L2CAP_NODE_HOOK_INFO: { ng_btsocket_l2cap_rtentry_t *rt = NULL; if (hook == NULL || NG_HOOK_NOT_VALID(hook) || msg->header.arglen != sizeof(bdaddr_t)) break; if (bcmp(msg->data, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) break; rt = (ng_btsocket_l2cap_rtentry_t *) NG_HOOK_PRIVATE(hook); if (rt == NULL) { MALLOC(rt, ng_btsocket_l2cap_rtentry_p, sizeof(*rt), M_NETGRAPH_BTSOCKET_L2CAP_RAW, M_NOWAIT|M_ZERO); if (rt == NULL) break; NG_HOOK_SET_PRIVATE(hook, rt); mtx_lock(&ng_btsocket_l2cap_raw_rt_mtx); LIST_INSERT_HEAD(&ng_btsocket_l2cap_raw_rt, rt, next); } else mtx_lock(&ng_btsocket_l2cap_raw_rt_mtx); bcopy(msg->data, &rt->src, sizeof(rt->src)); rt->hook = hook; NG_BTSOCKET_L2CAP_RAW_INFO( "%s: Updating hook \"%s\", src bdaddr=%x:%x:%x:%x:%x:%x\n", __func__, NG_HOOK_NAME(hook), rt->src.b[5], rt->src.b[4], rt->src.b[3], rt->src.b[2], rt->src.b[1], rt->src.b[0]); mtx_unlock(&ng_btsocket_l2cap_raw_rt_mtx); } break; case NGM_L2CAP_NODE_GET_FLAGS: case NGM_L2CAP_NODE_GET_DEBUG: case NGM_L2CAP_NODE_GET_CON_LIST: case NGM_L2CAP_NODE_GET_CHAN_LIST: case NGM_L2CAP_NODE_GET_AUTO_DISCON_TIMO: case NGM_L2CAP_L2CA_PING: case NGM_L2CAP_L2CA_GET_INFO: { ng_btsocket_l2cap_raw_pcb_p pcb = NULL; mtx_lock(&ng_btsocket_l2cap_raw_sockets_mtx); LIST_FOREACH(pcb,&ng_btsocket_l2cap_raw_sockets,next) { mtx_lock(&pcb->pcb_mtx); if (pcb->token == msg->header.token) { pcb->msg = msg; msg = NULL; wakeup(&pcb->msg); mtx_unlock(&pcb->pcb_mtx); break; } mtx_unlock(&pcb->pcb_mtx); } mtx_unlock(&ng_btsocket_l2cap_raw_sockets_mtx); } break; default: NG_BTSOCKET_L2CAP_RAW_WARN( "%s: Unknown message, cmd=%d\n", __func__, msg->header.cmd); break; } if (hook != NULL) NG_HOOK_UNREF(hook); /* remove extra reference */ NG_FREE_MSG(msg); /* Checks for msg != NULL */ } } /* ng_btsocket_l2cap_raw_input */ /* * Route cleanup task. Gets scheduled when hook is disconnected. Here we * will find all sockets that use "invalid" hook and disconnect them. */ static void ng_btsocket_l2cap_raw_rtclean(void *context, int pending) { ng_btsocket_l2cap_raw_pcb_p pcb = NULL; ng_btsocket_l2cap_rtentry_p rt = NULL; /* * First disconnect all sockets that use "invalid" hook */ mtx_lock(&ng_btsocket_l2cap_raw_sockets_mtx); LIST_FOREACH(pcb, &ng_btsocket_l2cap_raw_sockets, next) { mtx_lock(&pcb->pcb_mtx); if (pcb->rt != NULL && pcb->rt->hook != NULL && NG_HOOK_NOT_VALID(pcb->rt->hook)) { if (pcb->so != NULL && pcb->so->so_state & SS_ISCONNECTED) soisdisconnected(pcb->so); pcb->rt = NULL; } mtx_unlock(&pcb->pcb_mtx); } mtx_unlock(&ng_btsocket_l2cap_raw_sockets_mtx); /* * Now cleanup routing table */ mtx_lock(&ng_btsocket_l2cap_raw_rt_mtx); for (rt = LIST_FIRST(&ng_btsocket_l2cap_raw_rt); rt != NULL; ) { ng_btsocket_l2cap_rtentry_p rt_next = LIST_NEXT(rt, next); if (rt->hook != NULL && NG_HOOK_NOT_VALID(rt->hook)) { LIST_REMOVE(rt, next); NG_HOOK_SET_PRIVATE(rt->hook, NULL); NG_HOOK_UNREF(rt->hook); /* Remove extra reference */ bzero(rt, sizeof(*rt)); FREE(rt, M_NETGRAPH_BTSOCKET_L2CAP_RAW); } rt = rt_next; } mtx_unlock(&ng_btsocket_l2cap_raw_rt_mtx); } /* ng_btsocket_l2cap_raw_rtclean */ /* * Initialize everything */ void ng_btsocket_l2cap_raw_init(void) { int error = 0; ng_btsocket_l2cap_raw_node = NULL; ng_btsocket_l2cap_raw_debug_level = NG_BTSOCKET_WARN_LEVEL; ng_btsocket_l2cap_raw_ioctl_timeout = 5; /* Register Netgraph node type */ error = ng_newtype(&typestruct); if (error != 0) { NG_BTSOCKET_L2CAP_RAW_ALERT( "%s: Could not register Netgraph node type, error=%d\n", __func__, error); return; } /* Create Netgrapg node */ error = ng_make_node_common(&typestruct, &ng_btsocket_l2cap_raw_node); if (error != 0) { NG_BTSOCKET_L2CAP_RAW_ALERT( "%s: Could not create Netgraph node, error=%d\n", __func__, error); ng_btsocket_l2cap_raw_node = NULL; return; } error = ng_name_node(ng_btsocket_l2cap_raw_node, NG_BTSOCKET_L2CAP_RAW_NODE_TYPE); if (error != 0) { NG_BTSOCKET_L2CAP_RAW_ALERT( "%s: Could not name Netgraph node, error=%d\n", __func__, error); NG_NODE_UNREF(ng_btsocket_l2cap_raw_node); ng_btsocket_l2cap_raw_node = NULL; return; } /* Create input queue */ NG_BT_ITEMQ_INIT(&ng_btsocket_l2cap_raw_queue, ifqmaxlen); mtx_init(&ng_btsocket_l2cap_raw_queue_mtx, "btsocks_l2cap_raw_queue_mtx", NULL, MTX_DEF); TASK_INIT(&ng_btsocket_l2cap_raw_queue_task, 0, ng_btsocket_l2cap_raw_input, NULL); /* Create list of sockets */ LIST_INIT(&ng_btsocket_l2cap_raw_sockets); mtx_init(&ng_btsocket_l2cap_raw_sockets_mtx, "btsocks_l2cap_raw_sockets_mtx", NULL, MTX_DEF); /* Tokens */ ng_btsocket_l2cap_raw_token = 0; mtx_init(&ng_btsocket_l2cap_raw_token_mtx, "btsocks_l2cap_raw_token_mtx", NULL, MTX_DEF); /* Routing table */ LIST_INIT(&ng_btsocket_l2cap_raw_rt); mtx_init(&ng_btsocket_l2cap_raw_rt_mtx, "btsocks_l2cap_raw_rt_mtx", NULL, MTX_DEF); TASK_INIT(&ng_btsocket_l2cap_raw_rt_task, 0, ng_btsocket_l2cap_raw_rtclean, NULL); } /* ng_btsocket_l2cap_raw_init */ /* * Abort connection on socket */ void ng_btsocket_l2cap_raw_abort(struct socket *so) { ng_btsocket_l2cap_raw_detach(so); } /* ng_btsocket_l2cap_raw_abort */ /* * Create and attach new socket */ int ng_btsocket_l2cap_raw_attach(struct socket *so, int proto, struct thread *td) { ng_btsocket_l2cap_raw_pcb_p pcb = so2l2cap_raw_pcb(so); int error; if (pcb != NULL) return (EISCONN); if (ng_btsocket_l2cap_raw_node == NULL) return (EPROTONOSUPPORT); if (so->so_type != SOCK_RAW) return (ESOCKTNOSUPPORT); /* Reserve send and receive space if it is not reserved yet */ error = soreserve(so, NG_BTSOCKET_L2CAP_RAW_SENDSPACE, NG_BTSOCKET_L2CAP_RAW_RECVSPACE); if (error != 0) return (error); /* Allocate the PCB */ MALLOC(pcb, ng_btsocket_l2cap_raw_pcb_p, sizeof(*pcb), M_NETGRAPH_BTSOCKET_L2CAP_RAW, M_NOWAIT|M_ZERO); if (pcb == NULL) return (ENOMEM); /* Link the PCB and the socket */ so->so_pcb = (caddr_t) pcb; pcb->so = so; if (suser(td) == 0) pcb->flags |= NG_BTSOCKET_L2CAP_RAW_PRIVILEGED; mtx_init(&pcb->pcb_mtx, "btsocks_l2cap_raw_pcb_mtx", NULL, MTX_DEF); /* Add the PCB to the list */ mtx_lock(&ng_btsocket_l2cap_raw_sockets_mtx); LIST_INSERT_HEAD(&ng_btsocket_l2cap_raw_sockets, pcb, next); mtx_unlock(&ng_btsocket_l2cap_raw_sockets_mtx); return (0); } /* ng_btsocket_l2cap_raw_attach */ /* * Bind socket */ int ng_btsocket_l2cap_raw_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { ng_btsocket_l2cap_raw_pcb_t *pcb = so2l2cap_raw_pcb(so); struct sockaddr_l2cap *sa = (struct sockaddr_l2cap *) nam; ng_btsocket_l2cap_rtentry_t *rt = NULL; if (pcb == NULL) return (EINVAL); if (ng_btsocket_l2cap_raw_node == NULL) return (EINVAL); if (sa == NULL) return (EINVAL); if (sa->l2cap_family != AF_BLUETOOTH) return (EAFNOSUPPORT); if (sa->l2cap_len != sizeof(*sa)) return (EINVAL); if (bcmp(&sa->l2cap_bdaddr, NG_HCI_BDADDR_ANY, sizeof(sa->l2cap_bdaddr)) != 0) { mtx_lock(&ng_btsocket_l2cap_raw_rt_mtx); LIST_FOREACH(rt, &ng_btsocket_l2cap_raw_rt, next) { if (rt->hook == NULL || NG_HOOK_NOT_VALID(rt->hook)) continue; if (bcmp(&sa->l2cap_bdaddr, &rt->src, sizeof(rt->src)) == 0) break; } mtx_unlock(&ng_btsocket_l2cap_raw_rt_mtx); if (rt == NULL) return (ENETDOWN); } else rt = NULL; mtx_lock(&pcb->pcb_mtx); bcopy(&sa->l2cap_bdaddr, &pcb->src, sizeof(pcb->src)); pcb->rt = rt; mtx_unlock(&pcb->pcb_mtx); return (0); } /* ng_btsocket_l2cap_raw_bind */ /* * Connect socket */ int ng_btsocket_l2cap_raw_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { ng_btsocket_l2cap_raw_pcb_t *pcb = so2l2cap_raw_pcb(so); struct sockaddr_l2cap *sa = (struct sockaddr_l2cap *) nam; ng_btsocket_l2cap_rtentry_t *rt = NULL; int error; if (pcb == NULL) return (EINVAL); if (ng_btsocket_l2cap_raw_node == NULL) return (EINVAL); if (sa == NULL) return (EINVAL); if (sa->l2cap_family != AF_BLUETOOTH) return (EAFNOSUPPORT); if (sa->l2cap_len != sizeof(*sa)) return (EINVAL); if (bcmp(&sa->l2cap_bdaddr, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) return (EINVAL); mtx_lock(&pcb->pcb_mtx); bcopy(&sa->l2cap_bdaddr, &pcb->dst, sizeof(pcb->dst)); if (bcmp(&pcb->src, &pcb->dst, sizeof(pcb->src)) == 0) { mtx_unlock(&pcb->pcb_mtx); return (EADDRNOTAVAIL); } /* * If there is route already - use it */ if (pcb->rt != NULL) { soisconnected(so); mtx_unlock(&pcb->pcb_mtx); return (0); } /* * Find the first hook that does not match specified destination address */ mtx_lock(&ng_btsocket_l2cap_raw_rt_mtx); LIST_FOREACH(rt, &ng_btsocket_l2cap_raw_rt, next) { if (rt->hook == NULL || NG_HOOK_NOT_VALID(rt->hook)) continue; if (bcmp(&pcb->dst, &rt->src, sizeof(rt->src)) != 0) break; } if (rt != NULL) { soisconnected(so); pcb->rt = rt; bcopy(&rt->src, &pcb->src, sizeof(pcb->src)); error = 0; } else error = ENETDOWN; mtx_unlock(&ng_btsocket_l2cap_raw_rt_mtx); mtx_unlock(&pcb->pcb_mtx); return (error); } /* ng_btsocket_l2cap_raw_connect */ /* * Process ioctl's calls on socket */ int ng_btsocket_l2cap_raw_control(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp, struct thread *td) { ng_btsocket_l2cap_raw_pcb_p pcb = so2l2cap_raw_pcb(so); struct ng_mesg *msg = NULL; int error = 0; if (pcb == NULL) return (EINVAL); if (ng_btsocket_l2cap_raw_node == NULL) return (EINVAL); mtx_lock(&pcb->pcb_mtx); /* Check if we route info */ if (pcb->rt == NULL) { mtx_unlock(&pcb->pcb_mtx); return (EHOSTUNREACH); } /* Check if we have pending ioctl() */ if (pcb->token != 0) { mtx_unlock(&pcb->pcb_mtx); return (EBUSY); } switch (cmd) { case SIOC_L2CAP_NODE_GET_FLAGS: { struct ng_btsocket_l2cap_raw_node_flags *p = (struct ng_btsocket_l2cap_raw_node_flags *) data; error = ng_btsocket_l2cap_raw_send_sync_ngmsg(pcb, NGM_L2CAP_NODE_GET_FLAGS, &p->flags, sizeof(p->flags)); } break; case SIOC_L2CAP_NODE_GET_DEBUG: { struct ng_btsocket_l2cap_raw_node_debug *p = (struct ng_btsocket_l2cap_raw_node_debug *) data; error = ng_btsocket_l2cap_raw_send_sync_ngmsg(pcb, NGM_L2CAP_NODE_GET_DEBUG, &p->debug, sizeof(p->debug)); } break; case SIOC_L2CAP_NODE_SET_DEBUG: { struct ng_btsocket_l2cap_raw_node_debug *p = (struct ng_btsocket_l2cap_raw_node_debug *) data; if (pcb->flags & NG_BTSOCKET_L2CAP_RAW_PRIVILEGED) error = ng_btsocket_l2cap_raw_send_ngmsg(pcb->rt->hook, NGM_L2CAP_NODE_SET_DEBUG, &p->debug, sizeof(p->debug)); else error = EPERM; } break; case SIOC_L2CAP_NODE_GET_CON_LIST: { struct ng_btsocket_l2cap_raw_con_list *p = (struct ng_btsocket_l2cap_raw_con_list *) data; ng_l2cap_node_con_list_ep *p1 = NULL; ng_l2cap_node_con_ep *p2 = NULL; if (p->num_connections == 0 || p->num_connections > NG_L2CAP_MAX_CON_NUM || p->connections == NULL) { error = EINVAL; break; } NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_NODE_GET_CON_LIST, 0, M_NOWAIT); if (msg == NULL) { error = ENOMEM; break; } ng_btsocket_l2cap_raw_get_token(&msg->header.token); pcb->token = msg->header.token; pcb->msg = NULL; NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, msg, pcb->rt->hook, 0); if (error != 0) { pcb->token = 0; break; } error = msleep(&pcb->msg, &pcb->pcb_mtx, PZERO|PCATCH, "l2ctl", ng_btsocket_l2cap_raw_ioctl_timeout * hz); pcb->token = 0; if (error != 0) break; if (pcb->msg != NULL && pcb->msg->header.cmd == NGM_L2CAP_NODE_GET_CON_LIST) { /* Return data back to user space */ p1 = (ng_l2cap_node_con_list_ep *)(pcb->msg->data); p2 = (ng_l2cap_node_con_ep *)(p1 + 1); p->num_connections = min(p->num_connections, p1->num_connections); if (p->num_connections > 0) error = copyout((caddr_t) p2, (caddr_t) p->connections, p->num_connections * sizeof(*p2)); } else error = EINVAL; NG_FREE_MSG(pcb->msg); /* checks for != NULL */ } break; case SIOC_L2CAP_NODE_GET_CHAN_LIST: { struct ng_btsocket_l2cap_raw_chan_list *p = (struct ng_btsocket_l2cap_raw_chan_list *) data; ng_l2cap_node_chan_list_ep *p1 = NULL; ng_l2cap_node_chan_ep *p2 = NULL; if (p->num_channels == 0 || p->num_channels > NG_L2CAP_MAX_CHAN_NUM || p->channels == NULL) { error = EINVAL; break; } NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_NODE_GET_CHAN_LIST, 0, M_NOWAIT); if (msg == NULL) { error = ENOMEM; break; } ng_btsocket_l2cap_raw_get_token(&msg->header.token); pcb->token = msg->header.token; pcb->msg = NULL; NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, msg, pcb->rt->hook, 0); if (error != 0) { pcb->token = 0; break; } error = msleep(&pcb->msg, &pcb->pcb_mtx, PZERO|PCATCH, "l2ctl", ng_btsocket_l2cap_raw_ioctl_timeout * hz); pcb->token = 0; if (error != 0) break; if (pcb->msg != NULL && pcb->msg->header.cmd == NGM_L2CAP_NODE_GET_CHAN_LIST) { /* Return data back to user space */ p1 = (ng_l2cap_node_chan_list_ep *)(pcb->msg->data); p2 = (ng_l2cap_node_chan_ep *)(p1 + 1); p->num_channels = min(p->num_channels, p1->num_channels); if (p->num_channels > 0) error = copyout((caddr_t) p2, (caddr_t) p->channels, p->num_channels * sizeof(*p2)); } else error = EINVAL; NG_FREE_MSG(pcb->msg); /* checks for != NULL */ } break; case SIOC_L2CAP_L2CA_PING: { struct ng_btsocket_l2cap_raw_ping *p = (struct ng_btsocket_l2cap_raw_ping *) data; ng_l2cap_l2ca_ping_ip *ip = NULL; ng_l2cap_l2ca_ping_op *op = NULL; if (!(pcb->flags & NG_BTSOCKET_L2CAP_RAW_PRIVILEGED)) { error = EPERM; break; } if ((p->echo_size != 0 && p->echo_data == NULL) || p->echo_size > NG_L2CAP_MAX_ECHO_SIZE) { error = EINVAL; break; } NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_PING, sizeof(*ip) + p->echo_size, M_NOWAIT); if (msg == NULL) { error = ENOMEM; break; } ng_btsocket_l2cap_raw_get_token(&msg->header.token); pcb->token = msg->header.token; pcb->msg = NULL; ip = (ng_l2cap_l2ca_ping_ip *)(msg->data); bcopy(&pcb->dst, &ip->bdaddr, sizeof(ip->bdaddr)); ip->echo_size = p->echo_size; if (ip->echo_size > 0) { error = copyin(p->echo_data, ip + 1, p->echo_size); if (error != 0) { NG_FREE_MSG(msg); pcb->token = 0; break; } } NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, msg, pcb->rt->hook, 0); if (error != 0) { pcb->token = 0; break; } error = msleep(&pcb->msg, &pcb->pcb_mtx, PZERO|PCATCH, "l2ctl", bluetooth_l2cap_rtx_timeout()); pcb->token = 0; if (error != 0) break; if (pcb->msg != NULL && pcb->msg->header.cmd == NGM_L2CAP_L2CA_PING) { /* Return data back to the user space */ op = (ng_l2cap_l2ca_ping_op *)(pcb->msg->data); p->result = op->result; p->echo_size = min(p->echo_size, op->echo_size); if (p->echo_size > 0) error = copyout(op + 1, p->echo_data, p->echo_size); } else error = EINVAL; NG_FREE_MSG(pcb->msg); /* checks for != NULL */ } break; case SIOC_L2CAP_L2CA_GET_INFO: { struct ng_btsocket_l2cap_raw_get_info *p = (struct ng_btsocket_l2cap_raw_get_info *) data; ng_l2cap_l2ca_get_info_ip *ip = NULL; ng_l2cap_l2ca_get_info_op *op = NULL; if (!(pcb->flags & NG_BTSOCKET_L2CAP_RAW_PRIVILEGED)) { error = EPERM; break; } if (p->info_size != 0 && p->info_data == NULL) { error = EINVAL; break; } NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_L2CA_GET_INFO, sizeof(*ip) + p->info_size, M_NOWAIT); if (msg == NULL) { error = ENOMEM; break; } ng_btsocket_l2cap_raw_get_token(&msg->header.token); pcb->token = msg->header.token; pcb->msg = NULL; ip = (ng_l2cap_l2ca_get_info_ip *)(msg->data); bcopy(&pcb->dst, &ip->bdaddr, sizeof(ip->bdaddr)); ip->info_type = p->info_type; NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, msg, pcb->rt->hook, 0); if (error != 0) { pcb->token = 0; break; } error = msleep(&pcb->msg, &pcb->pcb_mtx, PZERO|PCATCH, "l2ctl", bluetooth_l2cap_rtx_timeout()); pcb->token = 0; if (error != 0) break; if (pcb->msg != NULL && pcb->msg->header.cmd == NGM_L2CAP_L2CA_GET_INFO) { /* Return data back to the user space */ op = (ng_l2cap_l2ca_get_info_op *)(pcb->msg->data); p->result = op->result; p->info_size = min(p->info_size, op->info_size); if (p->info_size > 0) error = copyout(op + 1, p->info_data, p->info_size); } else error = EINVAL; NG_FREE_MSG(pcb->msg); /* checks for != NULL */ } break; case SIOC_L2CAP_NODE_GET_AUTO_DISCON_TIMO: { struct ng_btsocket_l2cap_raw_auto_discon_timo *p = (struct ng_btsocket_l2cap_raw_auto_discon_timo *) data; error = ng_btsocket_l2cap_raw_send_sync_ngmsg(pcb, NGM_L2CAP_NODE_GET_AUTO_DISCON_TIMO, &p->timeout, sizeof(p->timeout)); } break; case SIOC_L2CAP_NODE_SET_AUTO_DISCON_TIMO: { struct ng_btsocket_l2cap_raw_auto_discon_timo *p = (struct ng_btsocket_l2cap_raw_auto_discon_timo *) data; if (pcb->flags & NG_BTSOCKET_L2CAP_RAW_PRIVILEGED) error = ng_btsocket_l2cap_raw_send_ngmsg(pcb->rt->hook, NGM_L2CAP_NODE_SET_AUTO_DISCON_TIMO, &p->timeout, sizeof(p->timeout)); else error = EPERM; } break; default: error = EINVAL; break; } mtx_unlock(&pcb->pcb_mtx); return (error); } /* ng_btsocket_l2cap_raw_control */ /* * Detach and destroy socket */ -int +void ng_btsocket_l2cap_raw_detach(struct socket *so) { ng_btsocket_l2cap_raw_pcb_p pcb = so2l2cap_raw_pcb(so); - if (pcb == NULL) - return (EINVAL); + KASSERT(pcb != NULL, ("nt_btsocket_l2cap_raw_detach: pcb == NULL")); if (ng_btsocket_l2cap_raw_node == NULL) - return (EINVAL); + return; mtx_lock(&ng_btsocket_l2cap_raw_sockets_mtx); mtx_lock(&pcb->pcb_mtx); LIST_REMOVE(pcb, next); mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&ng_btsocket_l2cap_raw_sockets_mtx); mtx_destroy(&pcb->pcb_mtx); bzero(pcb, sizeof(*pcb)); FREE(pcb, M_NETGRAPH_BTSOCKET_L2CAP_RAW); - ACCEPT_LOCK(); - SOCK_LOCK(so); so->so_pcb = NULL; - sotryfree(so); - - return (0); } /* ng_btsocket_l2cap_raw_detach */ /* * Disconnect socket */ int ng_btsocket_l2cap_raw_disconnect(struct socket *so) { ng_btsocket_l2cap_raw_pcb_p pcb = so2l2cap_raw_pcb(so); if (pcb == NULL) return (EINVAL); if (ng_btsocket_l2cap_raw_node == NULL) return (EINVAL); mtx_lock(&pcb->pcb_mtx); pcb->rt = NULL; soisdisconnected(so); mtx_unlock(&pcb->pcb_mtx); return (0); } /* ng_btsocket_l2cap_raw_disconnect */ /* * Get peer address */ int ng_btsocket_l2cap_raw_peeraddr(struct socket *so, struct sockaddr **nam) { ng_btsocket_l2cap_raw_pcb_p pcb = so2l2cap_raw_pcb(so); struct sockaddr_l2cap sa; if (pcb == NULL) return (EINVAL); if (ng_btsocket_l2cap_raw_node == NULL) return (EINVAL); mtx_lock(&pcb->pcb_mtx); bcopy(&pcb->dst, &sa.l2cap_bdaddr, sizeof(sa.l2cap_bdaddr)); mtx_unlock(&pcb->pcb_mtx); sa.l2cap_psm = 0; sa.l2cap_len = sizeof(sa); sa.l2cap_family = AF_BLUETOOTH; *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); return ((*nam == NULL)? ENOMEM : 0); } /* ng_btsocket_l2cap_raw_peeraddr */ /* * Send data to socket */ int ng_btsocket_l2cap_raw_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, struct mbuf *control, struct thread *td) { NG_FREE_M(m); /* Checks for m != NULL */ NG_FREE_M(control); return (EOPNOTSUPP); } /* ng_btsocket_l2cap_raw_send */ /* * Get socket address */ int ng_btsocket_l2cap_raw_sockaddr(struct socket *so, struct sockaddr **nam) { ng_btsocket_l2cap_raw_pcb_p pcb = so2l2cap_raw_pcb(so); struct sockaddr_l2cap sa; if (pcb == NULL) return (EINVAL); if (ng_btsocket_l2cap_raw_node == NULL) return (EINVAL); mtx_lock(&pcb->pcb_mtx); bcopy(&pcb->src, &sa.l2cap_bdaddr, sizeof(sa.l2cap_bdaddr)); mtx_unlock(&pcb->pcb_mtx); sa.l2cap_psm = 0; sa.l2cap_len = sizeof(sa); sa.l2cap_family = AF_BLUETOOTH; *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); return ((*nam == NULL)? ENOMEM : 0); } /* ng_btsocket_l2cap_raw_sockaddr */ /* * Get next token */ static void ng_btsocket_l2cap_raw_get_token(u_int32_t *token) { mtx_lock(&ng_btsocket_l2cap_raw_token_mtx); if (++ ng_btsocket_l2cap_raw_token == 0) ng_btsocket_l2cap_raw_token = 1; *token = ng_btsocket_l2cap_raw_token; mtx_unlock(&ng_btsocket_l2cap_raw_token_mtx); } /* ng_btsocket_l2cap_raw_get_token */ /* * Send Netgraph message to the node - do not expect reply */ static int ng_btsocket_l2cap_raw_send_ngmsg(hook_p hook, int cmd, void *arg, int arglen) { struct ng_mesg *msg = NULL; int error = 0; NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, cmd, arglen, M_NOWAIT); if (msg == NULL) return (ENOMEM); if (arg != NULL && arglen > 0) bcopy(arg, msg->data, arglen); NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, msg, hook, 0); return (error); } /* ng_btsocket_l2cap_raw_send_ngmsg */ /* * Send Netgraph message to the node (no data) and wait for reply */ static int ng_btsocket_l2cap_raw_send_sync_ngmsg(ng_btsocket_l2cap_raw_pcb_p pcb, int cmd, void *rsp, int rsplen) { struct ng_mesg *msg = NULL; int error = 0; mtx_assert(&pcb->pcb_mtx, MA_OWNED); NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, cmd, 0, M_NOWAIT); if (msg == NULL) return (ENOMEM); ng_btsocket_l2cap_raw_get_token(&msg->header.token); pcb->token = msg->header.token; pcb->msg = NULL; NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, msg, pcb->rt->hook, 0); if (error != 0) { pcb->token = 0; return (error); } error = msleep(&pcb->msg, &pcb->pcb_mtx, PZERO|PCATCH, "l2ctl", ng_btsocket_l2cap_raw_ioctl_timeout * hz); pcb->token = 0; if (error != 0) return (error); if (pcb->msg != NULL && pcb->msg->header.cmd == cmd) bcopy(pcb->msg->data, rsp, rsplen); else error = EINVAL; NG_FREE_MSG(pcb->msg); /* checks for != NULL */ return (0); } /* ng_btsocket_l2cap_raw_send_sync_ngmsg */ diff --git a/sys/netgraph/bluetooth/socket/ng_btsocket_rfcomm.c b/sys/netgraph/bluetooth/socket/ng_btsocket_rfcomm.c index c921067aeeb8..982705fbf7f0 100644 --- a/sys/netgraph/bluetooth/socket/ng_btsocket_rfcomm.c +++ b/sys/netgraph/bluetooth/socket/ng_btsocket_rfcomm.c @@ -1,3607 +1,3601 @@ /* * ng_btsocket_rfcomm.c */ /*- * Copyright (c) 2001-2003 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ng_btsocket_rfcomm.c,v 1.28 2003/09/14 23:29:06 max Exp $ * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* MALLOC define */ #ifdef NG_SEPARATE_MALLOC MALLOC_DEFINE(M_NETGRAPH_BTSOCKET_RFCOMM, "netgraph_btsocks_rfcomm", "Netgraph Bluetooth RFCOMM sockets"); #else #define M_NETGRAPH_BTSOCKET_RFCOMM M_NETGRAPH #endif /* NG_SEPARATE_MALLOC */ /* Debug */ #define NG_BTSOCKET_RFCOMM_INFO \ if (ng_btsocket_rfcomm_debug_level >= NG_BTSOCKET_INFO_LEVEL) \ printf #define NG_BTSOCKET_RFCOMM_WARN \ if (ng_btsocket_rfcomm_debug_level >= NG_BTSOCKET_WARN_LEVEL) \ printf #define NG_BTSOCKET_RFCOMM_ERR \ if (ng_btsocket_rfcomm_debug_level >= NG_BTSOCKET_ERR_LEVEL) \ printf #define NG_BTSOCKET_RFCOMM_ALERT \ if (ng_btsocket_rfcomm_debug_level >= NG_BTSOCKET_ALERT_LEVEL) \ printf #define ALOT 0x7fff /* Local prototypes */ static void ng_btsocket_rfcomm_upcall (struct socket *so, void *arg, int waitflag); static void ng_btsocket_rfcomm_sessions_task (void *ctx, int pending); static void ng_btsocket_rfcomm_session_task (ng_btsocket_rfcomm_session_p s); #define ng_btsocket_rfcomm_task_wakeup() \ taskqueue_enqueue(taskqueue_swi_giant, &ng_btsocket_rfcomm_task) static ng_btsocket_rfcomm_pcb_p ng_btsocket_rfcomm_connect_ind (ng_btsocket_rfcomm_session_p s, int channel); static void ng_btsocket_rfcomm_connect_cfm (ng_btsocket_rfcomm_session_p s); static int ng_btsocket_rfcomm_session_create (ng_btsocket_rfcomm_session_p *sp, struct socket *l2so, bdaddr_p src, bdaddr_p dst, struct thread *td); static int ng_btsocket_rfcomm_session_accept (ng_btsocket_rfcomm_session_p s0); static int ng_btsocket_rfcomm_session_connect (ng_btsocket_rfcomm_session_p s); static int ng_btsocket_rfcomm_session_receive (ng_btsocket_rfcomm_session_p s); static int ng_btsocket_rfcomm_session_send (ng_btsocket_rfcomm_session_p s); static void ng_btsocket_rfcomm_session_clean (ng_btsocket_rfcomm_session_p s); static void ng_btsocket_rfcomm_session_process_pcb (ng_btsocket_rfcomm_session_p s); static ng_btsocket_rfcomm_session_p ng_btsocket_rfcomm_session_by_addr (bdaddr_p src, bdaddr_p dst); static int ng_btsocket_rfcomm_receive_frame (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); static int ng_btsocket_rfcomm_receive_sabm (ng_btsocket_rfcomm_session_p s, int dlci); static int ng_btsocket_rfcomm_receive_disc (ng_btsocket_rfcomm_session_p s, int dlci); static int ng_btsocket_rfcomm_receive_ua (ng_btsocket_rfcomm_session_p s, int dlci); static int ng_btsocket_rfcomm_receive_dm (ng_btsocket_rfcomm_session_p s, int dlci); static int ng_btsocket_rfcomm_receive_uih (ng_btsocket_rfcomm_session_p s, int dlci, int pf, struct mbuf *m0); static int ng_btsocket_rfcomm_receive_mcc (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); static int ng_btsocket_rfcomm_receive_test (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); static int ng_btsocket_rfcomm_receive_fc (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); static int ng_btsocket_rfcomm_receive_msc (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); static int ng_btsocket_rfcomm_receive_rpn (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); static int ng_btsocket_rfcomm_receive_rls (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); static int ng_btsocket_rfcomm_receive_pn (ng_btsocket_rfcomm_session_p s, struct mbuf *m0); static void ng_btsocket_rfcomm_set_pn (ng_btsocket_rfcomm_pcb_p pcb, u_int8_t cr, u_int8_t flow_control, u_int8_t credits, u_int16_t mtu); static int ng_btsocket_rfcomm_send_command (ng_btsocket_rfcomm_session_p s, u_int8_t type, u_int8_t dlci); static int ng_btsocket_rfcomm_send_uih (ng_btsocket_rfcomm_session_p s, u_int8_t address, u_int8_t pf, u_int8_t credits, struct mbuf *data); static int ng_btsocket_rfcomm_send_msc (ng_btsocket_rfcomm_pcb_p pcb); static int ng_btsocket_rfcomm_send_pn (ng_btsocket_rfcomm_pcb_p pcb); static int ng_btsocket_rfcomm_send_credits (ng_btsocket_rfcomm_pcb_p pcb); static int ng_btsocket_rfcomm_pcb_send (ng_btsocket_rfcomm_pcb_p pcb, int limit); static int ng_btsocket_rfcomm_pcb_kill (ng_btsocket_rfcomm_pcb_p pcb, int error); static ng_btsocket_rfcomm_pcb_p ng_btsocket_rfcomm_pcb_by_channel (bdaddr_p src, int channel); static ng_btsocket_rfcomm_pcb_p ng_btsocket_rfcomm_pcb_by_dlci (ng_btsocket_rfcomm_session_p s, int dlci); static ng_btsocket_rfcomm_pcb_p ng_btsocket_rfcomm_pcb_listener (bdaddr_p src, int channel); static void ng_btsocket_rfcomm_timeout (ng_btsocket_rfcomm_pcb_p pcb); static void ng_btsocket_rfcomm_untimeout (ng_btsocket_rfcomm_pcb_p pcb); static void ng_btsocket_rfcomm_process_timeout (void *xpcb); static struct mbuf * ng_btsocket_rfcomm_prepare_packet (struct sockbuf *sb, int length); /* Globals */ extern int ifqmaxlen; static u_int32_t ng_btsocket_rfcomm_debug_level; static u_int32_t ng_btsocket_rfcomm_timo; struct task ng_btsocket_rfcomm_task; static LIST_HEAD(, ng_btsocket_rfcomm_session) ng_btsocket_rfcomm_sessions; static struct mtx ng_btsocket_rfcomm_sessions_mtx; static LIST_HEAD(, ng_btsocket_rfcomm_pcb) ng_btsocket_rfcomm_sockets; static struct mtx ng_btsocket_rfcomm_sockets_mtx; /* Sysctl tree */ SYSCTL_DECL(_net_bluetooth_rfcomm_sockets); SYSCTL_NODE(_net_bluetooth_rfcomm_sockets, OID_AUTO, stream, CTLFLAG_RW, 0, "Bluetooth STREAM RFCOMM sockets family"); SYSCTL_INT(_net_bluetooth_rfcomm_sockets_stream, OID_AUTO, debug_level, CTLFLAG_RW, &ng_btsocket_rfcomm_debug_level, NG_BTSOCKET_INFO_LEVEL, "Bluetooth STREAM RFCOMM sockets debug level"); SYSCTL_INT(_net_bluetooth_rfcomm_sockets_stream, OID_AUTO, timeout, CTLFLAG_RW, &ng_btsocket_rfcomm_timo, 60, "Bluetooth STREAM RFCOMM sockets timeout"); /***************************************************************************** ***************************************************************************** ** RFCOMM CRC ***************************************************************************** *****************************************************************************/ static u_int8_t ng_btsocket_rfcomm_crc_table[256] = { 0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75, 0x0e, 0x9f, 0xed, 0x7c, 0x09, 0x98, 0xea, 0x7b, 0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, 0xf8, 0x69, 0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67, 0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d, 0x36, 0xa7, 0xd5, 0x44, 0x31, 0xa0, 0xd2, 0x43, 0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, 0xc0, 0x51, 0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f, 0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05, 0x7e, 0xef, 0x9d, 0x0c, 0x79, 0xe8, 0x9a, 0x0b, 0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, 0x88, 0x19, 0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17, 0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d, 0x46, 0xd7, 0xa5, 0x34, 0x41, 0xd0, 0xa2, 0x33, 0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, 0xb0, 0x21, 0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f, 0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95, 0xee, 0x7f, 0x0d, 0x9c, 0xe9, 0x78, 0x0a, 0x9b, 0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, 0x18, 0x89, 0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87, 0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad, 0xd6, 0x47, 0x35, 0xa4, 0xd1, 0x40, 0x32, 0xa3, 0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, 0x20, 0xb1, 0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf, 0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5, 0x9e, 0x0f, 0x7d, 0xec, 0x99, 0x08, 0x7a, 0xeb, 0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, 0x68, 0xf9, 0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7, 0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd, 0xa6, 0x37, 0x45, 0xd4, 0xa1, 0x30, 0x42, 0xd3, 0xb4, 0x25, 0x57, 0xc6, 0xb3, 0x22, 0x50, 0xc1, 0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf }; /* CRC */ static u_int8_t ng_btsocket_rfcomm_crc(u_int8_t *data, int length) { u_int8_t crc = 0xff; while (length --) crc = ng_btsocket_rfcomm_crc_table[crc ^ *data++]; return (crc); } /* ng_btsocket_rfcomm_crc */ /* FCS on 2 bytes */ static u_int8_t ng_btsocket_rfcomm_fcs2(u_int8_t *data) { return (0xff - ng_btsocket_rfcomm_crc(data, 2)); } /* ng_btsocket_rfcomm_fcs2 */ /* FCS on 3 bytes */ static u_int8_t ng_btsocket_rfcomm_fcs3(u_int8_t *data) { return (0xff - ng_btsocket_rfcomm_crc(data, 3)); } /* ng_btsocket_rfcomm_fcs3 */ /* * Check FCS * * From Bluetooth spec * * "... In 07.10, the frame check sequence (FCS) is calculated on different * sets of fields for different frame types. These are the fields that the * FCS are calculated on: * * For SABM, DISC, UA, DM frames: on Address, Control and length field. * For UIH frames: on Address and Control field. * * (This is stated here for clarification, and to set the standard for RFCOMM; * the fields included in FCS calculation have actually changed in version * 7.0.0 of TS 07.10, but RFCOMM will not change the FCS calculation scheme * from the one above.) ..." */ static int ng_btsocket_rfcomm_check_fcs(u_int8_t *data, int type, u_int8_t fcs) { if (type != RFCOMM_FRAME_UIH) return (ng_btsocket_rfcomm_fcs3(data) != fcs); return (ng_btsocket_rfcomm_fcs2(data) != fcs); } /* ng_btsocket_rfcomm_check_fcs */ /***************************************************************************** ***************************************************************************** ** Socket interface ***************************************************************************** *****************************************************************************/ /* * Initialize everything */ void ng_btsocket_rfcomm_init(void) { ng_btsocket_rfcomm_debug_level = NG_BTSOCKET_WARN_LEVEL; ng_btsocket_rfcomm_timo = 60; /* RFCOMM task */ TASK_INIT(&ng_btsocket_rfcomm_task, 0, ng_btsocket_rfcomm_sessions_task, NULL); /* RFCOMM sessions list */ LIST_INIT(&ng_btsocket_rfcomm_sessions); mtx_init(&ng_btsocket_rfcomm_sessions_mtx, "btsocks_rfcomm_sessions_mtx", NULL, MTX_DEF); /* RFCOMM sockets list */ LIST_INIT(&ng_btsocket_rfcomm_sockets); mtx_init(&ng_btsocket_rfcomm_sockets_mtx, "btsocks_rfcomm_sockets_mtx", NULL, MTX_DEF); } /* ng_btsocket_rfcomm_init */ /* * Abort connection on socket */ void ng_btsocket_rfcomm_abort(struct socket *so) { so->so_error = ECONNABORTED; ng_btsocket_rfcomm_detach(so); } /* ng_btsocket_rfcomm_abort */ /* * Accept connection on socket. Nothing to do here, socket must be connected * and ready, so just return peer address and be done with it. */ int ng_btsocket_rfcomm_accept(struct socket *so, struct sockaddr **nam) { return (ng_btsocket_rfcomm_peeraddr(so, nam)); } /* ng_btsocket_rfcomm_accept */ /* * Create and attach new socket */ int ng_btsocket_rfcomm_attach(struct socket *so, int proto, struct thread *td) { ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); int error; /* Check socket and protocol */ if (so->so_type != SOCK_STREAM) return (ESOCKTNOSUPPORT); #if 0 /* XXX sonewconn() calls "pru_attach" with proto == 0 */ if (proto != 0) if (proto != BLUETOOTH_PROTO_RFCOMM) return (EPROTONOSUPPORT); #endif /* XXX */ if (pcb != NULL) return (EISCONN); /* Reserve send and receive space if it is not reserved yet */ if ((so->so_snd.sb_hiwat == 0) || (so->so_rcv.sb_hiwat == 0)) { error = soreserve(so, NG_BTSOCKET_RFCOMM_SENDSPACE, NG_BTSOCKET_RFCOMM_RECVSPACE); if (error != 0) return (error); } /* Allocate the PCB */ MALLOC(pcb, ng_btsocket_rfcomm_pcb_p, sizeof(*pcb), M_NETGRAPH_BTSOCKET_RFCOMM, M_NOWAIT | M_ZERO); if (pcb == NULL) return (ENOMEM); /* Link the PCB and the socket */ so->so_pcb = (caddr_t) pcb; pcb->so = so; /* Initialize PCB */ pcb->state = NG_BTSOCKET_RFCOMM_DLC_CLOSED; pcb->flags = NG_BTSOCKET_RFCOMM_DLC_CFC; pcb->lmodem = pcb->rmodem = (RFCOMM_MODEM_RTC | RFCOMM_MODEM_RTR | RFCOMM_MODEM_DV); pcb->mtu = RFCOMM_DEFAULT_MTU; pcb->tx_cred = 0; pcb->rx_cred = RFCOMM_DEFAULT_CREDITS; mtx_init(&pcb->pcb_mtx, "btsocks_rfcomm_pcb_mtx", NULL, MTX_DEF); callout_handle_init(&pcb->timo); /* Add the PCB to the list */ mtx_lock(&ng_btsocket_rfcomm_sockets_mtx); LIST_INSERT_HEAD(&ng_btsocket_rfcomm_sockets, pcb, next); mtx_unlock(&ng_btsocket_rfcomm_sockets_mtx); return (0); } /* ng_btsocket_rfcomm_attach */ /* * Bind socket */ int ng_btsocket_rfcomm_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { ng_btsocket_rfcomm_pcb_t *pcb = so2rfcomm_pcb(so); struct sockaddr_rfcomm *sa = (struct sockaddr_rfcomm *) nam; if (pcb == NULL) return (EINVAL); /* Verify address */ if (sa == NULL) return (EINVAL); if (sa->rfcomm_family != AF_BLUETOOTH) return (EAFNOSUPPORT); if (sa->rfcomm_len != sizeof(*sa)) return (EINVAL); if (sa->rfcomm_channel > 30) return (EINVAL); if (sa->rfcomm_channel != 0 && ng_btsocket_rfcomm_pcb_by_channel(&sa->rfcomm_bdaddr, sa->rfcomm_channel) != NULL) return (EADDRINUSE); bcopy(&sa->rfcomm_bdaddr, &pcb->src, sizeof(pcb->src)); pcb->channel = sa->rfcomm_channel; return (0); } /* ng_btsocket_rfcomm_bind */ /* * Connect socket */ int ng_btsocket_rfcomm_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { ng_btsocket_rfcomm_pcb_t *pcb = so2rfcomm_pcb(so); struct sockaddr_rfcomm *sa = (struct sockaddr_rfcomm *) nam; ng_btsocket_rfcomm_session_t *s = NULL; struct socket *l2so = NULL; int dlci, error = 0; if (pcb == NULL) return (EINVAL); /* Verify address */ if (sa == NULL) return (EINVAL); if (sa->rfcomm_family != AF_BLUETOOTH) return (EAFNOSUPPORT); if (sa->rfcomm_len != sizeof(*sa)) return (EINVAL); if (sa->rfcomm_channel > 30) return (EINVAL); if (sa->rfcomm_channel == 0 || bcmp(&sa->rfcomm_bdaddr, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) return (EDESTADDRREQ); /* * XXX FIXME - This is FUBAR. socreate() will call soalloc(1), i.e. * soalloc() is allowed to sleep in MALLOC. This creates "could sleep" * WITNESS warnings. To work around this problem we will create L2CAP * socket first and then check if we actually need it. Note that we * will not check for errors in socreate() because if we failed to * create L2CAP socket at this point we still might have already open * session. */ error = socreate(PF_BLUETOOTH, &l2so, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP, td->td_ucred, td); /* * Look for session between "pcb->src" and "sa->rfcomm_bdaddr" (dst) */ mtx_lock(&ng_btsocket_rfcomm_sessions_mtx); s = ng_btsocket_rfcomm_session_by_addr(&pcb->src, &sa->rfcomm_bdaddr); if (s == NULL) { /* * We need to create new RFCOMM session. Check if we have L2CAP * socket. If l2so == NULL then error has the error code from * socreate() */ if (l2so == NULL) { mtx_unlock(&ng_btsocket_rfcomm_sessions_mtx); return (error); } error = ng_btsocket_rfcomm_session_create(&s, l2so, &pcb->src, &sa->rfcomm_bdaddr, td); if (error != 0) { mtx_unlock(&ng_btsocket_rfcomm_sessions_mtx); soclose(l2so); return (error); } } else if (l2so != NULL) soclose(l2so); /* we don't need new L2CAP socket */ /* * Check if we already have the same DLCI the the same session */ mtx_lock(&s->session_mtx); mtx_lock(&pcb->pcb_mtx); dlci = RFCOMM_MKDLCI(!INITIATOR(s), sa->rfcomm_channel); if (ng_btsocket_rfcomm_pcb_by_dlci(s, dlci) != NULL) { mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&s->session_mtx); mtx_unlock(&ng_btsocket_rfcomm_sessions_mtx); return (EBUSY); } /* * Check session state and if its not acceptable then refuse connection */ switch (s->state) { case NG_BTSOCKET_RFCOMM_SESSION_CONNECTING: case NG_BTSOCKET_RFCOMM_SESSION_CONNECTED: case NG_BTSOCKET_RFCOMM_SESSION_OPEN: /* * Update destination address and channel and attach * DLC to the session */ bcopy(&sa->rfcomm_bdaddr, &pcb->dst, sizeof(pcb->dst)); pcb->channel = sa->rfcomm_channel; pcb->dlci = dlci; LIST_INSERT_HEAD(&s->dlcs, pcb, session_next); pcb->session = s; ng_btsocket_rfcomm_timeout(pcb); soisconnecting(pcb->so); if (s->state == NG_BTSOCKET_RFCOMM_SESSION_OPEN) { pcb->mtu = s->mtu; bcopy(&so2l2cap_pcb(s->l2so)->src, &pcb->src, sizeof(pcb->src)); pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONFIGURING; error = ng_btsocket_rfcomm_send_pn(pcb); if (error == 0) error = ng_btsocket_rfcomm_task_wakeup(); } else pcb->state = NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT; break; default: error = ECONNRESET; break; } mtx_unlock(&pcb->pcb_mtx); mtx_unlock(&s->session_mtx); mtx_unlock(&ng_btsocket_rfcomm_sessions_mtx); return (error); } /* ng_btsocket_rfcomm_connect */ /* * Process ioctl's calls on socket. * XXX FIXME this should provide interface to the RFCOMM multiplexor channel */ int ng_btsocket_rfcomm_control(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp, struct thread *td) { return (EINVAL); } /* ng_btsocket_rfcomm_control */ /* * Process getsockopt/setsockopt system calls */ int ng_btsocket_rfcomm_ctloutput(struct socket *so, struct sockopt *sopt) { ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); struct ng_btsocket_rfcomm_fc_info fcinfo; int error = 0; if (pcb == NULL) return (EINVAL); if (sopt->sopt_level != SOL_RFCOMM) return (0); mtx_lock(&pcb->pcb_mtx); switch (sopt->sopt_dir) { case SOPT_GET: switch (sopt->sopt_name) { case SO_RFCOMM_MTU: error = sooptcopyout(sopt, &pcb->mtu, sizeof(pcb->mtu)); break; case SO_RFCOMM_FC_INFO: fcinfo.lmodem = pcb->lmodem; fcinfo.rmodem = pcb->rmodem; fcinfo.tx_cred = pcb->tx_cred; fcinfo.rx_cred = pcb->rx_cred; fcinfo.cfc = (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC)? 1 : 0; fcinfo.reserved = 0; error = sooptcopyout(sopt, &fcinfo, sizeof(fcinfo)); break; default: error = ENOPROTOOPT; break; } break; case SOPT_SET: switch (sopt->sopt_name) { default: error = ENOPROTOOPT; break; } break; default: error = EINVAL; break; } mtx_unlock(&pcb->pcb_mtx); return (error); } /* ng_btsocket_rfcomm_ctloutput */ /* * Detach and destroy socket */ -int +void ng_btsocket_rfcomm_detach(struct socket *so) { ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); - if (pcb == NULL) - return (EINVAL); + KASSERT(pcb != NULL, ("ng_btsocket_rfcomm_detach: pcb == NULL")); mtx_lock(&pcb->pcb_mtx); switch (pcb->state) { case NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT: case NG_BTSOCKET_RFCOMM_DLC_CONFIGURING: case NG_BTSOCKET_RFCOMM_DLC_CONNECTING: case NG_BTSOCKET_RFCOMM_DLC_CONNECTED: /* XXX What to do with pending request? */ if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO) ng_btsocket_rfcomm_untimeout(pcb); if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT) pcb->flags |= NG_BTSOCKET_RFCOMM_DLC_DETACHED; else pcb->state = NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING; ng_btsocket_rfcomm_task_wakeup(); break; case NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING: ng_btsocket_rfcomm_task_wakeup(); break; } while (pcb->state != NG_BTSOCKET_RFCOMM_DLC_CLOSED) msleep(&pcb->state, &pcb->pcb_mtx, PZERO, "rf_det", 0); if (pcb->session != NULL) panic("%s: pcb->session != NULL\n", __func__); if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO) panic("%s: timeout on closed DLC, flags=%#x\n", __func__, pcb->flags); mtx_lock(&ng_btsocket_rfcomm_sockets_mtx); LIST_REMOVE(pcb, next); mtx_unlock(&ng_btsocket_rfcomm_sockets_mtx); mtx_unlock(&pcb->pcb_mtx); mtx_destroy(&pcb->pcb_mtx); bzero(pcb, sizeof(*pcb)); FREE(pcb, M_NETGRAPH_BTSOCKET_RFCOMM); soisdisconnected(so); - ACCEPT_LOCK(); - SOCK_LOCK(so); so->so_pcb = NULL; - sotryfree(so); - - return (0); } /* ng_btsocket_rfcomm_detach */ /* * Disconnect socket */ int ng_btsocket_rfcomm_disconnect(struct socket *so) { ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); if (pcb == NULL) return (EINVAL); mtx_lock(&pcb->pcb_mtx); if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING) { mtx_unlock(&pcb->pcb_mtx); return (EINPROGRESS); } /* XXX What to do with pending request? */ if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO) ng_btsocket_rfcomm_untimeout(pcb); switch (pcb->state) { case NG_BTSOCKET_RFCOMM_DLC_CONFIGURING: /* XXX can we get here? */ case NG_BTSOCKET_RFCOMM_DLC_CONNECTING: /* XXX can we get here? */ case NG_BTSOCKET_RFCOMM_DLC_CONNECTED: /* * Just change DLC state and enqueue RFCOMM task. It will * queue and send DISC on the DLC. */ pcb->state = NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING; soisdisconnecting(so); ng_btsocket_rfcomm_task_wakeup(); break; /* * case NG_BTSOCKET_RFCOMM_DLC_CLOSED: * case NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT: * case NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING: */ default: panic("%s: Invalid DLC state=%d, flags=%#x\n", __func__, pcb->state, pcb->flags); break; } mtx_unlock(&pcb->pcb_mtx); return (0); } /* ng_btsocket_rfcomm_disconnect */ /* * Listen on socket. First call to listen() will create listening RFCOMM session */ int ng_btsocket_rfcomm_listen(struct socket *so, int backlog, struct thread *td) { ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); ng_btsocket_rfcomm_session_p s = NULL; struct socket *l2so = NULL; int error; int socreate_error; if (pcb == NULL) return (EINVAL); if (pcb->channel < 1 || pcb->channel > 30) return (EDESTADDRREQ); /* * XXX FIXME - This is FUBAR. socreate() will call soalloc(1), i.e. * soalloc() is allowed to sleep in MALLOC. This creates "could sleep" * WITNESS warnings. To work around this problem we will create L2CAP * socket first and then check if we actually need it. Note that we * will not check for errors in socreate() because if we failed to * create L2CAP socket at this point we still might have already open * session. */ socreate_error = socreate(PF_BLUETOOTH, &l2so, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP, td->td_ucred, td); /* * Transition the socket and session into the LISTENING state. Check * for collisions first, as there can only be one. */ mtx_lock(&ng_btsocket_rfcomm_sessions_mtx); SOCK_LOCK(so); error = solisten_proto_check(so); if (error != 0) goto out; LIST_FOREACH(s, &ng_btsocket_rfcomm_sessions, next) if (s->state == NG_BTSOCKET_RFCOMM_SESSION_LISTENING) break; if (s == NULL) { /* * We need to create default RFCOMM session. Check if we have * L2CAP socket. If l2so == NULL then error has the error code * from socreate() */ if (l2so == NULL) { error = socreate_error; goto out; } /* * Create default listen RFCOMM session. The default RFCOMM * session will listen on ANY address. * * XXX FIXME Note that currently there is no way to adjust MTU * for the default session. */ error = ng_btsocket_rfcomm_session_create(&s, l2so, NG_HCI_BDADDR_ANY, NULL, td); if (error != 0) goto out; l2so = NULL; } solisten_proto(so, backlog); out: SOCK_UNLOCK(so); mtx_unlock(&ng_btsocket_rfcomm_sessions_mtx); /* * If we still have an l2so reference here, it's unneeded, so release * it. */ if (l2so != NULL) soclose(l2so); return (error); } /* ng_btsocket_listen */ /* * Get peer address */ int ng_btsocket_rfcomm_peeraddr(struct socket *so, struct sockaddr **nam) { ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); struct sockaddr_rfcomm sa; if (pcb == NULL) return (EINVAL); bcopy(&pcb->dst, &sa.rfcomm_bdaddr, sizeof(sa.rfcomm_bdaddr)); sa.rfcomm_channel = pcb->channel; sa.rfcomm_len = sizeof(sa); sa.rfcomm_family = AF_BLUETOOTH; *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); return ((*nam == NULL)? ENOMEM : 0); } /* ng_btsocket_rfcomm_peeraddr */ /* * Send data to socket */ int ng_btsocket_rfcomm_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, struct mbuf *control, struct thread *td) { ng_btsocket_rfcomm_pcb_t *pcb = so2rfcomm_pcb(so); int error = 0; /* Check socket and input */ if (pcb == NULL || m == NULL || control != NULL) { error = EINVAL; goto drop; } mtx_lock(&pcb->pcb_mtx); /* Make sure DLC is connected */ if (pcb->state != NG_BTSOCKET_RFCOMM_DLC_CONNECTED) { mtx_unlock(&pcb->pcb_mtx); error = ENOTCONN; goto drop; } /* Put the packet on the socket's send queue and wakeup RFCOMM task */ sbappend(&pcb->so->so_snd, m); m = NULL; if (!(pcb->flags & NG_BTSOCKET_RFCOMM_DLC_SENDING)) { pcb->flags |= NG_BTSOCKET_RFCOMM_DLC_SENDING; error = ng_btsocket_rfcomm_task_wakeup(); } mtx_unlock(&pcb->pcb_mtx); drop: NG_FREE_M(m); /* checks for != NULL */ NG_FREE_M(control); return (error); } /* ng_btsocket_rfcomm_send */ /* * Get socket address */ int ng_btsocket_rfcomm_sockaddr(struct socket *so, struct sockaddr **nam) { ng_btsocket_rfcomm_pcb_p pcb = so2rfcomm_pcb(so); struct sockaddr_rfcomm sa; if (pcb == NULL) return (EINVAL); bcopy(&pcb->src, &sa.rfcomm_bdaddr, sizeof(sa.rfcomm_bdaddr)); sa.rfcomm_channel = pcb->channel; sa.rfcomm_len = sizeof(sa); sa.rfcomm_family = AF_BLUETOOTH; *nam = sodupsockaddr((struct sockaddr *) &sa, M_NOWAIT); return ((*nam == NULL)? ENOMEM : 0); } /* ng_btsocket_rfcomm_sockaddr */ /* * Upcall function for L2CAP sockets. Enqueue RFCOMM task. */ static void ng_btsocket_rfcomm_upcall(struct socket *so, void *arg, int waitflag) { int error; if (so == NULL) panic("%s: so == NULL\n", __func__); if ((error = ng_btsocket_rfcomm_task_wakeup()) != 0) NG_BTSOCKET_RFCOMM_ALERT( "%s: Could not enqueue RFCOMM task, error=%d\n", __func__, error); } /* ng_btsocket_rfcomm_upcall */ /* * RFCOMM task. Will handle all RFCOMM sessions in one pass. * XXX FIXME does not scale very well */ static void ng_btsocket_rfcomm_sessions_task(void *ctx, int pending) { ng_btsocket_rfcomm_session_p s = NULL, s_next = NULL; mtx_lock(&ng_btsocket_rfcomm_sessions_mtx); for (s = LIST_FIRST(&ng_btsocket_rfcomm_sessions); s != NULL; ) { mtx_lock(&s->session_mtx); s_next = LIST_NEXT(s, next); ng_btsocket_rfcomm_session_task(s); if (s->state == NG_BTSOCKET_RFCOMM_SESSION_CLOSED) { /* Unlink and clean the session */ LIST_REMOVE(s, next); NG_BT_MBUFQ_DRAIN(&s->outq); if (!LIST_EMPTY(&s->dlcs)) panic("%s: DLC list is not empty\n", __func__); /* Close L2CAP socket */ s->l2so->so_upcallarg = NULL; s->l2so->so_upcall = NULL; SOCKBUF_LOCK(&s->l2so->so_rcv); s->l2so->so_rcv.sb_flags &= ~SB_UPCALL; SOCKBUF_UNLOCK(&s->l2so->so_rcv); SOCKBUF_LOCK(&s->l2so->so_snd); s->l2so->so_snd.sb_flags &= ~SB_UPCALL; SOCKBUF_UNLOCK(&s->l2so->so_snd); soclose(s->l2so); mtx_unlock(&s->session_mtx); mtx_destroy(&s->session_mtx); bzero(s, sizeof(*s)); FREE(s, M_NETGRAPH_BTSOCKET_RFCOMM); } else mtx_unlock(&s->session_mtx); s = s_next; } mtx_unlock(&ng_btsocket_rfcomm_sessions_mtx); } /* ng_btsocket_rfcomm_sessions_task */ /* * Process RFCOMM session. Will handle all RFCOMM sockets in one pass. */ static void ng_btsocket_rfcomm_session_task(ng_btsocket_rfcomm_session_p s) { mtx_assert(&s->session_mtx, MA_OWNED); if (s->l2so->so_rcv.sb_state & SBS_CANTRCVMORE) { NG_BTSOCKET_RFCOMM_INFO( "%s: L2CAP connection has been terminated, so=%p, so_state=%#x, so_count=%d, " \ "state=%d, flags=%#x\n", __func__, s->l2so, s->l2so->so_state, s->l2so->so_count, s->state, s->flags); s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; ng_btsocket_rfcomm_session_clean(s); } /* Now process upcall */ switch (s->state) { /* Try to accept new L2CAP connection(s) */ case NG_BTSOCKET_RFCOMM_SESSION_LISTENING: while (ng_btsocket_rfcomm_session_accept(s) == 0) ; break; /* Process the results of the L2CAP connect */ case NG_BTSOCKET_RFCOMM_SESSION_CONNECTING: ng_btsocket_rfcomm_session_process_pcb(s); if (ng_btsocket_rfcomm_session_connect(s) != 0) { s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; ng_btsocket_rfcomm_session_clean(s); } break; /* Try to receive/send more data */ case NG_BTSOCKET_RFCOMM_SESSION_CONNECTED: case NG_BTSOCKET_RFCOMM_SESSION_OPEN: case NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING: ng_btsocket_rfcomm_session_process_pcb(s); if (ng_btsocket_rfcomm_session_receive(s) != 0) { s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; ng_btsocket_rfcomm_session_clean(s); } else if (ng_btsocket_rfcomm_session_send(s) != 0) { s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; ng_btsocket_rfcomm_session_clean(s); } break; case NG_BTSOCKET_RFCOMM_SESSION_CLOSED: break; default: panic("%s: Invalid session state=%d, flags=%#x\n", __func__, s->state, s->flags); break; } } /* ng_btsocket_rfcomm_session_task */ /* * Process RFCOMM connection indicator. Caller must hold s->session_mtx */ static ng_btsocket_rfcomm_pcb_p ng_btsocket_rfcomm_connect_ind(ng_btsocket_rfcomm_session_p s, int channel) { ng_btsocket_rfcomm_pcb_p pcb = NULL, pcb1 = NULL; ng_btsocket_l2cap_pcb_p l2pcb = NULL; struct socket *so1 = NULL; mtx_assert(&s->session_mtx, MA_OWNED); /* * Try to find RFCOMM socket that listens on given source address * and channel. This will return the best possible match. */ l2pcb = so2l2cap_pcb(s->l2so); pcb = ng_btsocket_rfcomm_pcb_listener(&l2pcb->src, channel); if (pcb == NULL) return (NULL); /* * Check the pending connections queue and if we have space then * create new socket and set proper source and destination address, * and channel. */ mtx_lock(&pcb->pcb_mtx); if (pcb->so->so_qlen <= pcb->so->so_qlimit) so1 = sonewconn(pcb->so, 0); mtx_unlock(&pcb->pcb_mtx); if (so1 == NULL) return (NULL); /* * If we got here than we have created new socket. So complete the * connection. Set source and destination address from the session. */ pcb1 = so2rfcomm_pcb(so1); if (pcb1 == NULL) panic("%s: pcb1 == NULL\n", __func__); mtx_lock(&pcb1->pcb_mtx); bcopy(&l2pcb->src, &pcb1->src, sizeof(pcb1->src)); bcopy(&l2pcb->dst, &pcb1->dst, sizeof(pcb1->dst)); pcb1->channel = channel; /* Link new DLC to the session. We already hold s->session_mtx */ LIST_INSERT_HEAD(&s->dlcs, pcb1, session_next); pcb1->session = s; mtx_unlock(&pcb1->pcb_mtx); return (pcb1); } /* ng_btsocket_rfcomm_connect_ind */ /* * Process RFCOMM connect confirmation. Caller must hold s->session_mtx. */ static void ng_btsocket_rfcomm_connect_cfm(ng_btsocket_rfcomm_session_p s) { ng_btsocket_rfcomm_pcb_p pcb = NULL, pcb_next = NULL; struct socket *so = NULL; int error; mtx_assert(&s->session_mtx, MA_OWNED); /* * Wake up all waiting sockets and send PN request for each of them. * Note that timeout already been set in ng_btsocket_rfcomm_connect() * * Note: cannot use LIST_FOREACH because ng_btsocket_rfcomm_pcb_kill * will unlink DLC from the session */ for (pcb = LIST_FIRST(&s->dlcs); pcb != NULL; ) { mtx_lock(&pcb->pcb_mtx); pcb_next = LIST_NEXT(pcb, session_next); if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT) { pcb->mtu = s->mtu; bcopy(&so2l2cap_pcb(s->l2so)->src, &pcb->src, sizeof(pcb->src)); error = ng_btsocket_rfcomm_send_pn(pcb); if (error == 0) pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONFIGURING; else if (ng_btsocket_rfcomm_pcb_kill(pcb, error)) so = pcb->so; else so = NULL; } mtx_unlock(&pcb->pcb_mtx); pcb = pcb_next; if (so != NULL) ng_btsocket_rfcomm_detach(so); } } /* ng_btsocket_rfcomm_connect_cfm */ /***************************************************************************** ***************************************************************************** ** RFCOMM sessions ***************************************************************************** *****************************************************************************/ /* * Create new RFCOMM session. That function WILL NOT take ownership over l2so. * Caller MUST free l2so if function failed. */ static int ng_btsocket_rfcomm_session_create(ng_btsocket_rfcomm_session_p *sp, struct socket *l2so, bdaddr_p src, bdaddr_p dst, struct thread *td) { ng_btsocket_rfcomm_session_p s = NULL; struct sockaddr_l2cap l2sa; struct sockopt l2sopt; int mtu, error; mtx_assert(&ng_btsocket_rfcomm_sessions_mtx, MA_OWNED); /* Allocate the RFCOMM session */ MALLOC(s, ng_btsocket_rfcomm_session_p, sizeof(*s), M_NETGRAPH_BTSOCKET_RFCOMM, M_NOWAIT | M_ZERO); if (s == NULL) return (ENOMEM); /* Set defaults */ s->mtu = RFCOMM_DEFAULT_MTU; s->flags = 0; s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; NG_BT_MBUFQ_INIT(&s->outq, ifqmaxlen); /* * XXX Mark session mutex as DUPOK to prevent "duplicated lock of * the same type" message. When accepting new L2CAP connection * ng_btsocket_rfcomm_session_accept() holds both session mutexes * for "old" (accepting) session and "new" (created) session. */ mtx_init(&s->session_mtx, "btsocks_rfcomm_session_mtx", NULL, MTX_DEF|MTX_DUPOK); LIST_INIT(&s->dlcs); /* Prepare L2CAP socket */ l2so->so_upcallarg = NULL; l2so->so_upcall = ng_btsocket_rfcomm_upcall; SOCKBUF_LOCK(&l2so->so_rcv); l2so->so_rcv.sb_flags |= SB_UPCALL; SOCKBUF_UNLOCK(&l2so->so_rcv); SOCKBUF_LOCK(&l2so->so_snd); l2so->so_snd.sb_flags |= SB_UPCALL; SOCKBUF_UNLOCK(&l2so->so_snd); l2so->so_state |= SS_NBIO; s->l2so = l2so; mtx_lock(&s->session_mtx); /* * "src" == NULL and "dst" == NULL means just create session. * caller must do the rest */ if (src == NULL && dst == NULL) goto done; /* * Set incoming MTU on L2CAP socket. It is RFCOMM session default MTU * plus 5 bytes: RFCOMM frame header, one extra byte for length and one * extra byte for credits. */ mtu = s->mtu + sizeof(struct rfcomm_frame_hdr) + 1 + 1; l2sopt.sopt_dir = SOPT_SET; l2sopt.sopt_level = SOL_L2CAP; l2sopt.sopt_name = SO_L2CAP_IMTU; l2sopt.sopt_val = (void *) &mtu; l2sopt.sopt_valsize = sizeof(mtu); l2sopt.sopt_td = NULL; error = sosetopt(s->l2so, &l2sopt); if (error != 0) goto bad; /* Bind socket to "src" address */ l2sa.l2cap_len = sizeof(l2sa); l2sa.l2cap_family = AF_BLUETOOTH; l2sa.l2cap_psm = (dst == NULL)? htole16(NG_L2CAP_PSM_RFCOMM) : 0; bcopy(src, &l2sa.l2cap_bdaddr, sizeof(l2sa.l2cap_bdaddr)); error = sobind(s->l2so, (struct sockaddr *) &l2sa, td); if (error != 0) goto bad; /* If "dst" is not NULL then initiate connect(), otherwise listen() */ if (dst == NULL) { s->flags = 0; s->state = NG_BTSOCKET_RFCOMM_SESSION_LISTENING; error = solisten(s->l2so, 10, td); if (error != 0) goto bad; } else { s->flags = NG_BTSOCKET_RFCOMM_SESSION_INITIATOR; s->state = NG_BTSOCKET_RFCOMM_SESSION_CONNECTING; l2sa.l2cap_len = sizeof(l2sa); l2sa.l2cap_family = AF_BLUETOOTH; l2sa.l2cap_psm = htole16(NG_L2CAP_PSM_RFCOMM); bcopy(dst, &l2sa.l2cap_bdaddr, sizeof(l2sa.l2cap_bdaddr)); error = soconnect(s->l2so, (struct sockaddr *) &l2sa, td); if (error != 0) goto bad; } done: LIST_INSERT_HEAD(&ng_btsocket_rfcomm_sessions, s, next); *sp = s; mtx_unlock(&s->session_mtx); return (0); bad: mtx_unlock(&s->session_mtx); /* Return L2CAP socket back to its original state */ l2so->so_upcallarg = NULL; l2so->so_upcall = NULL; SOCKBUF_LOCK(&l2so->so_rcv); l2so->so_rcv.sb_flags &= ~SB_UPCALL; SOCKBUF_UNLOCK(&l2so->so_rcv); SOCKBUF_LOCK(&l2so->so_snd); l2so->so_snd.sb_flags &= ~SB_UPCALL; SOCKBUF_UNLOCK(&l2so->so_snd); l2so->so_state &= ~SS_NBIO; mtx_destroy(&s->session_mtx); bzero(s, sizeof(*s)); FREE(s, M_NETGRAPH_BTSOCKET_RFCOMM); return (error); } /* ng_btsocket_rfcomm_session_create */ /* * Process accept() on RFCOMM session * XXX FIXME locking for "l2so"? */ static int ng_btsocket_rfcomm_session_accept(ng_btsocket_rfcomm_session_p s0) { struct socket *l2so = NULL; struct sockaddr_l2cap *l2sa = NULL; ng_btsocket_l2cap_pcb_t *l2pcb = NULL; ng_btsocket_rfcomm_session_p s = NULL; int error = 0; mtx_assert(&ng_btsocket_rfcomm_sessions_mtx, MA_OWNED); mtx_assert(&s0->session_mtx, MA_OWNED); /* Check if there is a complete L2CAP connection in the queue */ if ((error = s0->l2so->so_error) != 0) { NG_BTSOCKET_RFCOMM_ERR( "%s: Could not accept connection on L2CAP socket, error=%d\n", __func__, error); s0->l2so->so_error = 0; return (error); } ACCEPT_LOCK(); if (TAILQ_EMPTY(&s0->l2so->so_comp)) { ACCEPT_UNLOCK(); if (s0->l2so->so_rcv.sb_state & SBS_CANTRCVMORE) return (ECONNABORTED); return (EWOULDBLOCK); } /* Accept incoming L2CAP connection */ l2so = TAILQ_FIRST(&s0->l2so->so_comp); if (l2so == NULL) panic("%s: l2so == NULL\n", __func__); TAILQ_REMOVE(&s0->l2so->so_comp, l2so, so_list); s0->l2so->so_qlen --; l2so->so_qstate &= ~SQ_COMP; l2so->so_head = NULL; SOCK_LOCK(l2so); soref(l2so); l2so->so_state |= SS_NBIO; SOCK_UNLOCK(l2so); ACCEPT_UNLOCK(); error = soaccept(l2so, (struct sockaddr **) &l2sa); if (error != 0) { NG_BTSOCKET_RFCOMM_ERR( "%s: soaccept() on L2CAP socket failed, error=%d\n", __func__, error); soclose(l2so); return (error); } /* * Check if there is already active RFCOMM session between two devices. * If so then close L2CAP connection. We only support one RFCOMM session * between each pair of devices. Note that here we assume session in any * state. The session even could be in the middle of disconnecting. */ l2pcb = so2l2cap_pcb(l2so); s = ng_btsocket_rfcomm_session_by_addr(&l2pcb->src, &l2pcb->dst); if (s == NULL) { /* Create a new RFCOMM session */ error = ng_btsocket_rfcomm_session_create(&s, l2so, NULL, NULL, curthread /* XXX */); if (error == 0) { mtx_lock(&s->session_mtx); s->flags = 0; s->state = NG_BTSOCKET_RFCOMM_SESSION_CONNECTED; /* * Adjust MTU on incomming connection. Reserve 5 bytes: * RFCOMM frame header, one extra byte for length and * one extra byte for credits. */ s->mtu = min(l2pcb->imtu, l2pcb->omtu) - sizeof(struct rfcomm_frame_hdr) - 1 - 1; mtx_unlock(&s->session_mtx); } else { NG_BTSOCKET_RFCOMM_ALERT( "%s: Failed to create new RFCOMM session, error=%d\n", __func__, error); soclose(l2so); } } else { NG_BTSOCKET_RFCOMM_WARN( "%s: Rejecting duplicating RFCOMM session between src=%x:%x:%x:%x:%x:%x and " \ "dst=%x:%x:%x:%x:%x:%x, state=%d, flags=%#x\n", __func__, l2pcb->src.b[5], l2pcb->src.b[4], l2pcb->src.b[3], l2pcb->src.b[2], l2pcb->src.b[1], l2pcb->src.b[0], l2pcb->dst.b[5], l2pcb->dst.b[4], l2pcb->dst.b[3], l2pcb->dst.b[2], l2pcb->dst.b[1], l2pcb->dst.b[0], s->state, s->flags); error = EBUSY; soclose(l2so); } return (error); } /* ng_btsocket_rfcomm_session_accept */ /* * Process connect() on RFCOMM session * XXX FIXME locking for "l2so"? */ static int ng_btsocket_rfcomm_session_connect(ng_btsocket_rfcomm_session_p s) { ng_btsocket_l2cap_pcb_p l2pcb = so2l2cap_pcb(s->l2so); int error; mtx_assert(&s->session_mtx, MA_OWNED); /* First check if connection has failed */ if ((error = s->l2so->so_error) != 0) { s->l2so->so_error = 0; NG_BTSOCKET_RFCOMM_ERR( "%s: Could not connect RFCOMM session, error=%d, state=%d, flags=%#x\n", __func__, error, s->state, s->flags); return (error); } /* Is connection still in progress? */ if (s->l2so->so_state & SS_ISCONNECTING) return (0); /* * If we got here then we are connected. Send SABM on DLCI 0 to * open multiplexor channel. */ if (error == 0) { s->state = NG_BTSOCKET_RFCOMM_SESSION_CONNECTED; /* * Adjust MTU on outgoing connection. Reserve 5 bytes: RFCOMM * frame header, one extra byte for length and one extra byte * for credits. */ s->mtu = min(l2pcb->imtu, l2pcb->omtu) - sizeof(struct rfcomm_frame_hdr) - 1 - 1; error = ng_btsocket_rfcomm_send_command(s,RFCOMM_FRAME_SABM,0); if (error == 0) error = ng_btsocket_rfcomm_task_wakeup(); } return (error); }/* ng_btsocket_rfcomm_session_connect */ /* * Receive data on RFCOMM session * XXX FIXME locking for "l2so"? */ static int ng_btsocket_rfcomm_session_receive(ng_btsocket_rfcomm_session_p s) { struct mbuf *m = NULL; struct uio uio; int more, flags, error; mtx_assert(&s->session_mtx, MA_OWNED); /* Can we read from the L2CAP socket? */ if (!soreadable(s->l2so)) return (0); /* First check for error on L2CAP socket */ if ((error = s->l2so->so_error) != 0) { s->l2so->so_error = 0; NG_BTSOCKET_RFCOMM_ERR( "%s: Could not receive data from L2CAP socket, error=%d, state=%d, flags=%#x\n", __func__, error, s->state, s->flags); return (error); } /* * Read all packets from the L2CAP socket. * XXX FIXME/VERIFY is that correct? For now use m->m_nextpkt as * indication that there is more packets on the socket's buffer. * Also what should we use in uio.uio_resid? * May be s->mtu + sizeof(struct rfcomm_frame_hdr) + 1 + 1? */ for (more = 1; more; ) { /* Try to get next packet from socket */ bzero(&uio, sizeof(uio)); /* uio.uio_td = NULL; */ uio.uio_resid = 1000000000; flags = MSG_DONTWAIT; m = NULL; error = (*s->l2so->so_proto->pr_usrreqs->pru_soreceive)(s->l2so, NULL, &uio, &m, (struct mbuf **) NULL, &flags); if (error != 0) { if (error == EWOULDBLOCK) return (0); /* XXX can happen? */ NG_BTSOCKET_RFCOMM_ERR( "%s: Could not receive data from L2CAP socket, error=%d\n", __func__, error); return (error); } more = (m->m_nextpkt != NULL); m->m_nextpkt = NULL; ng_btsocket_rfcomm_receive_frame(s, m); } return (0); } /* ng_btsocket_rfcomm_session_receive */ /* * Send data on RFCOMM session * XXX FIXME locking for "l2so"? */ static int ng_btsocket_rfcomm_session_send(ng_btsocket_rfcomm_session_p s) { struct mbuf *m = NULL; int error; mtx_assert(&s->session_mtx, MA_OWNED); /* Send as much as we can from the session queue */ while (sowriteable(s->l2so)) { /* Check if socket still OK */ if ((error = s->l2so->so_error) != 0) { s->l2so->so_error = 0; NG_BTSOCKET_RFCOMM_ERR( "%s: Detected error=%d on L2CAP socket, state=%d, flags=%#x\n", __func__, error, s->state, s->flags); return (error); } NG_BT_MBUFQ_DEQUEUE(&s->outq, m); if (m == NULL) return (0); /* we are done */ /* Call send function on the L2CAP socket */ error = (*s->l2so->so_proto->pr_usrreqs->pru_sosend) (s->l2so, NULL, NULL, m, NULL, 0, curthread /* XXX */); if (error != 0) { NG_BTSOCKET_RFCOMM_ERR( "%s: Could not send data to L2CAP socket, error=%d\n", __func__, error); return (error); } } return (0); } /* ng_btsocket_rfcomm_session_send */ /* * Close and disconnect all DLCs for the given session. Caller must hold * s->sesson_mtx. Will wakeup session. */ static void ng_btsocket_rfcomm_session_clean(ng_btsocket_rfcomm_session_p s) { ng_btsocket_rfcomm_pcb_p pcb = NULL, pcb_next = NULL; struct socket *so = NULL; int error; mtx_assert(&s->session_mtx, MA_OWNED); /* * Note: cannot use LIST_FOREACH because ng_btsocket_rfcomm_pcb_kill * will unlink DLC from the session */ for (pcb = LIST_FIRST(&s->dlcs); pcb != NULL; ) { mtx_lock(&pcb->pcb_mtx); pcb_next = LIST_NEXT(pcb, session_next); NG_BTSOCKET_RFCOMM_INFO( "%s: Disconnecting dlci=%d, state=%d, flags=%#x\n", __func__, pcb->dlci, pcb->state, pcb->flags); if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_CONNECTED) error = ECONNRESET; else error = ECONNREFUSED; if (ng_btsocket_rfcomm_pcb_kill(pcb, error)) so = pcb->so; else so = NULL; mtx_unlock(&pcb->pcb_mtx); pcb = pcb_next; if (so != NULL) ng_btsocket_rfcomm_detach(so); } } /* ng_btsocket_rfcomm_session_clean */ /* * Process all DLCs on the session. Caller MUST hold s->session_mtx. */ static void ng_btsocket_rfcomm_session_process_pcb(ng_btsocket_rfcomm_session_p s) { ng_btsocket_rfcomm_pcb_p pcb = NULL, pcb_next = NULL; struct socket *so = NULL; int error; mtx_assert(&s->session_mtx, MA_OWNED); /* * Note: cannot use LIST_FOREACH because ng_btsocket_rfcomm_pcb_kill * will unlink DLC from the session */ for (pcb = LIST_FIRST(&s->dlcs); pcb != NULL; ) { so = NULL; mtx_lock(&pcb->pcb_mtx); pcb_next = LIST_NEXT(pcb, session_next); switch (pcb->state) { /* * If DLC in W4_CONNECT state then we should check for both * timeout and detach. */ case NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT: if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_DETACHED) { if (ng_btsocket_rfcomm_pcb_kill(pcb, 0)) so = pcb->so; } else if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT) if (ng_btsocket_rfcomm_pcb_kill(pcb, ETIMEDOUT)) so = pcb->so; break; /* * If DLC in CONFIGURING or CONNECTING state then we only * should check for timeout. If detach() was called then * DLC will be moved into DISCONNECTING state. */ case NG_BTSOCKET_RFCOMM_DLC_CONFIGURING: case NG_BTSOCKET_RFCOMM_DLC_CONNECTING: if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT) if (ng_btsocket_rfcomm_pcb_kill(pcb, ETIMEDOUT)) so = pcb->so; break; /* * If DLC in CONNECTED state then we need to send data (if any) * from the socket's send queue. Note that we will send data * from either all sockets or none. This may overload session's * outgoing queue (but we do not check for that). * * XXX FIXME need scheduler for RFCOMM sockets */ case NG_BTSOCKET_RFCOMM_DLC_CONNECTED: error = ng_btsocket_rfcomm_pcb_send(pcb, ALOT); if (error != 0) if (ng_btsocket_rfcomm_pcb_kill(pcb, error)) so = pcb->so; break; /* * If DLC in DISCONNECTING state then we must send DISC frame. * Note that if DLC has timeout set then we do not need to * resend DISC frame. * * XXX FIXME need to drain all data from the socket's queue * if LINGER option was set */ case NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING: if (!(pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO)) { error = ng_btsocket_rfcomm_send_command( pcb->session, RFCOMM_FRAME_DISC, pcb->dlci); if (error == 0) ng_btsocket_rfcomm_timeout(pcb); else if (ng_btsocket_rfcomm_pcb_kill(pcb,error)) so = pcb->so; } else if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT) if (ng_btsocket_rfcomm_pcb_kill(pcb, ETIMEDOUT)) so = pcb->so; break; /* case NG_BTSOCKET_RFCOMM_DLC_CLOSED: */ default: panic("%s: Invalid DLC state=%d, flags=%#x\n", __func__, pcb->state, pcb->flags); break; } mtx_unlock(&pcb->pcb_mtx); pcb = pcb_next; if (so != NULL) ng_btsocket_rfcomm_detach(so); } } /* ng_btsocket_rfcomm_session_process_pcb */ /* * Find RFCOMM session between "src" and "dst". * Caller MUST hold ng_btsocket_rfcomm_sessions_mtx. */ static ng_btsocket_rfcomm_session_p ng_btsocket_rfcomm_session_by_addr(bdaddr_p src, bdaddr_p dst) { ng_btsocket_rfcomm_session_p s = NULL; ng_btsocket_l2cap_pcb_p l2pcb = NULL; int any_src; mtx_assert(&ng_btsocket_rfcomm_sessions_mtx, MA_OWNED); any_src = (bcmp(src, NG_HCI_BDADDR_ANY, sizeof(*src)) == 0); LIST_FOREACH(s, &ng_btsocket_rfcomm_sessions, next) { l2pcb = so2l2cap_pcb(s->l2so); if ((any_src || bcmp(&l2pcb->src, src, sizeof(*src)) == 0) && bcmp(&l2pcb->dst, dst, sizeof(*dst)) == 0) break; } return (s); } /* ng_btsocket_rfcomm_session_by_addr */ /***************************************************************************** ***************************************************************************** ** RFCOMM ***************************************************************************** *****************************************************************************/ /* * Process incoming RFCOMM frame. Caller must hold s->session_mtx. * XXX FIXME check frame length */ static int ng_btsocket_rfcomm_receive_frame(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) { struct rfcomm_frame_hdr *hdr = NULL; struct mbuf *m = NULL; u_int16_t length; u_int8_t dlci, type; int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); /* Pullup as much as we can into first mbuf (for direct access) */ length = min(m0->m_pkthdr.len, MHLEN); if (m0->m_len < length) { if ((m0 = m_pullup(m0, length)) == NULL) { NG_BTSOCKET_RFCOMM_ALERT( "%s: m_pullup(%d) failed\n", __func__, length); return (ENOBUFS); } } hdr = mtod(m0, struct rfcomm_frame_hdr *); dlci = RFCOMM_DLCI(hdr->address); type = RFCOMM_TYPE(hdr->control); /* Test EA bit in length. If not set then we have 2 bytes of length */ if (!RFCOMM_EA(hdr->length)) { bcopy(&hdr->length, &length, sizeof(length)); length = le16toh(length) >> 1; m_adj(m0, sizeof(*hdr) + 1); } else { length = hdr->length >> 1; m_adj(m0, sizeof(*hdr)); } NG_BTSOCKET_RFCOMM_INFO( "%s: Got frame type=%#x, dlci=%d, length=%d, cr=%d, pf=%d, len=%d\n", __func__, type, dlci, length, RFCOMM_CR(hdr->address), RFCOMM_PF(hdr->control), m0->m_pkthdr.len); /* * Get FCS (the last byte in the frame) * XXX this will not work if mbuf chain ends with empty mbuf. * XXX let's hope it never happens :) */ for (m = m0; m->m_next != NULL; m = m->m_next) ; if (m->m_len <= 0) panic("%s: Empty mbuf at the end of the chain, len=%d\n", __func__, m->m_len); /* * Check FCS. We only need to calculate FCS on first 2 or 3 bytes * and already m_pullup'ed mbuf chain, so it should be safe. */ if (ng_btsocket_rfcomm_check_fcs((u_int8_t *) hdr, type, m->m_data[m->m_len - 1])) { NG_BTSOCKET_RFCOMM_ERR( "%s: Invalid RFCOMM packet. Bad checksum\n", __func__); NG_FREE_M(m0); return (EINVAL); } m_adj(m0, -1); /* Trim FCS byte */ /* * Process RFCOMM frame. * * From TS 07.10 spec * * "... In the case where a SABM or DISC command with the P bit set * to 0 is received then the received frame shall be discarded..." * * "... If a unsolicited DM response is received then the frame shall * be processed irrespective of the P/F setting... " * * "... The station may transmit response frames with the F bit set * to 0 at any opportunity on an asynchronous basis. However, in the * case where a UA response is received with the F bit set to 0 then * the received frame shall be discarded..." * * From Bluetooth spec * * "... When credit based flow control is being used, the meaning of * the P/F bit in the control field of the RFCOMM header is redefined * for UIH frames..." */ switch (type) { case RFCOMM_FRAME_SABM: if (RFCOMM_PF(hdr->control)) error = ng_btsocket_rfcomm_receive_sabm(s, dlci); break; case RFCOMM_FRAME_DISC: if (RFCOMM_PF(hdr->control)) error = ng_btsocket_rfcomm_receive_disc(s, dlci); break; case RFCOMM_FRAME_UA: if (RFCOMM_PF(hdr->control)) error = ng_btsocket_rfcomm_receive_ua(s, dlci); break; case RFCOMM_FRAME_DM: error = ng_btsocket_rfcomm_receive_dm(s, dlci); break; case RFCOMM_FRAME_UIH: if (dlci == 0) error = ng_btsocket_rfcomm_receive_mcc(s, m0); else error = ng_btsocket_rfcomm_receive_uih(s, dlci, RFCOMM_PF(hdr->control), m0); return (error); /* NOT REACHED */ default: NG_BTSOCKET_RFCOMM_ERR( "%s: Invalid RFCOMM packet. Unknown type=%#x\n", __func__, type); error = EINVAL; break; } NG_FREE_M(m0); return (error); } /* ng_btsocket_rfcomm_receive_frame */ /* * Process RFCOMM SABM frame */ static int ng_btsocket_rfcomm_receive_sabm(ng_btsocket_rfcomm_session_p s, int dlci) { ng_btsocket_rfcomm_pcb_p pcb = NULL; struct socket *so = NULL; int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Got SABM, session state=%d, flags=%#x, mtu=%d, dlci=%d\n", __func__, s->state, s->flags, s->mtu, dlci); /* DLCI == 0 means open multiplexor channel */ if (dlci == 0) { switch (s->state) { case NG_BTSOCKET_RFCOMM_SESSION_CONNECTED: case NG_BTSOCKET_RFCOMM_SESSION_OPEN: error = ng_btsocket_rfcomm_send_command(s, RFCOMM_FRAME_UA, dlci); if (error == 0) { s->state = NG_BTSOCKET_RFCOMM_SESSION_OPEN; ng_btsocket_rfcomm_connect_cfm(s); } else { s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; ng_btsocket_rfcomm_session_clean(s); } break; default: NG_BTSOCKET_RFCOMM_WARN( "%s: Got SABM for session in invalid state state=%d, flags=%#x\n", __func__, s->state, s->flags); error = EINVAL; break; } return (error); } /* Make sure multiplexor channel is open */ if (s->state != NG_BTSOCKET_RFCOMM_SESSION_OPEN) { NG_BTSOCKET_RFCOMM_ERR( "%s: Got SABM for dlci=%d with mulitplexor channel closed, state=%d, " \ "flags=%#x\n", __func__, dlci, s->state, s->flags); return (EINVAL); } /* * Check if we have this DLCI. This might happen when remote * peer uses PN command before actual open (SABM) happens. */ pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, dlci); if (pcb != NULL) { mtx_lock(&pcb->pcb_mtx); if (pcb->state != NG_BTSOCKET_RFCOMM_DLC_CONNECTING) { NG_BTSOCKET_RFCOMM_ERR( "%s: Got SABM for dlci=%d in invalid state=%d, flags=%#x\n", __func__, dlci, pcb->state, pcb->flags); mtx_unlock(&pcb->pcb_mtx); return (ENOENT); } ng_btsocket_rfcomm_untimeout(pcb); error = ng_btsocket_rfcomm_send_command(s,RFCOMM_FRAME_UA,dlci); if (error == 0) error = ng_btsocket_rfcomm_send_msc(pcb); if (error == 0) { pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONNECTED; soisconnected(pcb->so); } else if (ng_btsocket_rfcomm_pcb_kill(pcb, error)) so = pcb->so; mtx_unlock(&pcb->pcb_mtx); if (so != NULL) ng_btsocket_rfcomm_detach(so); return (error); } /* * We do not have requested DLCI, so it must be an incoming connection * with default parameters. Try to accept it. */ pcb = ng_btsocket_rfcomm_connect_ind(s, RFCOMM_SRVCHANNEL(dlci)); if (pcb != NULL) { mtx_lock(&pcb->pcb_mtx); pcb->dlci = dlci; error = ng_btsocket_rfcomm_send_command(s,RFCOMM_FRAME_UA,dlci); if (error == 0) error = ng_btsocket_rfcomm_send_msc(pcb); if (error == 0) { pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONNECTED; soisconnected(pcb->so); } else if (ng_btsocket_rfcomm_pcb_kill(pcb, error)) so = pcb->so; mtx_unlock(&pcb->pcb_mtx); if (so != NULL) ng_btsocket_rfcomm_detach(so); } else /* Nobody is listen()ing on the requested DLCI */ error = ng_btsocket_rfcomm_send_command(s,RFCOMM_FRAME_DM,dlci); return (error); } /* ng_btsocket_rfcomm_receive_sabm */ /* * Process RFCOMM DISC frame */ static int ng_btsocket_rfcomm_receive_disc(ng_btsocket_rfcomm_session_p s, int dlci) { ng_btsocket_rfcomm_pcb_p pcb = NULL; int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Got DISC, session state=%d, flags=%#x, mtu=%d, dlci=%d\n", __func__, s->state, s->flags, s->mtu, dlci); /* DLCI == 0 means close multiplexor channel */ if (dlci == 0) { /* XXX FIXME assume that remote side will close the socket */ error = ng_btsocket_rfcomm_send_command(s, RFCOMM_FRAME_UA, 0); if (error == 0) { if (s->state == NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING) s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; /* XXX */ else s->state = NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING; } else s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; /* XXX */ ng_btsocket_rfcomm_session_clean(s); } else { pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, dlci); if (pcb != NULL) { struct socket *so = NULL; int err; mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_RFCOMM_INFO( "%s: Got DISC for dlci=%d, state=%d, flags=%#x\n", __func__, dlci, pcb->state, pcb->flags); error = ng_btsocket_rfcomm_send_command(s, RFCOMM_FRAME_UA, dlci); if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_CONNECTED) err = 0; else err = ECONNREFUSED; if (ng_btsocket_rfcomm_pcb_kill(pcb, err)) so = pcb->so; mtx_unlock(&pcb->pcb_mtx); if (so != NULL) ng_btsocket_rfcomm_detach(so); } else { NG_BTSOCKET_RFCOMM_WARN( "%s: Got DISC for non-existing dlci=%d\n", __func__, dlci); error = ng_btsocket_rfcomm_send_command(s, RFCOMM_FRAME_DM, dlci); } } return (error); } /* ng_btsocket_rfcomm_receive_disc */ /* * Process RFCOMM UA frame */ static int ng_btsocket_rfcomm_receive_ua(ng_btsocket_rfcomm_session_p s, int dlci) { ng_btsocket_rfcomm_pcb_p pcb = NULL; int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Got UA, session state=%d, flags=%#x, mtu=%d, dlci=%d\n", __func__, s->state, s->flags, s->mtu, dlci); /* dlci == 0 means multiplexor channel */ if (dlci == 0) { switch (s->state) { case NG_BTSOCKET_RFCOMM_SESSION_CONNECTED: s->state = NG_BTSOCKET_RFCOMM_SESSION_OPEN; ng_btsocket_rfcomm_connect_cfm(s); break; case NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING: s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; ng_btsocket_rfcomm_session_clean(s); break; default: NG_BTSOCKET_RFCOMM_WARN( "%s: Got UA for session in invalid state=%d(%d), flags=%#x, mtu=%d\n", __func__, s->state, INITIATOR(s), s->flags, s->mtu); error = ENOENT; break; } return (error); } /* Check if we have this DLCI */ pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, dlci); if (pcb != NULL) { struct socket *so = NULL; mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_RFCOMM_INFO( "%s: Got UA for dlci=%d, state=%d, flags=%#x\n", __func__, dlci, pcb->state, pcb->flags); switch (pcb->state) { case NG_BTSOCKET_RFCOMM_DLC_CONNECTING: ng_btsocket_rfcomm_untimeout(pcb); error = ng_btsocket_rfcomm_send_msc(pcb); if (error == 0) { pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONNECTED; soisconnected(pcb->so); } break; case NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING: if (ng_btsocket_rfcomm_pcb_kill(pcb, 0)) so = pcb->so; break; default: NG_BTSOCKET_RFCOMM_WARN( "%s: Got UA for dlci=%d in invalid state=%d, flags=%#x\n", __func__, dlci, pcb->state, pcb->flags); error = ENOENT; break; } mtx_unlock(&pcb->pcb_mtx); if (so != NULL) ng_btsocket_rfcomm_detach(so); } else { NG_BTSOCKET_RFCOMM_WARN( "%s: Got UA for non-existing dlci=%d\n", __func__, dlci); error = ng_btsocket_rfcomm_send_command(s,RFCOMM_FRAME_DM,dlci); } return (error); } /* ng_btsocket_rfcomm_receive_ua */ /* * Process RFCOMM DM frame */ static int ng_btsocket_rfcomm_receive_dm(ng_btsocket_rfcomm_session_p s, int dlci) { ng_btsocket_rfcomm_pcb_p pcb = NULL; int error; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Got DM, session state=%d, flags=%#x, mtu=%d, dlci=%d\n", __func__, s->state, s->flags, s->mtu, dlci); /* DLCI == 0 means multiplexor channel */ if (dlci == 0) { /* Disconnect all dlc's on the session */ s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; ng_btsocket_rfcomm_session_clean(s); } else { pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, dlci); if (pcb != NULL) { struct socket *so = NULL; mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_RFCOMM_INFO( "%s: Got DM for dlci=%d, state=%d, flags=%#x\n", __func__, dlci, pcb->state, pcb->flags); if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_CONNECTED) error = ECONNRESET; else error = ECONNREFUSED; if (ng_btsocket_rfcomm_pcb_kill(pcb, error)) so = pcb->so; mtx_unlock(&pcb->pcb_mtx); if (so != NULL) ng_btsocket_rfcomm_detach(so); } else NG_BTSOCKET_RFCOMM_WARN( "%s: Got DM for non-existing dlci=%d\n", __func__, dlci); } return (0); } /* ng_btsocket_rfcomm_receive_dm */ /* * Process RFCOMM UIH frame (data) */ static int ng_btsocket_rfcomm_receive_uih(ng_btsocket_rfcomm_session_p s, int dlci, int pf, struct mbuf *m0) { ng_btsocket_rfcomm_pcb_p pcb = NULL; int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Got UIH, session state=%d, flags=%#x, mtu=%d, dlci=%d, pf=%d, len=%d\n", __func__, s->state, s->flags, s->mtu, dlci, pf, m0->m_pkthdr.len); /* XXX should we do it here? Check for session flow control */ if (s->flags & NG_BTSOCKET_RFCOMM_SESSION_LFC) { NG_BTSOCKET_RFCOMM_WARN( "%s: Got UIH with session flow control asserted, state=%d, flags=%#x\n", __func__, s->state, s->flags); goto drop; } /* Check if we have this dlci */ pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, dlci); if (pcb == NULL) { NG_BTSOCKET_RFCOMM_WARN( "%s: Got UIH for non-existing dlci=%d\n", __func__, dlci); error = ng_btsocket_rfcomm_send_command(s,RFCOMM_FRAME_DM,dlci); goto drop; } mtx_lock(&pcb->pcb_mtx); /* Check dlci state */ if (pcb->state != NG_BTSOCKET_RFCOMM_DLC_CONNECTED) { NG_BTSOCKET_RFCOMM_WARN( "%s: Got UIH for dlci=%d in invalid state=%d, flags=%#x\n", __func__, dlci, pcb->state, pcb->flags); error = EINVAL; goto drop1; } /* Check dlci flow control */ if (((pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) && pcb->rx_cred <= 0) || (pcb->lmodem & RFCOMM_MODEM_FC)) { NG_BTSOCKET_RFCOMM_ERR( "%s: Got UIH for dlci=%d with asserted flow control, state=%d, " \ "flags=%#x, rx_cred=%d, lmodem=%#x\n", __func__, dlci, pcb->state, pcb->flags, pcb->rx_cred, pcb->lmodem); goto drop1; } /* Did we get any credits? */ if ((pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) && pf) { NG_BTSOCKET_RFCOMM_INFO( "%s: Got %d more credits for dlci=%d, state=%d, flags=%#x, " \ "rx_cred=%d, tx_cred=%d\n", __func__, *mtod(m0, u_int8_t *), dlci, pcb->state, pcb->flags, pcb->rx_cred, pcb->tx_cred); pcb->tx_cred += *mtod(m0, u_int8_t *); m_adj(m0, 1); /* Send more from the DLC. XXX check for errors? */ ng_btsocket_rfcomm_pcb_send(pcb, ALOT); } /* OK the of the rest of the mbuf is the data */ if (m0->m_pkthdr.len > 0) { /* If we are using credit flow control decrease rx_cred here */ if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) { /* Give remote peer more credits (if needed) */ if (-- pcb->rx_cred <= RFCOMM_MAX_CREDITS / 2) ng_btsocket_rfcomm_send_credits(pcb); else NG_BTSOCKET_RFCOMM_INFO( "%s: Remote side still has credits, dlci=%d, state=%d, flags=%#x, " \ "rx_cred=%d, tx_cred=%d\n", __func__, dlci, pcb->state, pcb->flags, pcb->rx_cred, pcb->tx_cred); } /* Check packet against mtu on dlci */ if (m0->m_pkthdr.len > pcb->mtu) { NG_BTSOCKET_RFCOMM_ERR( "%s: Got oversized UIH for dlci=%d, state=%d, flags=%#x, mtu=%d, len=%d\n", __func__, dlci, pcb->state, pcb->flags, pcb->mtu, m0->m_pkthdr.len); error = EMSGSIZE; } else if (m0->m_pkthdr.len > sbspace(&pcb->so->so_rcv)) { /* * This is really bad. Receive queue on socket does * not have enough space for the packet. We do not * have any other choice but drop the packet. */ NG_BTSOCKET_RFCOMM_ERR( "%s: Not enough space in socket receive queue. Dropping UIH for dlci=%d, " \ "state=%d, flags=%#x, len=%d, space=%ld\n", __func__, dlci, pcb->state, pcb->flags, m0->m_pkthdr.len, sbspace(&pcb->so->so_rcv)); error = ENOBUFS; } else { /* Append packet to the socket receive queue */ sbappend(&pcb->so->so_rcv, m0); m0 = NULL; sorwakeup(pcb->so); } } drop1: mtx_unlock(&pcb->pcb_mtx); drop: NG_FREE_M(m0); /* checks for != NULL */ return (error); } /* ng_btsocket_rfcomm_receive_uih */ /* * Process RFCOMM MCC command (Multiplexor) * * From TS 07.10 spec * * "5.4.3.1 Information Data * * ...The frames (UIH) sent by the initiating station have the C/R bit set * to 1 and those sent by the responding station have the C/R bit set to 0..." * * "5.4.6.2 Operating procedures * * Messages always exist in pairs; a command message and a corresponding * response message. If the C/R bit is set to 1 the message is a command, * if it is set to 0 the message is a response... * * ... * * NOTE: Notice that when UIH frames are used to convey information on DLCI 0 * there are at least two different fields that contain a C/R bit, and the * bits are set of different form. The C/R bit in the Type field shall be set * as it is stated above, while the C/R bit in the Address field (see subclause * 5.2.1.2) shall be set as it is described in subclause 5.4.3.1." */ static int ng_btsocket_rfcomm_receive_mcc(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) { struct rfcomm_mcc_hdr *hdr = NULL; u_int8_t cr, type, length; mtx_assert(&s->session_mtx, MA_OWNED); /* * We can access data directly in the first mbuf, because we have * m_pullup()'ed mbuf chain in ng_btsocket_rfcomm_receive_frame(). * All MCC commands should fit into single mbuf (except probably TEST). */ hdr = mtod(m0, struct rfcomm_mcc_hdr *); cr = RFCOMM_CR(hdr->type); type = RFCOMM_MCC_TYPE(hdr->type); length = RFCOMM_MCC_LENGTH(hdr->length); /* Check MCC frame length */ if (sizeof(*hdr) + length != m0->m_pkthdr.len) { NG_BTSOCKET_RFCOMM_ERR( "%s: Invalid MCC frame length=%d, len=%d\n", __func__, length, m0->m_pkthdr.len); NG_FREE_M(m0); return (EMSGSIZE); } switch (type) { case RFCOMM_MCC_TEST: return (ng_btsocket_rfcomm_receive_test(s, m0)); /* NOT REACHED */ case RFCOMM_MCC_FCON: case RFCOMM_MCC_FCOFF: return (ng_btsocket_rfcomm_receive_fc(s, m0)); /* NOT REACHED */ case RFCOMM_MCC_MSC: return (ng_btsocket_rfcomm_receive_msc(s, m0)); /* NOT REACHED */ case RFCOMM_MCC_RPN: return (ng_btsocket_rfcomm_receive_rpn(s, m0)); /* NOT REACHED */ case RFCOMM_MCC_RLS: return (ng_btsocket_rfcomm_receive_rls(s, m0)); /* NOT REACHED */ case RFCOMM_MCC_PN: return (ng_btsocket_rfcomm_receive_pn(s, m0)); /* NOT REACHED */ case RFCOMM_MCC_NSC: NG_BTSOCKET_RFCOMM_ERR( "%s: Got MCC NSC, type=%#x, cr=%d, length=%d, session state=%d, flags=%#x, " \ "mtu=%d, len=%d\n", __func__, RFCOMM_MCC_TYPE(*((u_int8_t *)(hdr + 1))), cr, length, s->state, s->flags, s->mtu, m0->m_pkthdr.len); NG_FREE_M(m0); break; default: NG_BTSOCKET_RFCOMM_ERR( "%s: Got unknown MCC, type=%#x, cr=%d, length=%d, session state=%d, " \ "flags=%#x, mtu=%d, len=%d\n", __func__, type, cr, length, s->state, s->flags, s->mtu, m0->m_pkthdr.len); /* Reuse mbuf to send NSC */ hdr = mtod(m0, struct rfcomm_mcc_hdr *); m0->m_pkthdr.len = m0->m_len = sizeof(*hdr); /* Create MCC NSC header */ hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_NSC); hdr->length = RFCOMM_MKLEN8(1); /* Put back MCC command type we did not like */ m0->m_data[m0->m_len] = RFCOMM_MKMCC_TYPE(cr, type); m0->m_pkthdr.len ++; m0->m_len ++; /* Send UIH frame */ return (ng_btsocket_rfcomm_send_uih(s, RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0)); /* NOT REACHED */ } return (0); } /* ng_btsocket_rfcomm_receive_mcc */ /* * Receive RFCOMM TEST MCC command */ static int ng_btsocket_rfcomm_receive_test(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) { struct rfcomm_mcc_hdr *hdr = mtod(m0, struct rfcomm_mcc_hdr *); int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Got MCC TEST, cr=%d, length=%d, session state=%d, flags=%#x, mtu=%d, " \ "len=%d\n", __func__, RFCOMM_CR(hdr->type), RFCOMM_MCC_LENGTH(hdr->length), s->state, s->flags, s->mtu, m0->m_pkthdr.len); if (RFCOMM_CR(hdr->type)) { hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_TEST); error = ng_btsocket_rfcomm_send_uih(s, RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); } else NG_FREE_M(m0); /* XXX ignore response */ return (error); } /* ng_btsocket_rfcomm_receive_test */ /* * Receive RFCOMM FCON/FCOFF MCC command */ static int ng_btsocket_rfcomm_receive_fc(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) { struct rfcomm_mcc_hdr *hdr = mtod(m0, struct rfcomm_mcc_hdr *); u_int8_t type = RFCOMM_MCC_TYPE(hdr->type); int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); /* * Turn ON/OFF aggregate flow on the entire session. When remote peer * asserted flow control no transmission shall occur except on dlci 0 * (control channel). */ NG_BTSOCKET_RFCOMM_INFO( "%s: Got MCC FC%s, cr=%d, length=%d, session state=%d, flags=%#x, mtu=%d, " \ "len=%d\n", __func__, (type == RFCOMM_MCC_FCON)? "ON" : "OFF", RFCOMM_CR(hdr->type), RFCOMM_MCC_LENGTH(hdr->length), s->state, s->flags, s->mtu, m0->m_pkthdr.len); if (RFCOMM_CR(hdr->type)) { if (type == RFCOMM_MCC_FCON) s->flags &= ~NG_BTSOCKET_RFCOMM_SESSION_RFC; else s->flags |= NG_BTSOCKET_RFCOMM_SESSION_RFC; hdr->type = RFCOMM_MKMCC_TYPE(0, type); error = ng_btsocket_rfcomm_send_uih(s, RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); } else NG_FREE_M(m0); /* XXX ignore response */ return (error); } /* ng_btsocket_rfcomm_receive_fc */ /* * Receive RFCOMM MSC MCC command */ static int ng_btsocket_rfcomm_receive_msc(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) { struct rfcomm_mcc_hdr *hdr = mtod(m0, struct rfcomm_mcc_hdr*); struct rfcomm_mcc_msc *msc = (struct rfcomm_mcc_msc *)(hdr+1); ng_btsocket_rfcomm_pcb_t *pcb = NULL; int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Got MCC MSC, dlci=%d, cr=%d, length=%d, session state=%d, flags=%#x, " \ "mtu=%d, len=%d\n", __func__, RFCOMM_DLCI(msc->address), RFCOMM_CR(hdr->type), RFCOMM_MCC_LENGTH(hdr->length), s->state, s->flags, s->mtu, m0->m_pkthdr.len); if (RFCOMM_CR(hdr->type)) { pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, RFCOMM_DLCI(msc->address)); if (pcb == NULL) { NG_BTSOCKET_RFCOMM_WARN( "%s: Got MSC command for non-existing dlci=%d\n", __func__, RFCOMM_DLCI(msc->address)); NG_FREE_M(m0); return (ENOENT); } mtx_lock(&pcb->pcb_mtx); if (pcb->state != NG_BTSOCKET_RFCOMM_DLC_CONNECTING && pcb->state != NG_BTSOCKET_RFCOMM_DLC_CONNECTED) { NG_BTSOCKET_RFCOMM_WARN( "%s: Got MSC on dlci=%d in invalid state=%d\n", __func__, RFCOMM_DLCI(msc->address), pcb->state); mtx_unlock(&pcb->pcb_mtx); NG_FREE_M(m0); return (EINVAL); } pcb->rmodem = msc->modem; /* Update remote port signals */ hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_MSC); error = ng_btsocket_rfcomm_send_uih(s, RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); #if 0 /* YYY */ /* Send more data from DLC. XXX check for errors? */ if (!(pcb->rmodem & RFCOMM_MODEM_FC) && !(pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC)) ng_btsocket_rfcomm_pcb_send(pcb, ALOT); #endif /* YYY */ mtx_unlock(&pcb->pcb_mtx); } else NG_FREE_M(m0); /* XXX ignore response */ return (error); } /* ng_btsocket_rfcomm_receive_msc */ /* * Receive RFCOMM RPN MCC command * XXX FIXME do we need htole16/le16toh for RPN param_mask? */ static int ng_btsocket_rfcomm_receive_rpn(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) { struct rfcomm_mcc_hdr *hdr = mtod(m0, struct rfcomm_mcc_hdr *); struct rfcomm_mcc_rpn *rpn = (struct rfcomm_mcc_rpn *)(hdr + 1); int error = 0; u_int16_t param_mask; u_int8_t bit_rate, data_bits, stop_bits, parity, flow_control, xon_char, xoff_char; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Got MCC RPN, dlci=%d, cr=%d, length=%d, session state=%d, flags=%#x, " \ "mtu=%d, len=%d\n", __func__, RFCOMM_DLCI(rpn->dlci), RFCOMM_CR(hdr->type), RFCOMM_MCC_LENGTH(hdr->length), s->state, s->flags, s->mtu, m0->m_pkthdr.len); if (RFCOMM_CR(hdr->type)) { param_mask = RFCOMM_RPN_PM_ALL; if (RFCOMM_MCC_LENGTH(hdr->length) == 1) { /* Request - return default setting */ bit_rate = RFCOMM_RPN_BR_115200; data_bits = RFCOMM_RPN_DATA_8; stop_bits = RFCOMM_RPN_STOP_1; parity = RFCOMM_RPN_PARITY_NONE; flow_control = RFCOMM_RPN_FLOW_NONE; xon_char = RFCOMM_RPN_XON_CHAR; xoff_char = RFCOMM_RPN_XOFF_CHAR; } else { /* * Ignore/accept bit_rate, 8 bits, 1 stop bit, no * parity, no flow control lines, default XON/XOFF * chars. */ bit_rate = rpn->bit_rate; rpn->param_mask = le16toh(rpn->param_mask); /* XXX */ data_bits = RFCOMM_RPN_DATA_BITS(rpn->line_settings); if (rpn->param_mask & RFCOMM_RPN_PM_DATA && data_bits != RFCOMM_RPN_DATA_8) { data_bits = RFCOMM_RPN_DATA_8; param_mask ^= RFCOMM_RPN_PM_DATA; } stop_bits = RFCOMM_RPN_STOP_BITS(rpn->line_settings); if (rpn->param_mask & RFCOMM_RPN_PM_STOP && stop_bits != RFCOMM_RPN_STOP_1) { stop_bits = RFCOMM_RPN_STOP_1; param_mask ^= RFCOMM_RPN_PM_STOP; } parity = RFCOMM_RPN_PARITY(rpn->line_settings); if (rpn->param_mask & RFCOMM_RPN_PM_PARITY && parity != RFCOMM_RPN_PARITY_NONE) { parity = RFCOMM_RPN_PARITY_NONE; param_mask ^= RFCOMM_RPN_PM_PARITY; } flow_control = rpn->flow_control; if (rpn->param_mask & RFCOMM_RPN_PM_FLOW && flow_control != RFCOMM_RPN_FLOW_NONE) { flow_control = RFCOMM_RPN_FLOW_NONE; param_mask ^= RFCOMM_RPN_PM_FLOW; } xon_char = rpn->xon_char; if (rpn->param_mask & RFCOMM_RPN_PM_XON && xon_char != RFCOMM_RPN_XON_CHAR) { xon_char = RFCOMM_RPN_XON_CHAR; param_mask ^= RFCOMM_RPN_PM_XON; } xoff_char = rpn->xoff_char; if (rpn->param_mask & RFCOMM_RPN_PM_XOFF && xoff_char != RFCOMM_RPN_XOFF_CHAR) { xoff_char = RFCOMM_RPN_XOFF_CHAR; param_mask ^= RFCOMM_RPN_PM_XOFF; } } rpn->bit_rate = bit_rate; rpn->line_settings = RFCOMM_MKRPN_LINE_SETTINGS(data_bits, stop_bits, parity); rpn->flow_control = flow_control; rpn->xon_char = xon_char; rpn->xoff_char = xoff_char; rpn->param_mask = htole16(param_mask); /* XXX */ m0->m_pkthdr.len = m0->m_len = sizeof(*hdr) + sizeof(*rpn); hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_RPN); error = ng_btsocket_rfcomm_send_uih(s, RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); } else NG_FREE_M(m0); /* XXX ignore response */ return (error); } /* ng_btsocket_rfcomm_receive_rpn */ /* * Receive RFCOMM RLS MCC command */ static int ng_btsocket_rfcomm_receive_rls(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) { struct rfcomm_mcc_hdr *hdr = mtod(m0, struct rfcomm_mcc_hdr *); struct rfcomm_mcc_rls *rls = (struct rfcomm_mcc_rls *)(hdr + 1); int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); /* * XXX FIXME Do we have to do anything else here? Remote peer tries to * tell us something about DLCI. Just report what we have received and * return back received values as required by TS 07.10 spec. */ NG_BTSOCKET_RFCOMM_INFO( "%s: Got MCC RLS, dlci=%d, status=%#x, cr=%d, length=%d, session state=%d, " \ "flags=%#x, mtu=%d, len=%d\n", __func__, RFCOMM_DLCI(rls->address), rls->status, RFCOMM_CR(hdr->type), RFCOMM_MCC_LENGTH(hdr->length), s->state, s->flags, s->mtu, m0->m_pkthdr.len); if (RFCOMM_CR(hdr->type)) { if (rls->status & 0x1) NG_BTSOCKET_RFCOMM_ERR( "%s: Got RLS dlci=%d, error=%#x\n", __func__, RFCOMM_DLCI(rls->address), rls->status >> 1); hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_RLS); error = ng_btsocket_rfcomm_send_uih(s, RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); } else NG_FREE_M(m0); /* XXX ignore responses */ return (error); } /* ng_btsocket_rfcomm_receive_rls */ /* * Receive RFCOMM PN MCC command */ static int ng_btsocket_rfcomm_receive_pn(ng_btsocket_rfcomm_session_p s, struct mbuf *m0) { struct rfcomm_mcc_hdr *hdr = mtod(m0, struct rfcomm_mcc_hdr*); struct rfcomm_mcc_pn *pn = (struct rfcomm_mcc_pn *)(hdr+1); ng_btsocket_rfcomm_pcb_t *pcb = NULL; int error = 0; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Got MCC PN, dlci=%d, cr=%d, length=%d, flow_control=%#x, priority=%d, " \ "ack_timer=%d, mtu=%d, max_retrans=%d, credits=%d, session state=%d, " \ "flags=%#x, session mtu=%d, len=%d\n", __func__, pn->dlci, RFCOMM_CR(hdr->type), RFCOMM_MCC_LENGTH(hdr->length), pn->flow_control, pn->priority, pn->ack_timer, le16toh(pn->mtu), pn->max_retrans, pn->credits, s->state, s->flags, s->mtu, m0->m_pkthdr.len); if (pn->dlci == 0) { NG_BTSOCKET_RFCOMM_ERR("%s: Zero dlci in MCC PN\n", __func__); NG_FREE_M(m0); return (EINVAL); } /* Check if we have this dlci */ pcb = ng_btsocket_rfcomm_pcb_by_dlci(s, pn->dlci); if (pcb != NULL) { mtx_lock(&pcb->pcb_mtx); if (RFCOMM_CR(hdr->type)) { /* PN Request */ ng_btsocket_rfcomm_set_pn(pcb, 1, pn->flow_control, pn->credits, pn->mtu); if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) { pn->flow_control = 0xe0; pn->credits = RFCOMM_DEFAULT_CREDITS; } else { pn->flow_control = 0; pn->credits = 0; } hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_PN); error = ng_btsocket_rfcomm_send_uih(s, RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); } else { /* PN Response - proceed with SABM. Timeout still set */ if (pcb->state == NG_BTSOCKET_RFCOMM_DLC_CONFIGURING) { ng_btsocket_rfcomm_set_pn(pcb, 0, pn->flow_control, pn->credits, pn->mtu); pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONNECTING; error = ng_btsocket_rfcomm_send_command(s, RFCOMM_FRAME_SABM, pn->dlci); } else NG_BTSOCKET_RFCOMM_WARN( "%s: Got PN response for dlci=%d in invalid state=%d\n", __func__, pn->dlci, pcb->state); NG_FREE_M(m0); } mtx_unlock(&pcb->pcb_mtx); } else if (RFCOMM_CR(hdr->type)) { /* PN request to non-existing dlci - incomming connection */ pcb = ng_btsocket_rfcomm_connect_ind(s, RFCOMM_SRVCHANNEL(pn->dlci)); if (pcb != NULL) { struct socket *so = NULL; mtx_lock(&pcb->pcb_mtx); pcb->dlci = pn->dlci; ng_btsocket_rfcomm_set_pn(pcb, 1, pn->flow_control, pn->credits, pn->mtu); if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) { pn->flow_control = 0xe0; pn->credits = RFCOMM_DEFAULT_CREDITS; } else { pn->flow_control = 0; pn->credits = 0; } hdr->type = RFCOMM_MKMCC_TYPE(0, RFCOMM_MCC_PN); error = ng_btsocket_rfcomm_send_uih(s, RFCOMM_MKADDRESS(INITIATOR(s), 0), 0, 0, m0); if (error == 0) { ng_btsocket_rfcomm_timeout(pcb); pcb->state = NG_BTSOCKET_RFCOMM_DLC_CONNECTING; soisconnecting(pcb->so); } else if (ng_btsocket_rfcomm_pcb_kill(pcb, error)) so = pcb->so; mtx_unlock(&pcb->pcb_mtx); if (so != NULL) ng_btsocket_rfcomm_detach(so); } else { /* Nobody is listen()ing on this channel */ error = ng_btsocket_rfcomm_send_command(s, RFCOMM_FRAME_DM, pn->dlci); NG_FREE_M(m0); } } else NG_FREE_M(m0); /* XXX ignore response to non-existing dlci */ return (error); } /* ng_btsocket_rfcomm_receive_pn */ /* * Set PN parameters for dlci. Caller must hold pcb->pcb_mtx. * * From Bluetooth spec. * * "... The CL1 - CL4 field is completely redefined. (In TS07.10 this defines * the convergence layer to use, which is not applicable to RFCOMM. In RFCOMM, * in Bluetooth versions up to 1.0B, this field was forced to 0). * * In the PN request sent prior to a DLC establishment, this field must contain * the value 15 (0xF), indicating support of credit based flow control in the * sender. See Table 5.3 below. If the PN response contains any other value * than 14 (0xE) in this field, it is inferred that the peer RFCOMM entity is * not supporting the credit based flow control feature. (This is only possible * if the peer RFCOMM implementation is only conforming to Bluetooth version * 1.0B.) If a PN request is sent on an already open DLC, then this field must * contain the value zero; it is not possible to set initial credits more * than once per DLC activation. A responding implementation must set this * field in the PN response to 14 (0xE), if (and only if) the value in the PN * request was 15..." */ static void ng_btsocket_rfcomm_set_pn(ng_btsocket_rfcomm_pcb_p pcb, u_int8_t cr, u_int8_t flow_control, u_int8_t credits, u_int16_t mtu) { mtx_assert(&pcb->pcb_mtx, MA_OWNED); pcb->mtu = le16toh(mtu); if (cr) { if (flow_control == 0xf0) { pcb->flags |= NG_BTSOCKET_RFCOMM_DLC_CFC; pcb->tx_cred = credits; } else { pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_CFC; pcb->tx_cred = 0; } } else { if (flow_control == 0xe0) { pcb->flags |= NG_BTSOCKET_RFCOMM_DLC_CFC; pcb->tx_cred = credits; } else { pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_CFC; pcb->tx_cred = 0; } } NG_BTSOCKET_RFCOMM_INFO( "%s: cr=%d, dlci=%d, state=%d, flags=%#x, mtu=%d, rx_cred=%d, tx_cred=%d\n", __func__, cr, pcb->dlci, pcb->state, pcb->flags, pcb->mtu, pcb->rx_cred, pcb->tx_cred); } /* ng_btsocket_rfcomm_set_pn */ /* * Send RFCOMM SABM/DISC/UA/DM frames. Caller must hold s->session_mtx */ static int ng_btsocket_rfcomm_send_command(ng_btsocket_rfcomm_session_p s, u_int8_t type, u_int8_t dlci) { struct rfcomm_cmd_hdr *hdr = NULL; struct mbuf *m = NULL; int cr; mtx_assert(&s->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Sending command type %#x, session state=%d, flags=%#x, mtu=%d, dlci=%d\n", __func__, type, s->state, s->flags, s->mtu, dlci); switch (type) { case RFCOMM_FRAME_SABM: case RFCOMM_FRAME_DISC: cr = INITIATOR(s); break; case RFCOMM_FRAME_UA: case RFCOMM_FRAME_DM: cr = !INITIATOR(s); break; default: panic("%s: Invalid frame type=%#x\n", __func__, type); return (EINVAL); /* NOT REACHED */ } MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) return (ENOBUFS); m->m_pkthdr.len = m->m_len = sizeof(*hdr); hdr = mtod(m, struct rfcomm_cmd_hdr *); hdr->address = RFCOMM_MKADDRESS(cr, dlci); hdr->control = RFCOMM_MKCONTROL(type, 1); hdr->length = RFCOMM_MKLEN8(0); hdr->fcs = ng_btsocket_rfcomm_fcs3((u_int8_t *) hdr); NG_BT_MBUFQ_ENQUEUE(&s->outq, m); return (0); } /* ng_btsocket_rfcomm_send_command */ /* * Send RFCOMM UIH frame. Caller must hold s->session_mtx */ static int ng_btsocket_rfcomm_send_uih(ng_btsocket_rfcomm_session_p s, u_int8_t address, u_int8_t pf, u_int8_t credits, struct mbuf *data) { struct rfcomm_frame_hdr *hdr = NULL; struct mbuf *m = NULL, *mcrc = NULL; u_int16_t length; mtx_assert(&s->session_mtx, MA_OWNED); MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) { NG_FREE_M(data); return (ENOBUFS); } m->m_pkthdr.len = m->m_len = sizeof(*hdr); MGET(mcrc, M_DONTWAIT, MT_DATA); if (mcrc == NULL) { NG_FREE_M(data); return (ENOBUFS); } mcrc->m_len = 1; /* Fill UIH frame header */ hdr = mtod(m, struct rfcomm_frame_hdr *); hdr->address = address; hdr->control = RFCOMM_MKCONTROL(RFCOMM_FRAME_UIH, pf); /* Calculate FCS */ mcrc->m_data[0] = ng_btsocket_rfcomm_fcs2((u_int8_t *) hdr); /* Put length back */ length = (data != NULL)? data->m_pkthdr.len : 0; if (length > 127) { u_int16_t l = htole16(RFCOMM_MKLEN16(length)); bcopy(&l, &hdr->length, sizeof(l)); m->m_pkthdr.len ++; m->m_len ++; } else hdr->length = RFCOMM_MKLEN8(length); if (pf) { m->m_data[m->m_len] = credits; m->m_pkthdr.len ++; m->m_len ++; } /* Add payload */ if (data != NULL) { m_cat(m, data); m->m_pkthdr.len += length; } /* Put FCS back */ m_cat(m, mcrc); m->m_pkthdr.len ++; NG_BTSOCKET_RFCOMM_INFO( "%s: Sending UIH state=%d, flags=%#x, address=%d, length=%d, pf=%d, " \ "credits=%d, len=%d\n", __func__, s->state, s->flags, address, length, pf, credits, m->m_pkthdr.len); NG_BT_MBUFQ_ENQUEUE(&s->outq, m); return (0); } /* ng_btsocket_rfcomm_send_uih */ /* * Send MSC request. Caller must hold pcb->pcb_mtx and pcb->session->session_mtx */ static int ng_btsocket_rfcomm_send_msc(ng_btsocket_rfcomm_pcb_p pcb) { struct mbuf *m = NULL; struct rfcomm_mcc_hdr *hdr = NULL; struct rfcomm_mcc_msc *msc = NULL; mtx_assert(&pcb->session->session_mtx, MA_OWNED); mtx_assert(&pcb->pcb_mtx, MA_OWNED); MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) return (ENOBUFS); m->m_pkthdr.len = m->m_len = sizeof(*hdr) + sizeof(*msc); hdr = mtod(m, struct rfcomm_mcc_hdr *); msc = (struct rfcomm_mcc_msc *)(hdr + 1); hdr->type = RFCOMM_MKMCC_TYPE(1, RFCOMM_MCC_MSC); hdr->length = RFCOMM_MKLEN8(sizeof(*msc)); msc->address = RFCOMM_MKADDRESS(1, pcb->dlci); msc->modem = pcb->lmodem; NG_BTSOCKET_RFCOMM_INFO( "%s: Sending MSC dlci=%d, state=%d, flags=%#x, address=%d, modem=%#x\n", __func__, pcb->dlci, pcb->state, pcb->flags, msc->address, msc->modem); return (ng_btsocket_rfcomm_send_uih(pcb->session, RFCOMM_MKADDRESS(INITIATOR(pcb->session), 0), 0, 0, m)); } /* ng_btsocket_rfcomm_send_msc */ /* * Send PN request. Caller must hold pcb->pcb_mtx and pcb->session->session_mtx */ static int ng_btsocket_rfcomm_send_pn(ng_btsocket_rfcomm_pcb_p pcb) { struct mbuf *m = NULL; struct rfcomm_mcc_hdr *hdr = NULL; struct rfcomm_mcc_pn *pn = NULL; mtx_assert(&pcb->session->session_mtx, MA_OWNED); mtx_assert(&pcb->pcb_mtx, MA_OWNED); MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) return (ENOBUFS); m->m_pkthdr.len = m->m_len = sizeof(*hdr) + sizeof(*pn); hdr = mtod(m, struct rfcomm_mcc_hdr *); pn = (struct rfcomm_mcc_pn *)(hdr + 1); hdr->type = RFCOMM_MKMCC_TYPE(1, RFCOMM_MCC_PN); hdr->length = RFCOMM_MKLEN8(sizeof(*pn)); pn->dlci = pcb->dlci; /* * Set default DLCI priority as described in GSM 07.10 * (ETSI TS 101 369) clause 5.6 page 42 */ pn->priority = (pcb->dlci < 56)? (((pcb->dlci >> 3) << 3) + 7) : 61; pn->ack_timer = 0; pn->mtu = htole16(pcb->mtu); pn->max_retrans = 0; if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) { pn->flow_control = 0xf0; pn->credits = pcb->rx_cred; } else { pn->flow_control = 0; pn->credits = 0; } NG_BTSOCKET_RFCOMM_INFO( "%s: Sending PN dlci=%d, state=%d, flags=%#x, mtu=%d, flow_control=%#x, " \ "credits=%d\n", __func__, pcb->dlci, pcb->state, pcb->flags, pcb->mtu, pn->flow_control, pn->credits); return (ng_btsocket_rfcomm_send_uih(pcb->session, RFCOMM_MKADDRESS(INITIATOR(pcb->session), 0), 0, 0, m)); } /* ng_btsocket_rfcomm_send_pn */ /* * Calculate and send credits based on available space in receive buffer */ static int ng_btsocket_rfcomm_send_credits(ng_btsocket_rfcomm_pcb_p pcb) { int error = 0; u_int8_t credits; mtx_assert(&pcb->pcb_mtx, MA_OWNED); mtx_assert(&pcb->session->session_mtx, MA_OWNED); NG_BTSOCKET_RFCOMM_INFO( "%s: Sending more credits, dlci=%d, state=%d, flags=%#x, mtu=%d, " \ "space=%ld, tx_cred=%d, rx_cred=%d\n", __func__, pcb->dlci, pcb->state, pcb->flags, pcb->mtu, sbspace(&pcb->so->so_rcv), pcb->tx_cred, pcb->rx_cred); credits = sbspace(&pcb->so->so_rcv) / pcb->mtu; if (credits > 0) { if (pcb->rx_cred + credits > RFCOMM_MAX_CREDITS) credits = RFCOMM_MAX_CREDITS - pcb->rx_cred; error = ng_btsocket_rfcomm_send_uih( pcb->session, RFCOMM_MKADDRESS(INITIATOR(pcb->session), pcb->dlci), 1, credits, NULL); if (error == 0) { pcb->rx_cred += credits; NG_BTSOCKET_RFCOMM_INFO( "%s: Gave remote side %d more credits, dlci=%d, state=%d, flags=%#x, " \ "rx_cred=%d, tx_cred=%d\n", __func__, credits, pcb->dlci, pcb->state, pcb->flags, pcb->rx_cred, pcb->tx_cred); } else NG_BTSOCKET_RFCOMM_ERR( "%s: Could not send credits, error=%d, dlci=%d, state=%d, flags=%#x, " \ "mtu=%d, space=%ld, tx_cred=%d, rx_cred=%d\n", __func__, error, pcb->dlci, pcb->state, pcb->flags, pcb->mtu, sbspace(&pcb->so->so_rcv), pcb->tx_cred, pcb->rx_cred); } return (error); } /* ng_btsocket_rfcomm_send_credits */ /***************************************************************************** ***************************************************************************** ** RFCOMM DLCs ***************************************************************************** *****************************************************************************/ /* * Send data from socket send buffer * Caller must hold pcb->pcb_mtx and pcb->session->session_mtx */ static int ng_btsocket_rfcomm_pcb_send(ng_btsocket_rfcomm_pcb_p pcb, int limit) { struct mbuf *m = NULL; int sent, length, error; mtx_assert(&pcb->session->session_mtx, MA_OWNED); mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) limit = min(limit, pcb->tx_cred); else if (!(pcb->rmodem & RFCOMM_MODEM_FC)) limit = min(limit, RFCOMM_MAX_CREDITS); /* XXX ??? */ else limit = 0; if (limit == 0) { NG_BTSOCKET_RFCOMM_INFO( "%s: Could not send - remote flow control asserted, dlci=%d, flags=%#x, " \ "rmodem=%#x, tx_cred=%d\n", __func__, pcb->dlci, pcb->flags, pcb->rmodem, pcb->tx_cred); return (0); } for (error = 0, sent = 0; sent < limit; sent ++) { length = min(pcb->mtu, pcb->so->so_snd.sb_cc); if (length == 0) break; /* Get the chunk from the socket's send buffer */ m = ng_btsocket_rfcomm_prepare_packet(&pcb->so->so_snd, length); if (m == NULL) { error = ENOBUFS; break; } sbdrop(&pcb->so->so_snd, length); error = ng_btsocket_rfcomm_send_uih(pcb->session, RFCOMM_MKADDRESS(INITIATOR(pcb->session), pcb->dlci), 0, 0, m); if (error != 0) break; } if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_CFC) pcb->tx_cred -= sent; if (error == 0 && sent > 0) { pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_SENDING; sowwakeup(pcb->so); } return (error); } /* ng_btsocket_rfcomm_pcb_send */ /* * Unlink and disconnect DLC. If ng_btsocket_rfcomm_pcb_kill() returns * non zero value than socket has no reference and has to be detached. * Caller must hold pcb->pcb_mtx and pcb->session->session_mtx */ static int ng_btsocket_rfcomm_pcb_kill(ng_btsocket_rfcomm_pcb_p pcb, int error) { ng_btsocket_rfcomm_session_p s = pcb->session; NG_BTSOCKET_RFCOMM_INFO( "%s: Killing DLC, so=%p, dlci=%d, state=%d, flags=%#x, error=%d\n", __func__, pcb->so, pcb->dlci, pcb->state, pcb->flags, error); if (pcb->session == NULL) panic("%s: DLC without session, pcb=%p, state=%d, flags=%#x\n", __func__, pcb, pcb->state, pcb->flags); mtx_assert(&pcb->session->session_mtx, MA_OWNED); mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO) ng_btsocket_rfcomm_untimeout(pcb); /* Detach DLC from the session. Does not matter which state DLC in */ LIST_REMOVE(pcb, session_next); pcb->session = NULL; /* Change DLC state and wakeup all sleepers */ pcb->state = NG_BTSOCKET_RFCOMM_DLC_CLOSED; pcb->so->so_error = error; soisdisconnected(pcb->so); wakeup(&pcb->state); /* Check if we have any DLCs left on the session */ if (LIST_EMPTY(&s->dlcs) && INITIATOR(s)) { NG_BTSOCKET_RFCOMM_INFO( "%s: Disconnecting session, state=%d, flags=%#x, mtu=%d\n", __func__, s->state, s->flags, s->mtu); switch (s->state) { case NG_BTSOCKET_RFCOMM_SESSION_CLOSED: case NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING: /* * Do not have to do anything here. We can get here * when L2CAP connection was terminated or we have * received DISC on multiplexor channel */ break; case NG_BTSOCKET_RFCOMM_SESSION_OPEN: /* Send DISC on multiplexor channel */ error = ng_btsocket_rfcomm_send_command(s, RFCOMM_FRAME_DISC, 0); if (error == 0) { s->state = NG_BTSOCKET_RFCOMM_SESSION_DISCONNECTING; break; } /* FALL THROUGH */ case NG_BTSOCKET_RFCOMM_SESSION_CONNECTING: case NG_BTSOCKET_RFCOMM_SESSION_CONNECTED: s->state = NG_BTSOCKET_RFCOMM_SESSION_CLOSED; break; /* case NG_BTSOCKET_RFCOMM_SESSION_LISTENING: */ default: panic("%s: Invalid session state=%d, flags=%#x\n", __func__, s->state, s->flags); break; } ng_btsocket_rfcomm_task_wakeup(); } return (pcb->so->so_state & SS_NOFDREF); } /* ng_btsocket_rfcomm_pcb_kill */ /* * Look for RFCOMM socket with given channel and source address */ static ng_btsocket_rfcomm_pcb_p ng_btsocket_rfcomm_pcb_by_channel(bdaddr_p src, int channel) { ng_btsocket_rfcomm_pcb_p pcb = NULL; mtx_lock(&ng_btsocket_rfcomm_sockets_mtx); LIST_FOREACH(pcb, &ng_btsocket_rfcomm_sockets, next) if (pcb->channel == channel && bcmp(&pcb->src, src, sizeof(*src)) == 0) break; mtx_unlock(&ng_btsocket_rfcomm_sockets_mtx); return (pcb); } /* ng_btsocket_rfcomm_pcb_by_channel */ /* * Look for given dlci for given RFCOMM session. Caller must hold s->session_mtx */ static ng_btsocket_rfcomm_pcb_p ng_btsocket_rfcomm_pcb_by_dlci(ng_btsocket_rfcomm_session_p s, int dlci) { ng_btsocket_rfcomm_pcb_p pcb = NULL; mtx_assert(&s->session_mtx, MA_OWNED); LIST_FOREACH(pcb, &s->dlcs, session_next) if (pcb->dlci == dlci) break; return (pcb); } /* ng_btsocket_rfcomm_pcb_by_dlci */ /* * Look for socket that listens on given src address and given channel */ static ng_btsocket_rfcomm_pcb_p ng_btsocket_rfcomm_pcb_listener(bdaddr_p src, int channel) { ng_btsocket_rfcomm_pcb_p pcb = NULL, pcb1 = NULL; mtx_lock(&ng_btsocket_rfcomm_sockets_mtx); LIST_FOREACH(pcb, &ng_btsocket_rfcomm_sockets, next) { if (pcb->channel != channel || !(pcb->so->so_options & SO_ACCEPTCONN)) continue; if (bcmp(&pcb->src, src, sizeof(*src)) == 0) break; if (bcmp(&pcb->src, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) pcb1 = pcb; } mtx_unlock(&ng_btsocket_rfcomm_sockets_mtx); return ((pcb != NULL)? pcb : pcb1); } /* ng_btsocket_rfcomm_pcb_listener */ /***************************************************************************** ***************************************************************************** ** Misc. functions ***************************************************************************** *****************************************************************************/ /* * Set timeout. Caller MUST hold pcb_mtx */ static void ng_btsocket_rfcomm_timeout(ng_btsocket_rfcomm_pcb_p pcb) { mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (!(pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO)) { pcb->flags |= NG_BTSOCKET_RFCOMM_DLC_TIMO; pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT; pcb->timo = timeout(ng_btsocket_rfcomm_process_timeout, pcb, ng_btsocket_rfcomm_timo * hz); } else panic("%s: Duplicated socket timeout?!\n", __func__); } /* ng_btsocket_rfcomm_timeout */ /* * Unset pcb timeout. Caller MUST hold pcb_mtx */ static void ng_btsocket_rfcomm_untimeout(ng_btsocket_rfcomm_pcb_p pcb) { mtx_assert(&pcb->pcb_mtx, MA_OWNED); if (pcb->flags & NG_BTSOCKET_RFCOMM_DLC_TIMO) { untimeout(ng_btsocket_rfcomm_process_timeout, pcb, pcb->timo); pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_TIMO; pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT; } else panic("%s: No socket timeout?!\n", __func__); } /* ng_btsocket_rfcomm_timeout */ /* * Process pcb timeout */ static void ng_btsocket_rfcomm_process_timeout(void *xpcb) { ng_btsocket_rfcomm_pcb_p pcb = (ng_btsocket_rfcomm_pcb_p) xpcb; mtx_lock(&pcb->pcb_mtx); NG_BTSOCKET_RFCOMM_INFO( "%s: Timeout, so=%p, dlci=%d, state=%d, flags=%#x\n", __func__, pcb->so, pcb->dlci, pcb->state, pcb->flags); pcb->flags &= ~NG_BTSOCKET_RFCOMM_DLC_TIMO; pcb->flags |= NG_BTSOCKET_RFCOMM_DLC_TIMEDOUT; switch (pcb->state) { case NG_BTSOCKET_RFCOMM_DLC_CONFIGURING: case NG_BTSOCKET_RFCOMM_DLC_CONNECTING: pcb->state = NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING; break; case NG_BTSOCKET_RFCOMM_DLC_W4_CONNECT: case NG_BTSOCKET_RFCOMM_DLC_DISCONNECTING: break; default: panic( "%s: DLC timeout in invalid state, dlci=%d, state=%d, flags=%#x\n", __func__, pcb->dlci, pcb->state, pcb->flags); break; } ng_btsocket_rfcomm_task_wakeup(); mtx_unlock(&pcb->pcb_mtx); } /* ng_btsocket_rfcomm_process_timeout */ /* * Get up to length bytes from the socket buffer */ static struct mbuf * ng_btsocket_rfcomm_prepare_packet(struct sockbuf *sb, int length) { struct mbuf *top = NULL, *m = NULL, *n = NULL, *nextpkt = NULL; int mlen, noff, len; MGETHDR(top, M_DONTWAIT, MT_DATA); if (top == NULL) return (NULL); top->m_pkthdr.len = length; top->m_len = 0; mlen = MHLEN; m = top; n = sb->sb_mb; nextpkt = n->m_nextpkt; noff = 0; while (length > 0 && n != NULL) { len = min(mlen - m->m_len, n->m_len - noff); if (len > length) len = length; bcopy(mtod(n, caddr_t)+noff, mtod(m, caddr_t)+m->m_len, len); m->m_len += len; noff += len; length -= len; if (length > 0 && m->m_len == mlen) { MGET(m->m_next, M_DONTWAIT, MT_DATA); if (m->m_next == NULL) { NG_FREE_M(top); return (NULL); } m = m->m_next; m->m_len = 0; mlen = MLEN; } if (noff == n->m_len) { noff = 0; n = n->m_next; if (n == NULL) n = nextpkt; nextpkt = (n != NULL)? n->m_nextpkt : NULL; } } if (length < 0) panic("%s: length=%d\n", __func__, length); if (length > 0 && n == NULL) panic("%s: bogus length=%d, n=%p\n", __func__, length, n); return (top); } /* ng_btsocket_rfcomm_prepare_packet */ diff --git a/sys/netgraph/ng_socket.c b/sys/netgraph/ng_socket.c index 5ad1bd695f84..5641effd641c 100644 --- a/sys/netgraph/ng_socket.c +++ b/sys/netgraph/ng_socket.c @@ -1,1200 +1,1196 @@ /* * ng_socket.c */ /*- * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Julian Elischer * * $FreeBSD$ * $Whistle: ng_socket.c,v 1.28 1999/11/01 09:24:52 julian Exp $ */ /* * Netgraph socket nodes * * There are two types of netgraph sockets, control and data. * Control sockets have a netgraph node, but data sockets are * parasitic on control sockets, and have no node of their own. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef NOTYET #include #endif #include #include #include #include #ifdef NG_SEPARATE_MALLOC MALLOC_DEFINE(M_NETGRAPH_PATH, "netgraph_path", "netgraph path info "); MALLOC_DEFINE(M_NETGRAPH_SOCK, "netgraph_sock", "netgraph socket info "); #else #define M_NETGRAPH_PATH M_NETGRAPH #define M_NETGRAPH_SOCK M_NETGRAPH #endif /* * It's Ascii-art time! * +-------------+ +-------------+ * |socket (ctl)| |socket (data)| * +-------------+ +-------------+ * ^ ^ * | | * v v * +-----------+ +-----------+ * |pcb (ctl)| |pcb (data)| * +-----------+ +-----------+ * ^ ^ * | | * v v * +--------------------------+ * | Socket type private | * | data | * +--------------------------+ * ^ * | * v * +----------------+ * | struct ng_node | * +----------------+ */ /* Netgraph node methods */ static ng_constructor_t ngs_constructor; static ng_rcvmsg_t ngs_rcvmsg; static ng_shutdown_t ngs_shutdown; static ng_newhook_t ngs_newhook; static ng_connect_t ngs_connect; static ng_rcvdata_t ngs_rcvdata; static ng_disconnect_t ngs_disconnect; /* Internal methods */ static int ng_attach_data(struct socket *so); static int ng_attach_cntl(struct socket *so); static int ng_attach_common(struct socket *so, int type); static void ng_detach_common(struct ngpcb *pcbp, int type); static void ng_socket_free_priv(struct ngsock *priv); /*static int ng_internalize(struct mbuf *m, struct thread *p); */ static int ng_connect_data(struct sockaddr *nam, struct ngpcb *pcbp); static int ng_bind(struct sockaddr *nam, struct ngpcb *pcbp); static int ngs_mod_event(module_t mod, int event, void *data); static int ship_msg(struct ngpcb *pcbp, struct ng_mesg *msg, struct sockaddr_ng *addr); static void ng_socket_item_applied(void *context, int error); /* Netgraph type descriptor */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_SOCKET_NODE_TYPE, .mod_event = ngs_mod_event, .constructor = ngs_constructor, .rcvmsg = ngs_rcvmsg, .shutdown = ngs_shutdown, .newhook = ngs_newhook, .connect = ngs_connect, .rcvdata = ngs_rcvdata, .disconnect = ngs_disconnect, }; NETGRAPH_INIT_ORDERED(socket, &typestruct, SI_SUB_PROTO_DOMAIN, SI_ORDER_ANY); /* Buffer space */ static u_long ngpdg_sendspace = 20 * 1024; /* really max datagram size */ SYSCTL_INT(_net_graph, OID_AUTO, maxdgram, CTLFLAG_RW, &ngpdg_sendspace , 0, "Maximum outgoing Netgraph datagram size"); static u_long ngpdg_recvspace = 20 * 1024; SYSCTL_INT(_net_graph, OID_AUTO, recvspace, CTLFLAG_RW, &ngpdg_recvspace , 0, "Maximum space for incoming Netgraph datagrams"); /* List of all sockets */ static LIST_HEAD(, ngpcb) ngsocklist; static struct mtx ngsocketlist_mtx; #define sotongpcb(so) ((struct ngpcb *)(so)->so_pcb) /* If getting unexplained errors returned, set this to "kdb_enter("X"); */ #ifndef TRAP_ERROR #define TRAP_ERROR #endif /*************************************************************** Control sockets ***************************************************************/ static int ngc_attach(struct socket *so, int proto, struct thread *td) { struct ngpcb *const pcbp = sotongpcb(so); if (suser(td)) return (EPERM); if (pcbp != NULL) return (EISCONN); return (ng_attach_cntl(so)); } -static int +static void ngc_detach(struct socket *so) { struct ngpcb *const pcbp = sotongpcb(so); - if (pcbp == NULL) - return (EINVAL); + KASSERT(pcbp != NULL, ("ngc_detach: pcbp == NULL")); ng_detach_common(pcbp, NG_CONTROL); - return (0); } static int ngc_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *control, struct thread *td) { struct ngpcb *const pcbp = sotongpcb(so); struct ngsock *const priv = NG_NODE_PRIVATE(pcbp->sockdata->node); struct sockaddr_ng *const sap = (struct sockaddr_ng *) addr; struct ng_mesg *msg; struct mbuf *m0; item_p item; char *path = NULL; int len, error = 0; if (pcbp == NULL) { error = EINVAL; goto release; } #ifdef NOTYET if (control && (error = ng_internalize(control, td))) { if (pcbp->sockdata == NULL) { error = ENOTCONN; goto release; } } #else /* NOTYET */ if (control) { error = EINVAL; goto release; } #endif /* NOTYET */ /* Require destination as there may be >= 1 hooks on this node */ if (addr == NULL) { error = EDESTADDRREQ; goto release; } /* Allocate an expendable buffer for the path, chop off * the sockaddr header, and make sure it's NUL terminated */ len = sap->sg_len - 2; MALLOC(path, char *, len + 1, M_NETGRAPH_PATH, M_WAITOK); if (path == NULL) { error = ENOMEM; goto release; } bcopy(sap->sg_data, path, len); path[len] = '\0'; /* Move the actual message out of mbufs into a linear buffer. * Start by adding up the size of the data. (could use mh_len?) */ for (len = 0, m0 = m; m0 != NULL; m0 = m0->m_next) len += m0->m_len; /* Move the data into a linear buffer as well. Messages are not * delivered in mbufs. */ MALLOC(msg, struct ng_mesg *, len + 1, M_NETGRAPH_MSG, M_WAITOK); if (msg == NULL) { error = ENOMEM; goto release; } m_copydata(m, 0, len, (char *)msg); if (msg->header.version != NG_VERSION) { error = EINVAL; goto release; } /* * Hack alert! * We look into the message and if it mkpeers a node of unknown type, we * try to load it. We need to do this now, in syscall thread, because if * message gets queued and applied later we will get panic. */ if (msg->header.typecookie == NGM_GENERIC_COOKIE && msg->header.cmd == NGM_MKPEER) { struct ngm_mkpeer *const mkp = (struct ngm_mkpeer *) msg->data; struct ng_type *type; if ((type = ng_findtype(mkp->type)) == NULL) { char filename[NG_TYPESIZ + 3]; linker_file_t lf; /* Not found, try to load it as a loadable module. */ snprintf(filename, sizeof(filename), "ng_%s", mkp->type); mtx_lock(&Giant); error = linker_load_module(NULL, filename, NULL, NULL, &lf); mtx_unlock(&Giant); if (error != 0) { FREE(msg, M_NETGRAPH_MSG); goto release; } lf->userrefs++; /* See if type has been loaded successfully. */ if ((type = ng_findtype(mkp->type)) == NULL) { FREE(msg, M_NETGRAPH_MSG); error = ENXIO; goto release; } } } if ((item = ng_package_msg(msg, M_WAITOK)) == NULL) { error = ENOMEM; #ifdef TRACE_MESSAGES printf("ng_package_msg: err=%d\n", error); #endif goto release; } if ((error = ng_address_path((pcbp->sockdata->node), item, path, 0)) != 0) { #ifdef TRACE_MESSAGES printf("ng_address_path: errx=%d\n", error); #endif goto release; } #ifdef TRACE_MESSAGES printf("[%x]:<---------[socket]: c=<%d>cmd=%x(%s) f=%x #%d (%s)\n", item->el_dest->nd_ID, msg->header.typecookie, msg->header.cmd, msg->header.cmdstr, msg->header.flags, msg->header.token, item->el_dest->nd_type->name); #endif SAVE_LINE(item); /* * We do not want to return from syscall until the item * is processed by destination node. We register callback * on the item, which will update priv->error when item * was applied. * If ng_snd_item() has queued item, we sleep until * callback wakes us up. */ item->apply = ng_socket_item_applied; item->context = priv; priv->error = -1; error = ng_snd_item(item, NG_PROGRESS); if (error == EINPROGRESS) { mtx_lock(&priv->mtx); if (priv->error == -1) msleep(priv, &priv->mtx, 0, "ngsock", 0); mtx_unlock(&priv->mtx); KASSERT(priv->error != -1, ("ng_socket: priv->error wasn't updated")); error = priv->error; } release: if (path != NULL) FREE(path, M_NETGRAPH_PATH); if (control != NULL) m_freem(control); if (m != NULL) m_freem(m); return (error); } static int ngc_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { struct ngpcb *const pcbp = sotongpcb(so); if (pcbp == 0) return (EINVAL); return (ng_bind(nam, pcbp)); } static int ngc_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { printf(" program tried to connect control socket to remote node\n "); /* * At this time refuse to do this.. it used to * do something but it was undocumented and not used. */ return (EINVAL); } /*************************************************************** Data sockets ***************************************************************/ static int ngd_attach(struct socket *so, int proto, struct thread *td) { struct ngpcb *const pcbp = sotongpcb(so); if (pcbp != NULL) return (EISCONN); return (ng_attach_data(so)); } -static int +static void ngd_detach(struct socket *so) { struct ngpcb *const pcbp = sotongpcb(so); - if (pcbp == NULL) - return (EINVAL); + KASSERT(pcbp == NULL, ("ngd_detach: pcbp == NULL")); ng_detach_common(pcbp, NG_DATA); - return (0); } static int ngd_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *control, struct thread *td) { struct ngpcb *const pcbp = sotongpcb(so); struct sockaddr_ng *const sap = (struct sockaddr_ng *) addr; int len, error; hook_p hook = NULL; char hookname[NG_HOOKSIZ]; if ((pcbp == NULL) || (control != NULL)) { error = EINVAL; goto release; } if (pcbp->sockdata == NULL) { error = ENOTCONN; goto release; } if (sap == NULL) len = 0; /* Make compiler happy. */ else len = sap->sg_len - 2; /* * If the user used any of these ways to not specify an address * then handle specially. */ if ((sap == NULL) || (len <= 0) || (*sap->sg_data == '\0')) { if (NG_NODE_NUMHOOKS(pcbp->sockdata->node) != 1) { error = EDESTADDRREQ; goto release; } /* * if exactly one hook exists, just use it. * Special case to allow write(2) to work on an ng_socket. */ hook = LIST_FIRST(&pcbp->sockdata->node->nd_hooks); } else { if (len >= NG_HOOKSIZ) { error = EINVAL; goto release; } /* * chop off the sockaddr header, and make sure it's NUL * terminated */ bcopy(sap->sg_data, hookname, len); hookname[len] = '\0'; /* Find the correct hook from 'hookname' */ LIST_FOREACH(hook, &pcbp->sockdata->node->nd_hooks, hk_hooks) { if (strcmp(hookname, NG_HOOK_NAME(hook)) == 0) { break; } } if (hook == NULL) { error = EHOSTUNREACH; } } /* Send data (OK if hook is NULL) */ NG_SEND_DATA_ONLY(error, hook, m); /* makes m NULL */ release: if (control != NULL) m_freem(control); if (m != NULL) m_freem(m); return (error); } static int ngd_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { struct ngpcb *const pcbp = sotongpcb(so); if (pcbp == 0) return (EINVAL); return (ng_connect_data(nam, pcbp)); } /* * Used for both data and control sockets */ static int ng_setsockaddr(struct socket *so, struct sockaddr **addr) { struct ngpcb *pcbp; struct sockaddr_ng *sg; int sg_len; int error = 0; /* Why isn't sg_data a `char[1]' ? :-( */ sg_len = sizeof(struct sockaddr_ng) - sizeof(sg->sg_data) + 1; pcbp = sotongpcb(so); if ((pcbp == NULL) || (pcbp->sockdata == NULL)) /* XXXGL: can this still happen? */ return (EINVAL); mtx_lock(&pcbp->sockdata->mtx); if (pcbp->sockdata->node != NULL) { node_p node = pcbp->sockdata->node; int namelen = 0; /* silence compiler! */ if (NG_NODE_HAS_NAME(node)) sg_len += namelen = strlen(NG_NODE_NAME(node)); sg = malloc(sg_len, M_SONAME, M_WAITOK | M_ZERO); if (NG_NODE_HAS_NAME(node)) bcopy(NG_NODE_NAME(node), sg->sg_data, namelen); sg->sg_len = sg_len; sg->sg_family = AF_NETGRAPH; *addr = (struct sockaddr *)sg; mtx_unlock(&pcbp->sockdata->mtx); } else { mtx_unlock(&pcbp->sockdata->mtx); error = EINVAL; } return (error); } /* * Attach a socket to it's protocol specific partner. * For a control socket, actually create a netgraph node and attach * to it as well. */ static int ng_attach_cntl(struct socket *so) { struct ngsock *priv; struct ngpcb *pcbp; int error; /* Allocate node private info */ MALLOC(priv, struct ngsock *, sizeof(*priv), M_NETGRAPH_SOCK, M_WAITOK | M_ZERO); if (priv == NULL) return (ENOMEM); /* Setup protocol control block */ if ((error = ng_attach_common(so, NG_CONTROL)) != 0) { FREE(priv, M_NETGRAPH_SOCK); return (error); } pcbp = sotongpcb(so); /* Link the pcb the private data. */ priv->ctlsock = pcbp; pcbp->sockdata = priv; priv->refs++; /* Initialize mutex. */ mtx_init(&priv->mtx, "ng_socket", NULL, MTX_DEF); /* Make the generic node components */ if ((error = ng_make_node_common(&typestruct, &priv->node)) != 0) { FREE(priv, M_NETGRAPH_SOCK); ng_detach_common(pcbp, NG_CONTROL); return (error); } /* Link the node and the private data. */ NG_NODE_SET_PRIVATE(priv->node, priv); NG_NODE_REF(priv->node); priv->refs++; return (0); } static int ng_attach_data(struct socket *so) { return(ng_attach_common(so, NG_DATA)); } /* * Set up a socket protocol control block. * This code is shared between control and data sockets. */ static int ng_attach_common(struct socket *so, int type) { struct ngpcb *pcbp; int error; /* Standard socket setup stuff */ error = soreserve(so, ngpdg_sendspace, ngpdg_recvspace); if (error) return (error); /* Allocate the pcb */ MALLOC(pcbp, struct ngpcb *, sizeof(*pcbp), M_PCB, M_WAITOK | M_ZERO); if (pcbp == NULL) return (ENOMEM); pcbp->type = type; /* Link the pcb and the socket */ so->so_pcb = (caddr_t) pcbp; pcbp->ng_socket = so; /* Add the socket to linked list */ mtx_lock(&ngsocketlist_mtx); LIST_INSERT_HEAD(&ngsocklist, pcbp, socks); mtx_unlock(&ngsocketlist_mtx); return (0); } /* * Disassociate the socket from it's protocol specific * partner. If it's attached to a node's private data structure, * then unlink from that too. If we were the last socket attached to it, * then shut down the entire node. Shared code for control and data sockets. */ static void ng_detach_common(struct ngpcb *pcbp, int which) { struct ngsock *priv = pcbp->sockdata; if (priv != NULL) { mtx_lock(&priv->mtx); switch (which) { case NG_CONTROL: priv->ctlsock = NULL; break; case NG_DATA: priv->datasock = NULL; break; default: panic(__func__); } pcbp->sockdata = NULL; ng_socket_free_priv(priv); } pcbp->ng_socket->so_pcb = NULL; mtx_lock(&ngsocketlist_mtx); LIST_REMOVE(pcbp, socks); mtx_unlock(&ngsocketlist_mtx); FREE(pcbp, M_PCB); } /* * Remove a reference from node private data. */ static void ng_socket_free_priv(struct ngsock *priv) { mtx_assert(&priv->mtx, MA_OWNED); priv->refs--; if (priv->refs == 0) { mtx_destroy(&priv->mtx); FREE(priv, M_NETGRAPH_SOCK); return; } if ((priv->refs == 1) && (priv->node != NULL)) { node_p node = priv->node; priv->node = NULL; mtx_unlock(&priv->mtx); NG_NODE_UNREF(node); ng_rmnode_self(node); } else mtx_unlock(&priv->mtx); } #ifdef NOTYET /* * File descriptors can be passed into an AF_NETGRAPH socket. * Note, that file descriptors cannot be passed OUT. * Only character device descriptors are accepted. * Character devices are useful to connect a graph to a device, * which after all is the purpose of this whole system. */ static int ng_internalize(struct mbuf *control, struct thread *td) { const struct cmsghdr *cm = mtod(control, const struct cmsghdr *); struct file *fp; struct vnode *vn; int oldfds; int fd; if (cm->cmsg_type != SCM_RIGHTS || cm->cmsg_level != SOL_SOCKET || cm->cmsg_len != control->m_len) { TRAP_ERROR; return (EINVAL); } /* Check there is only one FD. XXX what would more than one signify? */ oldfds = ((caddr_t)cm + cm->cmsg_len - (caddr_t)data) / sizeof (int); if (oldfds != 1) { TRAP_ERROR; return (EINVAL); } /* Check that the FD given is legit. and change it to a pointer to a * struct file. */ fd = CMSG_DATA(cm); if ((error = fget(td, fd, &fp)) != 0) return (error); /* Depending on what kind of resource it is, act differently. For * devices, we treat it as a file. For an AF_NETGRAPH socket, * shortcut straight to the node. */ switch (fp->f_type) { case DTYPE_VNODE: vn = fp->f_data; if (vn && (vn->v_type == VCHR)) { /* for a VCHR, actually reference the FILE */ fp->f_count++; /* XXX then what :) */ /* how to pass on to other modules? */ } else { fdrop(fp, td); TRAP_ERROR; return (EINVAL); } break; default: fdrop(fp, td); TRAP_ERROR; return (EINVAL); } fdrop(fp, td); return (0); } #endif /* NOTYET */ /* * Connect the data socket to a named control socket node. */ static int ng_connect_data(struct sockaddr *nam, struct ngpcb *pcbp) { struct sockaddr_ng *sap; node_p farnode; struct ngsock *priv; int error; item_p item; /* If we are already connected, don't do it again */ if (pcbp->sockdata != NULL) return (EISCONN); /* Find the target (victim) and check it doesn't already have a data * socket. Also check it is a 'socket' type node. * Use ng_package_data() and address_path() to do this. */ sap = (struct sockaddr_ng *) nam; /* The item will hold the node reference */ item = ng_package_data(NULL, NG_WAITOK); if (item == NULL) { return (ENOMEM); } if ((error = ng_address_path(NULL, item, sap->sg_data, 0))) return (error); /* item is freed on failure */ /* * Extract node from item and free item. Remember we now have * a reference on the node. The item holds it for us. * when we free the item we release the reference. */ farnode = item->el_dest; /* shortcut */ if (strcmp(farnode->nd_type->name, NG_SOCKET_NODE_TYPE) != 0) { NG_FREE_ITEM(item); /* drop the reference to the node */ return (EINVAL); } priv = NG_NODE_PRIVATE(farnode); if (priv->datasock != NULL) { NG_FREE_ITEM(item); /* drop the reference to the node */ return (EADDRINUSE); } /* * Link the PCB and the private data struct. and note the extra * reference. Drop the extra reference on the node. */ mtx_lock(&priv->mtx); priv->datasock = pcbp; pcbp->sockdata = priv; priv->refs++; mtx_unlock(&priv->mtx); NG_FREE_ITEM(item); /* drop the reference to the node */ return (0); } /* * Binding a socket means giving the corresponding node a name */ static int ng_bind(struct sockaddr *nam, struct ngpcb *pcbp) { struct ngsock *const priv = pcbp->sockdata; struct sockaddr_ng *const sap = (struct sockaddr_ng *) nam; if (priv == NULL) { TRAP_ERROR; return (EINVAL); } if ((sap->sg_len < 4) || (sap->sg_len > (NG_NODESIZ + 2)) || (sap->sg_data[0] == '\0') || (sap->sg_data[sap->sg_len - 3] != '\0')) { TRAP_ERROR; return (EINVAL); } return (ng_name_node(priv->node, sap->sg_data)); } /* * Take a message and pass it up to the control socket associated * with the node. */ static int ship_msg(struct ngpcb *pcbp, struct ng_mesg *msg, struct sockaddr_ng *addr) { struct socket *const so = pcbp->ng_socket; struct mbuf *mdata; int msglen; int error = 0; /* Copy the message itself into an mbuf chain */ msglen = sizeof(struct ng_mesg) + msg->header.arglen; mdata = m_devget((caddr_t) msg, msglen, 0, NULL, NULL); /* Here we free the message, as we are the end of the line. * We need to do that regardless of whether we got mbufs. */ NG_FREE_MSG(msg); if (mdata == NULL) { TRAP_ERROR; return (ENOBUFS); } /* Send it up to the socket */ if (sbappendaddr(&so->so_rcv, (struct sockaddr *) addr, mdata, NULL) == 0) { TRAP_ERROR; m_freem(mdata); error = so->so_error = ENOBUFS; } sorwakeup(so); return (error); } /*************************************************************** Netgraph node ***************************************************************/ /* * You can only create new nodes from the socket end of things. */ static int ngs_constructor(node_p nodep) { return (EINVAL); } /* * We allow any hook to be connected to the node. * There is no per-hook private information though. */ static int ngs_newhook(node_p node, hook_p hook, const char *name) { NG_HOOK_SET_PRIVATE(hook, NG_NODE_PRIVATE(node)); return (0); } /* * if only one hook, allow read(2) and write(2) to work. */ static int ngs_connect(hook_p hook) { node_p node = NG_HOOK_NODE(hook); struct ngsock *priv = NG_NODE_PRIVATE(node); if ((priv->datasock) && (priv->datasock->ng_socket)) { if (NG_NODE_NUMHOOKS(node) == 1) { priv->datasock->ng_socket->so_state |= SS_ISCONNECTED; } else { priv->datasock->ng_socket->so_state &= ~SS_ISCONNECTED; } } return (0); } /* * Incoming messages get passed up to the control socket. * Unless they are for us specifically (socket_type) */ static int ngs_rcvmsg(node_p node, item_p item, hook_p lasthook) { struct ngsock *const priv = NG_NODE_PRIVATE(node); struct ngpcb *const pcbp = priv->ctlsock; struct sockaddr_ng *addr; int addrlen; int error = 0; struct ng_mesg *msg; ng_ID_t retaddr = NGI_RETADDR(item); char retabuf[32]; NGI_GET_MSG(item, msg); NG_FREE_ITEM(item); /* we have all we need */ /* Only allow mesgs to be passed if we have the control socket. * Data sockets can only support the generic messages. */ if (pcbp == NULL) { TRAP_ERROR; return (EINVAL); } #ifdef TRACE_MESSAGES printf("[%x]:---------->[socket]: c=<%d>cmd=%x(%s) f=%x #%d\n", retaddr, msg->header.typecookie, msg->header.cmd, msg->header.cmdstr, msg->header.flags, msg->header.token); #endif if (msg->header.typecookie == NGM_SOCKET_COOKIE) { switch (msg->header.cmd) { case NGM_SOCK_CMD_NOLINGER: priv->flags |= NGS_FLAG_NOLINGER; break; case NGM_SOCK_CMD_LINGER: priv->flags &= ~NGS_FLAG_NOLINGER; break; default: error = EINVAL; /* unknown command */ } /* Free the message and return */ NG_FREE_MSG(msg); return(error); } /* Get the return address into a sockaddr */ sprintf(retabuf,"[%x]:", retaddr); addrlen = strlen(retabuf); MALLOC(addr, struct sockaddr_ng *, addrlen + 4, M_NETGRAPH_PATH, M_NOWAIT); if (addr == NULL) { TRAP_ERROR; return (ENOMEM); } addr->sg_len = addrlen + 3; addr->sg_family = AF_NETGRAPH; bcopy(retabuf, addr->sg_data, addrlen); addr->sg_data[addrlen] = '\0'; /* Send it up */ error = ship_msg(pcbp, msg, addr); FREE(addr, M_NETGRAPH_PATH); return (error); } /* * Receive data on a hook */ static int ngs_rcvdata(hook_p hook, item_p item) { struct ngsock *const priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct ngpcb *const pcbp = priv->datasock; struct socket *so; struct sockaddr_ng *addr; char *addrbuf[NG_HOOKSIZ + 4]; int addrlen; struct mbuf *m; NGI_GET_M(item, m); NG_FREE_ITEM(item); /* If there is no data socket, black-hole it */ if (pcbp == NULL) { NG_FREE_M(m); return (0); } so = pcbp->ng_socket; /* Get the return address into a sockaddr. */ addrlen = strlen(NG_HOOK_NAME(hook)); /* <= NG_HOOKSIZ - 1 */ addr = (struct sockaddr_ng *) addrbuf; addr->sg_len = addrlen + 3; addr->sg_family = AF_NETGRAPH; bcopy(NG_HOOK_NAME(hook), addr->sg_data, addrlen); addr->sg_data[addrlen] = '\0'; /* Try to tell the socket which hook it came in on */ if (sbappendaddr(&so->so_rcv, (struct sockaddr *) addr, m, NULL) == 0) { m_freem(m); TRAP_ERROR; return (ENOBUFS); } sorwakeup(so); return (0); } /* * Hook disconnection * * For this type, removal of the last link destroys the node * if the NOLINGER flag is set. */ static int ngs_disconnect(hook_p hook) { node_p node = NG_HOOK_NODE(hook); struct ngsock *const priv = NG_NODE_PRIVATE(node); if ((priv->datasock) && (priv->datasock->ng_socket)) { if (NG_NODE_NUMHOOKS(node) == 1) { priv->datasock->ng_socket->so_state |= SS_ISCONNECTED; } else { priv->datasock->ng_socket->so_state &= ~SS_ISCONNECTED; } } if ((priv->flags & NGS_FLAG_NOLINGER ) && (NG_NODE_NUMHOOKS(node) == 0) && (NG_NODE_IS_VALID(node))) { ng_rmnode_self(node); } return (0); } /* * Do local shutdown processing. * In this case, that involves making sure the socket * knows we should be shutting down. */ static int ngs_shutdown(node_p node) { struct ngsock *const priv = NG_NODE_PRIVATE(node); struct ngpcb *const dpcbp = priv->datasock; struct ngpcb *const pcbp = priv->ctlsock; if (dpcbp != NULL) soisdisconnected(dpcbp->ng_socket); if (pcbp != NULL) soisdisconnected(pcbp->ng_socket); mtx_lock(&priv->mtx); priv->node = NULL; NG_NODE_SET_PRIVATE(node, NULL); ng_socket_free_priv(priv); NG_NODE_UNREF(node); return (0); } static void ng_socket_item_applied(void *context, int error) { struct ngsock *const priv = (struct ngsock *)context; mtx_lock(&priv->mtx); priv->error = error; wakeup(priv); mtx_unlock(&priv->mtx); } static int dummy_disconnect(struct socket *so) { return (0); } /* * Control and data socket type descriptors */ static struct pr_usrreqs ngc_usrreqs = { .pru_abort = NULL, .pru_attach = ngc_attach, .pru_bind = ngc_bind, .pru_connect = ngc_connect, .pru_detach = ngc_detach, .pru_disconnect = dummy_disconnect, .pru_peeraddr = NULL, .pru_send = ngc_send, .pru_shutdown = NULL, .pru_sockaddr = ng_setsockaddr, }; static struct pr_usrreqs ngd_usrreqs = { .pru_abort = NULL, .pru_attach = ngd_attach, .pru_bind = NULL, .pru_connect = ngd_connect, .pru_detach = ngd_detach, .pru_disconnect = dummy_disconnect, .pru_peeraddr = NULL, .pru_send = ngd_send, .pru_shutdown = NULL, .pru_sockaddr = ng_setsockaddr, }; /* * Definitions of protocols supported in the NETGRAPH domain. */ extern struct domain ngdomain; /* stop compiler warnings */ static struct protosw ngsw[] = { { .pr_type = SOCK_DGRAM, .pr_domain = &ngdomain, .pr_protocol = NG_CONTROL, .pr_flags = PR_ATOMIC | PR_ADDR /* | PR_RIGHTS */, .pr_usrreqs = &ngc_usrreqs }, { .pr_type = SOCK_DGRAM, .pr_domain = &ngdomain, .pr_protocol = NG_DATA, .pr_flags = PR_ATOMIC | PR_ADDR, .pr_usrreqs = &ngd_usrreqs } }; struct domain ngdomain = { .dom_family = AF_NETGRAPH, .dom_name = "netgraph", .dom_protosw = ngsw, .dom_protoswNPROTOSW = &ngsw[sizeof(ngsw) / sizeof(ngsw[0])] }; /* * Handle loading and unloading for this node type * This is to handle auxiliary linkages (e.g protocol domain addition). */ static int ngs_mod_event(module_t mod, int event, void *data) { int error = 0; switch (event) { case MOD_LOAD: mtx_init(&ngsocketlist_mtx, "ng_socketlist", NULL, MTX_DEF); /* Register protocol domain */ net_add_domain(&ngdomain); break; case MOD_UNLOAD: /* Insure there are no open netgraph sockets */ if (!LIST_EMPTY(&ngsocklist)) { error = EBUSY; break; } #ifdef NOTYET if ((LIST_EMPTY(&ngsocklist)) && (typestruct.refs == 0)) { /* Unregister protocol domain XXX can't do this yet.. */ if ((error = net_rm_domain(&ngdomain)) != 0) break; mtx_destroy(&ngsocketlist_mtx); } else #endif error = EBUSY; break; default: error = EOPNOTSUPP; break; } return (error); } SYSCTL_INT(_net_graph, OID_AUTO, family, CTLFLAG_RD, 0, AF_NETGRAPH, ""); SYSCTL_NODE(_net_graph, OID_AUTO, data, CTLFLAG_RW, 0, "DATA"); SYSCTL_INT(_net_graph_data, OID_AUTO, proto, CTLFLAG_RD, 0, NG_DATA, ""); SYSCTL_NODE(_net_graph, OID_AUTO, control, CTLFLAG_RW, 0, "CONTROL"); SYSCTL_INT(_net_graph_control, OID_AUTO, proto, CTLFLAG_RD, 0, NG_CONTROL, ""); diff --git a/sys/netinet/ip_divert.c b/sys/netinet/ip_divert.c index b3b1a7c669a8..bb31630d52de 100644 --- a/sys/netinet/ip_divert.c +++ b/sys/netinet/ip_divert.c @@ -1,726 +1,725 @@ /*- * Copyright (c) 1982, 1986, 1988, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #if !defined(KLD_MODULE) #include "opt_inet.h" #include "opt_ipfw.h" #include "opt_mac.h" #ifndef INET #error "IPDIVERT requires INET." #endif #ifndef IPFIREWALL #error "IPDIVERT requires IPFIREWALL" #endif #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Divert sockets */ /* * Allocate enough space to hold a full IP packet */ #define DIVSNDQ (65536 + 100) #define DIVRCVQ (65536 + 100) /* * Divert sockets work in conjunction with ipfw, see the divert(4) * manpage for features. * Internally, packets selected by ipfw in ip_input() or ip_output(), * and never diverted before, are passed to the input queue of the * divert socket with a given 'divert_port' number (as specified in * the matching ipfw rule), and they are tagged with a 16 bit cookie * (representing the rule number of the matching ipfw rule), which * is passed to process reading from the socket. * * Packets written to the divert socket are again tagged with a cookie * (usually the same as above) and a destination address. * If the destination address is INADDR_ANY then the packet is * treated as outgoing and sent to ip_output(), otherwise it is * treated as incoming and sent to ip_input(). * In both cases, the packet is tagged with the cookie. * * On reinjection, processing in ip_input() and ip_output() * will be exactly the same as for the original packet, except that * ipfw processing will start at the rule number after the one * written in the cookie (so, tagging a packet with a cookie of 0 * will cause it to be effectively considered as a standard packet). */ /* Internal variables. */ static struct inpcbhead divcb; static struct inpcbinfo divcbinfo; static u_long div_sendspace = DIVSNDQ; /* XXX sysctl ? */ static u_long div_recvspace = DIVRCVQ; /* XXX sysctl ? */ /* * Initialize divert connection block queue. */ void div_init(void) { INP_INFO_LOCK_INIT(&divcbinfo, "div"); LIST_INIT(&divcb); divcbinfo.listhead = &divcb; /* * XXX We don't use the hash list for divert IP, but it's easier * to allocate a one entry hash list than it is to check all * over the place for hashbase == NULL. */ divcbinfo.hashbase = hashinit(1, M_PCB, &divcbinfo.hashmask); divcbinfo.porthashbase = hashinit(1, M_PCB, &divcbinfo.porthashmask); divcbinfo.ipi_zone = uma_zcreate("divcb", sizeof(struct inpcb), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_NOFREE); uma_zone_set_max(divcbinfo.ipi_zone, maxsockets); } /* * IPPROTO_DIVERT is not in the real IP protocol number space; this * function should never be called. Just in case, drop any packets. */ void div_input(struct mbuf *m, int off) { ipstat.ips_noproto++; m_freem(m); } /* * Divert a packet by passing it up to the divert socket at port 'port'. * * Setup generic address and protocol structures for div_input routine, * then pass them along with mbuf chain. */ static void divert_packet(struct mbuf *m, int incoming) { struct ip *ip; struct inpcb *inp; struct socket *sa; u_int16_t nport; struct sockaddr_in divsrc; struct m_tag *mtag; mtag = m_tag_find(m, PACKET_TAG_DIVERT, NULL); if (mtag == NULL) { printf("%s: no divert tag\n", __func__); m_freem(m); return; } /* Assure header */ if (m->m_len < sizeof(struct ip) && (m = m_pullup(m, sizeof(struct ip))) == 0) return; ip = mtod(m, struct ip *); /* Delayed checksums are currently not compatible with divert. */ if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA) { ip->ip_len = ntohs(ip->ip_len); in_delayed_cksum(m); m->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA; ip->ip_len = htons(ip->ip_len); } /* * Record receive interface address, if any. * But only for incoming packets. */ bzero(&divsrc, sizeof(divsrc)); divsrc.sin_len = sizeof(divsrc); divsrc.sin_family = AF_INET; divsrc.sin_port = divert_cookie(mtag); /* record matching rule */ if (incoming) { struct ifaddr *ifa; /* Sanity check */ M_ASSERTPKTHDR(m); /* Find IP address for receive interface */ TAILQ_FOREACH(ifa, &m->m_pkthdr.rcvif->if_addrhead, ifa_link) { if (ifa->ifa_addr == NULL) continue; if (ifa->ifa_addr->sa_family != AF_INET) continue; divsrc.sin_addr = ((struct sockaddr_in *) ifa->ifa_addr)->sin_addr; break; } } /* * Record the incoming interface name whenever we have one. */ if (m->m_pkthdr.rcvif) { /* * Hide the actual interface name in there in the * sin_zero array. XXX This needs to be moved to a * different sockaddr type for divert, e.g. * sockaddr_div with multiple fields like * sockaddr_dl. Presently we have only 7 bytes * but that will do for now as most interfaces * are 4 or less + 2 or less bytes for unit. * There is probably a faster way of doing this, * possibly taking it from the sockaddr_dl on the iface. * This solves the problem of a P2P link and a LAN interface * having the same address, which can result in the wrong * interface being assigned to the packet when fed back * into the divert socket. Theoretically if the daemon saves * and re-uses the sockaddr_in as suggested in the man pages, * this iface name will come along for the ride. * (see div_output for the other half of this.) */ strlcpy(divsrc.sin_zero, m->m_pkthdr.rcvif->if_xname, sizeof(divsrc.sin_zero)); } /* Put packet on socket queue, if any */ sa = NULL; nport = htons((u_int16_t)divert_info(mtag)); INP_INFO_RLOCK(&divcbinfo); LIST_FOREACH(inp, &divcb, inp_list) { INP_LOCK(inp); /* XXX why does only one socket match? */ if (inp->inp_lport == nport) { sa = inp->inp_socket; SOCKBUF_LOCK(&sa->so_rcv); if (sbappendaddr_locked(&sa->so_rcv, (struct sockaddr *)&divsrc, m, (struct mbuf *)0) == 0) { SOCKBUF_UNLOCK(&sa->so_rcv); sa = NULL; /* force mbuf reclaim below */ } else sorwakeup_locked(sa); INP_UNLOCK(inp); break; } INP_UNLOCK(inp); } INP_INFO_RUNLOCK(&divcbinfo); if (sa == NULL) { m_freem(m); ipstat.ips_noproto++; ipstat.ips_delivered--; } } /* * Deliver packet back into the IP processing machinery. * * If no address specified, or address is 0.0.0.0, send to ip_output(); * otherwise, send to ip_input() and mark as having been received on * the interface with that address. */ static int div_output(struct socket *so, struct mbuf *m, struct sockaddr_in *sin, struct mbuf *control) { struct m_tag *mtag; struct divert_tag *dt; int error = 0; /* * An mbuf may hasn't come from userland, but we pretend * that it has. */ m->m_pkthdr.rcvif = NULL; m->m_nextpkt = NULL; if (control) m_freem(control); /* XXX */ if ((mtag = m_tag_find(m, PACKET_TAG_DIVERT, NULL)) == NULL) { mtag = m_tag_get(PACKET_TAG_DIVERT, sizeof(struct divert_tag), M_NOWAIT | M_ZERO); if (mtag == NULL) { error = ENOBUFS; goto cantsend; } dt = (struct divert_tag *)(mtag+1); m_tag_prepend(m, mtag); } else dt = (struct divert_tag *)(mtag+1); /* Loopback avoidance and state recovery */ if (sin) { int i; dt->cookie = sin->sin_port; /* * Find receive interface with the given name, stuffed * (if it exists) in the sin_zero[] field. * The name is user supplied data so don't trust its size * or that it is zero terminated. */ for (i = 0; i < sizeof(sin->sin_zero) && sin->sin_zero[i]; i++) ; if ( i > 0 && i < sizeof(sin->sin_zero)) m->m_pkthdr.rcvif = ifunit(sin->sin_zero); } /* Reinject packet into the system as incoming or outgoing */ if (!sin || sin->sin_addr.s_addr == 0) { struct ip *const ip = mtod(m, struct ip *); struct inpcb *inp; dt->info |= IP_FW_DIVERT_OUTPUT_FLAG; INP_INFO_WLOCK(&divcbinfo); inp = sotoinpcb(so); INP_LOCK(inp); /* * Don't allow both user specified and setsockopt options, * and don't allow packet length sizes that will crash */ if (((ip->ip_hl != (sizeof (*ip) >> 2)) && inp->inp_options) || ((u_short)ntohs(ip->ip_len) > m->m_pkthdr.len)) { error = EINVAL; m_freem(m); } else { /* Convert fields to host order for ip_output() */ ip->ip_len = ntohs(ip->ip_len); ip->ip_off = ntohs(ip->ip_off); /* Send packet to output processing */ ipstat.ips_rawout++; /* XXX */ #ifdef MAC mac_create_mbuf_from_inpcb(inp, m); #endif error = ip_output(m, inp->inp_options, NULL, ((so->so_options & SO_DONTROUTE) ? IP_ROUTETOIF : 0) | IP_ALLOWBROADCAST | IP_RAWOUTPUT, inp->inp_moptions, NULL); } INP_UNLOCK(inp); INP_INFO_WUNLOCK(&divcbinfo); } else { dt->info |= IP_FW_DIVERT_LOOPBACK_FLAG; if (m->m_pkthdr.rcvif == NULL) { /* * No luck with the name, check by IP address. * Clear the port and the ifname to make sure * there are no distractions for ifa_ifwithaddr. */ struct ifaddr *ifa; bzero(sin->sin_zero, sizeof(sin->sin_zero)); sin->sin_port = 0; ifa = ifa_ifwithaddr((struct sockaddr *) sin); if (ifa == NULL) { error = EADDRNOTAVAIL; goto cantsend; } m->m_pkthdr.rcvif = ifa->ifa_ifp; } #ifdef MAC SOCK_LOCK(so); mac_create_mbuf_from_socket(so, m); SOCK_UNLOCK(so); #endif /* Send packet to input processing */ ip_input(m); } return error; cantsend: m_freem(m); return error; } static int div_attach(struct socket *so, int proto, struct thread *td) { struct inpcb *inp; int error; INP_INFO_WLOCK(&divcbinfo); inp = sotoinpcb(so); if (inp != 0) { INP_INFO_WUNLOCK(&divcbinfo); return EINVAL; } if (td && (error = suser(td)) != 0) { INP_INFO_WUNLOCK(&divcbinfo); return error; } error = soreserve(so, div_sendspace, div_recvspace); if (error) { INP_INFO_WUNLOCK(&divcbinfo); return error; } error = in_pcballoc(so, &divcbinfo, "divinp"); if (error) { INP_INFO_WUNLOCK(&divcbinfo); return error; } inp = (struct inpcb *)so->so_pcb; INP_LOCK(inp); INP_INFO_WUNLOCK(&divcbinfo); inp->inp_ip_p = proto; inp->inp_vflag |= INP_IPV4; inp->inp_flags |= INP_HDRINCL; INP_UNLOCK(inp); return 0; } -static int +static void div_detach(struct socket *so) { struct inpcb *inp; INP_INFO_WLOCK(&divcbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_WUNLOCK(&divcbinfo); - return EINVAL; + return; } INP_LOCK(inp); in_pcbdetach(inp); INP_INFO_WUNLOCK(&divcbinfo); - return 0; } static int div_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { struct inpcb *inp; int error; INP_INFO_WLOCK(&divcbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_WUNLOCK(&divcbinfo); return EINVAL; } /* in_pcbbind assumes that nam is a sockaddr_in * and in_pcbbind requires a valid address. Since divert * sockets don't we need to make sure the address is * filled in properly. * XXX -- divert should not be abusing in_pcbind * and should probably have its own family. */ if (nam->sa_family != AF_INET) error = EAFNOSUPPORT; else { ((struct sockaddr_in *)nam)->sin_addr.s_addr = INADDR_ANY; INP_LOCK(inp); error = in_pcbbind(inp, nam, td->td_ucred); INP_UNLOCK(inp); } INP_INFO_WUNLOCK(&divcbinfo); return error; } static int div_shutdown(struct socket *so) { struct inpcb *inp; INP_INFO_RLOCK(&divcbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_RUNLOCK(&divcbinfo); return EINVAL; } INP_LOCK(inp); INP_INFO_RUNLOCK(&divcbinfo); socantsendmore(so); INP_UNLOCK(inp); return 0; } static int div_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, struct mbuf *control, struct thread *td) { /* Packet must have a header (but that's about it) */ if (m->m_len < sizeof (struct ip) && (m = m_pullup(m, sizeof (struct ip))) == 0) { ipstat.ips_toosmall++; m_freem(m); return EINVAL; } /* Send packet */ return div_output(so, m, (struct sockaddr_in *)nam, control); } void div_ctlinput(int cmd, struct sockaddr *sa, void *vip) { struct in_addr faddr; faddr = ((struct sockaddr_in *)sa)->sin_addr; if (sa->sa_family != AF_INET || faddr.s_addr == INADDR_ANY) return; if (PRC_IS_REDIRECT(cmd)) return; } static int div_pcblist(SYSCTL_HANDLER_ARGS) { int error, i, n; struct inpcb *inp, **inp_list; inp_gen_t gencnt; struct xinpgen xig; /* * The process of preparing the TCB list is too time-consuming and * resource-intensive to repeat twice on every request. */ if (req->oldptr == 0) { n = divcbinfo.ipi_count; req->oldidx = 2 * (sizeof xig) + (n + n/8) * sizeof(struct xinpcb); return 0; } if (req->newptr != 0) return EPERM; /* * OK, now we're committed to doing something. */ INP_INFO_RLOCK(&divcbinfo); gencnt = divcbinfo.ipi_gencnt; n = divcbinfo.ipi_count; INP_INFO_RUNLOCK(&divcbinfo); error = sysctl_wire_old_buffer(req, 2 * sizeof(xig) + n*sizeof(struct xinpcb)); if (error != 0) return (error); xig.xig_len = sizeof xig; xig.xig_count = n; xig.xig_gen = gencnt; xig.xig_sogen = so_gencnt; error = SYSCTL_OUT(req, &xig, sizeof xig); if (error) return error; inp_list = malloc(n * sizeof *inp_list, M_TEMP, M_WAITOK); if (inp_list == 0) return ENOMEM; INP_INFO_RLOCK(&divcbinfo); for (inp = LIST_FIRST(divcbinfo.listhead), i = 0; inp && i < n; inp = LIST_NEXT(inp, inp_list)) { INP_LOCK(inp); if (inp->inp_gencnt <= gencnt && cr_canseesocket(req->td->td_ucred, inp->inp_socket) == 0) inp_list[i++] = inp; INP_UNLOCK(inp); } INP_INFO_RUNLOCK(&divcbinfo); n = i; error = 0; for (i = 0; i < n; i++) { inp = inp_list[i]; if (inp->inp_gencnt <= gencnt) { struct xinpcb xi; bzero(&xi, sizeof(xi)); xi.xi_len = sizeof xi; /* XXX should avoid extra copy */ bcopy(inp, &xi.xi_inp, sizeof *inp); if (inp->inp_socket) sotoxsocket(inp->inp_socket, &xi.xi_socket); error = SYSCTL_OUT(req, &xi, sizeof xi); } } if (!error) { /* * Give the user an updated idea of our state. * If the generation differs from what we told * her before, she knows that something happened * while we were processing this request, and it * might be necessary to retry. */ INP_INFO_RLOCK(&divcbinfo); xig.xig_gen = divcbinfo.ipi_gencnt; xig.xig_sogen = so_gencnt; xig.xig_count = divcbinfo.ipi_count; INP_INFO_RUNLOCK(&divcbinfo); error = SYSCTL_OUT(req, &xig, sizeof xig); } free(inp_list, M_TEMP); return error; } /* * This is the wrapper function for in_setsockaddr. We just pass down * the pcbinfo for in_setpeeraddr to lock. */ static int div_sockaddr(struct socket *so, struct sockaddr **nam) { return (in_setsockaddr(so, nam, &divcbinfo)); } /* * This is the wrapper function for in_setpeeraddr. We just pass down * the pcbinfo for in_setpeeraddr to lock. */ static int div_peeraddr(struct socket *so, struct sockaddr **nam) { return (in_setpeeraddr(so, nam, &divcbinfo)); } #ifdef SYSCTL_NODE SYSCTL_NODE(_net_inet, IPPROTO_DIVERT, divert, CTLFLAG_RW, 0, "IPDIVERT"); SYSCTL_PROC(_net_inet_divert, OID_AUTO, pcblist, CTLFLAG_RD, 0, 0, div_pcblist, "S,xinpcb", "List of active divert sockets"); #endif struct pr_usrreqs div_usrreqs = { .pru_attach = div_attach, .pru_bind = div_bind, .pru_control = in_control, .pru_detach = div_detach, .pru_peeraddr = div_peeraddr, .pru_send = div_send, .pru_shutdown = div_shutdown, .pru_sockaddr = div_sockaddr, .pru_sosetlabel = in_pcbsosetlabel }; struct protosw div_protosw = { .pr_type = SOCK_RAW, .pr_protocol = IPPROTO_DIVERT, .pr_flags = PR_ATOMIC|PR_ADDR, .pr_input = div_input, .pr_ctlinput = div_ctlinput, .pr_ctloutput = ip_ctloutput, .pr_init = div_init, .pr_usrreqs = &div_usrreqs }; static int div_modevent(module_t mod, int type, void *unused) { int err = 0; int n; switch (type) { case MOD_LOAD: /* * Protocol will be initialized by pf_proto_register(). * We don't have to register ip_protox because we are not * a true IP protocol that goes over the wire. */ err = pf_proto_register(PF_INET, &div_protosw); ip_divert_ptr = divert_packet; break; case MOD_QUIESCE: /* * IPDIVERT may normally not be unloaded because of the * potential race conditions. Tell kldunload we can't be * unloaded unless the unload is forced. */ err = EPERM; break; case MOD_UNLOAD: /* * Forced unload. * * Module ipdivert can only be unloaded if no sockets are * connected. Maybe this can be changed later to forcefully * disconnect any open sockets. * * XXXRW: Note that there is a slight race here, as a new * socket open request could be spinning on the lock and then * we destroy the lock. */ INP_INFO_WLOCK(&divcbinfo); n = divcbinfo.ipi_count; if (n != 0) { err = EBUSY; INP_INFO_WUNLOCK(&divcbinfo); break; } ip_divert_ptr = NULL; err = pf_proto_unregister(PF_INET, IPPROTO_DIVERT, SOCK_RAW); INP_INFO_WUNLOCK(&divcbinfo); INP_INFO_LOCK_DESTROY(&divcbinfo); uma_zdestroy(divcbinfo.ipi_zone); break; default: err = EOPNOTSUPP; break; } return err; } static moduledata_t ipdivertmod = { "ipdivert", div_modevent, 0 }; DECLARE_MODULE(ipdivert, ipdivertmod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY); MODULE_DEPEND(dummynet, ipfw, 2, 2, 2); MODULE_VERSION(ipdivert, 1); diff --git a/sys/netinet/raw_ip.c b/sys/netinet/raw_ip.c index b39e6930fb13..b6cf708e3561 100644 --- a/sys/netinet/raw_ip.c +++ b/sys/netinet/raw_ip.c @@ -1,922 +1,921 @@ /*- * Copyright (c) 1982, 1986, 1988, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)raw_ip.c 8.7 (Berkeley) 5/15/95 * $FreeBSD$ */ #include "opt_inet6.h" #include "opt_ipsec.h" #include "opt_mac.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef FAST_IPSEC #include #endif /*FAST_IPSEC*/ #ifdef IPSEC #include #endif /*IPSEC*/ struct inpcbhead ripcb; struct inpcbinfo ripcbinfo; /* control hooks for ipfw and dummynet */ ip_fw_ctl_t *ip_fw_ctl_ptr = NULL; ip_dn_ctl_t *ip_dn_ctl_ptr = NULL; /* * hooks for multicast routing. They all default to NULL, * so leave them not initialized and rely on BSS being set to 0. */ /* The socket used to communicate with the multicast routing daemon. */ struct socket *ip_mrouter; /* The various mrouter and rsvp functions */ int (*ip_mrouter_set)(struct socket *, struct sockopt *); int (*ip_mrouter_get)(struct socket *, struct sockopt *); int (*ip_mrouter_done)(void); int (*ip_mforward)(struct ip *, struct ifnet *, struct mbuf *, struct ip_moptions *); int (*mrt_ioctl)(int, caddr_t); int (*legal_vif_num)(int); u_long (*ip_mcast_src)(int); void (*rsvp_input_p)(struct mbuf *m, int off); int (*ip_rsvp_vif)(struct socket *, struct sockopt *); void (*ip_rsvp_force_done)(struct socket *); /* * Nominal space allocated to a raw ip socket. */ #define RIPSNDQ 8192 #define RIPRCVQ 8192 /* * Raw interface to IP protocol. */ /* * Initialize raw connection block q. */ void rip_init() { INP_INFO_LOCK_INIT(&ripcbinfo, "rip"); LIST_INIT(&ripcb); ripcbinfo.listhead = &ripcb; /* * XXX We don't use the hash list for raw IP, but it's easier * to allocate a one entry hash list than it is to check all * over the place for hashbase == NULL. */ ripcbinfo.hashbase = hashinit(1, M_PCB, &ripcbinfo.hashmask); ripcbinfo.porthashbase = hashinit(1, M_PCB, &ripcbinfo.porthashmask); ripcbinfo.ipi_zone = uma_zcreate("ripcb", sizeof(struct inpcb), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_NOFREE); uma_zone_set_max(ripcbinfo.ipi_zone, maxsockets); } static struct sockaddr_in ripsrc = { sizeof(ripsrc), AF_INET }; static int raw_append(struct inpcb *last, struct ip *ip, struct mbuf *n) { int policyfail = 0; INP_LOCK_ASSERT(last); #if defined(IPSEC) || defined(FAST_IPSEC) /* check AH/ESP integrity. */ if (ipsec4_in_reject(n, last)) { policyfail = 1; #ifdef IPSEC ipsecstat.in_polvio++; #endif /*IPSEC*/ /* do not inject data to pcb */ } #endif /*IPSEC || FAST_IPSEC*/ #ifdef MAC if (!policyfail && mac_check_inpcb_deliver(last, n) != 0) policyfail = 1; #endif /* Check the minimum TTL for socket. */ if (last->inp_ip_minttl && last->inp_ip_minttl > ip->ip_ttl) policyfail = 1; if (!policyfail) { struct mbuf *opts = NULL; struct socket *so; so = last->inp_socket; if ((last->inp_flags & INP_CONTROLOPTS) || (so->so_options & (SO_TIMESTAMP | SO_BINTIME))) ip_savecontrol(last, &opts, ip, n); SOCKBUF_LOCK(&so->so_rcv); if (sbappendaddr_locked(&so->so_rcv, (struct sockaddr *)&ripsrc, n, opts) == 0) { /* should notify about lost packet */ m_freem(n); if (opts) m_freem(opts); SOCKBUF_UNLOCK(&so->so_rcv); } else sorwakeup_locked(so); } else m_freem(n); return policyfail; } /* * Setup generic address and protocol structures * for raw_input routine, then pass them along with * mbuf chain. */ void rip_input(struct mbuf *m, int off) { struct ip *ip = mtod(m, struct ip *); int proto = ip->ip_p; struct inpcb *inp, *last; INP_INFO_RLOCK(&ripcbinfo); ripsrc.sin_addr = ip->ip_src; last = NULL; LIST_FOREACH(inp, &ripcb, inp_list) { INP_LOCK(inp); if (inp->inp_ip_p && inp->inp_ip_p != proto) { docontinue: INP_UNLOCK(inp); continue; } #ifdef INET6 if ((inp->inp_vflag & INP_IPV4) == 0) goto docontinue; #endif if (inp->inp_laddr.s_addr && inp->inp_laddr.s_addr != ip->ip_dst.s_addr) goto docontinue; if (inp->inp_faddr.s_addr && inp->inp_faddr.s_addr != ip->ip_src.s_addr) goto docontinue; if (jailed(inp->inp_socket->so_cred)) if (htonl(prison_getip(inp->inp_socket->so_cred)) != ip->ip_dst.s_addr) goto docontinue; if (last) { struct mbuf *n; n = m_copy(m, 0, (int)M_COPYALL); if (n != NULL) (void) raw_append(last, ip, n); /* XXX count dropped packet */ INP_UNLOCK(last); } last = inp; } if (last != NULL) { if (raw_append(last, ip, m) != 0) ipstat.ips_delivered--; INP_UNLOCK(last); } else { m_freem(m); ipstat.ips_noproto++; ipstat.ips_delivered--; } INP_INFO_RUNLOCK(&ripcbinfo); } /* * Generate IP header and pass packet to ip_output. * Tack on options user may have setup with control call. */ int rip_output(struct mbuf *m, struct socket *so, u_long dst) { struct ip *ip; int error; struct inpcb *inp = sotoinpcb(so); int flags = ((so->so_options & SO_DONTROUTE) ? IP_ROUTETOIF : 0) | IP_ALLOWBROADCAST; /* * If the user handed us a complete IP packet, use it. * Otherwise, allocate an mbuf for a header and fill it in. */ if ((inp->inp_flags & INP_HDRINCL) == 0) { if (m->m_pkthdr.len + sizeof(struct ip) > IP_MAXPACKET) { m_freem(m); return(EMSGSIZE); } M_PREPEND(m, sizeof(struct ip), M_DONTWAIT); if (m == NULL) return(ENOBUFS); INP_LOCK(inp); ip = mtod(m, struct ip *); ip->ip_tos = inp->inp_ip_tos; if (inp->inp_flags & INP_DONTFRAG) ip->ip_off = IP_DF; else ip->ip_off = 0; ip->ip_p = inp->inp_ip_p; ip->ip_len = m->m_pkthdr.len; if (jailed(inp->inp_socket->so_cred)) ip->ip_src.s_addr = htonl(prison_getip(inp->inp_socket->so_cred)); else ip->ip_src = inp->inp_laddr; ip->ip_dst.s_addr = dst; ip->ip_ttl = inp->inp_ip_ttl; } else { if (m->m_pkthdr.len > IP_MAXPACKET) { m_freem(m); return(EMSGSIZE); } INP_LOCK(inp); ip = mtod(m, struct ip *); if (jailed(inp->inp_socket->so_cred)) { if (ip->ip_src.s_addr != htonl(prison_getip(inp->inp_socket->so_cred))) { INP_UNLOCK(inp); m_freem(m); return (EPERM); } } /* don't allow both user specified and setsockopt options, and don't allow packet length sizes that will crash */ if (((ip->ip_hl != (sizeof (*ip) >> 2)) && inp->inp_options) || (ip->ip_len > m->m_pkthdr.len) || (ip->ip_len < (ip->ip_hl << 2))) { INP_UNLOCK(inp); m_freem(m); return EINVAL; } if (ip->ip_id == 0) ip->ip_id = ip_newid(); /* XXX prevent ip_output from overwriting header fields */ flags |= IP_RAWOUTPUT; ipstat.ips_rawout++; } if (inp->inp_vflag & INP_ONESBCAST) flags |= IP_SENDONES; #ifdef MAC mac_create_mbuf_from_inpcb(inp, m); #endif error = ip_output(m, inp->inp_options, NULL, flags, inp->inp_moptions, inp); INP_UNLOCK(inp); return error; } /* * Raw IP socket option processing. * * IMPORTANT NOTE regarding access control: Traditionally, raw sockets could * only be created by a privileged process, and as such, socket option * operations to manage system properties on any raw socket were allowed to * take place without explicit additional access control checks. However, * raw sockets can now also be created in jail(), and therefore explicit * checks are now required. Likewise, raw sockets can be used by a process * after it gives up privilege, so some caution is required. For options * passed down to the IP layer via ip_ctloutput(), checks are assumed to be * performed in ip_ctloutput() and therefore no check occurs here. * Unilaterally checking suser() here breaks normal IP socket option * operations on raw sockets. * * When adding new socket options here, make sure to add access control * checks here as necessary. */ int rip_ctloutput(struct socket *so, struct sockopt *sopt) { struct inpcb *inp = sotoinpcb(so); int error, optval; if (sopt->sopt_level != IPPROTO_IP) return (EINVAL); error = 0; switch (sopt->sopt_dir) { case SOPT_GET: switch (sopt->sopt_name) { case IP_HDRINCL: optval = inp->inp_flags & INP_HDRINCL; error = sooptcopyout(sopt, &optval, sizeof optval); break; case IP_FW_ADD: /* ADD actually returns the body... */ case IP_FW_GET: case IP_FW_TABLE_GETSIZE: case IP_FW_TABLE_LIST: error = suser(curthread); if (error != 0) return (error); if (ip_fw_ctl_ptr != NULL) error = ip_fw_ctl_ptr(sopt); else error = ENOPROTOOPT; break; case IP_DUMMYNET_GET: error = suser(curthread); if (error != 0) return (error); if (ip_dn_ctl_ptr != NULL) error = ip_dn_ctl_ptr(sopt); else error = ENOPROTOOPT; break ; case MRT_INIT: case MRT_DONE: case MRT_ADD_VIF: case MRT_DEL_VIF: case MRT_ADD_MFC: case MRT_DEL_MFC: case MRT_VERSION: case MRT_ASSERT: case MRT_API_SUPPORT: case MRT_API_CONFIG: case MRT_ADD_BW_UPCALL: case MRT_DEL_BW_UPCALL: error = suser(curthread); if (error != 0) return (error); error = ip_mrouter_get ? ip_mrouter_get(so, sopt) : EOPNOTSUPP; break; default: error = ip_ctloutput(so, sopt); break; } break; case SOPT_SET: switch (sopt->sopt_name) { case IP_HDRINCL: error = sooptcopyin(sopt, &optval, sizeof optval, sizeof optval); if (error) break; if (optval) inp->inp_flags |= INP_HDRINCL; else inp->inp_flags &= ~INP_HDRINCL; break; case IP_FW_ADD: case IP_FW_DEL: case IP_FW_FLUSH: case IP_FW_ZERO: case IP_FW_RESETLOG: case IP_FW_TABLE_ADD: case IP_FW_TABLE_DEL: case IP_FW_TABLE_FLUSH: error = suser(curthread); if (error != 0) return (error); if (ip_fw_ctl_ptr != NULL) error = ip_fw_ctl_ptr(sopt); else error = ENOPROTOOPT; break; case IP_DUMMYNET_CONFIGURE: case IP_DUMMYNET_DEL: case IP_DUMMYNET_FLUSH: error = suser(curthread); if (error != 0) return (error); if (ip_dn_ctl_ptr != NULL) error = ip_dn_ctl_ptr(sopt); else error = ENOPROTOOPT ; break ; case IP_RSVP_ON: error = suser(curthread); if (error != 0) return (error); error = ip_rsvp_init(so); break; case IP_RSVP_OFF: error = suser(curthread); if (error != 0) return (error); error = ip_rsvp_done(); break; case IP_RSVP_VIF_ON: case IP_RSVP_VIF_OFF: error = suser(curthread); if (error != 0) return (error); error = ip_rsvp_vif ? ip_rsvp_vif(so, sopt) : EINVAL; break; case MRT_INIT: case MRT_DONE: case MRT_ADD_VIF: case MRT_DEL_VIF: case MRT_ADD_MFC: case MRT_DEL_MFC: case MRT_VERSION: case MRT_ASSERT: case MRT_API_SUPPORT: case MRT_API_CONFIG: case MRT_ADD_BW_UPCALL: case MRT_DEL_BW_UPCALL: error = suser(curthread); if (error != 0) return (error); error = ip_mrouter_set ? ip_mrouter_set(so, sopt) : EOPNOTSUPP; break; default: error = ip_ctloutput(so, sopt); break; } break; } return (error); } /* * This function exists solely to receive the PRC_IFDOWN messages which * are sent by if_down(). It looks for an ifaddr whose ifa_addr is sa, * and calls in_ifadown() to remove all routes corresponding to that address. * It also receives the PRC_IFUP messages from if_up() and reinstalls the * interface routes. */ void rip_ctlinput(int cmd, struct sockaddr *sa, void *vip) { struct in_ifaddr *ia; struct ifnet *ifp; int err; int flags; switch (cmd) { case PRC_IFDOWN: TAILQ_FOREACH(ia, &in_ifaddrhead, ia_link) { if (ia->ia_ifa.ifa_addr == sa && (ia->ia_flags & IFA_ROUTE)) { /* * in_ifscrub kills the interface route. */ in_ifscrub(ia->ia_ifp, ia); /* * in_ifadown gets rid of all the rest of * the routes. This is not quite the right * thing to do, but at least if we are running * a routing process they will come back. */ in_ifadown(&ia->ia_ifa, 0); break; } } break; case PRC_IFUP: TAILQ_FOREACH(ia, &in_ifaddrhead, ia_link) { if (ia->ia_ifa.ifa_addr == sa) break; } if (ia == 0 || (ia->ia_flags & IFA_ROUTE)) return; flags = RTF_UP; ifp = ia->ia_ifa.ifa_ifp; if ((ifp->if_flags & IFF_LOOPBACK) || (ifp->if_flags & IFF_POINTOPOINT)) flags |= RTF_HOST; err = rtinit(&ia->ia_ifa, RTM_ADD, flags); if (err == 0) ia->ia_flags |= IFA_ROUTE; break; } } u_long rip_sendspace = RIPSNDQ; u_long rip_recvspace = RIPRCVQ; SYSCTL_ULONG(_net_inet_raw, OID_AUTO, maxdgram, CTLFLAG_RW, &rip_sendspace, 0, "Maximum outgoing raw IP datagram size"); SYSCTL_ULONG(_net_inet_raw, OID_AUTO, recvspace, CTLFLAG_RW, &rip_recvspace, 0, "Maximum space for incoming raw IP datagrams"); static int rip_attach(struct socket *so, int proto, struct thread *td) { struct inpcb *inp; int error; /* XXX why not lower? */ INP_INFO_WLOCK(&ripcbinfo); inp = sotoinpcb(so); if (inp) { /* XXX counter, printf */ INP_INFO_WUNLOCK(&ripcbinfo); return EINVAL; } if (jailed(td->td_ucred) && !jail_allow_raw_sockets) { INP_INFO_WUNLOCK(&ripcbinfo); return (EPERM); } if ((error = suser_cred(td->td_ucred, SUSER_ALLOWJAIL)) != 0) { INP_INFO_WUNLOCK(&ripcbinfo); return error; } if (proto >= IPPROTO_MAX || proto < 0) { INP_INFO_WUNLOCK(&ripcbinfo); return EPROTONOSUPPORT; } error = soreserve(so, rip_sendspace, rip_recvspace); if (error) { INP_INFO_WUNLOCK(&ripcbinfo); return error; } error = in_pcballoc(so, &ripcbinfo, "rawinp"); if (error) { INP_INFO_WUNLOCK(&ripcbinfo); return error; } inp = (struct inpcb *)so->so_pcb; INP_LOCK(inp); INP_INFO_WUNLOCK(&ripcbinfo); inp->inp_vflag |= INP_IPV4; inp->inp_ip_p = proto; inp->inp_ip_ttl = ip_defttl; INP_UNLOCK(inp); return 0; } static void rip_pcbdetach(struct socket *so, struct inpcb *inp) { INP_INFO_WLOCK_ASSERT(&ripcbinfo); INP_LOCK_ASSERT(inp); if (so == ip_mrouter && ip_mrouter_done) ip_mrouter_done(); if (ip_rsvp_force_done) ip_rsvp_force_done(so); if (so == ip_rsvpd) ip_rsvp_done(); in_pcbdetach(inp); } -static int +static void rip_detach(struct socket *so) { struct inpcb *inp; INP_INFO_WLOCK(&ripcbinfo); inp = sotoinpcb(so); if (inp == 0) { /* XXX counter, printf */ INP_INFO_WUNLOCK(&ripcbinfo); - return EINVAL; + return; } INP_LOCK(inp); rip_pcbdetach(so, inp); INP_INFO_WUNLOCK(&ripcbinfo); - return 0; } static void rip_abort(struct socket *so) { struct inpcb *inp; INP_INFO_WLOCK(&ripcbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_WUNLOCK(&ripcbinfo); return; /* ??? possible? panic instead? */ } INP_LOCK(inp); soisdisconnected(so); if (so->so_state & SS_NOFDREF) rip_pcbdetach(so, inp); else INP_UNLOCK(inp); INP_INFO_WUNLOCK(&ripcbinfo); } static int rip_disconnect(struct socket *so) { if ((so->so_state & SS_ISCONNECTED) == 0) return ENOTCONN; rip_abort(so); return 0; } static int rip_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { struct sockaddr_in *addr = (struct sockaddr_in *)nam; struct inpcb *inp; if (nam->sa_len != sizeof(*addr)) return EINVAL; if (jailed(td->td_ucred)) { if (addr->sin_addr.s_addr == INADDR_ANY) addr->sin_addr.s_addr = htonl(prison_getip(td->td_ucred)); if (htonl(prison_getip(td->td_ucred)) != addr->sin_addr.s_addr) return (EADDRNOTAVAIL); } if (TAILQ_EMPTY(&ifnet) || (addr->sin_family != AF_INET && addr->sin_family != AF_IMPLINK) || (addr->sin_addr.s_addr && ifa_ifwithaddr((struct sockaddr *)addr) == 0)) return EADDRNOTAVAIL; INP_INFO_WLOCK(&ripcbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_WUNLOCK(&ripcbinfo); return EINVAL; } INP_LOCK(inp); inp->inp_laddr = addr->sin_addr; INP_UNLOCK(inp); INP_INFO_WUNLOCK(&ripcbinfo); return 0; } static int rip_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { struct sockaddr_in *addr = (struct sockaddr_in *)nam; struct inpcb *inp; if (nam->sa_len != sizeof(*addr)) return EINVAL; if (TAILQ_EMPTY(&ifnet)) return EADDRNOTAVAIL; if (addr->sin_family != AF_INET && addr->sin_family != AF_IMPLINK) return EAFNOSUPPORT; INP_INFO_WLOCK(&ripcbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_WUNLOCK(&ripcbinfo); return EINVAL; } INP_LOCK(inp); inp->inp_faddr = addr->sin_addr; soisconnected(so); INP_UNLOCK(inp); INP_INFO_WUNLOCK(&ripcbinfo); return 0; } static int rip_shutdown(struct socket *so) { struct inpcb *inp; INP_INFO_RLOCK(&ripcbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_RUNLOCK(&ripcbinfo); return EINVAL; } INP_LOCK(inp); INP_INFO_RUNLOCK(&ripcbinfo); socantsendmore(so); INP_UNLOCK(inp); return 0; } static int rip_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, struct mbuf *control, struct thread *td) { struct inpcb *inp; u_long dst; int ret; INP_INFO_WLOCK(&ripcbinfo); inp = sotoinpcb(so); if (so->so_state & SS_ISCONNECTED) { if (nam) { INP_INFO_WUNLOCK(&ripcbinfo); m_freem(m); return EISCONN; } dst = inp->inp_faddr.s_addr; } else { if (nam == NULL) { INP_INFO_WUNLOCK(&ripcbinfo); m_freem(m); return ENOTCONN; } dst = ((struct sockaddr_in *)nam)->sin_addr.s_addr; } ret = rip_output(m, so, dst); INP_INFO_WUNLOCK(&ripcbinfo); return ret; } static int rip_pcblist(SYSCTL_HANDLER_ARGS) { int error, i, n; struct inpcb *inp, **inp_list; inp_gen_t gencnt; struct xinpgen xig; /* * The process of preparing the TCB list is too time-consuming and * resource-intensive to repeat twice on every request. */ if (req->oldptr == 0) { n = ripcbinfo.ipi_count; req->oldidx = 2 * (sizeof xig) + (n + n/8) * sizeof(struct xinpcb); return 0; } if (req->newptr != 0) return EPERM; /* * OK, now we're committed to doing something. */ INP_INFO_RLOCK(&ripcbinfo); gencnt = ripcbinfo.ipi_gencnt; n = ripcbinfo.ipi_count; INP_INFO_RUNLOCK(&ripcbinfo); xig.xig_len = sizeof xig; xig.xig_count = n; xig.xig_gen = gencnt; xig.xig_sogen = so_gencnt; error = SYSCTL_OUT(req, &xig, sizeof xig); if (error) return error; inp_list = malloc(n * sizeof *inp_list, M_TEMP, M_WAITOK); if (inp_list == 0) return ENOMEM; INP_INFO_RLOCK(&ripcbinfo); for (inp = LIST_FIRST(ripcbinfo.listhead), i = 0; inp && i < n; inp = LIST_NEXT(inp, inp_list)) { INP_LOCK(inp); if (inp->inp_gencnt <= gencnt && cr_canseesocket(req->td->td_ucred, inp->inp_socket) == 0) { /* XXX held references? */ inp_list[i++] = inp; } INP_UNLOCK(inp); } INP_INFO_RUNLOCK(&ripcbinfo); n = i; error = 0; for (i = 0; i < n; i++) { inp = inp_list[i]; if (inp->inp_gencnt <= gencnt) { struct xinpcb xi; bzero(&xi, sizeof(xi)); xi.xi_len = sizeof xi; /* XXX should avoid extra copy */ bcopy(inp, &xi.xi_inp, sizeof *inp); if (inp->inp_socket) sotoxsocket(inp->inp_socket, &xi.xi_socket); error = SYSCTL_OUT(req, &xi, sizeof xi); } } if (!error) { /* * Give the user an updated idea of our state. * If the generation differs from what we told * her before, she knows that something happened * while we were processing this request, and it * might be necessary to retry. */ INP_INFO_RLOCK(&ripcbinfo); xig.xig_gen = ripcbinfo.ipi_gencnt; xig.xig_sogen = so_gencnt; xig.xig_count = ripcbinfo.ipi_count; INP_INFO_RUNLOCK(&ripcbinfo); error = SYSCTL_OUT(req, &xig, sizeof xig); } free(inp_list, M_TEMP); return error; } /* * This is the wrapper function for in_setsockaddr. We just pass down * the pcbinfo for in_setpeeraddr to lock. */ static int rip_sockaddr(struct socket *so, struct sockaddr **nam) { return (in_setsockaddr(so, nam, &ripcbinfo)); } /* * This is the wrapper function for in_setpeeraddr. We just pass down * the pcbinfo for in_setpeeraddr to lock. */ static int rip_peeraddr(struct socket *so, struct sockaddr **nam) { return (in_setpeeraddr(so, nam, &ripcbinfo)); } SYSCTL_PROC(_net_inet_raw, OID_AUTO/*XXX*/, pcblist, CTLFLAG_RD, 0, 0, rip_pcblist, "S,xinpcb", "List of active raw IP sockets"); struct pr_usrreqs rip_usrreqs = { .pru_abort = rip_abort, .pru_attach = rip_attach, .pru_bind = rip_bind, .pru_connect = rip_connect, .pru_control = in_control, .pru_detach = rip_detach, .pru_disconnect = rip_disconnect, .pru_peeraddr = rip_peeraddr, .pru_send = rip_send, .pru_shutdown = rip_shutdown, .pru_sockaddr = rip_sockaddr, .pru_sosetlabel = in_pcbsosetlabel }; diff --git a/sys/netinet/tcp_usrreq.c b/sys/netinet/tcp_usrreq.c index ff4bd4758aa5..902cab66ccbd 100644 --- a/sys/netinet/tcp_usrreq.c +++ b/sys/netinet/tcp_usrreq.c @@ -1,1313 +1,1311 @@ /*- * Copyright (c) 1982, 1986, 1988, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * From: @(#)tcp_usrreq.c 8.2 (Berkeley) 1/3/94 * $FreeBSD$ */ #include "opt_inet.h" #include "opt_inet6.h" #include "opt_tcpdebug.h" #include #include #include #include #include #include #ifdef INET6 #include #endif /* INET6 */ #include #include #include #include #include #include #include #include #include #ifdef INET6 #include #endif #include #ifdef INET6 #include #endif #include #include #ifdef INET6 #include #include #endif #include #include #include #include #include #include #ifdef TCPDEBUG #include #endif /* * TCP protocol interface to socket abstraction. */ extern char *tcpstates[]; /* XXX ??? */ static int tcp_attach(struct socket *); static int tcp_connect(struct tcpcb *, struct sockaddr *, struct thread *td); #ifdef INET6 static int tcp6_connect(struct tcpcb *, struct sockaddr *, struct thread *td); #endif /* INET6 */ static struct tcpcb * tcp_disconnect(struct tcpcb *); static struct tcpcb * tcp_usrclosed(struct tcpcb *); static void tcp_fill_info(struct tcpcb *, struct tcp_info *); #ifdef TCPDEBUG #define TCPDEBUG0 int ostate = 0 #define TCPDEBUG1() ostate = tp ? tp->t_state : 0 #define TCPDEBUG2(req) if (tp && (so->so_options & SO_DEBUG)) \ tcp_trace(TA_USER, ostate, tp, 0, 0, req) #else #define TCPDEBUG0 #define TCPDEBUG1() #define TCPDEBUG2(req) #endif /* * TCP attaches to socket via pru_attach(), reserving space, * and an internet control block. */ static int tcp_usr_attach(struct socket *so, int proto, struct thread *td) { int error; struct inpcb *inp; struct tcpcb *tp = 0; TCPDEBUG0; INP_INFO_WLOCK(&tcbinfo); TCPDEBUG1(); inp = sotoinpcb(so); if (inp) { error = EISCONN; goto out; } error = tcp_attach(so); if (error) goto out; if ((so->so_options & SO_LINGER) && so->so_linger == 0) so->so_linger = TCP_LINGERTIME; inp = sotoinpcb(so); tp = intotcpcb(inp); out: TCPDEBUG2(PRU_ATTACH); INP_INFO_WUNLOCK(&tcbinfo); return error; } /* * pru_detach() detaches the TCP protocol from the socket. * If the protocol state is non-embryonic, then can't * do this directly: have to initiate a pru_disconnect(), * which may finish later; embryonic TCB's can just * be discarded here. */ -static int +static void tcp_usr_detach(struct socket *so) { - int error = 0; struct inpcb *inp; struct tcpcb *tp; TCPDEBUG0; INP_INFO_WLOCK(&tcbinfo); inp = sotoinpcb(so); if (inp == NULL) { INP_INFO_WUNLOCK(&tcbinfo); - return error; + return; } INP_LOCK(inp); tp = intotcpcb(inp); TCPDEBUG1(); tp = tcp_disconnect(tp); TCPDEBUG2(PRU_DETACH); if (tp) INP_UNLOCK(inp); INP_INFO_WUNLOCK(&tcbinfo); - return error; } #define INI_NOLOCK 0 #define INI_READ 1 #define INI_WRITE 2 #define COMMON_START() \ TCPDEBUG0; \ do { \ if (inirw == INI_READ) \ INP_INFO_RLOCK(&tcbinfo); \ else if (inirw == INI_WRITE) \ INP_INFO_WLOCK(&tcbinfo); \ inp = sotoinpcb(so); \ if (inp == 0) { \ if (inirw == INI_READ) \ INP_INFO_RUNLOCK(&tcbinfo); \ else if (inirw == INI_WRITE) \ INP_INFO_WUNLOCK(&tcbinfo); \ return EINVAL; \ } \ INP_LOCK(inp); \ if (inirw == INI_READ) \ INP_INFO_RUNLOCK(&tcbinfo); \ tp = intotcpcb(inp); \ TCPDEBUG1(); \ } while(0) #define COMMON_END(req) \ out: TCPDEBUG2(req); \ do { \ if (tp) \ INP_UNLOCK(inp); \ if (inirw == INI_WRITE) \ INP_INFO_WUNLOCK(&tcbinfo); \ return error; \ goto out; \ } while(0) /* * Give the socket an address. */ static int tcp_usr_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { int error = 0; struct inpcb *inp; struct tcpcb *tp; struct sockaddr_in *sinp; const int inirw = INI_WRITE; sinp = (struct sockaddr_in *)nam; if (nam->sa_len != sizeof (*sinp)) return (EINVAL); /* * Must check for multicast addresses and disallow binding * to them. */ if (sinp->sin_family == AF_INET && IN_MULTICAST(ntohl(sinp->sin_addr.s_addr))) return (EAFNOSUPPORT); COMMON_START(); error = in_pcbbind(inp, nam, td->td_ucred); if (error) goto out; COMMON_END(PRU_BIND); } #ifdef INET6 static int tcp6_usr_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { int error = 0; struct inpcb *inp; struct tcpcb *tp; struct sockaddr_in6 *sin6p; const int inirw = INI_WRITE; sin6p = (struct sockaddr_in6 *)nam; if (nam->sa_len != sizeof (*sin6p)) return (EINVAL); /* * Must check for multicast addresses and disallow binding * to them. */ if (sin6p->sin6_family == AF_INET6 && IN6_IS_ADDR_MULTICAST(&sin6p->sin6_addr)) return (EAFNOSUPPORT); COMMON_START(); inp->inp_vflag &= ~INP_IPV4; inp->inp_vflag |= INP_IPV6; if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) { if (IN6_IS_ADDR_UNSPECIFIED(&sin6p->sin6_addr)) inp->inp_vflag |= INP_IPV4; else if (IN6_IS_ADDR_V4MAPPED(&sin6p->sin6_addr)) { struct sockaddr_in sin; in6_sin6_2_sin(&sin, sin6p); inp->inp_vflag |= INP_IPV4; inp->inp_vflag &= ~INP_IPV6; error = in_pcbbind(inp, (struct sockaddr *)&sin, td->td_ucred); goto out; } } error = in6_pcbbind(inp, nam, td->td_ucred); if (error) goto out; COMMON_END(PRU_BIND); } #endif /* INET6 */ /* * Prepare to accept connections. */ static int tcp_usr_listen(struct socket *so, int backlog, struct thread *td) { int error = 0; struct inpcb *inp; struct tcpcb *tp; const int inirw = INI_WRITE; COMMON_START(); SOCK_LOCK(so); error = solisten_proto_check(so); if (error == 0 && inp->inp_lport == 0) error = in_pcbbind(inp, (struct sockaddr *)0, td->td_ucred); if (error == 0) { tp->t_state = TCPS_LISTEN; solisten_proto(so, backlog); } SOCK_UNLOCK(so); COMMON_END(PRU_LISTEN); } #ifdef INET6 static int tcp6_usr_listen(struct socket *so, int backlog, struct thread *td) { int error = 0; struct inpcb *inp; struct tcpcb *tp; const int inirw = INI_WRITE; COMMON_START(); SOCK_LOCK(so); error = solisten_proto_check(so); if (error == 0 && inp->inp_lport == 0) { inp->inp_vflag &= ~INP_IPV4; if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) inp->inp_vflag |= INP_IPV4; error = in6_pcbbind(inp, (struct sockaddr *)0, td->td_ucred); } if (error == 0) { tp->t_state = TCPS_LISTEN; solisten_proto(so, backlog); } SOCK_UNLOCK(so); COMMON_END(PRU_LISTEN); } #endif /* INET6 */ /* * Initiate connection to peer. * Create a template for use in transmissions on this connection. * Enter SYN_SENT state, and mark socket as connecting. * Start keep-alive timer, and seed output sequence space. * Send initial segment on connection. */ static int tcp_usr_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { int error = 0; struct inpcb *inp; struct tcpcb *tp; struct sockaddr_in *sinp; const int inirw = INI_WRITE; sinp = (struct sockaddr_in *)nam; if (nam->sa_len != sizeof (*sinp)) return (EINVAL); /* * Must disallow TCP ``connections'' to multicast addresses. */ if (sinp->sin_family == AF_INET && IN_MULTICAST(ntohl(sinp->sin_addr.s_addr))) return (EAFNOSUPPORT); if (jailed(td->td_ucred)) prison_remote_ip(td->td_ucred, 0, &sinp->sin_addr.s_addr); COMMON_START(); if ((error = tcp_connect(tp, nam, td)) != 0) goto out; error = tcp_output(tp); COMMON_END(PRU_CONNECT); } #ifdef INET6 static int tcp6_usr_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { int error = 0; struct inpcb *inp; struct tcpcb *tp; struct sockaddr_in6 *sin6p; const int inirw = INI_WRITE; sin6p = (struct sockaddr_in6 *)nam; if (nam->sa_len != sizeof (*sin6p)) return (EINVAL); /* * Must disallow TCP ``connections'' to multicast addresses. */ if (sin6p->sin6_family == AF_INET6 && IN6_IS_ADDR_MULTICAST(&sin6p->sin6_addr)) return (EAFNOSUPPORT); COMMON_START(); if (IN6_IS_ADDR_V4MAPPED(&sin6p->sin6_addr)) { struct sockaddr_in sin; if ((inp->inp_flags & IN6P_IPV6_V6ONLY) != 0) { error = EINVAL; goto out; } in6_sin6_2_sin(&sin, sin6p); inp->inp_vflag |= INP_IPV4; inp->inp_vflag &= ~INP_IPV6; if ((error = tcp_connect(tp, (struct sockaddr *)&sin, td)) != 0) goto out; error = tcp_output(tp); goto out; } inp->inp_vflag &= ~INP_IPV4; inp->inp_vflag |= INP_IPV6; inp->inp_inc.inc_isipv6 = 1; if ((error = tcp6_connect(tp, nam, td)) != 0) goto out; error = tcp_output(tp); COMMON_END(PRU_CONNECT); } #endif /* INET6 */ /* * Initiate disconnect from peer. * If connection never passed embryonic stage, just drop; * else if don't need to let data drain, then can just drop anyways, * else have to begin TCP shutdown process: mark socket disconnecting, * drain unread data, state switch to reflect user close, and * send segment (e.g. FIN) to peer. Socket will be really disconnected * when peer sends FIN and acks ours. * * SHOULD IMPLEMENT LATER PRU_CONNECT VIA REALLOC TCPCB. */ static int tcp_usr_disconnect(struct socket *so) { int error = 0; struct inpcb *inp; struct tcpcb *tp; const int inirw = INI_WRITE; COMMON_START(); tp = tcp_disconnect(tp); COMMON_END(PRU_DISCONNECT); } /* * Accept a connection. Essentially all the work is * done at higher levels; just return the address * of the peer, storing through addr. */ static int tcp_usr_accept(struct socket *so, struct sockaddr **nam) { int error = 0; struct inpcb *inp = NULL; struct tcpcb *tp = NULL; struct in_addr addr; in_port_t port = 0; TCPDEBUG0; if (so->so_state & SS_ISDISCONNECTED) { error = ECONNABORTED; goto out; } INP_INFO_RLOCK(&tcbinfo); inp = sotoinpcb(so); if (!inp) { INP_INFO_RUNLOCK(&tcbinfo); return (EINVAL); } INP_LOCK(inp); INP_INFO_RUNLOCK(&tcbinfo); tp = intotcpcb(inp); TCPDEBUG1(); /* * We inline in_setpeeraddr and COMMON_END here, so that we can * copy the data of interest and defer the malloc until after we * release the lock. */ port = inp->inp_fport; addr = inp->inp_faddr; out: TCPDEBUG2(PRU_ACCEPT); if (tp) INP_UNLOCK(inp); if (error == 0) *nam = in_sockaddr(port, &addr); return error; } #ifdef INET6 static int tcp6_usr_accept(struct socket *so, struct sockaddr **nam) { struct inpcb *inp = NULL; int error = 0; struct tcpcb *tp = NULL; struct in_addr addr; struct in6_addr addr6; in_port_t port = 0; int v4 = 0; TCPDEBUG0; if (so->so_state & SS_ISDISCONNECTED) { error = ECONNABORTED; goto out; } INP_INFO_RLOCK(&tcbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_RUNLOCK(&tcbinfo); return (EINVAL); } INP_LOCK(inp); INP_INFO_RUNLOCK(&tcbinfo); tp = intotcpcb(inp); TCPDEBUG1(); /* * We inline in6_mapped_peeraddr and COMMON_END here, so that we can * copy the data of interest and defer the malloc until after we * release the lock. */ if (inp->inp_vflag & INP_IPV4) { v4 = 1; port = inp->inp_fport; addr = inp->inp_faddr; } else { port = inp->inp_fport; addr6 = inp->in6p_faddr; } out: TCPDEBUG2(PRU_ACCEPT); if (tp) INP_UNLOCK(inp); if (error == 0) { if (v4) *nam = in6_v4mapsin6_sockaddr(port, &addr); else *nam = in6_sockaddr(port, &addr6); } return error; } #endif /* INET6 */ /* * This is the wrapper function for in_setsockaddr. We just pass down * the pcbinfo for in_setsockaddr to lock. We don't want to do the locking * here because in_setsockaddr will call malloc and can block. */ static int tcp_sockaddr(struct socket *so, struct sockaddr **nam) { return (in_setsockaddr(so, nam, &tcbinfo)); } /* * This is the wrapper function for in_setpeeraddr. We just pass down * the pcbinfo for in_setpeeraddr to lock. */ static int tcp_peeraddr(struct socket *so, struct sockaddr **nam) { return (in_setpeeraddr(so, nam, &tcbinfo)); } /* * Mark the connection as being incapable of further output. */ static int tcp_usr_shutdown(struct socket *so) { int error = 0; struct inpcb *inp; struct tcpcb *tp; const int inirw = INI_WRITE; COMMON_START(); socantsendmore(so); tp = tcp_usrclosed(tp); if (tp) error = tcp_output(tp); COMMON_END(PRU_SHUTDOWN); } /* * After a receive, possibly send window update to peer. */ static int tcp_usr_rcvd(struct socket *so, int flags) { int error = 0; struct inpcb *inp; struct tcpcb *tp; const int inirw = INI_READ; COMMON_START(); tcp_output(tp); COMMON_END(PRU_RCVD); } /* * Do a send by putting data in output queue and updating urgent * marker if URG set. Possibly send more data. Unlike the other * pru_*() routines, the mbuf chains are our responsibility. We * must either enqueue them or free them. The other pru_* routines * generally are caller-frees. */ static int tcp_usr_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, struct mbuf *control, struct thread *td) { int error = 0; struct inpcb *inp; struct tcpcb *tp; int unlocked = 0; #ifdef INET6 int isipv6; #endif TCPDEBUG0; /* * Need write lock here because this function might call * tcp_connect or tcp_usrclosed. * We really want to have to this function upgrade from read lock * to write lock. XXX */ INP_INFO_WLOCK(&tcbinfo); inp = sotoinpcb(so); if (inp == NULL) { /* * OOPS! we lost a race, the TCP session got reset after * we checked SBS_CANTSENDMORE, eg: while doing uiomove or a * network interrupt in the non-splnet() section of sosend(). */ if (m) m_freem(m); if (control) m_freem(control); error = ECONNRESET; /* XXX EPIPE? */ tp = NULL; TCPDEBUG1(); goto out; } INP_LOCK(inp); #ifdef INET6 isipv6 = nam && nam->sa_family == AF_INET6; #endif /* INET6 */ tp = intotcpcb(inp); TCPDEBUG1(); if (control) { /* TCP doesn't do control messages (rights, creds, etc) */ if (control->m_len) { m_freem(control); if (m) m_freem(m); error = EINVAL; goto out; } m_freem(control); /* empty control, just free it */ } if (!(flags & PRUS_OOB)) { sbappendstream(&so->so_snd, m); if (nam && tp->t_state < TCPS_SYN_SENT) { /* * Do implied connect if not yet connected, * initialize window to default value, and * initialize maxseg/maxopd using peer's cached * MSS. */ #ifdef INET6 if (isipv6) error = tcp6_connect(tp, nam, td); else #endif /* INET6 */ error = tcp_connect(tp, nam, td); if (error) goto out; tp->snd_wnd = TTCP_CLIENT_SND_WND; tcp_mss(tp, -1); } if (flags & PRUS_EOF) { /* * Close the send side of the connection after * the data is sent. */ socantsendmore(so); tp = tcp_usrclosed(tp); } INP_INFO_WUNLOCK(&tcbinfo); unlocked = 1; if (tp != NULL) { if (flags & PRUS_MORETOCOME) tp->t_flags |= TF_MORETOCOME; error = tcp_output(tp); if (flags & PRUS_MORETOCOME) tp->t_flags &= ~TF_MORETOCOME; } } else { SOCKBUF_LOCK(&so->so_snd); if (sbspace(&so->so_snd) < -512) { SOCKBUF_UNLOCK(&so->so_snd); m_freem(m); error = ENOBUFS; goto out; } /* * According to RFC961 (Assigned Protocols), * the urgent pointer points to the last octet * of urgent data. We continue, however, * to consider it to indicate the first octet * of data past the urgent section. * Otherwise, snd_up should be one lower. */ sbappendstream_locked(&so->so_snd, m); SOCKBUF_UNLOCK(&so->so_snd); if (nam && tp->t_state < TCPS_SYN_SENT) { /* * Do implied connect if not yet connected, * initialize window to default value, and * initialize maxseg/maxopd using peer's cached * MSS. */ #ifdef INET6 if (isipv6) error = tcp6_connect(tp, nam, td); else #endif /* INET6 */ error = tcp_connect(tp, nam, td); if (error) goto out; tp->snd_wnd = TTCP_CLIENT_SND_WND; tcp_mss(tp, -1); } INP_INFO_WUNLOCK(&tcbinfo); unlocked = 1; tp->snd_up = tp->snd_una + so->so_snd.sb_cc; tp->t_flags |= TF_FORCEDATA; error = tcp_output(tp); tp->t_flags &= ~TF_FORCEDATA; } out: TCPDEBUG2((flags & PRUS_OOB) ? PRU_SENDOOB : ((flags & PRUS_EOF) ? PRU_SEND_EOF : PRU_SEND)); if (tp) INP_UNLOCK(inp); if (!unlocked) INP_INFO_WUNLOCK(&tcbinfo); return (error); } /* * Abort the TCP. */ static void tcp_usr_abort(struct socket *so) { struct inpcb *inp; struct tcpcb *tp; TCPDEBUG0; INP_INFO_WLOCK(&tcbinfo); inp = sotoinpcb(so); if (inp == NULL) return; INP_LOCK(inp); tp = intotcpcb(inp); TCPDEBUG1(); tp = tcp_drop(tp, ECONNABORTED); TCPDEBUG2(PRU_ABORT); if (tp) INP_UNLOCK(inp); INP_INFO_WUNLOCK(&tcbinfo); } /* * Receive out-of-band data. */ static int tcp_usr_rcvoob(struct socket *so, struct mbuf *m, int flags) { int error = 0; struct inpcb *inp; struct tcpcb *tp; const int inirw = INI_READ; COMMON_START(); if ((so->so_oobmark == 0 && (so->so_rcv.sb_state & SBS_RCVATMARK) == 0) || so->so_options & SO_OOBINLINE || tp->t_oobflags & TCPOOB_HADDATA) { error = EINVAL; goto out; } if ((tp->t_oobflags & TCPOOB_HAVEDATA) == 0) { error = EWOULDBLOCK; goto out; } m->m_len = 1; *mtod(m, caddr_t) = tp->t_iobc; if ((flags & MSG_PEEK) == 0) tp->t_oobflags ^= (TCPOOB_HAVEDATA | TCPOOB_HADDATA); COMMON_END(PRU_RCVOOB); } struct pr_usrreqs tcp_usrreqs = { .pru_abort = tcp_usr_abort, .pru_accept = tcp_usr_accept, .pru_attach = tcp_usr_attach, .pru_bind = tcp_usr_bind, .pru_connect = tcp_usr_connect, .pru_control = in_control, .pru_detach = tcp_usr_detach, .pru_disconnect = tcp_usr_disconnect, .pru_listen = tcp_usr_listen, .pru_peeraddr = tcp_peeraddr, .pru_rcvd = tcp_usr_rcvd, .pru_rcvoob = tcp_usr_rcvoob, .pru_send = tcp_usr_send, .pru_shutdown = tcp_usr_shutdown, .pru_sockaddr = tcp_sockaddr, .pru_sosetlabel = in_pcbsosetlabel }; #ifdef INET6 struct pr_usrreqs tcp6_usrreqs = { .pru_abort = tcp_usr_abort, .pru_accept = tcp6_usr_accept, .pru_attach = tcp_usr_attach, .pru_bind = tcp6_usr_bind, .pru_connect = tcp6_usr_connect, .pru_control = in6_control, .pru_detach = tcp_usr_detach, .pru_disconnect = tcp_usr_disconnect, .pru_listen = tcp6_usr_listen, .pru_peeraddr = in6_mapped_peeraddr, .pru_rcvd = tcp_usr_rcvd, .pru_rcvoob = tcp_usr_rcvoob, .pru_send = tcp_usr_send, .pru_shutdown = tcp_usr_shutdown, .pru_sockaddr = in6_mapped_sockaddr, .pru_sosetlabel = in_pcbsosetlabel }; #endif /* INET6 */ /* * Common subroutine to open a TCP connection to remote host specified * by struct sockaddr_in in mbuf *nam. Call in_pcbbind to assign a local * port number if needed. Call in_pcbconnect_setup to do the routing and * to choose a local host address (interface). If there is an existing * incarnation of the same connection in TIME-WAIT state and if the remote * host was sending CC options and if the connection duration was < MSL, then * truncate the previous TIME-WAIT state and proceed. * Initialize connection parameters and enter SYN-SENT state. */ static int tcp_connect(tp, nam, td) register struct tcpcb *tp; struct sockaddr *nam; struct thread *td; { struct inpcb *inp = tp->t_inpcb, *oinp; struct socket *so = inp->inp_socket; struct in_addr laddr; u_short lport; int error; if (inp->inp_lport == 0) { error = in_pcbbind(inp, (struct sockaddr *)0, td->td_ucred); if (error) return error; } /* * Cannot simply call in_pcbconnect, because there might be an * earlier incarnation of this same connection still in * TIME_WAIT state, creating an ADDRINUSE error. */ laddr = inp->inp_laddr; lport = inp->inp_lport; error = in_pcbconnect_setup(inp, nam, &laddr.s_addr, &lport, &inp->inp_faddr.s_addr, &inp->inp_fport, &oinp, td->td_ucred); if (error && oinp == NULL) return error; if (oinp) return EADDRINUSE; inp->inp_laddr = laddr; in_pcbrehash(inp); /* Compute window scaling to request. */ while (tp->request_r_scale < TCP_MAX_WINSHIFT && (TCP_MAXWIN << tp->request_r_scale) < so->so_rcv.sb_hiwat) tp->request_r_scale++; soisconnecting(so); tcpstat.tcps_connattempt++; tp->t_state = TCPS_SYN_SENT; callout_reset(tp->tt_keep, tcp_keepinit, tcp_timer_keep, tp); tp->iss = tcp_new_isn(tp); tp->t_bw_rtseq = tp->iss; tcp_sendseqinit(tp); return 0; } #ifdef INET6 static int tcp6_connect(tp, nam, td) register struct tcpcb *tp; struct sockaddr *nam; struct thread *td; { struct inpcb *inp = tp->t_inpcb, *oinp; struct socket *so = inp->inp_socket; struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)nam; struct in6_addr *addr6; int error; if (inp->inp_lport == 0) { error = in6_pcbbind(inp, (struct sockaddr *)0, td->td_ucred); if (error) return error; } /* * Cannot simply call in_pcbconnect, because there might be an * earlier incarnation of this same connection still in * TIME_WAIT state, creating an ADDRINUSE error. * in6_pcbladdr() also handles scope zone IDs. */ error = in6_pcbladdr(inp, nam, &addr6); if (error) return error; oinp = in6_pcblookup_hash(inp->inp_pcbinfo, &sin6->sin6_addr, sin6->sin6_port, IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr) ? addr6 : &inp->in6p_laddr, inp->inp_lport, 0, NULL); if (oinp) return EADDRINUSE; if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr)) inp->in6p_laddr = *addr6; inp->in6p_faddr = sin6->sin6_addr; inp->inp_fport = sin6->sin6_port; /* update flowinfo - draft-itojun-ipv6-flowlabel-api-00 */ inp->in6p_flowinfo &= ~IPV6_FLOWLABEL_MASK; if (inp->in6p_flags & IN6P_AUTOFLOWLABEL) inp->in6p_flowinfo |= (htonl(ip6_randomflowlabel()) & IPV6_FLOWLABEL_MASK); in_pcbrehash(inp); /* Compute window scaling to request. */ while (tp->request_r_scale < TCP_MAX_WINSHIFT && (TCP_MAXWIN << tp->request_r_scale) < so->so_rcv.sb_hiwat) tp->request_r_scale++; soisconnecting(so); tcpstat.tcps_connattempt++; tp->t_state = TCPS_SYN_SENT; callout_reset(tp->tt_keep, tcp_keepinit, tcp_timer_keep, tp); tp->iss = tcp_new_isn(tp); tp->t_bw_rtseq = tp->iss; tcp_sendseqinit(tp); return 0; } #endif /* INET6 */ /* * Export TCP internal state information via a struct tcp_info, based on the * Linux 2.6 API. Not ABI compatible as our constants are mapped differently * (TCP state machine, etc). We export all information using FreeBSD-native * constants -- for example, the numeric values for tcpi_state will differ * from Linux. */ static void tcp_fill_info(tp, ti) struct tcpcb *tp; struct tcp_info *ti; { INP_LOCK_ASSERT(tp->t_inpcb); bzero(ti, sizeof(*ti)); ti->tcpi_state = tp->t_state; if ((tp->t_flags & TF_REQ_TSTMP) && (tp->t_flags & TF_RCVD_TSTMP)) ti->tcpi_options |= TCPI_OPT_TIMESTAMPS; if (tp->sack_enable) ti->tcpi_options |= TCPI_OPT_SACK; if ((tp->t_flags & TF_REQ_SCALE) && (tp->t_flags & TF_RCVD_SCALE)) { ti->tcpi_options |= TCPI_OPT_WSCALE; ti->tcpi_snd_wscale = tp->snd_scale; ti->tcpi_rcv_wscale = tp->rcv_scale; } ti->tcpi_snd_ssthresh = tp->snd_ssthresh; ti->tcpi_snd_cwnd = tp->snd_cwnd; /* * FreeBSD-specific extension fields for tcp_info. */ ti->tcpi_rcv_space = tp->rcv_wnd; ti->tcpi_snd_wnd = tp->snd_wnd; ti->tcpi_snd_bwnd = tp->snd_bwnd; } /* * The new sockopt interface makes it possible for us to block in the * copyin/out step (if we take a page fault). Taking a page fault at * splnet() is probably a Bad Thing. (Since sockets and pcbs both now * use TSM, there probably isn't any need for this function to run at * splnet() any more. This needs more examination.) * * XXXRW: The locking here is wrong; we may take a page fault while holding * the inpcb lock. */ int tcp_ctloutput(so, sopt) struct socket *so; struct sockopt *sopt; { int error, opt, optval; struct inpcb *inp; struct tcpcb *tp; struct tcp_info ti; error = 0; INP_INFO_RLOCK(&tcbinfo); inp = sotoinpcb(so); if (inp == NULL) { INP_INFO_RUNLOCK(&tcbinfo); return (ECONNRESET); } INP_LOCK(inp); INP_INFO_RUNLOCK(&tcbinfo); if (sopt->sopt_level != IPPROTO_TCP) { INP_UNLOCK(inp); #ifdef INET6 if (INP_CHECK_SOCKAF(so, AF_INET6)) error = ip6_ctloutput(so, sopt); else #endif /* INET6 */ error = ip_ctloutput(so, sopt); return (error); } tp = intotcpcb(inp); switch (sopt->sopt_dir) { case SOPT_SET: switch (sopt->sopt_name) { #ifdef TCP_SIGNATURE case TCP_MD5SIG: error = sooptcopyin(sopt, &optval, sizeof optval, sizeof optval); if (error) break; if (optval > 0) tp->t_flags |= TF_SIGNATURE; else tp->t_flags &= ~TF_SIGNATURE; break; #endif /* TCP_SIGNATURE */ case TCP_NODELAY: case TCP_NOOPT: error = sooptcopyin(sopt, &optval, sizeof optval, sizeof optval); if (error) break; switch (sopt->sopt_name) { case TCP_NODELAY: opt = TF_NODELAY; break; case TCP_NOOPT: opt = TF_NOOPT; break; default: opt = 0; /* dead code to fool gcc */ break; } if (optval) tp->t_flags |= opt; else tp->t_flags &= ~opt; break; case TCP_NOPUSH: error = sooptcopyin(sopt, &optval, sizeof optval, sizeof optval); if (error) break; if (optval) tp->t_flags |= TF_NOPUSH; else { tp->t_flags &= ~TF_NOPUSH; error = tcp_output(tp); } break; case TCP_MAXSEG: error = sooptcopyin(sopt, &optval, sizeof optval, sizeof optval); if (error) break; if (optval > 0 && optval <= tp->t_maxseg && optval + 40 >= tcp_minmss) tp->t_maxseg = optval; else error = EINVAL; break; case TCP_INFO: error = EINVAL; break; default: error = ENOPROTOOPT; break; } break; case SOPT_GET: switch (sopt->sopt_name) { #ifdef TCP_SIGNATURE case TCP_MD5SIG: optval = (tp->t_flags & TF_SIGNATURE) ? 1 : 0; error = sooptcopyout(sopt, &optval, sizeof optval); break; #endif case TCP_NODELAY: optval = tp->t_flags & TF_NODELAY; error = sooptcopyout(sopt, &optval, sizeof optval); break; case TCP_MAXSEG: optval = tp->t_maxseg; error = sooptcopyout(sopt, &optval, sizeof optval); break; case TCP_NOOPT: optval = tp->t_flags & TF_NOOPT; error = sooptcopyout(sopt, &optval, sizeof optval); break; case TCP_NOPUSH: optval = tp->t_flags & TF_NOPUSH; error = sooptcopyout(sopt, &optval, sizeof optval); break; case TCP_INFO: tcp_fill_info(tp, &ti); error = sooptcopyout(sopt, &ti, sizeof ti); break; default: error = ENOPROTOOPT; break; } break; } INP_UNLOCK(inp); return (error); } /* * tcp_sendspace and tcp_recvspace are the default send and receive window * sizes, respectively. These are obsolescent (this information should * be set by the route). */ u_long tcp_sendspace = 1024*32; SYSCTL_ULONG(_net_inet_tcp, TCPCTL_SENDSPACE, sendspace, CTLFLAG_RW, &tcp_sendspace , 0, "Maximum outgoing TCP datagram size"); u_long tcp_recvspace = 1024*64; SYSCTL_ULONG(_net_inet_tcp, TCPCTL_RECVSPACE, recvspace, CTLFLAG_RW, &tcp_recvspace , 0, "Maximum incoming TCP datagram size"); /* * Attach TCP protocol to socket, allocating * internet protocol control block, tcp control block, * bufer space, and entering LISTEN state if to accept connections. */ static int tcp_attach(so) struct socket *so; { register struct tcpcb *tp; struct inpcb *inp; int error; #ifdef INET6 int isipv6 = INP_CHECK_SOCKAF(so, AF_INET6) != 0; #endif INP_INFO_WLOCK_ASSERT(&tcbinfo); if (so->so_snd.sb_hiwat == 0 || so->so_rcv.sb_hiwat == 0) { error = soreserve(so, tcp_sendspace, tcp_recvspace); if (error) return (error); } error = in_pcballoc(so, &tcbinfo, "tcpinp"); if (error) return (error); inp = sotoinpcb(so); #ifdef INET6 if (isipv6) { inp->inp_vflag |= INP_IPV6; inp->in6p_hops = -1; /* use kernel default */ } else #endif inp->inp_vflag |= INP_IPV4; tp = tcp_newtcpcb(inp); if (tp == 0) { int nofd = so->so_state & SS_NOFDREF; /* XXX */ so->so_state &= ~SS_NOFDREF; /* don't free the socket yet */ INP_LOCK(inp); #ifdef INET6 if (isipv6) in6_pcbdetach(inp); else #endif in_pcbdetach(inp); so->so_state |= nofd; return (ENOBUFS); } tp->t_state = TCPS_CLOSED; return (0); } /* * Initiate (or continue) disconnect. * If embryonic state, just send reset (once). * If in ``let data drain'' option and linger null, just drop. * Otherwise (hard), mark socket disconnecting and drop * current input data; switch states based on user close, and * send segment to peer (with FIN). */ static struct tcpcb * tcp_disconnect(tp) register struct tcpcb *tp; { struct inpcb *inp = tp->t_inpcb; struct socket *so = inp->inp_socket; INP_INFO_WLOCK_ASSERT(&tcbinfo); INP_LOCK_ASSERT(inp); if (tp->t_state < TCPS_ESTABLISHED) tp = tcp_close(tp); else if ((so->so_options & SO_LINGER) && so->so_linger == 0) tp = tcp_drop(tp, 0); else { soisdisconnecting(so); sbflush(&so->so_rcv); tp = tcp_usrclosed(tp); if (tp) (void) tcp_output(tp); } return (tp); } /* * User issued close, and wish to trail through shutdown states: * if never received SYN, just forget it. If got a SYN from peer, * but haven't sent FIN, then go to FIN_WAIT_1 state to send peer a FIN. * If already got a FIN from peer, then almost done; go to LAST_ACK * state. In all other cases, have already sent FIN to peer (e.g. * after PRU_SHUTDOWN), and just have to play tedious game waiting * for peer to send FIN or not respond to keep-alives, etc. * We can let the user exit from the close as soon as the FIN is acked. */ static struct tcpcb * tcp_usrclosed(tp) register struct tcpcb *tp; { INP_INFO_WLOCK_ASSERT(&tcbinfo); INP_LOCK_ASSERT(tp->t_inpcb); switch (tp->t_state) { case TCPS_CLOSED: case TCPS_LISTEN: tp->t_state = TCPS_CLOSED; tp = tcp_close(tp); break; case TCPS_SYN_SENT: case TCPS_SYN_RECEIVED: tp->t_flags |= TF_NEEDFIN; break; case TCPS_ESTABLISHED: tp->t_state = TCPS_FIN_WAIT_1; break; case TCPS_CLOSE_WAIT: tp->t_state = TCPS_LAST_ACK; break; } if (tp && tp->t_state >= TCPS_FIN_WAIT_2) { soisdisconnected(tp->t_inpcb->inp_socket); /* To prevent the connection hanging in FIN_WAIT_2 forever. */ if (tp->t_state == TCPS_FIN_WAIT_2) callout_reset(tp->tt_2msl, tcp_maxidle, tcp_timer_2msl, tp); } return (tp); } diff --git a/sys/netinet/udp_usrreq.c b/sys/netinet/udp_usrreq.c index 69ba6fea5163..23d430132d1b 100644 --- a/sys/netinet/udp_usrreq.c +++ b/sys/netinet/udp_usrreq.c @@ -1,1135 +1,1133 @@ /*- * Copyright (c) 1982, 1986, 1988, 1990, 1993, 1995 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)udp_usrreq.c 8.6 (Berkeley) 5/23/95 * $FreeBSD$ */ #include "opt_ipfw.h" #include "opt_ipsec.h" #include "opt_inet6.h" #include "opt_mac.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET6 #include #endif #include #include #include #include #ifdef INET6 #include #endif #include #include #ifdef FAST_IPSEC #include #endif /*FAST_IPSEC*/ #ifdef IPSEC #include #endif /*IPSEC*/ #include /* * UDP protocol implementation. * Per RFC 768, August, 1980. */ #ifndef COMPAT_42 static int udpcksum = 1; #else static int udpcksum = 0; /* XXX */ #endif SYSCTL_INT(_net_inet_udp, UDPCTL_CHECKSUM, checksum, CTLFLAG_RW, &udpcksum, 0, ""); int log_in_vain = 0; SYSCTL_INT(_net_inet_udp, OID_AUTO, log_in_vain, CTLFLAG_RW, &log_in_vain, 0, "Log all incoming UDP packets"); static int blackhole = 0; SYSCTL_INT(_net_inet_udp, OID_AUTO, blackhole, CTLFLAG_RW, &blackhole, 0, "Do not send port unreachables for refused connects"); static int strict_mcast_mship = 0; SYSCTL_INT(_net_inet_udp, OID_AUTO, strict_mcast_mship, CTLFLAG_RW, &strict_mcast_mship, 0, "Only send multicast to member sockets"); struct inpcbhead udb; /* from udp_var.h */ #define udb6 udb /* for KAME src sync over BSD*'s */ struct inpcbinfo udbinfo; #ifndef UDBHASHSIZE #define UDBHASHSIZE 16 #endif struct udpstat udpstat; /* from udp_var.h */ SYSCTL_STRUCT(_net_inet_udp, UDPCTL_STATS, stats, CTLFLAG_RW, &udpstat, udpstat, "UDP statistics (struct udpstat, netinet/udp_var.h)"); static void udp_append(struct inpcb *last, struct ip *ip, struct mbuf *n, int off, struct sockaddr_in *udp_in); -static int udp_detach(struct socket *so); +static void udp_detach(struct socket *so); static int udp_output(struct inpcb *, struct mbuf *, struct sockaddr *, struct mbuf *, struct thread *); void udp_init() { INP_INFO_LOCK_INIT(&udbinfo, "udp"); LIST_INIT(&udb); udbinfo.listhead = &udb; udbinfo.hashbase = hashinit(UDBHASHSIZE, M_PCB, &udbinfo.hashmask); udbinfo.porthashbase = hashinit(UDBHASHSIZE, M_PCB, &udbinfo.porthashmask); udbinfo.ipi_zone = uma_zcreate("udpcb", sizeof(struct inpcb), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_NOFREE); uma_zone_set_max(udbinfo.ipi_zone, maxsockets); } void udp_input(m, off) register struct mbuf *m; int off; { int iphlen = off; register struct ip *ip; register struct udphdr *uh; register struct inpcb *inp; int len; struct ip save_ip; struct sockaddr_in udp_in; #ifdef IPFIREWALL_FORWARD struct m_tag *fwd_tag; #endif udpstat.udps_ipackets++; /* * Strip IP options, if any; should skip this, * make available to user, and use on returned packets, * but we don't yet have a way to check the checksum * with options still present. */ if (iphlen > sizeof (struct ip)) { ip_stripoptions(m, (struct mbuf *)0); iphlen = sizeof(struct ip); } /* * Get IP and UDP header together in first mbuf. */ ip = mtod(m, struct ip *); if (m->m_len < iphlen + sizeof(struct udphdr)) { if ((m = m_pullup(m, iphlen + sizeof(struct udphdr))) == 0) { udpstat.udps_hdrops++; return; } ip = mtod(m, struct ip *); } uh = (struct udphdr *)((caddr_t)ip + iphlen); /* destination port of 0 is illegal, based on RFC768. */ if (uh->uh_dport == 0) goto badunlocked; /* * Construct sockaddr format source address. * Stuff source address and datagram in user buffer. */ bzero(&udp_in, sizeof(udp_in)); udp_in.sin_len = sizeof(udp_in); udp_in.sin_family = AF_INET; udp_in.sin_port = uh->uh_sport; udp_in.sin_addr = ip->ip_src; /* * Make mbuf data length reflect UDP length. * If not enough data to reflect UDP length, drop. */ len = ntohs((u_short)uh->uh_ulen); if (ip->ip_len != len) { if (len > ip->ip_len || len < sizeof(struct udphdr)) { udpstat.udps_badlen++; goto badunlocked; } m_adj(m, len - ip->ip_len); /* ip->ip_len = len; */ } /* * Save a copy of the IP header in case we want restore it * for sending an ICMP error message in response. */ if (!blackhole) save_ip = *ip; /* * Checksum extended UDP header and data. */ if (uh->uh_sum) { if (m->m_pkthdr.csum_flags & CSUM_DATA_VALID) { if (m->m_pkthdr.csum_flags & CSUM_PSEUDO_HDR) uh->uh_sum = m->m_pkthdr.csum_data; else uh->uh_sum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, htonl((u_short)len + m->m_pkthdr.csum_data + IPPROTO_UDP)); uh->uh_sum ^= 0xffff; } else { char b[9]; bcopy(((struct ipovly *)ip)->ih_x1, b, 9); bzero(((struct ipovly *)ip)->ih_x1, 9); ((struct ipovly *)ip)->ih_len = uh->uh_ulen; uh->uh_sum = in_cksum(m, len + sizeof (struct ip)); bcopy(b, ((struct ipovly *)ip)->ih_x1, 9); } if (uh->uh_sum) { udpstat.udps_badsum++; m_freem(m); return; } } else udpstat.udps_nosum++; #ifdef IPFIREWALL_FORWARD /* Grab info from PACKET_TAG_IPFORWARD tag prepended to the chain. */ fwd_tag = m_tag_find(m, PACKET_TAG_IPFORWARD, NULL); if (fwd_tag != NULL) { struct sockaddr_in *next_hop; /* Do the hack. */ next_hop = (struct sockaddr_in *)(fwd_tag + 1); ip->ip_dst = next_hop->sin_addr; uh->uh_dport = ntohs(next_hop->sin_port); /* Remove the tag from the packet. We don't need it anymore. */ m_tag_delete(m, fwd_tag); } #endif INP_INFO_RLOCK(&udbinfo); if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr)) || in_broadcast(ip->ip_dst, m->m_pkthdr.rcvif)) { struct inpcb *last; /* * Deliver a multicast or broadcast datagram to *all* sockets * for which the local and remote addresses and ports match * those of the incoming datagram. This allows more than * one process to receive multi/broadcasts on the same port. * (This really ought to be done for unicast datagrams as * well, but that would cause problems with existing * applications that open both address-specific sockets and * a wildcard socket listening to the same port -- they would * end up receiving duplicates of every unicast datagram. * Those applications open the multiple sockets to overcome an * inadequacy of the UDP socket interface, but for backwards * compatibility we avoid the problem here rather than * fixing the interface. Maybe 4.5BSD will remedy this?) */ /* * Locate pcb(s) for datagram. * (Algorithm copied from raw_intr().) */ last = NULL; LIST_FOREACH(inp, &udb, inp_list) { if (inp->inp_lport != uh->uh_dport) continue; #ifdef INET6 if ((inp->inp_vflag & INP_IPV4) == 0) continue; #endif if (inp->inp_laddr.s_addr != INADDR_ANY) { if (inp->inp_laddr.s_addr != ip->ip_dst.s_addr) continue; } if (inp->inp_faddr.s_addr != INADDR_ANY) { if (inp->inp_faddr.s_addr != ip->ip_src.s_addr || inp->inp_fport != uh->uh_sport) continue; } INP_LOCK(inp); /* * Check multicast packets to make sure they are only * sent to sockets with multicast memberships for the * packet's destination address and arrival interface */ #define MSHIP(_inp, n) ((_inp)->inp_moptions->imo_membership[(n)]) #define NMSHIPS(_inp) ((_inp)->inp_moptions->imo_num_memberships) if (strict_mcast_mship && inp->inp_moptions != NULL) { int mship, foundmship = 0; for (mship = 0; mship < NMSHIPS(inp); mship++) { if (MSHIP(inp, mship)->inm_addr.s_addr == ip->ip_dst.s_addr && MSHIP(inp, mship)->inm_ifp == m->m_pkthdr.rcvif) { foundmship = 1; break; } } if (foundmship == 0) { INP_UNLOCK(inp); continue; } } #undef NMSHIPS #undef MSHIP if (last != NULL) { struct mbuf *n; n = m_copy(m, 0, M_COPYALL); if (n != NULL) udp_append(last, ip, n, iphlen + sizeof(struct udphdr), &udp_in); INP_UNLOCK(last); } last = inp; /* * Don't look for additional matches if this one does * not have either the SO_REUSEPORT or SO_REUSEADDR * socket options set. This heuristic avoids searching * through all pcbs in the common case of a non-shared * port. It * assumes that an application will never * clear these options after setting them. */ if ((last->inp_socket->so_options&(SO_REUSEPORT|SO_REUSEADDR)) == 0) break; } if (last == NULL) { /* * No matching pcb found; discard datagram. * (No need to send an ICMP Port Unreachable * for a broadcast or multicast datgram.) */ udpstat.udps_noportbcast++; goto badheadlocked; } udp_append(last, ip, m, iphlen + sizeof(struct udphdr), &udp_in); INP_UNLOCK(last); INP_INFO_RUNLOCK(&udbinfo); return; } /* * Locate pcb for datagram. */ inp = in_pcblookup_hash(&udbinfo, ip->ip_src, uh->uh_sport, ip->ip_dst, uh->uh_dport, 1, m->m_pkthdr.rcvif); if (inp == NULL) { if (log_in_vain) { char buf[4*sizeof "123"]; strcpy(buf, inet_ntoa(ip->ip_dst)); log(LOG_INFO, "Connection attempt to UDP %s:%d from %s:%d\n", buf, ntohs(uh->uh_dport), inet_ntoa(ip->ip_src), ntohs(uh->uh_sport)); } udpstat.udps_noport++; if (m->m_flags & (M_BCAST | M_MCAST)) { udpstat.udps_noportbcast++; goto badheadlocked; } if (blackhole) goto badheadlocked; if (badport_bandlim(BANDLIM_ICMP_UNREACH) < 0) goto badheadlocked; *ip = save_ip; ip->ip_len += iphlen; icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_PORT, 0, 0); INP_INFO_RUNLOCK(&udbinfo); return; } INP_LOCK(inp); /* Check the minimum TTL for socket. */ if (inp->inp_ip_minttl && inp->inp_ip_minttl > ip->ip_ttl) goto badheadlocked; udp_append(inp, ip, m, iphlen + sizeof(struct udphdr), &udp_in); INP_UNLOCK(inp); INP_INFO_RUNLOCK(&udbinfo); return; badheadlocked: if (inp) INP_UNLOCK(inp); INP_INFO_RUNLOCK(&udbinfo); badunlocked: m_freem(m); return; } /* * Subroutine of udp_input(), which appends the provided mbuf chain to the * passed pcb/socket. The caller must provide a sockaddr_in via udp_in that * contains the source address. If the socket ends up being an IPv6 socket, * udp_append() will convert to a sockaddr_in6 before passing the address * into the socket code. */ static void udp_append(last, ip, n, off, udp_in) struct inpcb *last; struct ip *ip; struct mbuf *n; int off; struct sockaddr_in *udp_in; { struct sockaddr *append_sa; struct socket *so; struct mbuf *opts = 0; #ifdef INET6 struct sockaddr_in6 udp_in6; #endif INP_LOCK_ASSERT(last); #if defined(IPSEC) || defined(FAST_IPSEC) /* check AH/ESP integrity. */ if (ipsec4_in_reject(n, last)) { #ifdef IPSEC ipsecstat.in_polvio++; #endif /*IPSEC*/ m_freem(n); return; } #endif /*IPSEC || FAST_IPSEC*/ #ifdef MAC if (mac_check_inpcb_deliver(last, n) != 0) { m_freem(n); return; } #endif if (last->inp_flags & INP_CONTROLOPTS || last->inp_socket->so_options & (SO_TIMESTAMP | SO_BINTIME)) { #ifdef INET6 if (last->inp_vflag & INP_IPV6) { int savedflags; savedflags = last->inp_flags; last->inp_flags &= ~INP_UNMAPPABLEOPTS; ip6_savecontrol(last, n, &opts); last->inp_flags = savedflags; } else #endif ip_savecontrol(last, &opts, ip, n); } #ifdef INET6 if (last->inp_vflag & INP_IPV6) { bzero(&udp_in6, sizeof(udp_in6)); udp_in6.sin6_len = sizeof(udp_in6); udp_in6.sin6_family = AF_INET6; in6_sin_2_v4mapsin6(udp_in, &udp_in6); append_sa = (struct sockaddr *)&udp_in6; } else #endif append_sa = (struct sockaddr *)udp_in; m_adj(n, off); so = last->inp_socket; SOCKBUF_LOCK(&so->so_rcv); if (sbappendaddr_locked(&so->so_rcv, append_sa, n, opts) == 0) { m_freem(n); if (opts) m_freem(opts); udpstat.udps_fullsock++; SOCKBUF_UNLOCK(&so->so_rcv); } else sorwakeup_locked(so); } /* * Notify a udp user of an asynchronous error; * just wake up so that he can collect error status. */ struct inpcb * udp_notify(inp, errno) register struct inpcb *inp; int errno; { inp->inp_socket->so_error = errno; sorwakeup(inp->inp_socket); sowwakeup(inp->inp_socket); return inp; } void udp_ctlinput(cmd, sa, vip) int cmd; struct sockaddr *sa; void *vip; { struct ip *ip = vip; struct udphdr *uh; struct inpcb *(*notify)(struct inpcb *, int) = udp_notify; struct in_addr faddr; struct inpcb *inp; faddr = ((struct sockaddr_in *)sa)->sin_addr; if (sa->sa_family != AF_INET || faddr.s_addr == INADDR_ANY) return; /* * Redirects don't need to be handled up here. */ if (PRC_IS_REDIRECT(cmd)) return; /* * Hostdead is ugly because it goes linearly through all PCBs. * XXX: We never get this from ICMP, otherwise it makes an * excellent DoS attack on machines with many connections. */ if (cmd == PRC_HOSTDEAD) ip = 0; else if ((unsigned)cmd >= PRC_NCMDS || inetctlerrmap[cmd] == 0) return; if (ip) { uh = (struct udphdr *)((caddr_t)ip + (ip->ip_hl << 2)); INP_INFO_RLOCK(&udbinfo); inp = in_pcblookup_hash(&udbinfo, faddr, uh->uh_dport, ip->ip_src, uh->uh_sport, 0, NULL); if (inp != NULL) { INP_LOCK(inp); if (inp->inp_socket != NULL) { (*notify)(inp, inetctlerrmap[cmd]); } INP_UNLOCK(inp); } INP_INFO_RUNLOCK(&udbinfo); } else in_pcbnotifyall(&udbinfo, faddr, inetctlerrmap[cmd], notify); } static int udp_pcblist(SYSCTL_HANDLER_ARGS) { int error, i, n; struct inpcb *inp, **inp_list; inp_gen_t gencnt; struct xinpgen xig; /* * The process of preparing the TCB list is too time-consuming and * resource-intensive to repeat twice on every request. */ if (req->oldptr == 0) { n = udbinfo.ipi_count; req->oldidx = 2 * (sizeof xig) + (n + n/8) * sizeof(struct xinpcb); return 0; } if (req->newptr != 0) return EPERM; /* * OK, now we're committed to doing something. */ INP_INFO_RLOCK(&udbinfo); gencnt = udbinfo.ipi_gencnt; n = udbinfo.ipi_count; INP_INFO_RUNLOCK(&udbinfo); error = sysctl_wire_old_buffer(req, 2 * (sizeof xig) + n * sizeof(struct xinpcb)); if (error != 0) return (error); xig.xig_len = sizeof xig; xig.xig_count = n; xig.xig_gen = gencnt; xig.xig_sogen = so_gencnt; error = SYSCTL_OUT(req, &xig, sizeof xig); if (error) return error; inp_list = malloc(n * sizeof *inp_list, M_TEMP, M_WAITOK); if (inp_list == 0) return ENOMEM; INP_INFO_RLOCK(&udbinfo); for (inp = LIST_FIRST(udbinfo.listhead), i = 0; inp && i < n; inp = LIST_NEXT(inp, inp_list)) { INP_LOCK(inp); if (inp->inp_gencnt <= gencnt && cr_canseesocket(req->td->td_ucred, inp->inp_socket) == 0) inp_list[i++] = inp; INP_UNLOCK(inp); } INP_INFO_RUNLOCK(&udbinfo); n = i; error = 0; for (i = 0; i < n; i++) { inp = inp_list[i]; if (inp->inp_gencnt <= gencnt) { struct xinpcb xi; bzero(&xi, sizeof(xi)); xi.xi_len = sizeof xi; /* XXX should avoid extra copy */ bcopy(inp, &xi.xi_inp, sizeof *inp); if (inp->inp_socket) sotoxsocket(inp->inp_socket, &xi.xi_socket); xi.xi_inp.inp_gencnt = inp->inp_gencnt; error = SYSCTL_OUT(req, &xi, sizeof xi); } } if (!error) { /* * Give the user an updated idea of our state. * If the generation differs from what we told * her before, she knows that something happened * while we were processing this request, and it * might be necessary to retry. */ INP_INFO_RLOCK(&udbinfo); xig.xig_gen = udbinfo.ipi_gencnt; xig.xig_sogen = so_gencnt; xig.xig_count = udbinfo.ipi_count; INP_INFO_RUNLOCK(&udbinfo); error = SYSCTL_OUT(req, &xig, sizeof xig); } free(inp_list, M_TEMP); return error; } SYSCTL_PROC(_net_inet_udp, UDPCTL_PCBLIST, pcblist, CTLFLAG_RD, 0, 0, udp_pcblist, "S,xinpcb", "List of active UDP sockets"); static int udp_getcred(SYSCTL_HANDLER_ARGS) { struct xucred xuc; struct sockaddr_in addrs[2]; struct inpcb *inp; int error; error = suser_cred(req->td->td_ucred, SUSER_ALLOWJAIL); if (error) return (error); error = SYSCTL_IN(req, addrs, sizeof(addrs)); if (error) return (error); INP_INFO_RLOCK(&udbinfo); inp = in_pcblookup_hash(&udbinfo, addrs[1].sin_addr, addrs[1].sin_port, addrs[0].sin_addr, addrs[0].sin_port, 1, NULL); if (inp == NULL || inp->inp_socket == NULL) { error = ENOENT; goto out; } error = cr_canseesocket(req->td->td_ucred, inp->inp_socket); if (error) goto out; cru2x(inp->inp_socket->so_cred, &xuc); out: INP_INFO_RUNLOCK(&udbinfo); if (error == 0) error = SYSCTL_OUT(req, &xuc, sizeof(struct xucred)); return (error); } SYSCTL_PROC(_net_inet_udp, OID_AUTO, getcred, CTLTYPE_OPAQUE|CTLFLAG_RW|CTLFLAG_PRISON, 0, 0, udp_getcred, "S,xucred", "Get the xucred of a UDP connection"); static int udp_output(inp, m, addr, control, td) register struct inpcb *inp; struct mbuf *m; struct sockaddr *addr; struct mbuf *control; struct thread *td; { register struct udpiphdr *ui; register int len = m->m_pkthdr.len; struct in_addr faddr, laddr; struct cmsghdr *cm; struct sockaddr_in *sin, src; int error = 0; int ipflags; u_short fport, lport; int unlock_udbinfo; /* * udp_output() may need to temporarily bind or connect the current * inpcb. As such, we don't know up front what inpcb locks we will * need. Do any work to decide what is needed up front before * acquiring locks. */ if (len + sizeof(struct udpiphdr) > IP_MAXPACKET) { if (control) m_freem(control); m_freem(m); return EMSGSIZE; } src.sin_addr.s_addr = INADDR_ANY; if (control != NULL) { /* * XXX: Currently, we assume all the optional information * is stored in a single mbuf. */ if (control->m_next) { m_freem(control); m_freem(m); return EINVAL; } for (; control->m_len > 0; control->m_data += CMSG_ALIGN(cm->cmsg_len), control->m_len -= CMSG_ALIGN(cm->cmsg_len)) { cm = mtod(control, struct cmsghdr *); if (control->m_len < sizeof(*cm) || cm->cmsg_len == 0 || cm->cmsg_len > control->m_len) { error = EINVAL; break; } if (cm->cmsg_level != IPPROTO_IP) continue; switch (cm->cmsg_type) { case IP_SENDSRCADDR: if (cm->cmsg_len != CMSG_LEN(sizeof(struct in_addr))) { error = EINVAL; break; } bzero(&src, sizeof(src)); src.sin_family = AF_INET; src.sin_len = sizeof(src); src.sin_port = inp->inp_lport; src.sin_addr = *(struct in_addr *)CMSG_DATA(cm); break; default: error = ENOPROTOOPT; break; } if (error) break; } m_freem(control); } if (error) { m_freem(m); return error; } if (src.sin_addr.s_addr != INADDR_ANY || addr != NULL) { INP_INFO_WLOCK(&udbinfo); unlock_udbinfo = 1; } else unlock_udbinfo = 0; INP_LOCK(inp); #ifdef MAC mac_create_mbuf_from_inpcb(inp, m); #endif laddr = inp->inp_laddr; lport = inp->inp_lport; if (src.sin_addr.s_addr != INADDR_ANY) { if (lport == 0) { error = EINVAL; goto release; } error = in_pcbbind_setup(inp, (struct sockaddr *)&src, &laddr.s_addr, &lport, td->td_ucred); if (error) goto release; } if (addr) { sin = (struct sockaddr_in *)addr; if (jailed(td->td_ucred)) prison_remote_ip(td->td_ucred, 0, &sin->sin_addr.s_addr); if (inp->inp_faddr.s_addr != INADDR_ANY) { error = EISCONN; goto release; } error = in_pcbconnect_setup(inp, addr, &laddr.s_addr, &lport, &faddr.s_addr, &fport, NULL, td->td_ucred); if (error) goto release; /* Commit the local port if newly assigned. */ if (inp->inp_laddr.s_addr == INADDR_ANY && inp->inp_lport == 0) { /* * Remember addr if jailed, to prevent rebinding. */ if (jailed(td->td_ucred)) inp->inp_laddr = laddr; inp->inp_lport = lport; if (in_pcbinshash(inp) != 0) { inp->inp_lport = 0; error = EAGAIN; goto release; } inp->inp_flags |= INP_ANONPORT; } } else { faddr = inp->inp_faddr; fport = inp->inp_fport; if (faddr.s_addr == INADDR_ANY) { error = ENOTCONN; goto release; } } /* * Calculate data length and get a mbuf for UDP, IP, and possible * link-layer headers. Immediate slide the data pointer back forward * since we won't use that space at this layer. */ M_PREPEND(m, sizeof(struct udpiphdr) + max_linkhdr, M_DONTWAIT); if (m == NULL) { error = ENOBUFS; goto release; } m->m_data += max_linkhdr; m->m_len -= max_linkhdr; m->m_pkthdr.len -= max_linkhdr; /* * Fill in mbuf with extended UDP header * and addresses and length put into network format. */ ui = mtod(m, struct udpiphdr *); bzero(ui->ui_x1, sizeof(ui->ui_x1)); /* XXX still needed? */ ui->ui_pr = IPPROTO_UDP; ui->ui_src = laddr; ui->ui_dst = faddr; ui->ui_sport = lport; ui->ui_dport = fport; ui->ui_ulen = htons((u_short)len + sizeof(struct udphdr)); /* * Set the Don't Fragment bit in the IP header. */ if (inp->inp_flags & INP_DONTFRAG) { struct ip *ip; ip = (struct ip *)&ui->ui_i; ip->ip_off |= IP_DF; } ipflags = 0; if (inp->inp_socket->so_options & SO_DONTROUTE) ipflags |= IP_ROUTETOIF; if (inp->inp_socket->so_options & SO_BROADCAST) ipflags |= IP_ALLOWBROADCAST; if (inp->inp_vflag & INP_ONESBCAST) ipflags |= IP_SENDONES; /* * Set up checksum and output datagram. */ if (udpcksum) { if (inp->inp_vflag & INP_ONESBCAST) faddr.s_addr = INADDR_BROADCAST; ui->ui_sum = in_pseudo(ui->ui_src.s_addr, faddr.s_addr, htons((u_short)len + sizeof(struct udphdr) + IPPROTO_UDP)); m->m_pkthdr.csum_flags = CSUM_UDP; m->m_pkthdr.csum_data = offsetof(struct udphdr, uh_sum); } else { ui->ui_sum = 0; } ((struct ip *)ui)->ip_len = sizeof (struct udpiphdr) + len; ((struct ip *)ui)->ip_ttl = inp->inp_ip_ttl; /* XXX */ ((struct ip *)ui)->ip_tos = inp->inp_ip_tos; /* XXX */ udpstat.udps_opackets++; if (unlock_udbinfo) INP_INFO_WUNLOCK(&udbinfo); error = ip_output(m, inp->inp_options, NULL, ipflags, inp->inp_moptions, inp); INP_UNLOCK(inp); return (error); release: INP_UNLOCK(inp); if (unlock_udbinfo) INP_INFO_WUNLOCK(&udbinfo); m_freem(m); return (error); } u_long udp_sendspace = 9216; /* really max datagram size */ /* 40 1K datagrams */ SYSCTL_ULONG(_net_inet_udp, UDPCTL_MAXDGRAM, maxdgram, CTLFLAG_RW, &udp_sendspace, 0, "Maximum outgoing UDP datagram size"); u_long udp_recvspace = 40 * (1024 + #ifdef INET6 sizeof(struct sockaddr_in6) #else sizeof(struct sockaddr_in) #endif ); SYSCTL_ULONG(_net_inet_udp, UDPCTL_RECVSPACE, recvspace, CTLFLAG_RW, &udp_recvspace, 0, "Maximum space for incoming UDP datagrams"); static void udp_abort(struct socket *so) { struct inpcb *inp; INP_INFO_WLOCK(&udbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_WUNLOCK(&udbinfo); return; /* ??? possible? panic instead? */ } INP_LOCK(inp); soisdisconnected(so); in_pcbdetach(inp); INP_INFO_WUNLOCK(&udbinfo); } static int udp_attach(struct socket *so, int proto, struct thread *td) { struct inpcb *inp; int error; INP_INFO_WLOCK(&udbinfo); inp = sotoinpcb(so); if (inp != 0) { INP_INFO_WUNLOCK(&udbinfo); return EINVAL; } error = soreserve(so, udp_sendspace, udp_recvspace); if (error) { INP_INFO_WUNLOCK(&udbinfo); return error; } error = in_pcballoc(so, &udbinfo, "udpinp"); if (error) { INP_INFO_WUNLOCK(&udbinfo); return error; } inp = (struct inpcb *)so->so_pcb; INP_LOCK(inp); INP_INFO_WUNLOCK(&udbinfo); inp->inp_vflag |= INP_IPV4; inp->inp_ip_ttl = ip_defttl; INP_UNLOCK(inp); return 0; } static int udp_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { struct inpcb *inp; int error; INP_INFO_WLOCK(&udbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_WUNLOCK(&udbinfo); return EINVAL; } INP_LOCK(inp); error = in_pcbbind(inp, nam, td->td_ucred); INP_UNLOCK(inp); INP_INFO_WUNLOCK(&udbinfo); return error; } static int udp_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { struct inpcb *inp; int error; struct sockaddr_in *sin; INP_INFO_WLOCK(&udbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_WUNLOCK(&udbinfo); return EINVAL; } INP_LOCK(inp); if (inp->inp_faddr.s_addr != INADDR_ANY) { INP_UNLOCK(inp); INP_INFO_WUNLOCK(&udbinfo); return EISCONN; } sin = (struct sockaddr_in *)nam; if (jailed(td->td_ucred)) prison_remote_ip(td->td_ucred, 0, &sin->sin_addr.s_addr); error = in_pcbconnect(inp, nam, td->td_ucred); if (error == 0) soisconnected(so); INP_UNLOCK(inp); INP_INFO_WUNLOCK(&udbinfo); return error; } -static int +static void udp_detach(struct socket *so) { struct inpcb *inp; INP_INFO_WLOCK(&udbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_WUNLOCK(&udbinfo); - return EINVAL; } INP_LOCK(inp); in_pcbdetach(inp); INP_INFO_WUNLOCK(&udbinfo); - return 0; } static int udp_disconnect(struct socket *so) { struct inpcb *inp; INP_INFO_WLOCK(&udbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_WUNLOCK(&udbinfo); return EINVAL; } INP_LOCK(inp); if (inp->inp_faddr.s_addr == INADDR_ANY) { INP_INFO_WUNLOCK(&udbinfo); INP_UNLOCK(inp); return ENOTCONN; } in_pcbdisconnect(inp); inp->inp_laddr.s_addr = INADDR_ANY; INP_UNLOCK(inp); INP_INFO_WUNLOCK(&udbinfo); so->so_state &= ~SS_ISCONNECTED; /* XXX */ return 0; } static int udp_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *control, struct thread *td) { struct inpcb *inp; inp = sotoinpcb(so); return udp_output(inp, m, addr, control, td); } int udp_shutdown(struct socket *so) { struct inpcb *inp; INP_INFO_RLOCK(&udbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_RUNLOCK(&udbinfo); return EINVAL; } INP_LOCK(inp); INP_INFO_RUNLOCK(&udbinfo); socantsendmore(so); INP_UNLOCK(inp); return 0; } /* * This is the wrapper function for in_setsockaddr. We just pass down * the pcbinfo for in_setsockaddr to lock. We don't want to do the locking * here because in_setsockaddr will call malloc and might block. */ static int udp_sockaddr(struct socket *so, struct sockaddr **nam) { return (in_setsockaddr(so, nam, &udbinfo)); } /* * This is the wrapper function for in_setpeeraddr. We just pass down * the pcbinfo for in_setpeeraddr to lock. */ static int udp_peeraddr(struct socket *so, struct sockaddr **nam) { return (in_setpeeraddr(so, nam, &udbinfo)); } struct pr_usrreqs udp_usrreqs = { .pru_abort = udp_abort, .pru_attach = udp_attach, .pru_bind = udp_bind, .pru_connect = udp_connect, .pru_control = in_control, .pru_detach = udp_detach, .pru_disconnect = udp_disconnect, .pru_peeraddr = udp_peeraddr, .pru_send = udp_send, .pru_shutdown = udp_shutdown, .pru_sockaddr = udp_sockaddr, .pru_sosetlabel = in_pcbsosetlabel }; diff --git a/sys/netinet6/raw_ip6.c b/sys/netinet6/raw_ip6.c index 8973fdcd65aa..5b6daacbc8e7 100644 --- a/sys/netinet6/raw_ip6.c +++ b/sys/netinet6/raw_ip6.c @@ -1,810 +1,809 @@ /*- * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /*- * Copyright (c) 1982, 1986, 1988, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)raw_ip.c 8.2 (Berkeley) 1/4/94 */ #include "opt_ipsec.h" #include "opt_inet6.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IPSEC #include #include #endif /*IPSEC*/ #ifdef FAST_IPSEC #include #include #endif /* FAST_IPSEC */ #include #define satosin6(sa) ((struct sockaddr_in6 *)(sa)) #define ifatoia6(ifa) ((struct in6_ifaddr *)(ifa)) /* * Raw interface to IP6 protocol. */ extern struct inpcbhead ripcb; extern struct inpcbinfo ripcbinfo; extern u_long rip_sendspace; extern u_long rip_recvspace; struct rip6stat rip6stat; /* * Setup generic address and protocol structures * for raw_input routine, then pass them along with * mbuf chain. */ int rip6_input(mp, offp, proto) struct mbuf **mp; int *offp, proto; { struct mbuf *m = *mp; register struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); register struct inpcb *in6p; struct inpcb *last = 0; struct mbuf *opts = NULL; struct sockaddr_in6 fromsa; rip6stat.rip6s_ipackets++; if (faithprefix_p != NULL && (*faithprefix_p)(&ip6->ip6_dst)) { /* XXX send icmp6 host/port unreach? */ m_freem(m); return IPPROTO_DONE; } init_sin6(&fromsa, m); /* general init */ INP_INFO_RLOCK(&ripcbinfo); LIST_FOREACH(in6p, &ripcb, inp_list) { INP_LOCK(in6p); if ((in6p->in6p_vflag & INP_IPV6) == 0) { docontinue: INP_UNLOCK(in6p); continue; } if (in6p->in6p_ip6_nxt && in6p->in6p_ip6_nxt != proto) goto docontinue; if (!IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_laddr) && !IN6_ARE_ADDR_EQUAL(&in6p->in6p_laddr, &ip6->ip6_dst)) goto docontinue; if (!IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_faddr) && !IN6_ARE_ADDR_EQUAL(&in6p->in6p_faddr, &ip6->ip6_src)) goto docontinue; if (in6p->in6p_cksum != -1) { rip6stat.rip6s_isum++; if (in6_cksum(m, proto, *offp, m->m_pkthdr.len - *offp)) { rip6stat.rip6s_badsum++; goto docontinue; } } if (last) { struct mbuf *n = m_copy(m, 0, (int)M_COPYALL); #if defined(IPSEC) || defined(FAST_IPSEC) /* * Check AH/ESP integrity. */ if (n && ipsec6_in_reject(n, last)) { m_freem(n); #ifdef IPSEC ipsec6stat.in_polvio++; #endif /*IPSEC*/ /* do not inject data into pcb */ } else #endif /*IPSEC || FAST_IPSEC*/ if (n) { if (last->in6p_flags & IN6P_CONTROLOPTS || last->in6p_socket->so_options & SO_TIMESTAMP) ip6_savecontrol(last, n, &opts); /* strip intermediate headers */ m_adj(n, *offp); if (sbappendaddr(&last->in6p_socket->so_rcv, (struct sockaddr *)&fromsa, n, opts) == 0) { m_freem(n); if (opts) m_freem(opts); rip6stat.rip6s_fullsock++; } else sorwakeup(last->in6p_socket); opts = NULL; } INP_UNLOCK(last); } last = in6p; } #if defined(IPSEC) || defined(FAST_IPSEC) /* * Check AH/ESP integrity. */ if (last && ipsec6_in_reject(m, last)) { m_freem(m); #ifdef IPSEC ipsec6stat.in_polvio++; #endif /*IPSEC*/ ip6stat.ip6s_delivered--; /* do not inject data into pcb */ INP_UNLOCK(last); } else #endif /*IPSEC || FAST_IPSEC*/ if (last) { if (last->in6p_flags & IN6P_CONTROLOPTS || last->in6p_socket->so_options & SO_TIMESTAMP) ip6_savecontrol(last, m, &opts); /* strip intermediate headers */ m_adj(m, *offp); if (sbappendaddr(&last->in6p_socket->so_rcv, (struct sockaddr *)&fromsa, m, opts) == 0) { m_freem(m); if (opts) m_freem(opts); rip6stat.rip6s_fullsock++; } else sorwakeup(last->in6p_socket); INP_UNLOCK(last); } else { rip6stat.rip6s_nosock++; if (m->m_flags & M_MCAST) rip6stat.rip6s_nosockmcast++; if (proto == IPPROTO_NONE) m_freem(m); else { char *prvnxtp = ip6_get_prevhdr(m, *offp); /* XXX */ icmp6_error(m, ICMP6_PARAM_PROB, ICMP6_PARAMPROB_NEXTHEADER, prvnxtp - mtod(m, char *)); } ip6stat.ip6s_delivered--; } INP_INFO_RUNLOCK(&ripcbinfo); return IPPROTO_DONE; } void rip6_ctlinput(cmd, sa, d) int cmd; struct sockaddr *sa; void *d; { struct ip6_hdr *ip6; struct mbuf *m; int off = 0; struct ip6ctlparam *ip6cp = NULL; const struct sockaddr_in6 *sa6_src = NULL; void *cmdarg; struct inpcb *(*notify) __P((struct inpcb *, int)) = in6_rtchange; if (sa->sa_family != AF_INET6 || sa->sa_len != sizeof(struct sockaddr_in6)) return; if ((unsigned)cmd >= PRC_NCMDS) return; if (PRC_IS_REDIRECT(cmd)) notify = in6_rtchange, d = NULL; else if (cmd == PRC_HOSTDEAD) d = NULL; else if (inet6ctlerrmap[cmd] == 0) return; /* if the parameter is from icmp6, decode it. */ if (d != NULL) { ip6cp = (struct ip6ctlparam *)d; m = ip6cp->ip6c_m; ip6 = ip6cp->ip6c_ip6; off = ip6cp->ip6c_off; cmdarg = ip6cp->ip6c_cmdarg; sa6_src = ip6cp->ip6c_src; } else { m = NULL; ip6 = NULL; cmdarg = NULL; sa6_src = &sa6_any; } (void) in6_pcbnotify(&ripcbinfo, sa, 0, (const struct sockaddr *)sa6_src, 0, cmd, cmdarg, notify); } /* * Generate IPv6 header and pass packet to ip6_output. * Tack on options user may have setup with control call. */ int #if __STDC__ rip6_output(struct mbuf *m, ...) #else rip6_output(m, va_alist) struct mbuf *m; va_dcl #endif { struct mbuf *control; struct socket *so; struct sockaddr_in6 *dstsock; struct in6_addr *dst; struct ip6_hdr *ip6; struct inpcb *in6p; u_int plen = m->m_pkthdr.len; int error = 0; struct ip6_pktopts opt, *optp; struct ifnet *oifp = NULL; int type = 0, code = 0; /* for ICMPv6 output statistics only */ int priv = 0; int scope_ambiguous = 0; struct in6_addr *in6a; va_list ap; va_start(ap, m); so = va_arg(ap, struct socket *); dstsock = va_arg(ap, struct sockaddr_in6 *); control = va_arg(ap, struct mbuf *); va_end(ap); in6p = sotoin6pcb(so); INP_LOCK(in6p); priv = 0; if (so->so_cred->cr_uid == 0) priv = 1; dst = &dstsock->sin6_addr; if (control) { if ((error = ip6_setpktopts(control, &opt, in6p->in6p_outputopts, priv, so->so_proto->pr_protocol)) != 0) { goto bad; } optp = &opt; } else optp = in6p->in6p_outputopts; /* * Check and convert scope zone ID into internal form. * XXX: we may still need to determine the zone later. */ if (!(so->so_state & SS_ISCONNECTED)) { if (dstsock->sin6_scope_id == 0 && !ip6_use_defzone) scope_ambiguous = 1; if ((error = sa6_embedscope(dstsock, ip6_use_defzone)) != 0) goto bad; } /* * For an ICMPv6 packet, we should know its type and code * to update statistics. */ if (so->so_proto->pr_protocol == IPPROTO_ICMPV6) { struct icmp6_hdr *icmp6; if (m->m_len < sizeof(struct icmp6_hdr) && (m = m_pullup(m, sizeof(struct icmp6_hdr))) == NULL) { error = ENOBUFS; goto bad; } icmp6 = mtod(m, struct icmp6_hdr *); type = icmp6->icmp6_type; code = icmp6->icmp6_code; } M_PREPEND(m, sizeof(*ip6), M_DONTWAIT); if (m == NULL) { error = ENOBUFS; goto bad; } ip6 = mtod(m, struct ip6_hdr *); /* * Source address selection. */ if ((in6a = in6_selectsrc(dstsock, optp, in6p->in6p_moptions, NULL, &in6p->in6p_laddr, &oifp, &error)) == NULL) { if (error == 0) error = EADDRNOTAVAIL; goto bad; } ip6->ip6_src = *in6a; if (oifp && scope_ambiguous) { /* * Application should provide a proper zone ID or the use of * default zone IDs should be enabled. Unfortunately, some * applications do not behave as it should, so we need a * workaround. Even if an appropriate ID is not determined * (when it's required), if we can determine the outgoing * interface. determine the zone ID based on the interface. */ error = in6_setscope(&dstsock->sin6_addr, oifp, NULL); if (error != 0) goto bad; } ip6->ip6_dst = dstsock->sin6_addr; /* fill in the rest of the IPv6 header fields */ ip6->ip6_flow = (ip6->ip6_flow & ~IPV6_FLOWINFO_MASK) | (in6p->in6p_flowinfo & IPV6_FLOWINFO_MASK); ip6->ip6_vfc = (ip6->ip6_vfc & ~IPV6_VERSION_MASK) | (IPV6_VERSION & IPV6_VERSION_MASK); /* ip6_plen will be filled in ip6_output, so not fill it here. */ ip6->ip6_nxt = in6p->in6p_ip6_nxt; ip6->ip6_hlim = in6_selecthlim(in6p, oifp); if (so->so_proto->pr_protocol == IPPROTO_ICMPV6 || in6p->in6p_cksum != -1) { struct mbuf *n; int off; u_int16_t *p; /* compute checksum */ if (so->so_proto->pr_protocol == IPPROTO_ICMPV6) off = offsetof(struct icmp6_hdr, icmp6_cksum); else off = in6p->in6p_cksum; if (plen < off + 1) { error = EINVAL; goto bad; } off += sizeof(struct ip6_hdr); n = m; while (n && n->m_len <= off) { off -= n->m_len; n = n->m_next; } if (!n) goto bad; p = (u_int16_t *)(mtod(n, caddr_t) + off); *p = 0; *p = in6_cksum(m, ip6->ip6_nxt, sizeof(*ip6), plen); } error = ip6_output(m, optp, NULL, 0, in6p->in6p_moptions, &oifp, in6p); if (so->so_proto->pr_protocol == IPPROTO_ICMPV6) { if (oifp) icmp6_ifoutstat_inc(oifp, type, code); icmp6stat.icp6s_outhist[type]++; } else rip6stat.rip6s_opackets++; goto freectl; bad: if (m) m_freem(m); freectl: if (control) { ip6_clearpktopts(&opt, -1); m_freem(control); } INP_UNLOCK(in6p); return (error); } /* * Raw IPv6 socket option processing. */ int rip6_ctloutput(so, sopt) struct socket *so; struct sockopt *sopt; { int error; if (sopt->sopt_level == IPPROTO_ICMPV6) /* * XXX: is it better to call icmp6_ctloutput() directly * from protosw? */ return (icmp6_ctloutput(so, sopt)); else if (sopt->sopt_level != IPPROTO_IPV6) return (EINVAL); error = 0; switch (sopt->sopt_dir) { case SOPT_GET: switch (sopt->sopt_name) { case MRT6_INIT: case MRT6_DONE: case MRT6_ADD_MIF: case MRT6_DEL_MIF: case MRT6_ADD_MFC: case MRT6_DEL_MFC: case MRT6_PIM: error = ip6_mrouter_get(so, sopt); break; case IPV6_CHECKSUM: error = ip6_raw_ctloutput(so, sopt); break; default: error = ip6_ctloutput(so, sopt); break; } break; case SOPT_SET: switch (sopt->sopt_name) { case MRT6_INIT: case MRT6_DONE: case MRT6_ADD_MIF: case MRT6_DEL_MIF: case MRT6_ADD_MFC: case MRT6_DEL_MFC: case MRT6_PIM: error = ip6_mrouter_set(so, sopt); break; case IPV6_CHECKSUM: error = ip6_raw_ctloutput(so, sopt); break; default: error = ip6_ctloutput(so, sopt); break; } break; } return (error); } static int rip6_attach(struct socket *so, int proto, struct thread *td) { struct inpcb *inp; struct icmp6_filter *filter; int error, s; INP_INFO_WLOCK(&ripcbinfo); inp = sotoinpcb(so); if (inp) { INP_INFO_WUNLOCK(&ripcbinfo); panic("rip6_attach"); } if (td && (error = suser(td)) != 0) { INP_INFO_WUNLOCK(&ripcbinfo); return error; } error = soreserve(so, rip_sendspace, rip_recvspace); if (error) { INP_INFO_WUNLOCK(&ripcbinfo); return error; } MALLOC(filter, struct icmp6_filter *, sizeof(struct icmp6_filter), M_PCB, M_NOWAIT); if (filter == NULL) { INP_INFO_WUNLOCK(&ripcbinfo); return ENOMEM; } s = splnet(); error = in_pcballoc(so, &ripcbinfo, "raw6inp"); splx(s); if (error) { INP_INFO_WUNLOCK(&ripcbinfo); FREE(filter, M_PCB); return error; } inp = (struct inpcb *)so->so_pcb; INP_LOCK(inp); INP_INFO_WUNLOCK(&ripcbinfo); inp->inp_vflag |= INP_IPV6; inp->in6p_ip6_nxt = (long)proto; inp->in6p_hops = -1; /* use kernel default */ inp->in6p_cksum = -1; inp->in6p_icmp6filt = filter; ICMP6_FILTER_SETPASSALL(inp->in6p_icmp6filt); INP_UNLOCK(inp); return 0; } -static int +static void rip6_detach(struct socket *so) { struct inpcb *inp; INP_INFO_WLOCK(&ripcbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_WUNLOCK(&ripcbinfo); panic("rip6_detach"); } /* xxx: RSVP */ if (so == ip6_mrouter) ip6_mrouter_done(); if (inp->in6p_icmp6filt) { FREE(inp->in6p_icmp6filt, M_PCB); inp->in6p_icmp6filt = NULL; } INP_LOCK(inp); in6_pcbdetach(inp); INP_INFO_WUNLOCK(&ripcbinfo); - return 0; } static void rip6_abort(struct socket *so) { soisdisconnected(so); rip6_detach(so); } static int rip6_disconnect(struct socket *so) { struct inpcb *inp = sotoinpcb(so); if ((so->so_state & SS_ISCONNECTED) == 0) return ENOTCONN; inp->in6p_faddr = in6addr_any; rip6_abort(so); return 0; } static int rip6_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { struct inpcb *inp = sotoinpcb(so); struct sockaddr_in6 *addr = (struct sockaddr_in6 *)nam; struct ifaddr *ia = NULL; int error = 0; if (nam->sa_len != sizeof(*addr)) return EINVAL; if (TAILQ_EMPTY(&ifnet) || addr->sin6_family != AF_INET6) return EADDRNOTAVAIL; if ((error = sa6_embedscope(addr, ip6_use_defzone)) != 0) return(error); if (!IN6_IS_ADDR_UNSPECIFIED(&addr->sin6_addr) && (ia = ifa_ifwithaddr((struct sockaddr *)addr)) == 0) return EADDRNOTAVAIL; if (ia && ((struct in6_ifaddr *)ia)->ia6_flags & (IN6_IFF_ANYCAST|IN6_IFF_NOTREADY| IN6_IFF_DETACHED|IN6_IFF_DEPRECATED)) { return (EADDRNOTAVAIL); } INP_INFO_WLOCK(&ripcbinfo); INP_LOCK(inp); inp->in6p_laddr = addr->sin6_addr; INP_UNLOCK(inp); INP_INFO_WUNLOCK(&ripcbinfo); return 0; } static int rip6_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { struct inpcb *inp = sotoinpcb(so); struct sockaddr_in6 *addr = (struct sockaddr_in6 *)nam; struct in6_addr *in6a = NULL; struct ifnet *ifp = NULL; int error = 0, scope_ambiguous = 0; if (nam->sa_len != sizeof(*addr)) return EINVAL; if (TAILQ_EMPTY(&ifnet)) return EADDRNOTAVAIL; if (addr->sin6_family != AF_INET6) return EAFNOSUPPORT; /* * Application should provide a proper zone ID or the use of * default zone IDs should be enabled. Unfortunately, some * applications do not behave as it should, so we need a * workaround. Even if an appropriate ID is not determined, * we'll see if we can determine the outgoing interface. If we * can, determine the zone ID based on the interface below. */ if (addr->sin6_scope_id == 0 && !ip6_use_defzone) scope_ambiguous = 1; if ((error = sa6_embedscope(addr, ip6_use_defzone)) != 0) return(error); INP_INFO_WLOCK(&ripcbinfo); INP_LOCK(inp); /* Source address selection. XXX: need pcblookup? */ in6a = in6_selectsrc(addr, inp->in6p_outputopts, inp->in6p_moptions, NULL, &inp->in6p_laddr, &ifp, &error); if (in6a == NULL) { INP_UNLOCK(inp); INP_INFO_WUNLOCK(&ripcbinfo); return (error ? error : EADDRNOTAVAIL); } /* XXX: see above */ if (ifp && scope_ambiguous && (error = in6_setscope(&addr->sin6_addr, ifp, NULL)) != 0) { INP_UNLOCK(inp); INP_INFO_WUNLOCK(&ripcbinfo); return(error); } inp->in6p_faddr = addr->sin6_addr; inp->in6p_laddr = *in6a; soisconnected(so); INP_UNLOCK(inp); INP_INFO_WUNLOCK(&ripcbinfo); return 0; } static int rip6_shutdown(struct socket *so) { struct inpcb *inp; INP_INFO_RLOCK(&ripcbinfo); inp = sotoinpcb(so); INP_LOCK(inp); INP_INFO_RUNLOCK(&ripcbinfo); socantsendmore(so); INP_UNLOCK(inp); return 0; } static int rip6_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, struct mbuf *control, struct thread *td) { struct inpcb *inp = sotoinpcb(so); struct sockaddr_in6 tmp; struct sockaddr_in6 *dst; int ret; INP_INFO_WLOCK(&ripcbinfo); /* always copy sockaddr to avoid overwrites */ /* Unlocked read. */ if (so->so_state & SS_ISCONNECTED) { if (nam) { INP_INFO_WUNLOCK(&ripcbinfo); m_freem(m); return EISCONN; } /* XXX */ bzero(&tmp, sizeof(tmp)); tmp.sin6_family = AF_INET6; tmp.sin6_len = sizeof(struct sockaddr_in6); bcopy(&inp->in6p_faddr, &tmp.sin6_addr, sizeof(struct in6_addr)); dst = &tmp; } else { if (nam == NULL) { INP_INFO_WUNLOCK(&ripcbinfo); m_freem(m); return ENOTCONN; } if (nam->sa_len != sizeof(struct sockaddr_in6)) { INP_INFO_WUNLOCK(&ripcbinfo); m_freem(m); return(EINVAL); } tmp = *(struct sockaddr_in6 *)nam; dst = &tmp; if (dst->sin6_family == AF_UNSPEC) { /* * XXX: we allow this case for backward * compatibility to buggy applications that * rely on old (and wrong) kernel behavior. */ log(LOG_INFO, "rip6 SEND: address family is " "unspec. Assume AF_INET6\n"); dst->sin6_family = AF_INET6; } else if (dst->sin6_family != AF_INET6) { INP_INFO_WUNLOCK(&ripcbinfo); m_freem(m); return(EAFNOSUPPORT); } } ret = rip6_output(m, so, dst, control); INP_INFO_WUNLOCK(&ripcbinfo); return (ret); } struct pr_usrreqs rip6_usrreqs = { .pru_abort = rip6_abort, .pru_attach = rip6_attach, .pru_bind = rip6_bind, .pru_connect = rip6_connect, .pru_control = in6_control, .pru_detach = rip6_detach, .pru_disconnect = rip6_disconnect, .pru_peeraddr = in6_setpeeraddr, .pru_send = rip6_send, .pru_shutdown = rip6_shutdown, .pru_sockaddr = in6_setsockaddr, }; diff --git a/sys/netinet6/udp6_usrreq.c b/sys/netinet6/udp6_usrreq.c index f58019e4ea7c..19b894f3de38 100644 --- a/sys/netinet6/udp6_usrreq.c +++ b/sys/netinet6/udp6_usrreq.c @@ -1,820 +1,819 @@ /* $FreeBSD$ */ /* $KAME: udp6_usrreq.c,v 1.27 2001/05/21 05:45:10 jinmei Exp $ */ /*- * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /*- * Copyright (c) 1982, 1986, 1989, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)udp_var.h 8.1 (Berkeley) 6/10/93 */ #include "opt_inet.h" #include "opt_inet6.h" #include "opt_ipsec.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IPSEC #include #include #endif /* IPSEC */ #ifdef FAST_IPSEC #include #include #endif /* FAST_IPSEC */ /* * UDP protocol inplementation. * Per RFC 768, August, 1980. */ extern struct protosw inetsw[]; -static int udp6_detach __P((struct socket *so)); +static void udp6_detach __P((struct socket *so)); int udp6_input(mp, offp, proto) struct mbuf **mp; int *offp, proto; { struct mbuf *m = *mp, *opts; register struct ip6_hdr *ip6; register struct udphdr *uh; register struct inpcb *in6p; int off = *offp; int plen, ulen; struct sockaddr_in6 fromsa; opts = NULL; ip6 = mtod(m, struct ip6_hdr *); if (faithprefix_p != NULL && (*faithprefix_p)(&ip6->ip6_dst)) { /* XXX send icmp6 host/port unreach? */ m_freem(m); return IPPROTO_DONE; } #ifndef PULLDOWN_TEST IP6_EXTHDR_CHECK(m, off, sizeof(struct udphdr), IPPROTO_DONE); ip6 = mtod(m, struct ip6_hdr *); uh = (struct udphdr *)((caddr_t)ip6 + off); #else IP6_EXTHDR_GET(uh, struct udphdr *, m, off, sizeof(*uh)); if (!uh) return IPPROTO_DONE; #endif udpstat.udps_ipackets++; plen = ntohs(ip6->ip6_plen) - off + sizeof(*ip6); ulen = ntohs((u_short)uh->uh_ulen); if (plen != ulen) { udpstat.udps_badlen++; goto bad; } /* * Checksum extended UDP header and data. */ if (uh->uh_sum == 0) { udpstat.udps_nosum++; goto bad; } if (in6_cksum(m, IPPROTO_UDP, off, ulen) != 0) { udpstat.udps_badsum++; goto bad; } if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { struct inpcb *last; /* * Deliver a multicast datagram to all sockets * for which the local and remote addresses and ports match * those of the incoming datagram. This allows more than * one process to receive multicasts on the same port. * (This really ought to be done for unicast datagrams as * well, but that would cause problems with existing * applications that open both address-specific sockets and * a wildcard socket listening to the same port -- they would * end up receiving duplicates of every unicast datagram. * Those applications open the multiple sockets to overcome an * inadequacy of the UDP socket interface, but for backwards * compatibility we avoid the problem here rather than * fixing the interface. Maybe 4.5BSD will remedy this?) */ /* * In a case that laddr should be set to the link-local * address (this happens in RIPng), the multicast address * specified in the received packet does not match with * laddr. To cure this situation, the matching is relaxed * if the receiving interface is the same as one specified * in the socket and if the destination multicast address * matches one of the multicast groups specified in the socket. */ /* * Construct sockaddr format source address. */ init_sin6(&fromsa, m); fromsa.sin6_port = uh->uh_sport; /* * KAME note: traditionally we dropped udpiphdr from mbuf here. * We need udphdr for IPsec processing so we do that later. */ /* * Locate pcb(s) for datagram. * (Algorithm copied from raw_intr().) */ last = NULL; LIST_FOREACH(in6p, &udb, inp_list) { if ((in6p->inp_vflag & INP_IPV6) == 0) continue; if (in6p->in6p_lport != uh->uh_dport) continue; if (!IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_laddr)) { if (!IN6_ARE_ADDR_EQUAL(&in6p->in6p_laddr, &ip6->ip6_dst)) continue; } if (!IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_faddr)) { if (!IN6_ARE_ADDR_EQUAL(&in6p->in6p_faddr, &ip6->ip6_src) || in6p->in6p_fport != uh->uh_sport) continue; } if (last != NULL) { struct mbuf *n; #if defined(IPSEC) || defined(FAST_IPSEC) /* * Check AH/ESP integrity. */ if (ipsec6_in_reject(m, last)) { #ifdef IPSEC ipsec6stat.in_polvio++; #endif /* IPSEC */ /* do not inject data into pcb */ } else #endif /*IPSEC || FAST_IPSEC*/ if ((n = m_copy(m, 0, M_COPYALL)) != NULL) { /* * KAME NOTE: do not * m_copy(m, offset, ...) above. * sbappendaddr() expects M_PKTHDR, * and m_copy() will copy M_PKTHDR * only if offset is 0. */ if (last->in6p_flags & IN6P_CONTROLOPTS || last->in6p_socket->so_options & SO_TIMESTAMP) ip6_savecontrol(last, n, &opts); m_adj(n, off + sizeof(struct udphdr)); if (sbappendaddr(&last->in6p_socket->so_rcv, (struct sockaddr *)&fromsa, n, opts) == 0) { m_freem(n); if (opts) m_freem(opts); udpstat.udps_fullsock++; } else sorwakeup(last->in6p_socket); opts = NULL; } } last = in6p; /* * Don't look for additional matches if this one does * not have either the SO_REUSEPORT or SO_REUSEADDR * socket options set. This heuristic avoids searching * through all pcbs in the common case of a non-shared * port. It assumes that an application will never * clear these options after setting them. */ if ((last->in6p_socket->so_options & (SO_REUSEPORT|SO_REUSEADDR)) == 0) break; } if (last == NULL) { /* * No matching pcb found; discard datagram. * (No need to send an ICMP Port Unreachable * for a broadcast or multicast datgram.) */ udpstat.udps_noport++; udpstat.udps_noportmcast++; goto bad; } #if defined(IPSEC) || defined(FAST_IPSEC) /* * Check AH/ESP integrity. */ if (ipsec6_in_reject(m, last)) { #ifdef IPSEC ipsec6stat.in_polvio++; #endif /* IPSEC */ goto bad; } #endif /*IPSEC || FAST_IPSEC*/ if (last->in6p_flags & IN6P_CONTROLOPTS || last->in6p_socket->so_options & SO_TIMESTAMP) ip6_savecontrol(last, m, &opts); m_adj(m, off + sizeof(struct udphdr)); if (sbappendaddr(&last->in6p_socket->so_rcv, (struct sockaddr *)&fromsa, m, opts) == 0) { udpstat.udps_fullsock++; goto bad; } sorwakeup(last->in6p_socket); return IPPROTO_DONE; } /* * Locate pcb for datagram. */ in6p = in6_pcblookup_hash(&udbinfo, &ip6->ip6_src, uh->uh_sport, &ip6->ip6_dst, uh->uh_dport, 1, m->m_pkthdr.rcvif); if (in6p == 0) { if (log_in_vain) { char buf[INET6_ADDRSTRLEN]; strcpy(buf, ip6_sprintf(&ip6->ip6_dst)); log(LOG_INFO, "Connection attempt to UDP [%s]:%d from [%s]:%d\n", buf, ntohs(uh->uh_dport), ip6_sprintf(&ip6->ip6_src), ntohs(uh->uh_sport)); } udpstat.udps_noport++; if (m->m_flags & M_MCAST) { printf("UDP6: M_MCAST is set in a unicast packet.\n"); udpstat.udps_noportmcast++; goto bad; } icmp6_error(m, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOPORT, 0); return IPPROTO_DONE; } #if defined(IPSEC) || defined(FAST_IPSEC) /* * Check AH/ESP integrity. */ if (ipsec6_in_reject(m, in6p)) { #ifdef IPSEC ipsec6stat.in_polvio++; #endif /* IPSEC */ goto bad; } #endif /*IPSEC || FAST_IPSEC*/ /* * Construct sockaddr format source address. * Stuff source address and datagram in user buffer. */ init_sin6(&fromsa, m); fromsa.sin6_port = uh->uh_sport; if (in6p->in6p_flags & IN6P_CONTROLOPTS || in6p->in6p_socket->so_options & SO_TIMESTAMP) ip6_savecontrol(in6p, m, &opts); m_adj(m, off + sizeof(struct udphdr)); if (sbappendaddr(&in6p->in6p_socket->so_rcv, (struct sockaddr *)&fromsa, m, opts) == 0) { udpstat.udps_fullsock++; goto bad; } sorwakeup(in6p->in6p_socket); return IPPROTO_DONE; bad: if (m) m_freem(m); if (opts) m_freem(opts); return IPPROTO_DONE; } void udp6_ctlinput(cmd, sa, d) int cmd; struct sockaddr *sa; void *d; { struct udphdr uh; struct ip6_hdr *ip6; struct mbuf *m; int off = 0; struct ip6ctlparam *ip6cp = NULL; const struct sockaddr_in6 *sa6_src = NULL; void *cmdarg; struct inpcb *(*notify) __P((struct inpcb *, int)) = udp_notify; struct udp_portonly { u_int16_t uh_sport; u_int16_t uh_dport; } *uhp; if (sa->sa_family != AF_INET6 || sa->sa_len != sizeof(struct sockaddr_in6)) return; if ((unsigned)cmd >= PRC_NCMDS) return; if (PRC_IS_REDIRECT(cmd)) notify = in6_rtchange, d = NULL; else if (cmd == PRC_HOSTDEAD) d = NULL; else if (inet6ctlerrmap[cmd] == 0) return; /* if the parameter is from icmp6, decode it. */ if (d != NULL) { ip6cp = (struct ip6ctlparam *)d; m = ip6cp->ip6c_m; ip6 = ip6cp->ip6c_ip6; off = ip6cp->ip6c_off; cmdarg = ip6cp->ip6c_cmdarg; sa6_src = ip6cp->ip6c_src; } else { m = NULL; ip6 = NULL; cmdarg = NULL; sa6_src = &sa6_any; } if (ip6) { /* * XXX: We assume that when IPV6 is non NULL, * M and OFF are valid. */ /* check if we can safely examine src and dst ports */ if (m->m_pkthdr.len < off + sizeof(*uhp)) return; bzero(&uh, sizeof(uh)); m_copydata(m, off, sizeof(*uhp), (caddr_t)&uh); (void) in6_pcbnotify(&udbinfo, sa, uh.uh_dport, (struct sockaddr *)ip6cp->ip6c_src, uh.uh_sport, cmd, cmdarg, notify); } else (void) in6_pcbnotify(&udbinfo, sa, 0, (const struct sockaddr *)sa6_src, 0, cmd, cmdarg, notify); } static int udp6_getcred(SYSCTL_HANDLER_ARGS) { struct xucred xuc; struct sockaddr_in6 addrs[2]; struct inpcb *inp; int error, s; error = suser(req->td); if (error) return (error); if (req->newlen != sizeof(addrs)) return (EINVAL); if (req->oldlen != sizeof(struct xucred)) return (EINVAL); error = SYSCTL_IN(req, addrs, sizeof(addrs)); if (error) return (error); if ((error = sa6_embedscope(&addrs[0], ip6_use_defzone)) != 0 || (error = sa6_embedscope(&addrs[1], ip6_use_defzone)) != 0) { return (error); } s = splnet(); inp = in6_pcblookup_hash(&udbinfo, &addrs[1].sin6_addr, addrs[1].sin6_port, &addrs[0].sin6_addr, addrs[0].sin6_port, 1, NULL); if (!inp || !inp->inp_socket) { error = ENOENT; goto out; } cru2x(inp->inp_socket->so_cred, &xuc); error = SYSCTL_OUT(req, &xuc, sizeof(struct xucred)); out: splx(s); return (error); } SYSCTL_PROC(_net_inet6_udp6, OID_AUTO, getcred, CTLTYPE_OPAQUE|CTLFLAG_RW, 0, 0, udp6_getcred, "S,xucred", "Get the xucred of a UDP6 connection"); static void udp6_abort(struct socket *so) { struct inpcb *inp; int s; INP_INFO_WLOCK(&udbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_WUNLOCK(&udbinfo); return; /* ??? possible? panic instead? */ } soisdisconnected(so); s = splnet(); INP_LOCK(inp); in6_pcbdetach(inp); INP_INFO_WUNLOCK(&udbinfo); splx(s); } static int udp6_attach(struct socket *so, int proto, struct thread *td) { struct inpcb *inp; int s, error; INP_INFO_WLOCK(&udbinfo); inp = sotoinpcb(so); if (inp != 0) { INP_INFO_WUNLOCK(&udbinfo); return EINVAL; } if (so->so_snd.sb_hiwat == 0 || so->so_rcv.sb_hiwat == 0) { error = soreserve(so, udp_sendspace, udp_recvspace); if (error) { INP_INFO_WUNLOCK(&udbinfo); return error; } } s = splnet(); error = in_pcballoc(so, &udbinfo, "udp6inp"); splx(s); if (error) { INP_INFO_WUNLOCK(&udbinfo); return error; } inp = (struct inpcb *)so->so_pcb; INP_LOCK(inp); INP_INFO_WUNLOCK(&udbinfo); inp->inp_vflag |= INP_IPV6; if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) inp->inp_vflag |= INP_IPV4; inp->in6p_hops = -1; /* use kernel default */ inp->in6p_cksum = -1; /* just to be sure */ /* * XXX: ugly!! * IPv4 TTL initialization is necessary for an IPv6 socket as well, * because the socket may be bound to an IPv6 wildcard address, * which may match an IPv4-mapped IPv6 address. */ inp->inp_ip_ttl = ip_defttl; INP_UNLOCK(inp); return 0; } static int udp6_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { struct inpcb *inp; int s, error; INP_INFO_WLOCK(&udbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_WUNLOCK(&udbinfo); return EINVAL; } INP_LOCK(inp); inp->inp_vflag &= ~INP_IPV4; inp->inp_vflag |= INP_IPV6; if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) { struct sockaddr_in6 *sin6_p; sin6_p = (struct sockaddr_in6 *)nam; if (IN6_IS_ADDR_UNSPECIFIED(&sin6_p->sin6_addr)) inp->inp_vflag |= INP_IPV4; else if (IN6_IS_ADDR_V4MAPPED(&sin6_p->sin6_addr)) { struct sockaddr_in sin; in6_sin6_2_sin(&sin, sin6_p); inp->inp_vflag |= INP_IPV4; inp->inp_vflag &= ~INP_IPV6; s = splnet(); error = in_pcbbind(inp, (struct sockaddr *)&sin, td->td_ucred); goto out; } } s = splnet(); error = in6_pcbbind(inp, nam, td->td_ucred); out: INP_UNLOCK(inp); INP_INFO_WUNLOCK(&udbinfo); splx(s); return error; } static int udp6_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { struct inpcb *inp; int s, error; INP_INFO_WLOCK(&udbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_WUNLOCK(&udbinfo); return EINVAL; } INP_LOCK(inp); if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) { struct sockaddr_in6 *sin6_p; sin6_p = (struct sockaddr_in6 *)nam; if (IN6_IS_ADDR_V4MAPPED(&sin6_p->sin6_addr)) { struct sockaddr_in sin; if (inp->inp_faddr.s_addr != INADDR_ANY) return EISCONN; in6_sin6_2_sin(&sin, sin6_p); s = splnet(); error = in_pcbconnect(inp, (struct sockaddr *)&sin, td->td_ucred); splx(s); if (error == 0) { inp->inp_vflag |= INP_IPV4; inp->inp_vflag &= ~INP_IPV6; soisconnected(so); } goto out; } } if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) { error = EISCONN; goto out; } s = splnet(); error = in6_pcbconnect(inp, nam, td->td_ucred); splx(s); if (error == 0) { if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) { /* should be non mapped addr */ inp->inp_vflag &= ~INP_IPV4; inp->inp_vflag |= INP_IPV6; } soisconnected(so); } out: INP_UNLOCK(inp); INP_INFO_WUNLOCK(&udbinfo); return error; } -static int +static void udp6_detach(struct socket *so) { struct inpcb *inp; int s; INP_INFO_WLOCK(&udbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_WUNLOCK(&udbinfo); - return EINVAL; + return; } INP_LOCK(inp); s = splnet(); in6_pcbdetach(inp); splx(s); INP_INFO_WUNLOCK(&udbinfo); - return 0; } static int udp6_disconnect(struct socket *so) { struct inpcb *inp; int error, s; INP_INFO_WLOCK(&udbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_WUNLOCK(&udbinfo); return EINVAL; } INP_LOCK(inp); #ifdef INET if (inp->inp_vflag & INP_IPV4) { struct pr_usrreqs *pru; pru = inetsw[ip_protox[IPPROTO_UDP]].pr_usrreqs; error = (*pru->pru_disconnect)(so); goto out; } #endif if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) { error = ENOTCONN; goto out; } s = splnet(); in6_pcbdisconnect(inp); inp->in6p_laddr = in6addr_any; splx(s); /* XXXRW: so_state locking? */ so->so_state &= ~SS_ISCONNECTED; /* XXX */ out: INP_UNLOCK(inp); INP_INFO_WUNLOCK(&udbinfo); return 0; } static int udp6_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *control, struct thread *td) { struct inpcb *inp; int error = 0; INP_INFO_WLOCK(&udbinfo); inp = sotoinpcb(so); if (inp == 0) { INP_INFO_WUNLOCK(&udbinfo); m_freem(m); return EINVAL; } INP_LOCK(inp); if (addr) { if (addr->sa_len != sizeof(struct sockaddr_in6)) { error = EINVAL; goto bad; } if (addr->sa_family != AF_INET6) { error = EAFNOSUPPORT; goto bad; } } #ifdef INET if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) { int hasv4addr; struct sockaddr_in6 *sin6 = 0; if (addr == 0) hasv4addr = (inp->inp_vflag & INP_IPV4); else { sin6 = (struct sockaddr_in6 *)addr; hasv4addr = IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr) ? 1 : 0; } if (hasv4addr) { struct pr_usrreqs *pru; if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr) && !IN6_IS_ADDR_V4MAPPED(&inp->in6p_laddr)) { /* * when remote addr is IPv4-mapped * address, local addr should not be * an IPv6 address; since you cannot * determine how to map IPv6 source * address to IPv4. */ error = EINVAL; goto out; } if (sin6) in6_sin6_2_sin_in_sock(addr); pru = inetsw[ip_protox[IPPROTO_UDP]].pr_usrreqs; error = ((*pru->pru_send)(so, flags, m, addr, control, td)); /* addr will just be freed in sendit(). */ goto out; } } #endif error = udp6_output(inp, m, addr, control, td); out: INP_UNLOCK(inp); INP_INFO_WUNLOCK(&udbinfo); return error; bad: INP_UNLOCK(inp); INP_INFO_WUNLOCK(&udbinfo); m_freem(m); return (error); } struct pr_usrreqs udp6_usrreqs = { .pru_abort = udp6_abort, .pru_attach = udp6_attach, .pru_bind = udp6_bind, .pru_connect = udp6_connect, .pru_control = in6_control, .pru_detach = udp6_detach, .pru_disconnect = udp6_disconnect, .pru_peeraddr = in6_mapped_peeraddr, .pru_send = udp6_send, .pru_shutdown = udp_shutdown, .pru_sockaddr = in6_mapped_sockaddr, .pru_sosetlabel = in_pcbsosetlabel }; diff --git a/sys/netipsec/keysock.c b/sys/netipsec/keysock.c index 4c84ed9c3347..9d87b1ffb0ae 100644 --- a/sys/netipsec/keysock.c +++ b/sys/netipsec/keysock.c @@ -1,600 +1,596 @@ /* $FreeBSD$ */ /* $KAME: keysock.c,v 1.25 2001/08/13 20:07:41 itojun Exp $ */ /*- * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "opt_ipsec.h" /* This code has derived from sys/net/rtsock.c on FreeBSD2.2.5 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct key_cb { int key_count; int any_count; }; static struct key_cb key_cb; static struct sockaddr key_dst = { 2, PF_KEY, }; static struct sockaddr key_src = { 2, PF_KEY, }; static int key_sendup0 __P((struct rawcb *, struct mbuf *, int)); struct pfkeystat pfkeystat; /* * key_output() */ int key_output(struct mbuf *m, struct socket *so) { struct sadb_msg *msg; int len, error = 0; int s; if (m == 0) panic("%s: NULL pointer was passed.\n", __func__); pfkeystat.out_total++; pfkeystat.out_bytes += m->m_pkthdr.len; len = m->m_pkthdr.len; if (len < sizeof(struct sadb_msg)) { pfkeystat.out_tooshort++; error = EINVAL; goto end; } if (m->m_len < sizeof(struct sadb_msg)) { if ((m = m_pullup(m, sizeof(struct sadb_msg))) == 0) { pfkeystat.out_nomem++; error = ENOBUFS; goto end; } } M_ASSERTPKTHDR(m); KEYDEBUG(KEYDEBUG_KEY_DUMP, kdebug_mbuf(m)); msg = mtod(m, struct sadb_msg *); pfkeystat.out_msgtype[msg->sadb_msg_type]++; if (len != PFKEY_UNUNIT64(msg->sadb_msg_len)) { pfkeystat.out_invlen++; error = EINVAL; goto end; } /*XXX giant lock*/ s = splnet(); error = key_parse(m, so); m = NULL; splx(s); end: if (m) m_freem(m); return error; } /* * send message to the socket. */ static int key_sendup0(rp, m, promisc) struct rawcb *rp; struct mbuf *m; int promisc; { int error; if (promisc) { struct sadb_msg *pmsg; M_PREPEND(m, sizeof(struct sadb_msg), M_DONTWAIT); if (m && m->m_len < sizeof(struct sadb_msg)) m = m_pullup(m, sizeof(struct sadb_msg)); if (!m) { pfkeystat.in_nomem++; m_freem(m); return ENOBUFS; } m->m_pkthdr.len += sizeof(*pmsg); pmsg = mtod(m, struct sadb_msg *); bzero(pmsg, sizeof(*pmsg)); pmsg->sadb_msg_version = PF_KEY_V2; pmsg->sadb_msg_type = SADB_X_PROMISC; pmsg->sadb_msg_len = PFKEY_UNIT64(m->m_pkthdr.len); /* pid and seq? */ pfkeystat.in_msgtype[pmsg->sadb_msg_type]++; } if (!sbappendaddr(&rp->rcb_socket->so_rcv, (struct sockaddr *)&key_src, m, NULL)) { pfkeystat.in_nomem++; m_freem(m); error = ENOBUFS; } else error = 0; sorwakeup(rp->rcb_socket); return error; } /* XXX this interface should be obsoleted. */ int key_sendup(so, msg, len, target) struct socket *so; struct sadb_msg *msg; u_int len; int target; /*target of the resulting message*/ { struct mbuf *m, *n, *mprev; int tlen; /* sanity check */ if (so == 0 || msg == 0) panic("%s: NULL pointer was passed.\n", __func__); KEYDEBUG(KEYDEBUG_KEY_DUMP, printf("%s: \n", __func__); kdebug_sadb(msg)); /* * we increment statistics here, just in case we have ENOBUFS * in this function. */ pfkeystat.in_total++; pfkeystat.in_bytes += len; pfkeystat.in_msgtype[msg->sadb_msg_type]++; /* * Get mbuf chain whenever possible (not clusters), * to save socket buffer. We'll be generating many SADB_ACQUIRE * messages to listening key sockets. If we simply allocate clusters, * sbappendaddr() will raise ENOBUFS due to too little sbspace(). * sbspace() computes # of actual data bytes AND mbuf region. * * TODO: SADB_ACQUIRE filters should be implemented. */ tlen = len; m = mprev = NULL; while (tlen > 0) { if (tlen == len) { MGETHDR(n, M_DONTWAIT, MT_DATA); n->m_len = MHLEN; } else { MGET(n, M_DONTWAIT, MT_DATA); n->m_len = MLEN; } if (!n) { pfkeystat.in_nomem++; return ENOBUFS; } if (tlen >= MCLBYTES) { /*XXX better threshold? */ MCLGET(n, M_DONTWAIT); if ((n->m_flags & M_EXT) == 0) { m_free(n); m_freem(m); pfkeystat.in_nomem++; return ENOBUFS; } n->m_len = MCLBYTES; } if (tlen < n->m_len) n->m_len = tlen; n->m_next = NULL; if (m == NULL) m = mprev = n; else { mprev->m_next = n; mprev = n; } tlen -= n->m_len; n = NULL; } m->m_pkthdr.len = len; m->m_pkthdr.rcvif = NULL; m_copyback(m, 0, len, (caddr_t)msg); /* avoid duplicated statistics */ pfkeystat.in_total--; pfkeystat.in_bytes -= len; pfkeystat.in_msgtype[msg->sadb_msg_type]--; return key_sendup_mbuf(so, m, target); } /* so can be NULL if target != KEY_SENDUP_ONE */ int key_sendup_mbuf(so, m, target) struct socket *so; struct mbuf *m; int target; { struct mbuf *n; struct keycb *kp; int sendup; struct rawcb *rp; int error = 0; if (m == NULL) panic("key_sendup_mbuf: NULL pointer was passed.\n"); if (so == NULL && target == KEY_SENDUP_ONE) panic("%s: NULL pointer was passed.\n", __func__); pfkeystat.in_total++; pfkeystat.in_bytes += m->m_pkthdr.len; if (m->m_len < sizeof(struct sadb_msg)) { #if 1 m = m_pullup(m, sizeof(struct sadb_msg)); if (m == NULL) { pfkeystat.in_nomem++; return ENOBUFS; } #else /* don't bother pulling it up just for stats */ #endif } if (m->m_len >= sizeof(struct sadb_msg)) { struct sadb_msg *msg; msg = mtod(m, struct sadb_msg *); pfkeystat.in_msgtype[msg->sadb_msg_type]++; } LIST_FOREACH(rp, &rawcb_list, list) { if (rp->rcb_proto.sp_family != PF_KEY) continue; if (rp->rcb_proto.sp_protocol && rp->rcb_proto.sp_protocol != PF_KEY_V2) { continue; } kp = (struct keycb *)rp; /* * If you are in promiscuous mode, and when you get broadcasted * reply, you'll get two PF_KEY messages. * (based on pf_key@inner.net message on 14 Oct 1998) */ if (((struct keycb *)rp)->kp_promisc) { if ((n = m_copy(m, 0, (int)M_COPYALL)) != NULL) { (void)key_sendup0(rp, n, 1); n = NULL; } } /* the exact target will be processed later */ if (so && sotorawcb(so) == rp) continue; sendup = 0; switch (target) { case KEY_SENDUP_ONE: /* the statement has no effect */ if (so && sotorawcb(so) == rp) sendup++; break; case KEY_SENDUP_ALL: sendup++; break; case KEY_SENDUP_REGISTERED: if (kp->kp_registered) sendup++; break; } pfkeystat.in_msgtarget[target]++; if (!sendup) continue; if ((n = m_copy(m, 0, (int)M_COPYALL)) == NULL) { m_freem(m); pfkeystat.in_nomem++; return ENOBUFS; } if ((error = key_sendup0(rp, n, 0)) != 0) { m_freem(m); return error; } n = NULL; } if (so) { error = key_sendup0(sotorawcb(so), m, 0); m = NULL; } else { error = 0; m_freem(m); } return error; } /* * key_abort() * derived from net/rtsock.c:rts_abort() */ static void key_abort(struct socket *so) { raw_usrreqs.pru_abort(so); } /* * key_attach() * derived from net/rtsock.c:rts_attach() */ static int key_attach(struct socket *so, int proto, struct thread *td) { struct keycb *kp; int s, error; if (sotorawcb(so) != 0) return EISCONN; /* XXX panic? */ kp = (struct keycb *)malloc(sizeof *kp, M_PCB, M_WAITOK|M_ZERO); /* XXX */ if (kp == 0) return ENOBUFS; /* * The splnet() is necessary to block protocols from sending * error notifications (like RTM_REDIRECT or RTM_LOSING) while * this PCB is extant but incompletely initialized. * Probably we should try to do more of this work beforehand and * eliminate the spl. */ s = splnet(); so->so_pcb = (caddr_t)kp; error = raw_usrreqs.pru_attach(so, proto, td); kp = (struct keycb *)sotorawcb(so); if (error) { free(kp, M_PCB); so->so_pcb = (caddr_t) 0; splx(s); return error; } kp->kp_promisc = kp->kp_registered = 0; if (kp->kp_raw.rcb_proto.sp_protocol == PF_KEY) /* XXX: AF_KEY */ key_cb.key_count++; key_cb.any_count++; kp->kp_raw.rcb_laddr = &key_src; kp->kp_raw.rcb_faddr = &key_dst; soisconnected(so); so->so_options |= SO_USELOOPBACK; splx(s); return 0; } /* * key_bind() * derived from net/rtsock.c:rts_bind() */ static int key_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { int s, error; s = splnet(); error = raw_usrreqs.pru_bind(so, nam, td); /* xxx just EINVAL */ splx(s); return error; } /* * key_connect() * derived from net/rtsock.c:rts_connect() */ static int key_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { int s, error; s = splnet(); error = raw_usrreqs.pru_connect(so, nam, td); /* XXX just EINVAL */ splx(s); return error; } /* * key_detach() * derived from net/rtsock.c:rts_detach() */ -static int +static void key_detach(struct socket *so) { struct keycb *kp = (struct keycb *)sotorawcb(so); int s, error; - s = splnet(); - if (kp != 0) { - if (kp->kp_raw.rcb_proto.sp_protocol - == PF_KEY) /* XXX: AF_KEY */ - key_cb.key_count--; - key_cb.any_count--; + KASSERT(kp != NULL, ("key_detach: kp == NULL")); + if (kp->kp_raw.rcb_proto.sp_protocol + == PF_KEY) /* XXX: AF_KEY */ + key_cb.key_count--; + key_cb.any_count--; - key_freereg(so); - } - error = raw_usrreqs.pru_detach(so); - splx(s); - return error; + key_freereg(so); + raw_usrreqs.pru_detach(so); } /* * key_disconnect() * derived from net/rtsock.c:key_disconnect() */ static int key_disconnect(struct socket *so) { int s, error; s = splnet(); error = raw_usrreqs.pru_disconnect(so); splx(s); return error; } /* * key_peeraddr() * derived from net/rtsock.c:rts_peeraddr() */ static int key_peeraddr(struct socket *so, struct sockaddr **nam) { int s, error; s = splnet(); error = raw_usrreqs.pru_peeraddr(so, nam); splx(s); return error; } /* * key_send() * derived from net/rtsock.c:rts_send() */ static int key_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, struct mbuf *control, struct thread *td) { int s, error; s = splnet(); error = raw_usrreqs.pru_send(so, flags, m, nam, control, td); splx(s); return error; } /* * key_shutdown() * derived from net/rtsock.c:rts_shutdown() */ static int key_shutdown(struct socket *so) { int s, error; s = splnet(); error = raw_usrreqs.pru_shutdown(so); splx(s); return error; } /* * key_sockaddr() * derived from net/rtsock.c:rts_sockaddr() */ static int key_sockaddr(struct socket *so, struct sockaddr **nam) { int s, error; s = splnet(); error = raw_usrreqs.pru_sockaddr(so, nam); splx(s); return error; } struct pr_usrreqs key_usrreqs = { .pru_abort = key_abort, .pru_attach = key_attach, .pru_bind = key_bind, .pru_connect = key_connect, .pru_detach = key_detach, .pru_disconnect = key_disconnect, .pru_peeraddr = key_peeraddr, .pru_send = key_send, .pru_shutdown = key_shutdown, .pru_sockaddr = key_sockaddr, }; /* sysctl */ SYSCTL_NODE(_net, PF_KEY, key, CTLFLAG_RW, 0, "Key Family"); /* * Definitions of protocols supported in the KEY domain. */ extern struct domain keydomain; struct protosw keysw[] = { { .pr_type = SOCK_RAW, .pr_domain = &keydomain, .pr_protocol = PF_KEY_V2, .pr_flags = PR_ATOMIC|PR_ADDR, .pr_output = key_output, .pr_ctlinput = raw_ctlinput, .pr_init = raw_init, .pr_usrreqs = &key_usrreqs } }; static void key_init0(void) { bzero((caddr_t)&key_cb, sizeof(key_cb)); key_init(); } struct domain keydomain = { .dom_family = PF_KEY, .dom_name = "key", .dom_init = key_init0, .dom_protosw = keysw, .dom_protoswNPROTOSW = &keysw[sizeof(keysw)/sizeof(keysw[0])] }; DOMAIN_SET(key); diff --git a/sys/netipx/ipx_usrreq.c b/sys/netipx/ipx_usrreq.c index b98d5a1cfdda..2125d04dbb12 100644 --- a/sys/netipx/ipx_usrreq.c +++ b/sys/netipx/ipx_usrreq.c @@ -1,673 +1,672 @@ /*- * Copyright (c) 1984, 1985, 1986, 1987, 1993 * The Regents of the University of California. * Copyright (c) 1995, Mike Mitchell * Copyright (c) 2004-2006 Robert N. M. Watson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)ipx_usrreq.c */ #include __FBSDID("$FreeBSD$"); #include "opt_ipx.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * IPX protocol implementation. */ static int ipxsendspace = IPXSNDQ; SYSCTL_INT(_net_ipx_ipx, OID_AUTO, ipxsendspace, CTLFLAG_RW, &ipxsendspace, 0, ""); static int ipxrecvspace = IPXRCVQ; SYSCTL_INT(_net_ipx_ipx, OID_AUTO, ipxrecvspace, CTLFLAG_RW, &ipxrecvspace, 0, ""); static void ipx_usr_abort(struct socket *so); static int ipx_attach(struct socket *so, int proto, struct thread *td); static int ipx_bind(struct socket *so, struct sockaddr *nam, struct thread *td); static int ipx_connect(struct socket *so, struct sockaddr *nam, struct thread *td); -static int ipx_detach(struct socket *so); +static void ipx_detach(struct socket *so); static int ipx_disconnect(struct socket *so); static int ipx_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *control, struct thread *td); static int ipx_shutdown(struct socket *so); static int ripx_attach(struct socket *so, int proto, struct thread *td); static int ipx_output(struct ipxpcb *ipxp, struct mbuf *m0); struct pr_usrreqs ipx_usrreqs = { .pru_abort = ipx_usr_abort, .pru_attach = ipx_attach, .pru_bind = ipx_bind, .pru_connect = ipx_connect, .pru_control = ipx_control, .pru_detach = ipx_detach, .pru_disconnect = ipx_disconnect, .pru_peeraddr = ipx_peeraddr, .pru_send = ipx_send, .pru_shutdown = ipx_shutdown, .pru_sockaddr = ipx_sockaddr, }; struct pr_usrreqs ripx_usrreqs = { .pru_abort = ipx_usr_abort, .pru_attach = ripx_attach, .pru_bind = ipx_bind, .pru_connect = ipx_connect, .pru_control = ipx_control, .pru_detach = ipx_detach, .pru_disconnect = ipx_disconnect, .pru_peeraddr = ipx_peeraddr, .pru_send = ipx_send, .pru_shutdown = ipx_shutdown, .pru_sockaddr = ipx_sockaddr, }; /* * This may also be called for raw listeners. */ void ipx_input(m, ipxp) struct mbuf *m; register struct ipxpcb *ipxp; { register struct ipx *ipx = mtod(m, struct ipx *); struct ifnet *ifp = m->m_pkthdr.rcvif; struct sockaddr_ipx ipx_ipx; KASSERT(ipxp != NULL, ("ipx_input: NULL ipxpcb")); IPX_LOCK_ASSERT(ipxp); /* * Construct sockaddr format source address. * Stuff source address and datagram in user buffer. */ ipx_ipx.sipx_len = sizeof(ipx_ipx); ipx_ipx.sipx_family = AF_IPX; ipx_ipx.sipx_addr = ipx->ipx_sna; ipx_ipx.sipx_zero[0] = '\0'; ipx_ipx.sipx_zero[1] = '\0'; if (ipx_neteqnn(ipx->ipx_sna.x_net, ipx_zeronet) && ifp != NULL) { register struct ifaddr *ifa; for (ifa = TAILQ_FIRST(&ifp->if_addrhead); ifa != NULL; ifa = TAILQ_NEXT(ifa, ifa_link)) { if (ifa->ifa_addr->sa_family == AF_IPX) { ipx_ipx.sipx_addr.x_net = IA_SIPX(ifa)->sipx_addr.x_net; break; } } } ipxp->ipxp_rpt = ipx->ipx_pt; if ((ipxp->ipxp_flags & IPXP_RAWIN) == 0) { m->m_len -= sizeof(struct ipx); m->m_pkthdr.len -= sizeof(struct ipx); m->m_data += sizeof(struct ipx); } if (sbappendaddr(&ipxp->ipxp_socket->so_rcv, (struct sockaddr *)&ipx_ipx, m, NULL) == 0) m_freem(m); else sorwakeup(ipxp->ipxp_socket); } /* * Drop connection, reporting * the specified error. */ void ipx_drop(ipxp, errno) register struct ipxpcb *ipxp; int errno; { struct socket *so = ipxp->ipxp_socket; IPX_LIST_LOCK_ASSERT(); IPX_LOCK_ASSERT(ipxp); /* * someday, in the IPX world * we will generate error protocol packets * announcing that the socket has gone away. * * XXX Probably never. IPX does not have error packets. */ /*if (TCPS_HAVERCVDSYN(tp->t_state)) { tp->t_state = TCPS_CLOSED; tcp_output(tp); }*/ so->so_error = errno; ipx_pcbdisconnect(ipxp); soisdisconnected(so); } static int ipx_output(ipxp, m0) struct ipxpcb *ipxp; struct mbuf *m0; { register struct ipx *ipx; register struct socket *so; register int len = 0; register struct route *ro; struct mbuf *m; struct mbuf *mprev = NULL; IPX_LOCK_ASSERT(ipxp); /* * Calculate data length. */ for (m = m0; m != NULL; m = m->m_next) { mprev = m; len += m->m_len; } /* * Make sure packet is actually of even length. */ if (len & 1) { m = mprev; if ((m->m_flags & M_EXT) == 0 && (m->m_len + m->m_data < &m->m_dat[MLEN])) { mtod(m, char*)[m->m_len++] = 0; } else { struct mbuf *m1 = m_get(M_DONTWAIT, MT_DATA); if (m1 == NULL) { m_freem(m0); return (ENOBUFS); } m1->m_len = 1; * mtod(m1, char *) = 0; m->m_next = m1; } m0->m_pkthdr.len++; } /* * Fill in mbuf with extended IPX header * and addresses and length put into network format. */ m = m0; if (ipxp->ipxp_flags & IPXP_RAWOUT) { ipx = mtod(m, struct ipx *); } else { M_PREPEND(m, sizeof(struct ipx), M_DONTWAIT); if (m == NULL) return (ENOBUFS); ipx = mtod(m, struct ipx *); ipx->ipx_tc = 0; ipx->ipx_pt = ipxp->ipxp_dpt; ipx->ipx_sna = ipxp->ipxp_laddr; ipx->ipx_dna = ipxp->ipxp_faddr; len += sizeof(struct ipx); } ipx->ipx_len = htons((u_short)len); if (ipxp->ipxp_flags & IPXP_CHECKSUM) { ipx->ipx_sum = ipx_cksum(m, len); } else ipx->ipx_sum = 0xffff; /* * Output datagram. */ so = ipxp->ipxp_socket; if (so->so_options & SO_DONTROUTE) return (ipx_outputfl(m, (struct route *)NULL, (so->so_options & SO_BROADCAST) | IPX_ROUTETOIF)); /* * Use cached route for previous datagram if * possible. If the previous net was the same * and the interface was a broadcast medium, or * if the previous destination was identical, * then we are ok. * * NB: We don't handle broadcasts because that * would require 3 subroutine calls. */ ro = &ipxp->ipxp_route; #ifdef ancient_history /* * I think that this will all be handled in ipx_pcbconnect! */ if (ro->ro_rt != NULL) { if(ipx_neteq(ipxp->ipxp_lastdst, ipx->ipx_dna)) { /* * This assumes we have no GH type routes */ if (ro->ro_rt->rt_flags & RTF_HOST) { if (!ipx_hosteq(ipxp->ipxp_lastdst, ipx->ipx_dna)) goto re_route; } if ((ro->ro_rt->rt_flags & RTF_GATEWAY) == 0) { register struct ipx_addr *dst = &satoipx_addr(ro->ro_dst); dst->x_host = ipx->ipx_dna.x_host; } /* * Otherwise, we go through the same gateway * and dst is already set up. */ } else { re_route: RTFREE(ro->ro_rt); ro->ro_rt = NULL; } } ipxp->ipxp_lastdst = ipx->ipx_dna; #endif /* ancient_history */ return (ipx_outputfl(m, ro, so->so_options & SO_BROADCAST)); } int ipx_ctloutput(so, sopt) struct socket *so; struct sockopt *sopt; { struct ipxpcb *ipxp = sotoipxpcb(so); int mask, error, optval; short soptval; struct ipx ioptval; long seq; KASSERT(ipxp != NULL, ("ipx_ctloutput: ipxp == NULL")); error = 0; switch (sopt->sopt_dir) { case SOPT_GET: switch (sopt->sopt_name) { case SO_ALL_PACKETS: mask = IPXP_ALL_PACKETS; goto get_flags; case SO_HEADERS_ON_INPUT: mask = IPXP_RAWIN; goto get_flags; case SO_IPX_CHECKSUM: mask = IPXP_CHECKSUM; goto get_flags; case SO_HEADERS_ON_OUTPUT: mask = IPXP_RAWOUT; get_flags: /* Unlocked read. */ soptval = ipxp->ipxp_flags & mask; error = sooptcopyout(sopt, &soptval, sizeof soptval); break; case SO_DEFAULT_HEADERS: ioptval.ipx_len = 0; ioptval.ipx_sum = 0; ioptval.ipx_tc = 0; IPX_LOCK(ipxp); ioptval.ipx_pt = ipxp->ipxp_dpt; ioptval.ipx_dna = ipxp->ipxp_faddr; ioptval.ipx_sna = ipxp->ipxp_laddr; IPX_UNLOCK(ipxp); error = sooptcopyout(sopt, &soptval, sizeof soptval); break; case SO_SEQNO: IPX_LIST_LOCK(); seq = ipx_pexseq; ipx_pexseq++; IPX_LIST_UNLOCK(); error = sooptcopyout(sopt, &seq, sizeof seq); break; default: error = EINVAL; } break; case SOPT_SET: switch (sopt->sopt_name) { case SO_ALL_PACKETS: mask = IPXP_ALL_PACKETS; goto set_head; case SO_HEADERS_ON_INPUT: mask = IPXP_RAWIN; goto set_head; case SO_IPX_CHECKSUM: mask = IPXP_CHECKSUM; case SO_HEADERS_ON_OUTPUT: mask = IPXP_RAWOUT; set_head: error = sooptcopyin(sopt, &optval, sizeof optval, sizeof optval); if (error) break; IPX_LOCK(ipxp); if (optval) ipxp->ipxp_flags |= mask; else ipxp->ipxp_flags &= ~mask; IPX_UNLOCK(ipxp); break; case SO_DEFAULT_HEADERS: error = sooptcopyin(sopt, &ioptval, sizeof ioptval, sizeof ioptval); if (error) break; /* Unlocked write. */ ipxp->ipxp_dpt = ioptval.ipx_pt; break; #ifdef IPXIP case SO_IPXIP_ROUTE: error = ipxip_route(so, sopt); break; #endif /* IPXIP */ default: error = EINVAL; } break; } return (error); } static void ipx_usr_abort(so) struct socket *so; { struct ipxpcb *ipxp = sotoipxpcb(so); KASSERT(ipxp != NULL, ("ipx_usr_abort: ipxp == NULL")); IPX_LIST_LOCK(); IPX_LOCK(ipxp); ipx_pcbdetach(ipxp); ipx_pcbfree(ipxp); IPX_LIST_UNLOCK(); soisdisconnected(so); } static int ipx_attach(so, proto, td) struct socket *so; int proto; struct thread *td; { struct ipxpcb *ipxp = sotoipxpcb(so); int error; KASSERT(ipxp == NULL, ("ipx_attach: ipxp != NULL")); error = soreserve(so, ipxsendspace, ipxrecvspace); if (error != 0) return (error); IPX_LIST_LOCK(); error = ipx_pcballoc(so, &ipxpcb_list, td); IPX_LIST_UNLOCK(); return (error); } static int ipx_bind(so, nam, td) struct socket *so; struct sockaddr *nam; struct thread *td; { struct ipxpcb *ipxp = sotoipxpcb(so); int error; KASSERT(ipxp != NULL, ("ipx_bind: ipxp == NULL")); IPX_LIST_LOCK(); IPX_LOCK(ipxp); error = ipx_pcbbind(ipxp, nam, td); IPX_UNLOCK(ipxp); IPX_LIST_UNLOCK(); return (error); } static int ipx_connect(so, nam, td) struct socket *so; struct sockaddr *nam; struct thread *td; { struct ipxpcb *ipxp = sotoipxpcb(so); int error; KASSERT(ipxp != NULL, ("ipx_connect: ipxp == NULL")); IPX_LIST_LOCK(); IPX_LOCK(ipxp); if (!ipx_nullhost(ipxp->ipxp_faddr)) { error = EISCONN; goto out; } error = ipx_pcbconnect(ipxp, nam, td); if (error == 0) soisconnected(so); out: IPX_UNLOCK(ipxp); IPX_LIST_UNLOCK(); return (error); } -static int +static void ipx_detach(so) struct socket *so; { struct ipxpcb *ipxp = sotoipxpcb(so); KASSERT(ipxp != NULL, ("ipx_detach: ipxp == NULL")); IPX_LIST_LOCK(); IPX_LOCK(ipxp); ipx_pcbdetach(ipxp); ipx_pcbfree(ipxp); IPX_LIST_UNLOCK(); - return (0); } static int ipx_disconnect(so) struct socket *so; { struct ipxpcb *ipxp = sotoipxpcb(so); int error; KASSERT(ipxp != NULL, ("ipx_disconnect: ipxp == NULL")); IPX_LIST_LOCK(); IPX_LOCK(ipxp); error = 0; if (ipx_nullhost(ipxp->ipxp_faddr)) { error = ENOTCONN; goto out; } ipx_pcbdisconnect(ipxp); soisdisconnected(so); out: IPX_UNLOCK(ipxp); IPX_LIST_UNLOCK(); return (0); } int ipx_peeraddr(so, nam) struct socket *so; struct sockaddr **nam; { struct ipxpcb *ipxp = sotoipxpcb(so); KASSERT(ipxp != NULL, ("ipx_peeraddr: ipxp == NULL")); ipx_setpeeraddr(ipxp, nam); return (0); } static int ipx_send(so, flags, m, nam, control, td) struct socket *so; int flags; struct mbuf *m; struct sockaddr *nam; struct mbuf *control; struct thread *td; { int error; struct ipxpcb *ipxp = sotoipxpcb(so); struct ipx_addr laddr; KASSERT(ipxp != NULL, ("ipxp_send: ipxp == NULL")); /* * Attempt to only acquire the necessary locks: if the socket is * already connected, we don't need to hold the IPX list lock to be * used by ipx_pcbconnect() and ipx_pcbdisconnect(), just the IPX * pcb lock. */ if (nam != NULL) { IPX_LIST_LOCK(); IPX_LOCK(ipxp); laddr = ipxp->ipxp_laddr; if (!ipx_nullhost(ipxp->ipxp_faddr)) { IPX_UNLOCK(ipxp); IPX_LIST_UNLOCK(); error = EISCONN; goto send_release; } /* * Must block input while temporarily connected. */ error = ipx_pcbconnect(ipxp, nam, td); if (error) { IPX_UNLOCK(ipxp); IPX_LIST_UNLOCK(); goto send_release; } } else { IPX_LOCK(ipxp); if (ipx_nullhost(ipxp->ipxp_faddr)) { IPX_UNLOCK(ipxp); error = ENOTCONN; goto send_release; } } error = ipx_output(ipxp, m); m = NULL; if (nam != NULL) { ipx_pcbdisconnect(ipxp); ipxp->ipxp_laddr = laddr; IPX_UNLOCK(ipxp); IPX_LIST_UNLOCK(); } else IPX_UNLOCK(ipxp); send_release: if (m != NULL) m_freem(m); return (error); } static int ipx_shutdown(so) struct socket *so; { KASSERT(so->so_pcb != NULL, ("ipx_shutdown: so_pcb == NULL")); socantsendmore(so); return (0); } int ipx_sockaddr(so, nam) struct socket *so; struct sockaddr **nam; { struct ipxpcb *ipxp = sotoipxpcb(so); KASSERT(ipxp != NULL, ("ipx_sockaddr: ipxp == NULL")); ipx_setsockaddr(ipxp, nam); return (0); } static int ripx_attach(so, proto, td) struct socket *so; int proto; struct thread *td; { int error = 0; struct ipxpcb *ipxp = sotoipxpcb(so); KASSERT(ipxp == NULL, ("ripx_attach: ipxp != NULL")); if (td != NULL && (error = suser(td)) != 0) return (error); /* * We hold the IPX list lock for the duration as address parameters * of the IPX pcb are changed. Since no one else holds a reference * to the ipxpcb yet, we don't need the ipxpcb lock here. */ IPX_LIST_LOCK(); error = ipx_pcballoc(so, &ipxrawpcb_list, td); if (error) goto out; ipxp = sotoipxpcb(so); error = soreserve(so, ipxsendspace, ipxrecvspace); if (error) goto out; ipxp->ipxp_faddr.x_host = ipx_broadhost; ipxp->ipxp_flags = IPXP_RAWIN | IPXP_RAWOUT; out: IPX_LIST_UNLOCK(); return (error); } diff --git a/sys/netipx/spx_usrreq.c b/sys/netipx/spx_usrreq.c index 637ea19cf45c..d9a56397e8f0 100644 --- a/sys/netipx/spx_usrreq.c +++ b/sys/netipx/spx_usrreq.c @@ -1,2057 +1,2056 @@ /*- * Copyright (c) 1984, 1985, 1986, 1987, 1993 * The Regents of the University of California. * Copyright (c) 1995, Mike Mitchell * Copyright (c) 2004-2006 Robert N. M. Watson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)spx_usrreq.h */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * SPX protocol implementation. */ static struct mtx spx_mtx; /* Protects only spx_iss. */ static u_short spx_iss; static u_short spx_newchecks[50]; static int spx_hardnosed; static int spx_use_delack = 0; static int traceallspxs = 0; static struct spx_istat spx_istat; static int spxrexmtthresh = 3; #define SPX_LOCK_INIT() mtx_init(&spx_mtx, "spx_mtx", NULL, MTX_DEF) #define SPX_LOCK() mtx_lock(&spx_mtx) #define SPX_UNLOCK() mtx_unlock(&spx_mtx) /* Following was struct spxstat spxstat; */ #ifndef spxstat #define spxstat spx_istat.newstats #endif static const int spx_backoff[SPX_MAXRXTSHIFT+1] = { 1, 2, 4, 8, 16, 32, 64, 64, 64, 64, 64, 64, 64 }; static void spx_close(struct spxpcb *cb); static void spx_disconnect(struct spxpcb *cb); static void spx_drop(struct spxpcb *cb, int errno); static int spx_output(struct spxpcb *cb, struct mbuf *m0); static int spx_reass(struct spxpcb *cb, struct spx *si); static void spx_setpersist(struct spxpcb *cb); static void spx_template(struct spxpcb *cb); static void spx_timers(struct spxpcb *cb, int timer); static void spx_usrclosed(struct spxpcb *cb); static void spx_usr_abort(struct socket *so); static int spx_accept(struct socket *so, struct sockaddr **nam); static int spx_attach(struct socket *so, int proto, struct thread *td); static int spx_bind(struct socket *so, struct sockaddr *nam, struct thread *td); static int spx_connect(struct socket *so, struct sockaddr *nam, struct thread *td); -static int spx_detach(struct socket *so); +static void spx_detach(struct socket *so); static void spx_pcbdetach(struct ipxpcb *ipxp); static int spx_usr_disconnect(struct socket *so); static int spx_listen(struct socket *so, int backlog, struct thread *td); static int spx_rcvd(struct socket *so, int flags); static int spx_rcvoob(struct socket *so, struct mbuf *m, int flags); static int spx_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *control, struct thread *td); static int spx_shutdown(struct socket *so); static int spx_sp_attach(struct socket *so, int proto, struct thread *td); struct pr_usrreqs spx_usrreqs = { .pru_abort = spx_usr_abort, .pru_accept = spx_accept, .pru_attach = spx_attach, .pru_bind = spx_bind, .pru_connect = spx_connect, .pru_control = ipx_control, .pru_detach = spx_detach, .pru_disconnect = spx_usr_disconnect, .pru_listen = spx_listen, .pru_peeraddr = ipx_peeraddr, .pru_rcvd = spx_rcvd, .pru_rcvoob = spx_rcvoob, .pru_send = spx_send, .pru_shutdown = spx_shutdown, .pru_sockaddr = ipx_sockaddr, }; struct pr_usrreqs spx_usrreq_sps = { .pru_abort = spx_usr_abort, .pru_accept = spx_accept, .pru_attach = spx_sp_attach, .pru_bind = spx_bind, .pru_connect = spx_connect, .pru_control = ipx_control, .pru_detach = spx_detach, .pru_disconnect = spx_usr_disconnect, .pru_listen = spx_listen, .pru_peeraddr = ipx_peeraddr, .pru_rcvd = spx_rcvd, .pru_rcvoob = spx_rcvoob, .pru_send = spx_send, .pru_shutdown = spx_shutdown, .pru_sockaddr = ipx_sockaddr, }; void spx_init(void) { SPX_LOCK_INIT(); spx_iss = 1; /* WRONG !! should fish it out of TODR */ } void spx_input(struct mbuf *m, struct ipxpcb *ipxp) { struct spxpcb *cb; struct spx *si = mtod(m, struct spx *); struct socket *so; struct spx spx_savesi; int dropsocket = 0; short ostate = 0; spxstat.spxs_rcvtotal++; KASSERT(ipxp != NULL, ("spx_input: ipxpcb == NULL")); /* * spx_input() assumes that the caller will hold both the pcb list * lock and also the ipxp lock. spx_input() will release both before * returning, and may in fact trade in the ipxp lock for another pcb * lock following sonewconn(). */ IPX_LIST_LOCK_ASSERT(); IPX_LOCK_ASSERT(ipxp); cb = ipxtospxpcb(ipxp); KASSERT(cb != NULL, ("spx_input: cb == NULL")); if (ipxp->ipxp_flags & IPXP_DROPPED) goto drop; if (m->m_len < sizeof(*si)) { if ((m = m_pullup(m, sizeof(*si))) == NULL) { IPX_UNLOCK(ipxp); IPX_LIST_UNLOCK(); spxstat.spxs_rcvshort++; return; } si = mtod(m, struct spx *); } si->si_seq = ntohs(si->si_seq); si->si_ack = ntohs(si->si_ack); si->si_alo = ntohs(si->si_alo); so = ipxp->ipxp_socket; KASSERT(so != NULL, ("spx_input: so == NULL")); if (so->so_options & SO_DEBUG || traceallspxs) { ostate = cb->s_state; spx_savesi = *si; } if (so->so_options & SO_ACCEPTCONN) { struct spxpcb *ocb = cb; so = sonewconn(so, 0); if (so == NULL) goto drop; /* * This is ugly, but .... * * Mark socket as temporary until we're committed to keeping * it. The code at ``drop'' and ``dropwithreset'' check the * flag dropsocket to see if the temporary socket created * here should be discarded. We mark the socket as * discardable until we're committed to it below in * TCPS_LISTEN. * * XXXRW: In the new world order of real kernel parallelism, * temporarily allocating the socket when we're "not sure" * seems like a bad idea, as we might race to remove it if * the listen socket is closed...? * * We drop the lock of the listen socket ipxp, and acquire * the lock of the new socket ippx. */ dropsocket++; IPX_UNLOCK(ipxp); ipxp = (struct ipxpcb *)so->so_pcb; IPX_LOCK(ipxp); ipxp->ipxp_laddr = si->si_dna; cb = ipxtospxpcb(ipxp); cb->s_mtu = ocb->s_mtu; /* preserve sockopts */ cb->s_flags = ocb->s_flags; /* preserve sockopts */ cb->s_flags2 = ocb->s_flags2; /* preserve sockopts */ cb->s_state = TCPS_LISTEN; } IPX_LOCK_ASSERT(ipxp); /* * Packet received on connection. Reset idle time and keep-alive * timer. */ cb->s_idle = 0; cb->s_timer[SPXT_KEEP] = SPXTV_KEEP; switch (cb->s_state) { case TCPS_LISTEN:{ struct sockaddr_ipx *sipx, ssipx; struct ipx_addr laddr; /* * If somebody here was carying on a conversation and went * away, and his pen pal thinks he can still talk, we get the * misdirected packet. */ if (spx_hardnosed && (si->si_did != 0 || si->si_seq != 0)) { spx_istat.gonawy++; goto dropwithreset; } sipx = &ssipx; bzero(sipx, sizeof *sipx); sipx->sipx_len = sizeof(*sipx); sipx->sipx_family = AF_IPX; sipx->sipx_addr = si->si_sna; laddr = ipxp->ipxp_laddr; if (ipx_nullhost(laddr)) ipxp->ipxp_laddr = si->si_dna; if (ipx_pcbconnect(ipxp, (struct sockaddr *)sipx, &thread0)) { ipxp->ipxp_laddr = laddr; spx_istat.noconn++; goto drop; } spx_template(cb); dropsocket = 0; /* committed to socket */ cb->s_did = si->si_sid; cb->s_rack = si->si_ack; cb->s_ralo = si->si_alo; #define THREEWAYSHAKE #ifdef THREEWAYSHAKE cb->s_state = TCPS_SYN_RECEIVED; cb->s_force = 1 + SPXT_KEEP; spxstat.spxs_accepts++; cb->s_timer[SPXT_KEEP] = SPXTV_KEEP; } break; case TCPS_SYN_RECEIVED: { /* * This state means that we have heard a response to our * acceptance of their connection. It is probably logically * unnecessary in this implementation. */ if (si->si_did != cb->s_sid) { spx_istat.wrncon++; goto drop; } #endif ipxp->ipxp_fport = si->si_sport; cb->s_timer[SPXT_REXMT] = 0; cb->s_timer[SPXT_KEEP] = SPXTV_KEEP; soisconnected(so); cb->s_state = TCPS_ESTABLISHED; spxstat.spxs_accepts++; } break; case TCPS_SYN_SENT: /* * This state means that we have gotten a response to our * attempt to establish a connection. We fill in the data * from the other side, telling us which port to respond to, * instead of the well-known one we might have sent to in the * first place. We also require that this is a response to * our connection id. */ if (si->si_did != cb->s_sid) { spx_istat.notme++; goto drop; } spxstat.spxs_connects++; cb->s_did = si->si_sid; cb->s_rack = si->si_ack; cb->s_ralo = si->si_alo; cb->s_dport = ipxp->ipxp_fport = si->si_sport; cb->s_timer[SPXT_REXMT] = 0; cb->s_flags |= SF_ACKNOW; soisconnected(so); cb->s_state = TCPS_ESTABLISHED; /* * Use roundtrip time of connection request for initial rtt. */ if (cb->s_rtt) { cb->s_srtt = cb->s_rtt << 3; cb->s_rttvar = cb->s_rtt << 1; SPXT_RANGESET(cb->s_rxtcur, ((cb->s_srtt >> 2) + cb->s_rttvar) >> 1, SPXTV_MIN, SPXTV_REXMTMAX); cb->s_rtt = 0; } } if (so->so_options & SO_DEBUG || traceallspxs) spx_trace(SA_INPUT, (u_char)ostate, cb, &spx_savesi, 0); m->m_len -= sizeof(struct ipx); m->m_pkthdr.len -= sizeof(struct ipx); m->m_data += sizeof(struct ipx); if (spx_reass(cb, si)) m_freem(m); if (cb->s_force || (cb->s_flags & (SF_ACKNOW|SF_WIN|SF_RXT))) spx_output(cb, NULL); cb->s_flags &= ~(SF_WIN|SF_RXT); IPX_UNLOCK(ipxp); IPX_LIST_UNLOCK(); return; dropwithreset: IPX_LOCK_ASSERT(ipxp); if (cb == NULL || (cb->s_ipxpcb->ipxp_socket->so_options & SO_DEBUG || traceallspxs)) spx_trace(SA_DROP, (u_char)ostate, cb, &spx_savesi, 0); IPX_UNLOCK(ipxp); if (dropsocket) { struct socket *head; ACCEPT_LOCK(); KASSERT((so->so_qstate & SQ_INCOMP) != 0, ("spx_input: nascent socket not SQ_INCOMP on soabort()")); head = so->so_head; TAILQ_REMOVE(&head->so_incomp, so, so_list); head->so_incqlen--; so->so_qstate &= ~SQ_INCOMP; so->so_head = NULL; ACCEPT_UNLOCK(); soabort(so); } IPX_LIST_UNLOCK(); m_freem(dtom(si)); return; drop: IPX_LOCK_ASSERT(ipxp); if (cb->s_ipxpcb->ipxp_socket->so_options & SO_DEBUG || traceallspxs) spx_trace(SA_DROP, (u_char)ostate, cb, &spx_savesi, 0); IPX_UNLOCK(ipxp); IPX_LIST_UNLOCK(); m_freem(m); } /* * This is structurally similar to the tcp reassembly routine but its * function is somewhat different: It merely queues packets up, and * suppresses duplicates. */ static int spx_reass(struct spxpcb *cb, struct spx *si) { struct spx_q *q; struct mbuf *m; struct socket *so = cb->s_ipxpcb->ipxp_socket; char packetp = cb->s_flags & SF_HI; int incr; char wakeup = 0; IPX_LOCK_ASSERT(cb->s_ipxpcb); if (si == SI(0)) goto present; /* * Update our news from them. */ if (si->si_cc & SPX_SA) cb->s_flags |= (spx_use_delack ? SF_DELACK : SF_ACKNOW); if (SSEQ_GT(si->si_alo, cb->s_ralo)) cb->s_flags |= SF_WIN; if (SSEQ_LEQ(si->si_ack, cb->s_rack)) { if ((si->si_cc & SPX_SP) && cb->s_rack != (cb->s_smax + 1)) { spxstat.spxs_rcvdupack++; /* * If this is a completely duplicate ack and other * conditions hold, we assume a packet has been * dropped and retransmit it exactly as in * tcp_input(). */ if (si->si_ack != cb->s_rack || si->si_alo != cb->s_ralo) cb->s_dupacks = 0; else if (++cb->s_dupacks == spxrexmtthresh) { u_short onxt = cb->s_snxt; int cwnd = cb->s_cwnd; cb->s_snxt = si->si_ack; cb->s_cwnd = CUNIT; cb->s_force = 1 + SPXT_REXMT; spx_output(cb, NULL); cb->s_timer[SPXT_REXMT] = cb->s_rxtcur; cb->s_rtt = 0; if (cwnd >= 4 * CUNIT) cb->s_cwnd = cwnd / 2; if (SSEQ_GT(onxt, cb->s_snxt)) cb->s_snxt = onxt; return (1); } } else cb->s_dupacks = 0; goto update_window; } cb->s_dupacks = 0; /* * If our correspondent acknowledges data we haven't sent TCP would * drop the packet after acking. We'll be a little more permissive. */ if (SSEQ_GT(si->si_ack, (cb->s_smax + 1))) { spxstat.spxs_rcvacktoomuch++; si->si_ack = cb->s_smax + 1; } spxstat.spxs_rcvackpack++; /* * If transmit timer is running and timed sequence number was acked, * update smoothed round trip time. See discussion of algorithm in * tcp_input.c */ if (cb->s_rtt && SSEQ_GT(si->si_ack, cb->s_rtseq)) { spxstat.spxs_rttupdated++; if (cb->s_srtt != 0) { short delta; delta = cb->s_rtt - (cb->s_srtt >> 3); if ((cb->s_srtt += delta) <= 0) cb->s_srtt = 1; if (delta < 0) delta = -delta; delta -= (cb->s_rttvar >> 2); if ((cb->s_rttvar += delta) <= 0) cb->s_rttvar = 1; } else { /* * No rtt measurement yet. */ cb->s_srtt = cb->s_rtt << 3; cb->s_rttvar = cb->s_rtt << 1; } cb->s_rtt = 0; cb->s_rxtshift = 0; SPXT_RANGESET(cb->s_rxtcur, ((cb->s_srtt >> 2) + cb->s_rttvar) >> 1, SPXTV_MIN, SPXTV_REXMTMAX); } /* * If all outstanding data is acked, stop retransmit timer and * remember to restart (more output or persist). If there is more * data to be acked, restart retransmit timer, using current * (possibly backed-off) value; */ if (si->si_ack == cb->s_smax + 1) { cb->s_timer[SPXT_REXMT] = 0; cb->s_flags |= SF_RXT; } else if (cb->s_timer[SPXT_PERSIST] == 0) cb->s_timer[SPXT_REXMT] = cb->s_rxtcur; /* * When new data is acked, open the congestion window. If the window * gives us less than ssthresh packets in flight, open exponentially * (maxseg at a time). Otherwise open linearly (maxseg^2 / cwnd at a * time). */ incr = CUNIT; if (cb->s_cwnd > cb->s_ssthresh) incr = max(incr * incr / cb->s_cwnd, 1); cb->s_cwnd = min(cb->s_cwnd + incr, cb->s_cwmx); /* * Trim Acked data from output queue. */ SOCKBUF_LOCK(&so->so_snd); while ((m = so->so_snd.sb_mb) != NULL) { if (SSEQ_LT((mtod(m, struct spx *))->si_seq, si->si_ack)) sbdroprecord_locked(&so->so_snd); else break; } sowwakeup_locked(so); cb->s_rack = si->si_ack; update_window: if (SSEQ_LT(cb->s_snxt, cb->s_rack)) cb->s_snxt = cb->s_rack; if (SSEQ_LT(cb->s_swl1, si->si_seq) || ((cb->s_swl1 == si->si_seq && (SSEQ_LT(cb->s_swl2, si->si_ack))) || (cb->s_swl2 == si->si_ack && SSEQ_LT(cb->s_ralo, si->si_alo)))) { /* keep track of pure window updates */ if ((si->si_cc & SPX_SP) && cb->s_swl2 == si->si_ack && SSEQ_LT(cb->s_ralo, si->si_alo)) { spxstat.spxs_rcvwinupd++; spxstat.spxs_rcvdupack--; } cb->s_ralo = si->si_alo; cb->s_swl1 = si->si_seq; cb->s_swl2 = si->si_ack; cb->s_swnd = (1 + si->si_alo - si->si_ack); if (cb->s_swnd > cb->s_smxw) cb->s_smxw = cb->s_swnd; cb->s_flags |= SF_WIN; } /* * If this packet number is higher than that which we have allocated * refuse it, unless urgent. */ if (SSEQ_GT(si->si_seq, cb->s_alo)) { if (si->si_cc & SPX_SP) { spxstat.spxs_rcvwinprobe++; return (1); } else spxstat.spxs_rcvpackafterwin++; if (si->si_cc & SPX_OB) { if (SSEQ_GT(si->si_seq, cb->s_alo + 60)) { m_freem(dtom(si)); return (0); } /* else queue this packet; */ } else { #ifdef BROKEN /* * XXXRW: This is broken on at least one count: * spx_close() will free the ipxp and related parts, * which are then touched by spx_input() after the * return from spx_reass(). */ /*struct socket *so = cb->s_ipxpcb->ipxp_socket; if (so->so_state && SS_NOFDREF) { spx_close(cb); } else would crash system*/ #endif spx_istat.notyet++; m_freem(dtom(si)); return (0); } } /* * If this is a system packet, we don't need to queue it up, and * won't update acknowledge #. */ if (si->si_cc & SPX_SP) return (1); /* * We have already seen this packet, so drop. */ if (SSEQ_LT(si->si_seq, cb->s_ack)) { spx_istat.bdreas++; spxstat.spxs_rcvduppack++; if (si->si_seq == cb->s_ack - 1) spx_istat.lstdup++; return (1); } /* * Loop through all packets queued up to insert in appropriate * sequence. */ for (q = cb->s_q.si_next; q != &cb->s_q; q = q->si_next) { if (si->si_seq == SI(q)->si_seq) { spxstat.spxs_rcvduppack++; return (1); } if (SSEQ_LT(si->si_seq, SI(q)->si_seq)) { spxstat.spxs_rcvoopack++; break; } } insque(si, q->si_prev); /* * If this packet is urgent, inform process */ if (si->si_cc & SPX_OB) { cb->s_iobc = ((char *)si)[1 + sizeof(*si)]; sohasoutofband(so); cb->s_oobflags |= SF_IOOB; } present: #define SPINC sizeof(struct spxhdr) SOCKBUF_LOCK(&so->so_rcv); /* * Loop through all packets queued up to update acknowledge number, * and present all acknowledged data to user; if in packet interface * mode, show packet headers. */ for (q = cb->s_q.si_next; q != &cb->s_q; q = q->si_next) { if (SI(q)->si_seq == cb->s_ack) { cb->s_ack++; m = dtom(q); if (SI(q)->si_cc & SPX_OB) { cb->s_oobflags &= ~SF_IOOB; if (so->so_rcv.sb_cc) so->so_oobmark = so->so_rcv.sb_cc; else so->so_rcv.sb_state |= SBS_RCVATMARK; } q = q->si_prev; remque(q->si_next); wakeup = 1; spxstat.spxs_rcvpack++; #ifdef SF_NEWCALL if (cb->s_flags2 & SF_NEWCALL) { struct spxhdr *sp = mtod(m, struct spxhdr *); u_char dt = sp->spx_dt; spx_newchecks[4]++; if (dt != cb->s_rhdr.spx_dt) { struct mbuf *mm = m_getclr(M_DONTWAIT, MT_CONTROL); spx_newchecks[0]++; if (mm != NULL) { u_short *s = mtod(mm, u_short *); cb->s_rhdr.spx_dt = dt; mm->m_len = 5; /*XXX*/ s[0] = 5; s[1] = 1; *(u_char *)(&s[2]) = dt; sbappend_locked(&so->so_rcv, mm); } } if (sp->spx_cc & SPX_OB) { MCHTYPE(m, MT_OOBDATA); spx_newchecks[1]++; so->so_oobmark = 0; so->so_rcv.sb_state &= ~SBS_RCVATMARK; } if (packetp == 0) { m->m_data += SPINC; m->m_len -= SPINC; m->m_pkthdr.len -= SPINC; } if ((sp->spx_cc & SPX_EM) || packetp) { sbappendrecord_locked(&so->so_rcv, m); spx_newchecks[9]++; } else sbappend_locked(&so->so_rcv, m); } else #endif if (packetp) sbappendrecord_locked(&so->so_rcv, m); else { cb->s_rhdr = *mtod(m, struct spxhdr *); m->m_data += SPINC; m->m_len -= SPINC; m->m_pkthdr.len -= SPINC; sbappend_locked(&so->so_rcv, m); } } else break; } if (wakeup) sorwakeup_locked(so); else SOCKBUF_UNLOCK(&so->so_rcv); return (0); } void spx_ctlinput(int cmd, struct sockaddr *arg_as_sa, void *dummy) { /* Currently, nothing. */ } static int spx_output(struct spxpcb *cb, struct mbuf *m0) { struct socket *so = cb->s_ipxpcb->ipxp_socket; struct mbuf *m; struct spx *si = NULL; struct sockbuf *sb = &so->so_snd; int len = 0, win, rcv_win; short span, off, recordp = 0; u_short alo; int error = 0, sendalot; #ifdef notdef int idle; #endif struct mbuf *mprev; IPX_LOCK_ASSERT(cb->s_ipxpcb); if (m0 != NULL) { int mtu = cb->s_mtu; int datalen; /* * Make sure that packet isn't too big. */ for (m = m0; m != NULL; m = m->m_next) { mprev = m; len += m->m_len; if (m->m_flags & M_EOR) recordp = 1; } datalen = (cb->s_flags & SF_HO) ? len - sizeof(struct spxhdr) : len; if (datalen > mtu) { if (cb->s_flags & SF_PI) { m_freem(m0); return (EMSGSIZE); } else { int oldEM = cb->s_cc & SPX_EM; cb->s_cc &= ~SPX_EM; while (len > mtu) { m = m_copym(m0, 0, mtu, M_DONTWAIT); if (m == NULL) { cb->s_cc |= oldEM; m_freem(m0); return (ENOBUFS); } if (cb->s_flags & SF_NEWCALL) { struct mbuf *mm = m; spx_newchecks[7]++; while (mm != NULL) { mm->m_flags &= ~M_EOR; mm = mm->m_next; } } error = spx_output(cb, m); if (error) { cb->s_cc |= oldEM; m_freem(m0); return (error); } m_adj(m0, mtu); len -= mtu; } cb->s_cc |= oldEM; } } /* * Force length even, by adding a "garbage byte" if * necessary. */ if (len & 1) { m = mprev; if (M_TRAILINGSPACE(m) >= 1) m->m_len++; else { struct mbuf *m1 = m_get(M_DONTWAIT, MT_DATA); if (m1 == NULL) { m_freem(m0); return (ENOBUFS); } m1->m_len = 1; *(mtod(m1, u_char *)) = 0; m->m_next = m1; } } m = m_gethdr(M_DONTWAIT, MT_DATA); if (m == NULL) { m_freem(m0); return (ENOBUFS); } /* * Fill in mbuf with extended SP header and addresses and * length put into network format. */ MH_ALIGN(m, sizeof(struct spx)); m->m_len = sizeof(struct spx); m->m_next = m0; si = mtod(m, struct spx *); si->si_i = *cb->s_ipx; si->si_s = cb->s_shdr; if ((cb->s_flags & SF_PI) && (cb->s_flags & SF_HO)) { struct spxhdr *sh; if (m0->m_len < sizeof(*sh)) { if((m0 = m_pullup(m0, sizeof(*sh))) == NULL) { m_free(m); m_freem(m0); return (EINVAL); } m->m_next = m0; } sh = mtod(m0, struct spxhdr *); si->si_dt = sh->spx_dt; si->si_cc |= sh->spx_cc & SPX_EM; m0->m_len -= sizeof(*sh); m0->m_data += sizeof(*sh); len -= sizeof(*sh); } len += sizeof(*si); if ((cb->s_flags2 & SF_NEWCALL) && recordp) { si->si_cc |= SPX_EM; spx_newchecks[8]++; } if (cb->s_oobflags & SF_SOOB) { /* * Per jqj@cornell: Make sure OB packets convey * exactly 1 byte. If the packet is 1 byte or * larger, we have already guaranted there to be at * least one garbage byte for the checksum, and extra * bytes shouldn't hurt! */ if (len > sizeof(*si)) { si->si_cc |= SPX_OB; len = (1 + sizeof(*si)); } } si->si_len = htons((u_short)len); m->m_pkthdr.len = ((len - 1) | 1) + 1; /* * Queue stuff up for output. */ sbappendrecord(sb, m); cb->s_seq++; } #ifdef notdef idle = (cb->s_smax == (cb->s_rack - 1)); #endif again: sendalot = 0; off = cb->s_snxt - cb->s_rack; win = min(cb->s_swnd, (cb->s_cwnd / CUNIT)); /* * If in persist timeout with window of 0, send a probe. Otherwise, * if window is small but nonzero and timer expired, send what we can * and go into transmit state. */ if (cb->s_force == 1 + SPXT_PERSIST) { if (win != 0) { cb->s_timer[SPXT_PERSIST] = 0; cb->s_rxtshift = 0; } } span = cb->s_seq - cb->s_rack; len = min(span, win) - off; if (len < 0) { /* * Window shrank after we went into it. If window shrank to * 0, cancel pending restransmission and pull s_snxt back to * (closed) window. We will enter persist state below. If * the widndow didn't close completely, just wait for an ACK. */ len = 0; if (win == 0) { cb->s_timer[SPXT_REXMT] = 0; cb->s_snxt = cb->s_rack; } } if (len > 1) sendalot = 1; rcv_win = sbspace(&so->so_rcv); /* * Send if we owe peer an ACK. */ if (cb->s_oobflags & SF_SOOB) { /* * Must transmit this out of band packet. */ cb->s_oobflags &= ~ SF_SOOB; sendalot = 1; spxstat.spxs_sndurg++; goto found; } if (cb->s_flags & SF_ACKNOW) goto send; if (cb->s_state < TCPS_ESTABLISHED) goto send; /* * Silly window can't happen in spx. Code from TCP deleted. */ if (len) goto send; /* * Compare available window to amount of window known to peer (as * advertised window less next expected input.) If the difference is * at least two packets or at least 35% of the mximum possible * window, then want to send a window update to peer. */ if (rcv_win > 0) { u_short delta = 1 + cb->s_alo - cb->s_ack; int adv = rcv_win - (delta * cb->s_mtu); if ((so->so_rcv.sb_cc == 0 && adv >= (2 * cb->s_mtu)) || (100 * adv / so->so_rcv.sb_hiwat >= 35)) { spxstat.spxs_sndwinup++; cb->s_flags |= SF_ACKNOW; goto send; } } /* * Many comments from tcp_output.c are appropriate here including ... * If send window is too small, there is data to transmit, and no * retransmit or persist is pending, then go to persist state. If * nothing happens soon, send when timer expires: if window is * nonzero, transmit what we can, otherwise send a probe. */ if (so->so_snd.sb_cc && cb->s_timer[SPXT_REXMT] == 0 && cb->s_timer[SPXT_PERSIST] == 0) { cb->s_rxtshift = 0; spx_setpersist(cb); } /* * No reason to send a packet, just return. */ cb->s_outx = 1; return (0); send: /* * Find requested packet. */ si = 0; if (len > 0) { cb->s_want = cb->s_snxt; for (m = sb->sb_mb; m != NULL; m = m->m_act) { si = mtod(m, struct spx *); if (SSEQ_LEQ(cb->s_snxt, si->si_seq)) break; } found: if (si != NULL) { if (si->si_seq == cb->s_snxt) cb->s_snxt++; else spxstat.spxs_sndvoid++, si = 0; } } /* * Update window. */ if (rcv_win < 0) rcv_win = 0; alo = cb->s_ack - 1 + (rcv_win / ((short)cb->s_mtu)); if (SSEQ_LT(alo, cb->s_alo)) alo = cb->s_alo; if (si != NULL) { /* * Must make a copy of this packet for ipx_output to monkey * with. */ m = m_copy(dtom(si), 0, (int)M_COPYALL); if (m == NULL) return (ENOBUFS); si = mtod(m, struct spx *); if (SSEQ_LT(si->si_seq, cb->s_smax)) spxstat.spxs_sndrexmitpack++; else spxstat.spxs_sndpack++; } else if (cb->s_force || cb->s_flags & SF_ACKNOW) { /* * Must send an acknowledgement or a probe. */ if (cb->s_force) spxstat.spxs_sndprobe++; if (cb->s_flags & SF_ACKNOW) spxstat.spxs_sndacks++; m = m_gethdr(M_DONTWAIT, MT_DATA); if (m == NULL) return (ENOBUFS); /* * Fill in mbuf with extended SP header and addresses and * length put into network format. */ MH_ALIGN(m, sizeof(struct spx)); m->m_len = sizeof(*si); m->m_pkthdr.len = sizeof(*si); si = mtod(m, struct spx *); si->si_i = *cb->s_ipx; si->si_s = cb->s_shdr; si->si_seq = cb->s_smax + 1; si->si_len = htons(sizeof(*si)); si->si_cc |= SPX_SP; } else { cb->s_outx = 3; if (so->so_options & SO_DEBUG || traceallspxs) spx_trace(SA_OUTPUT, cb->s_state, cb, si, 0); return (0); } /* * Stuff checksum and output datagram. */ if ((si->si_cc & SPX_SP) == 0) { if (cb->s_force != (1 + SPXT_PERSIST) || cb->s_timer[SPXT_PERSIST] == 0) { /* * If this is a new packet and we are not currently * timing anything, time this one. */ if (SSEQ_LT(cb->s_smax, si->si_seq)) { cb->s_smax = si->si_seq; if (cb->s_rtt == 0) { spxstat.spxs_segstimed++; cb->s_rtseq = si->si_seq; cb->s_rtt = 1; } } /* * Set rexmt timer if not currently set, initial * value for retransmit timer is smoothed round-trip * time + 2 * round-trip time variance. Initialize * shift counter which is used for backoff of * retransmit time. */ if (cb->s_timer[SPXT_REXMT] == 0 && cb->s_snxt != cb->s_rack) { cb->s_timer[SPXT_REXMT] = cb->s_rxtcur; if (cb->s_timer[SPXT_PERSIST]) { cb->s_timer[SPXT_PERSIST] = 0; cb->s_rxtshift = 0; } } } else if (SSEQ_LT(cb->s_smax, si->si_seq)) cb->s_smax = si->si_seq; } else if (cb->s_state < TCPS_ESTABLISHED) { if (cb->s_rtt == 0) cb->s_rtt = 1; /* Time initial handshake */ if (cb->s_timer[SPXT_REXMT] == 0) cb->s_timer[SPXT_REXMT] = cb->s_rxtcur; } /* * Do not request acks when we ack their data packets or when we do a * gratuitous window update. */ if (((si->si_cc & SPX_SP) == 0) || cb->s_force) si->si_cc |= SPX_SA; si->si_seq = htons(si->si_seq); si->si_alo = htons(alo); si->si_ack = htons(cb->s_ack); if (ipxcksum) si->si_sum = ipx_cksum(m, ntohs(si->si_len)); else si->si_sum = 0xffff; cb->s_outx = 4; if (so->so_options & SO_DEBUG || traceallspxs) spx_trace(SA_OUTPUT, cb->s_state, cb, si, 0); if (so->so_options & SO_DONTROUTE) error = ipx_outputfl(m, NULL, IPX_ROUTETOIF); else error = ipx_outputfl(m, &cb->s_ipxpcb->ipxp_route, 0); if (error) return (error); spxstat.spxs_sndtotal++; /* * Data sent (as far as we can tell). If this advertises a larger * window than any other segment, then remember the size of the * advertized window. Any pending ACK has now been sent. */ cb->s_force = 0; cb->s_flags &= ~(SF_ACKNOW|SF_DELACK); if (SSEQ_GT(alo, cb->s_alo)) cb->s_alo = alo; if (sendalot) goto again; cb->s_outx = 5; return (0); } static int spx_do_persist_panics = 0; static void spx_setpersist(struct spxpcb *cb) { int t = ((cb->s_srtt >> 2) + cb->s_rttvar) >> 1; IPX_LOCK_ASSERT(cb->s_ipxpcb); if (cb->s_timer[SPXT_REXMT] && spx_do_persist_panics) panic("spx_output REXMT"); /* * Start/restart persistance timer. */ SPXT_RANGESET(cb->s_timer[SPXT_PERSIST], t*spx_backoff[cb->s_rxtshift], SPXTV_PERSMIN, SPXTV_PERSMAX); if (cb->s_rxtshift < SPX_MAXRXTSHIFT) cb->s_rxtshift++; } int spx_ctloutput(struct socket *so, struct sockopt *sopt) { struct spxhdr spxhdr; struct ipxpcb *ipxp; struct spxpcb *cb; int mask, error; short soptval; u_short usoptval; int optval; ipxp = sotoipxpcb(so); KASSERT(ipxp != NULL, ("spx_ctloutput: ipxp == NULL")); /* * This will have to be changed when we do more general stacking of * protocols. */ if (sopt->sopt_level != IPXPROTO_SPX) return (ipx_ctloutput(so, sopt)); IPX_LOCK(ipxp); if (ipxp->ipxp_flags & IPXP_DROPPED) { IPX_UNLOCK(ipxp); return (ECONNRESET); } IPX_LOCK(ipxp); cb = ipxtospxpcb(ipxp); KASSERT(cb != NULL, ("spx_ctloutput: cb == NULL")); error = 0; switch (sopt->sopt_dir) { case SOPT_GET: switch (sopt->sopt_name) { case SO_HEADERS_ON_INPUT: mask = SF_HI; goto get_flags; case SO_HEADERS_ON_OUTPUT: mask = SF_HO; get_flags: soptval = cb->s_flags & mask; IPX_UNLOCK(ipxp); error = sooptcopyout(sopt, &soptval, sizeof(soptval)); break; case SO_MTU: usoptval = cb->s_mtu; IPX_UNLOCK(ipxp); error = sooptcopyout(sopt, &usoptval, sizeof(usoptval)); break; case SO_LAST_HEADER: spxhdr = cb->s_rhdr; IPX_UNLOCK(ipxp); error = sooptcopyout(sopt, &spxhdr, sizeof(spxhdr)); break; case SO_DEFAULT_HEADERS: spxhdr = cb->s_shdr; IPX_UNLOCK(ipxp); error = sooptcopyout(sopt, &spxhdr, sizeof(spxhdr)); break; default: IPX_UNLOCK(ipxp); error = ENOPROTOOPT; } break; case SOPT_SET: /* * XXX Why are these shorts on get and ints on set? That * doesn't make any sense... * * XXXRW: Note, when we re-acquire the ipxp lock, we should * re-check that it's not dropped. */ IPX_UNLOCK(ipxp); switch (sopt->sopt_name) { case SO_HEADERS_ON_INPUT: mask = SF_HI; goto set_head; case SO_HEADERS_ON_OUTPUT: mask = SF_HO; set_head: error = sooptcopyin(sopt, &optval, sizeof optval, sizeof optval); if (error) break; IPX_LOCK(ipxp); if (cb->s_flags & SF_PI) { if (optval) cb->s_flags |= mask; else cb->s_flags &= ~mask; } else error = EINVAL; IPX_UNLOCK(ipxp); break; case SO_MTU: error = sooptcopyin(sopt, &usoptval, sizeof usoptval, sizeof usoptval); if (error) break; /* Unlocked write. */ cb->s_mtu = usoptval; break; #ifdef SF_NEWCALL case SO_NEWCALL: error = sooptcopyin(sopt, &optval, sizeof optval, sizeof optval); if (error) break; IPX_LOCK(ipxp); if (optval) { cb->s_flags2 |= SF_NEWCALL; spx_newchecks[5]++; } else { cb->s_flags2 &= ~SF_NEWCALL; spx_newchecks[6]++; } IPX_UNLOCK(ipxp); break; #endif case SO_DEFAULT_HEADERS: { struct spxhdr sp; error = sooptcopyin(sopt, &sp, sizeof sp, sizeof sp); if (error) break; IPX_LOCK(ipxp); cb->s_dt = sp.spx_dt; cb->s_cc = sp.spx_cc & SPX_EM; IPX_UNLOCK(ipxp); } break; default: error = ENOPROTOOPT; } break; default: panic("spx_ctloutput: bad socket option direction"); } return (error); } static void spx_usr_abort(struct socket *so) { struct ipxpcb *ipxp; struct spxpcb *cb; ipxp = sotoipxpcb(so); KASSERT(ipxp != NULL, ("spx_usr_abort: ipxp == NULL")); cb = ipxtospxpcb(ipxp); KASSERT(cb != NULL, ("spx_usr_abort: cb == NULL")); IPX_LIST_LOCK(); IPX_LOCK(ipxp); spx_drop(cb, ECONNABORTED); spx_pcbdetach(ipxp); ipx_pcbdetach(ipxp); ipx_pcbfree(ipxp); IPX_LIST_UNLOCK(); } /* * Accept a connection. Essentially all the work is done at higher levels; * just return the address of the peer, storing through addr. */ static int spx_accept(struct socket *so, struct sockaddr **nam) { struct ipxpcb *ipxp; struct sockaddr_ipx *sipx, ssipx; ipxp = sotoipxpcb(so); KASSERT(ipxp != NULL, ("spx_accept: ipxp == NULL")); sipx = &ssipx; bzero(sipx, sizeof *sipx); sipx->sipx_len = sizeof *sipx; sipx->sipx_family = AF_IPX; IPX_LOCK(ipxp); sipx->sipx_addr = ipxp->ipxp_faddr; IPX_UNLOCK(ipxp); *nam = sodupsockaddr((struct sockaddr *)sipx, M_WAITOK); return (0); } static int spx_attach(struct socket *so, int proto, struct thread *td) { struct ipxpcb *ipxp; struct spxpcb *cb; struct mbuf *mm; struct sockbuf *sb; int error; ipxp = sotoipxpcb(so); KASSERT(ipxp == NULL, ("spx_attach: ipxp != NULL")); if (so->so_snd.sb_hiwat == 0 || so->so_rcv.sb_hiwat == 0) { error = soreserve(so, (u_long) 3072, (u_long) 3072); if (error) return (error); } MALLOC(cb, struct spxpcb *, sizeof *cb, M_PCB, M_NOWAIT | M_ZERO); if (cb == NULL) return (ENOBUFS); mm = m_getclr(M_DONTWAIT, MT_DATA); if (mm == NULL) { FREE(cb, M_PCB); return (ENOBUFS); } IPX_LIST_LOCK(); error = ipx_pcballoc(so, &ipxpcb_list, td); if (error) { IPX_LIST_UNLOCK(); m_free(mm); FREE(cb, M_PCB); return (error); } ipxp = sotoipxpcb(so); ipxp->ipxp_flags |= IPXP_SPX; cb->s_ipx = mtod(mm, struct ipx *); cb->s_state = TCPS_LISTEN; cb->s_smax = -1; cb->s_swl1 = -1; cb->s_q.si_next = cb->s_q.si_prev = &cb->s_q; cb->s_ipxpcb = ipxp; cb->s_mtu = 576 - sizeof(struct spx); sb = &so->so_snd; cb->s_cwnd = sbspace(sb) * CUNIT / cb->s_mtu; cb->s_ssthresh = cb->s_cwnd; cb->s_cwmx = sbspace(sb) * CUNIT / (2 * sizeof(struct spx)); /* * Above is recomputed when connecting to account for changed * buffering or mtu's. */ cb->s_rtt = SPXTV_SRTTBASE; cb->s_rttvar = SPXTV_SRTTDFLT << 2; SPXT_RANGESET(cb->s_rxtcur, ((SPXTV_SRTTBASE >> 2) + (SPXTV_SRTTDFLT << 2)) >> 1, SPXTV_MIN, SPXTV_REXMTMAX); ipxp->ipxp_pcb = (caddr_t)cb; IPX_LIST_UNLOCK(); return (0); } static void spx_pcbdetach(struct ipxpcb *ipxp) { struct spxpcb *cb; struct spx_q *s; struct mbuf *m; IPX_LOCK_ASSERT(ipxp); cb = ipxtospxpcb(ipxp); KASSERT(cb != NULL, ("spx_pcbdetach: cb == NULL")); s = cb->s_q.si_next; while (s != &(cb->s_q)) { s = s->si_next; remque(s); m = dtom(s); m_freem(m); } m_free(dtom(cb->s_ipx)); FREE(cb, M_PCB); ipxp->ipxp_pcb = NULL; } static int spx_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { struct ipxpcb *ipxp; int error; ipxp = sotoipxpcb(so); KASSERT(ipxp != NULL, ("spx_bind: ipxp == NULL")); IPX_LIST_LOCK(); IPX_LOCK(ipxp); if (ipxp->ipxp_flags & IPXP_DROPPED) { error = EINVAL; goto out; } error = ipx_pcbbind(ipxp, nam, td); out: IPX_UNLOCK(ipxp); IPX_LIST_UNLOCK(); return (error); } /* * Initiate connection to peer. Enter SYN_SENT state, and mark socket as * connecting. Start keep-alive timer, setup prototype header, send initial * system packet requesting connection. */ static int spx_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { struct ipxpcb *ipxp; struct spxpcb *cb; int error; ipxp = sotoipxpcb(so); KASSERT(ipxp != NULL, ("spx_connect: ipxp == NULL")); cb = ipxtospxpcb(ipxp); KASSERT(cb != NULL, ("spx_connect: cb == NULL")); IPX_LIST_LOCK(); IPX_LOCK(ipxp); if (ipxp->ipxp_flags & IPXP_DROPPED) { error = EINVAL; goto spx_connect_end; } if (ipxp->ipxp_lport == 0) { error = ipx_pcbbind(ipxp, NULL, td); if (error) goto spx_connect_end; } error = ipx_pcbconnect(ipxp, nam, td); if (error) goto spx_connect_end; soisconnecting(so); spxstat.spxs_connattempt++; cb->s_state = TCPS_SYN_SENT; cb->s_did = 0; spx_template(cb); cb->s_timer[SPXT_KEEP] = SPXTV_KEEP; cb->s_force = 1 + SPXTV_KEEP; /* * Other party is required to respond to the port I send from, but he * is not required to answer from where I am sending to, so allow * wildcarding. Original port I am sending to is still saved in * cb->s_dport. */ ipxp->ipxp_fport = 0; error = spx_output(cb, NULL); spx_connect_end: IPX_UNLOCK(ipxp); IPX_LIST_UNLOCK(); return (error); } -static int +static void spx_detach(struct socket *so) { struct ipxpcb *ipxp; struct spxpcb *cb; ipxp = sotoipxpcb(so); KASSERT(ipxp != NULL, ("spx_detach: ipxp == NULL")); cb = ipxtospxpcb(ipxp); KASSERT(cb != NULL, ("spx_detach: cb == NULL")); IPX_LIST_LOCK(); IPX_LOCK(ipxp); if (cb->s_state > TCPS_LISTEN) spx_disconnect(cb); else spx_close(cb); spx_pcbdetach(ipxp); ipx_pcbdetach(ipxp); ipx_pcbfree(ipxp); IPX_LIST_UNLOCK(); - return (0); } /* * We may decide later to implement connection closing handshaking at the spx * level optionally. Here is the hook to do it: */ static int spx_usr_disconnect(struct socket *so) { struct ipxpcb *ipxp; struct spxpcb *cb; int error; ipxp = sotoipxpcb(so); KASSERT(ipxp != NULL, ("spx_usr_disconnect: ipxp == NULL")); cb = ipxtospxpcb(ipxp); KASSERT(cb != NULL, ("spx_usr_disconnect: cb == NULL")); IPX_LIST_LOCK(); IPX_LOCK(ipxp); if (ipxp->ipxp_flags & IPXP_DROPPED) { error = EINVAL; goto out; } spx_disconnect(cb); error = 0; out: IPX_UNLOCK(ipxp); IPX_LIST_UNLOCK(); return (error); } static int spx_listen(struct socket *so, int backlog, struct thread *td) { int error; struct ipxpcb *ipxp; struct spxpcb *cb; error = 0; ipxp = sotoipxpcb(so); KASSERT(ipxp != NULL, ("spx_listen: ipxp == NULL")); cb = ipxtospxpcb(ipxp); KASSERT(cb != NULL, ("spx_listen: cb == NULL")); IPX_LIST_LOCK(); IPX_LOCK(ipxp); if (ipxp->ipxp_flags & IPXP_DROPPED) { error = EINVAL; goto out; } SOCK_LOCK(so); error = solisten_proto_check(so); if (error == 0 && ipxp->ipxp_lport == 0) error = ipx_pcbbind(ipxp, NULL, td); if (error == 0) { cb->s_state = TCPS_LISTEN; solisten_proto(so, backlog); } SOCK_UNLOCK(so); out: IPX_UNLOCK(ipxp); IPX_LIST_UNLOCK(); return (error); } /* * After a receive, possibly send acknowledgment updating allocation. */ static int spx_rcvd(struct socket *so, int flags) { struct ipxpcb *ipxp; struct spxpcb *cb; int error; ipxp = sotoipxpcb(so); KASSERT(ipxp != NULL, ("spx_rcvd: ipxp == NULL")); cb = ipxtospxpcb(ipxp); KASSERT(cb != NULL, ("spx_rcvd: cb == NULL")); IPX_LOCK(ipxp); if (ipxp->ipxp_flags & IPXP_DROPPED) { error = EINVAL; goto out; } cb->s_flags |= SF_RVD; spx_output(cb, NULL); cb->s_flags &= ~SF_RVD; error = 0; out: IPX_UNLOCK(ipxp); return (error); } static int spx_rcvoob(struct socket *so, struct mbuf *m, int flags) { struct ipxpcb *ipxp; struct spxpcb *cb; int error; ipxp = sotoipxpcb(so); KASSERT(ipxp != NULL, ("spx_rcvoob: ipxp == NULL")); cb = ipxtospxpcb(ipxp); KASSERT(cb != NULL, ("spx_rcvoob: cb == NULL")); IPX_LOCK(ipxp); if (ipxp->ipxp_flags & IPXP_DROPPED) { error = EINVAL; goto out; } SOCKBUF_LOCK(&so->so_rcv); if ((cb->s_oobflags & SF_IOOB) || so->so_oobmark || (so->so_rcv.sb_state & SBS_RCVATMARK)) { SOCKBUF_UNLOCK(&so->so_rcv); m->m_len = 1; *mtod(m, caddr_t) = cb->s_iobc; error = 0; goto out; } SOCKBUF_UNLOCK(&so->so_rcv); error = EINVAL; out: IPX_UNLOCK(ipxp); return (error); } static int spx_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *controlp, struct thread *td) { struct ipxpcb *ipxp; struct spxpcb *cb; int error; ipxp = sotoipxpcb(so); KASSERT(ipxp != NULL, ("spx_send: ipxp == NULL")); cb = ipxtospxpcb(ipxp); KASSERT(cb != NULL, ("spx_send: cb == NULL")); error = 0; IPX_LOCK(ipxp); if (ipxp->ipxp_flags & IPXP_DROPPED) { error = ECONNRESET; goto spx_send_end; } if (flags & PRUS_OOB) { if (sbspace(&so->so_snd) < -512) { error = ENOBUFS; goto spx_send_end; } cb->s_oobflags |= SF_SOOB; } if (controlp != NULL) { u_short *p = mtod(controlp, u_short *); spx_newchecks[2]++; if ((p[0] == 5) && (p[1] == 1)) { /* XXXX, for testing */ cb->s_shdr.spx_dt = *(u_char *)(&p[2]); spx_newchecks[3]++; } m_freem(controlp); } controlp = NULL; error = spx_output(cb, m); m = NULL; spx_send_end: IPX_UNLOCK(ipxp); if (controlp != NULL) m_freem(controlp); if (m != NULL) m_freem(m); return (error); } static int spx_shutdown(struct socket *so) { struct ipxpcb *ipxp; struct spxpcb *cb; int error; ipxp = sotoipxpcb(so); KASSERT(ipxp != NULL, ("spx_shutdown: ipxp == NULL")); cb = ipxtospxpcb(ipxp); KASSERT(cb != NULL, ("spx_shutdown: cb == NULL")); socantsendmore(so); IPX_LIST_LOCK(); IPX_LOCK(ipxp); if (ipxp->ipxp_flags & IPXP_DROPPED) { error = EINVAL; goto out; } spx_usrclosed(cb); error = 0; out: IPX_UNLOCK(ipxp); IPX_LIST_UNLOCK(); return (error); } static int spx_sp_attach(struct socket *so, int proto, struct thread *td) { struct ipxpcb *ipxp; struct spxpcb *cb; int error; KASSERT(so->so_pcb == NULL, ("spx_sp_attach: so_pcb != NULL")); error = spx_attach(so, proto, td); if (error) return (error); ipxp = sotoipxpcb(so); KASSERT(ipxp != NULL, ("spx_sp_attach: ipxp == NULL")); cb = ipxtospxpcb(ipxp); KASSERT(cb != NULL, ("spx_sp_attach: cb == NULL")); IPX_LOCK(ipxp); cb->s_flags |= (SF_HI | SF_HO | SF_PI); IPX_UNLOCK(ipxp); return (0); } /* * Create template to be used to send spx packets on a connection. Called * after host entry created, fills in a skeletal spx header (choosing * connection id), minimizing the amount of work necessary when the * connection is used. */ static void spx_template(struct spxpcb *cb) { struct ipxpcb *ipxp = cb->s_ipxpcb; struct ipx *ipx = cb->s_ipx; struct sockbuf *sb = &(ipxp->ipxp_socket->so_snd); IPX_LOCK_ASSERT(ipxp); ipx->ipx_pt = IPXPROTO_SPX; ipx->ipx_sna = ipxp->ipxp_laddr; ipx->ipx_dna = ipxp->ipxp_faddr; SPX_LOCK(); cb->s_sid = htons(spx_iss); spx_iss += SPX_ISSINCR/2; SPX_UNLOCK(); cb->s_alo = 1; cb->s_cwnd = (sbspace(sb) * CUNIT) / cb->s_mtu; /* Try to expand fast to full complement of large packets. */ cb->s_ssthresh = cb->s_cwnd; cb->s_cwmx = (sbspace(sb) * CUNIT) / (2 * sizeof(struct spx)); /* But allow for lots of little packets as well. */ cb->s_cwmx = max(cb->s_cwmx, cb->s_cwnd); } /* * Close a SPIP control block. Wake up any sleepers. We used to free any * queued packets and cb->s_ipx here, but now we defer that until the pcb is * discarded. */ void spx_close(struct spxpcb *cb) { struct ipxpcb *ipxp = cb->s_ipxpcb; struct socket *so = ipxp->ipxp_socket; KASSERT(ipxp != NULL, ("spx_close: ipxp == NULL")); IPX_LIST_LOCK_ASSERT(); IPX_LOCK_ASSERT(ipxp); ipxp->ipxp_flags |= IPXP_DROPPED; soisdisconnected(so); spxstat.spxs_closed++; } /* * Someday we may do level 3 handshaking to close a connection or send a * xerox style error. For now, just close. cb will always be invalid after * this call. */ static void spx_usrclosed(struct spxpcb *cb) { IPX_LIST_LOCK_ASSERT(); IPX_LOCK_ASSERT(cb->s_ipxpcb); spx_close(cb); } /* * cb will always be invalid after this call. */ static void spx_disconnect(struct spxpcb *cb) { IPX_LIST_LOCK_ASSERT(); IPX_LOCK_ASSERT(cb->s_ipxpcb); spx_close(cb); } /* * Drop connection, reporting the specified error. cb will always be invalid * after this call. */ static void spx_drop(struct spxpcb *cb, int errno) { struct socket *so = cb->s_ipxpcb->ipxp_socket; IPX_LIST_LOCK_ASSERT(); IPX_LOCK_ASSERT(cb->s_ipxpcb); /* * Someday, in the xerox world we will generate error protocol * packets announcing that the socket has gone away. */ if (TCPS_HAVERCVDSYN(cb->s_state)) { spxstat.spxs_drops++; cb->s_state = TCPS_CLOSED; /*tcp_output(cb);*/ } else spxstat.spxs_conndrops++; so->so_error = errno; spx_close(cb); } /* * Fast timeout routine for processing delayed acks. */ void spx_fasttimo(void) { struct ipxpcb *ipxp; struct spxpcb *cb; IPX_LIST_LOCK(); LIST_FOREACH(ipxp, &ipxpcb_list, ipxp_list) { IPX_LOCK(ipxp); if (!(ipxp->ipxp_flags & IPXP_SPX) || (ipxp->ipxp_flags & IPXP_DROPPED)) { IPX_UNLOCK(ipxp); continue; } cb = ipxtospxpcb(ipxp); if (cb->s_flags & SF_DELACK) { cb->s_flags &= ~SF_DELACK; cb->s_flags |= SF_ACKNOW; spxstat.spxs_delack++; spx_output(cb, NULL); } IPX_UNLOCK(ipxp); } IPX_LIST_UNLOCK(); } /* * spx protocol timeout routine called every 500 ms. Updates the timers in * all active pcb's and causes finite state machine actions if timers expire. */ void spx_slowtimo(void) { struct ipxpcb *ipxp; struct spxpcb *cb; int i; /* * Search through tcb's and update active timers. Once, timers could * free ipxp's, but now we do that only when detaching a socket. */ IPX_LIST_LOCK(); LIST_FOREACH(ipxp, &ipxpcb_list, ipxp_list) { IPX_LOCK(ipxp); if (!(ipxp->ipxp_flags & IPXP_SPX) || (ipxp->ipxp_flags & IPXP_DROPPED)) { IPX_UNLOCK(ipxp); continue; } cb = (struct spxpcb *)ipxp->ipxp_pcb; KASSERT(cb != NULL, ("spx_slowtimo: cb == NULL")); for (i = 0; i < SPXT_NTIMERS; i++) { if (cb->s_timer[i] && --cb->s_timer[i] == 0) { spx_timers(cb, i); if (ipxp->ipxp_flags & IPXP_DROPPED) break; } } if (!(ipxp->ipxp_flags & IPXP_DROPPED)) { cb->s_idle++; if (cb->s_rtt) cb->s_rtt++; } IPX_UNLOCK(ipxp); } IPX_LIST_UNLOCK(); SPX_LOCK(); spx_iss += SPX_ISSINCR/PR_SLOWHZ; /* increment iss */ SPX_UNLOCK(); } /* * SPX timer processing. */ static void spx_timers(struct spxpcb *cb, int timer) { long rexmt; int win; IPX_LIST_LOCK_ASSERT(); IPX_LOCK_ASSERT(cb->s_ipxpcb); cb->s_force = 1 + timer; switch (timer) { case SPXT_2MSL: /* * 2 MSL timeout in shutdown went off. TCP deletes * connection control block. */ printf("spx: SPXT_2MSL went off for no reason\n"); cb->s_timer[timer] = 0; break; case SPXT_REXMT: /* * Retransmission timer went off. Message has not been acked * within retransmit interval. Back off to a longer * retransmit interval and retransmit one packet. */ if (++cb->s_rxtshift > SPX_MAXRXTSHIFT) { cb->s_rxtshift = SPX_MAXRXTSHIFT; spxstat.spxs_timeoutdrop++; spx_drop(cb, ETIMEDOUT); break; } spxstat.spxs_rexmttimeo++; rexmt = ((cb->s_srtt >> 2) + cb->s_rttvar) >> 1; rexmt *= spx_backoff[cb->s_rxtshift]; SPXT_RANGESET(cb->s_rxtcur, rexmt, SPXTV_MIN, SPXTV_REXMTMAX); cb->s_timer[SPXT_REXMT] = cb->s_rxtcur; /* * If we have backed off fairly far, our srtt estimate is * probably bogus. Clobber it so we'll take the next rtt * measurement as our srtt; move the current srtt into rttvar * to keep the current retransmit times until then. */ if (cb->s_rxtshift > SPX_MAXRXTSHIFT / 4 ) { cb->s_rttvar += (cb->s_srtt >> 2); cb->s_srtt = 0; } cb->s_snxt = cb->s_rack; /* * If timing a packet, stop the timer. */ cb->s_rtt = 0; /* * See very long discussion in tcp_timer.c about congestion * window and sstrhesh. */ win = min(cb->s_swnd, (cb->s_cwnd/CUNIT)) / 2; if (win < 2) win = 2; cb->s_cwnd = CUNIT; cb->s_ssthresh = win * CUNIT; spx_output(cb, NULL); break; case SPXT_PERSIST: /* * Persistance timer into zero window. Force a probe to be * sent. */ spxstat.spxs_persisttimeo++; spx_setpersist(cb); spx_output(cb, NULL); break; case SPXT_KEEP: /* * Keep-alive timer went off; send something or drop * connection if idle for too long. */ spxstat.spxs_keeptimeo++; if (cb->s_state < TCPS_ESTABLISHED) goto dropit; if (cb->s_ipxpcb->ipxp_socket->so_options & SO_KEEPALIVE) { if (cb->s_idle >= SPXTV_MAXIDLE) goto dropit; spxstat.spxs_keepprobe++; spx_output(cb, NULL); } else cb->s_idle = 0; cb->s_timer[SPXT_KEEP] = SPXTV_KEEP; break; dropit: spxstat.spxs_keepdrops++; spx_drop(cb, ETIMEDOUT); break; default: panic("spx_timers: unknown timer %d", timer); } } diff --git a/sys/netkey/keysock.c b/sys/netkey/keysock.c index 7b57488f878a..7eb7e0b78f78 100644 --- a/sys/netkey/keysock.c +++ b/sys/netkey/keysock.c @@ -1,503 +1,496 @@ /* $KAME: keysock.c,v 1.32 2003/08/22 05:45:08 itojun Exp $ */ /*- * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_ipsec.h" /* This code has derived from sys/net/rtsock.c on FreeBSD 2.2.5 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct sockaddr key_dst = { 2, PF_KEY, }; struct sockaddr key_src = { 2, PF_KEY, }; static int key_sendup0(struct rawcb *, struct mbuf *, int); struct pfkeystat pfkeystat; /* * key_output() */ int key_output(struct mbuf *m, struct socket *so) { struct sadb_msg *msg; int len, error = 0; int s; if (m == 0) panic("key_output: NULL pointer was passed."); pfkeystat.out_total++; pfkeystat.out_bytes += m->m_pkthdr.len; len = m->m_pkthdr.len; if (len < sizeof(struct sadb_msg)) { pfkeystat.out_tooshort++; error = EINVAL; goto end; } if (m->m_len < sizeof(struct sadb_msg)) { if ((m = m_pullup(m, sizeof(struct sadb_msg))) == 0) { pfkeystat.out_nomem++; error = ENOBUFS; goto end; } } M_ASSERTPKTHDR(m); KEYDEBUG(KEYDEBUG_KEY_DUMP, kdebug_mbuf(m)); msg = mtod(m, struct sadb_msg *); pfkeystat.out_msgtype[msg->sadb_msg_type]++; if (len != PFKEY_UNUNIT64(msg->sadb_msg_len)) { pfkeystat.out_invlen++; error = EINVAL; goto end; } /*XXX giant lock*/ s = splnet(); error = key_parse(m, so); m = NULL; splx(s); end: if (m) m_freem(m); return error; } /* * send message to the socket. */ static int key_sendup0(rp, m, promisc) struct rawcb *rp; struct mbuf *m; int promisc; { int error; if (promisc) { struct sadb_msg *pmsg; M_PREPEND(m, sizeof(struct sadb_msg), M_DONTWAIT); if (m && m->m_len < sizeof(struct sadb_msg)) m = m_pullup(m, sizeof(struct sadb_msg)); if (!m) { pfkeystat.in_nomem++; return ENOBUFS; } m->m_pkthdr.len += sizeof(*pmsg); pmsg = mtod(m, struct sadb_msg *); bzero(pmsg, sizeof(*pmsg)); pmsg->sadb_msg_version = PF_KEY_V2; pmsg->sadb_msg_type = SADB_X_PROMISC; pmsg->sadb_msg_len = PFKEY_UNIT64(m->m_pkthdr.len); /* pid and seq? */ pfkeystat.in_msgtype[pmsg->sadb_msg_type]++; } if (!sbappendaddr(&rp->rcb_socket->so_rcv, (struct sockaddr *)&key_src, m, NULL)) { pfkeystat.in_nomem++; m_freem(m); error = ENOBUFS; } else error = 0; sorwakeup(rp->rcb_socket); return error; } /* so can be NULL if target != KEY_SENDUP_ONE */ int key_sendup_mbuf(so, m, target) struct socket *so; struct mbuf *m; int target; { struct mbuf *n; struct keycb *kp; int sendup; struct rawcb *rp; int error = 0; if (m == NULL) panic("key_sendup_mbuf: NULL pointer was passed."); if (so == NULL && target == KEY_SENDUP_ONE) panic("key_sendup_mbuf: NULL pointer was passed."); pfkeystat.in_total++; pfkeystat.in_bytes += m->m_pkthdr.len; if (m->m_len < sizeof(struct sadb_msg)) { m = m_pullup(m, sizeof(struct sadb_msg)); if (m == NULL) { pfkeystat.in_nomem++; return ENOBUFS; } } if (m->m_len >= sizeof(struct sadb_msg)) { struct sadb_msg *msg; msg = mtod(m, struct sadb_msg *); pfkeystat.in_msgtype[msg->sadb_msg_type]++; } LIST_FOREACH(rp, &rawcb_list, list) { if (rp->rcb_proto.sp_family != PF_KEY) continue; if (rp->rcb_proto.sp_protocol && rp->rcb_proto.sp_protocol != PF_KEY_V2) { continue; } kp = (struct keycb *)rp; /* * If you are in promiscuous mode, and when you get broadcasted * reply, you'll get two PF_KEY messages. * (based on pf_key@inner.net message on 14 Oct 1998) */ if (((struct keycb *)rp)->kp_promisc) { if ((n = m_copy(m, 0, (int)M_COPYALL)) != NULL) { (void)key_sendup0(rp, n, 1); n = NULL; } } /* the exact target will be processed later */ if (so && sotorawcb(so) == rp) continue; sendup = 0; switch (target) { case KEY_SENDUP_ONE: /* the statement has no effect */ if (so && sotorawcb(so) == rp) sendup++; break; case KEY_SENDUP_ALL: sendup++; break; case KEY_SENDUP_REGISTERED: if (kp->kp_registered) sendup++; break; } pfkeystat.in_msgtarget[target]++; if (!sendup) continue; if ((n = m_copy(m, 0, (int)M_COPYALL)) == NULL) { m_freem(m); pfkeystat.in_nomem++; return ENOBUFS; } /* * ignore error even if queue is full. PF_KEY does not * guarantee the delivery of the message. * this is important when target == KEY_SENDUP_ALL. */ key_sendup0(rp, n, 0); n = NULL; } if (so) { error = key_sendup0(sotorawcb(so), m, 0); m = NULL; } else { error = 0; m_freem(m); } return error; } /* * key_abort() * derived from net/rtsock.c:rts_abort() */ static void key_abort(struct socket *so) { raw_usrreqs.pru_abort(so); } /* * key_attach() * derived from net/rtsock.c:rts_attach() */ static int key_attach(struct socket *so, int proto, struct thread *p) { struct keycb *kp; int s, error; if (sotorawcb(so) != 0) return EISCONN; /* XXX panic? */ kp = (struct keycb *)malloc(sizeof *kp, M_PCB, M_WAITOK); /* XXX */ if (kp == 0) return ENOBUFS; bzero(kp, sizeof *kp); /* * The splnet() is necessary to block protocols from sending * error notifications (like RTM_REDIRECT or RTM_LOSING) while * this PCB is extant but incompletely initialized. * Probably we should try to do more of this work beforehand and * eliminate the spl. */ s = splnet(); so->so_pcb = (caddr_t)kp; error = raw_usrreqs.pru_attach(so, proto, p); kp = (struct keycb *)sotorawcb(so); if (error) { free(kp, M_PCB); so->so_pcb = (caddr_t) 0; splx(s); return error; } kp->kp_promisc = kp->kp_registered = 0; if (kp->kp_raw.rcb_proto.sp_protocol == PF_KEY) /* XXX: AF_KEY */ key_cb.key_count++; key_cb.any_count++; kp->kp_raw.rcb_laddr = &key_src; kp->kp_raw.rcb_faddr = &key_dst; soisconnected(so); so->so_options |= SO_USELOOPBACK; splx(s); return 0; } /* * key_bind() * derived from net/rtsock.c:rts_bind() */ static int key_bind(struct socket *so, struct sockaddr *nam, struct thread *p) { int s, error; s = splnet(); error = raw_usrreqs.pru_bind(so, nam, p); /* xxx just EINVAL */ splx(s); return error; } /* * key_connect() * derived from net/rtsock.c:rts_connect() */ static int key_connect(struct socket *so, struct sockaddr *nam, struct thread *p) { int s, error; s = splnet(); error = raw_usrreqs.pru_connect(so, nam, p); /* XXX just EINVAL */ splx(s); return error; } /* * key_detach() * derived from net/rtsock.c:rts_detach() */ -static int +static void key_detach(struct socket *so) { struct keycb *kp = (struct keycb *)sotorawcb(so); - int s, error; - - s = splnet(); - if (kp != 0) { - if (kp->kp_raw.rcb_proto.sp_protocol - == PF_KEY) /* XXX: AF_KEY */ - key_cb.key_count--; - key_cb.any_count--; - key_freereg(so); - } - error = raw_usrreqs.pru_detach(so); - splx(s); - return error; + KASSERT(kp != NULL, ("key_detach: kp == NULL")); + if (kp->kp_raw.rcb_proto.sp_protocol == PF_KEY) /* XXX: AF_KEY */ + key_cb.key_count--; + key_cb.any_count--; + key_freereg(so); + raw_usrreqs.pru_detach(so); } /* * key_disconnect() * derived from net/rtsock.c:key_disconnect() */ static int key_disconnect(struct socket *so) { int s, error; s = splnet(); error = raw_usrreqs.pru_disconnect(so); splx(s); return error; } /* * key_peeraddr() * derived from net/rtsock.c:rts_peeraddr() */ static int key_peeraddr(struct socket *so, struct sockaddr **nam) { int s, error; s = splnet(); error = raw_usrreqs.pru_peeraddr(so, nam); splx(s); return error; } /* * key_send() * derived from net/rtsock.c:rts_send() */ static int key_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, struct mbuf *control, struct thread *p) { int s, error; s = splnet(); error = raw_usrreqs.pru_send(so, flags, m, nam, control, p); splx(s); return error; } /* * key_shutdown() * derived from net/rtsock.c:rts_shutdown() */ static int key_shutdown(struct socket *so) { int s, error; s = splnet(); error = raw_usrreqs.pru_shutdown(so); splx(s); return error; } /* * key_sockaddr() * derived from net/rtsock.c:rts_sockaddr() */ static int key_sockaddr(struct socket *so, struct sockaddr **nam) { int s, error; s = splnet(); error = raw_usrreqs.pru_sockaddr(so, nam); splx(s); return error; } struct pr_usrreqs key_usrreqs = { .pru_abort = key_abort, .pru_attach = key_attach, .pru_bind = key_bind, .pru_connect = key_connect, .pru_detach = key_detach, .pru_disconnect = key_disconnect, .pru_peeraddr = key_peeraddr, .pru_send = key_send, .pru_shutdown = key_shutdown, .pru_sockaddr = key_sockaddr, }; /* sysctl */ SYSCTL_NODE(_net, PF_KEY, key, CTLFLAG_RW, 0, "Key Family"); /* * Definitions of protocols supported in the KEY domain. */ extern struct domain keydomain; struct protosw keysw[] = { { .pr_type = SOCK_RAW, .pr_domain = &keydomain, .pr_protocol = PF_KEY_V2, .pr_flags = PR_ATOMIC|PR_ADDR, .pr_output = key_output, .pr_ctlinput = raw_ctlinput, .pr_init = raw_init, .pr_usrreqs = &key_usrreqs } }; struct domain keydomain = { .dom_family = PF_KEY, .dom_name = "key", .dom_init = key_init, .dom_protosw = keysw, .dom_protoswNPROTOSW = &keysw[sizeof(keysw)/sizeof(keysw[0])] }; DOMAIN_SET(key); diff --git a/sys/netnatm/natm.c b/sys/netnatm/natm.c index 6b3b2025d76b..8b87694bf97d 100644 --- a/sys/netnatm/natm.c +++ b/sys/netnatm/natm.c @@ -1,452 +1,451 @@ /* $NetBSD: natm.c,v 1.5 1996/11/09 03:26:26 chuck Exp $ */ /*- * * Copyright (c) 1996 Charles D. Cranor and Washington University. * Copyright (c) 2005-2006 Robert N. M. Watson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Charles D. Cranor and * Washington University. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * natm.c: native mode ATM access (both aal0 and aal5). */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const u_long natm5_sendspace = 16*1024; static const u_long natm5_recvspace = 16*1024; static const u_long natm0_sendspace = 16*1024; static const u_long natm0_recvspace = 16*1024; struct mtx natm_mtx; /* * user requests */ static int natm_usr_attach(struct socket *, int, d_thread_t *); -static int natm_usr_detach(struct socket *); +static void natm_usr_detach(struct socket *); static int natm_usr_connect(struct socket *, struct sockaddr *, d_thread_t *); static int natm_usr_disconnect(struct socket *); static int natm_usr_shutdown(struct socket *); static int natm_usr_send(struct socket *, int, struct mbuf *, struct sockaddr *, struct mbuf *, d_thread_t *); static int natm_usr_peeraddr(struct socket *, struct sockaddr **); static int natm_usr_control(struct socket *, u_long, caddr_t, struct ifnet *, d_thread_t *); static void natm_usr_abort(struct socket *); static int natm_usr_bind(struct socket *, struct sockaddr *, d_thread_t *); static int natm_usr_sockaddr(struct socket *, struct sockaddr **); static int natm_usr_attach(struct socket *so, int proto, d_thread_t *p) { struct natmpcb *npcb; int error = 0; npcb = (struct natmpcb *)so->so_pcb; KASSERT(npcb == NULL, ("natm_usr_attach: so_pcb != NULL")); if (so->so_snd.sb_hiwat == 0 || so->so_rcv.sb_hiwat == 0) { if (proto == PROTO_NATMAAL5) error = soreserve(so, natm5_sendspace, natm5_recvspace); else error = soreserve(so, natm0_sendspace, natm0_recvspace); if (error) goto out; } so->so_pcb = (caddr_t) (npcb = npcb_alloc(M_WAITOK)); npcb->npcb_socket = so; out: return (error); } -static int +static void natm_usr_detach(struct socket *so) { struct natmpcb *npcb; NATM_LOCK(); npcb = (struct natmpcb *)so->so_pcb; KASSERT(npcb != NULL, ("natm_usr_detach: npcb == NULL")); npcb_free(npcb, NPCB_DESTROY); /* drain */ so->so_pcb = NULL; NATM_UNLOCK(); - return (0); } static int natm_usr_connect(struct socket *so, struct sockaddr *nam, d_thread_t *p) { struct natmpcb *npcb; struct sockaddr_natm *snatm; struct atmio_openvcc op; struct ifnet *ifp; int error = 0; int proto = so->so_proto->pr_protocol; NATM_LOCK(); npcb = (struct natmpcb *)so->so_pcb; KASSERT(npcb != NULL, ("natm_usr_connect: npcb == NULL")); /* * validate nam and npcb */ snatm = (struct sockaddr_natm *)nam; if (snatm->snatm_len != sizeof(*snatm) || (npcb->npcb_flags & NPCB_FREE) == 0) { error = EINVAL; goto out; } if (snatm->snatm_family != AF_NATM) { error = EAFNOSUPPORT; goto out; } snatm->snatm_if[IFNAMSIZ - 1] = '\0'; /* XXX ensure null termination since ifunit() uses strcmp */ /* * convert interface string to ifp, validate. */ ifp = ifunit(snatm->snatm_if); if (ifp == NULL || (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { error = ENXIO; goto out; } if (ifp->if_output != atm_output) { error = EAFNOSUPPORT; goto out; } /* * register us with the NATM PCB layer */ if (npcb_add(npcb, ifp, snatm->snatm_vci, snatm->snatm_vpi) != npcb) { error = EADDRINUSE; goto out; } NATM_UNLOCK(); /* * open the channel */ bzero(&op, sizeof(op)); op.rxhand = npcb; op.param.flags = ATMIO_FLAG_PVC; op.param.vpi = npcb->npcb_vpi; op.param.vci = npcb->npcb_vci; op.param.rmtu = op.param.tmtu = ifp->if_mtu; op.param.aal = (proto == PROTO_NATMAAL5) ? ATMIO_AAL_5 : ATMIO_AAL_0; op.param.traffic = ATMIO_TRAFFIC_UBR; IFF_LOCKGIANT(ifp); if (ifp->if_ioctl == NULL || ifp->if_ioctl(ifp, SIOCATMOPENVCC, (caddr_t)&op) != 0) { IFF_UNLOCKGIANT(ifp); error = EIO; goto out; } IFF_UNLOCKGIANT(ifp); soisconnected(so); out: return (error); } static int natm_usr_disconnect(struct socket *so) { struct natmpcb *npcb; struct atmio_closevcc cl; struct ifnet *ifp; int error = 0; NATM_LOCK(); npcb = (struct natmpcb *)so->so_pcb; KASSERT(npcb != NULL, ("natm_usr_disconnect: npcb == NULL")); if ((npcb->npcb_flags & NPCB_CONNECTED) == 0) { printf("natm: disconnected check\n"); error = EIO; goto out; } ifp = npcb->npcb_ifp; /* * disable rx */ cl.vpi = npcb->npcb_vpi; cl.vci = npcb->npcb_vci; NATM_UNLOCK(); if (ifp->if_ioctl != NULL) { IFF_LOCKGIANT(ifp); ifp->if_ioctl(ifp, SIOCATMCLOSEVCC, (caddr_t)&cl); IFF_UNLOCKGIANT(ifp); } NATM_LOCK(); soisdisconnected(so); out: NATM_UNLOCK(); return (error); } static int natm_usr_shutdown(struct socket *so) { socantsendmore(so); return (0); } static int natm_usr_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, struct mbuf *control, d_thread_t *p) { struct natmpcb *npcb; struct atm_pseudohdr *aph; int error = 0; int proto = so->so_proto->pr_protocol; NATM_LOCK(); npcb = (struct natmpcb *)so->so_pcb; KASSERT(npcb != NULL, ("natm_usr_send: npcb == NULL")); if (control && control->m_len) { m_freem(control); m_freem(m); error = EINVAL; goto out; } /* * send the data. we must put an atm_pseudohdr on first */ M_PREPEND(m, sizeof(*aph), M_DONTWAIT); if (m == NULL) { error = ENOBUFS; goto out; } aph = mtod(m, struct atm_pseudohdr *); ATM_PH_VPI(aph) = npcb->npcb_vpi; ATM_PH_SETVCI(aph, npcb->npcb_vci); ATM_PH_FLAGS(aph) = (proto == PROTO_NATMAAL5) ? ATM_PH_AAL5 : 0; error = atm_output(npcb->npcb_ifp, m, NULL, NULL); out: NATM_UNLOCK(); return (error); } static int natm_usr_peeraddr(struct socket *so, struct sockaddr **nam) { struct natmpcb *npcb; struct sockaddr_natm *snatm, ssnatm; NATM_LOCK(); npcb = (struct natmpcb *)so->so_pcb; KASSERT(npcb != NULL, ("natm_usr_peeraddr: npcb == NULL")); snatm = &ssnatm; bzero(snatm, sizeof(*snatm)); snatm->snatm_len = sizeof(*snatm); snatm->snatm_family = AF_NATM; strlcpy(snatm->snatm_if, npcb->npcb_ifp->if_xname, sizeof(snatm->snatm_if)); snatm->snatm_vci = npcb->npcb_vci; snatm->snatm_vpi = npcb->npcb_vpi; NATM_UNLOCK(); *nam = sodupsockaddr((struct sockaddr *)snatm, M_WAITOK); return (0); } static int natm_usr_control(struct socket *so, u_long cmd, caddr_t arg, struct ifnet *ifp, d_thread_t *p) { struct natmpcb *npcb; int error; npcb = (struct natmpcb *)so->so_pcb; KASSERT(npcb != NULL, ("natm_usr_control: npcb == NULL")); if (ifp == NULL || ifp->if_ioctl == NULL) return (EOPNOTSUPP); IFF_LOCKGIANT(ifp); error = ((*ifp->if_ioctl)(ifp, cmd, arg)); IFF_UNLOCKGIANT(ifp); return (error); } static void natm_usr_abort(struct socket *so) { natm_usr_shutdown(so); } static int natm_usr_bind(struct socket *so, struct sockaddr *nam, d_thread_t *p) { return (EOPNOTSUPP); } static int natm_usr_sockaddr(struct socket *so, struct sockaddr **nam) { return (EOPNOTSUPP); } /* xxx - should be const */ struct pr_usrreqs natm_usrreqs = { .pru_abort = natm_usr_abort, .pru_attach = natm_usr_attach, .pru_bind = natm_usr_bind, .pru_connect = natm_usr_connect, .pru_control = natm_usr_control, .pru_detach = natm_usr_detach, .pru_disconnect = natm_usr_disconnect, .pru_peeraddr = natm_usr_peeraddr, .pru_send = natm_usr_send, .pru_shutdown = natm_usr_shutdown, .pru_sockaddr = natm_usr_sockaddr, }; /* * natmintr: interrupt * * note: we expect a socket pointer in rcvif rather than an interface * pointer. we can get the interface pointer from the so's PCB if * we really need it. */ void natmintr(struct mbuf *m) { struct socket *so; struct natmpcb *npcb; #ifdef DIAGNOSTIC M_ASSERTPKTHDR(m); #endif NATM_LOCK(); npcb = (struct natmpcb *)m->m_pkthdr.rcvif; /* XXX: overloaded */ so = npcb->npcb_socket; npcb->npcb_inq--; if (npcb->npcb_flags & NPCB_DRAIN) { if (npcb->npcb_inq == 0) FREE(npcb, M_PCB); /* done! */ NATM_UNLOCK(); m_freem(m); return; } if (npcb->npcb_flags & NPCB_FREE) { NATM_UNLOCK(); m_freem(m); /* drop */ return; } #ifdef NEED_TO_RESTORE_IFP m->m_pkthdr.rcvif = npcb->npcb_ifp; #else #ifdef DIAGNOSTIC m->m_pkthdr.rcvif = NULL; /* null it out to be safe */ #endif #endif if (sbspace(&so->so_rcv) > m->m_pkthdr.len) { #ifdef NATM_STAT natm_sookcnt++; natm_sookbytes += m->m_pkthdr.len; #endif sbappendrecord(&so->so_rcv, m); sorwakeup(so); NATM_UNLOCK(); } else { #ifdef NATM_STAT natm_sodropcnt++; natm_sodropbytes += m->m_pkthdr.len; #endif NATM_UNLOCK(); m_freem(m); } } /* * natm0_sysctl: not used, but here in case we want to add something * later... */ int natm0_sysctl(SYSCTL_HANDLER_ARGS) { /* All sysctl names at this level are terminal. */ return (ENOENT); } /* * natm5_sysctl: not used, but here in case we want to add something * later... */ int natm5_sysctl(SYSCTL_HANDLER_ARGS) { /* All sysctl names at this level are terminal. */ return (ENOENT); } diff --git a/sys/sys/protosw.h b/sys/sys/protosw.h index 5f39c69501a8..760aa7e41090 100644 --- a/sys/sys/protosw.h +++ b/sys/sys/protosw.h @@ -1,360 +1,360 @@ /*- * Copyright (c) 1982, 1986, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)protosw.h 8.1 (Berkeley) 6/2/93 * $FreeBSD$ */ #ifndef _SYS_PROTOSW_H_ #define _SYS_PROTOSW_H_ /* Forward declare these structures referenced from prototypes below. */ struct mbuf; struct thread; struct sockaddr; struct socket; struct sockopt; /*#ifdef _KERNEL*/ /* * Protocol switch table. * * Each protocol has a handle initializing one of these structures, * which is used for protocol-protocol and system-protocol communication. * * A protocol is called through the pr_init entry before any other. * Thereafter it is called every 200ms through the pr_fasttimo entry and * every 500ms through the pr_slowtimo for timer based actions. * The system will call the pr_drain entry if it is low on space and * this should throw away any non-critical data. * * Protocols pass data between themselves as chains of mbufs using * the pr_input and pr_output hooks. Pr_input passes data up (towards * the users) and pr_output passes it down (towards the interfaces); control * information passes up and down on pr_ctlinput and pr_ctloutput. * The protocol is responsible for the space occupied by any the * arguments to these entries and must dispose it. * * In retrospect, it would be a lot nicer to use an interface * similar to the vnode VOP interface. */ /* USE THESE FOR YOUR PROTOTYPES ! */ typedef void pr_input_t (struct mbuf *, int); typedef int pr_input6_t (struct mbuf **, int*, int); /* XXX FIX THIS */ typedef void pr_in_input_t (struct mbuf *, int, int); /* XXX FIX THIS */ typedef int pr_output_t (struct mbuf *, struct socket *); typedef int pr_in_output_t (struct mbuf *, struct socket *, struct sockaddr *); typedef void pr_ctlinput_t (int, struct sockaddr *, void *); typedef int pr_ctloutput_t (struct socket *, struct sockopt *); typedef void pr_init_t (void); typedef void pr_fasttimo_t (void); typedef void pr_slowtimo_t (void); typedef void pr_drain_t (void); typedef int pr_usrreq_t(struct socket *, int, struct mbuf *, struct mbuf *, struct mbuf *, struct thread *); struct protosw { short pr_type; /* socket type used for */ struct domain *pr_domain; /* domain protocol a member of */ short pr_protocol; /* protocol number */ short pr_flags; /* see below */ /* protocol-protocol hooks */ pr_input_t *pr_input; /* input to protocol (from below) */ pr_output_t *pr_output; /* output to protocol (from above) */ pr_ctlinput_t *pr_ctlinput; /* control input (from below) */ pr_ctloutput_t *pr_ctloutput; /* control output (from above) */ /* user-protocol hook */ pr_usrreq_t *pr_ousrreq; /* utility hooks */ pr_init_t *pr_init; pr_fasttimo_t *pr_fasttimo; /* fast timeout (200ms) */ pr_slowtimo_t *pr_slowtimo; /* slow timeout (500ms) */ pr_drain_t *pr_drain; /* flush any excess space possible */ struct pr_usrreqs *pr_usrreqs; /* supersedes pr_usrreq() */ }; /*#endif*/ #define PR_SLOWHZ 2 /* 2 slow timeouts per second */ #define PR_FASTHZ 5 /* 5 fast timeouts per second */ /* * This number should be defined again within each protocol family to avoid * confusion. */ #define PROTO_SPACER 32767 /* spacer for loadable protocols */ /* * Values for pr_flags. * PR_ADDR requires PR_ATOMIC; * PR_ADDR and PR_CONNREQUIRED are mutually exclusive. * PR_IMPLOPCL means that the protocol allows sendto without prior connect, * and the protocol understands the MSG_EOF flag. The first property is * is only relevant if PR_CONNREQUIRED is set (otherwise sendto is allowed * anyhow). */ #define PR_ATOMIC 0x01 /* exchange atomic messages only */ #define PR_ADDR 0x02 /* addresses given with messages */ #define PR_CONNREQUIRED 0x04 /* connection required by protocol */ #define PR_WANTRCVD 0x08 /* want PRU_RCVD calls */ #define PR_RIGHTS 0x10 /* passes capabilities */ #define PR_IMPLOPCL 0x20 /* implied open/close */ #define PR_LASTHDR 0x40 /* enforce ipsec policy; last header */ /* * The arguments to usrreq are: * (*protosw[].pr_usrreq)(up, req, m, nam, opt); * where up is a (struct socket *), req is one of these requests, * m is an optional mbuf chain containing a message, * nam is an optional mbuf chain containing an address, * and opt is a pointer to a socketopt structure or nil. * The protocol is responsible for disposal of the mbuf chain m, * the caller is responsible for any space held by nam and opt. * A non-zero return from usrreq gives an * UNIX error number which should be passed to higher level software. */ #define PRU_ATTACH 0 /* attach protocol to up */ #define PRU_DETACH 1 /* detach protocol from up */ #define PRU_BIND 2 /* bind socket to address */ #define PRU_LISTEN 3 /* listen for connection */ #define PRU_CONNECT 4 /* establish connection to peer */ #define PRU_ACCEPT 5 /* accept connection from peer */ #define PRU_DISCONNECT 6 /* disconnect from peer */ #define PRU_SHUTDOWN 7 /* won't send any more data */ #define PRU_RCVD 8 /* have taken data; more room now */ #define PRU_SEND 9 /* send this data */ #define PRU_ABORT 10 /* abort (fast DISCONNECT, DETATCH) */ #define PRU_CONTROL 11 /* control operations on protocol */ #define PRU_SENSE 12 /* return status into m */ #define PRU_RCVOOB 13 /* retrieve out of band data */ #define PRU_SENDOOB 14 /* send out of band data */ #define PRU_SOCKADDR 15 /* fetch socket's address */ #define PRU_PEERADDR 16 /* fetch peer's address */ #define PRU_CONNECT2 17 /* connect two sockets */ /* begin for protocols internal use */ #define PRU_FASTTIMO 18 /* 200ms timeout */ #define PRU_SLOWTIMO 19 /* 500ms timeout */ #define PRU_PROTORCV 20 /* receive from below */ #define PRU_PROTOSEND 21 /* send to below */ /* end for protocol's internal use */ #define PRU_SEND_EOF 22 /* send and close */ #define PRU_NREQ 22 #ifdef PRUREQUESTS const char *prurequests[] = { "ATTACH", "DETACH", "BIND", "LISTEN", "CONNECT", "ACCEPT", "DISCONNECT", "SHUTDOWN", "RCVD", "SEND", "ABORT", "CONTROL", "SENSE", "RCVOOB", "SENDOOB", "SOCKADDR", "PEERADDR", "CONNECT2", "FASTTIMO", "SLOWTIMO", "PROTORCV", "PROTOSEND", "SEND_EOF", }; #endif #ifdef _KERNEL /* users shouldn't see this decl */ struct ifnet; struct stat; struct ucred; struct uio; /* * If the ordering here looks odd, that's because it's alphabetical. * Having this structure separated out from the main protoswitch is allegedly * a big (12 cycles per call) lose on high-end CPUs. We will eventually * migrate this stuff back into the main structure. * * Some fields initialized to defaults if they are NULL. * See uipc_domain.c:net_init_domain() */ struct pr_usrreqs { double __Break_the_struct_layout_for_now; void (*pru_abort)(struct socket *so); int (*pru_accept)(struct socket *so, struct sockaddr **nam); int (*pru_attach)(struct socket *so, int proto, struct thread *td); int (*pru_bind)(struct socket *so, struct sockaddr *nam, struct thread *td); int (*pru_connect)(struct socket *so, struct sockaddr *nam, struct thread *td); int (*pru_connect2)(struct socket *so1, struct socket *so2); int (*pru_control)(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp, struct thread *td); - int (*pru_detach)(struct socket *so); + void (*pru_detach)(struct socket *so); int (*pru_disconnect)(struct socket *so); int (*pru_listen)(struct socket *so, int backlog, struct thread *td); int (*pru_peeraddr)(struct socket *so, struct sockaddr **nam); int (*pru_rcvd)(struct socket *so, int flags); int (*pru_rcvoob)(struct socket *so, struct mbuf *m, int flags); int (*pru_send)(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *control, struct thread *td); #define PRUS_OOB 0x1 #define PRUS_EOF 0x2 #define PRUS_MORETOCOME 0x4 int (*pru_sense)(struct socket *so, struct stat *sb); int (*pru_shutdown)(struct socket *so); int (*pru_sockaddr)(struct socket *so, struct sockaddr **nam); /* * These three added later, so they are out of order. They are used * for shortcutting (fast path input/output) in some protocols. * XXX - that's a lie, they are not implemented yet * Rather than calling sosend() etc. directly, calls are made * through these entry points. For protocols which still use * the generic code, these just point to those routines. */ int (*pru_sosend)(struct socket *so, struct sockaddr *addr, struct uio *uio, struct mbuf *top, struct mbuf *control, int flags, struct thread *td); int (*pru_soreceive)(struct socket *so, struct sockaddr **paddr, struct uio *uio, struct mbuf **mp0, struct mbuf **controlp, int *flagsp); int (*pru_sopoll)(struct socket *so, int events, struct ucred *cred, struct thread *td); void (*pru_sosetlabel)(struct socket *so); }; /* * All nonvoid pru_*() functions below return EOPNOTSUPP. */ void pru_abort_notsupp(struct socket *so); int pru_accept_notsupp(struct socket *so, struct sockaddr **nam); int pru_attach_notsupp(struct socket *so, int proto, struct thread *td); int pru_bind_notsupp(struct socket *so, struct sockaddr *nam, struct thread *td); int pru_connect_notsupp(struct socket *so, struct sockaddr *nam, struct thread *td); int pru_connect2_notsupp(struct socket *so1, struct socket *so2); int pru_control_notsupp(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp, struct thread *td); -int pru_detach_notsupp(struct socket *so); +void pru_detach_notsupp(struct socket *so); int pru_disconnect_notsupp(struct socket *so); int pru_listen_notsupp(struct socket *so, int backlog, struct thread *td); int pru_peeraddr_notsupp(struct socket *so, struct sockaddr **nam); int pru_rcvd_notsupp(struct socket *so, int flags); int pru_rcvoob_notsupp(struct socket *so, struct mbuf *m, int flags); int pru_send_notsupp(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *control, struct thread *td); int pru_sense_null(struct socket *so, struct stat *sb); int pru_shutdown_notsupp(struct socket *so); int pru_sockaddr_notsupp(struct socket *so, struct sockaddr **nam); int pru_sosend_notsupp(struct socket *so, struct sockaddr *addr, struct uio *uio, struct mbuf *top, struct mbuf *control, int flags, struct thread *td); int pru_soreceive_notsupp(struct socket *so, struct sockaddr **paddr, struct uio *uio, struct mbuf **mp0, struct mbuf **controlp, int *flagsp); int pru_sopoll_notsupp(struct socket *so, int events, struct ucred *cred, struct thread *td); void pru_sosetlabel_null(struct socket *so); #endif /* _KERNEL */ /* * The arguments to the ctlinput routine are * (*protosw[].pr_ctlinput)(cmd, sa, arg); * where cmd is one of the commands below, sa is a pointer to a sockaddr, * and arg is a `void *' argument used within a protocol family. */ #define PRC_IFDOWN 0 /* interface transition */ #define PRC_ROUTEDEAD 1 /* select new route if possible ??? */ #define PRC_IFUP 2 /* interface has come back up */ #define PRC_QUENCH2 3 /* DEC congestion bit says slow down */ #define PRC_QUENCH 4 /* some one said to slow down */ #define PRC_MSGSIZE 5 /* message size forced drop */ #define PRC_HOSTDEAD 6 /* host appears to be down */ #define PRC_HOSTUNREACH 7 /* deprecated (use PRC_UNREACH_HOST) */ #define PRC_UNREACH_NET 8 /* no route to network */ #define PRC_UNREACH_HOST 9 /* no route to host */ #define PRC_UNREACH_PROTOCOL 10 /* dst says bad protocol */ #define PRC_UNREACH_PORT 11 /* bad port # */ /* was PRC_UNREACH_NEEDFRAG 12 (use PRC_MSGSIZE) */ #define PRC_UNREACH_SRCFAIL 13 /* source route failed */ #define PRC_REDIRECT_NET 14 /* net routing redirect */ #define PRC_REDIRECT_HOST 15 /* host routing redirect */ #define PRC_REDIRECT_TOSNET 16 /* redirect for type of service & net */ #define PRC_REDIRECT_TOSHOST 17 /* redirect for tos & host */ #define PRC_TIMXCEED_INTRANS 18 /* packet lifetime expired in transit */ #define PRC_TIMXCEED_REASS 19 /* lifetime expired on reass q */ #define PRC_PARAMPROB 20 /* header incorrect */ #define PRC_UNREACH_ADMIN_PROHIB 21 /* packet administrativly prohibited */ #define PRC_NCMDS 22 #define PRC_IS_REDIRECT(cmd) \ ((cmd) >= PRC_REDIRECT_NET && (cmd) <= PRC_REDIRECT_TOSHOST) #ifdef PRCREQUESTS char *prcrequests[] = { "IFDOWN", "ROUTEDEAD", "IFUP", "DEC-BIT-QUENCH2", "QUENCH", "MSGSIZE", "HOSTDEAD", "#7", "NET-UNREACH", "HOST-UNREACH", "PROTO-UNREACH", "PORT-UNREACH", "#12", "SRCFAIL-UNREACH", "NET-REDIRECT", "HOST-REDIRECT", "TOSNET-REDIRECT", "TOSHOST-REDIRECT", "TX-INTRANS", "TX-REASS", "PARAMPROB", "ADMIN-UNREACH" }; #endif /* * The arguments to ctloutput are: * (*protosw[].pr_ctloutput)(req, so, level, optname, optval, p); * req is one of the actions listed below, so is a (struct socket *), * level is an indication of which protocol layer the option is intended. * optname is a protocol dependent socket option request, * optval is a pointer to a mbuf-chain pointer, for value-return results. * The protocol is responsible for disposal of the mbuf chain *optval * if supplied, * the caller is responsible for any space held by *optval, when returned. * A non-zero return from usrreq gives an * UNIX error number which should be passed to higher level software. */ #define PRCO_GETOPT 0 #define PRCO_SETOPT 1 #define PRCO_NCMDS 2 #ifdef PRCOREQUESTS char *prcorequests[] = { "GETOPT", "SETOPT", }; #endif #ifdef _KERNEL void pfctlinput(int, struct sockaddr *); void pfctlinput2(int, struct sockaddr *, void *); struct protosw *pffindproto(int family, int protocol, int type); struct protosw *pffindtype(int family, int type); int pf_proto_register(int family, struct protosw *npr); int pf_proto_unregister(int family, int protocol, int type); #endif #endif