Compare commits

...

155 Commits

Author SHA1 Message Date
Simon 21c8087281 pprint 2024-03-11 18:09:26 +00:00
Simon 6c22819fe3 systemList 2024-03-11 16:35:51 +00:00
Simon 2546fd5442 options reload 2024-03-11 16:21:55 +00:00
Simon 59de0e5bd3 remsystem! 2024-03-08 17:32:23 +00:00
Simon 1ff51cceef pb 2024-03-08 17:23:59 +00:00
Simon 3a9f23f801 debug 2024-03-08 17:08:51 +00:00
Simon bca9017897 go on 2024-03-06 21:30:22 +00:00
Simon 6e21c57e2f bridgesystem error causing restart 2024-03-06 21:18:04 +00:00
Simon 87829a5f9f rbs 2024-03-06 21:08:30 +00:00
Simon a897ff7892 ff 2024-03-06 19:51:26 +00:00
Simon 058b40ce09 bt 2024-03-06 19:19:51 +00:00
Simon e903ad4cc5 Don't apply options if in reset cycle 2024-03-06 18:43:55 +00:00
Simon ed5e4d1495 stop resetter 2024-03-06 16:43:55 +00:00
Simon 21abf0b040 Rwerite reset routine 2024-03-06 15:41:35 +00:00
Simon a537f6b1e8 move bridgetemp 2024-03-06 13:14:10 +00:00
Simon 2356ccf216 Merge branch 'master' of ssh://gitlab.hacknix.net:2222/hacknix/FreeDMR 2024-03-06 13:01:24 +00:00
Simon 713a147011 Indenting 2024-03-06 13:00:06 +00:00
Simon 2dfcbd5383 Update .gitlab-ci.yml file 2024-03-06 11:27:57 +00:00
Simon 14cfa39aab Update .gitlab-ci.yml file 2024-03-06 11:04:45 +00:00
Simon 3ff636b8bd Try changing some of the deque()'s to [] 2024-03-06 10:59:15 +00:00
Simon dfd4182620 Add release to co config 2024-03-04 14:16:35 +00:00
Simon 27baed8a5e ff 2024-02-10 19:50:21 +00:00
Simon 6296d16081 dd 2024-02-10 19:01:28 +00:00
Simon d73bdaa762 debug 2024-02-10 18:52:22 +00:00
Simon 24121955d1 fix dial bug - i hope 2024-01-22 18:09:52 +00:00
Simon 9610bc8687 update() does not recurse nested dictionaries 2023-12-30 11:53:39 +00:00
Simon 88aa091b27 bug 2023-12-26 11:26:00 +00:00
Simon 2963f7875e Some tidying up 2023-12-26 10:46:59 +00:00
Simon 42b5e097bb Force options reload when connections are reset 2023-12-26 10:32:59 +00:00
Simon 8f2578581c int() 2023-12-26 10:27:14 +00:00
Simon 73657083d2 fix languages 2023-12-24 11:31:58 +00:00
Simon 024e7486b6 Fix problem in German language 2023-12-23 20:10:32 +00:00
Simon 5519ea4114 Make bridge reset more efficient by only updating changed items 2023-12-23 19:44:59 +00:00
Simon 7fa5c23c2b Fix timeout updating 2023-12-23 18:18:18 +00:00
Simon ca199e72e0 Fix default dial regression 2023-12-23 16:16:59 +00:00
Simon 803a26de8c Revert "Make all API returns JSON format"
This reverts commit e1503906d4.
2023-12-22 21:24:33 +00:00
Simon 50d9d8689d Revert "JSONing"
This reverts commit cf42b58994.
2023-12-22 21:24:31 +00:00
Simon cf42b58994 JSONing 2023-12-22 21:13:44 +00:00
Simon e1503906d4 Make all API returns JSON format 2023-12-22 20:26:26 +00:00
Simon 5ffadeb666 unicode 2023-12-22 19:08:14 +00:00
Simon d9f381ce7e test system 2023-12-22 18:05:13 +00:00
Simon d11ff13df2 frog 2023-12-22 17:49:57 +00:00
Simon b9fdc9838e print 2023-12-22 11:30:58 +00:00
Simon 4774f98f4f typo 2023-12-22 10:43:39 +00:00
Simon 89376ded87 getoptions 2023-12-21 20:05:41 +00:00
Simon 0936f90c49 Proxy logging 2023-10-31 19:23:10 +00:00
Simon 2380185963 Adjust service priority to ensure proxy comes up first and goes
down last, to ensure that connections with HBP clients get
closed cleanly.
2023-10-24 10:30:58 +01:00
Simon 4663b5c998 Handle KeyError - T_LC 2023-10-23 11:28:21 +01:00
Simon a7ab68f2a6 Handle KeyError - EMB_LC 2023-10-23 11:12:52 +01:00
Simon d55c01002b supervisor needs to be exec() 2023-10-23 11:01:37 +01:00
Simon 360b267fcb ddd 2023-10-23 10:50:49 +01:00
Simon 55097acf58 Bridge mode needs to work too ;-) 2023-10-23 10:42:12 +01:00
Simon 95cfec18ad Update supervisor config 2023-10-23 10:30:51 +01:00
Simon 8ec0e17475 Fix entry line 2023-10-23 10:11:19 +01:00
Simon cab3cda3c7 ffkl 2023-10-22 23:40:55 +01:00
Simon bb6797f17a full config file path 2023-10-22 23:39:23 +01:00
Simon 81fc0ab66f after save 2023-10-22 23:19:25 +01:00
Simon b52a8da86b Switch to supervisor for process control 2023-10-22 23:11:49 +01:00
Simon c5d7cb0bd2 Revert "upgrade python to 3.12"
This reverts commit 5d54ecacc1.
2023-10-14 12:03:10 +01:00
Simon 5d54ecacc1 upgrade python to 3.12 2023-10-14 11:56:03 +01:00
Simon 17a3a8a8ff python 2023-10-14 11:53:44 +01:00
Simon 77fd38bbab Exec 2023-10-14 11:48:32 +01:00
Simon 5e14fa8fd5 Only make bridge for ECHO if echo exists in config. 2023-10-14 11:45:59 +01:00
Simon 0008d471d0 Fix signal handler 2023-10-14 11:38:42 +01:00
Simon 2b0eda07a8 remove exec 2023-10-06 18:07:41 +01:00
Simon 4d6b4768ff Exec instead 2023-10-04 17:38:56 +01:00
Simon 0446c1a542 Backgrounding 2023-10-04 17:34:40 +01:00
Simon 5d61ffd90c /sbin/tini 2023-10-04 17:28:17 +01:00
Simon aa14ff9b65 Switch to tini for init in container 2023-10-04 17:23:21 +01:00
Simon b34e83060d Enable saving system key and loading it again on start.
New JSON file - keys.json - system keystore
2023-10-03 22:44:43 +01:00
Simon 787186bc60 Add killserver to API 2023-10-03 17:17:47 +01:00
Simon 917627c1da Set default for DEBUG_BRIDGES to False as it's no longer required as default 2023-10-02 16:23:55 +01:00
Simon 3da95c45a2 Remove rules.py requirement 2023-10-02 16:13:48 +01:00
Simon f7804a2515 Fix longstanding bug where multiple TGs got assigned to dial 2023-09-18 16:53:43 +01:00
Simon 763c240488 Allow setting DIAL to 0 to disable 2023-09-17 19:54:37 +01:00
Simon 1797759d03 crazy underscore 2023-09-17 17:28:51 +01:00
Simon c4ecbc4b86 Only look for peers in MASTER type 2023-09-17 17:08:03 +01:00
Simon 830d4b3441 Falsicle 2023-09-17 15:51:08 +01:00
Simon c60d1a53aa Working 2023-09-17 15:50:37 +01:00
Simon 708599551e systemkey working 2023-09-17 14:42:19 +01:00
Simon 4f7cd970b5 Mostly complete but untested API 2023-09-16 23:40:48 +01:00
Simon c086ff60fe Merge branch 'master' into api 2023-09-14 18:00:16 +01:00
Simon 1ff6419804 Remove default talkgroups and dial-a-tg options as these can cause server issues. 2023-09-14 17:37:05 +01:00
Simon 6ec6a0d2bf Stop stat trimmer killing bridges that are in use by statics. 2023-09-05 22:14:28 +01:00
Simon 23786bc690 Fix regression where statics are not working 2023-08-27 22:28:50 +01:00
Simon 4ca991f98b working on it 2023-08-16 17:37:30 +01:00
Simon 448c853760 f 2023-08-12 16:57:19 +01:00
Simon e4f43da5b5 import randint 2023-08-12 16:47:39 +01:00
Simon 92c14de40a typo 2023-08-12 16:39:22 +01:00
Simon a402d424cb options key work to plug a security hole and in preparation for API 2023-08-12 16:11:19 +01:00
Simon 184cef1e89 fix logging on dissallowed because of reset. 2023-07-23 12:25:39 +01:00
Simon 27f31fe079 Reduce STAT trimmer to 303 seconds 2023-06-30 14:47:04 +01:00
Simon 4220ab6aea indent 2023-06-21 01:36:54 +01:00
Simon eb13aef68d df 2023-06-21 01:32:32 +01:00
Simon 01def8e20d resetty 2023-06-21 01:26:08 +01:00
Simon c037e136ae trying 2023-06-21 00:59:23 +01:00
Simon 58fba9e135 Add prohibited TGs to startup as well as options 2023-06-21 00:29:21 +01:00
Simon 5e8bf5ae4e illegal bridge killer 2023-06-21 00:23:38 +01:00
Simon 293a8e4642 try 2023-06-20 19:46:59 +01:00
Simon 90d7d657bd Further refine bridge corrrection for dial 2023-06-20 19:33:38 +01:00
Simon 4b46079ea8 debu bridge debug - lol 2023-06-20 19:12:19 +01:00
Simon b4b9d51ae0 fkjfkdj 2023-06-19 23:56:05 +01:00
Simon 5794bb1cfe froo 2023-06-19 23:51:14 +01:00
Simon 07b93d3e35 fixy 2023-06-19 23:36:49 +01:00
Simon 387b5752b8 Make the bridge debugger actually work ;-) 2023-06-19 23:09:07 +01:00
Simon 47ad817d3b flip debug back to true. Add timer reset as well to bridge debug 2023-06-19 18:14:13 +01:00
Simon 7fb03aa839 Switch default for DEBUG_BRIDGES from True to False 2023-06-19 17:37:40 +01:00
Simon 46883e4413 bridge reset logging at info level 2023-06-18 23:18:08 +01:00
Simon 1bd7d3a16c run bridge resetter every 6 seconds 2023-06-18 23:09:31 +01:00
Simon c333873d32 Separate out bridge resetter and run more frequently 2023-06-18 20:49:43 +01:00
Simon c250bf0dc3 Only set reset if peers have been deleted in this cycle 2023-06-18 20:31:54 +01:00
Simon 048cf14545 Fix bridge reset on timeout or logout (bug found) 2023-06-18 20:22:43 +01:00
Simon 6e52a72c4f Limit downloaded files at 50Mb. 2023-06-12 17:57:30 +01:00
Simon a639543d8d Don't allow dial to TG 6 2023-06-11 16:27:45 +01:00
Simon 019744cdba clear 2023-06-04 23:52:00 +01:00
Simon 83c0ff9420 refine rptl 2023-06-04 23:31:46 +01:00
Simon dd79311f7a Flish 2023-06-04 18:31:41 +01:00
Simon 97aaef1a94 blocklist flush 2023-06-04 18:05:14 +01:00
Simon cfd8fd1dcd fklfk 2023-06-04 17:37:49 +01:00
Simon 77e2000317 600 2023-06-04 17:36:29 +01:00
Simon 90a918d432 bltime 2023-06-04 17:35:22 +01:00
Simon ad558e0773 Make rptl CLEAR 2023-06-04 17:19:35 +01:00
Simon 797226a6c1 Login requests too fast - protection for proxy 2023-06-04 16:57:00 +01:00
Simon 1be27fb828 Add prohibited TG list 2023-05-31 18:07:36 +01:00
Simon 0eb8d8853f Revert "Add priv-control to default install"
This reverts commit 0ff470d35c.
2023-05-13 11:09:43 +01:00
Simon 0ff470d35c Add priv-control to default install 2023-05-13 11:07:22 +01:00
Simon 347b81c8eb We don't want to push to docker hub anymore 2023-05-12 11:06:02 +01:00
Simon 534158f1ab build properly! 2023-05-12 11:01:06 +01:00
Simon c375bdf7bb group name not gid 2023-05-12 10:18:04 +01:00
Simon 599a18f352 Add freedmr group to container 2023-05-12 09:34:59 +01:00
Simon 8a9f624020 Squashed commit of the following:
commit a490c68326
Author: Simon <simon@gb7fr.org.uk>
Date:   Tue May 9 19:12:11 2023 +0100

    flkdlkf

commit 807887ac81
Author: Simon <simon@gb7fr.org.uk>
Date:   Tue May 9 19:08:22 2023 +0100

    klkl

commit 23c9a59a8e
Author: Simon <simon@gb7fr.org.uk>
Date:   Tue May 9 19:04:32 2023 +0100

    dd

commit ff8b8ee83f
Author: Simon <simon@gb7fr.org.uk>
Date:   Tue May 9 19:00:05 2023 +0100

    klkdlk

commit 30b72208c7
Author: Simon <simon@gb7fr.org.uk>
Date:   Tue May 9 18:51:47 2023 +0100

    typo

commit fa2a5bf105
Author: Simon <simon@gb7fr.org.uk>
Date:   Tue May 9 18:48:14 2023 +0100

    gmgm

commit b300a12731
Author: Simon <simon@gb7fr.org.uk>
Date:   Tue May 9 18:37:35 2023 +0100

    don't need self?

commit f9fec3ed68
Author: Simon <simon@gb7fr.org.uk>
Date:   Tue May 9 18:32:26 2023 +0100

    Trying to get callinthread correct

