有一台 linux 服务器,运营商提供了公网 ipv6 地址,在外面可以通过 ssh 访问。公网 ipv6 地址偶尔也会变,给访问造成了巨大的麻烦。为了实现在外部可靠 ssh 访问服务器的目标,通常有两种思路:
- 动态端口映射(
DDNS):将服务器端口映射到有公网ip地址的远程机器端口上。通过远程机器的公网ip地址访问。带有公网ip地址的云服务器的体验套餐比较便宜,可以实现。 - 服务器获取
ipv6地址,发送到指定邮箱,实现起来方便快捷,是本文要介绍的内容。
linux 服务器自动发送 ipv6 地址到指定邮箱需要经过三个过程:
- 用 python 获取本机
ip地址 - 用
smtp发送邮件(而不是登录邮箱点发送按钮) - 用
crond让整个过程自动化
1. 用 python 获取本机 ip 地址
使用 ipconfig 或 ip addr 可以获取本机 ip 地址:
通过 ip addr 命令查看 ip,其中 inet6 就是我们需要的 ipv6 地址。内网 ip 地址是 192.168.1.6。实际上也可以用这种方式获取本机 ip 地址。python 有 subprocess 和 os 这样的包,可以运行 shell 脚本,获取输出。为了得到真正想要的 ip 地址,还需要比较复杂的字符串处理。选择优雅的 socket 包:
import socket
def get_ip():
hostname = socket.gethostname()
addr_infos = socket.getaddrinfo(hostname, None)
ips = set([addr_info[-1][0] for addr_info in addr_infos])
global_ips = [ip for ip in ips if ip.startswith("24")]
return global_ips
socket 会获取所有的 ip 地址,但需要的是公网 ipv6。公网 ipv6 一般是以 24 开头。这里加了一个筛选。获取的结果是一个 list,每个元素是 ipv6 地址的字符串。
2. 用 smtp 发送邮件
使用 python 的 smtp 包发送邮件,需要一个支持 smtp 的邮箱(常见的邮箱都支持 smtp),以 sina.com 邮箱为例:
import smtplib
from email.header import Header
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from time import asctime
def send_an_email(email_content): # email_content是一个字符串
mail_host = "smtp.sina.com" # 这个去邮箱找,这里用的sina邮箱
mail_user = 发送的邮箱地址
mail_auth_code = 授权码,不是邮箱的登录密码
mail_sender = mail_user # 用mail_user 作为发送人
mail_receivers = [收件人list]
message = MIMEMultipart()
message['From'] = Header(mail_sender) # 寄件人
message['Subject'] = Header("主题名字")
message.attach(MIMEText(asctime(), 'plain', 'utf-8'))
message.attach(MIMEText(email_content, 'plain', 'utf-8'))
print("message is {}".format(message.as_string())) # debug用
smtpObj = smtplib.SMTP(mail_host)
# smtpObj.set_debuglevel(1) # 同样是debug用的
smtpObj.login(mail_user, mail_auth_code) # 登录
smtpObj.sendmail(mail_sender, mail_receivers, message.as_string()) # 真正发送邮件就是这里
登录自己的邮箱,找到设置,找到客户端 pop/imap/smtp。从这里找到 smtp 服务器的地址,以及生成授权码。
这里会看到授权码。(由于我之前是申请过了的,所以这里是重置。)如果没有的话,则是要你申请,在邮箱设置里面获得授权码。
将邮箱设置好之后,可以调用这个 send_an_email 函数。自己修改 email_content 和 Header,查看发送效果。
通过 smtp 发送了一封邮件。
3. 自动化这个过程
虽然我们实现了自动获取 ip 地址,也实现了通过 python 的 smtp 包进行邮件发送。但是离智能还是差一点:所有过程都得人工去执行这个脚本。我们希望的是:ipv6 地址一旦变化,就给我发邮件,但是又不要时时刻刻给我发邮件。
实现思路是监控服务器的 ipv6 地址,将当前的 ipv6 地址存储下来,每隔一个固定时间就检查一下服务器 ipv6 地址,如果没有变化,则不发送邮件,如果有变化,则发送邮件。按照这个思路对获取 ipv6 地址的代码进行改造:
def get_temp_ip(current_ip):
temp_ip_json_path = "/var/tmp/ip.json"
if not os.path.exists(temp_ip_json_path):
print("No {}, dump it.".format(temp_ip_json_path))
with open(temp_ip_json_path, 'w') as jo:
json.dump(current_ip, jo)
return True, current_ip
else:
with open(temp_ip_json_path, 'r') as jo:
origin_ip = json.load(jo)
if origin_ip == current_ip:
print("Current ip {} do not change, no need to send".format(current_ip))
return False, current_ip
else:
print("The ip updated from {} to {}, update it.".format(origin_ip, current_ip))
os.remove(temp_ip_json_path)
with open(temp_ip_json_path, 'w') as jo:
json.dump(current_ip, jo)
return True, current_ip
def get_ip():
hostname = socket.gethostname()
addr_infos = socket.getaddrinfo(hostname, None)
ips = set([addr_info[-1][0] for addr_info in addr_infos])
global_ips = [ip for ip in ips if ip.startswith("24")]
whether_to_send, send_ip = get_temp_ip(global_ips)
send_ip = json.dumps(send_ip)
return whether_to_send, send_ip
先判断是否有 /var/tmp/ip.json 文件。如没有就将当前 ipv6 地址存储,且发送邮件。如存在就读取其中的 ipv6 地址,若当前 ipv6 和存储的 ipv6 地址一样,就继续等待下一次检查,如不一样就更新 ip.json 文件并且发送邮件。通常还要给脚本新增上 main:
if __name__ == "__main__":
whether_to_send, global_ips = get_ip()
if whether_to_send:
send_an_email(global_ips)
else:
print("wait and no send")
给脚本新增上可执行权限:
sudo chmod +x send_ip_to_mailbox.py
使用 linux 的调度工具 crond 将任务排期自动执行,这里配置的是每隔 1 分钟执行一次 ipv6 地址检查。
# 在/etc/cron.d/下面创建一个空白文件
sudo vim /etc/cron.d/ipsync
# 内容如下
*/1 * * * * chinglin /abs_path_to_send_ip_to_mailbox.py
启动 crond:
sudo systemctl restart crond # 启动
sudo systemctl enable crond # 设置开机启动
至此,可以用邮箱收取服务器的 ipv6 地址了。
4. 小结
本文主要介绍自动将 linux 服务器 ip 地址发送到指定邮箱的方式。基本步骤是用 socket 获取 ipv6 地址,用 smtp 发送邮件,用 crond 自动化执行。