From 2554c2f393fa3849b10372bb3449250aee8c901c Mon Sep 17 00:00:00 2001 From: Gourav kumar <44055698+gouravkrosx@users.noreply.github.com> Date: Thu, 14 Mar 2024 15:12:38 +0530 Subject: [PATCH] fix: dns resolution in fedora (#1691) * fix: dns resolution in fedora Signed-off-by: gouravkrosx --------- Signed-off-by: gouravkrosx --- pkg/core/core.go | 95 ++++++++++++++++++++-- pkg/core/proxy/integrations/http/decode.go | 6 ++ pkg/core/proxy/integrations/http/http.go | 10 ++- pkg/core/proxy/proxy.go | 48 ++++++----- pkg/models/instrument.go | 1 + pkg/service/record/record.go | 4 +- pkg/service/replay/replay.go | 2 +- 7 files changed, 129 insertions(+), 37 deletions(-) diff --git a/pkg/core/core.go b/pkg/core/core.go index 64e46ad59..b0803d9c0 100644 --- a/pkg/core/core.go +++ b/pkg/core/core.go @@ -5,6 +5,8 @@ import ( "context" "errors" "fmt" + "os" + "strings" "sync" "golang.org/x/sync/errgroup" @@ -16,12 +18,13 @@ import ( ) type Core struct { - logger *zap.Logger - id utils.AutoInc - apps sync.Map - hook Hooks - proxy Proxy - proxyStarted bool + logger *zap.Logger + id utils.AutoInc + apps sync.Map + hook Hooks + proxy Proxy + proxyStarted bool + hostConfigStr string // hosts string in the nsswitch.conf of linux system. To restore the system hosts configuration after completion of test } func New(logger *zap.Logger, hook Hooks, proxy Proxy) *Core { @@ -63,7 +66,7 @@ func (c *Core) getApp(id uint64) (*app.App, error) { return h, nil } -func (c *Core) Hook(ctx context.Context, id uint64, _ models.HookOptions) error { +func (c *Core) Hook(ctx context.Context, id uint64, opts models.HookOptions) error { hookErr := errors.New("failed to hook into the app") a, err := c.getApp(id) @@ -117,6 +120,13 @@ func (c *Core) Hook(ctx context.Context, id uint64, _ models.HookOptions) error utils.LogError(c.logger, err, "failed to unload the hooks") } + // reset the hosts config in nsswitch.conf of the system (in test mode) + if opts.Mode == models.MODE_TEST && c.hostConfigStr != "" { + err := c.resetNsSwitchConfig() + if err != nil { + utils.LogError(c.logger, err, "") + } + } return nil }) @@ -157,6 +167,15 @@ func (c *Core) Hook(ctx context.Context, id uint64, _ models.HookOptions) error } c.proxyStarted = true + + if opts.Mode == models.MODE_TEST { + // setting up the dns routing in test mode (helpful in fedora distro) + err = c.setupNsswitchConfig() + if err != nil { + return err + } + } + return nil } @@ -227,3 +246,65 @@ func (c *Core) GetAppIP(_ context.Context, id uint64) (string, error) { return a.ContainerIPv4Addr(), nil } + +// setting up the dns routing for the linux system +func (c *Core) setupNsswitchConfig() error { + nsSwitchConfig := "/etc/nsswitch.conf" + + // Check if the nsswitch.conf present for the system + if _, err := os.Stat(nsSwitchConfig); err == nil { + // Read the current nsswitch.conf + data, err := os.ReadFile(nsSwitchConfig) + if err != nil { + utils.LogError(c.logger, err, "failed to read the nsswitch.conf file from system") + return errors.New("failed to setup the nsswitch.conf file to redirect the DNS queries to proxy") + } + + // Replace the hosts field value if it exists + lines := strings.Split(string(data), "\n") + for i, line := range lines { + if strings.HasPrefix(line, "hosts:") { + c.hostConfigStr = lines[i] + lines[i] = "hosts: files dns" + } + } + + // Write the modified nsswitch.conf back to the file + err = os.WriteFile("/etc/nsswitch.conf", []byte(strings.Join(lines, "\n")), 0644) + if err != nil { + utils.LogError(c.logger, err, "failed to write the configuration to the nsswitch.conf file to redirect the DNS queries to proxy") + return errors.New("failed to setup the nsswitch.conf file to redirect the DNS queries to proxy") + } + + c.logger.Debug("Successfully written to nsswitch config of linux") + } + return nil +} + +// resetNsSwitchConfig resets the hosts config of nsswitch of the system +func (c *Core) resetNsSwitchConfig() error { + nsSwitchConfig := "/etc/nsswitch.conf" + data, err := os.ReadFile(nsSwitchConfig) + if err != nil { + c.logger.Error("failed to read the nsswitch.conf file from system", zap.Error(err)) + return errors.New("failed to reset the nsswitch.conf back to the original state") + } + + // Replace the hosts field value if it exists with the actual system hosts value + lines := strings.Split(string(data), "\n") + for i, line := range lines { + if strings.HasPrefix(line, "hosts:") { + lines[i] = c.hostConfigStr + } + } + + // Write the modified nsswitch.conf back to the file + err = os.WriteFile(nsSwitchConfig, []byte(strings.Join(lines, "\n")), 0644) + if err != nil { + c.logger.Error("failed to write the configuration to the nsswitch.conf file to redirect the DNS queries to proxy", zap.Error(err)) + return errors.New("failed to reset the nsswitch.conf back to the original state") + } + + c.logger.Debug("Successfully reset the nsswitch config of linux") + return nil +} diff --git a/pkg/core/proxy/integrations/http/decode.go b/pkg/core/proxy/integrations/http/decode.go index 2788da5f9..28f6e02ba 100644 --- a/pkg/core/proxy/integrations/http/decode.go +++ b/pkg/core/proxy/integrations/http/decode.go @@ -35,6 +35,7 @@ func decodeHTTP(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientCo for { //Check if the expected header is present if bytes.Contains(reqBuf, []byte("Expect: 100-continue")) { + logger.Debug("The expect header is present in the request buffer and writing the 100 continue response to the client") //Send the 100 continue response _, err := clientConn.Write([]byte("HTTP/1.1 100 Continue\r\n\r\n")) if err != nil { @@ -45,6 +46,7 @@ func decodeHTTP(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientCo errCh <- err return } + logger.Debug("The 100 continue response has been sent to the user application") //Read the request buffer again newRequest, err := util.ReadBytes(ctx, logger, clientConn) if err != nil { @@ -56,6 +58,7 @@ func decodeHTTP(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientCo reqBuf = append(reqBuf, newRequest...) } + logger.Debug("handling the chunked requests to read the complete request") err := handleChunkedRequests(ctx, logger, &reqBuf, clientConn, nil) if err != nil { utils.LogError(logger, err, "failed to handle chunked requests") @@ -90,7 +93,10 @@ func decodeHTTP(ctx context.Context, logger *zap.Logger, reqBuf []byte, clientCo match, stub, err := match(ctx, logger, param, mockDb) if err != nil { utils.LogError(logger, err, "error while matching http mocks", zap.Any("metadata", getReqMeta(request))) + errCh <- err + return } + logger.Debug("after matching the http request", zap.Any("isMatched", match), zap.Any("stub", stub), zap.Error(err)) if !match { if !isPassThrough(logger, request, dstCfg.Port, opts) { diff --git a/pkg/core/proxy/integrations/http/http.go b/pkg/core/proxy/integrations/http/http.go index 12e149475..8464a4745 100755 --- a/pkg/core/proxy/integrations/http/http.go +++ b/pkg/core/proxy/integrations/http/http.go @@ -5,6 +5,7 @@ import ( "bytes" "compress/gzip" "context" + "fmt" "io" "net" "net/http" @@ -45,7 +46,7 @@ type finalHTTP struct { // MatchType function determines if the outgoing network call is HTTP by comparing the // message format with that of an HTTP text message. func (h *HTTP) MatchType(_ context.Context, buf []byte) bool { - return bytes.HasPrefix(buf[:], []byte("HTTP/")) || + isHTTP := bytes.HasPrefix(buf[:], []byte("HTTP/")) || bytes.HasPrefix(buf[:], []byte("GET ")) || bytes.HasPrefix(buf[:], []byte("POST ")) || bytes.HasPrefix(buf[:], []byte("PUT ")) || @@ -53,17 +54,20 @@ func (h *HTTP) MatchType(_ context.Context, buf []byte) bool { bytes.HasPrefix(buf[:], []byte("DELETE ")) || bytes.HasPrefix(buf[:], []byte("OPTIONS ")) || bytes.HasPrefix(buf[:], []byte("HEAD ")) + h.logger.Debug(fmt.Sprintf("is Http Protocol?: %v ", isHTTP)) + return isHTTP } func (h *HTTP) RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, mocks chan<- *models.Mock, opts models.OutgoingOptions) error { logger := h.logger.With(zap.Any("Client IP Address", src.RemoteAddr().String()), zap.Any("Client ConnectionID", util.GetNextID()), zap.Any("Destination ConnectionID", util.GetNextID())) + h.logger.Debug("Recording the outgoing http call in record mode") + reqBuf, err := util.ReadInitialBuf(ctx, logger, src) if err != nil { utils.LogError(logger, err, "failed to read the initial http message") return err } - err = encodeHTTP(ctx, logger, reqBuf, src, dst, mocks, opts) if err != nil { utils.LogError(logger, err, "failed to encode the http message into the yaml") @@ -75,6 +79,8 @@ func (h *HTTP) RecordOutgoing(ctx context.Context, src net.Conn, dst net.Conn, m func (h *HTTP) MockOutgoing(ctx context.Context, src net.Conn, dstCfg *integrations.ConditionalDstCfg, mockDb integrations.MockMemDb, opts models.OutgoingOptions) error { logger := h.logger.With(zap.Any("Client IP Address", src.RemoteAddr().String()), zap.Any("Client ConnectionID", util.GetNextID()), zap.Any("Destination ConnectionID", util.GetNextID())) + h.logger.Debug("Mocking the outgoing http call in test mode") + reqBuf, err := util.ReadInitialBuf(ctx, logger, src) if err != nil { utils.LogError(logger, err, "failed to read the initial http message") diff --git a/pkg/core/proxy/proxy.go b/pkg/core/proxy/proxy.go index a254440f9..bef574c63 100755 --- a/pkg/core/proxy/proxy.go +++ b/pkg/core/proxy/proxy.go @@ -142,33 +142,31 @@ func (p *Proxy) StartProxy(ctx context.Context, opts core.ProxyOptions) error { } }) - if models.GetMode() == models.MODE_TEST { - p.logger.Info("Keploy has taken control of the DNS resolution mechanism, your application may misbehave in test mode if you have provided wrong domain name in your application code.") - // start the UDP DNS server - p.logger.Debug("Starting Udp Dns Server in Test mode...") - g.Go(func() error { - utils.Recover(p.logger) - errCh := make(chan error, 1) - go func(errCh chan error) { - err := p.startUDPDNSServer(ctx) - if err != nil { - errCh <- err - } - }(errCh) + // start the UDP DNS server + p.logger.Debug("Starting Udp Dns Server for handling Dns queries over UDP") + g.Go(func() error { + utils.Recover(p.logger) + errCh := make(chan error, 1) + go func(errCh chan error) { + err := p.startUDPDNSServer(ctx) + if err != nil { + errCh <- err + } + }(errCh) - select { - case <-ctx.Done(): - err := p.UDPDNSServer.Shutdown() - if err != nil { - utils.LogError(p.logger, err, "failed to shutdown tcp dns server") - return err - } - return nil - case err := <-errCh: + select { + case <-ctx.Done(): + err := p.UDPDNSServer.Shutdown() + if err != nil { + utils.LogError(p.logger, err, "failed to shutdown tcp dns server") return err } - }) - } + return nil + case err := <-errCh: + return err + } + }) + p.logger.Info("Keploy has taken control of the DNS resolution mechanism, your application may misbehave if you have provided wrong domain name in your application code.") p.logger.Info(fmt.Sprintf("Proxy started at port:%v", p.Port)) return nil @@ -399,7 +397,7 @@ func (p *Proxy) handleConnection(ctx context.Context, srcConn net.Conn) error { //make new connection to the destination server if isTLS { - logger.Debug("", zap.Any("isTLS connection", isTLS)) + logger.Debug("the external call is tls-encrypted", zap.Any("isTLS", isTLS)) cfg := &tls.Config{ InsecureSkipVerify: true, ServerName: dstURL, diff --git a/pkg/models/instrument.go b/pkg/models/instrument.go index 881e641f1..5c55d8ec0 100644 --- a/pkg/models/instrument.go +++ b/pkg/models/instrument.go @@ -7,6 +7,7 @@ import ( ) type HookOptions struct { + Mode Mode } type OutgoingOptions struct { diff --git a/pkg/service/record/record.go b/pkg/service/record/record.go index 7ddddf2cd..8d89a2fdb 100755 --- a/pkg/service/record/record.go +++ b/pkg/service/record/record.go @@ -118,7 +118,7 @@ func (r *recorder) Start(ctx context.Context) error { return nil default: // Starting the hooks and proxy - err = r.instrumentation.Hook(hookCtx, appID, models.HookOptions{}) + err = r.instrumentation.Hook(hookCtx, appID, models.HookOptions{Mode: models.MODE_RECORD}) if err != nil { stopReason = "failed to start the hooks and proxy" utils.LogError(r.logger, err, stopReason) @@ -270,7 +270,7 @@ func (r *recorder) StartMock(ctx context.Context) error { utils.LogError(r.logger, err, stopReason) return fmt.Errorf(stopReason) } - err = r.instrumentation.Hook(ctx, appID, models.HookOptions{}) + err = r.instrumentation.Hook(ctx, appID, models.HookOptions{Mode: models.MODE_RECORD}) if err != nil { stopReason = "failed to start the hooks and proxy" utils.LogError(r.logger, err, stopReason) diff --git a/pkg/service/replay/replay.go b/pkg/service/replay/replay.go index 841834100..b096ed4a8 100644 --- a/pkg/service/replay/replay.go +++ b/pkg/service/replay/replay.go @@ -181,7 +181,7 @@ func (r *replayer) BootReplay(ctx context.Context) (string, uint64, context.Canc default: hookCtx := context.WithoutCancel(ctx) hookCtx, cancel = context.WithCancel(hookCtx) - err = r.instrumentation.Hook(hookCtx, appID, models.HookOptions{}) + err = r.instrumentation.Hook(hookCtx, appID, models.HookOptions{Mode: models.MODE_TEST}) if err != nil { cancel() if errors.Is(err, context.Canceled) {