| /* Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved. |
| * Portions Copyright © 1997-1999 Vita Nuova Limited |
| * Portions Copyright © 2000-2007 Vita Nuova Holdings Limited |
| * (www.vitanuova.com) |
| * Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others |
| * |
| * Modified for the Akaros operating system: |
| * Copyright (c) 2013-2014 The Regents of the University of California |
| * Copyright (c) 2013-2015 Google Inc. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. */ |
| |
| #include <slab.h> |
| #include <kmalloc.h> |
| #include <kref.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <assert.h> |
| #include <error.h> |
| #include <cpio.h> |
| #include <pmap.h> |
| #include <smp.h> |
| #include <net/ip.h> |
| #include <endian.h> |
| |
| /* |
| * well known IP addresses |
| */ |
| uint8_t IPv4_loopback[IPaddrlen] = { |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0xff, 0xff, |
| 0x7f, 0x00, 0x00, 0x01 |
| }; |
| |
| uint8_t IPv4_zeroes[IPaddrlen] = { |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0xff, 0xff, |
| 0x00, 0x00, 0x00, 0x00 |
| }; |
| |
| uint8_t IPv4bcast[IPaddrlen] = { |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff |
| }; |
| |
| uint8_t IPv4allsys[IPaddrlen] = { |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0xff, 0xff, |
| 0xe0, 0, 0, 0x01 |
| }; |
| |
| uint8_t IPv4allrouter[IPaddrlen] = { |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0xff, 0xff, |
| 0xe0, 0, 0, 0x02 |
| }; |
| |
| uint8_t IPallbits[IPaddrlen] = { |
| 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff |
| }; |
| |
| uint8_t IPnoaddr[IPaddrlen]; |
| |
| /* |
| * prefix of all v4 addresses |
| */ |
| uint8_t v4prefix[IPaddrlen] = { |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0xff, 0xff, |
| 0, 0, 0, 0 |
| }; |
| |
| char *v6hdrtypes[Maxhdrtype] = { |
| [HBH] "HopbyHop", |
| [ICMP] "ICMP", |
| [IGMP] "IGMP", |
| [GGP] "GGP", |
| [IPINIP] "IP", |
| [ST] "ST", |
| [TCP] "TCP", |
| [UDP] "UDP", |
| [ISO_TP4] "ISO_TP4", |
| [RH] "Routinghdr", |
| [FH] "Fraghdr", |
| [IDRP] "IDRP", |
| [RSVP] "RSVP", |
| [AH] "Authhdr", |
| [ESP] "ESP", |
| [ICMPv6] "ICMPv6", |
| [NNH] "Nonexthdr", |
| [ISO_IP] "ISO_IP", |
| [IGRP] "IGRP", |
| [OSPF] "OSPF", |
| }; |
| |
| /* |
| * well known IPv6 addresses |
| */ |
| uint8_t v6Unspecified[IPaddrlen] = { |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0 |
| }; |
| |
| uint8_t v6loopback[IPaddrlen] = { |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0x01 |
| }; |
| |
| uint8_t v6linklocal[IPaddrlen] = { |
| 0xfe, 0x80, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0 |
| }; |
| |
| uint8_t v6linklocalmask[IPaddrlen] = { |
| 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0 |
| }; |
| |
| int v6llpreflen = 8; // link-local prefix length |
| uint8_t v6sitelocal[IPaddrlen] = { |
| 0xfe, 0xc0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0 |
| }; |
| |
| uint8_t v6sitelocalmask[IPaddrlen] = { |
| 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0 |
| }; |
| |
| int v6slpreflen = 6; // site-local prefix length |
| uint8_t v6glunicast[IPaddrlen] = { |
| 0x08, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0 |
| }; |
| |
| uint8_t v6multicast[IPaddrlen] = { |
| 0xff, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0 |
| }; |
| |
| uint8_t v6multicastmask[IPaddrlen] = { |
| 0xff, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0 |
| }; |
| |
| int v6mcpreflen = 1; // multicast prefix length |
| uint8_t v6allnodesN[IPaddrlen] = { |
| 0xff, 0x01, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0x01 |
| }; |
| |
| uint8_t v6allnodesNmask[IPaddrlen] = { |
| 0xff, 0xff, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0 |
| }; |
| |
| int v6aNpreflen = 2; // all nodes (N) prefix |
| uint8_t v6allnodesL[IPaddrlen] = { |
| 0xff, 0x02, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0x01 |
| }; |
| |
| uint8_t v6allnodesLmask[IPaddrlen] = { |
| 0xff, 0xff, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0 |
| }; |
| |
| int v6aLpreflen = 2; // all nodes (L) prefix |
| uint8_t v6allroutersN[IPaddrlen] = { |
| 0xff, 0x01, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0x02 |
| }; |
| |
| uint8_t v6allroutersL[IPaddrlen] = { |
| 0xff, 0x02, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0x02 |
| }; |
| |
| uint8_t v6allroutersS[IPaddrlen] = { |
| 0xff, 0x05, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0x02 |
| }; |
| |
| uint8_t v6solicitednode[IPaddrlen] = { |
| 0xff, 0x02, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0x01, |
| 0xff, 0, 0, 0 |
| }; |
| |
| uint8_t v6solicitednodemask[IPaddrlen] = { |
| 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0x0, 0x0, 0x0 |
| }; |
| |
| int v6snpreflen = 13; |
| |
| uint16_t ptclcsum_one(struct block *bp, int offset, int len) |
| { |
| uint8_t *addr; |
| uint32_t losum, hisum; |
| uint16_t csum; |
| int odd, blocklen, x, i, boff; |
| struct extra_bdata *ebd; |
| |
| hisum = 0; |
| losum = 0; |
| odd = 0; |
| |
| if (offset < BHLEN(bp)) { |
| x = MIN(len, BHLEN(bp) - offset); |
| odd = (odd + x) & 1; |
| addr = bp->rp + offset; |
| losum = ptclbsum(addr, x); |
| len -= x; |
| offset = 0; |
| } else { |
| offset -= BHLEN(bp); |
| } |
| for (int i = 0; (i < bp->nr_extra_bufs) && len; i++) { |
| ebd = &bp->extra_data[i]; |
| boff = MIN(offset, ebd->len); |
| if (offset) { |
| offset -= boff; |
| if (offset) |
| continue; |
| } |
| x = MIN(len, ebd->len - boff); |
| addr = (void *)(ebd->base + ebd->off); |
| if (odd) |
| hisum += ptclbsum(addr, x); |
| else |
| losum += ptclbsum(addr, x); |
| odd = (odd + x) & 1; |
| len -= x; |
| } |
| losum += hisum >> 8; |
| losum += (hisum & 0xff) << 8; |
| while ((csum = losum >> 16) != 0) |
| losum = csum + (losum & 0xffff); |
| |
| return losum & 0xffff; |
| } |
| |
| uint16_t ptclcsum(struct block *bp, int offset, int len) |
| { |
| uint8_t *addr; |
| uint32_t losum, hisum; |
| uint16_t csum; |
| int odd, blocklen, x; |
| |
| /* Correct to front of data area */ |
| while (bp != NULL && offset && offset >= BLEN(bp)) { |
| offset -= BLEN(bp); |
| bp = bp->next; |
| } |
| if (bp == NULL) |
| return 0; |
| |
| addr = bp->rp + offset; |
| blocklen = BLEN(bp) - offset; |
| |
| if (bp->next == NULL && bp->extra_len == 0) { |
| if (blocklen < len) |
| len = blocklen; |
| return ~ptclbsum(addr, len) & 0xffff; |
| } |
| |
| losum = 0; |
| hisum = 0; |
| |
| odd = 0; |
| while (len) { |
| x = MIN(blocklen, len); |
| csum = ptclcsum_one(bp, offset, x); |
| if (odd) |
| hisum += csum; |
| else |
| losum += csum; |
| odd = (odd + x) & 1; |
| len -= x; |
| |
| bp = bp->next; |
| if (bp == NULL) |
| break; |
| blocklen = BLEN(bp); |
| offset = 0; |
| } |
| |
| losum += hisum >> 8; |
| losum += (hisum & 0xff) << 8; |
| while ((csum = losum >> 16) != 0) |
| losum = csum + (losum & 0xffff); |
| |
| return ~losum & 0xffff; |
| } |
| |
| enum { |
| Isprefix = 16, |
| }; |
| |
| static uint8_t prefixvals[256] = { |
| [0x00] 0 | Isprefix, |
| [0x80] 1 | Isprefix, |
| [0xC0] 2 | Isprefix, |
| [0xE0] 3 | Isprefix, |
| [0xF0] 4 | Isprefix, |
| [0xF8] 5 | Isprefix, |
| [0xFC] 6 | Isprefix, |
| [0xFE] 7 | Isprefix, |
| [0xFF] 8 | Isprefix, |
| }; |
| |
| #define CLASS(p) ((*( uint8_t *)(p))>>6) |
| |
| extern char *v4parseip(uint8_t * to, char *from) |
| { |
| int i; |
| char *p; |
| |
| p = from; |
| for (i = 0; i < 4 && *p; i++) { |
| to[i] = strtoul(p, &p, 0); |
| if (*p == '.') |
| p++; |
| } |
| switch (CLASS(to)) { |
| case 0: /* class A - 1 uint8_t net */ |
| case 1: |
| if (i == 3) { |
| to[3] = to[2]; |
| to[2] = to[1]; |
| to[1] = 0; |
| } else if (i == 2) { |
| to[3] = to[1]; |
| to[1] = 0; |
| } |
| break; |
| case 2: /* class B - 2 uint8_t net */ |
| if (i == 3) { |
| to[3] = to[2]; |
| to[2] = 0; |
| } |
| break; |
| } |
| return p; |
| } |
| |
| int isv4(uint8_t * ip) |
| { |
| unsigned short *ips = (unsigned short *)ip; |
| return 0xffff == ips[5]; |
| } |
| |
| /* |
| * the following routines are unrolled with no memset's to speed |
| * up the usual case. They assume IP addresses are naturally aligned. |
| */ |
| void v4tov6(uint8_t * v6, uint8_t * v4) |
| { |
| uint32_t *v6p = (uint32_t *)v6; |
| uint32_t *v4p = (uint32_t *)v4; |
| |
| v6p[0] = 0; |
| v6p[1] = 0; |
| v6p[2] = (unsigned int)PP_NTOHL(0xffff); |
| v6p[3] = v4p[0]; |
| } |
| |
| int v6tov4(uint8_t * v4, uint8_t * v6) |
| { |
| |
| uint32_t *v6p = (uint32_t *)v6; |
| uint32_t *v4p = (uint32_t *)v4; |
| |
| if (v6p[0] == 0 && v6p[1] == 0 && |
| v6p[2] == (unsigned int)PP_NTOHL(0xffff)) { |
| v4p[0] = v6p[3]; |
| return 0; |
| } else { |
| v4p[0] = 0; |
| return -1; |
| } |
| } |
| |
| uint32_t parseip(uint8_t * to, char *from) |
| { |
| int i, elipsis = 0, v4 = 1; |
| uint32_t x; |
| char *p, *op; |
| |
| memset(to, 0, IPaddrlen); |
| p = from; |
| for (i = 0; i < 16 && *p; i += 2) { |
| op = p; |
| x = strtoul(p, &p, 16); |
| if (*p == '.' || (*p == 0 && i == 0)) { |
| p = v4parseip(to + i, op); |
| i += 4; |
| break; |
| } else { |
| to[i] = x >> 8; |
| to[i + 1] = x; |
| } |
| if (*p == ':') { |
| v4 = 0; |
| if (*++p == ':') { |
| elipsis = i + 2; |
| p++; |
| } |
| } |
| } |
| if (i < 16) { |
| memmove(&to[elipsis + 16 - i], &to[elipsis], i - elipsis); |
| memset(&to[elipsis], 0, 16 - i); |
| } |
| if (v4) { |
| to[10] = to[11] = 0xff; |
| return nhgetl(to + 12); |
| } else |
| return 6; |
| } |
| |
| /* |
| * hack to allow ip v4 masks to be entered in the old |
| * style |
| */ |
| uint32_t parseipmask(uint8_t * to, char *from) |
| { |
| uint32_t x; |
| int i; |
| uint8_t *p; |
| |
| if (*from == '/') { |
| /* as a number of prefix bits */ |
| i = atoi(from + 1); |
| if (i < 0) |
| i = 0; |
| if (i > 128) |
| i = 128; |
| memset(to, 0, IPaddrlen); |
| for (p = to; i >= 8; i -= 8) |
| *p++ = 0xff; |
| if (i > 0) |
| *p = ~((1 << (8 - i)) - 1); |
| x = nhgetl(to + IPv4off); |
| } else { |
| /* as a straight bit mask */ |
| x = parseip(to, from); |
| if (memcmp(to, v4prefix, IPv4off) == 0) |
| memset(to, 0xff, IPv4off); |
| } |
| return x; |
| } |
| |
| void maskip(uint8_t * from, uint8_t * mask, uint8_t * to) |
| { |
| int i; |
| |
| for (i = 0; i < IPaddrlen; i++) |
| to[i] = from[i] & mask[i]; |
| } |
| |
| uint8_t classmask[4][16] = { |
| {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0x00, 0x00, 0x00} |
| , |
| {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0x00, 0x00, 0x00} |
| , |
| {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0x00, 0x00} |
| , |
| |
| {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0x00} |
| , |
| }; |
| |
| uint8_t *defmask(uint8_t * ip) |
| { |
| if (isv4(ip)) |
| return classmask[ip[IPv4off] >> 6]; |
| else { |
| if (ipcmp(ip, v6loopback) == 0) |
| return IPallbits; |
| else if (memcmp(ip, v6linklocal, v6llpreflen) == 0) |
| return v6linklocalmask; |
| else if (memcmp(ip, v6sitelocal, v6slpreflen) == 0) |
| return v6sitelocalmask; |
| else if (memcmp(ip, v6solicitednode, v6snpreflen) == 0) |
| return v6solicitednodemask; |
| else if (memcmp(ip, v6multicast, v6mcpreflen) == 0) |
| return v6multicastmask; |
| return IPallbits; |
| } |
| } |
| |
| void ipv62smcast(uint8_t * smcast, uint8_t * a) |
| { |
| assert(IPaddrlen == 16); |
| memmove(smcast, v6solicitednode, IPaddrlen); |
| smcast[13] = a[13]; |
| smcast[14] = a[14]; |
| smcast[15] = a[15]; |
| } |
| |
| /* |
| * parse a hex mac address |
| */ |
| int parsemac(uint8_t * to, char *from, int len) |
| { |
| char nip[4]; |
| char *p; |
| int i; |
| |
| p = from; |
| memset(to, 0, len); |
| for (i = 0; i < len; i++) { |
| if (p[0] == '\0' || p[1] == '\0') |
| break; |
| |
| nip[0] = p[0]; |
| nip[1] = p[1]; |
| nip[2] = '\0'; |
| p += 2; |
| |
| to[i] = strtoul(nip, 0, 16); |
| if (*p == ':') |
| p++; |
| } |
| return i; |
| } |
| |
| /* |
| * hashing tcp, udp, ... connections |
| * gcc weirdness: it gave a bogus result until ron split the %= out. |
| */ |
| uint32_t iphash(uint8_t * sa, uint16_t sp, uint8_t * da, uint16_t dp) |
| { |
| uint32_t ret; |
| |
| ret = (sa[IPaddrlen - 1] << 24) ^ (sp << 16) ^ (da[IPaddrlen - 1] << 8) |
| ^ dp; |
| ret %= Nhash; |
| return ret; |
| } |
| |
| void iphtadd(struct Ipht *ht, struct conv *c) |
| { |
| uint32_t hv; |
| struct Iphash *h; |
| |
| hv = iphash(c->raddr, c->rport, c->laddr, c->lport); |
| h = kzmalloc(sizeof(*h), 0); |
| if (ipcmp(c->raddr, IPnoaddr) != 0) |
| h->match = IPmatchexact; |
| else { |
| if (ipcmp(c->laddr, IPnoaddr) != 0) { |
| if (c->lport == 0) |
| h->match = IPmatchaddr; |
| else |
| h->match = IPmatchpa; |
| } else { |
| if (c->lport == 0) |
| h->match = IPmatchany; |
| else |
| h->match = IPmatchport; |
| } |
| } |
| h->c = c; |
| |
| spin_lock(&ht->lock); |
| h->next = ht->tab[hv]; |
| ht->tab[hv] = h; |
| spin_unlock(&ht->lock); |
| } |
| |
| void iphtrem(struct Ipht *ht, struct conv *c) |
| { |
| uint32_t hv; |
| struct Iphash **l, *h; |
| |
| hv = iphash(c->raddr, c->rport, c->laddr, c->lport); |
| spin_lock(&ht->lock); |
| for (l = &ht->tab[hv]; (*l) != NULL; l = &(*l)->next) |
| if ((*l)->c == c) { |
| h = *l; |
| (*l) = h->next; |
| kfree(h); |
| break; |
| } |
| spin_unlock(&ht->lock); |
| } |
| |
| /* look for a matching conversation with the following precedence |
| * connected && raddr,rport,laddr,lport |
| * announced && laddr,lport |
| * announced && *,lport |
| * announced && laddr,* |
| * announced && *,* |
| */ |
| struct conv *iphtlook(struct Ipht *ht, uint8_t * sa, uint16_t sp, uint8_t * da, |
| uint16_t dp) |
| { |
| uint32_t hv; |
| struct Iphash *h; |
| struct conv *c; |
| |
| /* exact 4 pair match (connection) */ |
| hv = iphash(sa, sp, da, dp); |
| spin_lock(&ht->lock); |
| for (h = ht->tab[hv]; h != NULL; h = h->next) { |
| if (h->match != IPmatchexact) |
| continue; |
| c = h->c; |
| if (sp == c->rport && dp == c->lport |
| && ipcmp(sa, c->raddr) == 0 && ipcmp(da, c->laddr) == 0) { |
| spin_unlock(&ht->lock); |
| return c; |
| } |
| } |
| |
| /* match local address and port */ |
| hv = iphash(IPnoaddr, 0, da, dp); |
| for (h = ht->tab[hv]; h != NULL; h = h->next) { |
| if (h->match != IPmatchpa) |
| continue; |
| c = h->c; |
| if (dp == c->lport && ipcmp(da, c->laddr) == 0) { |
| spin_unlock(&ht->lock); |
| return c; |
| } |
| } |
| |
| /* match just port */ |
| hv = iphash(IPnoaddr, 0, IPnoaddr, dp); |
| for (h = ht->tab[hv]; h != NULL; h = h->next) { |
| if (h->match != IPmatchport) |
| continue; |
| c = h->c; |
| if (dp == c->lport) { |
| spin_unlock(&ht->lock); |
| return c; |
| } |
| } |
| |
| /* match local address */ |
| hv = iphash(IPnoaddr, 0, da, 0); |
| for (h = ht->tab[hv]; h != NULL; h = h->next) { |
| if (h->match != IPmatchaddr) |
| continue; |
| c = h->c; |
| if (ipcmp(da, c->laddr) == 0) { |
| spin_unlock(&ht->lock); |
| return c; |
| } |
| } |
| |
| /* look for something that matches anything */ |
| hv = iphash(IPnoaddr, 0, IPnoaddr, 0); |
| for (h = ht->tab[hv]; h != NULL; h = h->next) { |
| if (h->match != IPmatchany) |
| continue; |
| c = h->c; |
| spin_unlock(&ht->lock); |
| return c; |
| } |
| spin_unlock(&ht->lock); |
| return NULL; |
| } |
| |
| void dump_ipht(struct Ipht *ht) |
| { |
| struct Iphash *h; |
| struct conv *c; |
| |
| spin_lock(&ht->lock); |
| for (int i = 0; i < Nipht; i++) { |
| for (h = ht->tab[i]; h != NULL; h = h->next) { |
| c = h->c; |
| printk("Conv proto %s, idx %d: local %I:%d, remote %I:%d\n", |
| c->p->name, c->x, c->laddr, c->lport, c->raddr, |
| c->rport); |
| } |
| } |
| spin_unlock(&ht->lock); |
| } |