diff --git a/CHANGELOG.md b/CHANGELOG.md index 056eaa0..78a4622 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and the release workflow reads it to set github's release notes. ### Added -- the status command show the chosen ICE candidate pairs for connected peers +- the status command shows the chosen ICE candidate pairs for connected peers ## [1.4.0] 2024-4-14 diff --git a/go.mod b/go.mod index 7578945..c14deb2 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ require ( git.rootprojects.org/root/go-gitver/v2 v2.0.2 github.com/coreos/go-semver v0.3.1 github.com/creack/pty v1.1.20 + github.com/dchest/uniuri v1.2.0 + github.com/fatih/color v1.17.0 github.com/google/uuid v1.3.1 github.com/gorilla/websocket v1.4.2 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 @@ -29,8 +31,6 @@ require ( github.com/benbjohnson/clock v1.3.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dchest/uniuri v1.2.0 // indirect - github.com/fatih/color v1.17.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/kr/pretty v0.3.1 // indirect diff --git a/peers/peer.go b/peers/peer.go index c270fa6..8253480 100644 --- a/peers/peer.go +++ b/peers/peer.go @@ -11,6 +11,7 @@ import ( "strconv" "strings" "sync" + "text/tabwriter" "time" "unicode" @@ -76,6 +77,24 @@ type Peer struct { Conf *Conf } +// CandidatePairStats is a struct that holds the values of a ICE candidate pair +type CandidatePairStats struct { + FP string `json:"fp"` + LocalAddr string `json:"local_addr"` // IP:Port + LocalProtocol string `json:"local_proto"` + LocalType string `json:"local_type"` + RemoteAddr string `json:"remote_addr"` + RemoteProtocol string `json:"remote_proto"` + RemoteType string `json:"remote_type"` +} + +// CandidatePairStats.Write writes the candidate pair to a tabwriter +func (p *CandidatePairStats) Write(w *tabwriter.Writer) { + fp := fmt.Sprintf("%s\uf141", string([]rune(p.FP)[:6])) + fmt.Fprintln(w, strings.Join([]string{fp, p.LocalAddr, p.LocalProtocol, + p.LocalType, "->", p.RemoteAddr, p.RemoteProtocol, p.RemoteType}, "\t")) +} + // NewPeer funcions starts listening to incoming peer connection from a remote func NewPeer(fp string, conf *Conf) (*Peer, error) { webrtcAPIM.Lock() @@ -477,6 +496,47 @@ func (peer *Peer) Broadcast(typ string, args interface{}) error { } return nil } +func (peer *Peer) GetCandidatePair(ret *CandidatePairStats) error { + ret.FP = peer.FP + if peer.PC == nil { + return fmt.Errorf("peer has no peer connection") + } + stats := peer.PC.GetStats() + var localP int32 + var remoteP int32 + for _, report := range stats { + pairStats, ok := report.(webrtc.ICECandidatePairStats) + if !ok || pairStats.Type != webrtc.StatsTypeCandidatePair { + continue + } + // Check if it is selected + if !pairStats.Nominated { + continue + } + local, ok := stats[pairStats.LocalCandidateID].(webrtc.ICECandidateStats) + if !ok { + return fmt.Errorf("failed to get local candidate") + } + remote, ok := stats[pairStats.RemoteCandidateID].(webrtc.ICECandidateStats) + if !ok { + return fmt.Errorf("failed to get remote candidate") + } + if local.Priority > localP { + localP = local.Priority + ret.LocalAddr = fmt.Sprintf("%s:%d", local.IP, local.Port) + ret.LocalProtocol = local.Protocol + ret.LocalType = local.CandidateType.String() + } + if remote.Priority > remoteP { + remoteP = remote.Priority + ret.RemoteAddr = fmt.Sprintf("%s:%d", remote.IP, remote.Port) + ret.RemoteProtocol = remote.Protocol + ret.RemoteType = remote.CandidateType.String() + } + } + return nil +} + func (peer *Peer) Close() { peer.Lock() defer peer.Unlock() diff --git a/sock.go b/sock.go index fdc9972..5ec674e 100644 --- a/sock.go +++ b/sock.go @@ -14,7 +14,6 @@ import ( "runtime" "strings" "sync" - "text/tabwriter" "time" "github.com/dchest/uniuri" @@ -42,18 +41,11 @@ type LiveOffer struct { p *peers.Peer id string } -type CandidatePairValues struct { - FP string `json:"fp"` - LocalAddr string `json:"local_addr"` // IP:Port - LocalProtocol string `json:"local_proto"` - LocalType string `json:"local_type"` - RemoteAddr string `json:"remote_addr"` - RemoteProtocol string `json:"remote_proto"` - RemoteType string `json:"remote_type"` -} + +// StatusMessage is a struct that holds the response to the status request type StatusMessage struct { - Version string `json:"version"` - Peers []CandidatePairValues `json:"peers,omitempty"` + Version string `json:"version"` + Peers []peers.CandidatePairStats `json:"peers,omitempty"` } const socketFileName = "webexec.sock" @@ -207,48 +199,22 @@ func StartSocketServer(lc fx.Lifecycle, s *sockServer, params SocketStartParams) return &server, nil } +// handleStatus now uses getPeerStat to extract peer stats. func (s *sockServer) handleStatus(w http.ResponseWriter, r *http.Request) { - Logger.Info("Got status request") if r.Method != "GET" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } + ret := StatusMessage{Version: version} for _, peer := range peers.Peers { - if peer.PC == nil { - continue - } - stats := peer.PC.GetStats() - for _, report := range stats { - pairStats, ok := report.(webrtc.ICECandidatePairStats) - if !ok || pairStats.Type != webrtc.StatsTypeCandidatePair { - continue - } - // check if it is selected - if pairStats.State != webrtc.StatsICECandidatePairStateSucceeded { - continue - } - local, ok := stats[pairStats.LocalCandidateID].(webrtc.ICECandidateStats) - if !ok { - http.Error(w, "Failed to get local candidate", http.StatusInternalServerError) - return - } - remote, ok := stats[pairStats.RemoteCandidateID].(webrtc.ICECandidateStats) - if !ok { - http.Error(w, "Failed to get remote candidate", http.StatusInternalServerError) - return - } - ret.Peers = append(ret.Peers, CandidatePairValues{ - FP: peer.FP, - LocalAddr: fmt.Sprintf("%s:%d", local.IP, local.Port), - LocalProtocol: local.Protocol, - LocalType: local.CandidateType.String(), - RemoteAddr: fmt.Sprintf("%s:%d", remote.IP, remote.Port), - RemoteProtocol: remote.Protocol, - RemoteType: remote.CandidateType.String(), - }) - break + var cp peers.CandidatePairStats + err := peer.GetCandidatePair(&cp) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return } + ret.Peers = append(ret.Peers, cp) } b, err := json.Marshal(ret) if err != nil { @@ -450,9 +416,3 @@ func writeClipboard(data []byte, mimeType string) error { } return cmd.Wait() } - -func (p *CandidatePairValues) Write(w *tabwriter.Writer) { - fp := fmt.Sprintf("%s\uf141", string([]rune(p.FP)[:6])) - fmt.Fprintln(w, strings.Join([]string{fp, p.LocalAddr, p.LocalProtocol, - p.LocalType, "->", p.RemoteAddr, p.RemoteProtocol, p.RemoteType}, "\t")) -} diff --git a/webexec.go b/webexec.go index a56e9f9..9533217 100644 --- a/webexec.go +++ b/webexec.go @@ -539,6 +539,14 @@ func statusCMD(c *cli.Context) error { label := color.New().PrintfFunc() value := color.New(color.FgGreen).PrintfFunc() header := color.New(color.FgYellow).FprintfFunc() + fp := getFP() + if fp == "" { + fmt.Println("Unitialized, please run `webexec init`") + } else { + label("FP") + fmt.Printf(": ") + value("%s\n", fp) + } pid, err := getAgentPid() if err != nil { return err @@ -550,15 +558,6 @@ func statusCMD(c *cli.Context) error { fmt.Printf(": ") value("%d\n", pid) } - // TODO: Get the the fingerprints of connected peers from the agent using the status socket - fp := getFP() - if fp == "" { - fmt.Println("Unitialized, please run `webexec init`") - } else { - label("FP") - fmt.Printf(": ") - value("%s\n", fp) - } httpc := newSocketClient() if httpc == nil { return nil