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