有一台 linux 服务器,运营商提供了公网 ipv6 地址,在外面可以通过 ssh 访问。公网 ipv6 地址偶尔也会变,给访问造成了巨大的麻烦。为了实现在外部可靠 ssh 访问服务器的目标,通常有两种思路:

  • 动态端口映射(DDNS):将服务器端口映射到有公网 ip 地址的远程机器端口上。通过远程机器的公网 ip 地址访问。带有公网 ip 地址的云服务器的体验套餐比较便宜,可以实现。
  • 服务器获取 ipv6 地址,发送到指定邮箱,实现起来方便快捷,是本文要介绍的内容。

linux 服务器自动发送 ipv6 地址到指定邮箱需要经过三个过程:

  1. 用 python 获取本机 ip 地址
  2. smtp 发送邮件(而不是登录邮箱点发送按钮)
  3. crond 让整个过程自动化

1. 用 python 获取本机 ip 地址

使用 ipconfigip addr 可以获取本机 ip 地址:

通过 ip addr 命令查看 ip,其中 inet6 就是我们需要的 ipv6 地址。内网 ip 地址是 192.168.1.6。实际上也可以用这种方式获取本机 ip 地址。python 有 subprocessos 这样的包,可以运行 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_contentHeader,查看发送效果。

通过 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 自动化执行。