commit 9a47064d56
Author: Simon <simon@gb7fr.org.uk>
Date:   Tue May 9 17:40:44 2023 +0100

    priv helper

commit 0b13165159
Author: Simon <simon@gb7fr.org.uk>
Date:   Tue May 9 17:36:22 2023 +0100

    ph

commit fc9e9dcbd9
Author: Simon <simon@gb7fr.org.uk>
Date:   Tue May 9 12:03:35 2023 +0100

    fix

commit e043fd4c82
Author: Simon <simon@gb7fr.org.uk>
Date:   Tue May 9 10:09:38 2023 +0100

    Create /run/priv_control in preparation for priv_control unix socket

commit 49df720303
Author: Simon <simon@gb7fr.org.uk>
Date:   Tue May 9 10:02:08 2023 +0100

    Add Pyro5 to requirements

commit 2ff6b49fb5
Author: Simon <simon@gb7fr.org.uk>
Date:   Mon May 8 22:21:58 2023 +0100

    ff

commit 44cf8e3182
Author: Simon <simon@gb7fr.org.uk>
Date:   Mon May 8 22:17:11 2023 +0100

    Add Pyro5 to Proxy

commit c70711deac
Merge: 7b13b9f b373917
Author: Simon <simon@gb7fr.org.uk>
Date:   Mon May 8 22:16:50 2023 +0100

    Merge branch 'master' into testing

commit 7b13b9f046
Author: Simon <simon@gb7fr.org.uk>
Date:   Sun Jan 29 19:01:33 2023 +0000

    SERVER_ID is bytes!

commit cee3bc76fb
Author: Simon <simon@gb7fr.org.uk>
Date:   Sun Jan 29 18:50:06 2023 +0000

    frog

commit 82432b9c2c
Author: Simon <simon@gb7fr.org.uk>
Date:   Sun Jan 29 18:49:53 2023 +0000

    fred

commit 6601573c7f
Author: Simon <simon@gb7fr.org.uk>
Date:   Sun Jan 29 18:39:08 2023 +0000

    Stringly

commit 28fa37f828
Author: Simon <simon@gb7fr.org.uk>
Date:   Sun Jan 29 18:30:37 2023 +0000

    self

commit 3e6d28d4dd
Author: Simon <simon@gb7fr.org.uk>
Date:   Sun Jan 29 18:23:42 2023 +0000

    Fix trace

commit a15901dc79
Author: Simon <simon@gb7fr.org.uk>
Date:   Sun Jan 29 18:21:30 2023 +0000

    Tweak config file used in install script

commit 654ec135ca
Merge: f75ff26 d4e3922
Author: Simon <simon@gb7fr.org.uk>
Date:   Sun Jan 29 17:48:22 2023 +0000

    Merge branch 'master' into testing

commit f75ff26cfa
Merge: c0b5216 48339d3
Author: Simon <simon@gb7fr.org.uk>
Date:   Sun Jan 29 17:46:07 2023 +0000

    Merge branch 'master' into testing

commit c0b5216e5a
Author: Simon <simon@gb7fr.org.uk>
Date:   Sun Jan 29 11:04:45 2023 +0000

    more config work

commit c79ce0551d
Author: Simon <simon@gb7fr.org.uk>
Date:   Sun Jan 29 00:54:56 2023 +0000

    ib

commit 294a09c8f1
Author: Simon <simon@gb7fr.org.uk>
Date:   Sun Jan 29 00:50:36 2023 +0000

    Enable minimal config and tidy up global ACL

commit d1dc58d46f
Author: Simon <simon@gb7fr.org.uk>
Date:   Sat Jan 28 23:12:41 2023 +0000

    Deprecate protocol versions 2 and 3
2023-05-09 23:10:16 +01:00
Simon f43b199b9b dd 2023-05-08 23:45:29 +01:00
Simon 5f370d54cf dlkdlk 2023-05-08 23:12:35 +01:00
Simon 51f3e2065b fff 2023-05-08 22:53:57 +01:00
Simon 96cf3ff7bb dd 2023-05-08 22:40:59 +01:00
Simon 8325f4a819 a 2023-05-08 22:40:49 +01:00
Simon b3739178ce Trap weird error reported by N2CID so we can see what's going on
Note this error appears to be in HBLink3 as well and may be related to
Python or library version
2023-05-05 00:06:26 +01:00
Simon 93a3fc2443 Add docker freedmr.cfg example 2023-04-15 12:27:58 +01:00
Simon 5e6c533b94 Allow 1000 bytes of tolerance in downloaded files 2023-04-05 01:09:19 +01:00
Simon 3d3cae5e1d Fix extranous logging in bridge debug 2023-03-22 17:59:42 +00:00
Simon 8517b7380d hh 2023-03-22 17:49:14 +00:00
Simon 2e87da2633 Tidy up bride debugging 2023-03-22 17:42:15 +00:00
Simon 5b18073e3a Make bridge debugging and fixing the default 2023-03-22 17:39:08 +00:00
Simon 18e038a873 Merge branch 'fixbridges' 2023-03-22 17:35:20 +00:00
Simon 77815f3182 Fix bridge debug and fix routine 2023-03-22 17:34:15 +00:00
Simon 1ba044b48a Revert "Add fix routine to bridge debug"
This reverts commit d9f6a6506f.
2023-03-22 13:20:46 +00:00
Simon 4ce46b299d Revert "fix == = ="
This reverts commit a653e7d7c4.
2023-03-22 13:20:45 +00:00
Simon 370a762bed Revert "fkljlkj"
This reverts commit 3e7cc7ea71.
2023-03-22 13:20:42 +00:00
Simon 3e7cc7ea71 fkljlkj 2023-03-22 13:13:41 +00:00
Simon a653e7d7c4 fix == = = 2023-03-22 13:10:31 +00:00
Simon d9f6a6506f Add fix routine to bridge debug 2023-03-22 12:59:54 +00:00
Simon a728854789 fix trace 2023-03-22 11:22:35 +00:00
Simon 13d642402e Enhance the bridge debugger 2023-03-22 10:58:47 +00:00
Simon 9176665347 Report keyerror ir reset_static_tg() better 2023-03-20 00:30:49 +00:00
Simon d3ca3d0e0f Add blacklist on rate drop
Fix es_ES voices
2023-03-13 00:35:35 +00:00
33 changed files with 973 additions and 216 deletions

View File

@ -26,7 +26,7 @@ build-dev: # This job runs in the build stage, which runs first.
# - docker buildx build --no-cache -f Dockerfile-proxy -t hacknix/freedmr:development-latest -t gitlab.hacknix.net:5050/hacknix/freedmr:development-latest --platform linux/arm/v7,linux/amd64,linux/i386,linux/arm64 --push .
#- docker login -u $CI_DEPLOY_USER -p $CI_DEPLOY_PASSWORD $CI_REGISTRY
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker buildx build --no-cache -f docker-configs/Dockerfile-ci -t hacknix/freedmr:development-latest -t $CI_REGISTRY/hacknix/freedmr:development-latest --platform linux/arm/v7,linux/amd64,linux/i386,linux/arm64 --push .
- docker buildx build --no-cache -f docker-configs/Dockerfile-ci -t $CI_REGISTRY/hacknix/freedmr:development-latest --platform linux/arm/v7,linux/amd64,linux/i386,linux/arm64 --push .
# - docker buildx build --no-cache -f docker-configs/Dockerfile-ci -t hacknix/freedmr:development-latest -t $CI_REGISTRY/hacknix/freedmr:development-latest --platform linux/amd64,linux/i386 --push .
@ -41,7 +41,7 @@ build-extrastats: # This job runs in the build stage, which runs first.
script:
- echo "Compiling the code..."
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker buildx build --no-cache -f Dockerfile-ci -t hacknix/freedmr:extrastats-development-latest -t gitlab.hacknix.net:5050/hacknix/freedmr:extrastats-development-latest --platform linux/amd64 --push .
- docker buildx build --no-cache -f Dockerfile-ci -t gitlab.hacknix.net:5050/hacknix/freedmr:extrastats-development-latest --platform linux/amd64 --push .
- echo "Compile complete."
only:
- extrastats2
@ -53,7 +53,7 @@ build-testing: # This job runs in the build stage, which runs first.
script:
- echo "Compiling the code..."
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker buildx build --no-cache -f docker-configs/Dockerfile-ci -t gitlab.hacknix.net:5050/hacknix/freedmr:testing --platform linux/amd64 --push .
- docker buildx build --no-cache -f docker-configs/Dockerfile-ci -t $CI_REGISTRY/hacknix/freedmr:testing --platform linux/amd64 --push .
only:
- testing
@ -75,11 +75,13 @@ build-release: # This job runs in the build stage, which runs first.
script:
- echo "Compiling the code..."
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker buildx build --no-cache -f docker-configs/Dockerfile-ci -t hacknix/freedmr:latest -t gitlab.hacknix.net:5050/hacknix/freedmr:latest -t hacknix/$CI_COMMIT_TAG-with-proxy -t gitlab.hacknix.net:5050/hacknix/freedmr:$CI_COMMIT_TAG-with-proxy -t hacknix/freedmr:development-latest -t gitlab.hacknix.net:5050/hacknix/freedmr:development-latest --platform linux/arm/v7,linux/amd64,linux/i386,linux/arm64 --push .
- docker buildx build --no-cache -f docker-configs/Dockerfile-ci -t gitlab.hacknix.net:5050/hacknix/freedmr:latest -t gitlab.hacknix.net:5050/hacknix/freedmr:$CI_COMMIT_TAG-with-proxy -t gitlab.hacknix.net:5050/hacknix/freedmr:development-latest --platform linux/arm/v7,linux/amd64,linux/i386,linux/arm64 --push .
#- docker buildx build --no-cache -f docker-configs/Dockerfile-ci -t hacknix/freedmr:latest -t gitlab.hacknix.net:5050/hacknix/freedmr:latest -t hacknix/$CI_COMMIT_TAG-with-proxy -t gitlab.hacknix.net:5050/hacknix/freedmr:$CI_COMMIT_TAG-with-proxy -t hacknix/freedmr:development-latest -t gitlab.hacknix.net:5050/hacknix/freedmr:development-latest --platform linux/amd64,linux/i386 --push .
- echo "Compile complete."
release:
tag_name: $CI_COMMIT_TAG-with-proxy
name: 'Release $CI_COMMIT_TAG-with-proxy'
description: 'Release created using CI.'
only:
- tags

148
API.py Normal file
View File

@ -0,0 +1,148 @@
#
###############################################################################
# Copyright (C) 2023 Simon Adlem, G7RZU <g7rzu@gb7fr.org.uk>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
###############################################################################
from spyne import ServiceBase, rpc, Integer, Decimal, UnsignedInteger32, Unicode, Iterable, error
from dmr_utils3.utils import bytes_3, bytes_4
class FD_APIUserDefinedContext(object):
def __init__(self,CONFIG,BRIDGES):
self.CONFIG = CONFIG
self.BRIDGES = BRIDGES
def getconfig(self):
return self.CONFIG
def getbridges(self):
return self.BRIDGES
def validateKey(self,dmrid,key):
systems = self.CONFIG['SYSTEMS']
dmrid = bytes_4(dmrid)
print(dmrid)
for system in systems:
if systems[system]['MODE'] == 'MASTER':
for peerid in systems[system]['PEERS']:
print(peerid)
if peerid == dmrid:
try:
if key == systems[system]['_opt_key']:
return(system)
else:
return(False)
except KeyError:
return(False)
return(False)
def validateSystemKey(self,systemkey):
if systemkey == self.CONFIG['GLOBAL']['SYSTEM_API_KEY']:
return True
else:
return False
def reset(self,system):
self.CONFIG['SYSTEMS'][system]['_reset'] = True
def options(self,system,options):
self.CONFIG['SYSTEMS'][system]['OPTIONS'] = options
def getoptions(self,system):
return self.CONFIG['SYSTEMS'][system]['OPTIONS']
def killserver(self):
self.CONFIG['GLOBAL']['_KILL_SERVER'] = True
def resetAllConnections(self):
systems = self.CONFIG['SYSTEMS']
for system in systems:
self.CONFIG['SYSTEMS'][system]['_reset'] = True
class FD_API(ServiceBase):
_version = 0.1
#return API version
@rpc(Unicode, _returns=Decimal())
def version(ctx, sessionid):
return(FD_API._version)
@rpc()
def dummy(ctx):
pass
######################
#User level API calls#
######################
@rpc(UnsignedInteger32,Unicode)
def reset(ctx,dmrid,key):
system = ctx.udc.validateKey(int(dmrid),key)
if system:
ctx.udc.reset(system)
else:
raise error.InvalidCredentialsError()
@rpc(UnsignedInteger32,Unicode,Unicode)
def setoptions(ctx,dmrid,key,options):
system = ctx.udc.validateKey(int(dmrid),key)
if system:
ctx.udc.options(system,options)
else:
raise error.InvalidCredentialsError()
@rpc(UnsignedInteger32,Unicode,_returns=Unicode())
def getoptions(ctx,dmrid,key):
system = ctx.udc.validateKey(int(dmrid),key)
if system:
return ctx.udc.getoptions(system)
else:
raise error.InvalidCredentialsError()
########################
#System level API calls#
########################
@rpc(Unicode)
def killserver(ctx,systemkey):
if ctx.udc.validateSystemKey(systemkey):
return ctx.udc.killserver()
else:
raise error.InvalidCredentialsError()
@rpc(Unicode)
def resetall(ctx,systemkey):
if ctx.udc.validateSystemKey(systemkey):
return ctx.udc.resetAllConnections()
else:
raise error.InvalidCredentialsError()
@rpc(Unicode,_returns=Unicode())
def getconfig(ctx,systemkey):
if ctx.udc.validateSystemKey(systemkey):
return ctx.udc.getconfig()
else:
raise error.InvalidCredentialsError()
@rpc(Unicode,_returns=Unicode())
def getbridges(ctx,systemkey):
if ctx.udc.validateSystemKey(systemkey):
return ctx.udc.getbridges()
else:
raise error.InvalidCredentialsError()

