From c9c167871cf8dd5a364e85962e06cc52a59f3ec3 Mon Sep 17 00:00:00 2001 From: Michal Zima Date: Sat, 30 Mar 2013 13:12:03 +0100 Subject: [PATCH] Generalised translation of ICMPv4 error packets --- src/icmp.c | 427 ++++++++++++++++++++++++++--------------------------- 1 file changed, 210 insertions(+), 217 deletions(-) diff --git a/src/icmp.c b/src/icmp.c index 9716788..dfac715 100644 --- a/src/icmp.c +++ b/src/icmp.c @@ -32,6 +32,11 @@ #include "udp.h" #include "wrapper.h" +/* declarations */ +int sub_icmp4_error(unsigned char *payload, unsigned short payload_size, + unsigned char *packet, unsigned short *packet_len, + struct s_icmp **icmp, struct s_nat **connection); + /** * Processing of incoming ICMPv4 packets. Directly sends translated ICMPv6 * packets. @@ -61,14 +66,10 @@ int icmp_ipv4(struct s_ethernet *eth4, struct s_ipv4 *ip4, struct s_ipv6 *ip6; /* for error messages */ - unsigned short payload_size_left, new_len; struct s_ipv4 *eip4; struct s_ipv6 *eip6; - struct s_tcp *etcp; - struct s_udp *eudp; - struct s_icmp *eicmp; - unsigned short eip4_hlen; - struct s_ipv6_fragment *eip6_frag; + unsigned short new_len = sizeof(struct s_ethernet) + + sizeof(struct s_ipv6); /* sanity check */ if (payload_size < sizeof(struct s_icmp) + 4) { @@ -112,208 +113,34 @@ int icmp_ipv4(struct s_ethernet *eth4, struct s_ipv4 *ip4, /* override information in original ICMP header */ icmp->type = ICMPV6_ECHO_REPLY; - new_len = sizeof(struct s_ethernet) + - sizeof(struct s_ipv6); - /* copy the payload data */ - payload_size_left = payload_size > - MTU - sizeof(struct s_ipv6) ? - MTU - sizeof(struct s_ipv6) : payload_size; - memcpy(&packet[new_len], payload, payload_size_left); - icmp = (struct s_icmp *) &packet[new_len]; - new_len += payload_size_left; + if (payload_size < MTU - sizeof(struct s_ipv6)) { + memcpy(&packet[new_len], payload, payload_size); + icmp = (struct s_icmp *) &packet[new_len]; + new_len += payload_size; + } else { + memcpy(&packet[new_len], payload, MTU - + sizeof(struct s_ipv6)); + icmp = (struct s_icmp *) &packet[new_len]; + new_len += MTU - sizeof(struct s_ipv6); + } break; case ICMPV4_DST_UNREACHABLE: if (icmp->code <= 13 || icmp->code == 15) { - /* sanity check */ - payload_size_left = payload_size - - sizeof(struct s_icmp) - 4; - if (payload_size_left < 1) { - log_debug("Too short ICMPv4 packet 2"); + if (sub_icmp4_error(icmp_data + 4, + payload_size - sizeof(struct s_icmp) - 4, + &packet[0], &new_len, &icmp, &connection)) { + /* something went wrong */ return 1; } - /* skip unused space */ - icmp_data += 4; - - /* parse IPv4 header */ - eip4 = (struct s_ipv4 *) icmp_data; - - /* # of 4 byte words */ - eip4_hlen = (eip4->ver_hdrlen & 0x0f) * 4; - - /* sanity check */ - if (payload_size_left < eip4_hlen + 4) { - log_debug("Too short ICMPv4 packet 3"); - return 1; - } - /* 4 B -> L4 addrs */ - payload_size_left -= eip4_hlen + 4; - - icmp_data += eip4_hlen; - - new_len = sizeof(struct s_ethernet) + - sizeof(struct s_ipv6); - - /* define new inner IPv6 header */ - /* new_len+4+4+40=102 < 1280 */ - eip6 = (struct s_ipv6 *) &packet[new_len + + eip4 = (struct s_ipv4 *) (icmp_data + 4); + eip6 = (struct s_ipv6 *) &packet[ + sizeof(struct s_ethernet) + + sizeof(struct s_ipv6) + sizeof(struct s_icmp) + 4]; - /* we'll need this right now */ - ipv4_to_ipv6(&eip4->ip_dest, &eip6->ip_dest); - - /* look for the original connection */ - switch (eip4->proto) { - case IPPROTO_TCP: - etcp = (struct s_tcp *) - icmp_data; - connection = nat_in(nat4_tcp, - eip4->ip_dest, - etcp->port_dest, - etcp->port_src); - - if (connection == NULL) { - log_debug("Incoming " - "TCP error " - "connection " - "wasn't found " - "in NAT"); - return 1; - } - - /* fix port for local client */ - etcp->port_src = connection-> - ipv6_port_src; - - break; - - case IPPROTO_UDP: - eudp = (struct s_udp *) - icmp_data; - connection = nat_in(nat4_udp, - eip4->ip_dest, - eudp->port_dest, - eudp->port_src); - - if (connection == NULL) { - log_debug("Incoming " - "UDP error " - "connection " - "wasn't found " - "in NAT"); - return 1; - } - - /* fix port for local client */ - eudp->port_src = connection-> - ipv6_port_src; - - break; - - case IPPROTO_ICMP: - eicmp = (struct s_icmp *) - icmp_data; - - /* we translate only echo - * requests so handle only them - * here too */ - if (eicmp->type != - ICMPV4_ECHO_REQUEST) { - log_debug("Unknown ICMP" - " type within " - "ICMP error"); - return 1; - } - - /* else: */ - - /* sanity check */ - if (payload_size_left < 4) { - log_debug("Too short " - "ICMPv4 packet " - "4"); - return 1; - } - - echo = (struct s_icmp_echo *) - (icmp_data + - sizeof(struct s_icmp)); - - connection = nat_in(nat4_icmp, - eip4->ip_dest, 0, - echo->id); - - if (connection == NULL) { - log_debug("Incoming " - "ICMP error " - "connection " - "wasn't found " - "in NAT"); - return 1; - } - - /* fix port for local client */ - echo->id = connection-> - ipv6_port_src; - - /* adjust ICMP type */ - eicmp->type = - ICMPV6_ECHO_REQUEST; - - break; - - default: - /* we don't know where to send - * it */ - return 1; - } - - /* copy ICMP header to new packet */ - memcpy(&packet[new_len], icmp, - sizeof(struct s_icmp) + 4); - icmp = (struct s_icmp *) &packet[new_len]; - - new_len += sizeof(struct s_icmp) + 4; - - /* complete inner IPv6 header */ - eip6->ver = 0x60 | (eip4->tos >> 4); - eip6->traffic_class = eip4->tos << 4; - eip6->flow_label = 0x0; - eip6->hop_limit = eip4->ttl; - if (eip4->proto != IPPROTO_ICMP) { - eip6->next_header = eip4->proto; - } else { - eip6->next_header = IPPROTO_ICMPV6; - } - eip6->ip_src = connection->ipv6; - - new_len += sizeof(struct s_ipv6); - - /* was the IPv4 packet fragmented? */ - if ((eip4->flags_offset & htons(0x1fff)) != - 0x0000) { - /* original length of error packet, - * but without IP header, but with - * fragment header(!) */ - eip6->len = htons(ntohs(eip4->len) - - eip4_hlen + - sizeof(struct s_ipv6_fragment)); - - eip6_frag = (struct s_ipv6_fragment *) - &packet[new_len]; - eip6_frag->next_header = - eip6->next_header; - eip6->next_header = IPPROTO_FRAGMENT; - eip6_frag->id = htonl(ntohs(eip4->id)); - - new_len += - sizeof(struct s_ipv6_fragment); - } else { - eip6->len = htons(ntohs(eip4->len) - - eip4_hlen); - } /* translate ICMP type&code */ if (icmp->code == 2) { @@ -382,24 +209,6 @@ int icmp_ipv4(struct s_ethernet *eth4, struct s_ipv4 *ip4, icmp->code = 0; } - /* copy payload, aligned to MTU */ - /* we can afford to use full MTU instead of - * just 1280 B as admin warrants this to us */ - if (payload_size - sizeof(struct s_icmp) - 4 - - eip4_hlen > (unsigned int) MTU - new_len) { - memcpy(&packet[new_len], icmp_data, - MTU - new_len); - new_len = MTU; - } else { - memcpy(&packet[new_len], icmp_data, - payload_size - - sizeof(struct s_icmp) - 4 - - eip4_hlen); - new_len += payload_size - - sizeof(struct s_icmp) - 4 - - eip4_hlen; - } - /* new packet is almost finished, yay! */ break; @@ -425,7 +234,8 @@ int icmp_ipv4(struct s_ethernet *eth4, struct s_ipv4 *ip4, ip6->ver = 0x60 | (ip4->tos >> 4); ip6->traffic_class = ip4->tos << 4; ip6->flow_label = 0x0; - ip6->len = htons(new_len - sizeof(struct s_ethernet) - sizeof(struct s_ipv6)); + ip6->len = htons(new_len - sizeof(struct s_ethernet) - + sizeof(struct s_ipv6)); ip6->next_header = IPPROTO_ICMPV6; ip6->hop_limit = ip4->ttl; ipv4_to_ipv6(&ip4->ip_src, &ip6->ip_src); @@ -818,3 +628,186 @@ int icmp6_error(struct s_mac_addr mac_dest, struct s_ipv6_addr ip_dest, return 0; } + +/** + * Helper function for translating inner packet in ICMPv4 error. + * + * @param payload Original inner packet data + * @param payload_size Length of original data + * @param packet Space for translated packet data + * @param packet_len Length of translated data + * @param icmp Outer ICMP header + * @param connection NAT connection for inner packet + * + * @return 0 for success + * @return 1 for failure + */ +int sub_icmp4_error(unsigned char *payload, unsigned short payload_size, + unsigned char *packet, unsigned short *packet_len, + struct s_icmp **icmp, struct s_nat **connection) +{ + struct s_ipv4 *eip4; + struct s_ipv6 *eip6; + struct s_tcp *etcp; + struct s_udp *eudp; + struct s_icmp *eicmp; + unsigned short eip4_hlen; + struct s_ipv6_fragment *eip6_frag; + struct s_icmp_echo *echo; + + /* sanity check */ + if (payload_size < 1) { + log_debug("Too short ICMPv4 packet 2"); + return 1; + } + + /* parse IPv4 header */ + eip4 = (struct s_ipv4 *) payload; + + /* # of 4 byte words */ + eip4_hlen = (eip4->ver_hdrlen & 0x0f) * 4; + + /* sanity check */ + if (payload_size < eip4_hlen + 4) { + log_debug("Too short ICMPv4 packet 3"); + return 1; + } + /* 4 B -> L4 addrs */ + payload_size -= eip4_hlen; + + payload += eip4_hlen; + + /* define new inner IPv6 header */ + /* new_len+4+4+40=102 < 1280 */ + eip6 = (struct s_ipv6 *) &packet[*packet_len + sizeof(struct s_icmp) + + 4]; + /* we'll need this right now */ + ipv4_to_ipv6(&eip4->ip_dest, &eip6->ip_dest); + + /* look for the original connection */ + switch (eip4->proto) { + case IPPROTO_TCP: + etcp = (struct s_tcp *) payload; + *connection = nat_in(nat4_tcp, eip4->ip_dest, + etcp->port_dest, etcp->port_src); + + if (*connection == NULL) { + log_debug("Incoming TCP error connection " + "wasn't found in NAT"); + return 1; + } + + /* fix port for local client */ + etcp->port_src = (*connection)->ipv6_port_src; + + break; + + case IPPROTO_UDP: + eudp = (struct s_udp *) payload; + *connection = nat_in(nat4_udp, eip4->ip_dest, + eudp->port_dest, eudp->port_src); + + if (*connection == NULL) { + log_debug("Incoming UDP error connection " + "wasn't found in NAT"); + return 1; + } + + /* fix port for local client */ + eudp->port_src = (*connection)->ipv6_port_src; + + break; + + case IPPROTO_ICMP: + eicmp = (struct s_icmp *) payload; + + /* we translate only echo requests so handle only them + * here too */ + if (eicmp->type != ICMPV4_ECHO_REQUEST) { + log_debug("Unknown ICMP type within ICMP " + "error"); + return 1; + } + + /* else: */ + + /* sanity check */ + if (payload_size < 8) { + log_debug("Too short ICMPv4 packet 4"); + return 1; + } + + echo = (struct s_icmp_echo *) (payload + + sizeof(struct s_icmp)); + + *connection = nat_in(nat4_icmp, eip4->ip_dest, 0, + echo->id); + + if (*connection == NULL) { + log_debug("Incoming ICMP error connection " + "wasn't found in NAT"); + return 1; + } + + /* fix port for local client */ + echo->id = (*connection)->ipv6_port_src; + + /* adjust ICMP type */ + eicmp->type = ICMPV6_ECHO_REQUEST; + + break; + + default: + /* we don't know where to send it */ + return 1; + } + + /* copy ICMP header to new packet */ + memcpy(&packet[*packet_len], *icmp, sizeof(struct s_icmp) + 4); + *icmp = (struct s_icmp *) &packet[*packet_len]; + + /* complete inner IPv6 header */ + eip6->ver = 0x60 | (eip4->tos >> 4); + eip6->traffic_class = eip4->tos << 4; + eip6->flow_label = 0x0; + eip6->hop_limit = eip4->ttl; + if (eip4->proto != IPPROTO_ICMP) { + eip6->next_header = eip4->proto; + } else { + eip6->next_header = IPPROTO_ICMPV6; + } + eip6->ip_src = (*connection)->ipv6; + + *packet_len += sizeof(struct s_icmp) + 4 + sizeof(struct s_ipv6); + + /* was the IPv4 packet fragmented? */ + if ((eip4->flags_offset & htons(0x1fff)) != 0x0000) { + /* original length of error packet, + * but without IP header, but with + * fragment header(!) */ + eip6->len = htons(ntohs(eip4->len) - eip4_hlen + + sizeof(struct s_ipv6_fragment)); + + eip6_frag = (struct s_ipv6_fragment *) &packet[*packet_len]; + eip6_frag->next_header = eip6->next_header; + eip6->next_header = IPPROTO_FRAGMENT; + eip6_frag->id = htonl(ntohs(eip4->id)); + + *packet_len += sizeof(struct s_ipv6_fragment); + } else { + eip6->len = htons(ntohs(eip4->len) - eip4_hlen); + } + + /* copy payload, aligned to MTU */ + /* we can afford to use full MTU instead of just 1280 B as admin + * warrants this to us */ + if (payload_size > (unsigned int) MTU - *packet_len) { + memcpy(&packet[*packet_len], payload, MTU - *packet_len); + *packet_len = MTU; + } else { + memcpy(&packet[*packet_len], payload, payload_size); + *packet_len += payload_size; + } + + return 0; +}