惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
L
LangChain Blog
Blog — PlanetScale
Blog — PlanetScale
A
About on SuperTechFans
Y
Y Combinator Blog
MyScale Blog
MyScale Blog
M
MIT News - Artificial intelligence
V
Visual Studio Blog
人人都是产品经理
人人都是产品经理
T
Threat Research - Cisco Blogs
L
Lohrmann on Cybersecurity
Application and Cybersecurity Blog
Application and Cybersecurity Blog
NISL@THU
NISL@THU
aimingoo的专栏
aimingoo的专栏
T
Tor Project blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Cisco Talos Blog
Cisco Talos Blog
A
Arctic Wolf
T
Troy Hunt's Blog
U
Unit 42
Forbes - Security
Forbes - Security
J
Java Code Geeks
P
Privacy International News Feed
W
WeLiveSecurity
T
The Exploit Database - CXSecurity.com
S
Schneier on Security
H
Heimdal Security Blog
量子位
Martin Fowler
Martin Fowler
G
Google Developers Blog
WordPress大学
WordPress大学
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
D
Docker
罗磊的独立博客
Security Archives - TechRepublic
Security Archives - TechRepublic
Engineering at Meta
Engineering at Meta
云风的 BLOG
云风的 BLOG
雷峰网
雷峰网
Simon Willison's Weblog
Simon Willison's Weblog
N
News and Events Feed by Topic
D
DataBreaches.Net
V2EX - 技术
V2EX - 技术
AWS News Blog
AWS News Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Attack and Defense Labs
Attack and Defense Labs
S
SegmentFault 最新的问题
MongoDB | Blog
MongoDB | Blog
S
Secure Thoughts
Apple Machine Learning Research
Apple Machine Learning Research

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Эксперименты с WAP в 2026 году
iliasam · 2026-05-28 · via Все публикации подряд на Хабре

Средний

15 мин

16K

Эта статья - продолжение моей предыдущей статьи: Создаем собственную базовую станцию при помощи SDR. В прошлый раз я экспериментировал с 2G GPRS, но на старых телефонах эта технология не поддерживается, а мне хотелось запустить WAP именно на таком. О том, как вернуться в эпоху до распространения GPRS - читайте далее.

Ведение

Краткое содержание предыдущей статьи:
Используя относительно дешевый SDR трансивер OpenSourceSDRLab PlutoSky 7020 и набор программ Osmocom, я смог запустить собственную 2G базовую станцию. С ее помощью можно было совершать звонки между телефонами, обмениваться SMS, и пользоваться на телефонах GPRS интернетом.

Тем не менее, не все старые телефоны поддерживают GPRS. До появления GPRS существовала технология CSD (Circuit Switched Data). Передача данных идет на скорости максимум 9,6 кбит/с и очень похожа на передачу голоса (по сути, вместо голосового трафика идут данные). Соединение устанавливается через звонок на другой телефон с модемом — очень похоже на dial‑up (но в CSD данные сразу идут в цифре).
Технология настолько древняя, что многие операторы сейчас ее отключают, или не предоставляют физическим лицам. Поэтому, если хочется поэкспериментировать с интернетом на очень старом мобильном телефоне, без собственной базовой станции (БС) не обойтись.

Все эксперименты с БС в предыдущей статье я делал, рассчитывая запустить CSD и посмотреть, как выглядит мобильный интернет на древнем телефоне Siemens C35i, но в тот раз мне это не удалось.
Однако позже, благодаря помощи @axilirator мне все-таки удалось запустить CSD.

CSD

Как вообще устанавливается тестовое CSD соединение?

Нужно иметь два телефона, поддерживающих CSD, и каким-то доступным образом подключить их к компьютеру. Я для этого использовал переходник USB-UART (у моего Siemens C35i на разъем выведен именно UART), и я подключал только RX/TX/GND. Подходящий кабель сейчас найти проблематично, так что я использовал ненужный FPC-шлейф с шагом 1 мм. Решение ужасно ненадежное, но все же рабочее:

