這個問題在我心中盤旋很久了, 但卻一直沒時間研究. 樹莓派的低功耗使其成為家中不關機的伺服器首選. 我曾將樹莓派連續開機好幾個月都不會當機, 不需要接螢幕或鍵盤滑鼠, 只要插上電源開機, 它會自動連線到家中的無線基地台, 並固定取得 192.168.2.192 這個 IP, 然後利用筆電以 Putty 用 SSH 連線樹莓派的固定 IP 192.168.2.192 即可完全操控它, 稱為無頭存取方式, 參考 :
#
樹莓派的 "無頭存取" (headless access)
#
設定樹莓派 WiFi 無線網卡固定 IP 的方法
#
樹莓派 Raspberry Pi 文章列表
在區域網路存取沒問題, 但 192.168.xxx.xxx 只限於家裡的區網能用, 如果在公司想要連線回來該怎麼做? 這就需要 WiFi 無線基地台 (路由器) 上設置虛擬伺服器, 將外網 IP 對應到區網的 192.168.xxx.xxx 了.
我參考了下列文章在我的 EDIMAX 無線基地台的管理網頁中為樹莓派的 外網 SSH 存取設定虛擬伺服器 :
#
樹莓派通過郵件上報實時IP,隨時隨地遠程登錄樹莓派
EDIMAX 管理網頁位置是在 "位址轉換/Virtual server" 頁籤 :
先將左上角的 "Enable virtual server" 打勾, 然後輸入 Private IP, 我的樹莓派因為有在 /etc/network/interfaces 檔案中設定固定 IP=192.168.2.192, 因此 Private IP 就輸入 192.168.2.192. 然後 Private port 與 Public port 都輸入 SSH 的埠號 22, 並在 Comment 欄輸入備註, 再按 "Add" 鈕, 上述的設定就會出現在下面的 "Current virtual server table" 中了, 最後要按 "Apply" 才會真正生效.
接下來要測試看看是否真能從外網與區網內的樹莓派建立 SSH 連線. 首先必須知道家裡無線基地台的外網 IP, 有許多網站提供 IP 查詢服務, 例如 :
#
http://cmp.nkuht.edu.tw/info/ip.asp
#
http://dir.twseo.org/ip-check.php
#
http://www.ip138.com/
我筆電原先是透過家裡的無線基地台上網, 利用上列網站查出無線基地台外網 IP 後, 這時將筆電改透過手機行動網路分享的 WiFi 上網, 這樣就可以模擬從 Internet 外網進行存取了. 這時打開 Putty, 輸入樹莓派所連之無線基地台外網 IP, 果然順利連線到區網內的樹莓派了.
#
https://github.com/laixintao/Report-IP-hourly
如果要在命令列查詢外網 IP, 可輸入下列 curl 指令 :
pi@raspberrypi:~ $ curl http://members.3322.org/dyndns/getip >>/home/pi/ip.log % Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 14 0 14 0 0 46 0 --:--:-- --:--:-- --:--:-- 46
pi@raspberrypi:~ $ cat ip.log
37.237.225.60
查詢內網則是用 ifconfig, 若只要擷取 IP 可用下列指令 :
pi@raspberrypi:~ $ ifconfig | grep 'Bcast' | cut -d B -f 1 >> /home/pi/ip.log
pi@raspberrypi:~ $ cat ip.log
37.237.225.60
inet addr:192.168.2.192
由於一般 ADSL 用戶都是浮動的公網 IP 並非固定, ISP 業者會一段時間會更換此 IP, 樹莓派必須在外網 IP 異動時自動以 E-mail 通知我們, 否則將無法以 SSH 從外網連線到樹莓派. 這篇文章的作者提供了 Python 程式碼來完成此項通報工作, 此程式已放在 GitHub 專案中 :
#
https://github.com/laixintao/Report-IP-hourly
主角是專案中的 Python 程式 reportip.py :
#
https://github.com/laixintao/Report-IP-hourly/blob/master/reportip.py
將此程式複製下來加以編輯, 主要是修改 e-mail config 部分 :
# the e-mail config
# this is just a simple format,this e-mail doesn't exist.
smtpserver = "smtp.sina.com"
username = "reaspberrypi@sina.com"
password = "123456"
sender = "reaspberrypi@sina.com"
receiver = ["receiver@sina.com","master@sina.com"]
subject = "[RPI]IP CHANGED"
改成如下 (注意, Hinet SMTP 主機是 msr) :
smtpserver = "
msr.hinet.net"
username = "blabla@ms5.hinet.net"
password = "1234567890"
sender = "blabla@ms5.hinet.net"
receiver = ["jyp@yahoo.com","jyp@google.com"]
subject = "[RPI]IP CHANGED"
另外 Getmyip 類別之 getip() 方法裡面的查詢 IP 網址也要改成台灣的網址, 不要用原作中的大陸網站, 我查詢 Google 發現下面三個網站還不錯, 都可以正常運作 :
- http://myip.com.tw
- http://cmp.nkuht.edu.tw/info/ip.asp
- http://dir.twseo.org/ip-check.php
修改後的完整程式如下 :
#!/usr/bin/python
#-*-coding:utf-8*-
__author__ = 'laixintao'
import socket
import time
import struct
import smtplib
import urllib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
import re
import urllib2
# the e-mail config
# this is just a simple format,this e-mail doesn't exist.
smtpserver = "
msr.hinet.net"
username = "blabla@ms5.hinet.net"
password = "1234567890"
sender = "blabla@ms5.hinet.net"
receiver = ["jyp@yahoo.com","jyp@google.com"]
subject = "[RPI]IP CHANGED"
# file_path config
file_path = "lastip.txt"
def sendEmail(msghtml):
msgRoot = MIMEMultipart('related')
msgRoot["To"] = ','.join(receiver)
msgRoot["From"] = sender
msgRoot['Subject'] = subject
msgText = MIMEText(msghtml,'html','utf-8')
msgRoot.attach(msgText)
smtp = smtplib.SMTP()
smtp.connect(smtpserver)
smtp.login(username, password)
smtp.sendmail(sender, receiver, msgRoot.as_string())
smtp.quit()
def check_network():
while True:
try:
print "Network is Ready!"
break
except Exception , e:
print e
print "Network is not ready,Sleep 5s...."
time.sleep(10)
return True
def get_lan_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("1.1.1.1",80))
ipaddr=s.getsockname()[0]
s.close()
return ipaddr
class Getmyip:
def getip(self):
try:
myip = self.visit("http://myip.com.tw")
except:
try:
myip = self.visit("http://cmp.nkuht.edu.tw/info/ip.asp")
except:
try:
myip = self.visit("http://dir.twseo.org/ip-check.php")
# if you want to add more,use the format "except try"
# make sure the most useful link be the first
except:
print "Fail to get the Network ip."
print "Get the LAN ip."
myip = get_lan_ip()
return myip
def visit(self,url):
opener = urllib2.urlopen(url,timeout=20)
if url == opener.geturl():
str = opener.read()
print "IP information from",url
return re.search('\d+\.\d+\.\d+\.\d+',str).group(0)
def get_network_ip():
getmyip = Getmyip()
localip = getmyip.getip()
return localip
if __name__ == '__main__':
check_network()
ipaddr=get_network_ip()
lanip=get_lan_ip()
emailip=str(ipaddr)+" "+str(lanip)
ip_file = open(file_path)
last_ip = ip_file.read()
ip_file.close()
if last_ip == emailip:
print "IP not change."
else:
print "IP changed. New ip: {}".format(emailip)
ip_file = open(file_path,"w")
ip_file.write(str(emailip))
ip_file.close()
sendEmail(ipaddr)
print "Successfully send the e-mail."
注意, Hinet 的 SMTP 發信主機要用 msr.hinet.net, 我的郵件主機是 ms5.hinet.net, 我原先以為要用這個, 結果信傳不出去, 改成 msr 就可以了, 參考 :
#
http://blog.xuite.net/yatpingchen/blog/199891664-國外收發Hinet郵件SMTP設定
其次, 上面這個程式原作是用 Python 2 寫的, 所以修改好後我改用 reportip2.py 存檔在 /home/pi 下, 手動執行時要用 python2 指令 :
pi@raspberrypi:~ $
python2 reportip2.py
Network is Ready!
IP information from http://cmp.nkuht.edu.tw/info/ip.asp
IP changed. New ip:
223.139.131.189 192.168.43.26
Successfully send the e-mail.
You have new mail in /var/mail/pi
pi@raspberrypi:~ $
python2 reportip2.py
Network is Ready!
IP information from http://myip.com.tw
IP not change.
You have new mail in /var/mail/pi
手動執行沒問題後, 接下來是編輯 contab 讓 reportip2.py 能夠定時自動執行, 這樣不論我在天涯海角都能透過 E-mail 得知目前的 IP 是多少而連線回去. 另外一個好處是可以知道家裡 ADSL 網路是否正常, 如果沒定時收到 E-mail 就表示網路異常了.
$
crontab -e
參考原作的 crontabs 設定 :
#
https://github.com/laixintao/Report-IP-hourly/blob/master/rootcron
0 */1 * * * /usr/bin/python2 /root/rootcrons/reportip2.py
將其改為每 10 分鐘檢查一次 :
*/10 * * * * /usr/bin/python2 /home/pi/reportip2.py
在 crontab 加入這一筆後存檔, 再用 chmod 指令將 reportip2.py 改為可執行 :
pi@raspberrypi:~ $ sudo chmod +x /home/pi/reportip2.py
2017-12-03 補充 :
下午 16:00 要回高雄, 因姊姊要搭高鐵回台北, 所以下午抽點時間測試上面的程式, 原作使用的 IP 查詢網站 www.138ip.com 每次 cron 執行回報的 IP 都不同, 事實上即使是浮動 IP 也不可能每 10 分鐘就變動一次, 我猜可能是該網站位於中國境內的關係, 改用台灣的 http://myip.com.tw 等位址就正常了.
Cron 執行紀錄放在 /var/log/cron.log 裡面, 不過紀錄功能預設是關閉的, 必須修改 /etc/rsyslog.conf 檔之設定將其打開才會記錄, 參考 :
以管理員身分開啟 /etc/rsyslog.conf, 在 "RULES" 項下可找到預設被 mark 掉的 #cron 設定, 拿掉 # 後存檔 :
pi@raspberrypi:~ $ sudo nano /etc/rsyslog.conf
###############
#### RULES ####
###############
#
# First some standard log files. Log by facility.
#
auth,authpriv.* /var/log/auth.log
*.*;auth,authpriv.none -/var/log/syslog
#cron.* /var/log/cron.log
daemon.* -/var/log/daemon.log
kern.* -/var/log/kern.log
lpr.* -/var/log/lpr.log
mail.* -/var/log/mail.log
user.* -/var/log/user.log
然後重啟系統記錄檔即可 :
$ sudo /etc/init.d/rsyslog restart
檢視 cron 執行紀錄 :
pi@raspberrypi:~ $
sudo cat /var/log/cron.log
Dec 3 23:25:01 raspberrypi CRON[20720]: (pi) CMD (/usr/bin/python2 /home/pi/reportip2.py) ortip2.py)
Dec 3 23:30:01 raspberrypi CRON[20785]: (pi) CMD (/usr/bin/python2 /home/pi/reportip2.py) ortip2.py)
Dec 3 23:35:01 raspberrypi CRON[20806]: (pi) CMD (/usr/bin/python2 /home/pi/reportip2.py) nclean ] && /usr/lib/php5/sessionclean)
Dec 3 23:40:01 raspberrypi CRON[20879]: (pi) CMD (/usr/bin/python2 /home/pi/reportip2.py) ortip2.py)
Dec 3 23:45:01 raspberrypi CRON[20947]: (pi) CMD (/usr/bin/python2 /home/pi/reportip2.py)
參考 :
#
Where do Cron error message go?
2017-12-09 補充 :
我把上面 Python 2 的程式改為 Python 3 版, 結果卻敗在 visit() 方法無法取得外網 IP, 原因可能出在 urllib 模組上 :
import socket
import time
import struct
import smtplib
from urllib.request import urlopen
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
import re
# the e-mail config
# this is just a simple format,this e-mail doesn't exist.
smtpserver = "
msr.hinet.net"
username = "blabla@ms5.hinet.net"
password = "1234567890"
sender = "blabla@ms5.hinet.net"
receiver = ["jyp@yahoo.com","jyp@google.com"]
subject = "[RPI]IP CHANGED"
# file_path config
file_path = "lastip.txt"
def sendEmail(msghtml):
msgRoot = MIMEMultipart('related')
msgRoot["To"] = ','.join(receiver)
msgRoot["From"] = sender
msgRoot['Subject'] = subject
msgText = MIMEText(msghtml,'html','utf-8')
msgRoot.attach(msgText)
smtp = smtplib.SMTP()
smtp.connect(smtpserver)
smtp.login(username, password)
smtp.sendmail(sender, receiver, msgRoot.as_string())
smtp.quit()
def check_network():
while True:
try:
print("Network is Ready!")
break
except Exception :
print(e)
print("Network is not ready,Sleep 5s....")
time.sleep(10)
return True
def get_lan_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("1.1.1.1",80))
ipaddr=s.getsockname()[0]
s.close()
return ipaddr
class Getmyip:
def getip(self):
try:
myip = self.visit("http://myip.com.tw")
except:
try:
myip = self.visit("http://cmp.nkuht.edu.tw/info/ip.asp")
except:
try:
myip = self.visit("http://dir.twseo.org/ip-check.php")
# if you want to add more,use the format "except try"
# make sure the most useful link be the first
except:
print("Fail to get the Network ip.")
print("Get the LAN ip.")
myip = get_lan_ip()
return myip
def visit(self,url):
opener = urlopen(url,timeout=20)
if url == opener.geturl():
str = opener.read()
print("IP information from",url)
return re.search('\d+\.\d+\.\d+\.\d+',str).group(0)
def get_network_ip():
getmyip = Getmyip()
localip = getmyip.getip()
return localip
if __name__ == '__main__':
check_network()
ipaddr=get_network_ip()
lanip=get_lan_ip()
emailip=str(ipaddr)+" "+str(lanip)
ip_file = open(file_path)
last_ip = ip_file.read()
ip_file.close()
if last_ip == emailip:
print("IP not change.")
else:
print("IP changed. New ip: {}".format(emailip))
ip_file = open(file_path,"w")
ip_file.write(str(emailip))
ip_file.close()
sendEmail(ipaddr)
print("Successfully send the e-mail.")
主要是把輸出改成函數式的 print(), 以及匯入 urllib.request.urlopen 而已, 執行結果如下 :
E:\test>python reportip3.py
Network is Ready!
IP information from http://myip.com.tw
IP information from http://cmp.nkuht.edu.tw/info/ip.asp
IP information from http://dir.twseo.org/ip-check.php
Fail to get the Network ip.
Get the LAN ip.
IP changed. New ip:
192.168.43.72 192.168.43.72
Successfully send the e-mail.
收到的 E-mail 是內網 IP, 到底哪裡出錯? 有空再研究.
#
How to: Connecting to VNC Server (5.x and before) over the Internet
2017-12-11 補充 :
前天週六晚上將改好的 report2.py 傳到 Pi 執行 crontab, 只要公網 IP 有變動就會發出郵件通知, 證實上面的做法確實可行. 由於傳檔用的 WinSCP 也是使用 22 埠 (sFTP), 也是可以透過 Internet 傳送檔案 :
Bingo! 以後就可以隨時從遠端存取鄉下樹莓派裡的檔案了.
參考 :
#
如何让树莓派可以被外网访问?