linux rx pmtu fixes
[openafs.git] / src / rx / LINUX / rx_knet.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 /*
11  * rx_knet.c - RX kernel send, receive and timer routines.
12  *
13  * Linux implementation.
14  */
15 #include <afsconfig.h>
16 #include "afs/param.h"
17
18
19 #include <linux/version.h>
20 #include "rx/rx_kcommon.h"
21 #include "h/smp_lock.h"
22 #include <asm/uaccess.h>
23 #ifdef ADAPT_PMTU
24 #include <linux/errqueue.h>
25 #include <linux/icmp.h>
26 #endif
27
28 #include "osi_compat.h"
29
30 /* rxk_NewSocket
31  * open and bind RX socket
32  */
33 osi_socket *
34 rxk_NewSocketHost(afs_uint32 ahost, short aport)
35 {
36     struct socket *sockp;
37     struct sockaddr_in myaddr;
38     int code;
39 #ifdef ADAPT_PMTU
40     int pmtu = IP_PMTUDISC_WANT;
41     int do_recverr = 1;
42 #else
43     int pmtu = IP_PMTUDISC_DONT;
44 #endif
45
46     /* We need a better test for this. if you need it back, tell us
47      * how to detect it. 
48      */
49 #ifdef LINUX_KERNEL_SOCK_CREATE_V
50     code = sock_create(AF_INET, SOCK_DGRAM, IPPROTO_UDP, &sockp, 0);
51 #else
52     code = sock_create(AF_INET, SOCK_DGRAM, IPPROTO_UDP, &sockp);
53 #endif
54     if (code < 0)
55         return NULL;
56
57     /* Bind socket */
58     myaddr.sin_family = AF_INET;
59     myaddr.sin_addr.s_addr = ahost;
60     myaddr.sin_port = aport;
61     code =
62         sockp->ops->bind(sockp, (struct sockaddr *)&myaddr, sizeof(myaddr));
63
64     if (code < 0) {
65         printk("sock_release(rx_socket) FIXME\n");
66         return NULL;
67     }
68
69     kernel_setsockopt(sockp, SOL_IP, IP_MTU_DISCOVER, (char *)&pmtu,
70                       sizeof(pmtu));
71 #ifdef ADAPT_PMTU
72     kernel_setsockopt(sockp, SOL_IP, IP_RECVERR, (char *)&do_recverr,
73                       sizeof(do_recverr));
74 #endif
75     return (osi_socket *)sockp;
76 }
77
78 osi_socket *
79 rxk_NewSocket(short aport)
80 {
81     return rxk_NewSocketHost(htonl(INADDR_ANY), aport);
82 }
83
84 /* free socket allocated by osi_NetSocket */
85 int
86 rxk_FreeSocket(struct socket *asocket)
87 {
88     AFS_STATCNT(osi_FreeSocket);
89     return 0;
90 }
91
92 #ifdef ADAPT_PMTU
93 void
94 handle_socket_error(osi_socket so)
95 {
96     struct msghdr msg;
97     struct cmsghdr *cmsg;
98     struct sock_extended_err *err;
99     struct sockaddr_in addr;
100     struct sockaddr *offender;
101     char *controlmsgbuf;
102     int code;
103     struct socket *sop = (struct socket *)so;
104
105     if (!(controlmsgbuf=rxi_Alloc(256)))
106         return;
107     msg.msg_name = &addr;
108     msg.msg_namelen = sizeof(addr);
109     msg.msg_control = controlmsgbuf;
110     msg.msg_controllen = 256;
111     msg.msg_flags = 0;
112
113     code = kernel_recvmsg(sop, &msg, NULL, 0, 256,
114                           MSG_ERRQUEUE|MSG_DONTWAIT|MSG_TRUNC);
115
116     if (code < 0 || !(msg.msg_flags & MSG_ERRQUEUE))
117         goto out;
118
119     for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
120         if (CMSG_OK(&msg, cmsg) && cmsg->cmsg_level == SOL_IP &&
121             cmsg->cmsg_type == IP_RECVERR)
122             break;
123     }
124     if (!cmsg)
125         goto out;
126     err = CMSG_DATA(cmsg);
127     offender = SO_EE_OFFENDER(err);
128     
129     if (offender->sa_family != AF_INET)
130        goto out;
131
132     memcpy(&addr, offender, sizeof(addr));
133
134     if (err->ee_origin == SO_EE_ORIGIN_ICMP &&
135         err->ee_type == ICMP_DEST_UNREACH &&
136         err->ee_code == ICMP_FRAG_NEEDED) {
137         rxi_SetPeerMtu(NULL, ntohl(addr.sin_addr.s_addr), ntohs(addr.sin_port),
138                        err->ee_info);
139     }
140     /* other DEST_UNREACH's and TIME_EXCEEDED should be dealt with too */
141
142 out:
143     rxi_Free(controlmsgbuf, 256);
144     return;
145 }
146 #endif
147
148 /* osi_NetSend
149  *
150  * Return codes:
151  * 0 = success
152  * non-zero = failure
153  */
154 int
155 osi_NetSend(osi_socket sop, struct sockaddr_in *to, struct iovec *iovec,
156             int iovcnt, afs_int32 size, int istack)
157 {
158     struct msghdr msg;
159     int code;
160 #ifdef ADAPT_PMTU
161     int sockerr;
162     size_t esize;
163
164     while (1) {
165         sockerr=0;
166         esize = sizeof(sockerr);
167         kernel_getsockopt(sop, SOL_SOCKET, SO_ERROR, (char *)&sockerr, &esize);
168         if (sockerr == 0)
169            break;
170         handle_socket_error(sop);
171     }
172 #endif
173
174     msg.msg_name = to;
175     msg.msg_namelen = sizeof(*to);
176     msg.msg_control = NULL;
177     msg.msg_controllen = 0;
178     msg.msg_flags = 0;
179
180     code = kernel_sendmsg(sop, &msg, (struct kvec *) iovec, iovcnt, size);
181     return (code < 0) ? code : 0;
182 }
183
184
185 /* osi_NetReceive
186  * OS dependent part of kernel RX listener thread.
187  *
188  * Arguments:
189  *      so      socket to receive on, typically rx_socket
190  *      from    pointer to a sockaddr_in. 
191  *      iov     array of iovecs to fill in.
192  *      iovcnt  how many iovecs there are.
193  *      lengthp IN/OUT in: total space available in iovecs. out: size of read.
194  *
195  * Return
196  * 0 if successful
197  * error code (such as EINTER) if not
198  *
199  * Environment
200  *      Note that the maximum number of iovecs is 2 + RX_MAXWVECS. This is
201  *      so we have a little space to look for packets larger than 
202  *      rx_maxReceiveSize.
203  */
204 int rxk_lastSocketError;
205 int rxk_nSocketErrors;
206 int
207 osi_NetReceive(osi_socket so, struct sockaddr_in *from, struct iovec *iov,
208                int iovcnt, int *lengthp)
209 {
210     struct msghdr msg;
211     int code;
212 #ifdef ADAPT_PMTU
213     int sockerr;
214     size_t esize;
215 #endif
216     struct iovec tmpvec[RX_MAXWVECS + 2];
217     struct socket *sop = (struct socket *)so;
218
219     if (iovcnt > RX_MAXWVECS + 2) {
220         osi_Panic("Too many (%d) iovecs passed to osi_NetReceive\n", iovcnt);
221     }
222 #ifdef ADAPT_PMTU
223     while (1) {
224         sockerr=0;
225         esize = sizeof(sockerr);
226         kernel_getsockopt(sop, SOL_SOCKET, SO_ERROR, (char *)&sockerr, &esize);
227         if (sockerr == 0)
228            break;
229         handle_socket_error(so);
230     }
231 #endif
232     memcpy(tmpvec, iov, iovcnt * sizeof(struct iovec));
233     msg.msg_name = from;
234     msg.msg_iov = tmpvec;
235     msg.msg_iovlen = iovcnt;
236     msg.msg_control = NULL;
237     msg.msg_controllen = 0;
238     msg.msg_flags = 0;
239
240     code = kernel_recvmsg(sop, &msg, (struct kvec *)tmpvec, iovcnt,
241                           *lengthp, 0);
242     if (code < 0) {
243         afs_try_to_freeze();
244
245         /* Clear the error before using the socket again.
246          * Oh joy, Linux has hidden header files as well. It appears we can
247          * simply call again and have it clear itself via sock_error().
248          */
249         flush_signals(current); /* We don't want no stinkin' signals. */
250         rxk_lastSocketError = code;
251         rxk_nSocketErrors++;
252     } else {
253         *lengthp = code;
254         code = 0;
255     }
256
257     return code;
258 }
259
260 void
261 osi_StopListener(void)
262 {
263     extern struct task_struct *rxk_ListenerTask;
264
265     while (rxk_ListenerTask) {
266         if (rxk_ListenerTask) {
267             flush_signals(rxk_ListenerTask);
268             force_sig(SIGKILL, rxk_ListenerTask);
269         }
270         if (!rxk_ListenerTask)
271             break;
272         afs_osi_Sleep(&rxk_ListenerTask);
273     }
274     sock_release(rx_socket);
275     rx_socket = NULL;
276 }
277