+---------------------+ | 报文头 | +---------------------+ | 问题 | 向服务器提出的查询部分 +---------------------+ | 回答 | 服务器回复的资源记录 +---------------------+ | 授权 | 权威的资源记录 +---------------------+ | 格外的 | 格外的资源记录 +---------------------+除了报文头是固定的12字节外,其他每一部分的长度均为不定字节数。
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ID | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |QR| Opcode |AA|TC|RD|RA| Z | RCODE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QDCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ANCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | NSCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ARCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
好家伙,是什么鬼画符!
其中最上边是位的数字标识,0-15(注意,后边的10-15写成上下的形式了,一开始我楞没看懂)。
接下来是:
ID:占16位,2个字节。此报文的编号,由客户端指定。DNS回复时带上此标识,以指示处理的对应请应请求。
QR:占1位,1/8字节。0代表查询,1代表DNS回复
Opcode:占4位,1/2字节。指示查询种类:0:标准查询;1:反向查询;2:服务器状态查询;3-15:未使用。
AA:占1位,1/8字节。是否权威回复。
TC:占1位,1/8字节。因为一个UDP报文为512字节,所以该位指示是否截掉超过的部分。
RD:占1位,1/8字节。此位在查询中指定,回复时相同。设置为1指示服务器进行递归查询。
RA:占1位,1/8字节。由DNS回复返回指定,说明DNS服务器是否支持递归查询。
Z:占3位,3/8字节。保留字段,必须设置为0。
RCODE:占4位,1/2字节。由回复时指定的返回码:0:无差错;1:格式错;2:DNS出错;3:域名不存在;4:DNS不支持这类查询;5:DNS拒绝查询;6-15:保留字段。
QDCOUNT:占16位,2字节。一个无符号数指示查询记录的个数。
ANCOUNT:占16位,2字节。一个无符号数指明回复记录的个数。
NSCOUNT:占16位,2字节。一个无符号数指明权威记录的个数。
ARCOUNT:占16位,2字节。一个无符号数指明格外记录的个数。
其中每个查询的资源记录格式:
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / QNAME / / / +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QTYPE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QCLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
QNAME:不定长,表示要查询的域名。(两边的方框用 / 来表示不定长)
QTYPE:2字节,根据RFC1035及nslookup的帮助文档,我定义以下枚举类型:
enum QueryType //查询的资源记录类型。
{
A=0x01, //指定计算机 IP 地址。
NS=0x02, //指定用于命名区域的 DNS 名称服务器。
MD=0x03, //指定邮件接收站(此类型已经过时了,使用MX代替)
MF=0x04, //指定邮件中转站(此类型已经过时了,使用MX代替)
CNAME=0x05, //指定用于别名的规范名称。
SOA=0x06, //指定用于 DNS 区域的“起始授权机构”。
MB=0x07, //指定邮箱域名。
MG=0x08, //指定邮件组成员。
MR=0x09, //指定邮件重命名域名。
NULL=0x0A, //指定空的资源记录
WKS=0x0B, //描述已知服务。
PTR=0x0C, //如果查询是 IP 地址,则指定计算机名;否则指定指向其它信息的指针。
HINFO=0x0D, //指定计算机 CPU 以及操作系统类型。
MINFO=0x0E, //指定邮箱或邮件列表信息。
MX=0x0F, //指定邮件交换器。
TXT=0x10, //指定文本信息。
UINFO=0x64, //指定用户信息。
UID=0x65, //指定用户标识符。
GID=0x66, //指定组名的组标识符。
ANY=0xFF //指定所有数据类型。
};
QTYPE:2字节。 根据RFC1035及nslookup的帮助文档,我定义以下枚举类型:
enum QueryClass //指定信息的协议组。
{
IN=0x01, //指定 Internet 类别。
CSNET=0x02, //指定 CSNET 类别。(已过时)
CHAOS=0x03, //指定 Chaos 类别。
HESIOD=0x04,//指定 MIT Athena Hesiod 类别。
ANY=0xFF //指定任何以前列出的通配符。
};
QTYPE中的A,MX,CNAME为常用,QCLASS中的IN为常用。
其中每个回复的记录格式:
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / / / NAME / | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TYPE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | CLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TTL | | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | RDLENGTH | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| / RDATA / / / +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+NAME:回复查询的域名,不定长。
四、DNS查询MX记录编程要点分析
foxmail 5.0 的邮件特快专递有一个很垃圾的地方是无法自动获得本地ISP的DNS服务器,还需要用户的手工输入,我想是因为API的局限或是foxmail开发组没有想到方法或是其他的不为我知的缘故吧。不管他,在C#中利用WMI,很容易的:
string[] dnses; ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration"); ManagementObjectCollection moc = mc.GetInstances(); //枚举当前机子上的所有网卡 foreach(ManagementObject mo in moc) { if((bool)mo["ipEnabled"]) { dnses = (string[]) mo["DNSServerSearchOrder"]; if (dnses!=null) { dnsServer=dnses[0]; //使用第一个找到的DNS服务器。 } } }
struct MxRecord { public string domain; //查询的域名 public QueryType queryType; //查询类型 public QueryClass queryClass; //查询类 public TimeSpan liveTime; //生存时间 public int dataLength; //资源部分的长度,即指示邮件服务器的优先级及名称的那部分资源的字节数。 public int preference; //优先级值,其值越小越优先. public string name; //邮件交换器名 }
220 Welcome to coremail System(With Anti-Spam) 2.1 for 263(040326) HELO dreamchild 250 mta6.x263.net MAIL FROM: <> 250 Ok RCPT TO:250 Ok Data 354 End data with . Date: Thu, 9 Sep 2004 01:00:35 +0800 From: "=?gb2312?B?w87Qobqi?=" <> To: "dreamchild" Subject: =?gb2312?B?xOO6w2RyZWFtY2hpbGSjrMOww8G08sjE?= X-mailer: Foxmail 5.0 [cn] Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=====001_Dragon788446150325_=====" This is a multi-part message in MIME format. --=====001_Dragon788446150325_===== Content-Type: text/plain; charset="gb2312" Content-Transfer-Encoding: base64 1/C+tLXEZHJlYW1jaGlsZM/Iyfqjug0KoaGhodXiysfSu7fi08q8/qGjDQo= --=====001_Dragon788446150325_===== Content-Type: image/gif; name="MM.GIF" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="MM.GIF" R0lGODlheAB4AIddABwNCixIjJtjkLO8iZux0NK2xpVJT9XayJWNuDA0Vt5Xh+Sw7dLc7p6Fh3mD (这边省略去关于一陀 MM.gif 文件内容的Base64编码) w75UTdHFHwD/1Ex6cBSs0jHpKaySFXI4XCwAEhPkRsUSBQQAOw== --=====001_Dragon788446150325_===== Content-Type: application/octet-stream; name="=?gb2312?B?y7XD9y50eHQ=?=" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="=?gb2312?B?y7XD9y50eHQ=?=" 1eK49k1Nv8mwrrK7v8mwrqGjDQo= --=====001_Dragon788446150325_=====-- . 250 Ok: queued as 7CDA613A26E QUIT 221 Bye
这边涉及到的就是SMTP协议了,其中文版的RFC821文档 http://itboy.cn/data/rfc821.doc
因为是中文版的,所以大家花些时间看,这边就不再赘述原理了。只列出状态标识
211 系统状态或系统帮助响应
214 帮助信息
220
221
250 要求的邮件操作完成
251 用户非本地,将转发向
354 开始邮件输入,以
421
450 要求的邮件操作未完成,邮箱不可用(例如,邮箱忙)
451 放弃要求的操作;处理过程中出错
452 系统存储不足,要求的操作未执行
500 格式错误,命令不可识别(此错误也包括命令行过长)
501 参数格式错误
502 命令不可实现
503 错误的命令序列
504 命令参数不可实现
550 要求的邮件操作未完成,邮箱不可用(例如,邮箱未找到,或不可访问)
551 用户非本地,请尝试
552 过量的存储分配,要求的操作未执行
553 邮箱名不可用,要求的操作未执行(例如邮箱格式错误)
554 操作失败
可以从此看出,与一般的通过SMTP代理不同的是少了SMTP服务器的指定及其验证的用户名跟密码。
描述一下整个过程:
首先通过前述的方法得到263.net的一个邮件交换器,然后连到这个交换器上。然后连到此服务器的25端口上,
服务器返回220。
然后依次指示用户名,发送邮箱(人),接收邮箱(人)。接收写入邮件的数据。
数据分为邮件头及邮件的正文两部分。
邮件头包含:时间,发送邮箱(人),接收邮箱(人),主题,发信程序,MIME版本号,邮件内容的类型及分割符。
当中有一些用BASE64编码的字符串就是原来的中文汉字,其实,我们在制作无SMTP代理邮件发送程序时可以直接写成中文的。
这边就讲一下邮件内容的类型及分割符,其他的很容易理解的。
这边的邮件内容类型是 multipart/mixed; 说明是由多种格式混合成的。
分隔符,是用于分隔邮件内容部分与各个附件。用boundary关键字及键值来定义。
比如本例用=====001_Dragon788446150325_=====来表示,这边有一个细节问题,键值最好要用"引起来,并不要出现空格。举个例子,如果你用boundary======001_Dragon788446150325_=====来表示的话,那FOXMAIl5.0将无法正确对邮件进行处理,邮件的内容部分被当成整个BASE64乱码文本,然而我登陆到263.net的网站去收信可以看到邮件被正常转化。
而邮件的内容部分是通过两个减号--再连上分隔符来分隔各部分的。
邮件主体从第一个--=====001_Dragon788446150325_=====开始,到第二个--=====001_Dragon788446150325_=====为内容的第一部分
Content-Type: text/plain;
charset="gb2312" Content-Transfer-Encoding: base64
这两句说明了其类型及内容的字符集和编码。
在这边是指定的是base64,然后一个空行,再加上“尊敬的dreamchild先生:\r\n 这是一封邮件。”这个字符串的BASE64编码构成邮件的正文部分。
实际上,我们可以指定 Content-Transfer-Encoding:8bit然后就可以在正文部分用上原本表示了。
接下来是隔开的附件1部分,
多了一个Content-Disposition: attachment;以说明这部分是附件,以及相关的文件名filename="MM.GIF"。
附件内容部分是把文件读成一个字节数组,然后把字节数组转为base64编码的字符串。这边的是mm.gif这个文件内容。
第三部分是附件2 测试.txt 文件,测试.txt 又被foxmail处理成base64格式了,可以用原文表示的。
最后完了之后,用“回车换行加一个.号再一个回车换行”表示Data部分的结束。
如若正确过发送到达服务器,那就返回一个250状态。
然后用Quit命令跟服务器3166
public struct MailContent //邮件的内容 { public string to;//收件人地址 public string toname;//收件人姓名 public string from;//发件人地址 public string fromname;//发件人姓名 public string title;//主题 public string body;//文本内容 public bool useAttachment; //是否使用附件 public string [] attachmentList; //附件列表 }
TcpClient sock = new TcpClient(); NetworkStream netStream; sock.NoDelay = true; //不使用延时算法,以加快小数据包的发送。 sock.ReceiveTimeout = 10000; //接收超时为10秒。 sock.Connect(server,port); netStream = sock.GetStream();
byte [] sendArray = Encoding.Default.GetBytes(sendString); netStream.Write(sendArray,0,sendArray.Length);
const int MaxReceiveSize = 1460; .... byte [] buffer=new byte[MaxReceiveSize]; length = netStream.Read(buffer, 0, MaxReceiveSize); if (length == 0) return null; .... receiveString = Encoding.Default.GetString(buffer, 0, length);
send = "Date: {$time}\r\n" + "From: {$fromname} <{$from}>\r\n" + "To: {$toname} <{$to}>\r\n" + "Subject: {$title}\r\n" + "X-mailer: NoSmtpSender [蔡晓晖 制作]\r\n" + "MIME_Version:1.0\r\n" + "Content-type:multipart/mixed;Boundary=\"{$splitline}\"\r\n" + "\r\n" //头部结束,开始正文部分。 + "--{$splitline}\r\n" //内容部分。 + "Content-type:text/plain;Charset=gb2312\r\n" + "Content-Transfer-Encoding:8bit\r\n" + "\r\n" + "{$body}\r\n" + "\r\n"; attachment = ""; attachment += "--{$splitline}\r\n"; attachment += "Content-Type:application/octet-stream;Name={$filename}\r\n"; attachment += "Content-Disposition:attachment;FileName={$filename}\r\n"; attachment += "Content-Transfer-Encoding:Base64\r\n"; attachment += "\r\n"; attachment = attachment.Replace("{$splitline}",splitLine); //先进行替换,以防加上附件内容后替换耗时。 attachment = attachment.Replace("{$filename}",file.Name); attachment += Convert.ToBase64String(fileBytes,0,length); attachment += "\r\n\r\n"; send += attachment;
本文地址:http://com.8s8s.com/it/it44172.htm