Compare commits

...

33 Commits

Author SHA1 Message Date
Simon Zolin
e04ffde105 Merge: - auth: fix crash on showing Dashboard in UI if authentication is disabled
Close #1119

* commit 'ab24ab2f1aa5a93c35e68b46abbb3f03decbaeb0':
  - auth: fix crash on showing Dashboard in UI if authentication is disabled
2019-10-25 13:28:50 +03:00
Simon Zolin
ab24ab2f1a - auth: fix crash on showing Dashboard in UI if authentication is disabled 2019-10-25 11:01:29 +03:00
Simon Zolin
8323c0c4b6 Merge: * querylog: skip decoding errors
Close #753

* commit 'c74ae0d0e7ca03a592663025dc644397c1e31d57':
  * querylog: skip decoding errors
2019-10-24 16:00:07 +03:00
Simon Zolin
b28582d630 Merge: - querylog: writing to a file could stop randomly
Close #1115

* commit 'c04705364663e7141d39764fce8fd1dc1a4633ab':
  - querylog: writing to a file could stop randomly
2019-10-24 14:45:46 +03:00
Simon Zolin
c047053646 - querylog: writing to a file could stop randomly 2019-10-24 14:28:24 +03:00
Simon Zolin
41649418fc Merge: * dns: enable DNS message compression
Close #1109

* commit '3b443bc9c80c39483f28a042886f53feb4aeed96':
  * dns: enable DNS message compression
2019-10-23 20:06:18 +03:00
Simon Zolin
3b443bc9c8 * dns: enable DNS message compression 2019-10-23 20:02:42 +03:00
Simon Zolin
03c4793010 Merge: * don't show "sign out" button if authorization is disabled
Close #1093

* commit '49e535336be47cccee07f2e1b05f0d514aa91aa7':
  * changelog
  + client: get profile info
  * minor
  + GET /control/profile
2019-10-23 19:23:37 +03:00
Simon Zolin
9d29fdea4b Merge: * filters: don't fail on filter update when filter file doesn't exist
Close #1112

* commit '0737354f534385c3c2036e15d65554bcb7090fb4':
  * filters: don't fail on filter update when filter file doesn't exist
2019-10-23 19:19:50 +03:00
Simon Zolin
49e535336b * changelog 2019-10-23 19:19:04 +03:00
Ildar Kamalov
2a2647dc3f + client: get profile info 2019-10-23 18:43:39 +03:00
Simon Zolin
0ede2b13c9 * minor 2019-10-23 18:43:39 +03:00
Simon Zolin
c185f6826a + GET /control/profile
* openapi: get /profile

* auth: store user names along with sessions
2019-10-23 18:43:35 +03:00
Simon Zolin
e8bb0fdcb7 Merge: - /control/version.json: don't show error message if auto-update is disabled
Close #1083

* commit '5bcd1545a8044aff7f35179e7a324ec9b4ad1c2e':
  - /control/version.json: don't show error message if auto-update is disabled
2019-10-23 14:21:02 +03:00
Simon Zolin
0737354f53 * filters: don't fail on filter update when filter file doesn't exist 2019-10-23 14:19:43 +03:00
Simon Zolin
c74ae0d0e7 * querylog: skip decoding errors
We read line from file and pass it to a JSON decoder.
JSON decoder is now a local object.
2019-10-22 19:16:04 +03:00
Simon Zolin
15e6311c63 Merge: * dnsfilter: windows: store rules in memory
Close #1088

* commit '6ba1d857ac7961ed5a97a85a328398296c520273':
  * dnsfilter: windows: store rules in memory
  * minor
2019-10-22 16:25:02 +03:00
Simon Zolin
6ba1d857ac * dnsfilter: windows: store rules in memory
* dnsfilter: ignore cosmetic rules
2019-10-22 16:15:51 +03:00
Simon Zolin
67f31ccf43 * minor 2019-10-22 16:15:51 +03:00
Simon Zolin
a52c4b4362 Merge: * rdns,whois: get client info for all question types (not just A/AAAA)
Close #1103

* commit '235b198ef97d7a46ab6d76a4074ec589fc0148eb':
  * rdns,whois: recheck IP addresses after some time
  * rdns,whois: get client info for all question types (not just A/AAAA)
2019-10-22 14:37:22 +03:00
Simon Zolin
235b198ef9 * rdns,whois: recheck IP addresses after some time 2019-10-22 13:11:22 +03:00
Simon Zolin
ddfd53bf06 * rdns,whois: get client info for all question types (not just A/AAAA) 2019-10-22 13:10:40 +03:00
Simon Zolin
ffffd74a6e Merge: * TLS: don't print certificate data
Close #1107

* commit '76c9e61199a70ec1455a630ab896698b4435751c':
  * TLS: don't print certificate data
