如何在騰訊雲平臺裡搭建vpn連線本地區域網
阿新 • • 發佈:2019-02-11
最近需要使用騰訊雲平臺,瞭解了一番之後發現cvm必須使用它所指定的代理後使用ssh登入,另外ssh登入的時候還需要一個動態密碼,這樣對運維來說很不方便,於是就想弄個vpn將cvm和線下環境弄到一個局域網裡。這樣就有個問題了,openvpn的伺服器放在哪裡,測試了一下發現cvm只能進不能出,也即不能直接訪問外網,除非是有請求進來。於是顯然cvm只能做伺服器了,但是新問題又來了,cvm做伺服器的話tcp協議要求對應用層協議進行改造,必須傳一個tgw的頭,這就讓人鬱悶了,難道去修改openvpn的程式碼嗎?這顯然不行,於是就想幹脆寫個proxy得了,它的原理很簡單,作為一個透明代理,唯一的不同是,當它作為客戶端時,會在連線建立後立即向伺服器傳送一個tgw頭;當它作為伺服器時,會將連線建立後收到的tgw頭幹掉,當這些事情作完之後它就是一個純粹的透明代理了。這樣的程式不難寫,用golang一會就可以寫一個,下面是寫完的程式
- package main
- import (
- "flag"
- "fmt"
- "log"
- "net"
- "time"
- )
- // 配置引數
- type TConfig struct {
- targetAddr string //目標地址,比如app12345.qzoneapp.com:80
- sourceAddr string //源地址,即本地監聽的地址
- logFile string //日誌檔案地址
- actAsServer bool //是否作為伺服器,如果是的話,需要將targetAddr裡的引數發給tgw
- bufferSize int
- }
- var (
- gConfig TConfig
- gTargetAddr *net.TCPAddr
- gSourceAddr *net.TCPAddr
- )
- const (
- TGW_PREFIX = "tgw_l7_forward"
- TGW_HEADER_SIZE = 1024 * 4
- TGW_HEADER_SEG_COUNT = 3
- )
- //初始化引數
- func initArgs() bool {
- flag.StringVar(&gConfig.targetAddr, "t", ":54321", "目標地址,比如app12345.qzoneapp.com:80")
- flag.StringVar(&gConfig.sourceAddr, "s", ":12345", "本地的監聽地址,比如192.168.1.200:8080")
- //flag.StringVar(&gConfig.logFile, "l", "/tmp/tgwProxy.log", "日誌輸出地址")
- flag.BoolVar(&gConfig.actAsServer, "a", false, "是否作為伺服器,如果是的話,需要將targetAddr裡的引數發給tgw")
- flag.IntVar(&gConfig.bufferSize, "b", 1024*1024, "快取大小")
- flag.Parse()
- var err error
- gSourceAddr, err = net.ResolveTCPAddr("tcp4", gConfig.sourceAddr)
- if err != nil {
- fmt.Printf("resolve tcp address:%s failed:%s\n", gConfig.sourceAddr, err.Error())
- return false
- }
- gTargetAddr, err = net.ResolveTCPAddr("tcp4", gConfig.targetAddr)
- if err != nil {
- fmt.Printf("resolve tcp address:%s failed:%s\n", gConfig.targetAddr, err.Error())
- return false
- }
- if gConfig.bufferSize < 1 {
- fmt.Printf("buffer size:%d is too small\n", gConfig.bufferSize)
- return false
- }
- return true
- }
- //初始化伺服器
- func runServer() {
- listener, err := net.ListenTCP("tcp4", gSourceAddr)
- if err != nil {
- fmt.Printf("listen on tcp address:%s failed:%s\n", gConfig.sourceAddr, err.Error())
- return
- }
- log.Printf("server started")
- for {
- conn, err := listener.AcceptTCP()
- if err != nil {
- log.Printf("accept tcp connection failed:%s", err.Error())
- time.Sleep(1000000000)
- }
- log.Printf("get one connection")
- conn.SetKeepAlive(true)
- conn.SetNoDelay(true)
- if gConfig.actAsServer {
- go doServer(conn)
- } else {
- go doClient(conn)
- }
- }
- }
- //將所有資料寫入連線中
- func writeAllData(conn net.Conn, buffer []byte) error {
- offset := 0
- length := len(buffer)
- for offset < length {
- bytes, err := conn.Write(buffer[offset:length])
- if err != nil {
- log.Printf("write to target:%s failed:%s", conn.RemoteAddr().String(), err.Error())
- return err
- }
- offset += bytes
- }
- //log.Printf("write %d bytes to connection", offset)
- return nil
- }
- //處理伺服器事務
- func doServer(conn net.Conn) {
- defer handlePanic()
- //去掉tgw的頭
- buffer := make([]byte, TGW_HEADER_SIZE)
- length, err := conn.Read(buffer)
- if err != nil {
- log.Printf("read from client failed:%s", err.Error())
- return
- }
- segCount := 0
- for i := 1; i < length; i++ {
- if buffer[i] == '\n' && buffer[i-1] == '\r' {
- segCount++
- if segCount == TGW_HEADER_SEG_COUNT {
- buffer = buffer[i+1 : length]
- break
- }
- }
- }
- if segCount < TGW_HEADER_SEG_COUNT {
- log.Printf("invalid tgw header:%s", string(buffer[0:length]))
- return
- }
- targetConn, err := net.DialTCP("tcp4", nil, gTargetAddr)
- if err != nil {
- log.Printf("connect to target:%s failed:%s", gTargetAddr.String(), err.Error())
- return
- }
- log.Printf("connect to server:%s succeed", gTargetAddr.String())
- //寫剩下的欄位
- err = writeAllData(targetConn, buffer)
- if err != nil {
- return
- }
- //源請求到目標連線,即寫請求
- go proxyTransfer(conn, targetConn)
- //目標響應到源連線,即寫響應
- go proxyTransfer(targetConn, conn)
- }
- //將從sourceConn中讀到的資料寫給targetConn
- func proxyTransfer(sourceConn net.Conn, targetConn net.Conn) {
- defer handlePanic()
- buffer := make([]byte, gConfig.bufferSize)
- for {
- length, err := sourceConn.Read(buffer)
- if err != nil {
- log.Printf("read from source:%s failed:%s", sourceConn.RemoteAddr().String(), err.Error())
- targetConn.Close()
- return
- }
- //log.Printf("read %d bytes from source connection", length)
- err = writeAllData(targetConn, buffer[0:length])
- if err != nil {
- targetConn.Close()
- return
- }
- }
- }
- //處理客戶端事務
- func doClient(conn net.Conn) {
- defer handlePanic()
- //建立連線到目錄地址
- targetConn, err := net.DialTCP("tcp4", nil, gTargetAddr)
- if err != nil {
- log.Printf("connect to target:%s failed:%s", gTargetAddr.String(), err.Error())
- return
- }
- log.Printf("connect to server:%s succeed", gTargetAddr.String())
- //加上tgw的頭
- tgwHeader := TGW_PREFIX + "\r\nHost:" + gConfig.targetAddr + "\r\n\r\n"
- log.Printf("send tgwHeader:%s to server:%s", tgwHeader, gTargetAddr.String())
- buffer := []byte(tgwHeader)
- err = writeAllData(targetConn, buffer)
- if err != nil {
- return
- }
- //源請求到目標連線,即寫請求
- go proxyTransfer(conn, targetConn)
- //目標響應到源連線,即寫響應
- go proxyTransfer(targetConn, conn)
- }
- //初始化日誌
- func initLogger() bool {
- log.SetFlags(log.Ldate | log.Lshortfile | log.Lmicroseconds | log.Ltime)
- log.Printf("logger init")
- return true
- }
- func handlePanic() {
- err := recover()
- if err != nil {
- log.Printf("uncaught panic:%s", err.(error).Error())
- }
- }
- func main() {
- if !initArgs() || !initLogger() {
- return
- }
- runServer()
- }
package main
import (
"flag"
"fmt"
"log"
"net"
"time"
)
// 配置引數
type TConfig struct {
targetAddr string //目標地址,比如app12345.qzoneapp.com:80
sourceAddr string //源地址,即本地監聽的地址
logFile string //日誌檔案地址
actAsServer bool //是否作為伺服器,如果是的話,需要將targetAddr裡的引數發給tgw
bufferSize int
}
var (
gConfig TConfig
gTargetAddr *net.TCPAddr
gSourceAddr *net.TCPAddr
)
const (
TGW_PREFIX = "tgw_l7_forward"
TGW_HEADER_SIZE = 1024 * 4
TGW_HEADER_SEG_COUNT = 3
)
//初始化引數
func initArgs() bool {
flag.StringVar(&gConfig.targetAddr, "t", ":54321", "目標地址,比如app12345.qzoneapp.com:80")
flag.StringVar(&gConfig.sourceAddr, "s", ":12345", "本地的監聽地址,比如192.168.1.200:8080")
//flag.StringVar(&gConfig.logFile, "l", "/tmp/tgwProxy.log", "日誌輸出地址")
flag.BoolVar(&gConfig.actAsServer, "a", false, "是否作為伺服器,如果是的話,需要將targetAddr裡的引數發給tgw")
flag.IntVar(&gConfig.bufferSize, "b", 1024*1024, "快取大小")
flag.Parse()
var err error
gSourceAddr, err = net.ResolveTCPAddr("tcp4", gConfig.sourceAddr)
if err != nil {
fmt.Printf("resolve tcp address:%s failed:%s\n", gConfig.sourceAddr, err.Error())
return false
}
gTargetAddr, err = net.ResolveTCPAddr("tcp4", gConfig.targetAddr)
if err != nil {
fmt.Printf("resolve tcp address:%s failed:%s\n", gConfig.targetAddr, err.Error())
return false
}
if gConfig.bufferSize < 1 {
fmt.Printf("buffer size:%d is too small\n", gConfig.bufferSize)
return false
}
return true
}
//初始化伺服器
func runServer() {
listener, err := net.ListenTCP("tcp4", gSourceAddr)
if err != nil {
fmt.Printf("listen on tcp address:%s failed:%s\n", gConfig.sourceAddr, err.Error())
return
}
log.Printf("server started")
for {
conn, err := listener.AcceptTCP()
if err != nil {
log.Printf("accept tcp connection failed:%s", err.Error())
time.Sleep(1000000000)
}
log.Printf("get one connection")
conn.SetKeepAlive(true)
conn.SetNoDelay(true)
if gConfig.actAsServer {
go doServer(conn)
} else {
go doClient(conn)
}
}
}
//將所有資料寫入連線中
func writeAllData(conn net.Conn, buffer []byte) error {
offset := 0
length := len(buffer)
for offset < length {
bytes, err := conn.Write(buffer[offset:length])
if err != nil {
log.Printf("write to target:%s failed:%s", conn.RemoteAddr().String(), err.Error())
return err
}
offset += bytes
}
//log.Printf("write %d bytes to connection", offset)
return nil
}
//處理伺服器事務
func doServer(conn net.Conn) {
defer handlePanic()
//去掉tgw的頭
buffer := make([]byte, TGW_HEADER_SIZE)
length, err := conn.Read(buffer)
if err != nil {
log.Printf("read from client failed:%s", err.Error())
return
}
segCount := 0
for i := 1; i < length; i++ {
if buffer[i] == '\n' && buffer[i-1] == '\r' {
segCount++
if segCount == TGW_HEADER_SEG_COUNT {
buffer = buffer[i+1 : length]
break
}
}
}
if segCount < TGW_HEADER_SEG_COUNT {
log.Printf("invalid tgw header:%s", string(buffer[0:length]))
return
}
targetConn, err := net.DialTCP("tcp4", nil, gTargetAddr)
if err != nil {
log.Printf("connect to target:%s failed:%s", gTargetAddr.String(), err.Error())
return
}
log.Printf("connect to server:%s succeed", gTargetAddr.String())
//寫剩下的欄位
err = writeAllData(targetConn, buffer)
if err != nil {
return
}
//源請求到目標連線,即寫請求
go proxyTransfer(conn, targetConn)
//目標響應到源連線,即寫響應
go proxyTransfer(targetConn, conn)
}
//將從sourceConn中讀到的資料寫給targetConn
func proxyTransfer(sourceConn net.Conn, targetConn net.Conn) {
defer handlePanic()
buffer := make([]byte, gConfig.bufferSize)
for {
length, err := sourceConn.Read(buffer)
if err != nil {
log.Printf("read from source:%s failed:%s", sourceConn.RemoteAddr().String(), err.Error())
targetConn.Close()
return
}
//log.Printf("read %d bytes from source connection", length)
err = writeAllData(targetConn, buffer[0:length])
if err != nil {
targetConn.Close()
return
}
}
}
//處理客戶端事務
func doClient(conn net.Conn) {
defer handlePanic()
//建立連線到目錄地址
targetConn, err := net.DialTCP("tcp4", nil, gTargetAddr)
if err != nil {
log.Printf("connect to target:%s failed:%s", gTargetAddr.String(), err.Error())
return
}
log.Printf("connect to server:%s succeed", gTargetAddr.String())
//加上tgw的頭
tgwHeader := TGW_PREFIX + "\r\nHost:" + gConfig.targetAddr + "\r\n\r\n"
log.Printf("send tgwHeader:%s to server:%s", tgwHeader, gTargetAddr.String())
buffer := []byte(tgwHeader)
err = writeAllData(targetConn, buffer)
if err != nil {
return
}
//源請求到目標連線,即寫請求
go proxyTransfer(conn, targetConn)
//目標響應到源連線,即寫響應
go proxyTransfer(targetConn, conn)
}
//初始化日誌
func initLogger() bool {
log.SetFlags(log.Ldate | log.Lshortfile | log.Lmicroseconds | log.Ltime)
log.Printf("logger init")
return true
}
func handlePanic() {
err := recover()
if err != nil {
log.Printf("uncaught panic:%s", err.(error).Error())
}
}
func main() {
if !initArgs() || !initLogger() {
return
}
runServer()
}
這樣的話,在cvm上啟動一個伺服器端,監聽到127.0.0.1:1179上,然後再在cvm上啟動一個proxy讓它代理到這個埠上,而外部埠則監聽到cvm的繫結埠上。
同樣的,在本地啟動一個proxy,讓它代理到cvm的繫結域名和埠上,然後openvpn客戶端連到代理的監聽埠上,這樣果然兩臺機器就弄到一個局域網裡了。
接下來你就只需要在cvm裡建一些”合適“的賬號就可以不用動態密碼直接像區域網一樣登入了。