BIN
Audio/es_ES/0.ambe Normal file

Binary file not shown.

BIN
Audio/es_ES/1.ambe Normal file

Binary file not shown.

BIN
Audio/es_ES/2.ambe Normal file

Binary file not shown.

BIN
Audio/es_ES/3.ambe Normal file

Binary file not shown.

BIN
Audio/es_ES/4.ambe Normal file

Binary file not shown.

BIN
Audio/es_ES/5.ambe Normal file

Binary file not shown.

BIN
Audio/es_ES/6.ambe Normal file

Binary file not shown.

6
Audio/es_ES/7.ambe Normal file
View File

@ -0,0 +1,6 @@
°Î†­²?à×¢Bp¥v5ã<0F>]#t¥ÝÑ÷Áb9òý;©ç@
!ßAÛ@¡dL ÕÜíúb biÇéϺÄaI#5îw™Æg !Ï!¼]£% ¼]Œ~ %+œ|¡a:-ž~i­†×®6>\‡®îwm#$+¢}p$\:gïp¡fQɰ*b¾XT§™÷-^ª]#•d—%¢fº
ÎXŒûDat#ÂI"yCt¡ÈÇ=•uq¦ =…6bPæy\\à"U“òáæ˜åGPVyˆ+oHŽ«ü©ÜE+Hy¢­®?^zlRæôlH—nB¶X#X”nR„]gëâs;¸»Û±]ú "ÕR<C395>ö\Î(í<>g
=XóŸãâ"L ­'K<>p
ä]¡yb´B ¢ <0C>)åHŒ
§Ig8ˆÉ ìÊvŠ ‰dš+ï%)«äR"¦}wr'£e7(}ýY

BIN
Audio/es_ES/8.ambe Normal file

Binary file not shown.

BIN
Audio/es_ES/9.ambe Normal file

Binary file not shown.

View File

@ -30,11 +30,11 @@ SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:ALL
TGID_TS2_ACL: PERMIT:ALL
DEFAULT_UA_TIMER: 60
SINGLE_MODE: True
VOICE_IDENT: True
TS1_STATIC:
TS2_STATIC:
DEFAULT_REFLECTOR: 0
SINGLE_MODE: True
VOICE_IDENT: True
ANNOUNCEMENT_LANGUAGE: en_GB
GENERATOR: 100
ALLOW_UNREG_ID: False

View File

@ -201,6 +201,7 @@ TGID_TS2_ACL: PERMIT:ALL
DEFAULT_UA_TIMER: 10
SINGLE_MODE: True
VOICE_IDENT: True
#the next three lines no longer have any effect
TS1_STATIC:
TS2_STATIC:
DEFAULT_REFLECTOR: 0

23
api_client.py Normal file
View File

@ -0,0 +1,23 @@
from twisted.internet import reactor
from twisted.web.xmlrpc import Proxy
def printValue(value):
print(repr(value))
reactor.stop()
def printError(error):
print("error", error)
reactor.stop()
def capitalize(value):
print(value)
proxy = Proxy(b"http://localhost:7080/xmlrpc")
# The callRemote method accepts a method name and an argument list.
proxy.callRemote("FD_API.reset", '2', '55555').addCallbacks(capitalize, printError)
reactor.run()

View File

@ -791,7 +791,11 @@ class routerHBP(HBSYSTEM):
systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore'))
# Create a Burst B-E packet (Embedded LC)
elif _dtype_vseq in [1,2,3,4]:
dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264]
#catch weird bug, so we can work out what's going on (N2CID)
try:
dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264]
except Exception as e:
logger.warning('(N2CID) Caught error [non-fatal] %s',e)
dmrpkt = dmrbits.tobytes()
_tmp_data = b''.join([_tmp_data, dmrpkt, _data[53:55]])

View File