2019-10-22 12:26:36 +03:00
Ildar Kamalov
b870db249e Merge: - client: set i18n language only from available languages
Closes #1082

* commit '3269766ea7e2340a658cc13343f9d3cc6565b7dc':
  - client: use lowercase lang codes
  - client: set i18n language only from available languages
2019-10-22 12:24:41 +03:00
Simon Zolin
76c9e61199 * TLS: don't print certificate data 2019-10-22 12:09:32 +03:00
Simon Zolin
0579e9bf99 Merge: - windows: dns: fix reconfigure procedure
* commit 'b7b32e2f01649500ca8224ffd05b24cc793982a1':
  - windows: dns: fix reconfigure procedure
2019-10-22 12:05:39 +03:00
Simon Zolin
c70389eb30 Merge: * /control/stats: set Content-Type: application/json
Close #1086

* commit '8985faa95d5414393165097fde032c02ff560b32':
  * minor
  * /control/stats: set Content-Type: application/json
2019-10-22 11:50:00 +03:00
Simon Zolin
5bcd1545a8 - /control/version.json: don't show error message if auto-update is disabled 2019-10-21 18:21:05 +03:00
Simon Zolin
8985faa95d * minor 2019-10-21 17:50:46 +03:00
Simon Zolin
2dc31bee20 * /control/stats: set Content-Type: application/json 2019-10-21 17:50:44 +03:00
Ildar Kamalov
3269766ea7 - client: use lowercase lang codes 2019-10-21 16:03:17 +03:00
Simon Zolin
b7b32e2f01 - windows: dns: fix reconfigure procedure 2019-10-21 15:58:14 +03:00
Ildar Kamalov
bd1ee48a4f - client: set i18n language only from available languages 2019-10-21 13:09:52 +03:00
23 changed files with 362 additions and 170 deletions

View File

@@ -54,6 +54,7 @@ Contents:
* Log-in page
* API: Log in
* API: Log out
* API: Get current user info
## Relations between subsystems
@@ -1207,7 +1208,7 @@ YAML configuration:
Session DB file:
session="..." expire=123456
session="..." user=name expire=123456
...
Session data is SHA(random()+name+password).
@@ -1270,3 +1271,20 @@ Response:
302 Found
Location: /login.html
Set-Cookie: session=...; Expires=Thu, 01 Jan 1970 00:00:00 GMT
### API: Get current user info
Request:
GET /control/profile
Response:
200 OK
{
"name":"..."
}
If no client is configured then authentication is disabled and server sends an empty response.

View File

@@ -213,6 +213,21 @@ export const getClients = () => async (dispatch) => {
}
};
export const getProfileRequest = createAction('GET_PROFILE_REQUEST');
export const getProfileFailure = createAction('GET_PROFILE_FAILURE');
export const getProfileSuccess = createAction('GET_PROFILE_SUCCESS');
export const getProfile = () => async (dispatch) => {
dispatch(getProfileRequest());
try {
const profile = await apiClient.getProfile();
dispatch(getProfileSuccess(profile));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getProfileFailure());
}
};
export const dnsStatusRequest = createAction('DNS_STATUS_REQUEST');
export const dnsStatusFailure = createAction('DNS_STATUS_FAILURE');
export const dnsStatusSuccess = createAction('DNS_STATUS_SUCCESS');
@@ -224,6 +239,7 @@ export const getDnsStatus = () => async (dispatch) => {
dispatch(dnsStatusSuccess(dnsStatus));
dispatch(getVersion());
dispatch(getTlsStatus());
dispatch(getProfile());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(dnsStatusFailure());

View File

@@ -525,6 +525,14 @@ class Api {
};
return this.makeRequest(path, method, config);
}
// Profile
GET_PROFILE = { path: 'profile', method: 'GET' };
getProfile() {
const { path, method } = this.GET_PROFILE;
return this.makeRequest(path, method);
}
}
const apiClient = new Api();

View File

