Skip to content

Commit

Permalink
添加 quic ping
Browse files Browse the repository at this point in the history
  • Loading branch information
wzv5 committed May 11, 2023
1 parent 7b1b99f commit 0bde8cd
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 25 deletions.
55 changes: 55 additions & 0 deletions cmd/pping/cmd/quic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package cmd

import (
"errors"
"fmt"
"net"
"time"

"github.com/quic-go/quic-go/http3"
"github.com/spf13/cobra"
"github.com/wzv5/pping/pkg/ping"
)

type quicFlags struct {
timeout time.Duration
port uint16
insecure bool
alpn string
}

var quicflag quicFlags

func addQuicCommand() {
var cmd = &cobra.Command{
Use: "quic <host> [ip]",
Short: "quic ping",
Long: "quic ping",
Args: cobra.RangeArgs(1, 2),
RunE: runquic,
}

cmd.Flags().DurationVarP(&quicflag.timeout, "timeout", "w", time.Second*4, "timeout")
cmd.Flags().Uint16VarP(&quicflag.port, "port", "p", 443, "port")
cmd.Flags().BoolVarP(&quicflag.insecure, "insecure", "k", false, "allow insecure server connections")
cmd.Flags().StringVarP(&quicflag.alpn, "alpn", "a", http3.NextProtoH3, "ALPN")
rootCmd.AddCommand(cmd)
}

func runquic(cmd *cobra.Command, args []string) error {
host := args[0]
var ip net.IP
if len(args) == 2 {
ip = net.ParseIP(args[1])
if ip == nil {
return errors.New("parse IP failed")
}
}

fmt.Printf("Ping %s (%d):\n", host, quicflag.port)
p := ping.NewQuicPing(host, quicflag.port, quicflag.timeout)
p.Insecure = quicflag.insecure
p.ALPN = quicflag.alpn
p.IP = ip
return RunPing(p)
}
1 change: 1 addition & 0 deletions cmd/pping/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func init() {
addHttpCommand()
addIcmpCommand()
addDnsCommand()
addQuicCommand()
}