@ -40,6 +40,8 @@ import re
import copy
from setproctitle import setproctitle
from collections import deque
from random import randint
import secrets
#from crccheck.crc import Crc32
from hashlib import blake2b
@ -48,6 +50,15 @@ from hashlib import blake2b
from twisted.internet.protocol import Factory, Protocol
from twisted.protocols.basic import NetstringReceiver
from twisted.internet import reactor, task
from twisted.web.server import Site
from spyne import Application
from spyne.server.twisted import TwistedWebResource
from spyne.protocol.http import HttpRpc
from spyne.protocol.json import JsonDocument
# Things we import from the main hblink module
from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, mk_aliases, acl_check
@ -58,6 +69,7 @@ from config import acl_build
import log
from const import *
from mk_voice import pkt_gen
from utils import load_json, save_json
#from voice_lib import words
#Read voices
@ -75,10 +87,13 @@ logger = logging.getLogger(__name__)
#REGEX
import re
#pretty print
import pprint
from binascii import b2a_hex as ahex
from AMI import AMI
from API import FD_API, FD_APIUserDefinedContext
# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
__author__ = 'Cortney T. Buffington, N0MJS, Forked by Simon Adlem - G7RZU'
@ -132,6 +147,27 @@ def config_reports(_config, _factory):
return report_server
# Start API server
def config_API(_config, _bridges):
application = Application([FD_API],
tns='freedmr.api',
in_protocol=HttpRpc(validator='soft'),
out_protocol=JsonDocument()
)
def _on_method_call(ctx):
ctx.udc = FD_APIUserDefinedContext(CONFIG,_bridges)
application.event_manager.add_listener('method_call', _on_method_call)
resource = TwistedWebResource(application)
site = Site(resource)
r = reactor.listenTCP(8000, site, interface='0.0.0.0')
return(r)
# Import Bridging rules
# Note: A stanza *must* exist for any MASTER or CLIENT configured in the main
@ -229,7 +265,7 @@ def make_default_reflector(reflector,_tmout,system):
if bridge not in BRIDGES:
BRIDGES[bridge] = []
make_single_reflector(bytes_3(reflector),_tmout, system)
bridgetemp = deque()
bridgetemp = []
for bridgesystem in BRIDGES[bridge]:
if bridgesystem['SYSTEM'] == system and bridgesystem['TS'] == 2:
bridgetemp.append({'SYSTEM': system, 'TS': 2, 'TGID': bytes_3(9),'ACTIVE': True,'TIMEOUT': _tmout * 60,'TO_TYPE': 'OFF','OFF': [],'ON': [bytes_3(reflector),],'RESET': [], 'TIMER': time() + (_tmout * 60)})
@ -242,7 +278,7 @@ def make_static_tg(tg,ts,_tmout,system):
#_tmout = CONFIG['SYSTEMS'][system]['DEFAULT_UA_TIMER']
if str(tg) not in BRIDGES:
make_single_bridge(bytes_3(tg),system,ts,_tmout)
bridgetemp = deque()
bridgetemp = []
for bridgesystem in BRIDGES[str(tg)]:
if bridgesystem['SYSTEM'] == system and bridgesystem['TS'] == ts:
bridgetemp.append({'SYSTEM': system, 'TS': ts, 'TGID': bytes_3(tg),'ACTIVE': True,'TIMEOUT': _tmout * 60,'TO_TYPE': 'OFF','OFF': [],'ON': [bytes_3(tg),],'RESET': [], 'TIMER': time() + (_tmout * 60)})
@ -253,7 +289,7 @@ def make_static_tg(tg,ts,_tmout,system):
def reset_static_tg(tg,ts,_tmout,system):
#_tmout = CONFIG['SYSTEMS'][system]['DEFAULT_UA_TIMER']
bridgetemp = deque()
bridgetemp = []
try:
for bridgesystem in BRIDGES[str(tg)]:
if bridgesystem['SYSTEM'] == system and bridgesystem['TS'] == ts:
@ -263,23 +299,53 @@ def reset_static_tg(tg,ts,_tmout,system):
BRIDGES[str(tg)] = bridgetemp
except KeyError:
logger.exception('(ERROR) KeyError in reset_static_tg() - bridge gone away?')
logger.exception('(%s) KeyError in reset_static_tg() - bridge gone away? TG: %s',system,tg)
return
def reset_default_reflector(reflector,_tmout,system):
bridge = ''.join(['#',str(reflector)])
#_tmout = CONFIG['SYSTEMS'][system]['DEFAULT_UA_TIMER']
if bridge not in BRIDGES:
BRIDGES[bridge] = []
make_single_reflector(bytes_3(reflector),_tmout, system)
bridgetemp = deque()
for bridgesystem in BRIDGES[bridge]:
if bridgesystem['SYSTEM'] == system and bridgesystem['TS'] == 2:
bridgetemp.append({'SYSTEM': system, 'TS': 2, 'TGID': bytes_3(9),'ACTIVE': False,'TIMEOUT': _tmout * 60,'TO_TYPE': 'ON','OFF': [],'ON': [bytes_3(reflector),],'RESET': [], 'TIMER': time() + (_tmout * 60)})
else:
bridgetemp.append(bridgesystem)
BRIDGES[bridge] = bridgetemp
#def reset_default_reflector(reflector,_tmout,system):
#print(reflector)
#bridge = ''.join(['#',str(reflector)])
#print(bridge)
##_tmout = CONFIG['SYSTEMS'][system]['DEFAULT_UA_TIMER']
#if bridge not in BRIDGES:
#BRIDGES[bridge] = []
#make_single_reflector(bytes_3(reflector),_tmout, system)
#bridgetemp = deque()
#for bridgesystem in BRIDGES[bridge]:
#print(bridgesystem)
#if bridgesystem['SYSTEM'] == system and bridgesystem['TS'] == 2:
#print(bridgesystem)
#bridgetemp.append({'SYSTEM': system, 'TS': 2, 'TGID': bytes_3(9),'ACTIVE': False,'TIMEOUT': _tmout * 60,'TO_TYPE': 'ON','OFF': [],'ON': [bytes_3(reflector),],'RESET': [], 'TIMER': time() + (_tmout * 60)})
#else:
#bridgetemp.append(bridgesystem)
#print(bridgetemp)
#BRIDGES[bridge] = bridgetemp
#print(BRIDGES[bridge])
def reset_all_reflector_system(_tmout,resetSystem):
logger.trace('RST: In reset_all_reflector_system - timeout: %s, resetSystem: %s',_tmout,resetSystem)
bt = {}
for system in CONFIG['SYSTEMS']:
logger.trace('RST: for %s in SYSTEMS',system)
for bridge in BRIDGES:
logger.trace('RST: for %s in BRIDGES',bridge)
if bridge[0:1] == '#':
bridgetemp = []
for bridgesystem in BRIDGES[bridge]:
logger.trace('RST: for %s in BRIDGES[%s]',bridgesystem,bridge)
if bridgesystem['SYSTEM'] == resetSystem and bridgesystem['TS'] == 2:
logger.trace('RST: MATCH: setting inactive for %s',bridgesystem['SYSTEM'])
bridgetemp.append({'SYSTEM': resetSystem, 'TS': 2, 'TGID': bytes_3(9),'ACTIVE': False,'TIMEOUT': _tmout * 60,'TO_TYPE': 'ON','OFF': [],'ON': [bytes_3(int(bridge[1:])),],'RESET': [], 'TIMER': time() + (_tmout * 60)})
else:
logger.trace('RST: NO MATCH: using existing: %s',bridgesystem)
bridgetemp.append(bridgesystem)
logger.trace('RST: bridgetemp %s',bridgetemp)
#BRIDGES[bridge] = bridgetemp
bt[bridge] = bridgetemp
for bridge in bt:
BRIDGES[bridge] = bt[bridge]
def make_single_reflector(_tgid,_tmout,_sourcesystem):
_tgid_s = str(int_id(_tgid))
_bridge = ''.join(['#',_tgid_s])
@ -298,22 +364,53 @@ def make_single_reflector(_tgid,_tmout,_sourcesystem):
if _system[0:3] == 'OBP' and (int_id(_tgid) >= 79 and (int_id(_tgid) < 9990 or int_id(_tgid) > 9999)):
BRIDGES[_bridge].append({'SYSTEM': _system, 'TS': 1, 'TGID': _tgid,'ACTIVE': True,'TIMEOUT': '','TO_TYPE': 'NONE','OFF': [],'ON': [],'RESET': [], 'TIMER': time()})
def remove_bridge_system(system):
#def remove_bridge_system(system):
#_bridgestemp = {}
#_bridgetemp = {}
#for _bridge in BRIDGES:
#for _bridgesystem in BRIDGES[_bridge]:
#if _bridgesystem['SYSTEM'] != system:
#if _bridge not in _bridgestemp:
#_bridgestemp[_bridge] = []
#_bridgestemp[_bridge].append(_bridgesystem)
#else:
#if _bridge not in _bridgestemp:
#_bridgestemp[_bridge] = []
#_bridgestemp[_bridge].append({'SYSTEM': system, 'TS': _bridgesystem['TS'], 'TGID': _bridgesystem['TGID'],'ACTIVE': False,'TIMEOUT': _bridgesystem['TIMEOUT'],'TO_TYPE': 'ON','OFF': [],'ON': [_bridgesystem['TGID'],],'RESET': [], 'TIMER': time() + _bridgesystem['TIMEOUT']})
def remove_bridge_system(remsystem):
bt = {}
for system in CONFIG['SYSTEMS']:
for bridge in BRIDGES:
bridgetemp = []
for bridgesystem in BRIDGES[bridge]:
if bridgesystem['SYSTEM'] == remsystem:
bridgetemp.append({'SYSTEM': system, 'TS': bridgesystem['TS'], 'TGID': bridgesystem['TGID'],'ACTIVE': False,'TIMEOUT': bridgesystem['TIMEOUT'],'TO_TYPE': 'ON','OFF': [],'ON': [bridgesystem['TGID'],],'RESET': [], 'TIMER': time() + bridgesystem['TIMEOUT']})
logger.debug('RBS False: %s: %s',system, {'SYSTEM': system, 'TS': bridgesystem['TS'], 'TGID': bridgesystem['TGID'],'ACTIVE': False,'TIMEOUT': bridgesystem['TIMEOUT'],'TO_TYPE': 'ON','OFF': [],'ON': [bridgesystem['TGID'],],'RESET': [], 'TIMER': time() + bridgesystem['TIMEOUT']} )
else:
bridgetemp.append(bridgesystem)
logger.debug('RBS: existing %s',bridgesystem)
bt[bridge] = bridgetemp
for bridge in bt:
BRIDGES[bridge] = bt[bridge]
def update_timeout(system,_tmout):
_bridgestemp = {}
_bridgetemp = {}
for _bridge in BRIDGES:
for _bridgesystem in BRIDGES[_bridge]:
if _bridgesystem['SYSTEM'] != system:
if _bridge not in _bridgestemp:
_bridgestemp[_bridge] = []
_bridgestemp[_bridge].append(_bridgesystem)
continue
else:
if _bridge not in _bridgestemp:
_bridgestemp[_bridge] = []
_bridgestemp[_bridge].append({'SYSTEM': system, 'TS': _bridgesystem['TS'], 'TGID': _bridgesystem['TGID'],'ACTIVE': False,'TIMEOUT': _bridgesystem['TIMEOUT'],'TO_TYPE': 'ON','OFF': [],'ON': [_bridgesystem['TGID'],],'RESET': [], 'TIMER': time() + _bridgesystem['TIMEOUT']})
_bridgesystem['TIMEOUT'] = _tmout * 60
_bridgestemp[_bridge].append(_bridgesystem)
BRIDGES.update(_bridgestemp)
# Run this every minute for rule timer updates
def rule_timer_loop():
@ -359,7 +456,9 @@ def rule_timer_loop():
if _bridge_used == False:
_remove_bridges.append(_bridge)
pretty = pprint.pformat(BRIDGES)
logger.debug('(ROUTER) BRIDGES: %s',pretty)
for _bridgerem in _remove_bridges:
del BRIDGES[_bridgerem]
logger.debug('(ROUTER) Unused conference bridge %s removed',_bridgerem)
@ -378,7 +477,7 @@ def statTrimmer():
_bridge_stat = True
if _system['TO_TYPE'] == 'ON' and _system['ACTIVE']:
_in_use = True
elif _system['TO_TYPE'] == 'OFF' and not _system['ACTIVE']:
elif _system['TO_TYPE'] == 'OFF':
_in_use = True
if _bridge_stat and not _in_use:
_remove_bridges.append(_bridge)
@ -388,17 +487,66 @@ def statTrimmer():
if CONFIG['REPORTS']['REPORT']:
report_server.send_clients(b'bridge updated')
#Identify systems with no bridges
#Debug and fix bridge table issues.
def bridgeDebug():
logger.info('(BRIDGEDEBUG) Running bridge debug')
logger.debug('(BRIDGEDEBUG) Running bridge debug')
_rst_time = time()
statroll = 0
#Kill off any bridges that should not exist, ever.
for b in ['0','1','2','3','4','5','6','7','8','9']:
BRIDGES.pop(b,None)
BRIDGES.pop('#'+b, None)
for system in CONFIG['SYSTEMS']:
bridgeroll = 0
for bridge in BRIDGES:
for enabled_system in BRIDGES[bridge]:
if enabled_system == system:
dialroll = 0
activeroll = 0
for _bridge in BRIDGES:
for enabled_system in BRIDGES[_bridge]:
if enabled_system['SYSTEM'] == system:
bridgeroll += 1
if not bridgeroll:
logger.warning('{BRIDGEDEBUG) system %s has no bridges', system)
if enabled_system['ACTIVE']:
if _bridge and _bridge[0:1] == '#':
dialroll += 1
activeroll += 1
else:
activeroll += 1
if enabled_system['TO_TYPE'] == 'STAT':
statroll += 1
if bridgeroll:
logger.debug('(BRIDGEDEBUG) system %s has %s bridges of which %s are in an ACTIVE state', system, bridgeroll, activeroll)
if dialroll > 1 and CONFIG['SYSTEMS'][system]['MODE'] == 'MASTER':
logger.warning('(BRIDGEDEBUG) system %s has more than one active dial bridge (%s) - fixing',system, dialroll)
times = {}
for _bridge in BRIDGES:
for enabled_system in BRIDGES[_bridge]:
if enabled_system['ACTIVE'] and _bridge and _bridge[0:1] == '#':
times[enabled_system['TIMER']] = _bridge
ordered = sorted(times.keys())
#bridgetmout = ordered.pop()
#_setbridge = str(times[bridgetmout])
if CONFIG['SYSTEMS'][system]['MODE'] == 'MASTER':
for _bridge in set(times.values()):
logger.warning('(BRIDGEDEBUG) deactivating system: %s for bridge: %s',system,_bridge)
bridgetemp = []
for bridgesystem in BRIDGES[_bridge]:
if _bridge[0:1] == '#':
_setbridge = int(_bridge[1:])
else:
_setbridge = int(_bridge)
if bridgesystem['SYSTEM'] == system and bridgesystem['TS'] == 2:
bridgetemp.append({'SYSTEM': system, 'TS': 2, 'TGID': bytes_3(9),'ACTIVE': False,'TIMEOUT': _tmout * 60,'TO_TYPE': 'ON','OFF': [],'ON': [bytes_3(_setbridge),],'RESET': [], 'TIMER': _rst_time + (_tmout * 60)})
else:
bridgetemp.append(bridgesystem)
BRIDGES[_bridge] = bridgetemp
logger.info('(BRIDGEDEBUG) The server currently has %s STATic bridges',statroll)
def kaReporting():
logger.debug('(ROUTER) KeepAlive reporting loop started')
@ -742,17 +890,33 @@ def ident():
_pkt_time = time()
reactor.callFromThread(sendVoicePacket,systems[system],pkt,_source_id,_dst_id,_slot)
def options_config():
logger.debug('(OPTIONS) Running options parser')
def bridge_reset():
logger.debug('(BRIDGERESET) Running bridge resetter')
for _system in CONFIG['SYSTEMS']:
if '_reset' in CONFIG['SYSTEMS'][_system] and CONFIG['SYSTEMS'][_system]['_reset']:
logger.debug('(OPTIONS) Bridge reset for %s - no peers',_system)
logger.info('(BRIDGERESET) Bridge reset for %s - no peers or API reset called',_system)
remove_bridge_system(_system)
try:
del(CONFIG['SYSTEMS'][_system]['_opt_key'])
except:
pass
CONFIG['SYSTEMS'][_system]['_reset'] = False
continue
CONFIG['SYSTEMS'][_system]['_resetlog'] = False
if 'OPTIONS' in CONFIG['SYSTEMS'][_system]['OPTIONS']:
CONFIG['SYSTEMS'][_system]['_reloadoptions'] = True
def options_config():
logger.debug('(OPTIONS) Running options parser')
prohibitedTGs = [0,1,2,3,4,5,6,7,8,9,9990,9991,9992,9993,9994,9995,9996,9997,9998,9999]
systemList = CONFIG['SYSTEMS'].keys()
for _system in systemList:
try:
if CONFIG['SYSTEMS'][_system]['MODE'] != 'MASTER':
continue
if '_reset' in CONFIG['SYSTEMS'][_system] and CONFIG['SYSTEMS'][_system]['_reset']:
continue
if CONFIG['SYSTEMS'][_system]['ENABLED'] == True:
if 'OPTIONS' in CONFIG['SYSTEMS'][_system]:
_options = {}
@ -768,6 +932,21 @@ def options_config():
continue
_options[k] = v
logger.debug('(OPTIONS) Options found for %s',_system)
if '_opt_key' in CONFIG['SYSTEMS'][_system] and CONFIG['SYSTEMS'][_system]['_opt_key']:
if 'KEY' not in _options:
logger.debug('(OPTIONS) %s, options key set but no key in options string, skipping',_system)
continue
elif CONFIG['SYSTEMS'][_system]['_opt_key'] != _options['KEY']:
logger.debug('(OPTIONS) %s, options key set but key sent does not match, skipping',_system)
continue
elif 'KEY' in _options and _options['KEY']:
logger.debug('(OPTIONS) %s, _opt_key not set but key sent. Setting to sent key',_system)
CONFIG['SYSTEMS'][_system]['_opt_key'] = _options['KEY']
else:
logger.debug('(OPTIONS) %s, _opt_key not set and no key sent. Set to false',_system)
CONFIG['SYSTEMS'][_system]['_opt_key'] = False
if 'DIAL' in _options:
_options['DEFAULT_REFLECTOR'] = _options.pop('DIAL')
@ -892,7 +1071,7 @@ def options_config():
if isinstance(_options['DEFAULT_UA_TIMER'], str) and not _options['DEFAULT_UA_TIMER'].isdigit():
logger.debug('(OPTIONS) %s - DEFAULT_REFLECTOR is not an integer, ignoring',_system)
logger.debug('(OPTIONS) %s - DEFAULT_UA_TIMER is not an integer, ignoring',_system)
continue
#if the UA timer is set to 0 - actually set it to (close to) maximum size of a 32
@ -903,37 +1082,24 @@ def options_config():
_tmout = int(_options['DEFAULT_UA_TIMER'])
if int(_options['DEFAULT_UA_TIMER']) != CONFIG['SYSTEMS'][_system]['DEFAULT_UA_TIMER']:
if ('_reloadoptions' in CONFIG['SYSTEMS'][_system] and CONFIG['SYSTEMS'][_system]['_reloadoptions']) or (int(_options['DEFAULT_UA_TIMER']) != CONFIG['SYSTEMS'][_system]['DEFAULT_UA_TIMER']):
logger.debug('(OPTIONS) %s Updating DEFAULT_UA_TIMER for existing bridges.',_system)
remove_bridge_system(_system)
for _bridge in BRIDGES:
ts1 = False
ts2 = False
for i,e in enumerate(BRIDGES[_bridge]):
if e['SYSTEM'] == _system and e['TS'] == 1:
ts1 = True
if e['SYSTEM'] == _system and e['TS'] == 2:
ts2 = True
if _bridge[0:1] != '#':
if ts1 == False:
BRIDGES[_bridge].append({'SYSTEM': _system, 'TS': 1, 'TGID': bytes_3(int(_bridge)),'ACTIVE': False,'TIMEOUT': _tmout * 60,'TO_TYPE': 'ON','OFF': [],'ON': [bytes_3(int(_bridge)),],'RESET': [], 'TIMER': time()})
if ts2 == False:
BRIDGES[_bridge].append({'SYSTEM': _system, 'TS': 2, 'TGID': bytes_3(int(_bridge)),'ACTIVE': False,'TIMEOUT': _tmout * 60,'TO_TYPE': 'ON','OFF': [],'ON': [bytes_3(int(_bridge)),],'RESET': [], 'TIMER': time()})
else:
if ts2 == False:
BRIDGES[_bridge].append({'SYSTEM': _system, 'TS': 2, 'TGID': bytes_3(9),'ACTIVE': False,'TIMEOUT': _tmout * 60,'TO_TYPE': 'ON','OFF': [bytes_3(4000)],'ON': [],'RESET': [], 'TIMER': time()})
update_timeout(_system,_tmout)
if int(_options['DEFAULT_REFLECTOR']) != CONFIG['SYSTEMS'][_system]['DEFAULT_REFLECTOR']:
if int(_options['DEFAULT_REFLECTOR']) > 0:
logger.debug('(OPTIONS) %s default reflector changed, updating',_system)
reset_default_reflector(CONFIG['SYSTEMS'][_system]['DEFAULT_REFLECTOR'],_tmout,_system)
if int(_options['DEFAULT_REFLECTOR']) in prohibitedTGs and int(_options['DEFAULT_REFLECTOR']) > 0:
logger.debug('(OPTIONS) %s default dial-a-tg is in prohibited list, ignoring change',_system)
elif int(_options['DEFAULT_REFLECTOR']) > 0:
logger.debug('(OPTIONS) %s default dial-a-tg changed, updating',_system)
reset_all_reflector_system(_tmout,_system)
make_default_reflector(int(_options['DEFAULT_REFLECTOR']),_tmout,_system)
else:
logger.debug('(OPTIONS) %s default reflector disabled, updating',_system)
reset_default_reflector(int(_options['DEFAULT_REFLECTOR']),_tmout,_system)
logger.debug('(OPTIONS) %s default dial-a-tg disabled, updating',_system)
reset_all_reflector_system(_tmout,_system)
ts1 = []
if _options['TS1_STATIC'] != CONFIG['SYSTEMS'][_system]['TS1_STATIC']:
if ('_reloadoptions' in CONFIG['SYSTEMS'][_system] and CONFIG['SYSTEMS'][_system]['_reloadoptions']) or (_options['TS1_STATIC'] != CONFIG['SYSTEMS'][_system]['TS1_STATIC']):
_tmout = int(_options['DEFAULT_UA_TIMER'])
logger.debug('(OPTIONS) %s TS1 static TGs changed, updating',_system)
ts1 = []
@ -949,10 +1115,12 @@ def options_config():
for tg in ts1:
if not tg:
continue
elif int(tg) in prohibitedTGs:
logger.debug('(OPTIONS) %s TS1 TG %s is prohibited, ignoring change',_system,tg)
tg = int(tg)
make_static_tg(tg,1,_tmout,_system)
ts2 = []
if _options['TS2_STATIC'] != CONFIG['SYSTEMS'][_system]['TS2_STATIC']:
if ('_reloadoptions' in CONFIG['SYSTEMS'][_system] and CONFIG['SYSTEMS'][_system]['_reloadoptions']) or (_options['TS2_STATIC'] != CONFIG['SYSTEMS'][_system]['TS2_STATIC']):
_tmout = int(_options['DEFAULT_UA_TIMER'])
logger.debug('(OPTIONS) %s TS2 static TGs changed, updating',_system)
if CONFIG['SYSTEMS'][_system]['TS2_STATIC']:
@ -968,6 +1136,10 @@ def options_config():
for tg in ts2:
if not tg or int(tg) == 0 or int(tg) >= 16777215:
continue
elif int(tg) in prohibitedTGs:
logger.debug('(OPTIONS) %s TS2 TG %s is prohibited, ignoring change',_system,tg)
continue
tg = int(tg)
make_static_tg(tg,2,_tmout,_system)
@ -975,6 +1147,9 @@ def options_config():
CONFIG['SYSTEMS'][_system]['TS2_STATIC'] = _options['TS2_STATIC']
CONFIG['SYSTEMS'][_system]['DEFAULT_REFLECTOR'] = int(_options['DEFAULT_REFLECTOR'])
CONFIG['SYSTEMS'][_system]['DEFAULT_UA_TIMER'] = int(_options['DEFAULT_UA_TIMER'])
if '_reloadoptions' in CONFIG['SYSTEMS'][_system] and CONFIG['SYSTEMS'][_system]['_reloadoptions']:
CONFIG['SYSTEMS'][_system]['_reloadoptions'] = False
except Exception as e:
logger.exception('(OPTIONS) caught exception: %s',e)
continue
@ -1077,13 +1252,20 @@ class routerOBP(OPENBRIDGE):
dmrbits = _target_status[_stream_id]['H_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['H_LC'][98:197]
# Create a voice terminator packet (FULL LC)
elif _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VTERM:
dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197]
try:
dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197]
except KeyError:
logger.warning('(%s) KeyError - T_LC, Skipping',system)
if CONFIG['REPORTS']['REPORT']:
call_duration = pkt_time - _target_status[_stream_id]['START']
systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore'))
# Create a Burst B-E packet (Embedded LC)
elif _dtype_vseq in [1,2,3,4]:
dmrbits = dmrbits[0:116] + _target_status[_stream_id]['EMB_LC'][_dtype_vseq] + dmrbits[148:264]
try:
dmrbits = dmrbits[0:116] + _target_status[_stream_id]['EMB_LC'][_dtype_vseq] + dmrbits[148:264]
except KeyError:
logger.warning('(%s) KeyError - EMB_LC, skipping',system)
continue
dmrpkt = dmrbits.tobytes()
_tmp_data = b''.join([_tmp_data, dmrpkt])
@ -1499,6 +1681,7 @@ class routerOBP(OPENBRIDGE):
#Rate drop
if self.STATUS[_stream_id]['packets'] > 18 and (self.STATUS[_stream_id]['packets'] / self.STATUS[_stream_id]['START'] > 25):
logger.warning("(%s) *PacketControl* RATE DROP! Stream ID:, %s TGID: %s",self._system,int_id(_stream_id),int_id(_dst_id))
self.proxy_BadPeer()
return
#Duplicate handling#
@ -1868,6 +2051,16 @@ class routerHBP(HBSYSTEM):
def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data):
try:
if CONFIG['SYSTEMS'][self._system]['_reset'] or CONFIG['SYSTEMS'][_system]['_reloadoptions']:
if not CONFIG['SYSTEMS'][self._system]['_resetlog']:
logger.info('(%s) disallow transmission until reset cycle is complete',_system)
CONFIG['SYSTEMS'][self._system]['_resetlog'] = True
return
except KeyError:
pass
pkt_time = time()
dmrpkt = _data[20:53]
@ -2055,7 +2248,7 @@ class routerHBP(HBSYSTEM):
#Handle private voice calls (for reflectors)
#Handle private voice calls (for dial-a-tg)
elif _call_type == 'unit' and not _data_call and not self.STATUS[_slot]['_allStarMode']:
if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']):
@ -2064,11 +2257,11 @@ class routerHBP(HBSYSTEM):
self.STATUS[_slot]['_stopTgAnnounce'] = False
logger.info('(%s) Reflector: Private call from %s to %s',self._system, int_id(_rf_src), _int_dst_id)
logger.info('(%s) Dial-A-TG: Private call from %s to %s',self._system, int_id(_rf_src), _int_dst_id)
if _int_dst_id >= 5 and _int_dst_id != 8 and _int_dst_id != 9 and _int_dst_id <= 999999:
_bridgename = ''.join(['#',str(_int_dst_id)])
if _bridgename not in BRIDGES and not (_int_dst_id >= 4000 and _int_dst_id <= 5000) and not (_int_dst_id >=9991 and _int_dst_id <= 9999):
logger.info('(%s) [A] Reflector for TG %s does not exist. Creating as User Activated. Timeout: %s',self._system, _int_dst_id,CONFIG['SYSTEMS'][self._system]['DEFAULT_UA_TIMER'])
logger.info('(%s) [A] Dial-A-TG for TG %s does not exist. Creating as User Activated. Timeout: %s',self._system, _int_dst_id,CONFIG['SYSTEMS'][self._system]['DEFAULT_UA_TIMER'])
make_single_reflector(_dst_id,CONFIG['SYSTEMS'][self._system]['DEFAULT_UA_TIMER'],self._system)
if _int_dst_id > 5 and _int_dst_id != 9 and _int_dst_id != 5000 and not (_int_dst_id >=9991 and _int_dst_id <= 9999):
@ -2081,7 +2274,7 @@ class routerHBP(HBSYSTEM):
# TGID matches a rule source, reset its timer
if _slot == _system['TS'] and _dst_id == _system['TGID'] and ((_system['TO_TYPE'] == 'ON' and (_system['ACTIVE'] == True)) or (_system['TO_TYPE'] == 'OFF' and _system['ACTIVE'] == False)):
_system['TIMER'] = pkt_time + _system['TIMEOUT']
logger.info('(%s) [B] Transmission match for Reflector: %s. Reset timeout to %s', self._system, _bridge, _system['TIMER'])
logger.info('(%s) [B] Transmission match for Dial-A-TG: %s. Reset timeout to %s', self._system, _bridge, _system['TIMER'])
# TGID matches an ACTIVATION trigger
if _int_dst_id == int(_dehash_bridge) and _system['SYSTEM'] == self._system and _slot == _system['TS']:
@ -2089,15 +2282,15 @@ class routerHBP(HBSYSTEM):
if _system['ACTIVE'] == False:
_system['ACTIVE'] = True
_system['TIMER'] = pkt_time + _system['TIMEOUT']
logger.info('(%s) [C] Reflector: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE'])
logger.info('(%s) [C] Dial-A-TG: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE'])
# Cancel the timer if we've enabled an "OFF" type timeout
if _system['TO_TYPE'] == 'OFF':
_system['TIMER'] = pkt_time
logger.info('(%s) [D] Reflector: %s has an "OFF" timer and set to "ON": timeout timer cancelled', self._system, _bridge)
logger.info('(%s) [D] Dial-A-TG: %s has an "OFF" timer and set to "ON": timeout timer cancelled', self._system, _bridge)
# Reset the timer for the rule
if _system['ACTIVE'] == True and _system['TO_TYPE'] == 'ON':
_system['TIMER'] = pkt_time + _system['TIMEOUT']
logger.info('(%s) [E] Reflector: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time)
logger.info('(%s) [E] Dial-A-TG: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time)
# TGID matches an DE-ACTIVATION trigger
#Single TG mode
@ -2107,41 +2300,41 @@ class routerHBP(HBSYSTEM):
if _dst_id in _system['OFF'] or _int_dst_id != int(_dehash_bridge) :
if _system['ACTIVE'] == True:
_system['ACTIVE'] = False
logger.info('(%s) [F] Reflector: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE'])
logger.info('(%s) [F] Dial-A-TG: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE'])
# Cancel the timer if we've enabled an "ON" type timeout
if _system['TO_TYPE'] == 'ON':
_system['TIMER'] = pkt_time
logger.info('(%s) [G] Reflector: %s has ON timer and set to "OFF": timeout timer cancelled', self._system, _bridge)
logger.info('(%s) [G] Dial-A-TG: %s has ON timer and set to "OFF": timeout timer cancelled', self._system, _bridge)
# Reset the timer for the rule
if _system['ACTIVE'] == False and _system['TO_TYPE'] == 'OFF':
_system['TIMER'] = pkt_time + _system['TIMEOUT']
logger.info('(%s) [H] Reflector: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time)
logger.info('(%s) [H] Dial-A-TG: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time)
# Cancel the timer if we've enabled an "ON" type timeout
if _system['ACTIVE'] == True and _system['TO_TYPE'] == 'ON' and _dst_id in _system['OFF']:
_system['TIMER'] = pkt_time
logger.info('(%s) [I] Reflector: %s has ON timer and set to "OFF": timeout timer cancelled', self._system, _bridge)
logger.info('(%s) [I] Dial-A-TG: %s has ON timer and set to "OFF": timeout timer cancelled', self._system, _bridge)
if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM):
_say = [words[_lang]['silence']]
if _int_dst_id <= 5 or _int_dst_id == 9:
logger.info('(%s) Reflector: voice called - TG < 5 or 9 - "busy""', self._system)
if _int_dst_id < 8 or _int_dst_id == 9 :
logger.info('(%s) Dial-A-TG: voice called - TG < 8 or 9 - "busy""', self._system)
_say.append(words[_lang]['busy'])
_say.append(words[_lang]['silence'])
self.STATUS[_slot]['_stopTgAnnounce'] = True
#Allstar mode switch
if CONFIG['ALLSTAR']['ENABLED'] and _int_dst_id == 8:
logger.info('(%s) Reflector: voice called - TG 8 AllStar"', self._system)
logger.info('(%s) Dial-A-TG: voice called - TG 8 AllStar"', self._system)
_say.append(words[_lang]['all-star-link-mode'])
_say.append(words[_lang]['silence'])
self.STATUS[_slot]['_stopTgAnnounce'] = True
self.STATUS[_slot]['_allStarMode'] = True
reactor.callLater(30,resetallStarMode)
elif not CONFIG['ALLSTAR']['ENABLED'] and _int_dst_id == 8:
logger.info('(%s) Reflector: TG 8 AllStar not enabled"', self._system)
logger.info('(%s) Dial-A-TG: TG 8 AllStar not enabled"', self._system)
_say.append(words[_lang]['busy'])
_say.append(words[_lang]['silence'])
self.STATUS[_slot]['_stopTgAnnounce'] = True
@ -2150,7 +2343,7 @@ class routerHBP(HBSYSTEM):
#If disconnection called
if _int_dst_id == 4000:
logger.info('(%s) Reflector: voice called - 4000 "not linked"', self._system)
logger.info('(%s) Dial-A-TG: voice called - 4000 "not linked"', self._system)
_say.append(words[_lang]['notlinked'])
_say.append(words[_lang]['silence'])
@ -2164,7 +2357,7 @@ class routerHBP(HBSYSTEM):
_dehash_bridge = _bridge[1:]
if _system['SYSTEM'] == self._system and _slot == _system['TS']:
if _system['ACTIVE'] == True:
logger.info('(%s) Reflector: voice called - 5000 status - "linked to %s"', self._system,_dehash_bridge)
logger.info('(%s) Dial-A-TG: voice called - 5000 status - "linked to %s"', self._system,_dehash_bridge)
_say.append(words[_lang]['silence'])
_say.append(words[_lang]['linkedto'])
_say.append(words[_lang]['silence'])
@ -2179,7 +2372,7 @@ class routerHBP(HBSYSTEM):
break
if _active == False:
logger.info('(%s) Reflector: voice called - 5000 status - "not linked"', self._system)
logger.info('(%s) Dial-A-TG: voice called - 5000 status - "not linked"', self._system)
_say.append(words[_lang]['notlinked'])
#Information services
@ -2191,7 +2384,7 @@ class routerHBP(HBSYSTEM):
#Speak what TG was requested to link
elif not self.STATUS[_slot]['_stopTgAnnounce']:
logger.info('(%s) Reflector: voice called (linking) "linked to %s"', self._system,_int_dst_id)
logger.info('(%s) Dial-A-TG: voice called (linking) "linked to %s"', self._system,_int_dst_id)
_say.append(words[_lang]['silence'])
_say.append(words[_lang]['linkedto'])
_say.append(words[_lang]['silence'])
@ -2505,12 +2698,17 @@ if __name__ == '__main__':
import sys
import os
import signal
global CONFIG
global KEYS
keys = {}
# Higheset peer ID permitted by HBP
PEER_MAX = 4294967295
ID_MAX = 16776415
#Set process title early
setproctitle(__file__)
@ -2520,7 +2718,7 @@ if __name__ == '__main__':
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually hblink.cfg)')
parser.add_argument('-r', '--rules', action='store', dest='RULES_FILE', help='/full/path/to/rules.file (usually rules.py)')
#parser.add_argument('-r', '--rules', action='store', dest='RULES_FILE', help='/full/path/to/rules.file (usually rules.py)')
parser.add_argument('-l', '--logging', action='store', dest='LOG_LEVEL', help='Override config file logging level.')
cli_args = parser.parse_args()
@ -2547,8 +2745,8 @@ if __name__ == '__main__':
CONFIG = config.build_config(cli_args.CONFIG_FILE)
# Ensure we have a path for the rules file, if one wasn't specified, then use the default (top of file)
if not cli_args.RULES_FILE:
cli_args.RULES_FILE = os.path.dirname(os.path.abspath(__file__))+'/rules.py'
#if not cli_args.RULES_FILE:
# cli_args.RULES_FILE = os.path.dirname(os.path.abspath(__file__))+'/rules.py'
# Start the system logger
if cli_args.LOG_LEVEL:
@ -2570,13 +2768,33 @@ if __name__ == '__main__':
logger.info('(GLOBAL) SHUTDOWN: CONFBRIDGE IS TERMINATING WITH SIGNAL %s', str(_signal))
hblink_handler(_signal, _frame)
logger.info('(GLOBAL) SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR')
reactor.stop()
if CONFIG['ALIASES']['SUB_MAP_FILE']:
subMapWrite()
if reactor.running:
CONFIG['GLOBAL']['_KILL_SERVER'] = True
else:
exit()
#Server kill routine
def kill_server():
try:
if CONFIG['GLOBAL']['_KILL_SERVER']:
logger.info('(GLOBAL) SHUTDOWN: CONFBRIDGE IS TERMINATING - killserver called')
if reactor.running:
reactor.stop()
if CONFIG['ALIASES']['SUB_MAP_FILE']:
subMapWrite()
try:
save_json(''.join([CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['KEYS_FILE']]),keys)
logger.info('(KEYS) saved system keys to keystore')
except Exception as e:
logger.error('(GLOBAL) Canot save key file: %s',e)
except KeyError:
pass
#install signal handlers
signal.signal(signal.SIGTERM, sig_handler)
signal.signal(signal.SIGINT, sig_handler)
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGINT, signal.SIGTERM]:
signal.signal(sig, sig_handler)
# Create the name-number mapping dictionaries
peer_ids, subscriber_ids, talkgroup_ids, local_subscriber_ids, server_ids, checksums = mk_aliases(CONFIG)
@ -2594,13 +2812,13 @@ if __name__ == '__main__':
# Import the ruiles file as a module, and create BRIDGES from it
spec = importlib.util.spec_from_file_location("module.name", cli_args.RULES_FILE)
rules_module = importlib.util.module_from_spec(spec)
try:
spec.loader.exec_module(rules_module)
logger.info('(ROUTER) Routing bridges file found and bridges imported: %s', cli_args.RULES_FILE)
except (ImportError, FileNotFoundError):
sys.exit('(ROUTER) TERMINATING: Routing bridges file not found or invalid: {}'.format(cli_args.RULES_FILE))
#spec = importlib.util.spec_from_file_location("module.name", cli_args.RULES_FILE)
#rules_module = importlib.util.module_from_spec(spec)
#try:
# spec.loader.exec_module(rules_module)
# logger.info('(ROUTER) Routing bridges file found and bridges imported: %s', cli_args.RULES_FILE)
#except (ImportError, FileNotFoundError):
#sys.exit('(ROUTER) TERMINATING: Routing bridges file not found or invalid: {}'.format(cli_args.RULES_FILE))
#Load pickle of bridges if it's less than 25 seconds old
#if os.path.isfile('bridge.pkl'):
@ -2616,9 +2834,17 @@ if __name__ == '__main__':
#BRIDGES = make_bridges(rules_module.BRIDGES)
#os.unlink("bridge.pkl")
#else:
BRIDGES = make_bridges(rules_module.BRIDGES)
if 'ECHO' in CONFIG['SYSTEMS'] and CONFIG['SYSTEMS']['ECHO']['MODE'] == 'PEER':
BRIDGES = make_bridges({'9990': [{'SYSTEM': 'ECHO', 'TS': 2, 'TGID': 9990, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [], 'OFF': [], 'RESET': []},]})
else:
BRIDGES = {}
#Subscriber map for unit calls - complete with test entry
#SUB_MAP = {bytes_3(73578):('REP-1',1,time())}
SUB_MAP = {}
@ -2646,7 +2872,7 @@ if __name__ == '__main__':
_systemname = ''.join([system,'-',str(count)])
generator[_systemname] = copy.deepcopy(CONFIG['SYSTEMS'][system])
generator[_systemname]['PORT'] = generator[_systemname]['PORT'] + count
generator[_systemname]['_default_options'] = "TS1_STATIC={};TS2_STATIC={};SINGLE={};DEFAULT_UA_TIMER={};DEFAULT_REFLECTOR={};VOICE={};LANG={}".format(generator[_systemname]['TS1_STATIC'],generator[_systemname]['TS2_STATIC'],int(generator[_systemname]['SINGLE_MODE']),generator[_systemname]['DEFAULT_UA_TIMER'],generator[_systemname]['DEFAULT_REFLECTOR'],int(generator[_systemname]['VOICE_IDENT']), generator[_systemname]['ANNOUNCEMENT_LANGUAGE'])
generator[_systemname]['_default_options'] = "SINGLE={};DEFAULT_UA_TIMER={};VOICE={};LANG={}".format(int(generator[_systemname]['SINGLE_MODE']),generator[_systemname]['DEFAULT_UA_TIMER'],int(generator[_systemname]['VOICE_IDENT']), generator[_systemname]['ANNOUNCEMENT_LANGUAGE'])
logger.debug('(GLOBAL) Generator - generated system %s',_systemname)
generator[_systemname]['_default_options']
systemdelete.append(system)
@ -2658,13 +2884,15 @@ if __name__ == '__main__':
del generator
del systemdelete
prohibitedTGs = [0,1,2,3,4,5,9,9990,9991,9992,9993,9994,9995,9996,9997,9998,9999]
# Default reflector
logger.debug('(ROUTER) Setting default reflectors')
logger.debug('(ROUTER) Setting default dial-a-tgs')
for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['MODE'] != 'MASTER':
continue
if CONFIG['SYSTEMS'][system]['DEFAULT_REFLECTOR'] > 0:
if CONFIG['SYSTEMS'][system]['DEFAULT_REFLECTOR'] not in prohibitedTGs:
make_default_reflector(CONFIG['SYSTEMS'][system]['DEFAULT_REFLECTOR'],CONFIG['SYSTEMS'][system]['DEFAULT_UA_TIMER'],system)
#static TGs
@ -2683,11 +2911,15 @@ if __name__ == '__main__':
for tg in ts1:
if not tg:
continue
if tg in prohibitedTGs:
continue
tg = int(tg)
make_static_tg(tg,1,_tmout,system)
for tg in ts2:
if not tg:
continue
if tg in prohibitedTGs:
continue
tg = int(tg)
make_static_tg(tg,2,_tmout,system)
@ -2731,6 +2963,9 @@ if __name__ == '__main__':
logger.warning('(GLOBAL) Invalid language in ANNOUNCEMENT_LANGUAGE, skipping system %s',system)
continue
systems[system] = routerHBP(system, CONFIG, report_server)
if (CONFIG['SYSTEMS'][system]['MODE'] == 'PEER' and system != 'ECHO') or CONFIG['SYSTEMS'][system]['MODE'] == 'XLXPEER':
logger.warning('(GLOBAL) PEER and XLXPEER connections only allowed in bridge mode, skipping system %s',system)
continue
listeningPorts[system] = reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP'])
logger.debug('(GLOBAL) %s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system])
@ -2738,6 +2973,29 @@ if __name__ == '__main__':
logger.error('(GLOBAL) STOPPING REACTOR TO AVOID MEMORY LEAK: Unhandled error in timed loop.\n %s', failure)
reactor.stop()
#load keys if exists
try:
keys = load_json(''.join([CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['KEYS_FILE']]))
except Exception as e:
logger.error('(KEYS) Cannot load keys: %s',e)
#Initialize API
if CONFIG['GLOBAL']['ENABLE_API']:
api = config_API(CONFIG,BRIDGES)
else:
api = False
if api:
logger.info('(API) API running')
if 'SYSTEM_API_KEY' not in keys or not keys['SYSTEM_API_KEY']:
CONFIG['GLOBAL']['SYSTEM_API_KEY'] = secrets.token_hex(16)
keys['SYSTEM_API_KEY'] = CONFIG['GLOBAL']['SYSTEM_API_KEY']
logger.info('(API) Random system API Key generated: %s',CONFIG['GLOBAL']['SYSTEM_API_KEY'])
else:
CONFIG['GLOBAL']['SYSTEM_API_KEY'] = keys['SYSTEM_API_KEY']
logger.info('(API) System API Key loaded from system key store')
else:
logger.info('(API) API not started')
# Initialize the rule timer -- this if for user activated stuff
rule_timer_task = task.LoopingCall(rule_timer_loop)
rule_timer = rule_timer_task.start(52)
@ -2764,11 +3022,16 @@ if __name__ == '__main__':
options_task = task.LoopingCall(options_config)
options = options_task.start(26)
options.addErrback(loopingErrHandle)
#bridge reset
bridge_task = task.LoopingCall(bridge_reset)
bridge = bridge_task.start(6)
bridge.addErrback(loopingErrHandle)
#STAT trimmer - once every 10 mins (roughly - shifted so all timed tasks don't run at once
#STAT trimmer - once every 5 mins (roughly - shifted so all timed tasks don't run at once
if CONFIG['GLOBAL']['GEN_STAT_BRIDGES']:
stat_trimmer_task = task.LoopingCall(statTrimmer)
stat_trimmer = stat_trimmer_task.start(523)#3600
stat_trimmer = stat_trimmer_task.start(303)#3600
stat_trimmer.addErrback(loopingErrHandle)
#KA Reporting
@ -2787,6 +3050,11 @@ if __name__ == '__main__':
sub_trimmer_task = task.LoopingCall(SubMapTrimmer)
sub_trimmer = sub_trimmer_task.start(3600)#3600
sub_trimmer.addErrback(loopingErrHandle)
#Server kill switch checker
killserver_task = task.LoopingCall(kill_server)
killserver = killserver_task.start(5)
killserver.addErrback(loopingErrHandle)
#more threads
reactor.suggestThreadPoolSize(100)

View File

@ -148,7 +148,9 @@ def build_config(_config_file):
'SERVER_ID': config.getint(section, 'SERVER_ID', fallback=0).to_bytes(4, 'big'),
'DATA_GATEWAY': config.getboolean(section, 'DATA_GATEWAY', fallback=False),
'VALIDATE_SERVER_IDS': config.getboolean(section, 'VALIDATE_SERVER_IDS', fallback=True),
'DEBUG_BRIDGES' : config.getboolean(section, 'DEBUG_BRIDGES', fallback=False)
'DEBUG_BRIDGES' : config.getboolean(section, 'DEBUG_BRIDGES', fallback=False),
'ENABLE_API' : config.getboolean(section, 'ENABLE_API', fallback=False)
})
if not CONFIG['GLOBAL']['ANNOUNCEMENT_LANGUAGES']:
@ -187,9 +189,8 @@ def build_config(_config_file):
'SERVER_ID_URL': config.get(section, 'SERVER_ID_URL', fallback='https://freedmr-lh.gb7fr.org.uk/json/server_ids.tsv'),
'SERVER_ID_FILE': config.get(section, 'SERVER_ID_FILE', fallback='server_ids.tsv'),
'CHECKSUM_URL': config.get(section, 'CHECKSUM_URL', fallback='https://freedmr-lh.gb7fr.org.uk/file_checksums.json'),
'CHECKSUM_FILE': config.get(section, 'CHECKSUM_FILE', fallback='file_checksums.json')
'CHECKSUM_FILE': config.get(section, 'CHECKSUM_FILE', fallback='file_checksums.json'),
'KEYS_FILE': config.get(section, 'KEYS_FILE', fallback='keys.json')
})

