-
-
Notifications
You must be signed in to change notification settings - Fork 329
Expand file tree
/
Copy pathconnect.go
More file actions
executable file
·412 lines (363 loc) · 15 KB
/
Copy pathconnect.go
File metadata and controls
executable file
·412 lines (363 loc) · 15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
package ios
import (
"encoding/binary"
"errors"
"fmt"
"net"
"os"
"strconv"
"time"
"github.com/danielpaulus/go-ios/ios/http"
"github.com/danielpaulus/go-ios/ios/xpc"
)
type connectMessage struct {
BundleID string
ClientVersionString string
MessageType string
ProgName string
LibUSBMuxVersion uint32 `plist:"kLibUSBMuxVersion"`
DeviceID uint32
PortNumber uint16
}
func newConnectMessage(deviceID int, portNumber uint16) connectMessage {
data := connectMessage{
BundleID: "go.ios.control",
ClientVersionString: "go-usbmux-0.0.1",
MessageType: "Connect",
ProgName: "go-usbmux",
LibUSBMuxVersion: 3,
DeviceID: uint32(deviceID),
PortNumber: portNumber,
}
return data
}
// Connect issues a Connect Message to UsbMuxd for the given deviceID on the given port
// enabling the newCodec for it.
// It returns an error containing the UsbMux error code should the connect fail.
func (muxConn *UsbMuxConnection) Connect(deviceID int, port uint16) error {
msg := newConnectMessage(deviceID, Ntohs(port))
muxConn.Send(msg)
resp, err := muxConn.ReadMessage()
if err != nil {
return err
}
response := MuxResponsefromBytes(resp.Payload)
if response.IsSuccessFull() {
return nil
}
return fmt.Errorf("Failed connecting to service, error code:%d", response.Number)
}
// serviceConfigurations stores info about which DTX based services only execute a SSL Handshake
// and then go back to sending unencrypted data right after the handshake.
var serviceConfigurations = map[string]bool{
"com.apple.instruments.remoteserver": true,
"com.apple.accessibility.axAuditDaemon.remoteserver": true,
"com.apple.testmanagerd.lockdown": true,
"com.apple.debugserver": true,
}
// ConnectLockdown connects this Usbmux connection to the LockDown service that
// always runs on the device on the same port. The connect call needs the deviceID which can be
// retrieved from a DeviceList using the ListDevices function. After this function
// is done, the UsbMuxConnection cannot be used anymore because the same underlying
// network connection is used for talking to Lockdown. Sending usbmux commands would break it.
// It returns a new LockDownConnection.
func (muxConn *UsbMuxConnection) ConnectLockdown(deviceID int) (*LockDownConnection, error) {
msg := newConnectMessage(deviceID, Lockdownport)
err := muxConn.Send(msg)
if err != nil {
return &LockDownConnection{}, err
}
resp, err := muxConn.ReadMessage()
if err != nil {
return &LockDownConnection{}, err
}
response := MuxResponsefromBytes(resp.Payload)
if response.IsSuccessFull() {
return &LockDownConnection{muxConn.deviceConn, "", NewPlistCodec()}, nil
}
return nil, fmt.Errorf("Failed connecting to Lockdown with error code:%d", response.Number)
}
func ConnectToService(device DeviceEntry, serviceName string) (DeviceConnectionInterface, error) {
startServiceResponse, err := StartService(device, serviceName)
if err != nil {
return nil, err
}
pairRecord, err := ReadPairRecord(device.Properties.SerialNumber)
if err != nil {
return nil, err
}
muxConn, err := NewUsbMuxConnectionSimple()
if err != nil {
return nil, fmt.Errorf("Could not connect to usbmuxd socket, is it running? %w", err)
}
err = muxConn.connectWithStartServiceResponse(device.DeviceID, startServiceResponse, pairRecord)
if err != nil {
return nil, err
}
return muxConn.ReleaseDeviceConnection(), nil
}
// RsdPortForService looks up the port a service is listening on in the given RSD service list.
// Services provided by the developer disk image only show up after a recent enough image has been
// mounted: mounting adds them, but an outdated image can lack newer services even when mounted.
// Without this check we would dial port 0 and report a confusing 'connection refused' instead of
// the actual cause.
func RsdPortForService(rsd RsdPortProvider, service string) (int, error) {
port := rsd.GetPort(service)
if port == 0 {
return 0, fmt.Errorf("service '%s' is not available in RSD. If it is provided by the developer disk image, make sure a recent image is mounted (run `ios image auto`) — an outdated image can lack this service even when mounted", service)
}
return port, nil
}
// ConnectToShimService opens a new connection of the tunnel interface of the provided device
// to the provided service.
// The 'RSDCheckin' required by shim services is also executed before returning the connection to the caller
func ConnectToShimService(device DeviceEntry, service string) (DeviceConnectionInterface, error) {
if !device.SupportsRsd() {
return nil, fmt.Errorf("ConnectToShimService: Cannot connect to %s, missing tunnel address and RSD port. To start the tunnel, run `ios tunnel start`", service)
}
port, err := RsdPortForService(device.Rsd, service)
if err != nil {
return nil, fmt.Errorf("ConnectToShimService: %w", err)
}
conn, err := ConnectTUNDevice(device.Address, port, device)
if err != nil {
return nil, err
}
err = RsdCheckin(conn)
if err != nil {
return nil, err
}
return NewDeviceConnectionWithRWC(conn), nil
}
// ConnectToServiceTunnelIface connects to a service on an iOS17+ device using a XPC over HTTP2 connection
// It returns a new xpc.Connection
func ConnectToXpcServiceTunnelIface(device DeviceEntry, serviceName string) (*xpc.Connection, error) {
if !device.SupportsRsd() {
return nil, fmt.Errorf("ConnectToXpcServiceTunnelIface: Cannot connect to %s, missing tunnel address and RSD port. To start the tunnel, run `ios tunnel start`", serviceName)
}
port, err := RsdPortForService(device.Rsd, serviceName)
if err != nil {
return nil, fmt.Errorf("ConnectToXpcServiceTunnelIface: %w", err)
}
conn, err := ConnectTUNDevice(device.Address, port, device)
if err != nil {
return nil, fmt.Errorf("ConnectToHttp2: failed to dial: %w", err)
}
h, err := http.NewHttpConnection(conn)
if err != nil {
return nil, fmt.Errorf("ConnectToXpcServiceTunnelIface: failed to connect to http2: %w", err)
}
return CreateXpcConnection(h)
}
func ConnectToServiceTunnelIface(device DeviceEntry, serviceName string) (DeviceConnectionInterface, error) {
if !device.SupportsRsd() {
return nil, fmt.Errorf("ConnectToServiceTunnelIface: Cannot connect to %s, missing tunnel address and RSD port. To start the tunnel, run `ios tunnel start`", serviceName)
}
port, err := RsdPortForService(device.Rsd, serviceName)
if err != nil {
return nil, fmt.Errorf("ConnectToServiceTunnelIface: %w", err)
}
conn, err := ConnectTUNDevice(device.Address, port, device)
if err != nil {
return nil, fmt.Errorf("ConnectToServiceTunnelIface: failed to connect to tunnel: %w", err)
}
return NewDeviceConnectionWithRWC(conn), nil
}
func CreateXpcConnection(h *http.HttpConnection) (*xpc.Connection, error) {
err := initializeXpcConnection(h)
if err != nil {
return nil, fmt.Errorf("CreateXpcConnection: failed to initialize xpc connection: %w", err)
}
clientServerChannel := http.NewStreamReadWriter(h, http.ClientServer)
serverClientChannel := http.NewStreamReadWriter(h, http.ServerClient)
xpcConn, err := xpc.New(clientServerChannel, serverClientChannel, h)
if err != nil {
return nil, fmt.Errorf("CreateXpcConnection: failed to create xpc connection: %w", err)
}
return xpcConn, nil
}
// connectWithStartServiceResponse issues a Connect Message to UsbMuxd for the given deviceID on the given port
// enabling the newCodec for it. It also enables SSL on the new service connection if requested by StartServiceResponse.
// It returns an error containing the UsbMux error code should the connect fail.
func (muxConn *UsbMuxConnection) connectWithStartServiceResponse(deviceID int, startServiceResponse StartServiceResponse, pairRecord PairRecord) error {
err := muxConn.Connect(deviceID, startServiceResponse.Port)
if err != nil {
return err
}
var sslerr error
if startServiceResponse.EnableServiceSSL {
if _, ok := serviceConfigurations[startServiceResponse.Service]; ok {
sslerr = muxConn.deviceConn.EnableSessionSslHandshakeOnly(pairRecord)
} else {
sslerr = muxConn.deviceConn.EnableSessionSsl(pairRecord)
}
if sslerr != nil {
return sslerr
}
}
return nil
}
func ConnectLockdownWithSession(device DeviceEntry) (*LockDownConnection, error) {
muxConnection, err := NewUsbMuxConnectionSimple()
if err != nil {
return nil, fmt.Errorf("USBMuxConnection failed with: %v", err)
}
defer muxConnection.ReleaseDeviceConnection()
pairRecord, err := muxConnection.ReadPair(device.Properties.SerialNumber)
if err != nil {
return nil, fmt.Errorf("could not retrieve PairRecord with error: %v", err)
}
lockdownConnection, err := muxConnection.ConnectLockdown(device.DeviceID)
if err != nil {
return nil, fmt.Errorf("Lockdown connection failed with: %v", err)
}
resp, err := lockdownConnection.StartSession(pairRecord)
if err != nil {
return nil, fmt.Errorf("StartSession failed: %+v error: %v", resp, err)
}
return lockdownConnection, nil
}
func initializeXpcConnection(h *http.HttpConnection) error {
csWriter := http.NewStreamReadWriter(h, http.ClientServer)
ssWriter := http.NewStreamReadWriter(h, http.ServerClient)
err := xpc.EncodeMessage(csWriter, xpc.Message{
Flags: xpc.AlwaysSetFlag,
Body: map[string]interface{}{},
Id: 0,
})
if err != nil {
return fmt.Errorf("initializeXpcConnection: failed to encode message: %w", err)
}
_, err = xpc.DecodeMessage(csWriter) // TODO : figure out if need to act on this frame
if err != nil {
return fmt.Errorf("initializeXpcConnection: failed to decode message: %w", err)
}
err = xpc.EncodeMessage(ssWriter, xpc.Message{
Flags: xpc.InitHandshakeFlag | xpc.AlwaysSetFlag,
Body: nil,
Id: 0,
})
if err != nil {
return fmt.Errorf("initializeXpcConnection: failed to encode message 2: %w", err)
}
_, err = xpc.DecodeMessage(ssWriter) // TODO : figure out if need to act on this frame
if err != nil {
return fmt.Errorf("initializeXpcConnection: failed to decode message 2: %w", err)
}
err = xpc.EncodeMessage(csWriter, xpc.Message{
Flags: 0x201, // alwaysSetFlag | 0x200
Body: nil,
Id: 0,
})
if err != nil {
return fmt.Errorf("initializeXpcConnection: failed to encode message 3: %w", err)
}
_, err = xpc.DecodeMessage(csWriter) // TODO : figure out if need to act on this frame
if err != nil {
return fmt.Errorf("initializeXpcConnection: failed to decode message 3: %w", err)
}
return nil
}
// TunnelDialTimeout bounds TCP connects to tunnel/RSD endpoints. Without an
// explicit timeout, a dial to a dead-but-still-routed tunnel address (device
// rebooted or hung while the host-side TUN interface and route stayed up)
// blocks for the kernel's TCP SYN timeout (~135s on Linux) per operation. 15s
// is far above any healthy tunnel connect (sub-second) while still failing
// fast enough for staleness handling to react.
const TunnelDialTimeout = 15 * time.Second
// ErrDialTimeout marks a tunnel/RSD TCP connect that exceeded go-ios' dial
// timeout rather than failing outright. Callers can use errors.Is to treat the
// endpoint as stale: the route existed but the device never answered, which is
// the signature of a dead tunnel whose interface lingers.
var ErrDialTimeout = errors.New("dial timed out")
// DialTunnelTCP connects to a tunnel/RSD TCP endpoint (address in the form
// accepted by net.Dial, e.g. "[fd00::1]:1234") with TunnelDialTimeout.
func DialTunnelTCP(address string) (*net.TCPConn, error) {
return DialTunnelTCPWithTimeout(address, TunnelDialTimeout)
}
// DialTunnelTCPWithTimeout is DialTunnelTCP with a caller-chosen timeout.
// Timeout errors are wrapped in ErrDialTimeout so they stay distinguishable
// from refused/unreachable errors.
func DialTunnelTCPWithTimeout(address string, timeout time.Duration) (*net.TCPConn, error) {
d := net.Dialer{Timeout: timeout}
conn, err := d.Dial("tcp", address)
if err != nil {
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
return nil, fmt.Errorf("%w after %v: %w", ErrDialTimeout, timeout, err)
}
return nil, err
}
return conn.(*net.TCPConn), nil
}
// ConnectTUNDevice creates a *net.TCPConn to the device at the given address and port.
// If the device is a userspaceTUN device provided by go-ios agent, it will connect to this
// automatically. Otherwise it will try a operating system level TUN device.
func ConnectTUNDevice(remoteIp string, port int, d DeviceEntry) (*net.TCPConn, error) {
// Backstop for callers that skip the RsdPortForService check: on the
// userspace-TUN path a port-0 dial would not even fail — the forwarder
// accepts it and the caller hangs later with no pointer to the cause.
if port <= 0 {
return nil, fmt.Errorf("ConnectTUNDevice: invalid port %d for %s — the service is not available in RSD (is the developer disk image mounted and recent enough?)", port, remoteIp)
}
if !d.UserspaceTUN {
return connectTUN(remoteIp, port)
}
conn, err := DialTunnelTCP(fmt.Sprintf("%s:%d", d.UserspaceTUNHost, d.UserspaceTUNPort))
if err != nil {
return nil, fmt.Errorf("ConnectUserSpaceTunnel: failed to dial: %w", err)
}
err = conn.SetKeepAlive(true)
if err != nil {
return nil, fmt.Errorf("ConnectUserSpaceTunnel: failed to set keepalive: %w", err)
}
err = conn.SetKeepAlivePeriod(1 * time.Second)
if err != nil {
return nil, fmt.Errorf("ConnectUserSpaceTunnel: failed to set keepalive period: %w", err)
}
_, err = conn.Write(net.ParseIP(remoteIp).To16())
portBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(portBytes, uint32(port))
_, err1 := conn.Write(portBytes)
return conn, errors.Join(err, err1)
}
// connect to a operating system level TUN device
func connectTUN(address string, port int) (*net.TCPConn, error) {
conn, err := DialTunnelTCP(fmt.Sprintf("[%s]:%d", address, port))
if err != nil {
return nil, fmt.Errorf("ConnectToHttp2WithAddr: failed to dial: %w", err)
}
err = conn.SetKeepAlive(true)
if err != nil {
return nil, fmt.Errorf("ConnectUserSpaceTunnel: failed to set keepalive: %w", err)
}
err = conn.SetKeepAlivePeriod(1 * time.Second)
if err != nil {
return nil, fmt.Errorf("ConnectUserSpaceTunnel: failed to set keepalive period: %w", err)
}
return conn, nil
}
// defaultHttpApiPort is the port on which we start the HTTP-Server for exposing started tunnels
// 60-105 is leetspeek for go-ios :-D
const defaultHttpApiPort = 60105
// defaultHttpApiHost is the host on which the HTTP-Server runs, by default it is 127.0.0.1
const defaultHttpApiHost = "127.0.0.1"
// DefaultHttpApiPort is the port on which we start the HTTP-Server for exposing started tunnels
// if GO_IOS_AGENT_PORT is set, we use that port. Otherwise we use the default port 60106.
// 60-105 is leetspeek for go-ios :-D
func HttpApiPort() int {
port, err := strconv.Atoi(os.Getenv("GO_IOS_AGENT_PORT"))
if err != nil {
return defaultHttpApiPort
}
return port
}
// DefaultHttpApiHost is the host on which the HTTP-Server runs, by default it is 127.0.0.1
// if GO_IOS_AGENT_HOST is set, we use that host. Otherwise we use the default host
func HttpApiHost() string {
host := os.Getenv("GO_IOS_AGENT_HOST")
if host == "" {
return defaultHttpApiHost
}
return host
}