func Execute() error {
Expand Down
19 changes: 15 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,26 @@ module github.com/wzv5/pping
go 1.19

require (
github.com/miekg/dns v1.1.53
github.com/miekg/dns v1.1.54
github.com/quic-go/quic-go v0.34.0
github.com/spf13/cobra v1.7.0
golang.org/x/net v0.9.0
golang.org/x/net v0.10.0
)

require (
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/onsi/ginkgo/v2 v2.9.4 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/tools v0.8.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/tools v0.9.1 // indirect
)
78 changes: 69 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,21 +1,81 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3 h1:2XF1Vzq06X+inNqgJ9tRnGuw+ZVCB3FazXODD6JE1R8=
github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI=
github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE=
github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/quic-go/quic-go v0.34.0 h1:OvOJ9LFjTySgwOTYUZmNoq0FzVicP8YujpV0kB7m2lU=
github.com/quic-go/quic-go v0.34.0/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4=
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
24 changes: 12 additions & 12 deletions pkg/ping/icmp.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,6 @@ func (this *IcmpPing) rawping(network string) IPingResult {
}
defer conn.Close()
conn.SetDeadline(time.Now().Add(this.Timeout))
if this.TTL > 0 {
if isipv6 {
conn.IPv6PacketConn().SetHopLimit(this.TTL)
} else {
conn.IPv4PacketConn().SetTTL(this.TTL)
}
}

// 发送
r := rand.New(rand.NewSource(time.Now().UnixNano()))
Expand Down Expand Up @@ -167,18 +160,18 @@ func (this *IcmpPing) rawping(network string) IPingResult {
return this.errorResult(err)
}
recvData, recvID, recvType := this.parserecvmsg(isipv6, recvMsg)
// 修正数据长度
// 是 echo 回复,但 ID 不一致,继续接收
if recvType == 1 && network == "ip" && recvID != id {
continue
}
// 无脑修正数据长度,开头数据其实为 IP 包头
if len(recvData) > len(sendData) {
recvData = recvData[len(recvData)-len(sendData):]
}
// 收到的数据和发送的数据不一致,继续接收
if !bytes.Equal(recvData, sendData) {
continue
}
// 是 echo 回复,但 ID 不一致,继续接收
if recvType == 1 && network == "ip" && recvID != id {
continue
}

if peer != nil {
if _ip := net.ParseIP(peer.String()); _ip != nil {
Expand Down Expand Up @@ -241,6 +234,13 @@ func (this *IcmpPing) getconn(network string, ip net.IP, isipv6 bool) (*icmp.Pac
} else {
conn.IPv4PacketConn().SetControlMessage(ipv4.FlagTTL, true)
}
if this.TTL > 0 {
if isipv6 {
conn.IPv6PacketConn().SetHopLimit(this.TTL)
} else {
conn.IPv4PacketConn().SetTTL(this.TTL)
}
}
return conn, nil
}

Expand Down
106 changes: 106 additions & 0 deletions pkg/ping/quic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package ping

import (
"context"
"crypto/tls"
"fmt"
"net"
"time"

"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
)

type QuicPingResult struct {
Time int
Err error
IP net.IP
QUICVersion uint32
TLSVersion uint16
}

func (this *QuicPingResult) Result() int {
return this.Time
}

func (this *QuicPingResult) Error() error {
return this.Err
}

func (this *QuicPingResult) String() string {
if this.Err != nil {
return fmt.Sprintf("%s", this.Err)
} else {
return fmt.Sprintf("%s: quic=%s, tls=%s, time=%d ms", this.IP.String(), quic.VersionNumber(this.QUICVersion).String(), tlsVersionToString(this.TLSVersion), this.Time)
}
}

type QuicPing struct {
Host string
Port uint16
Timeout time.Duration

// 以下为可选参数
Insecure bool
ALPN string
IP net.IP
}

func (this *QuicPing) Ping() IPingResult {
return this.PingContext(context.Background())
}

func (this *QuicPing) PingContext(ctx context.Context) IPingResult {
ip := cloneIP(this.IP)
if ip == nil {
var err error
ip, err = LookupFunc(this.Host)
if err != nil {
return this.errorResult(err)
}
}
addr := net.JoinHostPort(ip.String(), fmt.Sprint(this.Port))

alpn := http3.NextProtoH3
if this.ALPN != "" {
alpn = this.ALPN
}
tlsconf := tls.Config{
ServerName: this.Host,
InsecureSkipVerify: this.Insecure,
NextProtos: []string{alpn},
}
quicconf := quic.Config{
HandshakeIdleTimeout: this.Timeout,
}
t0 := time.Now()
conn, err := quic.DialAddrContext(ctx, addr, &tlsconf, &quicconf)
if err != nil {
return this.errorResult(err)
}
closecode := uint64(http3.ErrCodeNoError)
if alpn != http3.NextProtoH3 {
closecode = 0
}
defer conn.CloseWithError(quic.ApplicationErrorCode(closecode), "")
return &QuicPingResult{int(time.Since(t0).Milliseconds()), nil, ip, uint32(conn.ConnectionState().Version), conn.ConnectionState().TLS.Version}
}

func NewQuicPing(host string, port uint16, timeout time.Duration) *QuicPing {
return &QuicPing{
Host: host,
Port: port,
Timeout: timeout,
}
}

func (this *QuicPing) errorResult(err error) *QuicPingResult {
r := &QuicPingResult{}
r.Err = err
return r
}

var (
_ IPing = (*QuicPing)(nil)
_ IPingResult = (*QuicPingResult)(nil)
)
8 changes: 8 additions & 0 deletions test/pping_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,11 @@ func TestIcmp_size_root(t *testing.T) {
t.Fatal(result.Error())
}
}

func TestQuic(t *testing.T) {
p := ping.NewQuicPing("quic.nginx.org", 443, time.Second*5)
result := p.Ping()
if result.Error() != nil {
t.Fatal(result.Error())
}
}

0 comments on commit 0bde8cd

Please sign in to comment.