diff --git a/internal/daemon/relay.go b/internal/daemon/relay.go index af78fe6..0991b8c 100644 --- a/internal/daemon/relay.go +++ b/internal/daemon/relay.go @@ -46,6 +46,12 @@ func NewRelay(ctx context.Context, dev *usb.Device, tun tun.Interface, phoneMAC return nil, err } + // Opening the data interface clears the RNDIS packet filter on some + // devices, so it has to be enabled again before data can flow. + if err := rndis.NewSession(dev).ReapplyFilter(); err != nil { + return nil, err + } + ctx, cancel := context.WithCancel(ctx) return &Relay{ dev: dev, diff --git a/internal/rndis/messages.go b/internal/rndis/messages.go index fe8fa78..c120ace 100644 --- a/internal/rndis/messages.go +++ b/internal/rndis/messages.go @@ -13,12 +13,12 @@ type CommonHeader struct { // RemoteNdisInitializeMsg is sent by Host -> Device to start the handshake. type RemoteNdisInitializeMsg struct { - MessageType uint32 // MsgInit (0x02) - MessageLength uint32 // 24 bytes - RequestID uint32 - MajorVersion uint32 // 1 - MinorVersion uint32 // 0 - MaxTransferSz uint32 // Recommended: 16384 (16KB) + MessageType uint32 // MsgInit (0x02) + MessageLength uint32 // 24 bytes + RequestID uint32 + MajorVersion uint32 // 1 + MinorVersion uint32 // 0 + MaxTransferSz uint32 // Recommended: 16384 (16KB) } func (m *RemoteNdisInitializeMsg) Marshal() []byte { @@ -66,8 +66,8 @@ func UnmarshalInitializeCmplt(data []byte) (*RemoteNdisInitializeCmplt, error) { // RemoteNdisQueryMsg is sent by Host -> Device to query OIDs (e.g., getting MAC address). type RemoteNdisQueryMsg struct { - RequestID uint32 - OID uint32 + RequestID uint32 + OID uint32 InformationBufferLength uint32 // for simple queries, usually 0 } @@ -77,9 +77,9 @@ func (m *RemoteNdisQueryMsg) Marshal() []byte { binary.LittleEndian.PutUint32(b[4:8], 28) binary.LittleEndian.PutUint32(b[8:12], m.RequestID) binary.LittleEndian.PutUint32(b[12:16], m.OID) - binary.LittleEndian.PutUint32(b[16:20], 0) // Info Buffer Length + binary.LittleEndian.PutUint32(b[16:20], 0) // Info Buffer Length binary.LittleEndian.PutUint32(b[20:24], 20) // Info Buffer Offset (offset from RequestID field, which is 8 index + 12 = 20) - binary.LittleEndian.PutUint32(b[24:28], 0) // Device Context + binary.LittleEndian.PutUint32(b[24:28], 0) // Device Context return b } @@ -148,19 +148,24 @@ func UnmarshalSetCmplt(data []byte) (uint32, uint32, error) { // returns Request } // EncapsulatePacket wraps a raw Ethernet packet with an RNDIS header. +// +// The header is the standard 44-byte REMOTE_NDIS_PACKET_MSG with a DataOffset +// of 36, matching what the in-tree Linux host driver (rndis_host.c) sends. +// Some Android RNDIS gadgets (e.g. vivo/MediaTek) drop frames that pad the +// header beyond those 44 bytes and surface them as receive errors. func EncapsulatePacket(packet []byte) []byte { - headerLen := 48 // Use 48 bytes for 8-byte alignment (44 + 4 padding) + headerLen := 44 // Full REMOTE_NDIS_PACKET_MSG header; data starts right after it. totalLen := headerLen + len(packet) - + b := make([]byte, totalLen) binary.LittleEndian.PutUint32(b[0:4], MsgPacket) binary.LittleEndian.PutUint32(b[4:8], uint32(totalLen)) - binary.LittleEndian.PutUint32(b[8:12], 40) // DataOffset (relative to byte 8: 8 + 40 = 48) + binary.LittleEndian.PutUint32(b[8:12], 36) // DataOffset (relative to byte 8: 8 + 36 = 44) binary.LittleEndian.PutUint32(b[12:16], uint32(len(packet))) - // Bytes 16-47 are reserved/optional fields (offset/length for OOB data, info, etc.) + // Bytes 16-43 are reserved/optional fields (offset/length for OOB data, info, etc.) // We leave them zeroed. - - copy(b[48:], packet) + + copy(b[44:], packet) return b } diff --git a/internal/rndis/messages_test.go b/internal/rndis/messages_test.go new file mode 100644 index 0000000..6bc157c --- /dev/null +++ b/internal/rndis/messages_test.go @@ -0,0 +1,59 @@ +package rndis + +import ( + "bytes" + "encoding/binary" + "testing" +) + +// TestEncapsulatePacketLayout locks the wire format to the canonical +// REMOTE_NDIS_PACKET_MSG layout used by the Linux host driver: a 44-byte +// header, DataOffset 36 (relative to the start of RequestId) and the +// Ethernet frame immediately after the header. +func TestEncapsulatePacketLayout(t *testing.T) { + frame := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00} + pkt := EncapsulatePacket(frame) + + if len(pkt) != 44+len(frame) { + t.Fatalf("packet length = %d, want %d", len(pkt), 44+len(frame)) + } + if got := binary.LittleEndian.Uint32(pkt[0:4]); got != MsgPacket { + t.Errorf("MessageType = 0x%08X, want 0x%08X", got, MsgPacket) + } + if got := binary.LittleEndian.Uint32(pkt[4:8]); got != uint32(len(pkt)) { + t.Errorf("MessageLength = %d, want %d", got, len(pkt)) + } + if got := binary.LittleEndian.Uint32(pkt[8:12]); got != 36 { + t.Errorf("DataOffset = %d, want 36", got) + } + if got := binary.LittleEndian.Uint32(pkt[12:16]); got != uint32(len(frame)) { + t.Errorf("DataLength = %d, want %d", got, len(frame)) + } + if !bytes.Equal(pkt[44:], frame) { + t.Errorf("payload = %x, want %x", pkt[44:], frame) + } + for i := 16; i < 44; i++ { + if pkt[i] != 0 { + t.Errorf("reserved byte %d = 0x%02X, want 0", i, pkt[i]) + } + } +} + +// TestDecapsulatePacket checks that the canonical layout round-trips through +// the decoder and that truncated input is rejected. +func TestDecapsulatePacket(t *testing.T) { + frame := []byte{0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x42, 0x86, 0x6b, 0x49, 0x67, 0x24, 0x08, 0x06, 0x01, 0x02} + pkt := EncapsulatePacket(frame) + + got, err := DecapsulatePacket(pkt) + if err != nil { + t.Fatalf("DecapsulatePacket: %v", err) + } + if !bytes.Equal(got, frame) { + t.Errorf("round-trip payload = %x, want %x", got, frame) + } + + if _, err := DecapsulatePacket(pkt[:20]); err == nil { + t.Error("expected error for truncated packet") + } +} diff --git a/internal/rndis/rndis.go b/internal/rndis/rndis.go index 6eb9c6f..4275912 100644 --- a/internal/rndis/rndis.go +++ b/internal/rndis/rndis.go @@ -26,6 +26,12 @@ func NewSession(dev *usb.Device) *Session { func (s *Session) Handshake() ([]byte, error) { log.Info().Str("component", "rndis").Msg("Starting RNDIS handshake...") + // 0. Drain any stale encapsulated responses left on the control pipe by a + // previous host session. Without this the handshake can desync: the first + // read would consume an old completion and every subsequent read would be + // off by one message. + s.flushResponses() + // 1. Send INIT s.requestID++ initMsg := &RemoteNdisInitializeMsg{ @@ -109,6 +115,29 @@ func (s *Session) Handshake() ([]byte, error) { return macCmplt.Payload, nil } +// ReapplyFilter re-sends OID_GEN_CURRENT_PACKET_FILTER to enable data flow. +// +// Some RNDIS gadgets (e.g. vivo/MediaTek) reset the packet filter to zero when +// the data interface is re-selected with SET_INTERFACE, which makes the phone +// silently drop all of its own broadcast traffic — DHCP offers included. +// Re-applying the filter after the data interface has been opened restores it. +func (s *Session) ReapplyFilter() error { + s.requestID++ + setFilter := &RemoteNdisSetMsg{ + RequestID: s.requestID, + OID: OID_GEN_CURRENT_PACKET_FILTER, + Value: PacketTypeDirected | PacketTypeBroadcast | PacketTypeAllMulticast, + } + if err := s.sendControl(setFilter.Marshal()); err != nil { + return fmt.Errorf("rndis: failed to re-apply packet filter: %w", err) + } + if _, err := s.receiveControl(); err != nil { + log.Warn().Str("component", "rndis").Err(err).Msg("No SET_CMPLT after re-applying packet filter") + } + log.Info().Str("component", "rndis").Msg("Packet filter re-applied after opening data interface") + return nil +} + // KeepAlive sends a REMOTE_NDIS_KEEPALIVE_MSG to keep the connection active. func (s *Session) KeepAlive() error { s.requestID++ @@ -144,7 +173,7 @@ func (s *Session) sendControl(data []byte) error { // receiveControl retrieves an encapsulated RNDIS response. func (s *Session) receiveControl() ([]byte, error) { buf := make([]byte, 2048) - + // Direct polling with generous retries for macOS (50 * 20ms = 1s total) for i := 0; i < 50; i++ { time.Sleep(20 * time.Millisecond) @@ -155,3 +184,16 @@ func (s *Session) receiveControl() ([]byte, error) { } return nil, fmt.Errorf("rndis: timeout waiting for control response") } + +// flushResponses discards any encapsulated responses still queued on the +// device, so the next handshake starts from a clean slate. +func (s *Session) flushResponses() { + buf := make([]byte, 2048) + for i := 0; i < 16; i++ { + n, err := s.dev.Control(0xA1, 0x01, 0, uint16(s.dev.InterfaceNum), buf) + if err != nil || n == 0 { + return + } + time.Sleep(5 * time.Millisecond) + } +} diff --git a/internal/usb/device.go b/internal/usb/device.go index 2b796fc..04457c5 100644 --- a/internal/usb/device.go +++ b/internal/usb/device.go @@ -2,8 +2,8 @@ package usb import ( "fmt" - "time" "github.com/google/gousb" + "time" ) // Device wraps a gousb.Device with RNDIS specific fields and capabilities. @@ -152,9 +152,10 @@ func (d *Device) OpenBulkEndpoints() (in *gousb.InEndpoint, out *gousb.OutEndpoi continue } } - + in, out, _ = d.findEndpoints(intf) if in != nil && out != nil { + d.prepareDataInterface(intf) fmt.Printf("USB DEBUG: Found bulk endpoints on intf %d alt %d\n", intfDesc.Number, alt.Number) d.dataIntf = intf return in, out, nil @@ -166,12 +167,32 @@ func (d *Device) OpenBulkEndpoints() (in *gousb.InEndpoint, out *gousb.OutEndpoi } } } else { + d.prepareDataInterface(d.dataIntf) return d.findEndpoints(d.dataIntf) } return nil, nil, fmt.Errorf("could not find bulk endpoint pair") } +// prepareDataInterface makes a claimed RNDIS data interface ready for traffic: +// select alt 0 explicitly and clear any stale endpoint halt left behind by an +// earlier session. +// +// Some gadgets (e.g. vivo/MediaTek) keep their bulk endpoints disabled until +// the host issues SET_INTERFACE, and transfers to a halted endpoint NAK until +// they time out. Both requests are standard and harmless on other devices. +func (d *Device) prepareDataInterface(intf *gousb.Interface) { + num := intf.Setting.Number + if _, err := d.Control(0x01, 0x0B, 0, uint16(num), nil); err != nil { + fmt.Printf("USB DEBUG: SET_INTERFACE(%d, 0) failed: %v\n", num, err) + } + for _, ep := range intf.Setting.Endpoints { + if _, err := d.Control(0x02, 0x01, 0, uint16(ep.Address), nil); err != nil { + fmt.Printf("USB DEBUG: CLEAR_FEATURE(halt) ep 0x%02x failed: %v\n", ep.Address, err) + } + } +} + func (d *Device) findEndpoints(intf *gousb.Interface) (*gousb.InEndpoint, *gousb.OutEndpoint, error) { var in *gousb.InEndpoint var out *gousb.OutEndpoint