Compare commits

...

188 Commits

Author SHA1 Message Date
134c426e90 update for 1.8.1
All checks were successful
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
2025-06-01 11:52:51 +02:00
a27ccc28a1 version 1.8.0 (#15)
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #15
2024-12-23 19:17:12 +01:00
e2fd60a5eb updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2024-11-23 06:49:31 +01:00
1998e6e77a feat: bump 1.7.0
Some checks failed
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is failing
2024-10-06 10:28:15 +02:00
f0cb50e797 Merge pull request 'add systemd notify support' (#14) from develop into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #14
2024-10-02 19:32:47 +02:00
3c4d6fb2cf update errors and variable names
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-10-02 19:11:44 +02:00
bdf41fa605 add systemd notify statuses
Some checks failed
continuous-integration/drone/push Build is failing
2024-10-02 19:07:27 +02:00
ebb6e5ec6d added sd-notify dependency
All checks were successful
continuous-integration/drone/push Build is passing
2024-10-02 17:45:26 +02:00
46eaf6017f updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-28 14:50:07 +02:00
9b456d403f updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-24 12:44:32 +02:00
71d640f393 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-24 12:43:24 +02:00
0a82d46bf1 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-11 18:01:32 +02:00
29472b4d7f updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-11 17:59:36 +02:00
22214b8d55 Merge pull request 'updated dependencies - fosdem 2024 commit' (#13) from develop into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
Reviewed-on: #13
2024-02-03 10:21:22 +01:00
9bae2248df updated dependencies - fosdem 2024 commit
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-03 10:13:00 +01:00
ecd35fd37a update to 1.6.8
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2024-01-04 11:05:47 +01:00
129a7e9ada updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-03 21:47:13 +01:00
1e2f047824 add ips in chunks to nftables
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-03 21:44:00 +01:00
a60ec90608 update to 1.6.7
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-12-26 13:14:08 +01:00
ce6ca78087 added safety in ipblc
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-26 13:13:30 +01:00
2e6e7efdbf hotfix on ws connections
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-12-26 11:11:38 +01:00
bae5443ca4 update to version 1.6.6
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/tag Build is passing
2023-12-26 10:49:21 +01:00
f29ccd3f0b updated ipevent with Option<IpData>
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2023-12-26 10:42:39 +01:00
6c43635c92 update to version 1.6.6
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-24 07:44:21 +01:00
1067566e9d updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-23 13:19:14 +01:00
d47a4e218d update to version 1.6.5
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-23 13:18:12 +01:00
0b67bbdab3 update to version 1.6.5
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-12-23 13:13:52 +01:00
809b252df7 added error handling for monitoring
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-12 22:41:21 +01:00
5d132c6380 Merge branch 'develop'
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-12-04 12:31:08 +01:00
80c3faec58 fix exception handling on fw.rs
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-04 12:18:59 +01:00
103f8ea411 Merge pull request 'update to version 1.6.4' (#11) from develop into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #11
2023-11-27 13:49:42 +01:00
104d1558b1 update to version 1.6.4
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-27 13:44:44 +01:00
ad8744a92c Merge pull request 'fix of websocket error' (#10) from develop into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #10
2023-11-25 18:01:12 +01:00
1313296acf updated dependencies
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is passing
2023-11-25 17:46:24 +01:00
46a01efeea fix return in websocket.rs/send_to_ipbl_websocket
Some checks failed
continuous-integration/drone/push Build is failing
2023-11-25 17:35:48 +01:00
c681825efe fix error handling in websocket
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-24 21:16:07 +01:00
0806e66671 Merge pull request 'fix on monitoring' (#9) from develop into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #9
2023-11-18 13:16:32 +01:00
9187642172 fix on monitoring
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-18 13:13:25 +01:00
77ee68c081 fix on firewall rule building
All checks were successful
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
2023-11-12 17:13:47 +01:00
cd67b0d602 fix on monitoring socket
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-11-12 11:00:42 +01:00
b50a2d44d7 Merge pull request 'update to 1.6.0' (#8) from develop into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #8
2023-11-10 23:46:42 +01:00
7d45f708c3 update to 1.6.0
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-10 23:43:09 +01:00
a654889263 more simple code
* use of some simple macros
* simplified code blocks in ctx read/write access
2023-11-10 23:43:09 +01:00
05ef0cd339 feat: update monitoring and config reload
* monitoring: added read of current config
* config: get config by single url
2023-11-10 23:43:09 +01:00
3fb83f7f77 updated .drone.yml for sccache to use webdav
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-07 19:04:01 +01:00
59ad4a6624 updated .drone.yml
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-11-04 12:48:02 +01:00
db7001c749 fix typo in .drone.yml 2023-11-04 12:47:34 +01:00
af7f1a24a7 Merge pull request 'fix on filter' (#7) from ipblc-filter-fix into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #7
2023-11-04 12:47:04 +01:00
4c697c2e0c fix on filter
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-04 12:42:07 +01:00
b82551c562 updated to 1.5.0
All checks were successful
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
2023-11-02 11:54:01 +01:00
7a49ac320c misc updates for ipblc (#6)
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #6
2023-11-02 11:29:49 +01:00
5036dc3ba8 fix Cargo.lock
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-11-01 13:43:24 +01:00
db91b06f3b update version to 1.2.2
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-11-01 11:46:09 +01:00
c1e8261d29 Merge pull request 'fix error on daylight saving time change' (#5) from handle_timechange into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #5
2023-11-01 11:44:57 +01:00
e5444dde72 fix error on daylight saving time change
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-11-01 11:40:05 +01:00
157962af8e updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-23 23:21:13 +02:00
90c7fc453f updated README.md
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-12 22:30:34 +02:00
0780af8d91 update v1.2.1
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-07-01 15:59:50 +02:00
0434eae7b4 small fixes
All checks were successful
continuous-integration/drone/push Build is passing
* Cargo.lock update
* update how git_version generates version from commit
2023-07-01 15:56:47 +02:00
2ea6e892ab temp disabled checksum in .drone.yml 2023-07-01 15:48:21 +02:00
04294c4aa0 update to v1.2.0
All checks were successful
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
2023-06-23 10:48:15 +02:00
a9e18cfcdd Merge branch 'develop-websocket'
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-23 10:44:53 +02:00
9f76dcf47e updated dependencies
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2023-06-23 10:36:46 +02:00
31c2497e33 updated dependencies
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2023-06-09 13:45:19 +02:00
93a830d6ae updated ipblc
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-22 08:19:11 +02:00
9a1f4f69dd commented unused function
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-15 13:24:55 +02:00
0ad77342ea updated possible deadlock
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-13 11:03:07 +02:00
40811d9754 updated main loop timeout
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-13 10:40:46 +02:00
ea0c561102 removed zmq from drone
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-13 10:26:20 +02:00
a720562c3c some code refactor
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-05-10 21:32:27 +02:00
2134f09210 updated dependencies 2023-05-10 21:31:53 +02:00
1bc3358faf revert buggy commits
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-07 11:23:55 +02:00
93990307c4 updated monitoring socket
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-07 11:06:57 +02:00
f559fa9fd0 debug of possible deadlock
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-07 10:52:00 +02:00
43d3137dd2 updated monitoring socket
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-07 10:46:08 +02:00
df9d19f616 fixed mutable variable
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-07 02:28:27 +02:00
ccb3b4fff0 fixed probable bug in rwlocks
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-07 02:25:26 +02:00
cd33f9cf35 detached http client from context struct
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-06 10:36:50 +02:00
89c6796935 updated README.md 2023-05-02 13:51:54 +02:00
2a2bd97407 updated dependencies 2023-05-01 08:26:19 +02:00
09be314a97 misc updates
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-29 11:11:11 +02:00
1bef25165a updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-29 09:49:58 +02:00
dc3405d07a added timeout
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-23 10:14:16 +02:00
cdc3ac4064 updated loop timeout
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-23 09:57:38 +02:00
f444137684 misc changes
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-22 19:28:38 +02:00
699d12324b updated some variables
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-22 19:04:20 +02:00
fe9a577f61 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-22 18:34:52 +02:00
b4940ffe4a updated thread select sleep
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-22 18:33:44 +02:00
6a99d6b388 updated config loading handling
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-17 22:31:10 +02:00
b1b5f4ef7d updated error handling
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-13 23:07:42 +02:00
f557e36941 invert ps & rr init 2023-04-10 16:57:12 +02:00
a6f5a44c6a updated error handling 2023-04-10 16:44:53 +02:00
bb0a272d0f updated error handling 2023-04-10 16:33:03 +02:00
2dfee34f7c added upgrade script 2023-04-10 16:02:54 +02:00
301775c91d updated error handling 2023-04-10 16:00:57 +02:00
5b47d9d257 added error handling 2023-04-10 14:10:56 +02:00
ae7738d45a refactored mainloop 2023-04-10 11:57:03 +02:00
f72c503e93 updated dependencies 2023-04-10 11:35:49 +02:00
9c5cc95d4b updated ipblc websocket feat 2023-04-10 11:31:16 +02:00
50b9c7f7b2 more stable ipblc websocket feat 2023-04-09 15:05:09 +02:00
47cc30e79e updated ipblc websocket feat 2023-04-09 01:42:17 +02:00
715194ede5 Merge branch 'develop-websocket' of ssh://git.paulbsd.com:2222/paulbsd/ipblc into develop-websocket
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-02 02:01:00 +02:00
09ca2ada91 updated webservice.rs 2023-04-02 02:00:56 +02:00
4cf93a5034 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-02 01:38:15 +02:00
a930c367c5 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-02 01:36:36 +02:00
36f892cf42 updated websocket branch
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-12 14:27:05 +01:00
831dcdace5 added websocket feature
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-05 23:05:50 +01:00
fd61cdbbc5 fixed update_blocklist function
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-16 00:49:55 +01:00
e71d8f7cd4 fixed update_blocklist function
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-16 00:38:35 +01:00
bdedca78e7 fixed updates & gc
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-15 23:54:15 +01:00
af38ea1d84 added hostname field to ipevent
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-15 23:26:18 +01:00
14192e1aef changed ipevent struct
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-15 23:12:11 +01:00
35ac52ed46 fixed gc
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-15 22:42:17 +01:00
bb9404ec7c fixed sleep timeout
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-15 22:07:56 +01:00
a84d3d0ed8 added forced timeout
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2023-01-15 20:03:32 +01:00
72d377f53f fix bug in bootstrap
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2023-01-15 16:53:58 +01:00
df2668cdc1 updated bootstrap procedure
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2023-01-15 16:40:27 +01:00
e8c7172219 changed IpData->IpEvent{IpData} struct
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2023-01-15 16:05:34 +01:00
ee5119c512 updated ipblc
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2023-01-15 15:32:41 +01:00
6ffea4d0e8 updated dependencies
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2023-01-14 09:37:05 +01:00
2ef9e43d1a Merge pull request 'replaced mutex with rwlock' (#3) from feature-rwlock into develop
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #3
2023-01-13 08:24:36 +01:00
c619fea5cb fix: msg on non-root, removed useless logs
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-11 18:00:23 +01:00
f18ac68842 replaced mutex with rwlock
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-01-10 18:00:40 +01:00
dda5f09831 update tcp api server 2023-01-08 21:16:20 +01:00
5375446303 implemented first tcp api server 2023-01-08 14:09:13 +01:00
06cb6f72be clean of Cargo.toml 2023-01-08 14:07:36 +01:00
ce1abc0331 updated md docs 2023-01-08 14:06:31 +01:00
b4664ba797 update: release version 1.1.0
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-01-07 15:53:33 +01:00
367b0323b9 update: version 1.1.0 2023-01-07 15:46:37 +01:00
2e7bfcbfc6 update: config build refactor 2023-01-07 15:46:03 +01:00
9f03987285 Merge branch 'develop' of ssh://git.paulbsd.com:2222/paulbsd/ipblc into develop 2023-01-07 15:43:28 +01:00
cf781a3a9c update: clap 4, various code adapt 2023-01-07 15:42:32 +01:00
c05789132d fix: cleanup imports 2023-01-07 15:42:32 +01:00
7b598b549c fix: unit tests 2023-01-07 15:42:32 +01:00
0ea292b28c cleanup 2023-01-07 15:42:32 +01:00
9724911126 refactor of modules 2023-01-07 15:42:32 +01:00
a2a79d8459 fix bug in blocking thread 2023-01-07 15:42:32 +01:00
9644182c4e added git version to compile time variables 2023-01-07 15:42:32 +01:00
821740ee8a updated dependencies 2023-01-07 15:42:32 +01:00
fc8dcdaf84 added git version in code at build 2023-01-07 15:42:32 +01:00
3648993e36 renamed some variables 2023-01-07 15:42:32 +01:00
d3f1f71da7 updated dependencies 2023-01-07 15:42:32 +01:00
25916f9769 includes minimal changes 2023-01-07 15:42:32 +01:00
319d246235 fixed file scanning bug 2023-01-07 15:42:32 +01:00
f7b902aacf for testing
for testing

for testing

for testing

for testing
2023-01-07 15:42:12 +01:00
da0c4075b1 update: clap 4, various code adapt 2023-01-07 12:12:58 +01:00
2092d284d0 fix: cleanup imports 2023-01-06 13:11:08 +01:00
53333b5e82 fix: unit tests 2023-01-06 11:50:26 +01:00
443685018f cleanup 2022-12-30 20:21:38 +01:00
ff99fce62b refactor of modules 2022-12-30 20:18:15 +01:00
bbce4547cf fix bug in blocking thread 2022-12-29 14:02:01 +01:00
4922cd228f added git version to compile time variables 2022-12-29 13:56:36 +01:00
aafb4ed64e updated dependencies 2022-12-29 13:55:37 +01:00
04a2c71b00 updated dependencies
Some checks reported errors
continuous-integration/drone/push Build was killed
2022-12-28 15:34:57 +01:00
d17be39302 added git version in code at build
Some checks reported errors
continuous-integration/drone/push Build was killed
2022-12-22 08:34:30 +01:00
65a93505b0 renamed some variables
Some checks reported errors
continuous-integration/drone/push Build was killed
2022-12-18 19:36:40 +01:00
69d6074881 updated dependencies
Some checks reported errors
continuous-integration/drone/push Build was killed
2022-10-12 11:58:24 +02:00
742be2f506 includes minimal changes
Some checks failed
continuous-integration/drone/push Build is failing
2022-09-23 13:17:20 +02:00
479520416d fixed file scanning bug
Some checks failed
continuous-integration/drone/push Build is failing
2022-09-21 21:03:01 +02:00
c480b322df for testing 2022-09-18 10:48:32 +02:00
424b1457a6 for testing 2022-09-18 00:50:51 +02:00
759928a425 for testing 2022-09-18 00:42:11 +02:00
f1d0ff01ef for testing 2022-09-18 00:41:29 +02:00
3e2fc8ddde for testing 2022-09-18 00:00:25 +02:00
c2eff44009 for testing 2022-09-17 23:38:54 +02:00
b2eac5bfe0 optimized initialization 2022-09-17 23:01:36 +02:00
da6ef34b0e optimized initialization 2022-09-17 22:31:30 +02:00
b66c2ee677 updates on init functions 2022-09-17 21:49:03 +02:00
48df5def78 update of dependencies, enable firewall intialization 2022-09-17 21:26:19 +02:00
23353211ae isolation of zmq received ips 2022-09-17 21:24:36 +02:00
ebd969f6f8 handle fetch of already active ip addresses on other nodes
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-11 23:35:44 +02:00
dc7cad13fe version 1.0.1, fix bug in garbage collect of expired ip address
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-08-30 22:51:44 +02:00
bda1611582 updated .drone.yml
All checks were successful
continuous-integration/drone/push Build is passing
2022-08-28 18:38:46 +02:00
678f7e5060 added feature to block after max tries for a set
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is failing
misc:
* updated tests
* updated dependencies
2022-08-28 17:32:23 +02:00
35402de4f6 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-16 11:41:42 +02:00
8bae0bfd75 updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-10 18:57:43 +02:00
6d5d10a66e updated ipblc with zmq error printing on send
Some checks failed
continuous-integration/drone/push Build is failing
2022-07-10 18:56:44 +02:00
aa10a1ca87 updated ipblc with zmq error printing
Some checks failed
continuous-integration/drone/push Build is failing
2022-07-10 18:49:49 +02:00
8d6e2f72bf updated ipblc
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone Build is passing
2022-07-01 15:51:41 +02:00
31688e9486 added tests for buggy function gc_blocklist
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-04 19:24:17 +02:00
46d38fe318 updated .drone.yml
Some checks reported errors
continuous-integration/drone/push Build was killed
2022-06-04 15:09:55 +02:00
72b5916b56 updated dependencies 2022-06-04 15:09:50 +02:00
36651618fd updated .drone.yml
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-04 14:54:26 +02:00
2303e7663d updated .drone.yml
Some checks failed
continuous-integration/drone/push Build is failing
2022-06-04 14:52:06 +02:00
a5b9634f73 updated .drone.yml
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-04 14:40:35 +02:00
d561e253fb updated .drone.yml
Some checks reported errors
continuous-integration/drone/push Build was killed
2022-06-04 11:55:20 +02:00
f37fb7e3d8 updated .drone.yml
Some checks reported errors
continuous-integration/drone/push Build was killed
2022-06-04 11:47:42 +02:00
9739dd895c added per set blocktime
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-04 11:44:32 +02:00
31 changed files with 3665 additions and 1541 deletions

View File

@ -1,28 +1,59 @@
--- ---
kind: pipeline kind: pipeline
type: docker type: docker
name: default name: default-amd64
platform:
os: linux
arch: amd64
steps: steps:
- name: test and build - name: build and test
image: rust:1 image: rust:1
pull: always
commands: commands:
- apt-get update -y - apt-get update -y
- apt-get install -y libzmq3-dev libnftnl-dev libmnl-dev - apt-get install -y libnftnl-dev libmnl-dev libclang-dev
- cargo build --verbose --all - curl -o $${RUSTC_WRAPPER} https://assets.paulbsd.com/sccache_linux_${DRONE_STAGE_ARCH}
- cargo test --verbose --all - chmod +x $${RUSTC_WRAPPER}
- cargo b -v
- cargo t -v
environment:
RUSTC_WRAPPER: /usr/bin/sccache
SCCACHE_WEBDAV_ENDPOINT: https://sccache.paulbsd.com
SCCACHE_WEBDAV_KEY_PREFIX: sccache
volumes:
- name: cargo
path: /usr/local/cargo/registry
- name: apt
path: /var/cache/apt
when: when:
event: push event:
exclude:
- tag
- name: release - name: release
image: rust:1 image: rust:1
pull: always
commands: commands:
- apt-get update -y - apt-get update -y
- apt-get install -y libzmq3-dev libnftnl-dev libmnl-dev - apt-get install -y libnftnl-dev libmnl-dev libclang-dev
- cargo build --release --verbose --all - curl -o $${RUSTC_WRAPPER} https://assets.paulbsd.com/sccache_linux_${DRONE_STAGE_ARCH}
- chmod +x $${RUSTC_WRAPPER}
- cargo b -r -v
- cd target/release - cd target/release
- tar -czvf ipblc-${DRONE_TAG}.tar.gz ipblc - tar -czvf ipblc-${DRONE_TAG}-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}.tar.gz ipblc
environment:
RUSTC_WRAPPER: /usr/bin/sccache
SCCACHE_WEBDAV_ENDPOINT: https://sccache.paulbsd.com
SCCACHE_WEBDAV_KEY_PREFIX: sccache
volumes:
- name: cargo
path: /usr/local/cargo/registry
- name: apt
path: /var/cache/apt
when: when:
event: tag event:
- tag
- name: publish - name: publish
image: plugins/gitea-release image: plugins/gitea-release
settings: settings:
@ -30,10 +61,91 @@ steps:
api_key: api_key:
from_secret: gitea_token from_secret: gitea_token
files: "target/release/*.tar.gz" files: "target/release/*.tar.gz"
checksum:
- sha256
- sha512
environment: environment:
PLUGIN_TITLE: "" PLUGIN_TITLE: ""
when: when:
event: tag event:
- tag
volumes:
- name: cargo
host:
path: /home/drone/cache/cargo
- name: apt
host:
path: /home/drone/cache/apt
---
kind: pipeline
type: docker
name: default-arm64
platform:
os: linux
arch: arm64
steps:
- name: build and test
image: rust:1
pull: always
commands:
- apt-get update -y
- apt-get install -y libnftnl-dev libmnl-dev libclang-dev
- curl -o $${RUSTC_WRAPPER} https://assets.paulbsd.com/sccache_linux_${DRONE_STAGE_ARCH}
- chmod +x $${RUSTC_WRAPPER}
- cargo b -v
- cargo t -v
environment:
RUSTC_WRAPPER: /usr/bin/sccache
SCCACHE_WEBDAV_ENDPOINT: https://sccache.paulbsd.com
SCCACHE_WEBDAV_KEY_PREFIX: sccache
volumes:
- name: cargo
path: /usr/local/cargo/registry
- name: apt
path: /var/cache/apt
when:
event:
exclude:
- tag
- name: release
image: rust:1
pull: always
commands:
- apt-get update -y
- apt-get install -y libnftnl-dev libmnl-dev libclang-dev
- curl -o $${RUSTC_WRAPPER} https://assets.paulbsd.com/sccache_linux_${DRONE_STAGE_ARCH}
- chmod +x $${RUSTC_WRAPPER}
- cargo b -r -v
- cd target/release
- tar -czvf ipblc-${DRONE_TAG}-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}.tar.gz ipblc
environment:
RUSTC_WRAPPER: /usr/bin/sccache
SCCACHE_WEBDAV_ENDPOINT: https://sccache.paulbsd.com
SCCACHE_WEBDAV_KEY_PREFIX: sccache
volumes:
- name: cargo
path: /usr/local/cargo/registry
- name: apt
path: /var/cache/apt
when:
event:
- tag
- name: publish
image: plugins/gitea-release
settings:
base_url: https://git.paulbsd.com
api_key:
from_secret: gitea_token
files: "target/release/*.tar.gz"
environment:
PLUGIN_TITLE: ""
when:
event:
- tag
volumes:
- name: cargo
host:
path: /home/drone/cache/cargo
- name: apt
host:
path: /home/drone/cache/apt

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
*.json *.json
*.swp *.swp
/*diff*
/*.gz /*.gz
/perf* /perf*
/sample /sample

2032
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package] [package]
name = "ipblc" name = "ipblc"
version = "1.0.0" version = "1.8.1"
edition = "2021" edition = "2021"
authors = ["PaulBSD <paul@paulbsd.com>"] authors = ["PaulBSD <paul@paulbsd.com>"]
description = "ipblc is a tool that search and send attacking ip addresses to ipbl" description = "ipblc is a tool that search and send attacking ip addresses to ipbl"
@ -10,31 +10,23 @@ repository = "https://git.paulbsd.com/paulbsd/ipblc"
[dependencies] [dependencies]
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
clap = "3.1" clap = { version = "4.5", features = ["string"] }
ipnet = "2.5" git-version = "0.3"
lazy_static = "1.4" ipnet = "2.11"
mnl = "0.2" lazy_static = "1.5"
nftnl = "0.6" nix = { version = "0.30", features = ["hostname", "inotify"] }
nix = "0.24" regex = "1.11"
regex = "1.5" reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
reqwest = { version = "0.11", default-features = false, features = ["json","rustls-tls"] } rustables = "0.8.6"
rustables-macros = "0.1.2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
tokio = { version = "1.18", features = ["full"] } sd-notify = { version = "0.4" }
zmq = "0.9" tokio = { version = "1.45", features = ["full", "sync"] }
tungstenite = { version = "0.26", features = ["handshake", "rustls-tls-native-roots"] }
# [target.aarch64-unknown-linux-gnu.dependencies] ## to optimize binary size (slow compile time)
# flate2 = { version = "1.0", features = ["cloudflare_zlib"] } #[profile.release]
#strip = true
# [target.aarch64-linux-android.dependencies] #lto = true
# flate2 = { version = "1.0", features = ["zlib"] } #opt-level = "z"
# [target.armv7-unknown-linux-gnueabihf.dependencies]
# flate2 = { version = "1.0", features = ["zlib"] }
# [target.x86_64-unknown-linux-gnu.dependencies]
# flate2 = { version = "1.0", features = ["cloudflare_zlib"] }
[profile.release]
debug = false
opt-level = 3

View File

@ -2,5 +2,5 @@ FROM rustembedded/cross:aarch64-unknown-linux-musl
RUN dpkg --add-architecture arm64 RUN dpkg --add-architecture arm64
RUN apt-get update RUN apt-get update
RUN apt-get install -y libasound2-dev:arm64 libzmq3-dev libnftnl-dev libmnl-dev libmnl0:arm64 libnftnl7:arm64 libmnl0:amd64 libnftnl0:arm64 RUN apt-get install -y libnftnl-dev libmnl-dev libmnl0:arm64 libnftnl7:arm64 libmnl0:amd64 libnftnl0:arm64
RUN apt-get clean RUN apt-get clean

10
NOTES.md Normal file
View File

@ -0,0 +1,10 @@
# Notes
### Date formats
```
nginx: 2006-01-02T15:04:05+01:00
ssh: 2006-01-02T15:04:05.000000+01:00
openvpn: 2006-01-02 15:04:05
mail: 2006-01-02T15:04:05.000000+01:00
```

View File

@ -4,8 +4,8 @@
## Summary ## Summary
ipblc is a tool that search and send attacking ip addresses to ipbl ipblc is client-side intrusion prevention software working closely with ipbl
It's notification features are based on zeromq It's pub/sub features are websockets based
## Howto ## Howto
@ -26,38 +26,39 @@ cargo build --release
### Usage ### Usage
``` ```
USAGE: ipblc is a tool that search and send attacking ip addresses to ipbl
ipblc [OPTIONS]
OPTIONS: Usage: ipblc [OPTIONS]
Options:
-s, --server <server> Sets a http server [default: https://ipbl.paulbsd.com]
-d Enable debugging -d Enable debugging
-h, --help Print help information -h, --help Print help information
-s, --server <server> Sets a ipbl server [default: https://ipbl.paulbsd.com] -V, --version Print version information
-V, --version Print version informatio
``` ```
### TODO ### TODO
- ✅ Config centralization (Main config in ipbl) - ✅ Config centralization (Main config in ipbl)
- ✅ Handles date in log - ✅ Handles date in log
- ✅ fine grain file opening - ✅ Fine grain file opening
- ✅ Handle zeromq data transfer - ✅ Handle zeromq data transfer
- ✅ Code optimizations (WIP) - ✅ Code optimizations (WIP)
- ✅ Error handing when fetching config - ✅ Error handing when fetching config
- ✅ Local bound tcp api socket
- ✅ ZMQ -> Websocket
- ✅ Bug in RwLocks (agent often give up)
- ❌ Create memory friendly structs for ipdata
### Date formats
``` ### Notes
nginx: 2022-02-09T10:05:02+01:00
ssh: 2022-02-09T09:29:15.797469+01:00 See [here](NOTES.md)
openvpn: 2022-02-09 09:58:59
mail: 2022-02-09T09:59:31.228303+01:00
```
## License ## License
```text ```text
Copyright (c) 2021, 2022 PaulBSD Copyright (c) 2022, 2023 PaulBSD
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View File

@ -0,0 +1,143 @@
use rustables::*;
use rustables::{expr::*, Chain, Rule, Table};
use std::{io::*, net::*};
const TABLE_NAME: &str = "ipblc4";
const CHAIN_NAME: &str = "ipblc";
fn main() -> Result<()> {
/*let name = "blabla";
let mut batch = Batch::new();
let table = Table::new(ProtocolFamily::Ipv4).with_name(name);
batch.add(&table, MsgType::Add);
let mut chain = Chain::new(&table).with_name(name);
batch.add(&chain, MsgType::Add);
let toadd1: Ipv4Addr = "9.9.9.8".parse().unwrap();
let toadd2: Ipv4Addr = "9.9.9.1".parse().unwrap();
let mut setbuilder: SetBuilder<Ipv4Addr> = SetBuilder::new("s1", &table).unwrap();
setbuilder.add(&toadd1);
setbuilder.add(&toadd2);
let (mut set, setelem) = setbuilder.finish();
batch.add(&setelem, MsgType::Add);
//batch.add(&set, MsgType::Add);
set.family = ProtocolFamily::Ipv4;
set.id = Some(5);
set.flags = Some(0);
set.userdata = Some("test".into());
println!("{:?}", setelem);*/
let get_table = || -> Result<Option<Table>> {
let tables = list_tables().unwrap();
for table in tables {
if let Some(name) = table.get_name() {
println!("Found table {}", name);
if name == TABLE_NAME {
return Ok(Some(table));
}
}
}
Ok(None)
};
let get_chain = |table: &Table| -> Result<Option<Chain>> {
let chains = list_chains_for_table(table).unwrap();
for chain in chains {
if let Some(name) = chain.get_name() {
println!("Found chain {}", name);
if name == CHAIN_NAME {
return Ok(Some(chain));
}
}
}
Ok(None)
};
let table = get_table().unwrap().expect("no table?");
let chain = get_chain(&table).unwrap().expect("no chain?");
let ip: IpAddr = "184.73.167.217".parse().unwrap();
let cmprule = Rule::new(&chain).unwrap().saddr(ip).drop();
println!("{:?}", cmprule);
let mut gexpr = RawExpression::default();
for e in cmprule.get_expressions().unwrap().iter() {
if let Some(ExpressionVariant::Cmp(_)) = e.get_data() {
gexpr = e.clone();
}
}
let rules = list_rules_for_chain(&chain).unwrap();
for rule in rules {
let handle = rule.get_handle().unwrap();
println!("handle {}", handle);
let exprs = rule.get_expressions().unwrap();
for expr in exprs.iter() {
if let Some(ExpressionVariant::Cmp(_)) = expr.get_data() {
if expr.clone() == gexpr {
println!("{:?}", expr.get_data());
println!("test");
break;
}
}
//if expr.get_data()
//if expr.
}
}
//let mut set: Set<Ipv4Addr> = nft_set!(
// &CString::new("blabla").unwrap(),
// 32,
// &table,
// ProtoFamily::Ipv4 //ProtoFamily::Ipv4;
// //[&toadd1,&toadd2,]
//);
////println!("{:?}", set.0);
//set.add(&toadd1);
//set.add(&toadd2);
//batch.add(&set, MsgType::Add);
//let mut rule = Rule::new(&chain)
// .unwrap()
// .with_expr(
// HighLevelPayload::Network(NetworkHeaderField::IPv4(IPv4HeaderField::Saddr)).build(),
// )
// .with_expr(Lookup::new(&set).unwrap())
// .with_expr(Immediate::new_verdict(VerdictKind::Accept));
//println!("{:?}", rule);
//batch.add(&rule, rustables::MsgType::Add);
//match batch.send() {
// Ok(o) => {}
// Err(e) => {
// println!("{e}");
// }
//}
//rule.add_expr(&nft_expr!(payload ipv4 saddr));
//#[rustfmt::skip]
//rule.add_expr(&nft_expr!(lookup &set));
//rule.add_expr(&nft_expr!(ct state));
//rule.add_expr(&nft_expr!(verdict drop));
//batch.add(&rule, MsgType::Add);
//let finalized_batch = batch.finalize();
//send_and_process(&finalized_batch)?;
Ok(())
}
#[allow(dead_code)]
#[derive(Debug)]
struct Error(String);
impl<T: std::error::Error> From<T> for Error {
fn from(error: T) -> Self {
Error(error.to_string())
}
}

65
old/fw.rs Normal file
View File

@ -0,0 +1,65 @@
use nftnl::{nft_expr, set::Set, Batch, Chain, FinalizedBatch, ProtoFamily, Rule, Table};
use std::{ffi::CString, io::*, net::Ipv4Addr};
fn main() -> std::result::Result<(), Error> {
let table_name = format!("ipblc4");
let table = Table::new(
&CString::new(format!("{table_name}")).unwrap(),
ProtoFamily::Ipv4,
);
let mut batch = Batch::new();
batch.add(&table, nftnl::MsgType::Add);
batch.add(&table, nftnl::MsgType::Del);
batch.add(&table, nftnl::MsgType::Add);
let mut chain = Chain::new(&CString::new("test").unwrap(), &table);
chain.set_hook(nftnl::Hook::In, 1);
chain.set_policy(nftnl::Policy::Accept);
batch.add(&chain, nftnl::MsgType::Add);
batch.add(&Rule::new(&chain), nftnl::MsgType::Del);
let mut rule = Rule::new(&chain);
rule.add_expr(&nft_expr!(ct state));
rule.add_expr(&nft_expr!(bitwise mask 4u32, xor 0u32));
rule.add_expr(&nft_expr!(cmp != 0u32));
rule.add_expr(&nft_expr!(counter));
rule.add_expr(&nft_expr!(verdict accept));
batch.add(&rule, nftnl::MsgType::Add);
let finalized_batch = batch.finalize();
send_and_process(&finalized_batch)?;
Ok(())
}
fn send_and_process(batch: &FinalizedBatch) -> std::result::Result<(), Error> {
let seq: u32 = 2;
let socket = mnl::Socket::new(mnl::Bus::Netfilter)?;
socket.send_all(batch)?;
let mut buffer = vec![0; nftnl::nft_nlmsg_maxsize() as usize];
while let Some(message) = socket_recv(&socket, &mut buffer[..])? {
match mnl::cb_run(message, seq, socket.portid())? {
mnl::CbResult::Stop => {
break;
}
mnl::CbResult::Ok => (),
}
}
Ok(())
}
fn socket_recv<'a>(
socket: &mnl::Socket,
buf: &'a mut [u8],
) -> std::result::Result<Option<&'a [u8]>, Error> {
let ret = socket.recv(buf)?;
if ret > 0 {
Ok(Some(&buf[..ret]))
} else {
Ok(None)
}
}

28
old/oldfuncs.rs Normal file
View File

@ -0,0 +1,28 @@
pub fn _search_subfolders(path: &Path) -> Vec<String> {
let dirs = std::fs::read_dir(path).unwrap();
let mut folders: Vec<String> = vec![];
for dir in dirs {
let dirpath = dir.unwrap().path();
let path = Path::new(dirpath.as_path());
if path.is_dir() {
folders.push(dirpath.to_str().unwrap().to_string());
for f in _search_subfolders(path) {
folders.push(f);
}
}
}
folders
}
pub fn _dedup<T: Ord + PartialOrd>(list: &mut Vec<T>) -> usize {
// Begin with sorting entries
list.sort();
// Then deduplicate
list.dedup();
// Return the length
list.len()
}
pub async fn _sleep_ms(ms: u64) {
sleep(Duration::from_millis(ms)).await;
}

View File

@ -1,4 +1,3 @@
use ipnet::Ipv4Net;
use nftnl::{nft_set, set::Set, Batch, FinalizedBatch, ProtoFamily, Table}; use nftnl::{nft_set, set::Set, Batch, FinalizedBatch, ProtoFamily, Table};
use std::{ffi::CString, io::*, net::Ipv4Addr}; use std::{ffi::CString, io::*, net::Ipv4Addr};

106
old/zmqcom.rs Normal file
View File

@ -0,0 +1,106 @@
use crate::config::{Context, ZMQCfg};
use crate::ip::IpEvent;
use crate::utils::gethostname;
use std::sync::Arc;
use tokio::sync::mpsc::Sender;
use tokio::sync::RwLock;
const ZMQPROTO: &str = "tcp";
pub async fn zconnect<'a>(
zmqcfg: &ZMQCfg,
zmqtype: zmq::SocketType,
) -> Result<zmq::Socket, zmq::Error> {
let zctx = zmq::Context::new();
let zmqhost = &zmqcfg.hostname;
let zmqport = &zmqcfg.port;
let socket = zctx.socket(zmqtype).unwrap();
let connectstring = format!("{ZMQPROTO}://{zmqhost}:{zmqport}");
socket.connect(&connectstring.as_str())?;
Ok(socket)
}
pub async fn zmqinit(ctx: &Arc<RwLock<Context>>, ipeventtx: &Sender<IpEvent>) -> zmq::Socket {
let ctxarc = Arc::clone(&ctx);
let zmqreqsocket;
let zmqsubsocket;
{
let zmqctx = ctxarc.read().await;
zmqreqsocket = zconnect(&zmqctx.cfg.zmq.get("reqrep").unwrap(), zmq::REQ)
.await
.unwrap();
zmqsubsocket = zconnect(&zmqctx.cfg.zmq.get("pubsub").unwrap(), zmq::SUB)
.await
.unwrap();
}
listenpubsub(&ctx, ipeventtx.clone(), zmqsubsocket).await;
return zmqreqsocket;
}
async fn listenpubsub(ctx: &Arc<RwLock<Context>>, txpubsub: Sender<IpEvent>, socket: zmq::Socket) {
let prefix;
{
let ctx = ctx.read().await;
prefix = format!(
"{sub} ",
sub = ctx.cfg.zmq.get("pubsub").unwrap().subscription
);
socket
.set_subscribe(ctx.cfg.zmq.get("pubsub").unwrap().subscription.as_bytes())
.expect("failed setting subscription");
}
tokio::spawn(async move {
loop {
let msgs: Option<String> = match socket.recv_string(0) {
Ok(s) => match s {
Ok(ss) => Some(ss),
Err(e) => {
println!("{e:?}");
None
}
},
Err(e) => {
println!("{e:?}");
None
}
};
match msgs {
Some(ss) => {
let msg = ss.strip_prefix(prefix.as_str()).unwrap();
let tosend: IpEvent = serde_json::from_str(msg).unwrap();
if tosend.ipdata.hostname != gethostname(true)
|| tosend.msgtype == "init".to_string()
{
txpubsub.send(tosend).await.unwrap();
}
}
None => {}
};
}
});
}
pub async fn send_to_ipbl_zmq(reqsocket: &zmq::Socket, ip: &IpEvent, _ret: &mut Vec<String>) {
let msg = format!("{val}", val = serde_json::to_string(&ip).unwrap());
match reqsocket.send(&msg, 0) {
Ok(_) => {}
Err(e) => {
println!("{e:?}")
}
};
match reqsocket.recv_string(0) {
Ok(o) => match o {
Ok(_) => {}
Err(ee) => {
println!("{ee:?}")
}
},
Err(e) => {
println!("{e:?}")
}
};
}

6
scripts/upgrade.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/sh
git pull
cargo b --release
sudo systemctl stop ipblc
sudo cp target/release/ipblc /usr/local/apps/ipblc/ipblc
sudo systemctl start ipblc

685
src/config.rs Normal file
View File

@ -0,0 +1,685 @@
use crate::ip::{BlockIpData, IpData, IpEvent};
use crate::utils::{gethostname, sleep_s};
use std::{
collections::HashMap,
hash::{Hash, Hasher},
path::Path,
};
use chrono::prelude::*;
use chrono::Duration;
use clap::{Arg, ArgAction, ArgMatches, Command};
use git_version::git_version;
use ipnet::IpNet;
use nix::sys::inotify::{AddWatchFlags, Inotify, WatchDescriptor};
use regex::Regex;
use reqwest::{Client, Error as ReqError, Response};
use serde::{Deserialize, Serialize};
pub const GIT_VERSION: &str = git_version!(args = ["--always", "--dirty="]);
const MASTERSERVER: &str = "ipbl.paulbsd.com";
const WSSUBSCRIPTION: &str = "ipbl";
const CONFIG_RETRY_INTERVAL: u64 = 2;
const WEB_CLIENT_TIMEOUT: i64 = 5;
#[derive(Debug)]
pub struct Context {
pub blocklist: HashMap<String, BlockIpData>,
pub cfg: Config,
pub discovery: Discovery,
pub flags: Flags,
pub sas: HashMap<String, SetMap>,
pub hashwd: HashMap<String, WatchDescriptor>,
pub reloadinterval: u64,
}
#[derive(Debug, Clone)]
pub struct SetMap {
pub filename: String,
pub fullpath: String,
pub regex: Regex,
pub set: SetCfg,
pub watchedfiles: HashMap<String, u64>,
pub wd: WatchDescriptor,
}
#[derive(Debug, Clone)]
pub struct Flags {
#[allow(dead_code)]
pub debug: bool,
pub server: String,
}
impl Context {
pub async fn new(inotify: &Inotify) -> Self {
// Get flags
let argp: ArgMatches = Context::argparse();
let debug: bool = argp.get_one::<bool>("debug").unwrap().to_owned();
let server: String = argp.get_one::<String>("server").unwrap().to_string();
// Build context
let mut ctx = Context {
cfg: Config::new(),
flags: Flags { debug, server },
discovery: Discovery {
version: "1.0".to_string(),
urls: HashMap::new(),
},
sas: HashMap::new(),
blocklist: HashMap::new(),
hashwd: HashMap::new(),
reloadinterval: 5,
};
print!("Loading config ... ");
ctx.load(&inotify).await.unwrap();
ctx
}
pub fn argparse() -> ArgMatches {
Command::new(env!("CARGO_PKG_NAME"))
.version(format!("{}@{}", env!("CARGO_PKG_VERSION"), GIT_VERSION))
.author(env!("CARGO_PKG_AUTHORS"))
.about(env!("CARGO_PKG_DESCRIPTION"))
.arg(
Arg::new("server")
.short('s')
.long("server")
.value_name("server")
.default_value(format!("https://{MASTERSERVER}"))
.help("Sets a http server"),
)
.arg(
Arg::new("debug")
.short('d')
.help("Enable debugging")
.action(ArgAction::SetTrue),
)
.get_matches()
}
#[allow(dead_code)]
pub async fn discovery(&self) -> Result<Discovery, ReqError> {
let resp: Result<Response, ReqError> = httpclient()
.get(format!("{server}/discovery", server = self.flags.server))
.send()
.await;
let req = match resp {
Ok(o) => o,
Err(e) => return Err(e),
};
let data: Discovery = match req.json().await {
Ok(o) => o,
Err(e) => return Err(e),
};
Ok(data)
}
pub async fn load(&mut self, inotify: &Inotify) -> Result<(), Box<dyn std::error::Error>> {
if cfg!(test) {
return Ok(());
}
let mut last_in_err = false;
loop {
let res = self.cfg.load(&self.flags.server).await;
match res {
Ok(()) => {
if last_in_err {
println!("loaded config");
}
break;
}
Err(e) => {
println!("error loading config: {e}, retrying in {CONFIG_RETRY_INTERVAL}s");
last_in_err = true;
sleep_s(CONFIG_RETRY_INTERVAL).await;
}
};
}
let mut last_in_err = false;
loop {
let res = self.discovery().await;
match res {
Ok(o) => {
self.discovery = o;
if last_in_err {
println!("loaded discovery");
}
break;
}
Err(e) => {
println!("error loading disvoery: {e}, retrying in {CONFIG_RETRY_INTERVAL}s");
last_in_err = true;
sleep_s(CONFIG_RETRY_INTERVAL).await;
}
};
}
if last_in_err {
println!("creating sas");
}
self.create_sas(&inotify).await?;
if last_in_err {
println!("created sas");
}
Ok(())
}
#[cfg(test)]
pub async fn get_blocklist_pending(&self) -> Vec<BlockIpData> {
let mut res: Vec<BlockIpData> = vec![];
for (_, ipblock) in self.blocklist.iter() {
res.push(ipblock.clone());
}
res
}
pub async fn get_blocklist_toblock(&self, all: bool) -> Vec<BlockIpData> {
let mut res: Vec<BlockIpData> = vec![];
for (_, ipblock) in self.blocklist.iter() {
match self.cfg.sets.get(&ipblock.ipdata.src) {
Some(set) => {
if ipblock.tryfail >= set.tryfail && (!ipblock.blocked || all) {
res.push(ipblock.clone());
}
}
None => {}
}
}
res
}
pub async fn update_blocklist(&mut self, ipevent: &IpEvent) -> Option<IpEvent> {
match &ipevent.ipdata {
Some(ipdata) => match self.cfg.sets.get(&ipdata.src) {
Some(set) => {
let starttime = DateTime::parse_from_rfc3339(ipdata.date.as_str())
.unwrap()
.with_timezone(&chrono::Local);
let blocktime = set.blocktime;
let blocked = false;
let handle = u64::MIN;
if ipevent.mode == "file".to_string() && gethostname(true) == ipevent.hostname {
let block =
self.blocklist
.entry(ipdata.ip.to_string())
.or_insert(BlockIpData {
ipdata: ipdata.clone(),
tryfail: 0,
starttime,
blocktime,
blocked,
handle,
});
block.tryfail += 1;
block.blocktime = blocktime;
if block.tryfail >= set.tryfail {
return Some(ipevent.clone());
}
} else {
self.blocklist
.entry(ipdata.ip.to_string())
.or_insert(BlockIpData {
ipdata: ipdata.clone(),
tryfail: set.tryfail,
starttime,
blocktime,
blocked,
handle,
});
}
}
None => {}
},
None => {}
}
None
}
pub async fn gc_blocklist(&mut self) -> Vec<BlockIpData> {
let mut removed: Vec<BlockIpData> = vec![];
let now: DateTime<Local> = Local::now().trunc_subsecs(0);
// nightly, future use
// let drained: HashMap<String,IpData> = ctx.blocklist.drain_filter(|k,v| v.parse_date() < mindate)
for (ip, blocked) in self.blocklist.clone().iter() {
/*match self.cfg.sets.get(&blocked.ipdata.src) {
Some(set) => {
let mut block = self.blocklist.get_mut(ip).unwrap();
block.blocktime = set.blocktime.clone();
}
None => {}
}*/
let mindate = now - Duration::minutes(blocked.blocktime);
if blocked.starttime < mindate {
self.blocklist.remove(&ip.clone()).unwrap();
removed.push(blocked.clone());
}
}
removed
}
pub async fn create_sas(
&mut self,
inotify: &Inotify,
) -> Result<(), Box<dyn std::error::Error>> {
for (src, set) in self.cfg.sets.iter() {
let p = Path::new(set.path.as_str());
if p.is_dir() {
let wd = match self.hashwd.get(&set.path.to_string()) {
Some(wd) => *wd,
None => {
let res = inotify
.add_watch(set.path.as_str(), AddWatchFlags::IN_MODIFY)
.unwrap();
self.hashwd.insert(set.path.to_string(), res);
res
}
};
let fullpath: String = match set.filename.as_str() {
"" => set.path.clone(),
_ => {
format!(
"{path}/{filename}",
path = set.path,
filename = set.filename.clone()
)
}
};
match self.sas.get_mut(&src.clone()) {
Some(s) => {
s.filename = set.filename.clone();
s.fullpath = fullpath;
s.set = set.clone();
s.regex = Regex::new(set.regex.as_str()).unwrap();
}
None => {
self.sas.insert(
src.clone(),
SetMap {
filename: set.filename.clone(),
fullpath,
set: set.clone(),
regex: Regex::new(set.regex.as_str()).unwrap(),
wd,
watchedfiles: HashMap::new(),
},
);
}
}
}
}
Ok(())
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Config {
pub sets: HashMap<String, SetCfg>,
#[serde(skip_serializing)]
pub trustnets: Vec<String>,
pub ws: HashMap<String, WebSocketCfg>,
pub api: String,
}
impl Config {
pub fn new() -> Self {
Self {
sets: HashMap::from([
("smtp".to_string(),
SetCfg {
src: "smtp".to_string(),
filename: "mail.log".to_string(),
regex: "(SASL LOGIN authentication failed)".to_string(),
path: "/var/log".to_string(),
blocktime: 60,
tryfail: 5,
}),
("ssh".to_string(),
SetCfg {
src: "ssh".to_string(),
filename: "auth.log".to_string(),
regex: "(Invalid user|BREAK|not allowed because|no matching key exchange method found)".to_string(),
path: "/var/log".to_string(),
blocktime: 60,
tryfail: 5,
},),
("http".to_string(),
SetCfg {
src: "http".to_string(),
filename: "".to_string(),
regex: "(anonymousfox.co)".to_string(),
path: "/var/log/nginx".to_string(),
blocktime: 60,
tryfail: 5,
},),
("openvpn".to_string(),
SetCfg {
src: "openvpn".to_string(),
filename: "status".to_string(),
regex: "(UNDEF)".to_string(),
path: "/var/run/openvpn".to_string(),
blocktime: 60,
tryfail: 5,
},),
]),
trustnets: vec![
"127.0.0.0/8".to_string(),
"10.0.0.0/8".to_string(),
"172.16.0.0/12".to_string(),
"192.168.0.0/16".to_string(),
],
ws: HashMap::from([("pubsub".to_string(),WebSocketCfg{
t: "pubsub".to_string(),
endpoint: format!("wss://{}/wsps", MASTERSERVER.to_string()),
subscription: WSSUBSCRIPTION.to_string(),
}),("reqrep".to_string(), WebSocketCfg {
t: "reqrep".to_string(),
endpoint: format!("wss://{}/wsrr", MASTERSERVER.to_string()),
subscription: WSSUBSCRIPTION.to_string(),
})]),
api: String::from("127.0.0.1:8060")
}
}
pub async fn load(&mut self, server: &String) -> Result<(), ReqError> {
self.get_config(server).await?;
Ok(())
}
async fn get_config(&mut self, server: &String) -> Result<(), ReqError> {
let resp: Result<Response, ReqError> = httpclient()
.get(format!("{server}/config?v=2"))
.send()
.await;
let req = match resp {
Ok(o) => o,
Err(e) => return Err(e),
};
let data: GlobalConfigV2 = match req.json::<GlobalConfigV2>().await {
Ok(o) => o,
Err(e) => return Err(e),
};
for d in data.sets {
self.sets.insert(d.src.clone(), d);
}
self.trustnets = data.trustlists;
data.ws.into_iter().map(|x| x).for_each(|x| {
self.ws.insert(x.t.to_string(), x);
});
self.api = data
.cfg
.get(&"api".to_string())
.unwrap_or(&self.api)
.clone();
Ok(())
}
pub async fn _get_last(server: &String) -> Result<Vec<IpData>, ReqError> {
let resp = httpclient()
.get(format!("{server}/ips/last"))
.query(&[("interval", "3 hours")])
.send()
.await;
let req = match resp {
Ok(o) => o,
Err(e) => return Err(e),
};
let data: Vec<IpData> = match req.json::<Vec<IpData>>().await {
Ok(o) => o,
Err(e) => return Err(e),
};
Ok(data)
}
pub fn build_trustnets(&self) -> Vec<IpNet> {
let mut trustnets: Vec<IpNet> = vec![];
for trustnet in &self.trustnets {
match trustnet.parse() {
Ok(net) => trustnets.push(net),
Err(e) => {
println!("error parsing {trustnet}, error: {e}");
}
};
}
trustnets
}
pub fn bootstrap_event(&self) -> IpEvent {
IpEvent {
msgtype: String::from("bootstrap"),
mode: String::from("ws"),
hostname: gethostname(true),
ipdata: None,
}
}
}
pub fn httpclient() -> Client {
let client = Client::builder()
.user_agent(format!(
"{}/{}@{}/{}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
GIT_VERSION,
gethostname(false)
))
.timeout(Duration::seconds(WEB_CLIENT_TIMEOUT).to_std().unwrap())
.build()
.unwrap();
client
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct GlobalConfigV2 {
pub cfg: HashMap<String, String>,
pub sets: Vec<SetCfg>,
pub trustlists: Vec<String>,
pub ws: Vec<WebSocketCfg>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct SetCfg {
pub src: String,
pub filename: String,
pub regex: String,
pub path: String,
pub blocktime: i64,
pub tryfail: i64,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct WebSocketCfg {
#[serde(rename = "type")]
pub t: String,
pub endpoint: String,
pub subscription: String,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Discovery {
pub version: String,
pub urls: HashMap<String, URL>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct URL {
pub key: String,
pub path: String,
}
impl PartialEq for SetCfg {
fn eq(&self, other: &Self) -> bool {
self.src == other.src
}
}
impl Hash for SetCfg {
fn hash<H: Hasher>(&self, state: &mut H) {
self.src.hash(state);
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::ip::*;
use nix::sys::inotify::InitFlags;
use Context;
pub async fn prepare_test_data() -> Context {
let inotify = Inotify::init(InitFlags::empty()).unwrap();
let mut ctx = Context::new(&inotify).await;
let now: DateTime<Local> = Local::now().trunc_subsecs(0);
ctx.blocklist = HashMap::new();
for _i in 0..10 {
ctx.update_blocklist(&mut IpEvent {
msgtype: String::from("add"),
mode: String::from("ws"),
hostname: String::from("localhost"),
ipdata: Some(IpData {
t: 4,
ip: "1.1.1.1".to_string(),
hostname: "test1".to_string(),
date: now.to_rfc3339().to_string(),
src: "ssh".to_string(),
}),
})
.await;
}
for _ in 0..10 {
ctx.update_blocklist(&mut IpEvent {
msgtype: String::from("add"),
mode: String::from("ws"),
hostname: String::from("localhost"),
ipdata: Some(IpData {
t: 4,
ip: "1.1.1.2".to_string(),
hostname: "test2".to_string(),
date: now.to_rfc3339().to_string(),
src: "http".to_string(),
}),
})
.await;
}
ctx.update_blocklist(&mut IpEvent {
msgtype: String::from("add"),
mode: String::from("ws"),
hostname: String::from("localhost"),
ipdata: Some(IpData {
t: 4,
ip: "1.1.1.3".to_string(),
hostname: "testgood".to_string(),
date: now.to_rfc3339().to_string(),
src: "http".to_string(),
}),
})
.await;
ctx.update_blocklist(&mut IpEvent {
msgtype: String::from("add"),
mode: String::from("ws"),
hostname: String::from("localhost"),
ipdata: Some(IpData {
t: 4,
ip: "1.1.1.4".to_string(),
hostname: "testgood".to_string(),
date: now.to_rfc3339().to_string(),
src: "http".to_string(),
}),
})
.await;
ctx.update_blocklist(&mut IpEvent {
msgtype: String::from("add"),
mode: String::from("ws"),
hostname: String::from("localhost"),
ipdata: Some(IpData {
t: 4,
ip: "1.1.1.4".to_string(),
hostname: "testgood".to_string(),
date: now.to_rfc3339().to_string(),
src: "http".to_string(),
}),
})
.await;
ctx.update_blocklist(&mut IpEvent {
msgtype: String::from("add"),
mode: String::from("ws"),
hostname: String::from("localhost"),
ipdata: Some(IpData {
t: 6,
ip: "2a00:1450:4007:805::2003".to_string(),
hostname: "testgood".to_string(),
date: now.to_rfc3339().to_string(),
src: "http".to_string(),
}),
})
.await;
let ip1 = ctx.blocklist.get_mut(&"1.1.1.1".to_string()).unwrap();
ip1.starttime = DateTime::from(now) - Duration::minutes(61);
let ip2 = ctx.blocklist.get_mut(&"1.1.1.2".to_string()).unwrap();
ip2.starttime = DateTime::from(now) - Duration::minutes(62);
ctx
}
#[tokio::test]
pub async fn test_blocklist_pending() {
let ctx = prepare_test_data().await;
let pending = ctx.get_blocklist_pending().await;
assert_eq!(pending.len(), 5);
let ips = [
"1.1.1.1",
"1.1.1.2",
"1.1.1.3",
"1.1.1.4",
"2a00:1450:4007:805::2003",
];
for i in ips {
let ip = ctx
.blocklist
.get(&i.to_string())
.unwrap()
.ipdata
.ip
.as_str();
assert_eq!(ip, i);
}
}
#[tokio::test]
pub async fn test_blocklist_toblock() {
let mut ctx = prepare_test_data().await;
ctx.gc_blocklist().await;
let toblock = ctx.get_blocklist_toblock(false).await;
assert_eq!(toblock.len(), 3);
}
#[tokio::test]
pub async fn test_blocklist_gc() {
let mut ctx = prepare_test_data().await;
let after_gc = ctx.gc_blocklist().await;
assert_eq!(after_gc.len(), 2);
let ips = &["1.1.1.3", "1.1.1.4"];
for ip in ips {
let ipstring = ip.to_string();
assert_eq!(ctx.blocklist.get(&ipstring).unwrap().ipdata.ip, ipstring);
}
}
}

View File

@ -1,384 +0,0 @@
use crate::ip::*;
use crate::utils::*;
use chrono::prelude::*;
use chrono::Duration;
use clap::{Arg, ArgMatches, Command};
use nix::sys::inotify::{AddWatchFlags, InitFlags, Inotify, WatchDescriptor};
use regex::Regex;
use reqwest::{Client, Error as ReqError, Response};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::path::Path;
const SERVER: &str = "ipbl.paulbsd.com";
#[derive(Debug, Clone)]
pub struct Context {
pub blocklist: HashMap<String, IpData>,
pub cfg: Config,
pub client: Client,
pub discovery: Discovery,
pub flags: Flags,
pub hostname: String,
pub instance: Box<Inotify>,
pub sas: HashMap<String, SetMap>,
pub hashwd: HashMap<String, WatchDescriptor>,
}
#[derive(Debug, Clone)]
pub struct SetMap {
pub filename: String,
pub fullpath: String,
pub regex: Regex,
pub set: Set,
pub watchedfiles: HashMap<String, u64>,
pub wd: WatchDescriptor,
}
#[derive(Debug, Clone)]
pub struct Flags {
pub debug: bool,
pub interval: usize,
pub server: String,
}
impl Context {
pub async fn new() -> Self {
// Get flags
let debug = Context::argparse().is_present("debug");
let server = Context::argparse()
.value_of("server")
.unwrap_or(format!("https://{}", SERVER).as_str())
.to_string();
// Build context
let mut ctx = Context {
cfg: Config::new(),
flags: Flags {
debug: debug,
server: server,
interval: 60,
},
hostname: gethostname(true),
discovery: Discovery {
version: "1.0".to_string(),
urls: HashMap::new(),
},
client: Client::builder()
.user_agent(format!(
"{}/{}@{}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
gethostname(false)
))
.build()
.unwrap(),
sas: HashMap::new(),
instance: Box::new(Inotify::init(InitFlags::empty()).unwrap()),
blocklist: HashMap::new(),
hashwd: HashMap::new(),
};
ctx.discovery = ctx.discovery().await.unwrap();
print!("Loading config ... ");
match ctx.load().await {
Ok(_) => {}
Err(err) => {
println!("error loading config: {err}");
std::process::exit(1);
}
}
ctx
}
pub fn argparse() -> ArgMatches {
Command::new(env!("CARGO_PKG_NAME"))
.version(env!("CARGO_PKG_VERSION"))
.author(env!("CARGO_PKG_AUTHORS"))
.about(env!("CARGO_PKG_DESCRIPTION"))
.arg(
Arg::new("server")
.short('s')
.long("server")
.value_name("server")
.default_value("https://ipbl.paulbsd.com")
.help("Sets a http server")
.takes_value(true),
)
.arg(
Arg::new("debug")
.short('d')
.takes_value(false)
.help("Enable debugging"),
)
.get_matches()
}
pub async fn discovery(&self) -> Result<Discovery, ReqError> {
let resp: Result<Response, ReqError> = self
.client
.get(format!("{server}/discovery", server = self.flags.server))
.send()
.await;
let req = match resp {
Ok(re) => re,
Err(err) => return Err(err),
};
let data: Discovery = match req.json().await {
Ok(res) => res,
Err(err) => return Err(err),
};
Ok(data)
}
pub async fn load(&mut self) -> Result<(), Box<dyn std::error::Error>> {
self.cfg.load(self.to_owned()).await?;
self.create_sas().await?;
Ok(())
}
pub async fn get_blocklist(&self) -> Vec<IpData> {
let mut res: Vec<IpData> = vec![];
for (_, v) in self.blocklist.iter() {
res.push(v.clone());
}
res
}
pub async fn gc_blocklist(&mut self) -> Vec<IpData> {
let now: DateTime<Local> = Local::now().trunc_subsecs(0);
let delta: Duration = Duration::minutes(self.flags.interval as i64);
let mindate = now - delta;
let mut toremove: Vec<IpData> = vec![];
// nightly, future use
//let drained: HashMap<String,IpData> = ctx.blocklist.drain_filter(|k,v| v.parse_date() < mindate)
for (k, v) in self.blocklist.clone().iter() {
if v.parse_date() < mindate {
self.blocklist.remove(&k.to_string()).unwrap();
toremove.push(v.clone());
}
}
toremove
}
pub async fn update_blocklist(&mut self, ip: &IpData) {
if !self.blocklist.contains_key(&ip.ip) {
self.blocklist.insert(ip.ip.clone(), ip.clone());
}
}
pub async fn create_sas(&mut self) -> Result<(), Box<dyn std::error::Error>> {
for set in &self.cfg.sets {
let p = Path::new(set.path.as_str());
if p.is_dir() {
let res = match self.hashwd.get(&set.path.to_string()) {
Some(wd) => *wd,
None => {
let res = self
.instance
.add_watch(set.path.as_str(), AddWatchFlags::IN_MODIFY)
.unwrap();
self.hashwd.insert(set.path.to_string(), res);
res
}
};
let fullpath: String = match set.filename.as_str() {
"" => set.path.clone(),
_ => {
format!(
"{path}/{filename}",
path = set.path,
filename = set.filename.clone()
)
}
};
self.sas.insert(
set.t.clone(),
SetMap {
filename: set.filename.clone(),
fullpath: fullpath,
set: set.clone(),
regex: Regex::new(set.regex.as_str()).unwrap(),
wd: res,
watchedfiles: HashMap::new(),
},
);
}
}
Ok(())
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Config {
pub sets: Vec<Set>,
#[serde(skip_serializing)]
pub trustnets: Vec<String>,
pub zmq: HashMap<String, ZMQ>,
}
impl Config {
pub fn new() -> Self {
Self {
sets: vec![
Set {
t: "smtp".to_string(),
filename: "mail.log".to_string(),
regex: "(SASL LOGIN authentication failed)".to_string(),
path: "/var/log".to_string(),
},
Set {
t: "ssh".to_string(),
filename: "auth.log".to_string(),
regex: "(Invalid user|BREAK|not allowed because|no matching key exchange method found)".to_string(),
path: "/var/log".to_string(),
},
Set {
t: "http".to_string(),
filename: "".to_string(),
regex: "(anonymousfox.co)".to_string(),
path: "/var/log/nginx".to_string(),
}
,Set {
t: "openvpn".to_string(),
filename: "status".to_string(),
regex: "(UNDEF)".to_string(),
path: "/var/run/openvpn".to_string(),
},
],
trustnets: vec![
"127.0.0.0/8".to_string(),
"10.0.0.0/8".to_string(),
"172.16.0.0/12".to_string(),
"192.168.0.0/16".to_string(),
],
zmq: HashMap::from([("pubsub".to_string(),ZMQ{
t: "pubsub".to_string(),
hostname: SERVER.to_string(),
port: 9999,
subscription: "ipbl".to_string(),
}),("reqrep".to_string(),ZMQ {
t: "reqrep".to_string(),
hostname: SERVER.to_string(),
port: 9998,
subscription: String::new(),
})])
}
}
pub async fn load(&mut self, ctx: Context) -> Result<(), ReqError> {
self.get_sets(&ctx).await?;
self.get_trustnets(&ctx).await?;
self.get_zmq_config(&ctx).await?;
Ok(())
}
async fn get_trustnets(&mut self, ctx: &Context) -> Result<(), ReqError> {
let resp: Result<Response, ReqError> = ctx
.client
.get(format!(
"{server}/config/trustlist",
server = ctx.flags.server
))
.send()
.await;
let req = match resp {
Ok(re) => re,
Err(err) => return Err(err),
};
let data: Vec<String> = match req.json::<Vec<String>>().await {
Ok(res) => res,
Err(err) => return Err(err),
};
self.trustnets = data;
Ok(())
}
async fn get_sets(&mut self, ctx: &Context) -> Result<(), ReqError> {
let resp: Result<Response, ReqError> = ctx
.client
.get(format!("{server}/config/sets", server = ctx.flags.server))
.send()
.await;
let req = match resp {
Ok(re) => re,
Err(err) => return Err(err),
};
let data: Vec<Set> = match req.json::<Vec<Set>>().await {
Ok(res) => res,
Err(err) => return Err(err),
};
self.sets = data;
Ok(())
}
async fn get_zmq_config(&mut self, ctx: &Context) -> Result<(), ReqError> {
let resp: Result<Response, ReqError> = ctx
.client
.get(format!("{server}/config/zmq", server = ctx.flags.server))
.send()
.await;
let req = match resp {
Ok(re) => re,
Err(err) => return Err(err),
};
let data: HashMap<String, ZMQ> = match req.json::<Vec<ZMQ>>().await {
Ok(res) => {
let mut out: HashMap<String, ZMQ> = HashMap::new();
res.into_iter().map(|x| x).for_each(|x| {
out.insert(x.t.to_string(), x);
});
out
}
Err(err) => return Err(err),
};
self.zmq = data;
Ok(())
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Set {
#[serde(rename = "type")]
pub t: String,
pub filename: String,
pub regex: String,
pub path: String,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ZMQ {
#[serde(rename = "type")]
pub t: String,
pub hostname: String,
pub port: i64,
pub subscription: String,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Discovery {
pub version: String,
pub urls: HashMap<String, URL>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct URL {
pub key: String,
pub path: String,
}
impl PartialEq for Set {
fn eq(&self, other: &Self) -> bool {
self.t == other.t
}
}
impl Eq for Set {}
impl Hash for Set {
fn hash<H: Hasher>(&self, state: &mut H) {
self.t.hash(state);
}
}

View File

@ -1,95 +0,0 @@
use crate::ip::*;
use nftnl::{nft_expr, Batch, Chain, FinalizedBatch, ProtoFamily, Rule, Table};
use std::{ffi::CString, io::*, net::Ipv4Addr};
pub fn init(tablename: &String) -> (Batch, Table) {
let mut batch = Batch::new();
let table = Table::new(
&CString::new(tablename.as_str()).unwrap(),
ProtoFamily::Ipv4,
);
batch.add(&table, nftnl::MsgType::Add);
batch.add(&table, nftnl::MsgType::Del);
batch.add(&table, nftnl::MsgType::Add);
(batch, table)
}
pub fn block(
tablename: &String,
ips_add: &Vec<IpData>,
ret: &mut Vec<String>,
) -> std::result::Result<(), Error> {
// convert chain
let ips_add = convert(ips_add);
let (mut batch, table) = init(tablename);
// build chain
let mut chain = Chain::new(&CString::new(tablename.as_str()).unwrap(), &table);
chain.set_hook(nftnl::Hook::In, 1);
chain.set_policy(nftnl::Policy::Accept);
// add chain
batch.add(&chain, nftnl::MsgType::Add);
// build and add rules
for ip in ips_add.clone() {
let mut rule = Rule::new(&chain);
rule.add_expr(&nft_expr!(payload ipv4 saddr));
rule.add_expr(&nft_expr!(cmp == ip));
rule.add_expr(&nft_expr!(ct state));
rule.add_expr(&nft_expr!(bitwise mask 10u32, xor 0u32));
rule.add_expr(&nft_expr!(cmp != 0u32));
rule.add_expr(&nft_expr!(counter));
rule.add_expr(&nft_expr!(verdict drop));
batch.add(&rule, nftnl::MsgType::Add);
}
// validate and send batch
let finalized_batch = batch.finalize();
send_and_process(&finalized_batch)?;
ret.push(format!(
"nftables: {length} ip in memory",
length = ips_add.len()
));
Ok(())
}
fn send_and_process(batch: &FinalizedBatch) -> std::result::Result<(), Error> {
let seq: u32 = 2;
let socket = mnl::Socket::new(mnl::Bus::Netfilter)?;
socket.send_all(batch)?;
let mut buffer = vec![0; nftnl::nft_nlmsg_maxsize() as usize];
while let Some(message) = socket_recv(&socket, &mut buffer[..])? {
match mnl::cb_run(message, seq, socket.portid())? {
mnl::CbResult::Stop => {
break;
}
mnl::CbResult::Ok => (),
}
}
Ok(())
}
fn socket_recv<'a>(
socket: &mnl::Socket,
buf: &'a mut [u8],
) -> std::result::Result<Option<&'a [u8]>, Error> {
let ret = socket.recv(buf)?;
if ret > 0 {
Ok(Some(&buf[..ret]))
} else {
Ok(None)
}
}
fn convert(input: &Vec<IpData>) -> Vec<Ipv4Addr> {
let mut output: Vec<Ipv4Addr> = vec![];
for val in input {
output.push(val.ip.parse::<Ipv4Addr>().unwrap());
}
output
}

208
src/fw.rs Normal file
View File

@ -0,0 +1,208 @@
use crate::{config::Context, ip::BlockIpData, ipblc::PKG_NAME};
use std::{
io::Error,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
sync::Arc,
};
use tokio::sync::RwLock;
use rustables::{expr::*, *};
pub enum FwTableType {
IPv4,
IPv6,
}
#[allow(dead_code)]
pub enum FwAction {
Add,
Delete,
}
macro_rules! initrules {
($batch:expr, $table:expr, $chain:ident, $reset:expr) => {
$chain.set_hook(Hook::new(HookClass::In, 1));
$batch.add(&$chain, MsgType::Add);
if $reset {
$batch.add(&Rule::new(&$chain).unwrap(), MsgType::Del);
}
};
}
macro_rules! makerules {
($ipdata:ident, $chain:ident, $batch:ident, $t:ty, $ip_t:ident,$action:ty) => {
let ip = $ipdata.ipdata.ip.parse::<$t>().unwrap();
Rule::new(&$chain)
.unwrap()
.saddr(ip.into())
.drop()
.add_to_batch(&mut $batch);
};
}
pub fn fwglobalinit(t: FwTableType, reset: bool) -> (Batch, Chain) {
let table_name: String;
let table: Table;
let mut chain: Chain;
match t {
FwTableType::IPv4 => {
table_name = format!("{PKG_NAME}4");
table = Table::new(ProtocolFamily::Ipv4).with_name(table_name);
chain = Chain::new(&table)
.with_policy(ChainPolicy::Accept)
.with_name(PKG_NAME);
}
FwTableType::IPv6 => {
table_name = format!("{PKG_NAME}6");
table = Table::new(ProtocolFamily::Ipv6).with_name(table_name);
chain = Chain::new(&table)
.with_policy(ChainPolicy::Accept)
.with_name(PKG_NAME);
}
}
let mut batch = Batch::new();
batch.add(&table, MsgType::Add);
initrules!(batch, table, chain, reset);
(batch, chain)
}
pub fn fwblock<'a>(ip_add: &BlockIpData) -> std::result::Result<&String, error::QueryError> {
let (mut batch4, chain4) = fwglobalinit(FwTableType::IPv4, false);
let (mut batch6, chain6) = fwglobalinit(FwTableType::IPv6, false);
match ip_add.ipdata.t {
4 => {
makerules!(ip_add, chain4, batch4, Ipv4Addr, ipv4, FwAction::Add);
match batch4.send() {
Ok(_) => {}
Err(e) => {
println!("block not ok {e} {ip_add:?}")
}
}
}
6 => {
makerules!(ip_add, chain6, batch6, Ipv6Addr, ipv6, FwAction::Add);
match batch6.send() {
Ok(_) => {}
Err(e) => {
println!("block not ok {e} {ip_add:?}")
}
}
}
_ => {}
}
Ok(&ip_add.ipdata.ip)
}
pub fn fwunblock<'a>(ip_del: &BlockIpData) -> std::result::Result<&String, error::QueryError> {
let (mut batch4, chain4) = fwglobalinit(FwTableType::IPv4, false);
let (mut batch6, chain6) = fwglobalinit(FwTableType::IPv6, false);
match ip_del.ipdata.t {
4 => {
let r = Rule::new(&chain4).unwrap().with_handle(ip_del.handle);
batch4.add(&r, MsgType::Del);
match batch4.send() {
Ok(_) => {}
Err(e) => {
println!("delete not ok {e} {ip_del:?}")
}
}
}
6 => {
let r = Rule::new(&chain6).unwrap().with_handle(ip_del.handle);
batch6.add(&r, MsgType::Del);
match batch6.send() {
Ok(_) => {}
Err(e) => {
println!("delete not ok {e} {ip_del:?}")
}
}
}
_ => {}
}
Ok(&ip_del.ipdata.ip)
}
pub async fn get_current_rules(
ctx: &Arc<RwLock<Context>>,
ret: &mut Vec<String>,
fwlen: &mut usize,
) -> Result<(), Error> {
let mut ips_all_count = 0;
let tables = vec![format!("{PKG_NAME}4"), format!("{PKG_NAME}6")];
for table_name in tables {
let get_table = || -> Result<Option<Table>, Error> {
let tables = list_tables().unwrap();
for table in tables {
if let Some(name) = table.get_name() {
if *name == table_name {
return Ok(Some(table));
}
}
}
Ok(None)
};
let get_chain = |table: &Table| -> Result<Option<Chain>, Error> {
let chains = list_chains_for_table(table).unwrap();
for chain in chains {
if let Some(name) = chain.get_name() {
if *name == "ipblc" {
return Ok(Some(chain));
}
}
}
Ok(None)
};
let table = get_table()?.expect("no table?");
let chain = get_chain(&table)?.expect("no chain?");
let mut ctx = { ctx.write().await };
let rules = list_rules_for_chain(&chain).unwrap().clone();
for (ip, c) in ctx.blocklist.iter_mut() {
let ip_parsed: IpAddr = ip.parse().unwrap();
let cmprule = Rule::new(&chain).unwrap().saddr(ip_parsed).drop();
let mut gexpr = RawExpression::default();
for e in cmprule.get_expressions().unwrap().iter() {
if let Some(ExpressionVariant::Cmp(_ip)) = e.get_data() {
gexpr = e.clone();
}
}
for rule in rules.iter() {
for expr in rule.get_expressions().unwrap().iter() {
if let Some(expr::ExpressionVariant::Cmp(_)) = expr.get_data() {
if gexpr == expr.clone() {
ips_all_count += 1;
c.handle = *rule.get_handle().unwrap();
}
}
}
}
}
}
if *fwlen != ips_all_count {
ret.push(format!("{length} ip in firewall", length = ips_all_count));
}
*fwlen = ips_all_count;
Ok(())
}
#[allow(dead_code)]
fn fw_rules_count() -> i64 {
0
}

230
src/ip.rs
View File

@ -1,16 +1,18 @@
use crate::config::Context; use crate::utils::gethostname;
use crate::utils::*;
use std::{
cmp::Ordering,
fmt::{Display, Formatter},
io::{BufRead, BufReader, Read},
net::IpAddr,
};
use chrono::offset::LocalResult;
use chrono::prelude::*; use chrono::prelude::*;
use ipnet::IpNet; use ipnet::IpNet;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use reqwest::Error as ReqError;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt::{Display, Formatter};
use std::io::{BufRead, BufReader, Read};
use std::net::IpAddr;
lazy_static! { lazy_static! {
static ref R_IPV4: Regex = Regex::new(include_str!("regexps/ipv4.txt")).unwrap(); static ref R_IPV4: Regex = Regex::new(include_str!("regexps/ipv4.txt")).unwrap();
@ -18,18 +20,64 @@ lazy_static! {
static ref R_DATE: Regex = Regex::new(include_str!("regexps/date.txt")).unwrap(); static ref R_DATE: Regex = Regex::new(include_str!("regexps/date.txt")).unwrap();
} }
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct IpEvent {
pub msgtype: String,
pub mode: String,
pub hostname: String,
pub ipdata: Option<IpData>,
}
#[macro_export]
macro_rules! ipevent {
($msgtype:expr,$mode:expr,$hostname:expr,$ipdata:expr) => {
IpEvent {
msgtype: String::from($msgtype),
mode: String::from($mode),
hostname: $hostname,
ipdata: $ipdata,
}
};
($msgtype:expr,$mode:expr,$hostname:expr) => {
IpEvent {
msgtype: String::from($msgtype),
mode: String::from($mode),
hostname: $hostname,
ipdata: None,
}
};
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BlockIpData {
pub ipdata: IpData,
pub tryfail: i64,
pub blocktime: i64,
pub starttime: DateTime<Local>,
pub blocked: bool,
pub handle: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq)] #[derive(Clone, Debug, Serialize, Deserialize, Eq)]
pub struct IpData { pub struct IpData {
pub t: isize,
pub ip: String, pub ip: String,
pub src: String, pub src: String,
pub date: String, pub date: String,
pub hostname: String, pub hostname: String,
} }
impl IpData { #[macro_export]
pub fn parse_date(&self) -> DateTime<FixedOffset> { macro_rules! ipdata {
DateTime::parse_from_rfc3339(self.date.as_str()).unwrap() ($t:expr,$ip:expr,$src:expr,$date:expr,$hostname:expr) => {
IpData {
t: $t.clone(),
ip: $ip.clone(),
src: $src.clone(),
date: $date.clone(),
hostname: $hostname.clone(),
} }
};
} }
impl PartialEq for IpData { impl PartialEq for IpData {
@ -66,116 +114,38 @@ impl Display for IpData {
} }
} }
pub async fn push_ip(ctx: &Context, ip: &IpData, ret: &mut Vec<String>) -> Result<(), ReqError> {
let result: String;
let mut data: Vec<IpData> = vec![];
data.push(IpData {
ip: ip.ip.to_string(),
src: ip.src.to_string(),
date: ip.date.to_string(),
hostname: ip.hostname.to_string(),
});
let resp = ctx
.client
.post(format!("{server}/ips", server = ctx.flags.server))
.json(&data)
.send()
.await?;
ret.push(format!("status: {status}", status = resp.status()));
let res = resp.text().await.unwrap();
if res.trim().len() > 0 {
result = res.trim().to_string();
} else {
result = "".to_string();
}
ret.push(format!("response: {result}"));
Ok(())
}
pub async fn _push_ip_bulk(
ctx: &Context,
ips: &Vec<IpData>,
ret: &mut Vec<String>,
) -> Result<(), ReqError> {
let result: String;
let mut data: Vec<IpData> = vec![];
for ip in ips {
data.push(IpData {
ip: ip.ip.to_string(),
src: ip.src.to_string(),
date: ip.date.to_string(),
hostname: ip.hostname.to_string(),
})
}
let resp = ctx
.client
.post(format!("{server}/ips", server = ctx.flags.server))
.json(&data)
.send()
.await?;
ret.push(format!("status: {status}", status = resp.status()));
let res = resp.text().await.unwrap();
if res.trim().len() > 0 {
result = res.trim().to_string();
} else {
result = "".to_string();
}
ret.push(format!("response: {result}"));
Ok(())
}
pub fn filter( pub fn filter(
lines: Box<dyn Read>, reader: Box<dyn Read>,
list: &mut Vec<IpData>, iplist: &mut Vec<IpData>,
trustnets: &Vec<IpNet>, trustnets: &Vec<IpNet>,
regex: &Regex, regex: &Regex,
src: &String, src: &String,
lastprocess: &DateTime<Local>, last: &DateTime<Local>,
) -> isize { ) -> isize {
let mut ips = 0; let mut ips = 0;
let hostname = gethostname(true); let hostname = gethostname(true);
for line in BufReader::new(lines).lines() { let lines = BufReader::new(reader).lines();
for line in lines.into_iter() {
if let Ok(l) = line { if let Ok(l) = line {
if regex.is_match(l.as_str()) { if regex.is_match(l.as_str()) {
let s_ipaddr: String; let s_ipaddr: String;
let t: isize;
match R_IPV4.captures(l.as_str()) { match R_IPV4.captures(l.as_str()) {
Some(sv4) => { Some(sv4) => {
s_ipaddr = sv4.get(0).unwrap().as_str().to_string(); s_ipaddr = sv4.get(0).unwrap().as_str().to_string();
t = 4;
} }
None => { None => {
continue; match R_IPV6.captures(l.as_str()) {
/*match R_IPV6.captures(l.as_str()) {
Some(sv6) => { Some(sv6) => {
s_ipaddr = sv6.get(0).unwrap().as_str().to_string(); s_ipaddr = sv6.get(0).unwrap().as_str().to_string();
t = 6;
} }
None => { None => {
continue; continue;
} }
};*/
}
}; };
let s_date: DateTime<Local>;
match R_DATE.captures(l.as_str()) {
Some(sdt) => {
s_date = parse_date(sdt);
if &s_date < lastprocess {
continue;
}
}
None => {
s_date = Local::now();
} }
}; };
@ -187,13 +157,21 @@ pub fn filter(
} }
}; };
let s_date: DateTime<Local>;
match R_DATE.captures(l.as_str()) {
Some(sdt) => {
s_date = parse_date(sdt);
if &s_date < last {
continue;
}
}
None => {
s_date = Local::now();
}
};
if !is_trusted(&ipaddr, &trustnets) { if !is_trusted(&ipaddr, &trustnets) {
list.push(IpData { iplist.push(ipdata!(t, s_ipaddr, src, s_date.to_rfc3339(), hostname));
ip: s_ipaddr,
src: src.to_owned(),
date: s_date.to_rfc3339().to_owned(),
hostname: hostname.to_owned(),
});
ips += 1; ips += 1;
}; };
} }
@ -203,21 +181,24 @@ pub fn filter(
} }
fn parse_date(input: regex::Captures) -> DateTime<Local> { fn parse_date(input: regex::Captures) -> DateTime<Local> {
let mut ymd: Vec<u64> = vec![]; let mut ymd: Vec<u32> = vec![];
let mut hms: Vec<u64> = vec![]; let mut hms: Vec<u32> = vec![];
let ymd_range = 2..5;
let hms_range = 5..8;
let (daterange, hourrange) = (2..5, 5..8); for cap in ymd_range {
ymd.push(input.get(cap).unwrap().as_str().parse::<u32>().unwrap());
for i in daterange {
ymd.push(input.get(i).unwrap().as_str().parse::<u64>().unwrap());
} }
for i in hourrange { for cap in hms_range {
hms.push(input.get(i).unwrap().as_str().parse::<u64>().unwrap()); hms.push(input.get(cap).unwrap().as_str().parse::<u32>().unwrap());
} }
let date = Local let date: DateTime<Local> =
.ymd(ymd[0] as i32, ymd[1] as u32, ymd[2] as u32) match Local.with_ymd_and_hms(ymd[0] as i32, ymd[1], ymd[2], hms[0], hms[1], hms[2]) {
.and_hms(hms[0] as u32, hms[1] as u32, hms[2] as u32); LocalResult::Single(s) => s,
LocalResult::Ambiguous(a, _b) => a,
LocalResult::None => Local::now().trunc_subsecs(0),
};
date date
} }
@ -229,24 +210,3 @@ fn is_trusted(ip: &IpAddr, trustnets: &Vec<IpNet>) -> bool {
} }
false false
} }
pub async fn _get_last(ctx: &Context) -> Result<Vec<IpData>, ReqError> {
let resp = ctx
.client
.get(format!("{server}/ips/last", server = ctx.flags.server))
.query(&[("interval", "3 hours")])
.send()
.await;
let req = match resp {
Ok(re) => re,
Err(err) => return Err(err),
};
let data: Vec<IpData> = match req.json::<Vec<IpData>>().await {
Ok(res) => res,
Err(err) => return Err(err),
};
Ok(data)
}

326
src/ipblc.rs Normal file
View File

@ -0,0 +1,326 @@
use crate::config::{Context, GIT_VERSION};
use crate::fw::*;
use crate::ip::{filter, IpData, IpEvent};
use crate::ipevent;
use crate::monitoring::apiserver;
use crate::utils::{gethostname, read_lines, sleep_s};
use crate::webservice::send_to_ipbl_api;
use crate::websocket::{send_to_ipbl_websocket, websocketpubsub, websocketreqrep};
use std::{collections::HashMap, sync::Arc};
use chrono::prelude::*;
use chrono::prelude::{DateTime, Local};
use chrono::Duration;
use nix::sys::inotify::{InitFlags, Inotify, InotifyEvent};
use sd_notify::*;
use tokio::sync::mpsc::{channel, Receiver, Sender};
use tokio::sync::RwLock;
pub const PKG_NAME: &str = env!("CARGO_PKG_NAME");
const BL_CHAN_SIZE: usize = 32;
const WS_CHAN_SIZE: usize = 64;
macro_rules! log_with_systemd {
($msg:expr) => {
println!("{}", $msg);
notify(false, &[NotifyState::Status(format!("{}", $msg).as_str())]).unwrap();
};
}
pub async fn run() {
let inotify = Inotify::init(InitFlags::empty()).unwrap();
let globalctx = Context::new(&inotify).await;
let ctxarc = Arc::new(RwLock::new(globalctx));
let (batch4, _) = fwglobalinit(FwTableType::IPv4, true);
let (batch6, _) = fwglobalinit(FwTableType::IPv6, true);
batch4.send().unwrap();
batch6.send().unwrap();
let mut fwlen: usize = 0;
let pkgversion = format!("{}@{}", env!("CARGO_PKG_VERSION"), GIT_VERSION);
let mut last_cfg_reload: DateTime<Local> = Local::now().trunc_subsecs(0);
log_with_systemd!(format!("Launching {}, version {}", PKG_NAME, pkgversion));
let ctxapi = Arc::clone(&ctxarc);
apiserver(&ctxapi).await.unwrap();
// initialize sockets
let (ipeventtx, mut ipeventrx): (Sender<IpEvent>, Receiver<IpEvent>) = channel(WS_CHAN_SIZE);
let ipeventtxarc = Arc::new(RwLock::new(ipeventtx));
// init pubsub
let ctxwsps = Arc::clone(&ctxarc);
let ipeventws = Arc::clone(&ipeventtxarc);
websocketpubsub(&ctxwsps, ipeventws).await;
let ctxwsrr = Arc::clone(&ctxarc);
let mut wssocketrr = websocketreqrep(&ctxwsrr).await;
// init file watcher
let inoarc = Arc::new(RwLock::new(inotify));
let inoclone = Arc::clone(&inoarc);
let mut blrx = watchfiles(inoclone).await;
let ctxclone = Arc::clone(&ctxarc);
let ipeventclone = Arc::clone(&ipeventtxarc);
tokio::spawn(async move {
compare_files_changes(&ctxclone, &mut blrx, &ipeventclone).await;
});
notify(false, &[NotifyState::Ready]).unwrap();
loop {
let mut ret: Vec<String> = Vec::new();
let ctxclone = Arc::clone(&ctxarc);
let reloadinterval;
{
let ctx = ctxclone.read().await;
reloadinterval = ctx.reloadinterval;
}
tokio::select! {
ipevent = ipeventrx.recv() => {
let received_ip = ipevent.unwrap();
let (toblock,server) = {
let ctx = ctxclone.read().await;
(ctx.get_blocklist_toblock(true).await,ctx.flags.server.clone())
};
if received_ip.msgtype == "bootstrap".to_string() {
for ip_to_send in toblock {
let ipe = ipevent!("init","ws",gethostname(true),Some(ip_to_send.ipdata));
if !send_to_ipbl_websocket(&mut wssocketrr, &ipe).await {
wssocketrr.close(None).unwrap();
wssocketrr = websocketreqrep(&ctxwsrr).await;
break;
}
}
continue
}
// refresh context blocklist
let filtered_ipevent = {
ctxarc.write().await.update_blocklist(&received_ip).await
};
// send ip list to api and ws sockets
if let Some(ipevent) = filtered_ipevent {
if received_ip.msgtype != "init" {
log_with_systemd!(format!("sending {} to api and ws", ipevent.ipdata.clone().unwrap().ip));
let ipe = ipevent!("add","ws",gethostname(true),ipevent.ipdata);
send_to_ipbl_api(&server.clone(), &ipe).await;
if !send_to_ipbl_websocket(&mut wssocketrr, &ipe).await {
wssocketrr.close(None).unwrap();
wssocketrr = websocketreqrep(&ctxwsrr).await;
continue;
}
}
}
}
_val = sleep_s(reloadinterval) => {
let ipe = ipevent!("ping", "ws", gethostname(true));
if !send_to_ipbl_websocket(&mut wssocketrr, &ipe).await {
wssocketrr.close(None).unwrap();
wssocketrr = websocketreqrep(&ctxwsrr).await;
}
}
};
let ctxclone = Arc::clone(&ctxarc);
let ipstounblock = {
let mut ctx = ctxclone.write().await;
ctx.gc_blocklist().await
};
let ipstoblock = {
let ctx = ctxclone.read().await;
ctx.get_blocklist_toblock(false).await
};
get_current_rules(&ctxarc, &mut ret, &mut fwlen)
.await
.unwrap();
for ip in ipstoblock {
match fwblock(&ip) {
Ok(ip) => {
let mut ctx = ctxclone.write().await;
if let Some(x) = ctx.blocklist.get_mut(ip) {
x.blocked = true;
}
}
Err(e) => {
println!("err: {e}, unable to push firewall rules, use super user")
}
};
}
for ip in ipstounblock {
if ip.blocked {
match fwunblock(&ip) {
Ok(_) => {}
Err(e) => {
println!("err: {e}, unable to push firewall rules, use super user")
}
};
}
}
// log lines
if ret.len() > 0 {
let result = ret.join(", ");
log_with_systemd!(format!("{result}"));
}
let ctxclone = Arc::clone(&ctxarc);
let inoclone = Arc::clone(&inoarc);
handle_cfg_reload(&ctxclone, reloadinterval, &mut last_cfg_reload, inoclone).await;
}
}
async fn handle_cfg_reload(
ctxclone: &Arc<RwLock<Context>>,
reloadinterval: u64,
last_cfg_reload: &mut DateTime<Local>,
inoarc: Arc<RwLock<Inotify>>,
) {
let now_cfg_reload = Local::now().trunc_subsecs(0);
if (now_cfg_reload - *last_cfg_reload) > Duration::seconds(reloadinterval as i64) {
let inotify;
loop {
inotify = match inoarc.try_read() {
Ok(o) => o,
Err(e) => {
println!("{e}");
sleep_s(1).await;
continue;
}
};
break;
}
let mut ctx = match ctxclone.try_write() {
Ok(o) => o,
Err(e) => {
println!("{e}");
return;
}
};
match ctx.load(&inotify).await {
Ok(_) => {
*last_cfg_reload = Local::now().trunc_subsecs(0);
}
Err(_) => {
println!("error reloading config");
}
}
};
}
async fn watchfiles(inoarc: Arc<RwLock<Inotify>>) -> Receiver<FileEvent> {
let (bltx, blrx): (Sender<FileEvent>, Receiver<FileEvent>) = channel(BL_CHAN_SIZE);
tokio::spawn(async move {
loop {
let events = inoarc.read().await.read_events().unwrap();
for inevent in events {
let date: DateTime<Local> = Local::now().trunc_subsecs(0);
bltx.send(FileEvent { inevent, date }).await.unwrap();
}
}
});
blrx
}
async fn get_last_file_size(w: &mut HashMap<String, u64>, path: &str) -> (u64, bool) {
let currentlen = match std::fs::metadata(&path.to_string()) {
Ok(u) => u.len(),
Err(_) => 0u64,
};
let lastlen = match w.insert(path.to_string(), currentlen) {
Some(u) => u,
None => currentlen,
};
(lastlen, lastlen != currentlen)
}
async fn compare_files_changes(
ctxarc: &Arc<RwLock<Context>>,
inrx: &mut Receiver<FileEvent>,
ipeventtx: &Arc<RwLock<Sender<IpEvent>>>,
) {
let mut tnets;
loop {
let modfiles = inrx.recv().await.unwrap();
let mut iplist: Vec<IpData> = vec![];
let sas = {
let ctx = ctxarc.read().await;
tnets = ctx.cfg.build_trustnets();
ctx.sas.clone()
};
match modfiles.inevent.name {
Some(name) => {
let filename = name.to_str().unwrap();
for (sak, sa) in sas.clone().iter_mut() {
if modfiles.inevent.wd == sa.wd {
let handle: String;
if sa.filename.as_str() == "" {
handle = format!("{}/{}", &sa.fullpath, filename);
} else if filename.starts_with(sa.filename.as_str()) {
handle = sa.fullpath.to_owned();
} else {
continue;
}
let (filesize, sizechanged) = {
let mut ctx = ctxarc.write().await;
let sa = ctx.sas.get_mut(sak).unwrap();
get_last_file_size(&mut sa.watchedfiles, &handle).await
};
if !sizechanged {
continue;
}
match read_lines(&handle, filesize) {
Some(lines) => {
filter(
lines,
&mut iplist,
&tnets,
&sa.regex,
&sa.set.src,
&modfiles.date,
);
}
None => {}
};
break;
}
}
for ip in iplist {
let ipe = ipevent!("add", "file", gethostname(true), Some(ip));
let ipetx = ipeventtx.read().await;
ipetx.send(ipe).await.unwrap();
}
}
None => {}
}
}
}
pub struct FileEvent {
pub inevent: InotifyEvent,
pub date: DateTime<Local>,
}
impl std::fmt::Debug for FileEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{ie:?}", ie = self.inevent)
}
}

View File

@ -1,180 +0,0 @@
use super::*;
use chrono::prelude::*;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::mpsc::{channel, Receiver, Sender};
use tokio::sync::Mutex;
const BL_CHAN_SIZE: usize = 32;
const ZMQ_CHAN_SIZE: usize = 64;
pub async fn process(ctx: &Arc<Mutex<Context>>) {
println!(
"Launching {} version {}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION")
);
let (ipdatatx, mut ipdatarx): (Sender<IpData>, Receiver<IpData>) = channel(ZMQ_CHAN_SIZE);
// initialize the firewall table
//firewall::init(&env!("CARGO_PKG_NAME").to_string());
// initialize zeromq sockets
let reqsocket;
let subsocket;
{
let ctxarc = Arc::clone(&ctx);
let zmqctx = ctxarc.lock().await;
reqsocket = zconnect(&zmqctx.cfg.zmq.get("reqrep").unwrap(), zmq::REQ)
.await
.unwrap();
subsocket = zconnect(&zmqctx.cfg.zmq.get("pubsub").unwrap(), zmq::SUB)
.await
.unwrap();
}
listenpubsub(&ctx, ipdatatx.clone(), subsocket).await;
let mut blrx = watchfiles(&ctx).await;
let ctxarc = Arc::clone(&ctx);
tokio::spawn(async move {
compare_files_changes(&ctxarc, &mut blrx, &ipdatatx).await;
});
loop {
let mut ret: Vec<String> = Vec::new();
// wait for logs parse and zmq channel receive
let ip = ipdatarx.recv().await.unwrap();
// lock the context mutex
let ctxarc = Arc::clone(&ctx);
let mut ctx = ctxarc.lock().await;
// refresh context blocklist
ctx.update_blocklist(&ip).await;
ctx.gc_blocklist().await;
// send ip list to ws and zmq sockets
if ip.hostname == ctx.hostname {
send_to_ipbl_ws(&ctx, &ip, &mut ret).await;
send_to_ipbl_zmq(&reqsocket, &ip).await;
}
// apply firewall blocking
firewall::block(
&env!("CARGO_PKG_NAME").to_string(),
&ctx.get_blocklist().await,
&mut ret,
)
.unwrap();
// log lines
println!("{ret}", ret = ret.join(", "));
// reload configuration from the server
match ctx.load().await {
Ok(_) => {}
Err(err) => {
println!("error loading config: {err}");
}
}
}
}
async fn watchfiles(ctx: &Arc<Mutex<Context>>) -> Receiver<FileEvent> {
let (bltx, blrx): (Sender<FileEvent>, Receiver<FileEvent>) = channel(BL_CHAN_SIZE);
let ctx = Arc::clone(ctx);
tokio::spawn(async move {
loop {
let events: Vec<InotifyEvent>;
{
let ctx = ctx.lock().await;
events = ctx.instance.read_events().unwrap();
}
for event in events {
let date: DateTime<Local> = Local::now().trunc_subsecs(0);
bltx.send(FileEvent {
ie: event,
date: date,
})
.await
.unwrap();
}
}
});
blrx
}
async fn get_last_file_size(watchedfiles: &mut HashMap<String, u64>, path: &str) -> u64 {
let currentlen = match std::fs::metadata(&path.to_string()) {
Ok(u) => u.len().clone(),
Err(_) => 0u64,
};
let lastlen = match watchedfiles.insert(path.to_string(), currentlen) {
Some(u) => u,
None => 0,
};
lastlen
}
async fn compare_files_changes(
ctx: &Arc<Mutex<Context>>,
inotifyrx: &mut Receiver<FileEvent>,
ipdatatx: &Sender<IpData>,
) {
let mut trustnets;
loop {
let modifiedfiles = inotifyrx.recv().await.unwrap();
let mut list: Vec<IpData> = vec![];
let mut ctx = ctx.lock().await;
trustnets = build_trustnets(&ctx.cfg.trustnets);
match modifiedfiles.ie.name {
Some(name) => {
let inotify_filename = name.to_str().unwrap();
for sak in &mut ctx.clone().sas.keys() {
let sa = &mut ctx.sas.get_mut(sak).unwrap();
if modifiedfiles.ie.wd == sa.wd {
let handle_filename: String;
if sa.filename.as_str() == "" {
handle_filename = format!("{}/{}", &sa.fullpath, inotify_filename);
} else if inotify_filename.starts_with(sa.filename.as_str()) {
handle_filename = sa.fullpath.to_owned();
} else {
continue;
}
let filesize =
get_last_file_size(&mut sa.watchedfiles, &handle_filename).await;
match read_lines(&handle_filename, filesize) {
Some(lines) => {
filter(
lines,
&mut list,
&trustnets,
&sa.regex,
&sa.set.t,
&modifiedfiles.date,
);
}
None => {}
};
break;
}
}
drop(ctx);
for ip in list {
ipdatatx.send(ip).await.unwrap();
}
}
None => {}
}
}
}

View File

@ -1,79 +0,0 @@
pub mod inc;
use crate::config::*;
use crate::firewall;
use crate::ip::*;
use crate::utils::*;
use crate::zmqcom::*;
use chrono::prelude::{DateTime, Local};
use nix::sys::inotify::InotifyEvent;
use std::sync::Arc;
use tokio::sync::mpsc::Sender;
use tokio::sync::Mutex;
pub struct FileEvent {
pub ie: InotifyEvent,
pub date: DateTime<Local>,
}
impl std::fmt::Debug for FileEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{ie:?}", ie = self.ie)
}
}
async fn send_to_ipbl_zmq(socket: &zmq::Socket, ip: &IpData) {
let msg = format!("{value}", value = serde_json::to_string(&ip).unwrap());
socket.send(&msg, 0).unwrap();
socket.recv_string(0).unwrap().unwrap();
}
async fn send_to_ipbl_ws(ctx: &Context, ip: &IpData, ret: &mut Vec<String>) {
ret.push(format!("host: {hostname}", hostname = ctx.hostname));
loop {
match push_ip(&ctx, &ip, ret).await {
Ok(_) => {
break;
}
Err(err) => {
println!("{err}");
sleep(1);
}
};
}
}
async fn listenpubsub(ctx: &Arc<Mutex<Context>>, txpubsub: Sender<IpData>, socket: zmq::Socket) {
let ctx = ctx.lock().await;
let prefix = format!(
"{subscription} ",
subscription = ctx.cfg.zmq.get("pubsub").unwrap().subscription
);
socket
.set_subscribe(ctx.cfg.zmq.get("pubsub").unwrap().subscription.as_bytes())
.expect("failed setting subscription");
drop(ctx);
tokio::spawn(async move {
loop {
let msgs: Option<String> = match socket.recv_string(0) {
Ok(s) => match s {
Ok(ss) => Some(ss),
Err(_) => None,
},
Err(_) => None,
};
match msgs {
Some(ss) => {
let msg = ss.strip_prefix(prefix.as_str()).unwrap();
let tosend: IpData = serde_json::from_str(msg).unwrap();
if tosend.hostname != gethostname(true) {
txpubsub.send(tosend).await.unwrap();
}
}
None => {}
};
}
});
}

View File

@ -1,17 +1,13 @@
mod config; mod config;
mod firewall; mod fw;
mod ip; mod ip;
mod ipblc; mod ipblc;
mod monitoring;
mod utils; mod utils;
mod zmqcom; mod webservice;
mod websocket;
use config::Context;
use std::sync::Arc;
use tokio::sync::Mutex;
#[tokio::main] #[tokio::main]
pub async fn main() { pub async fn main() {
// Create a new context ipblc::run().await;
let ctx = Arc::new(Mutex::new(Context::new().await));
ipblc::inc::process(&ctx).await;
} }

75
src/monitoring.rs Normal file
View File

@ -0,0 +1,75 @@
use crate::config::Context;
use std::{io, sync::Arc};
use serde_json;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpListener;
use tokio::sync::RwLock;
pub async fn apiserver(ctxarc: &Arc<RwLock<Context>>) -> io::Result<()> {
let ctxarc = ctxarc.clone();
let addr: String = { ctxarc.read().await.cfg.api.parse().unwrap() };
let listener = match TcpListener::bind(addr).await {
Ok(o) => o,
Err(e) => {
println!("error: {e}");
std::process::exit(1);
}
};
tokio::spawn(async move {
loop {
match listener.accept().await {
Ok((mut socket, _addr)) => {
let mut buf = vec![0; 1024];
match socket.readable().await {
Ok(_) => {
match socket.try_read(&mut buf) {
Ok(_) => {}
Err(e) => {
println!("error: {e}");
continue;
}
};
}
Err(e) => {
println!("error: {e}");
continue;
}
}
let msg = match String::from_utf8(buf.to_vec()) {
Ok(o) => o.trim_matches(char::from(0)).trim().to_string(),
Err(_) => "".to_string(),
};
let res = format_result(&ctxarc, msg.as_str()).await;
match socket.write_all(res.as_bytes()).await {
Ok(_) => {}
Err(e) => {
println!("error: {e}");
}
}
}
Err(err) => {
println!("error: {err}");
}
}
}
});
Ok(())
}
async fn format_result(ctxarc: &Arc<RwLock<Context>>, mode: &str) -> String {
let data;
let ctx = ctxarc.read().await;
match mode {
"cfg" => data = serde_json::to_string(&ctx.cfg).unwrap(),
"blocklist" => data = serde_json::to_string(&ctx.blocklist).unwrap(),
_ => data = serde_json::to_string(&ctx.blocklist).unwrap(),
};
data
}

View File

@ -1 +1 @@
((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$)) (((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*)|(((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?)

View File

@ -1,22 +1,13 @@
use ipnet::IpNet; use std::{boxed::Box, fs::File, io::*};
use lazy_static::lazy_static;
use nix::unistd;
use regex::Regex;
use std::boxed::Box;
use std::fs::File;
use std::io::*;
use std::path::Path;
use std::time::Duration;
lazy_static! { use nix::unistd;
static ref R_FILE_GZIP: Regex = Regex::new(r".*\.gz.*").unwrap(); use tokio::time::{sleep, Duration};
}
pub fn read_lines(filename: &String, offset: u64) -> Option<Box<dyn Read>> { pub fn read_lines(filename: &String, offset: u64) -> Option<Box<dyn Read>> {
let mut file = match File::open(filename) { let mut file = match File::open(filename) {
Ok(f) => f, Ok(o) => o,
Err(err) => { Err(e) => {
println!("{err}"); println!("error: {e}");
return None; return None;
} }
}; };
@ -25,35 +16,17 @@ pub fn read_lines(filename: &String, offset: u64) -> Option<Box<dyn Read>> {
Some(lines) Some(lines)
} }
pub fn _dedup<T: Ord + PartialOrd>(list: &mut Vec<T>) -> usize { pub async fn sleep_s(s: u64) {
// Begin with sorting entries sleep(Duration::from_secs(s)).await;
list.sort();
// Then deduplicate
list.dedup();
// Return the length
list.len()
} }
pub fn build_trustnets(cfgtrustnets: &Vec<String>) -> Vec<IpNet> { #[allow(dead_code)]
let mut trustnets: Vec<IpNet> = vec![]; pub async fn sleep_ms(m: u64) {
for trustnet in cfgtrustnets { sleep(Duration::from_millis(m)).await;
match trustnet.parse() {
Ok(net) => trustnets.push(net),
Err(err) => {
println!("error parsing {trustnet}, error: {err}");
}
};
}
trustnets
}
pub fn sleep(seconds: u64) {
std::thread::sleep(Duration::from_secs(seconds));
} }
pub fn gethostname(show_fqdn: bool) -> String { pub fn gethostname(show_fqdn: bool) -> String {
let mut buf = [0u8; 64]; let hostname_cstr = unistd::gethostname().expect("Failed getting hostname");
let hostname_cstr = unistd::gethostname(&mut buf).expect("Failed getting hostname");
let fqdn = hostname_cstr let fqdn = hostname_cstr
.to_str() .to_str()
.expect("Hostname wasn't valid UTF-8") .expect("Hostname wasn't valid UTF-8")
@ -64,19 +37,3 @@ pub fn gethostname(show_fqdn: bool) -> String {
} }
hostname[0].to_string() hostname[0].to_string()
} }
pub fn _search_subfolders(path: &Path) -> Vec<String> {
let dirs = std::fs::read_dir(path).unwrap();
let mut folders: Vec<String> = vec![];
for dir in dirs {
let dirpath = dir.unwrap().path();
let path = Path::new(dirpath.as_path());
if path.is_dir() {
folders.push(dirpath.to_str().unwrap().to_string());
for f in _search_subfolders(path) {
folders.push(f);
}
}
}
folders
}

85
src/webservice.rs Normal file
View File

@ -0,0 +1,85 @@
use crate::config::{httpclient, Context};
use crate::ip::{IpData, IpEvent};
use crate::utils::sleep_s;
use reqwest::Client;
use reqwest::Error as ReqError;
const MAX_FAILED_API_RATE: u64 = 10;
pub async fn send_to_ipbl_api(server: &str, ip: &IpEvent) {
let mut try_req = 0;
let client = httpclient();
loop {
match push_ip(&client, &server, &ip.ipdata.clone().unwrap()).await {
Ok(_) => {
break;
}
Err(e) => {
println!("error: {e}");
sleep_s(1).await;
if try_req == MAX_FAILED_API_RATE {
break;
}
try_req += 1;
}
};
}
}
async fn push_ip(client: &Client, server: &str, ip: &IpData) -> Result<(), ReqError> {
let mut data: Vec<IpData> = vec![];
data.push(IpData {
t: ip.t,
ip: ip.ip.to_string(),
src: ip.src.to_string(),
date: ip.date.to_string(),
hostname: ip.hostname.to_string(),
});
client
.post(format!("{server}/ips"))
.json(&data)
.send()
.await?;
Ok(())
}
async fn _push_ip_bulk(
ctx: &Context,
ips: &Vec<IpData>,
ret: &mut Vec<String>,
) -> Result<(), ReqError> {
let result: String;
let mut data: Vec<IpData> = vec![];
for ip in ips {
data.push(IpData {
t: ip.t,
ip: ip.ip.to_string(),
src: ip.src.to_string(),
date: ip.date.to_string(),
hostname: ip.hostname.to_string(),
})
}
let resp = httpclient()
.post(format!("{server}/ips", server = ctx.flags.server))
.json(&data)
.send()
.await?;
ret.push(format!("status: {status}", status = resp.status()));
let res = resp.text().await.unwrap();
if res.trim().len() > 0 {
result = res.trim().to_string();
} else {
result = "".to_string();
}
ret.push(format!("response: {result}"));
Ok(())
}

142
src/websocket.rs Normal file
View File

@ -0,0 +1,142 @@
use crate::config::{Context, WebSocketCfg};
use crate::ip::IpEvent;
use crate::utils::{gethostname, sleep_s};
use std::{
io::{self, Write},
net::TcpStream,
sync::Arc,
};
use serde_json::json;
use tokio::sync::mpsc::Sender;
use tokio::sync::RwLock;
use tungstenite::stream::*;
use tungstenite::*;
pub async fn websocketreqrep(
ctxarc: &Arc<RwLock<Context>>,
) -> WebSocket<MaybeTlsStream<TcpStream>> {
let (mut wssocketrr, bootstrap_event, wscfg);
{
let ctx = ctxarc.read().await;
bootstrap_event = ctx.cfg.bootstrap_event().clone();
wscfg = ctx.cfg.ws.get("reqrep").unwrap().clone();
}
wssocketrr = websocketconnect(&wscfg, &gethostname(true)).await.unwrap();
send_to_ipbl_websocket(&mut wssocketrr, &bootstrap_event).await;
return wssocketrr;
}
pub async fn websocketpubsub(
ctxarc: &Arc<RwLock<Context>>,
txpubsub: Arc<RwLock<Sender<IpEvent>>>,
) {
let cfg;
{
let ctx = ctxarc.read().await;
cfg = ctx.cfg.ws.get("pubsub").unwrap().clone();
}
let mut websocket = Arc::new(RwLock::new(
websocketconnect(&cfg, &gethostname(true)).await.unwrap(),
));
tokio::spawn(async move {
loop {
let mut ws = websocket.write().await;
match ws.read() {
Ok(msg) => {
let tosend: IpEvent = match serde_json::from_str(msg.to_string().as_str()) {
Ok(o) => o,
Err(e) => {
println!("error in pubsub: {e:?}");
continue;
}
};
match tosend.ipdata.clone() {
Some(o) => {
if o.hostname != gethostname(true)
|| tosend.msgtype == "init".to_string()
{
let txps = txpubsub.read().await;
txps.send(tosend).await.unwrap();
}
}
None => {
let txps = txpubsub.read().await;
txps.send(tosend.clone()).await.unwrap();
}
}
}
Err(e) => {
println!("error in pubsub: {e:?}");
ws.close(None).unwrap();
drop(ws);
websocket = Arc::new(RwLock::new(
websocketconnect(&cfg, &gethostname(true)).await.unwrap(),
));
}
};
}
});
}
pub async fn websocketconnect<'a>(
wscfg: &WebSocketCfg,
hostname: &String,
) -> Result<WebSocket<MaybeTlsStream<TcpStream>>, Error> {
let endpoint = &wscfg.endpoint;
print!("connecting to {} ... ", endpoint);
io::stdout().flush().unwrap();
let mut socket;
loop {
(socket, _) = match connect(endpoint) {
Ok((o, e)) => (o, e),
_ => {
println!("error connecting to {endpoint}, retrying");
sleep_s(1).await;
continue;
}
};
break;
}
println!("connected to {endpoint}");
let msg = json!({ "hostname": hostname });
socket.send(Message::Text(msg.to_string().into())).unwrap();
Ok(socket)
}
pub async fn send_to_ipbl_websocket(
ws: &mut WebSocket<MaybeTlsStream<TcpStream>>,
ip: &IpEvent,
) -> bool {
let msg = format!("{val}", val = serde_json::to_string(&ip).unwrap());
if ws.can_write() {
match ws.send(Message::Text(msg.into())) {
Ok(_) => {}
Err(e) => {
println!("err send read: {e:?}");
return false;
}
};
} else {
println!("can't write to socket");
return false;
};
if ws.can_read() {
match ws.read() {
Ok(_) => {}
Err(e) => {
println!("err send read: {e:?}");
return false;
}
};
} else {
println!("can't read from socket");
return false;
};
true
}

View File

@ -1,15 +0,0 @@
use crate::config::*;
use zmq;
const ZMQPROTO: &str = "tcp";
pub async fn zconnect(zmqcfg: &ZMQ, zmqtype: zmq::SocketType) -> Result<zmq::Socket, zmq::Error> {
let zctx = zmq::Context::new();
let zmqhost = &zmqcfg.hostname;
let zmqport = zmqcfg.port;
let socket = zctx.socket(zmqtype).unwrap();
let connectstring = format!("{ZMQPROTO}://{zmqhost}:{zmqport}");
socket.connect(&connectstring.as_str())?;
Ok(socket)
}