View File

@ -17,20 +17,27 @@
###############################################################################
FROM python:3.11-alpine
ENTRYPOINT [ "/entrypoint" ]
#ENTRYPOINT [ "/entrypoint" ]
ENTRYPOINT ["/sbin/tini", "-g", "--", "/entrypoint"]
COPY . /opt/freedmr
RUN adduser -D -u 54000 radio && \
RUN addgroup -g 54000 freedmr && \
adduser -D -u 54000 -G freedmr freedmr && \
apk update && \
apk add git gcc musl-dev && \
apk add git gcc musl-dev supervisor && \
apk add --no-cache tini && \
cd /opt && \
cd /opt/freedmr && \
ls -lah && \
pip install --no-cache-dir -r requirements.txt && \
apk del git gcc musl-dev && \
chown -R radio: /opt/freedmr
chown -R freedmr:freedmr /opt/freedmr && \
mkdir /run/priv_control/ && \
chown -R freedmr:freedmr /run/priv_control
COPY docker-configs/entrypoint-proxy /entrypoint
USER radio
COPY docker-configs/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
USER freedmr

View File

@ -24,8 +24,6 @@ services:
mem_reservation: 600m
volumes:
- '/etc/freedmr/freedmr.cfg:/opt/freedmr/freedmr.cfg'
- '/var/log/freedmr/:/opt/freedmr/log/'
- '/etc/freedmr/rules.py:/opt/freedmr/rules.py'
#Write JSON files outside of container
- '/etc/freedmr/json/:/opt/freedmr/json/'