Интересно, что SIM-карта на фото была куплена вместе с этим телефоном (в 2002 году), и все еще работает с моей БС. Родной аккумулятор телефона давно испортился, однако, как оказалось, туда можно установить дешевый аккумулятор BL-5C.

После того, как оба телефона подключены к ПК, нужно подключится к ним, используя какой-нибудь терминал последовательного порта. Siemens C35i, насколько я понял, работает только на скорости 19200 бит/с.
Простейшая проверка работы модема, встроенного в телефон - отправить "AT", модем должен ответить "OK".
Далее нужно отправить "AT+CBST=71,0,1" на каждый их модемов. 71 - это скорость передачи данных 9600 бит/с протокол V.110, 1 - режим передачи данных Non-Transparent (про это далее). Модемы должны ответить "OK".
Далее нужно отправить команду "ATDnnnnnnnn", где nnnnnnnn - номер второго телефона. Телефон должен запустить CSD-вызов на второй телефон (для голосового вызова нужно, чтобы команда выглядела так: "ATDnnnnnnnn;").
Второй телефон должен увидеть входящий звонок, выдать в терминал "RING", после чего можно ввести в этот терминал "ATA" - подъем трубки.
При нормально работающей передаче данных в обоих терминалах должно появится сообщение "CONNECTED 9600", и модемы должны перейти в режим прозрачной передачи данных из терминала в терминал.

Правда, ранее, как я писал в предыдущей статье, я видел в логах Osmocom сообщения "We only support voice calls", и звонок даже не доходил до второго телефона. Как выяснилось позже, это было связано с тем, что программы Osmocom, взятые из репозитория Ubuntu 24.04, слишком старые.

Так как я проводил все эксперименты с GSM на отдельном ПК, я просто обновил на нем Ubuntu до 26.04. При этом обновились и программы Osmocom. На всякий случай я пересобрал библиотеки, нужные для работы трансивера и свой вариант OsmoTRX. После этого стек Osmocom нормально запустился, но главное - пропали сообщения "We only support voice calls", и звонок начал доходить до второго телефона.
Павда, как оказалось, CSD-соединение все равно не устанавливалось - через несколько секунд после ввода "ATA" звонок разрывался; по логам, полученным из Wireshark - с причиной "Cause = 16 Normal call clearing".

Вот тут стоит отметить определенную проблему, имеющуюся в Osmocom. В полноценной сотовой сети для поддержки CSD должен быть специальный программно-аппаратный блок IWF (Inter-working function). Его задача - сопряжение сети оператора с внешними сетями передачи данных (ISDN или даже обычные телефонные сети). Он выполняет следующие функции:

Преобразование протоколов: Адаптирует протоколы радиоинтерфейса GSM к стандартным протоколам фиксированных сетей (например, к ITU-T V.24, V.32, V.110).

