上一篇文章用telnet发送电子邮件提到通过nslookup获取域名的MX记录。
如果在程序里实现SMTP协议就不可能依赖于nslookup程序了。写一个与DNS通信获取MX记录的C++类。
效果如图所示:
DNS通信协议请参见RFC1035
实现代码如下:
#include <stdio.h>
#ifdef __WIN32__
#include <winsock.h>
#include <wininet.h>
#define SHUT_RDWR SD_BOTH
#else
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#define closesocket close
#endif
#include <assert.h>
#include <string.h>
#include <string>
#include <vector>
using namespace std;
struct DnsMxRecord {
int preference;
string address;
};
class DnsQuery
{
private:
int socketHandle;
string nameServer;
char buffer[512+4];
int bufferPos;
int errorCode;
vector<DnsMxRecord> mxRecords;
string parseName(int &pos) {
string name;
char temp[256];
while(buffer[pos]) {
int length = (unsigned char)buffer[pos ++];
if(length == 0xC0) {
int newPos = (unsigned char)buffer[pos ++];
if(name.empty())
name = parseName(newPos);
else
name = name + "." + parseName(newPos);
break;
} else {
memcpy(temp, &buffer[pos], length);
pos += length;
temp[length] = '\0';
if(name.empty())
name = temp;
else
name = name + "." + temp;
}
}
return name;
}
public:
DnsQuery() {
socketHandle = -1;
}
~DnsQuery() {
if(socketHandle != -1)
closesocket(socketHandle);
}
void setNameServer(string ns) {
this->nameServer = ns;
}
string getErrorString() {
static char errors[][32] = {
"No error.", "Incorrect message format.", "DNS error.",
"Domain name not found.", "Unimplemented command.", "Denied."
};
if(errorCode < 6)
return errors[errorCode];
else
return "Unknown error.";
}
bool connect() {
/* Create a UDP socket */
socketHandle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
assert(socketHandle != -1);
/* Connect to the name server */
struct sockaddr_in addr = {0,};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(nameServer.c_str());
addr.sin_port = htons(53);
return (0 == ::connect(socketHandle, (sockaddr*)&addr, sizeof(addr)));
}
bool queryMx(string name) {
printf("queryMx: %s\n", name.c_str());
errorCode = 0;
/* Initialize MX query header */
static const char queryHeader[] = {0x00,0x01,0x01,0x00,0x00,0x01,
0x00,0x00,0x00,0x00,0x00,0x00
};
memcpy(buffer, queryHeader, sizeof(queryHeader));
bufferPos = sizeof(queryHeader);
/* put domain name */
for(int next, current = 0; current<name.length(); ) {
/* divide by dot */
next = name.find('.', current);
if(next == -1)
next = name.length();
/* length of part */
int length = next - current;
buffer[bufferPos ++] = length;
memcpy(buffer + bufferPos, name.c_str() + current, length);
bufferPos += length;
current = next + 1;
}
buffer[bufferPos ++] = '\0';
/* query type and class */
*((unsigned int*)&buffer[bufferPos]) = 0x01000F00;
bufferPos += sizeof(unsigned int);
/* send & wait for reply */
send(this->socketHandle, buffer, bufferPos, 0);
int ret = recv(this->socketHandle, buffer, 512, 0);
if(ret < bufferPos) {
errorCode = 2;
return false;
}
if((errorCode = buffer[3] & 0xf) != 0)
return false;
/* Parse MX record data */
mxRecords.clear();
int mxCount = ntohs(*((unsigned short*)&buffer[6]));
for(int i=0; i<mxCount; i++) {
string domain = parseName(bufferPos);
int type = ntohs(*((unsigned short*)&buffer[bufferPos]));
/* pass class & ttl */
bufferPos += 8;
int rdLength = ntohs(*((unsigned short*)&buffer[bufferPos]));
bufferPos += 2;
if(type == 0xf) {
DnsMxRecord item;
item.preference = ntohs(*((unsigned short*)&buffer[bufferPos]));
bufferPos += 2;
item.address = parseName(bufferPos);
mxRecords.push_back(item);
printf("found MX record: %s, preference: %d\n", item.address.c_str(), item.preference);
} else {
bufferPos += rdLength;
}
}
return true;
}
};
int main(int argc, char **argv)
{
#ifdef __WIN32__
WSADATA wsaData;
assert(WSAStartup(MAKEWORD(2,1), static_cast<WSADATA*>(&wsaData)) == 0);
#endif
DnsQuery query;
query.setNameServer("8.8.8.8");
if(!query.connect()) {
perror("failed to connect to name server.");
_exit(1);
}
if(!query.queryMx("gmail.com")) {
printf("failed to get mx records: %s\n", query.getErrorString().c_str());
_exit(1);
}
return 0;
}

有一个疑问:为什么要开放connect?
开放connect?你是说不用connect?
我觉得没有必要公开connect,类私有调用就好拉~你觉得呢?
嗯,你说得有道理~!
感觉我这样写,是让connect这个步骤显得累赘了。
哈哈,向你学习了!
小虾写c++!
膜拜C++大牛iceboy!
那个麻烦问一下是在windows下的实现吗
跨平台的,哈哈!!!图是windows的:)
可你的程序在VS08里跑不起来。。很多库都是linux下的
在mingw下编译,别用vs。