View File

@ -176,8 +176,6 @@ TGID_TS2_ACL: PERMIT:ALL
ANNOUNCEMENT_LANGUAGE: en_GB
EOF
echo Install rules.py ...
echo "BRIDGES = {'9990': [{'SYSTEM': 'ECHO', 'TS': 2, 'TGID': 9990, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [], 'OFF': [], 'RESET': []},]}" > /etc/freedmr/rules.py &&
echo Set perms on config directory...
chown -R 54000 /etc/freedmr &&

View File

@ -22,10 +22,11 @@ cd /opt/freedmr
if [ "$BRIDGE_SERVER" == 1 ]
then
echo 'Starting in Bridge mode...'
python /opt/freedmr/bridge.py -c freedmr.cfg -r rules.py
exec python /opt/freedmr/bridge.py -c freedmr.cfg -r rules.py
else
echo 'Starting in FreeDMR mode...'
python /opt/freedmr/hotspot_proxy_v2.py &
python /opt/freedmr/playback.py -c loro.cfg &
python /opt/freedmr/bridge_master.py -c freedmr.cfg -r rules.py
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
#python /opt/freedmr/hotspot_proxy_v2.py &
#python /opt/freedmr/playback.py -c loro.cfg &
#exec python /opt/freedmr/bridge_master.py -c freedmr.cfg
fi

104
docker-configs/freedmr.cfg Normal file
View File