Адаптация скорости передачи данных: Преобразует скорость передачи данных со стороны мобильного телефона (например, 9.6 или 14.4 кбит/с в CSD (CSD имел и более быстрые реализации) в стандартные фиксированные скорости каналов ISDN (обычно 64 кбит/с).

Модемный пул: Содержит цифровые сигнальные процессоры (DSP), эмулирующие работу аналоговых модемов, чтобы мобильный абонент мог дозвониться на обычный городской факс или dial-up модем.

Соответственно, CSD-данные между двумя телефонами идут не напрямую от одного телефона к другому, а через IWF.

И вот этого самого IWF в Osmocom нет. Трафик CSD внутри Osmocom передается через медиашлюз (osmo-mgw) по протоколу RTP с использованием кодека CLEARMODE (RFC 4040, payload type 120). В результате после установления связи (звонка) весь CSD трафик просто пробрасывается от одного телефона к другому. Вроде бы это нам и нужно?

Вот-тут то и появляются Transparent / Non-Transparent режимы передачи данных. Первый режим - более старый, данные просто перебрасываются от одного телефона к другому без каких-то модификаций. Второй - куда более сложный, для запуска передачи данных телефон должен договорится с IWF о поддерживаемых режимах работы, а далее протокол передачи данных будет поддерживать коррекцию ошибок. Этот протокол передачи данных называется RLP, и описывается стандартом GSM 04.22.
Выглядит это как-то так: MS <-- (RLP) --> IWF <-- (RLP) --> MS (MS - это телефон).
Вот и получается, что в Osmocom IWF нет, а телефоны об этом не знают, и обмениваются пакетами RLP не с IWF, а друг с другом: MS <-- (RLP) --> RTP-Clearmoode <-- (RLP) --> MS.
Судя по довольно малочисленным обсуждениям работы CSD в Osmocom, это как-то работает.
Тем не менее, различия в реализации протокола RLP у разных производителей телефонов могут приводить к проблемам в работе.
Мой телефон Siemens С35i поддерживает только Non-Transparent режим. Это можно проверить, выполнив AT-команду "AT+CBST=?", на которую телефон отвечает: +CBST: (0,4,6,7,68,70,71),(0),(1). Единственная "1" в конце это подтверждает.

Поначалу я пытался в качестве второго телефона использовать Sony Ericson k310i и модем Quectel M95. Оба не заработали, соединение рвалось. Попытки изменять разные режимы работы не помогали. После этого я решил попробовать купить еще один Б/У Siemens С35i - и с ним тоже не заработало, проблема осталась все та же.
По совету @axilirator включил в конфиге osmo-bts-trx настройку gsmtap-rlp skip-null. При ее включении osmo-bts-trx начинает выдавать GSMTAP пакеты, которые можно поймать в Wireshark.
Дополнительно, сам osmo-bts-trx нужно запускать с аргументом "-i 127.0.0.1" (или добавить в конфиг "gsmtap-remote-host 127.0.0.1").
Для просмотра RLP пакетов в Wireshark нужно включить фильтр "gsm_rlp". Просмотр пакетов только с верной CRC (а у меня было много и плохих): gsm_rlp.fcs.status == "Good".

Анализ RLP пакетов в Wireshark показал, что телефоны пытаются обмениваться пакетами типа XID, видно, как БС принимает пакеты от одного телефона и пересылает их на другой, но дальше дело не продвигается, а после нескольких попыток соединение разрывается:

Есть только XID пакеты, других нет.

Есть только XID пакеты, других нет.

В XID пакетах, отправляемых телефоном, содержатся настройки, задаваемые командой "AT+CRLP". IWF анализирует их и предлагает свой вариант.
Рассматривая данные в Wireshark я заметил, что все пакеты абсолютно одинаковы. При этом внутри пакета есть бит C/R (COMMAND/RESPONSE BIT), документация описывает его так:

The C/R-bit is used to indicate whether the frame is a command or response frame and whether the P/F‑bit is to be interpreted as a poll or final bit, resp. For commands, the C/R bit shall be set to "1", for responses it shall be set to "0".

Я предположил, что именно отсутствие передачи "0" от БС на телефон мешает протоколу продвинуться дальше. Самым простым решением этой проблемы я посчитал модификацию OsmoBTS. Для этого требуется скачать ее, и собрать из исходников:

sudo apt-get install libosmo-abis-dev
sudo apt-get install libosmo-netif-dev

#Я использовал версию 1.9.0, совпадающую с той, что у меня установлена в Ubuntu 26.04
git clone --branch 1.9.0 https://github.com/osmocom/osmo-bts

cd  osmo-bts
autoreconf -fi
./configure --enable-trx
make

Я модифицировал файл sched_lchan_tchf.c и конкретно - функцию tx_tchf_fn() в нем. Эта функция отвечает за отправку данных к трансиверу (Downlink).
В ней есть такой участок:

case GSM48_CMODE_DATA_12k0:
		if (msg_tch == NULL)
			msg_tch = tch_dummy_msgb(4 * 60, 0x01);
		gsm0503_tch_fr96_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_tch));
		if (msg_facch != NULL)
			gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_facch));
		break;

