SO_BINDTODEVICE in 2.1.57-vger

David Woodhouse (D.W.Woodhouse@nortel.co.uk)
Fri, 03 Oct 1997 12:06:26 +0100


This is a multipart MIME message.

--===_0_Fri_Oct__3_12:06:09_BST_1997
Content-Type: text/plain; charset=us-ascii

OK, try this then.
Port of Elliot Poger's SO_BINDTODEVICE to the current CVS tree.

--===_0_Fri_Oct__3_12:06:09_BST_1997
Content-Type: application/octet-stream
Content-Description: sobind-57-cvs-diff

--- linux/include/asm-i386/socket.h.orig Thu Aug 28 16:09:26 1997
+++ linux/include/asm-i386/socket.h Fri Oct 3 11:10:44 1997
@@ -33,4 +33,6 @@
#define SO_SECURITY_ENCRYPTION_TRANSPORT 23
#define SO_SECURITY_ENCRYPTION_NETWORK 24

+#define SO_BINDTODEVICE 25
+
#endif /* _ASM_SOCKET_H */
--- linux/include/net/raw.h.orig Thu Aug 28 16:16:06 1997
+++ linux/include/net/raw.h Fri Oct 3 11:10:44 1997
@@ -32,6 +32,7 @@


extern struct sock *raw_v4_lookup(struct sock *sk, unsigned short num,
- unsigned long raddr, unsigned long laddr);
+ unsigned long raddr, unsigned long laddr,
+ int dif);

#endif /* _RAW_H */
--- linux/include/net/route.h.orig Wed Oct 1 02:12:19 1997
+++ linux/include/net/route.h Fri Oct 3 11:10:44 1997
@@ -130,17 +130,17 @@
return ip_tos2prio[IPTOS_TOS(tos)>>1];
}

-extern __inline__ int ip_route_connect(struct rtable **rp, u32 dst, u32 src, u32 tos)
+extern __inline__ int ip_route_connect(struct rtable **rp, u32 dst, u32 src, u32 tos, int oif)
{
int err;
- err = ip_route_output(rp, dst, src, tos, 0);
+ err = ip_route_output(rp, dst, src, tos, oif);
if (err || (dst && src))
return err;
dst = (*rp)->rt_dst;
src = (*rp)->rt_src;
ip_rt_put(*rp);
*rp = NULL;
- return ip_route_output(rp, dst, src, tos, 0);
+ return ip_route_output(rp, dst, src, tos, oif);
}

extern __inline__ void ip_ll_header(struct sk_buff *skb)
--- linux/include/net/sock.h.orig Wed Oct 1 02:08:25 1997
+++ linux/include/net/sock.h Fri Oct 3 11:10:44 1997
@@ -382,6 +382,7 @@
broadcast,
nonagle,
bsdism;
+ int bound_dev_if;
unsigned long lingertime;
int proc;

--- linux/net/core/sock.c.orig Wed Oct 1 01:19:16 1997
+++ linux/net/core/sock.c Fri Oct 3 11:10:44 1997
@@ -144,6 +144,7 @@
int valbool;
int err;
struct linger ling;
+ struct ifreq req;
int ret = 0;

/*
@@ -318,6 +319,46 @@
return -EINVAL;
break;
#endif
+ case SO_BINDTODEVICE:
+ /* Bind this socket to a particular device like "eth0",
+ * as specified in an ifreq structure. If the device
+ * is "", socket is NOT bound to a device.
+ */
+
+ if (!valbool) {
+ sk->bound_dev_if = 0;
+ }
+ else {
+ if (copy_from_user(&req, optval, sizeof(req)) < 0)
+ return -EFAULT;
+
+ /* Remove any cached route for this socket. */
+ if (sk->dst_cache) {
+ ip_rt_put((struct rtable*)sk->dst_cache);
+ sk->dst_cache = NULL;
+ }
+
+ if (req.ifr_ifrn.ifrn_name[0] == '\0') {
+ sk->bound_dev_if = 0;
+ }
+ else {
+ struct device *dev = dev_get(req.ifr_ifrn.ifrn_name);
+ if (!dev)
+ return -EINVAL;
+ sk->bound_dev_if = dev->ifindex;
+ if (sk->daddr) {
+ int ret;
+ ret = ip_route_output((struct rtable**)&sk->dst_cache,
+ sk->daddr, sk->saddr,
+ sk->ip_tos, sk->bound_dev_if);
+ if (ret)
+ return ret;
+ }
+ }
+ }
+ return 0;
+
+
/* We implement the SO_SNDLOWAT etc to
not be settable (1003.1g 5.3) */
default:
--- linux/net/ipv4/icmp.c.orig Thu Sep 18 00:11:00 1997
+++ linux/net/ipv4/icmp.c Fri Oct 3 11:10:44 1997
@@ -764,12 +764,12 @@
hash = iph->protocol & (MAX_INET_PROTOS - 1);
if ((raw_sk = raw_v4_htable[hash]) != NULL)
{
- raw_sk = raw_v4_lookup(raw_sk, iph->protocol, iph->saddr, iph->daddr);
+ raw_sk = raw_v4_lookup(raw_sk, iph->protocol, iph->saddr, iph->daddr, skb->dev->ifindex);
while (raw_sk)
{
raw_err(raw_sk, skb);
raw_sk = raw_v4_lookup(raw_sk->next, iph->protocol,
- iph->saddr, iph->daddr);
+ iph->saddr, iph->daddr, skb->dev->ifindex);
}
}

