static char rcsid[] = "$Id: PctestIpv6Tcp.cc 1082 2005-02-12 19:40:04Z bmah $"; // // $Id: PctestIpv6Tcp.cc 1082 2005-02-12 19:40:04Z bmah $ // // PctestIpv6Tcp.cc // Bruce A. Mah // // This work was first produced by an employee of Sandia National // Laboratories under a contract with the U.S. Department of Energy. // Sandia National Laboratories dedicates whatever right, title or // interest it may have in this software to the public. Although no // license from Sandia is needed to copy and use this software, // copying and using the software might infringe the rights of // others. This software is provided as-is. SANDIA DISCLAIMS ANY // WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. // // Class of IPv6 tests using TCP // // // Solaris needs some "extra stuff" to get msg_control in recvmsg(2) // according to Erik Nordmark . His quick // fix to do this is: #ifdef NEED_XOPEN #define _XOPEN_SOURCE 500 #define __EXTENSIONS__ #endif /* NEED_XOPEN */ #include #include #include #include #include #include #ifdef NEED_NRL_IPV6_HACK #include #endif /* NEED_NRL_IPV6_HACK */ #include #ifdef NEED_NRL_IPV6_HACK #include #include #else #include #include #endif /* NEED_NRL_IPV6_HACK */ #include #include #ifdef HAVE_PCAP #include #endif /* HAVE_PCAP */ #include "pc.h" #include "PctestIpv6Tcp.h" #include "TestRecord.h" // // PctestIpv6Tcp::PctestIpv6Tcp // PctestIpv6Tcp::PctestIpv6Tcp(int p) { extern bool PcapFlag; #ifdef HAVE_PCAP if (PcapFlag) { // Initialize packet filter if (pcap_compile(pc, &fp, "(ip6 and tcp) or (ip6 and icmp6)", 1, maskp) < 0) { fprintf(stderr, "pcap_compile failed\n"); exit(1); } if (pcap_setfilter(pc, &fp) < 0) { fprintf(stderr, "pcap_setfilter failed\n"); exit(1); } } else { #endif /* HAVE_PCAP */ fprintf(stderr, "ipv6tcp probes require libpcap functionality\n"); exit(1); #ifdef HAVE_PCAP } #endif /* HAVE_PCAP */ }; // // PctestIpv6Tcp::GetSocketOut // // Input: None // // Output: In return value, returns socket number. // // Get output socket of an appropriate type, store it in socketOut. // int PctestIpv6Tcp::GetSocketOut() { int rc; socketOut = socket(AF_INET6, SOCK_RAW, IPPROTO_TCP); if (socketOut < 0) { perror("socket"); return socketOut; } // Turn on checksum computation int checksumOffset; checksumOffset = 16; // voodoo constant: how far into the // packet does the TCP checksum live? rc = setsockopt(socketOut, IPPROTO_IPV6, IPV6_CHECKSUM, &checksumOffset, sizeof(checksumOffset)); if (rc < 0) { perror("setsockopt(IPV6_CHECKSUM)"); return rc; } // Make up a socket address structure for the source address // and attempt to bind the output socket to it. struct sockaddr_in6 originSocketAddress; memset((void *) &originSocketAddress, 0, sizeof(struct sockaddr_in6)); // See comments in PctestIpv6::SetTargetName() about the overloading // of HAVE_SOCKADDR_SA_LEN. #ifdef HAVE_SOCKADDR_SA_LEN originSocketAddress.sin6_len = sizeof(struct sockaddr_in6); #endif /* HAVE_SOCKADDR_SA_LEN */ originSocketAddress.sin6_family = AF_INET6; originSocketAddress.sin6_port = htons(0); memcpy(&(originSocketAddress.sin6_addr), &originAddress, sizeof(in6_addr)); rc = bind(socketOut, (struct sockaddr *) &originSocketAddress, sizeof(originSocketAddress)); if (rc < 0) { perror("bind()"); return rc; } // Bind remote side of socket rc = connect(socketOut, (struct sockaddr *) &targetSocketAddress, sizeof(targetSocketAddress)); if (rc < 0) { perror("connect"); return rc; } // Set up sockets for advance packets if (GetAdvanceSocketOut() < 0) { return -1; } return socketOut; } // // PctestIpv6Tcp::Test // // Input: // // Output: // // A negative icmpCode indicates an error. // int PctestIpv6Tcp::Test(TestRecord &tr) { struct timeval timeout; int rc; // syscall return code fd_set readFds; // reading file descriptors int done = 0; int i; // generic loop counter // Parameters stored as globals extern unsigned int Mtu; extern bool PcapFlag; extern int Timeout; #ifdef HAVE_PCAP if (PcapFlag) { // If the requested sending size is too small, then return an // error. The caller should have figured out the minimum sending // size by calling Pctest::GetMinSize(). if (tr.size < GetMinSize()) { return -1; } // Make up a TCP segment to send out. int tcpPayloadSize = tr.size - sizeof(ip6_hdr) - sizeof(icmp6_hdr); struct tcphdr tcpHeader; // Fill in TCP header fields, see PctestIpv4Tcp::Test() for // commentary. memset(&tcpHeader, 0, sizeof(tcpHeader)); tcpHeader.th_sport = htons(30003); tcpHeader.th_dport = htons(destPort++); tcpHeader.th_seq = htons(0); tcpHeader.th_ack = htons(0); #ifdef __osf__ // Tru64 does not declare th_off if __STDC__ == 1 tcpHeader.th_xoff = (sizeof(tcphdr)/4)<<4; // header length words with no options // shifted by 4 bits to cover unused field #else tcpHeader.th_off = sizeof(tcphdr)/4; // header length words with no options #endif /* __osf__ */ tcpHeader.th_flags = TH_FIN | TH_ACK; tcpHeader.th_win = htons(1); tcpHeader.th_sum = 0; tcpHeader.th_urp = htons(0); IF_DEBUG(2, fprintf(stdout, "test size %d, payload size %d\n", tr.size, tcpPayloadSize)); // TCP payload char *tcpPayload; tcpPayload = GeneratePayload(tcpPayloadSize); if (tcpPayload == NULL) { fprintf(stderr, "Couldn't allocate space for payload\n"); return -1; } // Build the packet (well, really just the TCP segment) char *tcpPacket; int tcpPacketSize; tcpPacketSize = sizeof(tcphdr) + tcpPayloadSize; tcpPacket = new char[tcpPacketSize]; memcpy(tcpPacket, &tcpHeader, sizeof(tcpHeader)); memcpy(tcpPacket + sizeof(tcpHeader), tcpPayload, tcpPayloadSize); // Set TTL. rc = setsockopt(socketOut, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *) &tr.hops, sizeof(tr.hops)); if (rc < 0) { perror("setsockopt(IPV6_UNICAST_HOPS)"); return rc; } // If we need to construct some advance packets, generate // an image of said packet. char *advancePacket; if (tr.burst - 1 == 0) { advancePacket = NULL; } else { advancePacket = GenerateAdvancePacket(tr); if (advancePacket == NULL) { return -1; } } // Use malloc(3) to allocate (memory-aligned) space for the inbound // packet. char *icmp6PacketIn; icmp6PacketIn = (char *) malloc(IPV6_MAXPACKET); if (icmp6PacketIn == NULL) { fprintf(stderr, "Couldn't allocate space for inbound packet\n"); return -1; } // Timestamp before gettimeofday(&tvBefore, NULL); // Send advance packets if necessary for (i = 1; i < tr.burst; i++) { rc = send(advanceSocketOut, advancePacket, Mtu - sizeof(ip6_hdr), 0); if (rc < 0) { perror("send"); goto exittest; } } tr.size += (tr.burst - 1) * Mtu; // Send packet rc = send(socketOut, tcpPacket, tcpPacketSize, 0); if (rc < 0) { perror("sendto"); goto exittest; } // We need to check the socket until we get a valid packet. // So we might end up doing this select/read several times. do { // Wait for a packet. Only take in one packet at a time. rc = pcap_dispatch(pc, 1, callback, (u_char *) this); if (rc < 0) { fprintf(stderr, "pcap_dispatch failed\n"); goto exittest; } // The callback will handle our after- timestamp if // we get a packet, but if the read timeout fired first, // we'll need to do a timestamp here to be sure that tvAfter // has a valid value in it. if (rc == 0) { gettimeofday(&tvAfter, NULL); } tr.tv.tv_sec = tvAfter.tv_sec - tvBefore.tv_sec - syscallTime.tv_sec; tr.tv.tv_usec = tvAfter.tv_usec - tvBefore.tv_usec - syscallTime.tv_usec; while (tr.tv.tv_usec < 0) { tr.tv.tv_usec += 1000000; tr.tv.tv_sec--; } // Read response if (rc == 1) { IF_DEBUG(2, fprintf(stderr, "Response packet received\n")); struct ip6_hdr *ipv6HeaderIn; ipv6HeaderIn = (struct ip6_hdr *) packet; if ((ipv6HeaderIn->ip6_nxt != IPPROTO_ICMPV6) && (ipv6HeaderIn->ip6_nxt != IPPROTO_TCP)) { IF_DEBUG(2, fprintf(stderr, "Ignoring non-ICMPv6, non-TCP packet\n")); continue; } memcpy(icmp6PacketIn, packet + sizeof(ip6_hdr), packetLength - sizeof(ip6_hdr)); tr.replsize = packetLength - sizeof(ip6_hdr); // Fill in icmpSourceSocketAddress from the IPv6 // header. memcpy(&icmpSourceSocketAddress.sin6_addr, &(ipv6HeaderIn->ip6_src), sizeof(in6_addr)); icmpSourceSocketAddress.sin6_family = AF_INET6; #ifdef HAVE_SOCKADDR_SA_LEN icmpSourceSocketAddress.sin6_len = sizeof(struct sockaddr_in6); #endif /* HAVE_SOCKADDR_SA_LEN */ // Parse the packet and error-check icmp6_hdr *icmp6HeaderIn, *icmp6HeaderIn2; // Check protocol if ((ipv6HeaderIn->ip6_nxt != proto) && (ipv6HeaderIn->ip6_nxt != IPPROTO_TCP)) { IF_DEBUG(0, fprintf(stderr, "Received unknown protocol %d in (supposedly) ICMPv6 or TCP packet\n", ipv6HeaderIn->ip6_nxt)); rc = -1; goto exittest; } // See if it was an ICMPv6 message if (ipv6HeaderIn->ip6_nxt == proto) { if (tr.replsize - (0) < sizeof(icmp6_hdr)) { IF_DEBUG(3, fprintf(stderr, "Received incomplete ICMPv6 packet of %d bytes\n", tr.replsize)); continue; } // Find the ICMPv6 header icmp6HeaderIn = (icmp6_hdr *) icmp6PacketIn; IF_DEBUG(3, fprintf(stderr, "ICMPv6 type = %d, code = %d\n", icmp6HeaderIn->icmp6_type, icmp6HeaderIn->icmp6_code)); // Check ICMPv6 type. See this code in // PctestIpv6Udp::Test for some commentary on a more graceful // way to deal with this whole issue of what type/codes we // want to take. if ((icmp6HeaderIn->icmp6_type != ICMP6_TIME_EXCEEDED) && (icmp6HeaderIn->icmp6_type != ICMP6_DST_UNREACH)) { IF_DEBUG(3, fprintf(stderr, "Ignoring ICMPv6 packet\n")); continue; } // Check the inner packet contained within the ICMP payload if (tr.replsize - (0 + sizeof(icmp6_hdr)) < sizeof(ip6_hdr)) { IF_DEBUG(3, fprintf(stderr, "Received incomplete inner IPv6 packet, %d bytes total\n", tr.replsize)); continue; } // Check for a valid (to us) IP header within the packet. // For "time exceeded" or "destination unreachable", this // header will be just past the ICMPv6 header. ipv6HeaderIn = (ip6_hdr *) ((char *) icmp6HeaderIn + sizeof(icmp6_hdr)); // Look for TCP header immediately following the inner // IPv6 header. See PctestIpv6Icmp::Test for commentary // on the lack of extension headers. if (ipv6HeaderIn->ip6_nxt != IPPROTO_TCP) { IF_DEBUG(3, fprintf(stderr, "Ignoring ICMPv6 packet for non-TCP\n")); continue; } // Check to see if we got enough for a complete TCP header. if (tr.replsize - (0 + sizeof(icmp6_hdr) + sizeof(ip6_hdr)) < sizeof(tcphdr)) { IF_DEBUG(3, fprintf(stderr, "Received incomplete inner TCP header, %d bytes total\n", tr.replsize)); continue; } // Align ICMP header template. tcphdr *tcpHeaderIn; tcpHeaderIn = (tcphdr *) (((char *) ipv6HeaderIn) + sizeof(ip6_hdr)); // XXX Check IPv6 addresses and port numbers to match what we // sent out. if ((memcmp(&(ipv6HeaderIn->ip6_src), &(originAddress), sizeof(struct in6_addr))) || (memcmp(&(ipv6HeaderIn->ip6_dst), &(targetAddress), sizeof(struct in6_addr))) || (tcpHeaderIn->th_dport != tcpHeader.th_dport) || (tcpHeaderIn->th_sport != tcpHeader.th_sport)) { IF_DEBUG(3, fprintf(stderr, "Ignoring ICMP packet referring to unknown TCP connection\n")); continue; } // Fill in return fields tr.icmpSourceAddress = new char[sizeof(in6_addr)]; memcpy(tr.icmpSourceAddress, &icmpSourceSocketAddress.sin6_addr, sizeof(in6_addr)); tr.icmpSourceAddressLength = sizeof(in6_addr); tr.result = GetAction(icmp6HeaderIn->icmp6_type, icmp6HeaderIn->icmp6_code); done = 1; } // See if we got back a TCP packet else if (ipv6HeaderIn->ip6_nxt == IPPROTO_TCP) { tcphdr *tcpHeaderIn; tcpHeaderIn = (tcphdr *) (((char *) ipv6HeaderIn) + sizeof(ip6_hdr)); if ((memcmp(&(ipv6HeaderIn->ip6_src), &(targetAddress), sizeof(struct in6_addr))) || (memcmp(&(ipv6HeaderIn->ip6_dst), &(originAddress), sizeof(struct in6_addr))) || (tcpHeaderIn->th_dport != tcpHeader.th_sport) || (tcpHeaderIn->th_sport != tcpHeader.th_dport)) { IF_DEBUG(3, fprintf(stderr, "Ignoring TCP packet for unknown connection\n")); continue; } // Fill in return fields tr.icmpSourceAddress = new char[sizeof(in6_addr)]; memcpy(tr.icmpSourceAddress, &(ipv6HeaderIn->ip6_src), sizeof(in6_addr)); tr.icmpSourceAddressLength = sizeof(in6_addr); tr.result = PctestActionValidLasthop; done = 1; } } else if (tr.tv.tv_sec >= Timeout) { IF_DEBUG(2, fprintf(stderr, "Timeout\n")); tr.icmpSourceAddress = new char[sizeof(in6_addr)]; memset(tr.icmpSourceAddress, 0, sizeof(in6_addr)); tr.icmpSourceAddressLength = sizeof(in6_addr); tr.result = PctestActionTimeout; done = 1; } } while (!done); rc = 0; exittest: delete [] tcpPayload; delete [] tcpPacket; free(icmp6PacketIn); return rc; } else { #endif /* HAVE_PCAP */ fprintf(stderr, "ipv6tcp probes require libpcap functionality"); exit(1); #ifdef HAVE_PCAP } #endif /* HAVE_PCAP */ } // // PctestIpv6Tcp::GetMinSize // // Input: None // // Output: Minimum packet size possible for this protocol (in return // value). // unsigned int PctestIpv6Tcp::GetMinSize() { return (sizeof(ip6_hdr) + sizeof(tcphdr) + 4); } // // PctestIpv6Tcp::GetAction // // Input: a test record // // Output: action code // // Figure out the meaning of a particular combination of ICMPv6 type // and code values. // PctestActionType PctestIpv6Tcp::GetAction(int icmp6_type, int icmp6_code) { if (icmp6_type == ICMP6_TIME_EXCEEDED) { return PctestActionValid; } else if (icmp6_type == ICMP6_ECHO_REPLY) { return PctestActionValidLasthop; } else if ((icmp6_type == ICMP6_DST_UNREACH) && (icmp6_code == ICMP6_DST_UNREACH_ADMIN)) { return PctestActionFiltered; } else { return PctestActionAbort; } }