svn commit: r265946 - in stable/10: lib/libc/net sys/netinet sys/netinet6 sys/sys
Bjoern A. Zeeb
bz at FreeBSD.org
Sat Aug 16 13:18:14 UTC 2014
On 13 May 2014, at 06:05 , Kevin Lo <kevlo at FreeBSD.org> wrote:
> Author: kevlo
> Date: Tue May 13 06:05:53 2014
> New Revision: 265946
> URL: http://svnweb.freebsd.org/changeset/base/265946
>
> Log:
> MFC r264212,r264213,r264248,r265776,r265811,r265909:
Just for the records; this commit also merged unrelated r259887:
Add more (IPv6) related Internet Protocols:
- Host Identity Protocol (RFC5201)
- Shim6 Protocol (RFC5533)
- 2x experimentation and testing (RFC3692, RFC4727)
This does not indicate interest to implement/support these protocols,
but they are part of the "IPv6 Extension Header Types" [1] based on RFC7045
and might thus be needed by filtering and next header parsing
implementations.
References: [1] http://www.iana.org/assignments/ipv6-parameters
Obtained from: http://www.iana.org/assignments/protocol-numbers
>
> - Add support for UDP-Lite protocol (RFC 3828) to IPv4 and IPv6 stacks.
> Tested with vlc and a test suite [1].
> [1] http://www.erg.abdn.ac.uk/~gerrit/udp-lite/files/udplite_linux.tar.gz
>
> Reviewed by: jhb, glebius, adrian
>
> - Fix a logic bug which prevented the sending of UDP packet with 0 checksum.
>
> - Disable TX checksum offload for UDP-Lite completely. It wasn't used for
> partial checksum coverage, but even for full checksum coverage it doesn't
> work.
>
> Added:
> stable/10/sys/netinet/udplite.h
> - copied unchanged from r264212, head/sys/netinet/udplite.h
> Modified:
> stable/10/lib/libc/net/getaddrinfo.c
> stable/10/sys/netinet/in.c
> stable/10/sys/netinet/in.h
> stable/10/sys/netinet/in_pcb.c
> stable/10/sys/netinet/in_proto.c
> stable/10/sys/netinet/udp_usrreq.c
> stable/10/sys/netinet/udp_var.h
> stable/10/sys/netinet6/in6_ifattach.c
> stable/10/sys/netinet6/in6_proto.c
> stable/10/sys/netinet6/udp6_usrreq.c
> stable/10/sys/netinet6/udp6_var.h
> stable/10/sys/sys/param.h
> Directory Properties:
> stable/10/ (props changed)
>
> Modified: stable/10/lib/libc/net/getaddrinfo.c
> ==============================================================================
> --- stable/10/lib/libc/net/getaddrinfo.c Tue May 13 05:26:43 2014 (r265945)
> +++ stable/10/lib/libc/net/getaddrinfo.c Tue May 13 06:05:53 2014 (r265946)
> @@ -170,12 +170,14 @@ static const struct explore explore[] =
> { PF_INET6, SOCK_STREAM, IPPROTO_TCP, 0x07 },
> { PF_INET6, SOCK_STREAM, IPPROTO_SCTP, 0x03 },
> { PF_INET6, SOCK_SEQPACKET, IPPROTO_SCTP, 0x07 },
> + { PF_INET6, SOCK_DGRAM, IPPROTO_UDPLITE, 0x03 },
> { PF_INET6, SOCK_RAW, ANY, 0x05 },
> #endif
> { PF_INET, SOCK_DGRAM, IPPROTO_UDP, 0x07 },
> { PF_INET, SOCK_STREAM, IPPROTO_TCP, 0x07 },
> { PF_INET, SOCK_STREAM, IPPROTO_SCTP, 0x03 },
> { PF_INET, SOCK_SEQPACKET, IPPROTO_SCTP, 0x07 },
> + { PF_INET, SOCK_DGRAM, IPPROTO_UDPLITE, 0x03 },
> { PF_INET, SOCK_RAW, ANY, 0x05 },
> { -1, 0, 0, 0 },
> };
> @@ -1477,6 +1479,9 @@ get_port(struct addrinfo *ai, const char
> case IPPROTO_SCTP:
> proto = "sctp";
> break;
> + case IPPROTO_UDPLITE:
> + proto = "udplite";
> + break;
> default:
> proto = NULL;
> break;
>
> Modified: stable/10/sys/netinet/in.c
> ==============================================================================
> --- stable/10/sys/netinet/in.c Tue May 13 05:26:43 2014 (r265945)
> +++ stable/10/sys/netinet/in.c Tue May 13 06:05:53 2014 (r265946)
> @@ -1167,6 +1167,7 @@ in_ifdetach(struct ifnet *ifp)
>
> in_pcbpurgeif0(&V_ripcbinfo, ifp);
> in_pcbpurgeif0(&V_udbinfo, ifp);
> + in_pcbpurgeif0(&V_ulitecbinfo, ifp);
> in_purgemaddrs(ifp);
> }
>
>
> Modified: stable/10/sys/netinet/in.h
> ==============================================================================
> --- stable/10/sys/netinet/in.h Tue May 13 05:26:43 2014 (r265945)
> +++ stable/10/sys/netinet/in.h Tue May 13 06:05:53 2014 (r265946)
> @@ -237,12 +237,17 @@ __END_DECLS
> #define IPPROTO_IPCOMP 108 /* payload compression (IPComp) */
> #define IPPROTO_SCTP 132 /* SCTP */
> #define IPPROTO_MH 135 /* IPv6 Mobility Header */
> +#define IPPROTO_UDPLITE 136 /* UDP-Lite */
> +#define IPPROTO_HIP 139 /* IP6 Host Identity Protocol */
> +#define IPPROTO_SHIM6 140 /* IP6 Shim6 Protocol */
> /* 101-254: Partly Unassigned */
> #define IPPROTO_PIM 103 /* Protocol Independent Mcast */
> #define IPPROTO_CARP 112 /* CARP */
> #define IPPROTO_PGM 113 /* PGM */
> #define IPPROTO_MPLS 137 /* MPLS-in-IP */
> #define IPPROTO_PFSYNC 240 /* PFSYNC */
> +#define IPPROTO_RESERVED_253 253 /* Reserved */
> +#define IPPROTO_RESERVED_254 254 /* Reserved */
> /* 255: Reserved */
> /* BSD Private, local use, namespace incursion, no longer used */
> #define IPPROTO_OLD_DIVERT 254 /* OLD divert pseudo-proto */
>
> Modified: stable/10/sys/netinet/in_pcb.c
> ==============================================================================
> --- stable/10/sys/netinet/in_pcb.c Tue May 13 05:26:43 2014 (r265945)
> +++ stable/10/sys/netinet/in_pcb.c Tue May 13 06:05:53 2014 (r265946)
> @@ -386,13 +386,14 @@ in_pcb_lport(struct inpcb *inp, struct i
> lastport = &pcbinfo->ipi_lastport;
> }
> /*
> - * For UDP, use random port allocation as long as the user
> + * For UDP(-Lite), use random port allocation as long as the user
> * allows it. For TCP (and as of yet unknown) connections,
> * use random port allocation only if the user allows it AND
> * ipport_tick() allows it.
> */
> if (V_ipport_randomized &&
> - (!V_ipport_stoprandom || pcbinfo == &V_udbinfo))
> + (!V_ipport_stoprandom || pcbinfo == &V_udbinfo ||
> + pcbinfo == &V_ulitecbinfo))
> dorandom = 1;
> else
> dorandom = 0;
> @@ -402,8 +403,8 @@ in_pcb_lport(struct inpcb *inp, struct i
> */
> if (first == last)
> dorandom = 0;
> - /* Make sure to not include UDP packets in the count. */
> - if (pcbinfo != &V_udbinfo)
> + /* Make sure to not include UDP(-Lite) packets in the count. */
> + if (pcbinfo != &V_udbinfo || pcbinfo != &V_ulitecbinfo)
> V_ipport_tcpallocs++;
> /*
> * Instead of having two loops further down counting up or down
>
> Modified: stable/10/sys/netinet/in_proto.c
> ==============================================================================
> --- stable/10/sys/netinet/in_proto.c Tue May 13 05:26:43 2014 (r265945)
> +++ stable/10/sys/netinet/in_proto.c Tue May 13 06:05:53 2014 (r265946)
> @@ -184,6 +184,20 @@ struct protosw inetsw[] = {
> },
> #endif /* SCTP */
> {
> + .pr_type = SOCK_DGRAM,
> + .pr_domain = &inetdomain,
> + .pr_protocol = IPPROTO_UDPLITE,
> + .pr_flags = PR_ATOMIC|PR_ADDR,
> + .pr_input = udp_input,
> + .pr_ctlinput = udplite_ctlinput,
> + .pr_ctloutput = udp_ctloutput,
> + .pr_init = udplite_init,
> +#ifdef VIMAGE
> + .pr_destroy = udplite_destroy,
> +#endif
> + .pr_usrreqs = &udp_usrreqs
> +},
> +{
> .pr_type = SOCK_RAW,
> .pr_domain = &inetdomain,
> .pr_protocol = IPPROTO_RAW,
>
> Modified: stable/10/sys/netinet/udp_usrreq.c
> ==============================================================================
> --- stable/10/sys/netinet/udp_usrreq.c Tue May 13 05:26:43 2014 (r265945)
> +++ stable/10/sys/netinet/udp_usrreq.c Tue May 13 06:05:53 2014 (r265946)
> @@ -3,6 +3,7 @@
> * The Regents of the University of California.
> * Copyright (c) 2008 Robert N. M. Watson
> * Copyright (c) 2010-2011 Juniper Networks, Inc.
> + * Copyright (c) 2014 Kevin Lo
> * All rights reserved.
> *
> * Portions of this software were developed by Robert N. M. Watson under
> @@ -87,6 +88,7 @@ __FBSDID("$FreeBSD$");
> #endif
> #include <netinet/udp.h>
> #include <netinet/udp_var.h>
> +#include <netinet/udplite.h>
>
> #ifdef IPSEC
> #include <netipsec/ipsec.h>
> @@ -98,8 +100,9 @@ __FBSDID("$FreeBSD$");
> #include <security/mac/mac_framework.h>
>
> /*
> - * UDP protocol implementation.
> + * UDP and UDP-Lite protocols implementation.
> * Per RFC 768, August, 1980.
> + * Per RFC 3828, July, 2004.
> */
>
> /*
> @@ -139,6 +142,8 @@ SYSCTL_ULONG(_net_inet_udp, UDPCTL_RECVS
>
> VNET_DEFINE(struct inpcbhead, udb); /* from udp_var.h */
> VNET_DEFINE(struct inpcbinfo, udbinfo);
> +VNET_DEFINE(struct inpcbhead, ulitecb);
> +VNET_DEFINE(struct inpcbinfo, ulitecbinfo);
> static VNET_DEFINE(uma_zone_t, udpcb_zone);
> #define V_udpcb_zone VNET(udpcb_zone)
>
> @@ -187,6 +192,16 @@ udp_inpcb_init(void *mem, int size, int
> return (0);
> }
>
> +static int
> +udplite_inpcb_init(void *mem, int size, int flags)
> +{
> + struct inpcb *inp;
> +
> + inp = mem;
> + INP_LOCK_INIT(inp, "inp", "udpliteinp");
> + return (0);
> +}
> +
> void
> udp_init(void)
> {
> @@ -202,6 +217,15 @@ udp_init(void)
> EVENTHANDLER_PRI_ANY);
> }
>
> +void
> +udplite_init(void)
> +{
> +
> + in_pcbinfo_init(&V_ulitecbinfo, "udplite", &V_ulitecb, UDBHASHSIZE,
> + UDBHASHSIZE, "udplite_inpcb", udplite_inpcb_init, NULL,
> + UMA_ZONE_NOFREE, IPI_HASHFIELDS_2TUPLE);
> +}
> +
> /*
> * Kernel module interface for updating udpstat. The argument is an index
> * into udpstat treated as an array of u_long. While this encodes the
> @@ -243,6 +267,13 @@ udp_destroy(void)
> in_pcbinfo_destroy(&V_udbinfo);
> uma_zdestroy(V_udpcb_zone);
> }
> +
> +void
> +udplite_destroy(void)
> +{
> +
> + in_pcbinfo_destroy(&V_ulitecbinfo);
> +}
> #endif
>
> #ifdef INET
> @@ -346,9 +377,12 @@ udp_input(struct mbuf *m, int off)
> struct ifnet *ifp;
> struct inpcb *inp;
> uint16_t len, ip_len;
> + struct inpcbinfo *pcbinfo;
> struct ip save_ip;
> struct sockaddr_in udp_in;
> struct m_tag *fwd_tag;
> + int cscov_partial;
> + uint8_t pr;
>
> ifp = m->m_pkthdr.rcvif;
> UDPSTAT_INC(udps_ipackets);
> @@ -368,13 +402,15 @@ udp_input(struct mbuf *m, int off)
> */
> ip = mtod(m, struct ip *);
> if (m->m_len < iphlen + sizeof(struct udphdr)) {
> - if ((m = m_pullup(m, iphlen + sizeof(struct udphdr))) == 0) {
> + if ((m = m_pullup(m, iphlen + sizeof(struct udphdr))) == NULL) {
> UDPSTAT_INC(udps_hdrops);
> return;
> }
> ip = mtod(m, struct ip *);
> }
> uh = (struct udphdr *)((caddr_t)ip + iphlen);
> + pr = ip->ip_p;
> + cscov_partial = (pr == IPPROTO_UDPLITE) ? 1 : 0;
>
> /*
> * Destination port of 0 is illegal, based on RFC768.
> @@ -398,12 +434,18 @@ udp_input(struct mbuf *m, int off)
> */
> len = ntohs((u_short)uh->uh_ulen);
> ip_len = ntohs(ip->ip_len) - iphlen;
> + if (pr == IPPROTO_UDPLITE && len == 0) {
> + /* Zero means checksum over the complete packet. */
> + len = ip_len;
> + cscov_partial = 0;
> + }
> if (ip_len != len) {
> if (len > ip_len || len < sizeof(struct udphdr)) {
> UDPSTAT_INC(udps_badlen);
> goto badunlocked;
> }
> - m_adj(m, len - ip_len);
> + if (pr == IPPROTO_UDP)
> + m_adj(m, len - ip_len);
> }
>
> /*
> @@ -421,20 +463,22 @@ udp_input(struct mbuf *m, int off)
> if (uh->uh_sum) {
> u_short uh_sum;
>
> - if (m->m_pkthdr.csum_flags & CSUM_DATA_VALID) {
> + if ((m->m_pkthdr.csum_flags & CSUM_DATA_VALID) &&
> + !cscov_partial) {
> if (m->m_pkthdr.csum_flags & CSUM_PSEUDO_HDR)
> uh_sum = m->m_pkthdr.csum_data;
> else
> 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));
> + m->m_pkthdr.csum_data + pr));
> 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;
> + ((struct ipovly *)ip)->ih_len = (pr == IPPROTO_UDP) ?
> + uh->uh_ulen : htons(ip_len);
> uh_sum = in_cksum(m, len + sizeof (struct ip));
> bcopy(b, ((struct ipovly *)ip)->ih_x1, 9);
> }
> @@ -446,14 +490,17 @@ udp_input(struct mbuf *m, int off)
> } else
> UDPSTAT_INC(udps_nosum);
>
> + pcbinfo = get_inpcbinfo(pr);
> if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr)) ||
> in_broadcast(ip->ip_dst, ifp)) {
> struct inpcb *last;
> + struct inpcbhead *pcblist;
> struct ip_moptions *imo;
>
> - INP_INFO_RLOCK(&V_udbinfo);
> + INP_INFO_RLOCK(pcbinfo);
> + pcblist = get_pcblist(pr);
> last = NULL;
> - LIST_FOREACH(inp, &V_udb, inp_list) {
> + LIST_FOREACH(inp, pcblist, inp_list) {
> if (inp->inp_lport != uh->uh_dport)
> continue;
> #ifdef INET6
> @@ -539,12 +586,12 @@ udp_input(struct mbuf *m, int off)
> UDPSTAT_INC(udps_noportbcast);
> if (inp)
> INP_RUNLOCK(inp);
> - INP_INFO_RUNLOCK(&V_udbinfo);
> + INP_INFO_RUNLOCK(pcbinfo);
> goto badunlocked;
> }
> udp_append(last, ip, m, iphlen, &udp_in);
> INP_RUNLOCK(last);
> - INP_INFO_RUNLOCK(&V_udbinfo);
> + INP_INFO_RUNLOCK(pcbinfo);
> return;
> }
>
> @@ -565,7 +612,7 @@ udp_input(struct mbuf *m, int off)
> * Transparently forwarded. Pretend to be the destination.
> * Already got one like this?
> */
> - inp = in_pcblookup_mbuf(&V_udbinfo, ip->ip_src, uh->uh_sport,
> + inp = in_pcblookup_mbuf(pcbinfo, ip->ip_src, uh->uh_sport,
> ip->ip_dst, uh->uh_dport, INPLOOKUP_RLOCKPCB, ifp, m);
> if (!inp) {
> /*
> @@ -573,7 +620,7 @@ udp_input(struct mbuf *m, int off)
> * Because we've rewritten the destination address,
> * any hardware-generated hash is ignored.
> */
> - inp = in_pcblookup(&V_udbinfo, ip->ip_src,
> + inp = in_pcblookup(pcbinfo, ip->ip_src,
> uh->uh_sport, next_hop->sin_addr,
> next_hop->sin_port ? htons(next_hop->sin_port) :
> uh->uh_dport, INPLOOKUP_WILDCARD |
> @@ -583,7 +630,7 @@ udp_input(struct mbuf *m, int off)
> m_tag_delete(m, fwd_tag);
> m->m_flags &= ~M_IP_NEXTHOP;
> } else
> - inp = in_pcblookup_mbuf(&V_udbinfo, ip->ip_src, uh->uh_sport,
> + inp = in_pcblookup_mbuf(pcbinfo, ip->ip_src, uh->uh_sport,
> ip->ip_dst, uh->uh_dport, INPLOOKUP_WILDCARD |
> INPLOOKUP_RLOCKPCB, ifp, m);
> if (inp == NULL) {
> @@ -619,6 +666,16 @@ udp_input(struct mbuf *m, int off)
> m_freem(m);
> return;
> }
> + if (cscov_partial) {
> + struct udpcb *up;
> +
> + up = intoudpcb(inp);
> + if (up->u_rxcslen > len) {
> + INP_RUNLOCK(inp);
> + m_freem(m);
> + return;
> + }
> + }
>
> UDP_PROBE(receive, NULL, inp, ip, inp, uh);
> udp_append(inp, ip, m, iphlen, &udp_in);
> @@ -653,8 +710,9 @@ udp_notify(struct inpcb *inp, int errno)
> }
>
> #ifdef INET
> -void
> -udp_ctlinput(int cmd, struct sockaddr *sa, void *vip)
> +static void
> +udp_common_ctlinput(int cmd, struct sockaddr *sa, void *vip,
> + struct inpcbinfo *pcbinfo)
> {
> struct ip *ip = vip;
> struct udphdr *uh;
> @@ -683,7 +741,7 @@ udp_ctlinput(int cmd, struct sockaddr *s
> return;
> if (ip != NULL) {
> uh = (struct udphdr *)((caddr_t)ip + (ip->ip_hl << 2));
> - inp = in_pcblookup(&V_udbinfo, faddr, uh->uh_dport,
> + inp = in_pcblookup(pcbinfo, faddr, uh->uh_dport,
> ip->ip_src, uh->uh_sport, INPLOOKUP_RLOCKPCB, NULL);
> if (inp != NULL) {
> INP_RLOCK_ASSERT(inp);
> @@ -693,9 +751,22 @@ udp_ctlinput(int cmd, struct sockaddr *s
> INP_RUNLOCK(inp);
> }
> } else
> - in_pcbnotifyall(&V_udbinfo, faddr, inetctlerrmap[cmd],
> + in_pcbnotifyall(pcbinfo, faddr, inetctlerrmap[cmd],
> udp_notify);
> }
> +void
> +udp_ctlinput(int cmd, struct sockaddr *sa, void *vip)
> +{
> +
> + return (udp_common_ctlinput(cmd, sa, vip, &V_udbinfo));
> +}
> +
> +void
> +udplite_ctlinput(int cmd, struct sockaddr *sa, void *vip)
> +{
> +
> + return (udp_common_ctlinput(cmd, sa, vip, &V_ulitecbinfo));
> +}
> #endif /* INET */
>
> static int
> @@ -851,16 +922,16 @@ SYSCTL_PROC(_net_inet_udp, OID_AUTO, get
> int
> udp_ctloutput(struct socket *so, struct sockopt *sopt)
> {
> - int error = 0, optval;
> struct inpcb *inp;
> -#ifdef IPSEC_NAT_T
> struct udpcb *up;
> -#endif
> + int isudplite, error, optval;
>
> + error = 0;
> + isudplite = (so->so_proto->pr_protocol == IPPROTO_UDPLITE) ? 1 : 0;
> inp = sotoinpcb(so);
> KASSERT(inp != NULL, ("%s: inp == NULL", __func__));
> INP_WLOCK(inp);
> - if (sopt->sopt_level != IPPROTO_UDP) {
> + if (sopt->sopt_level != so->so_proto->pr_protocol) {
> #ifdef INET6
> if (INP_CHECK_SOCKAF(so, AF_INET6)) {
> INP_WUNLOCK(inp);
> @@ -918,6 +989,34 @@ udp_ctloutput(struct socket *so, struct
> }
> INP_WUNLOCK(inp);
> break;
> + case UDPLITE_SEND_CSCOV:
> + case UDPLITE_RECV_CSCOV:
> + if (!isudplite) {
> + INP_WUNLOCK(inp);
> + error = ENOPROTOOPT;
> + break;
> + }
> + INP_WUNLOCK(inp);
> + error = sooptcopyin(sopt, &optval, sizeof(optval),
> + sizeof(optval));
> + if (error != 0)
> + break;
> + inp = sotoinpcb(so);
> + KASSERT(inp != NULL, ("%s: inp == NULL", __func__));
> + INP_WLOCK(inp);
> + up = intoudpcb(inp);
> + KASSERT(up != NULL, ("%s: up == NULL", __func__));
> + if (optval != 0 && optval < 8) {
> + INP_WUNLOCK(inp);
> + error = EINVAL;
> + break;
> + }
> + if (sopt->sopt_name == UDPLITE_SEND_CSCOV)
> + up->u_txcslen = optval;
> + else
> + up->u_rxcslen = optval;
> + INP_WUNLOCK(inp);
> + break;
> default:
> INP_WUNLOCK(inp);
> error = ENOPROTOOPT;
> @@ -935,6 +1034,22 @@ udp_ctloutput(struct socket *so, struct
> error = sooptcopyout(sopt, &optval, sizeof optval);
> break;
> #endif
> + case UDPLITE_SEND_CSCOV:
> + case UDPLITE_RECV_CSCOV:
> + if (!isudplite) {
> + INP_WUNLOCK(inp);
> + error = ENOPROTOOPT;
> + break;
> + }
> + up = intoudpcb(inp);
> + KASSERT(up != NULL, ("%s: up == NULL", __func__));
> + if (sopt->sopt_name == UDPLITE_SEND_CSCOV)
> + optval = up->u_txcslen;
> + else
> + optval = up->u_rxcslen;
> + INP_WUNLOCK(inp);
> + error = sooptcopyout(sopt, &optval, sizeof(optval));
> + break;
> default:
> INP_WUNLOCK(inp);
> error = ENOPROTOOPT;
> @@ -957,12 +1072,16 @@ udp_output(struct inpcb *inp, struct mbu
> int len = m->m_pkthdr.len;
> struct in_addr faddr, laddr;
> struct cmsghdr *cm;
> + struct inpcbinfo *pcbinfo;
> struct sockaddr_in *sin, src;
> + int cscov_partial = 0;
> int error = 0;
> int ipflags;
> u_short fport, lport;
> int unlock_udbinfo;
> u_char tos;
> + uint8_t pr;
> + uint16_t cscov = 0;
>
> /*
> * udp_output() may need to temporarily bind or connect the current
> @@ -1057,12 +1176,14 @@ udp_output(struct inpcb *inp, struct mbu
> *
> * XXXRW: Check that hash locking update here is correct.
> */
> + pr = inp->inp_socket->so_proto->pr_protocol;
> + pcbinfo = get_inpcbinfo(pr);
> sin = (struct sockaddr_in *)addr;
> if (sin != NULL &&
> (inp->inp_laddr.s_addr == INADDR_ANY && inp->inp_lport == 0)) {
> INP_RUNLOCK(inp);
> INP_WLOCK(inp);
> - INP_HASH_WLOCK(&V_udbinfo);
> + INP_HASH_WLOCK(pcbinfo);
> unlock_udbinfo = UH_WLOCKED;
> } else if ((sin != NULL && (
> (sin->sin_addr.s_addr == INADDR_ANY) ||
> @@ -1070,7 +1191,7 @@ udp_output(struct inpcb *inp, struct mbu
> (inp->inp_laddr.s_addr == INADDR_ANY) ||
> (inp->inp_lport == 0))) ||
> (src.sin_family == AF_INET)) {
> - INP_HASH_RLOCK(&V_udbinfo);
> + INP_HASH_RLOCK(pcbinfo);
> unlock_udbinfo = UH_RLOCKED;
> } else
> unlock_udbinfo = UH_UNLOCKED;
> @@ -1083,7 +1204,7 @@ udp_output(struct inpcb *inp, struct mbu
> laddr = inp->inp_laddr;
> lport = inp->inp_lport;
> if (src.sin_family == AF_INET) {
> - INP_HASH_LOCK_ASSERT(&V_udbinfo);
> + INP_HASH_LOCK_ASSERT(pcbinfo);
> if ((lport == 0) ||
> (laddr.s_addr == INADDR_ANY &&
> src.sin_addr.s_addr == INADDR_ANY)) {
> @@ -1134,7 +1255,7 @@ udp_output(struct inpcb *inp, struct mbu
> inp->inp_lport == 0 ||
> sin->sin_addr.s_addr == INADDR_ANY ||
> sin->sin_addr.s_addr == INADDR_BROADCAST) {
> - INP_HASH_LOCK_ASSERT(&V_udbinfo);
> + INP_HASH_LOCK_ASSERT(pcbinfo);
> error = in_pcbconnect_setup(inp, addr, &laddr.s_addr,
> &lport, &faddr.s_addr, &fport, NULL,
> td->td_ucred);
> @@ -1149,7 +1270,7 @@ udp_output(struct inpcb *inp, struct mbu
> if (inp->inp_laddr.s_addr == INADDR_ANY &&
> inp->inp_lport == 0) {
> INP_WLOCK_ASSERT(inp);
> - INP_HASH_WLOCK_ASSERT(&V_udbinfo);
> + INP_HASH_WLOCK_ASSERT(pcbinfo);
> /*
> * Remember addr if jailed, to prevent
> * rebinding.
> @@ -1198,13 +1319,30 @@ udp_output(struct inpcb *inp, struct mbu
> */
> ui = mtod(m, struct udpiphdr *);
> bzero(ui->ui_x1, sizeof(ui->ui_x1)); /* XXX still needed? */
> - ui->ui_v = IPVERSION << 4;
> - ui->ui_pr = IPPROTO_UDP;
> + ui->ui_pr = pr;
> 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));
> + if (pr == IPPROTO_UDPLITE) {
> + struct udpcb *up;
> + uint16_t plen;
> +
> + up = intoudpcb(inp);
> + cscov = up->u_txcslen;
> + plen = (u_short)len + sizeof(struct udphdr);
> + if (cscov >= plen)
> + cscov = 0;
> + ui->ui_len = htons(plen);
> + ui->ui_ulen = htons(cscov);
> + /*
> + * For UDP-Lite, checksum coverage length of zero means
> + * the entire UDPLite packet is covered by the checksum.
> + */
> + cscov_partial = (cscov == 0) ? 0 : 1;
> + } else
> + ui->ui_v = IPVERSION << 4;
>
> /*
> * Set the Don't Fragment bit in the IP header.
> @@ -1231,24 +1369,34 @@ udp_output(struct inpcb *inp, struct mbu
> /*
> * Set up checksum and output datagram.
> */
> - if (V_udp_cksum) {
> + ui->ui_sum = 0;
> + if (pr == IPPROTO_UDPLITE) {
> + if (inp->inp_flags & INP_ONESBCAST)
> + faddr.s_addr = INADDR_BROADCAST;
> + if (cscov_partial) {
> + if ((ui->ui_sum = in_cksum(m, sizeof(struct ip) + cscov)) == 0)
> + ui->ui_sum = 0xffff;
> + } else {
> + if ((ui->ui_sum = in_cksum(m, sizeof(struct udpiphdr) + len)) == 0)
> + ui->ui_sum = 0xffff;
> + }
> + } else if (V_udp_cksum) {
> if (inp->inp_flags & 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));
> + htons((u_short)len + sizeof(struct udphdr) + pr));
> 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 = htons(sizeof(struct udpiphdr) + len);
> ((struct ip *)ui)->ip_ttl = inp->inp_ip_ttl; /* XXX */
> ((struct ip *)ui)->ip_tos = tos; /* XXX */
> UDPSTAT_INC(udps_opackets);
>
> if (unlock_udbinfo == UH_WLOCKED)
> - INP_HASH_WUNLOCK(&V_udbinfo);
> + INP_HASH_WUNLOCK(pcbinfo);
> else if (unlock_udbinfo == UH_RLOCKED)
> - INP_HASH_RUNLOCK(&V_udbinfo);
> + INP_HASH_RUNLOCK(pcbinfo);
> UDP_PROBE(send, NULL, inp, &ui->ui_i, inp, &ui->ui_u);
> error = ip_output(m, inp->inp_options, NULL, ipflags,
> inp->inp_moptions, inp);
> @@ -1260,10 +1408,10 @@ udp_output(struct inpcb *inp, struct mbu
>
> release:
> if (unlock_udbinfo == UH_WLOCKED) {
> - INP_HASH_WUNLOCK(&V_udbinfo);
> + INP_HASH_WUNLOCK(pcbinfo);
> INP_WUNLOCK(inp);
> } else if (unlock_udbinfo == UH_RLOCKED) {
> - INP_HASH_RUNLOCK(&V_udbinfo);
> + INP_HASH_RUNLOCK(pcbinfo);
> INP_RUNLOCK(inp);
> } else
> INP_RUNLOCK(inp);
> @@ -1410,15 +1558,17 @@ static void
> udp_abort(struct socket *so)
> {
> struct inpcb *inp;
> + struct inpcbinfo *pcbinfo;
>
> + pcbinfo = get_inpcbinfo(so->so_proto->pr_protocol);
> inp = sotoinpcb(so);
> KASSERT(inp != NULL, ("udp_abort: inp == NULL"));
> INP_WLOCK(inp);
> if (inp->inp_faddr.s_addr != INADDR_ANY) {
> - INP_HASH_WLOCK(&V_udbinfo);
> + INP_HASH_WLOCK(pcbinfo);
> in_pcbdisconnect(inp);
> inp->inp_laddr.s_addr = INADDR_ANY;
> - INP_HASH_WUNLOCK(&V_udbinfo);
> + INP_HASH_WUNLOCK(pcbinfo);
> soisdisconnected(so);
> }
> INP_WUNLOCK(inp);
> @@ -1428,17 +1578,19 @@ static int
> udp_attach(struct socket *so, int proto, struct thread *td)
> {
> struct inpcb *inp;
> + struct inpcbinfo *pcbinfo;
> int error;
>
> + pcbinfo = get_inpcbinfo(so->so_proto->pr_protocol);
> inp = sotoinpcb(so);
> KASSERT(inp == NULL, ("udp_attach: inp != NULL"));
> error = soreserve(so, udp_sendspace, udp_recvspace);
> if (error)
> return (error);
> - INP_INFO_WLOCK(&V_udbinfo);
> - error = in_pcballoc(so, &V_udbinfo);
> + INP_INFO_WLOCK(pcbinfo);
> + error = in_pcballoc(so, pcbinfo);
> if (error) {
> - INP_INFO_WUNLOCK(&V_udbinfo);
> + INP_INFO_WUNLOCK(pcbinfo);
> return (error);
> }
>
> @@ -1450,12 +1602,12 @@ udp_attach(struct socket *so, int proto,
> if (error) {
> in_pcbdetach(inp);
> in_pcbfree(inp);
> - INP_INFO_WUNLOCK(&V_udbinfo);
> + INP_INFO_WUNLOCK(pcbinfo);
> return (error);
> }
>
> INP_WUNLOCK(inp);
> - INP_INFO_WUNLOCK(&V_udbinfo);
> + INP_INFO_WUNLOCK(pcbinfo);
> return (0);
> }
> #endif /* INET */
> @@ -1486,14 +1638,16 @@ static int
> udp_bind(struct socket *so, struct sockaddr *nam, struct thread *td)
> {
> struct inpcb *inp;
> + struct inpcbinfo *pcbinfo;
> int error;
>
> + pcbinfo = get_inpcbinfo(so->so_proto->pr_protocol);
> inp = sotoinpcb(so);
> KASSERT(inp != NULL, ("udp_bind: inp == NULL"));
> INP_WLOCK(inp);
> - INP_HASH_WLOCK(&V_udbinfo);
> + INP_HASH_WLOCK(pcbinfo);
> error = in_pcbbind(inp, nam, td->td_ucred);
> - INP_HASH_WUNLOCK(&V_udbinfo);
> + INP_HASH_WUNLOCK(pcbinfo);
> INP_WUNLOCK(inp);
> return (error);
> }
> @@ -1502,15 +1656,17 @@ static void
> udp_close(struct socket *so)
> {
> struct inpcb *inp;
> + struct inpcbinfo *pcbinfo;
>
> + pcbinfo = get_inpcbinfo(so->so_proto->pr_protocol);
> inp = sotoinpcb(so);
> KASSERT(inp != NULL, ("udp_close: inp == NULL"));
> INP_WLOCK(inp);
> if (inp->inp_faddr.s_addr != INADDR_ANY) {
> - INP_HASH_WLOCK(&V_udbinfo);
> + INP_HASH_WLOCK(pcbinfo);
> in_pcbdisconnect(inp);
> inp->inp_laddr.s_addr = INADDR_ANY;
> - INP_HASH_WUNLOCK(&V_udbinfo);
> + INP_HASH_WUNLOCK(pcbinfo);
> soisdisconnected(so);
> }
> INP_WUNLOCK(inp);
> @@ -1520,9 +1676,11 @@ static int
> udp_connect(struct socket *so, struct sockaddr *nam, struct thread *td)
> {
> struct inpcb *inp;
> - int error;
> + struct inpcbinfo *pcbinfo;
> struct sockaddr_in *sin;
> + int error;
>
> + pcbinfo = get_inpcbinfo(so->so_proto->pr_protocol);
> inp = sotoinpcb(so);
> KASSERT(inp != NULL, ("udp_connect: inp == NULL"));
> INP_WLOCK(inp);
> @@ -1536,9 +1694,9 @@ udp_connect(struct socket *so, struct so
> INP_WUNLOCK(inp);
> return (error);
> }
> - INP_HASH_WLOCK(&V_udbinfo);
> + INP_HASH_WLOCK(pcbinfo);
> error = in_pcbconnect(inp, nam, td->td_ucred);
> - INP_HASH_WUNLOCK(&V_udbinfo);
> + INP_HASH_WUNLOCK(pcbinfo);
> if (error == 0)
> soisconnected(so);
> INP_WUNLOCK(inp);
> @@ -1549,20 +1707,22 @@ static void
> udp_detach(struct socket *so)
> {
> struct inpcb *inp;
> + struct inpcbinfo *pcbinfo;
> struct udpcb *up;
>
> + pcbinfo = get_inpcbinfo(so->so_proto->pr_protocol);
> inp = sotoinpcb(so);
> KASSERT(inp != NULL, ("udp_detach: inp == NULL"));
> KASSERT(inp->inp_faddr.s_addr == INADDR_ANY,
> ("udp_detach: not disconnected"));
> - INP_INFO_WLOCK(&V_udbinfo);
> + INP_INFO_WLOCK(pcbinfo);
> INP_WLOCK(inp);
> up = intoudpcb(inp);
> KASSERT(up != NULL, ("%s: up == NULL", __func__));
> inp->inp_ppcb = NULL;
> in_pcbdetach(inp);
> in_pcbfree(inp);
> - INP_INFO_WUNLOCK(&V_udbinfo);
> + INP_INFO_WUNLOCK(pcbinfo);
> udp_discardcb(up);
> }
>
> @@ -1570,7 +1730,9 @@ static int
> udp_disconnect(struct socket *so)
> {
> struct inpcb *inp;
> + struct inpcbinfo *pcbinfo;
>
> + pcbinfo = get_inpcbinfo(so->so_proto->pr_protocol);
> inp = sotoinpcb(so);
> KASSERT(inp != NULL, ("udp_disconnect: inp == NULL"));
> INP_WLOCK(inp);
> @@ -1578,10 +1740,10 @@ udp_disconnect(struct socket *so)
> INP_WUNLOCK(inp);
> return (ENOTCONN);
> }
> - INP_HASH_WLOCK(&V_udbinfo);
> + INP_HASH_WLOCK(pcbinfo);
> in_pcbdisconnect(inp);
> inp->inp_laddr.s_addr = INADDR_ANY;
> - INP_HASH_WUNLOCK(&V_udbinfo);
> + INP_HASH_WUNLOCK(pcbinfo);
> SOCK_LOCK(so);
> so->so_state &= ~SS_ISCONNECTED; /* XXX */
> SOCK_UNLOCK(so);
>
> Modified: stable/10/sys/netinet/udp_var.h
> ==============================================================================
> --- stable/10/sys/netinet/udp_var.h Tue May 13 05:26:43 2014 (r265945)
> +++ stable/10/sys/netinet/udp_var.h Tue May 13 06:05:53 2014 (r265946)
> @@ -63,6 +63,8 @@ typedef void(*udp_tun_func_t)(struct mbu
> struct udpcb {
> udp_tun_func_t u_tun_func; /* UDP kernel tunneling callback. */
> u_int u_flags; /* Generic UDP flags. */
> + uint16_t u_rxcslen; /* Coverage for incoming datagrams. */
> + uint16_t u_txcslen; /* Coverage for outgoing datagrams. */
> };
>
> #define intoudpcb(ip) ((struct udpcb *)(ip)->inp_ppcb)
> @@ -130,8 +132,12 @@ SYSCTL_DECL(_net_inet_udp);
> extern struct pr_usrreqs udp_usrreqs;
> VNET_DECLARE(struct inpcbhead, udb);
> VNET_DECLARE(struct inpcbinfo, udbinfo);
> +VNET_DECLARE(struct inpcbhead, ulitecb);
> +VNET_DECLARE(struct inpcbinfo, ulitecbinfo);
> #define V_udb VNET(udb)
> #define V_udbinfo VNET(udbinfo)
> +#define V_ulitecb VNET(ulitecb)
> +#define V_ulitecbinfo VNET(ulitecbinfo)
>
> extern u_long udp_sendspace;
> extern u_long udp_recvspace;
> @@ -141,20 +147,37 @@ VNET_DECLARE(int, udp_blackhole);
> #define V_udp_blackhole VNET(udp_blackhole)
> extern int udp_log_in_vain;
>
> -int udp_newudpcb(struct inpcb *);
> -void udp_discardcb(struct udpcb *);
> -
> -void udp_ctlinput(int, struct sockaddr *, void *);
> -int udp_ctloutput(struct socket *, struct sockopt *);
> -void udp_init(void);
> +static __inline struct inpcbinfo *
> +get_inpcbinfo(uint8_t protocol)
> +{
> + return (protocol == IPPROTO_UDP) ? &V_udbinfo : &V_ulitecbinfo;
> +}
> +
> +static __inline struct inpcbhead *
> +get_pcblist(uint8_t protocol)
> +{
> + return (protocol == IPPROTO_UDP) ? &V_udb : &V_ulitecb;
> +}
> +
> +int udp_newudpcb(struct inpcb *);
> +void udp_discardcb(struct udpcb *);
> +
> +void udp_ctlinput(int, struct sockaddr *, void *);
> +void udplite_ctlinput(int, struct sockaddr *, void *);
> +int udp_ctloutput(struct socket *, struct sockopt *);
> +void udp_init(void);
> +void udplite_init(void);
> #ifdef VIMAGE
> -void udp_destroy(void);
> +void udp_destroy(void);
> +void udplite_destroy(void);
> #endif
> -void udp_input(struct mbuf *, int);
> +void udp_input(struct mbuf *, int);
> +void udplite_input(struct mbuf *, int);
> struct inpcb *udp_notify(struct inpcb *inp, int errno);
> -int udp_shutdown(struct socket *so);
> +int udp_shutdown(struct socket *so);
>
> -int udp_set_kernel_tunneling(struct socket *so, udp_tun_func_t f);
> -#endif
> +int udp_set_kernel_tunneling(struct socket *so, udp_tun_func_t f);
>
> -#endif
> +#endif /* _KERNEL */
> +
> +#endif /* _NETINET_UDP_VAR_H_ */
>
> Copied: stable/10/sys/netinet/udplite.h (from r264212, head/sys/netinet/udplite.h)
> ==============================================================================
> --- /dev/null 00:00:00 1970 (empty, because file is newly added)
> +++ stable/10/sys/netinet/udplite.h Tue May 13 06:05:53 2014 (r265946, copy of r264212, head/sys/netinet/udplite.h)
> @@ -0,0 +1,38 @@
> +/*-
> + * Copyright (c) 2014, Kevin Lo
> + * 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 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$
> + */
> +
> +#ifndef _NETINET_UDPLITE_H_
> +#define _NETINET_UDPLITE_H_
> +
> +/*
> + * User-settable options (used with setsockopt).
> + */
> +#define UDPLITE_SEND_CSCOV 2 /* Sender checksum coverage. */
> +#define UDPLITE_RECV_CSCOV 4 /* Receiver checksum coverage. */
> +
> +#endif /* !_NETINET_UDPLITE_H_ */
>
> Modified: stable/10/sys/netinet6/in6_ifattach.c
> ==============================================================================
> --- stable/10/sys/netinet6/in6_ifattach.c Tue May 13 05:26:43 2014 (r265945)
> +++ stable/10/sys/netinet6/in6_ifattach.c Tue May 13 06:05:53 2014 (r265946)
> @@ -856,6 +856,7 @@ in6_ifdetach(struct ifnet *ifp)
> }
>
> in6_pcbpurgeif0(&V_udbinfo, ifp);
> + in6_pcbpurgeif0(&V_ulitecbinfo, ifp);
> in6_pcbpurgeif0(&V_ripcbinfo, ifp);
> /* leave from all multicast groups joined */
> in6_purgemaddrs(ifp);
>
> Modified: stable/10/sys/netinet6/in6_proto.c
> ==============================================================================
> --- stable/10/sys/netinet6/in6_proto.c Tue May 13 05:26:43 2014 (r265945)
> +++ stable/10/sys/netinet6/in6_proto.c Tue May 13 06:05:53 2014 (r265946)
> @@ -207,13 +207,26 @@ struct ip6protosw inet6sw[] = {
> .pr_protocol = IPPROTO_SCTP,
> .pr_flags = PR_WANTRCVD,
> .pr_input = sctp6_input,
> - .pr_ctlinput = sctp6_ctlinput,
> + .pr_ctlinput = sctp6_ctlinput,
> .pr_ctloutput = sctp_ctloutput,
> .pr_drain = sctp_drain,
> .pr_usrreqs = &sctp6_usrreqs
> },
> #endif /* SCTP */
> {
> + .pr_type = SOCK_DGRAM,
> + .pr_domain = &inet6domain,
> + .pr_protocol = IPPROTO_UDPLITE,
> + .pr_flags = PR_ATOMIC|PR_ADDR,
> + .pr_input = udp6_input,
> + .pr_ctlinput = udplite6_ctlinput,
> + .pr_ctloutput = udp_ctloutput,
> +#ifndef INET /* Do not call initialization twice. */
> + .pr_init = udplite_init,
> +#endif
> + .pr_usrreqs = &udp6_usrreqs,
> +},
> +{
> .pr_type = SOCK_RAW,
> .pr_domain = &inet6domain,
> .pr_protocol = IPPROTO_RAW,
>
> Modified: stable/10/sys/netinet6/udp6_usrreq.c
> ==============================================================================
> --- stable/10/sys/netinet6/udp6_usrreq.c Tue May 13 05:26:43 2014 (r265945)
> +++ stable/10/sys/netinet6/udp6_usrreq.c Tue May 13 06:05:53 2014 (r265946)
> @@ -1,6 +1,7 @@
> /*-
> * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
> * Copyright (c) 2010-2011 Juniper Networks, Inc.
> + * Copyright (c) 2014 Kevin Lo
> * All rights reserved.
> *
> * Portions of this software were developed by Robert N. M. Watson under
> @@ -109,6 +110,7 @@ __FBSDID("$FreeBSD$");
> #include <netinet/ip_var.h>
> #include <netinet/udp.h>
> #include <netinet/udp_var.h>
> +#include <netinet/udplite.h>
>
> #include <netinet6/ip6protosw.h>
> #include <netinet6/ip6_var.h>
> @@ -181,12 +183,15 @@ udp6_input(struct mbuf **mp, int *offp,
> struct ip6_hdr *ip6;
> struct udphdr *uh;
> struct inpcb *inp;
> + struct inpcbinfo *pcbinfo;
> struct udpcb *up;
> int off = *offp;
> + int cscov_partial;
> int plen, ulen;
> struct sockaddr_in6 fromsa;
> struct m_tag *fwd_tag;
> uint16_t uh_sum;
> + uint8_t nxt;
>
> ifp = m->m_pkthdr.rcvif;
> ip6 = mtod(m, struct ip6_hdr *);
> @@ -218,6 +223,13 @@ udp6_input(struct mbuf **mp, int *offp,
> plen = ntohs(ip6->ip6_plen) - off + sizeof(*ip6);
> ulen = ntohs((u_short)uh->uh_ulen);
>
> + nxt = ip6->ip6_nxt;
> + cscov_partial = (nxt == IPPROTO_UDPLITE) ? 1 : 0;
> + if (nxt == IPPROTO_UDPLITE && ulen == 0) {
> + /* Zero means checksum over the complete packet. */
> + ulen = plen;
> + cscov_partial = 0;
> + }
>
> *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
>
—
Bjoern A. Zeeb "Come on. Learn, goddamn it.", WarGames, 1983
More information about the svn-src-stable
mailing list