mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2024-09-11 10:45:42 +03:00
Pull request 2019: 1700-update-static-lease
Updates #1700.
Squashed commit of the following:
commit b3fdf0a492e38be594500b1db4da20bf70cd7096
Merge: 507cb9bc7 4479b32ad
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date: Thu Oct 5 12:53:30 2023 +0300
Merge branch 'master' into 1700-update-static-lease
commit 507cb9bc7bec9884ce7db2f42688d0a409015756
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date: Wed Oct 4 18:54:06 2023 +0300
all: upd chlog
commit 0736b97bdd652a3da13bce4177c64daa0a3da2af
Author: Ildar Kamalov <ik@adguard.com>
Date: Wed Oct 4 16:05:35 2023 +0300
client: fix update action
commit 351986bb03b1c525f00b1e7cd44a3dab8dd9eb97
Author: Ildar Kamalov <ik@adguard.com>
Date: Wed Oct 4 16:01:38 2023 +0300
client: update static lease
commit 3c328283c8374480132a9907e1738978c0b0384f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date: Thu Sep 28 20:06:29 2023 +0300
dhcpd: fix err msg
commit 5b2f8f51b427ae07b227357fa3cc073a3278079b
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date: Thu Sep 28 16:28:07 2023 +0300
dhcpd: imp code
commit a9d24e816f602ad207e42124ddbb56ecdb0e03f6
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date: Wed Sep 27 17:43:04 2023 +0300
all: add tests
commit 453785796191179ef4136b613f4dd8665703b364
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date: Tue Sep 26 20:14:17 2023 +0300
dhcpd: update static lease
This commit is contained in:
parent
4479b32ad4
commit
e305bd8e40
@ -25,9 +25,11 @@ NOTE: Add new changes BELOW THIS COMMENT.
|
||||
|
||||
### Added
|
||||
|
||||
- Ability to edit static leases on *DHCP settings* page ([#1700]).
|
||||
- Ability to specify for how long clients should cache a filtered response,
|
||||
using the *Blocked response TTL* field on the *DNS settings* page ([#4569]).
|
||||
|
||||
[#1700]: https://github.com/AdguardTeam/AdGuardHome/issues/1700
|
||||
[#4569]: https://github.com/AdguardTeam/AdGuardHome/issues/4569
|
||||
|
||||
### Fixed
|
||||
|
@ -73,7 +73,9 @@
|
||||
"dhcp_dynamic_ip_found": "Your system uses dynamic IP address configuration for interface <0>{{interfaceName}}</0>. In order to use DHCP server, a static IP address must be set. Your current IP address is <0>{{ipAddress}}</0>. AdGuard Home will automatically set this IP address as static if you press the \"Enable DHCP server\" button.",
|
||||
"dhcp_lease_added": "Static lease \"{{key}}\" successfully added",
|
||||
"dhcp_lease_deleted": "Static lease \"{{key}}\" successfully deleted",
|
||||
"dhcp_lease_updated": "Static lease \"{{key}}\" successfully updated",
|
||||
"dhcp_new_static_lease": "New static lease",
|
||||
"dhcp_edit_static_lease": "Edit static lease",
|
||||
"dhcp_static_leases_not_found": "No DHCP static leases found",
|
||||
"dhcp_add_static_lease": "Add static lease",
|
||||
"dhcp_reset_leases": "Reset all leases",
|
||||
|
@ -660,6 +660,24 @@ export const removeStaticLease = (config) => async (dispatch) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const updateStaticLeaseRequest = createAction('UPDATE_STATIC_LEASE_REQUEST');
|
||||
export const updateStaticLeaseFailure = createAction('UPDATE_STATIC_LEASE_FAILURE');
|
||||
export const updateStaticLeaseSuccess = createAction('UPDATE_STATIC_LEASE_SUCCESS');
|
||||
|
||||
export const updateStaticLease = (config) => async (dispatch) => {
|
||||
dispatch(updateStaticLeaseRequest());
|
||||
try {
|
||||
await apiClient.updateStaticLease(config);
|
||||
dispatch(updateStaticLeaseSuccess(config));
|
||||
dispatch(addSuccessToast(i18next.t('dhcp_lease_updated', { key: config.hostname || config.ip })));
|
||||
dispatch(toggleLeaseModal());
|
||||
dispatch(getDhcpStatus());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(updateStaticLeaseFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const removeToast = createAction('REMOVE_TOAST');
|
||||
|
||||
export const toggleBlocking = (
|
||||
|
@ -274,6 +274,8 @@ class Api {
|
||||
|
||||
DHCP_REMOVE_STATIC_LEASE = { path: 'dhcp/remove_static_lease', method: 'POST' };
|
||||
|
||||
DHCP_UPDATE_STATIC_LEASE = { path: 'dhcp/update_static_lease', method: 'POST' };
|
||||
|
||||
DHCP_RESET = { path: 'dhcp/reset', method: 'POST' };
|
||||
|
||||
DHCP_LEASES_RESET = { path: 'dhcp/reset_leases', method: 'POST' };
|
||||
@ -320,6 +322,14 @@ class Api {
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
updateStaticLease(config) {
|
||||
const { path, method } = this.DHCP_UPDATE_STATIC_LEASE;
|
||||
const parameters = {
|
||||
data: config,
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
resetDhcp() {
|
||||
const { path, method } = this.DHCP_RESET;
|
||||
return this.makeRequest(path, method);
|
||||
|
@ -22,6 +22,7 @@ const Form = ({
|
||||
submitting,
|
||||
processingAdding,
|
||||
cidr,
|
||||
isEdit,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
@ -45,6 +46,7 @@ const Form = ({
|
||||
placeholder={t('form_enter_mac')}
|
||||
normalize={normalizeMac}
|
||||
validate={[validateRequiredValue, validateMac]}
|
||||
disabled={isEdit}
|
||||
/>
|
||||
</div>
|
||||
<div className="form__group">
|
||||
@ -112,6 +114,7 @@ Form.propTypes = {
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
processingAdding: PropTypes.bool.isRequired,
|
||||
cidr: PropTypes.string.isRequired,
|
||||
isEdit: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default reduxForm({ form: FORM_NAME.LEASE })(Form);
|
||||
|
@ -5,9 +5,11 @@ import ReactModal from 'react-modal';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import Form from './Form';
|
||||
import { toggleLeaseModal } from '../../../../actions';
|
||||
import { MODAL_TYPE } from '../../../../helpers/constants';
|
||||
|
||||
const Modal = ({
|
||||
isModalOpen,
|
||||
modalType,
|
||||
handleSubmit,
|
||||
processingAdding,
|
||||
cidr,
|
||||
@ -32,7 +34,11 @@ const Modal = ({
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title">
|
||||
{modalType === MODAL_TYPE.EDIT_LEASE ? (
|
||||
<Trans>dhcp_edit_static_lease</Trans>
|
||||
) : (
|
||||
<Trans>dhcp_new_static_lease</Trans>
|
||||
)}
|
||||
</h4>
|
||||
<button type="button" className="close" onClick={toggleModal}>
|
||||
<span className="sr-only">Close</span>
|
||||
@ -53,6 +59,7 @@ const Modal = ({
|
||||
cidr={cidr}
|
||||
rangeStart={rangeStart}
|
||||
rangeEnd={rangeEnd}
|
||||
isEdit={modalType === MODAL_TYPE.EDIT_LEASE}
|
||||
/>
|
||||
</div>
|
||||
</ReactModal>
|
||||
@ -61,6 +68,7 @@ const Modal = ({
|
||||
|
||||
Modal.propTypes = {
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
modalType: PropTypes.string.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
processingAdding: PropTypes.bool.isRequired,
|
||||
cidr: PropTypes.string.isRequired,
|
||||
|
@ -3,10 +3,15 @@ import PropTypes from 'prop-types';
|
||||
import ReactTable from 'react-table';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { LEASES_TABLE_DEFAULT_PAGE_SIZE } from '../../../../helpers/constants';
|
||||
import { LEASES_TABLE_DEFAULT_PAGE_SIZE, MODAL_TYPE } from '../../../../helpers/constants';
|
||||
import { sortIp } from '../../../../helpers/helpers';
|
||||
import Modal from './Modal';
|
||||
import { addStaticLease, removeStaticLease } from '../../../../actions';
|
||||
import {
|
||||
addStaticLease,
|
||||
removeStaticLease,
|
||||
toggleLeaseModal,
|
||||
updateStaticLease,
|
||||
} from '../../../../actions';
|
||||
|
||||
const cellWrap = ({ value }) => (
|
||||
<div className="logs__row o-hidden">
|
||||
@ -18,8 +23,10 @@ const cellWrap = ({ value }) => (
|
||||
|
||||
const StaticLeases = ({
|
||||
isModalOpen,
|
||||
modalType,
|
||||
processingAdding,
|
||||
processingDeleting,
|
||||
processingUpdating,
|
||||
staticLeases,
|
||||
cidr,
|
||||
rangeStart,
|
||||
@ -31,7 +38,12 @@ const StaticLeases = ({
|
||||
|
||||
const handleSubmit = (data) => {
|
||||
const { mac, ip, hostname } = data;
|
||||
|
||||
if (modalType === MODAL_TYPE.EDIT_LEASE) {
|
||||
dispatch(updateStaticLease({ mac, ip, hostname }));
|
||||
} else {
|
||||
dispatch(addStaticLease({ mac, ip, hostname }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = (ip, mac, hostname = '') => {
|
||||
@ -80,19 +92,35 @@ const StaticLeases = ({
|
||||
Cell: (row) => {
|
||||
const { ip, mac, hostname } = row.original;
|
||||
|
||||
return <div className="logs__row logs__row--center">
|
||||
return (
|
||||
<div className="logs__row logs__row--center">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-icon--green btn-outline-secondary btn-sm"
|
||||
title={t('delete_table_action')}
|
||||
disabled={processingDeleting}
|
||||
onClick={() => handleDelete(ip, mac, hostname)}
|
||||
className="btn btn-icon btn-outline-primary btn-sm mr-2"
|
||||
onClick={() => dispatch(toggleLeaseModal({
|
||||
type: MODAL_TYPE.EDIT_LEASE,
|
||||
config: { ip, mac, hostname },
|
||||
}))}
|
||||
disabled={processingUpdating}
|
||||
title={t('edit_table_action')}
|
||||
>
|
||||
<svg className="icons">
|
||||
<svg className="icons icon12">
|
||||
<use xlinkHref="#edit" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-secondary btn-sm"
|
||||
onClick={() => handleDelete(ip, mac, hostname)}
|
||||
disabled={processingDeleting}
|
||||
title={t('delete_table_action')}
|
||||
>
|
||||
<svg className="icons icon12">
|
||||
<use xlinkHref="#delete" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>;
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
@ -105,6 +133,7 @@ const StaticLeases = ({
|
||||
/>
|
||||
<Modal
|
||||
isModalOpen={isModalOpen}
|
||||
modalType={modalType}
|
||||
handleSubmit={handleSubmit}
|
||||
processingAdding={processingAdding}
|
||||
cidr={cidr}
|
||||
@ -119,8 +148,10 @@ const StaticLeases = ({
|
||||
StaticLeases.propTypes = {
|
||||
staticLeases: PropTypes.array.isRequired,
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
modalType: PropTypes.string.isRequired,
|
||||
processingAdding: PropTypes.bool.isRequired,
|
||||
processingDeleting: PropTypes.bool.isRequired,
|
||||
processingUpdating: PropTypes.bool.isRequired,
|
||||
cidr: PropTypes.string.isRequired,
|
||||
rangeStart: PropTypes.string,
|
||||
rangeEnd: PropTypes.string,
|
||||
|
@ -49,6 +49,7 @@ const Dhcp = () => {
|
||||
isModalOpen,
|
||||
processingAdding,
|
||||
processingDeleting,
|
||||
processingUpdating,
|
||||
processingDhcp,
|
||||
v4,
|
||||
v6,
|
||||
@ -56,6 +57,7 @@ const Dhcp = () => {
|
||||
enabled,
|
||||
dhcp_available,
|
||||
interfaces,
|
||||
modalType,
|
||||
} = useSelector((state) => state.dhcp, shallowEqual);
|
||||
|
||||
const interface_name = useSelector(
|
||||
@ -273,8 +275,11 @@ const Dhcp = () => {
|
||||
<StaticLeases
|
||||
staticLeases={staticLeases}
|
||||
isModalOpen={isModalOpen}
|
||||
toggleModal={toggleModal}
|
||||
modalType={modalType}
|
||||
processingAdding={processingAdding}
|
||||
processingDeleting={processingDeleting}
|
||||
processingUpdating={processingUpdating}
|
||||
cidr={cidr}
|
||||
rangeStart={dhcp?.values?.v4?.range_start}
|
||||
rangeEnd={dhcp?.values?.v4?.range_end}
|
||||
|
@ -175,6 +175,7 @@ export const MODAL_TYPE = {
|
||||
CHOOSE_FILTERING_LIST: 'CHOOSE_FILTERING_LIST',
|
||||
ADD_REWRITE: 'ADD_REWRITE',
|
||||
EDIT_REWRITE: 'EDIT_REWRITE',
|
||||
EDIT_LEASE: 'EDIT_LEASE',
|
||||
};
|
||||
|
||||
export const CLIENT_ID = {
|
||||
|
@ -128,7 +128,8 @@ const dhcp = handleActions(
|
||||
const newState = {
|
||||
...state,
|
||||
isModalOpen: !state.isModalOpen,
|
||||
leaseModalConfig: payload,
|
||||
modalType: payload?.type || '',
|
||||
leaseModalConfig: payload?.config,
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
@ -175,6 +176,16 @@ const dhcp = handleActions(
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.updateStaticLeaseRequest]: (state) => ({ ...state, processingUpdating: true }),
|
||||
[actions.updateStaticLeaseFailure]: (state) => ({ ...state, processingUpdating: false }),
|
||||
[actions.updateStaticLeaseSuccess]: (state) => {
|
||||
const newState = {
|
||||
...state,
|
||||
processingUpdating: false,
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
},
|
||||
{
|
||||
processing: true,
|
||||
@ -184,6 +195,7 @@ const dhcp = handleActions(
|
||||
processingConfig: false,
|
||||
processingAdding: false,
|
||||
processingDeleting: false,
|
||||
processingUpdating: false,
|
||||
enabled: false,
|
||||
interface_name: '',
|
||||
check: null,
|
||||
@ -202,6 +214,7 @@ const dhcp = handleActions(
|
||||
staticLeases: [],
|
||||
isModalOpen: false,
|
||||
leaseModalConfig: undefined,
|
||||
modalType: '',
|
||||
dhcp_available: false,
|
||||
},
|
||||
);
|
||||
|
@ -57,6 +57,9 @@ type DHCPServer interface {
|
||||
// RemoveStaticLease - remove a static lease
|
||||
RemoveStaticLease(l *Lease) (err error)
|
||||
|
||||
// UpdateStaticLease updates IP, hostname of the lease.
|
||||
UpdateStaticLease(l *Lease) (err error)
|
||||
|
||||
// FindMACbyIP returns a MAC address by the IP address of its lease, if
|
||||
// there is one.
|
||||
FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr)
|
||||
|
@ -5,6 +5,7 @@ package dhcpd
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
@ -290,12 +291,12 @@ func (s *server) handleDHCPSetConfigV6(
|
||||
func (s *server) createServers(conf *dhcpServerConfigJSON) (srv4, srv6 DHCPServer, err error) {
|
||||
srv4, v4Enabled, err := s.handleDHCPSetConfigV4(conf)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("bad dhcpv4 configuration: %s", err)
|
||||
return nil, nil, fmt.Errorf("bad dhcpv4 configuration: %w", err)
|
||||
}
|
||||
|
||||
srv6, v6Enabled, err := s.handleDHCPSetConfigV6(conf)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("bad dhcpv6 configuration: %s", err)
|
||||
return nil, nil, fmt.Errorf("bad dhcpv6 configuration: %w", err)
|
||||
}
|
||||
|
||||
if conf.Enabled == aghalg.NBTrue && !v4Enabled && !v6Enabled {
|
||||
@ -424,7 +425,7 @@ func newNetInterfaceJSON(iface net.Interface) (out *netInterfaceJSON, err error)
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to get addresses for interface %s: %s",
|
||||
"failed to get addresses for interface %s: %w",
|
||||
iface.Name,
|
||||
err,
|
||||
)
|
||||
@ -590,82 +591,78 @@ func setOtherDHCPResult(ifaceName string, result *dhcpSearchResult) {
|
||||
}
|
||||
}
|
||||
|
||||
// parseLease parses a lease from r. If there is no error returns DHCPServer
|
||||
// and *Lease. r must be non-nil.
|
||||
func (s *server) parseLease(r io.Reader) (srv DHCPServer, lease *Lease, err error) {
|
||||
l := &leaseStatic{}
|
||||
err = json.NewDecoder(r).Decode(l)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("decoding json: %w", err)
|
||||
}
|
||||
|
||||
if !l.IP.IsValid() {
|
||||
return nil, nil, errors.Error("invalid ip")
|
||||
}
|
||||
|
||||
l.IP = l.IP.Unmap()
|
||||
|
||||
lease, err = l.toLease()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("parsing: %w", err)
|
||||
}
|
||||
|
||||
if lease.IP.Is4() {
|
||||
srv = s.srv4
|
||||
} else {
|
||||
srv = s.srv6
|
||||
}
|
||||
|
||||
return srv, lease, nil
|
||||
}
|
||||
|
||||
// handleDHCPAddStaticLease is the handler for the POST
|
||||
// /control/dhcp/add_static_lease HTTP API.
|
||||
func (s *server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request) {
|
||||
l := &leaseStatic{}
|
||||
err := json.NewDecoder(r.Body).Decode(l)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !l.IP.IsValid() {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "invalid IP")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
l.IP = l.IP.Unmap()
|
||||
|
||||
var srv DHCPServer
|
||||
if l.IP.Is4() {
|
||||
srv = s.srv4
|
||||
} else {
|
||||
srv = s.srv6
|
||||
}
|
||||
|
||||
lease, err := l.toLease()
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "parsing: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err = srv.AddStaticLease(lease)
|
||||
srv, lease, err := s.parseLease(r.Body)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err = srv.AddStaticLease(lease); err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// handleDHCPRemoveStaticLease is the handler for the POST
|
||||
// /control/dhcp/remove_static_lease HTTP API.
|
||||
func (s *server) handleDHCPRemoveStaticLease(w http.ResponseWriter, r *http.Request) {
|
||||
l := &leaseStatic{}
|
||||
err := json.NewDecoder(r.Body).Decode(l)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !l.IP.IsValid() {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "invalid IP")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
l.IP = l.IP.Unmap()
|
||||
|
||||
var srv DHCPServer
|
||||
if l.IP.Is4() {
|
||||
srv = s.srv4
|
||||
} else {
|
||||
srv = s.srv6
|
||||
}
|
||||
|
||||
lease, err := l.toLease()
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "parsing: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err = srv.RemoveStaticLease(lease)
|
||||
srv, lease, err := s.parseLease(r.Body)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err = srv.RemoveStaticLease(lease); err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// handleDHCPUpdateStaticLease is the handler for the POST
|
||||
// /control/dhcp/update_static_lease HTTP API.
|
||||
func (s *server) handleDHCPUpdateStaticLease(w http.ResponseWriter, r *http.Request) {
|
||||
srv, lease, err := s.parseLease(r.Body)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err = srv.UpdateStaticLease(lease); err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) handleReset(w http.ResponseWriter, r *http.Request) {
|
||||
@ -729,6 +726,7 @@ func (s *server) registerHandlers() {
|
||||
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/find_active_dhcp", s.handleDHCPFindActiveServer)
|
||||
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/add_static_lease", s.handleDHCPAddStaticLease)
|
||||
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/remove_static_lease", s.handleDHCPRemoveStaticLease)
|
||||
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/update_static_lease", s.handleDHCPUpdateStaticLease)
|
||||
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/reset", s.handleReset)
|
||||
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/reset_leases", s.handleResetLeases)
|
||||
}
|
||||
|
319
internal/dhcpd/http_unix_internal_test.go
Normal file
319
internal/dhcpd/http_unix_internal_test.go
Normal file
@ -0,0 +1,319 @@
|
||||
//go:build darwin || freebsd || linux || openbsd
|
||||
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// defaultResponse is a helper that returns the response with default
|
||||
// configuration.
|
||||
func defaultResponse() *dhcpStatusResponse {
|
||||
conf4 := defaultV4ServerConf()
|
||||
conf4.LeaseDuration = 86400
|
||||
|
||||
resp := &dhcpStatusResponse{
|
||||
V4: *conf4,
|
||||
V6: V6ServerConf{},
|
||||
Leases: []*leaseDynamic{},
|
||||
StaticLeases: []*leaseStatic{},
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// handleLease is the helper function that calls handler with provided static
|
||||
// lease as body and returns modified response recorder.
|
||||
func handleLease(t *testing.T, lease *leaseStatic, handler http.HandlerFunc) (w *httptest.ResponseRecorder) {
|
||||
t.Helper()
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
err := json.NewEncoder(b).Encode(lease)
|
||||
require.NoError(t, err)
|
||||
|
||||
var r *http.Request
|
||||
r, err = http.NewRequest(http.MethodPost, "", b)
|
||||
require.NoError(t, err)
|
||||
|
||||
handler(w, r)
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
// checkStatus is a helper that asserts the response of
|
||||
// [*server.handleDHCPStatus].
|
||||
func checkStatus(t *testing.T, s *server, want *dhcpStatusResponse) {
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
err := json.NewEncoder(b).Encode(&want)
|
||||
require.NoError(t, err)
|
||||
|
||||
r, err := http.NewRequest(http.MethodPost, "", b)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.handleDHCPStatus(w, r)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
assert.JSONEq(t, b.String(), w.Body.String())
|
||||
}
|
||||
|
||||
func TestServer_handleDHCPStatus(t *testing.T) {
|
||||
const (
|
||||
staticName = "static-client"
|
||||
staticMAC = "aa:aa:aa:aa:aa:aa"
|
||||
)
|
||||
|
||||
staticIP := netip.MustParseAddr("192.168.10.10")
|
||||
|
||||
staticLease := &leaseStatic{
|
||||
HWAddr: staticMAC,
|
||||
IP: staticIP,
|
||||
Hostname: staticName,
|
||||
}
|
||||
|
||||
s, err := Create(&ServerConfig{
|
||||
Enabled: true,
|
||||
Conf4: *defaultV4ServerConf(),
|
||||
DataDir: t.TempDir(),
|
||||
ConfigModified: func() {},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
ok := t.Run("status", func(t *testing.T) {
|
||||
resp := defaultResponse()
|
||||
|
||||
checkStatus(t, s, resp)
|
||||
})
|
||||
require.True(t, ok)
|
||||
|
||||
ok = t.Run("add_static_lease", func(t *testing.T) {
|
||||
w := handleLease(t, staticLease, s.handleDHCPAddStaticLease)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
resp := defaultResponse()
|
||||
resp.StaticLeases = []*leaseStatic{staticLease}
|
||||
|
||||
checkStatus(t, s, resp)
|
||||
})
|
||||
require.True(t, ok)
|
||||
|
||||
ok = t.Run("add_invalid_lease", func(t *testing.T) {
|
||||
w := handleLease(t, staticLease, s.handleDHCPAddStaticLease)
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
})
|
||||
require.True(t, ok)
|
||||
|
||||
ok = t.Run("remove_static_lease", func(t *testing.T) {
|
||||
w := handleLease(t, staticLease, s.handleDHCPRemoveStaticLease)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
resp := defaultResponse()
|
||||
|
||||
checkStatus(t, s, resp)
|
||||
})
|
||||
require.True(t, ok)
|
||||
|
||||
ok = t.Run("set_config", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
resp := defaultResponse()
|
||||
resp.Enabled = false
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
err = json.NewEncoder(b).Encode(&resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
var r *http.Request
|
||||
r, err = http.NewRequest(http.MethodPost, "", b)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.handleDHCPSetConfig(w, r)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
checkStatus(t, s, resp)
|
||||
})
|
||||
require.True(t, ok)
|
||||
}
|
||||
|
||||
func TestServer_HandleUpdateStaticLease(t *testing.T) {
|
||||
const (
|
||||
leaseV4Name = "static-client-v4"
|
||||
leaseV4MAC = "44:44:44:44:44:44"
|
||||
|
||||
leaseV6Name = "static-client-v6"
|
||||
leaseV6MAC = "66:66:66:66:66:66"
|
||||
)
|
||||
|
||||
leaseV4IP := netip.MustParseAddr("192.168.10.10")
|
||||
leaseV6IP := netip.MustParseAddr("2001::6")
|
||||
|
||||
const (
|
||||
leaseV4Pos = iota
|
||||
leaseV6Pos
|
||||
)
|
||||
|
||||
leases := []*leaseStatic{
|
||||
leaseV4Pos: {
|
||||
HWAddr: leaseV4MAC,
|
||||
IP: leaseV4IP,
|
||||
Hostname: leaseV4Name,
|
||||
},
|
||||
leaseV6Pos: {
|
||||
HWAddr: leaseV6MAC,
|
||||
IP: leaseV6IP,
|
||||
Hostname: leaseV6Name,
|
||||
},
|
||||
}
|
||||
|
||||
s, err := Create(&ServerConfig{
|
||||
Enabled: true,
|
||||
Conf4: *defaultV4ServerConf(),
|
||||
Conf6: V6ServerConf{},
|
||||
DataDir: t.TempDir(),
|
||||
ConfigModified: func() {},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, l := range leases {
|
||||
w := handleLease(t, l, s.handleDHCPAddStaticLease)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
pos int
|
||||
lease *leaseStatic
|
||||
}{{
|
||||
name: "update_v4_name",
|
||||
pos: leaseV4Pos,
|
||||
lease: &leaseStatic{
|
||||
HWAddr: leaseV4MAC,
|
||||
IP: leaseV4IP,
|
||||
Hostname: "updated-client-v4",
|
||||
},
|
||||
}, {
|
||||
name: "update_v4_ip",
|
||||
pos: leaseV4Pos,
|
||||
lease: &leaseStatic{
|
||||
HWAddr: leaseV4MAC,
|
||||
IP: netip.MustParseAddr("192.168.10.200"),
|
||||
Hostname: "updated-client-v4",
|
||||
},
|
||||
}, {
|
||||
name: "update_v6_name",
|
||||
pos: leaseV6Pos,
|
||||
lease: &leaseStatic{
|
||||
HWAddr: leaseV6MAC,
|
||||
IP: leaseV6IP,
|
||||
Hostname: "updated-client-v6",
|
||||
},
|
||||
}, {
|
||||
name: "update_v6_ip",
|
||||
pos: leaseV6Pos,
|
||||
lease: &leaseStatic{
|
||||
HWAddr: leaseV6MAC,
|
||||
IP: netip.MustParseAddr("2001::666"),
|
||||
Hostname: "updated-client-v6",
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
w := handleLease(t, tc.lease, s.handleDHCPUpdateStaticLease)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
resp := defaultResponse()
|
||||
leases[tc.pos] = tc.lease
|
||||
resp.StaticLeases = leases
|
||||
|
||||
checkStatus(t, s, resp)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_HandleUpdateStaticLease_validation(t *testing.T) {
|
||||
const (
|
||||
leaseV4Name = "static-client-v4"
|
||||
leaseV4MAC = "44:44:44:44:44:44"
|
||||
|
||||
anotherV4Name = "another-client-v4"
|
||||
anotherV4MAC = "55:55:55:55:55:55"
|
||||
)
|
||||
|
||||
leaseV4IP := netip.MustParseAddr("192.168.10.10")
|
||||
anotherV4IP := netip.MustParseAddr("192.168.10.20")
|
||||
|
||||
leases := []*leaseStatic{{
|
||||
HWAddr: leaseV4MAC,
|
||||
IP: leaseV4IP,
|
||||
Hostname: leaseV4Name,
|
||||
}, {
|
||||
HWAddr: anotherV4MAC,
|
||||
IP: anotherV4IP,
|
||||
Hostname: anotherV4Name,
|
||||
}}
|
||||
|
||||
s, err := Create(&ServerConfig{
|
||||
Enabled: true,
|
||||
Conf4: *defaultV4ServerConf(),
|
||||
Conf6: V6ServerConf{},
|
||||
DataDir: t.TempDir(),
|
||||
ConfigModified: func() {},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, l := range leases {
|
||||
w := handleLease(t, l, s.handleDHCPAddStaticLease)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
lease *leaseStatic
|
||||
name string
|
||||
want string
|
||||
}{{
|
||||
name: "v4_unknown_mac",
|
||||
lease: &leaseStatic{
|
||||
HWAddr: "aa:aa:aa:aa:aa:aa",
|
||||
IP: leaseV4IP,
|
||||
Hostname: leaseV4Name,
|
||||
},
|
||||
want: "dhcpv4: updating static lease: can't find lease aa:aa:aa:aa:aa:aa\n",
|
||||
}, {
|
||||
name: "update_v4_same_ip",
|
||||
lease: &leaseStatic{
|
||||
HWAddr: leaseV4MAC,
|
||||
IP: anotherV4IP,
|
||||
Hostname: leaseV4Name,
|
||||
},
|
||||
want: "dhcpv4: updating static lease: ip address is not unique\n",
|
||||
}, {
|
||||
name: "update_v4_same_name",
|
||||
lease: &leaseStatic{
|
||||
HWAddr: leaseV4MAC,
|
||||
IP: leaseV4IP,
|
||||
Hostname: anotherV4Name,
|
||||
},
|
||||
want: "dhcpv4: updating static lease: hostname is not unique\n",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
w := handleLease(t, tc.lease, s.handleDHCPUpdateStaticLease)
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Equal(t, tc.want, w.Body.String())
|
||||
})
|
||||
}
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
//go:build darwin || freebsd || linux || openbsd
|
||||
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestServer_handleDHCPStatus(t *testing.T) {
|
||||
const (
|
||||
staticName = "static-client"
|
||||
staticMAC = "aa:aa:aa:aa:aa:aa"
|
||||
)
|
||||
|
||||
staticIP := netip.MustParseAddr("192.168.10.10")
|
||||
|
||||
staticLease := &leaseStatic{
|
||||
HWAddr: staticMAC,
|
||||
IP: staticIP,
|
||||
Hostname: staticName,
|
||||
}
|
||||
|
||||
s, err := Create(&ServerConfig{
|
||||
Enabled: true,
|
||||
Conf4: *defaultV4ServerConf(),
|
||||
DataDir: t.TempDir(),
|
||||
ConfigModified: func() {},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// checkStatus is a helper that asserts the response of
|
||||
// [*server.handleDHCPStatus].
|
||||
checkStatus := func(t *testing.T, want *dhcpStatusResponse) {
|
||||
w := httptest.NewRecorder()
|
||||
var req *http.Request
|
||||
req, err = http.NewRequest(http.MethodGet, "", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
err = json.NewEncoder(b).Encode(&want)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.handleDHCPStatus(w, req)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
assert.JSONEq(t, b.String(), w.Body.String())
|
||||
}
|
||||
|
||||
// defaultResponse is a helper that returns the response with default
|
||||
// configuration.
|
||||
defaultResponse := func() *dhcpStatusResponse {
|
||||
conf4 := defaultV4ServerConf()
|
||||
conf4.LeaseDuration = 86400
|
||||
|
||||
resp := &dhcpStatusResponse{
|
||||
V4: *conf4,
|
||||
V6: V6ServerConf{},
|
||||
Leases: []*leaseDynamic{},
|
||||
StaticLeases: []*leaseStatic{},
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
ok := t.Run("status", func(t *testing.T) {
|
||||
resp := defaultResponse()
|
||||
|
||||
checkStatus(t, resp)
|
||||
})
|
||||
require.True(t, ok)
|
||||
|
||||
ok = t.Run("add_static_lease", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
err = json.NewEncoder(b).Encode(staticLease)
|
||||
require.NoError(t, err)
|
||||
|
||||
var r *http.Request
|
||||
r, err = http.NewRequest(http.MethodPost, "", b)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.handleDHCPAddStaticLease(w, r)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
resp := defaultResponse()
|
||||
resp.StaticLeases = []*leaseStatic{staticLease}
|
||||
|
||||
checkStatus(t, resp)
|
||||
})
|
||||
require.True(t, ok)
|
||||
|
||||
ok = t.Run("add_invalid_lease", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
|
||||
err = json.NewEncoder(b).Encode(&leaseStatic{})
|
||||
require.NoError(t, err)
|
||||
|
||||
var r *http.Request
|
||||
r, err = http.NewRequest(http.MethodPost, "", b)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.handleDHCPAddStaticLease(w, r)
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
})
|
||||
require.True(t, ok)
|
||||
|
||||
ok = t.Run("remove_static_lease", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
err = json.NewEncoder(b).Encode(staticLease)
|
||||
require.NoError(t, err)
|
||||
|
||||
var r *http.Request
|
||||
r, err = http.NewRequest(http.MethodPost, "", b)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.handleDHCPRemoveStaticLease(w, r)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
resp := defaultResponse()
|
||||
|
||||
checkStatus(t, resp)
|
||||
})
|
||||
require.True(t, ok)
|
||||
|
||||
ok = t.Run("set_config", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
resp := defaultResponse()
|
||||
resp.Enabled = false
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
err = json.NewEncoder(b).Encode(&resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
var r *http.Request
|
||||
r, err = http.NewRequest(http.MethodPost, "", b)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.handleDHCPSetConfig(w, r)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
checkStatus(t, resp)
|
||||
})
|
||||
require.True(t, ok)
|
||||
}
|
@ -43,6 +43,7 @@ func (s *server) registerHandlers() {
|
||||
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/find_active_dhcp", s.notImplemented)
|
||||
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/add_static_lease", s.notImplemented)
|
||||
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/remove_static_lease", s.notImplemented)
|
||||
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/update_static_lease", s.notImplemented)
|
||||
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/reset", s.notImplemented)
|
||||
s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/reset_leases", s.notImplemented)
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ func (winServer) GetLeases(_ GetLeasesFlags) (leases []*Lease) { return nil }
|
||||
func (winServer) getLeasesRef() []*Lease { return nil }
|
||||
func (winServer) AddStaticLease(_ *Lease) (err error) { return nil }
|
||||
func (winServer) RemoveStaticLease(_ *Lease) (err error) { return nil }
|
||||
func (winServer) UpdateStaticLease(_ *Lease) (err error) { return nil }
|
||||
func (winServer) FindMACbyIP(_ netip.Addr) (mac net.HardwareAddr) { return nil }
|
||||
func (winServer) WriteDiskConfig4(_ *V4ServerConf) {}
|
||||
func (winServer) WriteDiskConfig6(_ *V6ServerConf) {}
|
||||
|
@ -309,9 +309,15 @@ func (s *v4Server) rmDynamicLease(lease *Lease) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ErrDupHostname is returned by addLease when the added lease has a not empty
|
||||
// non-unique hostname.
|
||||
const ErrDupHostname = errors.Error("hostname is not unique")
|
||||
const (
|
||||
// ErrDupHostname is returned by addLease, validateStaticLease when the
|
||||
// modified lease has a not empty non-unique hostname.
|
||||
ErrDupHostname = errors.Error("hostname is not unique")
|
||||
|
||||
// ErrDupIP is returned by addLease, validateStaticLease when the modified
|
||||
// lease has a non-unique IP address.
|
||||
ErrDupIP = errors.Error("ip address is not unique")
|
||||
)
|
||||
|
||||
// addLease adds a dynamic or static lease.
|
||||
func (s *v4Server) addLease(l *Lease) (err error) {
|
||||
@ -428,6 +434,81 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateStaticLease updates IP, hostname of the static lease.
|
||||
func (s *v4Server) UpdateStaticLease(l *Lease) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = errors.Annotate(err, "dhcpv4: updating static lease: %w")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
s.conf.notify(LeaseChangedDBStore)
|
||||
s.conf.notify(LeaseChangedRemovedStatic)
|
||||
}()
|
||||
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
found := s.findLease(l.HWAddr)
|
||||
if found == nil {
|
||||
return fmt.Errorf("can't find lease %s", l.HWAddr)
|
||||
}
|
||||
|
||||
err = s.validateStaticLease(l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.rmLease(found)
|
||||
if err != nil {
|
||||
return fmt.Errorf("removing previous lease for %s (%s): %w", l.IP, l.HWAddr, err)
|
||||
}
|
||||
|
||||
err = s.addLease(l)
|
||||
if err != nil {
|
||||
return fmt.Errorf("adding updated static lease for %s (%s): %w", l.IP, l.HWAddr, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateStaticLease returns an error if the static lease is invalid.
|
||||
func (s *v4Server) validateStaticLease(l *Lease) (err error) {
|
||||
hostname, err := normalizeHostname(l.Hostname)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
err = netutil.ValidateHostname(hostname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("validating hostname: %w", err)
|
||||
}
|
||||
|
||||
dup, ok := s.hostsIndex[hostname]
|
||||
if ok && !bytes.Equal(dup.HWAddr, l.HWAddr) {
|
||||
return ErrDupHostname
|
||||
}
|
||||
|
||||
dup, ok = s.ipIndex[l.IP]
|
||||
if ok && !bytes.Equal(dup.HWAddr, l.HWAddr) {
|
||||
return ErrDupIP
|
||||
}
|
||||
|
||||
l.Hostname = hostname
|
||||
|
||||
if gwIP := s.conf.GatewayIP; gwIP == l.IP {
|
||||
return fmt.Errorf("can't assign the gateway IP %q to the lease", gwIP)
|
||||
}
|
||||
|
||||
if sn := s.conf.subnet; !sn.Contains(l.IP) {
|
||||
return fmt.Errorf("subnet %s does not contain the ip %q", sn, l.IP)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateStaticLease safe removes dynamic lease with the same properties and
|
||||
// then adds a static lease l.
|
||||
func (s *v4Server) updateStaticLease(l *Lease) (err error) {
|
||||
|
@ -235,6 +235,37 @@ func (s *v6Server) AddStaticLease(l *Lease) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateStaticLease updates IP, hostname of the static lease.
|
||||
func (s *v6Server) UpdateStaticLease(l *Lease) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = errors.Annotate(err, "dhcpv6: updating static lease: %w")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
s.conf.notify(LeaseChangedDBStore)
|
||||
s.conf.notify(LeaseChangedRemovedStatic)
|
||||
}()
|
||||
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
found := s.findLease(l.HWAddr)
|
||||
if found == nil {
|
||||
return fmt.Errorf("can't find lease %s", l.HWAddr)
|
||||
}
|
||||
|
||||
err = s.rmLease(found)
|
||||
if err != nil {
|
||||
return fmt.Errorf("removing previous lease for %s (%s): %w", l.IP, l.HWAddr, err)
|
||||
}
|
||||
|
||||
s.addLease(l)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveStaticLease removes a static lease. It is safe for concurrent use.
|
||||
func (s *v6Server) RemoveStaticLease(l *Lease) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "dhcpv6: %w") }()
|
||||
@ -286,16 +317,14 @@ func (s *v6Server) rmLease(lease *Lease) (err error) {
|
||||
return fmt.Errorf("lease not found")
|
||||
}
|
||||
|
||||
// Find lease by MAC
|
||||
func (s *v6Server) findLease(mac net.HardwareAddr) *Lease {
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
// Find lease by MAC.
|
||||
func (s *v6Server) findLease(mac net.HardwareAddr) (lease *Lease) {
|
||||
for i := range s.leases {
|
||||
if bytes.Equal(mac, s.leases[i].HWAddr) {
|
||||
return s.leases[i]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -477,7 +506,14 @@ func (s *v6Server) process(msg *dhcpv6.Message, req, resp dhcpv6.DHCPv6) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
lease := s.findLease(mac)
|
||||
var lease *Lease
|
||||
func() {
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
lease = s.findLease(mac)
|
||||
}()
|
||||
|
||||
if lease == nil {
|
||||
log.Debug("dhcpv6: no lease for: %s", mac)
|
||||
|
||||
|
@ -6,6 +6,12 @@
|
||||
|
||||
## v0.107.39: API changes
|
||||
|
||||
### New HTTP API 'POST /control/dhcp/update_static_lease'
|
||||
|
||||
* The new `POST /control/dhcp/update_static_lease` HTTP API allows modifying IP
|
||||
address, hostname of the static DHCP lease. IP version must be the same as
|
||||
previous.
|
||||
|
||||
### The new field `"blocked_response_ttl"` in `DNSConfig` object
|
||||
|
||||
* The new field `"blocked_response_ttl"` in `GET /control/dns_info` and `POST
|
||||
|
@ -566,6 +566,26 @@
|
||||
'schema':
|
||||
'$ref': '#/components/schemas/Error'
|
||||
'description': 'Not implemented (for example, on Windows).'
|
||||
'/dhcp/update_static_lease':
|
||||
'post':
|
||||
'tags':
|
||||
- 'dhcp'
|
||||
'operationId': 'dhcpUpdateStaticLease'
|
||||
'description': >
|
||||
Updates IP address, hostname of the static lease. IP version must be
|
||||
the same as previous.
|
||||
'summary': 'Updates a static lease'
|
||||
'requestBody':
|
||||
'$ref': '#/components/requestBodies/DhcpStaticLease'
|
||||
'responses':
|
||||
'200':
|
||||
'description': 'OK.'
|
||||
'501':
|
||||
'content':
|
||||
'application/json':
|
||||
'schema':
|
||||
'$ref': '#/components/schemas/Error'
|
||||
'description': 'Not implemented (for example, on Windows).'
|
||||
'/dhcp/reset':
|
||||
'post':
|
||||
'tags':
|
||||
|
Loading…
Reference in New Issue
Block a user