VPN學(xué)習(xí)筆記

前言

VPN技術(shù)是企業(yè)比較常用的通信技術(shù),如果一個(gè)企業(yè)的分公司和總部的互訪,或者出差員工需要訪問(wèn)總部的網(wǎng)絡(luò)弹灭,都會(huì)使用VPN技術(shù)。

VPN是一類技術(shù)的統(tǒng)稱揪垄,隨著技術(shù)的發(fā)展穷吮,產(chǎn)生了多種可以實(shí)現(xiàn)VPN解決方案,如IPSec VPN饥努、GRE VPN捡鱼、L2TP VPN和SSL VPN等。但這些VPN解決方案都有兩個(gè)共同的基本特點(diǎn):

  • 主要應(yīng)用于通過(guò)公共的Internet進(jìn)行遠(yuǎn)程網(wǎng)絡(luò)連接酷愧,滿足了遠(yuǎn)程網(wǎng)絡(luò)連接的便捷性驾诈;
  • 不是直接通過(guò)公共的Internet來(lái)傳輸數(shù)據(jù),而是會(huì)采取各種安全保密技術(shù)(或稱“隧道”技術(shù))溶浴,使得人們擔(dān)心的安全問(wèn)題也隨之得到解決乍迄。

0x00 VPN產(chǎn)生的背景

image.png

如圖所示,企業(yè)的總部和分支機(jī)構(gòu)位于不同區(qū)域(比如位于不同的國(guó)家或城市)士败,當(dāng)分支機(jī)構(gòu)員工需訪問(wèn)總部服務(wù)器的時(shí)候就乓,數(shù)據(jù)傳輸要經(jīng)過(guò)Internet。由于Internet中存在多種不安全因素拱烁,則當(dāng)分支機(jī)構(gòu)的員工向總部服務(wù)器發(fā)送訪問(wèn)請(qǐng)求時(shí)生蚁,報(bào)文容易被網(wǎng)絡(luò)中的黑客竊取或篡改。最終造成數(shù)據(jù)泄密戏自、重要數(shù)據(jù)被破壞等后果邦投。

那么有沒(méi)有一種技術(shù)既能實(shí)現(xiàn)總部和分部間的互通,也能夠保證數(shù)據(jù)傳輸?shù)陌踩阅兀?/p>

答案是當(dāng)然有擅笔。

為了防止信息泄露志衣,可以在總部和分支機(jī)構(gòu)之間搭建一條物理專網(wǎng)連接。

image.png

物理專線:

  • 物理專用信道就是在服務(wù)商到用戶之間鋪設(shè)一條專用的線路猛们,線路只給用戶獨(dú)立使用念脯,其他數(shù)據(jù)不能進(jìn)入此線路。例如SDH弯淘、MSTP

確實(shí)能解決當(dāng)前的問(wèn)題绿店,但是價(jià)格比較昂貴(總行,分行銀行使用)

那么有沒(méi)有成本也比較低的方案呢?

有假勿,那就是通過(guò)公網(wǎng)建立私有數(shù)據(jù)通道借嗽,例如VPN

image.png

虛擬專線:

  • 虛擬專用信道通過(guò)公網(wǎng)建立私有數(shù)據(jù)通道,例如VPN

總結(jié):

  • 走公網(wǎng)转培,但是公網(wǎng)不安全恶导,你的隱私可能會(huì)被別人偷窺
  • 租用專線的方式,很貴
  • 使用VPN的方式浸须,安全又不貴

0x01 什么是VPN

VPN(Virtual Private Network) ,即虛擬個(gè)人網(wǎng)絡(luò)惨寿,是指在公共網(wǎng)絡(luò)中建立虛擬專用通信網(wǎng)絡(luò),基本原理是利用隧道技術(shù)删窒,把要傳輸?shù)脑g(shù)協(xié)議數(shù)據(jù)包封裝在隧道協(xié)議中進(jìn)行傳輸

0x02 VPN分類

站點(diǎn)到站點(diǎn)VPN(企業(yè)總部與分支機(jī)構(gòu)缤沦,企業(yè)與合作伙伴)

  • GRE(Generic Routing Encapsulation,通過(guò)路由封裝)
  • IPSec(Internet protocol security易稠,Internet 安全協(xié)議)
  • MPLS(Multi-Protocol Label Switching缸废,多協(xié)議標(biāo)簽交換)


    image.png

