# 樹莓派的 "無頭存取" (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 發現下面三個網站還不錯, 都可以正常運作 :
修改後的完整程式如下 :
#!/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
$ 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
pi@raspberrypi:~ $ ls reportip2.py -ls
-rwxr-xr-x 1 pi pi 2941 Dec 3 20:46 reportip2.py
參考:
# 在樹莓派上安裝 cURL 與設定 Crontab
# 在樹莓派上設定 Crontab 的新方法
# 树莓派实战1:查询自己内网,外网ip
# [应用方案] 【玩树莓】外网远程访问家里的树莓派
# 如何让树莓派可以被外网访问?
# SMTP AUTH extension not supported by server
# http://lib.webmail.hinet.net/statement/SAsetup.htm
-rwxr-xr-x 1 pi pi 2941 Dec 3 20:46 reportip2.py
參考:
# 在樹莓派上安裝 cURL 與設定 Crontab
# 在樹莓派上設定 Crontab 的新方法
# 树莓派实战1:查询自己内网,外网ip
# [应用方案] 【玩树莓】外网远程访问家里的树莓派
# 如何让树莓派可以被外网访问?
# SMTP AUTH extension not supported by server
# http://lib.webmail.hinet.net/statement/SAsetup.htm
# Finding local IP addresses using Python's stdlib
# 1.1.1.1 是哪里的 IP?
# Python3 error: “Import error: No module name urllib2”
# 1.1.1.1 是哪里的 IP?
# Python3 error: “Import error: No module name urllib2”
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! 以後就可以隨時從遠端存取鄉下樹莓派裡的檔案了.
參考 :
# 如何让树莓派可以被外网访问?
狐前輩 您好
回覆刪除最近剛好在弄從外網以ssh連線,搜尋到本文
做法也是用虛擬伺服器(Port forwarding)
在IP變動方面,因為家中Router為RT-AC66U B1
ASUS有提供自訂免費的DDNS
例:把DDNS設定為demo123.asuscomm.com
從外部連線時,輸入demo123.asuscomm.com:22即可ssh
不管浮動IP怎麼變,DDNS都是固定的
如果Router沒有提供DDNS,網路上不少服務都有提供free ddns
例:
Raspberry Pi | Free Dynamic DNS Service | Dynu
https://www.dynu.com/DynamicDNS/IPUpdateClient/RaspberryPi-Dynamic-DNS
以上分享~
感謝您的分享, 我找時間來試試看, 這應該比較方便, 不需要在路由器上鑽洞.
回覆刪除我有一個想法
回覆刪除只是要做很多前置作業
樹莓派裡面有要一支程式
把外網IP更改在一個TXT網頁
TXT網頁就有外網IP了(把TXT網址當做變數)
此時固定網址,只要手機APP或者直接讀TXT固定網址的IP
TXT網頁網站https://pastebin.com/
舉例像這樣https://pastebin.com/raw/xH0XaYxe
只是樹莓派要寫一支程式
要成登入該網頁做編輯的動作
如果使用gamil的話,在reportip.py裡面,在sendEmail()裡的
回覆刪除smtp.connect(smtpserver)
smtp.login(username, password)
這兩行之間新增另外兩行指令變成:
smtp.connect(smtpserver)
smtp.ehlo()
smtp.starttls()
smtp.login(username, password)
然後再到google帳戶裡面,在'登入和安全性'裡面,[允許安全性較低的應用程式] 設定處於啟用狀態,把這選項打開,然後送信就會正常了,以上是我用gmail送信遇到的問題~希望能幫助那些跟我遇到一樣問題的人
我還沒時間試試 Gmail, 原來用 Gmail 還有安全性設定問題, 實在非常感謝您的分享, 我也來試試看.
回覆刪除作者已經移除這則留言。
回覆刪除大大,如果我有一台中華電信的數據機,然後上面接了樹莓派,也接了一台TPLink,那我的筆電是連線至TPLink的,這樣要如何才能讓使用TPLink的筆電連線到它的父網路(中華電信數據機)上的樹莓派呢?
回覆刪除