Вот его-то и нужно переделать:

Модифицированный код
// You will need to add this line at the beginning of the file
// #include <osmocom/gsm/rlp.h>

/* CSD (TCH/F9.6): 12.0 kbit/s radio interface rate */
	case GSM48_CMODE_DATA_12k0:
		if (msg_tch == NULL)
			msg_tch = tch_dummy_msgb(4 * 60, 0x01);

        // Pointer to the data to be modified
        // Elements have values 0/1
		uint8_t *csd_data = msgb_l2(msg_tch);
		int csd_len = msgb_l2len(msg_tch);

		uint8_t rlp_buf[30];
		uint16_t byte_len = 0;
		if (csd_len == 240)
		{
            // Pack 240 bytes -> 30 bytes
			byte_len = osmo_ubit2pbit_ext(rlp_buf, 0, csd_data, 0, csd_len, 1);
			if (byte_len != 30)
				break;

            // Fill "rlpf" structure
			struct osmo_rlp_frame_decoded rlpf;
			int rc = osmo_rlp_decode(&rlpf, 0, rlp_buf, byte_len);

            // Calculate CRC of the data, last 3 bytes are received CRC
			uint32_t fcs = osmo_rlp_fcs_compute(rlp_buf, byte_len - 3);

			if ((rc == 0) && (rlpf.fcs == fcs))//check CRC
			{
				bool need_fix = false;
				if (rlpf.u_ftype == OSMO_RLP_U_FT_XID)
				{
					LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, "XID FOUND!\n");
					need_fix = true;
				}
				else if (rlpf.u_ftype == OSMO_RLP_U_FT_SABM)
					LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, "SABM FOUND!\n");
				else if (rlpf.u_ftype == OSMO_RLP_U_FT_UA)
					LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, "UA FOUND!\n");

				if (need_fix)
				{
					rlpf.c_r = false; // Set C/R to 0

					uint8_t changed_rlp[30];
                    // Fill bytes buffer and recalculate CRC
					osmo_rlp_encode(changed_rlp, 30, &rlpf);
                    
                    // Convert from 30 to 340 bytes
					osmo_pbit2ubit_ext((ubit_t *)csd_data, 0,
									   changed_rlp, 0,
									   csd_len, 1);
				}
			}
		}

		gsm0503_tch_fr96_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_tch));
		if (msg_facch != NULL)
			gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_facch));
		break;

Ради изменения одного бита приходится делать много операций. Важно, что данные находятся в особом виде, каждый бит передается отдельным байтом. Поэтому данные нужно преобразовать в набор классической последовательности байтов (из 240 байт - в 30), из полученного массива заполнить специальную структуру rlp-данных, с которой потом удобно работать. Именно в ней нужно поменять бит конкретно для XID-пакетов, а потом выполнить аналогичные операции кодирования в обратном порядке.

После сборки и запуска программы ./osmo-bts-trx -c osmo-bts-trx.cfg связь внезапно заработала. В обоих терминалах появилось "CONNECT 9600/RLP", после чего появилась возможность передавать данные между терминалами!

После модификации исходников можно установить модифицированный вариант osmo-bts-trx в систему, так ее проще запускать:

make
#Выполнить тесты
make check
sudo make install
sudo ldconfig

Честно скажу - не всегда соединение устанавливалось устойчиво, иногда оно самопроизвольно разрывалось через несколько десятков секунд после соединения. Причину этой проблемы я так и не нашел.
После этой доработки я еще раз попробовал проверить работу Siemens c Sony Ericson k310i и Quectel M95, но они так и не заработали.
Дополнительно отмечу, что для работы CSD хватает тех же настроек Osmocom, что у меня были сделаны для передачи голоса.
В принципе, получившуюся систему можно использовать, чтобы выходить в Интернет, используя старый ПК, но я не стал с этим экспериментировать.

