diff options
Diffstat (limited to 'net/ipx/ipx_route.c')
-rw-r--r-- | net/ipx/ipx_route.c | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/net/ipx/ipx_route.c b/net/ipx/ipx_route.c new file mode 100644 index 000000000000..67774448efd9 --- /dev/null +++ b/net/ipx/ipx_route.c | |||
@@ -0,0 +1,293 @@ | |||
1 | /* | ||
2 | * Implements the IPX routing routines. | ||
3 | * Code moved from af_ipx.c. | ||
4 | * | ||
5 | * Arnaldo Carvalho de Melo <acme@conectiva.com.br>, 2003 | ||
6 | * | ||
7 | * See net/ipx/ChangeLog. | ||
8 | */ | ||
9 | |||
10 | #include <linux/config.h> | ||
11 | #include <linux/list.h> | ||
12 | #include <linux/route.h> | ||
13 | #include <linux/spinlock.h> | ||
14 | |||
15 | #include <net/ipx.h> | ||
16 | #include <net/sock.h> | ||
17 | |||
18 | LIST_HEAD(ipx_routes); | ||
19 | DEFINE_RWLOCK(ipx_routes_lock); | ||
20 | |||
21 | extern struct ipx_interface *ipx_internal_net; | ||
22 | |||
23 | extern __u16 ipx_cksum(struct ipxhdr *packet, int length); | ||
24 | extern struct ipx_interface *ipxitf_find_using_net(__u32 net); | ||
25 | extern int ipxitf_demux_socket(struct ipx_interface *intrfc, | ||
26 | struct sk_buff *skb, int copy); | ||
27 | extern int ipxitf_demux_socket(struct ipx_interface *intrfc, | ||
28 | struct sk_buff *skb, int copy); | ||
29 | extern int ipxitf_send(struct ipx_interface *intrfc, struct sk_buff *skb, | ||
30 | char *node); | ||
31 | extern struct ipx_interface *ipxitf_find_using_net(__u32 net); | ||
32 | |||
33 | struct ipx_route *ipxrtr_lookup(__u32 net) | ||
34 | { | ||
35 | struct ipx_route *r; | ||
36 | |||
37 | read_lock_bh(&ipx_routes_lock); | ||
38 | list_for_each_entry(r, &ipx_routes, node) | ||
39 | if (r->ir_net == net) { | ||
40 | ipxrtr_hold(r); | ||
41 | goto unlock; | ||
42 | } | ||
43 | r = NULL; | ||
44 | unlock: | ||
45 | read_unlock_bh(&ipx_routes_lock); | ||
46 | return r; | ||
47 | } | ||
48 | |||
49 | /* | ||
50 | * Caller must hold a reference to intrfc | ||
51 | */ | ||
52 | int ipxrtr_add_route(__u32 network, struct ipx_interface *intrfc, | ||
53 | unsigned char *node) | ||
54 | { | ||
55 | struct ipx_route *rt; | ||
56 | int rc; | ||
57 | |||
58 | /* Get a route structure; either existing or create */ | ||
59 | rt = ipxrtr_lookup(network); | ||
60 | if (!rt) { | ||
61 | rt = kmalloc(sizeof(*rt), GFP_ATOMIC); | ||
62 | rc = -EAGAIN; | ||
63 | if (!rt) | ||
64 | goto out; | ||
65 | |||
66 | atomic_set(&rt->refcnt, 1); | ||
67 | ipxrtr_hold(rt); | ||
68 | write_lock_bh(&ipx_routes_lock); | ||
69 | list_add(&rt->node, &ipx_routes); | ||
70 | write_unlock_bh(&ipx_routes_lock); | ||
71 | } else { | ||
72 | rc = -EEXIST; | ||
73 | if (intrfc == ipx_internal_net) | ||
74 | goto out_put; | ||
75 | } | ||
76 | |||
77 | rt->ir_net = network; | ||
78 | rt->ir_intrfc = intrfc; | ||
79 | if (!node) { | ||
80 | memset(rt->ir_router_node, '\0', IPX_NODE_LEN); | ||
81 | rt->ir_routed = 0; | ||
82 | } else { | ||
83 | memcpy(rt->ir_router_node, node, IPX_NODE_LEN); | ||
84 | rt->ir_routed = 1; | ||
85 | } | ||
86 | |||
87 | rc = 0; | ||
88 | out_put: | ||
89 | ipxrtr_put(rt); | ||
90 | out: | ||
91 | return rc; | ||
92 | } | ||
93 | |||
94 | void ipxrtr_del_routes(struct ipx_interface *intrfc) | ||
95 | { | ||
96 | struct ipx_route *r, *tmp; | ||
97 | |||
98 | write_lock_bh(&ipx_routes_lock); | ||
99 | list_for_each_entry_safe(r, tmp, &ipx_routes, node) | ||
100 | if (r->ir_intrfc == intrfc) { | ||
101 | list_del(&r->node); | ||
102 | ipxrtr_put(r); | ||
103 | } | ||
104 | write_unlock_bh(&ipx_routes_lock); | ||
105 | } | ||
106 | |||
107 | static int ipxrtr_create(struct ipx_route_definition *rd) | ||
108 | { | ||
109 | struct ipx_interface *intrfc; | ||
110 | int rc = -ENETUNREACH; | ||
111 | |||
112 | /* Find the appropriate interface */ | ||
113 | intrfc = ipxitf_find_using_net(rd->ipx_router_network); | ||
114 | if (!intrfc) | ||
115 | goto out; | ||
116 | rc = ipxrtr_add_route(rd->ipx_network, intrfc, rd->ipx_router_node); | ||
117 | ipxitf_put(intrfc); | ||
118 | out: | ||
119 | return rc; | ||
120 | } | ||
121 | |||
122 | static int ipxrtr_delete(long net) | ||
123 | { | ||
124 | struct ipx_route *r, *tmp; | ||
125 | int rc; | ||
126 | |||
127 | write_lock_bh(&ipx_routes_lock); | ||
128 | list_for_each_entry_safe(r, tmp, &ipx_routes, node) | ||
129 | if (r->ir_net == net) { | ||
130 | /* Directly connected; can't lose route */ | ||
131 | rc = -EPERM; | ||
132 | if (!r->ir_routed) | ||
133 | goto out; | ||
134 | list_del(&r->node); | ||
135 | ipxrtr_put(r); | ||
136 | rc = 0; | ||
137 | goto out; | ||
138 | } | ||
139 | rc = -ENOENT; | ||
140 | out: | ||
141 | write_unlock_bh(&ipx_routes_lock); | ||
142 | return rc; | ||
143 | } | ||
144 | |||
145 | /* | ||
146 | * The skb has to be unshared, we'll end up calling ipxitf_send, that'll | ||
147 | * modify the packet | ||
148 | */ | ||
149 | int ipxrtr_route_skb(struct sk_buff *skb) | ||
150 | { | ||
151 | struct ipxhdr *ipx = ipx_hdr(skb); | ||
152 | struct ipx_route *r = ipxrtr_lookup(IPX_SKB_CB(skb)->ipx_dest_net); | ||
153 | |||
154 | if (!r) { /* no known route */ | ||
155 | kfree_skb(skb); | ||
156 | return 0; | ||
157 | } | ||
158 | |||
159 | ipxitf_hold(r->ir_intrfc); | ||
160 | ipxitf_send(r->ir_intrfc, skb, r->ir_routed ? | ||
161 | r->ir_router_node : ipx->ipx_dest.node); | ||
162 | ipxitf_put(r->ir_intrfc); | ||
163 | ipxrtr_put(r); | ||
164 | |||
165 | return 0; | ||
166 | } | ||
167 | |||
168 | /* | ||
169 | * Route an outgoing frame from a socket. | ||
170 | */ | ||
171 | int ipxrtr_route_packet(struct sock *sk, struct sockaddr_ipx *usipx, | ||
172 | struct iovec *iov, size_t len, int noblock) | ||
173 | { | ||
174 | struct sk_buff *skb; | ||
175 | struct ipx_sock *ipxs = ipx_sk(sk); | ||
176 | struct ipx_interface *intrfc; | ||
177 | struct ipxhdr *ipx; | ||
178 | size_t size; | ||
179 | int ipx_offset; | ||
180 | struct ipx_route *rt = NULL; | ||
181 | int rc; | ||
182 | |||
183 | /* Find the appropriate interface on which to send packet */ | ||
184 | if (!usipx->sipx_network && ipx_primary_net) { | ||
185 | usipx->sipx_network = ipx_primary_net->if_netnum; | ||
186 | intrfc = ipx_primary_net; | ||
187 | } else { | ||
188 | rt = ipxrtr_lookup(usipx->sipx_network); | ||
189 | rc = -ENETUNREACH; | ||
190 | if (!rt) | ||
191 | goto out; | ||
192 | intrfc = rt->ir_intrfc; | ||
193 | } | ||
194 | |||
195 | ipxitf_hold(intrfc); | ||
196 | ipx_offset = intrfc->if_ipx_offset; | ||
197 | size = sizeof(struct ipxhdr) + len + ipx_offset; | ||
198 | |||
199 | skb = sock_alloc_send_skb(sk, size, noblock, &rc); | ||
200 | if (!skb) | ||
201 | goto out_put; | ||
202 | |||
203 | skb_reserve(skb, ipx_offset); | ||
204 | skb->sk = sk; | ||
205 | |||
206 | /* Fill in IPX header */ | ||
207 | skb->h.raw = skb->nh.raw = skb_put(skb, sizeof(struct ipxhdr)); | ||
208 | ipx = ipx_hdr(skb); | ||
209 | ipx->ipx_pktsize = htons(len + sizeof(struct ipxhdr)); | ||
210 | IPX_SKB_CB(skb)->ipx_tctrl = 0; | ||
211 | ipx->ipx_type = usipx->sipx_type; | ||
212 | |||
213 | IPX_SKB_CB(skb)->last_hop.index = -1; | ||
214 | #ifdef CONFIG_IPX_INTERN | ||
215 | IPX_SKB_CB(skb)->ipx_source_net = ipxs->intrfc->if_netnum; | ||
216 | memcpy(ipx->ipx_source.node, ipxs->node, IPX_NODE_LEN); | ||
217 | #else | ||
218 | rc = ntohs(ipxs->port); | ||
219 | if (rc == 0x453 || rc == 0x452) { | ||
220 | /* RIP/SAP special handling for mars_nwe */ | ||
221 | IPX_SKB_CB(skb)->ipx_source_net = intrfc->if_netnum; | ||
222 | memcpy(ipx->ipx_source.node, intrfc->if_node, IPX_NODE_LEN); | ||
223 | } else { | ||
224 | IPX_SKB_CB(skb)->ipx_source_net = ipxs->intrfc->if_netnum; | ||
225 | memcpy(ipx->ipx_source.node, ipxs->intrfc->if_node, | ||
226 | IPX_NODE_LEN); | ||
227 | } | ||
228 | #endif /* CONFIG_IPX_INTERN */ | ||
229 | ipx->ipx_source.sock = ipxs->port; | ||
230 | IPX_SKB_CB(skb)->ipx_dest_net = usipx->sipx_network; | ||
231 | memcpy(ipx->ipx_dest.node, usipx->sipx_node, IPX_NODE_LEN); | ||
232 | ipx->ipx_dest.sock = usipx->sipx_port; | ||
233 | |||
234 | rc = memcpy_fromiovec(skb_put(skb, len), iov, len); | ||
235 | if (rc) { | ||
236 | kfree_skb(skb); | ||
237 | goto out_put; | ||
238 | } | ||
239 | |||
240 | /* Apply checksum. Not allowed on 802.3 links. */ | ||
241 | if (sk->sk_no_check || intrfc->if_dlink_type == IPX_FRAME_8023) | ||
242 | ipx->ipx_checksum = 0xFFFF; | ||
243 | else | ||
244 | ipx->ipx_checksum = ipx_cksum(ipx, len + sizeof(struct ipxhdr)); | ||
245 | |||
246 | rc = ipxitf_send(intrfc, skb, (rt && rt->ir_routed) ? | ||
247 | rt->ir_router_node : ipx->ipx_dest.node); | ||
248 | out_put: | ||
249 | ipxitf_put(intrfc); | ||
250 | if (rt) | ||
251 | ipxrtr_put(rt); | ||
252 | out: | ||
253 | return rc; | ||
254 | } | ||
255 | |||
256 | /* | ||
257 | * We use a normal struct rtentry for route handling | ||
258 | */ | ||
259 | int ipxrtr_ioctl(unsigned int cmd, void __user *arg) | ||
260 | { | ||
261 | struct rtentry rt; /* Use these to behave like 'other' stacks */ | ||
262 | struct sockaddr_ipx *sg, *st; | ||
263 | int rc = -EFAULT; | ||
264 | |||
265 | if (copy_from_user(&rt, arg, sizeof(rt))) | ||
266 | goto out; | ||
267 | |||
268 | sg = (struct sockaddr_ipx *)&rt.rt_gateway; | ||
269 | st = (struct sockaddr_ipx *)&rt.rt_dst; | ||
270 | |||
271 | rc = -EINVAL; | ||
272 | if (!(rt.rt_flags & RTF_GATEWAY) || /* Direct routes are fixed */ | ||
273 | sg->sipx_family != AF_IPX || | ||
274 | st->sipx_family != AF_IPX) | ||
275 | goto out; | ||
276 | |||
277 | switch (cmd) { | ||
278 | case SIOCDELRT: | ||
279 | rc = ipxrtr_delete(st->sipx_network); | ||
280 | break; | ||
281 | case SIOCADDRT: { | ||
282 | struct ipx_route_definition f; | ||
283 | f.ipx_network = st->sipx_network; | ||
284 | f.ipx_router_network = sg->sipx_network; | ||
285 | memcpy(f.ipx_router_node, sg->sipx_node, IPX_NODE_LEN); | ||
286 | rc = ipxrtr_create(&f); | ||
287 | break; | ||
288 | } | ||
289 | } | ||
290 | |||
291 | out: | ||
292 | return rc; | ||
293 | } | ||