遠(yuǎn)程訪問(wèn)VPN(出差員工訪問(wèn)企業(yè)內(nèi)部資源,移動(dòng)用戶訪問(wèn)企業(yè)內(nèi)部)

  • IPSec
  • PPTP(Point-to-Point Tunneling Protocol驶社,點(diǎn)對(duì)點(diǎn)隧道協(xié)議)
  • L2TP(Layer 2 Tunneling Protocol企量,二層隧道協(xié)議) + IPSec
  • SSL VPN (open VPN)


    image.png

0x03 VPN如何工作的

1、 GRE協(xié)議

GRE generic routing encapsulation 通用理由封裝亡电,是簡(jiǎn)單VPN届巩,GRE是三層隧道協(xié)議,采用了Tunnel隧道技術(shù)

報(bào)文格式??:

image.png

GRE 工作原理:


image.png

image.png
  • 首先沒(méi)有做NAT份乒,192.168.1.1 是不能訪問(wèn) 192.168.2.1
  • GRE封裝數(shù)據(jù)包恕汇,產(chǎn)生新的IP頭部(S:100.1.1.1 D:100.1.2.2)送到目的之后拆包
  • 可以看到原始報(bào)文在隧道的一端進(jìn)行封裝,封裝后的數(shù)據(jù)在公網(wǎng)上傳輸或辖,在隧道另一端進(jìn)行解封裝瘾英,從而實(shí)現(xiàn)了數(shù)據(jù)的傳輸。

優(yōu)點(diǎn):支持IP網(wǎng)絡(luò)最為承載網(wǎng)絡(luò),支持多種協(xié)議,支持IP組播,配置簡(jiǎn)單,容易部署

缺點(diǎn):缺少保護(hù)功能,不能執(zhí)行如認(rèn)證,加密,以及數(shù)據(jù)完整性檢查

因?yàn)榘踩系南拗?GRE通常不能用作一個(gè)完整的VPN解決方案,然而它可以和其他的解決方案結(jié)合使用,例如IPSec,來(lái)產(chǎn)生一個(gè)極強(qiáng)大的,具有擴(kuò)展性的VPN實(shí)施方案

2颂暇、IPSec協(xié)議

前面說(shuō)了缺谴,使用GRE不安全,所以接下來(lái)我們看一種十分安全的VPN耳鸯,IPSec VPN湿蛔。基于 IP 協(xié)議的安全隧道協(xié)議县爬,有如下特點(diǎn):

IPSec不是一個(gè)單獨(dú)的協(xié)議阳啥,而是包括一組協(xié)議,包括AH(Authentication Header 認(rèn)證頭)協(xié)議财喳,ESP(Eccapsulating Security Payload 封裝有效載荷)協(xié)議察迟,IKE(Internet key Exchange,秘鑰管理協(xié)議),以及用戶身份認(rèn)證和數(shù)據(jù)加密的一系列算法

更詳細(xì)介紹:劉超<<趣談網(wǎng)絡(luò)協(xié)議>>,<<華為VPN指南>>

0x04 Tun設(shè)備

那么在代碼層面 VPN 是如何實(shí)現(xiàn)的呢卷拘?VPN隧道的實(shí)現(xiàn)依賴于Linux內(nèi)核提供的tun虛擬網(wǎng)絡(luò)接口喊废,tun設(shè)備一端連著內(nèi)核網(wǎng)絡(luò)協(xié)議棧祝高,另一端連著用戶態(tài)程序栗弟,用戶態(tài)程序可以通過(guò)文件句柄的形式操作tun設(shè)備,tun設(shè)備對(duì)應(yīng)的設(shè)備文件為 /dev/net/tun工闺。當(dāng)內(nèi)核發(fā)送一個(gè)數(shù)據(jù)包給tun設(shè)備時(shí)乍赫,tun設(shè)備會(huì)把該數(shù)據(jù)包轉(zhuǎn)給用戶態(tài)程序,即用戶態(tài)程序通過(guò)文件句柄就能讀到tun設(shè)備過(guò)來(lái)的數(shù)據(jù)包陆蟆;用戶態(tài)程序?qū)υ撐募浔膶懖僮饕矔?huì)通過(guò)tun設(shè)備轉(zhuǎn)換成一個(gè)數(shù)據(jù)報(bào)文傳給內(nèi)核網(wǎng)絡(luò)協(xié)議棧雷厂。

