github.com/fafucoder/cilium@v1.6.11/pkg/monitor/datapath_debug.go (about)

     1  // Copyright 2016-2019 Authors of Cilium
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package monitor
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"net"
    21  
    22  	"github.com/cilium/cilium/pkg/byteorder"
    23  	"github.com/cilium/cilium/pkg/monitor/api"
    24  )
    25  
    26  // must be in sync with <bpf/lib/dbg.h>
    27  const (
    28  	DbgCaptureUnspec = iota
    29  	DbgCaptureReserved1
    30  	DbgCaptureReserved2
    31  	DbgCaptureReserved3
    32  	DbgCaptureDelivery
    33  	DbgCaptureFromLb
    34  	DbgCaptureAfterV46
    35  	DbgCaptureAfterV64
    36  	DbgCaptureProxyPre
    37  	DbgCaptureProxyPost
    38  	DbgCaptureSnatPre
    39  	DbgCaptureSnatPost
    40  )
    41  
    42  // must be in sync with <bpf/lib/dbg.h>
    43  const (
    44  	DbgUnspec = iota
    45  	DbgGeneric
    46  	DbgLocalDelivery
    47  	DbgEncap
    48  	DbgLxcFound
    49  	DbgPolicyDenied
    50  	DbgCtLookup
    51  	DbgCtLookupRev
    52  	DbgCtMatch
    53  	DbgCtCreated
    54  	DbgCtCreated2
    55  	DbgIcmp6Handle
    56  	DbgIcmp6Request
    57  	DbgIcmp6Ns
    58  	DbgIcmp6TimeExceeded
    59  	DbgCtVerdict
    60  	DbgDecap
    61  	DbgPortMap
    62  	DbgErrorRet
    63  	DbgToHost
    64  	DbgToStack
    65  	DbgPktHash
    66  	DbgLb6LookupMaster
    67  	DbgLb6LookupMasterFail
    68  	DbgLb6LookupSlave
    69  	DbgLb6LookupSlaveSuccess
    70  	DbgLb6LookupSlaveV2Fail
    71  	DbgLb6LookupBackendFail
    72  	DbgLb6ReverseNatLookup
    73  	DbgLb6ReverseNat
    74  	DbgLb4LookupMaster
    75  	DbgLb4LookupMasterFail
    76  	DbgLb4LookupSlave
    77  	DbgLb4LookupSlaveSuccess
    78  	DbgLb4LookupSlaveV2Fail
    79  	DbgLb4LookupBackendFail
    80  	DbgLb4ReverseNatLookup
    81  	DbgLb4ReverseNat
    82  	DbgLb4LoopbackSnat
    83  	DbgLb4LoopbackSnatRev
    84  	DbgCtLookup4
    85  	DbgRRSlaveSel
    86  	DbgRevProxyLookup
    87  	DbgRevProxyFound
    88  	DbgRevProxyUpdate
    89  	DbgL4Policy
    90  	DbgNetdevInCluster
    91  	DbgNetdevEncap4
    92  	DbgCTLookup41
    93  	DbgCTLookup42
    94  	DbgCTCreated4
    95  	DbgCTLookup61
    96  	DbgCTLookup62
    97  	DbgCTCreated6
    98  	DbgSkipProxy
    99  	DbgL4Create
   100  	DbgIPIDMapFailed4
   101  	DbgIPIDMapFailed6
   102  	DbgIPIDMapSucceed4
   103  	DbgIPIDMapSucceed6
   104  	DbgLbStaleCT
   105  	DbgInheritIdentity
   106  )
   107  
   108  // must be in sync with <bpf/lib/conntrack.h>
   109  const (
   110  	CtNew uint32 = iota
   111  	CtEstablished
   112  	CtReply
   113  	CtRelated
   114  )
   115  
   116  var ctStateText = map[uint32]string{
   117  	CtNew:         "New",
   118  	CtEstablished: "Established",
   119  	CtReply:       "Reply",
   120  	CtRelated:     "Related",
   121  }
   122  
   123  const (
   124  	ctEgress  = 0
   125  	ctIngress = 1
   126  )
   127  
   128  var ctDirection = map[int]string{
   129  	ctEgress:  "egress",
   130  	ctIngress: "ingress",
   131  }
   132  
   133  func ctState(state uint32) string {
   134  	txt, ok := ctStateText[state]
   135  	if ok {
   136  		return txt
   137  	}
   138  
   139  	return api.DropReason(uint8(state))
   140  }
   141  
   142  var tupleFlags = map[int16]string{
   143  	0: "IN",
   144  	1: "OUT",
   145  	2: "RELATED",
   146  }
   147  
   148  func ctFlags(flags int16) string {
   149  	s := ""
   150  	for k, v := range tupleFlags {
   151  		if k&flags != 0 {
   152  			if s != "" {
   153  				s += ", "
   154  			}
   155  			s += v
   156  		}
   157  	}
   158  	return s
   159  }
   160  
   161  func ctInfo(arg1 uint32, arg2 uint32) string {
   162  	return fmt.Sprintf("sport=%d dport=%d nexthdr=%d flags=%s",
   163  		arg1>>16, arg1&0xFFFF, arg2>>8, ctFlags(int16(arg2&0xFF)))
   164  }
   165  
   166  func ctLookup4Info1(n *DebugMsg) string {
   167  	return fmt.Sprintf("src=%s:%d dst=%s:%d", ip4Str(n.Arg1),
   168  		n.Arg3&0xFFFF, ip4Str(n.Arg2), n.Arg3>>16)
   169  }
   170  
   171  func ctLookup4Info2(n *DebugMsg) string {
   172  	return fmt.Sprintf("nexthdr=%d flags=%d",
   173  		n.Arg1>>8, n.Arg1&0xFF)
   174  }
   175  
   176  func ctCreate4Info(n *DebugMsg) string {
   177  	return fmt.Sprintf("proxy-port=%d revnat=%d src-identity=%d lb=%s",
   178  		n.Arg1>>16, byteorder.NetworkToHost(uint16(n.Arg1&0xFFFF)), n.Arg2, ip4Str(n.Arg3))
   179  }
   180  
   181  func ctLookup6Info1(n *DebugMsg) string {
   182  	return fmt.Sprintf("src=[::%s]:%d dst=[::%s]:%d", ip6Str(n.Arg1),
   183  		n.Arg3&0xFFFF, ip6Str(n.Arg2), n.Arg3>>16)
   184  }
   185  
   186  func ctCreate6Info(n *DebugMsg) string {
   187  	return fmt.Sprintf("proxy-port=%d revnat=%d src-identity=%d",
   188  		n.Arg1>>16, byteorder.NetworkToHost(uint16(n.Arg1&0xFFFF)), n.Arg2)
   189  }
   190  
   191  func verdictInfo(arg uint32) string {
   192  	revnat := byteorder.NetworkToHost(uint16(arg & 0xFFFF))
   193  	return fmt.Sprintf("revnat=%d", revnat)
   194  }
   195  
   196  func proxyInfo(arg1 uint32, arg2 uint32) string {
   197  	sport := byteorder.NetworkToHost(uint16(arg1 >> 16))
   198  	dport := byteorder.NetworkToHost(uint16(arg1 & 0xFFFF))
   199  	return fmt.Sprintf("sport=%d dport=%d saddr=%s", sport, dport, ip4Str(arg2))
   200  }
   201  
   202  func l4CreateInfo(n *DebugMsg) string {
   203  	src := n.Arg1
   204  	dst := n.Arg2
   205  	dport := byteorder.NetworkToHost(uint16(n.Arg3 >> 16))
   206  	proto := n.Arg3 & 0xFF
   207  	return fmt.Sprintf("src=%d dst=%d dport=%d proto=%d", src, dst, dport, proto)
   208  }
   209  
   210  func ip4Str(arg1 uint32) string {
   211  	ip := make(net.IP, 4)
   212  	byteorder.NetworkToHostPut(ip, arg1)
   213  	return ip.String()
   214  }
   215  
   216  func ip6Str(arg1 uint32) string {
   217  	ip6 := byteorder.NetworkToHost(arg1).(uint32)
   218  	return fmt.Sprintf("%x:%x", ip6>>16, ip6&0xFFFF)
   219  }
   220  
   221  // DebugMsg is the message format of the debug message found in the BPF ring buffer
   222  type DebugMsg struct {
   223  	Type    uint8
   224  	SubType uint8
   225  	Source  uint16
   226  	Hash    uint32
   227  	Arg1    uint32
   228  	Arg2    uint32
   229  	Arg3    uint32
   230  }
   231  
   232  // DumpInfo prints a summary of a subset of the debug messages which are related
   233  // to sending, not processing, of packets.
   234  func (n *DebugMsg) DumpInfo(data []byte) {
   235  }
   236  
   237  // Dump prints the debug message in a human readable format.
   238  func (n *DebugMsg) Dump(prefix string) {
   239  	fmt.Printf("%s MARK %#x FROM %d DEBUG: %s\n", prefix, n.Hash, n.Source, n.subTypeString())
   240  }
   241  
   242  func (n *DebugMsg) subTypeString() string {
   243  	switch n.SubType {
   244  	case DbgGeneric:
   245  		return fmt.Sprintf("No message, arg1=%d (%#x) arg2=%d (%#x)", n.Arg1, n.Arg1, n.Arg2, n.Arg2)
   246  	case DbgLocalDelivery:
   247  		return fmt.Sprintf("Attempting local delivery for container id %d from seclabel %d", n.Arg1, n.Arg2)
   248  	case DbgEncap:
   249  		return fmt.Sprintf("Encapsulating to node %d (%#x) from seclabel %d", n.Arg1, n.Arg1, n.Arg2)
   250  	case DbgLxcFound:
   251  		return fmt.Sprintf("Local container found ifindex %s seclabel %d", ifname(int(n.Arg1)), byteorder.NetworkToHost(uint16(n.Arg2)))
   252  	case DbgPolicyDenied:
   253  		return fmt.Sprintf("Policy evaluation would deny packet from %d to %d", n.Arg1, n.Arg2)
   254  	case DbgCtLookup:
   255  		return fmt.Sprintf("CT lookup: %s", ctInfo(n.Arg1, n.Arg2))
   256  	case DbgCtLookupRev:
   257  		return fmt.Sprintf("CT reverse lookup: %s", ctInfo(n.Arg1, n.Arg2))
   258  	case DbgCtLookup4:
   259  		return fmt.Sprintf("CT lookup address: %s", ip4Str(n.Arg1))
   260  	case DbgCtMatch:
   261  		return fmt.Sprintf("CT entry found lifetime=%d, %s", n.Arg1,
   262  			verdictInfo(n.Arg2))
   263  	case DbgCtCreated:
   264  		return fmt.Sprintf("CT created 1/2: %s %s",
   265  			ctInfo(n.Arg1, n.Arg2), verdictInfo(n.Arg3))
   266  	case DbgCtCreated2:
   267  		return fmt.Sprintf("CT created 2/2: %s revnat=%d", ip4Str(n.Arg1), byteorder.NetworkToHost(uint16(n.Arg2)))
   268  	case DbgCtVerdict:
   269  		return fmt.Sprintf("CT verdict: %s, %s",
   270  			ctState(n.Arg1), verdictInfo(n.Arg2))
   271  	case DbgIcmp6Handle:
   272  		return fmt.Sprintf("Handling ICMPv6 type=%d", n.Arg1)
   273  	case DbgIcmp6Request:
   274  		return fmt.Sprintf("ICMPv6 echo request for router offset=%d", n.Arg1)
   275  	case DbgIcmp6Ns:
   276  		return fmt.Sprintf("ICMPv6 neighbour soliciation for address %x:%x", n.Arg1, n.Arg2)
   277  	case DbgIcmp6TimeExceeded:
   278  		return fmt.Sprintf("Sending ICMPv6 time exceeded")
   279  	case DbgDecap:
   280  		return fmt.Sprintf("Tunnel decap: id=%d flowlabel=%x", n.Arg1, n.Arg2)
   281  	case DbgPortMap:
   282  		return fmt.Sprintf("Mapping port from=%d to=%d", n.Arg1, n.Arg2)
   283  	case DbgErrorRet:
   284  		return fmt.Sprintf("BPF function %d returned error %d", n.Arg1, n.Arg2)
   285  	case DbgToHost:
   286  		return fmt.Sprintf("Going to host, policy-skip=%d", n.Arg1)
   287  	case DbgToStack:
   288  		return fmt.Sprintf("Going to the stack, policy-skip=%d", n.Arg1)
   289  	case DbgPktHash:
   290  		return fmt.Sprintf("Packet hash=%d (%#x), selected_service=%d", n.Arg1, n.Arg1, n.Arg2)
   291  	case DbgRRSlaveSel:
   292  		return fmt.Sprintf("RR slave selection hash=%d (%#x), selected_service=%d", n.Arg1, n.Arg1, n.Arg2)
   293  	case DbgLb6LookupMaster:
   294  		return fmt.Sprintf("Master service lookup, addr.p4=%x key.dport=%d", n.Arg1, byteorder.NetworkToHost(uint16(n.Arg2)))
   295  	case DbgLb6LookupMasterFail:
   296  		return fmt.Sprintf("Master service lookup failed, addr.p2=%x addr.p3=%x", n.Arg1, n.Arg2)
   297  	case DbgLb6LookupSlave, DbgLb4LookupSlave:
   298  		return fmt.Sprintf("Slave service lookup: slave=%d, dport=%d", n.Arg1, byteorder.NetworkToHost(uint16(n.Arg2)))
   299  	case DbgLb6LookupSlaveV2Fail, DbgLb4LookupSlaveV2Fail:
   300  		return fmt.Sprintf("Slave service lookup failed: slave=%d, dport=%d", n.Arg1, byteorder.NetworkToHost(uint16(n.Arg2)))
   301  	case DbgLb6LookupBackendFail, DbgLb4LookupBackendFail:
   302  		return fmt.Sprintf("Backend service lookup failed: backend_id=%d", n.Arg1)
   303  	case DbgLb6LookupSlaveSuccess:
   304  		return fmt.Sprintf("Slave service lookup result: target.p4=%x port=%d", n.Arg1, byteorder.NetworkToHost(uint16(n.Arg2)))
   305  	case DbgLb6ReverseNatLookup, DbgLb4ReverseNatLookup:
   306  		return fmt.Sprintf("Reverse NAT lookup, index=%d", byteorder.NetworkToHost(uint16(n.Arg1)))
   307  	case DbgLb6ReverseNat:
   308  		return fmt.Sprintf("Performing reverse NAT, address.p4=%x port=%d", n.Arg1, byteorder.NetworkToHost(uint16(n.Arg2)))
   309  	case DbgLb4LookupMaster:
   310  		return fmt.Sprintf("Master service lookup, addr=%s key.dport=%d", ip4Str(n.Arg1), byteorder.NetworkToHost(uint16(n.Arg2)))
   311  	case DbgLb4LookupMasterFail:
   312  		return fmt.Sprintf("Master service lookup failed")
   313  	case DbgLb4LookupSlaveSuccess:
   314  		return fmt.Sprintf("Slave service lookup result: target=%s port=%d", ip4Str(n.Arg1), byteorder.NetworkToHost(uint16(n.Arg2)))
   315  	case DbgLb4ReverseNat:
   316  		return fmt.Sprintf("Performing reverse NAT, address=%s port=%d", ip4Str(n.Arg1), byteorder.NetworkToHost(uint16(n.Arg2)))
   317  	case DbgLb4LoopbackSnat:
   318  		return fmt.Sprintf("Loopback SNAT from=%s to=%s", ip4Str(n.Arg1), ip4Str(n.Arg2))
   319  	case DbgLb4LoopbackSnatRev:
   320  		return fmt.Sprintf("Loopback reverse SNAT from=%s to=%s", ip4Str(n.Arg1), ip4Str(n.Arg2))
   321  	case DbgRevProxyLookup:
   322  		return fmt.Sprintf("Reverse proxy lookup %s nexthdr=%d",
   323  			proxyInfo(n.Arg1, n.Arg2), n.Arg3)
   324  	case DbgRevProxyFound:
   325  		return fmt.Sprintf("Reverse proxy entry found, orig-daddr=%s orig-dport=%d", ip4Str(n.Arg1), n.Arg2)
   326  	case DbgRevProxyUpdate:
   327  		return fmt.Sprintf("Reverse proxy updated %s nexthdr=%d",
   328  			proxyInfo(n.Arg1, n.Arg2), n.Arg3)
   329  	case DbgL4Policy:
   330  		return fmt.Sprintf("Resolved L4 policy to: %d / %s",
   331  			byteorder.NetworkToHost(uint16(n.Arg1)), ctDirection[int(n.Arg2)])
   332  	case DbgNetdevInCluster:
   333  		return fmt.Sprintf("Destination is inside cluster prefix, source identity: %d", n.Arg1)
   334  	case DbgNetdevEncap4:
   335  		return fmt.Sprintf("Attempting encapsulation, lookup key: %s, identity: %d", ip4Str(n.Arg1), n.Arg2)
   336  	case DbgCTLookup41:
   337  		return fmt.Sprintf("Conntrack lookup 1/2: %s", ctLookup4Info1(n))
   338  	case DbgCTLookup42:
   339  		return fmt.Sprintf("Conntrack lookup 2/2: %s", ctLookup4Info2(n))
   340  	case DbgCTCreated4:
   341  		return fmt.Sprintf("Conntrack create: %s", ctCreate4Info(n))
   342  	case DbgCTLookup61:
   343  		return fmt.Sprintf("Conntrack lookup 1/2: %s", ctLookup6Info1(n))
   344  	case DbgCTLookup62:
   345  		return fmt.Sprintf("Conntrack lookup 2/2: %s", ctLookup4Info2(n))
   346  	case DbgCTCreated6:
   347  		return fmt.Sprintf("Conntrack create: %s", ctCreate6Info(n))
   348  	case DbgSkipProxy:
   349  		return fmt.Sprintf("Skipping proxy, tc_index is set=%x", n.Arg1)
   350  	case DbgL4Create:
   351  		return fmt.Sprintf("Matched L4 policy; creating conntrack %s", l4CreateInfo(n))
   352  	case DbgIPIDMapFailed4:
   353  		return fmt.Sprintf("Failed to map daddr=%s to identity", ip4Str(n.Arg1))
   354  	case DbgIPIDMapFailed6:
   355  		return fmt.Sprintf("Failed to map daddr.p4=[::%s] to identity", ip6Str(n.Arg1))
   356  	case DbgIPIDMapSucceed4:
   357  		return fmt.Sprintf("Successfully mapped daddr=%s to identity=%d", ip4Str(n.Arg1), n.Arg2)
   358  	case DbgIPIDMapSucceed6:
   359  		return fmt.Sprintf("Successfully mapped daddr.p4=[::%s] to identity=%d", ip6Str(n.Arg1), n.Arg2)
   360  	case DbgLbStaleCT:
   361  		return fmt.Sprintf("Stale CT entry found stale_ct.rev_nat_id=%d, svc.rev_nat_id=%d", n.Arg2, n.Arg1)
   362  	case DbgInheritIdentity:
   363  		return fmt.Sprintf("Inheriting identity=%d from stack", n.Arg1)
   364  	default:
   365  		return fmt.Sprintf("Unknown message type=%d arg1=%d arg2=%d", n.SubType, n.Arg1, n.Arg2)
   366  	}
   367  }
   368  
   369  func (n *DebugMsg) getJSON(cpuPrefix string) string {
   370  	return fmt.Sprintf(`{"cpu":%q,"type":"debug","message":%q}`,
   371  		cpuPrefix, n.subTypeString())
   372  }
   373  
   374  // DumpJSON prints notification in json format
   375  func (n *DebugMsg) DumpJSON(cpuPrefix string) {
   376  	fmt.Println(n.getJSON(cpuPrefix))
   377  }
   378  
   379  const (
   380  	// DebugCaptureLen is the amount of packet data in a packet capture message
   381  	DebugCaptureLen = 24
   382  )
   383  
   384  // DebugCapture is the metadata sent along with a captured packet frame
   385  type DebugCapture struct {
   386  	Type    uint8
   387  	SubType uint8
   388  	// Source, if populated, is the ID of the source endpoint.
   389  	Source  uint16
   390  	Hash    uint32
   391  	Len     uint32
   392  	OrigLen uint32
   393  	Arg1    uint32
   394  	Arg2    uint32
   395  	// data
   396  }
   397  
   398  // DumpInfo prints a summary of the capture messages.
   399  func (n *DebugCapture) DumpInfo(data []byte) {
   400  	prefix := n.infoPrefix()
   401  
   402  	if len(prefix) > 0 {
   403  		fmt.Printf("%s: %s\n", prefix, GetConnectionSummary(data[DebugCaptureLen:]))
   404  	}
   405  }
   406  
   407  func (n *DebugCapture) infoPrefix() string {
   408  	switch n.SubType {
   409  	case DbgCaptureDelivery:
   410  		return fmt.Sprintf("-> %s", ifname(int(n.Arg1)))
   411  
   412  	case DbgCaptureFromLb:
   413  		return fmt.Sprintf("<- load-balancer %s", ifname(int(n.Arg1)))
   414  
   415  	case DbgCaptureAfterV46:
   416  		return fmt.Sprintf("== v4->v6 %d", n.Arg1)
   417  
   418  	case DbgCaptureAfterV64:
   419  		return fmt.Sprintf("== v6->v4 %d", n.Arg1)
   420  
   421  	case DbgCaptureProxyPost:
   422  		return fmt.Sprintf("-> proxy port %d", byteorder.NetworkToHost(uint16(n.Arg1)))
   423  	default:
   424  		return ""
   425  	}
   426  }
   427  
   428  // DumpVerbose prints the captured packet in human readable format
   429  func (n *DebugCapture) DumpVerbose(dissect bool, data []byte, prefix string) {
   430  	fmt.Printf("%s MARK %#x FROM %d DEBUG: %d bytes, ", prefix, n.Hash, n.Source, n.Len)
   431  	fmt.Println(n.subTypeString())
   432  
   433  	if n.Len > 0 && len(data) > DebugCaptureLen {
   434  		Dissect(dissect, data[DebugCaptureLen:])
   435  	}
   436  }
   437  
   438  func (n *DebugCapture) subTypeString() string {
   439  	switch n.SubType {
   440  	case DbgCaptureDelivery:
   441  		return fmt.Sprintf("Delivery to ifindex %d", n.Arg1)
   442  	case DbgCaptureFromLb:
   443  		return fmt.Sprintf("Incoming packet to load balancer on ifindex %d", n.Arg1)
   444  	case DbgCaptureAfterV46:
   445  		return fmt.Sprintf("Packet after nat46 ifindex %d", n.Arg1)
   446  	case DbgCaptureAfterV64:
   447  		return fmt.Sprintf("Packet after nat64 ifindex %d", n.Arg1)
   448  	case DbgCaptureProxyPre:
   449  		return fmt.Sprintf("Packet to proxy port %d (Pre)", byteorder.NetworkToHost(uint16(n.Arg1)))
   450  	case DbgCaptureProxyPost:
   451  		return fmt.Sprintf("Packet to proxy port %d (Post)", byteorder.NetworkToHost(uint16(n.Arg1)))
   452  	case DbgCaptureSnatPre:
   453  		return fmt.Sprintf("Packet going into snat engine on ifindex %d", n.Arg1)
   454  	case DbgCaptureSnatPost:
   455  		return fmt.Sprintf("Packet coming from snat engine on ifindex %d", n.Arg1)
   456  	default:
   457  		return fmt.Sprintf("Unknown message type=%d arg1=%d", n.SubType, n.Arg1)
   458  	}
   459  }
   460  
   461  func (n *DebugCapture) getJSON(data []byte, cpuPrefix string) (string, error) {
   462  
   463  	v := DebugCaptureToVerbose(n)
   464  	v.CPUPrefix = cpuPrefix
   465  	v.Summary = GetConnectionSummary(data[DebugCaptureLen:])
   466  
   467  	ret, err := json.Marshal(v)
   468  	return string(ret), err
   469  }
   470  
   471  // DumpJSON prints notification in json format
   472  func (n *DebugCapture) DumpJSON(data []byte, cpuPrefix string) {
   473  	resp, err := n.getJSON(data, cpuPrefix)
   474  	if err != nil {
   475  		fmt.Println(fmt.Sprintf(`{"type":"debug_capture_error","message":%q}`, err.Error()))
   476  		return
   477  	}
   478  	fmt.Println(resp)
   479  }
   480  
   481  // DebugCaptureVerbose represents a json notification printed by monitor
   482  type DebugCaptureVerbose struct {
   483  	CPUPrefix string `json:"cpu,omitempty"`
   484  	Type      string `json:"type,omitempty"`
   485  	Mark      string `json:"mark,omitempty"`
   486  	Message   string `json:"message,omitempty"`
   487  	Prefix    string `json:"prefix,omitempty"`
   488  
   489  	Source uint16 `json:"source"`
   490  	Bytes  uint32 `json:"bytes"`
   491  
   492  	Summary string `json:"summary,omitempty"`
   493  }
   494  
   495  // DebugCaptureToVerbose creates verbose notification from base TraceNotify
   496  func DebugCaptureToVerbose(n *DebugCapture) DebugCaptureVerbose {
   497  	return DebugCaptureVerbose{
   498  		Type:    "capture",
   499  		Mark:    fmt.Sprintf("%#x", n.Hash),
   500  		Source:  n.Source,
   501  		Bytes:   n.Len,
   502  		Message: n.subTypeString(),
   503  		Prefix:  n.infoPrefix(),
   504  	}
   505  }