From f48a63a0b9b0f2559481baad7ef8a2537db3f5d3 Mon Sep 17 00:00:00 2001 From: Qtozdec <56160254+qtozdec@users.noreply.github.com> Date: Fri, 10 Apr 2026 13:45:33 +0300 Subject: [PATCH] feat(mobile,client,mux): Android integration + SOCKS5 auth + reliability fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - mobile/: gomobile-bindable entry point for Android (combined libgojni.so) - internal/protect/: Android socket protect via VpnService for olcRTC sockets - internal/names/data/: embedded name pools for client identity generation - client: add SOCKS5 USER/PASS auth (RFC 1929) and bind to 127.0.0.1 - mux: infinite backpressure via waitForBufferSpace, raise buffer to 32MB, remove close-on-overflow (was corrupting reliable TCP streams over DC) - peer: remove 3-second drop in send worker — wait for SCTP buffer to drain instead of dropping packets (broke large HTTP/2 transfers like Instagram/X) Co-Authored-By: Claude Opus 4.6 --- cmd/olcrtc/main.go | 2 +- internal/client/client.go | 63 ++- internal/mux/mux.go | 73 +++- internal/names/data/names | 735 +++++++++++++++++++++++++++++++++++ internal/names/data/surnames | 735 +++++++++++++++++++++++++++++++++++ internal/names/names.go | 35 +- internal/protect/protect.go | 66 ++++ internal/telemost/api.go | 4 +- internal/telemost/peer.go | 38 +- mobile/mobile.go | 133 +++++++ 10 files changed, 1835 insertions(+), 49 deletions(-) create mode 100644 internal/names/data/names create mode 100644 internal/names/data/surnames create mode 100644 internal/protect/protect.go create mode 100644 mobile/mobile.go diff --git a/cmd/olcrtc/main.go b/cmd/olcrtc/main.go index cac6b4c..8b3201b 100644 --- a/cmd/olcrtc/main.go +++ b/cmd/olcrtc/main.go @@ -87,7 +87,7 @@ func main() { case "srv": errCh <- server.Run(ctx, roomURL, keyHex, duo, dnsServer) case "cnc": - errCh <- client.Run(ctx, roomURL, keyHex, socksPort, duo) + errCh <- client.Run(ctx, roomURL, keyHex, socksPort, duo, "", "") } }() diff --git a/internal/client/client.go b/internal/client/client.go index 4928817..9ddf8a3 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -31,7 +31,7 @@ type Client struct { wg sync.WaitGroup } -func Run(ctx context.Context, roomURL, keyHex string, socksPort int, duo bool) error { +func Run(ctx context.Context, roomURL, keyHex string, socksPort int, duo bool, socksUser, socksPass string) error { var key []byte var err error @@ -150,7 +150,7 @@ func Run(ctx context.Context, roomURL, keyHex string, socksPort int, duo bool) e } log.Printf("Sent reset signal to server (clientID=%d)", c.clientID) - err = c.runSOCKS5(ctx, socksPort) + err = c.runSOCKS5(ctx, socksPort, socksUser, socksPass) log.Println("Waiting for client goroutines...") c.wg.Wait() @@ -170,13 +170,13 @@ func (c *Client) onData(data []byte) { c.mux.HandleFrame(plaintext) } -func (c *Client) runSOCKS5(ctx context.Context, port int) error { - listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port)) +func (c *Client) runSOCKS5(ctx context.Context, port int, username, password string) error { + listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) if err != nil { return err } - log.Printf("SOCKS5 proxy listening on 0.0.0.0:%d", port) + log.Printf("SOCKS5 proxy listening on 127.0.0.1:%d (auth=%v)", port, username != "") go func() { <-ctx.Done() @@ -190,11 +190,11 @@ func (c *Client) runSOCKS5(ctx context.Context, port int) error { select { case <-ctx.Done(): log.Println("SOCKS5 listener closed") - + for _, peer := range c.peers { peer.Close() } - + return nil default: log.Printf("Accept error: %v", err) @@ -202,15 +202,15 @@ func (c *Client) runSOCKS5(ctx context.Context, port int) error { } } - go c.handleSOCKS5(conn) + go c.handleSOCKS5(conn, username, password) } } -func (c *Client) handleSOCKS5(conn net.Conn) { +func (c *Client) handleSOCKS5(conn net.Conn, username, password string) { defer conn.Close() startTime := time.Now() - buf := make([]byte, 256) + buf := make([]byte, 513) if _, err := io.ReadFull(conn, buf[:2]); err != nil { return @@ -225,7 +225,48 @@ func (c *Client) handleSOCKS5(conn net.Conn) { return } - conn.Write([]byte{5, 0}) + requireAuth := username != "" + wantMethod := byte(0x00) + if requireAuth { + wantMethod = 0x02 + } + hasMethod := false + for i := 0; i < int(nmethods); i++ { + if buf[i] == wantMethod { + hasMethod = true + break + } + } + if !hasMethod { + conn.Write([]byte{5, 0xFF}) + return + } + conn.Write([]byte{5, wantMethod}) + + if requireAuth { + // RFC 1929: VER ULEN UNAME PLEN PASSWD + if _, err := io.ReadFull(conn, buf[:2]); err != nil { + return + } + if buf[0] != 0x01 { + return + } + ulen := int(buf[1]) + if _, err := io.ReadFull(conn, buf[:ulen+1]); err != nil { + return + } + gotUser := string(buf[:ulen]) + plen := int(buf[ulen]) + if _, err := io.ReadFull(conn, buf[:plen]); err != nil { + return + } + gotPass := string(buf[:plen]) + if gotUser != username || gotPass != password { + conn.Write([]byte{0x01, 0x01}) + return + } + conn.Write([]byte{0x01, 0x00}) + } if _, err := io.ReadFull(conn, buf[:4]); err != nil { return diff --git a/internal/mux/mux.go b/internal/mux/mux.go index bf5d784..c0de57a 100644 --- a/internal/mux/mux.go +++ b/internal/mux/mux.go @@ -7,18 +7,19 @@ package mux import ( "encoding/binary" "sync" + "time" "github.com/openlibrecommunity/olcrtc/internal/logger" ) type Stream struct { - ID uint16 - ClientID uint32 - recvBuf []byte - closed bool - mu sync.Mutex - nextSeq uint32 - outOfOrder map[uint32][]byte + ID uint16 + ClientID uint32 + recvBuf []byte + closed bool + mu sync.Mutex + nextSeq uint32 + outOfOrder map[uint32][]byte } func (s *Stream) RecvBuf() []byte { @@ -48,7 +49,7 @@ func New(clientID uint32, onSend func([]byte) error) *Multiplexer { clientID: clientID, onSend: onSend, maxStreams: 10000, - maxBufferSize: 1024 * 1024, + maxBufferSize: 32 * 1024 * 1024, dataReady: make(map[uint16]chan struct{}), sendSeq: make(map[uint16]uint32), } @@ -220,26 +221,37 @@ func (m *Multiplexer) HandleFrame(frame []byte) { } if seq == stream.nextSeq { - if len(stream.recvBuf)+len(data) > m.maxBufferSize { - stream.closed = true + // Backpressure: if the stream buffer is full, release the mux lock and + // wait for the reader to drain it. Dropping/closing here would corrupt + // the TCP stream carried over the mux — large HTTP/2 downloads (X, + // Instagram, YouTube) that push data faster than conn.Write can accept + // would lose bytes and hang forever. + if s := m.waitForBufferSpace(sid, clientID, len(data)); s == nil { return + } else { + stream = s } stream.recvBuf = append(stream.recvBuf, data...) stream.nextSeq++ - + for { - if nextData, ok := stream.outOfOrder[stream.nextSeq]; ok { - if len(stream.recvBuf)+len(nextData) > m.maxBufferSize { - stream.closed = true - return - } - stream.recvBuf = append(stream.recvBuf, nextData...) - delete(stream.outOfOrder, stream.nextSeq) - stream.nextSeq++ - logger.Verbose("Applied out-of-order packet sid=%d seq=%d", sid, stream.nextSeq-1) - } else { + nextData, ok := stream.outOfOrder[stream.nextSeq] + if !ok { break } + if s := m.waitForBufferSpace(sid, clientID, len(nextData)); s == nil { + return + } else { + stream = s + } + nextData, ok = stream.outOfOrder[stream.nextSeq] + if !ok { + break + } + stream.recvBuf = append(stream.recvBuf, nextData...) + delete(stream.outOfOrder, stream.nextSeq) + stream.nextSeq++ + logger.Verbose("Applied out-of-order packet sid=%d seq=%d", sid, stream.nextSeq-1) } m.dataReadyMu.Lock() @@ -260,6 +272,25 @@ func (m *Multiplexer) HandleFrame(frame []byte) { } } +// waitForBufferSpace releases m.mu and waits until the stream's recvBuf has +// room for `need` more bytes, then re-acquires the lock. Returns the (possibly +// re-fetched) stream, or nil if the stream disappeared / was reset / closed. +// Caller must hold m.mu (write-locked) on entry and will hold it on return. +func (m *Multiplexer) waitForBufferSpace(sid uint16, clientID uint32, need int) *Stream { + for { + stream, ok := m.streams[sid] + if !ok || stream.ClientID != clientID || stream.closed { + return nil + } + if len(stream.recvBuf)+need <= m.maxBufferSize { + return stream + } + m.mu.Unlock() + time.Sleep(5 * time.Millisecond) + m.mu.Lock() + } +} + func (m *Multiplexer) ReadStream(sid uint16) []byte { m.mu.Lock() defer m.mu.Unlock() diff --git a/internal/names/data/names b/internal/names/data/names new file mode 100644 index 0000000..60e45ff --- /dev/null +++ b/internal/names/data/names @@ -0,0 +1,735 @@ +Аарон +Аба +Аббас +Абд аль-Узза +Абдуллах +Абид +Аботур +Аввакум +Август +Авдей +Авель +Аверкий +Авигдор +Авирмэд +Авксентий +Авл +Авнер +Аврелий +Автандил +Автоном +Агапит +Агафангел +Агафодор +Агафон +Аги +Агриппа +Адам +Адар +Адиль +Адольф +Адонирам +Адриан +Азамат +Азарий +Азат +Азиз +Азим +Айварс +Айдар +Айрат +Акакий +Аквилий +Акиф +Акоп +Аксель +Алан +Аланус +Алек +Александр +Алексей +Алемдар +Алик +Алим +Алипий +Алишер +Алмат +Алоиз +Алон +Альберик +Альберт +Альбин +Альваро +Альвиан +Альвизе +Альфонс +Альфред +Амадис +Амвросий +Амедей +Амин +Амир +Амр +Амфилохий +Анания +Анас +Анастасий +Анатолий +Ангеляр +Андокид +Андрей +Андроник +Аннерс +Анри +Ансельм +Антипа +Антон +Антоний +Антонин +Антуан +Арам +Арефа +Арзуман +Аристарх +Аристон +Ариф +Аркадий +Арсений +Артём +Артур +Арфаксад +Асаф +Атанасий +Атом +Аттик +Афанасий +Афинагор +Афиней +Афиф +Африкан +Ахилл +Ахмад +Ахтям +Ашот +Бадар +Барни +Бартоломео +Басир +Бахтияр +Баян +Безсон +Бен +Беньямин +Берт +Бехруз +Билял +Богдан +Болеслав +Бонавентура +Борис +Борислав +Боян +Бронислав +Брячислав +Бурхан +Бутрос +Бямбасурэн +Вадим +Валентин +Валентино +Валерий +Валерьян +Вальдемар +Вангьял +Варлам +Варнава +Варфоломей +Василий +Вахтанг +Велвел +Венансио +Венедикт +Вениамин +Венцеслав +Вигго +Викентий +Виктор +Викторин +Вильгельм +Винцас +Виссарион +Виталий +Витаутас +Вито +Владимир +Владислав +Владлен +Влас +Воислав +Володарь +Вольфганг +Вописк +Всеволод +Всеслав +Вук +Вукол +Вышеслав +Вячеслав +Габриеле +Гавриил +Гай +Галактион +Галымжан +Гамлет +Гаспар +Гафур +Гвидо +Гейдар +Геласий +Гелий +Гельмут +Геннадий +Генри +Генрих +Георге +Георгий +Гераклид +Герасим +Герберт +Герман +Германн +Геронтий +Герхард +Гийом +Гильем +Гинкмар +Глеб +Гней +Гоар +Горацио +Гордей +Градислав +Григорий +Гримоальд +Гуго +Гурий +Густав +Гьялцен +Давид +Дамдинсурэн +Дамир +Даниил +Дарий +Демид +Демьян +Денеш +Денис +Децим +Джаббар +Джамиль +Джан +Джанер +Джанфранко +Джафар +Джейкоб +Джихангир +Джованни +Джон +Джохар +Джулиано +Джулиус +Дино +Диодор +Дитер +Дитмар +Дитрих +Дмитрий +Доминик +Дональд +Донат +Дорофей +Досифей +Евгений +Евграф +Евдоким +Еврит +Евсей +Евстафий +Евтихан +Евтихий +Егор +Елеазар +Елисей +Емельян +Епифаний +Ербол +Ерванд +Еремей +Ермак +Ермолай +Ерофей +Ефим +Ефрем +Жан +Ждан +Жером +Жоан +Захар +Захария +Збигнев +Зденек +Зейналабдин +Зенон +Зеэв +Зигмунд +Зинон +Зия +Золтан +Зосима +Иакинф +Иан +Ибрагим +Ибрахим +Иван +Игнатий +Игорь +Иероним +Иерофей +Израиль +Икрима +Иларий +Илия +Илларион +Илмари +Ильфат +Илья +Имран +Иннокентий +Иоаким +Иоанн +Иоанникий +Иоахим +Иов +Иоганн +Иоганнес +Ионафан +Иосафат +Ираклий +Иржи +Иринарх +Ириней +Иродион +Иса +Исаак +Исаакий +Исаия +Исидор +Ислам +Исмаил +Истислав +Истома +Истукарий +Иштван +Йюрген +Кадваллон +Кадир +Казимир +Каликст +Калин +Каллистрат +Кальман +Канат +Карен +Карлос +Карп +Картерий +Кассиан +Кассий +Касторий +Касьян +Катберт +Квинт +Кехлер +Киллиан +Ким +Кир +Кириак +Кирилл +Клаас +Клавдиан +Клеоник +Климент +Кондрат +Конон +Конрад +Константин +Корнелиус +Корнилий +Коррадо +Косьма +Кратет +Кратипп +Крис +Криспин +Кристиан +Кронид +Кузьма +Куприян +Курбан +Курт +Кутлуг-Буга +Кэлин +Лаврентий +Лавс +Ладислав +Лазарь +Лайл +Лампрехт +Ландульф +Лев +Леви +Ленни +Леонид +Леонтий +Леонхард +Лиам +Линкей +Логгин +Лоренц +Лоренцо +Луи +Луитпольд +Лука +Лукас +Лукий +Лукьян +Луций +Людовик +Люцифер +Макар +Максим +Максимиан +Максимилиан +Малик +Малх +Мамбет +Маний +Мануил +Мануэль +Мариан +Мариус +Марк +Маркел +Мартын +Марчелло +Матвей +Матео +Матиас +Матфей +Матфий +Махмуд +Меир +Мелентий +Мелитон +Менахем-Мендель +Месроп +Мефодий +Мечислав +Мика +Микеланджело +Микулаш +Милорад +Мина +Мирко +Мирон +Мирослав +Митрофан +Михаил +Михей +Младан +Модест +Моисей +Мордехай +Мстислав +Мурад +Мухаммед +Мэдисон +Мэлор +Мэлс +Назар +Наиль +Насиф +Натан +Натаниэль +Наум +Нафанаил +Нацагдорж +Нестор +Никандр +Никанор +Никита +Никифор +Никодим +Николай +Нил +Нильс +Ноа +Ной +Норд +Нуржан +Нурлан +Овадья +Оге +Одинец +Октав +Октавиан +Октавий +Октавио +Олаф +Оле +Олег +Оливер +Ольгерд +Онисим +Орест +Осип +Оскар +Осман +Отто +Оттон +Очирбат +Пабло +Павел +Павлин +Павсикакий +Паисий +Палладий +Панкратий +Пантелеймон +Папа +Паруйр +Парфений +Патрик +Пафнутий +Пахомий +Педро +Пётр +Пимен +Пинхас +Пипин +Питирим +Пол +Полидор +Полиевкт +Поликарп +Поликрат +Порфирий +Потап +Предраг +Премысл +Приск +Прокл +Прокопий +Прокул +Протасий +Прохор +Публий +Рагнар +Рагуил +Радмир +Радослав +Разумник +Раймонд +Рамадан +Рамазан +Рахман +Рашад +Рейнхард +Ренат +Реститут +Ричард +Роберт +Родерик +Родион +Рожер +Розарио +Роман +Ромен +Рон +Ронан +Ростислав +Рудольф +Руслан +Руф +Руфин +Рушан +Сабит +Савва +Савватий +Савелий +Савин +Саддам +Садик +Саид +Салават +Салих +Саллюстий +Салман +Самуил +Сармат +Святослав +Севастьян +Северин +Секст +Секунд +Семён +Септимий +Серапион +Сергей +Серж +Сигеберт +Сильвестр +Симеон +Симон +Созон +Соломон +Сонам +Софрон +Спиридон +Срджан +Станислав +Степан +Стефано +Стивен +Таврион +Тавус +Тадеуш +Тарас +Тарасий +Тейс +Тендзин +Теофил +Терентий +Терри +Тиберий +Тигран +Тимофей +Тимур +Тихомир +Тихон +Томас +Томоми +Торос +Тофик +Трифон +Трофим +Тудхалия +Тутмос +Тьерри +Тьяго +Уве +Уильям +Улдис +Ульрих +Ульф +Умар +Урызмаг +Усама +Усман +Фавст +Фаддей +Файзулла +Фарид +Фахраддин +Федериго +Федосей +Федот +Фейсал +Феликс +Феоктист +Феофан +Феофил +Феофилакт +Фердинанд +Ференц +Фёдор +Фидель +Филарет +Филат +Филип +Филипп +Философ +Филострат +Фирс +Фока +Фома +Фотий +Франц +Франческо +Фредерик +Фридрих +Фродо +Фрол +Фульк +Хайме +Ханс +Харальд +Харитон +Харри +Харрисон +Хасан +Хетаг +Хильдерик +Хирам +Хлодвиг +Хокон +Хорив +Хоселито +Хосрой +Хрисанф +Христофор +Хуан +Цэрэндорж +Чеслав +Шалом +Шамиль +Шамсуддин +Шапур +Шарль +Шейх-Хайдар +Шон +Эберхард +Эдмунд +Эдна +Эдуард +Элбэгдорж +Элджернон +Элиас +Эллиот +Эмиль +Энрик +Энрико +Энтони +Эразм +Эраст +Эрик +Эрнст +Эсекьель +Эстебан +Этьен +Ювеналий +Юлиан +Юлий +Юлиус +Юрий +Юстас +Юстин +Яков +Якуб +Якун +Ян +Яни +Януарий +Яромир +Ярополк +Ярослав diff --git a/internal/names/data/surnames b/internal/names/data/surnames new file mode 100644 index 0000000..60e45ff --- /dev/null +++ b/internal/names/data/surnames @@ -0,0 +1,735 @@ +Аарон +Аба +Аббас +Абд аль-Узза +Абдуллах +Абид +Аботур +Аввакум +Август +Авдей +Авель +Аверкий +Авигдор +Авирмэд +Авксентий +Авл +Авнер +Аврелий +Автандил +Автоном +Агапит +Агафангел +Агафодор +Агафон +Аги +Агриппа +Адам +Адар +Адиль +Адольф +Адонирам +Адриан +Азамат +Азарий +Азат +Азиз +Азим +Айварс +Айдар +Айрат +Акакий +Аквилий +Акиф +Акоп +Аксель +Алан +Аланус +Алек +Александр +Алексей +Алемдар +Алик +Алим +Алипий +Алишер +Алмат +Алоиз +Алон +Альберик +Альберт +Альбин +Альваро +Альвиан +Альвизе +Альфонс +Альфред +Амадис +Амвросий +Амедей +Амин +Амир +Амр +Амфилохий +Анания +Анас +Анастасий +Анатолий +Ангеляр +Андокид +Андрей +Андроник +Аннерс +Анри +Ансельм +Антипа +Антон +Антоний +Антонин +Антуан +Арам +Арефа +Арзуман +Аристарх +Аристон +Ариф +Аркадий +Арсений +Артём +Артур +Арфаксад +Асаф +Атанасий +Атом +Аттик +Афанасий +Афинагор +Афиней +Афиф +Африкан +Ахилл +Ахмад +Ахтям +Ашот +Бадар +Барни +Бартоломео +Басир +Бахтияр +Баян +Безсон +Бен +Беньямин +Берт +Бехруз +Билял +Богдан +Болеслав +Бонавентура +Борис +Борислав +Боян +Бронислав +Брячислав +Бурхан +Бутрос +Бямбасурэн +Вадим +Валентин +Валентино +Валерий +Валерьян +Вальдемар +Вангьял +Варлам +Варнава +Варфоломей +Василий +Вахтанг +Велвел +Венансио +Венедикт +Вениамин +Венцеслав +Вигго +Викентий +Виктор +Викторин +Вильгельм +Винцас +Виссарион +Виталий +Витаутас +Вито +Владимир +Владислав +Владлен +Влас +Воислав +Володарь +Вольфганг +Вописк +Всеволод +Всеслав +Вук +Вукол +Вышеслав +Вячеслав +Габриеле +Гавриил +Гай +Галактион +Галымжан +Гамлет +Гаспар +Гафур +Гвидо +Гейдар +Геласий +Гелий +Гельмут +Геннадий +Генри +Генрих +Георге +Георгий +Гераклид +Герасим +Герберт +Герман +Германн +Геронтий +Герхард +Гийом +Гильем +Гинкмар +Глеб +Гней +Гоар +Горацио +Гордей +Градислав +Григорий +Гримоальд +Гуго +Гурий +Густав +Гьялцен +Давид +Дамдинсурэн +Дамир +Даниил +Дарий +Демид +Демьян +Денеш +Денис +Децим +Джаббар +Джамиль +Джан +Джанер +Джанфранко +Джафар +Джейкоб +Джихангир +Джованни +Джон +Джохар +Джулиано +Джулиус +Дино +Диодор +Дитер +Дитмар +Дитрих +Дмитрий +Доминик +Дональд +Донат +Дорофей +Досифей +Евгений +Евграф +Евдоким +Еврит +Евсей +Евстафий +Евтихан +Евтихий +Егор +Елеазар +Елисей +Емельян +Епифаний +Ербол +Ерванд +Еремей +Ермак +Ермолай +Ерофей +Ефим +Ефрем +Жан +Ждан +Жером +Жоан +Захар +Захария +Збигнев +Зденек +Зейналабдин +Зенон +Зеэв +Зигмунд +Зинон +Зия +Золтан +Зосима +Иакинф +Иан +Ибрагим +Ибрахим +Иван +Игнатий +Игорь +Иероним +Иерофей +Израиль +Икрима +Иларий +Илия +Илларион +Илмари +Ильфат +Илья +Имран +Иннокентий +Иоаким +Иоанн +Иоанникий +Иоахим +Иов +Иоганн +Иоганнес +Ионафан +Иосафат +Ираклий +Иржи +Иринарх +Ириней +Иродион +Иса +Исаак +Исаакий +Исаия +Исидор +Ислам +Исмаил +Истислав +Истома +Истукарий +Иштван +Йюрген +Кадваллон +Кадир +Казимир +Каликст +Калин +Каллистрат +Кальман +Канат +Карен +Карлос +Карп +Картерий +Кассиан +Кассий +Касторий +Касьян +Катберт +Квинт +Кехлер +Киллиан +Ким +Кир +Кириак +Кирилл +Клаас +Клавдиан +Клеоник +Климент +Кондрат +Конон +Конрад +Константин +Корнелиус +Корнилий +Коррадо +Косьма +Кратет +Кратипп +Крис +Криспин +Кристиан +Кронид +Кузьма +Куприян +Курбан +Курт +Кутлуг-Буга +Кэлин +Лаврентий +Лавс +Ладислав +Лазарь +Лайл +Лампрехт +Ландульф +Лев +Леви +Ленни +Леонид +Леонтий +Леонхард +Лиам +Линкей +Логгин +Лоренц +Лоренцо +Луи +Луитпольд +Лука +Лукас +Лукий +Лукьян +Луций +Людовик +Люцифер +Макар +Максим +Максимиан +Максимилиан +Малик +Малх +Мамбет +Маний +Мануил +Мануэль +Мариан +Мариус +Марк +Маркел +Мартын +Марчелло +Матвей +Матео +Матиас +Матфей +Матфий +Махмуд +Меир +Мелентий +Мелитон +Менахем-Мендель +Месроп +Мефодий +Мечислав +Мика +Микеланджело +Микулаш +Милорад +Мина +Мирко +Мирон +Мирослав +Митрофан +Михаил +Михей +Младан +Модест +Моисей +Мордехай +Мстислав +Мурад +Мухаммед +Мэдисон +Мэлор +Мэлс +Назар +Наиль +Насиф +Натан +Натаниэль +Наум +Нафанаил +Нацагдорж +Нестор +Никандр +Никанор +Никита +Никифор +Никодим +Николай +Нил +Нильс +Ноа +Ной +Норд +Нуржан +Нурлан +Овадья +Оге +Одинец +Октав +Октавиан +Октавий +Октавио +Олаф +Оле +Олег +Оливер +Ольгерд +Онисим +Орест +Осип +Оскар +Осман +Отто +Оттон +Очирбат +Пабло +Павел +Павлин +Павсикакий +Паисий +Палладий +Панкратий +Пантелеймон +Папа +Паруйр +Парфений +Патрик +Пафнутий +Пахомий +Педро +Пётр +Пимен +Пинхас +Пипин +Питирим +Пол +Полидор +Полиевкт +Поликарп +Поликрат +Порфирий +Потап +Предраг +Премысл +Приск +Прокл +Прокопий +Прокул +Протасий +Прохор +Публий +Рагнар +Рагуил +Радмир +Радослав +Разумник +Раймонд +Рамадан +Рамазан +Рахман +Рашад +Рейнхард +Ренат +Реститут +Ричард +Роберт +Родерик +Родион +Рожер +Розарио +Роман +Ромен +Рон +Ронан +Ростислав +Рудольф +Руслан +Руф +Руфин +Рушан +Сабит +Савва +Савватий +Савелий +Савин +Саддам +Садик +Саид +Салават +Салих +Саллюстий +Салман +Самуил +Сармат +Святослав +Севастьян +Северин +Секст +Секунд +Семён +Септимий +Серапион +Сергей +Серж +Сигеберт +Сильвестр +Симеон +Симон +Созон +Соломон +Сонам +Софрон +Спиридон +Срджан +Станислав +Степан +Стефано +Стивен +Таврион +Тавус +Тадеуш +Тарас +Тарасий +Тейс +Тендзин +Теофил +Терентий +Терри +Тиберий +Тигран +Тимофей +Тимур +Тихомир +Тихон +Томас +Томоми +Торос +Тофик +Трифон +Трофим +Тудхалия +Тутмос +Тьерри +Тьяго +Уве +Уильям +Улдис +Ульрих +Ульф +Умар +Урызмаг +Усама +Усман +Фавст +Фаддей +Файзулла +Фарид +Фахраддин +Федериго +Федосей +Федот +Фейсал +Феликс +Феоктист +Феофан +Феофил +Феофилакт +Фердинанд +Ференц +Фёдор +Фидель +Филарет +Филат +Филип +Филипп +Философ +Филострат +Фирс +Фока +Фома +Фотий +Франц +Франческо +Фредерик +Фридрих +Фродо +Фрол +Фульк +Хайме +Ханс +Харальд +Харитон +Харри +Харрисон +Хасан +Хетаг +Хильдерик +Хирам +Хлодвиг +Хокон +Хорив +Хоселито +Хосрой +Хрисанф +Христофор +Хуан +Цэрэндорж +Чеслав +Шалом +Шамиль +Шамсуддин +Шапур +Шарль +Шейх-Хайдар +Шон +Эберхард +Эдмунд +Эдна +Эдуард +Элбэгдорж +Элджернон +Элиас +Эллиот +Эмиль +Энрик +Энрико +Энтони +Эразм +Эраст +Эрик +Эрнст +Эсекьель +Эстебан +Этьен +Ювеналий +Юлиан +Юлий +Юлиус +Юрий +Юстас +Юстин +Яков +Якуб +Якун +Ян +Яни +Януарий +Яромир +Ярополк +Ярослав diff --git a/internal/names/names.go b/internal/names/names.go index f443fd5..43a3e02 100644 --- a/internal/names/names.go +++ b/internal/names/names.go @@ -2,11 +2,18 @@ package names import ( "bufio" + _ "embed" "math/rand/v2" "os" "strings" ) +//go:embed data/names +var embeddedNames string + +//go:embed data/surnames +var embeddedSurnames string + var ( firstNames []string lastNames []string @@ -24,6 +31,17 @@ var defaultLastNames = []string{ "Орлов", "Андреев", "Макаров", "Никитин", "Захаров", "Зайцев", "Соловьёв", "Борисов", "Яковлев", "Григорьев", } +func parseEmbedded(raw string) []string { + var names []string + for _, line := range strings.Split(raw, "\n") { + line = strings.TrimSpace(line) + if line != "" { + names = append(names, line) + } + } + return names +} + func loadNames(path string) ([]string, error) { file, err := os.Open(path) if err != nil { @@ -43,10 +61,21 @@ func loadNames(path string) ([]string, error) { return names, scanner.Err() } -func LoadNameFiles(firstPath, lastPath string) error { - firstNames = defaultFirstNames - lastNames = defaultLastNames +func init() { + if names := parseEmbedded(embeddedNames); len(names) > 0 { + firstNames = names + } else { + firstNames = defaultFirstNames + } + if names := parseEmbedded(embeddedSurnames); len(names) > 0 { + lastNames = names + } else { + lastNames = defaultLastNames + } +} + +func LoadNameFiles(firstPath, lastPath string) error { if names, err := loadNames(firstPath); err == nil { firstNames = names } diff --git a/internal/protect/protect.go b/internal/protect/protect.go new file mode 100644 index 0000000..cf29f91 --- /dev/null +++ b/internal/protect/protect.go @@ -0,0 +1,66 @@ +package protect + +import ( + "context" + "net" + "net/http" + "syscall" + "time" +) + +// Protector is called with a socket file descriptor before connect. +// On Android, this calls VpnService.protect(fd) to bypass VPN routing. +var Protector func(fd int) bool + +func controlFunc(network, address string, c syscall.RawConn) error { + if Protector == nil { + return nil + } + var err error + c.Control(func(fd uintptr) { + if !Protector(int(fd)) { + err = &net.OpError{Op: "protect", Net: network, Err: net.ErrClosed} + } + }) + return err +} + +// NewDialer returns a net.Dialer that calls Protector on each new socket. +func NewDialer() *net.Dialer { + return &net.Dialer{ + Timeout: 10 * time.Second, + KeepAlive: 30 * time.Second, + Control: controlFunc, + } +} + +// NewHTTPClient returns an http.Client using protected sockets. +func NewHTTPClient() *http.Client { + dialer := NewDialer() + transport := &http.Transport{ + DialContext: dialer.DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 10, + IdleConnTimeout: 30 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ResponseHeaderTimeout: 10 * time.Second, + } + return &http.Client{Transport: transport} +} + +// DialContext dials using a protected socket. +func DialContext(ctx context.Context, network, address string) (net.Conn, error) { + return NewDialer().DialContext(ctx, network, address) +} + +// proxyDialer implements golang.org/x/net/proxy.Dialer for pion ICE. +type proxyDialer struct{} + +func (d *proxyDialer) Dial(network, addr string) (net.Conn, error) { + return NewDialer().Dial(network, addr) +} + +// NewProxyDialer returns a proxy.Dialer that protects ICE sockets. +func NewProxyDialer() *proxyDialer { + return &proxyDialer{} +} diff --git a/internal/telemost/api.go b/internal/telemost/api.go index 3c02a74..91e0d18 100644 --- a/internal/telemost/api.go +++ b/internal/telemost/api.go @@ -8,6 +8,7 @@ import ( "net/url" "github.com/google/uuid" + "github.com/openlibrecommunity/olcrtc/internal/protect" ) const apiBase = "https://cloud-api.yandex.ru/telemost_front/v2/telemost" @@ -44,7 +45,8 @@ func GetConnectionInfo(roomURL, displayName string) (*ConnectionInfo, error) { req.Header.Set("Origin", "https://telemost.yandex.ru") req.Header.Set("Referer", "https://telemost.yandex.ru/") - resp, err := http.DefaultClient.Do(req) + client := protect.NewHTTPClient() + resp, err := client.Do(req) if err != nil { return nil, err } diff --git a/internal/telemost/peer.go b/internal/telemost/peer.go index bdac266..b6393dc 100644 --- a/internal/telemost/peer.go +++ b/internal/telemost/peer.go @@ -12,6 +12,7 @@ import ( "github.com/google/uuid" "github.com/gorilla/websocket" "github.com/openlibrecommunity/olcrtc/internal/logger" + "github.com/openlibrecommunity/olcrtc/internal/protect" "github.com/pion/webrtc/v4" ) @@ -75,6 +76,9 @@ func (p *Peer) Connect(ctx context.Context) error { } settingEngine := webrtc.SettingEngine{} + if protect.Protector != nil { + settingEngine.SetICEProxyDialer(protect.NewProxyDialer()) + } api := webrtc.NewAPI(webrtc.WithSettingEngine(settingEngine)) var err error @@ -169,7 +173,11 @@ func (p *Peer) Connect(ctx context.Context) error { }) }) - ws, _, err := websocket.DefaultDialer.Dial(p.conn.ClientConfig.MediaServerURL, nil) + wsDialer := websocket.Dialer{ + NetDialContext: protect.DialContext, + HandshakeTimeout: 15 * time.Second, + } + ws, _, err := wsDialer.Dial(p.conn.ClientConfig.MediaServerURL, nil) if err != nil { return err } @@ -723,31 +731,37 @@ func (p *Peer) processSendQueue(workerID int) { if p.dc == nil || p.dc.ReadyState() != webrtc.DataChannelStateOpen { continue } - - start := time.Now() - + + // Wait until SCTP buffer drains. Dropping here would corrupt the + // carried TCP streams (the mux is a reliable transport) — large + // downloads like Instagram/Twitter assets would hang forever + // waiting for the missing bytes. Backpressure already propagates + // upstream via CanSend() / the sendQueue length. + waitStart := time.Now() for p.dc.BufferedAmount() > 64*1024 { - time.Sleep(10 * time.Millisecond) - if time.Since(start) > 3*time.Second { - log.Printf("[WORKER-%d] Buffer wait timeout, dropping packet size=%d", workerID, len(data)) + if p.dc.ReadyState() != webrtc.DataChannelStateOpen { break } + time.Sleep(10 * time.Millisecond) } - - if time.Since(start) > 3*time.Second { + if waited := time.Since(waitStart); waited > 500*time.Millisecond { + logger.Verbose("[WORKER-%d] Buffer drained after %v", workerID, waited) + } + + if p.dc == nil || p.dc.ReadyState() != webrtc.DataChannelStateOpen { continue } - + sendStart := time.Now() if err := p.dc.Send(data); err != nil { log.Printf("[WORKER-%d] Send error: %v", workerID, err) } else { elapsed := time.Since(sendStart) if elapsed > 50*time.Millisecond { - log.Printf("[WORKER-%d] Sent %d bytes in %v (buffered: %d)", + log.Printf("[WORKER-%d] Sent %d bytes in %v (buffered: %d)", workerID, len(data), elapsed, p.dc.BufferedAmount()) } else { - logger.Verbose("[WORKER-%d] Sent %d bytes (buffered: %d)", + logger.Verbose("[WORKER-%d] Sent %d bytes (buffered: %d)", workerID, len(data), p.dc.BufferedAmount()) } } diff --git a/mobile/mobile.go b/mobile/mobile.go new file mode 100644 index 0000000..c20c15f --- /dev/null +++ b/mobile/mobile.go @@ -0,0 +1,133 @@ +// Package mobile provides a gomobile-compatible API for olcRTC. +// Build with: gomobile bind -target=android ./mobile +package mobile + +import ( + "context" + "fmt" + "log" + "sync" + + "github.com/openlibrecommunity/olcrtc/internal/client" + "github.com/openlibrecommunity/olcrtc/internal/logger" + "github.com/openlibrecommunity/olcrtc/internal/protect" +) + +// SocketProtector protects sockets from VPN routing on Android. +// Implement this interface in Kotlin/Java and pass to SetProtector. +type SocketProtector interface { + Protect(fd int) bool +} + +// LogWriter receives log messages from olcRTC. +type LogWriter interface { + WriteLog(msg string) +} + +var ( + mu sync.Mutex + cancel context.CancelFunc + done chan error +) + +// SetProtector sets the Android VPN socket protector. +// Must be called before Start. +func SetProtector(p SocketProtector) { + if p == nil { + protect.Protector = nil + return + } + protect.Protector = func(fd int) bool { + return p.Protect(fd) + } +} + +// SetLogWriter sets a custom log writer for olcRTC output. +func SetLogWriter(w LogWriter) { + if w != nil { + log.SetOutput(&logBridge{w: w}) + } +} + +// SetDebug enables or disables verbose logging. +func SetDebug(enabled bool) { + logger.SetVerbose(enabled) + if enabled { + log.SetFlags(log.Ltime | log.Lshortfile) + } else { + log.SetFlags(log.Ltime) + } +} + +// Start launches the olcRTC client in background. +// roomID: Telemost room ID (e.g. "xxx-xxx-xxx") +// keyHex: 64-char hex encryption key +// socksPort: local SOCKS5 proxy port (e.g. 10808) +// duo: use dual channels for higher throughput +// socksUser/socksPass: SOCKS5 credentials (empty = no auth) +func Start(roomID, keyHex string, socksPort int, duo bool, socksUser, socksPass string) error { + mu.Lock() + defer mu.Unlock() + + if cancel != nil { + return fmt.Errorf("olcRTC already running") + } + + if roomID == "" { + return fmt.Errorf("roomID is required") + } + if keyHex == "" { + return fmt.Errorf("keyHex is required") + } + + roomURL := "https://telemost.yandex.ru/j/" + roomID + + ctx, c := context.WithCancel(context.Background()) + cancel = c + done = make(chan error, 1) + + go func() { + err := client.Run(ctx, roomURL, keyHex, socksPort, duo, socksUser, socksPass) + mu.Lock() + cancel = nil + mu.Unlock() + done <- err + }() + + return nil +} + +// Stop gracefully stops the olcRTC client. +func Stop() { + mu.Lock() + c := cancel + d := done + mu.Unlock() + + if c == nil { + return + } + + c() + + if d != nil { + <-d + } +} + +// IsRunning returns true if the olcRTC client is active. +func IsRunning() bool { + mu.Lock() + defer mu.Unlock() + return cancel != nil +} + +// logBridge adapts LogWriter to io.Writer for log package. +type logBridge struct { + w LogWriter +} + +func (b *logBridge) Write(p []byte) (n int, err error) { + b.w.WriteLog(string(p)) + return len(p), nil +}