diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..6da7786 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ main ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main ] + schedule: + - cron: '26 7 * * 2' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..f869ba3 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,25 @@ +name: Go + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.17 + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v ./... diff --git a/.gitignore b/.gitignore index 895d669..a7e163b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ config.ini +*.db .idea !.gitignore vendor -*.sum dnslogger \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..01b48aa --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Dem0ns + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index dfed170..7134030 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,78 @@ # DNSLogger -### 适用场景 +自建DNSLog平台。 -自建DNSLog环境。 +![Running](./images/running.jpg) -### 说明 +### 编译 & 运行 -初次使用请将`config.default.ini`文件重命名为`config.ini`,并填写数据库连接信息。 \ No newline at end of file +``` +export GOPROXY=https://proxy.golang.com.cn,direct # 可选 +go build +./dnslogger + +因采用了go-sqlite3组件,涉及到CGO,编译有问题请参考 https://github.com/mattn/go-sqlite3 +``` + +### 配置 + +``` +准备一个域名,假设为dnslogger.local +准备一个公网服务器,IP假设为1.1.1.1 +采用*.log.dnslogger.local为DNSLog域名 +第一步,添加NS记录 log -> ns.dnslogger.local. +第二步,添加A记录 ns -> 1.1.1.1 +第三步,运行dnslogger +``` + +### 测试 + +``` +# 发送DNS请求 +dig dnslogger.local @127.0.0.1 + +# 查询最新的5条DNS请求 +curl http://localhost:1965/api/latest -v + +# 查询domain为dnslogger.local的请求(5分钟内) +curl http://localhost:1965/api/validate -d '{"domain":"dnslogger.local"}' -v +``` + +### 说明 & API + +UDP 53 DNS + +TCP 1965 API + +API: + +``` +查看最新5条记录 +GET /api/latest + +根据域名查询 +POST /api/validate + +{"domain":"dnslogger.local"} +``` + +### 常见问题 + +``` +1. Ubuntu UDP 53端口被占用 +需要禁用系统自带的DNS解析服务 +systemctl stop systemd-resolved.service +systemctl disable systemd-resolved.service +echo "nameserver 223.5.5.5" > /etc/resolv.conf + +2. UDP53端口权限问题 +一般来说,在Linux系统中,监听UDP53端口需要root权限, +如果担心安全问题,可以在Docker中运行或者使用端口转发。 +``` + +### 在Docker中运行 + +``` +CGO_ENABLED=1 GOOS=linux go build && +docker-compose up -d +``` diff --git a/config.default.ini b/config.default.ini index d661025..af311ab 100644 --- a/config.default.ini +++ b/config.default.ini @@ -1,3 +1,6 @@ -[DNSLog_config] -conn = root:root@tcp(127.0.0.1:3306)/dnslog?parseTime=true -default_ip = 127.0.0.1 \ No newline at end of file +[config] +db_file = dnslog.db +return_ip = 127.0.0.1 +listen_dns = 0.0.0.0:53 +listen_http = 0.0.0.0:1965 +domain = log.dnslogger.local \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..44621ee --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +version: '3' +services: + web: + image: ubuntu:latest + command: bash -c 'cd /srv && ./dnslogger' + restart: always + volumes: + - ./dnslogger:/srv/dnslogger + ports: + - "53:53/udp" + - "1965:1965/tcp" \ No newline at end of file diff --git a/go.mod b/go.mod index 7231699..7cb6f99 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,9 @@ go 1.17 require ( github.com/gin-gonic/gin v1.7.7 - github.com/go-sql-driver/mysql v1.6.0 + github.com/mattn/go-sqlite3 v1.14.12 github.com/miekg/dns v1.1.45 + gopkg.in/ini.v1 v1.66.2 ) require ( @@ -20,10 +21,10 @@ require ( github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect github.com/ugorji/go/codec v1.1.7 // indirect - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect + golang.org/x/crypto v0.1.0 // indirect golang.org/x/mod v0.4.2 // indirect - golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 // indirect - golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect + golang.org/x/net v0.1.0 // indirect + golang.org/x/sys v0.1.0 // indirect golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/yaml.v2 v2.2.8 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..34556bd --- /dev/null +++ b/go.sum @@ -0,0 +1,106 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= +github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= +github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/miekg/dns v1.1.45 h1:g5fRIhm9nx7g8osrAvgb16QJfmyMsyOCb+J7LSv+Qzk= +github.com/miekg/dns v1.1.45/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/images/running.jpg b/images/running.jpg new file mode 100644 index 0000000..29b485a Binary files /dev/null and b/images/running.jpg differ diff --git a/main.go b/main.go index 370cd99..a9722f5 100644 --- a/main.go +++ b/main.go @@ -4,16 +4,21 @@ import ( "database/sql" "fmt" "github.com/gin-gonic/gin" - _ "github.com/gin-gonic/gin" - _ "github.com/go-sql-driver/mysql" + _ "github.com/mattn/go-sqlite3" "github.com/miekg/dns" + "gopkg.in/ini.v1" + "io" "log" "net" "net/http" - "strconv" + "os" "time" ) +var db *sql.DB + +const TimeLayout = "2006-01-02 15:04:05" + type DNS struct { Id int Domain string @@ -24,8 +29,10 @@ type DNS struct { } type Config struct { - Conn string - DefaultIp string + ReturnIP string + DbPath string + ListenHttp string + ListenDNS string } type Query struct { @@ -34,14 +41,40 @@ type Query struct { var config Config +func checkErr(err error) { + if err != nil { + panic(err) + } +} + +func checkErrWarmly(err error) { + if err != nil { + log.Println(err) + } +} + func getConfig(str string) string { var err error - var filepath = "config.ini" - config, err := ini.Load(filepath) + var config_file = "config.ini" + if _, err := os.Stat(config_file); os.IsNotExist(err) { + fmt.Println("[*] 配置文件不存在") + src, err := os.Open("config.default.ini") + defer func(src *os.File) { + checkErr(src.Close()) + }(src) + dst, err := os.OpenFile(config_file, os.O_WRONLY|os.O_CREATE, 0644) + checkErr(err) + defer func(dst *os.File) { + checkErr(dst.Close()) + }(dst) + _, _ = io.Copy(dst, src) + fmt.Println("[*] 已创建配置文件") + } + config, err := ini.Load(config_file) if err != nil { - log.Fatalln("请配置config.ini文件") + log.Fatalln(err) } - config_section, err := config.GetSection("DNSLog_config") + config_section, err := config.GetSection("config") if err != nil { log.Println("读取section失败") } @@ -52,24 +85,20 @@ func getConfig(str string) string { return value.String() } -func loadConfig() { - fmt.Println("[*] Loading config...") - config.Conn = getConfig("conn") - config.DefaultIp = getConfig("default_ip") - fmt.Println("[*] Done.") +func LoadConfig() { + fmt.Println("[*] 加载配置文件...") + config.ReturnIP = getConfig("return_ip") + config.DbPath = getConfig("db_file") + config.ListenHttp = getConfig("listen_http") + config.ListenDNS = getConfig("listen_dns") + fmt.Printf("[*] HTTP API: %s\n", config.ListenHttp) + fmt.Println("[*] 配置文件加载完毕") } func saveDatabase(record DNS) bool { - Db, err := sql.Open("mysql", config.Conn) - if err != nil { - log.Fatalln(err) - } - defer Db.Close() - _, err = Db.Exec("INSERT INTO `record` (`domain`, `type`, `resp`, `src`, `created_at`) VALUES (?, ?, ?, ?, ?)", &record.Domain, &record.Type, &record.Resp, &record.Src, &record.Created) - if err != nil { - log.Println(err) - } - fmt.Println("[+] " + record.Src + " asked " + record.Domain + " & response " + record.Resp) + _, err := db.Exec("INSERT INTO `dnslog` (`domain`, `type`, `resp`, `src`, `created_at`) VALUES (?, ?, ?, ?, ?)", &record.Domain, &record.Type, &record.Resp, &record.Src, &record.Created) + checkErrWarmly(err) + fmt.Printf("[+] REQ [%s] FROM [%s] RESP [%s]\n", record.Domain, record.Src, record.Resp) return true } @@ -86,7 +115,7 @@ func (this *handler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { var record DNS record.Domain = domain record.Type = "A" - record.Resp = config.DefaultIp + record.Resp = config.ReturnIP record.Src = w.RemoteAddr().String() record.Created = time.Now().Local() _ = saveDatabase(record) @@ -100,14 +129,23 @@ func (this *handler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { } func main() { - fmt.Println("[+] Welcome to DNSLogger.") + fmt.Println("[+] Hello from DNSLogger") fmt.Println("[+] Starting...") - loadConfig() + LoadConfig() + var err error + db, err = sql.Open("sqlite3", config.DbPath) + checkErr(err) + defer func(db *sql.DB) { + err := db.Close() + checkErr(err) + }(db) + err = db.Ping() + checkErr(err) check() go httpServer() - fmt.Println("[+] Server Started") - fmt.Println("[+] GitHub: https://github.com/dem0ns/dnslogger") - srv := &dns.Server{Addr: ":" + strconv.Itoa(53), Net: "udp"} + fmt.Println("[+] Server Started!") + fmt.Printf("[+] DNS Interface: %s\n", config.ListenDNS) + srv := &dns.Server{Addr: config.ListenDNS, Net: "udp"} srv.Handler = &handler{} if err := srv.ListenAndServe(); err != nil { log.Fatalf("Failed to set udp listener %s\n", err.Error()) @@ -115,36 +153,43 @@ func main() { } func check() { - fmt.Println("[*] Testing SQL connection...") - Db, err := sql.Open("mysql", config.Conn) - if err != nil { - log.Fatalln(err) - } - err = Db.Ping() - if err != nil { - log.Fatalln(err) + fmt.Println("[*] 数据库检查...") + exec, err := db.Query("SELECT name FROM sqlite_master WHERE type='table' AND name='dnslog';") + checkErr(err) + defer func(exec *sql.Rows) { + checkErr(exec.Close()) + }(exec) + if !exec.Next() { + fmt.Println("[*] 数据库初始化中") + initSql := "create table dnslog(id integer constraint dnslog_pk primary key autoincrement, domain text, type text, resp text, src text, created_at text);" + _, err := db.Exec(initSql) + checkErr(err) + initSql = "create index dnslog_domain_index on dnslog (domain);" + _, err = db.Exec(initSql) + checkErr(err) + fmt.Println("[*] 数据库初始化完毕") } - Db.Close() - fmt.Println("[*] Done.") + fmt.Println("[*] 数据库检查完毕") } func httpServer() { gin.SetMode(gin.ReleaseMode) r := gin.Default() - Db, err := sql.Open("mysql", config.Conn) - if err != nil { - log.Fatalln(err) - } r.GET("/api/latest", func(c *gin.Context) { - rows, err := Db.Query("SELECT `id`, `domain`, `type`, `resp`, `src`, `created_at` FROM record ORDER BY `id` DESC LIMIT 10") + rows, err := db.Query("SELECT `id`, `domain`, `type`, `resp`, `src`, datetime(created_at) FROM dnslog ORDER BY `id` DESC LIMIT 10") + checkErrWarmly(err) if err != nil { log.Fatal(err) } - defer rows.Close() + defer func(rows *sql.Rows) { + checkErrWarmly(rows.Close()) + }(rows) logs := make([]DNS, 0) for rows.Next() { var d DNS - _ = rows.Scan(&d.Id, &d.Domain, &d.Type, &d.Resp, &d.Src, &d.Created) + var timeCreated string + err = rows.Scan(&d.Id, &d.Domain, &d.Type, &d.Resp, &d.Src, &timeCreated) + d.Created, _ = time.Parse(TimeLayout, timeCreated) logs = append(logs, d) } c.JSON(http.StatusOK, gin.H{ @@ -155,12 +200,15 @@ func httpServer() { var query Query if c.ShouldBindJSON(&query) == nil { var d DNS - query.Domain = query.Domain + "." + query.Domain += "." m, _ := time.ParseDuration("-5m") - err := Db.QueryRow("SELECT `id`, `domain`,`type`,`resp`,`src`,`created_at` FROM record WHERE `domain` = ? and `created_at` >= ? LIMIT 1", query.Domain, time.Now().Add(m)).Scan(&d.Id, &d.Domain, &d.Type, &d.Resp, &d.Src, &d.Created) + var timeCreated string + err := db.QueryRow("SELECT `id`, `domain`,`type`,`resp`,`src`,datetime(created_at) FROM dnslog WHERE `domain` = ? and `created_at` >= ? LIMIT 1", query.Domain, time.Now().Add(m)).Scan(&d.Id, &d.Domain, &d.Type, &d.Resp, &d.Src, &timeCreated) + d.Created, _ = time.Parse(TimeLayout, timeCreated) if err != nil { + checkErrWarmly(err) c.JSON(http.StatusNoContent, gin.H{ - "msg": "No record(s) within 5 minute.", + "msg": "No record within 5 minute.", }) return } @@ -171,8 +219,10 @@ func httpServer() { } c.JSON(http.StatusNotAcceptable, gin.H{ "status": "0", - "msg": "You looks like a 🐖.", + "msg": "Wrong 🐖", }) }) - _ = r.Run("127.0.0.1:1965") + listenAddr := fmt.Sprintf(config.ListenHttp) + fmt.Printf("[*] HTTP API: %s\n", listenAddr) + _ = r.Run(listenAddr) } diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..a4c37d6 --- /dev/null +++ b/main_test.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + "github.com/miekg/dns" + "net/http" + "testing" + "time" +) + +func TestMain(m *testing.M) { + go main() + time.Sleep(3 * time.Second) + _ = m.Run() +} + +func TestDNS(t *testing.T) { + t.Run("req", func(t *testing.T) { + ns := "localhost:53" + c := dns.Client{} + m := dns.Msg{} + m.SetQuestion("dnslogger.local.", dns.TypeA) + r, _, err := c.Exchange(&m, ns) + checkErr(err) + for _, ans := range r.Answer { + if ans.(*dns.A).A.String() != config.ReturnIP { + t.Errorf("DNS解析错误.\n") + } + } + }) + ApiBase := fmt.Sprintf("http://%s", config.ListenHttp) + t.Run("queryLatest", func(t *testing.T) { + reqest, err := http.NewRequest("GET", fmt.Sprintf("%s/api/latest", ApiBase), nil) + if err != nil { + panic(err) + } + response, _ := (&http.Client{}).Do(reqest) + if response.StatusCode != 200 { + t.Errorf("API status code异常. %d\n", response.StatusCode) + } + }) +}