DNS Relay
Dear All, Most likely a ticket is not the right way to convey this info, but I haven't been able to find any other way. I had several apps leaking DNS packets so I wrote this simple dnsToTor relay (See below) that if you are interested I could release to the public domain. I needed it on a window machine so it is written specifically for window; however, porting should be trivial. On a Win machine, once you have the executable running, it is sufficient to change in the "Internet Protocol (TCP/IP) Properties the DNS setting to "Use the following DNS server address" and setting as IP "127.0.0.1". At this point all apps leaking DNS request will send their DNS request to the relay that will send a SOCKS5 Request to Tor and then generate a reply to the resolver providing the info returned by Tor. This is completely transparent for the apps that doesn't realize that the DNS query is handled by Tor. The relay only handles the basic DNS query (Type A Ipv4), but it should be sufficient for the most common usage scenarios. Hope it helps F. /*Copyright F.Ruta 2011 */ #define WIN32_LEAN_AND_MEAN #include <winsock2.h> #include <windows.h> #include <ws2tcpip.h> #include <mstcpip.h> #include <iostream> #include <string.h> #include <stdint.h> using namespace std; #define DEBUG 0 #define BUF_LEN 1024 #define MAX_QUERY 512 #define MAX_NAME 512 //A Query name is in the form of length octet+domain segment //so a query for www.mydomain.com would be encoded as [3]www[8]mydomain[3]com int convertName( uint8_t const * const query, int8_t * const name, int32_t maxLen ) { int i,j,len, totalLen; i = 0; j = 0; do { len = (uint8_t)query[i]; if ( len > 0 ) { totalLen += len; if ((j + len) < maxLen ) { memcpy(&(name[j]), &(query[i+1]), len ); i += len+1; j += len; name[j++] = '.'; } else { return 0; } } } while (len > 0); name[--j] = '\0'; return strlen((const char *)name); } int parseDnsQuery( uint8_t const * const packet, uint16_t *qid, uint8_t *rec, uint8_t * const query, int32_t *queryLen ) { uint16_t sv; uint16_t qdcount; uint8_t qr; uint8_t opcode; uint8_t len; int i; memcpy( &sv, &packet[0], 2 ); *qid = ntohs(sv); qr = (packet[2]&0x80)>>7; opcode = (packet[2]&0x78)>>3; *rec = (packet[2]&0x01); //Make sure it is a standard query if (( 0 == qr ) && (0 == opcode)) { memcpy( &sv, &packet[4], 2 ); qdcount = ntohs(sv); i = 12; len = 0; //store the query while( packet[i++] != '\0' ){len++;} if ( len < *queryLen ) { memcpy(query, &(packet[12]), len+1 ); *queryLen = len+1; } else { *queryLen = 0; } memcpy( &sv, &packet[12+len+1], 2 ); if ( 1 != ntohs(sv)) { //Type A, IPv4 address cout << "DNS request is not for type A, IPv4 address" <<endl; return -1; } memcpy( &sv, &packet[12+len+1+2], 2 ); if ( 1 != ntohs(sv)) { //Class IN, Internet cout << "DNS request is not for class IN" <<endl; return -1; } return len; } else { return -1; } return 0; } int parseDns( uint8_t const * const packet ) { uint16_t sv; uint8_t cv; uint32_t lv; uint16_t len; uint16_t type; uint16_t questions, answers, authority, additional; int i,j,k; memcpy( &sv, &packet[0], 2 ); cout << "ID = "<< (int)ntohs(sv) <<endl; cv = (packet[2]&0x80)>>7; cout << "QR = "<< (int)cv << " "; switch (cv) { case 0 : { cout << "(Query)"; break; } case 1 : { cout << "(Response)"; break; } default: { break; } } cout << endl; cv = (packet[2]&0x78)>>3; cout << "Opcode = "<< (int)cv <<" "; switch (cv) { case 0 : { cout << "QUERY, Standard query"<<endl; break; } case 1 : { cout << "IQUERY, Inverse query"<<endl; break; } case 2 : { cout << "STATUS, Server status request"<<endl; break; } case 3 : { cout << "NA"<<endl; break; } case 4 : { cout << "Notify"<<endl; break; } case 5 : { cout << "Update"<<endl; break; } default: { cout << "Reserved"<<endl; break; } } cv = (packet[2]&0x04)>>2; cout << "AA= "<< (int)cv; switch (cv) { case 0 : { cout << "(Not authoritative)"; break; } case 1 : { cout << "(Authoritative)"; break; } default: { break; } } cv = (packet[2]&0x02)>>1; cout << " TC= "<< (int)cv; switch (cv) { case 0 : { cout << "(Not truncated)"; break; } case 1 : { cout << "(Truncated)"; break; } default: { break; } } cv = (packet[2]&0x01); cout << " RD= "<< (int)cv; switch (cv) { case 0 : { cout << "(No Recursion)"; break; } case 1 : { cout << "(Recursion)"; break; } default: { break; } } cv = (packet[3]&0x80)>>7; cout << " RA= "<< (int)cv; switch (cv) { case 0 : { cout << "(Recursion Not Avail)"; break; } case 1 : { cout << "(Recursion Avail)"; break; } default: { break; } } cv = (packet[3]&0x40)>>6; cout << " Z= "<< (int)cv; cv = (packet[2]&0x20)>>5; cout << " AD= "<< (int)cv; switch (cv) { case 0 : { cout << "(Not Auth)"; break; } case 1 : { cout << "(Auth)"; break; } default: { break; } } cv = (packet[2]&0x10)>>4; cout << " CD= "<< (int)cv; switch (cv) { case 0 : { cout << "(Not Check)"; break; } case 1 : { cout << "(Check)"; break; } default: { break; } } cout << endl; cv = (packet[3]&0x0F); cout << "RCODE= "<< (int)cv << " "; switch (cv) { case 0 : { cout << "No Error"<<endl; break; } case 1 : { cout << "Format Error"<<endl; break; } case 2 : { cout << "Server failure"<<endl; break; } case 3 : { cout << "Name Error"<<endl; break; } case 4 : { cout << "Not Implemented"<<endl; break; } case 5 : { cout << "Refused"<<endl; break; } default: { cout << "Undef"<<endl; break; } } memcpy( &sv, &packet[4], 2 ); questions = ntohs(sv); cout << "QDCOUNT = "<< questions <<endl; memcpy( &sv, &packet[6], 2 ); answers = ntohs(sv); cout << "ANCOUNT = "<< answers <<endl; memcpy( &sv, &packet[8], 2 ); authority = ntohs(sv); cout << "NSCOUNT = "<< authority <<endl; memcpy( &sv, &packet[10], 2 ); additional = ntohs(sv); cout << "ARCOUNT = "<< additional <<endl; i = 12; j = 12; uint8_t buf[BUF_LEN]; int8_t name[MAX_NAME]; cout << "Questions : " <<endl; for (k=0; k < questions;k++ ) { len = 0; j = i; while( packet[j++] != '\0' ){len++;} memcpy(buf, &(packet[i]), len+1 ); convertName(buf, name, MAX_NAME-1); cout << k << ") Name: " << name << " "; i += len+1; memcpy( &sv, &packet[i], 2 ); cout << "Type = "<< ntohs(sv) <<" "; i += 2; memcpy( &sv, &packet[i], 2 ); cout << "Class = "<< ntohs(sv) <<endl; i += 2; } cout << "Answers : " <<endl; for (k=0; k < answers;k++ ) { len = 0; j = i; while( packet[j++] != '\0' ){len++;} memcpy(buf, &(packet[i]), len+1 ); buf[len+2] = '\0'; //redundant convertName(buf, name, MAX_NAME-1); cout << k << ") Name: " << name << " "; i += len+1; memcpy( &sv, &packet[i], 2 ); type = ntohs(sv); cout << "Type = "<< ntohs(sv) <<" "; i += 2; memcpy( &sv, &packet[i], 2 ); cout << "Class = "<< ntohs(sv) <<" "; i += 2; memcpy( &lv, &packet[i], 4 ); cout << "TTL = "<< ntohl(lv) <<endl; i += 4; memcpy( &sv, &packet[i], 2 ); len = ntohs(sv); cout << " RdataLen = "<< ntohs(sv) <<" "; i += 2; memcpy( buf, &packet[i], len ); i += len; switch (type) { case 1: //A records { cout << "Data: " << (unsigned int)((unsigned char)buf[0])<<"."<< (unsigned int)((unsigned char)buf[1])<<"."<< (unsigned int)((unsigned char)buf[2])<<"."<< (unsigned int)((unsigned char)buf[3]) << endl; break; } default : { cout << "Data: "; for(j=0;j<len;j++) cout << hex <<buf[j]; cout << endl; break; } } } return 0; } int createDnsAnswer( uint16_t status, uint8_t const * const query, uint16_t qid, int8_t rec, uint32_t addr, uint8_t * const packet, int32_t maxLen ) { uint16_t sv; uint8_t cv; uint32_t lv; int i,len; i = 0; if ( (12 + (int)strlen((const char *)query)+1 + 10 + 4) > maxLen ) { cerr << "Insufficient buffer space to build DNS answer"<<endl; return -1; } sv = htons(qid); memcpy( &packet[i], &sv, 2 ); //ID i+= 2; cv = 0; cv |= 1 << 7; //Response cv |= 1 << 2; //This is an authoritative answer cv |= rec; //Recursion was requested packet[i++] = cv; cv = 0; cv |= rec << 7; //Recursion is available cv |= 1 << 5; //Answer is authenticated if ( 0 != status ) { cv |= 0x02; //Hardcode error to Server Failure } packet[i++] = cv; sv = htons(1); //1 question was present memcpy( &packet[i], &sv, 2 ); i+=2; sv = htons(1); //Only 1 answer is present memcpy( &packet[i], &sv, 2 ); i+=2; sv = 0; //Zero Authority Responses are present memcpy( &packet[i], &sv, 2 ); i+=2; sv = 0; //Zero Additional Responses are present memcpy( &packet[i], &sv, 2 ); i+=2; //Populate the Queries portion len = strlen((const char *)query);//DNS query ends with a trailing zero memcpy( &packet[i], query, len+1 );//Name i+=len+1; sv = htons(1); //Type A memcpy( &packet[i], &sv, 2 ); i+= 2; sv = htons(1); //Class IN memcpy( &packet[i], &sv, 2 ); i+= 2; //Populate the Answers portion len = strlen((const char *)query);//DNS query ends with a trailing zero memcpy( &packet[i], query, len+1 );//Name i+=len+1; sv = htons(1); //Type A memcpy( &packet[i], &sv, 2 ); i+= 2; sv = htons(1); //Class IN memcpy( &packet[i], &sv, 2 ); i+= 2; lv = htonl(60); //TTL Hardcoded to 1 min memcpy( &packet[i], &lv, 4 ); i+= 4; //As this is a response only for class A query, the rdata len is 4 sv = htons(4); //RDLENGTH memcpy( &packet[i], &sv, 2 ); i+= 2; memcpy( &packet[i], &addr, sizeof(addr)); i+= sizeof(addr); return i; } int connectToSockProxy( int8_t const * const address, int8_t const * const port, SOCKET *outSocket ) { int status; uint8_t buf[BUF_LEN]; ADDRINFO hints, *addrInfo; SOCKET sock; int optval; if ( INVALID_SOCKET == *outSocket ) { if ( DEBUG ) cout << "Initializing TOR socket" << endl; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET;//PF_UNSPEC hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICHOST; hints.ai_protocol = IPPROTO_TCP; status = getaddrinfo((char *)address, (char *)port, &hints, &addrInfo); if ( 0 != status ) { cerr << "getaddrinfo: " << WSAGetLastError() << endl; WSACleanup(); return -1; } sock = socket(addrInfo->ai_family, addrInfo->ai_socktype, addrInfo->ai_protocol); if ( INVALID_SOCKET == sock ) { cerr << "socket -TOR client: " << WSAGetLastError() << endl; freeaddrinfo(addrInfo); return -1; } //Set SO_REUSEADDR to true (1): optval = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof optval); if ( DEBUG ) cout << "Connect to the TOR proxy" << endl; status = connect(sock, addrInfo->ai_addr, (int)addrInfo->ai_addrlen); if ( SOCKET_ERROR == status ) { cerr << "connect: " << WSAGetLastError() << endl; freeaddrinfo(addrInfo); closesocket(sock); return -1; } freeaddrinfo(addrInfo); } else { if ( DEBUG ) cout << "Reusing previous socket " << endl; sock = *outSocket; } // Send the initial SOCKS 5 connect request buf[0] = 0x5; //Ver 5 buf[1] = 0x1; //1 Method buf[2] = 0x0; //No authentication if ( DEBUG ) cout << "Sending connect request to the TOR Proxy" << endl; status = send(sock, (const char *) buf, (int) 3, 0); if ( SOCKET_ERROR == status ) { cerr << "send -TOR client: " << WSAGetLastError() << endl; closesocket(sock); return -1; } status = recv(sock, (char *) buf, BUF_LEN, 0); if ( SOCKET_ERROR == status ) { if ( 0x5 != buf[0] ) { cerr << "Received invalid header in SOCKS-5 Connection Established response" << endl; closesocket(sock); return -1; } else if ( 0xFF == buf[1] ) { cerr << "Received invalid auth method in Connection Established response" << endl; closesocket(sock); return -1; } } else if (0 == status) { cerr << "Connection closed" << endl; closesocket(sock); return -1; } if ( DEBUG ) cout << "Received connection ack from TOR Proxy" << endl; *outSocket = sock; return 0; } int bindToDnsPort( int8_t const * const address, int8_t const * const port, SOCKET *outSocket ) { int status; ADDRINFO hints, *addrInfo; SOCKET sock; int optval; *outSocket = INVALID_SOCKET; //Bind to localhost and default DNS port memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET;//PF_UNSPEC hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_NUMERICHOST | AI_PASSIVE; hints.ai_protocol = IPPROTO_UDP; status = getaddrinfo((char *)address, (char *)port, &hints, &addrInfo); if ( 0 != status ) { cerr << "getaddrinfo -Local DNS Listener: " << WSAGetLastError() << endl; freeaddrinfo(addrInfo); return -1; } sock = socket(addrInfo->ai_family, addrInfo->ai_socktype, addrInfo->ai_protocol);//PF_UNSPEC if ( INVALID_SOCKET == sock ) { cerr << "socket -Local DNS Listener: " << WSAGetLastError() << endl; freeaddrinfo(addrInfo); return -1; } //Set SO_REUSEADDR to true (1): optval = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof optval); status = bind( sock, addrInfo->ai_addr, (int)addrInfo->ai_addrlen); if ( SOCKET_ERROR == status ) { cerr << "bind -Local DNS Listener: " << WSAGetLastError() << endl; freeaddrinfo(addrInfo); closesocket(sock); return -1; } freeaddrinfo(addrInfo); *outSocket = sock; return 0; } int main(int argc, char* argv[]) { WSADATA wsaData; int status; struct sockaddr_in from; int len, queryLen, nameLen; uint32_t addr; SOCKET sockIn = INVALID_SOCKET; SOCKET sockOut = INVALID_SOCKET; int n; FD_SET fds; int8_t name[MAX_NAME]; uint8_t query[MAX_QUERY]; uint16_t qid; uint8_t recursive; uint8_t buf[BUF_LEN+1]; struct timeval tv; const char *localAddress = "127.0.0.1"; const char *dnsPort = "53"; //53 - DNS char socksPort[6] = {'9','0','5','0','\0'}; //SOCKS 5 Proxy - 9050 if ( 3 < argc ) { cerr << "Invalid number of arguments" << endl; cerr << argv[0] << " [-p proxy-port]" << endl; return -1; } else if ( 3 == argc ) { if (!strncmp("-p", argv[1], strlen("-p"))) { if ( 0 != atoi(argv[2])) { (void)strncpy(socksPort, argv[2], sizeof(socksPort)-1); } else { cerr << "Invalid argument:" << argv[2] << endl; cerr << argv[0] << " [-p proxy-port]" << endl; return -1; } } else { cerr << "Invalid argument:" << argv[1] << endl; cerr << argv[0] << " [-p proxy-port]" << endl; return -1; } } else if ( 2 == argc ) { cerr << "Invalid argument:" << argv[1] << endl; cerr << argv[0] << " [-p proxy-port]" << endl; return -1; } status = WSAStartup(MAKEWORD(2, 2), &wsaData); if ( 0 != status ) { cerr << "Winsock dll not found:"<< status <<endl; return -1; } if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2 ) { cerr << "Winsock version 2.2 not supported"<<endl; WSACleanup(); return -1; } status = bindToDnsPort((int8_t *)localAddress, (int8_t *)dnsPort, &sockIn ); if ( 0 != status ) { cerr << "Unable to bind as a local DNS server" << endl; WSACleanup(); return -1; } do { FD_ZERO(&fds); FD_SET(sockIn, &fds); //FD_SET(sockOut, &fds); n = sockIn +1; status = select(n, &fds, NULL, NULL, NULL); if ( SOCKET_ERROR == status ) { cerr << "select: " << WSAGetLastError() << endl; closesocket(sockIn);closesocket(sockOut); WSACleanup(); return -1; } if (FD_ISSET(sockIn, &fds)) { //Received a DNS Query if ( DEBUG ) cout << "Received a DNS request" <<endl; len = sizeof(struct sockaddr_in); status = recvfrom(sockIn, (char *) buf, BUF_LEN, 0, (struct sockaddr *) &from, &len ); if ( SOCKET_ERROR == status ) { if ( 10054 != WSAGetLastError()) { //Ignore reset connection cerr << "recvfrom -Local DNS Listener: " << WSAGetLastError() << endl; } status = 0; continue; } if ( DEBUG ) parseDns( buf); queryLen = MAX_QUERY-1; status = parseDnsQuery( buf, &qid, &recursive, query, &queryLen ); if ( 0 > status ) { status = 0; cerr << "Unsupported request" << endl; continue; } if ( 0 < queryLen ) { nameLen = convertName( query, name, MAX_NAME-1); if ( UCHAR_MAX < nameLen ) { cerr << "Queried Domain name length exceeds max length" << endl; continue; } if (DEBUG) cout << "Connecting to the TOR proxy" << endl; //Connect to the SOCKS5 Server at localhost and Tor-Socks proxy port status = connectToSockProxy((int8_t *)localAddress, (int8_t *)socksPort, &sockOut ); if ( 0 != status ) { cerr << "Unable to connect to the Tor server" << endl; continue; } // Send the UDP Resolve request buf[0] = 0x05; //Ver 5 buf[1] = 0xF0; //Cmd Resolve -- TOR Extension buf[2] = 0x00; //Reserved buf[3] = 0x03; //ATYP = DomainName buf[4] = (uint8_t) nameLen; memcpy( &(buf[5]), name, nameLen); buf[5+nameLen] = 0; //Still need to add a fake port buf[5+nameLen+1] = 0; len = nameLen + 5 + 2; if (DEBUG) cout << "Sending Resolve for [" << name << "] " << len << " to the TOR proxy" << endl; FD_ZERO(&fds); FD_SET(sockOut, &fds); //Forward the DNS request to the Socks proxy status = send(sockOut, (char *) buf, len, 0); if ( SOCKET_ERROR == status ) { cerr << "send -TOR client: " << WSAGetLastError() << endl; cerr <<"Unable to forward request" << endl; status = 0; continue; } //There's no way to correlate the TOR response with a request, we need to wait //for the answer to this request before handling any new query. //Wait for response/requests on the Tor sockets if ( DEBUG ) cout << "Waiting for TOR response" <<endl; tv.tv_sec = 10; tv.tv_usec = 500000; n = sockOut +1; status = select(n, &fds, NULL, NULL, &tv ); if ( SOCKET_ERROR == status ) { cerr << "select: " << WSAGetLastError() << endl; closesocket(sockIn);closesocket(sockOut); WSACleanup(); return -1; } else if ( 0 == status ) { cerr << "No response received from Tor Proxy" << endl; closesocket(sockOut); sockOut = INVALID_SOCKET; continue; } if (FD_ISSET(sockOut, &fds)) { if ( DEBUG ) cout << "Received data from TOR " << endl; //Receive a response from the TOR proxy status = recv(sockOut, (char *) buf, BUF_LEN, 0 ); if ( SOCKET_ERROR == status ) { cerr << "recv -TOR client: " << WSAGetLastError() << endl; status = 0; closesocket(sockOut); sockOut = INVALID_SOCKET; continue; } status = 0; if ( 0x05 != buf[0] ) { status = 1; cerr <<"Invalid Header in Tor Response" << endl; } else if ( 0x00 != buf[1] ) { status = 1; cerr <<"The Tor Proxy returned an error:" <<(int)((unsigned char)buf[1]) << endl; } else if ( 0x01 != buf[3] ) { status = 1; cerr <<"The Tor Proxy returned an invalid ATYP:" <<(int)((unsigned char)buf[3]) << endl; } memcpy( &addr, &buf[4], sizeof(addr)); if ( DEBUG ) cout << "Creating a DNS response " << endl; len = createDnsAnswer((uint16_t)status, query, qid, recursive, addr, buf, BUF_LEN); if ( DEBUG) parseDns( buf ); //Forward the DNS response sendto( sockIn, (const char *) buf, len, 0, (struct sockaddr *) &from, sizeof(struct sockaddr_in)); //Disconnect from the Socks proxy if (DEBUG) cout << "Disconnecting from the TOR proxy" <<endl; //status = shutdown(sockOut, SD_SEND); if ( SOCKET_ERROR == status ) { cerr << "shutdown: " << WSAGetLastError() << endl; } if (INVALID_SOCKET != sockOut) closesocket(sockOut); sockOut = INVALID_SOCKET; } } /* } else if (FD_ISSET(sockOut, &fds)) { //This should not happen unless the Tor proxy die status = recv(sockOut, (char *)buf, BUF_LEN, 0 ); if ( SOCKET_ERROR == status ) { cerr << "Connection with Tor server dropped"; status = 0; continue; } //discard } */ } } while (status >= 0); // shutdown the send half of the connection since no more data will be sent status = shutdown(sockOut, SD_SEND); if ( SOCKET_ERROR == status ) { cerr << "shutdown: " << WSAGetLastError() << endl; } if (INVALID_SOCKET != sockIn) closesocket(sockIn); if (INVALID_SOCKET != sockOut) closesocket(sockOut); WSACleanup(); return 0; } **Trac**: **Username**: F_Ruta
issue