semihalev / sdns Goto Github PK
View Code? Open in Web Editor NEWA high-performance, recursive DNS resolver server with DNSSEC support, focused on preserving privacy.
Home Page: https://sdns.dev
License: MIT License
A high-performance, recursive DNS resolver server with DNSSEC support, focused on preserving privacy.
Home Page: https://sdns.dev
License: MIT License
I'm happily running sdns on top of OpenWRT as the DNS upstream for AdGuardHome. From time to time, I'm seeing the following message in the log:
Wed Dec 6 03:46:26 2023 daemon.info sdns[17660]: t=2023-12-06T02:46:26+0000 lvl=eror msg="Root servers update failed" error="A record count mismatch"
I've been taking a look at the code, but I'm not really familiar with Go, so I'm not sure what the purpose of the snippet where appears this comparison: ipv4Count != nsCount
Is this something really expected ?
Thanks !
I guess this `checkPriming` thing is scheduled, so it happens from time to time.
1.3.5
Wed Dec 6 03:46:26 2023 daemon.info sdns[17660]: t=2023-12-06T02:46:26+0000 lvl=eror msg="Root servers update failed" error="A record count mismatch"
Linux
I'm using the default configuration for the root servers. Please note that my ISP hasn't enabled IPv6 yet, so currently using just IPv4 (I left the IPv6 addresses for the root servers though). sdns is listening just on localhost
# Address to bind to for the DNS server.
bind = "127.0.0.1:5354"
currently forwarders are only using port 53 on systems where it might be blocked or privacy is an issue can you add support for doing lookups over dns over https or tls?
alos a basic doh listner without tls so that this app could be used with local porxy like nginx or cloudflared. currently its only tls so it doesn't work if the use case scenario is to used this as a local dns.
also instead of manual whitelist can whitelists be loaded from urls?
Does sdns use random resolvers or minimum ping or something else?
For example I have set 3 forwarders but if the first one is not working then it doesn't use the other two. and if it is working then only that single one is used all the time the other two are never used.
How to open edns? I am using dig +subnet without answering.
Log
INFO[03-17|14:59:17] Starting sdns... version=0.3.0-rc1
INFO[03-17|14:59:17] DNS server listening... net=udp addr=:53
INFO[03-17|14:59:17] API server listening... addr=127.0.0.1:8080
INFO[03-17|14:59:17] DNS server listening... net=https addr=:8053
INFO[03-17|14:59:17] DNS server listening... net=tcp-tls addr=:853
EROR[03-17|14:59:17] DNS listener failed net=tcp-tls addr=:853 error="open : no such file or directory"
INFO[03-17|14:59:17] DNS server listening... net=tcp addr=:53
EROR[03-17|14:59:17] DNSs listener failed net=https addr=:8053 error="open : no such file or directory"
Config:
# config version, config and build versions can be different.
version = "0.3.0"
# address to bind to for the DNS server
bind = ":53"
# address to bind to for the DNS-over-TLS server
bindtls = ":853"
# address to bind to for the DNS-over-HTTPS server
binddoh = ":8053"
# tls certificate file
# tlscertificate = "server.crt"
# tls private key file
# tlsprivatekey = "server.key"
# outbound ip addresses, if you set multiple, sdns can use random outbound ip address
outboundips = []
# root servers
rootservers = [
"192.5.5.241:53",
"198.41.0.4:53",
"192.228.79.201:53",
"192.33.4.12:53",
"199.7.91.13:53",
"192.203.230.10:53",
"192.112.36.4:53",
"128.63.2.53:53",
"192.36.148.17:53",
"192.58.128.30:53",
"193.0.14.129:53",
"199.7.83.42:53",
"202.12.27.33:53"
]
# root ipv6 servers
root6servers = [
"[2001:500:2f::f]:53",
"[2001:503:ba3e::2:30]:53",
"[2001:500:200::b]:53",
"[2001:500:2::c]:53",
"[2001:500:2d::d]:53",
"[2001:500:a8::e]:53",
"[2001:500:12::d0d]:53",
"[2001:500:1::53]:53",
"[2001:7fe::53]:53",
"[2001:503:c27::2:30]:53",
"[2001:7fd::1]:53",
"[2001:500:9f::42]:53",
"[2001:dc3::35]:53"
]
# root keys for dnssec
rootkeys = [
". 172800 IN DNSKEY 256 3 8 AwEAAdp440E6Mz7c+Vl4sPd0lTv2Qnc85dTW64j0RDD7sS/zwxWDJ3QRES2VKDO0OXLMqVJSs2YCCSDKuZXpDPuf++YfAu0j7lzYYdWTGwyNZhEaXtMQJIKYB96pW6cRkiG2Dn8S2vvo/PxW9PKQsyLbtd8PcwWglHgReBVp 7kEv/Dd+3b3YMukt4jnWgDUddAySg558Zld+c9eGWkgWoOiuhg4rQRkF stMX1pRyOSHcZuH38o1WcsT4y3eT0U/SR6TOSLIB/8Ftirux/h297oS7tCcwSPt0wwry5OFNTlfMo8v7WGurogfk8hPipf7TTKHIi20LWen5RCsvYsQBkYGpF78=",
". 172800 IN DNSKEY 256 3 8 AwEAAcH+axCdUOsTc9o+jmyVq5rsGTh1EcatSumPqEfsPBT+whyj0/UhD7cWeixV9Wqzj/cnqs8iWELqhdzGX41ZtaNQUfWNfOriASnWmX2D9m/EunplHu8nMSlDnDcT7+llE9tjk5HI1Sr7d9N16ZTIrbVALf65VB2ABbBG39dyAb7tz21PICJbSp2cd77UF7NFqEVkqohl/LkDw+7Apalmp0qAQT1Mgwi2cVxZMKUiciA6EqS+KNajf0A6olO2oEhZnGGY6b1LTg34/YfHdiIIZQqAfqbieruCGHRiSscC2ZE7iNreL/76f4JyIEUNkt6bQA29JsegxorLzQkpF7NKqZc=",
". 172800 IN DNSKEY 257 3 8 AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjFFVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoXbfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaD X6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpzW5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relSQageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulqQxA+Uk1ihz0=",
". 172800 IN DNSKEY 257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq7HrxRixHlFlExOLAJr5emLvN7SWXgnLh4+B5xQlNVz8Og8kvArMtNROxVQuCaSnIDdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLrjyBxWezF0jLHwVN8efS3rCj/EWgvIWgb9tarpVUDK/b58Da+sqqls3eNbuv7pr+eoZG+SrDK6nWeL3c6H5Apxz7LjVc1uTIdsIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6+cn8HFRm+2hM8AnXGXws9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU="
]
# fallback servers
fallbackservers = [
"8.8.8.8:53",
"8.8.4.4:53"
]
# address to bind to for the http API server, leave blank to disable
api = "127.0.0.1:8080"
# what kind of information should be logged, Log verbosity level [crit,error,warn,info,debug]
loglevel = "info"
# list of remote blocklists
blocklists = [
"http://mirror1.malwaredomains.com/files/justdomains",
"https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
"http://sysctl.org/cameleon/hosts",
"https://zeustracker.abuse.ch/blocklist.php?download=domainblocklist",
"https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt",
"https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt",
"http://hosts-file.net/ad_servers.txt",
"https://raw.githubusercontent.com/quidsup/notrack/master/trackers.txt"
]
# list of locations to recursively read blocklists from (warning, every file found is assumed to be a hosts-file or domain list)
blocklistdir = "bl"
# ipv4 address to forward blocked queries to
nullroute = "0.0.0.0"
# ipv6 address to forward blocked queries to
nullroutev6 = "::0"
# which clients allowed to make queries
accesslist = [
"0.0.0.0/0",
"::0/0"
]
# enables serving zone data from a hosts file, leave blank to disable
# the form of the entries in the /etc/hosts file are based on IETF RFC 952 which was updated by IETF RFC 1123.
hostsfile = ""
# query timeout for dns lookups in duration
timeout = "5s"
# connect timeout for dns lookups in duration
connecttimeout = "2s"
# default cache TTL in seconds
expire = 600
# cache size (total records in cache)
cachesize = 256000
# maximum recursion depth for nameservers
maxdepth = 30
# query based ratelimit per second, 0 for disable
ratelimit = 0
# client ip address based ratelimit per minute, 0 for disable
clientratelimit = 0
# manual blocklist entries
blocklist = []
# manual whitelist entries
whitelist = []
In function ValidityPeriod(), the rr.Expiration is smaller than utc, so the validation failed.
This is the error displayed
delete db directory, run the application
sdns v1.3.5 rev bb819c1
built by go1.20.6 (windows amd64)
INFO[10-28|17:15:39] Starting sdns... version=1.3.5
INFO[10-28|17:15:39] Loading config file... path=sdns.conf
INFO[10-28|17:15:39] Working directory path=db
INFO[10-28|17:15:39] Empty zones loaded zones=99
INFO[10-28|17:15:39] DNS server listening... net=udp addr=:53
INFO[10-28|17:15:39] DNS server listening... net=tcp addr=:53
INFO[10-28|17:15:39] API server listening... addr=127.0.0.1:8080
WARN[10-28|17:15:39] No trust anchor state file found or the state corrupted! New one will be generate. path=db\\trust-anchor.db
CRIT[10-28|17:15:39] Root zone keys not verified
No response
No response
Using SDNS attempts to resolve www.bbc.com IN soa
returns only www.bbc.com. 300 IN CNAME www-bbc-com.bbc.net.uk.
. The cname portion is not resolved.
dig @195.244.44.44 www.bbc.com SOA
; <<>> DiG 9.16.4 <<>> @195.244.44.44 www.bbc.com SOA
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50031
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: e522e60e7ea0e5a6d7e75a1d63375c960aa359e277106e12e8345836627d3d5dc6efedd95fff65c9 (good)
;; QUESTION SECTION:
;www.bbc.com. IN SOA
;; ANSWER SECTION:
www.bbc.com. 300 IN CNAME www-bbc-com.bbc.net.uk.
;; Query time: 286 msec
;; SERVER: 195.244.44.44#53(195.244.44.44)
;; WHEN: Tue Aug 11 07:23:20 PDT 2020
;; MSG SIZE rcvd: 120
Google does return results for this same query
dig @8.8.8.8 www.bbc.com soa
; <<>> DiG 9.16.4 <<>> @8.8.8.8 www.bbc.com soa
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 56105
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 1, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;www.bbc.com. IN SOA
;; ANSWER SECTION:
www.bbc.com. 160 IN CNAME www-bbc-com.bbc.net.uk.
www-bbc-com.bbc.net.uk. 98 IN CNAME bbc.map.fastly.net.
;; AUTHORITY SECTION:
fastly.net. 29 IN SOA ns1.fastly.net. hostmaster.fastly.com. 2017052201 3600 600 604800 30
;; Query time: 26 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Tue Aug 11 07:24:05 PDT 2020
;; MSG SIZE rcvd: 166
It appears in the logs that there is an attempt to forward the SOA request to www-bbc-com.bbc.net.uk
which gets cancelled, im guessing that is because another server successfully responds.
DBUG[08-11|07:15:59] Dial failed to upstream server query="www-bbc-com.bbc.net.uk. IN SOA" upstream=212.58.224.6:53 upstream server=ns0.tcams.bbc.co.uk. net=udp rtt=10s error="dial udp 212.58.224.6:53: operation was canceled" retried=0
Cloudflare has the same results as SDNS, so Im unsure which is the correct operation, can you help clarify?
dig @1.1.1.1 www.bbc.com soa +dnssec
; <<>> DiG 9.16.4 <<>> @1.1.1.1 www.bbc.com soa +dnssec
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 62365
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
;; QUESTION SECTION:
;www.bbc.com. IN SOA
;; ANSWER SECTION:
www.bbc.com. 300 IN CNAME www-bbc-com.bbc.net.uk.
;; Query time: 333 msec
;; SERVER: 1.1.1.1#53(1.1.1.1)
;; WHEN: Tue Aug 11 07:29:20 PDT 2020
;; MSG SIZE rcvd: 87
Is it possible to log client ips that are querying? I currently have loglevel at debug but don't see them.
t=2019-04-10T10:01:06+0000 lvl=info msg="Starting sdns..." version=0.2.2-rc1
panic: root zone DS not verified
goroutine 1 [running]:
main.(*Resolver).verifyRootKeys(0xc000264990, 0xc000272090, 0x1)
/go/src/github.com/semihalev/sdns/resolver.go:705 +0x3c5
main.(*Resolver).verifyDNSSEC(0xc000264990, 0xbdbbcc, 0x3, 0x114706e, 0x1, 0x114706e, 0x1, 0xc000272090, 0xc000204440, 0x2, ...)
/go/src/github.com/semihalev/sdns/resolver.go:744 +0x9a5
main.(*Resolver).Resolve(0xc000264990, 0xbdbbcc, 0x3, 0xc000214d80, 0xc00007e6c0, 0xcb5001, 0x1e, 0x0, 0xc00027e000, 0x0, ...)
/go/src/github.com/semihalev/sdns/resolver.go:171 +0x65f
main.(*Resolver).verifyDNSSEC(0xc000264990, 0xbdbbcc, 0x3, 0x114706e, 0x1, 0xbdb4f4, 0x1, 0xc000272000, 0xc0002042e0, 0x2, ...)
/go/src/github.com/semihalev/sdns/resolver.go:728 +0xb19
main.(*Resolver).Resolve(0xc000264990, 0xbdbbcc, 0x3, 0xc000215ac8, 0xc00007e6c0, 0xc000245001, 0x5, 0x0, 0x0, 0x0, ...)
/go/src/github.com/semihalev/sdns/resolver.go:171 +0x65f
main.(*Resolver).checkPriming(0xc000264990, 0xc000082840, 0xc0000c6400)
/go/src/github.com/semihalev/sdns/resolver.go:833 +0x1c9
main.NewResolver(0x10)
/go/src/github.com/semihalev/sdns/resolver.go:55 +0x124
main.NewHandler(0x4)
/go/src/github.com/semihalev/sdns/handler.go:33 +0x22
main.(*Server).Run(0xc0000827e0)
/go/src/github.com/semihalev/sdns/server.go:31 +0x37
main.start()
/go/src/github.com/semihalev/sdns/main.go:171 +0x2e3
main.main()
/go/src/github.com/semihalev/sdns/main.go:184 +0xb7
Does it block wildcard domains too?
*.domain.com
I couldn't find anything in code
Why is this function needed?
Hi,
I was wanting to be able to assign local network names for the ten computers in my house. I have them setup in the hosts file, but sdns doesn't look at it. Is there a way to get it to? Or, can we assign local names in the config file?
Hi,
Is there any chance to handle different lookups route to different upstrem DNS servers?
For example (meta config structure, not valid):
server {
label="main_dns";
ip=8.8.8.8, 8.8.4.4, 1.1.1.1;
exclude=".office.lan, .private";
}
server {
label="private_dns";
ip=192.168.45.1, 10.34.216.1;
include=".office.lan, .private";
}
Thank you!
Hi,
Will you plan this few features in the future?
Like: https://github.com/shawn1m/overture
Feature:
Thanks
If you run the tests with go test -race
a whole bunch of data races show up.
RFC 5011: Automated Updates of DNS Security (DNSSEC) Trust Anchors
is an RFC that tells you how to detect a root (KSK) key rollover happens and how to update your currently configured root-anchor.
I haven't implemented this myself, but it would be a nice addition.
=== RUN Test_ClientTimeout
client_test.go:29:
Error Trace: /root/sdns/middleware/resolver/client_test.go:29
Error: An error is expected but got nil.
Test: Test_ClientTimeout
--- FAIL: Test_ClientTimeout (0.01s)
build from source and use makefile:
make
on tests it failing
current source
No response
Linux
No response
It is not possible to build the example plugin, so it would work.
If I checkout both repos, and try to build it as the readme told me, I cant load the plugin.
Once I build the sdns
with CGO_ENABLED=1
I can load the plugin, but not it will not accept the function assert in line 111 in middelware.go (newFn, ok := newFuncSym.(func(cfg *config.Config) Handler)
)
# this is from github release
Apr 09 13:04:40 homeprinter sdns[3382185]: t=2024-04-09T13:04:40+0200 lvl=eror msg="Plugin open failed" plugin=wildcardhostsfile error="plugin: not implemented"
or
# this is selfbuild from source with CGO_ENABLED=1
Apr 09 13:25:22 homeprinter sdns[3399475]: t=2024-04-09T13:25:22+0200 lvl=eror msg="Plugin new function assert failed" plugin=wildcardhostsfile
1.3.6 from releases (without CGO_ENABLED=1)
also 1.3.6 self build with CGO_ENDABLED=1
No response
Linux
No response
If I start sdns and then query:
dig +cd dnssec.fail @localhost -p 1053
dig dnssec.fail @localhost -p 1053
The latter returns a response while the answer is actually invalid, but has been put in the cache because of +cd
(checking disabled).
Currently the sdns program does not support the forwarding of dns query functions. For example, I can forward all dns domain name queries to 1.1.1.1 and 8.8.8.8 for resolution.
The checkGLUE func is odd:
func (h *DNSHandler) checkGLUE(proto string, req, mesg *dns.Msg) *dns.Msg {
//check cname response
is this following a CNAME? Then a better name would be "additional processing".
I'm experimenting with the HTTP API and all requests get 404 errors.
Here is an example I found in test cases.
GET http://127.0.0.1:8080/dns-query?name=www.google.com&type=a&do=true&cd=true&edns_client_subnet=127.0.0.1/32
Is there any user guide on HTTP API?
if err == nil {
if reflect.DeepEqual(nsCache.Servers, servers) {
return nil, errLoopDetection
}
probably nicer and fast to not use reflect here - although most latency will come from the network anyway.
Please do not fallback on Google resolvers for privacy reasons, more importantly, this can not be overriden in the configuration file like the Root IPs/keys. I'd use either the local ones or a mix of 1.1.1.1/9.9.9.9/ instead. At the very least, make it a config entry.
Is there a way to associate whitelists and blacklists only to specific client ips?
The resolver does parallel lookup:
// Start lookup on each nameserver top-down, in interval
for index, server := range servers {
go L(server, len(servers)-1 == index)
....
This is nice for latency, but it breaks things at larger scales. For one you get an awful lot of queries which means you can get blocked (common in cloud providers).
The config.Interval mitigates against this which is good - how did you determine that interval?
I downloaded release v0.25 and started it up on linux, this error message appears:
WARN[01-23|15:54:35] DNSSEC verify failed (answer) query=". IN NS" error="no DNSKEY records found"
EROR[01-23|15:54:35] root servers update failed error="no DNSKEY records found"
I try to query it and get:
WARN[01-23|15:56:29] DNSSEC verify failed (delegation) query="com. IN NS" signer=. signed=com. error="no DNSKEY records found"
WARN[01-23|15:56:29] Resolve query failed query="example.com. IN A" error="no DNSKEY records found"
any ideas?
As said I'm just spot checking things, but it's good code and comprehensive from a DNS standpoint.
We're attempting to update Homebrew's version of Go at Homebrew/homebrew-core#83413.
While testing the new version, CI produced the following error:
==> brew install --build-from-source sdns
==> FAILED
==> Downloading https://github.com/semihalev/sdns/archive/v1.1.8.tar.gz
==> Downloading from https://codeload.github.com/semihalev/sdns/tar.gz/v1.1.8
Error: SHA256 mismatch
Expected: 9b5ca516e711764ba1e840e97b679ef3d299c60945920b84baed155fb8df8711
Actual: 5ffc8a72be67c3f9ce7200fd638ade6435ae177b09eda5eb149daadc66955ba6
I can update the sha256 associated with sdns, but CI will come back to me with the following error:
sdns:
* stable sha256 changed without the url/version also changing; please create an issue upstream to rule out malicious circumstances and to find out why the file changed.
Can I confirm that the change in checksum was intentional and that nothing is amiss here? Thanks.
Not sure how to fix or workaround this but go test
without IPv6 connectivity segfault because the reply is nil.
=== RUN Test_handler
--- FAIL: Test_handler (15.01s)
handler_test.go:84:
Error Trace: handler_test.go:84
Error: Received unexpected error:
read udp [::1]:53289->[::1]:53288: i/o timeout
Test: Test_handler
handler_test.go:85:
Error Trace: handler_test.go:85
Error: Expected value not to be nil.
Test: Test_handler
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x48 pc=0x1677715]
goroutine 14 [running]:
testing.tRunner.func1(0xc000265100)
/usr/local/go/src/testing/testing.go:792 +0x387
panic(0x172dc20, 0x1da3150)
/usr/local/go/src/runtime/panic.go:513 +0x1b9
github.com/semihalev/sdns.Test_handler(0xc000265100)
/Users/roberto/Src/Go/src/github.com/semihalev/sdns/handler_test.go:86 +0x405
testing.tRunner(0xc000265100, 0x1835f50)
/usr/local/go/src/testing/testing.go:827 +0xbf
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:878 +0x353
Process finished with exit code 1
I added a assert.NotNil(t, r)
in line 85 so now it is line 86 in handler_test.go
which segfaults.
I'm opening issue as I go through your code (hope you don't mind).
In resolver. go the rootserver and root6server are both hardcoded. Usually these things live in a root.hints file. I.e. would be good to factor that out.
Further more there is a thing called priming; i.e. you go to the root and you validate (and update) the hints file with the data you get from (some) root servers. Is something like this currently implemented?
I downloaded 0.2.2 from the releases page. Started it up. wrote a config file with this at the top:
version = "0.2.1"
I should add that the first time I run it, it doesn't work. I have to quit/restart and then it starts working.
How to use a separate zone file?
Documentation is severely lacking.
When I turn on my VPN, I would like sdns to use their DNS to lookup external sources. But, all I can find to do it is to have two configs, kill the server, and rerun it with a different config file. I would rather send a command to a TCP/IP port on sdns and change the server address. The sdns program already has an API server. Does that allow for it? Where is documentation for the API server?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.