现在搞 p2p 很简单了

rkonfj · 2025-3-16 21:10:13 · 312 次点击

我是 https://github.com/sigcn/pg 仓库的作者。 经过几个月的努力,现在用 PG 写 P2P 网络程序似乎很简单了。 发帖希望

  1. 得到一些关于 P2P 开发库 api 更好的设计思路。易用性和扩展性。
  2. 有感兴趣的朋友会基于 PG 开发一些实用的 P2P 应用。

这是一个访问虚拟网络内节点 HTTP 服务的示例

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"os"
	"time"

	"github.com/sigcn/pg/disco"
	"github.com/sigcn/pg/langs"
	"github.com/sigcn/pg/p2p"
	"github.com/sigcn/pg/peermap/network"
	"github.com/sigcn/pg/vpn"
	"github.com/sigcn/pg/vpn/nic"
	"github.com/sigcn/pg/vpn/nic/gvisor"
	"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
	"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
	"gvisor.dev/gvisor/pkg/tcpip/stack"
	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
)

var (
	server     = "wss://openpg.in/pg"
	secretFile = "psns.json"
	_, ip4, _  = net.ParseCIDR("100.99.0.27/24")
)

// prepareSecret 准备 JSONSecret 用于加入 PG 网络
func prepareSecret() error {
	fetchSecret := func() error {
		join, err := network.JoinOIDC("", server)
		if err != nil {
			return err
		}
		fmt.Println("Open the following link to authenticate")
		fmt.Println(join.AuthURL())
		ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
		secret, err := join.Wait(ctx)
		cancel()
		if err != nil {
			panic(err)
		}
		f, err := os.Create(secretFile)
		if err != nil {
			return err
		}
		json.NewEncoder(f).Encode(secret)
		return nil
	}

	f, err := os.Open(secretFile)
	if err != nil {
		return fetchSecret()
	}
	var secret disco.NetworkSecret
	if err := json.NewDecoder(f).Decode(&secret); err != nil {
		return err
	}
	if time.Now().After(secret.Expire) {
		return fetchSecret()
	}
	return nil
}

// startVPN 启动 VPN (gVisor + p2p)
func startVPN(ctx context.Context) *gvisor.GvisorCard {
	s := stack.New(stack.Options{
		NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
		TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol},
	})

	vnic := nic.VirtualNIC{NIC: &gvisor.GvisorCard{Stack: s, Config: nic.Config{IPv4: ip4.String()}}}
	packetConn := langs.Must(p2p.ListenPacket(
		&disco.Server{Secret: &disco.FileSecretStore{StoreFilePath: secretFile}, URL: server},
		p2p.ListenPeerUp(func(pi disco.PeerID, v url.Values) {
			vnic.AddPeer(nic.Peer{Addr: pi, IPv4: v.Get("alias1"), IPv6: v.Get("alias2"), Meta: v})
		}),
		p2p.ListenPeerSecure(),
		p2p.PeerAlias1(ip4.IP.String()),
	))

	go vpn.New(vpn.Config{MTU: 1371}).Run(ctx, &vnic, packetConn)
	return vnic.NIC.(*gvisor.GvisorCard)
}

func main() {
	// 获取 JSONSecret
	if err := prepareSecret(); err != nil {
		panic(err)
	}

	// 启动 gVisor VPN
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	gvisorCard := startVPN(ctx)

	// HTTP 请求
	cli := http.Client{
		Transport: &http.Transport{DialContext: gvisorCard.DialContext},
		Timeout:   15 * time.Second,
	}

	r := langs.Must(cli.Get("http://100.99.0.2"))
	defer r.Body.Close()
	io.Copy(os.Stdout, r.Body)
}
举报· 312 次点击
登录 注册 站外分享
2 条回复  
lekai63 小成 2025-3-16 22:07:21
感谢。但似乎一下没想到很赞的使用场景。。
lizhenda 小成 2025-3-16 22:09:52
什么场景适用呢? 做个相关的 demo 是不是更好点。
返回顶部