image.png
// 創(chuàng)建tun0設(shè)備
[root@dev tun]$ ip tuntap add dev tun0 mod tun
[root@dev tun]$ ip link show | grep tun
5: tun0: <POINTOPOINT,MULTICAST,NOARP> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 500
// 配置IP并up
[root@dev tun]$ ifconfig tun0 1.1.1.101/24 up
[root@dev tun]$ ifconfig tun0
tun0: flags=4241<UP,POINTOPOINT,NOARP,MULTICAST>  mtu 1500
        inet 1.1.1.101  netmask 255.255.255.0  destination 1.1.1.101
        unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 500  (UNSPEC)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

[root@dev tun]$ route -n | grep tun
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
1.1.1.0         0.0.0.0         255.255.255.0   U     0      0        0 tun0

目的是1.2.3.0 的包,直接通過(guò)tun0轉(zhuǎn)發(fā)數(shù)據(jù)包

代碼實(shí)現(xiàn):

用戶態(tài)程序通過(guò) icmp 協(xié)議將原始的數(shù)據(jù)包發(fā)送到目標(biāo)主機(jī)叠殷。當(dāng)目標(biāo)主機(jī)通過(guò)網(wǎng)絡(luò)接受到數(shù)據(jù)包后再寫入到 /dev/net/tun 設(shè)備中改鲫,/dev/net/tun 再將數(shù)據(jù)包注入到內(nèi)核的網(wǎng)絡(luò)協(xié)議棧按照正常到達(dá)的數(shù)據(jù)包來(lái)處理

package main

import (
    "golang.org/x/net/icmp"
    "golang.org/x/net/ipv4"
    "log"
    "os"
    "syscall"
    "unsafe"
)

const (
    // tun設(shè)備文件
    tunDeviceFile = "/dev/net/tun"
    // tun設(shè)備名稱
    tunDeviceName = "tun0"
    // 默認(rèn)MTU為1500,此處用作數(shù)據(jù)buffer大小
    defaultMTU = 1500
)

// ifReq 表示網(wǎng)絡(luò)接口相關(guān)請(qǐng)求
type ifReq struct {
    // name字段可以用來(lái)存儲(chǔ)接口的名稱(例如"eth0")
    name [16]byte
    // flags字段可以用來(lái)存儲(chǔ)與該接口相關(guān)的各種標(biāo)志或設(shè)置
    flags uint16
}

