问题描述
连接SMTP服务器发送邮件,出现错误提示,如“Could not connect to SMTP host”。
问题原因
出现这种报错,是因为程序在发送邮件的最开始阶段,连接邮箱服务器时就出现了问题,此时邮箱服务端没有入信记录,不会有相关发信日志,通常需要从SMTP发信代码所在客户端开始排查。
解决方案
按步骤排查分析,根据具体情况采取对应措施。
1、事件分析
首先判断之前是好的吗?是否每次调用都报错(错误是偶现还是必现)?
如果之前是好的,那么出问题前是否做了代码更改,组件升级等操作,尝试回滚恢复。
若错误能复现会便于后续问题排查。
2、连通性分析
以465端口为例,请运行下面命令,并截图。
命令1(查看分配到的服务器IP,不同区域IP会不同):
ping smtp.qiye.aliyun.com
ping命令失败或有高延迟,可能是本地禁止ping或网络连接问题。
成功结果:
命令2(通过指定端口连接SMTP服务器,查看端口和网络连通性):
telnet smtp.qiye.aliyun.com 465
成功结果:
或
命令3(检查路由路径):
#linux或Mac命令:
mtr -n -i 1 -c 100 smtp.qiye.aliyun.com
traceroute smtp.qiye.aliyun.com
#windows命令:
tracert smtp.qiye.aliyun.com
pathping smtp.qiye.aliyun.com
查看到SMTP服务器的路由路径,并识别哪一跳开始出现问题。
tracert默认最多显示 30 跳,最后一个IP是邮箱服务器IP就行。
pathping运行较慢需要几分钟时间生成统计信息。
也可以使用WinMTR等可视化开源工具。
成功结果:
命令4(仅针对465端口):
#不指定TLS版本
openssl s_client -connect smtp.qiye.aliyun.com:465
#指定TLS版本
openssl s_client -connect smtp.qiye.aliyun.com:465 -tls1_2
使用加密方式进行连接测试,判断是否是TLS版本导致的握手异常。
成功结果:
CONNECTED
3、排除分析
排查其他端口是否可以正常发信,如25,80端口(需要注意ECS默认禁止25端口),可以尝试80端口发信是否成功,这两个端口不用SSL相关代码,请注释掉相关代码。
若其他端口正常,465不行:465端口强制启用SSL
final Properties props = new Properties(); props.setProperty("mail.smtp.ssl.enable", "true");
尝试从其他脚本或使用示例代码独立发信(排除其他业务逻辑的代码干扰)。
阿里邮箱产品示例:
邮件推送产品示例(仅参考):
使用其他服务器地址用465端口发信测试:如腾讯邮箱smtp.qq.com,网易邮箱smtp.163.com,阿里云邮件推送smtpdm.aliyun.com等。
4、抓包分析
若25或80端口没有禁用(465端口加密,看不到细节),可以对25或80端口抓包分析。
抓包过程及命令参考:
发信端启动抓包(指定邮箱IP和端口)
linux命令参考:默认保存根目录,也可以修改命令指定保存目录,如/tmp/capture.pcap
sudo tcpdump -i eth0 host xx.xx.xx.xx and port 80 -w capture.pcap
windows:使用wireshark。
测试发信并复现问题
停止抓包
linux:在抓包命令执行界面按ctrl+c停止抓包。
windows:软件上操作停止按钮。
用wireshark打开分析。
其他可能方案
握手阶段加密算法不匹配
Java环境为例,JRE 高版本把加密算法禁止了 SSLv3, TLSv1, TLSv1.1等,尝试指定版本或删除相关禁用项。
final Properties props = new Properties(); props.put("mail.smtp.ssl.protocols", "TLSv1.2");
SSL来建立连接,失败则回退兼容
如果出于某种原因无法使用SSL来建立连接(比如服务器不支持),则不会回退到非加密的连接方式,而是让连接尝试失败。
final Properties props = new Properties(); prop.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); prop.put("mail.smtp.socketFactory.fallback", "false");
其他分析方式
开启debug模式,(会打印与服务器的交互过程,可以用于其他阶段报错分析,当前场景可能不适用)。
Java示例:
//开启debug模式:
Session mailSession = Session.getInstance(props, authenticator);
mailSession.setDebug(true);