@ -0,0 +1,104 @@
#This empty config file will use defaults for everything apart from OBP and HBP config
#This is usually a sensible choice.
#I have moved to a config like this to encourage servers to use the accepted defaults
#unless you really know what you are doing.
[GLOBAL]
#If you join the FreeDMR network, you need to add your ServerID Here.
SERVER_ID: 0
[REPORTS]
[LOGGER]
[ALIASES]
[ALLSTAR]
#This is an example OpenBridgeProtocol (OBP) or FreeBridgeProtocol (FBP) configuration
#If you joing FreeDMR, you will be given a config like this to paste in
[OBP-TEST]
MODE: OPENBRIDGE
ENABLED: False
IP:
PORT: 62044
#The ID which you expect to see sent from the other end of the link.
NETWORK_ID: 1
PASSPHRASE: mypass
TARGET_IP:
TARGET_PORT: 62044
USE_ACL: True
SUB_ACL: DENY:1
TGID_ACL: PERMIT:ALL
#Should always be true if using docker.
RELAX_CHECKS: True
#True for FBP, False for OBP
ENHANCED_OBP: True
#PROTO_VER should be 5 for FreeDMR servers using FBP
#1 for other servers using OBP
PROTO_VER: 5
#This defines parameters for repeater/hotspot connections
#via HomeBrewProtocol (HBP)
#I don't recommend changing most of this unless you know what you are doing
[SYSTEM]
MODE: MASTER
ENABLED: True
REPEAT: True
MAX_PEERS: 1
EXPORT_AMBE: False
IP: 127.0.0.1
PORT: 54000
PASSPHRASE:
GROUP_HANGTIME: 5
USE_ACL: True
REG_ACL: DENY:1
SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:ALL
TGID_TS2_ACL: PERMIT:ALL
DEFAULT_UA_TIMER: 10
SINGLE_MODE: True
VOICE_IDENT: True
TS1_STATIC:
TS2_STATIC:
DEFAULT_REFLECTOR: 0
ANNOUNCEMENT_LANGUAGE: en_GB
GENERATOR: 100
ALLOW_UNREG_ID: False
PROXY_CONTROL: True
OVERRIDE_IDENT_TG:
#Echo (Loro / Parrot) server
[ECHO]
MODE: PEER
ENABLED: True
LOOSE: False
EXPORT_AMBE: False
IP: 127.0.0.1
PORT: 54916
MASTER_IP: 127.0.0.1
MASTER_PORT: 54915
PASSPHRASE: passw0rd
CALLSIGN: ECHO
RADIO_ID: 1000001
RX_FREQ: 449000000
TX_FREQ: 444000000
TX_POWER: 25
COLORCODE: 1
SLOTS: 1
LATITUDE: 00.0000
LONGITUDE: 000.0000
HEIGHT: 0
LOCATION: Earth
DESCRIPTION: ECHO
URL: www.freedmr.uk
SOFTWARE_ID: 20170620
PACKAGE_ID: MMDVM_FreeDMR
GROUP_HANGTIME: 5
OPTIONS:
USE_ACL: True
SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:ALL
TGID_TS2_ACL: PERMIT:ALL
ANNOUNCEMENT_LANGUAGE: en_GB

View File

@ -0,0 +1,31 @@
[supervisord]
nodaemon=true
logfile=/dev/null
logfile_maxbytes=0
pidfile=/tmp/supervisord.pid
[program:freedmr]
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
command=python /opt/freedmr/bridge_master.py -c freedmr.cfg
stopwaitsecs=30
autorestart=true
priority=2
[program:proxy]
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
command=python /opt/freedmr/hotspot_proxy_v2.py
stopwaitsecs=30
autorestart=true
priority=1
[program:playback]
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
command=/opt/freedmr/playback.py -c loro.cfg
autorestart=true
priority=3

78
freedmr.cfg Executable file
View File

@ -0,0 +1,78 @@
#This empty config file will use defaults for everything apart from OBP and HBP config
#This is usually a sensible choice.
[GLOBAL]
SERVER_ID: 0000
DEBUG_BRIDGES: True
ENABLE_API: True
[REPORTS]
[LOGGER]
LOG_LEVEL: TRACE
[ALIASES]
[ALLSTAR]
[SYSTEM]
MODE: MASTER
ENABLED: True
REPEAT: True
MAX_PEERS: 1
EXPORT_AMBE: False
IP:
PORT: 62031
PASSPHRASE:
GROUP_HANGTIME: 5
USE_ACL: True
REG_ACL: DENY:1
SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:ALL
TGID_TS2_ACL: PERMIT:ALL
DEFAULT_UA_TIMER: 60
SINGLE_MODE: True
VOICE_IDENT: True
TS1_STATIC:
TS2_STATIC:
DEFAULT_REFLECTOR: 0
ANNOUNCEMENT_LANGUAGE: en_GB
GENERATOR: 0
ALLOW_UNREG_ID: False
PROXY_CONTROL: True
OVERRIDE_IDENT_TG:
[ECHO]
MODE: PEER
ENABLED: True
LOOSE: False
EXPORT_AMBE: False
IP: 127.0.0.1
PORT: 54916
MASTER_IP: 127.0.0.1
MASTER_PORT: 54915
PASSPHRASE: passw0rd
CALLSIGN: M0XFD
RADIO_ID: 2340210
RX_FREQ: 449000000
TX_FREQ: 444000000
TX_POWER: 25
COLORCODE: 1
SLOTS: 1
LATITUDE: 00.0000
LONGITUDE: 000.0000
HEIGHT: 75
LOCATION: United Kingdom
DESCRIPTION: ECHO
URL: www.w1abc.org
SOFTWARE_ID: 20170620
PACKAGE_ID: MMDVM_FreeDMR
GROUP_HANGTIME: 5
OPTIONS:
USE_ACL: True
SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:ALL
TGID_TS2_ACL: PERMIT:ALL
ANNOUNCEMENT_LANGUAGE: en_GB

View File

@ -66,6 +66,8 @@ from urllib.request import urlopen
import shutil
import csv
import math
logging.TRACE = 5
logging.addLevelName(logging.TRACE, 'TRACE')
@ -74,7 +76,7 @@ logging.trace = partial(logging.log, logging.TRACE)
# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
__author__ = 'Cortney T. Buffington, N0MJS, Forked by Simon Adlem - G7RZU'
__copyright__ = 'Copyright (c) 2016-2019 Cortney T. Buffington, N0MJS and the K0USY Group, Simon Adlem, G7RZU 2020,2021,2022'
__copyright__ = 'Copyright (c) 2016-2019 Cortney T. Buffington, N0MJS and the K0USY Group, Simon Adlem, G7RZU 2020,2021,2022,2023'
__credits__ = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT; Jon Lee, G4TSN; Norman Williams, M6NBP'
__license__ = 'GNU GPLv3'
__maintainer__ = 'Simon Adlem G7RZU'
@ -691,16 +693,17 @@ class HBSYSTEM(DatagramProtocol):
self.transport.write(b''.join([MSTCL, peer]),self._CONFIG['SYSTEMS'][self._system]['PEERS'][peer]['SOCKADDR'])
# Remove any timed out peers from the configuration
del self._CONFIG['SYSTEMS'][self._system]['PEERS'][peer]
if 'PEERS' not in self._CONFIG['SYSTEMS'][self._system] and 'OPTIONS' in self._CONFIG['SYSTEMS'][self._system]:
if '_default_options' in self._CONFIG['SYSTEMS'][self._system]:
logger.info('(%s) Setting default Options: %s',self._system, self._CONFIG['SYSTEMS'][self._system]['_default_options'])
self._CONFIG['SYSTEMS'][self._system]['OPTIONS'] = self._CONFIG['SYSTEMS'][self._system]['_default_options']
if not self._CONFIG['SYSTEMS'][self._system]['PEERS']:
if'OPTIONS' in self._CONFIG['SYSTEMS'][self._system]:
if '_default_options' in self._CONFIG['SYSTEMS'][self._system]:
logger.info('(%s) Setting default Options: %s',self._system, self._CONFIG['SYSTEMS'][self._system]['_default_options'])
self._CONFIG['SYSTEMS'][self._system]['OPTIONS'] = self._CONFIG['SYSTEMS'][self._system]['_default_options']
else:
del self._CONFIG['SYSTEMS'][self._system]['OPTIONS']
logger.info('(%s) Deleting HBP Options',self._system)
self._CONFIG['SYSTEMS'][self._system]['_reset'] = True
else:
del self._CONFIG['SYSTEMS'][self._system]['OPTIONS']
w
logger.info('(%s) Deleting HBP Options',self._system)
# Aliased in __init__ to maintenance_loop if system is a peer
def peer_maintenance_loop(self):
@ -802,6 +805,10 @@ class HBSYSTEM(DatagramProtocol):
_bltime = str(_bltime)
_prpacket = b''.join([PRBL,peer_id,_bltime.encode('UTF-8')])
self.transport.write(_prpacket,sockaddr)
def proxy_BadPeer(self):
for _pi in self._peers:
self.proxy_IPBlackList(_pi,self._peers[_pi]['SOCKADDR'])
def validate_id(self,_peer_id):
@ -950,6 +957,7 @@ class HBSYSTEM(DatagramProtocol):
self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr)
if self._config['PROXY_CONTROL']:
self.proxy_IPBlackList(_peer_id,_sockaddr)
self._CONFIG['SYSTEMS'][self._system]['_reset'] = True
logger.warning('(%s) Invalid Login from %s Radio ID: %s Denied by Registation ACL or not registered ID', self._system, _sockaddr[0], int_id(_peer_id))
else:
self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr)
@ -995,11 +1003,11 @@ class HBSYSTEM(DatagramProtocol):
if '_default_options' in self._CONFIG['SYSTEMS'][self._system]:
self._CONFIG['SYSTEMS'][self._system]['OPTIONS'] = self._CONFIG['SYSTEMS'][self._system]['_default_options']
logger.info('(%s) Setting default Options: %s',self._system, self._CONFIG['SYSTEMS'][self._system]['_default_options'])
self._CONFIG['SYSTEMS'][self._system]['_reset'] = True
else:
logger.info('(%s) Deleting HBP Options',self._system)
del self._CONFIG['SYSTEMS'][self._system]['OPTIONS']
self._CONFIG['SYSTEMS'][self._system]['_reset'] = True
self._CONFIG['SYSTEMS'][self._system]['_reset'] = True
else:
_peer_id = _data[4:8] # Configure Command
@ -1030,6 +1038,7 @@ class HBSYSTEM(DatagramProtocol):
if self._config['PROXY_CONTROL']:
self.proxy_IPBlackList(_peer_id,_sockaddr)
self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr)
self._CONFIG['SYSTEMS'][self._system]['_reset'] = True
logger.info('(%s) Callsign does not match subscriber database: ID: %s, Sent Call: %s, DB call %s', self._system, int_id(_peer_id),_this_peer['CALLSIGN'].decode('utf8').rstrip(),self.validate_id(_peer_id))
else:
self.send_peer(_peer_id, b''.join([RPTACK, _peer_id]))
@ -1343,7 +1352,7 @@ def mk_aliases(_config):
# Make Dictionaries
#Peer IDs
try:
if exists(_config['ALIASES']['PATH'] + _config['ALIASES']['PEER_FILE'] + '.bak') and (getsize(_config['ALIASES']['PATH'] + _config['ALIASES']['PEER_FILE'] + '.bak') > getsize(_config['ALIASES']['PATH'] + _config['ALIASES']['PEER_FILE'])):
if exists(_config['ALIASES']['PATH'] + _config['ALIASES']['PEER_FILE'] + '.bak') and not math.isclose(getsize(_config['ALIASES']['PATH'] + _config['ALIASES']['PEER_FILE'] + '.bak'),getsize(_config['ALIASES']['PATH'] + _config['ALIASES']['PEER_FILE']), rel_tol=1000):
raise Exception('backup peer_ids file is larger than new file')
try:
if blake2bsum(''.join([_config['ALIASES']['PATH'], _config['ALIASES']['PEER_FILE']])) != checksums['peer_ids']:
@ -1371,8 +1380,10 @@ def mk_aliases(_config):
#Subscriber IDs
try:
if exists(_config['ALIASES']['PATH'] + _config['ALIASES']['SUBSCRIBER_FILE'] + '.bak') and (getsize(_config['ALIASES']['PATH'] + _config['ALIASES']['SUBSCRIBER_FILE'] + '.bak') > getsize(_config['ALIASES']['PATH'] + _config['ALIASES']['SUBSCRIBER_FILE'])):
if exists(_config['ALIASES']['PATH'] + _config['ALIASES']['SUBSCRIBER_FILE'] + '.bak') and not math.isclose(getsize(_config['ALIASES']['PATH'] + _config['ALIASES']['SUBSCRIBER_FILE'] + '.bak'), getsize(_config['ALIASES']['PATH'] + _config['ALIASES']['SUBSCRIBER_FILE']),rel_tol=1000):
raise Exception('backup subscriber_ids file is larger than new file')
if getsize(_config['ALIASES']['PATH'] + _config['ALIASES']['SUBSCRIBER_FILE']) > 52428800:
raise Exception('subscriber_ids file is larger than 50Mb')
try:
if blake2bsum(''.join([_config['ALIASES']['PATH'], _config['ALIASES']['SUBSCRIBER_FILE']])) != checksums['subscriber_ids']:
raise(Exception('bad checksum'))
@ -1402,8 +1413,10 @@ def mk_aliases(_config):
#Talkgroup IDs
try:
if exists(_config['ALIASES']['PATH'] + _config['ALIASES']['TGID_FILE'] + '.bak') and (getsize(_config['ALIASES']['PATH'] + _config['ALIASES']['TGID_FILE'] + '.bak') > getsize(_config['ALIASES']['PATH'] + _config['ALIASES']['TGID_FILE'])):
if exists(_config['ALIASES']['PATH'] + _config['ALIASES']['TGID_FILE'] + '.bak') and not math.isclose(getsize(_config['ALIASES']['PATH'] + _config['ALIASES']['TGID_FILE'] + '.bak'), getsize(_config['ALIASES']['PATH'] + _config['ALIASES']['TGID_FILE']),rel_tol=1000):
raise Exception('backup talkgroup_ids file is larger than new file')
if getsize(_config['ALIASES']['PATH'] + _config['ALIASES']['TGID_FILE']) > 52428800:
raise Exception('talkgroup_ids file is larger than 50Mb')
try:
if blake2bsum(''.join([_config['ALIASES']['PATH'], _config['ALIASES']['TGID_FILE']])) != checksums['talkgroup_ids']:
raise(Exception('bad checksum'))
@ -1470,7 +1483,7 @@ def mk_aliases(_config):
shutil.copy(_config['ALIASES']['PATH'] + _config['ALIASES']['SERVER_ID_FILE'],_config['ALIASES']['PATH'] + _config['ALIASES']['SERVER_ID_FILE'] + '.bak')
except IOError as g:
logger.info('(ALIAS) ID ALIAS MAPPER: couldn\'t make backup copy of server_ids file %s',g)
return peer_ids, subscriber_ids, talkgroup_ids, local_subscriber_ids, server_ids, checksums

