[tor-bugs] #2602 [Tor Relay]: DNS Relay
Tor Bug Tracker & Wiki
torproject-admin at torproject.org
Tue Feb 22 05:41:15 UTC 2011
#2602: DNS Relay
-------------------------+--------------------------------------------------
Reporter: F_Ruta | Owner:
Type: enhancement | Status: new
Priority: normal | Milestone:
Component: Tor Relay | Version:
Keywords: DNS relay | Parent:
Points: | Actualpoints:
-------------------------+--------------------------------------------------
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;
}
--
Ticket URL: <https://trac.torproject.org/projects/tor/ticket/2602>
Tor Bug Tracker & Wiki <https://trac.torproject.org/>
The Tor Project: anonymity online
More information about the tor-bugs
mailing list