I just have started to introduce myself into network programming using C++. So I started with Winsock. The code I made is compiled with MinGW and works perfectly!
As a beginner, the main purpose of code was to do what I wanted. Now it's time to go to next level!
The program will download a webpage source using a given socket and the website address/ip and port. After the connection to socks4 is established, the program sends a packet telling to associate with destination server. Now a "GET" request is send to sock and it return the response from server.
So far so good and we have the following code: main.cpp
#include <winsock.h>
#include <string>
#include <iostream>
#include "util.hpp"
using namespace std;
int main(void)
{
//Socks4 info
u_short sockPort = 1080;
std::string sockIp = "xx.xx.xx.xx";
//Destination info
u_short destPort = 80;
std::string destIPorURL = "checkip.dyndns.com";
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2,0), &wsaData)==0)
{
if (LOBYTE(wsaData.wVersion) < 2)
{
cout << "WSA Version error!";
return -1;
}
}
else
{
cout << "WSA Startup Failed";
return -1;
}
///////////////////////////////////////////////////////////////////////////////////////////
//Init socket for socks4
cout << "Initialize sock_addr for socks4 connection...";
sockaddr_in sock;
sock.sin_family = AF_INET; // host byte order
sock.sin_port = htons( sockPort ); // short, network byte order
if(!utils::getHostIP(sock.sin_addr.S_un.S_addr, sockIp)) // Write ip address in the right format
{
cout << "fail";
return -1;
}
cout << "done" << endl;
//Creating socket handler
cout << "Creating socket handler...";
SOCKET hSocketSock = INVALID_SOCKET;
if( (hSocketSock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET )
{
cout << "fail";
return -1;
}
cout << "done" << endl;
/////////////////////////////////////////////////////////////////////////////////////////////
//Init socket for destination server
cout << "Initialize sock_addr for destination server...";
sockaddr_in dest;
dest.sin_family = AF_INET;
dest.sin_port = htons( destPort );
if(!utils::getHostIP(dest.sin_addr.S_un.S_addr, destIPorURL)) // Write ip address in the right format
{
cout << "fail";
return -1;
}
memset( &(dest.sin_zero), '\0', 8 );
cout << "done" << endl;
////////////////////////////////////////////////////////////////////////////////////////////////
//Time to make connection to socks
cout << "Connecting to sock server...";
if(connect(hSocketSock, reinterpret_cast<sockaddr *>(&sock), sizeof(sock)) != 0)
{
cout << "failed";
return -1;
}
cout << "done" << endl;
//We are connected now to our socket!
//All we have to do is to send our desires.
//From now, the things differ from SOCKS4 to SOCKS5.
//So, the code below apply only for SOCKS4!
//So, we'll start with SOCKS4
//Documentation: http://www.openssh.com/txt/socks4.protocol
//The packet we have to build
// +----+----+----+----+----+----+----+----+----+----+....+----+
// | VN | CD | DSTPORT | DSTIP | USERID |NULL|
// +----+----+----+----+----+----+----+----+----+----+....+----+
//# of bytes: 1 1 2 4 variable 1
//This packet is meant to inform socks server who's the destination we want to communicate with.
char *initPacket = new char[9]; //9 because we don't use auth.
initPacket[0] = 4; //Sock version we use is 4
initPacket[1] = 1; //Connect code
memcpy(initPacket + 2, &dest.sin_port, 2); //Copy port into
memcpy(initPacket + 4, &dest.sin_addr.S_un.S_addr, 4); //Copy ip address
initPacket[8] = 0; //No username for auth provided
//Sending our packet!
cout << "Sending init packet to socks...";
if(send(hSocketSock, initPacket, 9, 0) == SOCKET_ERROR )
{
cout << "fail";
return -1;
}
cout << "done" << endl;
//Don't need init packet anymore as we have send it.
delete[] initPacket;
//We want a replay. This will tell us if the sock is able to communicate with destination
char replay[8]; //WHY 8? Because of table below :)
memset(&replay, 0, 8);
//Reading the response
cout << "Reading reaponse from sock...";
//if(recv(hSocketSock, replay, strlen((const char *)replay), 0) == SOCKET_ERROR)
if(recv(hSocketSock, replay, 8, 0) == SOCKET_ERROR)
{
"fail";
return -1;
}
cout << "done" << endl;
// Expected response format:
// +----+----+----+----+----+----+----+----+
// | VN | CD | DSTPORT | DSTIP |
// +----+----+----+----+----+----+----+----+
//# of bytes: 1 1 2 4
// VN is the version of the reply code and should be 0. CD is the result
// code with one of the following values:
//
// 90: request granted
// 91: request rejected or failed
// 92: request rejected becasue SOCKS server cannot connect to identd on the client
// 93: request rejected because the client program and identd report different user-ids.
//So, we have to check if replay is ok :)
cout << "Checking replay version code...";
if(replay[0] != 0)
{
cout << "fail - " << (int)replay[0];
return -1;
}
cout << "ok" << endl;
//Returned code: 90 = access granted
cout << "Checking replay returned code...";
if(replay[1] != 90)
{
cout << "failed - " << (int)replay[1];
return -1;
}
cout << (int)replay[1] << " - ok" << endl;
//Those being said, if everithing is ok, we can use @hSocketSock handler to send/recv data.
//Let's download the content of an webpage:
std::string headers = "GET " + destIPorURL + " HTTP/1.0\r\nHost: " + utils::getHostFromUrl(destIPorURL) + "\r\n\r\n";
//Send our request
cout << endl << "Sending custom request...";
int sendResult = send(hSocketSock, headers.c_str(), headers.length(), 0);
if(sendResult == SOCKET_ERROR)
{
cout << sendResult << "- failed";
return -1;
}
cout << "done!" << endl;
std::string fullResp = "";
char buffer[128];
cout << "Reading response from server...";
while(true)
{
int retval = recv(hSocketSock, buffer, strlen((const char *)buffer), 0);
if(retval == 0)
{
break;
}
else if(retval == SOCKET_ERROR)
{
cout << "failed";
return -1;
}
else
{
buffer[retval] = 0;
fullResp += buffer;
}
}
cout << "done" << endl;
cout << "What we have got:" << endl << fullResp;
//Make clean!
if(hSocketSock != INVALID_SOCKET)
{
closesocket(hSocketSock);
}
cout << endl;
return 0;
}
As you may have seen, the util.hpp contain some namespaces with the following functions used in program (and other useful functions):
std::string getHostFromUrl(std::string &url);
bool getHostIP(unsigned long &ipAddr, std::string urlOrHostnameOrIp);
Don't need a review for this. However I will post content of util.cpp and util.hpp in case someone wants to test.
util.hpp:
#include <winsock2.h>
#include <string>
#include <algorithm>
#include <vector>
namespace utils
{
std::string getHostFromUrl(std::string &url);
bool getHostIP(unsigned long &ipAddr, std::string urlOrHostnameOrIp);
namespace IPAddr
{
bool isValidIPv4(std::string &ip);
std::string reverseIpAddress(std::string ip);
std::string decimalToDottedIp(unsigned long ip);
unsigned long stripToDecimal(std::string &ip);
}
namespace strings
{
std::vector<std::string> split(std::string &s, char delim);
std::string removeSubstrs(std::string &source, std::string pattern);
}
};
util.cpp
#include <stdexcept>
#include <iostream>
#include <sstream>
#include <stdio.h>
#include "util.hpp"
#define cout std::cout
#define endl std::endl
/////////////////////////////////////////////////////////////////////////////////////
// _ _ _ _ _
// | \ | | __ _ _ __ ___ ___ ___ _ __ __ _ ___ ___ _ _| |_(_) |___
// | \| |/ _` | '_ ` _ \ / _ \/ __| '_ \ / _` |/ __/ _ \ | | | | __| | / __|
// | |\ | (_| | | | | | | __/\__ \ |_) | (_| | (_| __/ | |_| | |_| | \__ \
// |_| \_|\__,_|_| |_| |_|\___||___/ .__/ \__,_|\___\___| \__,_|\__|_|_|___/
// |_|
/////////////////////////////////////////////////////////////////////////////////////
bool utils::getHostIP(unsigned long &ipAddr, std::string url)
{
HOSTENT *pHostent;
std::string hostname = getHostFromUrl(url);
if( utils::IPAddr::isValidIPv4(hostname) )
{
//IP Address must be reversed in order to be compatible with sockAddr.sin_addr.S_un.S_addr
//example: 192.168.1.2 => 2.1.168.192
hostname = utils::IPAddr::reverseIpAddress(hostname);
ipAddr = utils::IPAddr::stripToDecimal(hostname);
return true;
}
if (!(pHostent = gethostbyname(hostname.c_str())))
{
return false;
}
if (pHostent->h_addr_list && pHostent->h_addr_list[0])
{
ipAddr = *reinterpret_cast<unsigned long *>(pHostent->h_addr_list[0]);
return true;
}
return false;
}
std::string utils::getHostFromUrl(std::string &url)
{
std::string urlcopy = url;
urlcopy = utils::strings::removeSubstrs(urlcopy, "http://");
urlcopy = utils::strings::removeSubstrs(urlcopy, "www.");
urlcopy = utils::strings::removeSubstrs(urlcopy, "https://");
urlcopy = urlcopy.substr(0, urlcopy.find("/"));
return urlcopy;
}
// ___ ____ _ _ _
// | _ _|| _ \ / \ __| | __| | _ __ ___ ___ ___
// | | | |_) | / _ \ / _` | / _` || '__|/ _ \/ __|/ __|
// | | | __/ / ___ \| (_| || (_| || | | __/\__ \\__ \
// |___||_| /_/ \_\\__,_| \__,_||_| \___||___/|___/
bool utils::IPAddr::isValidIPv4(std::string &ipv4)
{
const std::string address = ipv4;
std::vector<std::string> arr;
int k = 0;
arr.push_back(std::string());
for (std::string::const_iterator i = address.begin(); i != address.end(); ++i)
{
if (*i == '.')
{
++k;
arr.push_back(std::string());
if (k == 4)
{
return false;
}
continue;
}
if (*i >= '0' && *i <= '9')
{
arr[k] += *i;
}
else
{
return false;
}
if (arr[k].size() > 3)
{
return false;
}
}
if (k != 3)
{
return false;
}
for (int i = 0; i != 4; ++i)
{
const char* nPtr = arr[i].c_str();
char* endPtr = 0;
const unsigned long a = ::strtoul(nPtr, &endPtr, 10);
if (nPtr == endPtr)
{
return false;
}
if (a > 255)
{
return false;
}
}
return true;
}
std::string utils::IPAddr::reverseIpAddress(std::string ip)
{
std::vector<std::string> octeti = utils::strings::split(ip, '.');
return (octeti[3] + "." + octeti[2] + "." + octeti[1] + "." + octeti[0]);
}
unsigned long utils::IPAddr::stripToDecimal(std::string &ip)
{
unsigned long a,b,c,d,base10IP;
sscanf(ip.c_str(), "%lu.%lu.%lu.%lu", &a, &b, &c, &d);
// Do calculations to convert IP to base 10
a *= 16777216;
b *= 65536;
c *= 256;
base10IP = a + b + c + d;
return base10IP;
}
std::string utils::IPAddr::decimalToDottedIp(unsigned long ipAddr)
{
unsigned short a, b, c, d;
std::ostringstream os ;
std::string ip = "";
a = (ipAddr & (0xff << 24)) >> 24;
b = (ipAddr & (0xff << 16)) >> 16;
c = (ipAddr & (0xff << 8)) >> 8;
d = ipAddr & 0xff;
os << d << "." << c << "." << b << "." << a;
ip = os.str();
return ip;
}
// ____ _ _
// / ___| | |_ _ __ (_) _ __ __ _ ___
// \___ \ | __|| '__|| || '_ \ / _` |/ __|
// ___) || |_ | | | || | | || (_| |\__ \
// |____/ \__||_| |_||_| |_| \__, ||___/
// |___/
std::vector<std::string> utils::strings::split(std::string &s, char delim)
{
std::vector<std::string> elems;
std::stringstream ss;
ss.str(s);
std::string item;
while (std::getline(ss, item, delim))
{
elems.push_back(item);
}
return elems;
}
std::string utils::strings::removeSubstrs(std::string &input, std::string pattern)
{
std::string source = input;
std::string::size_type n = pattern.length();
for (std::string::size_type i = source.find(pattern); i != std::string::npos; i = source.find(pattern))
{
source.erase(i, n);
}
return source;
}
The thing is that I have some plans for some projects which uses sockets and I want to get a good start!
I think that the code I wrote is not so easy to understand. For me it's easy but for the others my not!
Hope it is not to soon to say that but I want to write some professional code. So, be as critical as possible!
PS: Not sure if it helps, but the output looks like the following: