use better dns proxying

This commit is contained in:
Fabian Freund
2026-05-26 20:11:30 +02:00
parent 25f95c6c94
commit 3b99bd8a93
5 changed files with 111 additions and 315 deletions
@@ -10,6 +10,7 @@ import _OnBeforeRequestDetails = browser.webRequest._OnBeforeRequestDetails
const localhosts = new Set(['localhost', '127.0.0.1', '[::1]'])
const containerIdentifier = 'firefox-container-'
const defaultIdentifier = 'firefox-default'
const privateIdentifier = 'firefox-private'
type DoNotProxy = never[]
@@ -32,6 +33,35 @@ export default class BackgroundMain {
this.store = store
}
private async tabForRequest(tabId?: number): Promise<browser.tabs.Tab | null> {
if (tabId === undefined || tabId <= -1) {
return null
}
try {
return await browser.tabs.get(tabId)
} catch (e) {
return null
}
}
private contextIdFromCookieStoreId(cookieStoreId?: string): string | null {
if (cookieStoreId === undefined || cookieStoreId.length === 0) {
return null
}
if (cookieStoreId.startsWith(containerIdentifier)) {
return cookieStoreId.substring(containerIdentifier.length)
}
if (cookieStoreId === privateIdentifier) {
return 'private'
}
if (cookieStoreId === defaultIdentifier) {
return 'general'
}
return cookieStoreId
}
/*
initializeAuthListener(cookieStoreId: string, proxy: HttpProxySettings | HttpsProxySettings): void {
const listener: (details: _OnAuthRequiredDetails) => BlockingResponse = (details) => {
@@ -60,72 +90,50 @@ export default class BackgroundMain {
*/
async onRequest(requestDetails: Pick<_OnRequestDetails, 'cookieStoreId' | 'url' | 'tabId'>): Promise<DoNotProxy | ProxyInfo[]> {
const tab = (requestDetails.tabId > -1) ? (await browser.tabs.get(requestDetails.tabId)) : null
try {
const tab = await this.tabForRequest(requestDetails.tabId)
const contextId = this.contextIdFromCookieStoreId(tab?.cookieStoreId ?? requestDetails.cookieStoreId)
if (this.store.hasGeneralRelation() ||
tab === null ||
tab.cookieStoreId?.startsWith(containerIdentifier) === true ||
tab.cookieStoreId === privateIdentifier
) {
try {
let cookieStoreId: string
if (contextId === null) {
return doNotProxy
}
if (tab?.cookieStoreId?.startsWith(containerIdentifier) === true) {
cookieStoreId = tab.cookieStoreId.substring(containerIdentifier.length)
} else if (tab?.cookieStoreId === privateIdentifier) {
// Handle private tabs - use 'private' as identifier
cookieStoreId = 'private'
} else {
cookieStoreId = 'general'
}
let proxies = this.store.getProxiesForContainer(cookieStoreId)
if ((proxies?.length ?? 0) == 0 && tab === null) {
//When no tab is specified and general relation doesnt exist, we get all proxies to avoid leakage
proxies = this.store.getAllProxies()
if (proxies.length == 0) {
return doNotProxy
}
} else if (proxies === null) {
return doNotProxy
}
if (proxies.length > 0) {
// proxies.forEach(p => {
// if (p.type === ProxyType.Http || p.type === ProxyType.Https) {
// this.initializeAuthListener(cookieStoreId, p)
// }
// })
const result: ProxyInfo[] = proxies.filter((p: ProxySettings) => {
try {
const documentUrl = new URL(requestDetails.url)
const isLocalhost = localhosts.has(documentUrl.hostname)
if (isLocalhost && p.doNotProxyLocal) {
return false
}
} catch (e) {
console.error(e)
}
return true
}).map(p => p.asProxyInfo())
if (result.length === 0) {
return [emergencyBreak]
}
return result
}
return [emergencyBreak]
} catch (e: unknown) {
console.error(`Error in onRequest listener: ${e as string}`)
const proxies = this.store.getProxiesForContainer(contextId)
if (proxies === null) {
return doNotProxy
}
if (proxies.length === 0) {
return [emergencyBreak]
}
}
return doNotProxy
// proxies.forEach(p => {
// if (p.type === ProxyType.Http || p.type === ProxyType.Https) {
// this.initializeAuthListener(cookieStoreId, p)
// }
// })
const result: ProxyInfo[] = proxies.filter((p: ProxySettings) => {
try {
const documentUrl = new URL(requestDetails.url)
const isLocalhost = localhosts.has(documentUrl.hostname)
if (isLocalhost && p.doNotProxyLocal) {
return false
}
} catch (e) {
console.error(e)
}
return true
}).map(p => p.asProxyInfo())
if (result.length === 0) {
return doNotProxy
}
return result
} catch (e: unknown) {
console.error(`Error in onRequest listener: ${e as string}`)
return [emergencyBreak]
}
}
async onBeforeRequest(options: _OnBeforeRequestDetails, port: browser.runtime.Port): Promise<browser.webRequest.BlockingResponse> {
@@ -9,11 +9,14 @@ const tryFromDao = ProxySettings.tryFromDao
const chrome = require('sinon-chrome/extensions');
const store = new Store()
let store: Store
let backgroundMain: BackgroundMain
describe('BackgroundMain', function () {
beforeEach(() => {
global.browser = chrome
store = new Store()
backgroundMain = new BackgroundMain({ store })
})
afterEach(() => {
@@ -21,10 +24,6 @@ describe('BackgroundMain', function () {
delete global.browser
})
const backgroundMain = new BackgroundMain({ store: store })
// TODO: Add test for proxyDNS property
describe('onRequest', function () {
it('should return empty array if no proxy is set up', async () => {
const result = await backgroundMain.onRequest({ cookieStoreId: 'firefox-default', url: 'https://google.com', tabId: 0 })
@@ -33,7 +32,7 @@ describe('BackgroundMain', function () {
})
it('should return proxy if proxy is set up', async () => {
await givenSomeProxyIsSetUpForContainer({ containerId: 'firefox-default', host: undefined, doNotProxyLocal: undefined })
await givenSomeProxyIsSetUpForContainer({ containerId: 'general', host: undefined, doNotProxyLocal: undefined })
const result = await backgroundMain.onRequest({ cookieStoreId: 'firefox-default', url: 'https://google.com', tabId: 0 })
@@ -41,6 +40,31 @@ describe('BackgroundMain', function () {
expect(result).to.be.not.empty
})
it('should not use an unrelated container proxy for default tabs', async () => {
await givenSomeProxyIsSetUpForContainer({ containerId: 'container1', host: undefined, doNotProxyLocal: undefined })
const result = await backgroundMain.onRequest({ cookieStoreId: 'firefox-default', url: 'https://google.com', tabId: -1 })
expect(result).to.be.deep.equal(doNotProxy)
})
it('should use request cookieStoreId when no tab is available', async () => {
await givenSomeProxyIsSetUpForContainer({ containerId: 'container1', host: undefined, doNotProxyLocal: undefined })
const result = await backgroundMain.onRequest({ cookieStoreId: 'firefox-container-container1', url: 'https://google.com', tabId: -1 })
expect(result).to.be.an('array')
expect(result).to.be.not.empty
})
it('should return empty array for tabless requests without a cookie store', async () => {
await givenSomeProxyIsSetUpForContainer({ containerId: 'container1', host: undefined, doNotProxyLocal: undefined })
const result = await backgroundMain.onRequest({ url: 'https://google.com', tabId: -1 })
expect(result).to.be.deep.equal(doNotProxy)
})
it('should block if an assigned proxy no longer exists', async () => {
const isolatedStore = new Store()
const isolatedBackgroundMain = new BackgroundMain({ store: isolatedStore })
@@ -52,16 +76,24 @@ describe('BackgroundMain', function () {
})
it('should remove doNotProxyLocal flag from proxy settings if proxy is set up', async () => {
await givenSomeProxyIsSetUpForContainer({ containerId: 'firefox-default', host: undefined, doNotProxyLocal: undefined })
await givenSomeProxyIsSetUpForContainer({ containerId: 'general', host: undefined, doNotProxyLocal: undefined })
const result = await backgroundMain.onRequest({ cookieStoreId: 'firefox-default', url: 'https://google.com', tabId: 0 })
expect((result[0] as any).doNotProxyLocal).to.be.undefined
})
it('should preserve proxyDNS on SOCKS proxy settings', async () => {
await givenSomeProxyIsSetUpForContainer({ containerId: 'container1', host: undefined, doNotProxyLocal: undefined })
const result = await backgroundMain.onRequest({ cookieStoreId: 'firefox-container-container1', url: 'https://google.com', tabId: -1 })
expect((result[0] as any).proxyDNS).to.be.true
})
it('should return proxy for the container if url is invalid', async () => {
// To be more on a safe side
await givenSomeProxyIsSetUpForContainer({ containerId: 'firefox-default', host: undefined, doNotProxyLocal: undefined })
await givenSomeProxyIsSetUpForContainer({ containerId: 'general', host: undefined, doNotProxyLocal: undefined })
const result = await backgroundMain.onRequest({ cookieStoreId: 'firefox-default', url: 'np-protocol-url.com', tabId: 0 })
@@ -88,7 +120,7 @@ describe('BackgroundMain', function () {
it(`should return empty array if the address is local: ${url}`, async () => {
await givenSomeProxyIsSetUpForContainer({ containerId: 'container1', host: undefined, doNotProxyLocal: true })
const result = await backgroundMain.onRequest({ cookieStoreId: 'container1', url, tabId: 0 })
const result = await backgroundMain.onRequest({ cookieStoreId: 'firefox-container-container1', url, tabId: -1 })
expect(result).to.be.deep.equal(doNotProxy)
})
@@ -101,7 +133,7 @@ describe('BackgroundMain', function () {
const host = 'proxyX.example.com'
await givenSomeProxyIsSetUpForContainer({ host, containerId: 'container1', doNotProxyLocal: false })
const result = await backgroundMain.onRequest({ cookieStoreId: 'container1', url, tabId: 0 })
const result = await backgroundMain.onRequest({ cookieStoreId: 'firefox-container-container1', url, tabId: -1 })
expect(result[0].host).to.be.equal(host)
})