View File

@ -25,10 +25,11 @@ import ipaddress
import os
from setproctitle import setproctitle
from datetime import datetime
import Pyro5.api
# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
__author__ = 'Simon Adlem - G7RZU'
__copyright__ = 'Copyright (c) Simon Adlem, G7RZU 2020,2021,2022'
__copyright__ = 'Copyright (c) Simon Adlem, G7RZU 2020,2021,2022,2023'
__credits__ = 'Jon Lee, G4TSN; Norman Williams, M6NBP; Christian, OA4DOA'
__license__ = 'GNU GPLv3'
__maintainer__ = 'Simon Adlem G7RZU'
@ -49,10 +50,45 @@ def IsIPv6Address(ip):
except ValueError as errorCode:
pass
class privHelper():
def __init__(self):
self._netfilterURI = 'PYRO:netfilterControl@./u:/run/priv_control/priv_control.unixsocket'
self._conntrackURI = 'PYRO:conntrackControl@./u:/run/priv_control/priv_control.unixsocket'
def addBL(self,dport,ip):
try:
with Pyro5.api.Proxy(self._netfilterURI) as nf:
nf.blocklistAdd(dport,ip)
except Exception as e:
print('(PROXY)(PrivError) {}'.format(e))
def delBL(self,dport,ip):
try:
with Pyro5.api.Proxy(self._netfilterURI) as nf:
nf.blocklistDel(dport,ip)
except Exception as e:
print('(PROXY)(PrivError) {}'.format(e))
def blocklistFlush(self):
try:
with Pyro5.api.Proxy(self._netfilterURI) as nf:
nf.blocklistFlush()
except Exception as e:
print('(PROXY)(PrivError) {}'.format(e))
def flushCT(self):
try:
with Pyro5.api.Proxy(self._conntrackURI) as ct:
ct.flushUDPTarget(62031)
except Exception as e:
print('(PROXY)(PrivError) {}'.format(e))
class Proxy(DatagramProtocol):
def __init__(self,Master,ListenPort,connTrack,peerTrack,blackList,IPBlackList,Timeout,Debug,ClientInfo,DestportStart,DestPortEnd):
def __init__(self,Master,ListenPort,connTrack,peerTrack,blackList,IPBlackList,Timeout,Debug,ClientInfo,DestportStart,DestPortEnd,privHelper,rptlTrack):
self.master = Master
self.ListenPort = ListenPort
self.connTrack = connTrack
self.peerTrack = peerTrack
self.timeout = Timeout
@ -63,7 +99,9 @@ class Proxy(DatagramProtocol):
self.destPortStart = DestportStart
self.destPortEnd = DestPortEnd
self.numPorts = DestPortEnd - DestportStart
self.privHelper = privHelper
self.rptlTrack = rptlTrack
def reaper(self,_peer_id):
if self.debug:
@ -130,7 +168,10 @@ class Proxy(DatagramProtocol):
except KeyError:
return
if self.clientinfo:
print('Add to blacklist: host {}. Expire time {}'.format(self.peerTrack[_peer_id]['shost'],_bltime))
print('(PROXY)Add to blacklist: host {}. Expire time {}'.format(self.peerTrack[_peer_id]['shost'],_bltime))
if self.privHelper:
print('(PROXY)Ask priv_helper to add to iptables: host {}, port {}.'.format(self.peerTrack[_peer_id]['shost'],self.ListenPort))
reactor.callInThread(self.privHelper.addBL,self.ListenPort,self.peerTrack[_peer_id]['shost'])
return
if _command == DMRD:
@ -168,6 +209,27 @@ class Proxy(DatagramProtocol):
_peer_id = data[4:8]
elif _command == RPTL: # RPTLogin -- a repeater wants to login
_peer_id = data[4:8]
#if we have seen more than 20 RPTL packets from this IP since the RPTL tracking table was reset (every 60 secs)
#blacklist IP for 10 minutes
if host not in self.rptlTrack:
self.rptlTrack[host] = 1
else:
self.rptlTrack[host] += 1
if self.rptlTrack[host] > 20:
print('(PROXY)(RPTL) exceeded max: {}'.format(self.rptlTrack[host]))
_bltime = nowtime + 600
self.IPBlackList[host] = _bltime
self.rptlTrack.pop(host)
if self.clientinfo:
print('(PROXY)(RPTL) Add to blacklist: host {}. Expire time {}'.format(host,_bltime))
if self.privHelper:
print('(PROXY)(RPTL) Ask priv_helper to add to iptables: host {}, port {}.'.format(host,self.ListenPort))
reactor.callInThread(self.privHelper.addBL,self.ListenPort,host)
return
elif _command == RPTK: # Repeater has answered our login challenge
_peer_id = data[4:8]
elif _command == RPTC: # Repeater is sending it's configuraiton OR disconnecting
@ -226,6 +288,10 @@ if __name__ == '__main__':
import argparse
import sys
import json
import stat
import functools
print = functools.partial(print, flush=True)
#Set process title early
setproctitle(__file__)
@ -248,7 +314,7 @@ if __name__ == '__main__':
config = configparser.ConfigParser()
if not config.read(_config_file):
print('Configuration file \''+_config_file+'\' is not a valid configuration file!')
print('(PROXY)Configuration file \''+_config_file+'\' is not a valid configuration file!')
try:
@ -265,9 +331,9 @@ if __name__ == '__main__':
IPBlackList = json.loads(config.get('PROXY','IPBlackList'))
except configparser.Error as err:
print('Error processing configuration file -- {}'.format(err))
print('(PROXY)Error processing configuration file -- {}'.format(err))
print('Using default config')
print('(PROXY)Using default config')
#*** CONFIG HERE ***
Master = "127.0.0.1"
@ -279,7 +345,7 @@ if __name__ == '__main__':
Timeout = 30
Stats = False
Debug = False
ClientInfo = False
ClientInfo = True
BlackList = [1234567]
#e.g. {10.0.0.1: 0, 10.0.0.2: 0}
IPBlackList = {}
@ -288,22 +354,27 @@ if __name__ == '__main__':
CONNTRACK = {}
PEERTRACK = {}
RPTLTRACK = {}
PRIV_HELPER = None
# Set up the signal handler
def sig_handler(_signal, _frame):
print('(GLOBAL) SHUTDOWN: PROXY IS TERMINATING WITH SIGNAL {}'.format(str(_signal)))
print('(PROXY)(GLOBAL) SHUTDOWN: PROXY IS TERMINATING WITH SIGNAL {}'.format(str(_signal)))
reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGINT, signal.SIGTERM]:
signal.signal(sig, sig_handler)
def sigt(_signal,_frame):
print('oooh')
#Install signal handlers
signal.signal(signal.SIGINT, sig_handler)
signal.signal(signal.SIGTERM, sigt)
#readState()
#If IPv6 is enabled by enivornment variable...
if ListenIP == '' and 'FDPROXY_IPV6' in os.environ and bool(os.environ['FDPROXY_IPV6']):
ListenIP = '::'
#Override static config from Environment
if 'FDPROXY_STATS' in os.environ:
Stats = bool(os.environ['FDPROXY_STATS'])
@ -313,7 +384,18 @@ if __name__ == '__main__':
ClientInfo = bool(os.environ['FDPROXY_CLIENTINFO'])
if 'FDPROXY_LISTENPORT' in os.environ:
ListenPort = int(os.environ['FDPROXY_LISTENPORT'])
unixSocket = '/run/priv_control/priv_control.unixsocket'
if os.path.exists(unixSocket) and stat.S_ISSOCK(os.stat(unixSocket).st_mode):
print('(PROXY)(PRIV) Found UNIX socket. Enabling priv helper')
PRIV_HELPER = privHelper()
print('(PROXY)(PRIV) flush conntrack')
PRIV_HELPER.flushCT()
print('(PROXY)(PRIV) flush blocklist')
PRIV_HELPER.blocklistFlush()
for port in range(DestportStart,DestPortEnd+1,1):
CONNTRACK[port] = False
@ -322,10 +404,10 @@ if __name__ == '__main__':
if ListenIP == '::' and IsIPv4Address(Master):
Master = '::ffff:' + Master
reactor.listenUDP(ListenPort,Proxy(Master,ListenPort,CONNTRACK,PEERTRACK,BlackList,IPBlackList,Timeout,Debug,ClientInfo,DestportStart,DestPortEnd),interface=ListenIP)
reactor.listenUDP(ListenPort,Proxy(Master,ListenPort,CONNTRACK,PEERTRACK,BlackList,IPBlackList,Timeout,Debug,ClientInfo,DestportStart,DestPortEnd,PRIV_HELPER, RPTLTRACK),interface=ListenIP)
def loopingErrHandle(failure):
print('(GLOBAL) STOPPING REACTOR TO AVOID MEMORY LEAK: Unhandled error innowtimed loop.\n {}'.format(failure))
print('(PROXY)(GLOBAL) STOPPING REACTOR TO AVOID MEMORY LEAK: Unhandled error innowtimed loop.\n {}'.format(failure))
reactor.stop()
def stats():
@ -351,7 +433,15 @@ if __name__ == '__main__':
for delete in _dellist:
IPBlackList.pop(delete)
if ClientInfo:
print('Remove dynamic blacklist entry for {}'.format(delete))
print('(PROXY)Remove dynamic blacklist entry for {}'.format(delete))
if PRIV_HELPER:
print('(PROXY)Ask priv helper to remove blacklist entry for {} from iptables'.format(delete))
reactor.callInThread(PRIV_HELPER.delBL,ListenPort,delete)
def rptlTrimmer():
RPTLTRACK.clear()
print('(PROXY)Purge RPTL table')
if Stats == True:
@ -362,6 +452,12 @@ if __name__ == '__main__':
blacklist_task = task.LoopingCall(blackListTrimmer)
blacklista = blacklist_task.start(15)
blacklista.addErrback(loopingErrHandle)
rptlTrimmer_task = task.LoopingCall(rptlTrimmer)
rptlTrimmera = rptlTrimmer_task.start(60)
rptlTrimmera.addErrback(loopingErrHandle)
reactor.run()

View File

@ -121,47 +121,7 @@ voiceMap = {
},
'es_ES': {
'1': 'one',
'2': 'two',
'3': 'three',
'4': 'four',
'5': 'five',
'6': 'six',
'7': 'seven',
'8': 'eight',
'9': 'nine',
'A': 'alfa',
'B': 'bravo',
'C': 'charlie',
'D': 'delta',
'E': 'echo',
'F': 'foxtrot',
'G': 'golf',
'H': 'hotel',
'I': 'india',
'J': 'juliet',
'K': 'kilo',
'L': 'lima',
'M': 'mike',
'N': 'november',
'O': 'oscar',
'P': 'papa',
'Q': 'quebec',
'R': 'romeo',
'S': 'sierra',
'T': 'tango',
'U': 'uniform',
'V': 'victor',
'W': 'whiskey',
'X': 'x-ray',
'Y': 'yankee',
'Z': 'zulu',
'to': 'silence',
'notlinked': 'not-linked',
'linkedto': 'linked-to',
'allstar-link-mode': 'alfa'
},
'es_ES_2': {
'0': 'zero',
'1': 'one',
'2': 'two',
'3': 'three',
@ -304,6 +264,8 @@ voiceMap = {
'de_DE': {
'to': 'silence',
'freedmr': 'silence',
'this-is': 'silence',
'allstar-link-mode': 'A'
},

1
lib64 Symbolic link
View File

@ -0,0 +1 @@
lib

View File

@ -92,9 +92,9 @@ REPORT_CLIENTS: 127.0.0.1
#
[LOGGER]
LOG_FILE: /dev/null
LOG_HANDLERS: null
LOG_LEVEL: DEBUG
LOG_NAME: HBlink
LOG_HANDLERS: console-timed
LOG_LEVEL: INFO
LOG_NAME: ECHO
# DOWNLOAD AND IMPORT SUBSCRIBER, PEER and TGID ALIASES
# Ok, not the TGID, there's no master list I know of to download

3
pyvenv.cfg Normal file
View File

@ -0,0 +1,3 @@
home = /usr/bin
include-system-site-packages = false
version = 3.10.12

View File

@ -5,3 +5,5 @@ dmr_utils3>=0.1.19
configparser>=3.0.0
resettabletimer>=0.7.0
setproctitle
Pyro5
spyne

View File

@ -26,7 +26,7 @@ import ssl
from time import time
from os.path import isfile, getmtime
from urllib.request import urlopen
from json import load as jload
from json import load as jload, dump as jdump
import hashlib
@ -92,6 +92,16 @@ def load_json(filename):
else:
return(data)
def save_json(filename,data):
try:
with open(filename, 'w', encoding='utf-8') as f:
jdump(data, f, ensure_ascii=False, indent=4)
except:
raise
else:
return(True)
#Calculate blake2b checksum of file
def blake2bsum(filename):