/*** * * ipv4/icmp.c * * rtnet - real-time networking subsystem * Copyright (C) 1999, 2000 Zentropic Computing, LLC * 2002 Ulrich Marx * 2002 Vinay Sridhara * 2003-2005 Jan Kiszka * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*** * Structure for sending the icmp packets */ struct icmp_bxm { unsigned int csum; size_t head_len; size_t data_len; off_t offset; struct { struct icmphdr icmph; nanosecs_abs_t timestamp; } head; union { struct rtskb *skb; void *buf; } data; }; struct rt_icmp_control { void (*handler)(struct rtskb *skb); short error; /* This ICMP is classed as an error message */ }; static DEFINE_RTDM_LOCK(echo_calls_lock); LIST_HEAD(echo_calls); static struct { /* * Scratch pad, provided so that rt_socket_dereference(&icmp_socket); * remains legal. */ struct rtdm_dev_context dummy; /* * Socket for icmp replies * It is not part of the socket pool. It may furthermore be used * concurrently by multiple tasks because all fields are static excect * skb_pool, but that one is spinlock protected. */ struct rtsocket socket; } icmp_socket_container; #define icmp_fd (&icmp_socket_container.dummy.fd) #define icmp_socket ((struct rtsocket *)rtdm_fd_to_private(icmp_fd)) void rt_icmp_queue_echo_request(struct rt_proc_call *call) { rtdm_lockctx_t context; rtdm_lock_get_irqsave(&echo_calls_lock, context); list_add_tail(&call->list_entry, &echo_calls); rtdm_lock_put_irqrestore(&echo_calls_lock, context); } void rt_icmp_dequeue_echo_request(struct rt_proc_call *call) { rtdm_lockctx_t context; rtdm_lock_get_irqsave(&echo_calls_lock, context); list_del(&call->list_entry); rtdm_lock_put_irqrestore(&echo_calls_lock, context); } void rt_icmp_cleanup_echo_requests(void) { rtdm_lockctx_t context; struct list_head *entry; struct list_head *next; rtdm_lock_get_irqsave(&echo_calls_lock, context); entry = echo_calls.next; INIT_LIST_HEAD(&echo_calls); rtdm_lock_put_irqrestore(&echo_calls_lock, context); while (entry != &echo_calls) { next = entry->next; rtpc_complete_call_nrt((struct rt_proc_call *)entry, -EINTR); entry = next; } /* purge any pending ICMP fragments */ rt_ip_frag_invalidate_socket(icmp_socket); } /*** * rt_icmp_discard - dummy function */ static void rt_icmp_discard(struct rtskb *skb) { } static int rt_icmp_glue_reply_bits(const void *p, unsigned char *to, unsigned int offset, unsigned int fraglen) { struct icmp_bxm *icmp_param = (struct icmp_bxm *)p; struct icmphdr *icmph; unsigned long csum; /* TODO: add support for fragmented ICMP packets */ if (offset != 0) return -EMSGSIZE; csum = rtnet_csum_copy((void *)&icmp_param->head, to, icmp_param->head_len, icmp_param->csum); csum = rtskb_copy_and_csum_bits(icmp_param->data.skb, icmp_param->offset, to + icmp_param->head_len, fraglen - icmp_param->head_len, csum); icmph = (struct icmphdr *)to; icmph->checksum = csum_fold(csum); return 0; } /*** * common reply function */ static void rt_icmp_send_reply(struct icmp_bxm *icmp_param, struct rtskb *skb) { struct dest_route rt; int err; icmp_param->head.icmph.checksum = 0; icmp_param->csum = 0; /* route back to the source address via the incoming device */ if (rt_ip_route_output(&rt, skb->nh.iph->saddr, skb->rtdev->local_ip) != 0) return; rt_socket_reference(icmp_socket); err = rt_ip_build_xmit(icmp_socket, rt_icmp_glue_reply_bits, icmp_param, sizeof(struct icmphdr) + icmp_param->data_len, &rt, MSG_DONTWAIT); if (err) rt_socket_dereference(icmp_socket); rtdev_dereference(rt.rtdev); RTNET_ASSERT(err == 0, rtdm_printk("RTnet: %s() error in xmit\n", __FUNCTION__);); (void)err; } /*** * rt_icmp_echo - handles echo replies on our previously sent requests */ static void rt_icmp_echo_reply(struct rtskb *skb) { rtdm_lockctx_t context; struct rt_proc_call *call; struct ipv4_cmd *cmd; rtdm_lock_get_irqsave(&echo_calls_lock, context); if (!list_empty(&echo_calls)) { call = (struct rt_proc_call *)echo_calls.next; list_del(&call->list_entry); rtdm_lock_put_irqrestore(&echo_calls_lock, context); } else { rtdm_lock_put_irqrestore(&echo_calls_lock, context); return; } cmd = rtpc_get_priv(call, struct ipv4_cmd); cmd->args.ping.ip_addr = skb->nh.iph->saddr; cmd->args.ping.rtt = 0; if ((skb->h.icmph->un.echo.id == cmd->args.ping.id) && (ntohs(skb->h.icmph->un.echo.sequence) == cmd->args.ping.sequence) && skb->len == cmd->args.ping.msg_size) { if (skb->len >= sizeof(nanosecs_abs_t)) cmd->args.ping.rtt = rtdm_clock_read() - *((nanosecs_abs_t *)skb->data); rtpc_complete_call(call, sizeof(struct icmphdr) + skb->len); } else rtpc_complete_call(call, 0); } /*** * rt_icmp_echo_request - handles echo requests sent by other stations */ static void rt_icmp_echo_request(struct rtskb *skb) { struct icmp_bxm icmp_param; icmp_param.head.icmph = *skb->h.icmph; icmp_param.head.icmph.type = ICMP_ECHOREPLY; icmp_param.data.skb = skb; icmp_param.offset = 0; icmp_param.data_len = skb->len; icmp_param.head_len = sizeof(struct icmphdr); rt_icmp_send_reply(&icmp_param, skb); return; } static int rt_icmp_glue_request_bits(const void *p, unsigned char *to, unsigned int offset, unsigned int fraglen) { struct icmp_bxm *icmp_param = (struct icmp_bxm *)p; struct icmphdr *icmph; unsigned long csum; /* TODO: add support for fragmented ICMP packets */ RTNET_ASSERT( offset == 0, rtdm_printk("RTnet: %s() does not support fragmentation.\n", __FUNCTION__); return -1;); csum = rtnet_csum_copy((void *)&icmp_param->head, to, icmp_param->head_len, icmp_param->csum); csum = rtnet_csum_copy(icmp_param->data.buf, to + icmp_param->head_len, fraglen - icmp_param->head_len, csum); icmph = (struct icmphdr *)to; icmph->checksum = csum_fold(csum); return 0; } /*** * common request function */ static int rt_icmp_send_request(u32 daddr, struct icmp_bxm *icmp_param) { struct dest_route rt; unsigned int size; int err; icmp_param->head.icmph.checksum = 0; icmp_param->csum = 0; if ((err = rt_ip_route_output(&rt, daddr, INADDR_ANY)) < 0) return err; /* TODO: add support for fragmented ICMP packets */ size = icmp_param->head_len + icmp_param->data_len; if (size + 20 /* ip header */ > rt.rtdev->get_mtu(rt.rtdev, RT_ICMP_PRIO)) err = -EMSGSIZE; else { rt_socket_reference(icmp_socket); err = rt_ip_build_xmit(icmp_socket, rt_icmp_glue_request_bits, icmp_param, size, &rt, MSG_DONTWAIT); if (err) rt_socket_dereference(icmp_socket); } rtdev_dereference(rt.rtdev); return err; } /*** * rt_icmp_send_echo - sends an echo request to the specified address */ int rt_icmp_send_echo(u32 daddr, u16 id, u16 sequence, size_t msg_size) { struct icmp_bxm icmp_param; unsigned char *pattern_buf; off_t pos; int ret; /* * This is just setup of a ping message, exec time is not critical, so * rtdm_malloc() is ok here. */ pattern_buf = rtdm_malloc(msg_size); if (pattern_buf == NULL) return -ENOMEM; /* first purge any potentially pending ICMP fragments */ rt_ip_frag_invalidate_socket(icmp_socket); icmp_param.head.icmph.type = ICMP_ECHO; icmp_param.head.icmph.code = 0; icmp_param.head.icmph.un.echo.id = id; icmp_param.head.icmph.un.echo.sequence = htons(sequence); icmp_param.offset = 0; if (msg_size >= sizeof(nanosecs_abs_t)) { icmp_param.head_len = sizeof(struct icmphdr) + sizeof(nanosecs_abs_t); icmp_param.data_len = msg_size - sizeof(nanosecs_abs_t); for (pos = 0; pos < icmp_param.data_len; pos++) pattern_buf[pos] = pos & 0xFF; icmp_param.head.timestamp = rtdm_clock_read(); } else { icmp_param.head_len = sizeof(struct icmphdr) + msg_size; icmp_param.data_len = 0; for (pos = 0; pos < msg_size; pos++) pattern_buf[pos] = pos & 0xFF; } icmp_param.data.buf = pattern_buf; ret = rt_icmp_send_request(daddr, &icmp_param); rtdm_free(pattern_buf); return ret; } /*** * rt_icmp_socket */ int rt_icmp_socket(struct rtdm_fd *fd) { /* we don't support user-created ICMP sockets */ return -ENOPROTOOPT; } static struct rt_icmp_control rt_icmp_pointers[NR_ICMP_TYPES + 1] = { /* ECHO REPLY (0) */ { rt_icmp_echo_reply, 0 }, { rt_icmp_discard, 1 }, { rt_icmp_discard, 1 }, /* DEST UNREACH (3) */ { rt_icmp_discard, 1 }, /* SOURCE QUENCH (4) */ { rt_icmp_discard, 1 }, /* REDIRECT (5) */ { rt_icmp_discard, 1 }, { rt_icmp_discard, 1 }, { rt_icmp_discard, 1 }, /* ECHO (8) */ { rt_icmp_echo_request, 0 }, { rt_icmp_discard, 1 }, { rt_icmp_discard, 1 }, /* TIME EXCEEDED (11) */ { rt_icmp_discard, 1 }, /* PARAMETER PROBLEM (12) */ { rt_icmp_discard, 1 }, /* TIMESTAMP (13) */ { rt_icmp_discard, 0 }, /* TIMESTAMP REPLY (14) */ { rt_icmp_discard, 0 }, /* INFO (15) */ { rt_icmp_discard, 0 }, /* INFO REPLY (16) */ { rt_icmp_discard, 0 }, /* ADDR MASK (17) */ { rt_icmp_discard, 0 }, /* ADDR MASK REPLY (18) */ { rt_icmp_discard, 0 } }; /*** * rt_icmp_dest_pool */ struct rtsocket *rt_icmp_dest_socket(struct rtskb *skb) { rt_socket_reference(icmp_socket); return icmp_socket; } /*** * rt_icmp_rcv */ void rt_icmp_rcv(struct rtskb *skb) { struct icmphdr *icmpHdr = skb->h.icmph; unsigned int length = skb->len; /* check header sanity and don't accept fragmented packets */ if ((length < sizeof(struct icmphdr)) || (skb->next != NULL)) { rtdm_printk("RTnet: improper length in icmp packet\n"); goto cleanup; } if (ip_compute_csum((unsigned char *)icmpHdr, length)) { rtdm_printk("RTnet: invalid checksum in icmp packet %d\n", length); goto cleanup; } if (!rtskb_pull(skb, sizeof(struct icmphdr))) { rtdm_printk("RTnet: pull failed %p\n", (skb->sk)); goto cleanup; } if (icmpHdr->type > NR_ICMP_TYPES) { rtdm_printk("RTnet: invalid icmp type\n"); goto cleanup; } /* sane packet, process it */ rt_icmp_pointers[icmpHdr->type].handler(skb); cleanup: kfree_rtskb(skb); } /*** * rt_icmp_rcv_err */ void rt_icmp_rcv_err(struct rtskb *skb) { rtdm_printk("RTnet: rt_icmp_rcv err\n"); } /*** * ICMP-Initialisation */ static struct rtinet_protocol icmp_protocol = { .protocol = IPPROTO_ICMP, .dest_socket = &rt_icmp_dest_socket, .rcv_handler = &rt_icmp_rcv, .err_handler = &rt_icmp_rcv_err, .init_socket = &rt_icmp_socket }; /*** * rt_icmp_init */ void __init rt_icmp_init(void) { int skbs; skbs = rt_bare_socket_init(icmp_fd, IPPROTO_ICMP, RT_ICMP_PRIO, ICMP_REPLY_POOL_SIZE); BUG_ON(skbs < 0); if (skbs < ICMP_REPLY_POOL_SIZE) printk("RTnet: allocated only %d icmp rtskbs\n", skbs); icmp_socket->prot.inet.tos = 0; icmp_fd->refs = 1; rt_inet_add_protocol(&icmp_protocol); } /*** * rt_icmp_release */ void rt_icmp_release(void) { rt_icmp_cleanup_echo_requests(); rt_inet_del_protocol(&icmp_protocol); rt_bare_socket_cleanup(icmp_socket); }