Любопытная деталь - в Wireshark видно, что во многих RLP пакетах в их тексте проскакивает фраза "schobi macht das schon!!!". Иногда она передается в искаженном виде, хотя CRC пакетов нормальная. Складывается ощущение, что фраза служит для изначально заполнения пустых пакетов, а полезные данные пишутся поверх нее. Фразу формирует телефон - я нашел ее в самой прошивке телефона.

WAP

WAP - это целый набор довольно старых протоколов, появившихся в конце 90-х, предназначенных для предоставления мобильным устройствам прямого доступа к услугам интернета. Пользователи WAP получали доступ не к "полноценному" WEB - можно было открывать только специально созданные WAP-страницы, написанные на языке WML (по сути, это подвид XML). При этом, для сокращения объема передаваемых данных и уменьшения вычислительной нагрузки на телефон, все передача данных шла через специальные WAP-шлюзы, которые превращали текстовые WML-страницы в сжатые бинарные данные.
Со стороны пользователя (телефона) для доступа к ресурсам WAP нужен был специальный браузер. В моем Siemens C35i он есть, и я еще помню, как он работал в 2002 году.

О том, как именно настроить телефон и компьютер для организации доступа в WAP, очень подробно описано тут: https://bs0dd.net/index.php?lang=rus&page=news/main&npage=7 . Автор до сих пор поддерживает свой WAP-шлюз (091.122.205.250. Порт: 9201) и свой WML-сайт: wap.bs0dd.net
Спасибо автору сайта bs0dd за проделанную работу!

Со стороны телефона нужно указать:
- Номер набора (номер второго телефона на БС)
- Тип соединения - ISDN
- Имя пользователя и пароль (я указывал user2/user2)
- IP-адрес и порт - (я указал как раз 091.122.205.250 / 9201)
- Домашнюю страницу (к примеру, wap.bs0dd.net)

В брошюре оператора настройки описывались так:

Брошюра из 2002

Брошюра из 2002

После применения настроек при попытке зайти на домашнюю страницу второй телефон должен зазвонить.

Далее нужно настроить ПК, к которому будет подключен второй телефон. Так как Osmocom у меня работает на Linux, я использовал инструкции для него: Настройка Dial-in сервиса: Linux. Настроить нужно будет mgetty и pppd. mgetty инициализирует модем и принимает от него входящий звонок, pppd обеспечивает авторизацию подключающихся клиентов и их доступ в сеть.

Я практически во всем следовал инструкции, хотя некоторые настройки пришлось поменять.
Программа mgetty рассчитана на то, что у модема будет возможность аппаратно управлять потоком передачи данных - через специальные линии COM-порта. Однако у меня их нет. Для того, чтобы mgetty работала хоть как-то, мне пришлось закоротить на плате USB-UART адаптера линии RTS+CTS и DTR+DSR. Это явно кривое решение, mgetty часто не обнаруживает, что звонок завершен, и подвисает. Да и отсутствие полноценного аппаратного управления потоком явно может приводить к проблемам с передачей данных.

Настройки /etc/mgetty/mgetty.config у меня выглядят так:

debug 9
port ttyUSB2
port-owner root
port-group dialout
port-mode 0660
direct no
data-only yes
ignore-carrier yes
toggle-dtr no
speed 19200
modem-check-time 60

В конце /etc/ppp/options я не добавлял IP сервера, как это указано в инструкции.
Вместо этого я сделал файл /etc/ppp/options.ttyUSB2 таким:

#Первый IP - адрес сервера, второй - адрес телефона
10.0.0.1:10.0.0.2
netmask 255.255.255.255
noproxyarp
local

