snort:预处理器开发HelloWorld

snort:预处理器开发HelloWorld文章目录参考 1 预处理器回顾 2 README PLUGINS3 spp template c 参考 Snort 预处理插件 HelloWorld 程序开发 Snort 预处理器介绍 详细 本专栏所有相关博文使用的 snort 版本均为 2 9 151 预处理器回顾预处理器在 Snort 应用规则前处理接收到的数据预处理器对每一个数据包只执行一次被捕获的数据包首先经过预处理器 然后经过探测引擎根

参考

本专栏所有相关博文使用的snort版本均为 2.9.15.1

0.开胃菜

1. 预处理器回顾

  • 预处理器在Snort应用规则前处理接收到的数据
  • 预处理器对每一个数据包只执行一次
  • 被捕获的数据包首先经过预处理器,然后经过探测引擎根据规则处理。
  • 输入插件和预处理器是同一概念
  • 当Snort接收到数据包的时候,主探测引擎并不能对它们进行处理和应用规则,比如,数据包有可能是分片的,需要重新组装,预处理器就是做这样的工作,使数据能够被探测引擎处理,另外,一些预处理器还可以做一些其它工作,比如探测包中的一些明显错误。
  • 你可以编写自己的预处理器,察看Snort源代码doc目录中的README.PLUGINS文件,你可以获得相关的资料,也可以在templates目录中查看源代码示例。

2. README.PLUGINS

Plugin Info 12/5/99 Martin Roesch Overview: Snort version 1.5 introduces a major new concept, plugins. There are two types of plugin currently available in Snort: detection plugins and preprocessors. Detection plugins check a single aspect of a packet for a value defined within a rule and determine if the packet data meets their acceptance criteria. For example, the tcp flags detection plugin checks the flags section of TCP packets for matches with flag combinations defined in a particular rule. Detection plugins may be called multiple times per packet with different arguments. Preprocessors are only called a single time per packet and may perform highly complex functions like TCP stream reassembly, IP defragmentation, or HTTP request normalization. They can directly manipulate packet data and even call the detection engine directly with their modified data. They can perform less complex tasks like statistics gathering or threshold monitoring as well. Adding New Plugins to Snort as a User: Right now, adding a new plugin to Snort is straightforward but requires you to edit two files by hand. The plugin should consist of two files, "sp_something.c"/"sp_something.h" for detection plugins, and "spp_something.c"/"spp_something.h" for preprocessors. For detection plugins, there are two steps to integrating it with Snort: 1) Edit plugbase.h and insert the line #include "sp_something.h" into the file with the other "#include" statements. Save and close the file. 2) Edit the plugbase.c file and in the InitPlugins() function, add the name of the setup function to the list with the other Setup functions. Save and close the file. 3) Edit the Makefile.am and add the names of the two files to the list of names on the "snort_SOURCES" line. Save and exit the file. Run "automake". Someday, there will be a nice little program that will do all this work for you! Writing New Snort Plugins as a Developer: This process is also pretty straight forward, and the best place to look for information on doing these things is at the files in the "templates" directory. The sp_* files are setup for detection plugins, and the spp_* files refer to the preprocessor files. The main thing to remember once you've got it written is to put the proper includes and function calls into plugbase[.c|.h]. I should probably flesh out this document more, but I think that the best info is in the template files. If you have any questions or comments, don't hesitate to send me an e-mail! 

机翻来自百度