func main() {
    // 打開tun設(shè)備文件
    tun, err := os.OpenFile(tunDeviceFile, os.O_RDWR, 0)
    if err != nil {
        log.Printf("OpenFile error: %s", err.Error())
        return
    }
    defer tun.Close()
    log.Printf("open tun file %s success", tunDeviceFile)

    // ioctl設(shè)置
    // IFF_TUN 和 IFF_TAP, TUNSETIF F定義在了 linux/if_tun.h 這個(gè)頭文件中
    // IFF_TUN 和 IFF_TAP 則表示是要使用 tun 類型還是 tap 類型的虛擬網(wǎng)卡
    // TUNSETIFF 這個(gè)常量是告訴 ioctl 要完成虛擬網(wǎng)卡的注冊(cè)林束,
    // IFF_NO_PI - 不需要提供包的信息
    var ir = ifReq{
        flags: syscall.IFF_TUN | syscall.IFF_NO_PI,
    }
    copy(ir.name[:], tunDeviceName)
    // 執(zhí)行系統(tǒng)調(diào)用像棘。傳遞給第一個(gè)參數(shù)的是 SYS_IOCTL 常量, 表明正在進(jìn)行 ioctl 系統(tǒng)調(diào)用。
    // 第二個(gè)參數(shù)是名為 tun 的對(duì)象的文件描述符,
    // 第三個(gè)參數(shù)是 TUNSETIFF 常量, 用于設(shè)置 TUN/TAP 設(shè)備的接口壶冒。
    _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, tun.Fd(), syscall.TUNSETIFF, uintptr(unsafe.Pointer(&ir)))
    if errno != 0 {
        log.Printf("ioctl error: expect 0 but got %d", errno)
        return
    }
    log.Printf("ioctl success")

    buffer := make([]byte, defaultMTU)
    for {
        // tun 讀取數(shù)據(jù)到 buffer
        n, err := tun.Read(buffer)
        if err != nil {
            log.Printf("read data from tun error: %s", err.Error())
            return
        }

        // %x是十六進(jìn)制輸出數(shù)據(jù)
        log.Printf("read %d bytes data from tun device: %x", n, buffer[:n])

        /* ICMP數(shù)據(jù)處理部分start */
        // 前20個(gè)字節(jié)是IP首部缕题,因此解析ICMP報(bào)文是從第21字節(jié)開始
        // 注意程序的下標(biāo)是從0開始
        const protocolICMP = 1
        message, err := icmp.ParseMessage(protocolICMP, buffer[20:n])
        if err != nil {
            log.Printf("icmp ParseMessage error")
            return
        }

        // 修改ICMP報(bào)文類型為0,結(jié)合ICMP代碼字段(echo request報(bào)文時(shí)為0)
        // 表示將該報(bào)文改為了echo reply
        const typeEchoReply = 0
        message.Type = ipv4.ICMPType(typeEchoReply)

        // Marshal過(guò)程中會(huì)對(duì)ICMP的校驗(yàn)和重新計(jì)算
        icmpBytes, err := message.Marshal(nil)
        if err != nil {
            log.Printf("icmp message marshal error: %s", err.Error())
            return
        }
        log.Printf("icmp bytes length: %d", len(icmpBytes))
        /* ICMP數(shù)據(jù)處理部分end */

        /* IP首部數(shù)據(jù)處理部分start */
        // 前20個(gè)字節(jié)是IP首部胖腾,IP首部解析時(shí)只解析到第20字節(jié)數(shù)據(jù)
        ipv4Header, err := ipv4.ParseHeader(buffer[:20])
        if err != nil {
            log.Printf("ipv4 ParseHeader error: %s", err.Error())
            return
        }

        // 回程報(bào)文交換源烟零、目的地址
        ipv4Header.Src, ipv4Header.Dst = ipv4Header.Dst, ipv4Header.Src

        // Marshal過(guò)程中會(huì)對(duì)IP首部的校驗(yàn)和重新計(jì)算
        ipv4HeaderBytes, err := ipv4Header.Marshal()
        if err != nil {
            log.Printf("ipv4 header marshal error: %s", err.Error())
            return
        }
        log.Printf("ipv4 header length: %d", len(ipv4HeaderBytes))
        /* IP首部數(shù)據(jù)處理部分end */

        // 把IP首部數(shù)據(jù)和ICMP數(shù)據(jù)拼接起來(lái)組成完成的ICMP echo reply三層報(bào)文
        reply := append(ipv4HeaderBytes, icmpBytes...)
        log.Printf("reply: %x", reply)

        // 將ICMP echo reply報(bào)文寫入tun設(shè)備
        n, err = tun.Write(reply)
        if err != nil {
            log.Printf("write data to tun device error: %s", err.Error())
            return
        }
        log.Printf("write %d bytes data to tun device", n)
    }
}
image.png

當(dāng)創(chuàng)建了虛擬網(wǎng)卡tun設(shè)備后,發(fā)到這個(gè)網(wǎng)卡的數(shù)據(jù)包會(huì)被/dev/net/tun攔截并返回給打開它的上層程序咸作,上層程序可以通過(guò)udp锨阿,tcpicmp 協(xié)議將原始的數(shù)據(jù)包發(fā)送到目標(biāo)主機(jī)记罚。

那么 當(dāng)目標(biāo)主機(jī)通過(guò)網(wǎng)絡(luò)接受到數(shù)據(jù)包后再寫入到 /dev/net/tun 設(shè)備中群井,/dev/net/tun 再將數(shù)據(jù)包注入到內(nèi)核的網(wǎng)絡(luò)協(xié)議棧按照正常到達(dá)的數(shù)據(jù)包來(lái)處理 這樣就可以實(shí)現(xiàn)一個(gè)簡(jiǎn)單的VPN

0x05 簡(jiǎn)單simple VPN實(shí)現(xiàn)

實(shí)現(xiàn)思路:

node1上數(shù)據(jù)包到了tun1后,tun1設(shè)備會(huì)把該數(shù)據(jù)包轉(zhuǎn)到simple VPN程序中毫胜,如果simple VPN以UDP方式把數(shù)據(jù)從node1發(fā)到node2上的simple VPN书斜,node2上simple VPN收到數(shù)據(jù)后再把該數(shù)據(jù)寫入node2上的tun1,這就相當(dāng)于node1上從tun1進(jìn)來(lái)的數(shù)據(jù)會(huì)從node2上的tun1出來(lái)酵使〖黾回程報(bào)文也類似流程,這樣就把兩個(gè)節(jié)點(diǎn)的tun1設(shè)備打通了

