123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538 |
- /*
- * QEMU network structures definitions and helper functions
- *
- * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com)
- *
- * Developed by Daynix Computing LTD (http://www.daynix.com)
- *
- * Authors:
- * Dmitry Fleytman <dmitry@daynix.com>
- * Tamir Shomer <tamirs@daynix.com>
- * Yan Vugenfirer <yan@daynix.com>
- *
- * This work is licensed under the terms of the GNU GPL, version 2 or later.
- * See the COPYING file in the top-level directory.
- *
- */
- #include "qemu/osdep.h"
- #include "qemu/log.h"
- #include "net/eth.h"
- #include "net/checksum.h"
- #include "net/tap.h"
- void eth_setup_vlan_headers(struct eth_header *ehdr, size_t *ehdr_size,
- uint16_t vlan_tag, uint16_t vlan_ethtype)
- {
- struct vlan_header *vhdr = PKT_GET_VLAN_HDR(ehdr);
- memmove(vhdr + 1, vhdr, *ehdr_size - ETH_HLEN);
- vhdr->h_tci = cpu_to_be16(vlan_tag);
- vhdr->h_proto = ehdr->h_proto;
- ehdr->h_proto = cpu_to_be16(vlan_ethtype);
- *ehdr_size += sizeof(*vhdr);
- }
- uint8_t
- eth_get_gso_type(uint16_t l3_proto, uint8_t *l3_hdr, uint8_t l4proto)
- {
- uint8_t ecn_state = 0;
- if (l3_proto == ETH_P_IP) {
- struct ip_header *iphdr = (struct ip_header *) l3_hdr;
- if (IP_HEADER_VERSION(iphdr) == IP_HEADER_VERSION_4) {
- if (IPTOS_ECN(iphdr->ip_tos) == IPTOS_ECN_CE) {
- ecn_state = VIRTIO_NET_HDR_GSO_ECN;
- }
- if (l4proto == IP_PROTO_TCP) {
- return VIRTIO_NET_HDR_GSO_TCPV4 | ecn_state;
- } else if (l4proto == IP_PROTO_UDP) {
- return VIRTIO_NET_HDR_GSO_UDP | ecn_state;
- }
- }
- } else if (l3_proto == ETH_P_IPV6) {
- struct ip6_header *ip6hdr = (struct ip6_header *) l3_hdr;
- if (IP6_ECN(ip6hdr->ip6_ecn_acc) == IP6_ECN_CE) {
- ecn_state = VIRTIO_NET_HDR_GSO_ECN;
- }
- if (l4proto == IP_PROTO_TCP) {
- return VIRTIO_NET_HDR_GSO_TCPV6 | ecn_state;
- }
- }
- qemu_log_mask(LOG_UNIMP, "%s: probably not GSO frame, "
- "unknown L3 protocol: 0x%04"PRIx16"\n", __func__, l3_proto);
- return VIRTIO_NET_HDR_GSO_NONE | ecn_state;
- }
- uint16_t
- eth_get_l3_proto(const struct iovec *l2hdr_iov, int iovcnt, size_t l2hdr_len)
- {
- uint16_t proto;
- size_t copied;
- size_t size = iov_size(l2hdr_iov, iovcnt);
- size_t proto_offset = l2hdr_len - sizeof(proto);
- if (size < proto_offset) {
- return ETH_P_UNKNOWN;
- }
- copied = iov_to_buf(l2hdr_iov, iovcnt, proto_offset,
- &proto, sizeof(proto));
- return (copied == sizeof(proto)) ? be16_to_cpu(proto) : ETH_P_UNKNOWN;
- }
- static bool
- _eth_copy_chunk(size_t input_size,
- const struct iovec *iov, int iovcnt,
- size_t offset, size_t length,
- void *buffer)
- {
- size_t copied;
- if (input_size < offset) {
- return false;
- }
- copied = iov_to_buf(iov, iovcnt, offset, buffer, length);
- if (copied < length) {
- return false;
- }
- return true;
- }
- static bool
- _eth_tcp_has_data(bool is_ip4,
- const struct ip_header *ip4_hdr,
- const struct ip6_header *ip6_hdr,
- size_t full_ip6hdr_len,
- const struct tcp_header *tcp)
- {
- uint32_t l4len;
- if (is_ip4) {
- l4len = be16_to_cpu(ip4_hdr->ip_len) - IP_HDR_GET_LEN(ip4_hdr);
- } else {
- size_t opts_len = full_ip6hdr_len - sizeof(struct ip6_header);
- l4len = be16_to_cpu(ip6_hdr->ip6_ctlun.ip6_un1.ip6_un1_plen) - opts_len;
- }
- return l4len > TCP_HEADER_DATA_OFFSET(tcp);
- }
- void eth_get_protocols(const struct iovec *iov, size_t iovcnt, size_t iovoff,
- bool *hasip4, bool *hasip6,
- size_t *l3hdr_off,
- size_t *l4hdr_off,
- size_t *l5hdr_off,
- eth_ip6_hdr_info *ip6hdr_info,
- eth_ip4_hdr_info *ip4hdr_info,
- eth_l4_hdr_info *l4hdr_info)
- {
- int proto;
- bool fragment = false;
- size_t input_size = iov_size(iov, iovcnt);
- size_t copied;
- uint8_t ip_p;
- *hasip4 = *hasip6 = false;
- *l3hdr_off = iovoff + eth_get_l2_hdr_length_iov(iov, iovcnt, iovoff);
- l4hdr_info->proto = ETH_L4_HDR_PROTO_INVALID;
- proto = eth_get_l3_proto(iov, iovcnt, *l3hdr_off);
- if (proto == ETH_P_IP) {
- struct ip_header *iphdr = &ip4hdr_info->ip4_hdr;
- if (input_size < *l3hdr_off) {
- return;
- }
- copied = iov_to_buf(iov, iovcnt, *l3hdr_off, iphdr, sizeof(*iphdr));
- if (copied < sizeof(*iphdr) ||
- IP_HEADER_VERSION(iphdr) != IP_HEADER_VERSION_4) {
- return;
- }
- *hasip4 = true;
- ip_p = iphdr->ip_p;
- ip4hdr_info->fragment = IP4_IS_FRAGMENT(iphdr);
- *l4hdr_off = *l3hdr_off + IP_HDR_GET_LEN(iphdr);
- fragment = ip4hdr_info->fragment;
- } else if (proto == ETH_P_IPV6) {
- if (!eth_parse_ipv6_hdr(iov, iovcnt, *l3hdr_off, ip6hdr_info)) {
- return;
- }
- *hasip6 = true;
- ip_p = ip6hdr_info->l4proto;
- *l4hdr_off = *l3hdr_off + ip6hdr_info->full_hdr_len;
- fragment = ip6hdr_info->fragment;
- } else {
- return;
- }
- if (fragment) {
- return;
- }
- switch (ip_p) {
- case IP_PROTO_TCP:
- if (_eth_copy_chunk(input_size,
- iov, iovcnt,
- *l4hdr_off, sizeof(l4hdr_info->hdr.tcp),
- &l4hdr_info->hdr.tcp)) {
- l4hdr_info->proto = ETH_L4_HDR_PROTO_TCP;
- *l5hdr_off = *l4hdr_off +
- TCP_HEADER_DATA_OFFSET(&l4hdr_info->hdr.tcp);
- l4hdr_info->has_tcp_data =
- _eth_tcp_has_data(proto == ETH_P_IP,
- &ip4hdr_info->ip4_hdr,
- &ip6hdr_info->ip6_hdr,
- *l4hdr_off - *l3hdr_off,
- &l4hdr_info->hdr.tcp);
- }
- break;
- case IP_PROTO_UDP:
- if (_eth_copy_chunk(input_size,
- iov, iovcnt,
- *l4hdr_off, sizeof(l4hdr_info->hdr.udp),
- &l4hdr_info->hdr.udp)) {
- l4hdr_info->proto = ETH_L4_HDR_PROTO_UDP;
- *l5hdr_off = *l4hdr_off + sizeof(l4hdr_info->hdr.udp);
- }
- break;
- case IP_PROTO_SCTP:
- l4hdr_info->proto = ETH_L4_HDR_PROTO_SCTP;
- break;
- }
- }
- size_t
- eth_strip_vlan(const struct iovec *iov, int iovcnt, size_t iovoff,
- void *new_ehdr_buf,
- uint16_t *payload_offset, uint16_t *tci)
- {
- struct vlan_header vlan_hdr;
- struct eth_header *new_ehdr = new_ehdr_buf;
- size_t copied = iov_to_buf(iov, iovcnt, iovoff,
- new_ehdr, sizeof(*new_ehdr));
- if (copied < sizeof(*new_ehdr)) {
- return 0;
- }
- switch (be16_to_cpu(new_ehdr->h_proto)) {
- case ETH_P_VLAN:
- case ETH_P_DVLAN:
- copied = iov_to_buf(iov, iovcnt, iovoff + sizeof(*new_ehdr),
- &vlan_hdr, sizeof(vlan_hdr));
- if (copied < sizeof(vlan_hdr)) {
- return 0;
- }
- new_ehdr->h_proto = vlan_hdr.h_proto;
- *tci = be16_to_cpu(vlan_hdr.h_tci);
- *payload_offset = iovoff + sizeof(*new_ehdr) + sizeof(vlan_hdr);
- if (be16_to_cpu(new_ehdr->h_proto) == ETH_P_VLAN) {
- copied = iov_to_buf(iov, iovcnt, *payload_offset,
- PKT_GET_VLAN_HDR(new_ehdr), sizeof(vlan_hdr));
- if (copied < sizeof(vlan_hdr)) {
- return 0;
- }
- *payload_offset += sizeof(vlan_hdr);
- return sizeof(struct eth_header) + sizeof(struct vlan_header);
- } else {
- return sizeof(struct eth_header);
- }
- default:
- return 0;
- }
- }
- size_t
- eth_strip_vlan_ex(const struct iovec *iov, int iovcnt, size_t iovoff, int index,
- uint16_t vet, uint16_t vet_ext, void *new_ehdr_buf,
- uint16_t *payload_offset, uint16_t *tci)
- {
- struct vlan_header vlan_hdr;
- uint16_t *new_ehdr_proto;
- size_t new_ehdr_size;
- size_t copied;
- switch (index) {
- case 0:
- new_ehdr_proto = &PKT_GET_ETH_HDR(new_ehdr_buf)->h_proto;
- new_ehdr_size = sizeof(struct eth_header);
- copied = iov_to_buf(iov, iovcnt, iovoff, new_ehdr_buf, new_ehdr_size);
- break;
- case 1:
- new_ehdr_proto = &PKT_GET_VLAN_HDR(new_ehdr_buf)->h_proto;
- new_ehdr_size = sizeof(struct eth_header) + sizeof(struct vlan_header);
- copied = iov_to_buf(iov, iovcnt, iovoff, new_ehdr_buf, new_ehdr_size);
- if (be16_to_cpu(PKT_GET_ETH_HDR(new_ehdr_buf)->h_proto) != vet_ext) {
- return 0;
- }
- break;
- default:
- return 0;
- }
- if (copied < new_ehdr_size || be16_to_cpu(*new_ehdr_proto) != vet) {
- return 0;
- }
- copied = iov_to_buf(iov, iovcnt, iovoff + new_ehdr_size,
- &vlan_hdr, sizeof(vlan_hdr));
- if (copied < sizeof(vlan_hdr)) {
- return 0;
- }
- *new_ehdr_proto = vlan_hdr.h_proto;
- *payload_offset = iovoff + new_ehdr_size + sizeof(vlan_hdr);
- *tci = be16_to_cpu(vlan_hdr.h_tci);
- return new_ehdr_size;
- }
- void
- eth_fix_ip4_checksum(void *l3hdr, size_t l3hdr_len)
- {
- struct ip_header *iphdr = (struct ip_header *) l3hdr;
- iphdr->ip_sum = 0;
- iphdr->ip_sum = cpu_to_be16(net_raw_checksum(l3hdr, l3hdr_len));
- }
- uint32_t
- eth_calc_ip4_pseudo_hdr_csum(struct ip_header *iphdr,
- uint16_t csl,
- uint32_t *cso)
- {
- struct ip_pseudo_header ipph;
- ipph.ip_src = iphdr->ip_src;
- ipph.ip_dst = iphdr->ip_dst;
- ipph.ip_payload = cpu_to_be16(csl);
- ipph.ip_proto = iphdr->ip_p;
- ipph.zeros = 0;
- *cso = sizeof(ipph);
- return net_checksum_add(*cso, (uint8_t *) &ipph);
- }
- uint32_t
- eth_calc_ip6_pseudo_hdr_csum(struct ip6_header *iphdr,
- uint16_t csl,
- uint8_t l4_proto,
- uint32_t *cso)
- {
- struct ip6_pseudo_header ipph;
- ipph.ip6_src = iphdr->ip6_src;
- ipph.ip6_dst = iphdr->ip6_dst;
- ipph.len = cpu_to_be16(csl);
- ipph.zero[0] = 0;
- ipph.zero[1] = 0;
- ipph.zero[2] = 0;
- ipph.next_hdr = l4_proto;
- *cso = sizeof(ipph);
- return net_checksum_add(*cso, (uint8_t *)&ipph);
- }
- static bool
- eth_is_ip6_extension_header_type(uint8_t hdr_type)
- {
- switch (hdr_type) {
- case IP6_HOP_BY_HOP:
- case IP6_ROUTING:
- case IP6_FRAGMENT:
- case IP6_AUTHENTICATION:
- case IP6_DESTINATON:
- case IP6_MOBILITY:
- return true;
- default:
- return false;
- }
- }
- static bool
- _eth_get_rss_ex_dst_addr(const struct iovec *pkt, int pkt_frags,
- size_t ext_hdr_offset,
- struct ip6_ext_hdr *ext_hdr,
- struct in6_address *dst_addr)
- {
- struct ip6_ext_hdr_routing rt_hdr;
- size_t input_size = iov_size(pkt, pkt_frags);
- size_t bytes_read;
- if (input_size < ext_hdr_offset + sizeof(rt_hdr) + sizeof(*dst_addr)) {
- return false;
- }
- bytes_read = iov_to_buf(pkt, pkt_frags, ext_hdr_offset,
- &rt_hdr, sizeof(rt_hdr));
- assert(bytes_read == sizeof(rt_hdr));
- if ((rt_hdr.rtype != 2) || (rt_hdr.segleft != 1)) {
- return false;
- }
- bytes_read = iov_to_buf(pkt, pkt_frags, ext_hdr_offset + sizeof(rt_hdr),
- dst_addr, sizeof(*dst_addr));
- assert(bytes_read == sizeof(*dst_addr));
- return true;
- }
- static bool
- _eth_get_rss_ex_src_addr(const struct iovec *pkt, int pkt_frags,
- size_t dsthdr_offset,
- struct ip6_ext_hdr *ext_hdr,
- struct in6_address *src_addr)
- {
- size_t bytes_left = (ext_hdr->ip6r_len + 1) * 8 - sizeof(*ext_hdr);
- struct ip6_option_hdr opthdr;
- size_t opt_offset = dsthdr_offset + sizeof(*ext_hdr);
- while (bytes_left > sizeof(opthdr)) {
- size_t input_size = iov_size(pkt, pkt_frags);
- size_t bytes_read, optlen;
- if (input_size < opt_offset) {
- return false;
- }
- bytes_read = iov_to_buf(pkt, pkt_frags, opt_offset,
- &opthdr, sizeof(opthdr));
- if (bytes_read != sizeof(opthdr)) {
- return false;
- }
- optlen = (opthdr.type == IP6_OPT_PAD1) ? 1
- : (opthdr.len + sizeof(opthdr));
- if (optlen > bytes_left) {
- return false;
- }
- if (opthdr.type == IP6_OPT_HOME) {
- if (input_size < opt_offset + sizeof(opthdr)) {
- return false;
- }
- bytes_read = iov_to_buf(pkt, pkt_frags,
- opt_offset + sizeof(opthdr),
- src_addr, sizeof(*src_addr));
- return bytes_read == sizeof(*src_addr);
- }
- opt_offset += optlen;
- bytes_left -= optlen;
- }
- return false;
- }
- bool eth_parse_ipv6_hdr(const struct iovec *pkt, int pkt_frags,
- size_t ip6hdr_off, eth_ip6_hdr_info *info)
- {
- struct ip6_ext_hdr ext_hdr;
- size_t bytes_read;
- uint8_t curr_ext_hdr_type;
- size_t input_size = iov_size(pkt, pkt_frags);
- info->rss_ex_dst_valid = false;
- info->rss_ex_src_valid = false;
- info->fragment = false;
- if (input_size < ip6hdr_off) {
- return false;
- }
- bytes_read = iov_to_buf(pkt, pkt_frags, ip6hdr_off,
- &info->ip6_hdr, sizeof(info->ip6_hdr));
- if (bytes_read < sizeof(info->ip6_hdr)) {
- return false;
- }
- info->full_hdr_len = sizeof(struct ip6_header);
- curr_ext_hdr_type = info->ip6_hdr.ip6_nxt;
- if (!eth_is_ip6_extension_header_type(curr_ext_hdr_type)) {
- info->l4proto = info->ip6_hdr.ip6_nxt;
- info->has_ext_hdrs = false;
- return true;
- }
- info->has_ext_hdrs = true;
- do {
- if (input_size < ip6hdr_off + info->full_hdr_len) {
- return false;
- }
- bytes_read = iov_to_buf(pkt, pkt_frags, ip6hdr_off + info->full_hdr_len,
- &ext_hdr, sizeof(ext_hdr));
- if (bytes_read < sizeof(ext_hdr)) {
- return false;
- }
- if (curr_ext_hdr_type == IP6_ROUTING) {
- if (ext_hdr.ip6r_len == sizeof(struct in6_address) / 8) {
- info->rss_ex_dst_valid =
- _eth_get_rss_ex_dst_addr(pkt, pkt_frags,
- ip6hdr_off + info->full_hdr_len,
- &ext_hdr, &info->rss_ex_dst);
- }
- } else if (curr_ext_hdr_type == IP6_DESTINATON) {
- info->rss_ex_src_valid =
- _eth_get_rss_ex_src_addr(pkt, pkt_frags,
- ip6hdr_off + info->full_hdr_len,
- &ext_hdr, &info->rss_ex_src);
- } else if (curr_ext_hdr_type == IP6_FRAGMENT) {
- info->fragment = true;
- }
- info->full_hdr_len += (ext_hdr.ip6r_len + 1) * IP6_EXT_GRANULARITY;
- curr_ext_hdr_type = ext_hdr.ip6r_nxt;
- } while (eth_is_ip6_extension_header_type(curr_ext_hdr_type));
- info->l4proto = ext_hdr.ip6r_nxt;
- return true;
- }
- bool eth_pad_short_frame(uint8_t *padded_pkt, size_t *padded_buflen,
- const void *pkt, size_t pkt_size)
- {
- assert(padded_buflen && *padded_buflen >= ETH_ZLEN);
- if (pkt_size >= ETH_ZLEN) {
- return false;
- }
- /* pad to minimum Ethernet frame length */
- memcpy(padded_pkt, pkt, pkt_size);
- memset(&padded_pkt[pkt_size], 0, ETH_ZLEN - pkt_size);
- *padded_buflen = ETH_ZLEN;
- return true;
- }
|