上一篇文章用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。