image.png

創(chuàng)建一個(gè)tun設(shè)備tun0口渔,給該tun設(shè)備配置IP并up:

// node1

// 創(chuàng)建tun1設(shè)備
ip tuntap add dev tun1 mod tun

// 配置IP并UP
ifconfig tun1 1.2.3.100/24 up

tun1: flags=4241<UP,POINTOPOINT,NOARP,MULTICAST>  mtu 1500
        inet 1.2.3.100  netmask 255.255.255.0  destination 1.2.3.200
        unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 500  (UNSPEC)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

// 查看路由
route -n
1.2.3.0         0.0.0.0         255.255.255.0   U     0      0        0 tun1


// node2

// 創(chuàng)建tun1設(shè)備
ip tuntap add dev tun1 mod tun

// 配置IP并UP
ifconfig tun1 1.2.3.200/24 up

tun1: flags=4241<UP,POINTOPOINT,NOARP,MULTICAST>  mtu 1500
        inet 1.2.3.200  netmask 255.255.255.0  destination 1.2.3.200
        unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 500  (UNSPEC)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

// 查看路由
route -n

1.2.3.0         0.0.0.0         255.255.255.0   U     0      0        0 tun1

代碼實(shí)現(xiàn)

package main

import (
    "flag"
    "fmt"
    "log"
    "net"
    "os"
    "syscall"
    "unsafe"
)

const (
    // tun設(shè)備文件
    tunDeviceFile = "/dev/net/tun"
    // tun設(shè)備名稱
    tunDeviceName = "tun1"
    // 默認(rèn)MTU為1500样屠,此處用作數(shù)據(jù)buffer大小
    // 一個(gè)IP頭(20字節(jié))和一個(gè)UDP頭(8)字節(jié)。如果f設(shè)置1500,
    // 加上IP和UDP頭的28字節(jié)數(shù)據(jù)痪欲,到達(dá)宿主機(jī)eth0的時(shí)候最大報(bào)文會(huì)超過(guò)eth0的MTU悦穿,eth0會(huì)把該數(shù)據(jù)包丟棄
    defaultMTU = 1472
    udpPort    = 8285
)

var dstUDPHost = flag.String("d", "127.0.0.1:8285", "destination UDP host")

type ifReq struct {
    name  [16]byte
    flags uint16
}

func main() {
    flag.Parse()
    // 初始化tun設(shè)備
    tun, err := InitTunDevice(SendUDPData)
    if err != nil {
        log.Printf("initTunDevice error: %s", err.Error())
        return
    }
    defer tun.Close()
    log.Printf("initTunDevice tun with file %s success", tunDeviceFile)

    handler := func(data []byte) {
        n, err := tun.Write(data)
        if err != nil {
            log.Printf("handler write data to tun device error: %s", err.Error())
            return
        }
        log.Printf("handler write %d bytes data to tun device", n)
    }
    UDPServer(handler)
}

// InitTunDevice 初始化tun設(shè)備
// 調(diào)用方負(fù)責(zé)關(guān)閉文件句柄
func InitTunDevice(udpSend func([]byte) (int, error)) (*os.File, error) {
    // 打開tun設(shè)備文件
    tun, err := os.OpenFile(tunDeviceFile, os.O_RDWR, 0)
    if err != nil {
        return nil, fmt.Errorf("os.OpenFile error: %s", err.Error())
    }

    // ioctl設(shè)置
    var ir = ifReq{
        flags: syscall.IFF_TUN | syscall.IFF_NO_PI,
    }
    copy(ir.name[:], tunDeviceName)

    _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, tun.Fd(), syscall.TUNSETIFF, uintptr(unsafe.Pointer(&ir)))
    if errno != 0 {
        return nil, fmt.Errorf("ioctl error: expect 0 but got %d", errno)
    }
    log.Printf("ioctl success")

    go func() {
        buffer := make([]byte, defaultMTU)
        for {
            n, err := tun.Read(buffer)
            if err != nil {
                log.Printf("read data from tun error: %s", err.Error())
                return
            }

            log.Printf("read %d bytes data from tun device", n)

            num, err := udpSend(buffer[:n])
            if err != nil {
                log.Printf("udpSend error: %s", err.Error())
                return
            }
            log.Printf("udp send %d bytes data", num)
        }
    }()

    return tun, nil
}

