Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add debug proxy for tunnel interface #353

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package debugproxy
package usbmuxd

import (
"encoding/hex"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package debugproxy
package usbmuxd

import (
"encoding/json"
Expand Down Expand Up @@ -77,8 +77,8 @@ func (d *DebugProxy) retrieveServiceInfoByPort(port uint16) (PhoneServiceInforma
}

// NewDebugProxy creates a new Default proxy
func NewDebugProxy() *DebugProxy {
return &DebugProxy{mux: sync.Mutex{}, serviceList: []PhoneServiceInformation{}}
func NewDebugProxy(workdir string) *DebugProxy {
return &DebugProxy{mux: sync.Mutex{}, serviceList: []PhoneServiceInformation{}, WorkingDir: workdir}
}

// Launch moves the original /var/run/usbmuxd to /var/run/usbmuxd.real and starts the server at /var/run/usbmuxd
Expand All @@ -104,7 +104,6 @@ func (d *DebugProxy) Launch(device ios.DeviceEntry, binaryMode bool) error {
log.WithFields(log.Fields{"error": err, "socket": ios.GetUsbmuxdSocket()}).Error("Unable to move, lacking permissions?")
return err
}
d.setupDirectory()
listener, err := net.Listen("unix", ios.ToUnixSocketPath(ios.GetUsbmuxdSocket()))
if err != nil {
log.Error("Could not listen on usbmuxd socket, do I have access permissions?", err)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package debugproxy
package usbmuxd

import (
"bytes"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package debugproxy
package usbmuxd

import (
"encoding/hex"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package debugproxy
package usbmuxd

import (
"bytes"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package debugproxy
package usbmuxd

import (
"bytes"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package debugproxy
package usbmuxd

import (
"fmt"
Expand Down
96 changes: 96 additions & 0 deletions ios/debugproxy/utun/capture.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//go:build darwin

package utun

import (
"context"
"errors"
"fmt"
"io"
"net"

"github.com/danielpaulus/go-ios/ios"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
log "github.com/sirupsen/logrus"
)

type direction uint8

const (
outgoing = iota
incoming
)

type connections map[connectionId]*connection

type connectionId struct {
localPort layers.TCPPort
remotePort layers.TCPPort
}

func Live(ctx context.Context, iface string, provider ios.RsdPortProvider, dumpDir string) error {
addr, err := ifaceAddr(iface)
if err != nil {
return err
}
log.Infof("Capture traffice for iface %s with address %s", iface, addr)
if handle, err := pcap.OpenLive(iface, 64*1024, true, pcap.BlockForever); err != nil {
return fmt.Errorf("Live: failed to connect to iface %s. %w", iface, err)
} else {
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
s := newSession(packetSource.Packets(), addr, provider, dumpDir)
s.readPackets(ctx)
}
return nil
}

func ifaceAddr(name string) (net.IP, error) {
ifaces, err := pcap.FindAllDevs()
if err != nil {
return nil, err
}
for _, iface := range ifaces {
if iface.Name == name {
return iface.Addresses[1].IP, nil
}
}
return nil, fmt.Errorf("ifaceAddr: could not find iface with name %s", name)
}

func (s *session) connectionIdentifier(ip *layers.IPv6, tcp *layers.TCP) connectionId {
if ip.SrcIP.String() == s.localAddr.String() {
return connectionId{
localPort: tcp.SrcPort,
remotePort: tcp.DstPort,
}
} else {
return connectionId{
localPort: tcp.DstPort,
remotePort: tcp.SrcPort,
}
}
}

type payloadWriter struct {
incoming io.WriteCloser
outgoing io.WriteCloser
}

func (p payloadWriter) Close() error {
inErr := p.incoming.Close()
outErr := p.outgoing.Close()
return errors.Join(inErr, outErr)
}

func (p payloadWriter) Write(d direction, b []byte) (int, error) {
switch d {
case outgoing:
return p.outgoing.Write(b)
case incoming:
return p.incoming.Write(b)
default:
return 0, fmt.Errorf("Write: unknown direction %d", d)
}
}
14 changes: 14 additions & 0 deletions ios/debugproxy/utun/capture_noop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//go:build !darwin

package utun

import (
"context"
"errors"

"github.com/danielpaulus/go-ios/ios"
)

func Live(ctx context.Context, iface string, provider ios.RsdPortProvider, dumpDir string) error {
return errors.New("capturing traffic on the utun interface is only supported on MacOS")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this still hold true?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can go-ios sniff tunnels created by itself running on a separate process?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should work on linux as well. however, after changing it to build everywhere the build fails on linux. And I think initially the motivation was that this anyways is mainly used on MacOS I would expect that you always want Xcode/MacOS to perform a certain action that we want to capture

}
157 changes: 157 additions & 0 deletions ios/debugproxy/utun/connection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
//go:build darwin

package utun

import (
"errors"
"fmt"
"io"
"os"
"path"

"github.com/google/gopacket/layers"
"github.com/sirupsen/logrus"
)

type connection struct {
id connectionId
w payloadWriter
outPath string
inPath string
service string
}

func newConnection(id connectionId, p string, service string) (*connection, error) {
inPath := path.Join(p, "incoming")
incoming, err := os.OpenFile(inPath, os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err != nil {
return nil, fmt.Errorf("newConnection: could not open file for incoming connection dump: %w", err)
}
outPath := path.Join(p, "outgoing")
outgoing, err := os.OpenFile(outPath, os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err != nil {
return nil, fmt.Errorf("newConnection: could not open file for outgoing connection dump: %w", err)
}
pw := payloadWriter{
incoming: incoming,
outgoing: outgoing,
}
return &connection{
id: id,
w: pw,
outPath: outPath,
inPath: inPath,
service: service,
}, nil
}

func (c connection) handlePacket(tcp *layers.TCP) {
if tcp.SYN && tcp.SrcPort == c.id.localPort {
logrus.Infof("new connection %s", c.id)
}
if len(tcp.Payload) > 0 {
c.w.Write(c.direction(tcp), tcp.Payload)
}
}

func (c connection) direction(tcp *layers.TCP) direction {
if c.id.localPort == tcp.SrcPort {
return outgoing
} else {
return incoming
}
}

func (c connection) Close() error {
_ = c.w.Close()
logrus.WithField("connection", c.id.String()).WithField("service", c.service).Info("closing connection")
err := parseConnectionData(c.outPath, c.inPath)
if err != nil {
logrus.WithField("connection", c.id.String()).
WithField("service", c.service).
WithError(err).
Warn("failed parsing data")
}
return nil
}

func (c connectionId) String() string {
return fmt.Sprintf("%d-%d", c.localPort, c.remotePort)
}

func parseConnectionData(outgoing string, incoming string) error {
dir := path.Dir(outgoing)

outFile, err := os.OpenFile(outgoing, os.O_RDONLY, os.ModePerm)
if err != nil {
return err
}
defer outFile.Close()
inFile, err := os.OpenFile(incoming, os.O_RDONLY, os.ModePerm)
if err != nil {
return err
}
defer inFile.Close()

t := detectType(outFile)

switch t {
case http2:
_ = createDecodingFiles(dir, "http.frames", func(outgoing, incoming pair) error {
outErr := decodeHttp2FrameHeaders(outgoing.w, outFile, true)
inErr := decodeHttp2FrameHeaders(incoming.w, inFile, false)
return errors.Join(outErr, inErr)
})
_, _ = outFile.Seek(0, io.SeekStart)
_, _ = inFile.Seek(0, io.SeekStart)
return createDecodingFiles(dir, "http.bin", func(outgoing, incoming pair) error {
outErr := decodeHttp2(outgoing.w, outFile, true)
inErr := decodeHttp2(incoming.w, inFile, false)
if err := errors.Join(outErr, inErr); err != nil {
//return err
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to keep this?

}
return parseConnectionData(outgoing.p, incoming.p)
})
case remoteXpc:
return createDecodingFiles(dir, "xpc.jsonl", func(outgoing, incoming pair) error {
outErr := decodeRemoteXpc(outgoing.w, outFile)
inErr := decodeRemoteXpc(incoming.w, inFile)
return errors.Join(outErr, inErr)
})
case remoteDtx:
return createDecodingFiles(dir, "dtx", func(outgoing, incoming pair) error {
outErr := decodeRemoteDtx(outgoing.w, outFile)
inErr := decodeRemoteDtx(incoming.w, inFile)
return errors.Join(outErr, inErr)
})
default:
stat, _ := os.Stat(outgoing)
if stat.Size() == 0 {
return nil
}
return fmt.Errorf("unknown content type: %s/%s", outgoing, incoming)
}
}

func createDecodingFiles(dir, suffix string, consumer func(outgoing, incoming pair) error) error {
outPath := path.Join(dir, fmt.Sprintf("outgoing.%s", suffix))
inPath := path.Join(dir, fmt.Sprintf("incoming.%s", suffix))

outFile, err := os.OpenFile(outPath, os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err != nil {
return err
}
defer outFile.Close()
inFile, err := os.OpenFile(inPath, os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err != nil {
return err
}
defer inFile.Close()

return consumer(pair{outPath, outFile}, pair{inPath, inFile})
}

type pair struct {
p string
w io.Writer
}
Loading
Loading