插件信息 1999512日 马丁·罗斯奇 概述: Snort版本1.5引入了一个重要的新概念,即插件。有两种类型 Snort中当前可用插件的数量:检测插件和预处理器。 检测插件检查数据包的一个方面是否有在 一种规则,用于确定包数据是否满足其接受标准。为了 例如,tcp标志检测插件检查tcp数据包的标志部分 用于与特定规则中定义的标志组合匹配。检测 每个包可以使用不同的参数多次调用插件。 预处理器在每个包中只调用一次,并且可以执行很高的性能 复杂功能,如TCP流重组、IP碎片整理或HTTP 请求规范化。它们可以直接操作包数据,甚至调用 检测引擎直接用他们修改过的数据。他们能做的更少 复杂的任务,例如统计数据收集或阈值监视。 以用户身份向Snort添加新插件: 现在,向Snort添加一个新插件很简单,但是需要您 手工编辑两个文件。插件应该由两个文件组成, “sp_something.c”或“sp_something.h”用于检测插件,以及 “spp_something.c”或“spp_something.h”表示预处理器。对于检测插件, 将它与Snort集成有两个步骤: 1) 编辑plugbase.h并插入行 #包括“sp_something.h” 与其他“#include”语句一起放入文件。保存并关闭 文件。 2) 编辑plugbase.c文件,在InitPlugins()函数中,添加 列表中的设置函数和其他设置函数。保存和 关闭文件。 3) 编辑Makefile.am并将两个文件的名称添加到 “snort_SOURCES”行中的名称。保存并退出文件。跑步 “汽车制造”。 总有一天,会有一个很好的小程序来帮你完成所有的工作! 作为开发人员编写新的Snort插件: 这个过程也是非常直接的,而且是最好的地方 有关执行这些操作的信息位于“templates”目录中的文件中。 sp U*文件是为检测插件设置的,spp U*文件是指 预处理器文件。一旦你写好了,最重要的是要记住 是将适当的include和函数调用放入plugbase[.c |.h]。我 也许应该更详细地说明这个文件,但是我认为最好的信息 在模板文件中。如果你有任何问题或意见,不要 不好意思给我发电子邮件! 

3. spp_template.c

/* $Id$ */ /* Snort Preprocessor Plugin Source File Template */ /* spp_template * * Purpose: * * Preprocessors perform some function *once* for *each* packet. This is * different from detection plugins, which are accessed depending on the * standard rules. When adding a plugin to the system, be sure to * add the "Setup" function to the InitPreprocessors() function call in * plugbase.c! * 注意:上面是Snort源文件中spp_template.c (预处理器模板)中的注释, * 这个注释没有及时更新 * 原文中的InitPreprocessors( ) 函数已经被替换成了 RegistPreprocessors( ) * * Arguments: * * This is the list of arguments that the plugin can take at the * "preprocessor" line in the rules file * * Effect: * * What the preprocessor does. Check out some of the default ones * (e.g. spp_frag2) for a good example of this description. * * Comments: * * Any comments? * */ #include <sys/types.h> #include <stdlib.h> #include <ctype.h> #include <rpc/types.h> /* * If you're going to issue any alerts from this preproc you * should include generators.h and event_wrapper.h */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "generators.h" #include "event_wrapper.h" #include "util.h" #include "plugbase.h" #include "parser.h" /* * put in other inculdes as necessary */ /* * your preprocessor header file goes here if necessary, don't forget * to include the header file in plugbase.h too! */ #include "spp_template.h" /* * define any needed data structs for things like configuration */ typedef struct _TemplateData { 
    /* Your struct members here */ } TemplateData; /* * If you need to instantiate the preprocessor's * data structure, do it here */ TemplateData SomeData; /* * function prototypes go here */ static void TemplateInit(u_char *); static void ParseTemplateArgs(char *); static void PreprocFunction(Packet *); static void PreprocCleanExitFunction(int, void *); static void PreprocRestartFunction(int, void *); /* * Function: SetupTemplate() * * Purpose: Registers the preprocessor keyword and initialization * function into the preprocessor list. This is the function that * gets called from InitPreprocessors() in plugbase.c. * * Arguments: None. * * Returns: void function * */ void SetupTemplate() { 
    /* * link the preprocessor keyword to the init function in * the preproc list */ RegisterPreprocessor("keyword", TemplateInit); DebugMessage(DEBUG_PLUGIN,"Preprocessor: Template is setup...\n"); } /* * Function: TemplateInit(u_char *) * * Purpose: Calls the argument parsing function, performs final setup on data * structs, links the preproc function into the function list. * * Arguments: args => ptr to argument string * * Returns: void function * */ static void TemplateInit(u_char *args) { 
    DebugMessage(DEBUG_PLUGIN,"Preprocessor: Template Initialized\n"); /* * parse the argument list from the rules file */ ParseTemplateArgs(args); /* * perform any other initialization functions that are required here */ /* * Set the preprocessor function into the function list */ AddFuncToPreprocList(PreprocFunction); AddFuncToCleanExitList(PreprocCleanExitFunction, NULL); AddFuncToRestartList(PreprocRestartFunction, NULL); } /* * Function: ParseTemplateArgs(char *) * * Purpose: Process the preprocessor arguments from the rules file and * initialize the preprocessor's data struct. This function doesn't * have to exist if it makes sense to parse the args in the init * function. * * Arguments: args => argument list * * Returns: void function * */ static void ParseTemplateArgs(char *args) { 
    /* your parsing function goes here, check out the other spp files for examples */ } /* * Function: PreprocFunction(Packet *) * * Purpose: Perform the preprocessor's intended function. This can be * simple (statistics collection) or complex (IP defragmentation) * as you like. Try not to destroy the performance of the whole * system by trying to do too much.... * * Arguments: p => pointer to the current packet data struct * * Returns: void function * */ static void PreprocFunction(Packet *p) { 
    /* your preproc function goes here.... */ /* * if you need to issue an alert from your preprocessor, check out * event_wrapper.h, there are some useful helper functions there */ } /* * Function: PreprocCleanExitFunction(int, void *) * * Purpose: This function gets called when Snort is exiting, if there's * any cleanup that needs to be performed (e.g. closing files) * it should be done here. * * Arguments: signal => the code of the signal that was issued to Snort * data => any arguments or data structs linked to this * functioin when it was registered, may be * needed to properly exit * * Returns: void function */ static void PreprocCleanExitFunction(int signal, void *data) { 
    /* clean exit code goes here */ } /* * Function: PreprocRestartFunction(int, void *) * * Purpose: This function gets called when Snort is restarting on a SIGHUP, * if there's any initialization or cleanup that needs to happen * it should be done here. * * Arguments: signal => the code of the signal that was issued to Snort * data => any arguments or data structs linked to this * functioin when it was registered, may be * needed to properly exit * * Returns: void function */ static void PreprocRestartFunction(int signal, void *foo) { 
    /* restart code goes here */ } 

