diff --git a/Makefile b/Makefile index f61e331..d7bbd9a 100644 --- a/Makefile +++ b/Makefile @@ -1,26 +1,33 @@ -PJSIP_DIR=pjproject-2.11.1 +PJSIP_DIR=pjproject PKG_CONFIG_PATH=pjsip.install/lib/pkgconfig -all: d-modem slmodemd +ifndef NO_PULSE + DM_LDFLAGS += -l pulse -l pulse-simple + DM_CFLAGS += -DHAS_PULSE +endif + +all: d-modem slmodemd $(if $(NO_PULSE),,d-modem.nopulse) $(PKG_CONFIG_PATH)/libpjproject.pc: - (cd $(PJSIP_DIR); [ -f ./config.status ] || ./configure --prefix=`pwd`/../pjsip.install --disable-video) + (cd $(PJSIP_DIR); [ -f ./config.status ] || ./configure --prefix=`pwd`/../pjsip.install --disable-video --disable-sound --enable-ext-sound --disable-speex-aec --enable-g711-codec --disable-l16-codec --disable-gsm-codec --disable-g722-codec --disable-g7221-codec --disable-speex-codec --disable-ilbc-codec --disable-sdl --disable-ffmpeg --disable-v4l2 --disable-openh264 --disable-vpx --disable-android-mediacodec --disable-darwin-ssl --disable-ssl --disable-opencore-amr --disable-silk --disable-opus --disable-bcg729 --disable-libyuv --disable-libwebrtc) $(MAKE) -C $(PJSIP_DIR) && \ $(MAKE) -C $(PJSIP_DIR) install -d-modem: d-modem.c $(PKG_CONFIG_PATH)/libpjproject.pc - $(CC) -o $@ $< `PKG_CONFIG_PATH="$(PKG_CONFIG_PATH)" pkg-config --static --cflags --libs libpjproject` +d-modem d-modem.nopulse : d-modem.c $(PKG_CONFIG_PATH)/libpjproject.pc + $(CC) $(if $(findstring nopulse,$@),,$(DM_CFLAGS) $(DM_LDFLAGS)) -o $@ $< \ + `PKG_CONFIG_PATH="$(PKG_CONFIG_PATH)" pkg-config --static --cflags --libs libpjproject` \ + -O2 -s slmodemd: $(MAKE) -C slmodemd clean: - rm -f d-modem.o d-modem + rm -f d-modem.o d-modem d-modem.nopulse $(MAKE) -C slmodemd clean realclean: clean $(MAKE) -C $(PJSIP_DIR) realclean rm -rf pjsip.install/* - + .PHONY: all clean realclean slmodemd diff --git a/d-modem.c b/d-modem.c index 79bc028..604c017 100644 --- a/d-modem.c +++ b/d-modem.c @@ -16,6 +16,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include #include #include #include @@ -24,14 +25,38 @@ #include // test #include +#include + +#ifdef HAS_PULSE +#include +#endif + +#define PCM_FILE "dmodem.%s.s16le.9600hz.pcm" +#undef PCM_FILE +//#define HAS_PULSE + +#ifdef HAS_PULSE +pa_simple *pa_s1 = NULL; +pa_simple *pa_s2 = NULL; +const pa_sample_spec pa_ss = { + .format = PA_SAMPLE_S16LE, + .rate = 9600, + .channels = 1 +}; +#endif +#ifdef PCM_FILE +int wave_recv = -1; +int wave_transmit = -1; +#endif #define SIGNATURE PJMEDIA_SIG_CLASS_PORT_AUD('D','M') #define DMODEM_DIAL_MODE 0 -#define DMODEM_ANSWER_MODE 2 -#define DMODEM_RING_DETECT_MODE 3 +#define DMODEM_ANSWER_MODE 1 uint8_t mode = DMODEM_DIAL_MODE; uint8_t ringing = 0; +uint8_t answered = 0; +int ppid; pjsua_call_id incoming; struct dmodem { @@ -44,20 +69,85 @@ static struct dmodem port; static bool destroying = false; static pj_pool_t *pool; +void stop_pa(); static void error_exit(const char *title, pj_status_t status) { pjsua_perror(__FILE__, title, status); if (!destroying) { destroying = true; pjsua_destroy(); + stop_pa(); + if (answered) + kill(ppid, SIGINT); exit(1); } } +void start_pa() { +#ifdef PCM_FILE + char _wavbuf[50]; + sprintf(_wavbuf, PCM_FILE, "recv"); + wave_recv = open(_wavbuf, O_WRONLY | O_CREAT, 00644); + sprintf(_wavbuf, PCM_FILE, "transmit"); + wave_recv = open(_wavbuf, O_WRONLY | O_CREAT, 00644); + if (wave_recv <= 0 || wave_transmit <= 0) + error_exit("open wave files", 1); +#endif +#ifdef HAS_PULSE + pa_s1 = pa_simple_new(NULL, // Use the default server. + "dmodem", // Our application's name. + PA_STREAM_PLAYBACK, + NULL, // Use the default device. + "recv", // Description of our stream. + &pa_ss, // Our sample format. + NULL, // Use default channel map + NULL, // Use default buffering attributes. + NULL // Ignore error code. + ); + pa_s2 = pa_simple_new(NULL, // Use the default server. + "dmodem", // Our application's name. + PA_STREAM_PLAYBACK, + NULL, // Use the default device. + "transmit", // Description of our stream. + &pa_ss, // Our sample format. + NULL, // Use default channel map + NULL, // Use default buffering attributes. + NULL // Ignore error code. + ); + if (!pa_s1 || !pa_s2) + error_exit("pulseaudio", 1); +#endif +} + +void stop_pa() { +#ifdef HAS_PULSE + if(pa_s1) + pa_simple_free(pa_s1); + if(pa_s2) + pa_simple_free(pa_s2); + pa_s1 = 0; + pa_s1 = 0; +#endif +#ifdef PCM_FILE + if (wave_recv >= 0) + close(wave_recv); + if (wave_transmit >= 0) + close(wave_transmit); + wave_recv = -1; + wave_transmit = -1; +#endif +} + static pj_status_t dmodem_put_frame(pjmedia_port *this_port, pjmedia_frame *frame) { struct dmodem *sm = (struct dmodem *)this_port; int len; if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) { +#ifdef HAS_PULSE + pa_simple_write(pa_s1, frame->buf, frame->size, NULL); +#endif +#ifdef PCM_FILE + write(wave_recv, frame->buf, frame->size); +#endif if ((len=write(sm->sock, frame->buf, frame->size)) != frame->size) { error_exit("error writing frame",0); } @@ -78,12 +168,20 @@ static pj_status_t dmodem_get_frame(pjmedia_port *this_port, pjmedia_frame *fram frame->timestamp.u64 = sm->timestamp.u64; frame->type = PJMEDIA_FRAME_TYPE_AUDIO; sm->timestamp.u64 += PJMEDIA_PIA_SPF(&this_port->info); +#ifdef HAS_PULSE + pa_simple_write(pa_s2, frame->buf, frame->size, NULL); +#endif +#ifdef PCM_FILE + write(wave_transmit, frame->buf, frame->size); +#endif return PJ_SUCCESS; } static pj_status_t dmodem_on_destroy(pjmedia_port *this_port) { printf("destroy\n"); + if (answered) + kill(ppid, SIGINT); exit(-1); } @@ -99,15 +197,15 @@ static void on_call_state(pjsua_call_id call_id, pjsip_event *e) { ci.state_text.ptr)); if (ci.state == PJSIP_INV_STATE_DISCONNECTED) { - if(mode != DMODEM_RING_DETECT_MODE) { - close(port.sock); - if (!destroying) { - destroying = true; - pjsua_destroy(); - exit(0); - } + close(port.sock); + if (!destroying) { + destroying = true; + pjsua_destroy(); + stop_pa(); + if (answered) + kill(ppid, SIGINT); + exit(0); } - ringing = 0; } } @@ -117,7 +215,12 @@ static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_r char* tmp = malloc(pj_strlen(&ci.remote_contact)+1); strcpy(tmp, ci.remote_contact.ptr); tmp[pj_strlen(&ci.remote_contact)] = '\0'; - printf("Incoming call from: %s\n", tmp); + if (answered) { + PJ_LOG(2,(__FILE__, "Incoming call rejected from: %s", tmp)); + pjsua_call_hangup(call_id, 0u, NULL, NULL); + return; + } + PJ_LOG(2,(__FILE__, "Incoming call from: %s", tmp)); free(tmp); incoming = call_id; ringing = 1; @@ -144,28 +247,54 @@ static void on_call_media_state(pjsua_call_id call_id) { } } - int main(int argc, char *argv[]) { pjsua_acc_id acc_id; pj_status_t status; - if (argc != 3) { + if (argc != 5) { return -1; } - ringing = 0; - if(!strncmp(argv[1], "++", 2)) { + ppid = atoi(argv[3]); + char* _modemid_tmp = argv[4]; + for (size_t i = strlen(_modemid_tmp) - 1 ; ; i--) { + if (i < 0 || _modemid_tmp[i] < '0' || _modemid_tmp[i] > '9') { + _modemid_tmp += i+1; + break; + } + } + int sip_port = atoi(_modemid_tmp); + sip_port = sip_port >= 0 ? sip_port : 0; + sip_port += 5060; + sip_port = sip_port <= 65535 ? sip_port : 65535; + if(!strncmp(argv[1], "rr", 2)) { mode = DMODEM_ANSWER_MODE; - } else if(!strncmp(argv[1], "rr", 2)) { - mode = DMODEM_RING_DETECT_MODE; } signal(SIGPIPE,SIG_IGN); char *dialstr = argv[1]; - char *sip_user = "dialupuser"; - char *sip_domain = "192.168.1.2"; - char *sip_pass = "pppasswdModem1"; - printf("sip data: user: %s, passwd: %s, server: %s\nMODE: %d\n", sip_user, sip_pass, sip_domain, mode); + int has_sip_user = 1; + const char *_sip_user = getenv("SIP_LOGIN"); + char *sip_user = NULL; + if (!_sip_user) { + has_sip_user = 0; + _sip_user = "placeholder:placeholder@placeholder"; + } + sip_user = malloc(strlen(_sip_user) + 1); + strcpy(sip_user, _sip_user); + char *sip_domain = strchr(sip_user,'@'); + if (!sip_domain) { + return -1; + } + *sip_domain++ = '\0'; + char *sip_pass = strchr(sip_user,':'); + if (!sip_pass) { + return -1; + } + *sip_pass++ = '\0'; + status = pjsua_create(); if (status != PJ_SUCCESS) error_exit("Error in pjsua_create()", status); + if (!has_sip_user) + PJ_LOG(2,(__FILE__, "SIP_LOGIN is empty, no registration will be attempted")); /* Init pjsua */ { @@ -177,19 +306,23 @@ int main(int argc, char *argv[]) { cfg.cb.on_call_media_state = &on_call_media_state; cfg.cb.on_call_state = &on_call_state; - if(mode == DMODEM_RING_DETECT_MODE) { + if(mode == DMODEM_ANSWER_MODE) { cfg.cb.on_incoming_call = &on_incoming_call; } pjsua_logging_config_default(&log_cfg); - log_cfg.console_level = 4; +// log_cfg.console_level = 4; + log_cfg.console_level = 2; pjsua_media_config_default(&med_cfg); med_cfg.no_vad = true; med_cfg.ec_tail_len = 0; - med_cfg.jb_max = 2000; -// med_cfg.jb_init = 200; - med_cfg.audio_frame_ptime = 5; + med_cfg.jb_max = -1; + med_cfg.jb_init = -1; + med_cfg.audio_frame_ptime = 10; + med_cfg.quality = 10; + med_cfg.enable_ice = PJ_FALSE; + med_cfg.enable_turn = PJ_FALSE; status = pjsua_init(&cfg, &log_cfg, &med_cfg); if (status != PJ_SUCCESS) error_exit("Error in pjsua_init()", status); @@ -197,33 +330,38 @@ int main(int argc, char *argv[]) { pjsua_set_ec(0,0); // maybe? pjsua_set_null_snd_dev(); - + /* g711 only */ pjsua_codec_info codecs[32]; unsigned count = sizeof(codecs)/sizeof(*codecs); pjsua_enum_codecs(codecs,&count); for (int i=0; itpdata[transport_id].has_bound_addr = PJ_TRUE; + if (getenv("PJSIP_IPV6")) + pjsua_get_var()->acc[acc_id].cfg.ipv6_media_use = PJSUA_IPV6_ENABLED; + start_pa(); if(mode == DMODEM_DIAL_MODE) { - snprintf(buf,sizeof(buf),"sip:%s@%s",dialstr,sip_domain); - printf("calling %s\n",buf); + if (has_sip_user) + snprintf(buf,sizeof(buf),"sip:%s@%s",dialstr,sip_domain); + else + snprintf(buf,sizeof(buf),"sip:%s",dialstr); + PJ_LOG(2,(__FILE__, "calling %s\n",buf)); pj_str_t uri = pj_str(buf); pjsua_call_id callid; status = pjsua_call_make_call(acc_id, &uri, 0, NULL, NULL, &callid); if (status != PJ_SUCCESS) error_exit("Error making call", status); } - if(mode == DMODEM_ANSWER_MODE) { - pjsua_call_id id; - char* cid = strrchr(argv[1], '+'); - id = atoi(cid); - status = pjsua_call_answer(id, 200, NULL, NULL); - if (status != PJ_SUCCESS) error_exit("Error answering call", status); - } - struct timespec ts = {100, 0}; - if(mode == DMODEM_RING_DETECT_MODE) - ts.tv_sec = 1; - time_t now = time(NULL); + + struct timespec ts = {1, 0}; while(1) { - if(mode == DMODEM_RING_DETECT_MODE) { + if(mode == DMODEM_ANSWER_MODE) { if(ringing) { - char cid[11]; - snprintf(cid, 10, "%d", incoming); - write(atoi(argv[2]), cid, strlen(cid)); + status = pjsua_call_answer(incoming, 200, NULL, NULL); + if (status != PJ_SUCCESS) error_exit("Error answering call", status); + ringing = 0; + answered = 1; } } nanosleep(&ts,NULL); diff --git a/mm.py b/mm.py new file mode 100644 index 0000000..c2e4d98 --- /dev/null +++ b/mm.py @@ -0,0 +1,419 @@ +import subprocess +import threading +import select +import time +import tty +import pathlib +import re +import os +import signal +import ipaddress +import io +import pty +import logging +import pwd +import ctypes +import argparse +from typing import Union, Dict, List, Tuple, Callable, Any + +logging.basicConfig(level=logging.DEBUG,format='%(threadName)-5s - %(levelname)s - %(message)s') +logger = logging.getLogger() + +PJSIP_V6 = False +# exports PJSIP_IPV6=1 to d-modem +# uses ipv6 sip transport to preserve address space +# utilize ipv4 over v6 next hop so that no v4 is required in the container + +PTY_LOC = pathlib.Path("/tmp") +RUN_AS = "nobody" +get_pty = lambda n: PTY_LOC / f"ttySL{n}" + +MODEMD = "./slmodemd/slmodemd" +D_MODEM = './d-modem.nopulse' + +IP4RANGE = ipaddress.ip_network("10.0.0.0/27") +IP6RANGE = ipaddress.ip_network("fd00::/64") + +PRODUCTION = False + +# Do we really need that many? +# modem_configs: Dict[int, List[str]] = { +# **{i: (["AT+MS=92,1" ], lambda: PPPProc) for i in range(00, 05)}, +# **{i: (["AT+MS=90,1" ], lambda: PPPProc) for i in range(05, 10)}, +# **{i: (["AT+MS=34,1" ], lambda: PPPProc) for i in range(10, 15)}, +# **{i: (["AT+MS=132,1"], lambda: PPPProc) for i in range(15, 20)}, +# **{i: (["AT+MS=32,1" ], lambda: PPPProc) for i in range(20, 25)}, # broken? +# **{i: (["AT+MS=122,1"], lambda: PPPProc) for i in range(25, 30)}, +# **{i: (["AT+MS=22,1" ], lambda: PPPProc) for i in range(30, 35)}, +# **{i: (["AT+MS=212,1"], lambda: PPPProc) for i in range(35, 40)}, +# **{i: (["AT+MS=23,1" ], lambda: PPPProc) for i in range(40, 45)}, +# **{i: (["AT+MS=21,1" ], lambda: PPPProc) for i in range(45, 50)}, # broken? +# **{i: (["AT+MS=103,1"], lambda: PPPProc) for i in range(50, 55)}, +# } + +modem_configs: Dict[int, Tuple[List[str], Callable]] = { + **{i: (["AT+MS=90,1"], lambda: PPPProc) for i in range(0, 1)}, + **{i: (["AT+MS=34,1"], lambda: PPPProc) for i in range(1, 2)}, + **{i: (["AT+MS=32,1"], lambda: PPPProc) for i in range(2, 3)}, + **{i: (["AT+MS=22,1"], lambda: PPPProc) for i in range(3, 5)}, + **{i: (["AT+MS=23,1"], lambda: PPPProc) for i in range(5, 7)}, +} + +assert all(i >= 0 for i in modem_configs) + +class BaseProc: + def __init__(self) -> None: + self.proc: subprocess.Popen = None + def proc(self) -> subprocess.Popen: + return self.proc + def terminate(self, *args, **kwargs): + return self.proc.terminate(*args, **kwargs) + def wait(self, *args, **kwargs): + return self.proc.wait(*args, **kwargs) + def poll(self, *args, **kwargs): + return self.proc.poll(*args, **kwargs) + def send_signal(self, *args, **kwargs): + return self.proc.send_signal(*args, **kwargs) + +class ShellProc(BaseProc): + ''' danger! ''' + def __init__(self, ptyr: io.BufferedReader, ptyw: io.BufferedWriter, speed: int, no: int, + pty_path: pathlib.Path, ip: Tuple[ipaddress.IPv4Address, ipaddress.IPv6Address]) -> None: + assert not PRODUCTION + self.proc = subprocess.Popen( + ['bash', '-c', 'stty sane;exec bash'], + stdin=ptyr, + stdout=ptyw, + stderr=ptyw, + preexec_fn=os.setsid, + env=dict(os.environ, TERM='vt100') + ) + +class PPPProc(BaseProc): + def __init__(self, ptyr: io.BufferedReader, ptyw: io.BufferedWriter, speed: int, no: int, + pty_path: pathlib.Path, ip: Tuple[ipaddress.IPv4Address, ipaddress.IPv6Address]) -> None: + self.no = no + self.ip = ip + ptyw.write(( + f"Welcome to D-Modem\r\nExample pppd launch options:\r\n" + f" pppd /dev/ttyACM0 {speed} defaultroute persist noproxyarp noauth modem nodetach\r\n" + f"Your ipv6 address is {ip[1]}.\r\n" + f"Please add Address={ip[1]}/{IP6RANGE.prefixlen} Gateway={IP6RANGE[1]}\r\n" + "to your ppp network device.\r\n" + "Enjoy!\r\n\r\n\r\n" + ).encode('utf-8')) + ll_ident = lambda x: ipaddress.IPv6Address(int(x) & int(ipaddress.IPv6Address(2**(128-IP6RANGE.prefixlen)-1))) + self.proc = PopenComm( + [ + 'pppd', str(pty_path.resolve()), str(speed), 'noproxyarp', 'passive', 'noauth', + 'unit', str(no), 'modem', 'nodefaultroute', 'nodetach', f"{IP4RANGE[0]}:{ip[0]}", + '+ipv6', 'ipv6', f"{ll_ident(IP6RANGE[1])},{ll_ident(ip[1])}", + 'ipcp-max-configure', '100', 'ipcp-max-failure', '100', 'ipcp-restart', '15', + 'ipv6cp-max-configure', '100', 'ipv6cp-max-failure', '100', 'ipv6cp-restart', '15' + ] + ) + def wait(self, *args, **kwargs): + while (lines := self.proc.readline()) is not None: + for line in lines: + logger.info(f"pppd: {line}") + if 'remote LL address' in line: + try: + subprocess.run( + [ + 'ip', '-6', 'a', 'replace', 'dev', f"ppp{self.no}", + f"{IP6RANGE[1]}/128", 'peer', f"{self.ip[1]}/128" + ], + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL + ) + logger.info(f"added v6 route to ppp{self.no}") + except Exception: + logger.exception(f"adding v6 route to ppp{self.no}") + super().wait(*args, **kwargs) + +class ModemManagerError(Exception): + pass +class ReadLineException(ModemManagerError): + pass +class PPPDDead(ModemManagerError): + pass +class CarrierLost(ModemManagerError): + pass +class ModemManager: + terminating = False + ''' lol not that modem_manager ''' + def __init__(self): + self.modems = {no: Modem(no, config) for no, config in modem_configs.items()} + self.ip_used4 = {IP4RANGE[0]} + self.ip_used6 = {IP6RANGE[0], IP6RANGE[1]} + +class PopenComm(subprocess.Popen): + def __init__(self, *args, **kwargs) -> None: + try: + self._popen_comm_master, self._popen_comm_slave = pty.openpty() + os.set_blocking(self._popen_comm_master, False) + self._popen_comm_r = open(self._popen_comm_master, "rb", buffering=0) + kwargs['stdin'] = kwargs['stdout'] = kwargs['stderr'] = self._popen_comm_master + super().__init__(*args, **kwargs) + except Exception: + self._popen_comm_cleanup() + raise + def w(self): + try: + super().wait(timeout=None) + except Exception: + logger.exception("Popen wait") + self._popen_comm_cleanup() + self.wtr = threading.Thread(target=w, args=(self,)) + self.wtr.start() + + def wait(self, timeout=None) -> int: + if timeout is not None: + return super().wait(timeout=timeout) + self.wtr.join() + if self.poll() is None: + return super().wait(timeout=None) + return self.returncode + + def _popen_comm_cleanup(self) -> None: + try: + os.close(self._popen_comm_slave) + except Exception: + pass + try: + self._popen_comm_r.close() + except Exception: + pass + try: + os.close(self._popen_comm_master) + except Exception: + pass + logger.info(f"{self} pty pair closed") + + def readline(self) -> Union[List[str], None]: + ''' blocks ''' + try: + select.select([self._popen_comm_master], list(), list()) + read = self._popen_comm_r.read().decode('utf-8', errors='replace') + except (OSError, ValueError): + return None + except Exception: + logger.exception("readline") + return None + if not read: + return None + got = read.split('\n') + if not got[-1]: + got.pop(-1) + return got + +class IP_Request: + lock = {4: threading.Lock(), 6: threading.Lock()} + def __init__(self, version: int) -> None: + assert version in {4, 6} + self.v = version + self.addr = None + self.used = modem_manger.ip_used4 if self.v == 4 else modem_manger.ip_used6 + def __enter__(self): + with self.lock[self.v]: + for address in (IP4RANGE if self.v == 4 else IP6RANGE): + if address in self.used: + continue + else: + self.addr = address + self.used.add(address) + logger.info(f"ipv{self.v} lease: {str(self.addr)}") + return address + raise RuntimeError(f"error: ipv{self.v} address exhausted") + def __exit__(self, *_): + if self.addr: + with self.lock[self.v]: + self.used.remove(self.addr) + logger.info(f"ipv{self.v} return: {str(self.addr)}") + +class Modem: + def __init__(self, no: int, config: Tuple[List[str], Callable]): + self.no = no + self.readline_quit = False + self.readlinebuf = [] + self.modem_proc: Union[subprocess.Popen, None] = None + self.ppp_proc: Union[subprocess.Popen, None] = None + self.modem_cmd, get_ppp_func = config + self.ppp_func = get_ppp_func() + self.modem_cmd = [*self.modem_cmd, "ATA"] + self.pty_path = get_pty(self.no) + if os.path.exists(self.pty_path): + logger.warning(f"pty link exists {str(self.pty_path)}") + self.pty_path.unlink() + self.modem_ctl: threading.Thread = threading.Thread(target=self._start_modem_ctl, name=f"md{no:03d}") + self.ppp_ctl: threading.Thread = threading.Thread(target=self._start_ppp_ctl, name=f"pp{no:03d}") + self.modem_ctl.start() + self.ppp_ctl.start() + + def _util_readline(self, dev: io.BufferedReader, timeout: float) -> bytes: + readlinebuf = self.readlinebuf + readlinebuf.clear() + time_remaining = timeout + while True: + read_ready, _, _ = select.select([dev], list(), list(), 0.1) + if self.readline_quit: + self.readline_quit = False + raise ReadLineException("process exit") + if not read_ready: + time_remaining -= 0.1 + if time_remaining < 0: + break + continue + readlinebuf.append(dev.read(1)) + if readlinebuf[-1] == b'\n': + return b''.join(readlinebuf) + return b''.join(readlinebuf) + + def _start_modem_ctl(self) -> None: + while not ModemManager.terminating: + try: + if os.geteuid() == 0: + pw_record = pwd.getpwnam(RUN_AS) + uid, gid = pw_record.pw_uid, pw_record.pw_gid + def demote(): + PR_SET_NO_NEW_PRIVS = 38 + PR_CAP_AMBIENT = 47 + PR_CAP_AMBIENT_CLEAR_ALL = 4 + PR_GET_SECUREBITS = 27 + PR_SET_SECUREBITS = 28 + libc = ctypes.CDLL('libc.so.6') + libc.prctl.restype = ctypes.c_int + assert libc.prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == 0 + assert libc.prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0) == 0 + assert libc.prctl(PR_SET_SECUREBITS, 0x2f) == 0 + # SECBIT_KEEP_CAPS_LOCKED | SECBIT_NO_SETUID_FIXUP | SECBIT_NO_SETUID_FIXUP_LOCKED | SECBIT_NOROOT | SECBIT_NOROOT_LOCKED + assert libc.prctl(PR_GET_SECUREBITS) == 0x2f + os.setgroups([]) + os.setresgid(gid, gid, gid) + os.setresuid(uid, uid, uid) + os.chdir(os.getcwd()) + preexec_func = demote + env = { + 'USER': pw_record.pw_name, + 'LOGNAME': pw_record.pw_name, + 'HOME': '/', + 'PATH': os.environ['PATH'], + } + else: + preexec_func = lambda: None + env = dict(os.environ) + logger.warning("d-modem is running as current user") + assert not PRODUCTION + if PJSIP_V6: + env['PJSIP_IPV6'] = '1' + else: + if 'PJSIP_IPV6' in env: + env.pop('PJSIP_IPV6') + self.modem_proc = PopenComm( + [MODEMD, '-e', D_MODEM, str(self.pty_path)], + preexec_fn=preexec_func, + env = env + ) + while (lines := self.modem_proc.readline()) is not None: + for line in lines: + logger.info(f"slmodemd: {line}") + self.modem_proc.wait() + except Exception: + logger.exception("unknown error") + try: + self.readline_quit = True + if self.ppp_proc: + self.ppp_proc.terminate() + self.ppp_proc.wait() + self.ppp_proc = None + except Exception as err: + logger.exception("unknown error") + self.ppp_ctl.join() + + def _start_ppp_ctl(self) -> None: + time.sleep(1) + while not ModemManager.terminating: + commands = self.modem_cmd.copy() + try: + with open(self.pty_path.resolve(), "rb", buffering=0) as ptyr, open(self.pty_path.resolve(), "wb", buffering=0) as ptyw: + tty.setraw(ptyr) + tty.setraw(ptyw) + while True: + while (got := self._util_readline(ptyr, 0.3).decode('utf-8', errors='replace')): + logger.debug(f"from pty: {got=}") + if got: + if (match := re.match(r'CONNECT ([0-9]+)', got)) and not self.ppp_proc: + speed = int(match.groups()[0]) + assert speed > 0 + logger.info(f"connection {speed=}, start subprocess") + ptyw.write(f"Remote speed {speed}\r\n".encode('utf-8')) + try: + with IP_Request(4) as ip4, IP_Request(6) as ip6: + self.ppp_proc = self.ppp_func(ptyr, ptyw, speed, self.no, self.pty_path, (ip4, ip6)) + self.ppp_proc.wait() + except Exception: + logger.exception("unknown subprocess error") + self.modem_proc.send_signal(signal.SIGINT) + self.modem_proc.wait() + raise PPPDDead + elif got == 'NO CARRIER\r\n': + self._util_readline(ptyr, 2) + raise CarrierLost + if commands: + cmd = commands.pop(0) + ptyw.write(f"{cmd}\n\r".encode('ascii')) + logger.info(f"command: {cmd}") + ptyw.flush() + time.sleep(0.1) + except ModemManagerError as err: + logger.info(f"pty detach: {repr(err)}") + time.sleep(0.1) + except Exception: + logger.exception("unknown error") + time.sleep(2) + +if __name__ == "__main__": + abspath=os.path.abspath(__file__) + abspath=os.path.dirname(abspath) + os.chdir(abspath) + + parser = argparse.ArgumentParser(description='ModemManager.py') + parser.add_argument('-4', '--ipv4', type=str, default=str(IP4RANGE), help='ipv4 subnet') + parser.add_argument('-6', '--ipv6', type=str, default=str(IP6RANGE), help='ipv6 subnet') + parser.add_argument('-m', '--modemd', type=str, default=str(MODEMD), help='modemd location') + parser.add_argument('-d', '--dmodem', type=str, default=str(D_MODEM), help='d-modem location') + parser.add_argument('-p', '--pty', type=str, default=str(PTY_LOC), help='pty link location') + parser.add_argument('-u', '--user', type=str, default=str(RUN_AS), help='run as user') + parser.add_argument('-s', '--production', action='store_true', help='enable strict checks') + parser.add_argument('--pjsip6', action='store_true', help='pjsip force v6') + args = parser.parse_args() + + def g(x: str, y: Any) -> None: + globals()[x] = y + g('IP4RANGE', ipaddress.ip_network(args.ipv4)) + g('IP6RANGE', ipaddress.ip_network(args.ipv6)) + g('MODEMD', args.modemd) + g('D_MODEM', args.dmodem) + g('PTY_LOC', pathlib.Path(args.pty)) + g('RUN_AS', args.user) + g('PRODUCTION', args.production) + g('PJSIP_V6', args.pjsip6) + if args.production: + logger.setLevel(logging.INFO) + + modem_manger = ModemManager() + + def sighandler(signum, _frame): + if ModemManager.terminating: + logger.error(f"received signal {signum} while terminating") + else: + ModemManager.terminating = True + logger.warning(f"terminate on signal {signum}") + for modem in modem_manger.modems.values(): + modem.modem_proc.terminate() + for sig in (signal.SIGTERM, signal.SIGHUP, signal.SIGINT, signal.SIGABRT): + signal.signal(sig, sighandler) + + for modem in modem_manger.modems.values(): + modem.modem_ctl.join() diff --git a/slmodemd/Makefile b/slmodemd/Makefile index 2bbd525..4553d40 100644 --- a/slmodemd/Makefile +++ b/slmodemd/Makefile @@ -24,7 +24,8 @@ endif RM:= rm -f -CFLAGS+= -Wall -g -O -I. -DCONFIG_DEBUG_MODEM +# CFLAGS+= -Wall -g -O -I. -DCONFIG_DEBUG_MODEM +CFLAGS+= -Wall -O1 -I. -DCONFIG_DEBUG_MODEM modem-objs:= \ modem.o modem_datafile.o modem_at.o modem_timer.o \ diff --git a/slmodemd/modem.c b/slmodemd/modem.c index 3a4c711..ab052d2 100644 --- a/slmodemd/modem.c +++ b/slmodemd/modem.c @@ -70,6 +70,7 @@ #define MODEM_DBG(fmt,arg...) dprintf("%s: " fmt , m->name , ##arg) #define MODEM_ERR(fmt,arg...) eprintf("%s: " fmt , m->name , ##arg) +short _MODEM_DO_ANSWER = 0; /* external symbols */ extern int process_at_command(struct modem *m, char *buf); extern void *dp_runtime_create(struct modem *m); @@ -1567,6 +1568,7 @@ int modem_recv_from_tty(struct modem *m, char *buf, int n) int modem_answer(struct modem *m) { + _MODEM_DO_ANSWER = 1; MODEM_DBG("modem answer...\n"); if ( m->dp ) { MODEM_ERR("dp %d is already exists.\n", m->dp->id); @@ -1613,6 +1615,7 @@ static int modem_dial_start(struct modem *m) int modem_dial(struct modem *m) { + _MODEM_DO_ANSWER = 0; int ret; MODEM_DBG("modem dial: %s...\n", m->dial_string); m->dp_requested = 0; diff --git a/slmodemd/modem.h b/slmodemd/modem.h index 3550d83..62b2e02 100644 --- a/slmodemd/modem.h +++ b/slmodemd/modem.h @@ -456,5 +456,7 @@ extern void modem_update_config(struct modem *m, struct modem_config *cfg); #define MODEM_DP(m) ((m)->sregs[SREG_DP]) #define MODEM_AUTOMODE(m) ((m)->sregs[SREG_AUTOMODE]) +extern short _MODEM_DO_ANSWER; + #endif /* __MODEM_H__ */ diff --git a/slmodemd/modem_cmdline.c b/slmodemd/modem_cmdline.c index ec20a91..52efcb9 100644 --- a/slmodemd/modem_cmdline.c +++ b/slmodemd/modem_cmdline.c @@ -68,10 +68,10 @@ extern unsigned int modem_debug_logging; /* config parameters */ const char *modem_dev_name = NULL; -const char *modem_default_dev_name = "/dev/slamr0"; +const char *modem_default_dev_name = "/tmp/ttySL0"; const char *modem_alsa_dev_name = "modem:1"; const char *modem_exec = NULL; -unsigned int need_realtime = 1; +unsigned int need_realtime = 0; #ifdef MODEM_CONFIG_RING_DETECTOR unsigned int ring_detector = 0; #endif @@ -125,7 +125,7 @@ static struct opt { {'s',"shortbuffer","use short buffer (4 periods length)"}, {'d',"debug","debug level (developers only, for ./sl...)",OPTIONAL,INTEGER,"0"}, {'l',"log","logging mode",OPTIONAL,INTEGER,"5"}, - {'e',"exec","path to external application that transmits audio over the socket (required)",MANDATORY,STRING,""}, + {'e',"exec","path to external application that transmits audio over the socket (required)",MANDATORY,STRING,"./d-modem"}, {} }; diff --git a/slmodemd/modem_main.c b/slmodemd/modem_main.c index 0a20e61..53bfddf 100644 --- a/slmodemd/modem_main.c +++ b/slmodemd/modem_main.c @@ -87,7 +87,7 @@ #define DBG(fmt,args...) dprintf("main: " fmt, ##args) -#define SLMODEMD_USER "nobody" +//#define SLMODEMD_USER "nobody" #define LOCKED_MEM_MIN_KB (8UL * 1024) #define LOCKED_MEM_MIN (LOCKED_MEM_MIN_KB * 1024) @@ -217,19 +217,19 @@ static int alsa_device_setup(struct device_struct *dev, const char *dev_name) ret = snd_pcm_open(&dev->phandle, dev_name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if(ret < 0) { ERR("alsa setup: cannot open playback device '%s': %s\n", - dev_name, snd_strerror(ret)); + dev_name, snd_strerror(ret)); return -1; } ret = snd_pcm_open(&dev->chandle, dev_name, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); if(ret < 0) { ERR("alsa setup: cannot open playback device '%s': %s\n", - dev_name, snd_strerror(ret)); + dev_name, snd_strerror(ret)); return -1; } ret = snd_pcm_poll_descriptors(dev->chandle, &pfd, 1); if(ret <= 0) { ERR("alsa setup: cannot get poll descriptors of '%s': %s\n", - dev_name, snd_strerror(ret)); + dev_name, snd_strerror(ret)); return -1; } dev->fd = pfd.fd; @@ -313,7 +313,7 @@ static int alsa_device_write(struct device_struct *dev, const char *buf, int cou if (ret == -EAGAIN) continue; if (ret == -EPIPE) { - ret = alsa_xrun_recovery(dev); + ret = alsa_xrun_recovery(dev); } written = ret; break; @@ -373,7 +373,7 @@ static int setup_stream(snd_pcm_t *handle, struct modem *m, const char *stream_n ERR("cannot set format for %s: %s\n", stream_name, snd_strerror(err)); return err; } - err = snd_pcm_hw_params_set_channels(handle, hw_params, 1); + err = snd_pcm_hw_params_set_channels(handle, hw_params, 1); if (err < 0) { ERR("cannot set channels for %s: %s\n", stream_name, snd_strerror(err)); return err; @@ -386,7 +386,7 @@ static int setup_stream(snd_pcm_t *handle, struct modem *m, const char *stream_n } if ( rrate != rate ) { ERR("rate %d is not supported by %s (%d).\n", - rate, stream_name, rrate); + rate, stream_name, rrate); return -1; } rsize = size = dev->period ; @@ -397,7 +397,7 @@ static int setup_stream(snd_pcm_t *handle, struct modem *m, const char *stream_n } if ( rsize < size ) { ERR("period size %ld is not supported by %s (%ld).\n", - size, stream_name, rsize); + size, stream_name, rsize); return -1; } rsize = size = use_short_buffer ? rsize * dev->buf_periods : rsize * 32; @@ -408,7 +408,7 @@ static int setup_stream(snd_pcm_t *handle, struct modem *m, const char *stream_n } if ( rsize != size ) { DBG("buffer size for %s is changed %ld -> %ld\n", - stream_name, size, rsize); + stream_name, size, rsize); } err = snd_pcm_hw_params(handle, hw_params); if (err < 0) { @@ -523,9 +523,9 @@ static int alsa_ioctl(struct modem *m, unsigned int cmd, unsigned long arg) struct device_struct *dev = m->dev_data; DBG("alsa_ioctl: cmd %x, arg %lx...\n",cmd,arg); switch(cmd) { - case MDMCTL_CAPABILITIES: - return -EINVAL; - case MDMCTL_HOOKSTATE: + case MDMCTL_CAPABILITIES: + return -EINVAL; + case MDMCTL_HOOKSTATE: return (dev->hook_off_elem) ? snd_mixer_selem_set_playback_switch_all( dev->hook_off_elem, @@ -534,9 +534,9 @@ static int alsa_ioctl(struct modem *m, unsigned int cmd, unsigned long arg) return (dev->speaker_elem) ? snd_mixer_selem_set_playback_volume_all( dev->speaker_elem, arg) : 0 ; - case MDMCTL_CODECTYPE: - return CODEC_SILABS; - case MDMCTL_IODELAY: + case MDMCTL_CODECTYPE: + return CODEC_SILABS; + case MDMCTL_IODELAY: DBG("delay = %d\n", dev->delay); return dev->delay; default: @@ -547,10 +547,10 @@ static int alsa_ioctl(struct modem *m, unsigned int cmd, unsigned long arg) struct modem_driver alsa_modem_driver = { - .name = "alsa modem driver", - .start = alsa_start, - .stop = alsa_stop, - .ioctl = alsa_ioctl, + .name = "alsa modem driver", + .start = alsa_start, + .stop = alsa_stop, + .ioctl = alsa_ioctl, }; @@ -568,7 +568,7 @@ static int modemap_start (struct modem *m) int ret; DBG("modemap_start...\n"); dev->delay = 0; - ret = ioctl(dev->fd,100000+MDMCTL_START,0); + ret = ioctl(dev->fd,100000+MDMCTL_START,0); if (ret < 0) return ret; ret = 192*2; @@ -586,7 +586,7 @@ static int modemap_stop (struct modem *m) { struct device_struct *dev = m->dev_data; DBG("modemap_stop...\n"); - return ioctl(dev->fd,100000+MDMCTL_STOP,0); + return ioctl(dev->fd,100000+MDMCTL_STOP,0); } static int modemap_ioctl(struct modem *m, unsigned int cmd, unsigned long arg) @@ -607,10 +607,10 @@ static int modemap_ioctl(struct modem *m, unsigned int cmd, unsigned long arg) struct modem_driver mdm_modem_driver = { - .name = "modemap driver", - .start = modemap_start, - .stop = modemap_stop, - .ioctl = modemap_ioctl, + .name = "modemap driver", + .start = modemap_start, + .stop = modemap_stop, + .ioctl = modemap_ioctl, }; static int socket_start (struct modem *m) @@ -626,20 +626,27 @@ static int socket_start (struct modem *m) exit(-1); } + pid_t ppid = getpid(); pid_t pid = fork(); if (pid == -1) { perror("fork"); exit(-1); } if (pid == 0) { // child - char str[16]; - snprintf(str,sizeof(str),"%d",sockets[0]); + char _str[12]; + char _parent[12]; + snprintf(_str,sizeof(_str),"%d",sockets[0]); + snprintf(_parent,sizeof(_parent),"%d",ppid); + const char *_modem_path = strrchr(modem_dev_name, '/'); + _modem_path = _modem_path == NULL ? modem_dev_name : _modem_path + 1; close(sockets[1]); - if(m->hook == MODEM_HOOK_SNOOPING) { - ret = execl(modem_exec,modem_exec,"rr",str,NULL); + if(_MODEM_DO_ANSWER) { + DBG("MODEM_ANSW execl arg: %s, %s, %s, %s, %s\n",modem_exec,"rr",_str,_parent,_modem_path); + ret = execl(modem_exec,modem_exec,"rr",_str,_parent,_modem_path,NULL); } else { - ret = execl(modem_exec,modem_exec,m->dial_string,str,NULL); + DBG("MODEM_DIAL execl arg: %s, %s, %s, %s, %s\n",modem_exec,m->dial_string,_str,_parent,_modem_path); + ret = execl(modem_exec,modem_exec,m->dial_string,_str,_parent,_modem_path,NULL); } if (ret == -1) { ERR("prog: %s\n", modem_exec); @@ -653,13 +660,13 @@ static int socket_start (struct modem *m) ret = 192*2; memset(outbuf, 0 , ret); ret = write(dev->fd, outbuf, ret); - DBG("done delay thing\n"); if (ret < 0) { close(dev->fd); dev->fd = -1; return ret; } dev->delay = ret/2; + DBG("done delay thing %d\n", dev->delay); } return 0; } @@ -713,10 +720,10 @@ static int socket_ioctl(struct modem *m, unsigned int cmd, unsigned long arg) } struct modem_driver socket_modem_driver = { - .name = "socket driver", - .start = socket_start, - .stop = socket_stop, - .ioctl = socket_ioctl, + .name = "socket driver", + .start = socket_start, + .stop = socket_stop, + .ioctl = socket_ioctl, }; static int mdm_device_read(struct device_struct *dev, char *buf, int size) @@ -788,11 +795,11 @@ int create_pty(struct modem *m) if(m->pty) close(m->pty); - pty = getpt(); - if (pty < 0 || grantpt(pty) < 0 || unlockpt(pty) < 0) { - ERR("getpt: %s\n", strerror(errno)); - return -1; - } + pty = getpt(); + if (pty < 0 || grantpt(pty) < 0 || unlockpt(pty) < 0) { + ERR("getpt: %s\n", strerror(errno)); + return -1; + } if(m->pty) { termios = m->termios; @@ -805,11 +812,11 @@ int create_pty(struct modem *m) cfsetospeed(&termios, B115200); } - ret = tcsetattr(pty, TCSANOW, &termios); - if (ret) { - ERR("tcsetattr: %s\n",strerror(errno)); - return -1; - } + ret = tcsetattr(pty, TCSANOW, &termios); + if (ret) { + ERR("tcsetattr: %s\n",strerror(errno)); + return -1; + } fcntl(pty,F_SETFL,O_NONBLOCK); @@ -820,17 +827,18 @@ int create_pty(struct modem *m) modem_update_termios(m,&termios); + modem_group = NULL; if(modem_group && *modem_group) { struct group *grp = getgrnam(modem_group); if(!grp) { ERR("cannot find group '%s': %s\n", modem_group, - strerror(errno)); + strerror(errno)); } else { ret = chown(pty_name, -1, grp->gr_gid); if(ret < 0) { ERR("cannot chown '%s' to ':%s': %s\n", - pty_name, modem_group, strerror(errno)); + pty_name, modem_group, strerror(errno)); } } } @@ -838,19 +846,19 @@ int create_pty(struct modem *m) ret = chmod(pty_name, modem_perm); if (ret < 0) { ERR("cannot chmod '%s' to %o: %s\n", - pty_name, modem_perm, strerror(errno)); + pty_name, modem_perm, strerror(errno)); } if(*link_name) { unlink(link_name); if(symlink(pty_name,link_name)) { ERR("cannot create symbolink link `%s' -> `%s': %s\n", - link_name,pty_name,strerror(errno)); + link_name,pty_name,strerror(errno)); *link_name = '\0'; } else { INFO("symbolic link `%s' -> `%s' created.\n", - link_name, pty_name); + link_name, pty_name); } } @@ -898,9 +906,9 @@ static int modem_run(struct modem *m, struct device_struct *dev) modem_ring_detector_start(m); #endif - tmo.tv_sec = 1; - tmo.tv_usec= 0; - FD_ZERO(&rset); + tmo.tv_sec = 1; + tmo.tv_usec= 0; + FD_ZERO(&rset); FD_ZERO(&eset); if(m->started) FD_SET(dev->fd,&rset); @@ -918,14 +926,14 @@ static int modem_run(struct modem *m, struct device_struct *dev) if(m->pty > max_fd) max_fd = m->pty; } - ret = select(max_fd + 1,&rset,NULL,&eset,&tmo); + ret = select(max_fd + 1,&rset,NULL,&eset,&tmo); - if (ret < 0) { + if (ret < 0) { if (errno == EINTR) continue; - ERR("select: %s\n",strerror(errno)); - return ret; - } + ERR("select: %s\n",strerror(errno)); + return ret; + } if ( ret == 0 ) continue; @@ -1002,7 +1010,7 @@ static int modem_run(struct modem *m, struct device_struct *dev) } if(count != m->update_delay) { ERR("cannot update delay: %d instead of %d.\n", - count, m->update_delay); + count, m->update_delay); return -1; } dev->delay += m->update_delay; @@ -1074,7 +1082,7 @@ int modem_main(const char *dev_name) struct modem *m; int pty; int ret = 0; - struct passwd *pwd; +// struct passwd *pwd; modem_debug_init(basename(dev_name)); @@ -1089,7 +1097,7 @@ int modem_main(const char *dev_name) prop_dp_init(); modem_timer_init(); - sprintf(link_name,"/dev/ttySL%d", device.num); + sprintf(link_name, "%s", modem_dev_name); m = modem_create(modem_driver,basename(dev_name)); m->name = basename(dev_name); @@ -1103,10 +1111,10 @@ int modem_main(const char *dev_name) } INFO("modem `%s' created. TTY is `%s'\n", - m->name, m->pty_name); + m->name, m->pty_name); - sprintf(path_name,"/var/lib/slmodem/data.%s",basename(dev_name)); - datafile_load_info(path_name,&m->dsp_info); +// sprintf(path_name,"/var/lib/slmodem/data.%s",basename(dev_name)); +// datafile_load_info(path_name,&m->dsp_info); if (need_realtime) { struct sched_param prm; @@ -1147,11 +1155,11 @@ int modem_main(const char *dev_name) } ret = (setgroups(1,&pwd->pw_gid) || - setgid(pwd->pw_gid) || - setuid(pwd->pw_uid)); + setgid(pwd->pw_gid) || + setuid(pwd->pw_uid)); if (ret) { ERR("setgroups or setgid %ld or setuid %ld failed: %s\n", - (long)pwd->pw_gid,(long)pwd->pw_uid,strerror(errno)); + (long)pwd->pw_gid,(long)pwd->pw_uid,strerror(errno)); exit(-1); } @@ -1160,11 +1168,11 @@ int modem_main(const char *dev_name) exit(-1); } DBG("dropped privileges to %ld.%ld\n", - (long)pwd->pw_gid,(long)pwd->pw_uid); + (long)pwd->pw_gid,(long)pwd->pw_uid); #endif INFO("Use `%s' as modem device, Ctrl+C for termination.\n", - *link_name ? link_name : m->pty_name); + *link_name ? link_name : m->pty_name); /* main loop here */ ret = modem_run(m,&device); @@ -1199,7 +1207,7 @@ int main(int argc, char *argv[]) extern void modem_cmdline(int argc, char *argv[]); int ret; modem_cmdline(argc,argv); - if(!modem_dev_name) modem_dev_name = "/dev/slamr0"; + if(!modem_dev_name) modem_dev_name = "/tmp/ttySL0"; device_setup = socket_device_setup; device_release = mdm_device_release;