// SPDX-License-Identifier: GPL-2.0-or-later /* * DDP: An implementation of the AppleTalk DDP protocol for * Ethernet 'ELAP'. * * Alan Cox <alan@lxorguk.ukuu.org.uk> * * With more than a little assistance from * * Wesley Craig <netatalk@umich.edu> * * Fixes: * Neil Horman : Added missing device ioctls * Michael Callahan : Made routing work * Wesley Craig : Fix probing to listen to a * passed node id. * Alan Cox : Added send/recvmsg support * Alan Cox : Moved at. to protinfo in * socket. * Alan Cox : Added firewall hooks. * Alan Cox : Supports new ARPHRD_LOOPBACK * Christer Weinigel : Routing and /proc fixes. * Bradford Johnson : LocalTalk. * Tom Dyas : Module support. * Alan Cox : Hooks for PPP (based on the * LocalTalk hook). * Alan Cox : Posix bits * Alan Cox/Mike Freeman : Possible fix to NBP problems * Bradford Johnson : IP-over-DDP (experimental) * Jay Schulist : Moved IP-over-DDP to its own * driver file. (ipddp.c & ipddp.h) * Jay Schulist : Made work as module with * AppleTalk drivers, cleaned it. * Rob Newberry : Added proxy AARP and AARP * procfs, moved probing to AARP * module. * Adrian Sun/ * Michael Zuelsdorff : fix for net.0 packets. don't * allow illegal ether/tokentalk * port assignment. we lose a * valid localtalk port as a * result. * Arnaldo C. de Melo : Cleanup, in preparation for * shared skb support 8) * Arnaldo C. de Melo : Move proc stuff to atalk_proc.c, * use seq_file
*/
if (to->sat_addr.s_net == ATADDR_ANYNET &&
to->sat_addr.s_node == ATADDR_BCAST) { if (atif->address.s_node == at->src_node &&
atif->address.s_net == at->src_net) { /* This socket's address matches the address of the interface * that received the packet -- use it
*/ goto found;
}
/* Continue searching for a socket matching the interface address, * but use this socket by default if no other one is found
*/
def_socket = s;
}
/* XXXX.0 -- we got a request for this router. make sure
* that the node is appropriately set. */ if (to->sat_addr.s_node == ATADDR_ANYNODE &&
to->sat_addr.s_net != ATADDR_ANYNET &&
atif->address.s_node == at->src_node) {
to->sat_addr.s_node = atif->address.s_node; goto found;
}
}
s = def_socket;
found:
read_unlock_bh(&atalk_sockets_lock); return s;
}
/** * atalk_find_or_insert_socket - Try to find a socket matching ADDR * @sk: socket to insert in the list if it is not there already * @sat: address to search for * * Try to find a socket matching ADDR in the socket list, if found then return * it. If not, insert SK into the socket list. * * This entire operation must execute atomically.
*/ staticstruct sock *atalk_find_or_insert_socket(struct sock *sk, struct sockaddr_at *sat)
{ struct sock *s; struct atalk_sock *at;
write_lock_bh(&atalk_sockets_lock);
sk_for_each(s, &atalk_sockets) {
at = at_sk(s);
/* For probing devices or in a routerless network */ struct atalk_route atrtr_default;
/* AppleTalk interface control */ /* * Drop a device. Doesn't drop any of its routes - that is the caller's * problem. Called when we down the interface or delete the address.
*/ staticvoid atif_drop_device(struct net_device *dev)
{ struct atalk_iface **iface = &atalk_interfaces; struct atalk_iface *tmp;
/* Perform AARP probing for a proxy address */ staticint atif_proxy_probe_device(struct atalk_iface *atif, struct atalk_addr *proxy_addr)
{ int netrange = ntohs(atif->nets.nr_lastnet) -
ntohs(atif->nets.nr_firstnet) + 1; /* we probe the interface's network */ int probe_net = ntohs(atif->address.s_net); int probe_node = ATADDR_ANYNODE; /* we'll take anything */ int netct, nodect;
/* Offset the network we start probing with */ if (probe_net == ATADDR_ANYNET) {
probe_net = ntohs(atif->nets.nr_firstnet); if (netrange)
probe_net += jiffies % netrange;
}
if (probe_node == ATADDR_ANYNODE)
probe_node = jiffies & 0xFF;
/* Scan the networks */ for (netct = 0; netct <= netrange; netct++) { /* Sweep the available nodes from a given start */
proxy_addr->s_net = htons(probe_net); for (nodect = 0; nodect < 256; nodect++) {
proxy_addr->s_node = (nodect + probe_node) & 0xFF; if (proxy_addr->s_node > 0 &&
proxy_addr->s_node < 254) { /* Tell AARP to probe a proposed address */ int ret = aarp_proxy_probe_network(atif,
proxy_addr);
if (ret != -EADDRINUSE) return ret;
}
}
probe_net++; if (probe_net > ntohs(atif->nets.nr_lastnet))
probe_net = ntohs(atif->nets.nr_firstnet);
}
/* * Find a match for 'any network' - ie any of our interfaces with that * node number will do just nicely.
*/ staticstruct atalk_iface *atalk_find_anynet(int node, struct net_device *dev)
{ struct atalk_iface *iface = dev->atalk_ptr;
if (!iface || iface->status & ATIF_PROBE) goto out_err;
/* XXXX.0 -- net.0 returns the iface associated with net */ if (node == ATADDR_ANYNODE && net != ATADDR_ANYNET &&
ntohs(iface->nets.nr_firstnet) <= ntohs(net) &&
ntohs(net) <= ntohs(iface->nets.nr_lastnet)) break;
}
read_unlock_bh(&atalk_interfaces_lock); return iface;
}
/* * Find a route for an AppleTalk packet. This ought to get cached in * the socket (later on...). We know about host routes and the fact * that a route must be direct to broadcast.
*/ staticstruct atalk_route *atrtr_find(struct atalk_addr *target)
{ /* * we must search through all routes unless we find a * host route, because some host routes might overlap * network routes
*/ struct atalk_route *net_route = NULL; struct atalk_route *r;
read_lock_bh(&atalk_routes_lock); for (r = atalk_routes; r; r = r->next) { if (!(r->flags & RTF_UP)) continue;
if (r->target.s_net == target->s_net) { if (r->flags & RTF_HOST) { /* * if this host route is for the target, * the we're done
*/ if (r->target.s_node == target->s_node) goto out;
} else /* * this route will work if there isn't a * direct host route, so cache it
*/
net_route = r;
}
}
/* * if we found a network route but not a direct host * route, then return it
*/ if (net_route)
r = net_route; elseif (atrtr_default.dev)
r = &atrtr_default; else/* No route can be found */
r = NULL;
out:
read_unlock_bh(&atalk_routes_lock); return r;
}
/* * Given an AppleTalk network, find the device to use. This can be * a simple lookup.
*/ struct net_device *atrtr_get_dev(struct atalk_addr *sa)
{ struct atalk_route *atr = atrtr_find(sa); return atr ? atr->dev : NULL;
}
/* Set up a default router */ staticvoid atrtr_set_default(struct net_device *dev)
{
atrtr_default.dev = dev;
atrtr_default.flags = RTF_UP;
atrtr_default.gateway.s_net = htons(0);
atrtr_default.gateway.s_node = 0;
}
/* * Add a router. Basically make sure it looks valid and stuff the * entry in the list. While it uses netranges we always set them to one * entry to work like netatalk.
*/ staticint atrtr_create(struct rtentry *r, struct net_device *devhint)
{ struct sockaddr_at *ta = (struct sockaddr_at *)&r->rt_dst; struct sockaddr_at *ga = (struct sockaddr_at *)&r->rt_gateway; struct atalk_route *rt; struct atalk_iface *iface, *riface; int retval = -EINVAL;
/* * Fixme: Raise/Lower a routing change semaphore for these * operations.
*/
/* Validate the request */ if (ta->sat_family != AF_APPLETALK ||
(!devhint && ga->sat_family != AF_APPLETALK)) goto out;
/* Now walk the routing table and make our decisions */
write_lock_bh(&atalk_routes_lock); for (rt = atalk_routes; rt; rt = rt->next) { if (r->rt_flags != rt->flags) continue;
if (ta->sat_addr.s_net == rt->target.s_net) { if (!(rt->flags & RTF_HOST)) break; if (ta->sat_addr.s_node == rt->target.s_node) break;
}
}
/* * Called when a device is downed. Just throw away any routes * via it.
*/ staticvoid atrtr_device_down(struct net_device *dev)
{ struct atalk_route **r = &atalk_routes; struct atalk_route *tmp;
write_lock_bh(&atalk_routes_lock); while ((tmp = *r) != NULL) { if (tmp->dev == dev) {
*r = tmp->next;
dev_put(dev);
kfree(tmp);
} else
r = &tmp->next;
}
write_unlock_bh(&atalk_routes_lock);
if (atrtr_default.dev == dev)
atrtr_set_default(NULL);
}
/* Actually down the interface */ staticinlinevoid atalk_dev_down(struct net_device *dev)
{
atrtr_device_down(dev); /* Remove all routes for the device */
aarp_device_down(dev); /* Remove AARP entries for the device */
atif_drop_device(dev); /* Remove the device */
}
/* * A device event has occurred. Watch for devices going down and * delete our use of them (iface and route).
*/ staticint ddp_device_event(struct notifier_block *this, unsignedlong event, void *ptr)
{ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
if (!net_eq(dev_net(dev), &init_net)) return NOTIFY_DONE;
if (event == NETDEV_DOWN) /* Discard any use of this */
atalk_dev_down(dev);
if (get_user_ifreq(&atreq, NULL, arg)) return -EFAULT;
dev = __dev_get_by_name(&init_net, atreq.ifr_name); if (!dev) return -ENODEV;
sa = (struct sockaddr_at *)&atreq.ifr_addr;
atif = atalk_find_dev(dev);
switch (cmd) { case SIOCSIFADDR: if (!capable(CAP_NET_ADMIN)) return -EPERM; if (sa->sat_family != AF_APPLETALK) return -EINVAL; if (dev->type != ARPHRD_ETHER &&
dev->type != ARPHRD_LOOPBACK &&
dev->type != ARPHRD_LOCALTLK &&
dev->type != ARPHRD_PPP) return -EPROTONOSUPPORT;
nr = (struct atalk_netrange *)&sa->sat_zero[0];
add_route = 1;
/* * if this is a point-to-point iface, and we already * have an iface for this AppleTalk address, then we * should not add a route
*/ if ((dev->flags & IFF_POINTOPOINT) &&
atalk_find_interface(sa->sat_addr.s_net,
sa->sat_addr.s_node)) {
printk(KERN_DEBUG "AppleTalk: point-to-point " "interface added with " "existing address\n");
add_route = 0;
}
/* * Phase 1 is fine on LocalTalk but we don't do * EtherTalk phase 1. Anyone wanting to add it, go ahead.
*/ if (dev->type == ARPHRD_ETHER && nr->nr_phase != 2) return -EPROTONOSUPPORT; if (sa->sat_addr.s_node == ATADDR_BCAST ||
sa->sat_addr.s_node == 254) return -EINVAL; if (atif) { /* Already setting address */ if (atif->status & ATIF_PROBE) return -EBUSY;
case SIOCATALKDIFADDR: case SIOCDIFADDR: if (!capable(CAP_NET_ADMIN)) return -EPERM; if (sa->sat_family != AF_APPLETALK) return -EINVAL;
atalk_dev_down(dev); break;
case SIOCSARP: if (!capable(CAP_NET_ADMIN)) return -EPERM; if (sa->sat_family != AF_APPLETALK) return -EINVAL; /* * for now, we only support proxy AARP on ELAP; * we should be able to do it for LocalTalk, too.
*/ if (dev->type != ARPHRD_ETHER) return -EPROTONOSUPPORT;
/* * atif points to the current interface on this network; * we aren't concerned about its current status (at * least for now), but it has all the settings about * the network we're going to probe. Consequently, it * must exist.
*/ if (!atif) return -EADDRNOTAVAIL;
nr = (struct atalk_netrange *)&(atif->nets); /* * Phase 1 is fine on Localtalk but we don't do * Ethertalk phase 1. Anyone wanting to add it, go ahead.
*/ if (dev->type == ARPHRD_ETHER && nr->nr_phase != 2) return -EPROTONOSUPPORT;
if (sa->sat_addr.s_node == ATADDR_BCAST ||
sa->sat_addr.s_node == 254) return -EINVAL;
/* * Check if the chosen address is used. If so we * error and ATCP will try another.
*/ if (atif_proxy_probe_device(atif, &(sa->sat_addr)) < 0) return -EADDRINUSE;
/* * We now have an address on the local network, and * the AARP code will defend it for us until we take it * down. We don't set up any routes right now, because * ATCP will install them manually via SIOCADDRT.
*/ break;
case SIOCDARP: if (!capable(CAP_NET_ADMIN)) return -EPERM; if (sa->sat_family != AF_APPLETALK) return -EINVAL; if (!atif) return -EADDRNOTAVAIL;
/* give to aarp module to remove proxy entry */
aarp_proxy_remove(atif->dev, &(sa->sat_addr)); return 0;
}
if (copy_from_user(&rt, arg, sizeof(rt))) return -EFAULT;
switch (cmd) { case SIOCDELRT: if (rt.rt_dst.sa_family != AF_APPLETALK) return -EINVAL; return atrtr_delete(&((struct sockaddr_at *)
&rt.rt_dst)->sat_addr);
case SIOCADDRT: return atrtr_ioctl_addrt(&rt);
} return -EINVAL;
}
/**************************************************************************\ * * * Handling for system calls applied via the various interfaces to an * * AppleTalk socket object. * * *
\**************************************************************************/
/* * Checksum: This is 'optional'. It's quite likely also a good * candidate for assembler hackery 8)
*/ staticunsignedlong atalk_sum_partial(constunsignedchar *data, int len, unsignedlong sum)
{ /* This ought to be unwrapped neatly. I'll trust gcc for now */ while (len--) {
sum += *data++;
sum = rol16(sum, 1);
} return sum;
}
/* Checksum skb data -- similar to skb_checksum */ staticunsignedlong atalk_sum_skb(conststruct sk_buff *skb, int offset, int len, unsignedlong sum)
{ int start = skb_headlen(skb); struct sk_buff *frag_iter; int i, copy;
/* checksum stuff in header space */ if ((copy = start - offset) > 0) { if (copy > len)
copy = len;
sum = atalk_sum_partial(skb->data + offset, copy, sum); if ((len -= copy) == 0) return sum;
offset += copy;
}
/* checksum stuff in frags */ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { int end; const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
WARN_ON(start > offset + len);
end = start + skb_frag_size(frag); if ((copy = end - offset) > 0) {
u8 *vaddr;
/* * Create a socket. Initialise the socket, blank the addresses * set the state.
*/ staticint atalk_create(struct net *net, struct socket *sock, int protocol, int kern)
{ struct sock *sk; int rc = -ESOCKTNOSUPPORT;
if (!net_eq(net, &init_net)) return -EAFNOSUPPORT;
/* * We permit SOCK_DGRAM and RAW is an extension. It is trivial to do * and gives you the full ELAP frame. Should be handy for CAP 8)
*/ if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM) goto out;
/** * atalk_pick_and_bind_port - Pick a source port when one is not given * @sk: socket to insert into the tables * @sat: address to search for * * Pick a source port when one is not given. If we can find a suitable free * one, we insert the socket into the tables using it. * * This whole operation must be atomic.
*/ staticint atalk_pick_and_bind_port(struct sock *sk, struct sockaddr_at *sat)
{ int retval;
/* Set the address we talk to */ staticint atalk_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags)
{ struct sock *sk = sock->sk; struct atalk_sock *at = at_sk(sk); struct sockaddr_at *addr; int err;
if (addr->sat_family != AF_APPLETALK) return -EAFNOSUPPORT;
if (addr->sat_addr.s_node == ATADDR_BCAST &&
!sock_flag(sk, SOCK_BROADCAST)) { #if 1
pr_warn("atalk_connect: %s is broken and did not set SO_BROADCAST.\n",
current->comm); #else return -EACCES; #endif
}
lock_sock(sk);
err = -EBUSY; if (sock_flag(sk, SOCK_ZAPPED)) if (atalk_autobind(sk) < 0) goto out;
err = -ENETUNREACH; if (!atrtr_get_dev(&addr->sat_addr)) goto out;
/* * Find the name of an AppleTalk socket. Just copy the right * fields into the sockaddr.
*/ staticint atalk_getname(struct socket *sock, struct sockaddr *uaddr, int peer)
{ struct sockaddr_at sat; struct sock *sk = sock->sk; struct atalk_sock *at = at_sk(sk); int err;
lock_sock(sk);
err = -ENOBUFS; if (sock_flag(sk, SOCK_ZAPPED)) if (atalk_autobind(sk) < 0) goto out;
memset(&sat, 0, sizeof(sat));
if (peer) {
err = -ENOTCONN; if (sk->sk_state != TCP_ESTABLISHED) goto out;
/* * Don't route multicast, etc., packets, or packets sent to "this * network"
*/ if (skb->pkt_type != PACKET_HOST || !ddp->deh_dnet) { /* * FIXME: * * Can it ever happen that a packet is from a PPP iface and * needs to be broadcast onto the default network?
*/ if (dev->type == ARPHRD_PPP)
printk(KERN_DEBUG "AppleTalk: didn't forward broadcast " "packet received from PPP iface\n"); goto free_it;
}
/* Fix up skb->len field */
skb_trim(skb, min_t(unsignedint, origlen,
(rt->dev->hard_header_len +
ddp_dl->header_length + (len_hops & 1023))));
/* FIXME: use skb->cb to be able to use shared skbs */
ddp->deh_len_hops = htons(len_hops);
/* * Send the buffer onwards * * Now we must always be careful. If it's come from LocalTalk to * EtherTalk it might not fit * * Order matters here: If a packet has to be copied to make a new * headroom (rare hopefully) then it won't need unsharing. * * Note. ddp-> becomes invalid at the realloc.
*/ if (skb_headroom(skb) < 22) { /* 22 bytes - 12 ether, 2 len, 3 802.2 5 snap */ struct sk_buff *nskb = skb_realloc_headroom(skb, 32);
kfree_skb(skb);
skb = nskb;
} else
skb = skb_unshare(skb, GFP_ATOMIC);
/* * If the buffer didn't vanish into the lack of space bitbucket we can * send it.
*/ if (skb == NULL) goto drop;
/** * atalk_rcv - Receive a packet (in skb) from device dev * @skb: packet received * @dev: network device where the packet comes from * @pt: packet type * @orig_dev: the original receive net device * * Receive a packet (in skb) from device dev. This has come from the SNAP * decoder, and on entry skb->transport_header is the DDP header, skb->len * is the DDP header, skb->len is the DDP length. The physical headers * have been extracted. PPP should probably pass frames marked as for this * layer. [ie ARPHRD_ETHERTALK]
*/ staticint atalk_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{ struct ddpehdr *ddp; struct sock *sock; struct atalk_iface *atif; struct sockaddr_at tosat; int origlen;
__u16 len_hops;
if (!net_eq(dev_net(dev), &init_net)) goto drop;
/* Don't mangle buffer if shared */ if (!(skb = skb_share_check(skb, GFP_ATOMIC))) goto out;
/* Size check and make sure header is contiguous */ if (!pskb_may_pull(skb, sizeof(*ddp))) goto drop;
ddp = ddp_hdr(skb);
len_hops = ntohs(ddp->deh_len_hops);
/* Trim buffer in case of stray trailing data */
origlen = skb->len;
skb_trim(skb, min_t(unsignedint, skb->len, len_hops & 1023));
/* * Size check to see if ddp->deh_len was crap * (Otherwise we'll detonate most spectacularly * in the middle of atalk_checksum() or recvmsg()).
*/ if (skb->len < sizeof(*ddp) || skb->len < (len_hops & 1023)) {
pr_debug("AppleTalk: dropping corrupted frame (deh_len=%u, " "skb->len=%u)\n", len_hops & 1023, skb->len); goto drop;
}
/* * Any checksums. Note we don't do htons() on this == is assumed to be * valid for net byte orders all over the networking code...
*/ if (ddp->deh_sum &&
atalk_checksum(skb, len_hops & 1023) != ddp->deh_sum) /* Not a valid AppleTalk frame - dustbin time */ goto drop;
/* Check the packet is aimed at us */ if (!ddp->deh_dnet) /* Net 0 is 'this network' */
atif = atalk_find_anynet(ddp->deh_dnode, dev); else
atif = atalk_find_interface(ddp->deh_dnet, ddp->deh_dnode);
if (!atif) { /* Not ours, so we route the packet via the correct * AppleTalk iface
*/ return atalk_route_packet(skb, dev, ddp, len_hops, origlen);
}
/* * Which socket - atalk_search_socket() looks for a *full match* * of the <net, node, port> tuple.
*/
tosat.sat_addr.s_net = ddp->deh_dnet;
tosat.sat_addr.s_node = ddp->deh_dnode;
tosat.sat_port = ddp->deh_dport;
sock = atalk_search_socket(&tosat, atif); if (!sock) /* But not one of our sockets */ goto drop;
/* * Receive a LocalTalk frame. We make some demands on the caller here. * Caller must provide enough headroom on the packet to pull the short * header and append a long one.
*/ staticint ltalk_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{ if (!net_eq(dev_net(dev), &init_net)) goto freeit;
/* Expand any short form frames */ if (skb_mac_header(skb)[2] == 1) { struct ddpehdr *ddp; /* Find our address */ struct atalk_addr *ap = atalk_find_dev_addr(dev);
/* Don't mangle buffer if shared */ if (!(skb = skb_share_check(skb, GFP_ATOMIC))) return 0;
/* * The push leaves us with a ddephdr not an shdr, and * handily the port bytes in the right place preset.
*/
ddp = skb_push(skb, sizeof(*ddp) - 4);
/* Now fill in the long header */
/* * These two first. The mac overlays the new source/dest * network information so we MUST copy these before * we write the network numbers !
*/
ddp->deh_dnode = skb_mac_header(skb)[0]; /* From physical header */
ddp->deh_snode = skb_mac_header(skb)[1]; /* From physical header */
ddp->deh_dnet = ap->s_net; /* Network number */
ddp->deh_snet = ap->s_net;
ddp->deh_sum = 0; /* No checksum */ /* * Not sure about this bit...
*/ /* Non routable, so force a drop if we slip up later */
ddp->deh_len_hops = htons(skb->len + (DDP_MAXHOPS << 10));
}
skb_reset_transport_header(skb);
if (sk->sk_no_check_tx)
ddp->deh_sum = 0; else
ddp->deh_sum = atalk_checksum(skb, len + sizeof(*ddp));
/* * Loopback broadcast packets to non gateway targets (ie routes * to group we are in)
*/ if (ddp->deh_dnode == ATADDR_BCAST &&
!(rt->flags & RTF_GATEWAY) && !(dev->flags & IFF_LOOPBACK)) { struct sk_buff *skb2 = skb_copy(skb, GFP_KERNEL);
if (skb2) {
loopback = 1;
net_dbg_ratelimited("SK %p: send out(copy).\n", sk); /* * If it fails it is queued/sent above in the aarp queue
*/
aarp_send_ddp(dev, skb2, &usat->sat_addr, NULL);
}
}
/* * If it fails it is queued/sent above in the aarp queue
*/
aarp_send_ddp(dev, skb, &usat->sat_addr, NULL);
}
net_dbg_ratelimited("SK %p: Done write (%zd).\n", sk, len);
out:
release_sock(sk); return err ? : len;
}
staticint atalk_recvmsg(struct socket *sock, struct msghdr *msg, size_t size, int flags)
{ struct sock *sk = sock->sk; struct ddpehdr *ddp; int copied = 0; int offset = 0; int err = 0; struct sk_buff *skb;
switch (cmd) { case SIOCADDRT: case SIOCDELRT: return atalk_compat_routing_ioctl(sk, cmd, argp); /* * SIOCATALKDIFADDR is a SIOCPROTOPRIVATE ioctl number, so we * cannot handle it in common code. The data we access if ifreq * here is compatible, so we can simply call the native * handler.
*/ case SIOCATALKDIFADDR: return atalk_ioctl(sock, cmd, (unsignedlong)argp); default: return -ENOIOCTLCMD;
}
} #endif/* CONFIG_COMPAT */
/* * No explicit module reference count manipulation is needed in the * protocol. Socket layer sets module reference count for us * and interfaces reference counting is done * by the network device layer. * * Ergo, before the AppleTalk module can be removed, all AppleTalk * sockets should be closed from user space.
*/ staticvoid __exit atalk_exit(void)
{ #ifdef CONFIG_SYSCTL
atalk_unregister_sysctl(); #endif/* CONFIG_SYSCTL */
atalk_proc_exit();
aarp_cleanup_module(); /* General aarp clean-up. */
unregister_netdevice_notifier(&ddp_notifier);
dev_remove_pack(<alk_packet_type);
dev_remove_pack(&ppptalk_packet_type);
unregister_snap_client(ddp_dl);
sock_unregister(PF_APPLETALK);
proto_unregister(&ddp_proto);
}
module_exit(atalk_exit);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.