4. spp_template.h

/* $Id$ */ /* Snort Preprocessor Plugin Header File Template */ /* This file gets included in plugbase.h when it is integrated into the rest * of the program. */ #ifndef __SPP_TEMPLATE_H__ #define __SPP_TEMPLATE_H__ /* * list of function prototypes to export for this preprocessor */ void SetupTemplate(); #endif /* __SPP_TEMPLATE_H__ */ 

5. 预处理器的Hello World

5.1 复制模板

进入snort-2.9.15.1/

cp templates/spp_template.h src/preprocessors/spp_helloworld.h 
cp templates/spp_template.c src/preprocessors/spp_helloworld.c 

5.2 包含新文件

vim src/plugbase.c 

在64行,插入

#include "preprocessors/spp_helloworld.h" 

在这里插入图片描述
703行插入

SetupHelloWorld(); 

稍后会在spp_helloworld.c编写这个函数并在spp_helloworld.h中声明

在这里插入图片描述
保存退出

5.3 修改spp_helloworld.h

vim src/preprocessors/spp_helloworld.h 

#ifndef改掉
13行声明上文修改的那个函数

void SetupHelloWorld(); 

在这里插入图片描述

保存退出

5.4 修改spp_helloworld.c

我这个写的挺丑的,应该把声明放一块,函数放一块…下次一定改

先总体看一下,此文件应有如下几个函数及其声明,从上到下:

  • 空函数:HelloWorldReloadFunction。snort reload时调用的函数,可不写函数体
  • 声明:HelloWorldInit
  • 函数:SetupHelloWorld。主程序启动,运行plugbase.c时,调用此函数。此函数内调用RegisterPreprocessor进行预处理器注册,以及调用HelloWorldInit进行预处理器的初始化
  • 函数:HelloWorldMain。预处理器的主函数,即每收到一个符合条件的包都会调用此函数
  • 函数:HelloWorldInit。预处理器的初始化函数,此函数内调用AddFuncToPreprocList,将预处理器的主函数(功能函数)HelloWorldMain添加到对应链表中。在新版snort中,需要有一句session_api->enable_preproc_all_ports(sc,PP_HELLOWORLD_RT, 0x01);
    否则预处理器主函数不能正常工作。
vim src/preprocessors/spp_helloworld.c 

58行,修改为

#include "spp_helloworld.h" 

在这里插入图片描述
然后加上

#include "session_api.h" #include "decode.h" 
// 添加函数到预处理器链表时用的 #define PROTO_MASK 0x0001 

