Skip to content

Commit

Permalink
fix: dns resolution in fedora (keploy#1691)
Browse files Browse the repository at this point in the history
* fix: dns resolution in fedora

Signed-off-by: gouravkrosx <[email protected]>
---------

Signed-off-by: gouravkrosx <[email protected]>
  • Loading branch information
gouravkrosx authored Mar 14, 2024
1 parent daadb36 commit 2554c2f
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 37 deletions.
95 changes: 88 additions & 7 deletions pkg/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"context"
"errors"
"fmt"
"os"
"strings"
"sync"

"golang.org/x/sync/errgroup"
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
})

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}
6 changes: 6 additions & 0 deletions pkg/core/proxy/integrations/http/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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")
Expand Down Expand Up @@ -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) {
Expand Down
10 changes: 8 additions & 2 deletions pkg/core/proxy/integrations/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"bytes"
"compress/gzip"
"context"
"fmt"
"io"
"net"
"net/http"
Expand Down Expand Up @@ -45,25 +46,28 @@ 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 ")) ||
bytes.HasPrefix(buf[:], []byte("PATCH ")) ||
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")
Expand All @@ -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")
Expand Down
48 changes: 23 additions & 25 deletions pkg/core/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions pkg/models/instrument.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
)

type HookOptions struct {
Mode Mode
}

type OutgoingOptions struct {
Expand Down
4 changes: 2 additions & 2 deletions pkg/service/record/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion pkg/service/replay/replay.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 2554c2f

Please sign in to comment.