diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be85b79..ad81941 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: run: go build -v ./... - name: Test - run: go test $(go list ./...) + run: sudo go test $(go list ./...) # ip test needs sudo lint: name: Lint diff --git a/gold/link/listen.go b/gold/link/listen.go index 225a415..2c3a469 100644 --- a/gold/link/listen.go +++ b/gold/link/listen.go @@ -51,7 +51,7 @@ func (m *Me) listen() (conn p2p.Conn, err error) { logrus.Debugln("[listen] lock index", i) lbf := listenbuff[i*lstnbufgragsz : (i+1)*lstnbufgragsz] n, addr, err := conn.ReadFromPeer(lbf) - if m.loop == nil || errors.Is(err, net.ErrClosed) { + if m.connections == nil || errors.Is(err, net.ErrClosed) { logrus.Warnln("[listen] quit listening") return } diff --git a/gold/link/me.go b/gold/link/me.go index bd051ee..775db23 100644 --- a/gold/link/me.go +++ b/gold/link/me.go @@ -31,8 +31,6 @@ type Me struct { subnet net.IPNet // 本机 endpoint ep p2p.EndPoint - // 本机环回 link - loop *Link // 本机活跃的所有连接 connections map[string]*Link // 读写同步锁 @@ -97,17 +95,6 @@ func NewMe(cfg *MyConfig) (m Me) { cache: ttl.NewCache[string, *Link](time.Minute), } m.router.SetDefault(nil) - _, localp, err := net.SplitHostPort(m.EndPoint().String()) - if err != nil { - panic(err) - } - m.loop = m.AddPeer(&PeerConfig{ - PeerIP: m.me.String(), - EndPoint: "127.0.0.1:" + localp, - AllowedIPs: []string{cfg.MyIPwithMask}, - NoPipe: cfg.NIC != nil, - MTU: cfg.MTU, - }) m.srcport = cfg.SrcPort m.dstport = cfg.DstPort m.mtu = cfg.MTU & 0xfff8 @@ -141,7 +128,6 @@ func (m *Me) EndPoint() p2p.EndPoint { } func (m *Me) Close() error { - m.loop = nil m.connections = nil if m.conn != nil { _ = m.conn.Close() @@ -222,6 +208,14 @@ func (m *Me) sendAllSameDst(packet []byte) (n int) { rem = rem[i:] dst := waterutil.IPv4Destination(packet) logrus.Debugln("[me] sending", len(packet), "bytes packet from :"+strconv.Itoa(int(m.SrcPort())), "to", dst.String()+":"+strconv.Itoa(int(m.DstPort())), "remain:", len(rem), "bytes") + if m.me.Equal(dst) { // is to myself, write to nic (pipe not allow loopback) + logrus.Debugln("[me] loopback packet") + _, err := m.nic.Write(packet) + if err != nil { + logrus.Warnln("[me] write to loopback err:", err) + } + return + } lnk := m.router.NextHop(dst.String()) if lnk == nil { logrus.Warnln("[me] drop packet to", dst.String()+":"+strconv.Itoa(int(m.DstPort())), ": nil nexthop") diff --git a/gold/link/nat.go b/gold/link/nat.go index 61b3c5b..fc8b884 100644 --- a/gold/link/nat.go +++ b/gold/link/nat.go @@ -19,7 +19,7 @@ func (l *Link) keepAlive(dur int64) { logrus.Infoln("[nat] start to keep alive") t := time.NewTicker(time.Second * time.Duration(dur)) for range t.C { - if l.me.loop == nil { + if l.me.connections == nil { return } n, err := l.WriteAndPut(head.NewPacket(head.ProtoHello, l.me.srcport, l.peerip, l.me.dstport, nil), false) diff --git a/gold/p2p/ip/init.go b/gold/p2p/ip/init.go new file mode 100644 index 0000000..e984e39 --- /dev/null +++ b/gold/p2p/ip/init.go @@ -0,0 +1,32 @@ +package ip + +import ( + "net" + "net/netip" + + "github.com/fumiama/WireGold/gold/p2p" + "github.com/fumiama/WireGold/helper" +) + +func NewEndpoint(endpoint string, configs ...any) (p2p.EndPoint, error) { + addr, err := netip.ParseAddr(endpoint) + if err != nil { + return nil, err + } + ptcl := uint(0x04) // IPIP + return &EndPoint{ + addr: &net.IPAddr{ + IP: addr.AsSlice(), + Zone: addr.Zone(), + }, + ptcl: ptcl, + }, nil +} + +func init() { + name := helper.FolderName() + _, hasexist := p2p.Register(name, NewEndpoint) + if hasexist { + panic("network " + name + " has been registered") + } +} diff --git a/gold/p2p/ip/ip.go b/gold/p2p/ip/ip.go new file mode 100644 index 0000000..6a3fd94 --- /dev/null +++ b/gold/p2p/ip/ip.go @@ -0,0 +1,79 @@ +package ip + +import ( + "net" + "strconv" + + "github.com/fumiama/WireGold/gold/p2p" +) + +type EndPoint struct { + addr *net.IPAddr + ptcl uint +} + +func (ep *EndPoint) String() string { + return ep.addr.String() +} + +func (ep *EndPoint) Network() string { + return ep.addr.Network() +} + +func (ep *EndPoint) Euqal(ep2 p2p.EndPoint) bool { + if ep == nil || ep2 == nil { + return ep == nil && ep2 == nil + } + ipep2, ok := ep2.(*EndPoint) + if !ok { + return false + } + ipep1 := ep + return ipep1.addr.IP.Equal(ipep2.addr.IP) && + ipep1.addr.Zone == ipep2.addr.Zone +} + +func (ep *EndPoint) Listen() (p2p.Conn, error) { + conn, err := net.ListenIP( + "ip:"+strconv.Itoa(int(ep.ptcl)), + ep.addr, + ) + return &Conn{ + ep: ep, + conn: conn, + }, err +} + +type Conn struct { + ep *EndPoint + conn *net.IPConn +} + +func (conn *Conn) Close() error { + return conn.conn.Close() +} + +func (conn *Conn) String() string { + return conn.conn.LocalAddr().String() +} + +func (conn *Conn) LocalAddr() p2p.EndPoint { + ep, _ := NewEndpoint(conn.conn.LocalAddr().String()) + return ep +} + +func (conn *Conn) ReadFromPeer(b []byte) (int, p2p.EndPoint, error) { + n, addr, err := conn.conn.ReadFromIP(b) + return n, &EndPoint{ + addr: addr, + ptcl: conn.ep.ptcl, + }, err +} + +func (conn *Conn) WriteToPeer(b []byte, ep p2p.EndPoint) (int, error) { + ipep, ok := ep.(*EndPoint) + if !ok { + return 0, p2p.ErrEndpointTypeMistatch + } + return conn.conn.WriteToIP(b, ipep.addr) +} diff --git a/gold/p2p/tcp/init.go b/gold/p2p/tcp/init.go index a69a7d8..b6ecd67 100644 --- a/gold/p2p/tcp/init.go +++ b/gold/p2p/tcp/init.go @@ -6,6 +6,7 @@ import ( "time" "github.com/fumiama/WireGold/gold/p2p" + "github.com/fumiama/WireGold/helper" ) type Config struct { @@ -38,8 +39,9 @@ func newEndpoint(endpoint string, configs ...any) (*EndPoint, error) { } func init() { - _, hasexist := p2p.Register("tcp", NewEndpoint) + name := helper.FolderName() + _, hasexist := p2p.Register(name, NewEndpoint) if hasexist { - panic("network tcp has been registered") + panic("network " + name + " has been registered") } } diff --git a/gold/p2p/udp/init.go b/gold/p2p/udp/init.go index 4b056df..9c2f92b 100644 --- a/gold/p2p/udp/init.go +++ b/gold/p2p/udp/init.go @@ -5,6 +5,7 @@ import ( "net/netip" "github.com/fumiama/WireGold/gold/p2p" + "github.com/fumiama/WireGold/helper" ) func NewEndpoint(endpoint string, _ ...any) (p2p.EndPoint, error) { @@ -16,8 +17,9 @@ func NewEndpoint(endpoint string, _ ...any) (p2p.EndPoint, error) { } func init() { - _, hasexist := p2p.Register("udp", NewEndpoint) + name := helper.FolderName() + _, hasexist := p2p.Register(name, NewEndpoint) if hasexist { - panic("network udp has been registered") + panic("network " + name + " has been registered") } } diff --git a/helper/file.go b/helper/file.go index a7731b2..510dbc4 100644 --- a/helper/file.go +++ b/helper/file.go @@ -1,6 +1,10 @@ package helper -import "os" +import ( + "os" + "runtime" + "strings" +) // IsExist 文件/路径存在 func IsExist(path string) bool { @@ -13,3 +17,21 @@ func IsNotExist(path string) bool { _, err := os.Stat(path) return err != nil && os.IsNotExist(err) } + +// FolderName 本文件所在最下级文件夹名 +func FolderName() string { + _, file, _, ok := runtime.Caller(1) + if !ok { + return "" + } + i := strings.LastIndex(file, "/") + if i <= 0 { + return file + } + file = file[:i] + i = strings.LastIndex(file, "/") + if i <= 0 || i+1 >= len(file) { + return file + } + return file[i+1:] +} diff --git a/upper/services/tunnel/tunnel.go b/upper/services/tunnel/tunnel.go index fc33d8e..f5df17b 100644 --- a/upper/services/tunnel/tunnel.go +++ b/upper/services/tunnel/tunnel.go @@ -8,6 +8,7 @@ import ( "github.com/sirupsen/logrus" + _ "github.com/fumiama/WireGold/gold/p2p/ip" // support ip connection _ "github.com/fumiama/WireGold/gold/p2p/tcp" // support tcp connection _ "github.com/fumiama/WireGold/gold/p2p/udp" // support udp connection diff --git a/upper/services/tunnel/tunnel_test.go b/upper/services/tunnel/tunnel_test.go index e3d4802..b5eecbf 100644 --- a/upper/services/tunnel/tunnel_test.go +++ b/upper/services/tunnel/tunnel_test.go @@ -30,9 +30,21 @@ func testTunnel(t *testing.T, nw string, isplain bool, pshk *[32]byte, mtu uint1 t.Log("peer priv key:", hex.EncodeToString(peerpk.Private()[:])) t.Log("peer publ key:", hex.EncodeToString(peerpk.Public()[:])) + epm := "127.0.0.1" + if nw != "ip" { + epm += ":0" + } + // under macos you need to run + // + // sudo ifconfig lo0 alias 127.0.0.2 + epp := "127.0.0.2" + if nw != "ip" { + epp += ":0" + } + m := link.NewMe(&link.MyConfig{ MyIPwithMask: "192.168.1.2/32", - MyEndpoint: "127.0.0.1:0", + MyEndpoint: epm, Network: nw, PrivateKey: selfpk.Private(), SrcPort: 1, @@ -43,7 +55,7 @@ func testTunnel(t *testing.T, nw string, isplain bool, pshk *[32]byte, mtu uint1 p := link.NewMe(&link.MyConfig{ MyIPwithMask: "192.168.1.3/32", - MyEndpoint: "127.0.0.1:0", + MyEndpoint: epp, Network: nw, PrivateKey: peerpk.Private(), SrcPort: 1, @@ -212,6 +224,38 @@ func TestTunnelTCPSmallMTU(t *testing.T) { testTunnel(t, "tcp", false, &buf, 1024) // test preshared } +func TestTunnelIP(t *testing.T) { + logrus.SetLevel(logrus.DebugLevel) + logrus.SetFormatter(&logFormat{enableColor: false}) + + testTunnel(t, "ip", true, nil, 4096) // test plain text + + testTunnel(t, "ip", false, nil, 4096) // test normal + + var buf [32]byte + _, err := rand.Read(buf[:]) + if err != nil { + panic(err) + } + testTunnel(t, "ip", false, &buf, 4096) // test preshared +} + +func TestTunnelIPSmallMTU(t *testing.T) { + logrus.SetLevel(logrus.DebugLevel) + logrus.SetFormatter(&logFormat{enableColor: false}) + + testTunnel(t, "ip", true, nil, 1024) // test plain text + + testTunnel(t, "ip", false, nil, 1024) // test normal + + var buf [32]byte + _, err := rand.Read(buf[:]) + if err != nil { + panic(err) + } + testTunnel(t, "ip", false, &buf, 1024) // test preshared +} + // logFormat specialize for go-cqhttp type logFormat struct { enableColor bool