Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions internal/command/mping.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ mping dns://8.8.8.8/google.com`,
probeManager.Stop()

// Final results
metrics := metricsManager.SortBy(stats.Success, true)
tableData := shared.NewTableData(metrics, stats.Success, true)
metrics := metricsManager.SortBy(stats.Fail, false)
tableData := shared.NewTableData(metrics, stats.Fail, false)
t := tableData.ToGoPrettyTable()
t.SetStyle(table.StyleLight)
cmd.Println(t.Render())
Expand All @@ -124,7 +124,7 @@ mping dns://8.8.8.8/google.com`,
return cmd
}

func startTUI(manager *stats.MetricsManager, cfg *shared.Config, interval, timeout time.Duration) {
func startTUI(manager stats.MetricsManager, cfg *shared.Config, interval, timeout time.Duration) {
app := tui.NewTUIApp(manager, cfg, interval, timeout)

refreshTime := time.Millisecond * 250 // Minimum refresh time that can be set
Expand Down
48 changes: 48 additions & 0 deletions internal/prober/details.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package prober

// Probe detail information (TargetIP and TCPDetails removed)
type ProbeDetails struct {
ProbeType string `json:"probe_type"`

// Type-specific details (only one should be used)
ICMP *ICMPDetails `json:"icmp,omitempty"`
HTTP *HTTPDetails `json:"http,omitempty"`
DNS *DNSDetails `json:"dns,omitempty"`
NTP *NTPDetails `json:"ntp,omitempty"`
// TCP has no detailed information (only connection availability)
}

type ICMPDetails struct {
Sequence int `json:"sequence"`
PacketSize int `json:"packet_size"`
ICMPType int `json:"icmp_type"`
ICMPCode int `json:"icmp_code"`
Checksum uint16 `json:"checksum"`
Payload string `json:"payload"` // Actual payload content with length limit
}

type HTTPDetails struct {
StatusCode int `json:"status_code"`
ResponseSize int64 `json:"response_size"`
Headers map[string]string `json:"headers,omitempty"`
Redirects []string `json:"redirects,omitempty"`
}

type DNSDetails struct {
Server string `json:"server"`
Port int `json:"port"`
Domain string `json:"domain"`
RecordType string `json:"record_type"`
ResponseCode int `json:"response_code"`
AnswerCount int `json:"answer_count"`
Answers []string `json:"answers,omitempty"`
UseTCP bool `json:"use_tcp"`
}

type NTPDetails struct {
Server string `json:"server"`
Port int `json:"port"`
Stratum int `json:"stratum"`
Offset int64 `json:"offset_microseconds"` // In microseconds
Precision int `json:"precision"`
}
25 changes: 23 additions & 2 deletions internal/prober/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ func (p *DNSProber) sendProbe(result chan *Event, target *DNSTarget, timeout tim
}

// Success
p.success(result, target, now, rtt)
p.success(result, target, now, rtt, r)
}

func (p *DNSProber) sent(result chan *Event, target *DNSTarget, sentTime time.Time) {
Expand All @@ -261,14 +261,35 @@ func (p *DNSProber) sent(result chan *Event, target *DNSTarget, sentTime time.Ti
}
}

func (p *DNSProber) success(result chan *Event, target *DNSTarget, sentTime time.Time, rtt time.Duration) {
func (p *DNSProber) success(result chan *Event, target *DNSTarget, sentTime time.Time, rtt time.Duration, resp *dns.Msg) {
// Create DNS detail information
var answers []string
for _, ans := range resp.Answer {
answers = append(answers, ans.String())
}

details := &ProbeDetails{
ProbeType: "dns",
DNS: &DNSDetails{
Server: target.Server,
Port: target.Port,
Domain: target.Domain,
RecordType: target.RecordType,
ResponseCode: resp.Rcode,
AnswerCount: len(resp.Answer),
Answers: answers,
UseTCP: target.UseTCP,
},
}

result <- &Event{
Key: target.OriginalTarget,
DisplayName: target.OriginalTarget,
Result: SUCCESS,
SentTime: sentTime,
Rtt: rtt,
Message: "",
Details: details,
}
}

Expand Down
32 changes: 32 additions & 0 deletions internal/prober/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"
"sync"
"time"

)

const (
Expand Down Expand Up @@ -181,12 +182,43 @@ func (p *HTTPProber) probe(r chan *Event, target string) {
} else if p.config.ExpectBody != "" && p.config.ExpectBody != strings.TrimRight(string(body), "\n") {
p.failed(r, target, now, errors.New("invalid body"))
} else {
// Create HTTP detail information
headers := make(map[string]string)
for key, values := range resp.Header {
if len(values) > 0 {
headers[key] = values[0] // Get only the first value
}
}

var redirects []string
if resp.Request.URL.String() != target {
redirects = append(redirects, resp.Request.URL.String())
}

var probeType string
if strings.HasPrefix(target, "https://") {
probeType = "https"
} else {
probeType = "http"
}

details := &ProbeDetails{
ProbeType: probeType,
HTTP: &HTTPDetails{
StatusCode: resp.StatusCode,
ResponseSize: int64(len(body)),
Headers: headers,
Redirects: redirects,
},
}

r <- &Event{
Key: target,
DisplayName: target,
Result: SUCCESS,
SentTime: now,
Rtt: time.Since(now),
Details: details,
}
}
}
Expand Down
94 changes: 91 additions & 3 deletions internal/prober/icmp.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ func (p *ICMPProber) sent(r chan *Event, addr string) {
}
}