5.4.1 空函数 HelloWorldReloadFunction

// 重加载的函数,可不写函数体 void HelloWorldReloadFunction(){ 
   } 

5.4.2 声明 HelloWorldInit

// 声明 static void HelloWorldInit(); 

5.4.3 函数 主配置函数 SetupHelloWorld

修改函数SetupTemplate()SetupHelloWorld()

// 主配置函数 void SetupHelloWorld() { 
    /* * link the preprocessor keyword to the init function in * the preproc list */ // RegisterPreprocessor("keyword", TemplateInit); // DebugMessage(DEBUG_PLUGIN,"Preprocessor: Template is setup...\n"); #ifndef SNORT_RELOAD // 预处理器名称, 用于初始化预处理器的函数 RegisterPreprocessor("HelloWorld", HelloWorldInit); printf("*wuming-register* HelloWorld start\n"); #else // ReloadFunction可以暂时不写 RegisterPreprocessor("HelloWorld", HelloWorldInit, HelloWorldReloadFunction,NULL,NULL, NULL); printf("*wuming-register* HelloWorld reload\n"); #endif printf("*wuming-setup done*\n"); DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN,"Preprocessor: HelloSnort is setup...\n");); } 

5.4.4 函数 HelloWorldMain

static void PreprocFunction(Packet *p) 
// 实际执行的主函数 static void HelloWorldMain(Packet *p){ 
    printf("*wuming-main* Hello World's main function*\n"); } 

5.4.5 函数 HelloWorldInit

修改 TemplateInit()如下