Также, как и описано в инструкции, я добавил в систему пользователя user2 с паролем user2.
Для организации маршрутизации в Интернет использовал эти команды:

# Разрешаем ядру Linux пересылать пакеты между интерфейсами
sudo sysctl -w net.ipv4.ip_forward=1

# wlo1 - имя интерфейса сетевой карты
sudo iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o wlo1 -j MASQUERADE

Контролировать работу mgetty и pppd можно в отдельных терминалах:

tail -f /var/log/mgetty/mg_tty*.log

sudo tail -f /var/log/syslog | grep pppd

# Перезапуск mgetty:
sudo systemctl restart mgetty@ttyUSB2

После того, как mgetty и pppd настроены, можно попробовать зайти на домашнюю страницу. Первый телефон должен запустить CSD-звонок, второй телефон - принять его (благодаря mgetty). После этого в логах pppd должны пойти сообщения о ходе подключения, у меня это выглядело так:

Логи pppd
pppd 2.5.2 started by root, uid 0
using channel 9
Using interface ppp0
Connect: ppp0 <--> /dev/ttyUSB2
sent [LCP ConfReq id=0x1 <asyncmap 0x0> <auth pap> <magic 0xa19fdfa3> <pcomp> <accomp>]
rcvd [LCP ConfReq id=0x13 <mru 2000> <asyncmap 0xa0000> <pcomp> <accomp> <magic 0x93ed0794>]
sent [LCP ConfAck id=0x13 <mru 2000> <asyncmap 0xa0000> <pcomp> <accomp> <magic 0x93ed0794>]
sent [LCP ConfReq id=0x1 <asyncmap 0x0> <auth pap> <magic 0xa19fdfa3> <pcomp> <accomp>]
rcvd [LCP ConfNak id=0x1 <asyncmap 0xa0000>]
sent [LCP ConfReq id=0x2 <asyncmap 0xa0000> <auth pap> <magic 0xa19fdfa3> <pcomp> <accomp>]
rcvd [LCP ConfNak id=0x1 <asyncmap 0xa0000>]
rcvd [LCP ConfAck id=0x2 <asyncmap 0xa0000> <auth pap> <magic 0xa19fdfa3> <pcomp> <accomp>]
sent [LCP EchoReq id=0x0 magic=0xa19fdfa3]
rcvd [PAP AuthReq id=0x1 user="user2" password=<hidden>]
Initializing PAM (3) for user user2
---> PAM INIT Result = 0
Attempting PAM authentication
PAM Authentication OK for user2
Attempting PAM account checks
PAM Account OK for user2
PAM Session opened for user user2
user user2 logged in on tty ttyUSB2 intf ppp0
PAP peer authentication succeeded for user2
Peer user2 authenticated with PAP
local  IP address 10.0.0.1
remote IP address 10.0.0.2

Как видно, телефон смог подключиться к pppd серверу, и далее браузер телефона работает уже через шлюз WAP. В Wireshark это можно контролировать, используя фильтр "ip.addr == 91.122.205.250".
Если все настроено верно, должен отобразится сайт wap.bs0dd.net:

Как видно, на экран влезает всего 3 строки текста, это ужасно мало.

Как видно, на экран влезает всего 3 строки текста, это ужасно мало.

В нынешнее время осталось совсем уж мало сайтов, написанных на WML.
Вот тут есть подборка таких сайтов: https://github.com/KedalionDaimon/list-of-wap-wml-sites
Приходилось встречать информацию, что FrogFind! умел конвертировать данные в WML, но сейчас он не работает, а frogfind.de на телефоне не открылся.
Зато проект W@PFind! - find.bevelgacom.be у меня на телефоне открылся. Этот проект, насколько я понял, основан на FrogFind, но специализируется на формировании именно WML. Как и FrogFind, он обеспечивает поиск в сети (при помощи DuckDuckGo) заданного текста, а потом конвертирует найденные сайты в WML:

weather.com и cnn.com

weather.com и cnn.com