func (p *ICMPProber) success(r chan *Event, runCnt int, addr string) {
func (p *ICMPProber) success(r chan *Event, runCnt int, addr string, payload icmp.Message, packetData []byte, packetSize int) {
p.mu.Lock()
defer p.mu.Unlock()
for k, table := range p.tables {
Expand All @@ -187,17 +187,105 @@ func (p *ICMPProber) success(r chan *Event, runCnt int, addr string) {
table[addr] = true
elapse := time.Since(k.sentTime)
key, displayName := p.getTargetInfo(addr)

// Extract detailed packet information
icmpDetails := p.extractICMPDetails(runCnt, addr, payload, packetData, packetSize)

// Create ICMP detail information
details := &ProbeDetails{
ProbeType: string(p.version),
ICMP: icmpDetails,
}

r <- &Event{
Key: key,
DisplayName: displayName,
Result: SUCCESS,
SentTime: k.sentTime,
Rtt: elapse,
Details: details,
}
return
}
}

// extractICMPDetails extracts detailed information from ICMP packet
func (p *ICMPProber) extractICMPDetails(runCnt int, addr string, payload icmp.Message, packetData []byte, packetSize int) *ICMPDetails {
var payloadContent string
var checksum uint16

// Extract echo data if available
if echoBody, ok := payload.Body.(*icmp.Echo); ok {

// Format payload content with length limit
payloadContent = formatPayloadContent(echoBody.Data)
}

// Extract checksum from raw packet data if available
// ICMP checksum is at offset 2-3 in the ICMP header
if len(packetData) >= 4 {
checksum = binary.BigEndian.Uint16(packetData[2:4])
}


// Convert ICMP type to int safely
var icmpType int
switch payload.Type {
case ipv4.ICMPTypeEchoReply:
icmpType = 0
case ipv6.ICMPTypeEchoReply:
icmpType = 129
default:
// Use reflection or type assertion for other types
if t, ok := payload.Type.(interface{ Int() int }); ok {
icmpType = t.Int()
} else {
icmpType = -1 // Unknown type
}
}

details := &ICMPDetails{
Sequence: runCnt,
PacketSize: packetSize,
ICMPType: icmpType,
ICMPCode: payload.Code,
Checksum: checksum,
Payload: payloadContent,
}

return details
}



// formatPayloadContent formats payload bytes for display with length limit
func formatPayloadContent(data []byte) string {
const maxDisplayLength = 32 // Maximum characters to display

if len(data) == 0 {
return ""
}

// Convert to string, replacing non-printable characters
var result strings.Builder
for _, b := range data {
if b >= 32 && b <= 126 { // Printable ASCII characters
result.WriteByte(b)
} else {
result.WriteString(fmt.Sprintf("\\x%02x", b))
}
}

payloadStr := result.String()

// Truncate if too long
if len(payloadStr) > maxDisplayLength {
payloadStr = payloadStr[:maxDisplayLength-3] + "..."
}

return payloadStr
}

func (p *ICMPProber) failed(r chan *Event, runCnt int, addr string, err error) {
p.mu.Lock()
defer p.mu.Unlock()
Expand Down Expand Up @@ -300,7 +388,7 @@ func (p *ICMPProber) probe(r chan *Event) {
func (p *ICMPProber) recvPkts(r chan *Event) {
pktbuf := make([]byte, maxPacketSize)
for {
n, ip, err := p.c.ReadFrom(pktbuf)
n, addr, err := p.c.ReadFrom(pktbuf)
if err != nil {
fmt.Printf("Error reading ICMP packet: %s\n", err)
os.Exit(1)
Expand All @@ -323,7 +411,7 @@ func (p *ICMPProber) recvPkts(r chan *Event) {
if rm.Code == 0 {
switch rm.Type {
case ipv4.ICMPTypeEchoReply, ipv6.ICMPTypeEchoReply:
p.success(r, int(seq), ip.String())
p.success(r, int(seq), addr.String(), *rm, pktbuf[:n], n)
}
}
}
Expand Down
27 changes: 25 additions & 2 deletions internal/prober/ntp.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"
"sync"
"time"

)

const (
Expand Down Expand Up @@ -235,7 +236,7 @@ func (p *NTPProber) sendProbe(result chan *Event, serverAddr string, timeout tim
}

// Success
p.success(result, serverAddr, displayName, now, rtt)
p.success(result, serverAddr, displayName, now, rtt, &resp, offset)
}

func (p *NTPProber) sent(result chan *Event, serverAddr, displayName string, sentTime time.Time) {
Expand All @@ -249,14 +250,36 @@ func (p *NTPProber) sent(result chan *Event, serverAddr, displayName string, sen
}
}

func (p *NTPProber) success(result chan *Event, serverAddr, displayName string, sentTime time.Time, rtt time.Duration) {
func (p *NTPProber) success(result chan *Event, serverAddr, displayName string, sentTime time.Time, rtt time.Duration, resp *ntpPacket, offset time.Duration) {
// Create NTP detail information
// Extract port (serverAddr is in "host:port" format)
_, portStr, _ := net.SplitHostPort(serverAddr)
port := 123 // Default NTP port
if portStr != "" {
if p, err := net.LookupPort("udp", portStr); err == nil {
port = p
}
}

details := &ProbeDetails{
ProbeType: "ntp",
NTP: &NTPDetails{
Server: displayName,
Port: port,
Stratum: int(resp.Stratum),
Offset: offset.Microseconds(),
Precision: int(resp.Precision),
},
}

result <- &Event{
Key: serverAddr,
DisplayName: displayName,
Result: SUCCESS,
SentTime: sentTime,
Rtt: rtt,
Message: "",
Details: details,
}
}

Expand Down
1 change: 1 addition & 0 deletions internal/prober/prober.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Event struct {
SentTime time.Time
Rtt time.Duration
Message string
Details *ProbeDetails // Added: detailed information
}

type Prober interface {
Expand Down
3 changes: 3 additions & 0 deletions internal/prober/tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,16 @@ func (p *TCPProber) sent(result chan *Event, target string, sentTime time.Time)

func (p *TCPProber) success(result chan *Event, target string, sentTime time.Time, rtt time.Duration) {
displayName := p.targets[target] // Get displayName from targets map

// TCP only checks connectivity, so no detailed information
result <- &Event{
Key: target,
DisplayName: displayName,
Result: SUCCESS,
SentTime: sentTime,
Rtt: rtt,
Message: "",
Details: nil, // TCP has no detailed information
}
}

Expand Down
Loading
Loading