/* * 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 "net/eth.h" #include "net/checksum.h" #include "qemu-common.h" #include "net/tap.h" void eth_setup_vlan_headers_ex(struct eth_header *ehdr, uint16_t vlan_tag, uint16_t vlan_ethtype, bool *is_new) { struct vlan_header *vhdr = PKT_GET_VLAN_HDR(ehdr); switch (be16_to_cpu(ehdr->h_proto)) { case ETH_P_VLAN: case ETH_P_DVLAN: /* vlan hdr exists */ *is_new = false; break; default: /* No VLAN header, put a new one */ vhdr->h_proto = ehdr->h_proto; ehdr->h_proto = cpu_to_be16(vlan_ethtype); *is_new = true; break; } vhdr->h_tci = cpu_to_be16(vlan_tag); } 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; } } /* Unsupported offload */ g_assert_not_reached(); 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, int iovcnt, bool *isip4, bool *isip6, bool *isudp, bool *istcp, 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 l2hdr_len = eth_get_l2_hdr_length_iov(iov, iovcnt); size_t input_size = iov_size(iov, iovcnt); size_t copied; *isip4 = *isip6 = *isudp = *istcp = false; proto = eth_get_l3_proto(iov, iovcnt, l2hdr_len); *l3hdr_off = l2hdr_len; if (proto == ETH_P_IP) { struct ip_header *iphdr = &ip4hdr_info->ip4_hdr; if (input_size < l2hdr_len) { return; } copied = iov_to_buf(iov, iovcnt, l2hdr_len, iphdr, sizeof(*iphdr)); *isip4 = true; if (copied < sizeof(*iphdr)) { return; } if (IP_HEADER_VERSION(iphdr) == IP_HEADER_VERSION_4) { if (iphdr->ip_p == IP_PROTO_TCP) { *istcp = true; } else if (iphdr->ip_p == IP_PROTO_UDP) { *isudp = true; } } ip4hdr_info->fragment = IP4_IS_FRAGMENT(iphdr); *l4hdr_off = l2hdr_len + IP_HDR_GET_LEN(iphdr); fragment = ip4hdr_info->fragment; } else if (proto == ETH_P_IPV6) { *isip6 = true; if (eth_parse_ipv6_hdr(iov, iovcnt, l2hdr_len, ip6hdr_info)) { if (ip6hdr_info->l4proto == IP_PROTO_TCP) { *istcp = true; } else if (ip6hdr_info->l4proto == IP_PROTO_UDP) { *isudp = true; } } else { return; } *l4hdr_off = l2hdr_len + ip6hdr_info->full_hdr_len; fragment = ip6hdr_info->fragment; } if (!fragment) { if (*istcp) { *istcp = _eth_copy_chunk(input_size, iov, iovcnt, *l4hdr_off, sizeof(l4hdr_info->hdr.tcp), &l4hdr_info->hdr.tcp); if (*istcp) { *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); } } else if (*isudp) { *isudp = _eth_copy_chunk(input_size, iov, iovcnt, *l4hdr_off, sizeof(l4hdr_info->hdr.udp), &l4hdr_info->hdr.udp); *l5hdr_off = *l4hdr_off + sizeof(l4hdr_info->hdr.udp); } } } size_t eth_strip_vlan(const struct iovec *iov, int iovcnt, size_t iovoff, uint8_t *new_ehdr_buf, uint16_t *payload_offset, uint16_t *tci) { struct vlan_header vlan_hdr; struct eth_header *new_ehdr = (struct eth_header *) 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, uint16_t vet, uint8_t *new_ehdr_buf, uint16_t *payload_offset, uint16_t *tci) { struct vlan_header vlan_hdr; struct eth_header *new_ehdr = (struct eth_header *) new_ehdr_buf; size_t copied = iov_to_buf(iov, iovcnt, iovoff, new_ehdr, sizeof(*new_ehdr)); if (copied < sizeof(*new_ehdr)) { return 0; } if (be16_to_cpu(new_ehdr->h_proto) == vet) { 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); return sizeof(struct eth_header); } return 0; } void eth_setup_ip4_fragmentation(const void *l2hdr, size_t l2hdr_len, void *l3hdr, size_t l3hdr_len, size_t l3payload_len, size_t frag_offset, bool more_frags) { const struct iovec l2vec = { .iov_base = (void *) l2hdr, .iov_len = l2hdr_len }; if (eth_get_l3_proto(&l2vec, 1, l2hdr_len) == ETH_P_IP) { uint16_t orig_flags; struct ip_header *iphdr = (struct ip_header *) l3hdr; uint16_t frag_off_units = frag_offset / IP_FRAG_UNIT_SIZE; uint16_t new_ip_off; assert(frag_offset % IP_FRAG_UNIT_SIZE == 0); assert((frag_off_units & ~IP_OFFMASK) == 0); orig_flags = be16_to_cpu(iphdr->ip_off) & ~(IP_OFFMASK|IP_MF); new_ip_off = frag_off_units | orig_flags | (more_frags ? IP_MF : 0); iphdr->ip_off = cpu_to_be16(new_ip_off); iphdr->ip_len = cpu_to_be16(l3payload_len + l3hdr_len); } } 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_ESP: 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 rthdr_offset, struct ip6_ext_hdr *ext_hdr, struct in6_address *dst_addr) { struct ip6_ext_hdr_routing *rthdr = (struct ip6_ext_hdr_routing *) ext_hdr; if ((rthdr->rtype == 2) && (rthdr->len == sizeof(struct in6_address) / 8) && (rthdr->segleft == 1)) { size_t input_size = iov_size(pkt, pkt_frags); size_t bytes_read; if (input_size < rthdr_offset + sizeof(*ext_hdr)) { return false; } bytes_read = iov_to_buf(pkt, pkt_frags, rthdr_offset + sizeof(*ext_hdr), dst_addr, sizeof(*dst_addr)); return bytes_read == sizeof(*dst_addr); } return false; } 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) { size_t input_size = iov_size(pkt, pkt_frags); 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) { 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; }