├── .gitignore ├── .golangci.yaml ├── LICENSE ├── README.md ├── build ├── .gitignore ├── build-all.sh ├── build.sh ├── configs │ ├── .gitignore │ ├── README.md │ ├── api_node.template.yaml │ └── cluster.template.yaml ├── data │ └── .gitignore ├── logs │ └── .gitignore ├── pages │ ├── 403.html │ ├── 404.html │ ├── 50x.html │ ├── shutdown_en.html │ ├── shutdown_upgrade_zh.html │ └── shutdown_zh.html ├── test.sh └── www │ ├── .gitignore │ └── index.html ├── cmd └── edge-node │ └── main.go ├── dist └── .gitignore ├── go.mod ├── go.sum └── internal ├── apps ├── app_cmd.go ├── directive.go ├── log_writer.go └── main.go ├── caches ├── consts.go ├── errors.go ├── errros_test.go ├── file_dir.go ├── hot_item.go ├── item.go ├── item_test.go ├── list_file_db_sqlite.go ├── list_file_db_sqlite_test.go ├── list_file_hash_map_sqlite.go ├── list_file_hash_map_sqlite_test.go ├── list_file_kv.go ├── list_file_kv_objects.go ├── list_file_kv_objects_test.go ├── list_file_kv_store.go ├── list_file_kv_test.go ├── list_file_sqlite.go ├── list_file_sqlite_test.go ├── list_interface.go ├── list_memory.go ├── list_memory_test.go ├── manager.go ├── manager_test.go ├── open_file.go ├── open_file_cache.go ├── open_file_cache_test.go ├── open_file_pool.go ├── open_file_pool_test.go ├── partial_ranges.go ├── partial_ranges_queue.go ├── partial_ranges_queue_test.go ├── partial_ranges_test.go ├── reader.go ├── reader_file.go ├── reader_file_mmap.go ├── reader_file_test.go ├── reader_memory.go ├── reader_memory_test.go ├── reader_partial_file.go ├── stat.go ├── storage_file.go ├── storage_file_ext.go ├── storage_file_test.go ├── storage_interface.go ├── storage_memory.go ├── storage_memory_test.go ├── utils.go ├── utils_partial.go ├── utils_test.go ├── writer.go ├── writer_file.go ├── writer_memory.go ├── writer_memory_test.go ├── writer_partial_file.go └── writer_partial_file_test.go ├── compressions ├── errors.go ├── reader.go ├── reader_base.go ├── reader_brotli.go ├── reader_brotli_test.go ├── reader_deflate.go ├── reader_deflate_test.go ├── reader_gzip.go ├── reader_gzip_test.go ├── reader_pool.go ├── reader_pool_brotli.go ├── reader_pool_deflate.go ├── reader_pool_gzip.go ├── reader_pool_zstd.go ├── reader_zstd.go ├── reader_zstd_test.go ├── utils.go ├── utils_test.go ├── writer.go ├── writer_base.go ├── writer_brotli.go ├── writer_brotli_test.go ├── writer_deflate.go ├── writer_deflate_test.go ├── writer_gzip.go ├── writer_gzip_test.go ├── writer_pool.go ├── writer_pool_brotli.go ├── writer_pool_deflate.go ├── writer_pool_gzip.go ├── writer_pool_zstd.go ├── writer_zstd.go └── writer_zstd_test.go ├── configs ├── api_config.go ├── api_config_test.go ├── cluster_config.go └── cluster_config_test.go ├── conns ├── linger.go ├── map.go └── map_test_test.go ├── const ├── build.go ├── build_plus.go ├── const.go └── vars.go ├── encrypt ├── magic_key.go ├── magic_key_test.go ├── method.go ├── method_aes_128_cfb.go ├── method_aes_128_cfb_test.go ├── method_aes_192_cfb.go ├── method_aes_192_cfb_test.go ├── method_aes_256_cfb.go ├── method_aes_256_cfb_test.go ├── method_raw.go ├── method_raw_test.go ├── method_utils.go └── method_utils_test.go ├── errors ├── error.go └── error_test.go ├── events ├── events.go ├── utils.go └── utils_test.go ├── firewalls ├── ddos_protection.go ├── ddos_protection_others.go ├── firewall.go ├── firewall_base.go ├── firewall_firewalld.go ├── firewall_http.go ├── firewall_interface.go ├── firewall_mock.go ├── firewall_nftables.go ├── firewall_nftables_others.go ├── nftables │ ├── .gitignore │ ├── chain.go │ ├── chain_policy.go │ ├── chain_test.go │ ├── conn.go │ ├── conn_test.go │ ├── element.go │ ├── errors.go │ ├── expration.go │ ├── expration_test.go │ ├── family.go │ ├── installer.go │ ├── rule.go │ ├── set.go │ ├── set_batch.go │ ├── set_data_type.go │ ├── set_ext.go │ ├── set_test.go │ ├── table.go │ └── table_test.go └── utils.go ├── goman ├── instance.go ├── lib.go ├── lib_test.go ├── task_group.go └── task_group_test.go ├── iplibrary ├── README.md ├── action_base.go ├── action_errors.go ├── action_firewalld.go ├── action_firewalld_test.go ├── action_html.go ├── action_http_api.go ├── action_http_api_test.go ├── action_interface.go ├── action_ipset.go ├── action_ipset_test.go ├── action_iptables.go ├── action_iptables_test.go ├── action_manager.go ├── action_manager_test.go ├── action_script.go ├── action_script_test.go ├── action_utils.go ├── action_utils_test.go ├── init.go ├── ip_item.go ├── ip_item_test.go ├── ip_list.go ├── ip_list_db.go ├── ip_list_kv.go ├── ip_list_kv_objects.go ├── ip_list_kv_test.go ├── ip_list_sqlite.go ├── ip_list_sqlite_test.go ├── ip_list_test.go ├── list_type.go ├── list_utils.go ├── list_utils_test.go ├── manager_ip_list.go ├── manager_ip_list_test.go ├── result.go └── server_list_manager.go ├── js └── .gitignore ├── metrics ├── manager.go ├── manager_test.go ├── metric_interface.go ├── stat.go ├── stat_test.go ├── sum_test.go ├── task.go ├── task_base.go ├── task_kv.go ├── task_kv_objects.go ├── task_kv_test.go └── task_sqlite.go ├── monitor ├── value.go ├── value_queue.go └── value_queue_test.go ├── nodes ├── api_stream.go ├── api_stream_test.go ├── client_conn.go ├── client_conn_base.go ├── client_conn_interface.go ├── client_conn_limiter.go ├── client_conn_limiter_test.go ├── client_conn_traffic.go ├── client_conn_utils.go ├── client_listener.go ├── client_tls_conn.go ├── conn_linger.go ├── http_access_log_queue.go ├── http_access_log_queue_test.go ├── http_access_log_viewer.go ├── http_cache_task_manager.go ├── http_cache_task_manager_test.go ├── http_client.go ├── http_client_pool.go ├── http_client_pool_test.go ├── http_client_transport.go ├── http_request.go ├── http_request_acme.go ├── http_request_auth.go ├── http_request_cache.go ├── http_request_cc.go ├── http_request_error.go ├── http_request_events.go ├── http_request_fastcgi.go ├── http_request_health_check.go ├── http_request_hls.go ├── http_request_host_redirect.go ├── http_request_http3.go ├── http_request_limit.go ├── http_request_ln.go ├── http_request_log.go ├── http_request_metrics.go ├── http_request_mismatch.go ├── http_request_oss.go ├── http_request_page.go ├── http_request_plan_before.go ├── http_request_redirect_https.go ├── http_request_referers.go ├── http_request_reverse_proxy.go ├── http_request_rewrite.go ├── http_request_root.go ├── http_request_shutdown.go ├── http_request_stat.go ├── http_request_sub.go ├── http_request_test.go ├── http_request_traffic_limit.go ├── http_request_uam.go ├── http_request_url.go ├── http_request_user_agent.go ├── http_request_utils.go ├── http_request_utils_test.go ├── http_request_waf.go ├── http_request_websocket.go ├── http_writer.go ├── http_writer_empty.go ├── http_writer_ext.go ├── ip_library_updater.go ├── listener.go ├── listener_base.go ├── listener_base_ext.go ├── listener_base_test.go ├── listener_http.go ├── listener_interface.go ├── listener_manager.go ├── listener_manager_test.go ├── listener_tcp.go ├── listener_test.go ├── listener_udp.go ├── node.go ├── node_ext.go ├── node_panic.go ├── node_panic_arm64.go ├── node_status_executor.go ├── node_status_executor_test.go ├── node_status_executor_unix.go ├── node_status_executor_windows.go ├── node_tasks.go ├── node_tasks_ext.go ├── node_test.go ├── origin_conn.go ├── origin_state.go ├── origin_state_manager.go ├── origin_state_manager_test.go ├── origin_utils.go ├── system_services.go ├── task_ocsp_update.go ├── task_ocsp_update_test.go ├── task_sync_api_nodes.go ├── task_trim_disks.go ├── toa_manager.go ├── upgrade_manager.go ├── upgrade_manager_test.go └── user_manager.go ├── re ├── regexp.go ├── regexp_test.go ├── rune_tree.go └── rune_tree_test.go ├── remotelogs └── utils.go ├── rpc ├── call_stat.go ├── call_stat_test.go ├── rpc_client.go ├── rpc_test.go └── rpc_utils.go ├── stats ├── bandwidth_stat_manager.go ├── bandwidth_stat_manager_test.go ├── dau_manager.go ├── dau_manager_test.go ├── http_request_stat_manager.go ├── http_request_stat_manager_test.go ├── traffic_stat_manager.go ├── traffic_stat_manager_test.go ├── user_agent_parser.go ├── user_agent_parser_result.go └── user_agent_parser_test.go ├── trackers ├── label.go ├── manager.go └── manager_test.go ├── ttlcache ├── cache.go ├── cache_test.go ├── item.go ├── manager.go ├── option.go ├── piece.go ├── piece_test.go ├── utils.go └── utils_test.go ├── utils ├── agents │ ├── agent.go │ ├── agent_ip.go │ ├── agents.go │ ├── agents_test.go │ ├── db.go │ ├── db_kv.go │ ├── db_kv_objects.go │ ├── db_kv_test.go │ ├── db_sqlite.go │ ├── ip_cache_map.go │ ├── ip_cache_map_test.go │ ├── manager.go │ ├── manager_test.go │ ├── queue.go │ └── queue_test.go ├── bfs │ ├── .gitignore │ ├── block_info.go │ ├── blocks_file.go │ ├── blocks_file_options.go │ ├── blocks_file_test.go │ ├── errors.go │ ├── file_header.go │ ├── file_header_lazy.go │ ├── file_header_lazy_test.go │ ├── file_header_test.go │ ├── file_reader.go │ ├── file_reader_test.go │ ├── file_writer.go │ ├── file_writer_test.go │ ├── fs.go │ ├── fs_options.go │ ├── fs_test.go │ ├── gzip_reader_pool.go │ ├── gzip_writer_pool.go │ ├── hash.go │ ├── hash_test.go │ ├── meta_block.go │ ├── meta_block_test.go │ ├── meta_file.go │ ├── meta_file_test.go │ └── threads_limiter.go ├── buffer_pool.go ├── buffer_pool_test.go ├── byte │ ├── utils.go │ └── utils_test.go ├── bytepool │ ├── byte_pool.go │ └── byte_pool_test.go ├── cachehits │ ├── stat.go │ └── stat_test.go ├── clock │ ├── manager.go │ ├── manager_test.go │ └── ntp_packet.go ├── common_files.go ├── common_files_test.go ├── conns │ └── conn_no_stat.go ├── counters │ ├── counter.go │ ├── counter_test.go │ ├── item.go │ └── item_test.go ├── dbs │ ├── batch.go │ ├── db.go │ ├── db_test.go │ ├── query_label.go │ ├── query_stat.go │ ├── query_stat_manager.go │ ├── query_stat_manager_test.go │ ├── stmt.go │ └── utils.go ├── encrypt.go ├── encrypt_test.go ├── errors.go ├── exec │ ├── cmd.go │ ├── cmd_test.go │ ├── look_linux.go │ └── look_others.go ├── exit.go ├── expires │ ├── id_key_map.go │ ├── id_key_map_test.go │ ├── list.go │ ├── list_test.go │ └── manager.go ├── fasttime │ ├── time_fast.go │ └── time_fast_test.go ├── fnv │ ├── hash.go │ └── hash_test.go ├── fs │ ├── disk.go │ ├── disk_test_test.go │ ├── file.go │ ├── file_test.go │ ├── limiter.go │ ├── limiter_test.go │ ├── locker.go │ ├── locker_test.go │ ├── os.go │ ├── os_test.go │ ├── stat.go │ ├── stat_test.go │ ├── status.go │ └── status_test.go ├── get.go ├── get_test.go ├── http.go ├── http_test.go ├── idles │ ├── run.go │ └── run_test.go ├── ip.go ├── ip_test.go ├── jsonutils │ ├── map.go │ ├── map_test.go │ ├── utils.go │ └── utils_test.go ├── kvstore │ ├── db.go │ ├── db_test.go │ ├── errors.go │ ├── item.go │ ├── iterator_options.go │ ├── logger.go │ ├── options.go │ ├── query.go │ ├── query_test.go │ ├── store.go │ ├── store_test.go │ ├── table.go │ ├── table_counter.go │ ├── table_counter_test.go │ ├── table_field.go │ ├── table_field_test.go │ ├── table_interface.go │ ├── table_test.go │ ├── tx.go │ ├── tx_test.go │ ├── utils.go │ ├── utils_test.go │ ├── value_encode_int.go │ ├── value_encoder.go │ ├── value_encoder_bool.go │ ├── value_encoder_bytes.go │ ├── value_encoder_nil.go │ ├── value_encoder_string.go │ └── value_encoder_test.go ├── linkedlist │ ├── item.go │ ├── list.go │ └── list_test.go ├── lookup.go ├── lookup_test.go ├── maps │ ├── map_fixed.go │ └── map_fixed_test.go ├── mem │ ├── system.go │ ├── system_1.19.go │ ├── system_before_1.19.go │ └── system_test.go ├── minifiers │ └── minify.go ├── net.go ├── net_darwin.go ├── net_linux.go ├── net_test.go ├── net_utils.go ├── number.go ├── path.go ├── path_test.go ├── percpu │ ├── chan.go │ ├── chan_test.go │ └── proc_id.go ├── ranges │ ├── range.go │ └── range_test.go ├── ratelimit │ ├── bandwidth.go │ ├── bandwidth_test.go │ ├── counter.go │ └── counter_test.go ├── reader_utils.go ├── readers │ ├── .gitignore │ ├── handlers.go │ ├── reader_base.go │ ├── reader_bytes_counter.go │ ├── reader_closer_byte_ranges.go │ ├── reader_closer_byte_ranges_test.go │ ├── reader_closer_filter.go │ ├── reader_closer_filter_test.go │ ├── reader_closer_tee.go │ ├── reader_print.go │ └── reader_tee.go ├── rlimit_darwin.go ├── rlimit_linux.go ├── rlimit_others.go ├── runes │ ├── runes.go │ └── runes_test.go ├── service.go ├── service_linux.go ├── service_others.go ├── service_test.go ├── service_windows.go ├── sets │ ├── set_fixed.go │ └── set_fixed_test.go ├── string.go ├── string_test.go ├── sync │ ├── .gitignore │ ├── map_int.go │ ├── map_int_test.go │ ├── rw_mutex.go │ └── rw_mutex_test.go ├── testutils │ ├── memory.go │ └── utils.go ├── ticker.go ├── ticker_test.go ├── ticker_utils.go ├── time.go ├── time_test.go ├── unzip.go ├── version.go ├── version_test.go ├── workspace.go └── writers │ ├── writer_bytes_counter.go │ ├── writer_closer_tee.go │ ├── writer_print.go │ ├── writer_rate_limit.go │ └── writer_rate_limit_test.go ├── waf ├── README.md ├── action_allow.go ├── action_base.go ├── action_block.go ├── action_captcha.go ├── action_category.go ├── action_config.go ├── action_definition.go ├── action_get_302.go ├── action_go_group.go ├── action_go_set.go ├── action_instance.go ├── action_interface.go ├── action_js_cookie.go ├── action_log.go ├── action_notify.go ├── action_page.go ├── action_post_307.go ├── action_record_ip.go ├── action_redirect.go ├── action_tag.go ├── action_types.go ├── action_utils.go ├── action_utils_test.go ├── allow_cookie_info.go ├── allow_cookie_info_test.go ├── captcha_counter.go ├── captcha_generator.go ├── captcha_generator_test.go ├── captcha_test.go ├── captcha_validator.go ├── checkpoints │ ├── cc.go │ ├── cc2.go │ ├── cc_test.go │ ├── checkpoint.go │ ├── checkpoint_definition.go │ ├── checkpoint_interface.go │ ├── option.go │ ├── option_field.go │ ├── option_options.go │ ├── param_option.go │ ├── request_all.go │ ├── request_all_test.go │ ├── request_arg.go │ ├── request_arg_test.go │ ├── request_args.go │ ├── request_body.go │ ├── request_body_test.go │ ├── request_cname.go │ ├── request_content_type.go │ ├── request_cookie.go │ ├── request_cookies.go │ ├── request_form_arg.go │ ├── request_form_arg_test.go │ ├── request_general_header_length.go │ ├── request_geo_city_name.go │ ├── request_geo_country_name.go │ ├── request_geo_province_name.go │ ├── request_header.go │ ├── request_header_names.go │ ├── request_header_names_test.go │ ├── request_headers.go │ ├── request_headers_test.go │ ├── request_host.go │ ├── request_host_test.go │ ├── request_is_cname.go │ ├── request_isp_name.go │ ├── request_json_arg.go │ ├── request_json_arg_test.go │ ├── request_length.go │ ├── request_method.go │ ├── request_path.go │ ├── request_path_test.go │ ├── request_proto.go │ ├── request_raw_remote_addr.go │ ├── request_referer.go │ ├── request_referer_block.go │ ├── request_referer_origin.go │ ├── request_referer_origin_test.go │ ├── request_remote_addr.go │ ├── request_remote_port.go │ ├── request_remote_user.go │ ├── request_scheme.go │ ├── request_scheme_test.go │ ├── request_upload.go │ ├── request_upload_test.go │ ├── request_uri.go │ ├── request_url.go │ ├── request_user_agent.go │ ├── response_body.go │ ├── response_body_test.go │ ├── response_bytes_sent.go │ ├── response_general_header_length.go │ ├── response_header.go │ ├── response_header_test.go │ ├── response_status.go │ ├── response_status_test.go │ ├── sample_request.go │ ├── sample_response.go │ ├── utils.go │ └── utils_test.go ├── get302_validator.go ├── info_arg.go ├── info_arg_test.go ├── injectionutils │ ├── libinjection │ │ ├── COPYING │ │ ├── README.md │ │ └── src │ │ │ ├── libinjection.h │ │ │ ├── libinjection_html5.c │ │ │ ├── libinjection_html5.h │ │ │ ├── libinjection_sqli.c │ │ │ ├── libinjection_sqli.h │ │ │ ├── libinjection_sqli_data.h │ │ │ ├── libinjection_xss.c │ │ │ ├── libinjection_xss.h │ │ │ ├── reader.c │ │ │ ├── sqli_cli.c │ │ │ └── sqlparse2c.py │ ├── libinjection_sqli.c │ ├── libinjection_xss.c │ ├── utils_sqli.go │ ├── utils_sqli_test.go │ ├── utils_xss.go │ └── utils_xss_test.go ├── ip_list.go ├── ip_list_test.go ├── ip_lists_deleted.go ├── param_filter.go ├── requests │ ├── request.go │ ├── response.go │ └── test_request.go ├── results.go ├── rule.go ├── rule_group.go ├── rule_operator.go ├── rule_set.go ├── rule_set_test.go ├── rule_test.go ├── template.go ├── template_test.go ├── utils │ ├── utils.go │ └── utils_test.go ├── values │ ├── ip_range.go │ ├── ip_range_test.go │ ├── number_list.go │ ├── number_list_test.go │ ├── string_list.go │ └── string_list_test.go ├── waf.go ├── waf_manager.go ├── waf_manager_test.go └── waf_test.go └── zero ├── zero.go └── zero_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | *_plus.go 2 | *_plus_test.go 3 | *-plus.sh -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GoEdge边缘节点源码 -------------------------------------------------------------------------------- /build/.gitignore: -------------------------------------------------------------------------------- 1 | bin/* 2 | caches 3 | upload.sh -------------------------------------------------------------------------------- /build/build-all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ./build.sh linux amd64 4 | #./build.sh linux 386 5 | ./build.sh linux arm64 6 | #./build.sh linux mips64 7 | #./build.sh linux mips64le 8 | #./build.sh darwin amd64 9 | #./build.sh darwin arm64 -------------------------------------------------------------------------------- /build/configs/.gitignore: -------------------------------------------------------------------------------- 1 | node.json 2 | api.yaml 3 | api_node.yaml 4 | cluster.yaml 5 | api_cluster.yaml 6 | *.cache -------------------------------------------------------------------------------- /build/configs/README.md: -------------------------------------------------------------------------------- 1 | * `api_node.template.yaml` - API相关配置模板 2 | * `cluster.template.yaml` - 通过集群自动接入节点模板 -------------------------------------------------------------------------------- /build/configs/api_node.template.yaml: -------------------------------------------------------------------------------- 1 | rpc.endpoints: [ "" ] 2 | nodeId: "" 3 | secret: "" -------------------------------------------------------------------------------- /build/configs/cluster.template.yaml: -------------------------------------------------------------------------------- 1 | rpc: 2 | endpoints: [ "" ] 3 | clusterId: "" 4 | secret: "" -------------------------------------------------------------------------------- /build/data/.gitignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /build/logs/.gitignore: -------------------------------------------------------------------------------- 1 | *.log -------------------------------------------------------------------------------- /build/pages/403.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Error 5 | 6 | 7 | 8 | 9 |

403 Forbidden

10 |

Sorry, your access to the page has been denied. Please try again later.

11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /build/pages/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Error 5 | 6 | 7 | 8 | 9 |

404 Not Found

10 |

Sorry, the page you are looking for is not found. Please try again later.

11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /build/pages/50x.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Error 5 | 6 | 7 | 8 | 9 |

An error occurred.

10 |

Sorry, the page you are looking for is currently unavailable. Please try again later.

11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /build/pages/shutdown_en.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Shutdown Notice 5 | 6 | 7 | 8 | 9 |

The website is shutdown.

10 |

Sorry, the page you are looking for is currently unavailable. Please try again later.

11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /build/pages/shutdown_upgrade_zh.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 升级中 5 | 6 | 7 | 8 | 9 |

网站升级中

10 |

为了给您提供更好的服务,我们正在升级网站,请稍后重新访问。

11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /build/pages/shutdown_zh.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 临时关闭提醒 5 | 6 | 7 | 8 | 9 |

网站暂时关闭

10 |

网站已被暂时关闭,请耐心等待我们的重新开通通知。

11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /build/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | TAG=${1} 4 | 5 | if [ -z "$TAG" ]; then 6 | TAG="community" 7 | fi 8 | 9 | # stop node 10 | go run -tags=${TAG} ../cmd/edge-node/main.go stop 11 | 12 | # reference: https://pkg.go.dev/cmd/go/internal/test 13 | go clean -testcache 14 | go test -timeout 60s -tags="${TAG}" -cover ../... -------------------------------------------------------------------------------- /build/www/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoEdgeLab/EdgeNode/be22116236164c895a3163331b97a3daa72414d3/build/www/.gitignore -------------------------------------------------------------------------------- /build/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Welcome 5 | 6 | 7 | I am index. 8 | 9 | -------------------------------------------------------------------------------- /dist/.gitignore: -------------------------------------------------------------------------------- 1 | *.zip 2 | edge-node -------------------------------------------------------------------------------- /internal/apps/directive.go: -------------------------------------------------------------------------------- 1 | package apps 2 | 3 | type Directive struct { 4 | Arg string 5 | Callback func() 6 | } 7 | -------------------------------------------------------------------------------- /internal/apps/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package apps 4 | 5 | import teaconst "github.com/TeaOSLab/EdgeNode/internal/const" 6 | 7 | func RunMain(f func()) { 8 | if teaconst.IsMain { 9 | f() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /internal/caches/consts.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package caches 4 | 5 | const ( 6 | SuffixAll = "@GOEDGE_" // 通用后缀 7 | SuffixWebP = "@GOEDGE_WEBP" // WebP后缀 8 | SuffixCompression = "@GOEDGE_" // 压缩后缀 SuffixCompression + Encoding 9 | SuffixMethod = "@GOEDGE_" // 请求方法后缀 SuffixMethod + RequestMethod 10 | SuffixPartial = "@GOEDGE_partial" // 分区缓存后缀 11 | ) 12 | -------------------------------------------------------------------------------- /internal/caches/errros_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package caches_test 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "github.com/TeaOSLab/EdgeNode/internal/caches" 9 | "github.com/iwind/TeaGo/assert" 10 | "testing" 11 | ) 12 | 13 | func TestCanIgnoreErr(t *testing.T) { 14 | var a = assert.NewAssertion(t) 15 | 16 | a.IsTrue(caches.CanIgnoreErr(caches.ErrFileIsWriting)) 17 | a.IsTrue(caches.CanIgnoreErr(fmt.Errorf("error: %w", caches.ErrFileIsWriting))) 18 | a.IsTrue(errors.Is(fmt.Errorf("error: %w", caches.ErrFileIsWriting), caches.ErrFileIsWriting)) 19 | a.IsTrue(errors.Is(caches.ErrFileIsWriting, caches.ErrFileIsWriting)) 20 | a.IsTrue(caches.CanIgnoreErr(caches.NewCapacityError("over capacity"))) 21 | a.IsTrue(caches.CanIgnoreErr(fmt.Errorf("error: %w", caches.NewCapacityError("over capacity")))) 22 | a.IsFalse(caches.CanIgnoreErr(caches.ErrNotFound)) 23 | a.IsFalse(caches.CanIgnoreErr(errors.New("test error"))) 24 | } 25 | -------------------------------------------------------------------------------- /internal/caches/file_dir.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package caches 4 | 5 | import "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared" 6 | 7 | type FileDir struct { 8 | Path string 9 | Capacity *shared.SizeCapacity 10 | IsFull bool 11 | } 12 | -------------------------------------------------------------------------------- /internal/caches/hot_item.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package caches 4 | 5 | type HotItem struct { 6 | Key string 7 | Hits uint32 8 | } 9 | -------------------------------------------------------------------------------- /internal/caches/open_file.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package caches 4 | 5 | import ( 6 | "io" 7 | "os" 8 | ) 9 | 10 | type OpenFile struct { 11 | fp *os.File 12 | meta []byte 13 | header []byte 14 | version int64 15 | size int64 16 | } 17 | 18 | func NewOpenFile(fp *os.File, meta []byte, header []byte, version int64, size int64) *OpenFile { 19 | return &OpenFile{ 20 | fp: fp, 21 | meta: meta, 22 | header: header, 23 | version: version, 24 | size: size, 25 | } 26 | } 27 | 28 | func (this *OpenFile) SeekStart() error { 29 | _, err := this.fp.Seek(0, io.SeekStart) 30 | return err 31 | } 32 | 33 | func (this *OpenFile) Close() error { 34 | this.meta = nil 35 | return this.fp.Close() 36 | } 37 | -------------------------------------------------------------------------------- /internal/caches/partial_ranges_queue_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package caches_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/caches" 7 | "github.com/iwind/TeaGo/assert" 8 | "testing" 9 | ) 10 | 11 | func TestNewPartialRangesQueue(t *testing.T) { 12 | var a = assert.NewAssertion(t) 13 | 14 | var queue = caches.NewPartialRangesQueue() 15 | queue.Put("a", []byte{1, 2, 3}) 16 | t.Log("add 'a':", queue.Len()) 17 | t.Log(queue.Get("a")) 18 | a.IsTrue(queue.Len() == 1) 19 | 20 | queue.Put("a", nil) 21 | t.Log("add 'a':", queue.Len()) 22 | a.IsTrue(queue.Len() == 1) 23 | 24 | queue.Put("b", nil) 25 | t.Log("add 'b':", queue.Len()) 26 | a.IsTrue(queue.Len() == 2) 27 | 28 | queue.Delete("a") 29 | t.Log("delete 'a':", queue.Len()) 30 | a.IsTrue(queue.Len() == 1) 31 | } 32 | -------------------------------------------------------------------------------- /internal/caches/reader.go: -------------------------------------------------------------------------------- 1 | package caches 2 | 3 | import "github.com/TeaOSLab/EdgeNode/internal/utils/ranges" 4 | 5 | type ReaderFunc func(n int) (goNext bool, err error) 6 | 7 | type Reader interface { 8 | // Init 初始化 9 | Init() error 10 | 11 | // TypeName 类型名称 12 | TypeName() string 13 | 14 | // ExpiresAt 过期时间 15 | ExpiresAt() int64 16 | 17 | // Status 状态码 18 | Status() int 19 | 20 | // LastModified 最后修改时间 21 | LastModified() int64 22 | 23 | // ReadHeader 读取Header 24 | ReadHeader(buf []byte, callback ReaderFunc) error 25 | 26 | // ReadBody 读取Body 27 | ReadBody(buf []byte, callback ReaderFunc) error 28 | 29 | // Read 实现io.Reader接口 30 | Read(buf []byte) (int, error) 31 | 32 | // ReadBodyRange 读取某个范围内的Body 33 | ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error 34 | 35 | // HeaderSize Header Size 36 | HeaderSize() int64 37 | 38 | // BodySize Body Size 39 | BodySize() int64 40 | 41 | // ContainsRange 是否包含某个区间内容 42 | ContainsRange(r rangeutils.Range) (r2 rangeutils.Range, ok bool) 43 | 44 | // Close 关闭 45 | Close() error 46 | } 47 | -------------------------------------------------------------------------------- /internal/caches/reader_file_mmap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | //go:build !plus 3 | 4 | package caches 5 | 6 | import ( 7 | "errors" 8 | "io" 9 | ) 10 | 11 | type MMAPFileReader struct { 12 | FileReader 13 | } 14 | 15 | func (this *MMAPFileReader) CopyBodyTo(writer io.Writer) (int, error) { 16 | // stub 17 | return 0, errors.New("not implemented") 18 | } 19 | -------------------------------------------------------------------------------- /internal/caches/stat.go: -------------------------------------------------------------------------------- 1 | package caches 2 | 3 | type Stat struct { 4 | Count int // 数量 5 | ValueSize int64 // 值占用的空间 6 | Size int64 // 占用的空间尺寸 7 | } 8 | -------------------------------------------------------------------------------- /internal/caches/storage_file_ext.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | //go:build !plus 3 | 4 | package caches 5 | 6 | func (this *FileStorage) tryMMAPReader(isPartial bool, estimatedSize int64, path string) (Reader, error) { 7 | // stub 8 | return nil, nil 9 | } 10 | -------------------------------------------------------------------------------- /internal/caches/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package caches 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeCommon/pkg/configutils" 7 | "net" 8 | "strings" 9 | ) 10 | 11 | func ParseHost(key string) string { 12 | var schemeIndex = strings.Index(key, "://") 13 | if schemeIndex <= 0 { 14 | return "" 15 | } 16 | 17 | var firstSlashIndex = strings.Index(key[schemeIndex+3:], "/") 18 | if firstSlashIndex <= 0 { 19 | return "" 20 | } 21 | 22 | var host = key[schemeIndex+3 : schemeIndex+3+firstSlashIndex] 23 | 24 | hostPart, _, err := net.SplitHostPort(host) 25 | if err == nil && len(hostPart) > 0 { 26 | host = configutils.QuoteIP(hostPart) 27 | } 28 | 29 | return host 30 | } 31 | -------------------------------------------------------------------------------- /internal/caches/utils_partial.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package caches 4 | 5 | import "strings" 6 | 7 | // PartialRangesFilePath 获取 ranges 文件路径 8 | func PartialRangesFilePath(path string) string { 9 | // ranges路径 10 | var dotIndex = strings.LastIndex(path, ".") 11 | var rangePath string 12 | if dotIndex < 0 { 13 | rangePath = path + "@ranges.cache" 14 | } else { 15 | rangePath = path[:dotIndex] + "@ranges" + path[dotIndex:] 16 | } 17 | return rangePath 18 | } 19 | -------------------------------------------------------------------------------- /internal/caches/writer.go: -------------------------------------------------------------------------------- 1 | package caches 2 | 3 | // Writer 缓存内容写入接口 4 | type Writer interface { 5 | // WriteHeader 写入Header数据 6 | WriteHeader(data []byte) (n int, err error) 7 | 8 | // Write 写入Body数据 9 | Write(data []byte) (n int, err error) 10 | 11 | // WriteAt 在指定位置写入数据 12 | WriteAt(offset int64, data []byte) error 13 | 14 | // HeaderSize 写入的Header数据大小 15 | HeaderSize() int64 16 | 17 | // BodySize 写入的Body数据大小 18 | BodySize() int64 19 | 20 | // Close 关闭 21 | Close() error 22 | 23 | // Discard 丢弃 24 | Discard() error 25 | 26 | // Key Key 27 | Key() string 28 | 29 | // ExpiredAt 过期时间 30 | ExpiredAt() int64 31 | 32 | // ItemType 内容类型 33 | ItemType() ItemType 34 | } 35 | -------------------------------------------------------------------------------- /internal/compressions/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package compressions 4 | 5 | import "errors" 6 | 7 | var ErrIsBusy = errors.New("the system is busy for compression") 8 | 9 | func CanIgnore(err error) bool { 10 | if err == nil { 11 | return true 12 | } 13 | return errors.Is(err, ErrIsBusy) 14 | } 15 | -------------------------------------------------------------------------------- /internal/compressions/reader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package compressions 4 | 5 | import "io" 6 | 7 | type Reader interface { 8 | Read(p []byte) (n int, err error) 9 | Reset(reader io.Reader) error 10 | RawClose() error 11 | Close() error 12 | IncreaseHit() uint32 13 | 14 | SetPool(pool *ReaderPool) 15 | ResetFinish() 16 | } 17 | -------------------------------------------------------------------------------- /internal/compressions/reader_base.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package compressions 4 | 5 | import "sync/atomic" 6 | 7 | type BaseReader struct { 8 | pool *ReaderPool 9 | 10 | isFinished bool 11 | hits uint32 12 | } 13 | 14 | func (this *BaseReader) SetPool(pool *ReaderPool) { 15 | this.pool = pool 16 | } 17 | 18 | func (this *BaseReader) Finish(obj Reader) error { 19 | if this.isFinished { 20 | return nil 21 | } 22 | err := obj.RawClose() 23 | if err == nil && this.pool != nil { 24 | this.pool.Put(obj) 25 | } 26 | this.isFinished = true 27 | return err 28 | } 29 | 30 | func (this *BaseReader) ResetFinish() { 31 | this.isFinished = false 32 | } 33 | 34 | func (this *BaseReader) IncreaseHit() uint32 { 35 | return atomic.AddUint32(&this.hits, 1) 36 | } 37 | -------------------------------------------------------------------------------- /internal/compressions/reader_brotli.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | //go:build !plus || !linux 3 | 4 | package compressions 5 | 6 | import ( 7 | "github.com/andybalholm/brotli" 8 | "io" 9 | "strings" 10 | ) 11 | 12 | type BrotliReader struct { 13 | BaseReader 14 | 15 | reader *brotli.Reader 16 | } 17 | 18 | func NewBrotliReader(reader io.Reader) (Reader, error) { 19 | return sharedBrotliReaderPool.Get(reader) 20 | } 21 | 22 | func newBrotliReader(reader io.Reader) (Reader, error) { 23 | return &BrotliReader{reader: brotli.NewReader(reader)}, nil 24 | } 25 | 26 | func (this *BrotliReader) Read(p []byte) (n int, err error) { 27 | n, err = this.reader.Read(p) 28 | if err != nil && strings.Contains(err.Error(), "excessive") { 29 | err = io.EOF 30 | } 31 | return 32 | } 33 | 34 | func (this *BrotliReader) Reset(reader io.Reader) error { 35 | return this.reader.Reset(reader) 36 | } 37 | 38 | func (this *BrotliReader) RawClose() error { 39 | return nil 40 | } 41 | 42 | func (this *BrotliReader) Close() error { 43 | return this.Finish(this) 44 | } 45 | -------------------------------------------------------------------------------- /internal/compressions/reader_deflate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package compressions 4 | 5 | import ( 6 | "compress/flate" 7 | "io" 8 | ) 9 | 10 | type DeflateReader struct { 11 | BaseReader 12 | 13 | reader io.ReadCloser 14 | } 15 | 16 | func NewDeflateReader(reader io.Reader) (Reader, error) { 17 | return sharedDeflateReaderPool.Get(reader) 18 | } 19 | 20 | func newDeflateReader(reader io.Reader) (Reader, error) { 21 | return &DeflateReader{reader: flate.NewReader(reader)}, nil 22 | } 23 | 24 | func (this *DeflateReader) Read(p []byte) (n int, err error) { 25 | return this.reader.Read(p) 26 | } 27 | 28 | func (this *DeflateReader) Reset(reader io.Reader) error { 29 | this.reader = flate.NewReader(reader) 30 | return nil 31 | } 32 | 33 | func (this *DeflateReader) RawClose() error { 34 | return this.reader.Close() 35 | } 36 | 37 | func (this *DeflateReader) Close() error { 38 | return this.Finish(this) 39 | } 40 | -------------------------------------------------------------------------------- /internal/compressions/reader_gzip.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package compressions 4 | 5 | import ( 6 | "github.com/klauspost/compress/gzip" 7 | "io" 8 | ) 9 | 10 | type GzipReader struct { 11 | BaseReader 12 | 13 | reader *gzip.Reader 14 | } 15 | 16 | func NewGzipReader(reader io.Reader) (Reader, error) { 17 | return sharedGzipReaderPool.Get(reader) 18 | } 19 | 20 | func newGzipReader(reader io.Reader) (Reader, error) { 21 | r, err := gzip.NewReader(reader) 22 | if err != nil { 23 | return nil, err 24 | } 25 | return &GzipReader{ 26 | reader: r, 27 | }, nil 28 | } 29 | 30 | func (this *GzipReader) Read(p []byte) (n int, err error) { 31 | return this.reader.Read(p) 32 | } 33 | 34 | func (this *GzipReader) Reset(reader io.Reader) error { 35 | return this.reader.Reset(reader) 36 | } 37 | 38 | func (this *GzipReader) RawClose() error { 39 | return this.reader.Close() 40 | } 41 | 42 | func (this *GzipReader) Close() error { 43 | return this.Finish(this) 44 | } 45 | -------------------------------------------------------------------------------- /internal/compressions/reader_pool_brotli.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package compressions 4 | 5 | import ( 6 | teaconst "github.com/TeaOSLab/EdgeNode/internal/const" 7 | "io" 8 | ) 9 | 10 | var sharedBrotliReaderPool *ReaderPool 11 | 12 | func init() { 13 | if !teaconst.IsMain { 14 | return 15 | } 16 | 17 | 18 | sharedBrotliReaderPool = NewReaderPool(CalculatePoolSize(), func(reader io.Reader) (Reader, error) { 19 | return newBrotliReader(reader) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /internal/compressions/reader_pool_deflate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package compressions 4 | 5 | import ( 6 | teaconst "github.com/TeaOSLab/EdgeNode/internal/const" 7 | "io" 8 | ) 9 | 10 | var sharedDeflateReaderPool *ReaderPool 11 | 12 | func init() { 13 | if !teaconst.IsMain { 14 | return 15 | } 16 | 17 | sharedDeflateReaderPool = NewReaderPool(CalculatePoolSize(), func(reader io.Reader) (Reader, error) { 18 | return newDeflateReader(reader) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /internal/compressions/reader_pool_gzip.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package compressions 4 | 5 | import ( 6 | teaconst "github.com/TeaOSLab/EdgeNode/internal/const" 7 | "io" 8 | ) 9 | 10 | var sharedGzipReaderPool *ReaderPool 11 | 12 | func init() { 13 | if !teaconst.IsMain { 14 | return 15 | } 16 | 17 | sharedGzipReaderPool = NewReaderPool(CalculatePoolSize(), func(reader io.Reader) (Reader, error) { 18 | return newGzipReader(reader) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /internal/compressions/reader_pool_zstd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package compressions 4 | 5 | import ( 6 | teaconst "github.com/TeaOSLab/EdgeNode/internal/const" 7 | "io" 8 | ) 9 | 10 | var sharedZSTDReaderPool *ReaderPool 11 | 12 | func init() { 13 | if !teaconst.IsMain { 14 | return 15 | } 16 | 17 | sharedZSTDReaderPool = NewReaderPool(CalculatePoolSize(), func(reader io.Reader) (Reader, error) { 18 | return newZSTDReader(reader) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /internal/compressions/reader_zstd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package compressions 4 | 5 | import ( 6 | "github.com/klauspost/compress/zstd" 7 | "io" 8 | ) 9 | 10 | type ZSTDReader struct { 11 | BaseReader 12 | 13 | reader *zstd.Decoder 14 | } 15 | 16 | func NewZSTDReader(reader io.Reader) (Reader, error) { 17 | return sharedZSTDReaderPool.Get(reader) 18 | } 19 | 20 | func newZSTDReader(reader io.Reader) (Reader, error) { 21 | r, err := zstd.NewReader(reader, zstd.WithDecoderMaxWindow(256<<20)) 22 | if err != nil { 23 | return nil, err 24 | } 25 | return &ZSTDReader{ 26 | reader: r, 27 | }, nil 28 | } 29 | 30 | func (this *ZSTDReader) Read(p []byte) (n int, err error) { 31 | return this.reader.Read(p) 32 | } 33 | 34 | func (this *ZSTDReader) Reset(reader io.Reader) error { 35 | return this.reader.Reset(reader) 36 | } 37 | 38 | func (this *ZSTDReader) RawClose() error { 39 | this.reader.Close() 40 | return nil 41 | } 42 | 43 | func (this *ZSTDReader) Close() error { 44 | return this.Finish(this) 45 | } 46 | -------------------------------------------------------------------------------- /internal/compressions/utils_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package compressions_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/compressions" 7 | "github.com/iwind/TeaGo/assert" 8 | "testing" 9 | ) 10 | 11 | func TestGenerateCompressLevel(t *testing.T) { 12 | var a = assert.NewAssertion(t) 13 | 14 | t.Log(compressions.GenerateCompressLevel(0, 10)) 15 | t.Log(compressions.GenerateCompressLevel(1, 10)) 16 | t.Log(compressions.GenerateCompressLevel(1, 4)) 17 | 18 | { 19 | var level = compressions.GenerateCompressLevel(1, 2) 20 | t.Log(level) 21 | a.IsTrue(level >= 1 && level <= 2) 22 | } 23 | } 24 | 25 | func TestCalculatePoolSize(t *testing.T) { 26 | t.Log(compressions.CalculatePoolSize()) 27 | } 28 | -------------------------------------------------------------------------------- /internal/compressions/writer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package compressions 4 | 5 | import "io" 6 | 7 | type Writer interface { 8 | Write(p []byte) (int, error) 9 | Flush() error 10 | Reset(writer io.Writer) 11 | RawClose() error 12 | Close() error 13 | Level() int 14 | IncreaseHit() uint32 15 | 16 | SetPool(pool *WriterPool) 17 | ResetFinish() 18 | } 19 | -------------------------------------------------------------------------------- /internal/compressions/writer_base.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package compressions 4 | 5 | import ( 6 | "sync/atomic" 7 | ) 8 | 9 | type BaseWriter struct { 10 | pool *WriterPool 11 | 12 | isFinished bool 13 | 14 | hits uint32 15 | } 16 | 17 | func (this *BaseWriter) SetPool(pool *WriterPool) { 18 | this.pool = pool 19 | } 20 | 21 | func (this *BaseWriter) Finish(obj Writer) error { 22 | if this.isFinished { 23 | return nil 24 | } 25 | err := obj.RawClose() 26 | if err == nil && this.pool != nil { 27 | this.pool.Put(obj) 28 | } 29 | this.isFinished = true 30 | return err 31 | } 32 | 33 | func (this *BaseWriter) ResetFinish() { 34 | this.isFinished = false 35 | } 36 | 37 | func (this *BaseWriter) IncreaseHit() uint32 { 38 | return atomic.AddUint32(&this.hits, 1) 39 | } 40 | -------------------------------------------------------------------------------- /internal/compressions/writer_deflate_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package compressions_test 4 | 5 | import ( 6 | "bytes" 7 | "github.com/TeaOSLab/EdgeNode/internal/compressions" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func BenchmarkDeflateWriter_Write(b *testing.B) { 13 | var data = []byte(strings.Repeat("A", 1024)) 14 | 15 | for i := 0; i < b.N; i++ { 16 | var buf = &bytes.Buffer{} 17 | writer, err := compressions.NewDeflateWriter(buf, 5) 18 | if err != nil { 19 | b.Fatal(err) 20 | } 21 | 22 | for j := 0; j < 100; j++ { 23 | _, err = writer.Write(data) 24 | if err != nil { 25 | b.Fatal(err) 26 | } 27 | 28 | /**err = writer.Flush() 29 | if err != nil { 30 | b.Fatal(err) 31 | }**/ 32 | } 33 | 34 | _ = writer.Close() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /internal/compressions/writer_pool_brotli.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package compressions 4 | 5 | import ( 6 | teaconst "github.com/TeaOSLab/EdgeNode/internal/const" 7 | "io" 8 | ) 9 | 10 | var sharedBrotliWriterPool *WriterPool 11 | 12 | func init() { 13 | if !teaconst.IsMain { 14 | return 15 | } 16 | 17 | sharedBrotliWriterPool = NewWriterPool(CalculatePoolSize(), func(writer io.Writer, level int) (Writer, error) { 18 | return newBrotliWriter(writer) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /internal/compressions/writer_pool_deflate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package compressions 4 | 5 | import ( 6 | teaconst "github.com/TeaOSLab/EdgeNode/internal/const" 7 | "io" 8 | ) 9 | 10 | var sharedDeflateWriterPool *WriterPool 11 | 12 | func init() { 13 | if !teaconst.IsMain { 14 | return 15 | } 16 | 17 | sharedDeflateWriterPool = NewWriterPool(CalculatePoolSize(), func(writer io.Writer, level int) (Writer, error) { 18 | return newDeflateWriter(writer) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /internal/compressions/writer_pool_gzip.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package compressions 4 | 5 | import ( 6 | teaconst "github.com/TeaOSLab/EdgeNode/internal/const" 7 | "io" 8 | ) 9 | 10 | var sharedGzipWriterPool *WriterPool 11 | 12 | func init() { 13 | if !teaconst.IsMain { 14 | return 15 | } 16 | 17 | sharedGzipWriterPool = NewWriterPool(CalculatePoolSize(), func(writer io.Writer, level int) (Writer, error) { 18 | return newGzipWriter(writer) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /internal/compressions/writer_pool_zstd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package compressions 4 | 5 | import ( 6 | teaconst "github.com/TeaOSLab/EdgeNode/internal/const" 7 | "io" 8 | ) 9 | 10 | var sharedZSTDWriterPool *WriterPool 11 | 12 | func init() { 13 | if !teaconst.IsMain { 14 | return 15 | } 16 | 17 | sharedZSTDWriterPool = NewWriterPool(CalculatePoolSize(), func(writer io.Writer, level int) (Writer, error) { 18 | return newZSTDWriter(writer) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /internal/configs/api_config_test.go: -------------------------------------------------------------------------------- 1 | package configs_test 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/configs" 5 | _ "github.com/iwind/TeaGo/bootstrap" 6 | "gopkg.in/yaml.v3" 7 | "testing" 8 | ) 9 | 10 | func TestLoadAPIConfig(t *testing.T) { 11 | config, err := configs.LoadAPIConfig() 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | t.Logf("%+v", config) 16 | 17 | configData, err := yaml.Marshal(config) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | t.Log(string(configData)) 22 | } 23 | -------------------------------------------------------------------------------- /internal/configs/cluster_config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package configs_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/configs" 7 | "github.com/TeaOSLab/EdgeNode/internal/utils/testutils" 8 | "gopkg.in/yaml.v3" 9 | "testing" 10 | ) 11 | 12 | func TestLoadClusterConfig(t *testing.T) { 13 | if !testutils.IsSingleTesting() { 14 | return 15 | } 16 | 17 | config, err := configs.LoadClusterConfig() 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | t.Logf("%+v", config) 22 | 23 | configData, err := yaml.Marshal(config) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | t.Log(string(configData)) 28 | } 29 | -------------------------------------------------------------------------------- /internal/conns/linger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package conns 4 | 5 | type LingerConn interface { 6 | SetLinger(sec int) error 7 | } 8 | -------------------------------------------------------------------------------- /internal/const/build.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | //go:build !plus 3 | // +build !plus 4 | 5 | package teaconst 6 | 7 | const BuildCommunity = true 8 | const BuildPlus = false 9 | const Tag = "community" 10 | -------------------------------------------------------------------------------- /internal/const/build_plus.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | //go:build plus 3 | // +build plus 4 | 5 | package teaconst 6 | 7 | const BuildCommunity = false 8 | const BuildPlus = true 9 | const Tag = "plus" 10 | -------------------------------------------------------------------------------- /internal/const/const.go: -------------------------------------------------------------------------------- 1 | package teaconst 2 | 3 | const ( 4 | Version = "1.3.8.2" 5 | 6 | ProductName = "Edge Node" 7 | ProcessName = "edge-node" 8 | 9 | Role = "node" 10 | 11 | EncryptMethod = "aes-256-cfb" 12 | 13 | // SystemdServiceName systemd 14 | SystemdServiceName = "edge-node" 15 | 16 | AccessLogSockName = "edge-node.accesslog" 17 | CacheGarbageSockName = "edge-node.cache.garbage" 18 | 19 | EnableKVCacheStore = true // determine store cache keys in KVStore or sqlite 20 | ) 21 | -------------------------------------------------------------------------------- /internal/const/vars.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package teaconst 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | var ( 12 | // 流量统计 13 | 14 | InTrafficBytes = uint64(0) 15 | OutTrafficBytes = uint64(0) 16 | 17 | NodeId int64 = 0 18 | NodeIdString = "" 19 | IsMain = checkMain() 20 | 21 | GlobalProductName = nodeconfigs.DefaultProductName 22 | 23 | IsQuiting = false // 是否正在退出 24 | EnableDBStat = false // 是否开启本地数据库统计 25 | ) 26 | 27 | // 检查是否为主程序 28 | func checkMain() bool { 29 | if len(os.Args) == 1 || 30 | (len(os.Args) >= 2 && os.Args[1] == "pprof") { 31 | return true 32 | } 33 | exe, _ := os.Executable() 34 | return strings.HasSuffix(exe, ".test") || 35 | strings.HasSuffix(exe, ".test.exe") || 36 | strings.Contains(exe, "___") 37 | } 38 | -------------------------------------------------------------------------------- /internal/encrypt/magic_key.go: -------------------------------------------------------------------------------- 1 | package encrypt 2 | 3 | import ( 4 | "github.com/iwind/TeaGo/logs" 5 | ) 6 | 7 | const ( 8 | MagicKey = "f1c8eafb543f03023e97b7be864a4e9b" 9 | ) 10 | 11 | // 加密特殊信息 12 | func MagicKeyEncode(data []byte) []byte { 13 | method, err := NewMethodInstance("aes-256-cfb", MagicKey, MagicKey[:16]) 14 | if err != nil { 15 | logs.Println("[MagicKeyEncode]" + err.Error()) 16 | return data 17 | } 18 | 19 | dst, err := method.Encrypt(data) 20 | if err != nil { 21 | logs.Println("[MagicKeyEncode]" + err.Error()) 22 | return data 23 | } 24 | return dst 25 | } 26 | 27 | // 解密特殊信息 28 | func MagicKeyDecode(data []byte) []byte { 29 | method, err := NewMethodInstance("aes-256-cfb", MagicKey, MagicKey[:16]) 30 | if err != nil { 31 | logs.Println("[MagicKeyEncode]" + err.Error()) 32 | return data 33 | } 34 | 35 | src, err := method.Decrypt(data) 36 | if err != nil { 37 | logs.Println("[MagicKeyEncode]" + err.Error()) 38 | return data 39 | } 40 | return src 41 | } 42 | -------------------------------------------------------------------------------- /internal/encrypt/magic_key_test.go: -------------------------------------------------------------------------------- 1 | package encrypt 2 | 3 | import "testing" 4 | 5 | func TestMagicKeyEncode(t *testing.T) { 6 | dst := MagicKeyEncode([]byte("Hello,World")) 7 | t.Log("dst:", string(dst)) 8 | 9 | src := MagicKeyDecode(dst) 10 | t.Log("src:", string(src)) 11 | } 12 | -------------------------------------------------------------------------------- /internal/encrypt/method.go: -------------------------------------------------------------------------------- 1 | package encrypt 2 | 3 | type MethodInterface interface { 4 | // Init 初始化 5 | Init(key []byte, iv []byte) error 6 | 7 | // Encrypt 加密 8 | Encrypt(src []byte) (dst []byte, err error) 9 | 10 | // Decrypt 解密 11 | Decrypt(dst []byte) (src []byte, err error) 12 | } 13 | -------------------------------------------------------------------------------- /internal/encrypt/method_aes_192_cfb_test.go: -------------------------------------------------------------------------------- 1 | package encrypt 2 | 3 | import ( 4 | "runtime" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestAES192CFBMethod_Encrypt(t *testing.T) { 10 | method, err := NewMethodInstance("aes-192-cfb", "abc", "123") 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | src := []byte("Hello, World") 15 | dst, err := method.Encrypt(src) 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | dst = dst[:len(src)] 20 | t.Log("dst:", string(dst)) 21 | 22 | src, err = method.Decrypt(dst) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | t.Log("src:", string(src)) 27 | } 28 | 29 | func BenchmarkAES192CFBMethod_Encrypt(b *testing.B) { 30 | runtime.GOMAXPROCS(1) 31 | 32 | method, err := NewMethodInstance("aes-192-cfb", "abc", "123") 33 | if err != nil { 34 | b.Fatal(err) 35 | } 36 | 37 | src := []byte(strings.Repeat("Hello", 1024)) 38 | for i := 0; i < b.N; i++ { 39 | dst, err := method.Encrypt(src) 40 | if err != nil { 41 | b.Fatal(err) 42 | } 43 | _ = dst 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /internal/encrypt/method_aes_256_cfb_test.go: -------------------------------------------------------------------------------- 1 | package encrypt 2 | 3 | import "testing" 4 | 5 | func TestAES256CFBMethod_Encrypt(t *testing.T) { 6 | method, err := NewMethodInstance("aes-256-cfb", "abc", "123") 7 | if err != nil { 8 | t.Fatal(err) 9 | } 10 | src := []byte("Hello, World") 11 | dst, err := method.Encrypt(src) 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | dst = dst[:len(src)] 16 | t.Log("dst:", string(dst)) 17 | 18 | src, err = method.Decrypt(dst) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | t.Log("src:", string(src)) 23 | } 24 | 25 | func TestAES256CFBMethod_Encrypt2(t *testing.T) { 26 | method, err := NewMethodInstance("aes-256-cfb", "abc", "123") 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | src := []byte("Hello, World") 31 | dst, err := method.Encrypt(src) 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | t.Log("dst:", string(dst)) 36 | 37 | src, err = method.Decrypt(dst) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | t.Log("src:", string(src)) 42 | } 43 | -------------------------------------------------------------------------------- /internal/encrypt/method_raw.go: -------------------------------------------------------------------------------- 1 | package encrypt 2 | 3 | type RawMethod struct { 4 | } 5 | 6 | func (this *RawMethod) Init(key, iv []byte) error { 7 | return nil 8 | } 9 | 10 | func (this *RawMethod) Encrypt(src []byte) (dst []byte, err error) { 11 | if len(src) == 0 { 12 | return 13 | } 14 | dst = make([]byte, len(src)) 15 | copy(dst, src) 16 | return 17 | } 18 | 19 | func (this *RawMethod) Decrypt(dst []byte) (src []byte, err error) { 20 | if len(dst) == 0 { 21 | return 22 | } 23 | src = make([]byte, len(dst)) 24 | copy(src, dst) 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /internal/encrypt/method_raw_test.go: -------------------------------------------------------------------------------- 1 | package encrypt 2 | 3 | import "testing" 4 | 5 | func TestRawMethod_Encrypt(t *testing.T) { 6 | method, err := NewMethodInstance("raw", "abc", "123") 7 | if err != nil { 8 | t.Fatal(err) 9 | } 10 | src := []byte("Hello, World") 11 | dst, err := method.Encrypt(src) 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | dst = dst[:len(src)] 16 | t.Log("dst:", string(dst)) 17 | 18 | src, err = method.Decrypt(dst) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | t.Log("src:", string(src)) 23 | } 24 | -------------------------------------------------------------------------------- /internal/encrypt/method_utils_test.go: -------------------------------------------------------------------------------- 1 | package encrypt 2 | 3 | import "testing" 4 | 5 | func TestFindMethodInstance(t *testing.T) { 6 | t.Log(NewMethodInstance("a", "b", "")) 7 | t.Log(NewMethodInstance("aes-256-cfb", "123456", "")) 8 | } 9 | -------------------------------------------------------------------------------- /internal/errors/error_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | ) 7 | 8 | func TestNew(t *testing.T) { 9 | t.Log(New("hello")) 10 | t.Log(Wrap(errors.New("hello"))) 11 | t.Log(testError1()) 12 | t.Log(Wrap(testError1())) 13 | t.Log(Wrap(testError2())) 14 | } 15 | 16 | func testError1() error { 17 | return New("test error1") 18 | } 19 | 20 | func testError2() error { 21 | return Wrap(testError1()) 22 | } 23 | -------------------------------------------------------------------------------- /internal/events/events.go: -------------------------------------------------------------------------------- 1 | package events 2 | 3 | type Event = string 4 | 5 | const ( 6 | EventStart Event = "start" // start loading 7 | EventLoaded Event = "loaded" // first load 8 | EventQuit Event = "quit" // quit node gracefully 9 | EventReload Event = "reload" // reload config 10 | EventTerminated Event = "terminated" // process terminated 11 | EventNFTablesReady Event = "nftablesReady" // nftables ready 12 | EventReloadSomeServers Event = "reloadSomeServers" // reload some servers 13 | ) 14 | -------------------------------------------------------------------------------- /internal/events/utils_test.go: -------------------------------------------------------------------------------- 1 | package events_test 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/events" 5 | "testing" 6 | ) 7 | 8 | func TestOn(t *testing.T) { 9 | type User struct { 10 | name string 11 | } 12 | var u = &User{name: "lily"} 13 | var u2 = &User{name: "lucy"} 14 | 15 | events.On("hello", func() { 16 | t.Log("world") 17 | }) 18 | events.On("hello", func() { 19 | t.Log("world2") 20 | }) 21 | events.OnKey("hello", u, func() { 22 | t.Log("world3") 23 | }) 24 | events.OnKey("hello", u, func() { 25 | t.Log("world4") 26 | }) 27 | events.Remove(u) 28 | events.Remove(u2) 29 | events.OnKey("hello2", nil, func() { 30 | t.Log("world2") 31 | }) 32 | events.Notify("hello") 33 | } 34 | -------------------------------------------------------------------------------- /internal/firewalls/ddos_protection_others.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | //go:build !linux 3 | // +build !linux 4 | 5 | package firewalls 6 | 7 | import ( 8 | "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs" 9 | ) 10 | 11 | var SharedDDoSProtectionManager = NewDDoSProtectionManager() 12 | 13 | type DDoSProtectionManager struct { 14 | } 15 | 16 | func NewDDoSProtectionManager() *DDoSProtectionManager { 17 | return &DDoSProtectionManager{} 18 | } 19 | 20 | func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) error { 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /internal/firewalls/firewall_interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package firewalls 4 | 5 | // FirewallInterface 防火墙接口 6 | type FirewallInterface interface { 7 | // Name 名称 8 | Name() string 9 | 10 | // IsReady 是否已准备被调用 11 | IsReady() bool 12 | 13 | // IsMock 是否为模拟 14 | IsMock() bool 15 | 16 | // AllowPort 允许端口 17 | AllowPort(port int, protocol string) error 18 | 19 | // RemovePort 删除端口 20 | RemovePort(port int, protocol string) error 21 | 22 | // RejectSourceIP 拒绝某个源IP连接 23 | RejectSourceIP(ip string, timeoutSeconds int) error 24 | 25 | // DropSourceIP 丢弃某个源IP数据 26 | // ip 要封禁的IP 27 | // timeoutSeconds 过期时间 28 | // async 是否异步 29 | DropSourceIP(ip string, timeoutSeconds int, async bool) error 30 | 31 | // RemoveSourceIP 删除某个源IP 32 | RemoveSourceIP(ip string) error 33 | } 34 | -------------------------------------------------------------------------------- /internal/firewalls/nftables/.gitignore: -------------------------------------------------------------------------------- 1 | build_remote.sh -------------------------------------------------------------------------------- /internal/firewalls/nftables/chain_policy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | //go:build linux 3 | 4 | package nftables 5 | 6 | import nft "github.com/google/nftables" 7 | 8 | type ChainPolicy = nft.ChainPolicy 9 | 10 | // Possible ChainPolicy values. 11 | const ( 12 | ChainPolicyDrop = nft.ChainPolicyDrop 13 | ChainPolicyAccept = nft.ChainPolicyAccept 14 | ) 15 | -------------------------------------------------------------------------------- /internal/firewalls/nftables/element.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | //go:build linux 3 | // +build linux 4 | 5 | package nftables 6 | 7 | type Element struct { 8 | } 9 | -------------------------------------------------------------------------------- /internal/firewalls/nftables/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | //go:build linux 3 | // +build linux 4 | 5 | package nftables 6 | 7 | import ( 8 | "errors" 9 | "strings" 10 | ) 11 | 12 | var ErrTableNotFound = errors.New("table not found") 13 | var ErrChainNotFound = errors.New("chain not found") 14 | var ErrSetNotFound = errors.New("set not found") 15 | var ErrRuleNotFound = errors.New("rule not found") 16 | 17 | func IsNotFound(err error) bool { 18 | if err == nil { 19 | return false 20 | } 21 | return err == ErrTableNotFound || err == ErrChainNotFound || err == ErrSetNotFound || err == ErrRuleNotFound || strings.Contains(err.Error(), "no such file or directory") 22 | } 23 | -------------------------------------------------------------------------------- /internal/firewalls/nftables/family.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | //go:build linux 3 | 4 | package nftables 5 | 6 | import ( 7 | nft "github.com/google/nftables" 8 | ) 9 | 10 | type TableFamily = nft.TableFamily 11 | 12 | const ( 13 | TableFamilyINet TableFamily = nft.TableFamilyINet 14 | TableFamilyIPv4 TableFamily = nft.TableFamilyIPv4 15 | TableFamilyIPv6 TableFamily = nft.TableFamilyIPv6 16 | TableFamilyARP TableFamily = nft.TableFamilyARP 17 | TableFamilyNetdev TableFamily = nft.TableFamilyNetdev 18 | TableFamilyBridge TableFamily = nft.TableFamilyBridge 19 | ) 20 | -------------------------------------------------------------------------------- /internal/firewalls/nftables/rule.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | //go:build linux 3 | 4 | package nftables 5 | 6 | import ( 7 | nft "github.com/google/nftables" 8 | "github.com/google/nftables/expr" 9 | ) 10 | 11 | type Rule struct { 12 | rawRule *nft.Rule 13 | } 14 | 15 | func NewRule(rawRule *nft.Rule) *Rule { 16 | return &Rule{ 17 | rawRule: rawRule, 18 | } 19 | } 20 | 21 | func (this *Rule) Raw() *nft.Rule { 22 | return this.rawRule 23 | } 24 | 25 | func (this *Rule) LookupSetName() string { 26 | for _, e := range this.rawRule.Exprs { 27 | exp, ok := e.(*expr.Lookup) 28 | if ok { 29 | return exp.SetName 30 | } 31 | } 32 | return "" 33 | } 34 | 35 | func (this *Rule) VerDict() expr.VerdictKind { 36 | for _, e := range this.rawRule.Exprs { 37 | exp, ok := e.(*expr.Verdict) 38 | if ok { 39 | return exp.Kind 40 | } 41 | } 42 | 43 | return -100 44 | } 45 | 46 | func (this *Rule) Handle() uint64 { 47 | return this.rawRule.Handle 48 | } 49 | 50 | func (this *Rule) UserData() []byte { 51 | return this.rawRule.UserData 52 | } 53 | -------------------------------------------------------------------------------- /internal/firewalls/nftables/set_batch.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | //go:build linux 3 | 4 | package nftables 5 | 6 | import ( 7 | nft "github.com/google/nftables" 8 | ) 9 | 10 | type SetBatch struct { 11 | conn *Conn 12 | rawSet *nft.Set 13 | } 14 | 15 | func (this *SetBatch) AddElement(key []byte, options *ElementOptions) error { 16 | var rawElement = nft.SetElement{ 17 | Key: key, 18 | } 19 | if options != nil { 20 | rawElement.Timeout = options.Timeout 21 | } 22 | return this.conn.Raw().SetAddElements(this.rawSet, []nft.SetElement{ 23 | rawElement, 24 | }) 25 | } 26 | 27 | func (this *SetBatch) DeleteElement(key []byte) error { 28 | return this.conn.Raw().SetDeleteElements(this.rawSet, []nft.SetElement{ 29 | { 30 | Key: key, 31 | }, 32 | }) 33 | } 34 | 35 | func (this *SetBatch) Commit() error { 36 | return this.conn.Commit() 37 | } 38 | -------------------------------------------------------------------------------- /internal/firewalls/nftables/set_ext.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | //go:build linux && !plus 3 | 4 | package nftables 5 | 6 | func (this *Set) initElements() { 7 | // NOT IMPLEMENTED 8 | } 9 | -------------------------------------------------------------------------------- /internal/firewalls/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package firewalls 4 | 5 | import ( 6 | "time" 7 | ) 8 | 9 | // DropTemporaryTo 使用本地防火墙临时拦截IP数据包 10 | func DropTemporaryTo(ip string, expiresAt int64) { 11 | // 如果为0,则表示是长期有效 12 | if expiresAt <= 0 { 13 | expiresAt = time.Now().Unix() + 3600 14 | } 15 | 16 | var timeout = expiresAt - time.Now().Unix() 17 | if timeout < 1 { 18 | return 19 | } 20 | if timeout > 3600 { 21 | timeout = 3600 22 | } 23 | 24 | // 使用本地防火墙延长封禁 25 | var fw = Firewall() 26 | if fw != nil && !fw.IsMock() { 27 | // 这里 int(int64) 转换的前提是限制了 timeout <= 3600,否则将有整型溢出的风险 28 | _ = fw.DropSourceIP(ip, int(timeout), true) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /internal/goman/instance.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package goman 4 | 5 | import "time" 6 | 7 | type Instance struct { 8 | Id uint64 9 | CreatedTime time.Time 10 | File string 11 | Line int 12 | } 13 | -------------------------------------------------------------------------------- /internal/goman/lib_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package goman_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/goman" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestNew(t *testing.T) { 12 | goman.New(func() { 13 | t.Log("Hello") 14 | 15 | t.Log(goman.List()) 16 | }) 17 | 18 | time.Sleep(1 * time.Second) 19 | t.Log(goman.List()) 20 | 21 | time.Sleep(1 * time.Second) 22 | } 23 | 24 | func TestNewWithArgs(t *testing.T) { 25 | goman.NewWithArgs(func(args ...interface{}) { 26 | t.Log(args[0], args[1]) 27 | }, 1, 2) 28 | time.Sleep(1 * time.Second) 29 | } 30 | -------------------------------------------------------------------------------- /internal/goman/task_group.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package goman 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/zero" 7 | "runtime" 8 | "sync" 9 | ) 10 | 11 | type TaskGroup struct { 12 | semi chan zero.Zero 13 | wg *sync.WaitGroup 14 | locker *sync.RWMutex 15 | } 16 | 17 | func NewTaskGroup() *TaskGroup { 18 | var concurrent = runtime.NumCPU() 19 | if concurrent <= 1 { 20 | concurrent = 2 21 | } 22 | return &TaskGroup{ 23 | semi: make(chan zero.Zero, concurrent), 24 | wg: &sync.WaitGroup{}, 25 | locker: &sync.RWMutex{}, 26 | } 27 | } 28 | 29 | func (this *TaskGroup) Run(f func()) { 30 | this.wg.Add(1) 31 | go func() { 32 | defer this.wg.Done() 33 | 34 | this.semi <- zero.Zero{} 35 | 36 | f() 37 | 38 | <-this.semi 39 | }() 40 | } 41 | 42 | func (this *TaskGroup) Wait() { 43 | this.wg.Wait() 44 | } 45 | 46 | func (this *TaskGroup) Lock() { 47 | this.locker.Lock() 48 | } 49 | 50 | func (this *TaskGroup) Unlock() { 51 | this.locker.Unlock() 52 | } 53 | -------------------------------------------------------------------------------- /internal/goman/task_group_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package goman_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/goman" 7 | "runtime" 8 | "testing" 9 | ) 10 | 11 | func TestNewTaskGroup(t *testing.T) { 12 | var group = goman.NewTaskGroup() 13 | var m = map[int]bool{} 14 | 15 | for i := 0; i < runtime.NumCPU()*2; i++ { 16 | var index = i 17 | group.Run(func() { 18 | t.Log("task", index) 19 | 20 | group.Lock() 21 | _, ok := m[index] 22 | if ok { 23 | t.Error("duplicated:", index) 24 | } 25 | m[index] = true 26 | group.Unlock() 27 | }) 28 | } 29 | group.Wait() 30 | } 31 | -------------------------------------------------------------------------------- /internal/iplibrary/README.md: -------------------------------------------------------------------------------- 1 | # IPList 2 | List Check Order: 3 | ~~~ 4 | Global List --> Node List--> Server List --> WAF List --> Bind List 5 | ~~~ 6 | -------------------------------------------------------------------------------- /internal/iplibrary/action_base.go: -------------------------------------------------------------------------------- 1 | package iplibrary 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/iwind/TeaGo/maps" 6 | "net/http" 7 | ) 8 | 9 | type BaseAction struct { 10 | } 11 | 12 | func (this *BaseAction) Close() error { 13 | return nil 14 | } 15 | 16 | // DoHTTP 处理HTTP请求 17 | func (this *BaseAction) DoHTTP(req *http.Request, resp http.ResponseWriter) (goNext bool, err error) { 18 | return true, nil 19 | } 20 | 21 | func (this *BaseAction) convertParams(params maps.Map, ptr interface{}) error { 22 | data, err := json.Marshal(params) 23 | if err != nil { 24 | return err 25 | } 26 | err = json.Unmarshal(data, ptr) 27 | if err != nil { 28 | return err 29 | } 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /internal/iplibrary/action_errors.go: -------------------------------------------------------------------------------- 1 | package iplibrary 2 | 3 | // FataError 是否是致命错误 4 | type FataError struct { 5 | err string 6 | } 7 | 8 | func (this *FataError) Error() string { 9 | return this.err 10 | } 11 | 12 | func NewFataError(err string) error { 13 | return &FataError{err: err} 14 | } 15 | 16 | func IsFatalError(err error) bool { 17 | if err == nil { 18 | return false 19 | } 20 | _, ok := err.(*FataError) 21 | return ok 22 | } 23 | -------------------------------------------------------------------------------- /internal/iplibrary/action_interface.go: -------------------------------------------------------------------------------- 1 | package iplibrary 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" 5 | "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" 6 | "net/http" 7 | ) 8 | 9 | type ActionInterface interface { 10 | // Init 初始化 11 | Init(config *firewallconfigs.FirewallActionConfig) error 12 | 13 | // AddItem 添加 14 | AddItem(listType IPListType, item *pb.IPItem) error 15 | 16 | // DeleteItem 删除 17 | DeleteItem(listType IPListType, item *pb.IPItem) error 18 | 19 | // Close 关闭 20 | Close() error 21 | 22 | // DoHTTP 处理HTTP请求 23 | DoHTTP(req *http.Request, resp http.ResponseWriter) (goNext bool, err error) 24 | } 25 | -------------------------------------------------------------------------------- /internal/iplibrary/action_utils_test.go: -------------------------------------------------------------------------------- 1 | package iplibrary 2 | 3 | import "testing" 4 | 5 | func TestIPv4RangeToCIDRRange(t *testing.T) { 6 | t.Log(iPv4RangeToCIDRRange("192.168.0.0", "192.168.255.255")) 7 | } 8 | -------------------------------------------------------------------------------- /internal/iplibrary/init.go: -------------------------------------------------------------------------------- 1 | package iplibrary 2 | 3 | func init() { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /internal/iplibrary/ip_list_db.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package iplibrary 4 | 5 | import "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" 6 | 7 | type IPListDB interface { 8 | Name() string 9 | DeleteExpiredItems() error 10 | ReadMaxVersion() (int64, error) 11 | UpdateMaxVersion(version int64) error 12 | ReadItems(offset int64, size int64) (items []*pb.IPItem, goNext bool, err error) 13 | AddItem(item *pb.IPItem) error 14 | } 15 | -------------------------------------------------------------------------------- /internal/iplibrary/list_type.go: -------------------------------------------------------------------------------- 1 | package iplibrary 2 | 3 | type IPListType = string 4 | 5 | const ( 6 | IPListTypeWhite IPListType = "white" 7 | IPListTypeBlack IPListType = "black" 8 | ) 9 | -------------------------------------------------------------------------------- /internal/iplibrary/list_utils_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package iplibrary 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils/testutils" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestIPIsAllowed(t *testing.T) { 12 | if !testutils.IsSingleTesting() { 13 | return 14 | } 15 | 16 | var manager = NewIPListManager() 17 | manager.Init() 18 | 19 | var before = time.Now() 20 | defer func() { 21 | t.Log(time.Since(before).Seconds()*1000, "ms") 22 | }() 23 | t.Log(AllowIP("127.0.0.1", 0)) 24 | t.Log(AllowIP("127.0.0.1", 23)) 25 | } 26 | -------------------------------------------------------------------------------- /internal/iplibrary/result.go: -------------------------------------------------------------------------------- 1 | package iplibrary 2 | 3 | type Result struct { 4 | CityId int64 5 | Country string 6 | Region string 7 | Province string 8 | City string 9 | ISP string 10 | } 11 | -------------------------------------------------------------------------------- /internal/js/.gitignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /internal/metrics/metric_interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package metrics 4 | 5 | type MetricInterface interface { 6 | // MetricKey 指标对象 7 | MetricKey(key string) string 8 | 9 | // MetricValue 指标值 10 | MetricValue(value string) (result int64, ok bool) 11 | 12 | // MetricServerId 服务ID 13 | MetricServerId() int64 14 | 15 | // MetricCategory 指标分类 16 | MetricCategory() string 17 | } 18 | -------------------------------------------------------------------------------- /internal/metrics/sum_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package metrics_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/metrics" 7 | timeutil "github.com/iwind/TeaGo/utils/time" 8 | "runtime" 9 | "testing" 10 | ) 11 | 12 | func BenchmarkSumStat(b *testing.B) { 13 | runtime.GOMAXPROCS(2) 14 | 15 | b.RunParallel(func(pb *testing.PB) { 16 | for pb.Next() { 17 | metrics.UniqueKey(1, []string{"1.2.3.4"}, timeutil.Format("Ymd"), 1, 1) 18 | } 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /internal/metrics/task.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package metrics 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" 7 | "time" 8 | ) 9 | 10 | type Task interface { 11 | Init() error 12 | Item() *serverconfigs.MetricItemConfig 13 | SetItem(item *serverconfigs.MetricItemConfig) 14 | Add(obj MetricInterface) 15 | InsertStat(stat *Stat) error 16 | Upload(pauseDuration time.Duration) error 17 | Start() error 18 | Stop() error 19 | Delete() error 20 | CleanExpired() error 21 | } 22 | -------------------------------------------------------------------------------- /internal/metrics/task_kv_objects.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package metrics 4 | 5 | import ( 6 | "encoding/json" 7 | "errors" 8 | ) 9 | 10 | type ItemEncoder[T interface{ *Stat }] struct { 11 | } 12 | 13 | func (this *ItemEncoder[T]) Encode(value T) ([]byte, error) { 14 | return json.Marshal(value) 15 | } 16 | 17 | func (this *ItemEncoder[T]) EncodeField(value T, fieldName string) ([]byte, error) { 18 | return nil, errors.New("invalid field name '" + fieldName + "'") 19 | } 20 | 21 | func (this *ItemEncoder[T]) Decode(valueBytes []byte) (value T, err error) { 22 | err = json.Unmarshal(valueBytes, &value) 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /internal/monitor/value.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package monitor 4 | 5 | // ItemValue 数据值定义 6 | type ItemValue struct { 7 | Item string 8 | ValueJSON []byte 9 | CreatedAt int64 10 | } 11 | -------------------------------------------------------------------------------- /internal/monitor/value_queue_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package monitor 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" 7 | "github.com/TeaOSLab/EdgeNode/internal/rpc" 8 | "github.com/TeaOSLab/EdgeNode/internal/utils/testutils" 9 | _ "github.com/iwind/TeaGo/bootstrap" 10 | "github.com/iwind/TeaGo/logs" 11 | "google.golang.org/grpc/status" 12 | "testing" 13 | ) 14 | 15 | func TestValueQueue_RPC(t *testing.T) { 16 | if !testutils.IsSingleTesting() { 17 | return 18 | } 19 | 20 | rpcClient, err := rpc.SharedRPC() 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | _, err = rpcClient.NodeValueRPC.CreateNodeValue(rpcClient.Context(), &pb.CreateNodeValueRequest{}) 25 | if err != nil { 26 | statusErr, ok := status.FromError(err) 27 | if ok { 28 | logs.Println(statusErr.Code()) 29 | } 30 | t.Fatal(err) 31 | } 32 | t.Log("ok") 33 | } 34 | -------------------------------------------------------------------------------- /internal/nodes/api_stream_test.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/utils/testutils" 5 | "testing" 6 | ) 7 | 8 | func TestAPIStream_Start(t *testing.T) { 9 | if !testutils.IsSingleTesting() { 10 | return 11 | } 12 | 13 | apiStream := NewAPIStream() 14 | apiStream.Start() 15 | } 16 | -------------------------------------------------------------------------------- /internal/nodes/client_conn_interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package nodes 4 | 5 | type ClientConnInterface interface { 6 | // IsClosed 是否已关闭 7 | IsClosed() bool 8 | 9 | // IsBound 是否已绑定服务 10 | IsBound() bool 11 | 12 | // Bind 绑定服务 13 | Bind(serverId int64, remoteAddr string, maxConnsPerServer int, maxConnsPerIP int) bool 14 | 15 | // ServerId 获取服务ID 16 | ServerId() int64 17 | 18 | // SetServerId 设置服务ID 19 | SetServerId(serverId int64) (goNext bool) 20 | 21 | // SetUserId 设置所属网站的用户ID 22 | SetUserId(userId int64) 23 | 24 | // SetUserPlanId 设置 25 | SetUserPlanId(userPlanId int64) 26 | 27 | // UserId 获取当前连接所属服务的用户ID 28 | UserId() int64 29 | 30 | // SetIsPersistent 设置是否为持久化 31 | SetIsPersistent(isPersistent bool) 32 | 33 | // SetFingerprint 设置指纹信息 34 | SetFingerprint(fingerprint []byte) 35 | 36 | // Fingerprint 读取指纹信息 37 | Fingerprint() []byte 38 | 39 | // LastRequestBytes 读取上一次请求发送的字节数 40 | LastRequestBytes() int64 41 | } 42 | -------------------------------------------------------------------------------- /internal/nodes/client_conn_limiter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package nodes 4 | 5 | import ( 6 | "github.com/iwind/TeaGo/logs" 7 | "testing" 8 | ) 9 | 10 | func TestClientConnLimiter_Add(t *testing.T) { 11 | var limiter = NewClientConnLimiter() 12 | { 13 | b := limiter.Add("127.0.0.1:1234", 1, "192.168.1.100", 10, 5) 14 | t.Log(b) 15 | } 16 | { 17 | b := limiter.Add("127.0.0.1:1235", 1, "192.168.1.100", 10, 5) 18 | t.Log(b) 19 | } 20 | { 21 | b := limiter.Add("127.0.0.1:1236", 1, "192.168.1.100", 10, 5) 22 | t.Log(b) 23 | } 24 | { 25 | b := limiter.Add("127.0.0.1:1237", 1, "192.168.1.101", 10, 5) 26 | t.Log(b) 27 | } 28 | { 29 | b := limiter.Add("127.0.0.1:1238", 1, "192.168.1.100", 5, 5) 30 | t.Log(b) 31 | } 32 | limiter.Remove("127.0.0.1:1238") 33 | limiter.Remove("127.0.0.1:1239") 34 | limiter.Remove("127.0.0.1:1237") 35 | logs.PrintAsJSON(limiter.remoteAddrMap, t) 36 | logs.PrintAsJSON(limiter.ipConns, t) 37 | logs.PrintAsJSON(limiter.serverConns, t) 38 | } 39 | -------------------------------------------------------------------------------- /internal/nodes/client_conn_utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package nodes 4 | 5 | import ( 6 | "net" 7 | ) 8 | 9 | // 判断客户端连接是否已关闭 10 | func isClientConnClosed(conn net.Conn) bool { 11 | if conn == nil { 12 | return true 13 | } 14 | clientConn, ok := conn.(ClientConnInterface) 15 | if ok { 16 | return clientConn.IsClosed() 17 | } 18 | 19 | return true 20 | } 21 | -------------------------------------------------------------------------------- /internal/nodes/conn_linger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package nodes 4 | 5 | type LingerConn interface { 6 | SetLinger(sec int) error 7 | } 8 | -------------------------------------------------------------------------------- /internal/nodes/http_cache_task_manager_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package nodes_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" 7 | "github.com/TeaOSLab/EdgeNode/internal/caches" 8 | "github.com/TeaOSLab/EdgeNode/internal/nodes" 9 | "testing" 10 | ) 11 | 12 | func TestHTTPCacheTaskManager_Loop(t *testing.T) { 13 | // initialize cache policies 14 | config, err := nodeconfigs.SharedNodeConfig() 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | caches.SharedManager.UpdatePolicies(config.HTTPCachePolicies) 19 | 20 | var manager = nodes.NewHTTPCacheTaskManager() 21 | err = manager.Loop() 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /internal/nodes/http_client_transport.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package nodes 4 | 5 | import ( 6 | "net/http" 7 | ) 8 | 9 | const emptyHTTPLocation = "/$EmptyHTTPLocation$" 10 | 11 | type HTTPClientTransport struct { 12 | *http.Transport 13 | } 14 | 15 | func (this *HTTPClientTransport) RoundTrip(req *http.Request) (*http.Response, error) { 16 | resp, err := this.Transport.RoundTrip(req) 17 | if err != nil { 18 | return resp, err 19 | } 20 | 21 | // 检查在跳转相关状态中Location是否存在 22 | if httpStatusIsRedirect(resp.StatusCode) && len(resp.Header.Get("Location")) == 0 { 23 | resp.Header.Set("Location", emptyHTTPLocation) 24 | } 25 | return resp, nil 26 | } 27 | -------------------------------------------------------------------------------- /internal/nodes/http_request_cc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | //go:build !plus 3 | 4 | package nodes 5 | 6 | func (this *HTTPRequest) doCC() (block bool) { 7 | return 8 | } 9 | -------------------------------------------------------------------------------- /internal/nodes/http_request_events.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | //go:build !script 3 | // +build !script 4 | 5 | package nodes 6 | 7 | func (this *HTTPRequest) onInit() { 8 | } 9 | 10 | func (this *HTTPRequest) onRequest() { 11 | } 12 | -------------------------------------------------------------------------------- /internal/nodes/http_request_health_check.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package nodes 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeCommon/pkg/nodeutils" 7 | "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" 8 | "github.com/TeaOSLab/EdgeNode/internal/remotelogs" 9 | ) 10 | 11 | // 健康检查 12 | func (this *HTTPRequest) doHealthCheck(key string, isHealthCheck *bool) (stop bool) { 13 | this.tags = append(this.tags, "healthCheck") 14 | 15 | this.RawReq.Header.Del(serverconfigs.HealthCheckHeaderName) 16 | 17 | data, err := nodeutils.Base64DecodeMap(key) 18 | if err != nil { 19 | remotelogs.Error("HTTP_REQUEST_HEALTH_CHECK", "decode key failed: "+err.Error()) 20 | return 21 | } 22 | *isHealthCheck = true 23 | 24 | this.web.StatRef = nil 25 | 26 | if !data.GetBool("accessLogIsOn") { 27 | this.disableLog = true 28 | } 29 | 30 | if data.GetBool("onlyBasicRequest") { 31 | return true 32 | } 33 | 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /internal/nodes/http_request_hls.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | //go:build !plus 3 | 4 | package nodes 5 | 6 | import "net/http" 7 | 8 | func (this *HTTPRequest) processHLSBefore() (blocked bool) { 9 | // stub 10 | return false 11 | } 12 | 13 | func (this *HTTPRequest) processM3u8Response(resp *http.Response) error { 14 | // stub 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /internal/nodes/http_request_http3.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | //go:build !plus 3 | 4 | package nodes 5 | 6 | import "net/http" 7 | 8 | func (this *HTTPRequest) processHTTP3Headers(respHeader http.Header) { 9 | // stub 10 | } 11 | -------------------------------------------------------------------------------- /internal/nodes/http_request_ln.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | //go:build !plus 3 | // +build !plus 4 | 5 | package nodes 6 | 7 | import ( 8 | "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" 9 | ) 10 | 11 | const ( 12 | LNExpiresHeader = "X-Edge-Ln-Expires" 13 | ) 14 | 15 | func existsLnNodeIP(nodeIP string) bool { 16 | return false 17 | } 18 | 19 | func (this *HTTPRequest) checkLnRequest() bool { 20 | return false 21 | } 22 | 23 | func (this *HTTPRequest) getLnOrigin(excludingNodeIds []int64, urlHash uint64) (originConfig *serverconfigs.OriginConfig, lnNodeId int64, hasMultipleNodes bool) { 24 | return nil, 0, false 25 | } 26 | -------------------------------------------------------------------------------- /internal/nodes/http_request_oss.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | //go:build !plus 3 | 4 | package nodes 5 | 6 | import ( 7 | "errors" 8 | "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" 9 | "net/http" 10 | ) 11 | 12 | func (this *HTTPRequest) doOSSOrigin(origin *serverconfigs.OriginConfig) (resp *http.Response, goNext bool, errorCode string, ossBucketName string, err error) { 13 | // stub 14 | return nil, false, "", "", errors.New("not implemented") 15 | } 16 | -------------------------------------------------------------------------------- /internal/nodes/http_request_plan_before.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | //go:build !plus 3 | 4 | package nodes 5 | 6 | // 检查套餐 7 | func (this *HTTPRequest) doPlanBefore() (blocked bool) { 8 | // stub 9 | return false 10 | } 11 | -------------------------------------------------------------------------------- /internal/nodes/http_request_stat.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/stats" 5 | ) 6 | 7 | // 统计 8 | func (this *HTTPRequest) doStat() { 9 | if this.ReqServer == nil || this.web == nil || this.web.StatRef == nil { 10 | return 11 | } 12 | 13 | // 内置的统计 14 | stats.SharedHTTPRequestStatManager.AddRemoteAddr(this.ReqServer.Id, this.requestRemoteAddr(true), this.writer.SentBodyBytes(), this.isAttack) 15 | stats.SharedHTTPRequestStatManager.AddUserAgent(this.ReqServer.Id, this.requestHeader("User-Agent"), this.remoteAddr) 16 | } 17 | -------------------------------------------------------------------------------- /internal/nodes/http_request_sub.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package nodes 4 | 5 | import "net/http" 6 | 7 | // 执行子请求 8 | func (this *HTTPRequest) doSubRequest(writer http.ResponseWriter, rawReq *http.Request) { 9 | // 包装新请求对象 10 | req := &HTTPRequest{ 11 | RawReq: rawReq, 12 | RawWriter: writer, 13 | ReqServer: this.ReqServer, 14 | ReqHost: this.ReqHost, 15 | ServerName: this.ServerName, 16 | ServerAddr: this.ServerAddr, 17 | IsHTTP: this.IsHTTP, 18 | IsHTTPS: this.IsHTTPS, 19 | } 20 | req.isSubRequest = true 21 | req.Do() 22 | } 23 | -------------------------------------------------------------------------------- /internal/nodes/http_request_uam.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | //go:build !plus 3 | // +build !plus 4 | 5 | package nodes 6 | 7 | func (this *HTTPRequest) isUAMRequest() bool { 8 | // stub 9 | return false 10 | } 11 | 12 | // UAM 13 | func (this *HTTPRequest) doUAM() (block bool) { 14 | // stub 15 | return false 16 | } 17 | -------------------------------------------------------------------------------- /internal/nodes/http_request_user_agent.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package nodes 4 | 5 | import ( 6 | "net/http" 7 | ) 8 | 9 | func (this *HTTPRequest) doCheckUserAgent() (shouldStop bool) { 10 | if this.web.UserAgent == nil || !this.web.UserAgent.IsOn { 11 | return 12 | } 13 | 14 | const cacheSeconds = "3600" // 时间不能过长,防止修改设置后长期无法生效 15 | 16 | if this.web.UserAgent.MatchURL(this.URL()) && !this.web.UserAgent.AllowRequest(this.RawReq) { 17 | this.tags = append(this.tags, "userAgentCheck") 18 | this.writer.Header().Set("Cache-Control", "max-age="+cacheSeconds) 19 | this.writeCode(http.StatusForbidden, "The User-Agent has been blocked.", "当前访问已被UA名单拦截。") 20 | return true 21 | } 22 | 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /internal/nodes/http_writer_ext.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | //go:build !plus 3 | // +build !plus 4 | 5 | package nodes 6 | 7 | import ( 8 | "os" 9 | ) 10 | 11 | func (this *HTTPWriter) canSendfile() (*os.File, bool) { 12 | return nil, false 13 | } 14 | 15 | func (this *HTTPWriter) checkPlanBandwidth(n int) { 16 | // stub 17 | } 18 | -------------------------------------------------------------------------------- /internal/nodes/listener_base_ext.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | //go:build !plus 3 | 4 | package nodes 5 | 6 | import "crypto/tls" 7 | 8 | func (this *BaseListener) calculateFingerprint(clientInfo *tls.ClientHelloInfo) []byte { 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /internal/nodes/listener_base_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package nodes 4 | 5 | import ( 6 | "context" 7 | "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" 8 | "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" 9 | "github.com/iwind/TeaGo/types" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | func TestBaseListener_FindServer(t *testing.T) { 15 | sharedNodeConfig = &nodeconfigs.NodeConfig{} 16 | 17 | var listener = &BaseListener{} 18 | listener.Group = serverconfigs.NewServerAddressGroup("https://*:443") 19 | for i := 0; i < 1_000_000; i++ { 20 | var server = &serverconfigs.ServerConfig{ 21 | IsOn: true, 22 | Name: types.String(i) + ".hello.com", 23 | ServerNames: []*serverconfigs.ServerNameConfig{ 24 | {Name: types.String(i) + ".hello.com"}, 25 | }, 26 | } 27 | _ = server.Init(context.Background()) 28 | listener.Group.Add(server) 29 | } 30 | 31 | var before = time.Now() 32 | defer func() { 33 | t.Log(time.Since(before).Seconds()*1000, "ms") 34 | }() 35 | 36 | t.Log(listener.findNamedServerMatched("855555.hello.com")) 37 | } 38 | -------------------------------------------------------------------------------- /internal/nodes/listener_interface.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" 4 | 5 | // ListenerInterface 各协议监听器的接口 6 | type ListenerInterface interface { 7 | // Init 初始化 8 | Init() 9 | 10 | // Serve 监听 11 | Serve() error 12 | 13 | // Close 关闭 14 | Close() error 15 | 16 | // Reload 重载配置 17 | Reload(serverGroup *serverconfigs.ServerAddressGroup) 18 | 19 | // CountActiveConnections 获取当前活跃的连接数 20 | CountActiveConnections() int 21 | } 22 | -------------------------------------------------------------------------------- /internal/nodes/listener_test.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" 5 | "testing" 6 | ) 7 | 8 | func TestListener_Listen(t *testing.T) { 9 | listener := NewListener() 10 | 11 | group := serverconfigs.NewServerAddressGroup("https://:1234") 12 | 13 | listener.Reload(group) 14 | err := listener.Listen() 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /internal/nodes/node_ext.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | //go:build !script 3 | // +build !script 4 | 5 | package nodes 6 | 7 | func (this *Node) reloadCommonScripts() error { 8 | return nil 9 | } 10 | 11 | func (this *Node) reloadIPLibrary() { 12 | 13 | } 14 | 15 | func (this *Node) notifyPlusChange() error { 16 | return nil 17 | } 18 | 19 | func (this *Node) execTOAChangedTask() error { 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /internal/nodes/node_panic_arm64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | //go:build arm64 3 | 4 | package nodes 5 | 6 | // 处理异常 7 | func (this *Node) handlePanic() { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /internal/nodes/node_status_executor_test.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/shirou/gopsutil/v3/cpu" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestNodeStatusExecutor_CPU(t *testing.T) { 10 | countLogicCPU, err := cpu.Counts(true) 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | t.Log("logic count:", countLogicCPU) 15 | 16 | countPhysicalCPU, err := cpu.Counts(false) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | t.Log("physical count:", countPhysicalCPU) 21 | 22 | percents, err := cpu.Percent(100*time.Millisecond, false) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | t.Log(percents) 27 | } 28 | -------------------------------------------------------------------------------- /internal/nodes/node_tasks_ext.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | //go:build !plus 3 | 4 | package nodes 5 | 6 | import "github.com/TeaOSLab/EdgeNode/internal/rpc" 7 | 8 | func (this *Node) execScriptsChangedTask() error { 9 | // stub 10 | return nil 11 | } 12 | 13 | func (this *Node) execUAMPolicyChangedTask(rpcClient *rpc.RPCClient) error { 14 | // stub 15 | return nil 16 | } 17 | 18 | func (this *Node) execHTTPCCPolicyChangedTask(rpcClient *rpc.RPCClient) error { 19 | // stub 20 | return nil 21 | } 22 | 23 | func (this *Node) execHTTP3PolicyChangedTask(rpcClient *rpc.RPCClient) error { 24 | // stub 25 | return nil 26 | } 27 | 28 | func (this *Node) execHTTPPagesPolicyChangedTask(rpcClient *rpc.RPCClient) error { 29 | // stub 30 | return nil 31 | } 32 | 33 | func (this *Node) execNetworkSecurityPolicyChangedTask(rpcClient *rpc.RPCClient) error { 34 | // stub 35 | return nil 36 | } 37 | 38 | func (this *Node) execPlanChangedTask(rpcClient *rpc.RPCClient) error { 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /internal/nodes/node_test.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/utils/testutils" 5 | _ "github.com/iwind/TeaGo/bootstrap" 6 | "testing" 7 | ) 8 | 9 | func TestNode_Start(t *testing.T) { 10 | if !testutils.IsSingleTesting() { 11 | return 12 | } 13 | 14 | var node = NewNode() 15 | node.Start() 16 | } 17 | 18 | func TestNode_Test(t *testing.T) { 19 | if !testutils.IsSingleTesting() { 20 | return 21 | } 22 | 23 | var node = NewNode() 24 | err := node.Test() 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | t.Log("ok") 29 | } 30 | -------------------------------------------------------------------------------- /internal/nodes/origin_state.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package nodes 4 | 5 | import "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" 6 | 7 | type OriginState struct { 8 | CountFails int64 9 | UpdatedAt int64 10 | Config *serverconfigs.OriginConfig 11 | Addr string 12 | TLSHost string 13 | ReverseProxy *serverconfigs.ReverseProxyConfig 14 | } 15 | -------------------------------------------------------------------------------- /internal/nodes/origin_state_manager_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package nodes 4 | 5 | import "testing" 6 | 7 | func TestOriginManager_Loop(t *testing.T) { 8 | var manager = NewOriginStateManager() 9 | err := manager.Loop() 10 | if err != nil { 11 | t.Fatal(err) 12 | } 13 | 14 | t.Log(manager.stateMap) 15 | } 16 | -------------------------------------------------------------------------------- /internal/nodes/task_ocsp_update_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package nodes_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/nodes" 7 | "testing" 8 | ) 9 | 10 | func TestOCSPUpdateTask_Loop(t *testing.T) { 11 | var task = &nodes.OCSPUpdateTask{} 12 | err := task.Loop() 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /internal/nodes/toa_manager.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | //go:build !plus 3 | 4 | package nodes 5 | 6 | import "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" 7 | 8 | var sharedTOAManager = NewTOAManager() 9 | 10 | type TOAManager struct { 11 | } 12 | 13 | func NewTOAManager() *TOAManager { 14 | return &TOAManager{} 15 | } 16 | 17 | func (this *TOAManager) Apply(config *nodeconfigs.TOAConfig) error { 18 | return nil 19 | } 20 | 21 | func (this *TOAManager) Config() *nodeconfigs.TOAConfig { 22 | return nil 23 | } 24 | 25 | func (this *TOAManager) Quit() error { 26 | return nil 27 | } 28 | 29 | func (this *TOAManager) SendMsg(msg string) error { 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /internal/nodes/upgrade_manager_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package nodes 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils/testutils" 7 | _ "github.com/iwind/TeaGo/bootstrap" 8 | "testing" 9 | ) 10 | 11 | func TestUpgradeManager_install(t *testing.T) { 12 | if !testutils.IsSingleTesting() { 13 | return 14 | } 15 | 16 | err := NewUpgradeManager().install() 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | t.Log("ok") 21 | } 22 | -------------------------------------------------------------------------------- /internal/rpc/call_stat_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package rpc_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/rpc" 7 | "testing" 8 | ) 9 | 10 | func TestNewCallStat(t *testing.T) { 11 | var stat = rpc.NewCallStat(10) 12 | stat.Add(true, 1) 13 | stat.Add(true, 2) 14 | stat.Add(true, 3) 15 | stat.Add(false, 4) 16 | stat.Add(true, 0) 17 | stat.Add(true, 1) 18 | t.Log(stat.Sum()) 19 | } 20 | -------------------------------------------------------------------------------- /internal/stats/user_agent_parser_result.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package stats 4 | 5 | import ( 6 | "github.com/mssola/useragent" 7 | ) 8 | 9 | type UserAgentParserResult struct { 10 | OS useragent.OSInfo 11 | BrowserName string 12 | BrowserVersion string 13 | IsMobile bool 14 | } 15 | -------------------------------------------------------------------------------- /internal/trackers/label.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package trackers 4 | 5 | import "time" 6 | 7 | type tracker struct { 8 | label string 9 | startTime time.Time 10 | } 11 | 12 | func Begin(label string) *tracker { 13 | return &tracker{label: label, startTime: time.Now()} 14 | } 15 | 16 | func Run(label string, f func()) { 17 | var tr = Begin(label) 18 | f() 19 | tr.End() 20 | } 21 | 22 | func (this *tracker) End() { 23 | SharedManager.Add(this.label, time.Since(this.startTime).Seconds()*1000) 24 | } 25 | 26 | func (this *tracker) Begin(subLabel string) *tracker { 27 | return Begin(this.label + ":" + subLabel) 28 | } 29 | 30 | func (this *tracker) Add(duration time.Duration) { 31 | this.startTime = this.startTime.Add(-duration) 32 | } 33 | -------------------------------------------------------------------------------- /internal/ttlcache/item.go: -------------------------------------------------------------------------------- 1 | package ttlcache 2 | 3 | type Item[T any] struct { 4 | Value T 5 | expiredAt int64 6 | } 7 | -------------------------------------------------------------------------------- /internal/ttlcache/option.go: -------------------------------------------------------------------------------- 1 | package ttlcache 2 | 3 | type OptionInterface interface { 4 | } 5 | 6 | type PiecesOption struct { 7 | Count int 8 | } 9 | 10 | func NewPiecesOption(count int) *PiecesOption { 11 | return &PiecesOption{Count: count} 12 | } 13 | 14 | type MaxItemsOption struct { 15 | Count int 16 | } 17 | 18 | func NewMaxItemsOption(count int) *MaxItemsOption { 19 | return &MaxItemsOption{Count: count} 20 | } 21 | -------------------------------------------------------------------------------- /internal/ttlcache/utils.go: -------------------------------------------------------------------------------- 1 | package ttlcache 2 | 3 | import "github.com/cespare/xxhash/v2" 4 | 5 | func HashKeyBytes(key []byte) uint64 { 6 | return xxhash.Sum64(key) 7 | } 8 | 9 | func HashKeyString(key string) uint64 { 10 | return xxhash.Sum64String(key) 11 | } 12 | -------------------------------------------------------------------------------- /internal/utils/agents/agent.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package agents 4 | 5 | import ( 6 | "regexp" 7 | "strings" 8 | ) 9 | 10 | type Agent struct { 11 | Code string 12 | Keywords []string // user agent keywords 13 | 14 | suffixes []string // PTR suffixes 15 | reg *regexp.Regexp 16 | } 17 | 18 | func NewAgent(code string, suffixes []string, reg *regexp.Regexp, keywords []string) *Agent { 19 | return &Agent{ 20 | Code: code, 21 | suffixes: suffixes, 22 | reg: reg, 23 | Keywords: keywords, 24 | } 25 | } 26 | 27 | func (this *Agent) Match(ptr string) bool { 28 | if len(this.suffixes) > 0 { 29 | for _, suffix := range this.suffixes { 30 | if strings.HasSuffix(ptr, suffix) { 31 | return true 32 | } 33 | } 34 | } 35 | if this.reg != nil { 36 | return this.reg.MatchString(ptr) 37 | } 38 | return false 39 | } 40 | -------------------------------------------------------------------------------- /internal/utils/agents/agent_ip.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package agents 4 | 5 | type AgentIP struct { 6 | Id int64 `json:"id"` 7 | IP string `json:"ip"` 8 | AgentCode string `json:"agentCode"` 9 | } 10 | -------------------------------------------------------------------------------- /internal/utils/agents/agents_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package agents_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils/agents" 7 | "testing" 8 | ) 9 | 10 | func TestIsAgentFromUserAgent(t *testing.T) { 11 | t.Log(agents.IsAgentFromUserAgent("Mozilla/5.0 (Linux;u;Android 4.2.2;zh-cn;) AppleWebKit/534.46 (KHTML,like Gecko) Version/5.1 Mobile Safari/10600.6.3 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)")) 12 | t.Log(agents.IsAgentFromUserAgent("Mozilla/5.0 (Linux;u;Android 4.2.2;zh-cn;)")) 13 | } 14 | 15 | func BenchmarkIsAgentFromUserAgent(b *testing.B) { 16 | for i := 0; i < b.N; i++ { 17 | agents.IsAgentFromUserAgent("Mozilla/5.0 (Linux;u;Android 4.2.2;zh-cn;) AppleWebKit/534.46 (KHTML,like Gecko) Version/5.1 Mobile Safari/10600.6.3 (compatible; Yaho)") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /internal/utils/agents/db.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package agents 4 | 5 | type DB interface { 6 | Init() error 7 | InsertAgentIP(ipId int64, ip string, agentCode string) error 8 | ListAgentIPs(offset int64, size int64) (agentIPs []*AgentIP, err error) 9 | } 10 | -------------------------------------------------------------------------------- /internal/utils/agents/db_kv_objects.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package agents 4 | 5 | import ( 6 | "encoding/binary" 7 | "encoding/json" 8 | "errors" 9 | ) 10 | 11 | type AgentIPEncoder[T interface{ *AgentIP }] struct { 12 | } 13 | 14 | func (this *AgentIPEncoder[T]) Encode(value T) ([]byte, error) { 15 | return json.Marshal(value) 16 | } 17 | 18 | func (this *AgentIPEncoder[T]) EncodeField(value T, fieldName string) ([]byte, error) { 19 | return nil, errors.New("invalid field name '" + fieldName + "'") 20 | } 21 | 22 | func (this *AgentIPEncoder[T]) Decode(valueBytes []byte) (value T, err error) { 23 | err = json.Unmarshal(valueBytes, &value) 24 | return 25 | } 26 | 27 | // EncodeKey generate key for ip item 28 | func (this *AgentIPEncoder[T]) EncodeKey(item *AgentIP) string { 29 | var b = make([]byte, 8) 30 | if item.Id < 0 { 31 | item.Id = 0 32 | } 33 | 34 | binary.BigEndian.PutUint64(b, uint64(item.Id)) 35 | return string(b) 36 | } 37 | -------------------------------------------------------------------------------- /internal/utils/agents/ip_cache_map_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package agents 4 | 5 | import ( 6 | "github.com/iwind/TeaGo/logs" 7 | "testing" 8 | ) 9 | 10 | func TestNewIPCacheMap(t *testing.T) { 11 | var cacheMap = NewIPCacheMap(3) 12 | 13 | t.Log("====") 14 | cacheMap.Add("1") 15 | cacheMap.Add("2") 16 | logs.PrintAsJSON(cacheMap.m, t) 17 | logs.PrintAsJSON(cacheMap.list, t) 18 | 19 | t.Log("====") 20 | cacheMap.Add("3") 21 | logs.PrintAsJSON(cacheMap.m, t) 22 | logs.PrintAsJSON(cacheMap.list, t) 23 | 24 | t.Log("====") 25 | cacheMap.Add("4") 26 | logs.PrintAsJSON(cacheMap.m, t) 27 | logs.PrintAsJSON(cacheMap.list, t) 28 | 29 | t.Log("====") 30 | cacheMap.Add("3") 31 | logs.PrintAsJSON(cacheMap.m, t) 32 | logs.PrintAsJSON(cacheMap.list, t) 33 | } 34 | -------------------------------------------------------------------------------- /internal/utils/agents/manager_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package agents_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils/agents" 7 | "github.com/TeaOSLab/EdgeNode/internal/utils/testutils" 8 | "github.com/iwind/TeaGo/Tea" 9 | _ "github.com/iwind/TeaGo/bootstrap" 10 | "testing" 11 | ) 12 | 13 | func TestNewManager(t *testing.T) { 14 | if !testutils.IsSingleTesting() { 15 | return 16 | } 17 | 18 | var db = agents.NewSQLiteDB(Tea.Root + "/data/agents.db") 19 | err := db.Init() 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | var manager = agents.NewManager() 25 | manager.SetDB(db) 26 | err = manager.Load() 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | 31 | _, err = manager.Loop() 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | 36 | t.Log(manager.LookupIP("192.168.3.100")) 37 | } 38 | -------------------------------------------------------------------------------- /internal/utils/bfs/.gitignore: -------------------------------------------------------------------------------- 1 | DESIGN.md 2 | test.* 3 | -------------------------------------------------------------------------------- /internal/utils/bfs/block_info.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package bfs 4 | 5 | type BlockInfo struct { 6 | OriginOffsetFrom int64 `json:"1,omitempty"` 7 | OriginOffsetTo int64 `json:"2,omitempty"` 8 | 9 | BFileOffsetFrom int64 `json:"3,omitempty"` 10 | BFileOffsetTo int64 `json:"4,omitempty"` 11 | } 12 | 13 | func (this BlockInfo) Contains(offset int64) bool { 14 | return this.OriginOffsetFrom <= offset && this.OriginOffsetTo > /** MUST be gt, NOT gte **/ offset 15 | } 16 | -------------------------------------------------------------------------------- /internal/utils/bfs/blocks_file_options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package bfs 4 | 5 | type BlockFileOptions struct { 6 | BytesPerSync int64 7 | } 8 | 9 | func (this *BlockFileOptions) EnsureDefaults() { 10 | if this.BytesPerSync <= 0 { 11 | this.BytesPerSync = 1 << 20 12 | } 13 | } 14 | 15 | var DefaultBlockFileOptions = &BlockFileOptions{ 16 | BytesPerSync: 1 << 20, 17 | } 18 | -------------------------------------------------------------------------------- /internal/utils/bfs/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package bfs 4 | 5 | import ( 6 | "errors" 7 | "os" 8 | ) 9 | 10 | var ErrClosed = errors.New("the file closed") 11 | var ErrInvalidHash = errors.New("invalid hash") 12 | var ErrFileIsWriting = errors.New("the file is writing") 13 | 14 | func IsWritingErr(err error) bool { 15 | return err != nil && errors.Is(err, ErrFileIsWriting) 16 | } 17 | 18 | func IsNotExist(err error) bool { 19 | return err != nil && os.IsNotExist(err) 20 | } 21 | -------------------------------------------------------------------------------- /internal/utils/bfs/hash.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package bfs 4 | 5 | import ( 6 | "fmt" 7 | stringutil "github.com/iwind/TeaGo/utils/string" 8 | ) 9 | 10 | var HashLen = 32 11 | 12 | // CheckHash check hash string format 13 | func CheckHash(hash string) bool { 14 | if len(hash) != HashLen { 15 | return false 16 | } 17 | 18 | for _, b := range hash { 19 | if !((b >= '0' && b <= '9') || (b >= 'a' && b <= 'f')) { 20 | return false 21 | } 22 | } 23 | 24 | return true 25 | } 26 | 27 | func CheckHashErr(hash string) error { 28 | if CheckHash(hash) { 29 | return nil 30 | } 31 | return fmt.Errorf("check hash '%s' failed: %w", hash, ErrInvalidHash) 32 | } 33 | 34 | func Hash(s string) string { 35 | return stringutil.Md5(s) 36 | } 37 | -------------------------------------------------------------------------------- /internal/utils/bfs/hash_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package bfs_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils/bfs" 7 | "github.com/iwind/TeaGo/assert" 8 | "math/rand" 9 | "strconv" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | func TestCheckHash(t *testing.T) { 15 | var a = assert.NewAssertion(t) 16 | 17 | a.IsFalse(bfs.CheckHash("123456")) 18 | a.IsFalse(bfs.CheckHash(strings.Repeat("A", 32))) 19 | a.IsTrue(bfs.CheckHash(strings.Repeat("a", 32))) 20 | a.IsTrue(bfs.CheckHash(bfs.Hash("123456"))) 21 | } 22 | 23 | func BenchmarkCheckHashErr(b *testing.B) { 24 | for i := 0; i < b.N; i++ { 25 | _ = bfs.CheckHash(bfs.Hash(strconv.Itoa(rand.Int()))) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/utils/bfs/threads_limiter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package bfs 4 | 5 | import "github.com/TeaOSLab/EdgeNode/internal/zero" 6 | 7 | // TODO 线程数可以根据硬盘数量动态调整? 8 | var readThreadsLimiter = make(chan zero.Zero, 8) 9 | var writeThreadsLimiter = make(chan zero.Zero, 8) 10 | 11 | func AckReadThread() { 12 | readThreadsLimiter <- zero.Zero{} 13 | } 14 | 15 | func ReleaseReadThread() { 16 | <-readThreadsLimiter 17 | } 18 | 19 | func AckWriteThread() { 20 | writeThreadsLimiter <- zero.Zero{} 21 | } 22 | 23 | func ReleaseWriteThread() { 24 | <-writeThreadsLimiter 25 | } 26 | -------------------------------------------------------------------------------- /internal/utils/buffer_pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package utils 4 | 5 | import ( 6 | "bytes" 7 | "sync" 8 | ) 9 | 10 | var SharedBufferPool = NewBufferPool() 11 | 12 | // BufferPool pool for get byte slice 13 | type BufferPool struct { 14 | rawPool *sync.Pool 15 | } 16 | 17 | // NewBufferPool 创建新对象 18 | func NewBufferPool() *BufferPool { 19 | var pool = &BufferPool{} 20 | pool.rawPool = &sync.Pool{ 21 | New: func() any { 22 | return &bytes.Buffer{} 23 | }, 24 | } 25 | return pool 26 | } 27 | 28 | // Get 获取一个新的Buffer 29 | func (this *BufferPool) Get() (b *bytes.Buffer) { 30 | var buffer = this.rawPool.Get().(*bytes.Buffer) 31 | if buffer.Len() > 0 { 32 | buffer.Reset() 33 | } 34 | return buffer 35 | } 36 | 37 | // Put 放回一个使用过的byte slice 38 | func (this *BufferPool) Put(b *bytes.Buffer) { 39 | this.rawPool.Put(b) 40 | } 41 | -------------------------------------------------------------------------------- /internal/utils/byte/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package byteutils 4 | 5 | // Copy bytes 6 | func Copy(b []byte) []byte { 7 | var l = len(b) 8 | if l == 0 { 9 | return []byte{} 10 | } 11 | var d = make([]byte, l) 12 | copy(d, b) 13 | return d 14 | } 15 | 16 | // Append bytes 17 | func Append(b []byte, b2 ...byte) []byte { 18 | return append(Copy(b), b2...) 19 | } 20 | 21 | // Concat bytes 22 | func Concat(b []byte, b2 ...[]byte) []byte { 23 | b = Copy(b) 24 | for _, b3 := range b2 { 25 | b = append(b, b3...) 26 | } 27 | return b 28 | } 29 | -------------------------------------------------------------------------------- /internal/utils/bytepool/byte_pool.go: -------------------------------------------------------------------------------- 1 | package bytepool 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | var Pool1k = NewPool(1 << 10) 8 | var Pool4k = NewPool(4 << 10) 9 | var Pool16k = NewPool(16 << 10) 10 | var Pool32k = NewPool(32 << 10) 11 | 12 | type Buf struct { 13 | Bytes []byte 14 | } 15 | 16 | // Pool for get byte slice 17 | type Pool struct { 18 | length int 19 | rawPool *sync.Pool 20 | } 21 | 22 | // NewPool 创建新对象 23 | func NewPool(length int) *Pool { 24 | if length < 0 { 25 | length = 1024 26 | } 27 | return &Pool{ 28 | length: length, 29 | rawPool: &sync.Pool{ 30 | New: func() any { 31 | return &Buf{ 32 | Bytes: make([]byte, length), 33 | } 34 | }, 35 | }, 36 | } 37 | } 38 | 39 | // Get 获取一个新的byte slice 40 | func (this *Pool) Get() *Buf { 41 | return this.rawPool.Get().(*Buf) 42 | } 43 | 44 | // Put 放回一个使用过的byte slice 45 | func (this *Pool) Put(ptr *Buf) { 46 | this.rawPool.Put(ptr) 47 | } 48 | 49 | // Length 单个字节slice长度 50 | func (this *Pool) Length() int { 51 | return this.length 52 | } 53 | -------------------------------------------------------------------------------- /internal/utils/clock/manager_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package clock_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils/clock" 7 | "github.com/TeaOSLab/EdgeNode/internal/utils/testutils" 8 | "testing" 9 | ) 10 | 11 | func TestReadServer(t *testing.T) { 12 | if !testutils.IsSingleTesting() { 13 | return 14 | } 15 | 16 | t.Log(clock.NewClockManager().ReadServer("pool.ntp.org")) 17 | } 18 | -------------------------------------------------------------------------------- /internal/utils/clock/ntp_packet.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package clock 4 | 5 | type NTPPacket struct { 6 | Settings uint8 // leap yr indicator, ver number, and mode 7 | Stratum uint8 // stratum of local clock 8 | Poll int8 // poll exponent 9 | Precision int8 // precision exponent 10 | RootDelay uint32 // root delay 11 | RootDispersion uint32 // root dispersion 12 | ReferenceID uint32 // reference id 13 | RefTimeSec uint32 // reference timestamp sec 14 | RefTimeFrac uint32 // reference timestamp fractional 15 | OrigTimeSec uint32 // origin time secs 16 | OrigTimeFrac uint32 // origin time fractional 17 | RxTimeSec uint32 // receive time secs 18 | RxTimeFrac uint32 // receive time frac 19 | TxTimeSec uint32 // transmit time secs 20 | TxTimeFrac uint32 // transmit time frac 21 | } 22 | -------------------------------------------------------------------------------- /internal/utils/common_files.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package utils 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/zero" 7 | "strings" 8 | ) 9 | 10 | var commonFileExtensionsMap = map[string]zero.Zero{ 11 | ".ico": zero.New(), 12 | ".jpg": zero.New(), 13 | ".jpeg": zero.New(), 14 | ".gif": zero.New(), 15 | ".png": zero.New(), 16 | ".webp": zero.New(), 17 | ".woff2": zero.New(), 18 | ".js": zero.New(), 19 | ".css": zero.New(), 20 | ".ttf": zero.New(), 21 | ".otf": zero.New(), 22 | ".fnt": zero.New(), 23 | ".svg": zero.New(), 24 | ".map": zero.New(), 25 | ".avif": zero.New(), 26 | ".bmp": zero.New(), 27 | ".cur": zero.New(), 28 | } 29 | 30 | // IsCommonFileExtension 判断是否为常用文件扩展名 31 | // 不区分大小写,且不限于是否加点符号(.) 32 | func IsCommonFileExtension(ext string) bool { 33 | if len(ext) == 0 { 34 | return false 35 | } 36 | if ext[0] != '.' { 37 | ext = "." + ext 38 | } 39 | _, ok := commonFileExtensionsMap[strings.ToLower(ext)] 40 | return ok 41 | } 42 | -------------------------------------------------------------------------------- /internal/utils/common_files_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package utils_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils" 7 | "github.com/iwind/TeaGo/assert" 8 | "testing" 9 | ) 10 | 11 | func TestIsCommonFileExtension(t *testing.T) { 12 | var a = assert.NewAssertion(t) 13 | 14 | a.IsTrue(utils.IsCommonFileExtension(".jpg")) 15 | a.IsTrue(utils.IsCommonFileExtension("png")) 16 | a.IsTrue(utils.IsCommonFileExtension("PNG")) 17 | a.IsTrue(utils.IsCommonFileExtension(".PNG")) 18 | a.IsTrue(utils.IsCommonFileExtension("Png")) 19 | a.IsFalse(utils.IsCommonFileExtension("zip")) 20 | } 21 | -------------------------------------------------------------------------------- /internal/utils/dbs/db_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package dbs_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils/dbs" 7 | "net/url" 8 | "testing" 9 | ) 10 | 11 | func TestParseDSN(t *testing.T) { 12 | var dsn = "file:/home/cache/p43/.indexes/db-3.db?cache=private&mode=ro&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_cache_size=88000" 13 | u, err := url.Parse(dsn) 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | t.Log(u.Path) // expect: :/home/cache/p43/.indexes/db-3.db 18 | } 19 | -------------------------------------------------------------------------------- /internal/utils/dbs/query_label.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package dbs 4 | 5 | import "time" 6 | 7 | type QueryLabel struct { 8 | manager *QueryStatManager 9 | query string 10 | before time.Time 11 | } 12 | 13 | func NewQueryLabel(manager *QueryStatManager, query string) *QueryLabel { 14 | return &QueryLabel{ 15 | manager: manager, 16 | query: query, 17 | before: time.Now(), 18 | } 19 | } 20 | 21 | func (this *QueryLabel) End() { 22 | var cost = time.Since(this.before).Seconds() 23 | this.manager.AddCost(this.query, cost) 24 | } 25 | -------------------------------------------------------------------------------- /internal/utils/dbs/query_stat.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package dbs 4 | 5 | type QueryStat struct { 6 | Query string 7 | CostMin float64 8 | CostMax float64 9 | 10 | CostTotal float64 11 | Calls int64 12 | } 13 | 14 | func NewQueryStat(query string) *QueryStat { 15 | return &QueryStat{ 16 | Query: query, 17 | } 18 | } 19 | 20 | func (this *QueryStat) AddCost(cost float64) { 21 | if this.CostMin == 0 || this.CostMin > cost { 22 | this.CostMin = cost 23 | } 24 | if this.CostMax == 0 || this.CostMax < cost { 25 | this.CostMax = cost 26 | } 27 | 28 | this.CostTotal += cost 29 | this.Calls++ 30 | } 31 | -------------------------------------------------------------------------------- /internal/utils/dbs/query_stat_manager_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package dbs_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils/dbs" 7 | "github.com/iwind/TeaGo/logs" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestQueryStatManager(t *testing.T) { 13 | var manager = dbs.NewQueryStatManager() 14 | { 15 | var label = manager.AddQuery("sql 1") 16 | time.Sleep(1 * time.Second) 17 | label.End() 18 | } 19 | manager.AddQuery("sql 1").End() 20 | manager.AddQuery("sql 2").End() 21 | for _, stat := range manager.TopN(10) { 22 | logs.PrintAsJSON(stat, t) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internal/utils/dbs/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package dbs 4 | 5 | func IsClosedErr(err error) bool { 6 | return err == errDBIsClosed 7 | } 8 | -------------------------------------------------------------------------------- /internal/utils/errors.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "github.com/iwind/TeaGo/logs" 4 | 5 | func PrintError(err error) { 6 | // TODO 记录调用的文件名、行数 7 | logs.Println("[ERROR]" + err.Error()) 8 | } 9 | -------------------------------------------------------------------------------- /internal/utils/exec/look_others.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | //go:build !linux 3 | 4 | package executils 5 | 6 | import "os/exec" 7 | 8 | func LookPath(file string) (string, error) { 9 | return exec.LookPath(file) 10 | } 11 | -------------------------------------------------------------------------------- /internal/utils/exit.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package utils 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/events" 7 | "os" 8 | ) 9 | 10 | func Exit() { 11 | events.Notify(events.EventTerminated) 12 | os.Exit(0) 13 | } 14 | -------------------------------------------------------------------------------- /internal/utils/expires/id_key_map_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package expires_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils/expires" 7 | "github.com/iwind/TeaGo/assert" 8 | "github.com/iwind/TeaGo/logs" 9 | "testing" 10 | ) 11 | 12 | func TestNewIdKeyMap(t *testing.T) { 13 | var a = assert.NewAssertion(t) 14 | 15 | var m = expires.NewIdKeyMap() 16 | m.Add(1, "1") 17 | m.Add(1, "2") 18 | m.Add(100, "100") 19 | logs.PrintAsJSON(m.IdKeys(), t) 20 | logs.PrintAsJSON(m.KeyIds(), t) 21 | 22 | { 23 | k, ok := m.Key(1) 24 | a.IsTrue(ok) 25 | a.IsTrue(k == "2") 26 | } 27 | 28 | { 29 | _, ok := m.Key(2) 30 | a.IsFalse(ok) 31 | } 32 | 33 | m.DeleteKey("2") 34 | 35 | { 36 | _, ok := m.Key(1) 37 | a.IsFalse(ok) 38 | } 39 | 40 | logs.PrintAsJSON(m.IdKeys(), t) 41 | logs.PrintAsJSON(m.KeyIds(), t) 42 | 43 | m.DeleteId(100) 44 | 45 | logs.PrintAsJSON(m.IdKeys(), t) 46 | logs.PrintAsJSON(m.KeyIds(), t) 47 | } 48 | -------------------------------------------------------------------------------- /internal/utils/fnv/hash.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package fnv 4 | 5 | const ( 6 | offset64 uint64 = 14695981039346656037 7 | prime64 uint64 = 1099511628211 8 | ) 9 | 10 | // HashString 11 | // 非unique Hash 12 | func HashString(key string) uint64 { 13 | var hash = offset64 14 | for _, b := range key { 15 | hash ^= uint64(b) 16 | hash *= prime64 17 | } 18 | return hash 19 | } 20 | 21 | // Hash 22 | // 非unique Hash 23 | func Hash(key []byte) uint64 { 24 | var hash = offset64 25 | for _, b := range key { 26 | hash ^= uint64(b) 27 | hash *= prime64 28 | } 29 | return hash 30 | } 31 | -------------------------------------------------------------------------------- /internal/utils/fnv/hash_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package fnv_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils/fnv" 7 | "github.com/iwind/TeaGo/types" 8 | "testing" 9 | ) 10 | 11 | func TestHash(t *testing.T) { 12 | for _, key := range []string{"costarring", "liquid", "hello"} { 13 | var h = fnv.HashString(key) 14 | t.Log(key + " => " + types.String(h)) 15 | } 16 | } 17 | 18 | func BenchmarkHashString(b *testing.B) { 19 | b.RunParallel(func(pb *testing.PB) { 20 | for pb.Next() { 21 | _ = fnv.HashString("abcdefh") 22 | } 23 | }) 24 | } 25 | 26 | func BenchmarkHashString_Long(b *testing.B) { 27 | b.RunParallel(func(pb *testing.PB) { 28 | for pb.Next() { 29 | _ = fnv.HashString("HELLO,WORLDHELLO,WORLDHELLO,WORLDHELLO,WORLDHELLO,WORLDHELLO,WORLD") 30 | } 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /internal/utils/fs/disk_test_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package fsutils_test 4 | 5 | import ( 6 | fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs" 7 | "testing" 8 | ) 9 | 10 | func TestCheckDiskWritingSpeed(t *testing.T) { 11 | t.Log(fsutils.CheckDiskWritingSpeed()) 12 | } 13 | 14 | func TestCheckDiskIsFast(t *testing.T) { 15 | t.Log(fsutils.CheckDiskIsFast()) 16 | } -------------------------------------------------------------------------------- /internal/utils/fs/file_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package fsutils_test 4 | 5 | import ( 6 | fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs" 7 | "github.com/iwind/TeaGo/assert" 8 | "testing" 9 | ) 10 | 11 | func TestFileFlags(t *testing.T) { 12 | var a = assert.NewAssertion(t) 13 | a.IsTrue(fsutils.FlagRead&fsutils.FlagRead == fsutils.FlagRead) 14 | a.IsTrue(fsutils.FlagWrite&fsutils.FlagWrite != fsutils.FlagRead) 15 | a.IsTrue((fsutils.FlagWrite|fsutils.FlagRead)&fsutils.FlagRead == fsutils.FlagRead) 16 | } 17 | -------------------------------------------------------------------------------- /internal/utils/fs/locker_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package fsutils_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils/fs" 7 | "testing" 8 | ) 9 | 10 | func TestLocker_Lock(t *testing.T) { 11 | var path = "/tmp/file-test" 12 | var locker = fsutils.NewLocker(path) 13 | err := locker.Lock() 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | _ = locker.Release() 18 | 19 | var locker2 = fsutils.NewLocker(path) 20 | err = locker2.Lock() 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internal/utils/fs/os_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package fsutils_test 4 | 5 | import ( 6 | fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs" 7 | "github.com/iwind/TeaGo/assert" 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func TestOpenFile(t *testing.T) { 13 | f, err := fsutils.OpenFile("./os_test.go", os.O_RDONLY, 0444) 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | _ = f.Close() 18 | } 19 | 20 | func TestExistFile(t *testing.T) { 21 | var a = assert.NewAssertion(t) 22 | 23 | { 24 | b, err := fsutils.ExistFile("./os_test.go") 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | a.IsTrue(b) 29 | } 30 | 31 | { 32 | b, err := fsutils.ExistFile("./os_test2.go") 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | a.IsFalse(b) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/utils/fs/status_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package fsutils_test 4 | 5 | import ( 6 | fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestWaitLoad(t *testing.T) { 12 | fsutils.WaitLoad(100, 5, 1*time.Minute) 13 | } 14 | -------------------------------------------------------------------------------- /internal/utils/http_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/iwind/TeaGo/assert" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestNewHTTPClient(t *testing.T) { 10 | a := assert.NewAssertion(t) 11 | 12 | client := NewHTTPClient(1 * time.Second) 13 | a.IsTrue(client.Timeout == 1*time.Second) 14 | 15 | client2 := NewHTTPClient(1 * time.Second) 16 | a.IsTrue(client != client2) 17 | } 18 | 19 | func TestSharedHTTPClient(t *testing.T) { 20 | a := assert.NewAssertion(t) 21 | 22 | _ = SharedHttpClient(2 * time.Second) 23 | _ = SharedHttpClient(3 * time.Second) 24 | 25 | client := SharedHttpClient(1 * time.Second) 26 | a.IsTrue(client.Timeout == 1*time.Second) 27 | 28 | client2 := SharedHttpClient(1 * time.Second) 29 | a.IsTrue(client == client2) 30 | 31 | t.Log(timeoutClientMap) 32 | } 33 | -------------------------------------------------------------------------------- /internal/utils/jsonutils/map.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package jsonutils 4 | 5 | import ( 6 | "encoding/json" 7 | "github.com/iwind/TeaGo/maps" 8 | ) 9 | 10 | func MapToObject(m maps.Map, ptr any) error { 11 | if m == nil { 12 | return nil 13 | } 14 | mJSON, err := json.Marshal(m) 15 | if err != nil { 16 | return err 17 | } 18 | return json.Unmarshal(mJSON, ptr) 19 | } 20 | 21 | func ObjectToMap(ptr any) (maps.Map, error) { 22 | if ptr == nil { 23 | return maps.Map{}, nil 24 | } 25 | ptrJSON, err := json.Marshal(ptr) 26 | if err != nil { 27 | return nil, err 28 | } 29 | var result = maps.Map{} 30 | err = json.Unmarshal(ptrJSON, &result) 31 | if err != nil { 32 | return nil, err 33 | } 34 | return result, nil 35 | } 36 | 37 | func Copy(destPtr any, srcPtr any) error { 38 | data, err := json.Marshal(srcPtr) 39 | if err != nil { 40 | return err 41 | } 42 | err = json.Unmarshal(data, destPtr) 43 | return err 44 | } 45 | -------------------------------------------------------------------------------- /internal/utils/jsonutils/map_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package jsonutils 4 | 5 | import ( 6 | "github.com/iwind/TeaGo/assert" 7 | "github.com/iwind/TeaGo/maps" 8 | "testing" 9 | ) 10 | 11 | func TestMapToObject(t *testing.T) { 12 | a := assert.NewAssertion(t) 13 | 14 | type typeA struct { 15 | B int `json:"b"` 16 | C bool `json:"c"` 17 | } 18 | 19 | { 20 | var obj = &typeA{B: 1, C: true} 21 | m, err := ObjectToMap(obj) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | PrintT(m, t) 26 | a.IsTrue(m.GetInt("b") == 1) 27 | a.IsTrue(m.GetBool("c") == true) 28 | } 29 | 30 | { 31 | var obj = &typeA{} 32 | err := MapToObject(maps.Map{ 33 | "b": 1024, 34 | "c": true, 35 | }, obj) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | a.IsTrue(obj.B == 1024) 40 | a.IsTrue(obj.C == true) 41 | PrintT(obj, t) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /internal/utils/jsonutils/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package jsonutils 4 | 5 | import ( 6 | "bytes" 7 | "encoding/json" 8 | "testing" 9 | ) 10 | 11 | func PrintT(obj any, t *testing.T) { 12 | data, err := json.MarshalIndent(obj, "", " ") 13 | if err != nil { 14 | t.Log(err) 15 | } else { 16 | t.Log(string(data)) 17 | } 18 | } 19 | 20 | func Equal(obj1 any, obj2 any) bool { 21 | data1, err := json.Marshal(obj1) 22 | if err != nil { 23 | return false 24 | } 25 | 26 | data2, err := json.Marshal(obj2) 27 | if err != nil { 28 | return false 29 | } 30 | 31 | return bytes.Equal(data1, data2) 32 | } 33 | -------------------------------------------------------------------------------- /internal/utils/jsonutils/utils_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package jsonutils_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils/jsonutils" 7 | "github.com/iwind/TeaGo/assert" 8 | "github.com/iwind/TeaGo/maps" 9 | "testing" 10 | ) 11 | 12 | func TestEqual(t *testing.T) { 13 | var a = assert.NewAssertion(t) 14 | 15 | { 16 | var m1 = maps.Map{"a": 1, "b2": true} 17 | var m2 = maps.Map{"b2": true, "a": 1} 18 | a.IsTrue(jsonutils.Equal(m1, m2)) 19 | } 20 | 21 | { 22 | var m1 = maps.Map{"a": 1, "b2": true, "c": nil} 23 | var m2 = maps.Map{"b2": true, "a": 1} 24 | a.IsFalse(jsonutils.Equal(m1, m2)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/utils/kvstore/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package kvstore 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "github.com/cockroachdb/pebble" 9 | ) 10 | 11 | var ErrTableNotFound = errors.New("table not found") 12 | var ErrKeyTooLong = errors.New("too long key") 13 | var ErrSkip = errors.New("skip") // skip count in iterator 14 | var ErrTableClosed = errors.New("table closed") 15 | 16 | func IsNotFound(err error) bool { 17 | return err != nil && errors.Is(err, pebble.ErrNotFound) 18 | } 19 | 20 | func IsSkipError(err error) bool { 21 | return err != nil && errors.Is(err, ErrSkip) 22 | } 23 | 24 | func Skip() (bool, error) { 25 | return true, ErrSkip 26 | } 27 | 28 | func NewTableClosedErr(tableName string) error { 29 | return fmt.Errorf("table '"+tableName+"' closed: %w", ErrTableClosed) 30 | } 31 | -------------------------------------------------------------------------------- /internal/utils/kvstore/item.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package kvstore 4 | 5 | type Item[T any] struct { 6 | Key string 7 | Value T 8 | FieldKey []byte 9 | } 10 | -------------------------------------------------------------------------------- /internal/utils/kvstore/iterator_options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package kvstore 4 | 5 | import "github.com/cockroachdb/pebble" 6 | 7 | type IteratorOptions struct { 8 | LowerBound []byte 9 | UpperBound []byte 10 | } 11 | 12 | func (this *IteratorOptions) RawOptions() *pebble.IterOptions { 13 | return &pebble.IterOptions{ 14 | LowerBound: this.LowerBound, 15 | UpperBound: this.UpperBound, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /internal/utils/kvstore/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package kvstore 4 | 5 | import ( 6 | "fmt" 7 | "github.com/TeaOSLab/EdgeNode/internal/remotelogs" 8 | ) 9 | 10 | type Logger struct { 11 | } 12 | 13 | func NewLogger() *Logger { 14 | return &Logger{} 15 | } 16 | 17 | func (this *Logger) Infof(format string, args ...any) { 18 | // stub 19 | } 20 | 21 | func (this *Logger) Errorf(format string, args ...any) { 22 | remotelogs.Error("KV", fmt.Sprintf(format, args...)) 23 | } 24 | 25 | func (this *Logger) Fatalf(format string, args ...any) { 26 | remotelogs.Error("KV", fmt.Sprintf(format, args...)) 27 | } 28 | -------------------------------------------------------------------------------- /internal/utils/kvstore/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package kvstore 4 | 5 | import "github.com/cockroachdb/pebble" 6 | 7 | var DefaultWriteOptions = &pebble.WriteOptions{ 8 | Sync: false, 9 | } 10 | 11 | var DefaultWriteSyncOptions = &pebble.WriteOptions{ 12 | Sync: true, 13 | } 14 | -------------------------------------------------------------------------------- /internal/utils/kvstore/table_counter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package kvstore 4 | 5 | type CounterTable[T int64 | uint64] struct { 6 | *Table[T] 7 | } 8 | 9 | func NewCounterTable[T int64 | uint64](name string) (*CounterTable[T], error) { 10 | table, err := NewTable[T](name, NewIntValueEncoder[T]()) 11 | if err != nil { 12 | return nil, err 13 | } 14 | 15 | return &CounterTable[T]{ 16 | Table: table, 17 | }, nil 18 | } 19 | 20 | func (this *CounterTable[T]) Increase(key string, delta T) (newValue T, err error) { 21 | if this.isClosed { 22 | err = NewTableClosedErr(this.name) 23 | return 24 | } 25 | 26 | err = this.Table.WriteTx(func(tx *Tx[T]) error { 27 | value, getErr := tx.Get(key) 28 | if getErr != nil { 29 | if !IsNotFound(getErr) { 30 | return getErr 31 | } 32 | } 33 | 34 | newValue = value + delta 35 | return tx.Set(key, newValue) 36 | }) 37 | return 38 | } 39 | -------------------------------------------------------------------------------- /internal/utils/kvstore/table_field.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package kvstore 4 | 5 | import ( 6 | "errors" 7 | ) 8 | 9 | func (this *Table[T]) AddField(fieldName string) error { 10 | if !IsValidName(fieldName) { 11 | return errors.New("invalid field name '" + fieldName + "'") 12 | } 13 | 14 | // check existence 15 | for _, field := range this.fieldNames { 16 | if field == fieldName { 17 | return nil 18 | } 19 | } 20 | 21 | this.fieldNames = append(this.fieldNames, fieldName) 22 | return nil 23 | } 24 | 25 | func (this *Table[T]) AddFields(fieldName ...string) error { 26 | for _, subFieldName := range fieldName { 27 | err := this.AddField(subFieldName) 28 | if err != nil { 29 | return err 30 | } 31 | } 32 | return nil 33 | } 34 | 35 | func (this *Table[T]) DropField(fieldName string) error { 36 | this.mu.Lock() 37 | defer this.mu.Unlock() 38 | 39 | var start = this.FieldKey(fieldName + "$") 40 | return this.db.store.rawDB.DeleteRange(start, append(start, 0xFF), DefaultWriteOptions) 41 | } 42 | -------------------------------------------------------------------------------- /internal/utils/kvstore/table_interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package kvstore 4 | 5 | type TableInterface interface { 6 | Name() string 7 | SetNamespace(namespace []byte) 8 | SetDB(db *DB) 9 | Close() error 10 | } 11 | -------------------------------------------------------------------------------- /internal/utils/kvstore/utils_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package kvstore_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils/kvstore" 7 | "github.com/iwind/TeaGo/Tea" 8 | "github.com/iwind/TeaGo/assert" 9 | _ "github.com/iwind/TeaGo/bootstrap" 10 | "testing" 11 | ) 12 | 13 | func TestRemoveDB(t *testing.T) { 14 | err := kvstore.RemoveStore(Tea.Root + "/data/stores/test2.store") 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | } 19 | 20 | func TestIsValidName(t *testing.T) { 21 | var a = assert.NewAssertion(t) 22 | 23 | a.IsTrue(kvstore.IsValidName("a")) 24 | a.IsTrue(kvstore.IsValidName("aB")) 25 | a.IsTrue(kvstore.IsValidName("aBC1")) 26 | a.IsTrue(kvstore.IsValidName("aBC1._-")) 27 | a.IsFalse(kvstore.IsValidName(" aBC1._-")) 28 | a.IsFalse(kvstore.IsValidName("")) 29 | } 30 | -------------------------------------------------------------------------------- /internal/utils/kvstore/value_encoder.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package kvstore 4 | 5 | import "encoding/json" 6 | 7 | type ValueEncoder[T any] interface { 8 | Encode(value T) ([]byte, error) 9 | EncodeField(value T, fieldName string) ([]byte, error) 10 | Decode(valueBytes []byte) (value T, err error) 11 | } 12 | 13 | type BaseObjectEncoder[T any] struct { 14 | } 15 | 16 | func (this *BaseObjectEncoder[T]) Encode(value T) ([]byte, error) { 17 | return json.Marshal(value) 18 | } 19 | 20 | func (this *BaseObjectEncoder[T]) Decode(valueData []byte) (value T, err error) { 21 | err = json.Unmarshal(valueData, &value) 22 | return 23 | } 24 | -------------------------------------------------------------------------------- /internal/utils/kvstore/value_encoder_bool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package kvstore 4 | 5 | type BoolValueEncoder[T bool] struct { 6 | } 7 | 8 | func NewBoolValueEncoder[T bool]() *BoolValueEncoder[T] { 9 | return &BoolValueEncoder[T]{} 10 | } 11 | 12 | func (this *BoolValueEncoder[T]) Encode(value T) ([]byte, error) { 13 | if value { 14 | return []byte{1}, nil 15 | } 16 | return []byte{0}, nil 17 | } 18 | 19 | func (this *BoolValueEncoder[T]) EncodeField(value T, fieldName string) ([]byte, error) { 20 | _ = fieldName 21 | return this.Encode(value) 22 | } 23 | 24 | func (this *BoolValueEncoder[T]) Decode(valueData []byte) (value T, err error) { 25 | if len(valueData) == 1 { 26 | value = valueData[0] == 1 27 | } 28 | return 29 | } 30 | -------------------------------------------------------------------------------- /internal/utils/kvstore/value_encoder_bytes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package kvstore 4 | 5 | type BytesValueEncoder[T []byte] struct { 6 | } 7 | 8 | func NewBytesValueEncoder[T []byte]() *BytesValueEncoder[T] { 9 | return &BytesValueEncoder[T]{} 10 | } 11 | 12 | func (this *BytesValueEncoder[T]) Encode(value T) ([]byte, error) { 13 | if len(value) == 0 { 14 | return nil, nil 15 | } 16 | 17 | var resultValue = make([]byte, len(value)) 18 | copy(resultValue, value) 19 | return resultValue, nil 20 | } 21 | 22 | func (this *BytesValueEncoder[T]) EncodeField(value T, fieldName string) ([]byte, error) { 23 | _ = fieldName 24 | return this.Encode(value) 25 | } 26 | 27 | func (this *BytesValueEncoder[T]) Decode(valueData []byte) (value T, err error) { 28 | if len(valueData) == 0 { 29 | return 30 | } 31 | 32 | value = make([]byte, len(valueData)) 33 | copy(value, valueData) 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /internal/utils/kvstore/value_encoder_nil.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package kvstore 4 | 5 | type NilValueEncoder[T []byte] struct { 6 | } 7 | 8 | func NewNilValueEncoder[T []byte]() *NilValueEncoder[T] { 9 | return &NilValueEncoder[T]{} 10 | } 11 | 12 | func (this *NilValueEncoder[T]) Encode(value T) ([]byte, error) { 13 | return nil, nil 14 | } 15 | 16 | func (this *NilValueEncoder[T]) EncodeField(value T, fieldName string) ([]byte, error) { 17 | return nil, nil 18 | } 19 | 20 | func (this *NilValueEncoder[T]) Decode(valueData []byte) (value T, err error) { 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /internal/utils/kvstore/value_encoder_string.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package kvstore 4 | 5 | type StringValueEncoder[T string] struct { 6 | } 7 | 8 | func NewStringValueEncoder[T string]() *StringValueEncoder[T] { 9 | return &StringValueEncoder[T]{} 10 | } 11 | 12 | func (this *StringValueEncoder[T]) Encode(value T) ([]byte, error) { 13 | return []byte(value), nil 14 | } 15 | 16 | func (this *StringValueEncoder[T]) EncodeField(value T, fieldName string) ([]byte, error) { 17 | _ = fieldName 18 | return this.Encode(value) 19 | } 20 | 21 | func (this *StringValueEncoder[T]) Decode(valueData []byte) (value T, err error) { 22 | value = T(valueData) 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /internal/utils/linkedlist/item.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package linkedlist 4 | 5 | type Item[T any] struct { 6 | prev *Item[T] 7 | next *Item[T] 8 | 9 | Value T 10 | } 11 | 12 | func NewItem[T any](value T) *Item[T] { 13 | return &Item[T]{Value: value} 14 | } 15 | -------------------------------------------------------------------------------- /internal/utils/lookup.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeCommon/pkg/configutils" 5 | "github.com/miekg/dns" 6 | ) 7 | 8 | // LookupCNAME 获取CNAME 9 | func LookupCNAME(host string) (string, error) { 10 | config, err := dns.ClientConfigFromFile("/etc/resolv.conf") 11 | if err != nil { 12 | return "", err 13 | } 14 | 15 | c := new(dns.Client) 16 | m := new(dns.Msg) 17 | 18 | m.SetQuestion(host+".", dns.TypeCNAME) 19 | m.RecursionDesired = true 20 | 21 | var lastErr error 22 | for _, serverAddr := range config.Servers { 23 | r, _, err := c.Exchange(m, configutils.QuoteIP(serverAddr)+":"+config.Port) 24 | if err != nil { 25 | lastErr = err 26 | continue 27 | } 28 | if len(r.Answer) == 0 { 29 | continue 30 | } 31 | 32 | return r.Answer[0].(*dns.CNAME).Target, nil 33 | } 34 | return "", lastErr 35 | } 36 | -------------------------------------------------------------------------------- /internal/utils/lookup_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package utils 4 | 5 | import "testing" 6 | 7 | func TestLookupCNAME(t *testing.T) { 8 | t.Log(LookupCNAME("www.yun4s.cn")) 9 | } 10 | -------------------------------------------------------------------------------- /internal/utils/maps/map_fixed_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package maputils_test 4 | 5 | import ( 6 | maputils "github.com/TeaOSLab/EdgeNode/internal/utils/maps" 7 | "testing" 8 | ) 9 | 10 | func TestNewFixedMap(t *testing.T) { 11 | var m = maputils.NewFixedMap[string, int](3) 12 | m.Put("a", 1) 13 | t.Log(m.RawMap()) 14 | t.Log(m.Get("a")) 15 | t.Log(m.Get("b")) 16 | 17 | m.Put("b", 2) 18 | m.Put("c", 3) 19 | t.Log(m.RawMap(), m.Keys()) 20 | 21 | m.Put("d", 4) 22 | t.Log(m.RawMap(), m.Keys()) 23 | 24 | m.Put("b", 200) 25 | t.Log(m.RawMap(), m.Keys()) 26 | } 27 | -------------------------------------------------------------------------------- /internal/utils/mem/system_1.19.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | //go:build go1.19 3 | 4 | package memutils 5 | 6 | import ( 7 | "runtime/debug" 8 | ) 9 | 10 | // 设置软内存最大值 11 | func setMaxMemory(memoryGB int) { 12 | if memoryGB <= 0 { 13 | memoryGB = 1 14 | } 15 | 16 | var maxMemoryBytes = (int64(memoryGB) << 30) * 80 / 100 // 默认 80% 17 | debug.SetMemoryLimit(maxMemoryBytes) 18 | } 19 | -------------------------------------------------------------------------------- /internal/utils/mem/system_before_1.19.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | //go:build !go1.19 3 | 4 | package memutils 5 | 6 | // 设置软内存最大值 7 | func setMaxMemory(memoryGB int) { 8 | 9 | } -------------------------------------------------------------------------------- /internal/utils/mem/system_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package memutils_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils/mem" 7 | "testing" 8 | ) 9 | 10 | func TestSystemMemoryGB(t *testing.T) { 11 | t.Log(memutils.SystemMemoryGB()) 12 | t.Log(memutils.SystemMemoryGB()) 13 | t.Log(memutils.SystemMemoryGB()) 14 | t.Log(memutils.SystemMemoryBytes()) 15 | t.Log(memutils.SystemMemoryBytes()) 16 | t.Log(memutils.SystemMemoryBytes()>>30, "GB") 17 | t.Log("available:", memutils.AvailableMemoryGB()) 18 | } 19 | -------------------------------------------------------------------------------- /internal/utils/minifiers/minify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | //go:build !plus 3 | 4 | package minifiers 5 | 6 | import ( 7 | "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" 8 | "net/http" 9 | ) 10 | 11 | // MinifyResponse minify response body 12 | func MinifyResponse(config *serverconfigs.HTTPPageOptimizationConfig, url string, resp *http.Response) error { 13 | // stub 14 | return nil 15 | } 16 | -------------------------------------------------------------------------------- /internal/utils/net.go: -------------------------------------------------------------------------------- 1 | //go:build !freebsd 2 | // +build !freebsd 3 | 4 | package utils 5 | 6 | import ( 7 | "context" 8 | "github.com/iwind/TeaGo/logs" 9 | "net" 10 | "syscall" 11 | ) 12 | 13 | // ListenReuseAddr 监听可重用的端口 14 | func ListenReuseAddr(network string, addr string) (net.Listener, error) { 15 | config := &net.ListenConfig{ 16 | Control: func(network, address string, c syscall.RawConn) error { 17 | return c.Control(func(fd uintptr) { 18 | err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, SO_REUSEPORT, 1) 19 | if err != nil { 20 | logs.Println("[LISTEN]" + err.Error()) 21 | } 22 | }) 23 | }, 24 | KeepAlive: 0, 25 | } 26 | return config.Listen(context.Background(), network, addr) 27 | } 28 | -------------------------------------------------------------------------------- /internal/utils/net_darwin.go: -------------------------------------------------------------------------------- 1 | // +build darwin 2 | 3 | package utils 4 | 5 | import ( 6 | "syscall" 7 | ) 8 | 9 | const SO_REUSEPORT = syscall.SO_REUSEPORT 10 | -------------------------------------------------------------------------------- /internal/utils/net_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | // 可以在 /usr/include/asm-generic/socket.h 中找到 SO_REUSEPORT 值 3 | 4 | package utils 5 | 6 | const SO_REUSEPORT = 15 7 | -------------------------------------------------------------------------------- /internal/utils/net_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package utils_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils" 7 | "testing" 8 | ) 9 | 10 | func TestParseAddrHost(t *testing.T) { 11 | for _, addr := range []string{"a", "example.com", "example.com:1234", "::1", "[::1]", "[::1]:8080"} { 12 | t.Log(addr + " => " + utils.ParseAddrHost(addr)) 13 | } 14 | } 15 | 16 | func TestMergePorts(t *testing.T) { 17 | for _, ports := range [][]int{ 18 | {}, 19 | {80}, 20 | {80, 83, 85}, 21 | {80, 81, 83, 85, 86, 87, 88, 90}, 22 | {0, 0, 1, 1, 2, 2, 2, 3, 3, 3}, 23 | } { 24 | t.Log(ports, "=>", utils.MergePorts(ports)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/utils/number.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func MinInt(min1 int, min2 int) int { 4 | if min1 < min2 { 5 | return min1 6 | } 7 | return min2 8 | } 9 | 10 | func MaxInt(min1 int, min2 int) int { 11 | if min1 < min2 { 12 | return min2 13 | } 14 | return min1 15 | } 16 | -------------------------------------------------------------------------------- /internal/utils/path.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | // CleanPath 清理Path中的多余的字符 4 | func CleanPath(path string) string { 5 | l := len(path) 6 | if l == 0 { 7 | return "/" 8 | } 9 | result := []byte{'/'} 10 | isSlash := true 11 | for i := 0; i < l; i++ { 12 | if path[i] == '?' { 13 | result = append(result, path[i:]...) 14 | break 15 | } 16 | if path[i] == '\\' || path[i] == '/' { 17 | if !isSlash { 18 | isSlash = true 19 | result = append(result, '/') 20 | } 21 | } else { 22 | isSlash = false 23 | result = append(result, path[i]) 24 | } 25 | } 26 | return string(result) 27 | } 28 | -------------------------------------------------------------------------------- /internal/utils/path_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/iwind/TeaGo/assert" 5 | "testing" 6 | ) 7 | 8 | func TestCleanPath(t *testing.T) { 9 | a := assert.NewAssertion(t) 10 | 11 | a.IsTrue(CleanPath("") == "/") 12 | a.IsTrue(CleanPath("/hello/world") == "/hello/world") 13 | a.IsTrue(CleanPath("\\hello\\world") == "/hello/world") 14 | a.IsTrue(CleanPath("/\\hello\\//world") == "/hello/world") 15 | a.IsTrue(CleanPath("hello/world") == "/hello/world") 16 | a.IsTrue(CleanPath("/hello////world") == "/hello/world") 17 | } 18 | 19 | func TestCleanPath_Args(t *testing.T) { 20 | a := assert.NewAssertion(t) 21 | a.IsTrue(CleanPath("/hello/world?base=///////") == "/hello/world?base=///////") 22 | } 23 | 24 | func BenchmarkCleanPath(b *testing.B) { 25 | for i := 0; i < b.N; i++ { 26 | _ = CleanPath("/hello///world/very/long/very//long") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /internal/utils/percpu/chan.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package percpu 4 | 5 | import ( 6 | "runtime" 7 | ) 8 | 9 | type Chan[T any] struct { 10 | c chan T 11 | 12 | count int 13 | cList []chan T 14 | } 15 | 16 | func NewChan[T any](size int) *Chan[T] { 17 | var count = max(runtime.NumCPU(), runtime.GOMAXPROCS(0)) 18 | var cList []chan T 19 | for i := 0; i < count; i++ { 20 | cList = append(cList, make(chan T, size)) 21 | } 22 | 23 | return &Chan[T]{ 24 | c: make(chan T, size), 25 | count: count, 26 | cList: cList, 27 | } 28 | } 29 | 30 | func (this *Chan[T]) C() chan T { 31 | var procId = GetProcId() 32 | if procId < this.count { 33 | return this.cList[procId] 34 | } 35 | return this.c 36 | } 37 | -------------------------------------------------------------------------------- /internal/utils/percpu/chan_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package percpu_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils/percpu" 7 | "github.com/TeaOSLab/EdgeNode/internal/zero" 8 | "testing" 9 | ) 10 | 11 | func TestChan_C(t *testing.T) { 12 | var c = percpu.NewChan[zero.Zero](10) 13 | c.C() <- zero.Zero{} 14 | 15 | t.Log(<-c.C()) 16 | 17 | select { 18 | case <-c.C(): 19 | t.Fatal("should not return from here") 20 | default: 21 | t.Log("ok") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /internal/utils/percpu/proc_id.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package percpu 4 | 5 | import ( 6 | _ "unsafe" 7 | ) 8 | 9 | //go:linkname runtime_procPin runtime.procPin 10 | func runtime_procPin() int 11 | 12 | //go:linkname runtime_procUnpin runtime.procUnpin 13 | func runtime_procUnpin() int 14 | 15 | func GetProcId() int { 16 | var pid = runtime_procPin() 17 | runtime_procUnpin() 18 | return pid 19 | } 20 | -------------------------------------------------------------------------------- /internal/utils/ratelimit/bandwidth_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package ratelimit_test 4 | 5 | import ( 6 | "context" 7 | "github.com/TeaOSLab/EdgeNode/internal/utils/ratelimit" 8 | "github.com/TeaOSLab/EdgeNode/internal/utils/testutils" 9 | "testing" 10 | ) 11 | 12 | func TestBandwidth(t *testing.T) { 13 | if !testutils.IsSingleTesting() { 14 | return 15 | } 16 | 17 | var bandwidth = ratelimit.NewBandwidth(32 << 10) 18 | bandwidth.Ack(context.Background(), 123) 19 | bandwidth.Ack(context.Background(), 16 << 10) 20 | bandwidth.Ack(context.Background(), 32 << 10) 21 | } 22 | 23 | func TestBandwidth_0(t *testing.T) { 24 | var bandwidth = ratelimit.NewBandwidth(0) 25 | bandwidth.Ack(context.Background(), 123) 26 | bandwidth.Ack(context.Background(), 123456) 27 | } 28 | -------------------------------------------------------------------------------- /internal/utils/ratelimit/counter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package ratelimit_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils/ratelimit" 7 | "github.com/TeaOSLab/EdgeNode/internal/utils/testutils" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestCounter_ACK(t *testing.T) { 13 | if !testutils.IsSingleTesting() { 14 | return 15 | } 16 | 17 | var counter = ratelimit.NewCounter(10) 18 | 19 | go func() { 20 | for i := 0; i < 10; i++ { 21 | counter.Ack() 22 | } 23 | //counter.Release() 24 | t.Log("waiting", time.Now().Unix()) 25 | counter.Ack() 26 | t.Log("done", time.Now().Unix()) 27 | }() 28 | 29 | time.Sleep(1 * time.Second) 30 | counter.Close() 31 | time.Sleep(1 * time.Second) 32 | } 33 | 34 | func TestCounter_Release(t *testing.T) { 35 | var counter = ratelimit.NewCounter(10) 36 | 37 | for i := 0; i < 10; i++ { 38 | counter.Ack() 39 | } 40 | for i := 0; i < 10; i++ { 41 | counter.Release() 42 | } 43 | t.Log(counter.Len()) 44 | } 45 | -------------------------------------------------------------------------------- /internal/utils/reader_utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package utils 4 | 5 | import "io" 6 | 7 | func CopyWithFilter(writer io.Writer, reader io.Reader, buf []byte, filter func(p []byte) []byte) (written int64, err error) { 8 | for { 9 | n, err := reader.Read(buf) 10 | if n > 0 { 11 | n2, err := writer.Write(filter(buf[:n])) 12 | written += int64(n2) 13 | if err != nil { 14 | return written, err 15 | } 16 | } 17 | if err != nil { 18 | if err == io.EOF { 19 | break 20 | } 21 | return written, err 22 | } 23 | } 24 | return written, nil 25 | } 26 | -------------------------------------------------------------------------------- /internal/utils/readers/.gitignore: -------------------------------------------------------------------------------- 1 | readers_concurrent_file* -------------------------------------------------------------------------------- /internal/utils/readers/handlers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package readers 4 | -------------------------------------------------------------------------------- /internal/utils/readers/reader_base.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package readers 4 | 5 | type BaseReader struct { 6 | } 7 | -------------------------------------------------------------------------------- /internal/utils/readers/reader_bytes_counter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package readers 4 | 5 | import "io" 6 | 7 | type BytesCounterReader struct { 8 | rawReader io.Reader 9 | count int64 10 | } 11 | 12 | func NewBytesCounterReader(rawReader io.Reader) *BytesCounterReader { 13 | return &BytesCounterReader{ 14 | rawReader: rawReader, 15 | } 16 | } 17 | 18 | func (this *BytesCounterReader) Read(p []byte) (n int, err error) { 19 | n, err = this.rawReader.Read(p) 20 | this.count += int64(n) 21 | return 22 | } 23 | 24 | func (this *BytesCounterReader) TotalBytes() int64 { 25 | return this.count 26 | } 27 | -------------------------------------------------------------------------------- /internal/utils/readers/reader_closer_filter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package readers 4 | 5 | import "io" 6 | 7 | type FilterFunc = func(p []byte, readErr error) error 8 | 9 | type FilterReaderCloser struct { 10 | rawReader io.Reader 11 | filters []FilterFunc 12 | } 13 | 14 | func NewFilterReaderCloser(rawReader io.Reader) *FilterReaderCloser { 15 | return &FilterReaderCloser{ 16 | rawReader: rawReader, 17 | } 18 | } 19 | 20 | func (this *FilterReaderCloser) Add(filter FilterFunc) { 21 | this.filters = append(this.filters, filter) 22 | } 23 | 24 | func (this *FilterReaderCloser) Read(p []byte) (n int, err error) { 25 | n, err = this.rawReader.Read(p) 26 | for _, filter := range this.filters { 27 | filterErr := filter(p[:n], err) 28 | if (err == nil || err != io.EOF) && filterErr != nil { 29 | err = filterErr 30 | return 31 | } 32 | } 33 | return 34 | } 35 | 36 | func (this *FilterReaderCloser) Close() error { 37 | closer, ok := this.rawReader.(io.Closer) 38 | if ok { 39 | return closer.Close() 40 | } 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /internal/utils/readers/reader_closer_filter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package readers_test 4 | 5 | import ( 6 | "bytes" 7 | "errors" 8 | "github.com/TeaOSLab/EdgeNode/internal/utils/readers" 9 | "testing" 10 | ) 11 | 12 | func TestNewFilterReader(t *testing.T) { 13 | var reader = readers.NewFilterReaderCloser(bytes.NewBufferString("0123456789")) 14 | reader.Add(func(p []byte, err error) error { 15 | t.Log("filter1:", string(p), err) 16 | return nil 17 | }) 18 | reader.Add(func(p []byte, err error) error { 19 | t.Log("filter2:", string(p), err) 20 | if string(p) == "345" { 21 | return errors.New("end") 22 | } 23 | return nil 24 | }) 25 | reader.Add(func(p []byte, err error) error { 26 | t.Log("filter3:", string(p), err) 27 | return nil 28 | }) 29 | 30 | var buf = make([]byte, 3) 31 | for { 32 | n, err := reader.Read(buf) 33 | if n > 0 { 34 | t.Log(string(buf[:n])) 35 | } 36 | if err != nil { 37 | t.Log(err) 38 | break 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /internal/utils/readers/reader_print.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package readers 4 | 5 | import ( 6 | "io" 7 | "log" 8 | ) 9 | 10 | type PrintReader struct { 11 | rawReader io.Reader 12 | tag string 13 | } 14 | 15 | func NewPrintReader(rawReader io.Reader, tag string) io.Reader { 16 | return &PrintReader{ 17 | rawReader: rawReader, 18 | tag: tag, 19 | } 20 | } 21 | 22 | func (this *PrintReader) Read(p []byte) (n int, err error) { 23 | n, err = this.rawReader.Read(p) 24 | if n > 0 { 25 | log.Println("[" + this.tag + "]" + string(p[:n])) 26 | } 27 | return 28 | } 29 | -------------------------------------------------------------------------------- /internal/utils/readers/reader_tee.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package readers 4 | 5 | import ( 6 | "io" 7 | ) 8 | 9 | type TeeReader struct { 10 | r io.Reader 11 | w io.Writer 12 | 13 | onFail func(err error) 14 | onEOF func() 15 | } 16 | 17 | func NewTeeReader(reader io.Reader, writer io.Writer) *TeeReader { 18 | return &TeeReader{ 19 | r: reader, 20 | w: writer, 21 | } 22 | } 23 | 24 | func (this *TeeReader) Read(p []byte) (n int, err error) { 25 | n, err = this.r.Read(p) 26 | if n > 0 { 27 | _, wErr := this.w.Write(p[:n]) 28 | if err == nil && wErr != nil { 29 | err = wErr 30 | } 31 | } 32 | if err != nil { 33 | if err == io.EOF { 34 | if this.onEOF != nil { 35 | this.onEOF() 36 | } 37 | } else { 38 | if this.onFail != nil { 39 | this.onFail(err) 40 | } 41 | } 42 | } 43 | return 44 | } 45 | 46 | func (this *TeeReader) OnFail(onFail func(err error)) { 47 | this.onFail = onFail 48 | } 49 | 50 | func (this *TeeReader) OnEOF(onEOF func()) { 51 | this.onEOF = onEOF 52 | } 53 | -------------------------------------------------------------------------------- /internal/utils/rlimit_darwin.go: -------------------------------------------------------------------------------- 1 | //go:build darwin 2 | // +build darwin 3 | 4 | package utils 5 | 6 | import ( 7 | "syscall" 8 | ) 9 | 10 | // SetRLimit set resource limit 11 | func SetRLimit(limit uint64) error { 12 | var rLimit = &syscall.Rlimit{} 13 | err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rLimit) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | if rLimit.Cur < limit { 19 | rLimit.Cur = limit 20 | } 21 | if rLimit.Max < limit { 22 | rLimit.Max = limit 23 | } 24 | return syscall.Setrlimit(syscall.RLIMIT_NOFILE, rLimit) 25 | } 26 | 27 | // SetSuitableRLimit set best resource limit value 28 | func SetSuitableRLimit() error { 29 | return SetRLimit(4096 * 100) // 1M=100Files 30 | } 31 | -------------------------------------------------------------------------------- /internal/utils/rlimit_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package utils 5 | 6 | import ( 7 | "syscall" 8 | ) 9 | 10 | // SetRLimit set resource limit 11 | func SetRLimit(limit uint64) error { 12 | var rLimit = &syscall.Rlimit{} 13 | err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rLimit) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | if rLimit.Cur < limit { 19 | rLimit.Cur = limit 20 | } 21 | if rLimit.Max < limit { 22 | rLimit.Max = limit 23 | } 24 | return syscall.Setrlimit(syscall.RLIMIT_NOFILE, rLimit) 25 | } 26 | 27 | // SetSuitableRLimit set best resource limit value 28 | func SetSuitableRLimit() error { 29 | return SetRLimit(4096 * 100) // 1M=100Files 30 | } 31 | -------------------------------------------------------------------------------- /internal/utils/rlimit_others.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !darwin 2 | // +build !linux,!darwin 3 | 4 | package utils 5 | 6 | // set resource limit 7 | func SetRLimit(limit uint64) error { 8 | return nil 9 | } 10 | 11 | // set best resource limit value 12 | func SetSuitableRLimit() error { 13 | return nil 14 | } 15 | -------------------------------------------------------------------------------- /internal/utils/service_others.go: -------------------------------------------------------------------------------- 1 | // +build !linux,!windows 2 | 3 | package utils 4 | 5 | // 安装服务 6 | func (this *ServiceManager) Install(exePath string, args []string) error { 7 | return nil 8 | } 9 | 10 | // 启动服务 11 | func (this *ServiceManager) Start() error { 12 | return nil 13 | } 14 | 15 | // 删除服务 16 | func (this *ServiceManager) Uninstall() error { 17 | return nil 18 | } 19 | -------------------------------------------------------------------------------- /internal/utils/service_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | teaconst "github.com/TeaOSLab/EdgeNode/internal/const" 5 | "testing" 6 | ) 7 | 8 | func TestServiceManager_Log(t *testing.T) { 9 | manager := NewServiceManager(teaconst.ProductName, teaconst.ProductName+" Server") 10 | manager.Log("Hello, World") 11 | manager.LogError("Hello, World") 12 | } 13 | -------------------------------------------------------------------------------- /internal/utils/sync/.gitignore: -------------------------------------------------------------------------------- 1 | # experiments 2 | counter* 3 | values* -------------------------------------------------------------------------------- /internal/utils/testutils/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package testutils 4 | 5 | import ( 6 | "fmt" 7 | "math/rand" 8 | "os" 9 | ) 10 | 11 | // IsSingleTesting 判断当前测试环境是否为单个函数测试 12 | func IsSingleTesting() bool { 13 | for _, arg := range os.Args { 14 | if arg == "-test.run" { 15 | return true 16 | } 17 | } 18 | return false 19 | } 20 | 21 | // RandIP 生成一个随机IP用于测试 22 | func RandIP() string { 23 | return fmt.Sprintf("%d.%d.%d.%d", rand.Int()%255, rand.Int()%255, rand.Int()%255, rand.Int()%255) 24 | } 25 | -------------------------------------------------------------------------------- /internal/utils/ticker.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/zero" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Ticker 类似于time.Ticker,但能够真正地停止 10 | type Ticker struct { 11 | raw *time.Ticker 12 | done chan zero.Zero 13 | once sync.Once 14 | 15 | C <-chan time.Time 16 | } 17 | 18 | // NewTicker 创建新Ticker 19 | func NewTicker(duration time.Duration) *Ticker { 20 | raw := time.NewTicker(duration) 21 | return &Ticker{ 22 | raw: raw, 23 | C: raw.C, 24 | done: make(chan zero.Zero, 1), 25 | } 26 | } 27 | 28 | // Next 查找下一个Tick 29 | func (this *Ticker) Next() bool { 30 | select { 31 | case <-this.raw.C: 32 | return true 33 | case <-this.done: 34 | return false 35 | } 36 | } 37 | 38 | // Stop 停止 39 | func (this *Ticker) Stop() { 40 | this.once.Do(func() { 41 | this.raw.Stop() 42 | this.done <- zero.New() 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /internal/utils/ticker_utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/goman" 5 | "time" 6 | ) 7 | 8 | // Every 定时运行某个函数 9 | func Every(duration time.Duration, f func(ticker *Ticker)) *Ticker { 10 | ticker := NewTicker(duration) 11 | goman.New(func() { 12 | for ticker.Next() { 13 | f(ticker) 14 | } 15 | }) 16 | 17 | return ticker 18 | } 19 | -------------------------------------------------------------------------------- /internal/utils/time.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // GMTUnixTime 计算GMT时间戳 8 | func GMTUnixTime(timestamp int64) int64 { 9 | _, offset := time.Now().Zone() 10 | return timestamp - int64(offset) 11 | } 12 | 13 | // GMTTime 计算GMT时间 14 | func GMTTime(t time.Time) time.Time { 15 | _, offset := time.Now().Zone() 16 | return t.Add(-time.Duration(offset) * time.Second) 17 | } 18 | -------------------------------------------------------------------------------- /internal/utils/time_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/utils" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestGMTUnixTime(t *testing.T) { 10 | t.Log(utils.GMTUnixTime(time.Now().Unix())) 11 | } 12 | 13 | func TestGMTTime(t *testing.T) { 14 | t.Log(utils.GMTTime(time.Now())) 15 | } 16 | -------------------------------------------------------------------------------- /internal/utils/version.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/binary" 5 | "net" 6 | "strings" 7 | ) 8 | 9 | // VersionToLong 计算版本代号 10 | func VersionToLong(version string) uint32 { 11 | var countDots = strings.Count(version, ".") 12 | if countDots == 2 { 13 | version += ".0" 14 | } else if countDots == 1 { 15 | version += ".0.0" 16 | } else if countDots == 0 { 17 | version += ".0.0.0" 18 | } 19 | var ip = net.ParseIP(version) 20 | if ip == nil || ip.To4() == nil { 21 | return 0 22 | } 23 | return binary.BigEndian.Uint32(ip.To4()) 24 | } 25 | -------------------------------------------------------------------------------- /internal/utils/version_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package utils_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils" 7 | "testing" 8 | ) 9 | 10 | func TestVersionToLong(t *testing.T) { 11 | for _, v := range []string{ 12 | "", 13 | "a", 14 | "1", 15 | "1.2", 16 | "1.2.1", 17 | "1.2.1.4", 18 | "1.2.3.4.5", 19 | } { 20 | t.Log(v, "=>", utils.VersionToLong(v)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /internal/utils/workspace.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package utils 4 | 5 | import "regexp" 6 | 7 | var workspaceReg = regexp.MustCompile(`/Edge[A-Z]\w+/`) 8 | 9 | func RemoveWorkspace(path string) string { 10 | var indexes = workspaceReg.FindAllStringIndex(path, -1) 11 | if len(indexes) > 0 { 12 | return path[indexes[len(indexes)-1][0]:] 13 | } 14 | return path 15 | } 16 | -------------------------------------------------------------------------------- /internal/utils/writers/writer_bytes_counter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package writers 4 | 5 | import "io" 6 | 7 | type BytesCounterWriter struct { 8 | writer io.Writer 9 | count int64 10 | } 11 | 12 | func NewBytesCounterWriter(rawWriter io.Writer) *BytesCounterWriter { 13 | return &BytesCounterWriter{writer: rawWriter} 14 | } 15 | 16 | func (this *BytesCounterWriter) RawWriter() io.Writer { 17 | return this.writer 18 | } 19 | 20 | func (this *BytesCounterWriter) Write(p []byte) (n int, err error) { 21 | n, err = this.writer.Write(p) 22 | this.count += int64(n) 23 | return 24 | } 25 | 26 | func (this *BytesCounterWriter) Close() error { 27 | return nil 28 | } 29 | 30 | func (this *BytesCounterWriter) TotalBytes() int64 { 31 | return this.count 32 | } 33 | -------------------------------------------------------------------------------- /internal/utils/writers/writer_print.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package writers 4 | 5 | import ( 6 | "io" 7 | "log" 8 | ) 9 | 10 | type PrintWriter struct { 11 | rawWriter io.Writer 12 | tag string 13 | } 14 | 15 | func NewPrintWriter(rawWriter io.Writer, tag string) io.Writer { 16 | return &PrintWriter{ 17 | rawWriter: rawWriter, 18 | tag: tag, 19 | } 20 | } 21 | 22 | func (this *PrintWriter) Write(p []byte) (n int, err error) { 23 | n, err = this.rawWriter.Write(p) 24 | if n > 0 { 25 | log.Println("[" + this.tag + "]" + string(p[:n])) 26 | } 27 | return 28 | } 29 | -------------------------------------------------------------------------------- /internal/utils/writers/writer_rate_limit_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package writers 4 | 5 | import ( 6 | "sync" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestSleep(t *testing.T) { 12 | var count = 2000 13 | var wg = sync.WaitGroup{} 14 | wg.Add(count) 15 | var before = time.Now() 16 | for i := 0; i < count; i++ { 17 | go func() { 18 | defer wg.Done() 19 | time.Sleep(1 * time.Second) 20 | }() 21 | } 22 | wg.Wait() 23 | t.Log(time.Since(before).Seconds()*1000, "ms") 24 | } 25 | 26 | func TestTimeout(t *testing.T) { 27 | var count = 2000 28 | var wg = sync.WaitGroup{} 29 | wg.Add(count) 30 | var before = time.Now() 31 | for i := 0; i < count; i++ { 32 | go func() { 33 | defer wg.Done() 34 | 35 | var timeout = time.NewTimer(1 * time.Second) 36 | <-timeout.C 37 | }() 38 | } 39 | wg.Wait() 40 | t.Log(time.Since(before).Seconds()*1000, "ms") 41 | } 42 | -------------------------------------------------------------------------------- /internal/waf/README.md: -------------------------------------------------------------------------------- 1 | # WAF 2 | A basic WAF for TeaWeb. 3 | 4 | ## Config Constructions 5 | ~~~ 6 | WAF 7 | Inbound 8 | Rule Groups 9 | Rule Sets 10 | Rules 11 | Checkpoint Param Value 12 | Outbound 13 | Rule Groups 14 | ... 15 | ~~~ 16 | 17 | ## Apply WAF 18 | ~~~ 19 | Request --> WAF --> Backends 20 | / 21 | Response <-- WAF <---- 22 | ~~~ 23 | 24 | ## Coding 25 | ~~~go 26 | waf := teawaf.NewWAF() 27 | 28 | // add rule groups here 29 | 30 | err := waf.Init() 31 | if err != nil { 32 | return 33 | } 34 | waf.Start() 35 | 36 | // match http request 37 | // (req *http.Request, responseWriter http.ResponseWriter) 38 | goNext, ruleSet, _ := waf.MatchRequest(req, responseWriter) 39 | if ruleSet != nil { 40 | log.Println("meet rule set:", ruleSet.Name, "action:", ruleSet.Action) 41 | } 42 | if !goNext { 43 | return 44 | } 45 | 46 | // stop the waf 47 | // waf.Stop() 48 | ~~~ -------------------------------------------------------------------------------- /internal/waf/action_allow.go: -------------------------------------------------------------------------------- 1 | package waf 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "net/http" 6 | ) 7 | 8 | type AllowScope = string 9 | 10 | const ( 11 | AllowScopeGroup AllowScope = "group" 12 | AllowScopeServer AllowScope = "server" 13 | AllowScopeGlobal AllowScope = "global" 14 | ) 15 | 16 | type AllowAction struct { 17 | BaseAction 18 | 19 | Scope AllowScope `yaml:"scope" json:"scope"` 20 | } 21 | 22 | func (this *AllowAction) Init(waf *WAF) error { 23 | return nil 24 | } 25 | 26 | func (this *AllowAction) Code() string { 27 | return ActionAllow 28 | } 29 | 30 | func (this *AllowAction) IsAttack() bool { 31 | return false 32 | } 33 | 34 | func (this *AllowAction) WillChange() bool { 35 | return true 36 | } 37 | 38 | func (this *AllowAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult { 39 | // do nothing 40 | return PerformResult{ 41 | ContinueRequest: true, 42 | GoNextGroup: this.Scope == AllowScopeGroup, 43 | IsAllowed: true, 44 | AllowScope: this.Scope, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /internal/waf/action_base.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package waf 4 | 5 | type BaseAction struct { 6 | currentActionId int64 7 | } 8 | 9 | // ActionId 读取ActionId 10 | func (this *BaseAction) ActionId() int64 { 11 | return this.currentActionId 12 | } 13 | 14 | // SetActionId 设置Id 15 | func (this *BaseAction) SetActionId(actionId int64) { 16 | this.currentActionId = actionId 17 | } 18 | -------------------------------------------------------------------------------- /internal/waf/action_category.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package waf 4 | 5 | import "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" 6 | 7 | type ActionCategory = string 8 | 9 | const ( 10 | ActionCategoryAllow ActionCategory = firewallconfigs.HTTPFirewallActionCategoryAllow 11 | ActionCategoryBlock ActionCategory = firewallconfigs.HTTPFirewallActionCategoryBlock 12 | ActionCategoryVerify ActionCategory = firewallconfigs.HTTPFirewallActionCategoryVerify 13 | ) 14 | -------------------------------------------------------------------------------- /internal/waf/action_config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package waf 4 | 5 | import "github.com/iwind/TeaGo/maps" 6 | 7 | type ActionConfig struct { 8 | Code string `yaml:"code" json:"code"` 9 | Options maps.Map `yaml:"options" json:"options"` 10 | } 11 | -------------------------------------------------------------------------------- /internal/waf/action_definition.go: -------------------------------------------------------------------------------- 1 | package waf 2 | 3 | import "reflect" 4 | 5 | // ActionDefinition action definition 6 | type ActionDefinition struct { 7 | Name string 8 | Code ActionString 9 | Description string 10 | Category string // category: block, verify, allow 11 | Instance ActionInterface 12 | Type reflect.Type 13 | } 14 | -------------------------------------------------------------------------------- /internal/waf/action_instance.go: -------------------------------------------------------------------------------- 1 | package waf 2 | 3 | type Action struct { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /internal/waf/action_interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package waf 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 7 | "net/http" 8 | ) 9 | 10 | type ActionInterface interface { 11 | // Init 初始化 12 | Init(waf *WAF) error 13 | 14 | // ActionId 读取ActionId 15 | ActionId() int64 16 | 17 | // SetActionId 设置ID 18 | SetActionId(id int64) 19 | 20 | // Code 代号 21 | Code() string 22 | 23 | // IsAttack 是否为拦截攻击动作 24 | IsAttack() bool 25 | 26 | // WillChange determine if the action will change the request 27 | WillChange() bool 28 | 29 | // Perform the action 30 | Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult 31 | } 32 | -------------------------------------------------------------------------------- /internal/waf/action_log.go: -------------------------------------------------------------------------------- 1 | package waf 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "net/http" 6 | ) 7 | 8 | type LogAction struct { 9 | BaseAction 10 | } 11 | 12 | func (this *LogAction) Init(waf *WAF) error { 13 | return nil 14 | } 15 | 16 | func (this *LogAction) Code() string { 17 | return ActionLog 18 | } 19 | 20 | func (this *LogAction) IsAttack() bool { 21 | return false 22 | } 23 | 24 | func (this *LogAction) WillChange() bool { 25 | return false 26 | } 27 | 28 | func (this *LogAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult { 29 | return PerformResult{ 30 | ContinueRequest: true, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /internal/waf/action_tag.go: -------------------------------------------------------------------------------- 1 | package waf 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "net/http" 6 | ) 7 | 8 | type TagAction struct { 9 | BaseAction 10 | 11 | Tags []string `yaml:"tags" json:"tags"` 12 | } 13 | 14 | func (this *TagAction) Init(waf *WAF) error { 15 | return nil 16 | } 17 | 18 | func (this *TagAction) Code() string { 19 | return ActionTag 20 | } 21 | 22 | func (this *TagAction) IsAttack() bool { 23 | return false 24 | } 25 | 26 | func (this *TagAction) WillChange() bool { 27 | return false 28 | } 29 | 30 | func (this *TagAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult { 31 | return PerformResult{ 32 | ContinueRequest: true, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /internal/waf/allow_cookie_info_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package waf_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/utils/fasttime" 7 | "github.com/TeaOSLab/EdgeNode/internal/waf" 8 | "github.com/iwind/TeaGo/assert" 9 | "github.com/iwind/TeaGo/types" 10 | "testing" 11 | ) 12 | 13 | func TestAllowCookieInfo_Encode(t *testing.T) { 14 | var a = assert.NewAssertion(t) 15 | 16 | var info = &waf.AllowCookieInfo{ 17 | SetId: 123, 18 | ExpiresAt: fasttime.Now().Unix(), 19 | } 20 | data, err := info.Encode() 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | t.Log("encrypted: ["+types.String(len(data))+"]", data) 25 | 26 | var info2 = &waf.AllowCookieInfo{} 27 | err = info2.Decode(data) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | t.Logf("%+v", info2) 33 | a.IsTrue(info.SetId == info2.SetId) 34 | a.IsTrue(info.ExpiresAt == info2.ExpiresAt) 35 | } 36 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/checkpoint_definition.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | // CheckpointDefinition check point definition 4 | type CheckpointDefinition struct { 5 | Name string 6 | Description string 7 | Prefix string 8 | HasParams bool // has sub params 9 | Instance CheckpointInterface 10 | Priority int 11 | } 12 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/option.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | type OptionInterface interface { 4 | Type() string 5 | } 6 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/option_field.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | // attach option 4 | type FieldOption struct { 5 | Name string 6 | Code string 7 | Value string // default value 8 | IsRequired bool 9 | Size int 10 | Comment string 11 | Placeholder string 12 | RightLabel string 13 | MaxLength int 14 | Validate func(value string) (ok bool, message string) 15 | } 16 | 17 | func NewFieldOption(name string, code string) *FieldOption { 18 | return &FieldOption{ 19 | Name: name, 20 | Code: code, 21 | } 22 | } 23 | 24 | func (this *FieldOption) Type() string { 25 | return "field" 26 | } 27 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/option_options.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import "github.com/iwind/TeaGo/maps" 4 | 5 | type OptionsOption struct { 6 | Name string 7 | Code string 8 | Value string // default value 9 | IsRequired bool 10 | Size int 11 | Comment string 12 | RightLabel string 13 | Validate func(value string) (ok bool, message string) 14 | Options []maps.Map 15 | } 16 | 17 | func NewOptionsOption(name string, code string) *OptionsOption { 18 | return &OptionsOption{ 19 | Name: name, 20 | Code: code, 21 | } 22 | } 23 | 24 | func (this *OptionsOption) Type() string { 25 | return "options" 26 | } 27 | 28 | func (this *OptionsOption) SetOptions(options []maps.Map) { 29 | this.Options = options 30 | } 31 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/param_option.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | type KeyValue struct { 4 | Name string `json:"name"` 5 | Value string `json:"value"` 6 | } 7 | 8 | type ParamOptions struct { 9 | Options []*KeyValue `json:"options"` 10 | } 11 | 12 | func NewParamOptions() *ParamOptions { 13 | return &ParamOptions{} 14 | } 15 | 16 | func (this *ParamOptions) AddParam(name string, value string) { 17 | this.Options = append(this.Options, &KeyValue{ 18 | Name: name, 19 | Value: value, 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_arg.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | type RequestArgCheckpoint struct { 10 | Checkpoint 11 | } 12 | 13 | func (this *RequestArgCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 14 | return req.WAFRaw().URL.Query().Get(param), hasRequestBody, nil, nil 15 | } 16 | 17 | func (this *RequestArgCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 18 | if this.IsRequest() { 19 | return this.RequestValue(req, param, options, ruleId) 20 | } 21 | return 22 | } 23 | 24 | func (this *RequestArgCheckpoint) CacheLife() utils.CacheLife { 25 | return utils.CacheMiddleLife 26 | } 27 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_arg_test.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | func TestArgParam_RequestValue(t *testing.T) { 10 | rawReq, err := http.NewRequest(http.MethodGet, "http://teaos.cn/?name=lu", nil) 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | 15 | req := requests.NewTestRequest(rawReq) 16 | 17 | checkpoint := new(RequestArgCheckpoint) 18 | t.Log(checkpoint.RequestValue(req, "name", nil, 1)) 19 | t.Log(checkpoint.ResponseValue(req, nil, "name", nil, 1)) 20 | t.Log(checkpoint.RequestValue(req, "name2", nil, 1)) 21 | } 22 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_args.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | type RequestArgsCheckpoint struct { 10 | Checkpoint 11 | } 12 | 13 | func (this *RequestArgsCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 14 | value = req.WAFRaw().URL.RawQuery 15 | return 16 | } 17 | 18 | func (this *RequestArgsCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 19 | if this.IsRequest() { 20 | return this.RequestValue(req, param, options, ruleId) 21 | } 22 | return 23 | } 24 | 25 | func (this *RequestArgsCheckpoint) CacheLife() utils.CacheLife { 26 | return utils.CacheMiddleLife 27 | } 28 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_cname.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | type RequestCNAMECheckpoint struct { 10 | Checkpoint 11 | } 12 | 13 | func (this *RequestCNAMECheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 14 | value = req.Format("${cname}") 15 | return 16 | } 17 | 18 | func (this *RequestCNAMECheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 19 | if this.IsRequest() { 20 | return this.RequestValue(req, param, options, ruleId) 21 | } 22 | return 23 | } 24 | 25 | func (this *RequestCNAMECheckpoint) CacheLife() utils.CacheLife { 26 | return utils.CacheLongLife 27 | } 28 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_content_type.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | type RequestContentTypeCheckpoint struct { 10 | Checkpoint 11 | } 12 | 13 | func (this *RequestContentTypeCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 14 | value = req.WAFRaw().Header.Get("Content-Type") 15 | return 16 | } 17 | 18 | func (this *RequestContentTypeCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 19 | if this.IsRequest() { 20 | return this.RequestValue(req, param, options, ruleId) 21 | } 22 | return 23 | } 24 | 25 | func (this *RequestContentTypeCheckpoint) CacheLife() utils.CacheLife { 26 | return utils.CacheLongLife 27 | } 28 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_cookie.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | type RequestCookieCheckpoint struct { 10 | Checkpoint 11 | } 12 | 13 | func (this *RequestCookieCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 14 | cookie, err := req.WAFRaw().Cookie(param) 15 | if err != nil { 16 | value = "" 17 | return 18 | } 19 | 20 | value = cookie.Value 21 | return 22 | } 23 | 24 | func (this *RequestCookieCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 25 | if this.IsRequest() { 26 | return this.RequestValue(req, param, options, ruleId) 27 | } 28 | return 29 | } 30 | 31 | func (this *RequestCookieCheckpoint) CacheLife() utils.CacheLife { 32 | return utils.CacheMiddleLife 33 | } 34 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_form_arg_test.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "bytes" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 6 | "io" 7 | "net/http" 8 | "net/url" 9 | "testing" 10 | ) 11 | 12 | func TestRequestFormArgCheckpoint_RequestValue(t *testing.T) { 13 | rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte("name=lu&age=20&encoded="+url.QueryEscape("ENCODED STRING")))) 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | 18 | req := requests.NewTestRequest(rawReq) 19 | req.WAFRaw().Header.Set("Content-Type", "application/x-www-form-urlencoded") 20 | 21 | checkpoint := new(RequestFormArgCheckpoint) 22 | t.Log(checkpoint.RequestValue(req, "name", nil, 1)) 23 | t.Log(checkpoint.RequestValue(req, "age", nil, 1)) 24 | t.Log(checkpoint.RequestValue(req, "Hello", nil, 1)) 25 | t.Log(checkpoint.RequestValue(req, "encoded", nil, 1)) 26 | 27 | body, err := io.ReadAll(req.WAFRaw().Body) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | t.Log(string(body)) 32 | } 33 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_header.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | "strings" 8 | ) 9 | 10 | type RequestHeaderCheckpoint struct { 11 | Checkpoint 12 | } 13 | 14 | func (this *RequestHeaderCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 15 | v, found := req.WAFRaw().Header[param] 16 | if !found { 17 | value = "" 18 | return 19 | } 20 | value = strings.Join(v, ";") 21 | return 22 | } 23 | 24 | func (this *RequestHeaderCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 25 | if this.IsRequest() { 26 | return this.RequestValue(req, param, options, ruleId) 27 | } 28 | return 29 | } 30 | 31 | func (this *RequestHeaderCheckpoint) CacheLife() utils.CacheLife { 32 | return utils.CacheMiddleLife 33 | } 34 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_header_names_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package checkpoints_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/waf/checkpoints" 7 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 8 | "net/http" 9 | "testing" 10 | ) 11 | 12 | func TestRequestHeaderNamesCheckpoint_RequestValue(t *testing.T) { 13 | var checkpoint = &checkpoints.RequestHeaderNamesCheckpoint{} 14 | rawReq, err := http.NewRequest(http.MethodGet, "https://example.com", nil) 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | rawReq.Header.Set("Accept", "text/html") 19 | rawReq.Header.Set("User-Agent", "Chrome") 20 | rawReq.Header.Set("Accept-Encoding", "br, gzip") 21 | var req = requests.NewTestRequest(rawReq) 22 | t.Log(checkpoint.RequestValue(req, "", nil, 0)) 23 | } 24 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_headers_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package checkpoints 4 | 5 | import ( 6 | "net/http" 7 | "runtime" 8 | "sort" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | func BenchmarkRequestHeadersCheckpoint_RequestValue(b *testing.B) { 14 | runtime.GOMAXPROCS(1) 15 | 16 | var header = http.Header{ 17 | "Content-Type": []string{"keep-alive"}, 18 | "User-Agent": []string{"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"}, 19 | "Accept-Encoding": []string{"gzip, deflate, br"}, 20 | "Referer": []string{"https://goedge.cn/"}, 21 | } 22 | 23 | for i := 0; i < b.N; i++ { 24 | var headers = []string{} 25 | for k, v := range header { 26 | for _, subV := range v { 27 | headers = append(headers, k+": "+subV) 28 | } 29 | } 30 | sort.Strings(headers) 31 | _ = strings.Join(headers, "\n") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_host.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | type RequestHostCheckpoint struct { 10 | Checkpoint 11 | } 12 | 13 | func (this *RequestHostCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 14 | value = req.WAFRaw().Host 15 | return 16 | } 17 | 18 | func (this *RequestHostCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 19 | if this.IsRequest() { 20 | return this.RequestValue(req, param, options, ruleId) 21 | } 22 | return 23 | } 24 | 25 | func (this *RequestHostCheckpoint) CacheLife() utils.CacheLife { 26 | return utils.CacheLongLife 27 | } 28 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_host_test.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | func TestRequestHostCheckpoint_RequestValue(t *testing.T) { 10 | rawReq, err := http.NewRequest(http.MethodGet, "https://teaos.cn/?name=lu", nil) 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | 15 | req := requests.NewTestRequest(rawReq) 16 | req.WAFRaw().Header.Set("Host", "cloud.teaos.cn") 17 | 18 | checkpoint := new(RequestHostCheckpoint) 19 | t.Log(checkpoint.RequestValue(req, "", nil, 1)) 20 | } 21 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_is_cname.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | type RequestIsCNAMECheckpoint struct { 10 | Checkpoint 11 | } 12 | 13 | func (this *RequestIsCNAMECheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 14 | if req.Format("${cname}") == req.Format("${host}") { 15 | value = 1 16 | } else { 17 | value = 0 18 | } 19 | return 20 | } 21 | 22 | func (this *RequestIsCNAMECheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 23 | if this.IsRequest() { 24 | return this.RequestValue(req, param, options, ruleId) 25 | } 26 | return 27 | } 28 | 29 | func (this *RequestIsCNAMECheckpoint) CacheLife() utils.CacheLife { 30 | return utils.CacheLongLife 31 | } 32 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_isp_name.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package checkpoints 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 7 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 8 | "github.com/iwind/TeaGo/maps" 9 | ) 10 | 11 | type RequestISPNameCheckpoint struct { 12 | Checkpoint 13 | } 14 | 15 | func (this *RequestISPNameCheckpoint) IsComposed() bool { 16 | return false 17 | } 18 | 19 | func (this *RequestISPNameCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 20 | value = req.Format("${isp.name}") 21 | return 22 | } 23 | 24 | func (this *RequestISPNameCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 25 | return this.RequestValue(req, param, options, ruleId) 26 | } 27 | 28 | func (this *RequestISPNameCheckpoint) CacheLife() utils.CacheLife { 29 | return utils.CacheLongLife 30 | } 31 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_length.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | type RequestLengthCheckpoint struct { 10 | Checkpoint 11 | } 12 | 13 | func (this *RequestLengthCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 14 | value = req.WAFRaw().ContentLength 15 | return 16 | } 17 | 18 | func (this *RequestLengthCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 19 | if this.IsRequest() { 20 | return this.RequestValue(req, param, options, ruleId) 21 | } 22 | return 23 | } 24 | 25 | func (this *RequestLengthCheckpoint) CacheLife() utils.CacheLife { 26 | return utils.CacheShortLife 27 | } 28 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_method.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | type RequestMethodCheckpoint struct { 10 | Checkpoint 11 | } 12 | 13 | func (this *RequestMethodCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 14 | value = req.WAFRaw().Method 15 | return 16 | } 17 | 18 | func (this *RequestMethodCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 19 | if this.IsRequest() { 20 | return this.RequestValue(req, param, options, ruleId) 21 | } 22 | return 23 | } 24 | 25 | func (this *RequestMethodCheckpoint) CacheLife() utils.CacheLife { 26 | return utils.CacheLongLife 27 | } 28 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_path.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | type RequestPathCheckpoint struct { 10 | Checkpoint 11 | } 12 | 13 | func (this *RequestPathCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 14 | return req.WAFRaw().URL.Path, false, nil, nil 15 | } 16 | 17 | func (this *RequestPathCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 18 | if this.IsRequest() { 19 | return this.RequestValue(req, param, options, ruleId) 20 | } 21 | return 22 | } 23 | 24 | func (this *RequestPathCheckpoint) CacheLife() utils.CacheLife { 25 | return utils.CacheMiddleLife 26 | } 27 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_path_test.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | func TestRequestPathCheckpoint_RequestValue(t *testing.T) { 10 | rawReq, err := http.NewRequest(http.MethodGet, "http://teaos.cn/index?name=lu", nil) 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | 15 | req := requests.NewTestRequest(rawReq) 16 | checkpoint := new(RequestPathCheckpoint) 17 | t.Log(checkpoint.RequestValue(req, "", nil, 1)) 18 | } 19 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_proto.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | type RequestProtoCheckpoint struct { 10 | Checkpoint 11 | } 12 | 13 | func (this *RequestProtoCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 14 | value = req.WAFRaw().Proto 15 | return 16 | } 17 | 18 | func (this *RequestProtoCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 19 | if this.IsRequest() { 20 | return this.RequestValue(req, param, options, ruleId) 21 | } 22 | return 23 | } 24 | 25 | func (this *RequestProtoCheckpoint) CacheLife() utils.CacheLife { 26 | return utils.CacheLongLife 27 | } 28 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_referer.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | type RequestRefererCheckpoint struct { 10 | Checkpoint 11 | } 12 | 13 | func (this *RequestRefererCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 14 | value = req.WAFRaw().Referer() 15 | return 16 | } 17 | 18 | func (this *RequestRefererCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 19 | if this.IsRequest() { 20 | return this.RequestValue(req, param, options, ruleId) 21 | } 22 | return 23 | } 24 | 25 | func (this *RequestRefererCheckpoint) CacheLife() utils.CacheLife { 26 | return utils.CacheMiddleLife 27 | } 28 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_referer_origin_test.go: -------------------------------------------------------------------------------- 1 | package checkpoints_test 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/checkpoints" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 6 | "net/http" 7 | "testing" 8 | ) 9 | 10 | func TestRequestRefererOriginCheckpoint_RequestValue(t *testing.T) { 11 | rawReq, err := http.NewRequest(http.MethodGet, "https://example.com", nil) 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | var req = requests.NewTestRequest(rawReq) 16 | 17 | var checkpoint = &checkpoints.RequestRefererOriginCheckpoint{} 18 | 19 | { 20 | t.Log(checkpoint.RequestValue(req, "", nil, 0)) 21 | } 22 | 23 | { 24 | rawReq.Header.Set("Referer", "https://example.com/hello.yaml") 25 | t.Log(checkpoint.RequestValue(req, "", nil, 0)) 26 | } 27 | 28 | { 29 | rawReq.Header.Set("Origin", "https://example.com/world.yaml") 30 | t.Log(checkpoint.RequestValue(req, "", nil, 0)) 31 | } 32 | 33 | { 34 | rawReq.Header.Del("Referer") 35 | rawReq.Header.Set("Origin", "https://example.com/world.yaml") 36 | t.Log(checkpoint.RequestValue(req, "", nil, 0)) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_remote_addr.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | type RequestRemoteAddrCheckpoint struct { 10 | Checkpoint 11 | } 12 | 13 | func (this *RequestRemoteAddrCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 14 | value = req.WAFRemoteIP() 15 | return 16 | } 17 | 18 | func (this *RequestRemoteAddrCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 19 | if this.IsRequest() { 20 | return this.RequestValue(req, param, options, ruleId) 21 | } 22 | return 23 | } 24 | 25 | func (this *RequestRemoteAddrCheckpoint) CacheLife() utils.CacheLife { 26 | return utils.CacheShortLife 27 | } 28 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_remote_user.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | type RequestRemoteUserCheckpoint struct { 10 | Checkpoint 11 | } 12 | 13 | func (this *RequestRemoteUserCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 14 | username, _, ok := req.WAFRaw().BasicAuth() 15 | if !ok { 16 | value = "" 17 | return 18 | } 19 | value = username 20 | return 21 | } 22 | 23 | func (this *RequestRemoteUserCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 24 | if this.IsRequest() { 25 | return this.RequestValue(req, param, options, ruleId) 26 | } 27 | return 28 | } 29 | 30 | func (this *RequestRemoteUserCheckpoint) CacheLife() utils.CacheLife { 31 | return utils.CacheMiddleLife 32 | } 33 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_scheme.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | type RequestSchemeCheckpoint struct { 10 | Checkpoint 11 | } 12 | 13 | func (this *RequestSchemeCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 14 | value = req.Format("${scheme}") 15 | return 16 | } 17 | 18 | func (this *RequestSchemeCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 19 | if this.IsRequest() { 20 | return this.RequestValue(req, param, options, ruleId) 21 | } 22 | return 23 | } 24 | 25 | func (this *RequestSchemeCheckpoint) CacheLife() utils.CacheLife { 26 | return utils.CacheLongLife 27 | } 28 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_scheme_test.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | func TestRequestSchemeCheckpoint_RequestValue(t *testing.T) { 10 | rawReq, err := http.NewRequest(http.MethodGet, "https://teaos.cn/?name=lu", nil) 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | 15 | req := requests.NewTestRequest(rawReq) 16 | checkpoint := new(RequestSchemeCheckpoint) 17 | t.Log(checkpoint.RequestValue(req, "", nil, 1)) 18 | } 19 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_uri.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | type RequestURICheckpoint struct { 10 | Checkpoint 11 | } 12 | 13 | func (this *RequestURICheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 14 | if len(req.WAFRaw().RequestURI) > 0 { 15 | value = req.WAFRaw().RequestURI 16 | } else if req.WAFRaw().URL != nil { 17 | value = req.WAFRaw().URL.RequestURI() 18 | } 19 | return 20 | } 21 | 22 | func (this *RequestURICheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 23 | if this.IsRequest() { 24 | return this.RequestValue(req, param, options, ruleId) 25 | } 26 | return 27 | } 28 | 29 | func (this *RequestURICheckpoint) CacheLife() utils.CacheLife { 30 | return utils.CacheShortLife 31 | } 32 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_url.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | type RequestURLCheckpoint struct { 10 | Checkpoint 11 | } 12 | 13 | func (this *RequestURLCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 14 | return req.Format("${requestURL}"), hasRequestBody, nil, nil 15 | } 16 | 17 | func (this *RequestURLCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 18 | if this.IsRequest() { 19 | return this.RequestValue(req, param, options, ruleId) 20 | } 21 | return 22 | } 23 | 24 | func (this *RequestURLCheckpoint) CacheLife() utils.CacheLife { 25 | return utils.CacheShortLife 26 | } 27 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/request_user_agent.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | type RequestUserAgentCheckpoint struct { 10 | Checkpoint 11 | } 12 | 13 | func (this *RequestUserAgentCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 14 | value = req.WAFRaw().UserAgent() 15 | return 16 | } 17 | 18 | func (this *RequestUserAgentCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 19 | if this.IsRequest() { 20 | return this.RequestValue(req, param, options, ruleId) 21 | } 22 | return 23 | } 24 | 25 | func (this *RequestUserAgentCheckpoint) CacheLife() utils.CacheLife { 26 | return utils.CacheShortLife 27 | } 28 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/response_body_test.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "bytes" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 6 | "io" 7 | "net/http" 8 | "testing" 9 | ) 10 | 11 | func TestResponseBodyCheckpoint_ResponseValue(t *testing.T) { 12 | resp := requests.NewResponse(new(http.Response)) 13 | resp.StatusCode = 200 14 | resp.Header = http.Header{} 15 | resp.Header.Set("Hello", "World") 16 | resp.Body = io.NopCloser(bytes.NewBuffer([]byte("Hello, World"))) 17 | 18 | checkpoint := new(ResponseBodyCheckpoint) 19 | t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1)) 20 | t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1)) 21 | t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1)) 22 | t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1)) 23 | 24 | data, err := io.ReadAll(resp.Body) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | t.Log("after read:", string(data)) 29 | } 30 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/response_bytes_sent.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | // ResponseBytesSentCheckpoint ${bytesSent} 10 | type ResponseBytesSentCheckpoint struct { 11 | Checkpoint 12 | } 13 | 14 | func (this *ResponseBytesSentCheckpoint) IsRequest() bool { 15 | return false 16 | } 17 | 18 | func (this *ResponseBytesSentCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 19 | value = 0 20 | return 21 | } 22 | 23 | func (this *ResponseBytesSentCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 24 | value = 0 25 | if resp != nil { 26 | value = resp.ContentLength 27 | } 28 | return 29 | } 30 | 31 | func (this *ResponseBytesSentCheckpoint) CacheLife() utils.CacheLife { 32 | return utils.CacheShortLife 33 | } 34 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/response_header_test.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | func TestResponseHeaderCheckpoint_ResponseValue(t *testing.T) { 10 | resp := requests.NewResponse(new(http.Response)) 11 | resp.StatusCode = 200 12 | resp.Header = http.Header{} 13 | resp.Header.Set("Hello", "World") 14 | 15 | checkpoint := new(ResponseHeaderCheckpoint) 16 | t.Log(checkpoint.ResponseValue(nil, resp, "Hello", nil, 1)) 17 | } 18 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/response_status.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | // ResponseStatusCheckpoint ${bytesSent} 10 | type ResponseStatusCheckpoint struct { 11 | Checkpoint 12 | } 13 | 14 | func (this *ResponseStatusCheckpoint) IsRequest() bool { 15 | return false 16 | } 17 | 18 | func (this *ResponseStatusCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 19 | value = 0 20 | return 21 | } 22 | 23 | func (this *ResponseStatusCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 24 | if resp != nil { 25 | value = resp.StatusCode 26 | } 27 | return 28 | } 29 | 30 | func (this *ResponseStatusCheckpoint) CacheLife() utils.CacheLife { 31 | return utils.CacheLongLife 32 | } 33 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/response_status_test.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | func TestResponseStatusCheckpoint_ResponseValue(t *testing.T) { 10 | resp := requests.NewResponse(new(http.Response)) 11 | resp.StatusCode = 200 12 | 13 | checkpoint := new(ResponseStatusCheckpoint) 14 | t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1)) 15 | } 16 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/sample_request.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | // SampleRequestCheckpoint just a sample checkpoint, copy and change it for your new checkpoint 10 | type SampleRequestCheckpoint struct { 11 | Checkpoint 12 | } 13 | 14 | func (this *SampleRequestCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 15 | return 16 | } 17 | 18 | func (this *SampleRequestCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 19 | if this.IsRequest() { 20 | return this.RequestValue(req, param, options, ruleId) 21 | } 22 | return 23 | } 24 | 25 | func (this *SampleRequestCheckpoint) CacheLife() utils.CacheLife { 26 | return utils.CacheMiddleLife 27 | } 28 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/sample_response.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeNode/internal/waf/requests" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | "github.com/iwind/TeaGo/maps" 7 | ) 8 | 9 | // SampleResponseCheckpoint just a sample checkpoint, copy and change it for your new checkpoint 10 | type SampleResponseCheckpoint struct { 11 | Checkpoint 12 | } 13 | 14 | func (this *SampleResponseCheckpoint) IsRequest() bool { 15 | return false 16 | } 17 | 18 | func (this *SampleResponseCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, sysErr error, userErr error) { 19 | return 20 | } 21 | 22 | func (this *SampleResponseCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) { 23 | return 24 | } 25 | 26 | func (this *SampleResponseCheckpoint) CacheLife() utils.CacheLife { 27 | return utils.CacheMiddleLife 28 | } 29 | -------------------------------------------------------------------------------- /internal/waf/checkpoints/utils_test.go: -------------------------------------------------------------------------------- 1 | package checkpoints 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestFindCheckpointDefinition_Markdown(t *testing.T) { 10 | result := []string{} 11 | for _, def := range AllCheckpoints { 12 | row := "## " + def.Name + "\n* 前缀:`${" + def.Prefix + "}`\n* 描述:" + def.Description 13 | if def.HasParams { 14 | row += "\n* 是否有子参数:YES" 15 | 16 | paramOptions := def.Instance.ParamOptions() 17 | if paramOptions != nil && len(paramOptions.Options) > 0 { 18 | row += "\n* 可选子参数" 19 | for _, option := range paramOptions.Options { 20 | row += "\n * `" + option.Name + "`:值为 `" + option.Value + "`" 21 | } 22 | } 23 | } else { 24 | row += "\n* 是否有子参数:NO" 25 | } 26 | row += "\n" 27 | result = append(result, row) 28 | } 29 | 30 | fmt.Print(strings.Join(result, "\n") + "\n") 31 | } 32 | -------------------------------------------------------------------------------- /internal/waf/injectionutils/libinjection/README.md: -------------------------------------------------------------------------------- 1 | copy from https://github.com/libinjection/libinjection -------------------------------------------------------------------------------- /internal/waf/injectionutils/libinjection/src/libinjection_xss.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBINJECTION_XSS 2 | #define LIBINJECTION_XSS 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | /** 9 | * HEY THIS ISN'T DONE 10 | */ 11 | 12 | /* pull in size_t */ 13 | 14 | #include 15 | 16 | int libinjection_is_xss(const char* s, size_t len, int flags, int strictMode); 17 | 18 | #ifdef __cplusplus 19 | } 20 | #endif 21 | #endif 22 | -------------------------------------------------------------------------------- /internal/waf/injectionutils/libinjection_sqli.c: -------------------------------------------------------------------------------- 1 | #define LIBINJECTION_VERSION "3.9.1" 2 | 3 | #include "libinjection/src/libinjection_sqli.c" -------------------------------------------------------------------------------- /internal/waf/injectionutils/libinjection_xss.c: -------------------------------------------------------------------------------- 1 | #define LIBINJECTION_VERSION "3.9.1" 2 | 3 | #include "libinjection/src/libinjection_xss.c" 4 | #include "libinjection/src/libinjection_html5.c" 5 | 6 | #define GOEDGE_VERSION "25" // last version is for GoEdge change -------------------------------------------------------------------------------- /internal/waf/ip_lists_deleted.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package waf 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/zero" 7 | "sync" 8 | ) 9 | 10 | var deletedIPListIdMap = map[int64]zero.Zero{} // listId => Zero 11 | var deletedIPListLocker = sync.RWMutex{} 12 | 13 | // AddDeletedIPList add deleted ip list 14 | func AddDeletedIPList(ipListId int64) { 15 | if ipListId <= 0 { 16 | return 17 | } 18 | 19 | deletedIPListLocker.Lock() 20 | deletedIPListIdMap[ipListId] = zero.Zero{} 21 | deletedIPListLocker.Unlock() 22 | } 23 | 24 | // ExistDeletedIPList check if ip list has been deleted 25 | func ExistDeletedIPList(ipListId int64) bool { 26 | deletedIPListLocker.RLock() 27 | _, ok := deletedIPListIdMap[ipListId] 28 | deletedIPListLocker.RUnlock() 29 | return ok 30 | } 31 | -------------------------------------------------------------------------------- /internal/waf/param_filter.go: -------------------------------------------------------------------------------- 1 | package waf 2 | 3 | import "github.com/iwind/TeaGo/maps" 4 | 5 | type ParamFilter struct { 6 | Code string `yaml:"code" json:"code"` 7 | Options maps.Map `yaml:"options" json:"options"` 8 | } 9 | -------------------------------------------------------------------------------- /internal/waf/requests/response.go: -------------------------------------------------------------------------------- 1 | package requests 2 | 3 | import "net/http" 4 | 5 | type Response struct { 6 | *http.Response 7 | 8 | BodyData []byte 9 | } 10 | 11 | func NewResponse(resp *http.Response) *Response { 12 | return &Response{ 13 | Response: resp, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /internal/waf/results.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package waf 4 | 5 | // PerformResult action performing result 6 | type PerformResult struct { 7 | ContinueRequest bool 8 | GoNextGroup bool 9 | GoNextSet bool 10 | IsAllowed bool 11 | AllowScope AllowScope 12 | } 13 | 14 | // MatchResult request match result 15 | type MatchResult struct { 16 | GoNext bool 17 | HasRequestBody bool 18 | Group *RuleGroup 19 | Set *RuleSet 20 | IsAllowed bool 21 | AllowScope AllowScope 22 | } 23 | -------------------------------------------------------------------------------- /internal/waf/template.go: -------------------------------------------------------------------------------- 1 | package waf 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf/utils" 6 | ) 7 | 8 | func Template() (*WAF, error) { 9 | var config = firewallconfigs.HTTPFirewallTemplate() 10 | if config.Inbound != nil { 11 | config.Inbound.IsOn = true 12 | } 13 | 14 | for _, group := range config.AllRuleGroups() { 15 | if group.Code == "cc" || group.Code == "cc2" { 16 | continue 17 | } 18 | group.IsOn = true 19 | 20 | for _, set := range group.Sets { 21 | set.IsOn = true 22 | } 23 | } 24 | 25 | instance, err := SharedWAFManager.ConvertWAF(config) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | for _, group := range instance.Inbound { 31 | for _, set := range group.RuleSets { 32 | for _, rule := range set.Rules { 33 | rule.cacheLife = utils.CacheDisabled // for performance test 34 | _ = rule 35 | } 36 | } 37 | } 38 | 39 | return instance, nil 40 | } 41 | -------------------------------------------------------------------------------- /internal/waf/values/number_list_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package values_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/waf/values" 7 | "github.com/iwind/TeaGo/assert" 8 | "testing" 9 | ) 10 | 11 | func TestParseNumberList(t *testing.T) { 12 | var a = assert.NewAssertion(t) 13 | 14 | { 15 | var list = values.ParseNumberList("") 16 | a.IsFalse(list.Contains(123)) 17 | } 18 | 19 | { 20 | var list = values.ParseNumberList(`123 21 | 456 22 | 23 | 789.1234`) 24 | a.IsTrue(list.Contains(123)) 25 | a.IsFalse(list.Contains(0)) 26 | a.IsFalse(list.Contains(789.123)) 27 | a.IsTrue(list.Contains(789.1234)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /internal/waf/values/string_list_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 | 3 | package values_test 4 | 5 | import ( 6 | "github.com/TeaOSLab/EdgeNode/internal/waf/values" 7 | "github.com/iwind/TeaGo/assert" 8 | "testing" 9 | ) 10 | 11 | func TestParseStringList(t *testing.T) { 12 | var a = assert.NewAssertion(t) 13 | 14 | { 15 | var list = values.ParseStringList("", false) 16 | a.IsFalse(list.Contains("hello")) 17 | } 18 | 19 | { 20 | var list = values.ParseStringList(`hello 21 | 22 | world 23 | hi 24 | 25 | people`, false) 26 | a.IsTrue(list.Contains("hello")) 27 | a.IsFalse(list.Contains("hello1")) 28 | a.IsFalse(list.Contains("Hello")) 29 | a.IsTrue(list.Contains("hi")) 30 | } 31 | { 32 | var list = values.ParseStringList(`Hello 33 | 34 | world 35 | hi 36 | 37 | people`, true) 38 | a.IsTrue(list.Contains("hello")) 39 | a.IsTrue(list.Contains("Hello")) 40 | a.IsTrue(list.Contains("HELLO")) 41 | a.IsFalse(list.Contains("How")) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /internal/waf/waf_manager_test.go: -------------------------------------------------------------------------------- 1 | package waf_test 2 | 3 | import ( 4 | "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" 5 | "github.com/TeaOSLab/EdgeNode/internal/waf" 6 | "github.com/iwind/TeaGo/logs" 7 | "testing" 8 | ) 9 | 10 | func TestWAFManager_convert(t *testing.T) { 11 | p := &firewallconfigs.HTTPFirewallPolicy{ 12 | Id: 1, 13 | IsOn: true, 14 | Inbound: &firewallconfigs.HTTPFirewallInboundConfig{ 15 | IsOn: true, 16 | Groups: []*firewallconfigs.HTTPFirewallRuleGroup{ 17 | { 18 | Id: 1, 19 | Sets: []*firewallconfigs.HTTPFirewallRuleSet{ 20 | { 21 | Id: 1, 22 | }, 23 | { 24 | Id: 2, 25 | Rules: []*firewallconfigs.HTTPFirewallRule{ 26 | { 27 | Id: 1, 28 | }, 29 | { 30 | Id: 2, 31 | }, 32 | }, 33 | }, 34 | }, 35 | }, 36 | }, 37 | }, 38 | } 39 | w, err := waf.SharedWAFManager.ConvertWAF(p) 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | 44 | logs.PrintAsJSON(w, t) 45 | } 46 | -------------------------------------------------------------------------------- /internal/zero/zero.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package zero 4 | 5 | type Zero = struct{} 6 | 7 | func New() Zero { 8 | return Zero{} 9 | } 10 | -------------------------------------------------------------------------------- /internal/zero/zero_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 | 3 | package zero 4 | 5 | import ( 6 | "runtime" 7 | "testing" 8 | ) 9 | 10 | func TestZero_Chan(t *testing.T) { 11 | var stat1 = &runtime.MemStats{} 12 | runtime.ReadMemStats(stat1) 13 | 14 | var m = make(chan Zero, 2_000_000) 15 | for i := 0; i < 1_000_000; i++ { 16 | m <- New() 17 | } 18 | 19 | var stat2 = &runtime.MemStats{} 20 | runtime.ReadMemStats(stat2) 21 | t.Log(stat2.HeapInuse, stat1.HeapInuse, stat2.HeapInuse-stat1.HeapInuse, "B") 22 | t.Log(len(m)) 23 | } 24 | 25 | func TestZero_Map(t *testing.T) { 26 | var stat1 = &runtime.MemStats{} 27 | runtime.ReadMemStats(stat1) 28 | 29 | var m = map[int]Zero{} 30 | for i := 0; i < 1_000_000; i++ { 31 | m[i] = New() 32 | } 33 | 34 | var stat2 = &runtime.MemStats{} 35 | runtime.ReadMemStats(stat2) 36 | t.Log((stat2.HeapInuse-stat1.HeapInuse)/1024/1024, "MB") 37 | t.Log(len(m)) 38 | 39 | _, ok := m[1024] 40 | t.Log(ok) 41 | } 42 | --------------------------------------------------------------------------------