@@ -980,8 +980,8 @@
*/

/* This should work with the new hashes now. -DaveM */
-extern struct sock *tcp_v4_lookup(u32 saddr, u16 sport, u32 daddr, u16 dport);
-extern struct sock *udp_v4_lookup(u32 saddr, u16 sport, u32 daddr, u16 dport);
+extern struct sock *tcp_v4_lookup(u32 saddr, u16 sport, u32 daddr, u16 dport, int dif);
+extern struct sock *udp_v4_lookup(u32 saddr, u16 sport, u32 daddr, u16 dport, int dif);

int icmp_chkaddr(struct sk_buff *skb)
{
@@ -997,7 +997,7 @@
{
struct tcphdr *th = (struct tcphdr *)(((unsigned char *)iph)+(iph->ihl<<2));

- sk = tcp_v4_lookup(iph->daddr, th->dest, iph->saddr, th->source);
+ sk = tcp_v4_lookup(iph->daddr, th->dest, iph->saddr, th->source, skb->dev->ifindex);
if (!sk) return 0;
if (sk->saddr != iph->saddr) return 0;
if (sk->daddr != iph->daddr) return 0;
@@ -1011,7 +1011,7 @@
{
struct udphdr *uh = (struct udphdr *)(((unsigned char *)iph)+(iph->ihl<<2));

- sk = udp_v4_lookup(iph->daddr, uh->dest, iph->saddr, uh->source);
+ sk = udp_v4_lookup(iph->daddr, uh->dest, iph->saddr, uh->source, skb->dev->ifindex);
if (!sk) return 0;
if (sk->saddr != iph->saddr && inet_addr_type(iph->saddr) != RTN_LOCAL)
return 0;
--- linux/net/ipv4/ip_input.c.orig Wed Sep 17 23:06:44 1997
+++ linux/net/ipv4/ip_input.c Fri Oct 3 11:10:44 1997
@@ -268,12 +268,12 @@
if((raw_sk = raw_v4_htable[hash]) != NULL) {
struct sock *sknext = NULL;
struct sk_buff *skb1;
- raw_sk = raw_v4_lookup(raw_sk, iph->protocol, iph->saddr, iph->daddr);
+ raw_sk = raw_v4_lookup(raw_sk, iph->protocol, iph->saddr, iph->daddr, skb->dev->ifindex);
if(raw_sk) { /* Any raw sockets */
do {
/* Find the next */
sknext = raw_v4_lookup(raw_sk->next, iph->protocol,
- iph->saddr, iph->daddr);
+ iph->saddr, iph->daddr, skb->dev->ifindex);
if (iph->protocol != IPPROTO_ICMP || !icmp_filter(raw_sk, skb)) {
if (sknext == NULL)
break;
--- linux/net/ipv4/ip_output.c.orig Fri Sep 19 06:02:36 1997
+++ linux/net/ipv4/ip_output.c Fri Oct 3 11:10:44 1997
@@ -92,7 +92,7 @@
daddr = opt->faddr;

err = ip_route_output(&rt, daddr, saddr, RT_TOS(sk->ip_tos) |
- (sk->localroute||0), 0);
+ (sk->localroute||0), sk->bound_dev_if);
if (err)
{
ip_statistics.IpOutNoRoutes++;
@@ -172,7 +172,7 @@
sk->dst_cache = NULL;
ip_rt_put(rt);
err = ip_route_output(&rt, daddr, sk->saddr, RT_TOS(sk->ip_tos) |
- (sk->localroute||0), 0);
+ (sk->localroute||0), sk->bound_dev_if);
if (err)
return err;
sk->dst_cache = &rt->u.dst;
@@ -435,7 +435,7 @@
*/
{
struct rtable *nrt;
- if (ip_route_output(&nrt, rt->key.dst, rt->key.src, rt->key.tos, 0)) {
+ if (ip_route_output(&nrt, rt->key.dst, rt->key.src, rt->key.tos, sk?sk->bound_dev_if:0)) {
kfree_skb(skb, 0);
return;
}
--- linux/net/ipv4/ip_sockglue.c.orig Sun Sep 14 08:37:24 1997
+++ linux/net/ipv4/ip_sockglue.c Fri Oct 3 11:10:44 1997
@@ -350,13 +350,16 @@
*/

dev=ip_dev_find(addr.s_addr);
-
+
/*
* Did we find one
*/

if(dev)
{
+ if (sk->bound_dev_if && dev->ifindex != sk->bound_dev_if)
+ return -EINVAL;
+
sk->ip_mc_index = dev->ifindex;
sk->ip_mc_addr = 0;
return 0;
@@ -385,6 +388,9 @@

if (!dev)
return -ENODEV;
+
+ if (sk->bound_dev_if && dev->ifindex != sk->bound_dev_if)
+ return -EINVAL;

sk->ip_mc_index = mreq.imr_ifindex;
sk->ip_mc_addr = mreq.imr_address.s_addr;
--- linux/net/ipv4/raw.c.orig Wed Sep 17 23:06:48 1997
+++ linux/net/ipv4/raw.c Fri Oct 3 11:10:44 1997
@@ -126,7 +126,7 @@

/* Grumble... icmp and ip_input want to get at this... */
struct sock *raw_v4_lookup(struct sock *sk, unsigned short num,
- unsigned long raddr, unsigned long laddr)
+ unsigned long raddr, unsigned long laddr, int dif)
{
struct sock *s = sk;

@@ -135,7 +135,8 @@
if((s->num == num) &&
!(s->dead && (s->state == TCP_CLOSE)) &&
!(s->daddr && s->daddr != raddr) &&
- !(s->rcv_saddr && s->rcv_saddr != laddr))
+ !(s->rcv_saddr && s->rcv_saddr != laddr) &&
+ !(s->bound_dev_if && s->bound_dev_if != dif))
break; /* gotcha */
}
SOCKHASH_UNLOCK();
@@ -301,7 +302,7 @@

ipc.addr = sk->saddr;
ipc.opt = NULL;
- ipc.oif = 0;
+ ipc.oif = sk->bound_dev_if;

if (msg->msg_controllen) {
int tmp = ip_cmsg_send(msg, &ipc);
--- linux/net/ipv4/route.c.orig Tue Sep 16 23:42:07 1997
+++ linux/net/ipv4/route.c Fri Oct 3 11:10:44 1997
@@ -1180,6 +1180,8 @@
dev_out = ip_dev_find(saddr);
if (dev_out == NULL)
return -EINVAL;
+ if (oif && dev_out->ifindex != oif)
+ return -EINVAL;

if (oif == 0 && (MULTICAST(daddr) || daddr == 0xFFFFFFFF)) {
/* Special hack: user can direct multicasts
@@ -1200,7 +1202,7 @@
key.oif = dev_out->ifindex;
goto make_route;
}
- dev_out = NULL;
+ dev_out = dev_get_by_index(oif);
} else if (oif) {
dev_out = dev_get_by_index(oif);
if (dev_out == NULL)
@@ -1232,17 +1234,23 @@

if (fib_lookup(&key, &res)) {
res.fi = NULL;
+ #if 0
if (oif) {
/* Apparently, routing tables are wrong. Assume,
that the destination is on link.

+ WHY? DW.
+
We could make it even if oif is unknown,
likely IPv6, but we do not.
*/
+
+ printk("Dest not on link. Forcing...\n");
if (key.src == 0)
key.src = inet_select_addr(dev_out, 0, RT_SCOPE_LINK);
goto make_route;
}
+#endif
return -ENETUNREACH;
}

--- linux/net/ipv4/tcp_ipv4.c.orig Sun Sep 21 00:32:41 1997
+++ linux/net/ipv4/tcp_ipv4.c Fri Oct 3 11:10:44 1997
@@ -123,6 +123,13 @@
unsigned char state = sk2->state;
int sk2_reuse = sk2->reuse;

+ /* Two sockets can be bound to the same port if they're
+ * bound to different interfaces.
+ */
+
+ if(sk->bound_dev_if != sk2->bound_dev_if)
+ continue;
+
if(!sk2->rcv_saddr || !sk->rcv_saddr) {
if((!sk2_reuse) ||
(!sk_reuse) ||
@@ -308,20 +315,34 @@
* connection. So always assume those are both wildcarded
* during the search since they can never be otherwise.
*/
-static struct sock *tcp_v4_lookup_listener(u32 daddr, unsigned short hnum)
+static struct sock *tcp_v4_lookup_listener(u32 daddr, unsigned short hnum, int dif)
{
struct sock *sk;
struct sock *result = NULL;
+ int score, hiscore;

+ hiscore=0;
for(sk = tcp_listening_hash[tcp_lhashfn(hnum)]; sk; sk = sk->next) {
if(sk->num == hnum) {
__u32 rcv_saddr = sk->rcv_saddr;
+ score = 1;

if(rcv_saddr) {
- if(rcv_saddr == daddr)
- return sk; /* Best possible match. */
- } else if(!result)
+ if (rcv_saddr != daddr)
+ continue;
+ score++;
+ }
+ if (sk->bound_dev_if) {
+ if (sk->bound_dev_if != dif)
+ continue;
+ score++;
+ }
+ if (score == 3)
+ return sk;
+ if (score > hiscore) {
+ hiscore = score;
result = sk;
+ }
}
}
return result;
@@ -331,7 +352,7 @@
* we need not check it for TCP lookups anymore, thanks Alexey. -DaveM
*/
static inline struct sock *__tcp_v4_lookup(struct tcphdr *th,
- u32 saddr, u16 sport, u32 daddr, u16 dport)
+ u32 saddr, u16 sport, u32 daddr, u16 dport, int dif)
{
unsigned short hnum = ntohs(dport);
struct sock *sk;
@@ -345,7 +366,8 @@
if(sk->daddr == saddr && /* remote address */
sk->dummy_th.dest == sport && /* remote port */
sk->num == hnum && /* local port */
- sk->rcv_saddr == daddr) /* local address */
+ sk->rcv_saddr == daddr && /* local address */
+ (!sk->bound_dev_if || sk->bound_dev_if == dif))
goto hit; /* You sunk my battleship! */

/* Must check for a TIME_WAIT'er before going to listener hash. */
@@ -353,17 +375,18 @@
if(sk->daddr == saddr && /* remote address */
sk->dummy_th.dest == sport && /* remote port */
sk->num == hnum && /* local port */
- sk->rcv_saddr == daddr) /* local address */
+ sk->rcv_saddr == daddr && /* local address */
+ (!sk->bound_dev_if || sk->bound_dev_if == dif))
goto hit;

- sk = tcp_v4_lookup_listener(daddr, hnum);
+ sk = tcp_v4_lookup_listener(daddr, hnum, dif);
hit:
return sk;
}

-__inline__ struct sock *tcp_v4_lookup(u32 saddr, u16 sport, u32 daddr, u16 dport)
+__inline__ struct sock *tcp_v4_lookup(u32 saddr, u16 sport, u32 daddr, u16 dport, int dif)
{
- return __tcp_v4_lookup(0, saddr, sport, daddr, dport);
+ return __tcp_v4_lookup(0, saddr, sport, daddr, dport, dif);
}

#ifdef CONFIG_IP_TRANSPARENT_PROXY
@@ -383,7 +406,8 @@

struct sock *tcp_v4_proxy_lookup(unsigned short num, unsigned long raddr,
unsigned short rnum, unsigned long laddr,
- unsigned long paddr, unsigned short pnum)
+ unsigned long paddr, unsigned short pnum,
+ int dif)
{
struct sock *s, *result = NULL;
int badness = -1;
@@ -415,7 +439,12 @@
continue;
score++;
}
- if(score == 3 && s->num == hnum) {
+ if(s->bound_dev_if) {
+ if(s->bound_dev_if != dif)
+ continue;
+ score++;
+ }
+ if(score == 4 && s->num == hnum) {
result = s;
break;
} else if(score > badness && (s->num == hpnum || s->rcv_saddr)) {
@@ -523,7 +552,7 @@
}

tmp = ip_route_connect(&rt, usin->sin_addr.s_addr, sk->saddr,
- RT_TOS(sk->ip_tos)|(sk->localroute || 0));
+ RT_TOS(sk->ip_tos)|(sk->localroute || 0), sk->bound_dev_if);
if (tmp < 0)
return tmp;

@@ -798,7 +827,7 @@

th = (struct tcphdr*)(dp+(iph->ihl<<2));

- sk = tcp_v4_lookup(iph->daddr, th->dest, iph->saddr, th->source);
+ sk = tcp_v4_lookup(iph->daddr, th->dest, iph->saddr, th->source, skb->dev->ifindex);
if (sk == NULL) {
icmp_statistics.IcmpInErrors++;
return;
@@ -992,7 +1021,7 @@
struct tcphdr *th = (struct tcphdr *)(skb->nh.raw + iph->ihl*4);
struct sock *sk;

- sk = tcp_v4_lookup(iph->saddr, th->source, iph->daddr, th->dest);
+ sk = tcp_v4_lookup(iph->saddr, th->source, iph->daddr, th->dest, skb->dev->ifindex);

if (!sk)
return 0;
@@ -1526,10 +1555,10 @@
#ifdef CONFIG_IP_TRANSPARENT_PROXY
if (IPCB(skb)->redirport)
sk = tcp_v4_proxy_lookup(th->dest, saddr, th->source, daddr,
- skb->dev->pa_addr, IPCB(skb)->redirport);
+ skb->dev->pa_addr, IPCB(skb)->redirport, skb->dev->ifindex);
else
#endif
- sk = __tcp_v4_lookup(th, skb->nh.iph->saddr, th->source, skb->nh.iph->daddr, th->dest);
+ sk = __tcp_v4_lookup(th, skb->nh.iph->saddr, th->source, skb->nh.iph->daddr, th->dest, skb->dev->ifindex);
if (!sk)
goto no_tcp_socket;
if(!ipsec_sk_policy(sk,skb))
@@ -1596,7 +1625,7 @@
static struct sock * tcp_v4_get_sock(struct sk_buff *skb, struct tcphdr *th)
{
return tcp_v4_lookup(skb->nh.iph->saddr, th->source,
- skb->nh.iph->daddr, th->dest);
+ skb->nh.iph->daddr, th->dest, skb->dev->ifindex);
}

static void v4_addr2sockaddr(struct sock *sk, struct sockaddr * uaddr)
--- linux/net/ipv4/udp.c.orig Wed Sep 17 23:06:55 1997
+++ linux/net/ipv4/udp.c Fri Oct 3 11:10:44 1997
@@ -133,6 +133,13 @@
unsigned char state = sk2->state;
int sk2_reuse = sk2->reuse;

+ /* Two sockets can be bound to the same port if they're
+ * bound to different interfaces.
+ */
+
+ if(sk2->bound_dev_if != sk->bound_dev_if)
+ continue;
+
if(!sk2->rcv_saddr || !sk->rcv_saddr) {
if((!sk2_reuse) ||
(!sk_reuse) ||
@@ -288,7 +295,7 @@
/* UDP is nearly always wildcards out the wazoo, it makes no sense to try
* harder than this here plus the last hit cache. -DaveM
*/
-struct sock *udp_v4_lookup_longway(u32 saddr, u16 sport, u32 daddr, u16 dport)
+struct sock *udp_v4_lookup_longway(u32 saddr, u16 sport, u32 daddr, u16 dport, int dif)
{
struct sock *sk, *result = NULL;
unsigned short hnum = ntohs(dport);
@@ -312,7 +319,12 @@
continue;
score++;
}
- if(score == 3) {
+ if(sk->bound_dev_if) {
+ if(sk->bound_dev_if != dif)
+ continue;
+ score++;
+ }
+ if(score == 4) {
result = sk;
break;
} else if(score > badness) {
@@ -324,23 +336,25 @@
return result;
}

-__inline__ struct sock *udp_v4_lookup(u32 saddr, u16 sport, u32 daddr, u16 dport)
+__inline__ struct sock *udp_v4_lookup(u32 saddr, u16 sport, u32 daddr, u16 dport, int dif)
{
struct sock *sk;

- if(uh_cache_sk &&
+ if(!dif && uh_cache_sk &&
uh_cache_saddr == saddr &&
uh_cache_sport == sport &&
uh_cache_dport == dport &&
uh_cache_daddr == daddr)
return uh_cache_sk;

- sk = udp_v4_lookup_longway(saddr, sport, daddr, dport);
- uh_cache_sk = sk;
- uh_cache_saddr = saddr;
- uh_cache_daddr = daddr;
- uh_cache_sport = sport;
- uh_cache_dport = dport;
+ sk = udp_v4_lookup_longway(saddr, sport, daddr, dport, dif);
+ if(!dif) {
+ uh_cache_sk = sk;
+ uh_cache_saddr = saddr;
+ uh_cache_daddr = daddr;
+ uh_cache_sport = sport;
+ uh_cache_dport = dport;
+ }
return sk;
}

@@ -361,7 +375,8 @@

struct sock *udp_v4_proxy_lookup(unsigned short num, unsigned long raddr,
unsigned short rnum, unsigned long laddr,
- unsigned long paddr, unsigned short pnum)
+ unsigned long paddr, unsigned short pnum,
+ int dif)
{
struct sock *s, *result = NULL;
int badness = -1;
@@ -393,7 +408,12 @@
continue;
score++;
}
- if(score == 3 && s->num == hnum) {
+ if(s->bound_dev_if) {
+ if(s->bound_dev_if != dif)
+ continue;
+ score++;
+ }
+ if(score == 4 && s->num == hnum) {
result = s;
break;
} else if(score > badness && (s->num == hpnum || s->rcv_saddr)) {
@@ -458,7 +478,7 @@
return;
}

- sk = udp_v4_lookup(iph->daddr, uh->dest, iph->saddr, uh->source);
+ sk = udp_v4_lookup(iph->daddr, uh->dest, iph->saddr, uh->source, skb->dev->ifindex);
if (sk == NULL) {
icmp_statistics.IcmpInErrors++;
return; /* No socket for error */
@@ -642,7 +662,7 @@

ipc.addr = sk->saddr;
ipc.opt = NULL;
- ipc.oif = 0;
+ ipc.oif = sk->bound_dev_if;
if (msg->msg_controllen) {
err = ip_cmsg_send(msg, &ipc);
if (err)
@@ -879,7 +899,7 @@
return(-EAFNOSUPPORT);

err = ip_route_connect(&rt, usin->sin_addr.s_addr, sk->saddr,
- sk->ip_tos|sk->localroute);
+ sk->ip_tos|sk->localroute, sk->bound_dev_if);
if (err)
return err;
if ((rt->rt_flags&RTCF_BROADCAST) && !sk->broadcast) {
@@ -996,7 +1016,7 @@
struct udphdr *uh = (struct udphdr *)(skb->nh.raw + iph->ihl*4);
struct sock *sk;

- sk = udp_v4_lookup(iph->saddr, uh->source, iph->daddr, uh->dest);
+ sk = udp_v4_lookup(iph->saddr, uh->source, iph->daddr, uh->dest, skb->dev->ifindex);
if (!sk)
return 0;

@@ -1092,10 +1112,10 @@
if (IPCB(skb)->redirport)
sk = udp_v4_proxy_lookup(uh->dest, saddr, uh->source,
daddr, skb->dev->pa_addr,
- IPCB(skb)->redirport);
+ IPCB(skb)->redirport, skb->dev->ifindex);
else
#endif
- sk = udp_v4_lookup(saddr, uh->source, daddr, uh->dest);
+ sk = udp_v4_lookup(saddr, uh->source, daddr, uh->dest, skb->dev->ifindex);

if (sk == NULL) {
udp_statistics.UdpNoPorts++;
--- linux/CREDITS.orig Wed Sep 17 23:06:55 1997
+++ linux/CREDITS Fri Oct 3 12:00:17 1997
@@ -1692,6 +1692,7 @@
E: dwmw2@cam.ac.uk
D: Extensive ARCnet rewrite
D: ARCnet COM20020, COM90xx IO-MAP drivers
+D: SO_BINDTODEVICE in 2.1.x (from Elliot Poger's code in 2.0.31)
S: Robinson College, Grange Road
S: Cambridge. CB3 9AN
S: England

--===_0_Fri_Oct__3_12:06:09_BST_1997
Content-Type: text/plain; charset=us-ascii

David Woodhouse, CB3 9AN http://dwmw2.robinson.cam.ac.uk/
dwmw2@cam.ac.uk Tel: 0976 658355
( D.W.Woodhouse@nortel.co.uk Tel: 01279 402332 )

(Use the former; I'm going back to College next week.)

--===_0_Fri_Oct__3_12:06:09_BST_1997--