К сожалению, в большинстве случаев конвертация происходит криво, и сайты просто не открываются.

Я решил попробовать создать свой собственный WAP-сайт, используя Python. Так как у меня нет сервера с "белым" IP-адресом, использовать чужой внешний WAP-шлюз для этих целей уже не выйдет.
Так что я установил WAP-шлюз Kannel, как это описано тут: Настройка WAP-шлюза Kannel: Linux.
В его настройках я вообще ничего не менял. В настройках телефона нужно поменять IP-адрес шлюза на 10.0.0.1.

После этого я создал простейший сайт на Python, который при запросе WAP-страницы скачивает из сети RSS-страницу, и конвертирует ее содержимое в WML. Так как я не знаю толком Python, использовал для этого нейронку. Код вышел таким:

RSS Server
from flask import Flask, Response, request
import feedparser
import html

app = Flask(__name__)

RSS_URL = "https://www.space.com/feeds.xml"

# Функция для безопасной очистки текста под строгий XML-формат WML
def clean_text(text):
    if not text:
        return ""
    # Убираем HTML теги, если они пролезли из RSS
    clean = html.unescape(text)
    # Экранируем спецсимволы XML
    return clean.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")

@app.route("/")
def index():
    feed = feedparser.parse(RSS_URL)
    
    wml = '<?xml version="1.0" encoding="UTF-8"?>\n'
    wml += '<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://wapforum.org">\n'
    wml += '<wml>\n'
    wml += '  <card id="list" title="WAP News">\n'
    wml += f'    <p><b>{clean_text(feed.feed.title)}</b></p>\n'
    
    # Передаем индекс новости в параметре URL (например, /story?id=3)
    for index, entry in enumerate(feed.entries[:10]):
        title = clean_text(entry.title)
        
        wml += '    <p>\n'
        # Используем WML-ссылку. Для совместимости со старыми браузерами пишем через anchor
        wml += f'      <anchor>{title}<go href="/story?id={index}"/></anchor>\n'
        wml += '    </p>\n'
        
    wml += '  </card>\n'
    wml += '</wml>'
    
    return Response(wml, mimetype='text/vnd.wap.wml')

@app.route("/story")
def story():
    # Получаем ID новости из запроса телефона
    story_id = int(request.args.get('id', 0))
    feed = feedparser.parse(RSS_URL)
    
    # Проверяем, существует ли новость с таким индексом
    if story_id >= len(feed.entries):
        return Response("Story not found", status=404)
        
    entry = feed.entries[story_id]
    title = clean_text(entry.title)
    
    # Берем описание (summary или description). Если его нет — пишем заглушку.
    description = clean_text(getattr(entry, 'summary', 'No description'))
    
    wml = '<?xml version="1.0" encoding="UTF-8"?>\n'
    wml += '<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://wapforum.org">\n'
    wml += '<wml>\n'
    wml += f'  <card id="item" title="{title[:12]}...">\n' # Ограничиваем длину заголовка в шапке
    wml += f'    <p><b>{title}</b></p>\n'
    wml += f'    <p>{description}</p>\n'
    # Кнопка возврата на главную страницу новостей
    wml += '    <p><anchor>BACK<go href="/"/></anchor></p>\n'
    wml += '  </card>\n'
    wml += '</wml>'
    
    return Response(wml, mimetype='text/vnd.wap.wml')

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)

Как видно из кода, сервер работает на порту 8080, так что в я добавил в избранное на телефоне адрес 10.0.0.1:8080. Сайт вполне себе заработал, хотя и отображается на нем всего 10 новостей, зато актуальные.
Работу сайта удобно проверять в Linux, используя WAP-браузер wApua - он есть в репозитории Ubuntu:

Мой сайт в wApua

Мой сайт в wApua

Так это выглядит на телефоне:

Изображение в центре - одна из новостей, справа - она открыта целиком.

Изображение в центре - одна из новостей, справа - она открыта целиком.