// UDPServer 接收UDP數(shù)據(jù)
func UDPServer(handler func(data []byte)) {
    updServerHost := fmt.Sprintf(":%d", udpPort)
    conn, err := net.ListenUDP("udp", &net.UDPAddr{
        IP:   net.IPv4(0, 0, 0, 0),
        Port: udpPort,
    })
    if err != nil {
        log.Fatalf("net.Listen error: %s", err.Error())
    }
    defer conn.Close()

    log.Printf("udp listen on: %s", updServerHost)

    var buffer = make([]byte, defaultMTU)
    for {
        n, _, err := conn.ReadFromUDP(buffer)
        if err != nil {
            fmt.Printf("conn.ReadFromUDP err:[%v]\n", err)
        }
        defer conn.Close()
        log.Printf("read %d bytes data from udp", n)

        // 接收數(shù)據(jù)
        go handler(buffer[:n])
    }
}

// SendUDPData 發(fā)送udp數(shù)據(jù)
func SendUDPData(data []byte) (int, error) {
    serverAddr, err := net.ResolveUDPAddr("udp", *dstUDPHost)
    if err != nil {
        log.Fatalln("failed to resolve server addr:", err)
    }
    conn, err := net.DialUDP("udp", nil, serverAddr)
    if err != nil {
        return 0, fmt.Errorf("net.Dial error: %s", err.Error())
    }

    return conn.Write(data)
}

// 在node1 執(zhí)行
go run main.go -d 10.128.128.28:8285
// 在node2 執(zhí)行
go run main.go -d 10.128.128.140:8285

驗(yàn)證

驗(yàn)證下ping功能:

// node1
$ ping -c 1 1.2.3.200
PING 1.2.3.200 (1.2.3.200) 56(84) bytes of data.
64 bytes from 1.2.3.200: icmp_seq=1 ttl=62 time=1.05 ms

--- 1.2.3.200 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.053/1.053/1.053/0.000 ms


// node2
$ ping -c 1 1.2.3.100
PING 1.2.3.100 (1.2.3.100) 56(84) bytes of data.
64 bytes from 1.2.3.100: icmp_seq=1 ttl=62 time=0.564 ms

--- 1.2.3.100 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.564/0.564/0.564/0.000 ms

0x06參數(shù):

一起動(dòng)手寫一個(gè)VPN

VPN 原理以及實(shí)現(xiàn)

淺談tun設(shè)備

Creating a mesh VPN tool for fun and learning

21張圖帶你了解什么是VPN

華為VPN學(xué)習(xí)指南

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市业踢,隨后出現(xiàn)的幾起案子栗柒,更是在濱河造成了極大的恐慌,老刑警劉巖知举,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞬沦,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡雇锡,警方通過(guò)查閱死者的電腦和手機(jī)逛钻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)锰提,“玉大人曙痘,你說(shuō)我怎么就攤上這事×⒅猓” “怎么了边坤?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)赛不。 經(jīng)常有香客問(wèn)我惩嘉,道長(zhǎng),這世上最難降的妖魔是什么踢故? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任文黎,我火速辦了婚禮,結(jié)果婚禮上殿较,老公的妹妹穿的比我還像新娘耸峭。我一直安慰自己,他們只是感情好淋纲,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布劳闹。 她就那樣靜靜地躺著,像睡著了一般洽瞬。 火紅的嫁衣襯著肌膚如雪本涕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天伙窃,我揣著相機(jī)與錄音菩颖,去河邊找鬼。 笑死为障,一個(gè)胖子當(dāng)著我的面吹牛晦闰,可吹牛的內(nèi)容都是我干的放祟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼呻右,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼跪妥!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起声滥,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤眉撵,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后醒串,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體执桌,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鄙皇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年芜赌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伴逸。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缠沈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出错蝴,到底是詐尸還是另有隱情洲愤,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布顷锰,位于F島的核電站柬赐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏官紫。R本人自食惡果不足惜肛宋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望束世。 院中可真熱鬧酝陈,春花似錦、人聲如沸毁涉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)贫堰。三九已至穆壕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間其屏,已是汗流浹背喇勋。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留漫玄,地道東北人茄蚯。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓压彭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親渗常。 傳聞我的和親對(duì)象是個(gè)殘疾皇子壮不,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容