1. 程式人生 > >如何在騰訊雲平臺裡搭建vpn連線本地區域網

如何在騰訊雲平臺裡搭建vpn連線本地區域網

最近需要使用騰訊雲平臺,瞭解了一番之後發現cvm必須使用它所指定的代理後使用ssh登入,另外ssh登入的時候還需要一個動態密碼,這樣對運維來說很不方便,於是就想弄個vpn將cvm和線下環境弄到一個局域網裡。這樣就有個問題了,openvpn的伺服器放在哪裡,測試了一下發現cvm只能進不能出,也即不能直接訪問外網,除非是有請求進來。於是顯然cvm只能做伺服器了,但是新問題又來了,cvm做伺服器的話tcp協議要求對應用層協議進行改造,必須傳一個tgw的頭,這就讓人鬱悶了,難道去修改openvpn的程式碼嗎?這顯然不行,於是就想幹脆寫個proxy得了,它的原理很簡單,作為一個透明代理,唯一的不同是,當它作為客戶端時,會在連線建立後立即向伺服器傳送一個tgw頭;當它作為伺服器時,會將連線建立後收到的tgw頭幹掉,當這些事情作完之後它就是一個純粹的透明代理了。這樣的程式不難寫,用golang一會就可以寫一個,下面是寫完的程式