@@ -60,9 +60,11 @@ class Header extends Component {
/>
<div className="header__column">
<div className="header__right">
<a href="/control/logout" className="btn btn-sm btn-outline-secondary">
<Trans>sign_out</Trans>
</a>
{!dashboard.processingProfile && dashboard.name &&
<a href="/control/logout" className="btn btn-sm btn-outline-secondary">
<Trans>sign_out</Trans>
</a>
}
</div>
</div>
</div>

View File

@@ -30,10 +30,6 @@ export const REPOSITORY = {
export const PRIVACY_POLICY_LINK = 'https://adguard.com/privacy/home.html';
export const LANGUAGES = [
{
key: 'en',
name: 'English',
},
{
key: 'da',
name: 'Dansk',
@@ -46,6 +42,10 @@ export const LANGUAGES = [
key: 'nl',
name: 'Dutch',
},
{
key: 'en',
name: 'English',
},
{
key: 'es',
name: 'Español',

View File

@@ -3,6 +3,8 @@ import { reactI18nextModule } from 'react-i18next';
import { initReactI18n } from 'react-i18next/hooks';
import langDetect from 'i18next-browser-languagedetector';
import { DEFAULT_LANGUAGE } from './helpers/constants';
import vi from './__locales/vi.json';
import en from './__locales/en.json';
import ru from './__locales/ru.json';
@@ -49,16 +51,16 @@ const resources = {
sv: {
translation: sv,
},
'pt-BR': {
'pt-br': {
translation: ptBR,
},
'zh-TW': {
'zh-tw': {
translation: zhTW,
},
bg: {
translation: bg,
},
'zh-CN': {
'zh-cn': {
translation: zhCN,
},
cs: {
@@ -85,7 +87,7 @@ const resources = {
pl: {
translation: pl,
},
'pt-PT': {
'pt-pt': {
translation: ptPT,
},
sk: {
@@ -99,22 +101,29 @@ const resources = {
},
};
const availableLanguages = Object.keys(resources);
i18n
.use(langDetect)
.use(initReactI18n)
.use(reactI18nextModule) // passes i18n down to react-i18next
.use(reactI18nextModule)
.init({
resources,
fallbackLng: 'en',
keySeparator: false, // we use content as keys
nsSeparator: false, // Fix character in content
returnEmptyString: false, // count empty value as invalid
lowerCaseLng: true,
fallbackLng: DEFAULT_LANGUAGE,
keySeparator: false,
nsSeparator: false,
returnEmptyString: false,
interpolation: {
escapeValue: false, // not needed for react!!
escapeValue: false,
},
react: {
wait: true,
},
}, () => {
if (!availableLanguages.includes(i18n.language)) {
i18n.changeLanguage(DEFAULT_LANGUAGE);
}
});
export default i18n;

View File

@@ -189,6 +189,14 @@ const dashboard = handleActions(
processingDnsSettings: false,
};
},
[actions.getProfileRequest]: state => ({ ...state, processingProfile: true }),
[actions.getProfileFailure]: state => ({ ...state, processingProfile: false }),
[actions.getProfileSuccess]: (state, { payload }) => ({
...state,
name: payload.name,
processingProfile: false,
}),
},
{
processing: true,
@@ -198,6 +206,7 @@ const dashboard = handleActions(
processingClients: true,
processingUpdate: false,
processingDnsSettings: true,
processingProfile: true,
upstreamDns: '',
bootstrapDns: '',
allServers: false,
@@ -209,6 +218,7 @@ const dashboard = handleActions(
dnsVersion: '',
clients: [],
autoClients: [],
name: '',
},
);

View File

@@ -13,6 +13,7 @@ import (
"net"
"net/http"
"os"
"runtime"
"strings"
"sync"
"sync/atomic"
@@ -772,18 +773,31 @@ func (d *Dnsfilter) initFiltering(filters map[int]string) error {
list = &urlfilter.StringRuleList{
ID: 0,
RulesText: dataOrFilePath,
IgnoreCosmetic: false,
IgnoreCosmetic: true,
}
} else if !fileExists(dataOrFilePath) {
list = &urlfilter.StringRuleList{
ID: id,
IgnoreCosmetic: false,
IgnoreCosmetic: true,
}
} else if runtime.GOOS == "windows" {
// On Windows we don't pass a file to urlfilter because
// it's difficult to update this file while it's being used.
data, err := ioutil.ReadFile(dataOrFilePath)
if err != nil {
return fmt.Errorf("ioutil.ReadFile(): %s: %s", dataOrFilePath, err)
}
list = &urlfilter.StringRuleList{
ID: id,
RulesText: string(data),
IgnoreCosmetic: true,
}
} else {
var err error
list, err = urlfilter.NewFileRuleList(id, dataOrFilePath, false)
list, err = urlfilter.NewFileRuleList(id, dataOrFilePath, true)
if err != nil {
return fmt.Errorf("urlfilter.NewFileRuleList(): %s: %s", dataOrFilePath, err)
}

View File

@@ -5,6 +5,7 @@ import (
"errors"
"net"
"net/http"
"runtime"
"strings"
"sync"
"time"
@@ -302,6 +303,12 @@ func (s *Server) Reconfigure(config *ServerConfig) error {
if err != nil {
return errorx.Decorate(err, "could not reconfigure the server")
}
// On some Windows versions the UDP port we've just closed in proxy.Stop() doesn't get actually closed right away.
if runtime.GOOS == "windows" {
time.Sleep(1 * time.Second)
}
err = s.startInternal(config)
if err != nil {
return errorx.Decorate(err, "could not reconfigure the server")
@@ -425,7 +432,6 @@ func (s *Server) handleDNSRequest(p *proxy.Proxy, d *proxy.DNSContext) error {
if res.Reason == dnsfilter.ReasonRewrite && len(res.CanonName) != 0 {
d.Req.Question[0] = originalQuestion
d.Res.Question[0] = originalQuestion
if len(d.Res.Answer) != 0 {
answer = append(answer, d.Res.Answer...) // host -> IP
@@ -434,6 +440,10 @@ func (s *Server) handleDNSRequest(p *proxy.Proxy, d *proxy.DNSContext) error {
}
}
if d.Res != nil {
d.Res.Compress = true // some devices require DNS message compression
}
shouldLog := true
msg := d.Req

View File

@@ -20,10 +20,44 @@ import (
const cookieTTL = 365 * 24 // in hours
const expireTime = 30 * 24 // in hours
type session struct {
userName string
expire uint32 // expiration time (in seconds)
}
/*
expire byte[4]
name_len byte[2]
name byte[]
*/
func (s *session) serialize() []byte {
var data []byte
data = make([]byte, 4+2+len(s.userName))
binary.BigEndian.PutUint32(data[0:4], s.expire)
binary.BigEndian.PutUint16(data[4:6], uint16(len(s.userName)))
copy(data[6:], []byte(s.userName))
return data
}
func (s *session) deserialize(data []byte) bool {
if len(data) < 4+2 {
return false
}
s.expire = binary.BigEndian.Uint32(data[0:4])
nameLen := binary.BigEndian.Uint16(data[4:6])
data = data[6:]
if len(data) < int(nameLen) {
return false
}
s.userName = string(data)
return true
}
// Auth - global object
type Auth struct {
db *bbolt.DB
sessions map[string]uint32 // session -> expiration time (in seconds)
sessions map[string]*session // session name -> session data
lock sync.Mutex
users []User
}
@@ -37,7 +71,7 @@ type User struct {
// InitAuth - create a global object
func InitAuth(dbFilename string, users []User) *Auth {
a := Auth{}
a.sessions = make(map[string]uint32)
a.sessions = make(map[string]*session)
rand.Seed(time.Now().UTC().Unix())
var err error
a.db, err = bbolt.Open(dbFilename, 0644, nil)
@@ -56,6 +90,10 @@ func (a *Auth) Close() {
_ = a.db.Close()
}
func bucketName() []byte {
return []byte("sessions-2")
}
// load sessions from file, remove expired sessions
func (a *Auth) loadSessions() {
tx, err := a.db.Begin(true)
@@ -67,16 +105,22 @@ func (a *Auth) loadSessions() {
_ = tx.Rollback()
}()
bkt := tx.Bucket([]byte("sessions"))
bkt := tx.Bucket(bucketName())
if bkt == nil {
return
}
removed := 0
if tx.Bucket([]byte("sessions")) != nil {
_ = tx.DeleteBucket([]byte("sessions"))
removed = 1
}
now := uint32(time.Now().UTC().Unix())
forEach := func(k, v []byte) error {
i := binary.BigEndian.Uint32(v)
if i <= now {
s := session{}
if !s.deserialize(v) || s.expire <= now {
err = bkt.Delete(k)
if err != nil {
log.Error("Auth: bbolt.Delete: %s", err)
@@ -85,7 +129,8 @@ func (a *Auth) loadSessions() {
}
return nil
}
a.sessions[hex.EncodeToString(k)] = i
a.sessions[hex.EncodeToString(k)] = &s
return nil
}
_ = bkt.ForEach(forEach)
@@ -99,11 +144,15 @@ func (a *Auth) loadSessions() {
}
// store session data in file
func (a *Auth) storeSession(data []byte, expire uint32) {
func (a *Auth) addSession(data []byte, s *session) {
a.lock.Lock()
a.sessions[hex.EncodeToString(data)] = expire
a.sessions[hex.EncodeToString(data)] = s
a.lock.Unlock()
a.storeSession(data, s)
}
// store session data in file
func (a *Auth) storeSession(data []byte, s *session) {
tx, err := a.db.Begin(true)
if err != nil {
log.Error("Auth: bbolt.Begin: %s", err)
@@ -113,15 +162,12 @@ func (a *Auth) storeSession(data []byte, expire uint32) {
_ = tx.Rollback()
}()
bkt, err := tx.CreateBucketIfNotExists([]byte("sessions"))
bkt, err := tx.CreateBucketIfNotExists(bucketName())
if err != nil {
log.Error("Auth: bbolt.CreateBucketIfNotExists: %s", err)
return
}
var val []byte
val = make([]byte, 4)
binary.BigEndian.PutUint32(val, expire)
err = bkt.Put(data, val)
err = bkt.Put(data, s.serialize())
if err != nil {
log.Error("Auth: bbolt.Put: %s", err)
return
@@ -147,7 +193,7 @@ func (a *Auth) removeSession(sess []byte) {
_ = tx.Rollback()
}()
bkt := tx.Bucket([]byte("sessions"))
bkt := tx.Bucket(bucketName())
if bkt == nil {
log.Error("Auth: bbolt.Bucket")
return
@@ -174,12 +220,12 @@ func (a *Auth) CheckSession(sess string) int {
update := false
a.lock.Lock()
expire, ok := a.sessions[sess]
s, ok := a.sessions[sess]
if !ok {
a.lock.Unlock()
return -1
}
if expire <= now {
if s.expire <= now {
delete(a.sessions, sess)
key, _ := hex.DecodeString(sess)
a.removeSession(key)
@@ -188,17 +234,17 @@ func (a *Auth) CheckSession(sess string) int {
}
newExpire := now + expireTime*60*60
if expire/(24*60*60) != newExpire/(24*60*60) {
if s.expire/(24*60*60) != newExpire/(24*60*60) {
// update expiration time once a day
update = true
a.sessions[sess] = newExpire
s.expire = newExpire
}
a.lock.Unlock()
if update {
key, _ := hex.DecodeString(sess)
a.storeSession(key, expire)
a.storeSession(key, s)
}
return 0
@@ -238,8 +284,10 @@ func httpCookie(req loginJSON) string {
expstr = expstr[:len(expstr)-len("UTC")] // "UTC" -> "GMT"
expstr += "GMT"
expireSess := uint32(now.Unix()) + expireTime*60*60
config.auth.storeSession(sess, expireSess)
s := session{}
s.userName = u.Name
s.expire = uint32(now.Unix()) + expireTime*60*60
config.auth.addSession(sess, &s)
return fmt.Sprintf("session=%s; Path=/; HttpOnly; Expires=%s", hex.EncodeToString(sess), expstr)
}
@@ -402,6 +450,35 @@ func (a *Auth) UserFind(login string, password string) User {
return User{}
}
// GetCurrentUser - get the current user
func (a *Auth) GetCurrentUser(r *http.Request) User {
cookie, err := r.Cookie("session")
if err != nil {
// there's no Cookie, check Basic authentication
user, pass, ok := r.BasicAuth()
if ok {
u := config.auth.UserFind(user, pass)
return u
}
return User{}
}
a.lock.Lock()
s, ok := a.sessions[cookie.Value]
if !ok {
a.lock.Unlock()
return User{}
}
for _, u := range a.users {
if u.Name == s.userName {
a.lock.Unlock()
return u
}
}
a.lock.Unlock()
return User{}
}
// GetUsers - get users
func (a *Auth) GetUsers() []User {
a.lock.Lock()

View File

@@ -28,6 +28,7 @@ func TestAuth(t *testing.T) {
User{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"},
}
a := InitAuth(fn, nil)
s := session{}
user := User{Name: "name"}
a.UserAdd(&user, "password")
@@ -38,12 +39,16 @@ func TestAuth(t *testing.T) {
sess := getSession(&users[0])
sessStr := hex.EncodeToString(sess)
now := time.Now().UTC().Unix()
// check expiration
a.storeSession(sess, uint32(time.Now().UTC().Unix()))
s.expire = uint32(now)
a.addSession(sess, &s)
assert.True(t, a.CheckSession(sessStr) == 1)
// add session with TTL = 2 sec
a.storeSession(sess, uint32(time.Now().UTC().Unix()+2))
s = session{}
s.expire = uint32(now + 2)
a.addSession(sess, &s)
assert.True(t, a.CheckSession(sessStr) == 0)
a.Close()
@@ -53,6 +58,9 @@ func TestAuth(t *testing.T) {
// the session is still alive
assert.True(t, a.CheckSession(sessStr) == 0)
// reset our expiration time because CheckSession() has just updated it
s.expire = uint32(now + 2)
a.storeSession(sess, &s)
a.Close()
u := a.UserFind("name", "password")

View File

@@ -377,6 +377,23 @@ func checkDNS(input string, bootstrap []string) error {
return nil
}
type profileJSON struct {
Name string `json:"name"`
}
func handleGetProfile(w http.ResponseWriter, r *http.Request) {
pj := profileJSON{}
u := config.auth.GetCurrentUser(r)
pj.Name = u.Name
data, err := json.Marshal(pj)
if err != nil {
httpError(w, http.StatusInternalServerError, "json.Marshal: %s", err)
return
}
_, _ = w.Write(data)
}
// --------------
// DNS-over-HTTPS
// --------------
@@ -416,6 +433,7 @@ func registerControlHandlers() {
httpRegister(http.MethodGet, "/control/access/list", handleAccessList)
httpRegister(http.MethodPost, "/control/access/set", handleAccessSet)
httpRegister("GET", "/control/profile", handleGetProfile)
RegisterFilteringHandlers()
RegisterTLSHandlers()

View File

@@ -156,7 +156,7 @@ func handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
}
func verifyCertChain(data *tlsConfigStatus, certChain string, serverName string) error {
log.Tracef("got certificate: %s", certChain)
log.Tracef("TLS: got certificate: %d bytes", len(certChain))
// now do a more extended validation
var certs []*pem.Block // PEM-encoded certificates

View File

@@ -59,7 +59,6 @@ type getVersionJSONRequest struct {
func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) {
if config.disableUpdate {
httpError(w, http.StatusInternalServerError, "New app version check is disabled by user")
return
}

View File

@@ -13,7 +13,6 @@ import (
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/log"
"github.com/joomcode/errorx"
"github.com/miekg/dns"
)
type dnsContext struct {
@@ -137,11 +136,6 @@ func isPublicIP(ip net.IP) bool {
}
func onDNSRequest(d *proxy.DNSContext) {
qType := d.Req.Question[0].Qtype
if qType != dns.TypeA && qType != dns.TypeAAAA {
return
}
ip := dnsforward.GetIPString(d.Addr)
if ip == "" {
// This would be quite weird if we get here

View File

@@ -217,8 +217,12 @@ func refreshFilters() (int, error) {
// . For each filter run the download and checksum check operation
// . For each filter:
// . If filter data hasn't changed, just set new update time on file
// . If filter data has changed: rename the old file, store the new data on disk
// . Pass new filters to dnsfilter object
// . If filter data has changed:
// . rename the old file (1.txt -> 1.txt.old)
// . store the new data on disk (1.txt)
// . Pass new filters to dnsfilter object - it analyzes new data while the old filters are still active
// . dnsfilter activates new filters
// . Remove the old filter files (1.txt.old)
func refreshFiltersIfNecessary(force bool) int {
var updateFilters []filter
var updateFlags []bool // 'true' if filter data has changed
@@ -431,7 +435,10 @@ func (filter *filter) save() error {
func (filter *filter) saveAndBackupOld() error {
filterFilePath := filter.Path()
_ = os.Rename(filterFilePath, filterFilePath+".old")
err := os.Rename(filterFilePath, filterFilePath+".old")
if err != nil && !os.IsNotExist(err) {
return err
}
return filter.save()
}

View File

@@ -1,12 +1,13 @@
package home
import (
"encoding/binary"
"fmt"
"strings"
"sync"
"time"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/cache"
"github.com/AdguardTeam/golibs/log"
"github.com/miekg/dns"
)
@@ -18,12 +19,14 @@ const (
// RDNS - module context
type RDNS struct {
clients *clientsContainer
ipChannel chan string // pass data from DNS request handling thread to rDNS thread
// contains IP addresses of clients to be resolved by rDNS
// if IP address couldn't be resolved, it stays here forever to prevent further attempts to resolve the same IP
ips map[string]bool
lock sync.Mutex // synchronize access to 'ips'
upstream upstream.Upstream // Upstream object for our own DNS server
ipChannel chan string // pass data from DNS request handling thread to rDNS thread
upstream upstream.Upstream // Upstream object for our own DNS server
// Contains IP addresses of clients to be resolved by rDNS
// If IP address is resolved, it stays here while it's inside Clients.
// If it's removed from Clients, this IP address will be resolved once again.
// If IP address couldn't be resolved, it stays here for some time to prevent further attempts to resolve the same IP.
ipAddrs cache.Cache
}
// InitRDNS - create module context
@@ -47,7 +50,11 @@ func InitRDNS(clients *clientsContainer) *RDNS {
return nil
}
r.ips = make(map[string]bool)
cconf := cache.Config{}
cconf.EnableLRU = true
cconf.MaxCount = 10000
r.ipAddrs = cache.New(cconf)
r.ipChannel = make(chan string, 256)
go r.workerLoop()
return &r
@@ -55,25 +62,30 @@ func InitRDNS(clients *clientsContainer) *RDNS {
// Begin - add IP address to rDNS queue
func (r *RDNS) Begin(ip string) {
now := uint64(time.Now().Unix())
expire := r.ipAddrs.Get([]byte(ip))
if len(expire) != 0 {
exp := binary.BigEndian.Uint64(expire)
if exp > now {
return
}
// TTL expired
}
expire = make([]byte, 8)
const ttl = 12 * 60 * 60
binary.BigEndian.PutUint64(expire, now+ttl)
_ = r.ipAddrs.Set([]byte(ip), expire)
if r.clients.Exists(ip, ClientSourceRDNS) {
return
}
// add IP to ips, if not exists
r.lock.Lock()
defer r.lock.Unlock()
_, ok := r.ips[ip]
if ok {
return
}
r.ips[ip] = true
log.Tracef("Adding %s for rDNS resolve", ip)
log.Tracef("rDNS: adding %s", ip)
select {
case r.ipChannel <- ip:
//
default:
log.Tracef("rDNS queue is full")
log.Tracef("rDNS: queue is full")
}
}
@@ -132,10 +144,6 @@ func (r *RDNS) workerLoop() {
continue
}
r.lock.Lock()
delete(r.ips, ip)
r.lock.Unlock()
_, _ = config.clients.AddHost(ip, host, ClientSourceRDNS)
}
}

View File

@@ -1,13 +1,14 @@
package home
import (
"encoding/binary"
"fmt"
"io/ioutil"
"net"
"strings"
"sync"
"time"
"github.com/AdguardTeam/golibs/cache"
"github.com/AdguardTeam/golibs/log"
)
@@ -20,10 +21,13 @@ const (
// Whois - module context
type Whois struct {
clients *clientsContainer
ips map[string]bool
lock sync.Mutex
ipChan chan string
timeoutMsec uint
// Contains IP addresses of clients
// An active IP address is resolved once again after it expires.
// If IP address couldn't be resolved, it stays here for some time to prevent further attempts to resolve the same IP.
ipAddrs cache.Cache
}
// Create module context
@@ -31,7 +35,12 @@ func initWhois(clients *clientsContainer) *Whois {
w := Whois{}
w.timeoutMsec = 5000
w.clients = clients
w.ips = make(map[string]bool)
cconf := cache.Config{}
cconf.EnableLRU = true
cconf.MaxCount = 10000
w.ipAddrs = cache.New(cconf)
w.ipChan = make(chan string, 255)
go w.workerLoop()
return &w
@@ -186,14 +195,19 @@ func (w *Whois) process(ip string) [][]string {
// Begin - begin requesting WHOIS info
func (w *Whois) Begin(ip string) {
w.lock.Lock()
_, found := w.ips[ip]
if found {
w.lock.Unlock()
return
now := uint64(time.Now().Unix())
expire := w.ipAddrs.Get([]byte(ip))
if len(expire) != 0 {
exp := binary.BigEndian.Uint64(expire)
if exp > now {
return
}
// TTL expired
}
w.ips[ip] = true
w.lock.Unlock()
expire = make([]byte, 8)
const ttl = 12 * 60 * 60
binary.BigEndian.PutUint64(expire, now+ttl)
_ = w.ipAddrs.Set([]byte(ip), expire)
log.Debug("Whois: adding %s", ip)
select {

View File

@@ -1,6 +1,23 @@
# AdGuard Home API Change Log
## v0.99.1: API changes
### API: Get current user info: GET /control/profile
Request:
GET /control/profile
Response:
200 OK
{
"name":"..."
}
## v0.99: incompatible API changes
* A note about web user authentication

View File

@@ -970,6 +970,18 @@ paths:
302:
description: OK
/profile:
get:
tags:
- global
operationId: getProfile
summary: ""
responses:
200:
description: OK
schema:
$ref: "#/definitions/ProfileInfo"
definitions:
ServerStatus:
type: "object"
@@ -1559,6 +1571,14 @@ definitions:
description: "Network interfaces dictionary (key is the interface name)"
additionalProperties:
$ref: "#/definitions/NetInterface"
ProfileInfo:
type: "object"
description: "Information about the current user"
properties:
name:
type: "string"
Client:
type: "object"
description: "Client information"

View File

@@ -1,16 +1,16 @@
package querylog
import (
"bufio"
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"time"
"github.com/AdguardTeam/golibs/log"
"github.com/go-test/deep"
)
const enableGzip = false
@@ -61,12 +61,7 @@ func (l *queryLog) flushToFile(buffer []*logEntry) error {
elapsed := time.Since(start)
log.Debug("%d elements serialized via json in %v: %d kB, %v/entry, %v/entry", len(buffer), elapsed, b.Len()/1024, float64(b.Len())/float64(len(buffer)), elapsed/time.Duration(len(buffer)))
err := checkBuffer(buffer, b)
if err != nil {
log.Error("failed to check buffer: %s", err)
return err
}
var err error
var zb bytes.Buffer
filename := l.logFile
@@ -113,34 +108,6 @@ func (l *queryLog) flushToFile(buffer []*logEntry) error {
return nil
}
func checkBuffer(buffer []*logEntry, b bytes.Buffer) error {
l := len(buffer)
d := json.NewDecoder(&b)
i := 0
for d.More() {
entry := &logEntry{}
err := d.Decode(entry)
if err != nil {
log.Error("Failed to decode: %s", err)
return err
}
if diff := deep.Equal(entry, buffer[i]); diff != nil {
log.Error("decoded buffer differs: %s", diff)
return fmt.Errorf("decoded buffer differs: %s", diff)
}
i++
}
if i != l {
err := fmt.Errorf("check fail: %d vs %d entries", l, i)
log.Error("%v", err)
return err
}
log.Debug("check ok: %d entries", i)
return nil
}
func (l *queryLog) rotate() error {
from := l.logFile
to := l.logFile + ".1"
@@ -181,7 +148,7 @@ type Reader struct {
ql *queryLog
f *os.File
jd *json.Decoder
reader *bufio.Reader // reads file line by line
now time.Time
validFrom int64 // UNIX time (ns)
olderThan int64 // UNIX time (ns)
@@ -250,7 +217,6 @@ func (r *Reader) BeginRead(olderThan time.Time, count uint64) {
}
r.filePrepared = false
r.searching = false
r.jd = nil
}
// BeginReadPrev - start reading the previous data chunk
@@ -280,7 +246,6 @@ func (r *Reader) BeginReadPrev(olderThan time.Time, count uint64) {
r.filePrepared = true
r.searching = false
r.jd = nil
}
// Perform binary seek
@@ -314,27 +279,17 @@ func (fs *fileSeeker) seekBinary(cur uint64) int32 {
// Seek to a new line
func (r *Reader) seekToNewLine() bool {
b := make([]byte, maxEntrySize*2)
_, err := r.f.Read(b)
r.reader = bufio.NewReader(r.f)
b, err := r.reader.ReadBytes('\n')
if err != nil {
r.reader = nil
log.Error("QueryLog: file.Read: %s: %s", r.files[r.ifile], err)
return false
}
off := bytes.IndexByte(b, '\n') + 1
if off == 0 {
log.Error("QueryLog: Can't find a new line: %s", r.files[r.ifile])
return false
}
off := len(b)
r.fpos += uint64(off)
log.Debug("QueryLog: seek: %x (+%d)", r.fpos, off)
_, err = r.f.Seek(int64(r.fpos), io.SeekStart)
if err != nil {
log.Error("QueryLog: file.Seek: %s: %s", r.files[r.ifile], err)
return false
}
return true
}
@@ -405,7 +360,6 @@ func (r *Reader) prepareRead() bool {
// Next - return the next entry or nil if reading is finished
func (r *Reader) Next() *logEntry { // nolint
var err error
for {
// open file if needed
if r.f == nil {
@@ -425,30 +379,26 @@ func (r *Reader) Next() *logEntry { // nolint
r.filePrepared = true
}
// open decoder if needed
if r.jd == nil {
r.jd = json.NewDecoder(r.f)
}
// check if there's data
if !r.jd.More() {
r.jd = nil
// open decoder
b, err := r.reader.ReadBytes('\n')
if err != nil {
return nil
}
strReader := strings.NewReader(string(b))
jd := json.NewDecoder(strReader)
// read data
var entry logEntry
err = r.jd.Decode(&entry)
err = jd.Decode(&entry)
if err != nil {
log.Error("QueryLog: Failed to decode: %s", err)
r.jd = nil
return nil
log.Debug("QueryLog: Failed to decode: %s", err)
continue
}
t := entry.Time.UnixNano()
if r.searching {
r.jd = nil
r.reader = nil
rr := r.fseeker.seekBinary(uint64(t))
r.fpos = r.fseeker.pos
if rr < 0 {

View File

@@ -41,6 +41,7 @@ func (s *statsCtx) handleStats(w http.ResponseWriter, r *http.Request) {
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(data)
}
@@ -94,16 +95,8 @@ func (s *statsCtx) initWeb() {
return
}
s.conf.HTTPRegister("GET", "/control/stats", func(w http.ResponseWriter, r *http.Request) {
s.handleStats(w, r)
})
s.conf.HTTPRegister("POST", "/control/stats_reset", func(w http.ResponseWriter, r *http.Request) {
s.handleStatsReset(w, r)
})
s.conf.HTTPRegister("POST", "/control/stats_config", func(w http.ResponseWriter, r *http.Request) {
s.handleStatsConfig(w, r)
})
s.conf.HTTPRegister("GET", "/control/stats_info", func(w http.ResponseWriter, r *http.Request) {
s.handleStatsInfo(w, r)
})
s.conf.HTTPRegister("GET", "/control/stats", s.handleStats)
s.conf.HTTPRegister("POST", "/control/stats_reset", s.handleStatsReset)
s.conf.HTTPRegister("POST", "/control/stats_config", s.handleStatsConfig)
s.conf.HTTPRegister("GET", "/control/stats_info", s.handleStatsInfo)
}

View File

@@ -346,7 +346,7 @@ func (s *statsCtx) loadUnitFromDB(tx *bolt.Tx, id uint32) *unitDB {
return nil
}
log.Tracef("Loading unit %d", id)
// log.Tracef("Loading unit %d", id)
var buf bytes.Buffer
buf.Write(bkt.Get([]byte{0}))