mirror of
https://github.com/kubeshark/kubeshark.git
synced 2026-03-02 10:32:48 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b745f65971 | ||
|
|
873f252544 | ||
|
|
9696ad9bad | ||
|
|
a1bda0a6c3 | ||
|
|
a62842ac9f | ||
|
|
e667597e6e | ||
|
|
86240e4121 | ||
|
|
b0c8c0c192 | ||
|
|
1c18eb1b84 |
@@ -42,8 +42,8 @@ RUN go build -ldflags="-s -w \
|
||||
-X 'mizuserver/pkg/version.SemVer=${SEM_VER}'" -o mizuagent .
|
||||
|
||||
# Download Basenine executable, verify the sha1sum and move it to a directory in $PATH
|
||||
ADD https://github.com/up9inc/basenine/releases/download/v0.2.11/basenine_linux_amd64 ./basenine_linux_amd64
|
||||
ADD https://github.com/up9inc/basenine/releases/download/v0.2.11/basenine_linux_amd64.sha256 ./basenine_linux_amd64.sha256
|
||||
ADD https://github.com/up9inc/basenine/releases/download/v0.2.12/basenine_linux_amd64 ./basenine_linux_amd64
|
||||
ADD https://github.com/up9inc/basenine/releases/download/v0.2.12/basenine_linux_amd64.sha256 ./basenine_linux_amd64.sha256
|
||||
RUN shasum -a 256 -c basenine_linux_amd64.sha256
|
||||
RUN chmod +x ./basenine_linux_amd64
|
||||
|
||||
|
||||
12
README.md
12
README.md
@@ -185,15 +185,3 @@ Mizu can be run detached from the cli using the daemon flag: `mizu tap --daemon`
|
||||
indefinitely in the cluster.
|
||||
|
||||
For more information please refer to [DAEMON MODE](docs/DAEMON_MODE.md)
|
||||
|
||||
## How to Run local UI
|
||||
|
||||
- run from mizu/agent `go run main.go --hars-read --hars-dir <folder>`
|
||||
|
||||
- copy Har files into the folder from last command
|
||||
|
||||
- change `MizuWebsocketURL` and `apiURL` in `api.js` file
|
||||
|
||||
- run from mizu/ui - `npm run start`
|
||||
|
||||
- open browser on `localhost:3000`
|
||||
|
||||
@@ -16,7 +16,7 @@ require (
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/orcaman/concurrent-map v0.0.0-20210106121528-16402b402231
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20211121072216-04366911881c
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20211125004153-d0e8aec03fea
|
||||
github.com/up9inc/mizu/shared v0.0.0
|
||||
github.com/up9inc/mizu/tap v0.0.0
|
||||
github.com/up9inc/mizu/tap/api v0.0.0
|
||||
|
||||
@@ -450,8 +450,8 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20211121072216-04366911881c h1:GJsCVhDKjV/k3mNG255VN7hAQ7fxyNgX5T+VJyzoOQ0=
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20211121072216-04366911881c/go.mod h1:SvJGPoa/6erhUQV7kvHBwM/0x5LyO6XaG2lUaCaKiUI=
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20211125004153-d0e8aec03fea h1:GpJGO2PNTS/S0j2E1yqBi3ST/VXfokzuOrTQyaYgWnA=
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20211125004153-d0e8aec03fea/go.mod h1:SvJGPoa/6erhUQV7kvHBwM/0x5LyO6XaG2lUaCaKiUI=
|
||||
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
|
||||
@@ -493,19 +493,19 @@ func watchMizuEvents(ctx context.Context, kubernetesProvider *kubernetes.Provide
|
||||
|
||||
mizuResourceRegex := regexp.MustCompile(fmt.Sprintf("^%s.*", kubernetes.MizuResourcesPrefix))
|
||||
eventWatchHelper := kubernetes.NewEventWatchHelper(kubernetesProvider, mizuResourceRegex)
|
||||
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, eventWatchHelper, []string{config.Config.MizuResourcesNamespace}, eventWatchHelper)
|
||||
eventChan, errorChan := kubernetes.FilteredWatch(ctx, eventWatchHelper, []string{config.Config.MizuResourcesNamespace}, eventWatchHelper)
|
||||
|
||||
for {
|
||||
select {
|
||||
case wEvent, ok := <-added:
|
||||
case wEvent, ok := <-eventChan:
|
||||
if !ok {
|
||||
added = nil
|
||||
eventChan = nil
|
||||
continue
|
||||
}
|
||||
|
||||
event, err := wEvent.ToEvent()
|
||||
if err != nil {
|
||||
logger.Log.Errorf("error parsing Mizu resource added event: %+v", err)
|
||||
logger.Log.Errorf("error parsing Mizu resource event: %+v", err)
|
||||
cancel()
|
||||
}
|
||||
|
||||
@@ -514,45 +514,7 @@ func watchMizuEvents(ctx context.Context, kubernetesProvider *kubernetes.Provide
|
||||
}
|
||||
|
||||
if event.Type == v1.EventTypeWarning {
|
||||
logger.Log.Warningf("Resource %s in state %s - %s", event.Regarding.Name, event.Reason, event.Note)
|
||||
}
|
||||
case wEvent, ok := <-removed:
|
||||
if !ok {
|
||||
removed = nil
|
||||
continue
|
||||
}
|
||||
|
||||
event, err := wEvent.ToEvent()
|
||||
if err != nil {
|
||||
logger.Log.Errorf("error parsing Mizu resource removed event: %+v", err)
|
||||
cancel()
|
||||
}
|
||||
|
||||
if startTime.After(event.CreationTimestamp.Time) {
|
||||
continue
|
||||
}
|
||||
|
||||
if event.Type == v1.EventTypeWarning {
|
||||
logger.Log.Warningf("Resource %s in state %s - %s", event.Regarding.Name, event.Reason, event.Note)
|
||||
}
|
||||
case wEvent, ok := <-modified:
|
||||
if !ok {
|
||||
modified = nil
|
||||
continue
|
||||
}
|
||||
|
||||
event, err := wEvent.ToEvent()
|
||||
if err != nil {
|
||||
logger.Log.Errorf("error parsing Mizu resource modified event: %+v", err)
|
||||
cancel()
|
||||
}
|
||||
|
||||
if startTime.After(event.CreationTimestamp.Time) {
|
||||
continue
|
||||
}
|
||||
|
||||
if event.Type == v1.EventTypeWarning {
|
||||
logger.Log.Warningf("Resource %s in state %s - %s", event.Regarding.Name, event.Reason, event.Note)
|
||||
logger.Log.Warningf("resource %s in state %s - %s", event.Regarding.Name, event.Reason, event.Note)
|
||||
}
|
||||
case err, ok := <-errorChan:
|
||||
if !ok {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"mizuserver/pkg/models"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -94,6 +95,8 @@ func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers Even
|
||||
startTimeBytes, _ := models.CreateWebsocketStartTimeMessage(startTime)
|
||||
SendToSocket(socketId, startTimeBytes)
|
||||
|
||||
queryRecieved := false
|
||||
|
||||
for {
|
||||
_, msg, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
@@ -101,65 +104,75 @@ func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers Even
|
||||
break
|
||||
}
|
||||
|
||||
if !isTapper && !isQuerySet {
|
||||
query := string(msg)
|
||||
err = basenine.Validate(shared.BasenineHost, shared.BaseninePort, query)
|
||||
if err != nil {
|
||||
toastBytes, _ := models.CreateWebsocketToastMessage(&models.ToastMessage{
|
||||
Type: "error",
|
||||
AutoClose: 5000,
|
||||
Text: fmt.Sprintf("Syntax error: %s", err.Error()),
|
||||
})
|
||||
SendToSocket(socketId, toastBytes)
|
||||
break
|
||||
}
|
||||
|
||||
isQuerySet = true
|
||||
|
||||
handleDataChannel := func(c *basenine.Connection, data chan []byte) {
|
||||
for {
|
||||
bytes := <-data
|
||||
|
||||
if string(bytes) == basenine.CloseChannel {
|
||||
return
|
||||
}
|
||||
|
||||
var dataMap map[string]interface{}
|
||||
err = json.Unmarshal(bytes, &dataMap)
|
||||
|
||||
base := dataMap["base"].(map[string]interface{})
|
||||
base["id"] = uint(dataMap["id"].(float64))
|
||||
|
||||
baseEntryBytes, _ := models.CreateBaseEntryWebSocketMessage(base)
|
||||
SendToSocket(socketId, baseEntryBytes)
|
||||
if !queryRecieved {
|
||||
if !isTapper && !isQuerySet {
|
||||
queryRecieved = true
|
||||
query := string(msg)
|
||||
err = basenine.Validate(shared.BasenineHost, shared.BaseninePort, query)
|
||||
if err != nil {
|
||||
toastBytes, _ := models.CreateWebsocketToastMessage(&models.ToastMessage{
|
||||
Type: "error",
|
||||
AutoClose: 5000,
|
||||
Text: fmt.Sprintf("Syntax error: %s", err.Error()),
|
||||
})
|
||||
SendToSocket(socketId, toastBytes)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
handleMetaChannel := func(c *basenine.Connection, meta chan []byte) {
|
||||
for {
|
||||
bytes := <-meta
|
||||
isQuerySet = true
|
||||
|
||||
if string(bytes) == basenine.CloseChannel {
|
||||
return
|
||||
handleDataChannel := func(c *basenine.Connection, data chan []byte) {
|
||||
for {
|
||||
bytes := <-data
|
||||
|
||||
if string(bytes) == basenine.CloseChannel {
|
||||
return
|
||||
}
|
||||
|
||||
var dataMap map[string]interface{}
|
||||
err = json.Unmarshal(bytes, &dataMap)
|
||||
|
||||
base := dataMap["base"].(map[string]interface{})
|
||||
base["id"] = uint(dataMap["id"].(float64))
|
||||
|
||||
baseEntryBytes, _ := models.CreateBaseEntryWebSocketMessage(base)
|
||||
SendToSocket(socketId, baseEntryBytes)
|
||||
}
|
||||
|
||||
var metadata *basenine.Metadata
|
||||
err = json.Unmarshal(bytes, &metadata)
|
||||
if err != nil {
|
||||
logger.Log.Debugf("Error recieving metadata: %v", err.Error())
|
||||
}
|
||||
|
||||
metadataBytes, _ := models.CreateWebsocketQueryMetadataMessage(metadata)
|
||||
SendToSocket(socketId, metadataBytes)
|
||||
}
|
||||
|
||||
handleMetaChannel := func(c *basenine.Connection, meta chan []byte) {
|
||||
for {
|
||||
bytes := <-meta
|
||||
|
||||
if string(bytes) == basenine.CloseChannel {
|
||||
return
|
||||
}
|
||||
|
||||
var metadata *basenine.Metadata
|
||||
err = json.Unmarshal(bytes, &metadata)
|
||||
if err != nil {
|
||||
logger.Log.Debugf("Error recieving metadata: %v", err.Error())
|
||||
}
|
||||
|
||||
metadataBytes, _ := models.CreateWebsocketQueryMetadataMessage(metadata)
|
||||
SendToSocket(socketId, metadataBytes)
|
||||
}
|
||||
}
|
||||
|
||||
go handleDataChannel(connection, data)
|
||||
go handleMetaChannel(connection, meta)
|
||||
|
||||
connection.Query(query, data, meta)
|
||||
} else {
|
||||
eventHandlers.WebSocketMessage(socketId, msg)
|
||||
}
|
||||
|
||||
go handleDataChannel(connection, data)
|
||||
go handleMetaChannel(connection, meta)
|
||||
|
||||
connection.Query(query, data, meta)
|
||||
} else {
|
||||
eventHandlers.WebSocketMessage(socketId, msg)
|
||||
id, err := strconv.Atoi(string(msg))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
focusEntryBytes, _ := models.CreateWebsocketFocusEntry(id)
|
||||
SendToSocket(socketId, focusEntryBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,11 @@ type WebSocketStartTimeMessage struct {
|
||||
Data int64 `json:"data"`
|
||||
}
|
||||
|
||||
type WebSocketFocusEntryMessage struct {
|
||||
*shared.WebSocketMessageMetadata
|
||||
Id int `json:"id"`
|
||||
}
|
||||
|
||||
func CreateBaseEntryWebSocketMessage(base map[string]interface{}) ([]byte, error) {
|
||||
message := &WebSocketEntryMessage{
|
||||
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
||||
@@ -117,6 +122,16 @@ func CreateWebsocketStartTimeMessage(base int64) ([]byte, error) {
|
||||
return json.Marshal(message)
|
||||
}
|
||||
|
||||
func CreateWebsocketFocusEntry(id int) ([]byte, error) {
|
||||
message := &WebSocketFocusEntryMessage{
|
||||
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
||||
MessageType: shared.WebSocketMessageFocusEntry,
|
||||
},
|
||||
Id: id,
|
||||
}
|
||||
return json.Marshal(message)
|
||||
}
|
||||
|
||||
// ExtendedHAR is the top level object of a HAR log.
|
||||
type ExtendedHAR struct {
|
||||
Log *ExtendedLog `json:"log"`
|
||||
|
||||
@@ -3,11 +3,12 @@ package apiserver
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/up9inc/mizu/shared/kubernetes"
|
||||
@@ -61,7 +62,14 @@ func (provider *Provider) GetHealthStatus() (*shared.HealthResponse, error) {
|
||||
if response, err := provider.client.Get(healthUrl); err != nil {
|
||||
return nil, err
|
||||
} else if response.StatusCode > 299 {
|
||||
return nil, errors.New(fmt.Sprintf("status code: %d", response.StatusCode))
|
||||
responseBody := new(strings.Builder)
|
||||
|
||||
if _, err := io.Copy(responseBody, response.Body); err != nil {
|
||||
return nil, fmt.Errorf("status code: %d - (bad response - %v)", response.StatusCode, err)
|
||||
} else {
|
||||
singleLineResponse := strings.ReplaceAll(responseBody.String(), "\n", "")
|
||||
return nil, fmt.Errorf("status code: %d - (response - %v)", response.StatusCode, singleLineResponse)
|
||||
}
|
||||
} else {
|
||||
defer response.Body.Close()
|
||||
|
||||
|
||||
@@ -559,76 +559,73 @@ func waitUntilNamespaceDeleted(ctx context.Context, cancel context.CancelFunc, k
|
||||
func watchApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
|
||||
podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s$", kubernetes.ApiServerPodName))
|
||||
podWatchHelper := kubernetes.NewPodWatchHelper(kubernetesProvider, podExactRegex)
|
||||
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, podWatchHelper, []string{config.Config.MizuResourcesNamespace}, podWatchHelper)
|
||||
eventChan, errorChan := kubernetes.FilteredWatch(ctx, podWatchHelper, []string{config.Config.MizuResourcesNamespace}, podWatchHelper)
|
||||
isPodReady := false
|
||||
timeAfter := time.After(25 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-added:
|
||||
case wEvent, ok := <-eventChan:
|
||||
if !ok {
|
||||
added = nil
|
||||
eventChan = nil
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Log.Debugf("Watching API Server pod loop, added")
|
||||
case _, ok := <-removed:
|
||||
if !ok {
|
||||
removed = nil
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Log.Infof("%s removed", kubernetes.ApiServerPodName)
|
||||
cancel()
|
||||
return
|
||||
case wEvent, ok := <-modified:
|
||||
if !ok {
|
||||
modified = nil
|
||||
continue
|
||||
}
|
||||
|
||||
modifiedPod, err := wEvent.ToPod()
|
||||
if err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, err)
|
||||
switch wEvent.Type {
|
||||
case kubernetes.EventAdded:
|
||||
logger.Log.Debugf("Watching API Server pod loop, added")
|
||||
case kubernetes.EventDeleted:
|
||||
logger.Log.Infof("%s removed", kubernetes.ApiServerPodName)
|
||||
cancel()
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Log.Debugf("Watching API Server pod loop, modified: %v", modifiedPod.Status.Phase)
|
||||
|
||||
if modifiedPod.Status.Phase == core.PodPending {
|
||||
if modifiedPod.Status.Conditions[0].Type == core.PodScheduled && modifiedPod.Status.Conditions[0].Status != core.ConditionTrue {
|
||||
logger.Log.Debugf("Wasn't able to deploy the API server. Reason: \"%s\"", modifiedPod.Status.Conditions[0].Message)
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Wasn't able to deploy the API server, for more info check logs at %s", fsUtils.GetLogFilePath()))
|
||||
return
|
||||
case kubernetes.EventModified:
|
||||
modifiedPod, err := wEvent.ToPod()
|
||||
if err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, err)
|
||||
cancel()
|
||||
break
|
||||
continue
|
||||
}
|
||||
|
||||
if len(modifiedPod.Status.ContainerStatuses) > 0 && modifiedPod.Status.ContainerStatuses[0].State.Waiting != nil && modifiedPod.Status.ContainerStatuses[0].State.Waiting.Reason == "ErrImagePull" {
|
||||
logger.Log.Debugf("Wasn't able to deploy the API server. (ErrImagePull) Reason: \"%s\"", modifiedPod.Status.ContainerStatuses[0].State.Waiting.Message)
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Wasn't able to deploy the API server: failed to pull the image, for more info check logs at %v", fsUtils.GetLogFilePath()))
|
||||
cancel()
|
||||
break
|
||||
}
|
||||
}
|
||||
logger.Log.Debugf("Watching API Server pod loop, modified: %v", modifiedPod.Status.Phase)
|
||||
|
||||
if modifiedPod.Status.Phase == core.PodRunning && !isPodReady {
|
||||
isPodReady = true
|
||||
go startProxyReportErrorIfAny(kubernetesProvider, cancel)
|
||||
if modifiedPod.Status.Phase == core.PodPending {
|
||||
if modifiedPod.Status.Conditions[0].Type == core.PodScheduled && modifiedPod.Status.Conditions[0].Status != core.ConditionTrue {
|
||||
logger.Log.Debugf("Wasn't able to deploy the API server. Reason: \"%s\"", modifiedPod.Status.Conditions[0].Message)
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Wasn't able to deploy the API server, for more info check logs at %s", fsUtils.GetLogFilePath()))
|
||||
cancel()
|
||||
break
|
||||
}
|
||||
|
||||
url := GetApiServerUrl()
|
||||
if err := apiProvider.TestConnection(); err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Couldn't connect to API server, for more info check logs at %s", fsUtils.GetLogFilePath()))
|
||||
cancel()
|
||||
break
|
||||
if len(modifiedPod.Status.ContainerStatuses) > 0 && modifiedPod.Status.ContainerStatuses[0].State.Waiting != nil && modifiedPod.Status.ContainerStatuses[0].State.Waiting.Reason == "ErrImagePull" {
|
||||
logger.Log.Debugf("Wasn't able to deploy the API server. (ErrImagePull) Reason: \"%s\"", modifiedPod.Status.ContainerStatuses[0].State.Waiting.Message)
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Wasn't able to deploy the API server: failed to pull the image, for more info check logs at %v", fsUtils.GetLogFilePath()))
|
||||
cancel()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
logger.Log.Infof("Mizu is available at %s", url)
|
||||
if !config.Config.HeadlessMode {
|
||||
uiUtils.OpenBrowser(url)
|
||||
}
|
||||
if err := apiProvider.ReportTappedPods(state.tapperSyncer.CurrentlyTappedPods); err != nil {
|
||||
logger.Log.Debugf("[Error] failed update tapped pods %v", err)
|
||||
if modifiedPod.Status.Phase == core.PodRunning && !isPodReady {
|
||||
isPodReady = true
|
||||
go startProxyReportErrorIfAny(kubernetesProvider, cancel)
|
||||
|
||||
url := GetApiServerUrl()
|
||||
if err := apiProvider.TestConnection(); err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Couldn't connect to API server, for more info check logs at %s", fsUtils.GetLogFilePath()))
|
||||
cancel()
|
||||
break
|
||||
}
|
||||
|
||||
logger.Log.Infof("Mizu is available at %s", url)
|
||||
if !config.Config.HeadlessMode {
|
||||
uiUtils.OpenBrowser(url)
|
||||
}
|
||||
if err := apiProvider.ReportTappedPods(state.tapperSyncer.CurrentlyTappedPods); err != nil {
|
||||
logger.Log.Debugf("[Error] failed update tapped pods %v", err)
|
||||
}
|
||||
}
|
||||
case kubernetes.EventBookmark:
|
||||
break
|
||||
case kubernetes.EventError:
|
||||
break
|
||||
}
|
||||
case err, ok := <-errorChan:
|
||||
if !ok {
|
||||
@@ -654,70 +651,53 @@ func watchApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provi
|
||||
func watchTapperPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
|
||||
podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s.*", kubernetes.TapperDaemonSetName))
|
||||
podWatchHelper := kubernetes.NewPodWatchHelper(kubernetesProvider, podExactRegex)
|
||||
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, podWatchHelper, []string{config.Config.MizuResourcesNamespace}, podWatchHelper)
|
||||
eventChan, errorChan := kubernetes.FilteredWatch(ctx, podWatchHelper, []string{config.Config.MizuResourcesNamespace}, podWatchHelper)
|
||||
|
||||
for {
|
||||
select {
|
||||
case wEvent, ok := <-added:
|
||||
case wEvent, ok := <-eventChan:
|
||||
if !ok {
|
||||
added = nil
|
||||
eventChan = nil
|
||||
continue
|
||||
}
|
||||
|
||||
addedPod, err := wEvent.ToPod()
|
||||
pod, err := wEvent.ToPod()
|
||||
if err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, err)
|
||||
cancel()
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Log.Debugf("Tapper is created [%s]", addedPod.Name)
|
||||
case wEvent, ok := <-removed:
|
||||
if !ok {
|
||||
removed = nil
|
||||
continue
|
||||
}
|
||||
switch wEvent.Type {
|
||||
case kubernetes.EventAdded:
|
||||
logger.Log.Debugf("Tapper is created [%s]", pod.Name)
|
||||
case kubernetes.EventDeleted:
|
||||
logger.Log.Debugf("Tapper is removed [%s]", pod.Name)
|
||||
case kubernetes.EventModified:
|
||||
if pod.Status.Phase == core.PodPending && pod.Status.Conditions[0].Type == core.PodScheduled && pod.Status.Conditions[0].Status != core.ConditionTrue {
|
||||
logger.Log.Infof(uiUtils.Red, fmt.Sprintf("Wasn't able to deploy the tapper %s. Reason: \"%s\"", pod.Name, pod.Status.Conditions[0].Message))
|
||||
cancel()
|
||||
continue
|
||||
}
|
||||
|
||||
removedPod, err := wEvent.ToPod()
|
||||
if err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, err)
|
||||
cancel()
|
||||
continue
|
||||
}
|
||||
podStatus := pod.Status
|
||||
|
||||
|
||||
logger.Log.Debugf("Tapper is removed [%s]", removedPod.Name)
|
||||
case wEvent, ok := <-modified:
|
||||
if !ok {
|
||||
modified = nil
|
||||
continue
|
||||
}
|
||||
|
||||
modifiedPod, err := wEvent.ToPod()
|
||||
if err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, err)
|
||||
cancel()
|
||||
continue
|
||||
}
|
||||
|
||||
if modifiedPod.Status.Phase == core.PodPending && modifiedPod.Status.Conditions[0].Type == core.PodScheduled && modifiedPod.Status.Conditions[0].Status != core.ConditionTrue {
|
||||
logger.Log.Infof(uiUtils.Red, fmt.Sprintf("Wasn't able to deploy the tapper %s. Reason: \"%s\"", modifiedPod.Name, modifiedPod.Status.Conditions[0].Message))
|
||||
cancel()
|
||||
continue
|
||||
}
|
||||
|
||||
podStatus := modifiedPod.Status
|
||||
|
||||
if podStatus.Phase == core.PodRunning {
|
||||
state := podStatus.ContainerStatuses[0].State
|
||||
if state.Terminated != nil {
|
||||
switch state.Terminated.Reason {
|
||||
case "OOMKilled":
|
||||
logger.Log.Infof(uiUtils.Red, fmt.Sprintf("Tapper %s was terminated (reason: OOMKilled). You should consider increasing machine resources.", modifiedPod.Name))
|
||||
if podStatus.Phase == core.PodRunning {
|
||||
state := podStatus.ContainerStatuses[0].State
|
||||
if state.Terminated != nil {
|
||||
switch state.Terminated.Reason {
|
||||
case "OOMKilled":
|
||||
logger.Log.Infof(uiUtils.Red, fmt.Sprintf("Tapper %s was terminated (reason: OOMKilled). You should consider increasing machine resources.", pod.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.Log.Debugf("Tapper %s is %s", modifiedPod.Name, strings.ToLower(string(podStatus.Phase)))
|
||||
logger.Log.Debugf("Tapper %s is %s", pod.Name, strings.ToLower(string(podStatus.Phase)))
|
||||
case kubernetes.EventBookmark:
|
||||
break
|
||||
case kubernetes.EventError:
|
||||
break
|
||||
}
|
||||
case err, ok := <-errorChan:
|
||||
if !ok {
|
||||
errorChan = nil
|
||||
@@ -740,57 +720,19 @@ func watchMizuEvents(ctx context.Context, kubernetesProvider *kubernetes.Provide
|
||||
|
||||
mizuResourceRegex := regexp.MustCompile(fmt.Sprintf("^%s.*", kubernetes.MizuResourcesPrefix))
|
||||
eventWatchHelper := kubernetes.NewEventWatchHelper(kubernetesProvider, mizuResourceRegex)
|
||||
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, eventWatchHelper, []string{config.Config.MizuResourcesNamespace}, eventWatchHelper)
|
||||
eventChan, errorChan := kubernetes.FilteredWatch(ctx, eventWatchHelper, []string{config.Config.MizuResourcesNamespace}, eventWatchHelper)
|
||||
|
||||
for {
|
||||
select {
|
||||
case wEvent, ok := <-added:
|
||||
case wEvent, ok := <-eventChan:
|
||||
if !ok {
|
||||
added = nil
|
||||
eventChan = nil
|
||||
continue
|
||||
}
|
||||
|
||||
event, err := wEvent.ToEvent()
|
||||
if err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("error parsing Mizu resource added event: %+v", err))
|
||||
cancel()
|
||||
}
|
||||
|
||||
if startTime.After(event.CreationTimestamp.Time) {
|
||||
continue
|
||||
}
|
||||
|
||||
if event.Type == core.EventTypeWarning {
|
||||
logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Resource %s in state %s - %s", event.Regarding.Name, event.Reason, event.Note))
|
||||
}
|
||||
case wEvent, ok := <-removed:
|
||||
if !ok {
|
||||
removed = nil
|
||||
continue
|
||||
}
|
||||
|
||||
event, err := wEvent.ToEvent()
|
||||
if err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("error parsing Mizu resource removed event: %+v", err))
|
||||
cancel()
|
||||
}
|
||||
|
||||
if startTime.After(event.CreationTimestamp.Time) {
|
||||
continue
|
||||
}
|
||||
|
||||
if event.Type == core.EventTypeWarning {
|
||||
logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Resource %s in state %s - %s", event.Regarding.Name, event.Reason, event.Note))
|
||||
}
|
||||
case wEvent, ok := <-modified:
|
||||
if !ok {
|
||||
modified = nil
|
||||
continue
|
||||
}
|
||||
|
||||
event, err := wEvent.ToEvent()
|
||||
if err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("error parsing Mizu resource modified event: %+v", err))
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("error parsing Mizu resource event: %+v", err))
|
||||
cancel()
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@ COPY agent .
|
||||
RUN go build -gcflags="all=-N -l" -o mizuagent .
|
||||
|
||||
# Download Basenine executable, verify the sha1sum and move it to a directory in $PATH
|
||||
ADD https://github.com/up9inc/basenine/releases/download/v0.2.11/basenine_linux_amd64 ./basenine_linux_amd64
|
||||
ADD https://github.com/up9inc/basenine/releases/download/v0.2.11/basenine_linux_amd64.sha256 ./basenine_linux_amd64.sha256
|
||||
ADD https://github.com/up9inc/basenine/releases/download/v0.2.12/basenine_linux_amd64 ./basenine_linux_amd64
|
||||
ADD https://github.com/up9inc/basenine/releases/download/v0.2.12/basenine_linux_amd64.sha256 ./basenine_linux_amd64.sha256
|
||||
RUN shasum -a 256 -c basenine_linux_amd64.sha256
|
||||
RUN chmod +x ./basenine_linux_amd64
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ func CreateAndStartMizuTapperSyncer(ctx context.Context, kubernetesProvider *Pro
|
||||
|
||||
func (tapperSyncer *MizuTapperSyncer) watchPodsForTapping() {
|
||||
podWatchHelper := NewPodWatchHelper(tapperSyncer.kubernetesProvider, &tapperSyncer.config.PodFilterRegex)
|
||||
added, modified, removed, errorChan := FilteredWatch(tapperSyncer.context, podWatchHelper, tapperSyncer.config.TargetNamespaces, podWatchHelper)
|
||||
eventChan, errorChan := FilteredWatch(tapperSyncer.context, podWatchHelper, tapperSyncer.config.TargetNamespaces, podWatchHelper)
|
||||
|
||||
restartTappers := func() {
|
||||
err, changeFound := tapperSyncer.updateCurrentlyTappedPods()
|
||||
@@ -96,9 +96,9 @@ func (tapperSyncer *MizuTapperSyncer) watchPodsForTapping() {
|
||||
|
||||
for {
|
||||
select {
|
||||
case wEvent, ok := <-added:
|
||||
case wEvent, ok := <-eventChan:
|
||||
if !ok {
|
||||
added = nil
|
||||
eventChan = nil
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -109,44 +109,28 @@ func (tapperSyncer *MizuTapperSyncer) watchPodsForTapping() {
|
||||
}
|
||||
|
||||
|
||||
logger.Log.Debugf("Added matching pod %s, ns: %s", pod.Name, pod.Namespace)
|
||||
restartTappersDebouncer.SetOn()
|
||||
case wEvent, ok := <-removed:
|
||||
if !ok {
|
||||
removed = nil
|
||||
continue
|
||||
}
|
||||
|
||||
pod, err := wEvent.ToPod()
|
||||
if err != nil {
|
||||
tapperSyncer.handleErrorInWatchLoop(err, restartTappersDebouncer)
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Log.Debugf("Removed matching pod %s, ns: %s", pod.Name, pod.Namespace)
|
||||
restartTappersDebouncer.SetOn()
|
||||
case wEvent, ok := <-modified:
|
||||
if !ok {
|
||||
modified = nil
|
||||
continue
|
||||
}
|
||||
|
||||
pod, err := wEvent.ToPod()
|
||||
if err != nil {
|
||||
tapperSyncer.handleErrorInWatchLoop(err, restartTappersDebouncer)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
logger.Log.Debugf("Modified matching pod %s, ns: %s, phase: %s, ip: %s", pod.Name, pod.Namespace, pod.Status.Phase, pod.Status.PodIP)
|
||||
// Act only if the modified pod has already obtained an IP address.
|
||||
// After filtering for IPs, on a normal pod restart this includes the following events:
|
||||
// - Pod deletion
|
||||
// - Pod reaches start state
|
||||
// - Pod reaches ready state
|
||||
// Ready/unready transitions might also trigger this event.
|
||||
if pod.Status.PodIP != "" {
|
||||
switch wEvent.Type {
|
||||
case EventAdded:
|
||||
logger.Log.Debugf("Added matching pod %s, ns: %s", pod.Name, pod.Namespace)
|
||||
restartTappersDebouncer.SetOn()
|
||||
case EventDeleted:
|
||||
logger.Log.Debugf("Removed matching pod %s, ns: %s", pod.Name, pod.Namespace)
|
||||
restartTappersDebouncer.SetOn()
|
||||
case EventModified:
|
||||
logger.Log.Debugf("Modified matching pod %s, ns: %s, phase: %s, ip: %s", pod.Name, pod.Namespace, pod.Status.Phase, pod.Status.PodIP)
|
||||
// Act only if the modified pod has already obtained an IP address.
|
||||
// After filtering for IPs, on a normal pod restart this includes the following events:
|
||||
// - Pod deletion
|
||||
// - Pod reaches start state
|
||||
// - Pod reaches ready state
|
||||
// Ready/unready transitions might also trigger this event.
|
||||
if pod.Status.PodIP != "" {
|
||||
restartTappersDebouncer.SetOn()
|
||||
}
|
||||
case EventBookmark:
|
||||
break
|
||||
case EventError:
|
||||
break
|
||||
}
|
||||
case err, ok := <-errorChan:
|
||||
if !ok {
|
||||
|
||||
@@ -20,10 +20,8 @@ type WatchCreator interface {
|
||||
NewWatcher(ctx context.Context, namespace string) (watch.Interface, error)
|
||||
}
|
||||
|
||||
func FilteredWatch(ctx context.Context, watcherCreator WatchCreator, targetNamespaces []string, filterer EventFilterer) (chan *WatchEvent, chan *WatchEvent, chan *WatchEvent, chan error) {
|
||||
addedChan := make(chan *WatchEvent)
|
||||
modifiedChan := make(chan *WatchEvent)
|
||||
removedChan := make(chan *WatchEvent)
|
||||
func FilteredWatch(ctx context.Context, watcherCreator WatchCreator, targetNamespaces []string, filterer EventFilterer) (<-chan *WatchEvent, <-chan error) {
|
||||
eventChan := make(chan *WatchEvent)
|
||||
errorChan := make(chan error)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
@@ -42,7 +40,7 @@ func FilteredWatch(ctx context.Context, watcherCreator WatchCreator, targetNames
|
||||
break
|
||||
}
|
||||
|
||||
err = startWatchLoop(ctx, watcher, filterer, addedChan, modifiedChan, removedChan) // blocking
|
||||
err = startWatchLoop(ctx, watcher, filterer, eventChan) // blocking
|
||||
watcher.Stop()
|
||||
|
||||
select {
|
||||
@@ -73,16 +71,14 @@ func FilteredWatch(ctx context.Context, watcherCreator WatchCreator, targetNames
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
wg.Wait()
|
||||
close(addedChan)
|
||||
close(modifiedChan)
|
||||
close(removedChan)
|
||||
close(eventChan)
|
||||
close(errorChan)
|
||||
}()
|
||||
|
||||
return addedChan, modifiedChan, removedChan, errorChan
|
||||
return eventChan, errorChan
|
||||
}
|
||||
|
||||
func startWatchLoop(ctx context.Context, watcher watch.Interface, filterer EventFilterer, addedChan chan *WatchEvent, modifiedChan chan *WatchEvent, removedChan chan *WatchEvent) error {
|
||||
func startWatchLoop(ctx context.Context, watcher watch.Interface, filterer EventFilterer, eventChan chan<- *WatchEvent) error {
|
||||
resultChan := watcher.ResultChan()
|
||||
for {
|
||||
select {
|
||||
@@ -103,14 +99,7 @@ func startWatchLoop(ctx context.Context, watcher watch.Interface, filterer Event
|
||||
continue
|
||||
}
|
||||
|
||||
switch wEvent.Type {
|
||||
case watch.Added:
|
||||
addedChan <- &wEvent
|
||||
case watch.Modified:
|
||||
modifiedChan <- &wEvent
|
||||
case watch.Deleted:
|
||||
removedChan <- &wEvent
|
||||
}
|
||||
eventChan <- &wEvent
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,14 @@ import (
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
)
|
||||
|
||||
const (
|
||||
EventAdded watch.EventType = watch.Added
|
||||
EventModified watch.EventType = watch.Modified
|
||||
EventDeleted watch.EventType = watch.Deleted
|
||||
EventBookmark watch.EventType = watch.Bookmark
|
||||
EventError watch.EventType = watch.Error
|
||||
)
|
||||
|
||||
type InvalidObjectType struct {
|
||||
RequestedType reflect.Type
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/op/go-logging"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
"github.com/up9inc/mizu/tap/api"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -21,6 +22,7 @@ const (
|
||||
WebSocketMessageTypeToast WebSocketMessageType = "toast"
|
||||
WebSocketMessageTypeQueryMetadata WebSocketMessageType = "queryMetadata"
|
||||
WebSocketMessageTypeStartTime WebSocketMessageType = "startTime"
|
||||
WebSocketMessageFocusEntry WebSocketMessageType = "focusEntry"
|
||||
)
|
||||
|
||||
type Resources struct {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
@@ -34,12 +35,13 @@ func handleHTTP2Stream(http2Assembler *Http2Assembler, tcpID *api.TcpID, superTi
|
||||
switch messageHTTP1 := messageHTTP1.(type) {
|
||||
case http.Request:
|
||||
ident := fmt.Sprintf(
|
||||
"%s->%s %s->%s %d",
|
||||
"%s->%s %s->%s %d %s",
|
||||
tcpID.SrcIP,
|
||||
tcpID.DstIP,
|
||||
tcpID.SrcPort,
|
||||
tcpID.DstPort,
|
||||
streamID,
|
||||
"HTTP2",
|
||||
)
|
||||
item = reqResMatcher.registerRequest(ident, &messageHTTP1, superTimer.CaptureTime)
|
||||
if item != nil {
|
||||
@@ -53,12 +55,13 @@ func handleHTTP2Stream(http2Assembler *Http2Assembler, tcpID *api.TcpID, superTi
|
||||
}
|
||||
case http.Response:
|
||||
ident := fmt.Sprintf(
|
||||
"%s->%s %s->%s %d",
|
||||
"%s->%s %s->%s %d %s",
|
||||
tcpID.DstIP,
|
||||
tcpID.SrcIP,
|
||||
tcpID.DstPort,
|
||||
tcpID.SrcPort,
|
||||
streamID,
|
||||
"HTTP2",
|
||||
)
|
||||
item = reqResMatcher.registerResponse(ident, &messageHTTP1, superTimer.CaptureTime)
|
||||
if item != nil {
|
||||
@@ -84,23 +87,30 @@ func handleHTTP2Stream(http2Assembler *Http2Assembler, tcpID *api.TcpID, superTi
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleHTTP1ClientStream(b *bufio.Reader, tcpID *api.TcpID, counterPair *api.CounterPair, superTimer *api.SuperTimer, emitter api.Emitter, options *api.TrafficFilteringOptions) error {
|
||||
req, err := http.ReadRequest(b)
|
||||
func handleHTTP1ClientStream(b *bufio.Reader, tcpID *api.TcpID, counterPair *api.CounterPair, superTimer *api.SuperTimer, emitter api.Emitter, options *api.TrafficFilteringOptions) (switchingProtocolsHTTP2 bool, req *http.Request, err error) {
|
||||
req, err = http.ReadRequest(b)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
counterPair.Request++
|
||||
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
// Check HTTP2 upgrade - HTTP2 Over Cleartext (H2C)
|
||||
if strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") && strings.ToLower(req.Header.Get("Upgrade")) == "h2c" {
|
||||
switchingProtocolsHTTP2 = true
|
||||
}
|
||||
|
||||
var body []byte
|
||||
body, err = ioutil.ReadAll(req.Body)
|
||||
req.Body = io.NopCloser(bytes.NewBuffer(body)) // rewind
|
||||
|
||||
ident := fmt.Sprintf(
|
||||
"%s->%s %s->%s %d",
|
||||
"%s->%s %s->%s %d %s",
|
||||
tcpID.SrcIP,
|
||||
tcpID.DstIP,
|
||||
tcpID.SrcPort,
|
||||
tcpID.DstPort,
|
||||
counterPair.Request,
|
||||
"HTTP1",
|
||||
)
|
||||
item := reqResMatcher.registerRequest(ident, req, superTimer.CaptureTime)
|
||||
if item != nil {
|
||||
@@ -113,26 +123,34 @@ func handleHTTP1ClientStream(b *bufio.Reader, tcpID *api.TcpID, counterPair *api
|
||||
}
|
||||
filterAndEmit(item, emitter, options)
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
func handleHTTP1ServerStream(b *bufio.Reader, tcpID *api.TcpID, counterPair *api.CounterPair, superTimer *api.SuperTimer, emitter api.Emitter, options *api.TrafficFilteringOptions) error {
|
||||
res, err := http.ReadResponse(b, nil)
|
||||
func handleHTTP1ServerStream(b *bufio.Reader, tcpID *api.TcpID, counterPair *api.CounterPair, superTimer *api.SuperTimer, emitter api.Emitter, options *api.TrafficFilteringOptions) (switchingProtocolsHTTP2 bool, err error) {
|
||||
var res *http.Response
|
||||
res, err = http.ReadResponse(b, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
counterPair.Response++
|
||||
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
// Check HTTP2 upgrade - HTTP2 Over Cleartext (H2C)
|
||||
if res.StatusCode == 101 && strings.Contains(strings.ToLower(res.Header.Get("Connection")), "upgrade") && strings.ToLower(res.Header.Get("Upgrade")) == "h2c" {
|
||||
switchingProtocolsHTTP2 = true
|
||||
}
|
||||
|
||||
var body []byte
|
||||
body, err = ioutil.ReadAll(res.Body)
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(body)) // rewind
|
||||
|
||||
ident := fmt.Sprintf(
|
||||
"%s->%s %s->%s %d",
|
||||
"%s->%s %s->%s %d %s",
|
||||
tcpID.DstIP,
|
||||
tcpID.SrcIP,
|
||||
tcpID.DstPort,
|
||||
tcpID.SrcPort,
|
||||
counterPair.Response,
|
||||
"HTTP1",
|
||||
)
|
||||
item := reqResMatcher.registerResponse(ident, res, superTimer.CaptureTime)
|
||||
if item != nil {
|
||||
@@ -145,5 +163,5 @@ func handleHTTP1ServerStream(b *bufio.Reader, tcpID *api.TcpID, counterPair *api
|
||||
}
|
||||
filterAndEmit(item, emitter, options)
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
@@ -85,7 +86,15 @@ func (d dissecting) Dissect(b *bufio.Reader, isClient bool, tcpID *api.TcpID, co
|
||||
}
|
||||
|
||||
dissected := false
|
||||
switchingProtocolsHTTP2 := false
|
||||
for {
|
||||
if switchingProtocolsHTTP2 {
|
||||
switchingProtocolsHTTP2 = false
|
||||
isHTTP2, err = checkIsHTTP2Connection(b, isClient)
|
||||
prepareHTTP2Connection(b, isClient)
|
||||
http2Assembler = createHTTP2Assembler(b)
|
||||
}
|
||||
|
||||
if superIdentifier.Protocol != nil && superIdentifier.Protocol != &protocol {
|
||||
return errors.New("Identified by another protocol")
|
||||
}
|
||||
@@ -99,15 +108,39 @@ func (d dissecting) Dissect(b *bufio.Reader, isClient bool, tcpID *api.TcpID, co
|
||||
}
|
||||
dissected = true
|
||||
} else if isClient {
|
||||
err = handleHTTP1ClientStream(b, tcpID, counterPair, superTimer, emitter, options)
|
||||
var req *http.Request
|
||||
switchingProtocolsHTTP2, req, err = handleHTTP1ClientStream(b, tcpID, counterPair, superTimer, emitter, options)
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
continue
|
||||
}
|
||||
dissected = true
|
||||
|
||||
// In case of an HTTP2 upgrade, duplicate the HTTP1 request into HTTP2 with stream ID 1
|
||||
if switchingProtocolsHTTP2 {
|
||||
ident := fmt.Sprintf(
|
||||
"%s->%s %s->%s 1 %s",
|
||||
tcpID.SrcIP,
|
||||
tcpID.DstIP,
|
||||
tcpID.SrcPort,
|
||||
tcpID.DstPort,
|
||||
"HTTP2",
|
||||
)
|
||||
item := reqResMatcher.registerRequest(ident, req, superTimer.CaptureTime)
|
||||
if item != nil {
|
||||
item.ConnectionInfo = &api.ConnectionInfo{
|
||||
ClientIP: tcpID.SrcIP,
|
||||
ClientPort: tcpID.SrcPort,
|
||||
ServerIP: tcpID.DstIP,
|
||||
ServerPort: tcpID.DstPort,
|
||||
IsOutgoing: true,
|
||||
}
|
||||
filterAndEmit(item, emitter, options)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = handleHTTP1ServerStream(b, tcpID, counterPair, superTimer, emitter, options)
|
||||
switchingProtocolsHTTP2, err = handleHTTP1ServerStream(b, tcpID, counterPair, superTimer, emitter, options)
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
@@ -132,6 +165,8 @@ func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string,
|
||||
reqDetails := request["details"].(map[string]interface{})
|
||||
resDetails := response["details"].(map[string]interface{})
|
||||
|
||||
isRequestUpgradedH2C := false
|
||||
|
||||
for _, header := range reqDetails["headers"].([]interface{}) {
|
||||
h := header.(map[string]interface{})
|
||||
if h["name"] == "Host" {
|
||||
@@ -143,13 +178,19 @@ func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string,
|
||||
if h["name"] == ":path" {
|
||||
path = h["value"].(string)
|
||||
}
|
||||
|
||||
if h["name"] == "Upgrade" {
|
||||
if h["value"].(string) == "h2c" {
|
||||
isRequestUpgradedH2C = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if resDetails["bodySize"].(float64) < 0 {
|
||||
resDetails["bodySize"] = 0
|
||||
}
|
||||
|
||||
if item.Protocol.Version == "2.0" {
|
||||
if item.Protocol.Version == "2.0" && !isRequestUpgradedH2C {
|
||||
service = authority
|
||||
} else {
|
||||
service = host
|
||||
@@ -162,6 +203,7 @@ func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string,
|
||||
}
|
||||
|
||||
request["url"] = reqDetails["url"].(string)
|
||||
reqDetails["targetUri"] = reqDetails["url"]
|
||||
reqDetails["path"] = path
|
||||
reqDetails["summary"] = path
|
||||
|
||||
@@ -191,7 +233,7 @@ func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string,
|
||||
resDetails["statusText"] = grpcStatusCodes[statusCode]
|
||||
}
|
||||
|
||||
if item.Protocol.Version == "2.0" {
|
||||
if item.Protocol.Version == "2.0" && !isRequestUpgradedH2C {
|
||||
reqDetails["url"] = path
|
||||
request["url"] = path
|
||||
}
|
||||
@@ -269,9 +311,9 @@ func representRequest(request map[string]interface{}) (repRequest []interface{})
|
||||
Selector: `request.method`,
|
||||
},
|
||||
{
|
||||
Name: "URL",
|
||||
Value: request["url"].(string),
|
||||
Selector: `request.url`,
|
||||
Name: "Target URI",
|
||||
Value: request["targetUri"].(string),
|
||||
Selector: `request.targetUri`,
|
||||
},
|
||||
{
|
||||
Name: "Path",
|
||||
|
||||
@@ -92,6 +92,6 @@ func splitIdent(ident string) []string {
|
||||
}
|
||||
|
||||
func genKey(split []string) string {
|
||||
key := fmt.Sprintf("%s:%s->%s:%s,%s", split[0], split[2], split[1], split[3], split[4])
|
||||
key := fmt.Sprintf("%s:%s->%s:%s,%s%s", split[0], split[2], split[1], split[3], split[4], split[5])
|
||||
return key
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React from "react";
|
||||
import EntryViewer from "./EntryDetailed/EntryViewer";
|
||||
import {EntryItem} from "./EntryListItem/EntryListItem";
|
||||
import {makeStyles} from "@material-ui/core";
|
||||
import Protocol from "./UI/Protocol"
|
||||
import StatusCode from "./UI/StatusCode";
|
||||
import {Summary} from "./UI/Summary";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
entryTitle: {
|
||||
@@ -12,6 +11,7 @@ const useStyles = makeStyles(() => ({
|
||||
maxHeight: 46,
|
||||
alignItems: 'center',
|
||||
marginBottom: 4,
|
||||
marginLeft: 6,
|
||||
padding: 2,
|
||||
paddingBottom: 0
|
||||
},
|
||||
@@ -64,18 +64,17 @@ const EntryTitle: React.FC<any> = ({protocol, data, bodySize, elapsedTime, updat
|
||||
};
|
||||
|
||||
const EntrySummary: React.FC<any> = ({data, updateQuery}) => {
|
||||
const classes = useStyles();
|
||||
const entry = data.base;
|
||||
|
||||
const response = data.response;
|
||||
|
||||
return <div className={classes.entrySummary}>
|
||||
{response && "status" in response && <div style={{marginRight: 8}}>
|
||||
<StatusCode statusCode={response.status} updateQuery={updateQuery}/>
|
||||
</div>}
|
||||
<div style={{flexGrow: 1, overflow: 'hidden'}}>
|
||||
<Summary method={data.method} summary={data.summary} updateQuery={updateQuery}/>
|
||||
</div>
|
||||
</div>;
|
||||
return <EntryItem
|
||||
key={entry.id}
|
||||
entry={entry}
|
||||
focusedEntryId={null}
|
||||
setFocusedEntryId={null}
|
||||
style={{}}
|
||||
updateQuery={updateQuery}
|
||||
headingMode={true}
|
||||
/>;
|
||||
};
|
||||
|
||||
export const EntryDetailed: React.FC<EntryDetailedProps> = ({entryData, updateQuery}) => {
|
||||
|
||||
@@ -130,7 +130,7 @@ export const EntryBodySection: React.FC<EntryBodySectionProps> = ({
|
||||
<table>
|
||||
<tbody>
|
||||
<EntryViewLine label={'Mime type'} value={contentType} updateQuery={updateQuery} selector={selector} overrideQueryValue={`r".*"`}/>
|
||||
<EntryViewLine label={'Encoding'} value={encoding} updateQuery={updateQuery} selector={selector} overrideQueryValue={`r".*"`}/>
|
||||
{encoding && <EntryViewLine label={'Encoding'} value={encoding} updateQuery={updateQuery} selector={selector} overrideQueryValue={`r".*"`}/>}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
font-family: "Source Sans Pro", Lucida Grande, Tahoma, sans-serif
|
||||
height: calc(100% - 70px)
|
||||
width: 100%
|
||||
margin-top: 10px
|
||||
|
||||
h3,
|
||||
h4
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useState} from "react";
|
||||
import React from "react";
|
||||
import styles from './EntryListItem.module.sass';
|
||||
import StatusCode, {getClassification, StatusCodeClassification} from "../UI/StatusCode";
|
||||
import Protocol, {ProtocolInterface} from "../UI/Protocol"
|
||||
@@ -37,14 +37,16 @@ interface Rules {
|
||||
|
||||
interface EntryProps {
|
||||
entry: Entry;
|
||||
focusedEntryId: string;
|
||||
setFocusedEntryId: (id: string) => void;
|
||||
style: object;
|
||||
updateQuery: any;
|
||||
headingMode: boolean;
|
||||
}
|
||||
|
||||
export const EntryItem: React.FC<EntryProps> = ({entry, setFocusedEntryId, style, updateQuery}) => {
|
||||
export const EntryItem: React.FC<EntryProps> = ({entry, focusedEntryId, setFocusedEntryId, style, updateQuery, headingMode}) => {
|
||||
|
||||
const [isSelected, setIsSelected] = useState(false);
|
||||
const isSelected = focusedEntryId === entry.id.toString();
|
||||
|
||||
const classification = getClassification(entry.statusCode)
|
||||
const numberOfRules = entry.rules.numberOfRules
|
||||
@@ -122,22 +124,22 @@ export const EntryItem: React.FC<EntryProps> = ({entry, setFocusedEntryId, style
|
||||
className={`${styles.row}
|
||||
${isSelected && !rule && !contractEnabled ? styles.rowSelected : additionalRulesProperties}`}
|
||||
onClick={() => {
|
||||
setIsSelected(!isSelected);
|
||||
if (!setFocusedEntryId) return;
|
||||
setFocusedEntryId(entry.id.toString());
|
||||
}}
|
||||
style={{
|
||||
border: isSelected ? `1px ${entry.protocol.backgroundColor} solid` : "1px transparent solid",
|
||||
position: "absolute",
|
||||
position: !headingMode ? "absolute" : "unset",
|
||||
top: style['top'],
|
||||
marginTop: style['marginTop'],
|
||||
width: "calc(100% - 25px)",
|
||||
width: !headingMode ? "calc(100% - 25px)" : "calc(100% - 18px)",
|
||||
}}
|
||||
>
|
||||
<Protocol
|
||||
{!headingMode ? <Protocol
|
||||
protocol={entry.protocol}
|
||||
horizontal={false}
|
||||
updateQuery={updateQuery}
|
||||
/>
|
||||
/> : null}
|
||||
{((entry.protocol.name === "http" && "statusCode" in entry) || entry.statusCode !== 0) && <div>
|
||||
<StatusCode statusCode={entry.statusCode} updateQuery={updateQuery}/>
|
||||
</div>}
|
||||
|
||||
@@ -13,7 +13,7 @@ interface FiltersProps {
|
||||
setQuery: any
|
||||
backgroundColor: string
|
||||
ws: any
|
||||
openWebSocket: (query: string) => void;
|
||||
openWebSocket: (query: string, resetEntriesBuffer: boolean) => void;
|
||||
}
|
||||
|
||||
export const Filters: React.FC<FiltersProps> = ({query, setQuery, backgroundColor, ws, openWebSocket}) => {
|
||||
@@ -33,7 +33,7 @@ interface QueryFormProps {
|
||||
setQuery: any
|
||||
backgroundColor: string
|
||||
ws: any
|
||||
openWebSocket: (query: string) => void;
|
||||
openWebSocket: (query: string, resetEntriesBuffer: boolean) => void;
|
||||
}
|
||||
|
||||
const style = {
|
||||
@@ -63,8 +63,8 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
|
||||
}
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
ws.close()
|
||||
openWebSocket(query)
|
||||
ws.close();
|
||||
openWebSocket(query, true);
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
|
||||
|
||||
const [queriedCurrent, setQueriedCurrent] = useState(0);
|
||||
const [queriedTotal, setQueriedTotal] = useState(0);
|
||||
const [leftOff, setLeftOff] = useState(0);
|
||||
|
||||
const [startTime, setStartTime] = useState(0);
|
||||
|
||||
@@ -100,16 +101,30 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
|
||||
|
||||
const listEntry = useRef(null);
|
||||
|
||||
const openWebSocket = (query) => {
|
||||
setFocusedEntryId(null);
|
||||
setEntries([]);
|
||||
setEntriesBuffer([]);
|
||||
const openWebSocket = (query: string, resetEntriesBuffer: boolean) => {
|
||||
if (resetEntriesBuffer) {
|
||||
setFocusedEntryId(null);
|
||||
setEntries([]);
|
||||
setEntriesBuffer([]);
|
||||
} else {
|
||||
setEntriesBuffer(entries);
|
||||
}
|
||||
ws.current = new WebSocket(MizuWebsocketURL);
|
||||
ws.current.onopen = () => {
|
||||
ws.current.send(query)
|
||||
setConnection(ConnectionStatus.Connected);
|
||||
ws.current.send(query);
|
||||
}
|
||||
ws.current.onclose = () => {
|
||||
setConnection(ConnectionStatus.Closed);
|
||||
}
|
||||
ws.current.onerror = (event) => {
|
||||
console.error("WebSocket error:", event);
|
||||
if (query) {
|
||||
openWebSocket(`(${query}) and leftOff(${leftOff})`, false);
|
||||
} else {
|
||||
openWebSocket(`leftOff(${leftOff})`, false);
|
||||
}
|
||||
}
|
||||
ws.current.onclose = () => setConnection(ConnectionStatus.Closed);
|
||||
}
|
||||
|
||||
if (ws.current) {
|
||||
@@ -119,15 +134,21 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
|
||||
switch (message.messageType) {
|
||||
case "entry":
|
||||
const entry = message.data;
|
||||
if (!focusedEntryId) setFocusedEntryId(entry.id.toString());
|
||||
var focusThis = false;
|
||||
if (!focusedEntryId) {
|
||||
focusThis = true;
|
||||
setFocusedEntryId(entry.id.toString());
|
||||
}
|
||||
setEntriesBuffer([
|
||||
...entriesBuffer,
|
||||
<EntryItem
|
||||
key={entry.id}
|
||||
entry={entry}
|
||||
focusedEntryId={focusThis ? entry.id.toString() : focusedEntryId}
|
||||
setFocusedEntryId={setFocusedEntryId}
|
||||
style={{}}
|
||||
updateQuery={updateQuery}
|
||||
headingMode={false}
|
||||
/>
|
||||
]);
|
||||
break
|
||||
@@ -155,11 +176,22 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
|
||||
case "queryMetadata":
|
||||
setQueriedCurrent(message.data.current);
|
||||
setQueriedTotal(message.data.total);
|
||||
setLeftOff(message.data.leftOff);
|
||||
setEntries(entriesBuffer);
|
||||
break;
|
||||
case "startTime":
|
||||
setStartTime(message.data);
|
||||
break;
|
||||
case "focusEntry":
|
||||
// To achieve selecting only one entry, render all elements in the buffer
|
||||
// with the current `focusedEntryId` value.
|
||||
entriesBuffer.forEach((entry: any, i: number) => {
|
||||
entriesBuffer[i] = React.cloneElement(entry, {
|
||||
focusedEntryId: focusedEntryId
|
||||
});
|
||||
})
|
||||
setEntries(entriesBuffer);
|
||||
break;
|
||||
default:
|
||||
console.error(`unsupported websocket message type, Got: ${message.messageType}`)
|
||||
}
|
||||
@@ -168,7 +200,7 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
openWebSocket("rlimit(100)");
|
||||
openWebSocket("leftOff(-1)", true);
|
||||
try{
|
||||
const tapStatusResponse = await api.tapStatus();
|
||||
setTappingStatus(tapStatusResponse);
|
||||
@@ -185,21 +217,28 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
|
||||
useEffect(() => {
|
||||
if (!focusedEntryId) return;
|
||||
setSelectedEntryData(null);
|
||||
|
||||
if (ws.current.readyState === WebSocket.OPEN) {
|
||||
ws.current.send(focusedEntryId);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const entryData = await api.getEntry(focusedEntryId);
|
||||
setSelectedEntryData(entryData);
|
||||
} catch (error) {
|
||||
toast[error.response.data.type](`Entry[${focusedEntryId}]: ${error.response.data.msg}`, {
|
||||
position: "bottom-right",
|
||||
theme: "colored",
|
||||
autoClose: error.response.data.autoClose,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
});
|
||||
if (error.response) {
|
||||
toast[error.response.data.type](`Entry[${focusedEntryId}]: ${error.response.data.msg}`, {
|
||||
position: "bottom-right",
|
||||
theme: "colored",
|
||||
autoClose: error.response.data.autoClose,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
});
|
||||
}
|
||||
console.error(error);
|
||||
}
|
||||
})()
|
||||
@@ -209,8 +248,11 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
|
||||
if (connection === ConnectionStatus.Connected) {
|
||||
ws.current.close();
|
||||
} else {
|
||||
openWebSocket(query);
|
||||
setConnection(ConnectionStatus.Connected);
|
||||
if (query) {
|
||||
openWebSocket(`(${query}) and leftOff(${leftOff})`, false);
|
||||
} else {
|
||||
openWebSocket(`leftOff(${leftOff})`, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user