| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 2010,2011 Free Software Foundation, Inc. |
| * |
| * GRUB 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 3 of the License, or |
| * (at your option) any later version. |
| * |
| * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <grub/net/ip.h> |
| #include <grub/misc.h> |
| #include <grub/net/arp.h> |
| #include <grub/net/udp.h> |
| #include <grub/net/ethernet.h> |
| #include <grub/net.h> |
| #include <grub/net/netbuff.h> |
| #include <grub/mm.h> |
| #include <grub/priority_queue.h> |
| #include <grub/time.h> |
| |
| struct iphdr { |
| grub_uint8_t verhdrlen; |
| grub_uint8_t service; |
| grub_uint16_t len; |
| grub_uint16_t ident; |
| grub_uint16_t frags; |
| grub_uint8_t ttl; |
| grub_uint8_t protocol; |
| grub_uint16_t chksum; |
| grub_uint32_t src; |
| grub_uint32_t dest; |
| } GRUB_PACKED ; |
| |
| enum |
| { |
| DONT_FRAGMENT = 0x4000, |
| MORE_FRAGMENTS = 0x2000, |
| OFFSET_MASK = 0x1fff |
| }; |
| |
| typedef grub_uint64_t ip6addr[2]; |
| |
| struct ip6hdr { |
| grub_uint32_t version_class_flow; |
| grub_uint16_t len; |
| grub_uint8_t protocol; |
| grub_uint8_t ttl; |
| ip6addr src; |
| ip6addr dest; |
| } GRUB_PACKED ; |
| |
| static int |
| cmp (const void *a__, const void *b__) |
| { |
| struct grub_net_buff *a_ = *(struct grub_net_buff **) a__; |
| struct grub_net_buff *b_ = *(struct grub_net_buff **) b__; |
| struct iphdr *a = (struct iphdr *) a_->data; |
| struct iphdr *b = (struct iphdr *) b_->data; |
| /* We want the first elements to be on top. */ |
| if ((grub_be_to_cpu16 (a->frags) & OFFSET_MASK) |
| < (grub_be_to_cpu16 (b->frags) & OFFSET_MASK)) |
| return +1; |
| if ((grub_be_to_cpu16 (a->frags) & OFFSET_MASK) |
| > (grub_be_to_cpu16 (b->frags) & OFFSET_MASK)) |
| return -1; |
| return 0; |
| } |
| |
| struct reassemble |
| { |
| struct reassemble *next; |
| grub_uint32_t source; |
| grub_uint32_t dest; |
| grub_uint16_t id; |
| grub_uint8_t proto; |
| grub_uint64_t last_time; |
| grub_priority_queue_t pq; |
| struct grub_net_buff *asm_netbuff; |
| grub_size_t total_len; |
| grub_size_t cur_ptr; |
| grub_uint8_t ttl; |
| }; |
| |
| static struct reassemble *reassembles; |
| |
| grub_uint16_t |
| grub_net_ip_chksum (void *ipv, grub_size_t len) |
| { |
| grub_uint16_t *ip = (grub_uint16_t *) ipv; |
| grub_uint32_t sum = 0; |
| |
| for (; len >= 2; len -= 2) |
| { |
| sum += grub_be_to_cpu16 (grub_get_unaligned16 (ip++)); |
| if (sum > 0xFFFF) |
| sum -= 0xFFFF; |
| } |
| if (len) |
| { |
| sum += *((grub_uint8_t *) ip) << 8; |
| if (sum > 0xFFFF) |
| sum -= 0xFFFF; |
| } |
| |
| if (sum >= 0xFFFF) |
| sum -= 0xFFFF; |
| |
| return grub_cpu_to_be16 ((~sum) & 0x0000FFFF); |
| } |
| |
| static int id = 0x2400; |
| |
| static grub_err_t |
| send_fragmented (struct grub_net_network_level_interface * inf, |
| const grub_net_network_level_address_t * target, |
| struct grub_net_buff * nb, |
| grub_net_ip_protocol_t proto, |
| grub_net_link_level_address_t ll_target_addr) |
| { |
| grub_size_t off = 0; |
| grub_size_t fraglen; |
| grub_err_t err; |
| |
| fraglen = (inf->card->mtu - sizeof (struct iphdr)) & ~7; |
| id++; |
| |
| while (nb->tail - nb->data) |
| { |
| grub_size_t len = fraglen; |
| struct grub_net_buff *nb2; |
| struct iphdr *iph; |
| |
| if ((grub_ssize_t) len > nb->tail - nb->data) |
| len = nb->tail - nb->data; |
| nb2 = grub_netbuff_alloc (fraglen + sizeof (struct iphdr) |
| + GRUB_NET_MAX_LINK_HEADER_SIZE); |
| if (!nb2) |
| return grub_errno; |
| err = grub_netbuff_reserve (nb2, GRUB_NET_MAX_LINK_HEADER_SIZE); |
| if (err) |
| return err; |
| err = grub_netbuff_put (nb2, sizeof (struct iphdr)); |
| if (err) |
| return err; |
| |
| iph = (struct iphdr *) nb2->data; |
| iph->verhdrlen = ((4 << 4) | 5); |
| iph->service = 0; |
| iph->len = grub_cpu_to_be16 (len + sizeof (struct iphdr)); |
| iph->ident = grub_cpu_to_be16 (id); |
| iph->frags = grub_cpu_to_be16 (off | (((grub_ssize_t) len |
| == nb->tail - nb->data) |
| ? 0 : MORE_FRAGMENTS)); |
| iph->ttl = 0xff; |
| iph->protocol = proto; |
| iph->src = inf->address.ipv4; |
| iph->dest = target->ipv4; |
| off += len / 8; |
| |
| iph->chksum = 0; |
| iph->chksum = grub_net_ip_chksum ((void *) nb2->data, sizeof (*iph)); |
| err = grub_netbuff_put (nb2, len); |
| if (err) |
| return err; |
| grub_memcpy (iph + 1, nb->data, len); |
| err = grub_netbuff_pull (nb, len); |
| if (err) |
| return err; |
| err = send_ethernet_packet (inf, nb2, ll_target_addr, |
| GRUB_NET_ETHERTYPE_IP); |
| if (err) |
| return err; |
| } |
| return GRUB_ERR_NONE; |
| } |
| |
| static grub_err_t |
| grub_net_send_ip4_packet (struct grub_net_network_level_interface *inf, |
| const grub_net_network_level_address_t *target, |
| const grub_net_link_level_address_t *ll_target_addr, |
| struct grub_net_buff *nb, |
| grub_net_ip_protocol_t proto) |
| { |
| struct iphdr *iph; |
| grub_err_t err; |
| |
| COMPILE_TIME_ASSERT (GRUB_NET_OUR_IPV4_HEADER_SIZE == sizeof (*iph)); |
| |
| if (nb->tail - nb->data + sizeof (struct iphdr) > inf->card->mtu) |
| return send_fragmented (inf, target, nb, proto, *ll_target_addr); |
| |
| err = grub_netbuff_push (nb, sizeof (*iph)); |
| if (err) |
| return err; |
| |
| iph = (struct iphdr *) nb->data; |
| iph->verhdrlen = ((4 << 4) | 5); |
| iph->service = 0; |
| iph->len = grub_cpu_to_be16 (nb->tail - nb->data); |
| iph->ident = grub_cpu_to_be16 (++id); |
| iph->frags = 0; |
| iph->ttl = 0xff; |
| iph->protocol = proto; |
| iph->src = inf->address.ipv4; |
| iph->dest = target->ipv4; |
| |
| iph->chksum = 0; |
| iph->chksum = grub_net_ip_chksum ((void *) nb->data, sizeof (*iph)); |
| |
| return send_ethernet_packet (inf, nb, *ll_target_addr, |
| GRUB_NET_ETHERTYPE_IP); |
| } |
| |
| static grub_err_t |
| handle_dgram (struct grub_net_buff *nb, |
| struct grub_net_card *card, |
| const grub_net_link_level_address_t *source_hwaddress, |
| const grub_net_link_level_address_t *hwaddress, |
| grub_net_ip_protocol_t proto, |
| const grub_net_network_level_address_t *source, |
| const grub_net_network_level_address_t *dest, |
| grub_uint16_t *vlantag, |
| grub_uint8_t ttl) |
| { |
| struct grub_net_network_level_interface *inf = NULL; |
| grub_err_t err; |
| int multicast = 0; |
| |
| /* DHCP needs special treatment since we don't know IP yet. */ |
| { |
| struct udphdr *udph; |
| udph = (struct udphdr *) nb->data; |
| if (proto == GRUB_NET_IP_UDP && grub_be_to_cpu16 (udph->dst) == 68) |
| { |
| const struct grub_net_bootp_packet *bootp; |
| if (udph->chksum) |
| { |
| grub_uint16_t chk, expected; |
| chk = udph->chksum; |
| udph->chksum = 0; |
| expected = grub_net_ip_transport_checksum (nb, |
| GRUB_NET_IP_UDP, |
| source, |
| dest); |
| if (expected != chk) |
| { |
| grub_dprintf ("net", "Invalid UDP checksum. " |
| "Expected %x, got %x\n", |
| grub_be_to_cpu16 (expected), |
| grub_be_to_cpu16 (chk)); |
| grub_netbuff_free (nb); |
| return GRUB_ERR_NONE; |
| } |
| udph->chksum = chk; |
| } |
| |
| err = grub_netbuff_pull (nb, sizeof (*udph)); |
| if (err) |
| { |
| grub_netbuff_free (nb); |
| return err; |
| } |
| |
| bootp = (const struct grub_net_bootp_packet *) nb->data; |
| |
| FOR_NET_NETWORK_LEVEL_INTERFACES (inf) |
| if (inf->card == card |
| && inf->address.type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV |
| && inf->hwaddress.type == GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET |
| && grub_memcmp (inf->hwaddress.mac, &bootp->mac_addr, |
| sizeof (inf->hwaddress.mac)) == 0) |
| { |
| grub_net_process_dhcp (nb, inf->card); |
| grub_netbuff_free (nb); |
| return GRUB_ERR_NONE; |
| } |
| grub_netbuff_free (nb); |
| return GRUB_ERR_NONE; |
| } |
| } |
| |
| FOR_NET_NETWORK_LEVEL_INTERFACES (inf) |
| { |
| if (inf->card == card |
| && grub_net_addr_cmp (&inf->address, dest) == 0 |
| && grub_net_hwaddr_cmp (&inf->hwaddress, hwaddress) == 0) |
| break; |
| |
| /* Verify vlantag id */ |
| if (inf->card == card && inf->vlantag != *vlantag) |
| { |
| grub_dprintf ("net", "invalid vlantag! %x != %x\n", |
| inf->vlantag, *vlantag); |
| break; |
| } |
| |
| /* Solicited node multicast. */ |
| if (inf->card == card |
| && inf->address.type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6 |
| && dest->type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6 |
| && dest->ipv6[0] == grub_be_to_cpu64_compile_time (0xff02ULL << 48) |
| && dest->ipv6[1] == (grub_be_to_cpu64_compile_time (0x01ff000000ULL) |
| | (inf->address.ipv6[1] |
| & grub_be_to_cpu64_compile_time (0xffffff))) |
| && hwaddress->type == GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET |
| && hwaddress->mac[0] == 0x33 && hwaddress->mac[1] == 0x33 |
| && hwaddress->mac[2] == 0xff |
| && hwaddress->mac[3] == ((grub_be_to_cpu64 (inf->address.ipv6[1]) |
| >> 16) & 0xff) |
| && hwaddress->mac[4] == ((grub_be_to_cpu64 (inf->address.ipv6[1]) |
| >> 8) & 0xff) |
| && hwaddress->mac[5] == ((grub_be_to_cpu64 (inf->address.ipv6[1]) |
| >> 0) & 0xff)) |
| { |
| multicast = 1; |
| break; |
| } |
| } |
| |
| if (!inf && !(dest->type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6 |
| && dest->ipv6[0] == grub_be_to_cpu64_compile_time (0xff02ULL |
| << 48) |
| && dest->ipv6[1] == grub_be_to_cpu64_compile_time (1))) |
| { |
| grub_netbuff_free (nb); |
| return GRUB_ERR_NONE; |
| } |
| if (multicast) |
| inf = NULL; |
| |
| switch (proto) |
| { |
| case GRUB_NET_IP_UDP: |
| return grub_net_recv_udp_packet (nb, inf, source); |
| case GRUB_NET_IP_TCP: |
| return grub_net_recv_tcp_packet (nb, inf, source); |
| case GRUB_NET_IP_ICMP: |
| return grub_net_recv_icmp_packet (nb, inf, source_hwaddress, source); |
| case GRUB_NET_IP_ICMPV6: |
| return grub_net_recv_icmp6_packet (nb, card, inf, source_hwaddress, |
| source, dest, ttl); |
| default: |
| grub_netbuff_free (nb); |
| break; |
| } |
| return GRUB_ERR_NONE; |
| } |
| |
| static void |
| free_rsm (struct reassemble *rsm) |
| { |
| struct grub_net_buff **nb; |
| while ((nb = grub_priority_queue_top (rsm->pq))) |
| { |
| grub_netbuff_free (*nb); |
| grub_priority_queue_pop (rsm->pq); |
| } |
| grub_netbuff_free (rsm->asm_netbuff); |
| grub_priority_queue_destroy (rsm->pq); |
| grub_free (rsm); |
| } |
| |
| static void |
| free_old_fragments (void) |
| { |
| struct reassemble *rsm, **prev; |
| grub_uint64_t limit_time = grub_get_time_ms (); |
| |
| limit_time = (limit_time > 90000) ? limit_time - 90000 : 0; |
| |
| for (prev = &reassembles, rsm = *prev; rsm; rsm = *prev) |
| if (rsm->last_time < limit_time) |
| { |
| *prev = rsm->next; |
| free_rsm (rsm); |
| } |
| else |
| { |
| prev = &rsm->next; |
| } |
| } |
| |
| static grub_err_t |
| grub_net_recv_ip4_packets (struct grub_net_buff *nb, |
| struct grub_net_card *card, |
| const grub_net_link_level_address_t *hwaddress, |
| const grub_net_link_level_address_t *src_hwaddress, |
| grub_uint16_t *vlantag) |
| { |
| struct iphdr *iph = (struct iphdr *) nb->data; |
| grub_err_t err; |
| struct reassemble *rsm, **prev; |
| |
| if ((iph->verhdrlen >> 4) != 4) |
| { |
| grub_dprintf ("net", "Bad IP version: %d\n", (iph->verhdrlen >> 4)); |
| grub_netbuff_free (nb); |
| return GRUB_ERR_NONE; |
| } |
| |
| if ((iph->verhdrlen & 0xf) < 5) |
| { |
| grub_dprintf ("net", "IP header too short: %d\n", |
| (iph->verhdrlen & 0xf)); |
| grub_netbuff_free (nb); |
| return GRUB_ERR_NONE; |
| } |
| |
| if (nb->tail - nb->data < (grub_ssize_t) ((iph->verhdrlen & 0xf) |
| * sizeof (grub_uint32_t))) |
| { |
| grub_dprintf ("net", "IP packet too short: %" PRIdGRUB_SSIZE "\n", |
| (grub_ssize_t) (nb->tail - nb->data)); |
| grub_netbuff_free (nb); |
| return GRUB_ERR_NONE; |
| } |
| |
| /* Check size. */ |
| { |
| grub_size_t expected_size = grub_be_to_cpu16 (iph->len); |
| grub_size_t actual_size = (nb->tail - nb->data); |
| if (actual_size > expected_size) |
| { |
| err = grub_netbuff_unput (nb, actual_size - expected_size); |
| if (err) |
| { |
| grub_netbuff_free (nb); |
| return err; |
| } |
| } |
| if (actual_size < expected_size) |
| { |
| grub_dprintf ("net", "Cut IP packet actual: %" PRIuGRUB_SIZE |
| ", expected %" PRIuGRUB_SIZE "\n", actual_size, |
| expected_size); |
| grub_netbuff_free (nb); |
| return GRUB_ERR_NONE; |
| } |
| } |
| |
| /* Unfragmented packet. Good. */ |
| if (((grub_be_to_cpu16 (iph->frags) & MORE_FRAGMENTS) == 0) |
| && (grub_be_to_cpu16 (iph->frags) & OFFSET_MASK) == 0) |
| { |
| grub_net_network_level_address_t source; |
| grub_net_network_level_address_t dest; |
| |
| err = grub_netbuff_pull (nb, ((iph->verhdrlen & 0xf) |
| * sizeof (grub_uint32_t))); |
| if (err) |
| { |
| grub_netbuff_free (nb); |
| return err; |
| } |
| |
| source.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; |
| source.ipv4 = iph->src; |
| |
| dest.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; |
| dest.ipv4 = iph->dest; |
| |
| return handle_dgram (nb, card, src_hwaddress, hwaddress, iph->protocol, |
| &source, &dest, vlantag, iph->ttl); |
| } |
| |
| for (prev = &reassembles, rsm = *prev; rsm; prev = &rsm->next, rsm = *prev) |
| if (rsm->source == iph->src && rsm->dest == iph->dest |
| && rsm->id == iph->ident && rsm->proto == iph->protocol) |
| break; |
| if (!rsm) |
| { |
| rsm = grub_malloc (sizeof (*rsm)); |
| if (!rsm) |
| return grub_errno; |
| rsm->source = iph->src; |
| rsm->dest = iph->dest; |
| rsm->id = iph->ident; |
| rsm->proto = iph->protocol; |
| rsm->next = reassembles; |
| reassembles = rsm; |
| prev = &reassembles; |
| rsm->pq = grub_priority_queue_new (sizeof (struct grub_net_buff **), cmp); |
| if (!rsm->pq) |
| { |
| grub_free (rsm); |
| return grub_errno; |
| } |
| rsm->asm_netbuff = 0; |
| rsm->total_len = 0; |
| rsm->cur_ptr = 0; |
| rsm->ttl = 0xff; |
| } |
| if (rsm->ttl > iph->ttl) |
| rsm->ttl = iph->ttl; |
| rsm->last_time = grub_get_time_ms (); |
| free_old_fragments (); |
| |
| err = grub_priority_queue_push (rsm->pq, &nb); |
| if (err) |
| return err; |
| |
| if (!(grub_be_to_cpu16 (iph->frags) & MORE_FRAGMENTS)) |
| { |
| rsm->total_len = (8 * (grub_be_to_cpu16 (iph->frags) & OFFSET_MASK) |
| + (nb->tail - nb->data)); |
| rsm->total_len -= ((iph->verhdrlen & 0xf) * sizeof (grub_uint32_t)); |
| rsm->asm_netbuff = grub_netbuff_alloc (rsm->total_len); |
| if (!rsm->asm_netbuff) |
| { |
| *prev = rsm->next; |
| free_rsm (rsm); |
| return grub_errno; |
| } |
| } |
| if (!rsm->asm_netbuff) |
| return GRUB_ERR_NONE; |
| |
| while (1) |
| { |
| struct grub_net_buff **nb_top_p, *nb_top; |
| grub_size_t copy; |
| grub_size_t res_len; |
| struct grub_net_buff *ret; |
| grub_net_ip_protocol_t proto; |
| grub_uint32_t src; |
| grub_uint32_t dst; |
| grub_net_network_level_address_t source; |
| grub_net_network_level_address_t dest; |
| grub_uint8_t ttl; |
| |
| nb_top_p = grub_priority_queue_top (rsm->pq); |
| if (!nb_top_p) |
| return GRUB_ERR_NONE; |
| nb_top = *nb_top_p; |
| grub_priority_queue_pop (rsm->pq); |
| iph = (struct iphdr *) nb_top->data; |
| err = grub_netbuff_pull (nb_top, ((iph->verhdrlen & 0xf) |
| * sizeof (grub_uint32_t))); |
| if (err) |
| { |
| grub_netbuff_free (nb_top); |
| return err; |
| } |
| if (rsm->cur_ptr < (grub_size_t) 8 * (grub_be_to_cpu16 (iph->frags) |
| & OFFSET_MASK)) |
| { |
| grub_netbuff_free (nb_top); |
| return GRUB_ERR_NONE; |
| } |
| |
| rsm->cur_ptr = (8 * (grub_be_to_cpu16 (iph->frags) & OFFSET_MASK) |
| + (nb_top->tail - nb_top->head)); |
| if ((grub_size_t) 8 * (grub_be_to_cpu16 (iph->frags) & OFFSET_MASK) |
| >= rsm->total_len) |
| { |
| grub_netbuff_free (nb_top); |
| continue; |
| } |
| copy = nb_top->tail - nb_top->data; |
| if (rsm->total_len - 8 * (grub_be_to_cpu16 (iph->frags) & OFFSET_MASK) |
| < copy) |
| copy = rsm->total_len - 8 * (grub_be_to_cpu16 (iph->frags) |
| & OFFSET_MASK); |
| grub_memcpy (&rsm->asm_netbuff->data[8 * (grub_be_to_cpu16 (iph->frags) |
| & OFFSET_MASK)], |
| nb_top->data, copy); |
| |
| if ((grub_be_to_cpu16 (iph->frags) & MORE_FRAGMENTS)) |
| { |
| grub_netbuff_free (nb_top); |
| continue; |
| } |
| grub_netbuff_free (nb_top); |
| |
| ret = rsm->asm_netbuff; |
| proto = rsm->proto; |
| src = rsm->source; |
| dst = rsm->dest; |
| ttl = rsm->ttl; |
| |
| rsm->asm_netbuff = 0; |
| res_len = rsm->total_len; |
| *prev = rsm->next; |
| free_rsm (rsm); |
| |
| if (grub_netbuff_put (ret, res_len)) |
| { |
| grub_netbuff_free (ret); |
| return GRUB_ERR_NONE; |
| } |
| |
| source.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; |
| source.ipv4 = src; |
| |
| dest.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; |
| dest.ipv4 = dst; |
| |
| return handle_dgram (ret, card, src_hwaddress, |
| hwaddress, proto, &source, &dest, vlantag, |
| ttl); |
| } |
| } |
| |
| static grub_err_t |
| grub_net_send_ip6_packet (struct grub_net_network_level_interface *inf, |
| const grub_net_network_level_address_t *target, |
| const grub_net_link_level_address_t *ll_target_addr, |
| struct grub_net_buff *nb, |
| grub_net_ip_protocol_t proto) |
| { |
| struct ip6hdr *iph; |
| grub_err_t err; |
| |
| COMPILE_TIME_ASSERT (GRUB_NET_OUR_IPV6_HEADER_SIZE == sizeof (*iph)); |
| |
| if (nb->tail - nb->data + sizeof (struct iphdr) > inf->card->mtu) |
| return grub_error (GRUB_ERR_NET_PACKET_TOO_BIG, "packet too big"); |
| |
| err = grub_netbuff_push (nb, sizeof (*iph)); |
| if (err) |
| return err; |
| |
| iph = (struct ip6hdr *) nb->data; |
| iph->version_class_flow = grub_cpu_to_be32_compile_time ((6 << 28)); |
| iph->len = grub_cpu_to_be16 (nb->tail - nb->data - sizeof (*iph)); |
| iph->protocol = proto; |
| iph->ttl = 0xff; |
| grub_memcpy (&iph->src, inf->address.ipv6, sizeof (iph->src)); |
| grub_memcpy (&iph->dest, target->ipv6, sizeof (iph->dest)); |
| |
| return send_ethernet_packet (inf, nb, *ll_target_addr, |
| GRUB_NET_ETHERTYPE_IP6); |
| } |
| |
| grub_err_t |
| grub_net_send_ip_packet (struct grub_net_network_level_interface *inf, |
| const grub_net_network_level_address_t *target, |
| const grub_net_link_level_address_t *ll_target_addr, |
| struct grub_net_buff *nb, |
| grub_net_ip_protocol_t proto) |
| { |
| switch (target->type) |
| { |
| case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4: |
| return grub_net_send_ip4_packet (inf, target, ll_target_addr, nb, proto); |
| case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6: |
| return grub_net_send_ip6_packet (inf, target, ll_target_addr, nb, proto); |
| default: |
| return grub_error (GRUB_ERR_BUG, "not an IP"); |
| } |
| } |
| |
| static grub_err_t |
| grub_net_recv_ip6_packets (struct grub_net_buff *nb, |
| struct grub_net_card *card, |
| const grub_net_link_level_address_t *hwaddress, |
| const grub_net_link_level_address_t *src_hwaddress, |
| grub_uint16_t *vlantag) |
| { |
| struct ip6hdr *iph = (struct ip6hdr *) nb->data; |
| grub_err_t err; |
| grub_net_network_level_address_t source; |
| grub_net_network_level_address_t dest; |
| |
| if (nb->tail - nb->data < (grub_ssize_t) sizeof (*iph)) |
| { |
| grub_dprintf ("net", "IP packet too short: %" PRIdGRUB_SSIZE "\n", |
| (grub_ssize_t) (nb->tail - nb->data)); |
| grub_netbuff_free (nb); |
| return GRUB_ERR_NONE; |
| } |
| |
| err = grub_netbuff_pull (nb, sizeof (*iph)); |
| if (err) |
| { |
| grub_netbuff_free (nb); |
| return err; |
| } |
| |
| /* Check size. */ |
| { |
| grub_size_t expected_size = grub_be_to_cpu16 (iph->len); |
| grub_size_t actual_size = (nb->tail - nb->data); |
| if (actual_size > expected_size) |
| { |
| err = grub_netbuff_unput (nb, actual_size - expected_size); |
| if (err) |
| { |
| grub_netbuff_free (nb); |
| return err; |
| } |
| } |
| if (actual_size < expected_size) |
| { |
| grub_dprintf ("net", "Cut IP packet actual: %" PRIuGRUB_SIZE |
| ", expected %" PRIuGRUB_SIZE "\n", actual_size, |
| expected_size); |
| grub_netbuff_free (nb); |
| return GRUB_ERR_NONE; |
| } |
| } |
| |
| source.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6; |
| dest.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6; |
| grub_memcpy (source.ipv6, &iph->src, sizeof (source.ipv6)); |
| grub_memcpy (dest.ipv6, &iph->dest, sizeof (dest.ipv6)); |
| |
| return handle_dgram (nb, card, src_hwaddress, hwaddress, iph->protocol, |
| &source, &dest, vlantag, iph->ttl); |
| } |
| |
| grub_err_t |
| grub_net_recv_ip_packets (struct grub_net_buff *nb, |
| struct grub_net_card *card, |
| const grub_net_link_level_address_t *hwaddress, |
| const grub_net_link_level_address_t *src_hwaddress, |
| grub_uint16_t *vlantag) |
| { |
| struct iphdr *iph = (struct iphdr *) nb->data; |
| |
| if ((iph->verhdrlen >> 4) == 4) |
| return grub_net_recv_ip4_packets (nb, card, hwaddress, src_hwaddress, |
| vlantag); |
| if ((iph->verhdrlen >> 4) == 6) |
| return grub_net_recv_ip6_packets (nb, card, hwaddress, src_hwaddress, |
| vlantag); |
| grub_dprintf ("net", "Bad IP version: %d\n", (iph->verhdrlen >> 4)); |
| grub_netbuff_free (nb); |
| return GRUB_ERR_NONE; |
| } |