[plain] view plaincopyprint?
  1. package main  
  2. import (  
  3.     "flag"  
  4.     "fmt"  
  5.     "log"  
  6.     "net"  
  7.     "time"  
  8. )  
  9. // 配置引數  
  10. type TConfig struct {  
  11.     targetAddr  string //目標地址,比如app12345.qzoneapp.com:80  
  12.     sourceAddr  string //源地址,即本地監聽的地址  
  13.     logFile     string //日誌檔案地址  
  14.     actAsServer bool   //是否作為伺服器,如果是的話,需要將targetAddr裡的引數發給tgw  
  15.     bufferSize  int  
  16. }  
  17. var (  
  18.     gConfig     TConfig  
  19.     gTargetAddr *net.TCPAddr  
  20.     gSourceAddr *net.TCPAddr  
  21. )  
  22. const (  
  23.     TGW_PREFIX           = "tgw_l7_forward"  
  24.     TGW_HEADER_SIZE      = 1024 * 4  
  25.     TGW_HEADER_SEG_COUNT = 3  
  26. )  
  27. //初始化引數  
  28. func initArgs() bool {  
  29.     flag.StringVar(&gConfig.targetAddr, "t", ":54321", "目標地址,比如app12345.qzoneapp.com:80")  
  30.     flag.StringVar(&gConfig.sourceAddr, "s", ":12345", "本地的監聽地址,比如192.168.1.200:8080")  
  31.     //flag.StringVar(&gConfig.logFile, "l", "/tmp/tgwProxy.log", "日誌輸出地址")  
  32.     flag.BoolVar(&gConfig.actAsServer, "a", false, "是否作為伺服器,如果是的話,需要將targetAddr裡的引數發給tgw")  
  33.     flag.IntVar(&gConfig.bufferSize, "b", 1024*1024, "快取大小")  
  34.     flag.Parse()  
  35.     var err error  
  36.     gSourceAddr, err = net.ResolveTCPAddr("tcp4", gConfig.sourceAddr)  
  37.     if err != nil {  
  38.         fmt.Printf("resolve tcp address:%s failed:%s\n", gConfig.sourceAddr, err.Error())  
  39.         return false  
  40.     }  
  41.     gTargetAddr, err = net.ResolveTCPAddr("tcp4", gConfig.targetAddr)  
  42.     if err != nil {  
  43.         fmt.Printf("resolve tcp address:%s failed:%s\n", gConfig.targetAddr, err.Error())  
  44.         return false  
  45.     }  
  46.     if gConfig.bufferSize < 1 {  
  47.         fmt.Printf("buffer size:%d is too small\n", gConfig.bufferSize)  
  48.         return false  
  49.     }  
  50.     return true  
  51. }  
  52. //初始化伺服器  
  53. func runServer() {  
  54.     listener, err := net.ListenTCP("tcp4", gSourceAddr)  
  55.     if err != nil {  
  56.         fmt.Printf("listen on tcp address:%s failed:%s\n", gConfig.sourceAddr, err.Error())  
  57.         return  
  58.     }  
  59.     log.Printf("server started")  
  60.     for {  
  61.         conn, err := listener.AcceptTCP()  
  62.         if err != nil {  
  63.             log.Printf("accept tcp connection failed:%s", err.Error())  
  64.             time.Sleep(1000000000)  
  65.         }  
  66.         log.Printf("get one connection")  
  67.         conn.SetKeepAlive(true)  
  68.         conn.SetNoDelay(true)  
  69.         if gConfig.actAsServer {  
  70.             go doServer(conn)  
  71.         } else {  
  72.             go doClient(conn)  
  73.         }  
  74.     }  
  75. }  
  76. //將所有資料寫入連線中  
  77. func writeAllData(conn net.Conn, buffer []byte) error {  
  78.     offset := 0  
  79.     length := len(buffer)  
  80.     for offset < length {  
  81.         bytes, err := conn.Write(buffer[offset:length])  
  82.         if err != nil {  
  83.             log.Printf("write to target:%s failed:%s", conn.RemoteAddr().String(), err.Error())  
  84.             return err  
  85.         }  
  86.         offset += bytes  
  87.     }  
  88.     //log.Printf("write %d bytes to connection", offset)  
  89.     return nil  
  90. }  
  91. //處理伺服器事務  
  92. func doServer(conn net.Conn) {  
  93.     defer handlePanic()  
  94.     //去掉tgw的頭  
  95.     buffer := make([]byte, TGW_HEADER_SIZE)  
  96.     length, err := conn.Read(buffer)  
  97.     if err != nil {  
  98.         log.Printf("read from client failed:%s", err.Error())  
  99.         return  
  100.     }  
  101.     segCount := 0  
  102.     for i := 1; i < length; i++ {  
  103.         if buffer[i] == '\n' && buffer[i-1] == '\r' {  
  104.             segCount++  
  105.             if segCount == TGW_HEADER_SEG_COUNT {  
  106.                 buffer = buffer[i+1 : length]  
  107.                 break  
  108.             }  
  109.         }  
  110.     }  
  111.     if segCount < TGW_HEADER_SEG_COUNT {  
  112.         log.Printf("invalid tgw header:%s", string(buffer[0:length]))  
  113.         return  
  114.     }  
  115.     targetConn, err := net.DialTCP("tcp4", nil, gTargetAddr)  
  116.     if err != nil {  
  117.         log.Printf("connect to target:%s failed:%s", gTargetAddr.String(), err.Error())  
  118.         return  
  119.     }  
  120.     log.Printf("connect to server:%s succeed", gTargetAddr.String())  
  121.     //寫剩下的欄位  
  122.     err = writeAllData(targetConn, buffer)  
  123.     if err != nil {  
  124.         return  
  125.     }  
  126.     //源請求到目標連線,即寫請求  
  127.     go proxyTransfer(conn, targetConn)  
  128.     //目標響應到源連線,即寫響應  
  129.     go proxyTransfer(targetConn, conn)  
  130. }  
  131. //將從sourceConn中讀到的資料寫給targetConn  
  132. func proxyTransfer(sourceConn net.Conn, targetConn net.Conn) {  
  133.     defer handlePanic()  
  134.     buffer := make([]byte, gConfig.bufferSize)  
  135.     for {  
  136.         length, err := sourceConn.Read(buffer)  
  137.         if err != nil {  
  138.             log.Printf("read from source:%s failed:%s", sourceConn.RemoteAddr().String(), err.Error())  
  139.             targetConn.Close()  
  140.             return  
  141.         }  
  142.         //log.Printf("read %d bytes from source connection", length)  
  143.         err = writeAllData(targetConn, buffer[0:length])  
  144.         if err != nil {  
  145.             targetConn.Close()  
  146.             return  
  147.         }  
  148.     }  
  149. }  
  150. //處理客戶端事務  
  151. func doClient(conn net.Conn) {  
  152.     defer handlePanic()  
  153.     //建立連線到目錄地址  
  154.     targetConn, err := net.DialTCP("tcp4", nil, gTargetAddr)  
  155.     if err != nil {  
  156.         log.Printf("connect to target:%s failed:%s", gTargetAddr.String(), err.Error())  
  157.         return  
  158.     }  
  159.     log.Printf("connect to server:%s succeed", gTargetAddr.String())  
  160.     //加上tgw的頭  
  161.     tgwHeader := TGW_PREFIX + "\r\nHost:" + gConfig.targetAddr + "\r\n\r\n"  
  162.     log.Printf("send tgwHeader:%s to server:%s", tgwHeader, gTargetAddr.String())  
  163.     buffer := []byte(tgwHeader)  
  164.     err = writeAllData(targetConn, buffer)  
  165.     if err != nil {  
  166.         return  
  167.     }  
  168.     //源請求到目標連線,即寫請求  
  169.     go proxyTransfer(conn, targetConn)  
  170.     //目標響應到源連線,即寫響應  
  171.     go proxyTransfer(targetConn, conn)  
  172. }  
  173. //初始化日誌  
  174. func initLogger() bool {  
  175.     log.SetFlags(log.Ldate | log.Lshortfile | log.Lmicroseconds | log.Ltime)  
  176.     log.Printf("logger init")  
  177.     return true  
  178. }  
  179. func handlePanic() {  
  180.     err := recover()  
  181.     if err != nil {  
  182.         log.Printf("uncaught panic:%s", err.(error).Error())  
  183.     }  
  184. }  
  185. func main() {  
  186.     if !initArgs() || !initLogger() {  
  187.         return  
  188.     }  
  189.     runServer()  
  190. }  
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裡建一些”合適“的賬號就可以不用動態密碼直接像區域網一樣登入了。