proxy.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. package store
  2. import (
  3. "context"
  4. "database/sql"
  5. "errors"
  6. "fmt"
  7. "strings"
  8. "time"
  9. )
  10. func (s *Store) UpsertLocalProxy(ctx context.Context, value LocalProxyConfig) error {
  11. tx, err := s.db.BeginTx(ctx, nil)
  12. if err != nil {
  13. return fmt.Errorf("begin local proxy update: %w", err)
  14. }
  15. defer tx.Rollback()
  16. if err := upsertLocalProxy(ctx, tx, value); err != nil {
  17. return err
  18. }
  19. if err := tx.Commit(); err != nil {
  20. return fmt.Errorf("commit local proxy update: %w", err)
  21. }
  22. return nil
  23. }
  24. func upsertLocalProxy(
  25. ctx context.Context,
  26. executor contextQueryExecer,
  27. value LocalProxyConfig,
  28. ) error {
  29. value.ID = strings.TrimSpace(value.ID)
  30. value.Name = strings.TrimSpace(value.Name)
  31. value.Mode = strings.ToLower(strings.TrimSpace(value.Mode))
  32. value.DeviceID = strings.TrimSpace(value.DeviceID)
  33. value.ListenAddr = strings.TrimSpace(value.ListenAddr)
  34. if value.ID == "" || value.Name == "" || value.DeviceID == "" {
  35. return errors.New("local proxy id, name, and device id are required")
  36. }
  37. if value.Mode != "socks5" && value.Mode != "http" {
  38. return fmt.Errorf("unsupported local proxy mode %q", value.Mode)
  39. }
  40. if value.ListenAddr == "" {
  41. value.ListenAddr = "0.0.0.0"
  42. }
  43. if value.ListenPort < 1 || value.ListenPort > 65535 {
  44. return errors.New("local proxy listen port must be between 1 and 65535")
  45. }
  46. extra, err := normalizeJSONObject(value.Extra)
  47. if err != nil {
  48. return fmt.Errorf("normalize local proxy extra data: %w", err)
  49. }
  50. if !value.AuthEnabled {
  51. value.Username = ""
  52. value.Password = ""
  53. } else {
  54. current, currentErr := localProxy(
  55. executor.QueryRowContext(ctx, localProxySelect+` WHERE id = ?`, value.ID),
  56. )
  57. if currentErr != nil && !errors.Is(currentErr, ErrNotFound) {
  58. return fmt.Errorf("read local proxy before update: %w", currentErr)
  59. }
  60. if currentErr == nil && (value.Password == "" || value.Password == SecretMask) {
  61. value.Password = current.Password
  62. }
  63. if strings.TrimSpace(value.Username) == "" || value.Password == "" || value.Password == SecretMask {
  64. return errors.New("enabled local proxy authentication requires username and password")
  65. }
  66. }
  67. now := time.Now().UTC()
  68. createdAt := value.CreatedAt
  69. if createdAt.IsZero() {
  70. createdAt = now
  71. }
  72. updatedAt := value.UpdatedAt
  73. if updatedAt.IsZero() {
  74. updatedAt = now
  75. }
  76. _, err = executor.ExecContext(ctx, `
  77. INSERT INTO local_proxy_config (
  78. id, name, mode, device_id, listen_addr, listen_port, enabled,
  79. auth_enabled, username, password, extra_json, created_at, updated_at
  80. ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  81. ON CONFLICT(id) DO UPDATE SET
  82. name = excluded.name,
  83. mode = excluded.mode,
  84. device_id = excluded.device_id,
  85. listen_addr = excluded.listen_addr,
  86. listen_port = excluded.listen_port,
  87. enabled = excluded.enabled,
  88. auth_enabled = excluded.auth_enabled,
  89. username = excluded.username,
  90. password = excluded.password,
  91. extra_json = excluded.extra_json,
  92. updated_at = excluded.updated_at
  93. `,
  94. value.ID, value.Name, value.Mode, value.DeviceID, value.ListenAddr,
  95. value.ListenPort, boolInt(value.Enabled), boolInt(value.AuthEnabled),
  96. value.Username, value.Password, string(extra), createdAt.Unix(),
  97. updatedAt.Unix(),
  98. )
  99. if err != nil {
  100. return fmt.Errorf("upsert local proxy %q: %w", value.ID, err)
  101. }
  102. return nil
  103. }
  104. func (s *Store) LocalProxy(ctx context.Context, id string) (LocalProxyConfig, error) {
  105. return localProxy(s.db.QueryRowContext(ctx, localProxySelect+` WHERE id = ?`, id))
  106. }
  107. func (s *Store) ListLocalProxies(ctx context.Context) ([]LocalProxyConfig, error) {
  108. rows, err := s.db.QueryContext(ctx, localProxySelect+` ORDER BY name COLLATE NOCASE, id`)
  109. if err != nil {
  110. return nil, fmt.Errorf("list local proxies: %w", err)
  111. }
  112. defer rows.Close()
  113. values := make([]LocalProxyConfig, 0)
  114. for rows.Next() {
  115. value, err := localProxy(rows)
  116. if err != nil {
  117. return nil, fmt.Errorf("scan local proxy: %w", err)
  118. }
  119. values = append(values, value)
  120. }
  121. if err := rows.Err(); err != nil {
  122. return nil, fmt.Errorf("iterate local proxies: %w", err)
  123. }
  124. return values, nil
  125. }
  126. func (s *Store) ReplaceLocalProxies(ctx context.Context, values []LocalProxyConfig) error {
  127. tx, err := s.db.BeginTx(ctx, nil)
  128. if err != nil {
  129. return fmt.Errorf("begin local proxy replacement: %w", err)
  130. }
  131. defer tx.Rollback()
  132. seen := make(map[string]struct{}, len(values))
  133. for index, value := range values {
  134. if _, duplicate := seen[value.ID]; duplicate {
  135. return fmt.Errorf("duplicate local proxy id %q", value.ID)
  136. }
  137. if err := upsertLocalProxy(ctx, tx, value); err != nil {
  138. return fmt.Errorf("replace local proxy item %d: %w", index, err)
  139. }
  140. seen[value.ID] = struct{}{}
  141. }
  142. rows, err := tx.QueryContext(ctx, `SELECT id FROM local_proxy_config`)
  143. if err != nil {
  144. return fmt.Errorf("list stale local proxies: %w", err)
  145. }
  146. var stale []string
  147. for rows.Next() {
  148. var id string
  149. if err := rows.Scan(&id); err != nil {
  150. rows.Close()
  151. return fmt.Errorf("scan stale local proxy: %w", err)
  152. }
  153. if _, keep := seen[id]; !keep {
  154. stale = append(stale, id)
  155. }
  156. }
  157. if err := rows.Close(); err != nil {
  158. return fmt.Errorf("close local proxy cursor: %w", err)
  159. }
  160. if err := rows.Err(); err != nil {
  161. return fmt.Errorf("iterate stale local proxies: %w", err)
  162. }
  163. for _, id := range stale {
  164. if _, err := tx.ExecContext(ctx, `DELETE FROM local_proxy_config WHERE id = ?`, id); err != nil {
  165. return fmt.Errorf("delete stale local proxy %q: %w", id, err)
  166. }
  167. }
  168. if err := tx.Commit(); err != nil {
  169. return fmt.Errorf("commit local proxy replacement: %w", err)
  170. }
  171. return nil
  172. }
  173. func (s *Store) DeleteLocalProxy(ctx context.Context, id string) error {
  174. result, err := s.db.ExecContext(ctx, `DELETE FROM local_proxy_config WHERE id = ?`, id)
  175. if err != nil {
  176. return fmt.Errorf("delete local proxy %q: %w", id, err)
  177. }
  178. return requireAffected(result)
  179. }
  180. const localProxySelect = `
  181. SELECT id, name, mode, device_id, listen_addr, listen_port, enabled,
  182. auth_enabled, username, password, extra_json, created_at, updated_at
  183. FROM local_proxy_config`
  184. func localProxy(row rowScanner) (LocalProxyConfig, error) {
  185. var value LocalProxyConfig
  186. var enabled, authEnabled int
  187. var extra string
  188. var createdAt, updatedAt int64
  189. err := row.Scan(
  190. &value.ID, &value.Name, &value.Mode, &value.DeviceID,
  191. &value.ListenAddr, &value.ListenPort, &enabled, &authEnabled,
  192. &value.Username, &value.Password, &extra, &createdAt, &updatedAt,
  193. )
  194. if errors.Is(err, sql.ErrNoRows) {
  195. return LocalProxyConfig{}, ErrNotFound
  196. }
  197. if err != nil {
  198. return LocalProxyConfig{}, err
  199. }
  200. value.Enabled = enabled != 0
  201. value.AuthEnabled = authEnabled != 0
  202. value.Extra = []byte(extra)
  203. value.CreatedAt = time.Unix(createdAt, 0).UTC()
  204. value.UpdatedAt = time.Unix(updatedAt, 0).UTC()
  205. return value, nil
  206. }
  207. func (s *Store) UpsertUpstreamProxy(ctx context.Context, value UpstreamProxy) error {
  208. tx, err := s.db.BeginTx(ctx, nil)
  209. if err != nil {
  210. return fmt.Errorf("begin upstream proxy update: %w", err)
  211. }
  212. defer tx.Rollback()
  213. if err := upsertUpstreamProxy(ctx, tx, value); err != nil {
  214. return err
  215. }
  216. if err := tx.Commit(); err != nil {
  217. return fmt.Errorf("commit upstream proxy update: %w", err)
  218. }
  219. return nil
  220. }
  221. func upsertUpstreamProxy(
  222. ctx context.Context,
  223. executor contextQueryExecer,
  224. value UpstreamProxy,
  225. ) error {
  226. value.ID = strings.TrimSpace(value.ID)
  227. value.Name = strings.TrimSpace(value.Name)
  228. value.Addr = strings.TrimSpace(value.Addr)
  229. value.Username = strings.TrimSpace(value.Username)
  230. if value.ID == "" || value.Name == "" || value.Addr == "" {
  231. return errors.New("upstream proxy id, name, and address are required")
  232. }
  233. extra, err := normalizeJSONObject(value.Extra)
  234. if err != nil {
  235. return fmt.Errorf("normalize upstream proxy extra data: %w", err)
  236. }
  237. if value.Username == "" {
  238. value.Password = ""
  239. } else if value.Password == "" || value.Password == SecretMask {
  240. current, currentErr := upstreamProxy(
  241. executor.QueryRowContext(ctx, upstreamProxySelect+` WHERE id = ?`, value.ID),
  242. )
  243. if currentErr != nil && !errors.Is(currentErr, ErrNotFound) {
  244. return fmt.Errorf("read upstream proxy before update: %w", currentErr)
  245. }
  246. if currentErr == nil {
  247. value.Password = current.Password
  248. }
  249. if value.Password == SecretMask {
  250. value.Password = ""
  251. }
  252. }
  253. now := time.Now().UTC()
  254. createdAt := value.CreatedAt
  255. if createdAt.IsZero() {
  256. createdAt = now
  257. }
  258. updatedAt := value.UpdatedAt
  259. if updatedAt.IsZero() {
  260. updatedAt = now
  261. }
  262. _, err = executor.ExecContext(ctx, `
  263. INSERT INTO upstream_proxies (
  264. id, name, addr, username, password, enabled, extra_json,
  265. created_at, updated_at
  266. ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
  267. ON CONFLICT(id) DO UPDATE SET
  268. name = excluded.name,
  269. addr = excluded.addr,
  270. username = excluded.username,
  271. password = excluded.password,
  272. enabled = excluded.enabled,
  273. extra_json = excluded.extra_json,
  274. updated_at = excluded.updated_at
  275. `,
  276. value.ID, value.Name, value.Addr, value.Username, value.Password,
  277. boolInt(value.Enabled), string(extra), createdAt.Unix(), updatedAt.Unix(),
  278. )
  279. if err != nil {
  280. return fmt.Errorf("upsert upstream proxy %q: %w", value.ID, err)
  281. }
  282. return nil
  283. }
  284. func (s *Store) UpstreamProxy(ctx context.Context, id string) (UpstreamProxy, error) {
  285. return upstreamProxy(s.db.QueryRowContext(ctx, upstreamProxySelect+` WHERE id = ?`, id))
  286. }
  287. func (s *Store) ListUpstreamProxies(ctx context.Context) ([]UpstreamProxy, error) {
  288. rows, err := s.db.QueryContext(ctx, upstreamProxySelect+` ORDER BY name COLLATE NOCASE, id`)
  289. if err != nil {
  290. return nil, fmt.Errorf("list upstream proxies: %w", err)
  291. }
  292. defer rows.Close()
  293. values := make([]UpstreamProxy, 0)
  294. for rows.Next() {
  295. value, err := upstreamProxy(rows)
  296. if err != nil {
  297. return nil, fmt.Errorf("scan upstream proxy: %w", err)
  298. }
  299. values = append(values, value)
  300. }
  301. if err := rows.Err(); err != nil {
  302. return nil, fmt.Errorf("iterate upstream proxies: %w", err)
  303. }
  304. return values, nil
  305. }
  306. func (s *Store) DeleteUpstreamProxy(ctx context.Context, id string) error {
  307. result, err := s.db.ExecContext(ctx, `DELETE FROM upstream_proxies WHERE id = ?`, id)
  308. if err != nil {
  309. return fmt.Errorf("delete upstream proxy %q: %w", id, err)
  310. }
  311. return requireAffected(result)
  312. }
  313. const upstreamProxySelect = `
  314. SELECT id, name, addr, username, password, enabled, extra_json,
  315. created_at, updated_at
  316. FROM upstream_proxies`
  317. func upstreamProxy(row rowScanner) (UpstreamProxy, error) {
  318. var value UpstreamProxy
  319. var enabled int
  320. var extra string
  321. var createdAt, updatedAt int64
  322. err := row.Scan(
  323. &value.ID, &value.Name, &value.Addr, &value.Username,
  324. &value.Password, &enabled, &extra, &createdAt, &updatedAt,
  325. )
  326. if errors.Is(err, sql.ErrNoRows) {
  327. return UpstreamProxy{}, ErrNotFound
  328. }
  329. if err != nil {
  330. return UpstreamProxy{}, err
  331. }
  332. value.Enabled = enabled != 0
  333. value.Extra = []byte(extra)
  334. value.CreatedAt = time.Unix(createdAt, 0).UTC()
  335. value.UpdatedAt = time.Unix(updatedAt, 0).UTC()
  336. return value, nil
  337. }
  338. func (s *Store) UpsertDeviceProxyBinding(ctx context.Context, value DeviceProxyBinding) error {
  339. value.DeviceID = strings.TrimSpace(value.DeviceID)
  340. value.UpstreamProxyID = strings.TrimSpace(value.UpstreamProxyID)
  341. if value.DeviceID == "" || value.UpstreamProxyID == "" {
  342. return errors.New("device proxy binding requires device and upstream proxy IDs")
  343. }
  344. now := time.Now().UTC()
  345. createdAt := value.CreatedAt
  346. if createdAt.IsZero() {
  347. createdAt = now
  348. }
  349. updatedAt := value.UpdatedAt
  350. if updatedAt.IsZero() {
  351. updatedAt = now
  352. }
  353. _, err := s.db.ExecContext(ctx, `
  354. INSERT INTO device_proxy_bindings (
  355. device_id, upstream_proxy_id, created_at, updated_at
  356. ) VALUES (?, ?, ?, ?)
  357. ON CONFLICT(device_id) DO UPDATE SET
  358. upstream_proxy_id = excluded.upstream_proxy_id,
  359. updated_at = excluded.updated_at
  360. `, value.DeviceID, value.UpstreamProxyID, createdAt.Unix(), updatedAt.Unix())
  361. if err != nil {
  362. return fmt.Errorf("upsert proxy binding for device %q: %w", value.DeviceID, err)
  363. }
  364. return nil
  365. }
  366. func (s *Store) DeviceProxyBinding(ctx context.Context, deviceID string) (DeviceProxyBinding, error) {
  367. return deviceProxyBinding(s.db.QueryRowContext(
  368. ctx,
  369. deviceProxyBindingSelect+` WHERE device_id = ?`,
  370. strings.TrimSpace(deviceID),
  371. ))
  372. }
  373. func (s *Store) ListDeviceProxyBindings(ctx context.Context) ([]DeviceProxyBinding, error) {
  374. rows, err := s.db.QueryContext(ctx, deviceProxyBindingSelect+` ORDER BY device_id`)
  375. if err != nil {
  376. return nil, fmt.Errorf("list device proxy bindings: %w", err)
  377. }
  378. defer rows.Close()
  379. values := make([]DeviceProxyBinding, 0)
  380. for rows.Next() {
  381. value, err := deviceProxyBinding(rows)
  382. if err != nil {
  383. return nil, fmt.Errorf("scan device proxy binding: %w", err)
  384. }
  385. values = append(values, value)
  386. }
  387. if err := rows.Err(); err != nil {
  388. return nil, fmt.Errorf("iterate device proxy bindings: %w", err)
  389. }
  390. return values, nil
  391. }
  392. func (s *Store) DeleteDeviceProxyBinding(ctx context.Context, deviceID string) error {
  393. result, err := s.db.ExecContext(
  394. ctx,
  395. `DELETE FROM device_proxy_bindings WHERE device_id = ?`,
  396. strings.TrimSpace(deviceID),
  397. )
  398. if err != nil {
  399. return fmt.Errorf("delete proxy binding for device %q: %w", deviceID, err)
  400. }
  401. return requireAffected(result)
  402. }
  403. const deviceProxyBindingSelect = `
  404. SELECT device_id, upstream_proxy_id, created_at, updated_at
  405. FROM device_proxy_bindings`
  406. func deviceProxyBinding(row rowScanner) (DeviceProxyBinding, error) {
  407. var value DeviceProxyBinding
  408. var createdAt, updatedAt int64
  409. err := row.Scan(&value.DeviceID, &value.UpstreamProxyID, &createdAt, &updatedAt)
  410. if errors.Is(err, sql.ErrNoRows) {
  411. return DeviceProxyBinding{}, ErrNotFound
  412. }
  413. if err != nil {
  414. return DeviceProxyBinding{}, err
  415. }
  416. value.CreatedAt = time.Unix(createdAt, 0).UTC()
  417. value.UpdatedAt = time.Unix(updatedAt, 0).UTC()
  418. return value, nil
  419. }
  420. func (s *Store) UpsertCountryRule(ctx context.Context, value CountryRule) error {
  421. value.CountryCode = strings.ToUpper(strings.TrimSpace(value.CountryCode))
  422. value.CountryName = strings.TrimSpace(value.CountryName)
  423. value.UpstreamProxyID = strings.TrimSpace(value.UpstreamProxyID)
  424. if len(value.CountryCode) != 2 {
  425. return errors.New("country rule requires a two-letter country code")
  426. }
  427. for _, character := range value.CountryCode {
  428. if character < 'A' || character > 'Z' {
  429. return errors.New("country rule requires an ISO alpha-2 country code")
  430. }
  431. }
  432. if value.UpstreamProxyID == "" {
  433. return errors.New("country rule upstream proxy id is required")
  434. }
  435. extra, err := normalizeJSONObject(value.Extra)
  436. if err != nil {
  437. return fmt.Errorf("normalize country rule extra data: %w", err)
  438. }
  439. now := time.Now().UTC()
  440. createdAt := value.CreatedAt
  441. if createdAt.IsZero() {
  442. createdAt = now
  443. }
  444. updatedAt := value.UpdatedAt
  445. if updatedAt.IsZero() {
  446. updatedAt = now
  447. }
  448. _, err = s.db.ExecContext(ctx, `
  449. INSERT INTO country_rules (
  450. country_code, country_name, upstream_proxy_id, enabled,
  451. extra_json, created_at, updated_at
  452. ) VALUES (?, ?, ?, ?, ?, ?, ?)
  453. ON CONFLICT(country_code) DO UPDATE SET
  454. country_name = excluded.country_name,
  455. upstream_proxy_id = excluded.upstream_proxy_id,
  456. enabled = excluded.enabled,
  457. extra_json = excluded.extra_json,
  458. updated_at = excluded.updated_at
  459. `,
  460. value.CountryCode, value.CountryName, value.UpstreamProxyID,
  461. boolInt(value.Enabled), string(extra), createdAt.Unix(), updatedAt.Unix(),
  462. )
  463. if err != nil {
  464. return fmt.Errorf("upsert country rule %q: %w", value.CountryCode, err)
  465. }
  466. return nil
  467. }
  468. func (s *Store) CountryRule(ctx context.Context, countryCode string) (CountryRule, error) {
  469. return countryRule(s.db.QueryRowContext(
  470. ctx,
  471. countryRuleSelect+` WHERE country_code = ?`,
  472. strings.ToUpper(strings.TrimSpace(countryCode)),
  473. ))
  474. }
  475. func (s *Store) ListCountryRules(ctx context.Context) ([]CountryRule, error) {
  476. rows, err := s.db.QueryContext(ctx, countryRuleSelect+` ORDER BY country_code`)
  477. if err != nil {
  478. return nil, fmt.Errorf("list country rules: %w", err)
  479. }
  480. defer rows.Close()
  481. values := make([]CountryRule, 0)
  482. for rows.Next() {
  483. value, err := countryRule(rows)
  484. if err != nil {
  485. return nil, fmt.Errorf("scan country rule: %w", err)
  486. }
  487. values = append(values, value)
  488. }
  489. if err := rows.Err(); err != nil {
  490. return nil, fmt.Errorf("iterate country rules: %w", err)
  491. }
  492. return values, nil
  493. }
  494. func (s *Store) DeleteCountryRule(ctx context.Context, countryCode string) error {
  495. result, err := s.db.ExecContext(
  496. ctx,
  497. `DELETE FROM country_rules WHERE country_code = ?`,
  498. strings.ToUpper(strings.TrimSpace(countryCode)),
  499. )
  500. if err != nil {
  501. return fmt.Errorf("delete country rule %q: %w", countryCode, err)
  502. }
  503. return requireAffected(result)
  504. }
  505. const countryRuleSelect = `
  506. SELECT country_code, country_name, upstream_proxy_id, enabled,
  507. extra_json, created_at, updated_at
  508. FROM country_rules`
  509. func countryRule(row rowScanner) (CountryRule, error) {
  510. var value CountryRule
  511. var enabled int
  512. var extra string
  513. var createdAt, updatedAt int64
  514. err := row.Scan(
  515. &value.CountryCode, &value.CountryName, &value.UpstreamProxyID,
  516. &enabled, &extra, &createdAt, &updatedAt,
  517. )
  518. if errors.Is(err, sql.ErrNoRows) {
  519. return CountryRule{}, ErrNotFound
  520. }
  521. if err != nil {
  522. return CountryRule{}, err
  523. }
  524. value.Enabled = enabled != 0
  525. value.Extra = []byte(extra)
  526. value.CreatedAt = time.Unix(createdAt, 0).UTC()
  527. value.UpdatedAt = time.Unix(updatedAt, 0).UTC()
  528. return value, nil
  529. }