C++/Win32: DNS Resolution
March 2, 2008
This is another mercifully short lesson. We are now going to fix the one gripe I had with our port scanner about 7 lessons back: it doesn’t resolve DNS.
This is going to be a little more work than just handing our domain name off to a function and getting an IP address in return. But not much more. We will first have to decide whether we have an IP address or a domain name, and only try to resolve it in the latter case. We will also need to bail out of scanning in the event we have a domain name that we can’t resolve. So our program logic needs to reflect these decisions.
Here’s how it will work:
1. User enters text in the IP Address field.
2. If it is an IP address, goScan
3. Else, attempt to resolve
4. If resolution succeeds, goScan the resolved address
Next question: where will all this take place? Right now, a click on the SCAN button causes a call to validateInput, and on successful return a thread is spawned. ThreadProc, remember, then calls our goScan function to perform the work.
So we’ll resolve DNS inside goScan. If resolution fails, goScan will immediately return FALSE, signaling ThreadProc to popup a message box announcing an error, and the thread will die. If we do this properly, we will prevent goScan from attempting to scan an IP address that is invalid.
First, let’s look at the new goScan function:
BOOL PortScannerWindow::goScan() {
SOCKET lhSocket; // Our socket
SOCKADDR_IN lSockAddr; // The SOCKADDR_IN structure containing IP and port info
WSADATA wsaData; // WinSock config data structure
int lConnect; // Connection status flag
int StartPort;
int EndPort;
char* IPAddress;
char* IPResolved;
BOOL bSuccess; // Success flag needed for GetDlgItemInt
char* buffer; // Buffer to hold updated info for the text output area
int nTxtLen;
// Get IP Address, Start Port and End Port
// Remember, we've already validated this input
IPAddress = GrabTextFromEdit(IDE_IPADDRESS);
StartPort = GetDlgItemInt(m_hWnd,IDE_STARTPORT,&bSuccess,FALSE);
EndPort = GetDlgItemInt(m_hWnd,IDE_ENDPORT, &bSuccess,FALSE);
// check if we have a name or an IP
int isIP = 1;
int i;
for(i=0; i < strlen(IPAddress); i++) {
if( IPAddress[i] == '.' ) continue;
else if( isalpha(IPAddress[i]) ) {
isIP = 0;
break;
}
}
// ResolveDNS if we have a name
if (!isIP) IPResolved = ResolveDNS(IPAddress);
else IPResolved = IPAddress;
// Bail out if resolution failed
if (IPResolved == NULL) return FALSE;
// Initialize WinSock
if(WSAStartup(MAKEWORD(2,0),&wsaData) != 0)
{
MessageBox(m_hWnd, "Socket Initialization Error.", "Socket Error",
MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}
// Allocate space for text output
buffer = (char*)malloc(100*sizeof(char));
// Loop through from StartPort to EndPort
for (int i=StartPort;i<=EndPort;i++) {
// Initialize Socket
lhSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(lhSocket == INVALID_SOCKET)
{
MessageBox(m_hWnd, "Invalid Socket.", "Socket Error",
MB_OK | MB_ICONEXCLAMATION);
}
// Zero out SOCKADDR_IN structure
memset(&lSockAddr,0, sizeof(lSockAddr));
// Fill SOCKADDR_IN structure
lSockAddr.sin_family = AF_INET;
lSockAddr.sin_port = htons(i);
lSockAddr.sin_addr.s_addr = inet_addr(IPResolved);
// Attempt connect
lConnect = connect(lhSocket,(SOCKADDR *)&lSockAddr,sizeof(SOCKADDR_IN));
if(lConnect != 0)
{
// On connect failure:
// Output result of this scan
if (m_bShowClosed) {
sprintf(buffer,"%s:%d - Closed\r\n",IPAddress,i);
nTxtLen = GetWindowTextLength(GetDlgItem(m_hWnd,IDT_OUTPUT));
SendMessage(GetDlgItem(m_hWnd,IDT_OUTPUT), EM_SETSEL, nTxtLen, nTxtLen); // move caret to end
SendMessage(GetDlgItem(m_hWnd,IDT_OUTPUT), EM_REPLACESEL, 0, (LPARAM)buffer); // append text
SendMessage(GetDlgItem(m_hWnd,IDT_OUTPUT), EM_SCROLLCARET, 0, 0); // scroll to caret
}
} else {
// On connect success:
// Output result of this scan
if (m_bShowOpen) {
sprintf(buffer,"%s:%d - Open\r\n",IPAddress,i);
nTxtLen = GetWindowTextLength(GetDlgItem(m_hWnd,IDT_OUTPUT));
SendMessage(GetDlgItem(m_hWnd,IDT_OUTPUT), EM_SETSEL, nTxtLen, nTxtLen); // move caret to end
SendMessage(GetDlgItem(m_hWnd,IDT_OUTPUT), EM_REPLACESEL, 0, (LPARAM)buffer); // append text
SendMessage(GetDlgItem(m_hWnd,IDT_OUTPUT), EM_SCROLLCARET, 0, 0); // scroll to caret
}
}
// Close the socket and clean up
closesocket(lhSocket);
}
WSACleanup();
return TRUE;
}
Wow. Long function. But we only need to look at what changed. First, we grab the input from the edit boxes. At this point, validateInput has already checked to make sure our start and end port make sense. We just need to make sure our IP address is good. First, we check to see if its an address or a name:
// check if we have a name or an IP
int isIP = 1;
int i;
for(i=0; i < strlen(IPAddress); i++) {
if ( IPAddress[i] == '.' ) continue;
else if( isalpha(IPAddress[i]) ) {
isIP = 0;
break;
}
}
We are going through our IP address string, character by character. If we spot an alpha character that is not ., we set the isIP flag to 1, signaling that this is not an address (because addresses should contain all numeric values except the . characters).
Now that we know what we have, we’ll need to resolve DNS on this address. We do this with a call to gethostbyaddr. Because gethostbyaddr requires winsock, there is a lot of extra code involved — initializing winsock, filling in a structure, and some other tasks. So we wrap all that code in a function called ResolveDNS. We will discuss that function a bit later, but for now you need to know that it takes a char * value (the domain name) and returns another char * (the resolved IP address). It will return NULL if the name can’t be resolved. So we’ll need to check for a NULL result after the call, and if we get it, we’ll bail out of goScan with a return value of FALSE, which will in turn cause ThreadProc to display a message box indicating the error. As you’ll see in a minute, ResolveDNS will also display text in our output box explaining what went wrong.
The rest of goScan is as it was - the scan is performed, and the results are outputted to the display area edit control.
So let’s look at ResolveDNS:
char* PortScannerWindow::ResolveDNS(char *unresolvedIP) {
WORD sockVersion;
WSADATA wsaData;
int nret;
char *IPResolved = "";
char *buffer;
sockVersion = MAKEWORD(2, 0);
// Initialize Winsock as before
WSAStartup(sockVersion, &wsaData);
// pointer to the hostent structure
LPHOSTENT Host;
Host = gethostbyname(unresolvedIP);
if (!Host) {
// Output failure message and return a NULL pointer signaling failure
buffer = (char*)malloc(100*sizeof(char));
sprintf(buffer,"Resolving %s:\r\nName resolution failed for %s.\r\n\r\n",unresolvedIP,unresolvedIP);
int nTxtLen = GetWindowTextLength(GetDlgItem(m_hWnd,IDT_OUTPUT));
SendMessage(GetDlgItem(m_hWnd,IDT_OUTPUT), EM_SETSEL, nTxtLen, nTxtLen); // move caret to end
SendMessage(GetDlgItem(m_hWnd,IDT_OUTPUT), EM_REPLACESEL, 0, (LPARAM)buffer); // append text
SendMessage(GetDlgItem(m_hWnd,IDT_OUTPUT), EM_SCROLLCARET, 0, 0); // scroll to caret
WSACleanup();
return NULL;
}
else {
// Set IPResolved to the IP address returned by gethostbyname
struct in_addr *address = (struct in_addr *) Host->h_addr_list[0];
IPResolved = inet_ntoa(*address);
// Output message stating that resolution was performed and successful.
buffer = (char*)malloc(100*sizeof(char));
sprintf(buffer,"Resolving %s:\r\n%s resolves to %s.\r\n\r\n",unresolvedIP,unresolvedIP,IPResolved);
int nTxtLen = GetWindowTextLength(GetDlgItem(m_hWnd,IDT_OUTPUT));
SendMessage(GetDlgItem(m_hWnd,IDT_OUTPUT), EM_SETSEL, nTxtLen, nTxtLen); // move caret to end
SendMessage(GetDlgItem(m_hWnd,IDT_OUTPUT), EM_REPLACESEL, 0, (LPARAM)buffer); // append text
SendMessage(GetDlgItem(m_hWnd,IDT_OUTPUT), EM_SCROLLCARET, 0, 0); // scroll to caret
}
return IPResolved;
WSACleanup();
}
First, we declare a few variables we’re going to need.
WORD sockVersion: will contain the version of winsock we’re requesting
WSDATA wsaData: will hold the winsock information returned to us when we call WSAStartup
char *IPResolved: will hold the resolved IP address
In order to let Windows know which version of winsock we want, we will need to pass it a word. No, we can’t just pass it 2.2 for some reason, we have to build a word (using MAKEWORD) out of 2 and 2.
Now we can call WSAStartup to initialize winsock, passing it the address of our WSADATA structure.
Next, we need to create a LPHOSTENT structure to hold the host information. This is a long pointer (LP) to a hostent structure. This structure will be assigned the return value of gethostbyaddr. For our purposes, we only need to know that the h_addr_list field will contain a char ** which points to a list of valid IP addresses for this host. Why a char *? Well, you might assume that the addresses in the list are strings, but they aren’t. So the char * makes no sense. But it is what it is, so we’ll have to cast it to a in_addr structure, which we will then convert to a string. Really roundabout, I’d say.
I’ll try to explain this process without making everyone’s head pop off from all the pointer confusion. We begin with a char ** in Host->h_addr_list. That’s a pointer to a pointer to a char. But since char * can be thought of as a string, we have a pointer to a string. It’s actually a list of strings, but we only need the first one, so we can pretend it’s only pointing to one string. If we dereference this **, we get a * — one string. We can either dereference it using the * operator, or we can dereference it by treating it like an array, appending [0] at the end of it. Either way we’ll get the same result.
So now we have Host->h_addr_list[0], which is a string containing our resolved IP address. Only, it’s not really a string (senseless!), it’s a pointer to an in_addr structure. An in_addr structure contains an IP address with each octet occupying its own field. So “127.0.0.1″ would be represented by four integer values — 127, 0, 0, and 1. In other words, the address is broken apart into four fields. The in_addr structure contains more than just the four octets of the address, but for now we only need to know this much. And in fact we don’t need to dwell on this, even, because we are going to call a function called inet_ntoa, which takes an in_addr and returns a string representation of the address. You’ll see and use a lot of functions that are named like inet_ntoa — ntoa means “number to alpha.”
In the end, our resolved IP address, in string form, is stored in the char * called IPResolved.
Here’s the whole conversion:
struct in_addr *address = (struct in_addr *) Host->h_addr_list[0]; IPResolved = inet_ntoa(*address);
There. Now that we’ve resolved our name, we can return it and let goScan do what it does best. Remember that ResolveDNS will return NULL if resolution fails. Remember to call WSACleanup when you are done with winsock, and that’s all there is to it.
BUG ALERT: Now that this functionality exists, I realized that previous builds didn’t allow text input in the IDE_IPADDRESS edit control to exceed the width of the control window. Adding the ES_AUTOHSCROLL style to the list of windows styles passed to CreateWindow fixes this.
Next: Dialogs.
Source Code:
win32tut_part12.zip [2.22MB zipped]
Additional Information:
· CoderSource.Net - Win32 Socket Client
· CodeProject - A Simple Windows Port Scanner
Further Reading:
· Nitty Gritty Windows Programming with C++ by Henning Hansen.
· Thinking in C++ by Bruce Eckel.




Comments
Got something to say?