static void HelloWorldInit(struct _SnortConfig *sc,u_char *args) { 
    // DebugMessage(DEBUG_PLUGIN,"Preprocessor: Template Initialized\n"); /* * parse the argument list from the rules file */ // ParseTemplateArgs(args); /* * perform any other initialization functions that are required here */ /* * Set the preprocessor function into the function list */ DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN,"Preprocessor: HelloSnortInit Initialized\n");); // printf("HelloSnortInit ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^is setup"); // 参数分别为:结构体指针 预处理器实际执行的函数 处理器优先级 预定义常数 自定义解码类型标志 AddFuncToPreprocList(sc, HelloWorldMain, 0x01, PP_HELLOWORLD_RT, PROTO_MASK); session_api->enable_preproc_all_ports(sc,PP_HELLOWORLD_RT, 0x01); printf("*wuming-init done*\n"); // 不需要总结输出的话这行可以删掉 // 预处理器名称, //RegisterPreprocStats("HelloWorld", ProfinetPrintStats); // AddFuncToCleanExitList(PreprocCleanExitFunction, NULL); // AddFuncToRestartList(PreprocRestartFunction, NULL); } 

保存退出

5.5 修改preprocids.h

spp_helloworld.c中预定义常数PP_HELLOWORLD_RT需要添加到 src/preprocids.h

vim src/preprocids.h 

增加一行,然后PP_MAX增加1

在这里插入图片描述

5.6 MakeFile.am

vim src/preprocessors/Makefile.am 

把预处理器的两个文件添加到libspp_a_SOURCES

在这里插入图片描述

5.7 snort.conf

vim your_path/snort.conf 

270行附近 step5,添加5.4中定义的预处理器名称HelloWorld
在这里插入图片描述

5.8 automake && make && make install

automake && make && make install 

在这里插入图片描述
没有报错的话就可以启动了,有报错的话就回去DEBUG
一定要注意对应的函数名、常量名是否正确,否则会出现各种奇怪的BUG

5.9 启动snort

我使用-A参数直接打印到控制台

snort -A console -i ens33 -c your_path/snort.conf 

在这里插入图片描述

6. 完整的 spp_helloworld.c

再次说明一下,写的很丑,其实原来的模板中都有注释的,最好还是按对应的位置来写。

/* $Id$ */ /* Snort Preprocessor Plugin Source File Template */ /* spp_template * * Purpose: * * Preprocessors perform some function *once* for *each* packet. This is * different from detection plugins, which are accessed depending on the * standard rules. When adding a plugin to the system, be sure to * add the "Setup" function to the InitPreprocessors() function call in * plugbase.c! * * Arguments: * * This is the list of arguments that the plugin can take at the * "preprocessor" line in the rules file * * Effect: * * What the preprocessor does. Check out some of the default ones * (e.g. spp_frag2) for a good example of this description. * * Comments: * * Any comments? * */ #include <sys/types.h> #include <stdlib.h> #include <ctype.h> #include <rpc/types.h> /* * If you're going to issue any alerts from this preproc you * should include generators.h and event_wrapper.h */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "generators.h" #include "event_wrapper.h" #include "util.h" #include "plugbase.h" #include "parser.h" #include "session_api.h" #include "decode.h" /* * put in other inculdes as necessary */ /* * your preprocessor header file goes here if necessary, don't forget * to include the header file in plugbase.h too! */ #include "spp_helloworld.h" // 添加函数到预处理器链表时用的 #define PROTO_MASK 0x0001 /* * define any needed data structs for things like configuration */ typedef struct _TemplateData { 
    /* Your struct members here */ } TemplateData; /* * If you need to instantiate the preprocessor's * data structure, do it here */ TemplateData SomeData; /* * function prototypes go here */ static void TemplateInit(u_char *); static void ParseTemplateArgs(char *); static void PreprocFunction(Packet *); static void PreprocCleanExitFunction(int, void *); static void PreprocRestartFunction(int, void *); /* * Function: SetupTemplate() * * Purpose: Registers the preprocessor keyword and initialization * function into the preprocessor list. This is the function that * gets called from InitPreprocessors() in plugbase.c. * * Arguments: None. * * Returns: void function * */ // 重加载的函数,可不写 void HelloWorldReloadFunction(){ 
   } // 声明 static void HelloWorldInit(); // 此函数用于调用下面的初始化函数 HelloWorldRTInit() void SetupHelloWorld() { 
    /* * link the preprocessor keyword to the init function in * the preproc list */ // RegisterPreprocessor("keyword", TemplateInit); // DebugMessage(DEBUG_PLUGIN,"Preprocessor: Template is setup...\n"); #ifndef SNORT_RELOAD // 预处理器名称, 初始化预处理器的函数 RegisterPreprocessor("HelloWorld", HelloWorldInit); printf("*wuming-register* HelloWorld start\n"); #else // ReloadFunction可以暂时不写 RegisterPreprocessor("HelloWorld", HelloWorldInit, HelloWorldReloadFunction,NULL,NULL, NULL); printf("*wuming-register* HelloWorld reload\n"); #endif printf("*wuming-setup done*\n"); DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN,"Preprocessor: HelloSnort is setup...\n");); } // 实际执行的主函数 static void HelloWorldMain(Packet *p){ 
    printf("*wuming-main* Hello World's main function*\n"); } /* * Function: TemplateInit(u_char *) * * Purpose: Calls the argument parsing function, performs final setup on data * structs, links the preproc function into the function list. * * Arguments: args => ptr to argument string * * Returns: void function * */ // 初始化预处理器 static void HelloWorldInit(struct _SnortConfig *sc,u_char *args) { 
    // DebugMessage(DEBUG_PLUGIN,"Preprocessor: Template Initialized\n"); /* * parse the argument list from the rules file */ // ParseTemplateArgs(args); /* * perform any other initialization functions that are required here */ /* * Set the preprocessor function into the function list */ DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN,"Preprocessor: HelloSnortInit Initialized\n");); // 参数分别为:结构体指针 预处理器实际执行的函数 处理器优先级 预定义常数 自定义解码类型标志 AddFuncToPreprocList(sc, HelloWorldMain, 0x01, PP_HELLOWORLD_RT, PROTO_MASK); session_api->enable_preproc_all_ports(sc,PP_HELLOWORLD_RT, 0x01); printf("*wuming-init done*\n"); // 不需要总结输出的话这行可以删掉 // 预处理器名称, //RegisterPreprocStats("HelloWorld", ProfinetPrintStats); // session_api->enable_preproc_all_ports(sc, PP_HELLOWORLD_RT, PROTO_BIT_IP); // AddFuncToCleanExitList(PreprocCleanExitFunction, NULL); // AddFuncToRestartList(PreprocRestartFunction, NULL); } /* * Function: ParseTemplateArgs(char *) * * Purpose: Process the preprocessor arguments from the rules file and * initialize the preprocessor's data struct. This function doesn't * have to exist if it makes sense to parse the args in the init * function. * * Arguments: args => argument list * * Returns: void function * */ static void ParseTemplateArgs(char *args) { 
    /* your parsing function goes here, check out the other spp files for examples */ } /* * Function: PreprocFunction(Packet *) * * Purpose: Perform the preprocessor's intended function. This can be * simple (statistics collection) or complex (IP defragmentation) * as you like. Try not to destroy the performance of the whole * system by trying to do too much.... * * Arguments: p => pointer to the current packet data struct * * Returns: void function * */ static void PreprocFunction(Packet *p) { 
    /* your preproc function goes here.... */ /* * if you need to issue an alert from your preprocessor, check out * event_wrapper.h, there are some useful helper functions there */ } /* * Function: PreprocCleanExitFunction(int, void *) * * Purpose: This function gets called when Snort is exiting, if there's * any cleanup that needs to be performed (e.g. closing files) * it should be done here. * * Arguments: signal => the code of the signal that was issued to Snort * data => any arguments or data structs linked to this * functioin when it was registered, may be * needed to properly exit * * Returns: void function */ static void PreprocCleanExitFunction(int signal, void *data) { 
    /* clean exit code goes here */ } /* * Function: PreprocRestartFunction(int, void *) * * Purpose: This function gets called when Snort is restarting on a SIGHUP, * if there's any initialization or cleanup that needs to happen * it should be done here. * * Arguments: signal => the code of the signal that was issued to Snort * data => any arguments or data structs linked to this * functioin when it was registered, may be * needed to properly exit * * Returns: void function */ static void PreprocRestartFunction(int signal, void *foo) { 
    /* restart code goes here */ } 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/233419.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • ajax请求的五个步骤java_js ajax请求的五个步骤实现详解

    ajax请求的五个步骤java_js ajax请求的五个步骤实现详解Ajax是Java前端最重要的技术之一,是支撑着前端交互数据的基石,今天我们就来了解下ajax发送请求所需的五个步骤。首先我们需要来了解一下ajax,ajax的全称是AsynchronousJavascript+XML。异步传输+js+xml。所谓异步,在这里简单地解释就是:向服务器发送请求的时候,我们不必等待结果,而是可以同时做其他的事情,等到有了结果我们可以再来处理这个事。这个很重要,如果不是…

    2022年5月17日
    35
  • 如何查看任何一下网站的全部二级域名?

    如何查看任何一下网站的全部二级域名?

    2021年10月21日
    406
  • linux手动安装gcc-5.1.0「建议收藏」

    linux手动安装gcc-5.1.0「建议收藏」yum源和apt-get源安装linux下安装gcc和g++时,可以使用源安装,例如:yuminstallgcc或者apt-getinstallgcc,但是这有个缺点,就是可能不能安装到你想要的版本,因此我们需要手动安装。下载gcc不同版本gcc是gnu的产品,所以我们可以去gnu官网去下,但是gnu下载的比减慢,这里提供一些大学的软件开元镜像源,比如清华大学:清华大学开元镜像源…

    2022年5月26日
    41
  • mysql分组后,取每组第一条数据或最新一条

    mysql分组后,取每组第一条数据或最新一条环境 MySQL 5 7Java 1 8SQL 语句的写法 select from selectdistin a id tid a fromtemplate detailawhere template idin 3 4 orderbya iddesc ttgroupbytt template id 思路 先进行排序 然后再进行分组 获取每组的第一条 Q 为什么要写 distinct a i

    2025年11月23日
    2
  • org.postgresql.util.PSQLException: Connection to localhost:5432 refused. Check that the hostname and

    org.postgresql.util.PSQLException: Connection to localhost:5432 refused. Check that the hostname and项目启动报错org.postgresql.util.PSQLException:Connectiontolocalhost:5432refused.CheckthatthehostnameandportarecorrectandthatthepostmasterisacceptingTCP/IPconnections.解决:swin+R打开命令框…

    2022年6月19日
    40
  • docker-compose 集群_docker redis 集群

    docker-compose 集群_docker redis 集群前言实际工作中我们部署一个应用,一般不仅仅只有一个容器,可能会涉及到多个,比如用到数据库,中间件MQ,web前端和后端服务,等多个容器。我们如果一个个去启动应用,当项目非常多时,就很难记住了,所有

    2022年8月6日
    5

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号