Если использовать RSS на русском, то страницы вообще не открывались, так что я переделал сервер:

RSS Server Ru
from flask import Flask, Response, request
import feedparser
import html

app = Flask(__name__)

#RSS_URL = "https://www.space.com/feeds.xml"
RSS_URL = "https://elementy.ru/rss/news/cosmos"


MAX_TITLE_LEN = 60        # Длина заголовка в списке
MAX_DESC_LEN = 400        # Максимальная длина текста новости

def clean_text(text):
    if not text:
        return ""
    # 1. Убираем HTML-теги и декодируем сущности
    clean = html.unescape(text)
    
    # 2. Заменяем спецсимволы, которые ломают парсер Сименса
    clean = clean.replace("«", '"').replace("»", '"')
    clean = clean.replace("—", "-").replace("–", "-")
    clean = clean.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")
    
    # 3. Превращаем кириллицу в числовые XML-коды (NCR)
    ncr_text = ""
    for char in clean:
        ord_char = ord(char)
        if 1024 <= ord_char <= 1105:
            ncr_text += f"&#{ord_char};"
        else:
            ncr_text += char
    return ncr_text

@app.route("/")
def index():
    feed = feedparser.parse(RSS_URL)
    
    wml = '<?xml version="1.0" encoding="utf-8"?>\n'
    wml += '<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://wapforum.org">\n'
    wml += '<wml>\n'
    wml += '  <card id="list" title="Space News">\n'
    
    # Строго 3 новости для пролезания в лимит памяти 1.4 КБ
    for index, entry in enumerate(feed.entries[:10]):
        raw_title = entry.title
        if len(raw_title) > MAX_TITLE_LEN:
            raw_title = raw_title[:MAX_TITLE_LEN] + "..."
            
        title = clean_text(raw_title)
        
        wml += '    <p>\n'
        wml += f'      <anchor>{title}<go href="/story?id={index}"/></anchor>\n'
        wml += '    </p>\n'
        
    wml += '  </card>\n'
    wml += '</wml>'
    
    return Response(wml, mimetype='text/vnd.wap.wml; charset=utf-8')

@app.route("/story")
def story():
    story_id = int(request.args.get('id', 0))
    feed = feedparser.parse(RSS_URL)
    
    if story_id >= len(feed.entries):
        return Response("Not found", status=404)
        
    entry = feed.entries[story_id]
    
    # Обработка заголовка статьи
    raw_title = entry.title
    if len(raw_title) > MAX_TITLE_LEN:
        raw_title = raw_title[:MAX_TITLE_LEN] + "..."
    title = clean_text(raw_title)
    
    # Безопасное получение и жесткая обрезка текста новости
    raw_desc = getattr(entry, 'summary', 'No description.')
    if len(raw_desc) > MAX_DESC_LEN:
        raw_desc = raw_desc[:MAX_DESC_LEN] + "..."
    description = clean_text(raw_desc)
    
    # Сборка WML страницы новости
    wml = '<?xml version="1.0" encoding="utf-8"?>\n'
    wml += '<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://wapforum.org">\n'
    wml += '<wml>\n'
    wml += '  <card id="item" title="News">\n'
    wml += f'    <p><b>{title}</b></p>\n'
    wml += f'    <p>{description}</p>\n'
    wml += '    <p><anchor>BACK<go href="/"/></anchor></p>\n'
    wml += '  </card>\n'
    wml += '</wml>'
    
    return Response(wml, mimetype='text/vnd.wap.wml; charset=utf-8')

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)

Вот в таком варианте и сайты на русском стали открываться:

Как видно, мобильный телефон, которому уже скоро исполнится 25 лет, все еще может быть использован для отображения новостей)

Окончательно структурная схема получившейся системы выглядит так:

Структурная схема получившейся системы, не все программы Osmocom показаны

Структурная схема получившейся системы, не все программы Osmocom показаны